[PATCH] Compatibility improvement to reparse point handling, v3

Joe Lowe joe@pismotec.com
Wed Jun 14 20:17:00 GMT 2017


3rd pass at reparse point handling patch.

Changes to this version of the patch.
1. Refactored, smaller, less code impact.
2. readdir() and stat() consistency changes now also handle native file 
(non-directory) symbolic links. readir() returns DT_REG to match lstat() 
indicating a normal file.

Joe L.

-------------- next part --------------
>From 736979422efb815f4b2906642d522c51d98ba230 Mon Sep 17 00:00:00 2001
From: Joe_Lowe <joe@pismotec.com>
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;
 
-- 
2.12.2.windows.2



More information about the Cygwin-patches mailing list