usbloadergx/source/libntfs/reparse.c

1206 lines
37 KiB
C
Raw Normal View History

2009-12-19 15:06:57 +01:00
/**
* reparse.c - Processing of reparse points
*
* This module is part of ntfs-3g library
2009-12-19 15:06:57 +01:00
*
* Copyright (c) 2008-2009 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 "types.h"
#include "debug.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"
/* the definitions in layout.h are wrong, we use names defined in
2010-09-24 02:48:03 +02:00
http://msdn.microsoft.com/en-us/library/aa365740(VS.85).aspx
*/
2009-12-19 15:06:57 +01:00
#define IO_REPARSE_TAG_DFS const_cpu_to_le32(0x8000000A)
#define IO_REPARSE_TAG_DFSR const_cpu_to_le32(0x80000012)
#define IO_REPARSE_TAG_HSM const_cpu_to_le32(0xC0000004)
#define IO_REPARSE_TAG_HSM2 const_cpu_to_le32(0x80000006)
#define IO_REPARSE_TAG_MOUNT_POINT const_cpu_to_le32(0xA0000003)
#define IO_REPARSE_TAG_SIS const_cpu_to_le32(0x80000007)
#define IO_REPARSE_TAG_SYMLINK const_cpu_to_le32(0xA000000C)
2010-09-24 02:48:03 +02:00
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') };
2009-12-19 15:06:57 +01:00
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.
2009-12-19 15:06:57 +01:00
*
* 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.
2009-12-19 15:06:57 +01:00
*
* 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.
2009-12-19 15:06:57 +01:00
*/
2010-09-24 02:48:03 +02:00
static u64 ntfs_fix_file_name(ntfs_inode *dir_ni, ntfschar *uname, int uname_len)
{
2010-09-24 02:48:03 +02:00
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);
for (i = 0; i < found->file_name_length; i++)
uname[i] = found->file_name[i];
}
}
ntfs_index_ctx_put(icx);
}
return (mref);
2009-12-19 15:06:57 +01:00
}
/*
* Search for a directory junction or a symbolic link
* along the target path, with target defined as a full absolute path
2009-12-19 15:06:57 +01:00
*
* Returns the path translated to a Linux path
* or NULL if the path is not valid
2009-12-19 15:06:57 +01:00
*/
2010-09-24 02:48:03 +02:00
static char *search_absolute(ntfs_volume *vol, ntfschar *path, int count, BOOL isdir)
{
2010-09-24 02:48:03 +02:00
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;
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) && (start < count));
if (ni && (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY ? isdir : !isdir)) if (ntfs_ucstombs(path, count, &target,
0) < 0)
{
if (target)
{
free(target);
target = (char*) NULL;
}
}
if (ni) ntfs_inode_close(ni);
}
return (target);
2009-12-19 15:06:57 +01:00
}
/*
* Search for a symbolic link along the target path,
* with the target defined as a relative path
2009-12-19 15:06:57 +01:00
*
* 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
2009-12-19 15:06:57 +01:00
*/
static char *search_relative(ntfs_inode *ni, ntfschar *path, int count)
{
2010-09-24 02:48:03 +02:00
char *target = (char*) NULL;
ntfs_inode *curni;
ntfs_inode *newni;
u64 inum;
int pos;
int lth;
BOOL ok;
int max = 32; /* safety */
pos = 0;
ok = TRUE;
curni = ntfs_dir_parent_inode(ni);
while (curni && ok && (pos < (count - 1)) && --max)
{
if ((count >= (pos + 2)) && (path[pos] == const_cpu_to_le16('.')) && (path[pos + 1] == const_cpu_to_le16('\\')))
{
path[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[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 (ok && ((pos + lth) < count))
{
path[pos + lth] = const_cpu_to_le16('/');
pos += lth + 1;
}
else
{
pos += lth;
if ((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);
2009-12-19 15:06:57 +01:00
}
/*
* Check whether a drive letter has been defined in .NTFS-3G
2009-12-19 15:06:57 +01:00
*
* Returns 1 if found,
* 0 if not found,
* -1 if there was an error (described by errno)
2009-12-19 15:06:57 +01:00
*/
static int ntfs_drive_letter(ntfs_volume *vol, ntfschar letter)
{
2010-09-24 02:48:03 +02:00
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);
2009-12-19 15:06:57 +01:00
}
/*
* 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.
*
*/
2010-09-24 02:48:03 +02:00
static BOOL valid_reparse_data(ntfs_inode *ni, const REPARSE_POINT *reparse_attr, size_t size)
{
2010-09-24 02:48:03 +02:00
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);
}
2009-12-19 15:06:57 +01:00
/*
* Check and translate the target of a junction point or
* a full absolute symbolic link.
2009-12-19 15:06:57 +01:00
*
* A full target definition begins with "\??\" or "\\?\"
2009-12-19 15:06:57 +01:00
*
* 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.
2009-12-19 15:06:57 +01:00
*
* returns the target converted to a relative symlink
* or NULL if there were some problem, as described by errno
2009-12-19 15:06:57 +01:00
*/
2010-09-24 02:48:03 +02:00
static char *ntfs_get_fulllink(ntfs_volume *vol, ntfschar *junction, int count, const char *mnt_point, BOOL isdir)
{
2010-09-24 02:48:03 +02:00
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
2010-09-24 02:48:03 +02:00
*/
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);
2009-12-19 15:06:57 +01:00
}
/*
* Check and translate the target of an absolute symbolic link.
2009-12-19 15:06:57 +01:00
*
* An absolute target definition begins with "\" or "x:\"
2009-12-19 15:06:57 +01:00
*
* 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.
2009-12-19 15:06:57 +01:00
*
* returns the target converted to a relative symlink
* or NULL if there were some problem, as described by errno
2009-12-19 15:06:57 +01:00
*/
2010-09-24 02:48:03 +02:00
static char *ntfs_get_abslink(ntfs_volume *vol, ntfschar *junction, int count, const char *mnt_point, BOOL isdir)
{
2010-09-24 02:48:03 +02:00
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
2010-09-24 02:48:03 +02:00
*/
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 \
*/
2010-09-24 02:48:03 +02:00
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);
2009-12-19 15:06:57 +01:00
}
/*
* Check and translate the target of a relative symbolic link.
2009-12-19 15:06:57 +01:00
*
* A relative target definition does not begin with "\"
2009-12-19 15:06:57 +01:00
*
* The original definition of relative target is kept, it is just
* translated to a case-sensitive path.
2009-12-19 15:06:57 +01:00
*
* returns the target converted to a relative symlink
* or NULL if there were some problem, as described by errno
2009-12-19 15:06:57 +01:00
*/
static char *ntfs_get_rellink(ntfs_inode *ni, ntfschar *junction, int count)
{
2010-09-24 02:48:03 +02:00
char *target;
2009-12-19 15:06:57 +01:00
2010-09-24 02:48:03 +02:00
target = search_relative(ni, junction, count);
return (target);
2009-12-19 15:06:57 +01:00
}
/*
* Get the target for a junction point or symbolic link
* Should only be called for files or directories with reparse data
2009-12-19 15:06:57 +01:00
*
* 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
2009-12-19 15:06:57 +01:00
*/
2010-09-24 02:48:03 +02:00
char *ntfs_make_symlink(ntfs_inode *ni, const char *mnt_point, int *pattr_size)
{
2010-09-24 02:48:03 +02:00
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);
2009-12-19 15:06:57 +01:00
}
/*
* 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
2009-12-19 15:06:57 +01:00
*
* The validity of the target is not checked.
2009-12-19 15:06:57 +01:00
*/
BOOL ntfs_possible_symlink(ntfs_inode *ni)
{
2010-09-24 02:48:03 +02:00
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);
2009-12-19 15:06:57 +01:00
}
#ifdef HAVE_SETXATTR /* extended attributes interface required */
2009-12-19 15:06:57 +01:00
/*
* Set the index for new reparse data
2009-12-19 15:06:57 +01:00
*
* Returns 0 if success
* -1 if failure, explained by errno
2009-12-19 15:06:57 +01:00
*/
static int set_reparse_index(ntfs_inode *ni, ntfs_index_context *xr,
2010-09-24 02:48:03 +02:00
le32 reparse_tag)
{
2010-09-24 02:48:03 +02:00
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));
2009-12-19 15:06:57 +01:00
}
#endif /* HAVE_SETXATTR */
2009-12-19 15:06:57 +01:00
/*
* Remove a reparse data index entry if attribute present
2009-12-19 15:06:57 +01:00
*
* Returns the size of existing reparse data
* (the existing reparse tag is returned)
* -1 if failure, explained by errno
2009-12-19 15:06:57 +01:00
*/
2010-09-24 02:48:03 +02:00
static int remove_reparse_index(ntfs_attr *na, ntfs_index_context *xr, le32 *preparse_tag)
{
2010-09-24 02:48:03 +02:00
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);
2009-12-19 15:06:57 +01:00
}
/*
* Open the $Extend/$Reparse file and its index
2009-12-19 15:06:57 +01:00
*
* Return the index context if opened
* or NULL if an error occurred (errno tells why)
2009-12-19 15:06:57 +01:00
*
* The index has to be freed and inode closed when not needed any more.
2009-12-19 15:06:57 +01:00
*/
static ntfs_index_context *open_reparse_index(ntfs_volume *vol)
{
2010-09-24 02:48:03 +02:00
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);
2009-12-19 15:06:57 +01:00
}
#ifdef HAVE_SETXATTR /* extended attributes interface required */
2009-12-19 15:06:57 +01:00
/*
* Update the reparse data and index
2009-12-19 15:06:57 +01:00
*
* The reparse data attribute should have been created, and
* an existing index is expected if there is an existing value.
2009-12-19 15:06:57 +01:00
*
* 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
2009-12-19 15:06:57 +01:00
*/
static int update_reparse_data(ntfs_inode *ni, ntfs_index_context *xr,
2010-09-24 02:48:03 +02:00
const char *value, size_t size)
{
2010-09-24 02:48:03 +02:00
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);
2009-12-19 15:06:57 +01:00
}
#endif /* HAVE_SETXATTR */
2009-12-19 15:06:57 +01:00
/*
* Delete a reparse index entry
2009-12-19 15:06:57 +01:00
*
* Returns 0 if success
* -1 if failure, explained by errno
2009-12-19 15:06:57 +01:00
*/
int ntfs_delete_reparse_index(ntfs_inode *ni)
{
2010-09-24 02:48:03 +02:00
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);
2009-12-19 15:06:57 +01:00
}
#ifdef HAVE_SETXATTR /* extended attributes interface required */
2009-12-19 15:06:57 +01:00
/*
* Get the ntfs reparse data into an extended attribute
2009-12-19 15:06:57 +01:00
*
* Returns the reparse data size
* and the buffer is updated if it is long enough
2009-12-19 15:06:57 +01:00
*/
int ntfs_get_ntfs_reparse_data(ntfs_inode *ni, char *value, size_t size)
{
2010-09-24 02:48:03 +02:00
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);
2009-12-19 15:06:57 +01:00
}
/*
* Set the reparse data from an extended attribute
2009-12-19 15:06:57 +01:00
*
* Warning : the new data is not checked
2009-12-19 15:06:57 +01:00
*
* Returns 0, or -1 if there is a problem
2009-12-19 15:06:57 +01:00
*/
int ntfs_set_ntfs_reparse_data(ntfs_inode *ni,
2010-09-24 02:48:03 +02:00
const char *value, size_t size, int flags)
{
2010-09-24 02:48:03 +02:00
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);
2009-12-19 15:06:57 +01:00
}
/*
* Remove the reparse data
2009-12-19 15:06:57 +01:00
*
* Returns 0, or -1 if there is a problem
2009-12-19 15:06:57 +01:00
*/
int ntfs_remove_ntfs_reparse_data(ntfs_inode *ni)
{
2010-09-24 02:48:03 +02:00
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);
2009-12-19 15:06:57 +01:00
}
#endif /* HAVE_SETXATTR */