diff --git a/include/ntfs-3g/reparse.h b/include/ntfs-3g/reparse.h new file mode 100644 index 00000000..3c7a5c90 --- /dev/null +++ b/include/ntfs-3g/reparse.h @@ -0,0 +1,30 @@ +/* + * + * Copyright (c) 2008 Jean-Pierre Andre + * + */ + +/* + * This program 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 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 + */ + +#ifndef REPARSE_H +#define REPARSE_H + +char *ntfs_junction_point(ntfs_volume *vol, const char *org_path, + ntfs_inode *ni, int *pattr_size); + +#endif /* REPARSE_H */ diff --git a/libntfs-3g/reparse.c b/libntfs-3g/reparse.c new file mode 100644 index 00000000..538e5d9d --- /dev/null +++ b/libntfs-3g/reparse.c @@ -0,0 +1,423 @@ +/** + * reparse.c - Processing of reparse points + * + * This module is part of ntfs-3g library, but may also be + * integrated in tools running over Linux or Windows + * + * Copyright (c) 2008 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 +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif + +#ifdef HAVE_SYS_SYSMACROS_H +#include +#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 definition in layout.h is wrong. + source : http://www.opensource.apple.com/darwinsource/WWDC2004/tcl-14/tcl/win/tclWinFile.c +*/ +#undef IO_REPARSE_TAG_MOUNT_POINT +#define IO_REPARSE_TAG_MOUNT_POINT 0xA0000003 + + +struct SYMLNK_REPARSE_DATA { + u16 subst_name_offset; + u16 subst_name_length; + u16 print_name_offset; + u16 print_name_length; + char path_buffer[0]; /* above data assume this is char array */ +} ; + +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 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 as 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 i; + FILE_NAME_ATTR *found; + struct { + FILE_NAME_ATTR attr; + ntfschar file_name[NTFS_MAX_NAME_LEN + 1]; + } find; + + mref = (u64)-1; /* default return */ + icx = ntfs_index_ctx_get(dir_ni, NTFS_INDEX_I30, 4); + if (uname_len > NTFS_MAX_NAME_LEN) + uname_len = NTFS_MAX_NAME_LEN; + find.attr.file_name_length = uname_len; + for (i=0; iupcase_len) + find.attr.file_name[i] = vol->upcase[uname[i]]; + else + find.attr.file_name[i] = uname[i]; + lkup = ntfs_index_lookup(&find, uname_len, icx); + found = (FILE_NAME_ATTR*)icx->data; + /* + * We generally only get the first matching candidate, + * so we still have to check whether this is a real match + */ + if (icx && icx->data && icx->data_len) { + if (lkup + && !ntfs_names_collate(find.attr.file_name, find.attr.file_name_length, + found->file_name, found->file_name_length, + 1, TRUE /* IGNORE_CASE_BOOL */, + vol->upcase, vol->upcase_len)) + lkup = 0; + if (!lkup) { + /* + * name found : + * fix original name and return inode + */ + lemref = *(le64*)((char*)found->file_name - sizeof(INDEX_ENTRY_HEADER) - sizeof(FILE_NAME_ATTR)); + mref = le64_to_cpu(lemref); + for (i=0; ifile_name_length; i++) + uname[i] = found->file_name[i]; + } + } + ntfs_index_ctx_put(icx); + + return (mref); +} + +/* + * Search a directory junction along the target path + * + * Returns the path translated to a Linux path + * or NULL if the path does not designate a valid directory + */ + +static char *search_junction(ntfs_volume *vol, ntfschar *path, int count) +{ + ntfs_inode *ni; + u64 inum; + char *target; + int start; + int len; + + target = (char*)NULL; /* default return */ + ni = ntfs_inode_open(vol, FILE_root); + if (ni) { + start = 0; + do { + len = 0; + while (((start + len) < count) + && path[len + start] != 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)) + if (ntfs_ucstombs(path, count, &target, 0) < 0) { + if (target) { + free(target); + target = (char*)NULL; + } + } + if (ni) + ntfs_inode_close(ni); + } + return (target); +} + +/* + * Check whether a drive letter has been defined in .NTFS-3G + * + * Returns 1 if found, + * 0 if not found, + * -1 if there was an error (described by errno) + */ + +static int ntfs_drive_letter(ntfs_volume *vol, ntfschar letter) +{ + char defines[NTFS_MAX_NAME_LEN + 5]; + char *drive; + int ret; + int sz; + int olderrno; + ntfs_inode *ni; + + ret = -1; + drive = (char*)NULL; + sz = ntfs_ucstombs(&letter, 1, &drive, 0); + if (sz > 0) { + strcpy(defines,mappingdir); + if ((*drive >= 'a') && (*drive <= 'z')) + *drive += 'A' - 'a'; + strcat(defines,drive); + strcat(defines,":"); + olderrno = errno; + ni = ntfs_pathname_to_inode(vol, NULL, defines); + if (ni && !ntfs_inode_close(ni)) + ret = 1; + else + if (errno == ENOENT) { + ret = 0; + /* avoid errno pollution */ + errno = olderrno; + } + } + if (drive) + free(drive); + return (ret); +} + +/* + * Check and translate the target of a junction point + * If the target is a directory junction or a volume junction, it + * 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 + * + * returns the target converted to a relative symlink + * or NULL if there were some problem described by errno + */ + + +static char *ntfs_get_junction(ntfs_volume *vol, ntfschar *junction, + int count, const char *path) +{ + char *target; + char *fulltarget; + int i; + int sz; + int level; + const char *p; + 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_junction(vol,&junction[7],count - 7); + if (target) { + level = 0; + for (p=path; *p; p++) + if (*p == '/') + level++; + fulltarget = ntfs_malloc(3*level + strlen(target) + 1); + if (fulltarget) { + fulltarget[0] = 0; + if (level > 1) { + for (i=1; i 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'; + level = 0; + for (p=path; *p; p++) + if (*p == '/') + level++; + fulltarget = ntfs_malloc(3*level + sizeof(mappingdir) + count - 4); + if (fulltarget) { + fulltarget[0] = 0; + if (level > 1) { + for (i=1; ireparse_tag == IO_REPARSE_TAG_MOUNT_POINT) { + path_data = (struct SYMLNK_REPARSE_DATA*)reparse_attr->reparse_data; + offs = le16_to_cpu(path_data->subst_name_offset); + lth = le16_to_cpu(path_data->subst_name_length); + /* consistency checks */ + if (((le16_to_cpu(reparse_attr->reparse_data_length) + + 8) == attr_size) + && ((int)((sizeof(REPARSE_POINT) + + sizeof(struct SYMLNK_REPARSE_DATA) + + offs + lth)) <= attr_size)) { + target = ntfs_get_junction(vol, + (ntfschar*)&path_data->path_buffer[offs], + lth/2, org_path); + if (target) + bad = FALSE; + } + } + free(reparse_attr); + } + *pattr_size = attr_size; + if (bad) + errno = EOPNOTSUPP; + return (target); +} diff --git a/src/ntfs-3g.c b/src/ntfs-3g.c index d9a1f15e..ca602831 100644 --- a/src/ntfs-3g.c +++ b/src/ntfs-3g.c @@ -94,6 +94,7 @@ #include "version.h" #include "ntfstime.h" #include "security.h" +#include "reparse.h" #include "logging.h" #include "misc.h" @@ -172,6 +173,7 @@ static const char *usage_msg = "\n" "Copyright (C) 2006-2008 Szabolcs Szakacsits\n" "Copyright (C) 2005-2007 Yura Pakhuchiy\n" +"Copyright (C) 2007-2008 Jean-Pierre Andre\n" "\n" "Usage: %s [-o option[,...]]\n" "\n" @@ -183,6 +185,8 @@ static const char *usage_msg = "\n" "%s"; +static const char ntfs_bad_reparse[] = "unsupported reparse point"; + #ifdef FUSE_INTERNAL int drop_privs(void); int restore_privs(void); @@ -410,15 +414,43 @@ static int ntfs_fuse_getattr(const char *org_path, struct stat *stbuf) } #endif if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY && !stream_name_len) { - /* Directory. */ - stbuf->st_mode = S_IFDIR | (0777 & ~ctx->dmask); - na = ntfs_attr_open(ni, AT_INDEX_ALLOCATION, NTFS_INDEX_I30, 4); - if (na) { - stbuf->st_size = na->data_size; - stbuf->st_blocks = na->allocated_size >> 9; - ntfs_attr_close(na); + if (ni->flags & FILE_ATTR_REPARSE_POINT) { + char *target; + int attr_size; + + errno = 0; + target = ntfs_junction_point(ctx->vol,org_path, ni, + &attr_size); + /* + * If the reparse point is not a valid + * directory junction, and there is no error + * we still display as a symlink + */ + if (target || (errno == EOPNOTSUPP)) { + /* returning attribute size */ + if (target) + stbuf->st_size = attr_size; + else + stbuf->st_size = sizeof(ntfs_bad_reparse); + stbuf->st_blocks = (ni->allocated_size + 511) >> 9; + stbuf->st_nlink = le16_to_cpu(ni->mrec->link_count); + stbuf->st_mode = S_IFLNK; + free(target); + } else { + res = -errno; + goto exit; + } + } else { + /* Directory. */ + stbuf->st_mode = S_IFDIR | (0777 & ~ctx->dmask); + na = ntfs_attr_open(ni, AT_INDEX_ALLOCATION, NTFS_INDEX_I30, 4); + if (na) { + stbuf->st_size = na->data_size; + stbuf->st_blocks = na->allocated_size >> 9; + ntfs_attr_close(na); + } + stbuf->st_nlink = 1; /* Make find(1) work */ } - stbuf->st_nlink = 1; /* Make find(1) work */ } else { /* Regular or Interix (INTX) file. */ stbuf->st_mode = S_IFREG; @@ -549,7 +581,22 @@ static int ntfs_fuse_readlink(const char *org_path, char *buf, size_t buf_size) } /* Sanity checks. */ if (!(ni->flags & FILE_ATTR_SYSTEM)) { - res = -EINVAL; + if (ni->flags & FILE_ATTR_REPARSE_POINT) { + char *target; + int attr_size; + + errno = 0; + target = ntfs_junction_point(ctx->vol,org_path, ni, &attr_size); + if (target) { + strncpy(buf,target,buf_size); + free(target); + } else + if (errno == EOPNOTSUPP) + strcpy(buf,ntfs_bad_reparse); + else + res = -errno; + } else + res = -EINVAL; goto exit; } na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);