diff --git a/configure b/configure index 19a3cd09a0..071d050ef0 100755 --- a/configure +++ b/configure @@ -16137,6 +16137,12 @@ esac ;; esac + case " $LIBOBJS " in + *" win32stat.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS win32stat.$ac_objext" + ;; +esac + $as_echo "#define HAVE_SYMLINK 1" >>confdefs.h diff --git a/configure.ac b/configure.ac index 6b9d0487a8..e9ce611d23 100644 --- a/configure.ac +++ b/configure.ac @@ -1807,6 +1807,7 @@ if test "$PORTNAME" = "win32"; then AC_LIBOBJ(win32error) AC_LIBOBJ(win32security) AC_LIBOBJ(win32setlocale) + AC_LIBOBJ(win32stat) AC_DEFINE([HAVE_SYMLINK], 1, [Define to 1 if you have the `symlink' function.]) AC_CHECK_TYPES(MINIDUMP_TYPE, [pgac_minidump_type=yes], [pgac_minidump_type=no], [ diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h index 8b6576b23d..f65f426cdb 100644 --- a/src/include/port/win32_port.h +++ b/src/include/port/win32_port.h @@ -51,7 +51,13 @@ #include #include #undef near -#include /* needed before sys/stat hacking below */ + +/* needed before sys/stat hacking below: */ +#define fstat microsoft_native_fstat +#define stat microsoft_native_stat +#include +#undef fstat +#undef stat /* Must be here to avoid conflicting with prototype in windows.h */ #define mkdir(a,b) mkdir(a) @@ -240,20 +246,34 @@ typedef int pid_t; * Supplement to . * * We must pull in sys/stat.h before this part, else our overrides lose. - */ -#define lstat(path, sb) stat(path, sb) - -/* - * stat() is not guaranteed to set the st_size field on win32, so we - * redefine it to our own implementation that is. * - * Some frontends don't need the size from stat, so if UNSAFE_STAT_OK - * is defined we don't bother with this. + * stat() is not guaranteed to set the st_size field on win32, so we + * redefine it to our own implementation. See src/port/win32stat.c. + * + * The struct stat is 32 bit in MSVC, so we redefine it as a copy of + * struct __stat64. This also fixes the struct size for MINGW builds. */ -#ifndef UNSAFE_STAT_OK -extern int pgwin32_safestat(const char *path, struct stat *buf); -#define stat(a,b) pgwin32_safestat(a,b) -#endif +struct stat /* This should match struct __stat64 */ +{ + _dev_t st_dev; + _ino_t st_ino; + unsigned short st_mode; + short st_nlink; + short st_uid; + short st_gid; + _dev_t st_rdev; + __int64 st_size; + __time64_t st_atime; + __time64_t st_mtime; + __time64_t st_ctime; +}; + +extern int _pgfstat64(int fileno, struct stat *buf); +extern int _pgstat64(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) /* These macros are not provided by older MinGW, nor by MSVC */ #ifndef S_IRUSR diff --git a/src/port/dirmod.c b/src/port/dirmod.c index e22a41c77e..8979f10080 100644 --- a/src/port/dirmod.c +++ b/src/port/dirmod.c @@ -353,55 +353,3 @@ pgwin32_is_junction(const char *path) return ((attr & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT); } #endif /* defined(WIN32) && !defined(__CYGWIN__) */ - - -#if defined(WIN32) && !defined(__CYGWIN__) - -#undef stat - -/* - * The stat() function in win32 is not guaranteed to update the st_size - * field when run. So we define our own version that uses the Win32 API - * to update this field. - */ -int -pgwin32_safestat(const char *path, struct stat *buf) -{ - int r; - WIN32_FILE_ATTRIBUTE_DATA attr; - - r = stat(path, buf); - if (r < 0) - { - if (GetLastError() == ERROR_DELETE_PENDING) - { - /* - * File has been deleted, but is not gone from the filesystem yet. - * This can happen when some process with FILE_SHARE_DELETE has it - * open and it will be fully removed once that handle is closed. - * Meanwhile, we can't open it, so indicate that the file just - * doesn't exist. - */ - errno = ENOENT; - return -1; - } - - return r; - } - - if (!GetFileAttributesEx(path, GetFileExInfoStandard, &attr)) - { - _dosmaperr(GetLastError()); - return -1; - } - - /* - * XXX no support for large files here, but we don't do that in general on - * Win32 yet. - */ - buf->st_size = attr.nFileSizeLow; - - return 0; -} - -#endif diff --git a/src/port/win32stat.c b/src/port/win32stat.c new file mode 100644 index 0000000000..a70df3a590 --- /dev/null +++ b/src/port/win32stat.c @@ -0,0 +1,299 @@ +/*------------------------------------------------------------------------- + * + * win32stat.c + * Replacements for functions using GetFileInformationByHandle + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/port/win32stat.c + * + *------------------------------------------------------------------------- + */ + +#ifdef WIN32 + +#include "c.h" +#include + +/* + * In order to support MinGW and MSVC2013 we use NtQueryInformationFile as an + * alternative for GetFileInformationByHandleEx. It is loaded from the ntdll + * library. + */ +#if _WIN32_WINNT < 0x0600 +#include + +#if !defined(__MINGW32__) && !defined(__MINGW64__) +/* MinGW includes this in , but it is missing in MSVC */ +typedef struct _FILE_STANDARD_INFORMATION +{ + LARGE_INTEGER AllocationSize; + LARGE_INTEGER EndOfFile; + ULONG NumberOfLinks; + BOOLEAN DeletePending; + BOOLEAN Directory; +} FILE_STANDARD_INFORMATION; +#define FileStandardInformation 5 +#endif /* !defined(__MINGW32__) && + * !defined(__MINGW64__) */ + +typedef NTSTATUS(NTAPI * PFN_NTQUERYINFORMATIONFILE) +( + IN HANDLE FileHandle, + OUT PIO_STATUS_BLOCK IoStatusBlock, + OUT PVOID FileInformation, + IN ULONG Length, + IN FILE_INFORMATION_CLASS FileInformationClass +); + +static PFN_NTQUERYINFORMATIONFILE _NtQueryInformationFile = NULL; + +static HMODULE ntdll = NULL; + +/* + * Load DLL file just once regardless of how many functions we load/call in it. + */ +static void +LoadNtdll(void) +{ + if (ntdll != NULL) + return; + ntdll = LoadLibraryEx("ntdll.dll", NULL, 0); +} + +#endif /* _WIN32_WINNT < 0x0600 */ + + +/* + * Convert a FILETIME struct into a 64 bit time_t. + */ +static __time64_t +filetime_to_time(const FILETIME *ft) +{ + ULARGE_INTEGER unified_ft = {0}; + static const uint64 EpochShift = UINT64CONST(116444736000000000); + + unified_ft.LowPart = ft->dwLowDateTime; + unified_ft.HighPart = ft->dwHighDateTime; + + if (unified_ft.QuadPart < EpochShift) + return -1; + + unified_ft.QuadPart -= EpochShift; + unified_ft.QuadPart /= 10 * 1000 * 1000; + + return unified_ft.QuadPart; +} + +/* + * Convert WIN32 file attributes to a Unix-style mode. + * + * Only owner permissions are set. + */ +static unsigned short +fileattr_to_unixmode(int attr) +{ + unsigned short uxmode = 0; + + uxmode |= (unsigned short) ((attr & FILE_ATTRIBUTE_DIRECTORY) ? + (_S_IFDIR) : (_S_IFREG)); + + uxmode |= (unsigned short) (attr & FILE_ATTRIBUTE_READONLY) ? + (_S_IREAD) : (_S_IREAD | _S_IWRITE); + + /* there is no need to simulate _S_IEXEC using CMD's PATHEXT extensions */ + uxmode |= _S_IEXEC; + + return uxmode; +} + +/* + * Convert WIN32 file information (from a HANDLE) to a struct stat. + */ +static int +fileinfo_to_stat(HANDLE hFile, struct stat *buf) +{ + BY_HANDLE_FILE_INFORMATION fiData; + + memset(buf, 0, sizeof(*buf)); + + /* + * GetFileInformationByHandle minimum supported version: Windows XP and + * Windows Server 2003, so it exists everywhere we care about. + */ + if (!GetFileInformationByHandle(hFile, &fiData)) + { + _dosmaperr(GetLastError()); + return -1; + } + + if (fiData.ftLastWriteTime.dwLowDateTime || + fiData.ftLastWriteTime.dwHighDateTime) + buf->st_mtime = filetime_to_time(&fiData.ftLastWriteTime); + + if (fiData.ftLastAccessTime.dwLowDateTime || + fiData.ftLastAccessTime.dwHighDateTime) + buf->st_atime = filetime_to_time(&fiData.ftLastAccessTime); + else + buf->st_atime = buf->st_mtime; + + if (fiData.ftCreationTime.dwLowDateTime || + fiData.ftCreationTime.dwHighDateTime) + buf->st_ctime = filetime_to_time(&fiData.ftCreationTime); + else + buf->st_ctime = buf->st_mtime; + + buf->st_mode = fileattr_to_unixmode(fiData.dwFileAttributes); + buf->st_nlink = fiData.nNumberOfLinks; + + buf->st_size = (((uint64) fiData.nFileSizeHigh) << 32) | + (uint64) fiData.nFileSizeLow; + + return 0; +} + +/* + * Windows implementation of stat(). + * + * This currently also implements lstat(), though perhaps that should change. + */ +int +_pgstat64(const char *name, struct stat *buf) +{ + /* + * We must use a handle so lstat() returns the information of the target + * file. To have a reliable test for ERROR_DELETE_PENDING, we use + * NtQueryInformationFile from Windows 2000 or + * GetFileInformationByHandleEx from Server 2008 / Vista. + */ + SECURITY_ATTRIBUTES sa; + HANDLE hFile; + int ret; +#if _WIN32_WINNT < 0x0600 + IO_STATUS_BLOCK ioStatus; + FILE_STANDARD_INFORMATION standardInfo; +#else + FILE_STANDARD_INFO standardInfo; +#endif + + if (name == NULL || buf == NULL) + { + errno = EINVAL; + return -1; + } + + /* fast not-exists check */ + if (GetFileAttributes(name) == INVALID_FILE_ATTRIBUTES) + { + _dosmaperr(GetLastError()); + return -1; + } + + /* get a file handle as lightweight as we can */ + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = NULL; + hFile = CreateFile(name, + GENERIC_READ, + (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), + &sa, + OPEN_EXISTING, + (FILE_FLAG_NO_BUFFERING | FILE_FLAG_BACKUP_SEMANTICS | + FILE_FLAG_OVERLAPPED), + NULL); + if (hFile == INVALID_HANDLE_VALUE) + { + CloseHandle(hFile); + errno = ENOENT; + return -1; + } + + memset(&standardInfo, 0, sizeof(standardInfo)); + +#if _WIN32_WINNT < 0x0600 + if (_NtQueryInformationFile == NULL) + { + /* First time through: load ntdll.dll and find NtQueryInformationFile */ + LoadNtdll(); + if (ntdll == NULL) + { + _dosmaperr(GetLastError()); + CloseHandle(hFile); + return -1; + } + + _NtQueryInformationFile = (PFN_NTQUERYINFORMATIONFILE) + GetProcAddress(ntdll, "NtQueryInformationFile"); + if (_NtQueryInformationFile == NULL) + { + _dosmaperr(GetLastError()); + CloseHandle(hFile); + return -1; + } + } + + if (!NT_SUCCESS(_NtQueryInformationFile(hFile, &ioStatus, &standardInfo, + sizeof(standardInfo), + FileStandardInformation))) + { + _dosmaperr(GetLastError()); + CloseHandle(hFile); + return -1; + } +#else + if (!GetFileInformationByHandleEx(hFile, FileStandardInfo, &standardInfo, + sizeof(standardInfo))) + { + _dosmaperr(GetLastError()); + CloseHandle(hFile); + return -1; + } +#endif /* _WIN32_WINNT < 0x0600 */ + + if (standardInfo.DeletePending) + { + /* + * File has been deleted, but is not gone from the filesystem yet. + * This can happen when some process with FILE_SHARE_DELETE has it + * open, and it will be fully removed once that handle is closed. + * Meanwhile, we can't open it, so indicate that the file just doesn't + * exist. + */ + CloseHandle(hFile); + errno = ENOENT; + return -1; + } + + /* At last we can invoke fileinfo_to_stat */ + ret = fileinfo_to_stat(hFile, buf); + + CloseHandle(hFile); + return ret; +} + +/* + * Windows implementation of fstat(). + */ +int +_pgfstat64(int fileno, struct stat *buf) +{ + HANDLE hFile = (HANDLE) _get_osfhandle(fileno); + + if (hFile == INVALID_HANDLE_VALUE || buf == NULL) + { + errno = EINVAL; + return -1; + } + + /* + * Since we already have a file handle there is no need to check for + * ERROR_DELETE_PENDING. + */ + + return fileinfo_to_stat(hFile, buf); +} + +#endif /* WIN32 */ diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 89e1b39036..90594bd41b 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -103,7 +103,7 @@ sub mkvcbuild pg_strong_random.c pgcheckdir.c pgmkdirp.c pgsleep.c pgstrcasecmp.c pqsignal.c mkdtemp.c qsort.c qsort_arg.c quotes.c system.c strerror.c tar.c thread.c - win32env.c win32error.c win32security.c win32setlocale.c); + win32env.c win32error.c win32security.c win32setlocale.c win32stat.c); push(@pgportfiles, 'strtof.c') if ($vsVersion < '14.00');