#include "c.h"
#include <windows.h>
+/*
+ * 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 <winternl.h>
+
+#if !defined(__MINGW32__) && !defined(__MINGW64__)
+/* MinGW includes this in <winternl.h>, 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.
*/
{
/*
* We must use a handle so lstat() returns the information of the target
- * file. To have a reliable test for ERROR_DELETE_PENDING, this uses a
- * method similar to open() with a loop using stat() and some waits when
- * facing ERROR_ACCESS_DENIED.
+ * 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;
- int loops = 0;
+#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)
{
- DWORD err = GetLastError();
-
- if (err != ERROR_ACCESS_DENIED)
- {
- _dosmaperr(err);
- return -1;
- }
+ _dosmaperr(GetLastError());
+ return -1;
}
/* get a file handle as lightweight as we can */
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
- while ((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)) == INVALID_HANDLE_VALUE)
+ 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)
{
DWORD err = GetLastError();
- /*
- * ERROR_ACCESS_DENIED is returned if the file is deleted but not yet
- * gone (Windows NT status code is STATUS_DELETE_PENDING). In that
- * case we want to wait a bit and try again, giving up after 1 second
- * (since this condition should never persist very long). However,
- * there are other commonly-hit cases that return ERROR_ACCESS_DENIED,
- * so care is needed. In particular that happens if we try to open a
- * directory, or of course if there's an actual file-permissions
- * problem. To distinguish these cases, try a stat(). In the
- * delete-pending case, it will either also get STATUS_DELETE_PENDING,
- * or it will see the file as gone and fail with ENOENT. In other
- * cases it will usually succeed. The only somewhat-likely case where
- * this coding will uselessly wait is if there's a permissions problem
- * with a containing directory, which we hope will never happen in any
- * performance-critical code paths.
- */
- if (err == ERROR_ACCESS_DENIED)
+ CloseHandle(hFile);
+ _dosmaperr(err);
+ 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)
+ {
+ DWORD err = GetLastError();
+
+ CloseHandle(hFile);
+ _dosmaperr(err);
+ return -1;
+ }
+
+ _NtQueryInformationFile = (PFN_NTQUERYINFORMATIONFILE) (pg_funcptr_t)
+ GetProcAddress(ntdll, "NtQueryInformationFile");
+ if (_NtQueryInformationFile == NULL)
{
- if (loops < 10)
- {
- struct microsoft_native_stat st;
-
- if (microsoft_native_stat(name, &st) != 0)
- {
- pg_usleep(100000);
- loops++;
- continue;
- }
- }
+ DWORD err = GetLastError();
+
+ CloseHandle(hFile);
+ _dosmaperr(err);
+ return -1;
}
+ }
+ if (!NT_SUCCESS(_NtQueryInformationFile(hFile, &ioStatus, &standardInfo,
+ sizeof(standardInfo),
+ FileStandardInformation)))
+ {
+ DWORD err = GetLastError();
+
+ CloseHandle(hFile);
+ _dosmaperr(err);
+ return -1;
+ }
+#else
+ if (!GetFileInformationByHandleEx(hFile, FileStandardInfo, &standardInfo,
+ sizeof(standardInfo)))
+ {
+ DWORD err = GetLastError();
+
+ CloseHandle(hFile);
_dosmaperr(err);
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);