https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;h=7a4e299a18a48280f36d29a86f23384ab821fc36

commit 7a4e299a18a48280f36d29a86f23384ab821fc36
Author: Joe_Lowe <[email protected]>
Date:   Wed Jun 14 13:01:28 2017 -0700

    Compatibility improvements to reparse point handling.

Diff:
---
 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 01a9afe..f8adcaa 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 7d1d23d..53cbf49 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 c6b2d2b..0468928 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;

Reply via email to