diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h index 0eaa97561a0..c27b34de5bf 100644 --- a/src/include/port/win32_port.h +++ b/src/include/port/win32_port.h @@ -278,10 +278,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 @@ -327,6 +328,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 e03ed5f35ca..26443293d78 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,110 @@ _pgstat64(const char *name, struct stat *buf) ret = fileinfo_to_stat(hFile, buf); + /* + * Junction points appear as directories to fileinfo_to_stat(), so we'll + * need to do a bit more work to distinguish them. + */ + if (ret == 0 && S_ISDIR(buf->st_mode)) + { + char next[MAXPGPATH]; + ssize_t size; + + /* + * POSIX says we need to put the length of the target path into + * st_size. Use readlink() to get it, or learn that this is not a + * junction point. + */ + size = readlink(name, next, sizeof(next)); + if (size < 0) + { + if (errno == EACCES && + pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING) + { + /* Unlinked underneath us. */ + errno = ENOENT; + ret = -1; + } + else if (errno == EINVAL) + { + /* It's not a junction point, nothing to do. */ + } + else + { + /* Some other failure. */ + ret = -1; + } + } + else + { + /* It's a junction point, so report it as a symlink. */ + buf->st_mode &= ~S_IFDIR; + buf->st_mode |= S_IFLNK; + buf->st_size = size; + } + } + 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]; + ssize_t size; + + /* + * _pglstat64() already called readlink() once to be able to fill in + * st_size, and now we need to do it again to get the path to follow. + * That could be optimized, but stat() on symlinks is probably rare + * and this way is simple. + */ + size = readlink(name, next, sizeof(next)); + if (size < 0) + { + if (errno == EACCES && + pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING) + { + /* Unlinked underneath us. */ + errno = ENOENT; + } + return -1; + } + if (size >= sizeof(next)) + { + errno = ENAMETOOLONG; + return -1; + } + next[size] = 0; + + 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(). */