Compatibility improvements to reparse point handling.

This commit is contained in:
Joe_Lowe 2017-06-14 13:01:28 -07:00 committed by Corinna Vinschen
parent ec86124748
commit 7a4e299a18
3 changed files with 87 additions and 36 deletions

View File

@ -161,15 +161,19 @@ path_conv::isgood_inode (ino_t ino) const
return true;
}
/* Check reparse point for type. IO_REPARSE_TAG_MOUNT_POINT types are
either volume mount points, which are treated as directories, or they
are directory mount points, which are treated as symlinks.
IO_REPARSE_TAG_SYMLINK types are always symlinks. We don't know
anything about other reparse points, so they are treated as unknown. */
static inline uint8_t
readdir_check_reparse_point (POBJECT_ATTRIBUTES attr)
/* Check reparse point to determine if it should be treated as a posix symlink
or as a normal file/directory. Mount points are treated as normal directories
to match behavior of other systems. Unknown reparse tags are used for
things other than links (HSM, compression, dedup), and generally should be
treated as a normal file/directory. Native symlinks and mount points are
treated as posix symlinks, depending on the prefix of the target name.
This logic needs to agree with equivalent logic in path.cc
symlink_info::check_reparse_point() .
*/
static inline bool
readdir_check_reparse_point (POBJECT_ATTRIBUTES attr, bool remote)
{
uint8_t ret = DT_UNKNOWN;
bool ret = false;
IO_STATUS_BLOCK io;
HANDLE reph;
UNICODE_STRING subst;
@ -185,20 +189,29 @@ readdir_check_reparse_point (POBJECT_ATTRIBUTES attr)
&io, FSCTL_GET_REPARSE_POINT, NULL, 0,
(LPVOID) rp, MAXIMUM_REPARSE_DATA_BUFFER_SIZE)))
{
if (rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
if (!remote && rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
{
RtlInitCountedUnicodeString (&subst,
(WCHAR *)((char *)rp->MountPointReparseBuffer.PathBuffer
+ rp->MountPointReparseBuffer.SubstituteNameOffset),
rp->MountPointReparseBuffer.SubstituteNameLength);
/* Only volume mountpoints are treated as directories. */
if (RtlEqualUnicodePathPrefix (&subst, &ro_u_volume, TRUE))
ret = DT_DIR;
else
ret = DT_LNK;
if (check_reparse_point_target (&subst))
ret = true;
}
else if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK)
ret = DT_LNK;
{
if (rp->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE)
ret = true;
else
{
RtlInitCountedUnicodeString (&subst,
(WCHAR *)((char *)rp->SymbolicLinkReparseBuffer.PathBuffer
+ rp->SymbolicLinkReparseBuffer.SubstituteNameOffset),
rp->SymbolicLinkReparseBuffer.SubstituteNameLength);
if (check_reparse_point_target (&subst))
ret = true;
}
}
NtClose (reph);
}
}
@ -1995,8 +2008,7 @@ fhandler_disk_file::readdir_helper (DIR *dir, dirent *de, DWORD w32_err,
/* Set d_type if type can be determined from file attributes. For .lnk
symlinks, d_type will be reset below. Reparse points can be NTFS
symlinks, even if they have the FILE_ATTRIBUTE_DIRECTORY flag set. */
if (attr &&
!(attr & (~FILE_ATTRIBUTE_VALID_FLAGS | FILE_ATTRIBUTE_REPARSE_POINT)))
if (attr && !(attr & ~FILE_ATTRIBUTE_VALID_FLAGS))
{
if (attr & FILE_ATTRIBUTE_DIRECTORY)
de->d_type = DT_DIR;
@ -2005,19 +2017,22 @@ fhandler_disk_file::readdir_helper (DIR *dir, dirent *de, DWORD w32_err,
de->d_type = DT_REG;
}
/* Check for directory reparse point. These may be treated as a posix
symlink, or as mount point, so need to figure out whether to return
a directory or link type. In all cases, returning the INO of the
reparse point (not of the target) matches behavior of posix systems.
/* Check for reparse points that can be treated as posix symlinks.
Mountpoints and unknown or unhandled reparse points will be treated
as normal file/directory/unknown. In all cases, returning the INO of
the reparse point (not of the target) matches behavior of posix systems.
*/
if ((attr & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT))
== (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT))
if (attr & FILE_ATTRIBUTE_REPARSE_POINT)
{
OBJECT_ATTRIBUTES oattr;
InitializeObjectAttributes (&oattr, fname, pc.objcaseinsensitive (),
get_handle (), NULL);
de->d_type = readdir_check_reparse_point (&oattr);
/* FUTURE: Ideally would know at this point if reparse point
is stored on a remote volume. Without this, may return DT_LNK
for remote names that end up lstat-ing as a normal directory. */
if (readdir_check_reparse_point (&oattr, false/*remote*/))
de->d_type = DT_LNK;
}
/* Check for Windows shortcut. If it's a Cygwin or U/WIN symlink, drop the

View File

@ -2261,6 +2261,31 @@ symlink_info::check_sysfile (HANDLE h)
return res;
}
bool
check_reparse_point_target (PUNICODE_STRING subst)
{
/* Native mount points, or native non-relative symbolic links,
can be treated as posix symlinks only if the SubstituteName
can be converted from a native NT object namespace name to
a win32 name. We only know how to convert names with two
prefixes :
"\??\UNC\..."
"\??\X:..."
Other reparse points will be treated as files or
directories, not as posix symlinks.
*/
if (RtlEqualUnicodePathPrefix (subst, &ro_u_natp, FALSE))
{
if (subst->Length >= 6*sizeof(WCHAR) && subst->Buffer[5] == L':' &&
(subst->Length == 6*sizeof(WCHAR) || subst->Buffer[6] == L'\\'))
return true;
else if (subst->Length >= 8*sizeof(WCHAR) &&
wcsncmp (subst->Buffer + 4, L"UNC\\", 4) == 0)
return true;
}
return false;
}
int
symlink_info::check_reparse_point (HANDLE h, bool remote)
{
@ -2299,6 +2324,7 @@ symlink_info::check_reparse_point (HANDLE h, bool remote)
return 0;
}
if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK)
{
/* Windows evaluates native symlink literally. If a remote symlink points
to, say, C:\foo, it will be handled as if the target is the local file
C:\foo. That comes in handy since that's how symlinks are treated under
@ -2307,6 +2333,15 @@ symlink_info::check_reparse_point (HANDLE h, bool remote)
(WCHAR *)((char *)rp->SymbolicLinkReparseBuffer.PathBuffer
+ rp->SymbolicLinkReparseBuffer.SubstituteNameOffset),
rp->SymbolicLinkReparseBuffer.SubstituteNameLength);
if (!(rp->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE) &&
!check_reparse_point_target (&subst))
{
/* Unsupport native symlink target prefix. Not treated as symlink.
The return value of -1 indicates name needs to be opened without
FILE_OPEN_REPARSE_POINT flag. */
return -1;
}
}
else if (!remote && rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
{
/* Don't handle junctions on remote filesystems as symlinks. This type
@ -2318,11 +2353,11 @@ symlink_info::check_reparse_point (HANDLE h, bool remote)
(WCHAR *)((char *)rp->MountPointReparseBuffer.PathBuffer
+ rp->MountPointReparseBuffer.SubstituteNameOffset),
rp->MountPointReparseBuffer.SubstituteNameLength);
if (RtlEqualUnicodePathPrefix (&subst, &ro_u_volume, TRUE))
if (!check_reparse_point_target (&subst))
{
/* Volume mount point. Not treated as symlink. The return
value of -1 is a hint for the caller to treat this as a
volume mount point. */
/* Volume mount point, or unsupported native target prefix. Not
treated as symlink. The return value of -1 indicates name needs
to be opened without FILE_OPEN_REPARSE_POINT flag. */
return -1;
}
}

View File

@ -88,6 +88,7 @@ enum path_types
};
NTSTATUS file_get_fai (HANDLE, PFILE_ALL_INFORMATION);
bool check_reparse_point_target (PUNICODE_STRING);
class symlink_info;