mingw: let `mingw_lstat()` error early upon problems with reparse points

When obtaining lstat information for reparse points, we need to call `FindFirstFile()` in addition to `GetFileInformationEx()` to obtain the type of the reparse point (symlink, mount point etc.). However, currently there is no error handling whatsoever if `FindFirstFile()` fails. Call `FindFirstFile()` before modifying the `stat *buf` output parameter and error out if the call fails. Note: The `FindFirstFile()` return value includes all the data that we get from `GetFileAttributesEx()`, so we could replace `GetFileAttributesEx()` with `FindFirstFile()`. We don't do that because `GetFileAttributesEx()` is about twice as fast for single files. I.e. we only pay the extra cost of calling `FindFirstFile()` in the rare case that we encounter a reparse point. Please also note that the indentation the remaining reparse point code changed, and hence the best way to look at this diff is with `--color-moved -w`. That code was _not_ moved because a subsequent commit will move it to an altogether different function, anyway. Signed-off-by: Karsten Blees <karsten.blees@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Karsten Blees committed Jan 9, 2026 at 20:05 UTC 2c37842ff97c28876e980f1829bc732c98dcde14
1 file changed +14 -10
compat/mingw.c
+14 -10
@@ -920,6 +920,7 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
920 int mingw_lstat(const char *file_name, struct stat *buf)
921 {
922 WIN32_FILE_ATTRIBUTE_DATA fdata;
923 + WIN32_FIND_DATAW findbuf = { 0 };
924 wchar_t wfilename[MAX_PATH];
925 int wlen = xutftowcs_path(wfilename, file_name);
926 if (wlen < 0)
@@ -934,6 +935,13 @@ int mingw_lstat(const char *file_name, struct stat *buf)
935 }
936
937 if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
938 + /* for reparse points, use FindFirstFile to get the reparse tag */
939 + if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
940 + HANDLE handle = FindFirstFileW(wfilename, &findbuf);
941 + if (handle == INVALID_HANDLE_VALUE)
942 + goto error;
943 + FindClose(handle);
944 + }
945 buf->st_ino = 0;
946 buf->st_gid = 0;
947 buf->st_uid = 0;
@@ -946,20 +954,16 @@ int mingw_lstat(const char *file_name, struct stat *buf)
954 filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
955 filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
956 if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
949 - WIN32_FIND_DATAW findbuf;
950 - HANDLE handle = FindFirstFileW(wfilename, &findbuf);
951 - if (handle != INVALID_HANDLE_VALUE) {
952 - if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
953 - (findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) {
954 - buf->st_mode = S_IFLNK | S_IREAD;
955 - if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
956 - buf->st_mode |= S_IWRITE;
957 - }
958 - FindClose(handle);
957 + if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
958 + (findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) {
959 + buf->st_mode = S_IFLNK | S_IREAD;
960 + if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
961 + buf->st_mode |= S_IWRITE;
962 }
963 }
964 return 0;
965 }
966 +error:
967 switch (GetLastError()) {
968 case ERROR_ACCESS_DENIED:
969 case ERROR_SHARING_VIOLATION: