From 5e64700facc43fd937349bc8d843b15abb8da5cc Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Thu, 28 Jul 2022 15:19:38 +1200 Subject: [PATCH v3 2/4] Provide lstat() for Windows. Junction points will be reported with S_ISLNK(x.st_mode), like in POSIX. stat() will follow symlinks, like in POSIX (but only one level before it gives up, unlike in POSIX). Discussion: https://postgr.es/m/CA%2BhUKGLfOOeyZpm5ByVcAt7x5Pn-%3DxGRNCvgiUPVVzjFLtnY0w%40mail.gmail.com --- src/include/port/win32_port.h | 18 +++++++- src/port/win32stat.c | 79 +++++++++++++++++++++++++++++++++-- 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h index 4de5bf3bf6..b8cf2e1480 100644 --- a/src/include/port/win32_port.h +++ b/src/include/port/win32_port.h @@ -286,10 +286,11 @@ struct stat /* This should match struct __stat64 */ extern int _pgfstat64(int fileno, struct stat *buf); extern int _pgstat64(const char *name, struct stat *buf); +extern int _pglstat64(const char *name, struct stat *buf); #define fstat(fileno, sb) _pgfstat64(fileno, sb) #define stat(path, sb) _pgstat64(path, sb) -#define lstat(path, sb) _pgstat64(path, sb) +#define lstat(path, sb) _pglstat64(path, sb) /* These macros are not provided by older MinGW, nor by MSVC */ #ifndef S_IRUSR @@ -335,6 +336,21 @@ extern int _pgstat64(const char *name, struct stat *buf); #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #endif +/* + * In order for lstat() to be able to report junction points as symlinks, we + * need to hijack a bit in st_mode, since neither MSVC nor MinGW provides + * S_ISLNK and there aren't any spare bits. We'll steal the one for character + * devices, because we don't otherwise make use of those. + */ +#ifdef S_ISLNK +#error "S_ISLNK is already defined" +#endif +#ifdef S_IFLNK +#error "S_IFLNK is already defined" +#endif +#define S_IFLNK S_IFCHR +#define S_ISLNK(m) (((m) & S_IFLNK) == S_IFLNK) + /* * Supplement to . * This is the same value as _O_NOINHERIT in the MS header file. This is diff --git a/src/port/win32stat.c b/src/port/win32stat.c index e03ed5f35c..686c510fed 100644 --- a/src/port/win32stat.c +++ b/src/port/win32stat.c @@ -15,7 +15,11 @@ #ifdef WIN32 +#define UMDF_USING_NTSTATUS + #include "c.h" +#include "port/win32ntdll.h" + #include /* @@ -107,12 +111,10 @@ fileinfo_to_stat(HANDLE hFile, struct stat *buf) } /* - * Windows implementation of stat(). - * - * This currently also implements lstat(), though perhaps that should change. + * Windows implementation of lstat(). */ int -_pgstat64(const char *name, struct stat *buf) +_pglstat64(const char *name, struct stat *buf) { /* * Our open wrapper will report STATUS_DELETE_PENDING as ENOENT. We @@ -129,10 +131,79 @@ _pgstat64(const char *name, struct stat *buf) ret = fileinfo_to_stat(hFile, buf); + /* + * Unfortunately it's not possible for fileinfo_to_stat() to see a + * FILE_ATTRIBUTE_REPARSE_POINT flag with just a handle, so at this point + * junction points appear as directories. Ask for the full attributes + * while we still have the handle open. Someone else can unlink the file + * while we have it open, but they can't create another file with the same + * name. + */ + if (ret == 0 && S_ISDIR(buf->st_mode)) + { + DWORD attr; + + attr = GetFileAttributes(name); + if (attr == INVALID_FILE_ATTRIBUTES) + { + DWORD err; + + /* If it's been unlinked since we opened it, report as ENOENT. */ + err = GetLastError(); + if (err == ERROR_ACCESS_DENIED && + pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING) + errno = ENOENT; + else + _dosmaperr(err); + + ret = -1; + } + else if (attr & FILE_ATTRIBUTE_REPARSE_POINT) + { + buf->st_mode &= ~S_IFDIR; + buf->st_mode |= S_IFLNK; + } + } + CloseHandle(hFile); return ret; } +/* + * Windows implementation of stat(). + */ +int +_pgstat64(const char *name, struct stat *buf) +{ + int ret; + + ret = _pglstat64(name, buf); + + /* Do we need to follow a symlink (junction point)? */ + if (ret == 0 && S_ISLNK(buf->st_mode)) + { + char next[MAXPGPATH]; + + if (readlink(name, next, sizeof(next)) < 0) + return -1; + + ret = _pglstat64(next, buf); + if (ret == 0 && S_ISLNK(buf->st_mode)) + { + /* + * We're only prepared to go one hop, because we only expect to + * deal with the simple cases that we create. The error for too + * many symlinks is supposed to be ELOOP, but Windows hasn't got + * it. + */ + errno = EIO; + return -1; + } + } + + return ret; +} + /* * Windows implementation of fstat(). */ -- 2.37.1