summaryrefslogtreecommitdiffstats
path: root/fs/ntfs/reparse.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/ntfs/reparse.c')
-rw-r--r--fs/ntfs/reparse.c1034
1 files changed, 1034 insertions, 0 deletions
diff --git a/fs/ntfs/reparse.c b/fs/ntfs/reparse.c
new file mode 100644
index 000000000..1a6073e22
--- /dev/null
+++ b/fs/ntfs/reparse.c
@@ -0,0 +1,1034 @@
+// 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[];
+};
+
+struct wof_reparse_data {
+ __le32 version;
+ __le32 provider;
+ __le32 provider_version;
+ __le32 compression_format;
+} __packed;
+
+#define WOF_CURRENT_VERSION cpu_to_le32(1)
+
+#define WOF_PROVIDER_WIM cpu_to_le32(1)
+#define WOF_PROVIDER_FILE cpu_to_le32(2)
+
+#define WOF_PROVIDER_CURRENT_VERSION cpu_to_le32(1)
+
+#define WOF_COMPRESSION_XPRESS4K cpu_to_le32(0)
+#define WOF_COMPRESSION_LZX cpu_to_le32(1)
+#define WOF_COMPRESSION_XPRESS8K cpu_to_le32(2)
+#define WOF_COMPRESSION_XPRESS16K cpu_to_le32(3)
+
+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;
+ case IO_REPARSE_TAG_WOF: {
+ if (!valid_reparse_buffer(ni, reparse_attr, size,
+ sizeof(struct wof_reparse_data)))
+ 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;
+}
+
+/*
+ * Parse reparse point data and initialize its in-memory representation.
+ */
+int ntfs_parse_reparse(struct ntfs_inode *ni, unsigned int *mode)
+{
+ s64 attr_size = 0;
+ int err = -EINVAL;
+ unsigned int lth;
+ struct reparse_point *reparse_attr;
+
+ kvfree(ni->target);
+ ni->target = NULL;
+ ni->reparse_tag = 0;
+ ni->reparse_flags = 0;
+ *mode = 0;
+
+ reparse_attr = ntfs_attr_readall(ni, AT_REPARSE_POINT, NULL, 0,
+ &attr_size);
+ if (IS_ERR(reparse_attr)) {
+ err = PTR_ERR(reparse_attr);
+ ntfs_error(ni->vol->sb,
+ "Failed to read reparse point: %d.", err);
+ return err;
+ }
+ if (!valid_reparse_data(ni, reparse_attr, attr_size)) {
+ ntfs_error(ni->vol->sb, "Invalid reparse point.");
+ err = -EFSCORRUPTED;
+ goto out;
+ }
+
+ 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;
+ }
+ case IO_REPARSE_TAG_WOF:
+ {
+#ifdef CONFIG_NTFS_FS_WOF_COMPRESSION
+ const struct wof_reparse_data *wof_data =
+ (const struct wof_reparse_data *)reparse_attr->reparse_data;
+
+ ni->itype.compressed.block_size_bits = 0;
+ ni->itype.compressed.block_size = 0;
+ if (wof_data->version == WOF_CURRENT_VERSION &&
+ wof_data->provider == WOF_PROVIDER_FILE &&
+ wof_data->provider_version ==
+ WOF_PROVIDER_CURRENT_VERSION) {
+ switch (wof_data->compression_format) {
+ case WOF_COMPRESSION_XPRESS4K:
+ ni->itype.compressed.block_size_bits =
+ 12;
+ break;
+ case WOF_COMPRESSION_XPRESS8K:
+ ni->itype.compressed.block_size_bits =
+ 13;
+ break;
+ case WOF_COMPRESSION_XPRESS16K:
+ ni->itype.compressed.block_size_bits =
+ 14;
+ break;
+ case WOF_COMPRESSION_LZX:
+ ni->itype.compressed.block_size_bits =
+ 15;
+ break;
+ }
+ }
+ if (ni->itype.compressed.block_size_bits)
+ ni->itype.compressed.block_size =
+ 1
+ << ni->itype.compressed.block_size_bits;
+#endif
+ NInoSetWofCompressed(ni);
+ VFS_I(ni)->i_mode &= ~0222;
+ err = 0;
+ break;
+ }
+ default:
+ err = 0;
+ }
+
+ if (!err) {
+ *mode = ntfs_reparse_tag_mode(
+ reparse_attr->reparse_tag);
+ ni->reparse_tag = reparse_attr->reparse_tag;
+ }
+
+out:
+ kvfree(reparse_attr);
+
+ return err;
+}
+
+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 DT_UNKNOWN;
+
+ reparse_attr = (struct reparse_point *)ntfs_attr_readall(NTFS_I(vi),
+ AT_REPARSE_POINT, NULL, 0, &attr_size);
+ if (IS_ERR(reparse_attr))
+ reparse_attr = NULL;
+
+ 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;
+ }
+ }
+
+ 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;
+ }
+
+ err = set_reparse_index(ni, xr,
+ ((const struct reparse_point *)value)->reparse_tag);
+ if (err && 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) {
+ strreplace(sub_name, '\\', '/');
+ 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;
+}