]> sourceware.org Git - newlib-cygwin.git/commitdiff
Cygwin: path handling: never substitute virtual drive with target path
authorCorinna Vinschen <corinna@vinschen.de>
Mon, 12 Dec 2022 20:56:14 +0000 (21:56 +0100)
committerCorinna Vinschen <corinna@vinschen.de>
Wed, 14 Dec 2022 11:00:36 +0000 (12:00 +0100)
Move the drive substitution code after the call to
GetFinalPathNameByHandleW into a local function revert_virtual_drive
and add code to handle non-remote virtual drives, i. e., those
created with the subst command.  (Try to) make sure that virtual
drives are never treated like symlinks.

Fixes: 19d59ce75d53 ("Cygwin: path_conv: Rework handling native symlinks as inner path components")
Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
winsup/cygwin/path.cc

index 4babd10b8a1d5445bcfd64a07ffcc49c73d71f12..c2ad39cb53947a2b658e18dd79469b38e7c1c8c2 100644 (file)
@@ -3024,6 +3024,65 @@ symlink_info::parse_device (const char *contents)
   return isdevice = true;
 }
 
+/* Probably we have a virtual drive input path and the resulting full path
+   starts with the substitution.  Retrieve the target path of the virtual
+   drive and try to revert what GetFinalPathNameByHandleW did to the
+   drive letter. */
+static bool
+revert_virtual_drive (PUNICODE_STRING upath, PUNICODE_STRING fpath,
+                     bool is_remote, ULONG ci_flag)
+{
+  /* Get the drive's target path. */
+  WCHAR drive[3] = {(WCHAR) towupper (upath->Buffer[4]), L':', L'\0'};
+  WCHAR target[MAX_PATH];
+  UNICODE_STRING tpath;
+  WCHAR *p;
+
+  DWORD remlen = QueryDosDeviceW (drive, target, MAX_PATH);
+  if (remlen < 3)
+    return false;
+  remlen -= 2; /* Two L'\0' */
+
+  if (target[remlen - 1] == L'\\')
+    remlen--;
+  RtlInitCountedUnicodeString (&tpath, target, remlen * sizeof (WCHAR));
+
+  const USHORT uncp_len = is_remote ? ro_u_uncp.Length / sizeof (WCHAR) - 1 : 0;
+
+  if (is_remote)
+    {
+      /* target path starts with \??\UNC\. */
+      if (RtlEqualUnicodePathPrefix (&tpath, &ro_u_uncp, TRUE))
+       {
+         remlen -= uncp_len;
+         p = target + uncp_len;
+       }
+      /* target path starts with \Device\<redirector>. */
+      else if ((p = wcschr (target, L';'))
+              && p + 3 < target + remlen
+              && wcsncmp (p + 1, drive, 2) == 0
+              && (p = wcschr (p + 3, L'\\')))
+       remlen -= p - target;
+      else
+       return false;
+      if (wcsncasecmp (fpath->Buffer + uncp_len, p, remlen))
+       return false;
+    }
+  else if (!RtlEqualUnicodePathPrefix (fpath, &tpath, TRUE))
+    return false;
+  /* Replace fpath with source drive letter and append reminder of
+     final path after skipping target path */
+  fpath->Buffer[4] = drive[0]; /* Drive letter */
+  fpath->Buffer[5] = L':';
+  WCHAR *to = fpath->Buffer + 6; /* Next to L':' */
+  WCHAR *from = fpath->Buffer + uncp_len + remlen;
+  memmove (to, from, (wcslen (from) + 1) * sizeof (WCHAR));
+  fpath->Length -= (from - to) * sizeof (WCHAR);
+  if (RtlEqualUnicodeString (upath, fpath, !!ci_flag))
+    return false;
+  return true;
+}
+
 /* Check if PATH is a symlink.  PATH must be a valid Win32 path name.
 
    If PATH is a symlink, put the value of the symlink--the file to
@@ -3343,6 +3402,14 @@ restart:
          continue;
        }
 
+      /* Consider the situation where a virtual drive points to a native
+         symlink.  Opening the virtual drive with FILE_OPEN_REPARSE_POINT
+        actually opens the symlink.  If this symlink points to another
+        directory using a relative path, symlink evaluation goes totally
+        awry.  We never want a virtual drive evaluated as symlink. */
+      if (upath.Length <= 14)
+         goto file_not_symlink;
+
       /* Reparse points are potentially symlinks.  This check must be
         performed before checking the SYSTEM attribute for sysfile
         symlinks, since reparse points can have this flag set, too. */
@@ -3490,80 +3557,38 @@ restart:
              RtlInitCountedUnicodeString (&fpath, fpbuf, ret * sizeof (WCHAR));
              if (!RtlEqualUnicodeString (&upath, &fpath, !!ci_flag))
                {
-                 /* Check if the final path is an UNC path and the incoming
-                    path isn't.  If so... */
-                 if (RtlEqualUnicodePathPrefix (&fpath, &ro_u_uncp, TRUE)
-                     && !RtlEqualUnicodePathPrefix (&upath, &ro_u_uncp, TRUE))
+                 /* If the incoming path is a local drive letter path... */
+                 if (!RtlEqualUnicodePathPrefix (&upath, &ro_u_uncp, TRUE))
                    {
-                     /* ...get the remote path, replace remote path
-                        with drive letter, check again. */
-                     WCHAR drive[3] =
-                       {(WCHAR) towupper (upath.Buffer[4]), L':', L'\0'};
-                     WCHAR remote[MAX_PATH];
-
-                     DWORD remlen = QueryDosDeviceW (drive, remote, MAX_PATH);
-                     if (remlen < 3)
-                       goto file_not_symlink; /* fallback */
-                     remlen -= 2; /* Two L'\0' */
-
-                     if (remote[remlen - 1] == L'\\')
-                       remlen--;
-                     WCHAR *p;
-                     UNICODE_STRING rpath;
-                     RtlInitCountedUnicodeString (&rpath, remote,
-                                                  remlen * sizeof (WCHAR));
-                     const USHORT uncp_len =
-                       ro_u_uncp.Length / sizeof (WCHAR) - 1;
-                     if (RtlEqualUnicodePathPrefix (&rpath, &ro_u_uncp, TRUE))
+                     /* ...and the final path is an UNC path, revert to the
+                        drive letter path syntax. */
+                     if (RtlEqualUnicodePathPrefix (&fpath, &ro_u_uncp, TRUE))
+                       {
+                         if (!revert_virtual_drive (&upath, &fpath, true,
+                                                    ci_flag))
+                           goto file_not_symlink;
+                       }
+                     /* ...otherwise, if the final path changes the drive
+                        letter, let revert_virtual_drive check for a
+                        virtual drive and revert that. */
+                     else if (upath.Buffer[5] == L':'
+                              && (WCHAR) towupper (upath.Buffer[4])
+                                 != (WCHAR) towupper (fpath.Buffer[4]))
                        {
-                         remlen -= uncp_len;
-                         p = remote + uncp_len;
+                         if (!revert_virtual_drive (&upath, &fpath, false,
+                                                    ci_flag))
+                           goto file_not_symlink;
                        }
-                     else if ((p = wcschr (remote, L';'))
-                              && p + 3 < remote + remlen
-                              && wcsncmp (p + 1, drive, 2) == 0
-                              && (p = wcschr (p + 3, L'\\')))
-                       remlen -= p - remote;
-                     else
-                       goto file_not_symlink; /* fallback */
-                     if (wcsncasecmp (fpath.Buffer + uncp_len, p, remlen))
-                       goto file_not_symlink; /* fallback (not expected) */
-                     /* Hackfest */
-                     fpath.Buffer[4] = drive[0]; /* Drive letter */
-                     fpath.Buffer[5] = L':';
-                     WCHAR *to = fpath.Buffer + 6; /* Next to L':' */
-                     WCHAR *from = fpath.Buffer + uncp_len + remlen;
-                     memmove (to, from,
-                              (wcslen (from) + 1) * sizeof (WCHAR));
-                     fpath.Length -= (from - to) * sizeof (WCHAR);
-                     if (RtlEqualUnicodeString (&upath, &fpath, !!ci_flag))
-                       goto file_not_symlink;
                    }
                  issymlink = true;
                  /* upath.Buffer is big enough and unused from this point on.
                     Reuse it here, avoiding yet another buffer allocation. */
                  char *nfpath = (char *) upath.Buffer;
                  sys_wcstombs (nfpath, NT_MAX_PATH, fpbuf);
-                 res = posixify (nfpath);
-
-                 /* If the incoming path consisted of a drive prefix only,
-                    we just handle a virtual drive, created with, e.g.
-
-                      subst X: C:\foo\bar
-
-                    Treat it like a symlink.  This is required to tell an
-                    lstat caller that the "drive" is actually pointing
-                    somewhere else, thus, it's a symlink in POSIX speak. */
-                 if (upath.Length == 14)       /* \??\X:\ */
-                   {
-                     fileattr &= ~FILE_ATTRIBUTE_DIRECTORY;
-                     path_flags |= PATH_SYMLINK;
-                   }
                  /* For final paths differing in inner path components return
                     length as negative value.  This informs path_conv::check
                     to skip realpath handling on the last path component. */
-                 else
-                   res = -res;
+                 res = -posixify (nfpath);
                  break;
                }
            }
This page took 0.040927 seconds and 5 git commands to generate.