From 7a4e299a18a48280f36d29a86f23384ab821fc36 Mon Sep 17 00:00:00 2001 From: Joe_Lowe Date: Wed, 14 Jun 2017 13:01:28 -0700 Subject: [PATCH] Compatibility improvements to reparse point handling. --- winsup/cygwin/fhandler_disk_file.cc | 63 ++++++++++++++++++----------- winsup/cygwin/path.cc | 59 +++++++++++++++++++++------ winsup/cygwin/path.h | 1 + 3 files changed, 87 insertions(+), 36 deletions(-) diff --git a/winsup/cygwin/fhandler_disk_file.cc b/winsup/cygwin/fhandler_disk_file.cc index 01a9afe15..f8adcaa4c 100644 --- a/winsup/cygwin/fhandler_disk_file.cc +++ b/winsup/cygwin/fhandler_disk_file.cc @@ -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 diff --git a/winsup/cygwin/path.cc b/winsup/cygwin/path.cc index 7d1d23d72..53cbf4917 100644 --- a/winsup/cygwin/path.cc +++ b/winsup/cygwin/path.cc @@ -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,14 +2324,24 @@ 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 - POSIX as well. */ - RtlInitCountedUnicodeString (&subst, - (WCHAR *)((char *)rp->SymbolicLinkReparseBuffer.PathBuffer - + rp->SymbolicLinkReparseBuffer.SubstituteNameOffset), - rp->SymbolicLinkReparseBuffer.SubstituteNameLength); + { + /* 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 + POSIX as well. */ + RtlInitCountedUnicodeString (&subst, + (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; } } diff --git a/winsup/cygwin/path.h b/winsup/cygwin/path.h index c6b2d2bed..046892879 100644 --- a/winsup/cygwin/path.h +++ b/winsup/cygwin/path.h @@ -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;