ntfs-3g/libntfs-3g/reparse.c

1287 lines
32 KiB
C
Raw Normal View History

/**
* reparse.c - Processing of reparse points
*
2009-12-18 17:13:54 +08:00
* This module is part of ntfs-3g library
*
* Copyright (c) 2008-2016 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('$'),
2009-12-18 17:12:54 +08:00
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;
2008-11-04 21:36:08 +08:00
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,
2008-11-29 18:52:03 +08:00
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);
}
/*
* 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;
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 :
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)
2008-11-29 18:52:03 +08:00
target = search_absolute(vol, &junction[1],
count - 1, isdir);
else
2008-11-29 18:52:03 +08:00
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;
2008-11-29 18:52:03 +08:00
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 :
2008-11-29 18:52:03 +08:00
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 :
2008-11-29 18:52:03 +08:00
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;
/* reparse data is not compatible with EA */
if (ni
&& !ntfs_attr_exist(ni, AT_EA_INFORMATION, AT_UNNAMED, 0)
&& !ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0)
&& 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 */
/*
* 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 = ENOENT;
}
} else
errno = EINVAL;
return (reparse_attr);
}