On 2025-02-23 Lasse Collin wrote:
> On 2025-02-23 Pali Rohár wrote:
> > WinAPI \\?\ has meaning to not do any normalization, so consumer
> > should not add or remove some characters.  
[...]
> In the new dirent, GetFullPathNameW converts mixed \/ usage to \, then
> * is appended and FindFirstFileW is called. The old code calls
> GetFileAttributes before constructing a full path

Even the old dirent has a problem: \??\ is broken because "\??\C:\"
works with GetFileAttributes, but _fullpath converts it to "C:\??\C:\",
and then readdir fails.

I attached a patch to the new dirent. It's shorter to read if white
space change is ignored. It skips GetFullPathNameW with the \\?\ and
\??\ prefixes. I didn't test but maybe this helps if there is a weird
directory name which doesn't survive filename normalization.

I will comment GetFileAttributesW considerations later.

-- 
Lasse Collin
From c849f103459c12599bcaf0d7f6674bb8541f02fe Mon Sep 17 00:00:00 2001
From: Lasse Collin <lasse.col...@tukaani.org>
Date: Mon, 24 Feb 2025 16:30:00 +0200
Subject: [PATCH] ... Skip GetFullPathNameW for \\?\ and \??\ paths

---
 mingw-w64-crt/misc/dirent.c | 93 ++++++++++++++++++++++++++-----------
 1 file changed, 65 insertions(+), 28 deletions(-)

diff --git a/mingw-w64-crt/misc/dirent.c b/mingw-w64-crt/misc/dirent.c
index 1f7a726ce..ffea4adc7 100644
--- a/mingw-w64-crt/misc/dirent.c
+++ b/mingw-w64-crt/misc/dirent.c
@@ -40,7 +40,8 @@
 
 
 /* Maximum number of wide characters allowed in a pathname including
- * the terminating \0. This constant is based on the behavior of
+ * the terminating \0. This is the limit for paths that begin with \\?\
+ * and for all paths in long path aware applications.
  * GetFullPathNameW which rejects input strings that exceed this size. */
 #define DIRENT_WPATH_MAX 32767
 
@@ -122,6 +123,7 @@ get_code_page (void)
 DIR *
 _wopendir (const wchar_t *path)
 {
+  BOOL normalize_path;
   DWORD full_path_alloc_size;
   DWORD full_path_len;
   int err;
@@ -141,31 +143,52 @@ _wopendir (const wchar_t *path)
     }
 
   /*
-   * Make an absolute pathname.
+   * We need an absolute pathname so that things keep working even if
+   * the current directory is changed.
    *
-   * When buffer is too small or not provided, the return value of
-   * GetFullPathNameW includes the space needed for the terminating \0.
+   * If the pathname begins with "\\?\" or "\??\" it is used as is.
+   * Otherwise GetFullPathNameW is used, including variations with
+   * forward slashes like "//?/" or "/\?\".
    */
-  full_path_alloc_size = GetFullPathNameW (path, 0, NULL, NULL);
-  if (full_path_alloc_size == 0)
+  if (path[0] == '\\' &&
+      (path[1] == '\\' || path[1] == '?') &&
+      path[2] == '?' &&
+      path[3] == '\\')
     {
-      switch (GetLastError ())
+      normalize_path = FALSE;
+      full_path_alloc_size = wcslen (path) + 1;
+      if (full_path_alloc_size > DIRENT_WPATH_MAX)
        {
-         case ERROR_CALL_NOT_IMPLEMENTED:
-           /* Windows 95/98/ME is not supported by this dirent code. */
-           errno = ENOSYS;
-           return NULL;
+         errno = ENAMETOOLONG;
+         return NULL;
+       }
+    }
+  else
+    {
+      /* When buffer is too small or not provided, the return value of
+       * GetFullPathNameW includes the space needed for the terminating \0. */
+      normalize_path = TRUE;
+      full_path_alloc_size = GetFullPathNameW (path, 0, NULL, NULL);
+      if (full_path_alloc_size == 0)
+       {
+         switch (GetLastError ())
+           {
+             case ERROR_CALL_NOT_IMPLEMENTED:
+               /* Windows 95/98/ME is not supported by this dirent code. */
+               errno = ENOSYS;
+               return NULL;
 
-         case ERROR_FILENAME_EXCED_RANGE:
-           /* The limit is 32767 wide chars including the terminating \0. */
-           errno = ENAMETOOLONG;
-           return NULL;
+             case ERROR_FILENAME_EXCED_RANGE:
+               /* See the comment of DIRENT_WPATH_MAX in this file. */
+               errno = ENAMETOOLONG;
+               return NULL;
 
-         default:
-           /* GetFullPathNameW accepts various invalid inputs so errors
-            * should be uncommon. */
-           errno = ENOENT;
-           return NULL;
+             default:
+               /* GetFullPathNameW accepts various invalid inputs so errors
+                * should be uncommon. */
+               errno = ENOENT;
+               return NULL;
+           }
        }
     }
 
@@ -179,15 +202,24 @@ _wopendir (const wchar_t *path)
       return NULL;
     }
 
-  /* On success, the return value from GetFullPathNameW does not include
-   * the terminating \0. */
-  full_path_len = GetFullPathNameW (path, full_path_alloc_size,
-                                   dirp->dd_name, NULL);
-  if (full_path_len >= full_path_alloc_size)
+  if (normalize_path)
     {
-      free (dirp);
-      errno = ENOENT;
-      return NULL;
+      /* On success, the return value from GetFullPathNameW does not include
+       * the terminating \0. */
+      full_path_len = GetFullPathNameW (path, full_path_alloc_size,
+                                       dirp->dd_name, NULL);
+      if (full_path_len >= full_path_alloc_size)
+       {
+         free (dirp);
+         errno = ENOENT;
+         return NULL;
+       }
+    }
+  else
+    {
+      /* Copy the \\?\ or \??\ pathname as is. */
+      memcpy (dirp->dd_name, path, full_path_alloc_size * sizeof (wchar_t));
+      full_path_len = full_path_alloc_size - 1;
     }
 
   /* Create the search expression:
@@ -308,6 +340,11 @@ _wopendir (const wchar_t *path)
           * That error shouldn't be possible with FindFirstFileW.
           */
 
+         case ERROR_CALL_NOT_IMPLEMENTED:
+           /* We might get here if GetFullPathNameW wasn't called. */
+           errno = ENOSYS;
+           return NULL;
+
          default:
            /* Unknown error. */
            err = EIO;
-- 
2.48.1

_______________________________________________
Mingw-w64-public mailing list
Mingw-w64-public@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/mingw-w64-public

Reply via email to