GetFinalPathNameByHandle canonicalizes the path of an open handle, which
costs a round trip into the file system.  Only the file name suffix is
needed here, and GetFileInformationByHandleEx with FileNameInfo obtains
it directly from the handle.

Prefer the handle name over the supplied path so that stat classifies a
symbolic link from its target suffix.  Inspect the WCHAR suffix directly,
avoiding a lossy multibyte conversion.

* lib/stat-w32.c: Include <wchar.h>.
(GetFinalPathNameByHandleFuncType, GetFinalPathNameByHandleFunc): Remove,
along with the GetFinalPathNameByHandle and VOLUME_NAME_NONE macros.
(GetFileInformationByHandleExFuncType, GetFileInformationByHandleExFunc):
Declare regardless of _GL_WINDOWS_STAT_INODES, as the file name lookup
needs them too.
(initialize): Do not resolve GetFinalPathNameByHandleA.
(_gl_fstat_by_handle): Prefer the name from FileNameInfo and inspect its
wide-character suffix directly.

Signed-off-by: Oleg Tolmatcev <[email protected]>
---
 ChangeLog      | 12 +++++++++
 lib/stat-w32.c | 68 ++++++++++++++++++++++++--------------------------
 2 files changed, 45 insertions(+), 35 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 7fb59f0be5..eeedca45af 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2026-09-05  Oleg Tolmatcev  <[email protected]>
+
+       stat-w32: determine the file name through GetFileInformationByHandleEx
+       * lib/stat-w32.c (GetFinalPathNameByHandleFuncType)
+       (GetFinalPathNameByHandleFunc): Remove.
+       (GetFileInformationByHandleExFuncType)
+       (GetFileInformationByHandleExFunc): Declare regardless of
+       _GL_WINDOWS_STAT_INODES.
+       (initialize): Do not resolve GetFinalPathNameByHandleA.
+       (_gl_fstat_by_handle): Determine the file name through
+       GetFileInformationByHandleEx with argument FileNameInfo.
+
 2026-09-04  Oleg Tolmatcev  <[email protected]>
 
        fts: don't use FTS_CWDFD where openat is emulated via fchdir
diff --git a/lib/stat-w32.c b/lib/stat-w32.c
index 583ef09b0c..2c8971f5b0 100644
--- a/lib/stat-w32.c
+++ b/lib/stat-w32.c
@@ -44,6 +44,7 @@
 #include <limits.h>
 #include <string.h>
 #include <unistd.h>
+#include <wchar.h>
 #include <windows.h>
 
 /* Specification.  */
@@ -54,13 +55,6 @@
 /* Don't assume that UNICODE is not defined.  */
 #undef LoadLibrary
 #define LoadLibrary LoadLibraryA
-#undef GetFinalPathNameByHandle
-#define GetFinalPathNameByHandle GetFinalPathNameByHandleA
-
-/* Older mingw headers do not define VOLUME_NAME_NONE.  */
-#ifndef VOLUME_NAME_NONE
-# define VOLUME_NAME_NONE 4
-#endif
 
 #if !WIN32_ASSUME_VISTA
 
@@ -68,20 +62,12 @@
 # define GetProcAddress \
    (void *) GetProcAddress
 
-# if _GL_WINDOWS_STAT_INODES == 2
 /* GetFileInformationByHandleEx was introduced only in Windows Vista.  */
 typedef DWORD (WINAPI * GetFileInformationByHandleExFuncType) (HANDLE hFile,
                                                                
FILE_INFO_BY_HANDLE_CLASS fiClass,
                                                                LPVOID lpBuffer,
                                                                DWORD 
dwBufferSize);
 static GetFileInformationByHandleExFuncType GetFileInformationByHandleExFunc = 
NULL;
-# endif
-/* GetFinalPathNameByHandle was introduced only in Windows Vista.  */
-typedef DWORD (WINAPI * GetFinalPathNameByHandleFuncType) (HANDLE hFile,
-                                                           LPSTR lpFilePath,
-                                                           DWORD lenFilePath,
-                                                           DWORD dwFlags);
-static GetFinalPathNameByHandleFuncType GetFinalPathNameByHandleFunc = NULL;
 static BOOL initialized = FALSE;
 
 static void
@@ -89,21 +75,14 @@ initialize (void)
 {
   HMODULE kernel32 = LoadLibrary ("kernel32.dll");
   if (kernel32 != NULL)
-    {
-# if _GL_WINDOWS_STAT_INODES == 2
-      GetFileInformationByHandleExFunc =
-        (GetFileInformationByHandleExFuncType) GetProcAddress (kernel32, 
"GetFileInformationByHandleEx");
-# endif
-      GetFinalPathNameByHandleFunc =
-        (GetFinalPathNameByHandleFuncType) GetProcAddress (kernel32, 
"GetFinalPathNameByHandleA");
-    }
+    GetFileInformationByHandleExFunc =
+      (GetFileInformationByHandleExFuncType) GetProcAddress (kernel32, 
"GetFileInformationByHandleEx");
   initialized = TRUE;
 }
 
 #else
 
 # define GetFileInformationByHandleExFunc GetFileInformationByHandleEx
-# define GetFinalPathNameByHandleFunc GetFinalPathNameByHandle
 
 #endif
 
@@ -272,22 +251,41 @@ _gl_fstat_by_handle (HANDLE h, const char *path, struct 
stat *buf)
              name suffix.
              If the file name is already known, use it. Otherwise, for
              non-empty files, it can be determined through
-             GetFinalPathNameByHandle
-             
<https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-getfinalpathnamebyhandlea>
-             or through
              GetFileInformationByHandleEx with argument FileNameInfo
              
<https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getfileinformationbyhandleex>
              
<https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_file_name_info>
-             Both require -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher.  */
+             This requires -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher.  */
           if (info.nFileSizeHigh > 0 || info.nFileSizeLow > 0)
             {
-              char fpath[PATH_MAX];
-              if (path != NULL
-                  || (GetFinalPathNameByHandleFunc != NULL
-                      && GetFinalPathNameByHandleFunc (h, fpath, sizeof 
(fpath), VOLUME_NAME_NONE)
-                         < sizeof (fpath)
-                      && (path = fpath, 1)))
+              /* Room for the header and for the file name.  */
+              union
+                {
+                  FILE_NAME_INFO info;
+                  char storage[sizeof (FILE_NAME_INFO)
+                               + PATH_MAX * sizeof (WCHAR)];
+                } fni;
+              bool name_known = false;
+              if (GetFileInformationByHandleExFunc != NULL
+                  && GetFileInformationByHandleExFunc (h, FileNameInfo,
+                                                       &fni.info,
+                                                       sizeof (fni)))
+                {
+                  size_t length = fni.info.FileNameLength / sizeof (WCHAR);
+                  WCHAR const *name = fni.info.FileName;
+                  name_known = true;
+                  if (length >= 4 && name[length - 4] == L'.')
+                    {
+                      WCHAR const *suffix = name + length - 3;
+                      if (_wcsnicmp (suffix, L"exe", 3) == 0
+                          || _wcsnicmp (suffix, L"bat", 3) == 0
+                          || _wcsnicmp (suffix, L"cmd", 3) == 0
+                          || _wcsnicmp (suffix, L"com", 3) == 0)
+                        mode |= S_IEXEC_UGO;
+                    }
+                }
+              else if (path != NULL)
                 {
+                  name_known = true;
                   const char *last_dot = NULL;
                   for (const char *p = path; *p != '\0'; p++)
                     if (*p == '.')
@@ -302,7 +300,7 @@ _gl_fstat_by_handle (HANDLE h, const char *path, struct 
stat *buf)
                         mode |= S_IEXEC_UGO;
                     }
                 }
-              else
+              if (!name_known)
                 /* Cannot determine file name.  Pretend that it is executable. 
 */
                 mode |= S_IEXEC_UGO;
             }
-- 
2.55.0.windows.5


Reply via email to