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; return true;
} }
/* Check reparse point for type. IO_REPARSE_TAG_MOUNT_POINT types are /* Check reparse point to determine if it should be treated as a posix symlink
either volume mount points, which are treated as directories, or they or as a normal file/directory. Mount points are treated as normal directories
are directory mount points, which are treated as symlinks. to match behavior of other systems. Unknown reparse tags are used for
IO_REPARSE_TAG_SYMLINK types are always symlinks. We don't know things other than links (HSM, compression, dedup), and generally should be
anything about other reparse points, so they are treated as unknown. */ treated as a normal file/directory. Native symlinks and mount points are
static inline uint8_t treated as posix symlinks, depending on the prefix of the target name.
readdir_check_reparse_point (POBJECT_ATTRIBUTES attr) 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; IO_STATUS_BLOCK io;
HANDLE reph; HANDLE reph;
UNICODE_STRING subst; UNICODE_STRING subst;
@ -185,20 +189,29 @@ readdir_check_reparse_point (POBJECT_ATTRIBUTES attr)
&io, FSCTL_GET_REPARSE_POINT, NULL, 0, &io, FSCTL_GET_REPARSE_POINT, NULL, 0,
(LPVOID) rp, MAXIMUM_REPARSE_DATA_BUFFER_SIZE))) (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, RtlInitCountedUnicodeString (&subst,
(WCHAR *)((char *)rp->MountPointReparseBuffer.PathBuffer (WCHAR *)((char *)rp->MountPointReparseBuffer.PathBuffer
+ rp->MountPointReparseBuffer.SubstituteNameOffset), + rp->MountPointReparseBuffer.SubstituteNameOffset),
rp->MountPointReparseBuffer.SubstituteNameLength); rp->MountPointReparseBuffer.SubstituteNameLength);
/* Only volume mountpoints are treated as directories. */ if (check_reparse_point_target (&subst))
if (RtlEqualUnicodePathPrefix (&subst, &ro_u_volume, TRUE)) ret = true;
ret = DT_DIR;
else
ret = DT_LNK;
} }
else if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK) 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); 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 /* 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, d_type will be reset below. Reparse points can be NTFS
symlinks, even if they have the FILE_ATTRIBUTE_DIRECTORY flag set. */ symlinks, even if they have the FILE_ATTRIBUTE_DIRECTORY flag set. */
if (attr && if (attr && !(attr & ~FILE_ATTRIBUTE_VALID_FLAGS))
!(attr & (~FILE_ATTRIBUTE_VALID_FLAGS | FILE_ATTRIBUTE_REPARSE_POINT)))
{ {
if (attr & FILE_ATTRIBUTE_DIRECTORY) if (attr & FILE_ATTRIBUTE_DIRECTORY)
de->d_type = DT_DIR; 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; de->d_type = DT_REG;
} }
/* Check for directory reparse point. These may be treated as a posix /* Check for reparse points that can be treated as posix symlinks.
symlink, or as mount point, so need to figure out whether to return Mountpoints and unknown or unhandled reparse points will be treated
a directory or link type. In all cases, returning the INO of the as normal file/directory/unknown. In all cases, returning the INO of
reparse point (not of the target) matches behavior of posix systems. the reparse point (not of the target) matches behavior of posix systems.
*/ */
if ((attr & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) if (attr & FILE_ATTRIBUTE_REPARSE_POINT)
== (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT))
{ {
OBJECT_ATTRIBUTES oattr; OBJECT_ATTRIBUTES oattr;
InitializeObjectAttributes (&oattr, fname, pc.objcaseinsensitive (), InitializeObjectAttributes (&oattr, fname, pc.objcaseinsensitive (),
get_handle (), NULL); 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 /* 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; 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 int
symlink_info::check_reparse_point (HANDLE h, bool remote) symlink_info::check_reparse_point (HANDLE h, bool remote)
{ {
@ -2299,14 +2324,24 @@ symlink_info::check_reparse_point (HANDLE h, bool remote)
return 0; return 0;
} }
if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK) 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 /* Windows evaluates native symlink literally. If a remote symlink points
C:\foo. That comes in handy since that's how symlinks are treated under to, say, C:\foo, it will be handled as if the target is the local file
POSIX as well. */ C:\foo. That comes in handy since that's how symlinks are treated under
RtlInitCountedUnicodeString (&subst, POSIX as well. */
(WCHAR *)((char *)rp->SymbolicLinkReparseBuffer.PathBuffer RtlInitCountedUnicodeString (&subst,
+ rp->SymbolicLinkReparseBuffer.SubstituteNameOffset), (WCHAR *)((char *)rp->SymbolicLinkReparseBuffer.PathBuffer
rp->SymbolicLinkReparseBuffer.SubstituteNameLength); + 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) else if (!remote && rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
{ {
/* Don't handle junctions on remote filesystems as symlinks. This type /* 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 (WCHAR *)((char *)rp->MountPointReparseBuffer.PathBuffer
+ rp->MountPointReparseBuffer.SubstituteNameOffset), + rp->MountPointReparseBuffer.SubstituteNameOffset),
rp->MountPointReparseBuffer.SubstituteNameLength); 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 /* Volume mount point, or unsupported native target prefix. Not
value of -1 is a hint for the caller to treat this as a treated as symlink. The return value of -1 indicates name needs
volume mount point. */ to be opened without FILE_OPEN_REPARSE_POINT flag. */
return -1; return -1;
} }
} }

View File

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