text: /**
   * reparse.c - Processing of reparse points
   *
   *	This module is part of ntfs-3g library
   *
   * Copyright (c) 2008-2013 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 HAVE_SETXATTR
  #include <sys/xattr.h>
  #endif
  
  #ifdef HAVE_SYS_SYSMACROS_H
  #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"
  
  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 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[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);
  }
  
  /*
   *		Do some sanity checks on reparse data
   *
   *	The only general check is about the size (at least the tag must
   *	be present)
   *	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;
  
  	ok = ni && reparse_attr
  		&& (size >= sizeof(REPARSE_POINT))
  		&& (((size_t)le16_to_cpu(reparse_attr->reparse_data_length)
  				 + sizeof(REPARSE_POINT)) == size);
  	if (ok) {
  		switch (reparse_attr->reparse_tag) {
  		case IO_REPARSE_TAG_MOUNT_POINT :
  			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 :
  			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;
  		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
   */
  
  static char *ntfs_get_abslink(ntfs_volume *vol, ntfschar *junction,
  			int count, const char *mnt_point, 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(mnt_point)
  					+ strlen(target) + 2);
  			if (fulltarget) {
  				strcpy(fulltarget,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(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 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,
  			int *pattr_size)
  {
  	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;
  	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;
  		}
  		free(reparse_attr);
  	}
  	*pattr_size = attr_size;
  	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 :
  			possible = TRUE;
  		default : ;
  		}
  		free(reparse_attr);
  	}
  	return (possible);
  }
  
  #ifdef HAVE_SETXATTR	/* extended attributes interface required */
  
  /*
   *			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));
  }
  
  #endif /* HAVE_SETXATTR */
  
  /*
   *		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);
  }
  
  #ifdef HAVE_SETXATTR	/* extended attributes interface required */
  
  /*
   *		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);
  }
  
  #endif /* HAVE_SETXATTR */
  
  /*
   *		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);
  }
  
  #ifdef HAVE_SETXATTR	/* extended attributes interface required */
  
  /*
   *		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;
  	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);
  }
  
  #endif /* HAVE_SETXATTR */
  