crt: Fix mingw-w64 stat implementation MS CRT _stat() implementation is known to be broken in different cases. mingw-w64 already contains fixup code for path processing, but this is not enough. For example _stat() returns incorrect type for Windows devices (like NUL) and rejects \\?\ paths with ENOENT error. This is probably because WinAPI FindFirstFile function (which is in lot of times used as replacement for POSIX stat) does not return file type at all. mingw-w64 already provides working fstat() implementation (as wrapper around MS CRT _fstat()) which can be used as stat() fallback for already opened file handle. So if the MS CRT _stat() fails or returns that file type is regular file then try to open path in read-only mode and call mingw-w64 fstat() on it. Note that opening path can fail for many reasons (for example denied by ACL, denied by shared reservation or denied by delete_pending state) while regular FindFirstFile is working fine. So do not take errors from opening path as fatal error. Use the fstat just for correcting information returned by MS CRT _stat() function. Bug: https://sourceforge.net/p/mingw-w64/bugs/1009/ Bug: https://github.com/mingw-w64/mingw-w64/issues/139 Co-authored-by: LIU Hao <lh_mouse@126.com> Signed-off-by: LIU Hao <lh_mouse@126.com>
diff --git a/mingw-w64-crt/Makefile.am b/mingw-w64-crt/Makefile.am index 57d16a8..1d85c9d 100644 --- a/mingw-w64-crt/Makefile.am +++ b/mingw-w64-crt/Makefile.am
@@ -1306,6 +1306,7 @@ stdio/mingw_ftruncate64.c stdio/lltoa.c stdio/lltow.c \ stdio/__mingw_fix_stat.h stdio/__mingw_fix_stat_finish.c stdio/__mingw_fix_fstat_finish.c \ stdio/__mingw_fix_stat_path.c stdio/__mingw_fix_wstat_path.c \ + stdio/__mingw_fix_stat_fallback_fd.c stdio/__mingw_fix_wstat_fallback_fd.c \ \ stdio/mingw_pformat.h stdio/mingw_sformat.h stdio/mingw_swformat.h \ stdio/mingw_fprintf.c stdio/mingw_fwprintf.c stdio/mingw_fscanf.c stdio/mingw_fwscanf.c stdio/mingw_pformat.c \
diff --git a/mingw-w64-crt/stdio/__mingw_fix_stat.h b/mingw-w64-crt/stdio/__mingw_fix_stat.h index 13e4bf6..4381829 100644 --- a/mingw-w64-crt/stdio/__mingw_fix_stat.h +++ b/mingw-w64-crt/stdio/__mingw_fix_stat.h
@@ -12,6 +12,8 @@ int __mingw_fix_stat_finish(int ret, const void *orig_path, void *used_path, unsigned short mode); int __mingw_fix_fstat_finish(int ret, int fd, unsigned short *mode); +int __mingw_fix_wstat_fallback_fd(int ret, const wchar_t *filename, unsigned short mode); +int __mingw_fix_stat_fallback_fd(int ret, const char *filename, unsigned short mode); #define __MINGW_FIXED_FSTAT(fstat_func, fd, obj) ({ \ int _fstat_ret = fstat_func(fd, obj); \ @@ -30,7 +32,17 @@ #define __MINGW_FIX_STAT_PATH(path) \ __MINGW_CHOOSE_CHAR_WCHART_EXPR((path)[0], __mingw_fix_stat_path, __mingw_fix_wstat_path, NULL)(path) -#define __MINGW_FIXED_STAT(stat_func, filename, obj) ({ \ +#define __MINGW_FIX_STAT_FALLBACK_FD(ret, path, mode) \ + __MINGW_CHOOSE_CHAR_WCHART_EXPR((path)[0], __mingw_fix_stat_fallback_fd, __mingw_fix_wstat_fallback_fd, NULL)((ret), (path), (mode)) + +#define __MINGW_CREATE_FILE(path, ...) \ + __MINGW_CHOOSE_CHAR_WCHART_EXPR((path)[0], CreateFileA, CreateFileW, NULL)((path), ##__VA_ARGS__) + +#define __MINGW_STR_PBRK(path, accept) \ + __MINGW_CHOOSE_CHAR_WCHART_EXPR((path)[0], strpbrk, wcspbrk, NULL)((path), __MINGW_CHOOSE_CHAR_WCHART_EXPR((path)[0], accept, L##accept, NULL)) + +#define __MINGW_FIXED_STAT(fstat_func, stat_func, filename, obj) ({ \ + /* First call CRT _stat function with mingw path correction */ \ int _stat_ret; \ __MINGW_PATH_PTR_TYPE(filename) path = __MINGW_FIX_STAT_PATH(filename); \ if (path == NULL && (filename) != NULL) { \ @@ -38,6 +50,14 @@ } else { \ _stat_ret = (stat_func)(path, (obj)); \ _stat_ret = __mingw_fix_stat_finish(_stat_ret, (filename), path, (obj)->st_mode); \ + /* If the CRT _stat function failed then fallback to mingw fstat function */ \ + int _stat_fd = __MINGW_FIX_STAT_FALLBACK_FD(_stat_ret, (filename), (obj)->st_mode); \ + if (_stat_fd >= 0) { \ + _stat_ret = fstat_func(_stat_fd, (void*)(obj)); \ + int _stat_errno = errno; \ + close(_stat_fd); \ + errno = _stat_errno; \ + } \ } \ _stat_ret; \ })
diff --git a/mingw-w64-crt/stdio/__mingw_fix_stat_fallback_fd.c b/mingw-w64-crt/stdio/__mingw_fix_stat_fallback_fd.c new file mode 100644 index 0000000..b8a6e44 --- /dev/null +++ b/mingw-w64-crt/stdio/__mingw_fix_stat_fallback_fd.c
@@ -0,0 +1,44 @@ +/** + * This file has no copyright assigned and is placed in the Public Domain. + * This file is part of the mingw-w64 runtime package. + * No warranty is given; refer to the file DISCLAIMER.PD within this package. + */ + +#include <sys/stat.h> +#include <string.h> +#include <wchar.h> +#include <errno.h> +#include <fcntl.h> +#include <windows.h> +#include "__mingw_fix_stat.h" + +#ifdef MINGW_FIX_STAT_IS_WIDE +int __mingw_fix_wstat_fallback_fd(int ret, const wchar_t *filename, unsigned short mode) +#else +int __mingw_fix_stat_fallback_fd(int ret, const char *filename, unsigned short mode) +#endif +{ + int fd = -1; + + /* + * CRT _stat does not handle paths with ? and * characters and returns ENOENT. + * This prevents _stat from working on paths like \\?\C:\foo.txt. + * CRT _stat incorrectly sets S_IFREG for pipe and char devices. + * For these cases open specified filename and return its fd which will be passed to CRT fstat() by caller. + */ + if ((ret < 0 && errno == ENOENT && __MINGW_STR_PBRK((filename), "?*")) || (ret == 0 && S_ISREG(mode))) { + HANDLE handle = __MINGW_CREATE_FILE((filename), FILE_READ_ATTRIBUTES, FILE_SHARE_VALID_FLAGS, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (handle != NULL && handle != INVALID_HANDLE_VALUE) { + /* Open filename and return fd if CRT _stat failed or if the file is not regular disk file. */ + if (ret < 0 || GetFileType(handle) != FILE_TYPE_DISK) { + int saved_errno = errno; + fd = _open_osfhandle((intptr_t)handle, O_RDONLY); + errno = saved_errno; + } + if (fd < 0) + CloseHandle(handle); + } + } + + return fd; +}
diff --git a/mingw-w64-crt/stdio/__mingw_fix_wstat_fallback_fd.c b/mingw-w64-crt/stdio/__mingw_fix_wstat_fallback_fd.c new file mode 100644 index 0000000..1182ab5 --- /dev/null +++ b/mingw-w64-crt/stdio/__mingw_fix_wstat_fallback_fd.c
@@ -0,0 +1,2 @@ +#define MINGW_FIX_STAT_IS_WIDE +#include "__mingw_fix_stat_fallback_fd.c"
diff --git a/mingw-w64-crt/stdio/msvcr110plus_stat32.c b/mingw-w64-crt/stdio/msvcr110plus_stat32.c index 400ac1f..9042cea 100644 --- a/mingw-w64-crt/stdio/msvcr110plus_stat32.c +++ b/mingw-w64-crt/stdio/msvcr110plus_stat32.c
@@ -8,10 +8,11 @@ #include <stdlib.h> #include "__mingw_fix_stat.h" +int __cdecl fstat32(int fd, struct _stat32 *stat); int __cdecl stat32(const char *_Filename, struct _stat32 *_Stat); int __cdecl stat32(const char *_Filename, struct _stat32 *_Stat) { - return __MINGW_FIXED_STAT(_stat32, _Filename, _Stat); + return __MINGW_FIXED_STAT(fstat32, _stat32, _Filename, _Stat); } int (__cdecl *__MINGW_IMP_SYMBOL(stat32))(const char *, struct _stat32 *) = stat32;
diff --git a/mingw-w64-crt/stdio/msvcr110plus_stat64i32.c b/mingw-w64-crt/stdio/msvcr110plus_stat64i32.c index 299abd8..9ccd462 100644 --- a/mingw-w64-crt/stdio/msvcr110plus_stat64i32.c +++ b/mingw-w64-crt/stdio/msvcr110plus_stat64i32.c
@@ -8,10 +8,11 @@ #include <stdlib.h> #include "__mingw_fix_stat.h" +int __cdecl fstat64i32(int fd, struct _stat64i32 *stat); int __cdecl stat64i32(const char *_Filename, struct _stat64i32 *_Stat); int __cdecl stat64i32(const char *_Filename, struct _stat64i32 *_Stat) { - return __MINGW_FIXED_STAT(_stat64i32, _Filename, _Stat); + return __MINGW_FIXED_STAT(fstat64i32, _stat64i32, _Filename, _Stat); } int (__cdecl *__MINGW_IMP_SYMBOL(stat64i32))(const char *, struct _stat64i32 *) = stat64i32;
diff --git a/mingw-w64-crt/stdio/msvcr110plus_wstat32.c b/mingw-w64-crt/stdio/msvcr110plus_wstat32.c index 85291d9..bbae9c2 100644 --- a/mingw-w64-crt/stdio/msvcr110plus_wstat32.c +++ b/mingw-w64-crt/stdio/msvcr110plus_wstat32.c
@@ -8,10 +8,11 @@ #include <stdlib.h> #include "__mingw_fix_stat.h" +int __cdecl fstat32(int fd, struct _stat32 *stat); int __cdecl wstat32(const wchar_t *_Filename, struct _stat32 *_Stat); int __cdecl wstat32(const wchar_t *_Filename, struct _stat32 *_Stat) { - return __MINGW_FIXED_STAT(_wstat32, _Filename, _Stat); + return __MINGW_FIXED_STAT(fstat32, _wstat32, _Filename, _Stat); } int (__cdecl *__MINGW_IMP_SYMBOL(wstat32))(const wchar_t *, struct _stat32 *) = wstat32;
diff --git a/mingw-w64-crt/stdio/msvcr110plus_wstat64i32.c b/mingw-w64-crt/stdio/msvcr110plus_wstat64i32.c index baff6a0..e0a37f4 100644 --- a/mingw-w64-crt/stdio/msvcr110plus_wstat64i32.c +++ b/mingw-w64-crt/stdio/msvcr110plus_wstat64i32.c
@@ -8,10 +8,11 @@ #include <stdlib.h> #include "__mingw_fix_stat.h" +int __cdecl fstat64i32(int fd, struct _stat64i32 *stat); int __cdecl wstat64i32(const wchar_t *_Filename, struct _stat64i32 *_Stat); int __cdecl wstat64i32(const wchar_t *_Filename, struct _stat64i32 *_Stat) { - return __MINGW_FIXED_STAT(_wstat64i32, _Filename, _Stat); + return __MINGW_FIXED_STAT(fstat64i32, _wstat64i32, _Filename, _Stat); } int (__cdecl *__MINGW_IMP_SYMBOL(wstat64i32))(const wchar_t *, struct _stat64i32 *) = wstat64i32;
diff --git a/mingw-w64-crt/stdio/msvcr110pre_stat32.c b/mingw-w64-crt/stdio/msvcr110pre_stat32.c index 8650db2..296f7c3 100644 --- a/mingw-w64-crt/stdio/msvcr110pre_stat32.c +++ b/mingw-w64-crt/stdio/msvcr110pre_stat32.c
@@ -15,11 +15,12 @@ * as it is required by POSIX stat(). * This file is used only for pre-msvcr110 builds. */ +int __cdecl fstat32i64(int fd,struct _stat32i64 *stat); int __cdecl stat32(const char *_Filename, struct _stat32 *_Stat); int __cdecl stat32(const char *_Filename, struct _stat32 *_Stat) { struct _stat32i64 st; - int ret = __MINGW_FIXED_STAT(_stat32i64, _Filename, &st); + int ret = __MINGW_FIXED_STAT(fstat32i64, _stat32i64, _Filename, &st); if (ret != 0) return ret; if (st.st_size > INT32_MAX) {
diff --git a/mingw-w64-crt/stdio/msvcr110pre_stat64i32.c b/mingw-w64-crt/stdio/msvcr110pre_stat64i32.c index d929a4f..7e2cdf7 100644 --- a/mingw-w64-crt/stdio/msvcr110pre_stat64i32.c +++ b/mingw-w64-crt/stdio/msvcr110pre_stat64i32.c
@@ -19,7 +19,7 @@ int __cdecl stat64i32(const char *_Filename, struct _stat64i32 *_Stat) { struct _stat64 st; - int ret = __MINGW_FIXED_STAT(_stat64, _Filename, &st); + int ret = __MINGW_FIXED_STAT(fstat64, _stat64, _Filename, &st); if (ret != 0) return ret; if (st.st_size > INT32_MAX) {
diff --git a/mingw-w64-crt/stdio/msvcr110pre_wstat32.c b/mingw-w64-crt/stdio/msvcr110pre_wstat32.c index 308c7f6..db5886b 100644 --- a/mingw-w64-crt/stdio/msvcr110pre_wstat32.c +++ b/mingw-w64-crt/stdio/msvcr110pre_wstat32.c
@@ -15,11 +15,12 @@ * as it is required by POSIX stat(). * This file is used only for pre-msvcr110 builds. */ +int __cdecl fstat32i64(int fd, struct _stat32i64 *stat); int __cdecl wstat32(const wchar_t *_Filename, struct _stat32 *_Stat); int __cdecl wstat32(const wchar_t *_Filename, struct _stat32 *_Stat) { struct _stat32i64 st; - int ret = __MINGW_FIXED_STAT(_wstat32i64, _Filename, &st); + int ret = __MINGW_FIXED_STAT(fstat32i64, _wstat32i64, _Filename, &st); if (ret != 0) return ret; if (st.st_size > INT32_MAX) {
diff --git a/mingw-w64-crt/stdio/msvcr110pre_wstat64i32.c b/mingw-w64-crt/stdio/msvcr110pre_wstat64i32.c index f8ed493..4266287 100644 --- a/mingw-w64-crt/stdio/msvcr110pre_wstat64i32.c +++ b/mingw-w64-crt/stdio/msvcr110pre_wstat64i32.c
@@ -19,7 +19,7 @@ int __cdecl wstat64i32(const wchar_t *_Filename, struct _stat64i32 *_Stat) { struct _stat64 st; - int ret = __MINGW_FIXED_STAT(_wstat64, _Filename, &st); + int ret = __MINGW_FIXED_STAT(fstat64, _wstat64, _Filename, &st); if (ret != 0) return ret; if (st.st_size > INT32_MAX) {
diff --git a/mingw-w64-crt/stdio/stat32i64.c b/mingw-w64-crt/stdio/stat32i64.c index f8e84bf..ddde920 100644 --- a/mingw-w64-crt/stdio/stat32i64.c +++ b/mingw-w64-crt/stdio/stat32i64.c
@@ -8,9 +8,10 @@ #include <stdlib.h> #include "__mingw_fix_stat.h" +int __cdecl fstat32i64(int fd, struct _stat32i64 *stat); int __cdecl stat32i64(const char *_Filename, struct _stat32i64 *_Stat); int __cdecl stat32i64(const char *_Filename, struct _stat32i64 *_Stat) { - return __MINGW_FIXED_STAT(_stat32i64, _Filename, _Stat); + return __MINGW_FIXED_STAT(fstat32i64, _stat32i64, _Filename, _Stat); } int (__cdecl *__MINGW_IMP_SYMBOL(stat32i64))(const char *, struct _stat32i64 *) = stat32i64;
diff --git a/mingw-w64-crt/stdio/stat64.c b/mingw-w64-crt/stdio/stat64.c index 0cef610..ab8afd6 100644 --- a/mingw-w64-crt/stdio/stat64.c +++ b/mingw-w64-crt/stdio/stat64.c
@@ -10,6 +10,6 @@ int __cdecl stat64(const char *_Filename, struct stat64 *_Stat) { - return __MINGW_FIXED_STAT(_stat64, _Filename, (struct _stat64 *)_Stat); + return __MINGW_FIXED_STAT(fstat64, _stat64, _Filename, (struct _stat64 *)_Stat); } int (__cdecl *__MINGW_IMP_SYMBOL(stat64))(const char *, struct stat64 *) = stat64;
diff --git a/mingw-w64-crt/stdio/wstat32i64.c b/mingw-w64-crt/stdio/wstat32i64.c index d4bb87e..2406355 100644 --- a/mingw-w64-crt/stdio/wstat32i64.c +++ b/mingw-w64-crt/stdio/wstat32i64.c
@@ -8,9 +8,10 @@ #include <stdlib.h> #include "__mingw_fix_stat.h" +int __cdecl fstat32i64(int fd, struct _stat32i64 *stat); int __cdecl wstat32i64(const wchar_t *_Filename, struct _stat32i64 *_Stat); int __cdecl wstat32i64(const wchar_t *_Filename, struct _stat32i64 *_Stat) { - return __MINGW_FIXED_STAT(_wstat32i64, _Filename, _Stat); + return __MINGW_FIXED_STAT(fstat32i64, _wstat32i64, _Filename, _Stat); } int (__cdecl *__MINGW_IMP_SYMBOL(wstat32i64))(const wchar_t *, struct _stat32i64 *) = wstat32i64;
diff --git a/mingw-w64-crt/stdio/wstat64.c b/mingw-w64-crt/stdio/wstat64.c index 0f00a4e..dbcf958 100644 --- a/mingw-w64-crt/stdio/wstat64.c +++ b/mingw-w64-crt/stdio/wstat64.c
@@ -10,6 +10,6 @@ int __cdecl wstat64(const wchar_t *_Filename, struct stat64 *_Stat) { - return __MINGW_FIXED_STAT(_wstat64, _Filename, (struct _stat64 *)_Stat); + return __MINGW_FIXED_STAT(fstat64, _wstat64, _Filename, (struct _stat64 *)_Stat); } int (__cdecl *__MINGW_IMP_SYMBOL(wstat64))(const wchar_t *, struct stat64 *) = wstat64;
diff --git a/mingw-w64-crt/testcases/t_stat.c b/mingw-w64-crt/testcases/t_stat.c index 33fa4a1..1e2c23d 100644 --- a/mingw-w64-crt/testcases/t_stat.c +++ b/mingw-w64-crt/testcases/t_stat.c
@@ -1,15 +1,118 @@ #include <stdio.h> +#include <assert.h> #include <sys/types.h> #include <sys/stat.h> +#include <windows.h> + +#define _WIN32_WINNT_NT35 MAKEWORD(50, 3) /* really decimal, not hex */ + +#define type(mode) (S_ISDIR(mode) ? "DIR" : S_ISFIFO(mode) ? "FIFO" : S_ISCHR(mode) ? "CHR" : S_ISBLK(mode) ? "BLK" : S_ISREG(mode) ? "REG" : "UNKN") int main(int argc, char *argv[]) { + char windows_dir_path[MAX_PATH + 1]; + char buffer[sizeof(windows_dir_path) + 128]; struct stat st; struct stat64 st64; + DWORD raw_ver; + unsigned ver; + unsigned type; + if (0 == stat(argv[0], &st)) printf("mode = %x\n", st.st_mode); if (0 == stat64(argv[0], &st64)) printf("mode = %x\n", st64.st_mode); + + assert(stat("nul", &st) == 0); + printf("nul: %s\n", type(st.st_mode)); + assert(S_ISCHR(st.st_mode)); + + assert(stat("NUL", &st) == 0); + printf("NUL: %s\n", type(st.st_mode)); + assert(S_ISCHR(st.st_mode)); + + if (!GetWindowsDirectoryA(windows_dir_path, MAX_PATH)) + return 77; + + raw_ver = GetVersion(); + ver = ((raw_ver & 0xff) << 8) | ((raw_ver & 0xff00) >> 8); + type = (raw_ver >> 30) ^ 2; + + if (type != VER_PLATFORM_WIN32_NT) + return 77; + + snprintf(buffer, sizeof(buffer), "\\\\.\\%s", windows_dir_path); + assert(stat(buffer, &st) == 0); + printf("%s: %s\n", buffer, type(st.st_mode)); + assert(S_ISDIR(st.st_mode)); + + /* WinAPI namespace \\?\ is supported since Windows NT 3.5 */ + if (ver < _WIN32_WINNT_NT35) + return 77; + + snprintf(buffer, sizeof(buffer), "\\\\?\\%s", windows_dir_path); + assert(stat(buffer, &st) == 0); + printf("%s: %s\n", buffer, type(st.st_mode)); + assert(S_ISDIR(st.st_mode)); + + /* NT \??\Global and NT \??\GLOBALROOT is available since Windows 2000 */ + if (ver < _WIN32_WINNT_WIN2K) + return 0; + + snprintf(buffer, sizeof(buffer), "\\\\.\\Global\\%s", windows_dir_path); + assert(stat(buffer, &st) == 0); + printf("%s: %s\n", buffer, type(st.st_mode)); + assert(S_ISDIR(st.st_mode)); + + snprintf(buffer, sizeof(buffer), "\\\\?\\Global\\%s", windows_dir_path); + assert(stat(buffer, &st) == 0); + printf("%s: %s\n", buffer, type(st.st_mode)); + assert(S_ISDIR(st.st_mode)); + + snprintf(buffer, sizeof(buffer), "\\\\.\\GLOBALROOT\\??\\%s", windows_dir_path); + assert(stat(buffer, &st) == 0); + printf("%s: %s\n", buffer, type(st.st_mode)); + assert(S_ISDIR(st.st_mode)); + + snprintf(buffer, sizeof(buffer), "\\\\?\\GLOBALROOT\\??\\%s", windows_dir_path); + assert(stat(buffer, &st) == 0); + printf("%s: %s\n", buffer, type(st.st_mode)); + assert(S_ISDIR(st.st_mode)); + + snprintf(buffer, sizeof(buffer), "\\\\.\\GLOBALROOT\\DosDevices\\%s", windows_dir_path); + assert(stat(buffer, &st) == 0); + printf("%s: %s\n", buffer, type(st.st_mode)); + assert(S_ISDIR(st.st_mode)); + + snprintf(buffer, sizeof(buffer), "\\\\?\\GLOBALROOT\\DosDevices\\%s", windows_dir_path); + assert(stat(buffer, &st) == 0); + printf("%s: %s\n", buffer, type(st.st_mode)); + assert(S_ISDIR(st.st_mode)); + + /* NT \GLOBAL?? is available since Windows XP */ + if (ver < _WIN32_WINNT_WINXP) + return 0; + + snprintf(buffer, sizeof(buffer), "\\\\.\\GLOBALROOT\\GLOBAL??\\%s", windows_dir_path); + assert(stat(buffer, &st) == 0); + printf("%s: %s\n", buffer, type(st.st_mode)); + assert(S_ISDIR(st.st_mode)); + + snprintf(buffer, sizeof(buffer), "\\\\?\\GLOBALROOT\\GLOBAL??\\%s", windows_dir_path); + assert(stat(buffer, &st) == 0); + printf("%s: %s\n", buffer, type(st.st_mode)); + assert(S_ISDIR(st.st_mode)); + + snprintf(buffer, sizeof(buffer), "\\\\.\\GLOBALROOT\\GLOBAL??\\GLOBALROOT\\??\\%s", windows_dir_path); + assert(stat(buffer, &st) == 0); + printf("%s: %s\n", buffer, type(st.st_mode)); + assert(S_ISDIR(st.st_mode)); + + snprintf(buffer, sizeof(buffer), "\\\\?\\GLOBALROOT\\GLOBAL??\\GLOBALROOT\\??\\%s", windows_dir_path); + assert(stat(buffer, &st) == 0); + printf("%s: %s\n", buffer, type(st.st_mode)); + assert(S_ISDIR(st.st_mode)); + return 0; }