root/fs/ntfs/reparse.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;
}