mirror of
https://git.code.sf.net/p/ntfs-3g/ntfs-3g.git
synced 2024-11-24 02:25:02 +08:00
424 lines
11 KiB
C
424 lines
11 KiB
C
|
/**
|
||
|
* 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 <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_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 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; i<uname_len; i++)
|
||
|
if (uname[i] < vol->upcase_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; i<found->file_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<level; i++)
|
||
|
strcat(fulltarget,"../");
|
||
|
} else
|
||
|
strcpy(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';
|
||
|
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; i<level; i++)
|
||
|
strcat(fulltarget,"../");
|
||
|
} else
|
||
|
strcpy(fulltarget,"./");
|
||
|
strcat(fulltarget,mappingdir);
|
||
|
strcat(fulltarget,target);
|
||
|
}
|
||
|
}
|
||
|
if (target)
|
||
|
free(target);
|
||
|
}
|
||
|
return (fulltarget);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Get the target for a directory or volume junction
|
||
|
* Should only be called for directories with reparse data
|
||
|
*
|
||
|
* returns the target directory 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
|
||
|
* directory junction
|
||
|
*/
|
||
|
|
||
|
char *ntfs_junction_point(ntfs_volume *vol, const char *org_path,
|
||
|
ntfs_inode *ni, int *pattr_size)
|
||
|
{
|
||
|
s64 attr_size = 0;
|
||
|
char *target;
|
||
|
unsigned int offs;
|
||
|
unsigned int lth;
|
||
|
REPARSE_POINT *reparse_attr;
|
||
|
struct SYMLNK_REPARSE_DATA *path_data;
|
||
|
BOOL bad;
|
||
|
|
||
|
target = (char*)NULL;
|
||
|
bad = TRUE;
|
||
|
reparse_attr = (REPARSE_POINT*)ntfs_attr_readall(ni,
|
||
|
AT_REPARSE_POINT,(ntfschar*)NULL, 0, &attr_size);
|
||
|
if (reparse_attr && attr_size) {
|
||
|
if (reparse_attr->reparse_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);
|
||
|
}
|