mirror of
https://github.com/torvalds/linux
synced 2026-07-22 10:10:47 +09:00
And introduce the symlink=<value> mount option to configure how symbolic links are created. The option accepts "wsl" or "native", with "wsl" being the default. Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
965 lines
23 KiB
C
965 lines
23 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Processing of reparse points
|
|
*
|
|
* Part of this file is based on code from the NTFS-3G.
|
|
*
|
|
* Copyright (c) 2008-2021 Jean-Pierre Andre
|
|
* Copyright (c) 2025 LG Electronics Co., Ltd.
|
|
*/
|
|
|
|
#include "ntfs.h"
|
|
#include "layout.h"
|
|
#include "attrib.h"
|
|
#include "inode.h"
|
|
#include "dir.h"
|
|
#include "volume.h"
|
|
#include "mft.h"
|
|
#include "index.h"
|
|
#include "lcnalloc.h"
|
|
#include "reparse.h"
|
|
|
|
struct wsl_link_reparse_data {
|
|
__le32 type;
|
|
char link[];
|
|
};
|
|
|
|
static bool reparse_name_is_valid(size_t size, size_t name_off, u16 len)
|
|
{
|
|
if ((name_off | len) & 1)
|
|
return false;
|
|
|
|
return name_off + len <= size;
|
|
}
|
|
|
|
/*
|
|
* Windows-native reparse payloads store pathnames as UTF-16 strings with '\\'
|
|
* separators. Convert the on-disk UTF-16 target into the mount's NLS and
|
|
* normalize path separators.
|
|
*/
|
|
static int ntfs_reparse_target_to_nls(struct ntfs_volume *vol,
|
|
const __le16 *uname, u16 ulen,
|
|
char **target)
|
|
{
|
|
int err, i;
|
|
|
|
*target = NULL;
|
|
ulen >>= 1;
|
|
if (!ulen)
|
|
return -EINVAL;
|
|
|
|
if (!uname[ulen - 1])
|
|
ulen--;
|
|
|
|
err = ntfs_ucstonls(vol, uname, ulen, (unsigned char **)target, 0);
|
|
if (err < 0) {
|
|
ntfs_attr_name_free((unsigned char **)target);
|
|
return err;
|
|
}
|
|
|
|
for (i = 0; i < err; i++) {
|
|
if ((*target)[i] == '\\')
|
|
(*target)[i] = '/';
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Index entry in $Extend/$Reparse */
|
|
struct reparse_index {
|
|
struct index_entry_header header;
|
|
struct reparse_index_key key;
|
|
__le32 filling;
|
|
};
|
|
|
|
__le16 reparse_index_name[] = {cpu_to_le16('$'), cpu_to_le16('R'), 0};
|
|
|
|
|
|
/*
|
|
* Check if the reparse point attribute buffer is valid.
|
|
* Returns true if valid, false otherwise.
|
|
*/
|
|
static bool valid_reparse_buffer(struct ntfs_inode *ni,
|
|
const struct reparse_point *reparse_attr,
|
|
size_t size,
|
|
size_t payload_min_len)
|
|
{
|
|
size_t expected;
|
|
|
|
if (!ni || !reparse_attr)
|
|
return false;
|
|
|
|
/* Minimum size must cover reparse_point header */
|
|
if (size < sizeof(struct reparse_point))
|
|
return false;
|
|
|
|
/* The payload must contain the fixed fields for the current tag. */
|
|
if (payload_min_len &&
|
|
le16_to_cpu(reparse_attr->reparse_data_length) < payload_min_len)
|
|
return false;
|
|
|
|
/* Reserved zero tag is invalid */
|
|
if (reparse_attr->reparse_tag == IO_REPARSE_TAG_RESERVED_ZERO)
|
|
return false;
|
|
|
|
/* Calculate expected total size */
|
|
expected = sizeof(struct reparse_point) +
|
|
le16_to_cpu(reparse_attr->reparse_data_length);
|
|
|
|
/* Add GUID size for non-Microsoft tags */
|
|
if (!(reparse_attr->reparse_tag & IO_REPARSE_TAG_IS_MICROSOFT))
|
|
expected += sizeof(struct guid);
|
|
|
|
/* Buffer must exactly match the expected size */
|
|
return expected == size;
|
|
}
|
|
|
|
/*
|
|
* Do some sanity checks on reparse data
|
|
*
|
|
* Microsoft reparse points have an 8-byte header whereas
|
|
* non-Microsoft reparse points have a 24-byte header. In each case,
|
|
* 'reparse_data_length' must equal the number of non-header bytes.
|
|
*
|
|
* If the reparse data looks like a junction point or symbolic
|
|
* link, more checks can be done.
|
|
*/
|
|
static bool valid_reparse_data(struct ntfs_inode *ni,
|
|
const struct reparse_point *reparse_attr, size_t size)
|
|
{
|
|
if (size < sizeof(*reparse_attr))
|
|
return false;
|
|
|
|
switch (reparse_attr->reparse_tag) {
|
|
case IO_REPARSE_TAG_MOUNT_POINT:
|
|
{
|
|
struct mount_point_reparse_data *data;
|
|
size_t data_offs;
|
|
|
|
if (!valid_reparse_buffer(ni, reparse_attr, size, sizeof(*data)))
|
|
return false;
|
|
|
|
data = (struct mount_point_reparse_data *)reparse_attr->reparse_data;
|
|
data_offs = offsetof(struct reparse_point, reparse_data) +
|
|
offsetof(struct mount_point_reparse_data, path_buffer);
|
|
|
|
if (!reparse_name_is_valid(size,
|
|
data_offs +
|
|
le16_to_cpu(data->substitute_name_offset),
|
|
le16_to_cpu(data->substitute_name_length)) ||
|
|
!reparse_name_is_valid(size,
|
|
data_offs +
|
|
le16_to_cpu(data->print_name_offset),
|
|
le16_to_cpu(data->print_name_length)))
|
|
return false;
|
|
break;
|
|
}
|
|
case IO_REPARSE_TAG_SYMLINK:
|
|
{
|
|
struct symlink_reparse_data *data;
|
|
size_t data_offs;
|
|
|
|
if (!valid_reparse_buffer(ni, reparse_attr, size,
|
|
sizeof(*data)))
|
|
return false;
|
|
|
|
data = (struct symlink_reparse_data *)reparse_attr->reparse_data;
|
|
data_offs = offsetof(struct reparse_point, reparse_data) +
|
|
offsetof(struct symlink_reparse_data, path_buffer);
|
|
|
|
if (!reparse_name_is_valid(size,
|
|
data_offs +
|
|
le16_to_cpu(data->substitute_name_offset),
|
|
le16_to_cpu(data->substitute_name_length)) ||
|
|
!reparse_name_is_valid(size,
|
|
data_offs +
|
|
le16_to_cpu(data->print_name_offset),
|
|
le16_to_cpu(data->print_name_length)))
|
|
return false;
|
|
break;
|
|
}
|
|
case IO_REPARSE_TAG_LX_SYMLINK:
|
|
{
|
|
struct wsl_link_reparse_data *data;
|
|
|
|
if (!valid_reparse_buffer(ni, reparse_attr, size,
|
|
sizeof(*data)))
|
|
return false;
|
|
|
|
data = (struct wsl_link_reparse_data *)reparse_attr->reparse_data;
|
|
|
|
if (le16_to_cpu(reparse_attr->reparse_data_length) <= sizeof(data->type) ||
|
|
data->type != cpu_to_le32(2))
|
|
return false;
|
|
break;
|
|
}
|
|
case IO_REPARSE_TAG_AF_UNIX:
|
|
case IO_REPARSE_TAG_LX_FIFO:
|
|
case IO_REPARSE_TAG_LX_CHR:
|
|
case IO_REPARSE_TAG_LX_BLK:
|
|
if (!valid_reparse_buffer(ni, reparse_attr, size, 0))
|
|
return false;
|
|
if (le16_to_cpu(reparse_attr->reparse_data_length) ||
|
|
!(ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN))
|
|
return false;
|
|
break;
|
|
default:
|
|
if (!valid_reparse_buffer(ni, reparse_attr, size, 0))
|
|
return false;
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static unsigned int ntfs_reparse_tag_mode(__le32 reparse_tag)
|
|
{
|
|
unsigned int mode = 0;
|
|
|
|
switch (reparse_tag) {
|
|
case IO_REPARSE_TAG_MOUNT_POINT:
|
|
case IO_REPARSE_TAG_SYMLINK:
|
|
case IO_REPARSE_TAG_LX_SYMLINK:
|
|
mode = S_IFLNK;
|
|
break;
|
|
case IO_REPARSE_TAG_AF_UNIX:
|
|
mode = S_IFSOCK;
|
|
break;
|
|
case IO_REPARSE_TAG_LX_FIFO:
|
|
mode = S_IFIFO;
|
|
break;
|
|
case IO_REPARSE_TAG_LX_CHR:
|
|
mode = S_IFCHR;
|
|
break;
|
|
case IO_REPARSE_TAG_LX_BLK:
|
|
mode = S_IFBLK;
|
|
}
|
|
|
|
return mode;
|
|
}
|
|
|
|
/*
|
|
* Get the target for symbolic link
|
|
*/
|
|
unsigned int ntfs_make_symlink(struct ntfs_inode *ni)
|
|
{
|
|
s64 attr_size = 0;
|
|
int err;
|
|
unsigned int lth;
|
|
struct reparse_point *reparse_attr;
|
|
unsigned int mode = 0;
|
|
|
|
kvfree(ni->target);
|
|
ni->target = NULL;
|
|
ni->reparse_tag = 0;
|
|
ni->reparse_flags = 0;
|
|
|
|
reparse_attr = ntfs_attr_readall(ni, AT_REPARSE_POINT, NULL, 0,
|
|
&attr_size);
|
|
if (reparse_attr &&
|
|
valid_reparse_data(ni, reparse_attr, attr_size)) {
|
|
err = -EINVAL;
|
|
|
|
switch (reparse_attr->reparse_tag) {
|
|
case IO_REPARSE_TAG_MOUNT_POINT:
|
|
{
|
|
struct mount_point_reparse_data *data =
|
|
(struct mount_point_reparse_data *)reparse_attr->reparse_data;
|
|
const __le16 *name = (const __le16 *)((u8 *)data->path_buffer +
|
|
le16_to_cpu(data->substitute_name_offset));
|
|
|
|
err = ntfs_reparse_target_to_nls(ni->vol,
|
|
name,
|
|
le16_to_cpu(data->substitute_name_length),
|
|
&ni->target);
|
|
break;
|
|
}
|
|
case IO_REPARSE_TAG_SYMLINK:
|
|
{
|
|
struct symlink_reparse_data *data =
|
|
(struct symlink_reparse_data *)reparse_attr->reparse_data;
|
|
const __le16 *name = (const __le16 *)((u8 *)data->path_buffer +
|
|
le16_to_cpu(data->substitute_name_offset));
|
|
|
|
err = ntfs_reparse_target_to_nls(ni->vol,
|
|
name,
|
|
le16_to_cpu(data->substitute_name_length),
|
|
&ni->target);
|
|
if (!err)
|
|
ni->reparse_flags = data->flags;
|
|
break;
|
|
}
|
|
case IO_REPARSE_TAG_LX_SYMLINK:
|
|
{
|
|
struct wsl_link_reparse_data *wsl_link_data =
|
|
(struct wsl_link_reparse_data *)reparse_attr->reparse_data;
|
|
|
|
if (wsl_link_data->type == cpu_to_le32(2)) {
|
|
lth = le16_to_cpu(reparse_attr->reparse_data_length) -
|
|
sizeof(wsl_link_data->type);
|
|
ni->target = kvzalloc(lth + 1, GFP_NOFS);
|
|
if (ni->target) {
|
|
memcpy(ni->target, wsl_link_data->link, lth);
|
|
ni->target[lth] = 0;
|
|
err = 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
err = 0;
|
|
}
|
|
|
|
if (!err) {
|
|
mode = ntfs_reparse_tag_mode(reparse_attr->reparse_tag);
|
|
ni->reparse_tag = reparse_attr->reparse_tag;
|
|
}
|
|
} else
|
|
ni->flags &= ~FILE_ATTR_REPARSE_POINT;
|
|
|
|
if (reparse_attr)
|
|
kvfree(reparse_attr);
|
|
|
|
return mode;
|
|
}
|
|
|
|
unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mref)
|
|
{
|
|
s64 attr_size = 0;
|
|
struct reparse_point *reparse_attr;
|
|
unsigned int dt_type = DT_UNKNOWN;
|
|
struct inode *vi;
|
|
|
|
vi = ntfs_iget(vol->sb, mref);
|
|
if (IS_ERR(vi))
|
|
return PTR_ERR(vi);
|
|
|
|
reparse_attr = (struct reparse_point *)ntfs_attr_readall(NTFS_I(vi),
|
|
AT_REPARSE_POINT, NULL, 0, &attr_size);
|
|
|
|
if (reparse_attr && attr_size >= sizeof(*reparse_attr)) {
|
|
switch (reparse_attr->reparse_tag) {
|
|
case IO_REPARSE_TAG_MOUNT_POINT:
|
|
case IO_REPARSE_TAG_SYMLINK:
|
|
case IO_REPARSE_TAG_LX_SYMLINK:
|
|
dt_type = DT_LNK;
|
|
break;
|
|
case IO_REPARSE_TAG_AF_UNIX:
|
|
dt_type = DT_SOCK;
|
|
break;
|
|
case IO_REPARSE_TAG_LX_FIFO:
|
|
dt_type = DT_FIFO;
|
|
break;
|
|
case IO_REPARSE_TAG_LX_CHR:
|
|
dt_type = DT_CHR;
|
|
break;
|
|
case IO_REPARSE_TAG_LX_BLK:
|
|
dt_type = DT_BLK;
|
|
}
|
|
}
|
|
|
|
if (reparse_attr)
|
|
kvfree(reparse_attr);
|
|
|
|
iput(vi);
|
|
return dt_type;
|
|
}
|
|
|
|
static bool ntfs_is_drive_letter(const char *target)
|
|
{
|
|
return ((target[0] >= 'A' && target[0] <= 'Z') ||
|
|
(target[0] >= 'a' && target[0] <= 'z')) &&
|
|
target[1] == ':';
|
|
}
|
|
|
|
/*
|
|
* ntfs_translate_symlink_path
|
|
*
|
|
* @dentry: dentry of the symlink/junction being resolved
|
|
* @target: NUL-terminated NLS target string with '\\' already normalized to '/'
|
|
* @translated: out parameter, set to a newly kmalloc'd relative path on success
|
|
*
|
|
* Windows junctions (IO_REPARSE_TAG_MOUNT_POINT) and non-relative symlinks
|
|
* (IO_REPARSE_TAG_SYMLINK without SYMLINK_FLAG_RELATIVE) store substitute
|
|
* names such as "/??/C:/foo", "//?/C:/foo", "/foo", or "C:/foo". Linux
|
|
* cannot continue pathname lookup from those syntaxes, so rewrite them as a
|
|
* path relative to the symlink's containing directory on this NTFS volume,
|
|
* anchored at the volume root via "../".
|
|
*
|
|
* Note: bind-mounted subtrees of the volume may resolve to unexpected
|
|
* locations because the computed "../" depth is relative to the NTFS volume
|
|
* root, not the bind-mounted subtree root.
|
|
*
|
|
* Return: 0 on success with *translated set to a newly allocated string the
|
|
* caller must kfree(); negative errno on failure.
|
|
*/
|
|
int ntfs_translate_symlink_path(struct dentry *dentry, const char *target,
|
|
char **translated)
|
|
{
|
|
char *buf, *link_path, *out, *p;
|
|
const char *path, *tail;
|
|
unsigned int up_levels = 0;
|
|
size_t tail_len, out_len;
|
|
int err;
|
|
|
|
if (!dentry || !target || !translated)
|
|
return -EINVAL;
|
|
|
|
path = target;
|
|
/* reject UNC path. */
|
|
if (path[0] == '/' && path[1] == '/' &&
|
|
!(path[2] == '?' && path[3] == '/'))
|
|
return -EOPNOTSUPP;
|
|
|
|
/* target starts with "/??/" or "//?/"? */
|
|
if ((path[0] == '/' && path[1] == '?' && path[2] == '?' && path[3] == '/') ||
|
|
(path[0] == '/' && path[1] == '/' && path[2] == '?' && path[3] == '/'))
|
|
path += 4;
|
|
|
|
/* target must start with a drive character or '/'. */
|
|
if (ntfs_is_drive_letter(path)) {
|
|
if (path[2] && path[2] != '/')
|
|
return -EOPNOTSUPP;
|
|
tail = path + 2;
|
|
if (*tail == '/')
|
|
tail++;
|
|
} else if (*path == '/') {
|
|
tail = path + 1;
|
|
} else {
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
tail_len = strlen(tail);
|
|
|
|
buf = kmalloc(PATH_MAX, GFP_NOFS);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
link_path = dentry_path_raw(dentry, buf, PATH_MAX);
|
|
if (IS_ERR(link_path)) {
|
|
err = PTR_ERR(link_path);
|
|
goto out;
|
|
}
|
|
|
|
/* count '/' after the leading slash. */
|
|
for (p = link_path + 1; *p; p++)
|
|
if (*p == '/')
|
|
up_levels++;
|
|
|
|
/* build "./" + ("../" * up_levels) + tail. */
|
|
out_len = 2 + up_levels * 3 + tail_len;
|
|
if (out_len >= PATH_MAX) {
|
|
err = -ENAMETOOLONG;
|
|
goto out;
|
|
}
|
|
|
|
out = kmalloc(out_len + 1, GFP_NOFS);
|
|
if (!out) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
memcpy(out, "./", 2);
|
|
p = out + 2;
|
|
while (up_levels--) {
|
|
memcpy(p, "../", 3);
|
|
p += 3;
|
|
}
|
|
memcpy(p, tail, tail_len + 1);
|
|
|
|
*translated = out;
|
|
err = 0;
|
|
out:
|
|
kfree(buf);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Set the index for new reparse data
|
|
*/
|
|
static int set_reparse_index(struct ntfs_inode *ni, struct ntfs_index_context *xr,
|
|
__le32 reparse_tag)
|
|
{
|
|
struct reparse_index indx;
|
|
u64 file_id_cpu;
|
|
__le64 file_id;
|
|
|
|
file_id_cpu = MK_MREF(ni->mft_no, ni->seq_no);
|
|
file_id = cpu_to_le64(file_id_cpu);
|
|
indx.header.data.vi.data_offset =
|
|
cpu_to_le16(sizeof(struct index_entry_header) + sizeof(struct reparse_index_key));
|
|
indx.header.data.vi.data_length = 0;
|
|
indx.header.data.vi.reservedV = 0;
|
|
indx.header.length = cpu_to_le16(sizeof(struct reparse_index));
|
|
indx.header.key_length = cpu_to_le16(sizeof(struct reparse_index_key));
|
|
indx.header.flags = 0;
|
|
indx.header.reserved = 0;
|
|
indx.key.reparse_tag = reparse_tag;
|
|
/* danger on processors which require proper alignment! */
|
|
memcpy(&indx.key.file_id, &file_id, 8);
|
|
indx.filling = 0;
|
|
ntfs_index_ctx_reinit(xr);
|
|
|
|
return ntfs_ie_add(xr, (struct index_entry *)&indx);
|
|
}
|
|
|
|
/*
|
|
* Remove a reparse data index entry if attribute present
|
|
*/
|
|
static int remove_reparse_index(struct inode *rp, struct ntfs_index_context *xr,
|
|
__le32 *preparse_tag)
|
|
{
|
|
struct reparse_index_key key;
|
|
u64 file_id_cpu;
|
|
__le64 file_id;
|
|
s64 size;
|
|
struct ntfs_inode *ni = NTFS_I(rp);
|
|
int err = 0, ret = ni->data_size;
|
|
|
|
if (ni->data_size == 0)
|
|
return 0;
|
|
|
|
/* read the existing reparse_tag */
|
|
size = ntfs_inode_attr_pread(rp, 0, 4, (char *)preparse_tag);
|
|
if (size != 4)
|
|
return -ENODATA;
|
|
|
|
file_id_cpu = MK_MREF(ni->mft_no, ni->seq_no);
|
|
file_id = cpu_to_le64(file_id_cpu);
|
|
key.reparse_tag = *preparse_tag;
|
|
/* danger on processors which require proper alignment! */
|
|
memcpy(&key.file_id, &file_id, 8);
|
|
if (!ntfs_index_lookup(&key, sizeof(struct reparse_index_key), xr)) {
|
|
err = ntfs_index_rm(xr);
|
|
if (err)
|
|
ret = err;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Open the $Extend/$Reparse file and its index
|
|
*/
|
|
static struct ntfs_index_context *open_reparse_index(struct ntfs_volume *vol)
|
|
{
|
|
struct ntfs_index_context *xr = NULL;
|
|
u64 mref;
|
|
__le16 *uname;
|
|
struct ntfs_name *name = NULL;
|
|
int uname_len;
|
|
struct inode *vi, *dir_vi;
|
|
|
|
/* do not use path_name_to inode - could reopen root */
|
|
dir_vi = ntfs_iget(vol->sb, FILE_Extend);
|
|
if (IS_ERR(dir_vi))
|
|
return NULL;
|
|
|
|
uname_len = ntfs_nlstoucs(vol, "$Reparse", 8, &uname,
|
|
NTFS_MAX_NAME_LEN);
|
|
if (uname_len < 0) {
|
|
iput(dir_vi);
|
|
return NULL;
|
|
}
|
|
|
|
mutex_lock_nested(&NTFS_I(dir_vi)->mrec_lock, NTFS_EXTEND_MUTEX_PARENT);
|
|
mref = ntfs_lookup_inode_by_name(NTFS_I(dir_vi), uname, uname_len,
|
|
&name);
|
|
mutex_unlock(&NTFS_I(dir_vi)->mrec_lock);
|
|
kfree(name);
|
|
kmem_cache_free(ntfs_name_cache, uname);
|
|
if (IS_ERR_MREF(mref))
|
|
goto put_dir_vi;
|
|
|
|
vi = ntfs_iget(vol->sb, MREF(mref));
|
|
if (IS_ERR(vi))
|
|
goto put_dir_vi;
|
|
|
|
xr = ntfs_index_ctx_get(NTFS_I(vi), reparse_index_name, 2);
|
|
if (!xr)
|
|
iput(vi);
|
|
put_dir_vi:
|
|
iput(dir_vi);
|
|
return xr;
|
|
}
|
|
|
|
|
|
/*
|
|
* Update the reparse data and index
|
|
*
|
|
* The reparse data attribute should have been created, and
|
|
* an existing index is expected if there is an existing value.
|
|
*
|
|
*/
|
|
static int update_reparse_data(struct ntfs_inode *ni, struct ntfs_index_context *xr,
|
|
char *value, size_t size)
|
|
{
|
|
struct inode *rp_inode;
|
|
int err = 0;
|
|
s64 written;
|
|
int oldsize;
|
|
__le32 reparse_tag;
|
|
struct ntfs_inode *rp_ni;
|
|
|
|
rp_inode = ntfs_attr_iget(VFS_I(ni), AT_REPARSE_POINT, AT_UNNAMED, 0);
|
|
if (IS_ERR(rp_inode))
|
|
return -EINVAL;
|
|
rp_ni = NTFS_I(rp_inode);
|
|
|
|
/* remove the existing reparse data */
|
|
oldsize = remove_reparse_index(rp_inode, xr, &reparse_tag);
|
|
if (oldsize < 0) {
|
|
err = oldsize;
|
|
goto put_rp_inode;
|
|
}
|
|
|
|
/* overwrite value if any */
|
|
written = ntfs_inode_attr_pwrite(rp_inode, 0, size, value, false);
|
|
if (written != size) {
|
|
ntfs_error(ni->vol->sb, "Failed to update reparse data\n");
|
|
err = -EIO;
|
|
goto put_rp_inode;
|
|
}
|
|
|
|
if (set_reparse_index(ni, xr, ((const struct reparse_point *)value)->reparse_tag) &&
|
|
oldsize > 0) {
|
|
/*
|
|
* If cannot index, try to remove the reparse
|
|
* data and log the error. There will be an
|
|
* inconsistency if removal fails.
|
|
*/
|
|
ntfs_attr_rm(rp_ni);
|
|
ntfs_error(ni->vol->sb,
|
|
"Failed to index reparse data. Possible corruption.\n");
|
|
}
|
|
|
|
mark_mft_record_dirty(ni);
|
|
put_rp_inode:
|
|
iput(rp_inode);
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Delete a reparse index entry
|
|
*/
|
|
int ntfs_delete_reparse_index(struct ntfs_inode *ni)
|
|
{
|
|
struct inode *vi;
|
|
struct ntfs_index_context *xr;
|
|
struct ntfs_inode *xrni;
|
|
__le32 reparse_tag;
|
|
int err = 0;
|
|
|
|
if (!(ni->flags & FILE_ATTR_REPARSE_POINT))
|
|
return 0;
|
|
|
|
vi = ntfs_attr_iget(VFS_I(ni), AT_REPARSE_POINT, AT_UNNAMED, 0);
|
|
if (IS_ERR(vi))
|
|
return PTR_ERR(vi);
|
|
|
|
/*
|
|
* read the existing reparse data (the tag is enough)
|
|
* and un-index it
|
|
*/
|
|
xr = open_reparse_index(ni->vol);
|
|
if (xr) {
|
|
xrni = xr->idx_ni;
|
|
mutex_lock_nested(&xrni->mrec_lock, NTFS_EXTEND_MUTEX_PARENT);
|
|
err = remove_reparse_index(vi, xr, &reparse_tag);
|
|
if (err < 0) {
|
|
ntfs_index_ctx_put(xr);
|
|
mutex_unlock(&xrni->mrec_lock);
|
|
iput(VFS_I(xrni));
|
|
goto out;
|
|
}
|
|
mark_mft_record_dirty(xrni);
|
|
ntfs_index_ctx_put(xr);
|
|
mutex_unlock(&xrni->mrec_lock);
|
|
iput(VFS_I(xrni));
|
|
}
|
|
|
|
ni->flags &= ~FILE_ATTR_REPARSE_POINT;
|
|
NInoSetFileNameDirty(ni);
|
|
mark_mft_record_dirty(ni);
|
|
|
|
out:
|
|
iput(vi);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Set the reparse data from an extended attribute
|
|
*/
|
|
static int ntfs_set_ntfs_reparse_data(struct ntfs_inode *ni, char *value, size_t size)
|
|
{
|
|
int err = 0;
|
|
struct ntfs_inode *xrni;
|
|
struct ntfs_index_context *xr;
|
|
|
|
if (!ni)
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* reparse data compatibily with EA is not checked
|
|
* any more, it is required by Windows 10, but may
|
|
* lead to problems with earlier versions.
|
|
*/
|
|
if (valid_reparse_data(ni, (const struct reparse_point *)value, size) == false)
|
|
return -EINVAL;
|
|
|
|
xr = open_reparse_index(ni->vol);
|
|
if (!xr)
|
|
return -EINVAL;
|
|
xrni = xr->idx_ni;
|
|
|
|
if (!ntfs_attr_exist(ni, AT_REPARSE_POINT, AT_UNNAMED, 0)) {
|
|
struct reparse_point rp = {0, };
|
|
|
|
/*
|
|
* no reparse data attribute : add one,
|
|
* apparently, this does not feed the new value in
|
|
* Note : NTFS version must be >= 3
|
|
*/
|
|
if (ni->vol->major_ver < 3) {
|
|
err = -EOPNOTSUPP;
|
|
ntfs_index_ctx_put(xr);
|
|
goto out;
|
|
}
|
|
|
|
err = ntfs_attr_add(ni, AT_REPARSE_POINT, AT_UNNAMED, 0, (u8 *)&rp, sizeof(rp));
|
|
if (err) {
|
|
ntfs_index_ctx_put(xr);
|
|
goto out;
|
|
}
|
|
ni->flags |= FILE_ATTR_REPARSE_POINT;
|
|
NInoSetFileNameDirty(ni);
|
|
mark_mft_record_dirty(ni);
|
|
}
|
|
|
|
/* update value and index */
|
|
mutex_lock_nested(&xrni->mrec_lock, NTFS_EXTEND_MUTEX_PARENT);
|
|
err = update_reparse_data(ni, xr, value, size);
|
|
if (err) {
|
|
ni->flags &= ~FILE_ATTR_REPARSE_POINT;
|
|
NInoSetFileNameDirty(ni);
|
|
mark_mft_record_dirty(ni);
|
|
}
|
|
ntfs_index_ctx_put(xr);
|
|
mutex_unlock(&xrni->mrec_lock);
|
|
|
|
out:
|
|
if (!err)
|
|
mark_mft_record_dirty(xrni);
|
|
iput(VFS_I(xrni));
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Set reparse data for a WSL type symlink
|
|
*/
|
|
int ntfs_reparse_set_wsl_symlink(struct ntfs_inode *ni,
|
|
const char *target, int target_len)
|
|
{
|
|
int err = 0;
|
|
int reparse_len;
|
|
struct reparse_point *reparse;
|
|
struct wsl_link_reparse_data *data;
|
|
|
|
reparse_len = sizeof(struct reparse_point) + sizeof(data->type) +
|
|
target_len;
|
|
reparse = kvzalloc(reparse_len, GFP_NOFS);
|
|
if (!reparse)
|
|
return -ENOMEM;
|
|
|
|
ni->target = kstrdup(target, GFP_NOFS);
|
|
if (!ni->target) {
|
|
kvfree(reparse);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
data = (struct wsl_link_reparse_data *)reparse->reparse_data;
|
|
reparse->reparse_tag = IO_REPARSE_TAG_LX_SYMLINK;
|
|
reparse->reparse_data_length =
|
|
cpu_to_le16(sizeof(data->type) + target_len);
|
|
reparse->reserved = 0;
|
|
data->type = cpu_to_le32(2);
|
|
memcpy(data->link, target, target_len);
|
|
err = ntfs_set_ntfs_reparse_data(ni,
|
|
(char *)reparse, reparse_len);
|
|
kvfree(reparse);
|
|
if (err) {
|
|
kfree(ni->target);
|
|
ni->target = NULL;
|
|
} else {
|
|
ni->reparse_tag = IO_REPARSE_TAG_LX_SYMLINK;
|
|
ni->reparse_flags = 0;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int ntfs_reparse_set_native_symlink(struct ntfs_inode *ni,
|
|
const char *target, int target_len)
|
|
{
|
|
int err = 0;
|
|
bool is_absolute, prt_sub_shared = true;
|
|
char *sub_name = NULL;
|
|
char *prt_name = NULL;
|
|
__le16 *sub_name_utf16 = NULL;
|
|
__le16 *prt_name_utf16 = NULL;
|
|
int sub_len, prt_len;
|
|
int total_data_len, total_reparse_len;
|
|
struct reparse_point *reparse = NULL;
|
|
struct symlink_reparse_data *data;
|
|
int i;
|
|
|
|
/* Determine if target is absolute (starts with drive letter like C:/ or C:\) */
|
|
is_absolute = target_len > 2 &&
|
|
ntfs_is_drive_letter(target) &&
|
|
(target[2] == '/' || target[2] == '\\');
|
|
|
|
|
|
/* Normalize and prepare NLS paths */
|
|
prt_name = kstrdup(target, GFP_NOFS);
|
|
if (!prt_name)
|
|
return -ENOMEM;
|
|
|
|
/* Replace '/' with '\' */
|
|
for (i = 0; i < target_len; i++) {
|
|
if (prt_name[i] == '/')
|
|
prt_name[i] = '\\';
|
|
}
|
|
|
|
if (is_absolute) {
|
|
/* Prepend '\??\' to Substitutename */
|
|
sub_name = kmalloc(target_len + 5, GFP_NOFS);
|
|
if (!sub_name) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
snprintf(sub_name, target_len + 5, "\\??\\%s", prt_name);
|
|
prt_sub_shared = false;
|
|
} else {
|
|
/* For relative symlinks (including absolute paths without drive letters),
|
|
* SubstituteName and PrintName are identical.
|
|
*/
|
|
sub_name = prt_name;
|
|
}
|
|
|
|
/* Convert NLS paths to UTF-16 */
|
|
sub_len = ntfs_nlstoucs(ni->vol, sub_name, strlen(sub_name),
|
|
&sub_name_utf16, PATH_MAX);
|
|
if (sub_len < 0) {
|
|
err = sub_len;
|
|
goto out;
|
|
}
|
|
|
|
prt_len = ntfs_nlstoucs(ni->vol, prt_name, strlen(prt_name),
|
|
&prt_name_utf16, PATH_MAX);
|
|
if (prt_len < 0) {
|
|
err = prt_len;
|
|
goto out;
|
|
}
|
|
|
|
/* Check for buffer size limits */
|
|
total_data_len = sizeof(struct symlink_reparse_data) +
|
|
(sub_len + prt_len) * sizeof(__le16);
|
|
if (total_data_len > 16384) { /* 16KB max reparse tag size */
|
|
err = -EFBIG;
|
|
goto out;
|
|
}
|
|
|
|
total_reparse_len = sizeof(struct reparse_point) + total_data_len;
|
|
reparse = kvzalloc(total_reparse_len, GFP_NOFS);
|
|
if (!reparse) {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
/* Pack fields in reparse buffer */
|
|
reparse->reparse_tag = IO_REPARSE_TAG_SYMLINK;
|
|
reparse->reparse_data_length = cpu_to_le16(total_data_len);
|
|
reparse->reserved = 0;
|
|
|
|
data = (struct symlink_reparse_data *)reparse->reparse_data;
|
|
data->substitute_name_offset = 0;
|
|
data->substitute_name_length = cpu_to_le16(sub_len * sizeof(__le16));
|
|
data->print_name_offset = data->substitute_name_length;
|
|
data->print_name_length = cpu_to_le16(prt_len * sizeof(__le16));
|
|
data->flags = is_absolute ? 0 : cpu_to_le32(SYMLINK_FLAG_RELATIVE);
|
|
|
|
/* Copy names to path_buffer */
|
|
memcpy(data->path_buffer, sub_name_utf16, sub_len * sizeof(__le16));
|
|
memcpy(data->path_buffer + sub_len, prt_name_utf16, prt_len * sizeof(__le16));
|
|
|
|
err = ntfs_set_ntfs_reparse_data(ni, (char *)reparse, total_reparse_len);
|
|
if (!err) {
|
|
int len = strlen(sub_name);
|
|
|
|
for (i = 0; i < len; i++) {
|
|
if (sub_name[i] == '\\')
|
|
sub_name[i] = '/';
|
|
}
|
|
ni->target = sub_name;
|
|
sub_name = NULL;
|
|
if (prt_sub_shared)
|
|
prt_name = NULL;
|
|
ni->reparse_tag = IO_REPARSE_TAG_SYMLINK;
|
|
ni->reparse_flags = is_absolute ? 0 :
|
|
cpu_to_le32(SYMLINK_FLAG_RELATIVE);
|
|
}
|
|
|
|
out:
|
|
kfree(prt_name);
|
|
if (!prt_sub_shared)
|
|
kfree(sub_name);
|
|
kvfree(sub_name_utf16);
|
|
kvfree(prt_name_utf16);
|
|
kvfree(reparse);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Set reparse data for a WSL special file other than a symlink
|
|
* (socket, fifo, character or block device)
|
|
*/
|
|
int ntfs_reparse_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode)
|
|
{
|
|
int err;
|
|
int len;
|
|
int reparse_len;
|
|
__le32 reparse_tag;
|
|
struct reparse_point *reparse;
|
|
|
|
len = 0;
|
|
if (S_ISSOCK(mode))
|
|
reparse_tag = IO_REPARSE_TAG_AF_UNIX;
|
|
else if (S_ISFIFO(mode))
|
|
reparse_tag = IO_REPARSE_TAG_LX_FIFO;
|
|
else if (S_ISCHR(mode))
|
|
reparse_tag = IO_REPARSE_TAG_LX_CHR;
|
|
else if (S_ISBLK(mode))
|
|
reparse_tag = IO_REPARSE_TAG_LX_BLK;
|
|
else
|
|
return -EOPNOTSUPP;
|
|
|
|
reparse_len = sizeof(struct reparse_point) + len;
|
|
reparse = kvzalloc(reparse_len, GFP_NOFS);
|
|
if (!reparse)
|
|
err = -ENOMEM;
|
|
else {
|
|
reparse->reparse_tag = reparse_tag;
|
|
reparse->reparse_data_length = cpu_to_le16(len);
|
|
reparse->reserved = cpu_to_le16(0);
|
|
err = ntfs_set_ntfs_reparse_data(ni, (char *)reparse,
|
|
reparse_len);
|
|
kvfree(reparse);
|
|
if (!err) {
|
|
ni->reparse_tag = reparse_tag;
|
|
ni->reparse_flags = 0;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|