root/src/add-ons/kernel/file_systems/ntfs/libntfs/reparse.c
/**
 * reparse.c - Processing of reparse points
 *
 *      This module is part of ntfs-3g library
 *
 * Copyright (c) 2008-2021 Jean-Pierre Andre
 *
 * This program/include file is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program/include file is distributed in the hope that it will be
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program (in the main directory of the NTFS-3G
 * distribution in the file COPYING); if not, write to the Free Software
 * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef MAJOR_IN_MKDEV
#include <sys/mkdev.h>
#endif
#ifdef MAJOR_IN_SYSMACROS
#include <sys/sysmacros.h>
#endif

#include "compat.h"
#include "types.h"
#include "debug.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 "logging.h"
#include "misc.h"
#include "reparse.h"
#include "xattrs.h"
#include "ea.h"

struct MOUNT_POINT_REPARSE_DATA {      /* reparse data for junctions */
        le16    subst_name_offset;
        le16    subst_name_length;
        le16    print_name_offset;
        le16    print_name_length;
        char    path_buffer[0];      /* above data assume this is char array */
} ;

struct SYMLINK_REPARSE_DATA {          /* reparse data for symlinks */
        le16    subst_name_offset;
        le16    subst_name_length;
        le16    print_name_offset;
        le16    print_name_length;
        le32    flags;               /* 1 for full target, otherwise 0 */
        char    path_buffer[0];      /* above data assume this is char array */
} ;

struct WSL_LINK_REPARSE_DATA {
        le32    type;
        char    link[0];
} ;

struct REPARSE_INDEX {                  /* index entry in $Extend/$Reparse */
        INDEX_ENTRY_HEADER header;
        REPARSE_INDEX_KEY key;
        le32 filling;
} ;

static const ntfschar dir_junction_head[] = {
        const_cpu_to_le16('\\'),
        const_cpu_to_le16('?'),
        const_cpu_to_le16('?'),
        const_cpu_to_le16('\\')
} ;

static const ntfschar vol_junction_head[] = {
        const_cpu_to_le16('\\'),
        const_cpu_to_le16('?'),
        const_cpu_to_le16('?'),
        const_cpu_to_le16('\\'),
        const_cpu_to_le16('V'),
        const_cpu_to_le16('o'),
        const_cpu_to_le16('l'),
        const_cpu_to_le16('u'),
        const_cpu_to_le16('m'),
        const_cpu_to_le16('e'),
        const_cpu_to_le16('{'),
} ;

static ntfschar reparse_index_name[] = { const_cpu_to_le16('$'),
                                         const_cpu_to_le16('R') };

static const char mappingdir[] = ".NTFS-3G/";

/*
 *              Fix a file name with doubtful case in some directory index
 *      and return the name with the casing used in directory.
 *
 *      Should only be used to translate paths stored with case insensitivity
 *      (such as directory junctions) when no case conflict is expected.
 *      If there some ambiguity, the name which collates first is returned.
 *
 *      The name is converted to upper case and searched the usual way.
 *      The collation rules for file names are such that we should get the
 *      first candidate if any.
 */

static u64 ntfs_fix_file_name(ntfs_inode *dir_ni, ntfschar *uname,
                int uname_len)
{
        ntfs_volume *vol = dir_ni->vol;
        ntfs_index_context *icx;
        u64 mref;
        le64 lemref;
        int lkup;
        int olderrno;
        int i;
        u32 cpuchar;
        INDEX_ENTRY *entry;
        FILE_NAME_ATTR *found;
        struct {
                FILE_NAME_ATTR attr;
                ntfschar file_name[NTFS_MAX_NAME_LEN + 1];
        } find;

        mref = (u64)-1; /* default return (not found) */
        icx = ntfs_index_ctx_get(dir_ni, NTFS_INDEX_I30, 4);
        if (icx) {
                if (uname_len > NTFS_MAX_NAME_LEN)
                        uname_len = NTFS_MAX_NAME_LEN;
                find.attr.file_name_length = uname_len;
                for (i=0; i<uname_len; i++) {
                        cpuchar = le16_to_cpu(uname[i]);
                        /*
                         * We need upper or lower value, whichever is smaller,
                         * but we can only convert to upper case, so we
                         * will fail when searching for an upper case char
                         * whose lower case is smaller (such as umlauted Y)
                         */
                        if ((cpuchar < vol->upcase_len)
                            && (le16_to_cpu(vol->upcase[cpuchar]) < cpuchar))
                                find.attr.file_name[i] = vol->upcase[cpuchar];
                        else
                                find.attr.file_name[i] = uname[i];
                }
                olderrno = errno;
                lkup = ntfs_index_lookup((char*)&find, uname_len, icx);
                if (errno == ENOENT)
                        errno = olderrno;
                /*
                 * We generally only get the first matching candidate,
                 * so we still have to check whether this is a real match
                 */
                if (icx->entry && (icx->entry->ie_flags & INDEX_ENTRY_END))
                                /* get next entry if reaching end of block */
                        entry = ntfs_index_next(icx->entry, icx);
                else
                        entry = icx->entry;
                if (entry) {
                        found = &entry->key.file_name;
                        if (lkup
                           && ntfs_names_are_equal(find.attr.file_name,
                                find.attr.file_name_length,
                                found->file_name, found->file_name_length,
                                IGNORE_CASE,
                                vol->upcase, vol->upcase_len))
                                        lkup = 0;
                        if (!lkup) {
                                /*
                                 * name found :
                                 *    fix original name and return inode
                                 */
                                lemref = entry->indexed_file;
                                mref = le64_to_cpu(lemref);
                                if (NVolCaseSensitive(vol) || !vol->locase) {
                                        for (i=0; i<found->file_name_length; i++)
                                                uname[i] = found->file_name[i];
                                } else {
                                        for (i=0; i<found->file_name_length; i++)
                                                uname[i] = vol->locase[le16_to_cpu(found->file_name[i])];
                                }
                        }
                }
                ntfs_index_ctx_put(icx);
        }
        return (mref);
}

/*
 *              Search for a directory junction or a symbolic link
 *      along the target path, with target defined as a full absolute path
 *
 *      Returns the path translated to a Linux path
 *              or NULL if the path is not valid
 */

static char *search_absolute(ntfs_volume *vol, ntfschar *path,
                                int count, BOOL isdir)
{
        ntfs_inode *ni;
        u64 inum;
        char *target;
        int start;
        int len;

        target = (char*)NULL; /* default return */
        ni = ntfs_inode_open(vol, (MFT_REF)FILE_root);
        if (ni) {
                start = 0;
                /*
                 * Examine and translate the path, until we reach either
                 *  - the end,
                 *  - an unknown item
                 *  - a non-directory
                 *  - another reparse point,
                 * A reparse point is not dereferenced, it will be
                 * examined later when the translated path is dereferenced,
                 * however the final part of the path will not be adjusted
                 * to correct case.
                 */
                do {
                        len = 0;
                        while (((start + len) < count)
                            && (path[start + len] != const_cpu_to_le16('\\')))
                                len++;
                        inum = ntfs_fix_file_name(ni, &path[start], len);
                        ntfs_inode_close(ni);
                        ni = (ntfs_inode*)NULL;
                        if (inum != (u64)-1) {
                                inum = MREF(inum);
                                ni = ntfs_inode_open(vol, inum);
                                start += len;
                                if (start < count)
                                        path[start++] = const_cpu_to_le16('/');
                        }
                } while (ni
                    && (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
                    && !(ni->flags & FILE_ATTR_REPARSE_POINT)
                    && (start < count));
        if (ni
            && ((ni->mrec->flags & MFT_RECORD_IS_DIRECTORY ? isdir : !isdir)
                || (ni->flags & FILE_ATTR_REPARSE_POINT)))
                if (ntfs_ucstombs(path, count, &target, 0) < 0) {
                        if (target) {
                                free(target);
                                target = (char*)NULL;
                        }
                }
        if (ni)
                ntfs_inode_close(ni);
        }
        return (target);
}

/*
 *              Search for a symbolic link along the target path,
 *      with the target defined as a relative path
 *
 *      Note : the path used to access the current inode, may be
 *      different from the one implied in the target definition,
 *      when an inode has names in several directories.
 *
 *      Returns the path translated to a Linux path
 *              or NULL if the path is not valid
 */

static char *search_relative(ntfs_inode *ni, ntfschar *path, int count)
{
        char *target = (char*)NULL;
        ntfs_inode *curni;
        ntfs_inode *newni;
        u64 inum;
        int pos;
        int lth;
        BOOL ok;
        BOOL morelinks;
        int max = 32; /* safety */

        pos = 0;
        ok = TRUE;
        morelinks = FALSE;
        curni = ntfs_dir_parent_inode(ni);
                /*
                 * Examine and translate the path, until we reach either
                 *  - the end,
                 *  - an unknown item
                 *  - a non-directory
                 *  - another reparse point,
                 * A reparse point is not dereferenced, it will be
                 * examined later when the translated path is dereferenced,
                 * however the final part of the path will not be adjusted
                 * to correct case.
                 */
        while (curni && ok && !morelinks && (pos < (count - 1)) && --max) {
                if ((count >= (pos + 2))
                    && (path[pos] == const_cpu_to_le16('.'))
                    && (path[pos+1] == const_cpu_to_le16('\\'))) {
                        path[pos+1] = const_cpu_to_le16('/');
                        pos += 2;
                } else {
                        if ((count >= (pos + 3))
                            && (path[pos] == const_cpu_to_le16('.'))
                            &&(path[pos+1] == const_cpu_to_le16('.'))
                            && (path[pos+2] == const_cpu_to_le16('\\'))) {
                                path[pos+2] = const_cpu_to_le16('/');
                                pos += 3;
                                newni = ntfs_dir_parent_inode(curni);
                                if (curni != ni)
                                        ntfs_inode_close(curni);
                                curni = newni;
                                if (!curni)
                                        ok = FALSE;
                        } else {
                                lth = 0;
                                while (((pos + lth) < count)
                                    && (path[pos + lth] != const_cpu_to_le16('\\')))
                                        lth++;
                                if (lth > 0)
                                        inum = ntfs_fix_file_name(curni,&path[pos],lth);
                                else
                                        inum = (u64)-1;
                                if (!lth
                                    || ((curni != ni)
                                        && ntfs_inode_close(curni))
                                    || (inum == (u64)-1))
                                        ok = FALSE;
                                else {
                                        curni = ntfs_inode_open(ni->vol, MREF(inum));
                                        if (!curni)
                                                ok = FALSE;
                                        else {
                                                if (curni->flags & FILE_ATTR_REPARSE_POINT)
                                                        morelinks = TRUE;
                                                if (ok && ((pos + lth) < count)) {
                                                        path[pos + lth] = const_cpu_to_le16('/');
                                                        pos += lth + 1;
                                                        if (morelinks
                                                           && ntfs_inode_close(curni))
                                                                ok = FALSE;
                                                } else {
                                                        pos += lth;
                                                        if (!morelinks
                                                          && (ni->mrec->flags ^ curni->mrec->flags)
                                                            & MFT_RECORD_IS_DIRECTORY)
                                                                ok = FALSE;
                                                        if (ntfs_inode_close(curni))
                                                                ok = FALSE;
                                                }
                                        }
                                }
                        }
                }
        }

        if (ok && (ntfs_ucstombs(path, count, &target, 0) < 0)) {
                free(target); // needed ?
                target = (char*)NULL;
        }
        return (target);
}

/*
 *              Check whether a drive letter has been defined in .NTFS-3G
 *
 *      Returns 1 if found,
 *              0 if not found,
 *              -1 if there was an error (described by errno)
 */

static int ntfs_drive_letter(ntfs_volume *vol, ntfschar letter)
{
        char defines[NTFS_MAX_NAME_LEN + 5];
        char *drive;
        int ret;
        int sz;
        int olderrno;
        ntfs_inode *ni;

        ret = -1;
        drive = (char*)NULL;
        sz = ntfs_ucstombs(&letter, 1, &drive, 0);
        if (sz > 0) {
                strcpy(defines,mappingdir);
                if ((*drive >= 'a') && (*drive <= 'z'))
                        *drive += 'A' - 'a';
                strcat(defines,drive);
                strcat(defines,":");
                olderrno = errno;
                ni = ntfs_pathname_to_inode(vol, NULL, defines);
                if (ni && !ntfs_inode_close(ni))
                        ret = 1;
                else
                        if (errno == ENOENT) {
                                ret = 0;
                                        /* avoid errno pollution */
                                errno = olderrno;
                        }
        }
        if (drive)
                free(drive);
        return (ret);
}

/*
 *              Check whether reparse data describes a valid wsl special file
 *      which is either a socket, a fifo, or a character or block device 
 *
 *      Return zero if valid, otherwise returns a negative error code
 */

int ntfs_reparse_check_wsl(ntfs_inode *ni, const REPARSE_POINT *reparse)
{
        int res;

        res = -EOPNOTSUPP;
        switch (reparse->reparse_tag) {
        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 (!reparse->reparse_data_length
                    && (ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN))
                        res = 0;
                break;
        default :
                break;
        }
        if (res)
                errno = EOPNOTSUPP;
        return (res);
}

/*
 *              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(ntfs_inode *ni,
                        const REPARSE_POINT *reparse_attr, size_t size)
{
        BOOL ok;
        unsigned int offs;
        unsigned int lth;
        const struct MOUNT_POINT_REPARSE_DATA *mount_point_data;
        const struct SYMLINK_REPARSE_DATA *symlink_data;
        const struct WSL_LINK_REPARSE_DATA *wsl_reparse_data;

        ok = ni && reparse_attr
                && (size >= sizeof(REPARSE_POINT))
                && (reparse_attr->reparse_tag != IO_REPARSE_TAG_RESERVED_ZERO)
                && (((size_t)le16_to_cpu(reparse_attr->reparse_data_length)
                         + sizeof(REPARSE_POINT)
                         + ((reparse_attr->reparse_tag &
                             IO_REPARSE_TAG_IS_MICROSOFT) ? 0 : sizeof(GUID))) == size);
        if (ok) {
                switch (reparse_attr->reparse_tag) {
                case IO_REPARSE_TAG_MOUNT_POINT :
                        if (size < sizeof(REPARSE_POINT) +
                                   sizeof(struct MOUNT_POINT_REPARSE_DATA)) {
                                ok = FALSE;
                                break;
                        }
                        mount_point_data = (const struct MOUNT_POINT_REPARSE_DATA*)
                                                reparse_attr->reparse_data;
                        offs = le16_to_cpu(mount_point_data->subst_name_offset);
                        lth = le16_to_cpu(mount_point_data->subst_name_length);
                                /* consistency checks */
                        if (!(ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
                            || ((size_t)((sizeof(REPARSE_POINT)
                                 + sizeof(struct MOUNT_POINT_REPARSE_DATA)
                                 + offs + lth)) > size))
                                ok = FALSE;
                        break;
                case IO_REPARSE_TAG_SYMLINK :
                        if (size < sizeof(REPARSE_POINT) +
                                   sizeof(struct SYMLINK_REPARSE_DATA)) {
                                ok = FALSE;
                                break;
                        }
                        symlink_data = (const struct SYMLINK_REPARSE_DATA*)
                                                reparse_attr->reparse_data;
                        offs = le16_to_cpu(symlink_data->subst_name_offset);
                        lth = le16_to_cpu(symlink_data->subst_name_length);
                        if ((size_t)((sizeof(REPARSE_POINT)
                                 + sizeof(struct SYMLINK_REPARSE_DATA)
                                 + offs + lth)) > size)
                                ok = FALSE;
                        break;
                case IO_REPARSE_TAG_LX_SYMLINK :
                        wsl_reparse_data = (const struct WSL_LINK_REPARSE_DATA*)
                                                reparse_attr->reparse_data;
                        if ((le16_to_cpu(reparse_attr->reparse_data_length)
                                        <= sizeof(wsl_reparse_data->type))
                            || (wsl_reparse_data->type != const_cpu_to_le32(2)))
                                ok = 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 (reparse_attr->reparse_data_length
                            || !(ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN))
                                ok = FALSE;
                        break;
                default :
                        break;
                }
        }
        if (!ok)
                errno = EINVAL;
        return (ok);
}

/*
 *              Check and translate the target of a junction point or
 *      a full absolute symbolic link.
 *
 *      A full target definition begins with "\??\" or "\\?\"
 *
 *      The fully defined target is redefined as a relative link,
 *              - either to the target if found on the same device.
 *              - or into the /.NTFS-3G directory for the user to define
 *      In the first situation, the target is translated to case-sensitive path.
 *
 *      returns the target converted to a relative symlink
 *              or NULL if there were some problem, as described by errno
 */

static char *ntfs_get_fulllink(ntfs_volume *vol, ntfschar *junction,
                        int count, const char *mnt_point, BOOL isdir)
{
        char *target;
        char *fulltarget;
        int sz;
        char *q;
        enum { DIR_JUNCTION, VOL_JUNCTION, NO_JUNCTION } kind;

        target = (char*)NULL;
        fulltarget = (char*)NULL;
                        /*
                         * For a valid directory junction we want \??\x:\
                         * where \ is an individual char and x a non-null char
                         */
        if ((count >= 7)
            && !memcmp(junction,dir_junction_head,8)
            && junction[4]
            && (junction[5] == const_cpu_to_le16(':'))
            && (junction[6] == const_cpu_to_le16('\\')))
                kind = DIR_JUNCTION;
        else
                        /*
                         * For a valid volume junction we want \\?\Volume{
                         * and a final \ (where \ is an individual char)
                         */
                if ((count >= 12)
                    && !memcmp(junction,vol_junction_head,22)
                    && (junction[count-1] == const_cpu_to_le16('\\')))
                        kind = VOL_JUNCTION;
                else
                        kind = NO_JUNCTION;
                        /*
                         * Directory junction with an explicit path and
                         * no specific definition for the drive letter :
                         * try to interpret as a target on the same volume
                         */
        if ((kind == DIR_JUNCTION)
            && (count >= 7)
            && junction[7]
            && !ntfs_drive_letter(vol, junction[4])) {
                target = search_absolute(vol,&junction[7],count - 7, isdir);
                if (target) {
                        fulltarget = (char*)ntfs_malloc(strlen(mnt_point)
                                        + strlen(target) + 2);
                        if (fulltarget) {
                                strcpy(fulltarget,mnt_point);
                                strcat(fulltarget,"/");
                                strcat(fulltarget,target);
                        }
                        free(target);
                }
        }
                        /*
                         * Volume junctions or directory junctions with
                         * target not found on current volume :
                         * link to /.NTFS-3G/target which the user can
                         * define as a symbolic link to the real target
                         */
        if (((kind == DIR_JUNCTION) && !fulltarget)
            || (kind == VOL_JUNCTION)) {
                sz = ntfs_ucstombs(&junction[4],
                        (kind == VOL_JUNCTION ? count - 5 : count - 4),
                        &target, 0);
                if ((sz > 0) && target) {
                                /* reverse slashes */
                        for (q=target; *q; q++)
                                if (*q == '\\')
                                        *q = '/';
                                /* force uppercase drive letter */
                        if ((target[1] == ':')
                            && (target[0] >= 'a')
                            && (target[0] <= 'z'))
                                target[0] += 'A' - 'a';
                        fulltarget = (char*)ntfs_malloc(strlen(mnt_point)
                                    + sizeof(mappingdir) + strlen(target) + 1);
                        if (fulltarget) {
                                strcpy(fulltarget,mnt_point);
                                strcat(fulltarget,"/");
                                strcat(fulltarget,mappingdir);
                                strcat(fulltarget,target);
                        }
                }
                if (target)
                        free(target);
        }
        return (fulltarget);
}

/*
 *              Check and translate the target of an absolute symbolic link.
 *
 *      An absolute target definition begins with "\" or "x:\"
 *
 *      The absolute target is redefined as a relative link,
 *              - either to the target if found on the same device.
 *              - or into the /.NTFS-3G directory for the user to define
 *      In the first situation, the target is translated to case-sensitive path.
 *
 *      returns the target converted to a relative symlink
 *              or NULL if there were some problem, as described by errno
 */

char *ntfs_get_abslink(ntfs_volume *vol, ntfschar *junction, int count,
                        const char *mnt_point __attribute__((unused)),
                        BOOL isdir)
{
        char *target;
        char *fulltarget;
        int sz;
        char *q;
        enum { FULL_PATH, ABS_PATH, REJECTED_PATH } kind;

        target = (char*)NULL;
        fulltarget = (char*)NULL;
                        /*
                         * For a full valid path we want x:\
                         * where \ is an individual char and x a non-null char
                         */
        if ((count >= 3)
            && junction[0]
            && (junction[1] == const_cpu_to_le16(':'))
            && (junction[2] == const_cpu_to_le16('\\')))
                kind = FULL_PATH;
        else
                        /*
                         * For an absolute path we want an initial \
                         */
                if ((count >= 0)
                    && (junction[0] == const_cpu_to_le16('\\')))
                        kind = ABS_PATH;
                else
                        kind = REJECTED_PATH;
                        /*
                         * Full path, with a drive letter and
                         * no specific definition for the drive letter :
                         * try to interpret as a target on the same volume.
                         * Do the same for an abs path with no drive letter.
                         */
        if (((kind == FULL_PATH)
            && (count >= 3)
            && junction[3]
            && !ntfs_drive_letter(vol, junction[0]))
            || (kind == ABS_PATH)) {
                if (kind == ABS_PATH)
                        target = search_absolute(vol, &junction[1],
                                count - 1, isdir);
                else
                        target = search_absolute(vol, &junction[3],
                                count - 3, isdir);
                if (target) {
                        fulltarget = (char*)ntfs_malloc(
                                        strlen(vol->abs_mnt_point)
                                        + strlen(target) + 2);
                        if (fulltarget) {
                                strcpy(fulltarget,vol->abs_mnt_point);
                                strcat(fulltarget,"/");
                                strcat(fulltarget,target);
                        }
                        free(target);
                }
        }
                        /*
                         * full path with target not found on current volume :
                         * link to /.NTFS-3G/target which the user can
                         * define as a symbolic link to the real target
                         */
        if ((kind == FULL_PATH) && !fulltarget) {
                sz = ntfs_ucstombs(&junction[0],
                        count,&target, 0);
                if ((sz > 0) && target) {
                                /* reverse slashes */
                        for (q=target; *q; q++)
                                if (*q == '\\')
                                        *q = '/';
                                /* force uppercase drive letter */
                        if ((target[1] == ':')
                            && (target[0] >= 'a')
                            && (target[0] <= 'z'))
                                target[0] += 'A' - 'a';
                        fulltarget = (char*)ntfs_malloc(
                                strlen(vol->abs_mnt_point)
                                    + sizeof(mappingdir) + strlen(target) + 1);
                        if (fulltarget) {
                                strcpy(fulltarget,vol->abs_mnt_point);
                                strcat(fulltarget,"/");
                                strcat(fulltarget,mappingdir);
                                strcat(fulltarget,target);
                        }
                }
                if (target)
                        free(target);
        }
        return (fulltarget);
}

/*
 *              Check and translate the target of a relative symbolic link.
 *
 *      A relative target definition does not begin with "\"
 *
 *      The original definition of relative target is kept, it is just
 *      translated to a case-sensitive path.
 *
 *      returns the target converted to a relative symlink
 *              or NULL if there were some problem, as described by errno
 */

static char *ntfs_get_rellink(ntfs_inode *ni, ntfschar *junction, int count)
{
        char *target;

        target = search_relative(ni,junction,count);
        return (target);
}

/*
 *              Get the target for a junction point or symbolic link
 *      Should only be called for files or directories with reparse data
 *
 *      returns the target converted to a relative path, or NULL
 *              if some error occurred, as described by errno
 *              errno is EOPNOTSUPP if the reparse point is not a valid
 *                      symbolic link or directory junction
 */

char *ntfs_make_symlink(ntfs_inode *ni, const char *mnt_point)
{
        s64 attr_size = 0;
        char *target;
        unsigned int offs;
        unsigned int lth;
        ntfs_volume *vol;
        REPARSE_POINT *reparse_attr;
        struct MOUNT_POINT_REPARSE_DATA *mount_point_data;
        struct SYMLINK_REPARSE_DATA *symlink_data;
        struct WSL_LINK_REPARSE_DATA *wsl_link_data;
        enum { FULL_TARGET, ABS_TARGET, REL_TARGET } kind;
        ntfschar *p;
        BOOL bad;
        BOOL isdir;

        target = (char*)NULL;
        bad = TRUE;
        isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
                         != const_cpu_to_le16(0);
        vol = ni->vol;
        reparse_attr = (REPARSE_POINT*)ntfs_attr_readall(ni,
                        AT_REPARSE_POINT,(ntfschar*)NULL, 0, &attr_size);
        if (reparse_attr && attr_size
                        && valid_reparse_data(ni, reparse_attr, attr_size)) {
                switch (reparse_attr->reparse_tag) {
                case IO_REPARSE_TAG_MOUNT_POINT :
                        mount_point_data = (struct MOUNT_POINT_REPARSE_DATA*)
                                                reparse_attr->reparse_data;
                        offs = le16_to_cpu(mount_point_data->subst_name_offset);
                        lth = le16_to_cpu(mount_point_data->subst_name_length);
                                /* reparse data consistency has been checked */
                        target = ntfs_get_fulllink(vol,
                                (ntfschar*)&mount_point_data->path_buffer[offs],
                                lth/2, mnt_point, isdir);
                        if (target)
                                bad = FALSE;
                        break;
                case IO_REPARSE_TAG_SYMLINK :
                        symlink_data = (struct SYMLINK_REPARSE_DATA*)
                                                reparse_attr->reparse_data;
                        offs = le16_to_cpu(symlink_data->subst_name_offset);
                        lth = le16_to_cpu(symlink_data->subst_name_length);
                        p = (ntfschar*)&symlink_data->path_buffer[offs];
                                /*
                                 * Predetermine the kind of target,
                                 * the called function has to make a full check
                                 */
                        if (*p++ == const_cpu_to_le16('\\')) {
                                if ((*p == const_cpu_to_le16('?'))
                                    || (*p == const_cpu_to_le16('\\')))
                                        kind = FULL_TARGET;
                                else
                                        kind = ABS_TARGET;
                        } else
                                if (*p == const_cpu_to_le16(':'))
                                        kind = ABS_TARGET;
                                else
                                        kind = REL_TARGET;
                        p--;
                                /* reparse data consistency has been checked */
                        switch (kind) {
                        case FULL_TARGET :
                                if (!(symlink_data->flags
                                   & const_cpu_to_le32(1))) {
                                        target = ntfs_get_fulllink(vol,
                                                p, lth/2,
                                                mnt_point, isdir);
                                        if (target)
                                                bad = FALSE;
                                }
                                break;
                        case ABS_TARGET :
                                if (symlink_data->flags
                                   & const_cpu_to_le32(1)) {
                                        target = ntfs_get_abslink(vol,
                                                p, lth/2,
                                                mnt_point, isdir);
                                        if (target)
                                                bad = FALSE;
                                }
                                break;
                        case REL_TARGET :
                                if (symlink_data->flags
                                   & const_cpu_to_le32(1)) {
                                        target = ntfs_get_rellink(ni,
                                                p, lth/2);
                                        if (target)
                                                bad = FALSE;
                                }
                                break;
                        }
                        break;
                case IO_REPARSE_TAG_LX_SYMLINK :
                        wsl_link_data = (struct WSL_LINK_REPARSE_DATA*)
                                                reparse_attr->reparse_data;
                        if (wsl_link_data->type == const_cpu_to_le32(2)) {
                                lth = le16_to_cpu(
                                        reparse_attr->reparse_data_length)
                                        - sizeof(wsl_link_data->type);
                                target = (char*)ntfs_malloc(lth + 1);
                                if (target) {
                                        memcpy(target, wsl_link_data->link,
                                                lth);
                                        target[lth] = 0;
                                        bad = FALSE;
                                }
                        }
                        break;
                }
                free(reparse_attr);
        }
        if (bad)
                errno = EOPNOTSUPP;
        return (target);
}

/*
 *              Check whether a reparse point looks like a junction point
 *      or a symbolic link.
 *      Should only be called for files or directories with reparse data
 *
 *      The validity of the target is not checked.
 */

BOOL ntfs_possible_symlink(ntfs_inode *ni)
{
        s64 attr_size = 0;
        REPARSE_POINT *reparse_attr;
        BOOL possible;

        possible = FALSE;
        reparse_attr = (REPARSE_POINT*)ntfs_attr_readall(ni,
                        AT_REPARSE_POINT,(ntfschar*)NULL, 0, &attr_size);
        if (reparse_attr && attr_size) {
                switch (reparse_attr->reparse_tag) {
                case IO_REPARSE_TAG_MOUNT_POINT :
                case IO_REPARSE_TAG_SYMLINK :
                case IO_REPARSE_TAG_LX_SYMLINK :
                        possible = TRUE;
                default : ;
                }
                free(reparse_attr);
        }
        return (possible);
}


/*
 *                      Set the index for new reparse data
 *
 *      Returns 0 if success
 *              -1 if failure, explained by errno
 */

static int set_reparse_index(ntfs_inode *ni, ntfs_index_context *xr,
                        le32 reparse_tag)
{
        struct REPARSE_INDEX indx;
        u64 file_id_cpu;
        le64 file_id;
        le16 seqn;

        seqn = ni->mrec->sequence_number;
        file_id_cpu = MK_MREF(ni->mft_no,le16_to_cpu(seqn));
        file_id = cpu_to_le64(file_id_cpu);
        indx.header.data_offset = const_cpu_to_le16(
                                        sizeof(INDEX_ENTRY_HEADER)
                                        + sizeof(REPARSE_INDEX_KEY));
        indx.header.data_length = const_cpu_to_le16(0);
        indx.header.reservedV = const_cpu_to_le32(0);
        indx.header.length = const_cpu_to_le16(
                                        sizeof(struct REPARSE_INDEX));
        indx.header.key_length = const_cpu_to_le16(
                                        sizeof(REPARSE_INDEX_KEY));
        indx.header.flags = const_cpu_to_le16(0);
        indx.header.reserved = const_cpu_to_le16(0);
        indx.key.reparse_tag = reparse_tag;
                /* danger on processors which require proper alignment ! */
        memcpy(&indx.key.file_id, &file_id, 8);
        indx.filling = const_cpu_to_le32(0);
        ntfs_index_ctx_reinit(xr);
        return (ntfs_ie_add(xr,(INDEX_ENTRY*)&indx));
}


/*
 *              Remove a reparse data index entry if attribute present
 *
 *      Returns the size of existing reparse data
 *                      (the existing reparse tag is returned)
 *              -1 if failure, explained by errno
 */

static int remove_reparse_index(ntfs_attr *na, ntfs_index_context *xr,
                                le32 *preparse_tag)
{
        REPARSE_INDEX_KEY key;
        u64 file_id_cpu;
        le64 file_id;
        s64 size;
        le16 seqn;
        int ret;

        ret = na->data_size;
        if (ret) {
                        /* read the existing reparse_tag */
                size = ntfs_attr_pread(na, 0, 4, preparse_tag);
                if (size == 4) {
                        seqn = na->ni->mrec->sequence_number;
                        file_id_cpu = MK_MREF(na->ni->mft_no,le16_to_cpu(seqn));
                        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(REPARSE_INDEX_KEY), xr)
                            && ntfs_index_rm(xr))
                                ret = -1;
                } else {
                        ret = -1;
                        errno = ENODATA;
                }
        }
        return (ret);
}

/*
 *              Open the $Extend/$Reparse file and its index
 *
 *      Return the index context if opened
 *              or NULL if an error occurred (errno tells why)
 *
 *      The index has to be freed and inode closed when not needed any more.
 */

static ntfs_index_context *open_reparse_index(ntfs_volume *vol)
{
        u64 inum;
        ntfs_inode *ni;
        ntfs_inode *dir_ni;
        ntfs_index_context *xr;

                /* do not use path_name_to inode - could reopen root */
        dir_ni = ntfs_inode_open(vol, FILE_Extend);
        ni = (ntfs_inode*)NULL;
        if (dir_ni) {
                inum = ntfs_inode_lookup_by_mbsname(dir_ni,"$Reparse");
                if (inum != (u64)-1)
                        ni = ntfs_inode_open(vol, inum);
                ntfs_inode_close(dir_ni);
        }
        if (ni) {
                xr = ntfs_index_ctx_get(ni, reparse_index_name, 2);
                if (!xr) {
                        ntfs_inode_close(ni);
                }
        } else
                xr = (ntfs_index_context*)NULL;
        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.
 *
 *      Returns 0 if success
 *              -1 if failure, explained by errno
 *      If could not remove the existing index, nothing is done,
 *      If could not write the new data, no index entry is inserted
 *      If failed to insert the index, data is removed
 */

static int update_reparse_data(ntfs_inode *ni, ntfs_index_context *xr,
                        const char *value, size_t size)
{
        int res;
        int written;
        int oldsize;
        ntfs_attr *na;
        le32 reparse_tag;

        res = 0;
        na = ntfs_attr_open(ni, AT_REPARSE_POINT, AT_UNNAMED, 0);
        if (na) {
                        /* remove the existing reparse data */
                oldsize = remove_reparse_index(na,xr,&reparse_tag);
                if (oldsize < 0)
                        res = -1;
                else {
                        /* resize attribute */
                        res = ntfs_attr_truncate(na, (s64)size);
                        /* overwrite value if any */
                        if (!res && value) {
                                written = (int)ntfs_attr_pwrite(na,
                                                 (s64)0, (s64)size, value);
                                if (written != (s64)size) {
                                        ntfs_log_error("Failed to update "
                                                "reparse data\n");
                                        errno = EIO;
                                        res = -1;
                                }
                        }
                        if (!res
                            && set_reparse_index(ni,xr,
                                ((const 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(na);
                                ntfs_log_error("Failed to index reparse data."
                                                " Possible corruption.\n");
                        }
                }
                ntfs_attr_close(na);
                NInoSetDirty(ni);
        } else
                res = -1;
        return (res);
}


/*
 *              Delete a reparse index entry
 *
 *      Returns 0 if success
 *              -1 if failure, explained by errno
 */

int ntfs_delete_reparse_index(ntfs_inode *ni)
{
        ntfs_index_context *xr;
        ntfs_inode *xrni;
        ntfs_attr *na;
        le32 reparse_tag;
        int res;

        res = 0;
        na = ntfs_attr_open(ni, AT_REPARSE_POINT, AT_UNNAMED, 0);
        if (na) {
                        /*
                         * read the existing reparse data (the tag is enough)
                         * and un-index it
                         */
                xr = open_reparse_index(ni->vol);
                if (xr) {
                        if (remove_reparse_index(na,xr,&reparse_tag) < 0)
                                res = -1;
                        xrni = xr->ni;
                        ntfs_index_entry_mark_dirty(xr);
                        NInoSetDirty(xrni);
                        ntfs_index_ctx_put(xr);
                        ntfs_inode_close(xrni);
                }
                ntfs_attr_close(na);
        }
        return (res);
}


/*
 *              Get the ntfs reparse data into an extended attribute
 *
 *      Returns the reparse data size
 *              and the buffer is updated if it is long enough
 */

int ntfs_get_ntfs_reparse_data(ntfs_inode *ni, char *value, size_t size)
{
        REPARSE_POINT *reparse_attr;
        s64 attr_size;

        attr_size = 0;  /* default to no data and no error */
        if (ni) {
                if (ni->flags & FILE_ATTR_REPARSE_POINT) {
                        reparse_attr = (REPARSE_POINT*)ntfs_attr_readall(ni,
                                AT_REPARSE_POINT,(ntfschar*)NULL, 0, &attr_size);
                        if (reparse_attr) {
                                if (attr_size <= (s64)size) {
                                        if (value)
                                                memcpy(value,reparse_attr,
                                                        attr_size);
                                        else
                                                errno = EINVAL;
                                }
                                free(reparse_attr);
                        }
                } else
                        errno = ENODATA;
        }
        return (attr_size ? (int)attr_size : -errno);
}

/*
 *              Set the reparse data from an extended attribute
 *
 *      Warning : the new data is not checked
 *
 *      Returns 0, or -1 if there is a problem
 */

int ntfs_set_ntfs_reparse_data(ntfs_inode *ni,
                        const char *value, size_t size, int flags)
{
        int res;
        u8 dummy;
        ntfs_inode *xrni;
        ntfs_index_context *xr;

        res = 0;
                        /*
                         * 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 (ni && valid_reparse_data(ni, (const REPARSE_POINT*)value, size)) {
                xr = open_reparse_index(ni->vol);
                if (xr) {
                        if (!ntfs_attr_exist(ni,AT_REPARSE_POINT,
                                                AT_UNNAMED,0)) {
                                if (!(flags & XATTR_REPLACE)) {
                        /*
                         * 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) {
                                                res = ntfs_attr_add(ni,
                                                        AT_REPARSE_POINT,
                                                        AT_UNNAMED,0,&dummy,
                                                        (s64)0);
                                                if (!res) {
                                                    ni->flags |=
                                                        FILE_ATTR_REPARSE_POINT;
                                                    NInoFileNameSetDirty(ni);
                                                }
                                                NInoSetDirty(ni);
                                        } else {
                                                errno = EOPNOTSUPP;
                                                res = -1;
                                        }
                                } else {
                                        errno = ENODATA;
                                        res = -1;
                                }
                        } else {
                                if (flags & XATTR_CREATE) {
                                        errno = EEXIST;
                                        res = -1;
                                }
                        }
                        if (!res) {
                                        /* update value and index */
                                res = update_reparse_data(ni,xr,value,size);
                        }
                        xrni = xr->ni;
                        ntfs_index_entry_mark_dirty(xr);
                        NInoSetDirty(xrni);
                        ntfs_index_ctx_put(xr);
                        ntfs_inode_close(xrni);
                } else {
                        res = -1;
                }
        } else {
                errno = EINVAL;
                res = -1;
        }
        return (res ? -1 : 0);
}

/*
 *              Remove the reparse data
 *
 *      Returns 0, or -1 if there is a problem
 */

int ntfs_remove_ntfs_reparse_data(ntfs_inode *ni)
{
        int res;
        int olderrno;
        ntfs_attr *na;
        ntfs_inode *xrni;
        ntfs_index_context *xr;
        le32 reparse_tag;

        res = 0;
        if (ni) {
                /*
                 * open and delete the reparse data
                 */
                na = ntfs_attr_open(ni, AT_REPARSE_POINT,
                        AT_UNNAMED,0);
                if (na) {
                        /* first remove index (reparse data needed) */
                        xr = open_reparse_index(ni->vol);
                        if (xr) {
                                if (remove_reparse_index(na,xr,
                                                &reparse_tag) < 0) {
                                        res = -1;
                                } else {
                                        /* now remove attribute */
                                        res = ntfs_attr_rm(na);
                                        if (!res) {
                                                ni->flags &=
                                                    ~FILE_ATTR_REPARSE_POINT;
                                                NInoFileNameSetDirty(ni);
                                        } else {
                                        /*
                                         * If we could not remove the
                                         * attribute, try to restore the
                                         * index and log the error. There
                                         * will be an inconsistency if
                                         * the reindexing fails.
                                         */
                                                set_reparse_index(ni, xr,
                                                        reparse_tag);
                                                ntfs_log_error(
                                                "Failed to remove reparse data."
                                                " Possible corruption.\n");
                                        }
                                }
                                xrni = xr->ni;
                                ntfs_index_entry_mark_dirty(xr);
                                NInoSetDirty(xrni);
                                ntfs_index_ctx_put(xr);
                                ntfs_inode_close(xrni);
                        }
                        olderrno = errno;
                        ntfs_attr_close(na);
                                        /* avoid errno pollution */
                        if (errno == ENOENT)
                                errno = olderrno;
                } else {
                        errno = ENODATA;
                        res = -1;
                }
                NInoSetDirty(ni);
        } else {
                errno = EINVAL;
                res = -1;
        }
        return (res ? -1 : 0);
}

/*
 *              Set reparse data for a WSL type symlink
 */

int ntfs_reparse_set_wsl_symlink(ntfs_inode *ni,
                        const ntfschar *target, int target_len)
{
        int res;
        int len;
        int reparse_len;
        char *utarget;
        REPARSE_POINT *reparse;
        struct WSL_LINK_REPARSE_DATA *data;

        res = -1;
        utarget = (char*)NULL;
        len = ntfs_ucstombs(target, target_len, &utarget, 0);
        if (len > 0) {
                reparse_len = sizeof(REPARSE_POINT) + sizeof(data->type) + len;
                reparse = (REPARSE_POINT*)malloc(reparse_len);
                if (reparse) {
                        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) + len);
                        reparse->reserved = const_cpu_to_le16(0);
                        data->type = const_cpu_to_le32(2);
                        memcpy(data->link, utarget, len);
                        res = ntfs_set_ntfs_reparse_data(ni,
                                (char*)reparse, reparse_len, 0);
                        free(reparse);
                }
        }
        free(utarget);
        return (res);
}

/*
 *              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(ntfs_inode *ni, mode_t mode)
{
        int res;
        int len;
        int reparse_len;
        le32 reparse_tag;
        REPARSE_POINT *reparse;

        res = -1;
        len = 0;
        switch (mode) {
        case S_IFSOCK :
                reparse_tag = IO_REPARSE_TAG_AF_UNIX;
                break;
        case S_IFIFO :
                reparse_tag = IO_REPARSE_TAG_LX_FIFO;
                break;
        case S_IFCHR :
                reparse_tag = IO_REPARSE_TAG_LX_CHR;
                break;
        case S_IFBLK :
                reparse_tag = IO_REPARSE_TAG_LX_BLK;
                break;
        default :
                len = -1;
                errno = EOPNOTSUPP;
                break;
        }
        if (len >= 0) {
                reparse_len = sizeof(REPARSE_POINT) + len;
                reparse = (REPARSE_POINT*)malloc(reparse_len);
                if (reparse) {
                        reparse->reparse_tag = reparse_tag;
                        reparse->reparse_data_length = cpu_to_le16(len);
                        reparse->reserved = const_cpu_to_le16(0);
                        res = ntfs_set_ntfs_reparse_data(ni,
                                (char*)reparse, reparse_len, 0);
                        free(reparse);
                }
        }
        return (res);
}


/*
 *              Get the reparse data into a buffer
 *
 *      Returns the buffer if the reparse data exists and is valid
 *              NULL otherwise (with errno set according to the cause).
 *      When a buffer is returned, it has to be freed by caller.
 */

REPARSE_POINT *ntfs_get_reparse_point(ntfs_inode *ni)
{
        s64 attr_size = 0;
        REPARSE_POINT *reparse_attr;

        reparse_attr = (REPARSE_POINT*)NULL;
        if (ni) {
                reparse_attr = (REPARSE_POINT*)ntfs_attr_readall(ni,
                        AT_REPARSE_POINT,(ntfschar*)NULL, 0, &attr_size);
                if (reparse_attr
                    && !valid_reparse_data(ni, reparse_attr, attr_size)) {
                        free(reparse_attr);
                        reparse_attr = (REPARSE_POINT*)NULL;
                        errno = EINVAL;
                }
        } else
                errno = EINVAL;
        return (reparse_attr);
}