| /** |
| * reparse.c - Processing of reparse points |
| * |
| * This module is part of ntfs-3g library |
| * |
| * Copyright (c) 2008-2014 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[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) |
| 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; |
| /* 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 */ |