Skip to content

Commit e2f0f8e

Browse files
committed
Check for STATUS_DELETE_PENDING on Windows.
1. Update our open() wrapper to check for NT's STATUS_DELETE_PENDING and translate it to Unix-like errors. This is done with RtlGetLastNtStatus(), which is dynamically loaded from ntdll. A new file win32ntdll.c centralizes lookup of NT functions, in case we decide to add more in the future. 2. Remove non-working code that was trying to do something similar for stat(), and just reuse the open() wrapper code. As a side effect, stat() also gains resilience against "sharing violation" errors. 3. Since stat() is used very early in process startup, remove the requirement that the Win32 signal event has been created before pgwin32_open_handle() is reached. Instead, teach pg_usleep() to fall back to a non-interruptible sleep if reached before the signal event is available. This could be back-patched, but for now it's in master only. The problem has apparently been with us for a long time and generated only a few complaints. Proposed patches trigger it more often, which led to this investigation and fix. Reviewed-by: Andres Freund <andres@anarazel.de> Reviewed-by: Alexander Lakhin <exclusion@gmail.com> Reviewed-by: Juan José Santamaría Flecha <juanjo.santamaria@gmail.com> Discussion: https://postgr.es/m/CA%2BhUKGJz_pZTF9mckn6XgSv69%2BjGwdgLkxZ6b3NWGLBCVjqUZA%40mail.gmail.com
1 parent 5d08137 commit e2f0f8e

File tree

9 files changed

+178
-209
lines changed

9 files changed

+178
-209
lines changed

configure

+6
Original file line numberDiff line numberDiff line change
@@ -16738,6 +16738,12 @@ esac
1673816738
;;
1673916739
esac
1674016740

16741+
case " $LIBOBJS " in
16742+
*" win32ntdll.$ac_objext "* ) ;;
16743+
*) LIBOBJS="$LIBOBJS win32ntdll.$ac_objext"
16744+
;;
16745+
esac
16746+
1674116747
case " $LIBOBJS " in
1674216748
*" win32security.$ac_objext "* ) ;;
1674316749
*) LIBOBJS="$LIBOBJS win32security.$ac_objext"

configure.ac

+1
Original file line numberDiff line numberDiff line change
@@ -1932,6 +1932,7 @@ if test "$PORTNAME" = "win32"; then
19321932
AC_LIBOBJ(system)
19331933
AC_LIBOBJ(win32env)
19341934
AC_LIBOBJ(win32error)
1935+
AC_LIBOBJ(win32ntdll)
19351936
AC_LIBOBJ(win32security)
19361937
AC_LIBOBJ(win32setlocale)
19371938
AC_LIBOBJ(win32stat)

src/backend/port/win32/signal.c

+11-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,17 @@ static BOOL WINAPI pg_console_handler(DWORD dwCtrlType);
5252
void
5353
pg_usleep(long microsec)
5454
{
55-
Assert(pgwin32_signal_event != NULL);
55+
if (unlikely(pgwin32_signal_event == NULL))
56+
{
57+
/*
58+
* If we're reached by pgwin32_open_handle() early in startup before
59+
* the signal event is set up, just fall back to a regular
60+
* non-interruptible sleep.
61+
*/
62+
SleepEx((microsec < 500 ? 1 : (microsec + 500) / 1000), FALSE);
63+
return;
64+
}
65+
5666
if (WaitForSingleObject(pgwin32_signal_event,
5767
(microsec < 500 ? 1 : (microsec + 500) / 1000))
5868
== WAIT_OBJECT_0)

src/include/port.h

+1
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ extern bool rmtree(const char *path, bool rmtopdir);
296296
* passing of other special options.
297297
*/
298298
#define O_DIRECT 0x80000000
299+
extern HANDLE pgwin32_open_handle(const char *, int, bool);
299300
extern int pgwin32_open(const char *, int,...);
300301
extern FILE *pgwin32_fopen(const char *, const char *);
301302
#define open(a,b,c) pgwin32_open(a,b,c)

src/include/port/win32ntdll.h

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*-------------------------------------------------------------------------
2+
*
3+
* win32ntdll.h
4+
* Dynamically loaded Windows NT functions.
5+
*
6+
* Portions Copyright (c) 2021, PostgreSQL Global Development Group
7+
* Portions Copyright (c) 1994, Regents of the University of California
8+
*
9+
* src/include/port/win32ntdll.h
10+
*
11+
*-------------------------------------------------------------------------
12+
*/
13+
14+
/*
15+
* Because this includes NT headers that normally conflict with Win32 headers,
16+
* any translation unit that includes it should #define UMDF_USING_NTSTATUS
17+
* before including <windows.h>.
18+
*/
19+
20+
#include <ntstatus.h>
21+
#include <winternl.h>
22+
23+
typedef NTSTATUS (__stdcall *RtlGetLastNtStatus_t) (void);
24+
25+
extern RtlGetLastNtStatus_t pg_RtlGetLastNtStatus;
26+
27+
extern int initialize_ntdll(void);

src/port/open.c

+56-48
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,20 @@
1313

1414
#ifdef WIN32
1515

16+
#define UMDF_USING_NTSTATUS
17+
1618
#ifndef FRONTEND
1719
#include "postgres.h"
1820
#else
1921
#include "postgres_fe.h"
2022
#endif
2123

24+
#include "port/win32ntdll.h"
25+
2226
#include <fcntl.h>
2327
#include <assert.h>
2428
#include <sys/stat.h>
2529

26-
2730
static int
2831
openFlagsToCreateFileFlags(int openFlags)
2932
{
@@ -56,38 +59,25 @@ openFlagsToCreateFileFlags(int openFlags)
5659
}
5760

5861
/*
59-
* - file attribute setting, based on fileMode?
62+
* Internal function used by pgwin32_open() and _pgstat64(). When
63+
* backup_semantics is true, directories may be opened (for limited uses). On
64+
* failure, INVALID_HANDLE_VALUE is returned and errno is set.
6065
*/
61-
int
62-
pgwin32_open(const char *fileName, int fileFlags,...)
66+
HANDLE
67+
pgwin32_open_handle(const char *fileName, int fileFlags, bool backup_semantics)
6368
{
64-
int fd;
65-
HANDLE h = INVALID_HANDLE_VALUE;
69+
HANDLE h;
6670
SECURITY_ATTRIBUTES sa;
6771
int loops = 0;
6872

73+
if (initialize_ntdll() < 0)
74+
return INVALID_HANDLE_VALUE;
75+
6976
/* Check that we can handle the request */
7077
assert((fileFlags & ((O_RDONLY | O_WRONLY | O_RDWR) | O_APPEND |
7178
(O_RANDOM | O_SEQUENTIAL | O_TEMPORARY) |
7279
_O_SHORT_LIVED | O_DSYNC | O_DIRECT |
7380
(O_CREAT | O_TRUNC | O_EXCL) | (O_TEXT | O_BINARY))) == fileFlags);
74-
#ifndef FRONTEND
75-
Assert(pgwin32_signal_event != NULL); /* small chance of pg_usleep() */
76-
#endif
77-
78-
#ifdef FRONTEND
79-
80-
/*
81-
* Since PostgreSQL 12, those concurrent-safe versions of open() and
82-
* fopen() can be used by frontends, having as side-effect to switch the
83-
* file-translation mode from O_TEXT to O_BINARY if none is specified.
84-
* Caller may want to enforce the binary or text mode, but if nothing is
85-
* defined make sure that the default mode maps with what versions older
86-
* than 12 have been doing.
87-
*/
88-
if ((fileFlags & O_BINARY) == 0)
89-
fileFlags |= O_TEXT;
90-
#endif
9181

9282
sa.nLength = sizeof(sa);
9383
sa.bInheritHandle = TRUE;
@@ -102,6 +92,7 @@ pgwin32_open(const char *fileName, int fileFlags,...)
10292
&sa,
10393
openFlagsToCreateFileFlags(fileFlags),
10494
FILE_ATTRIBUTE_NORMAL |
95+
(backup_semantics ? FILE_FLAG_BACKUP_SEMANTICS : 0) |
10596
((fileFlags & O_RANDOM) ? FILE_FLAG_RANDOM_ACCESS : 0) |
10697
((fileFlags & O_SEQUENTIAL) ? FILE_FLAG_SEQUENTIAL_SCAN : 0) |
10798
((fileFlags & _O_SHORT_LIVED) ? FILE_ATTRIBUTE_TEMPORARY : 0) |
@@ -140,38 +131,55 @@ pgwin32_open(const char *fileName, int fileFlags,...)
140131
/*
141132
* ERROR_ACCESS_DENIED is returned if the file is deleted but not yet
142133
* gone (Windows NT status code is STATUS_DELETE_PENDING). In that
143-
* case we want to wait a bit and try again, giving up after 1 second
144-
* (since this condition should never persist very long). However,
145-
* there are other commonly-hit cases that return ERROR_ACCESS_DENIED,
146-
* so care is needed. In particular that happens if we try to open a
147-
* directory, or of course if there's an actual file-permissions
148-
* problem. To distinguish these cases, try a stat(). In the
149-
* delete-pending case, it will either also get STATUS_DELETE_PENDING,
150-
* or it will see the file as gone and fail with ENOENT. In other
151-
* cases it will usually succeed. The only somewhat-likely case where
152-
* this coding will uselessly wait is if there's a permissions problem
153-
* with a containing directory, which we hope will never happen in any
154-
* performance-critical code paths.
134+
* case, we'd better ask for the NT status too so we can translate it
135+
* to a more Unix-like error. We hope that nothing clobbers the NT
136+
* status in between the internal NtCreateFile() call and CreateFile()
137+
* returning.
138+
*
139+
* If there's no O_CREAT flag, then we'll pretend the file is
140+
* invisible. With O_CREAT, we have no choice but to report that
141+
* there's a file in the way (which wouldn't happen on Unix).
155142
*/
156-
if (err == ERROR_ACCESS_DENIED)
143+
if (err == ERROR_ACCESS_DENIED &&
144+
pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING)
157145
{
158-
if (loops < 10)
159-
{
160-
struct stat st;
161-
162-
if (stat(fileName, &st) != 0)
163-
{
164-
pg_usleep(100000);
165-
loops++;
166-
continue;
167-
}
168-
}
146+
if (fileFlags & O_CREAT)
147+
err = ERROR_FILE_EXISTS;
148+
else
149+
err = ERROR_FILE_NOT_FOUND;
169150
}
170151

171152
_dosmaperr(err);
172-
return -1;
153+
return INVALID_HANDLE_VALUE;
173154
}
174155

156+
return h;
157+
}
158+
159+
int
160+
pgwin32_open(const char *fileName, int fileFlags,...)
161+
{
162+
HANDLE h;
163+
int fd;
164+
165+
h = pgwin32_open_handle(fileName, fileFlags, false);
166+
if (h == INVALID_HANDLE_VALUE)
167+
return -1;
168+
169+
#ifdef FRONTEND
170+
171+
/*
172+
* Since PostgreSQL 12, those concurrent-safe versions of open() and
173+
* fopen() can be used by frontends, having as side-effect to switch the
174+
* file-translation mode from O_TEXT to O_BINARY if none is specified.
175+
* Caller may want to enforce the binary or text mode, but if nothing is
176+
* defined make sure that the default mode maps with what versions older
177+
* than 12 have been doing.
178+
*/
179+
if ((fileFlags & O_BINARY) == 0)
180+
fileFlags |= O_TEXT;
181+
#endif
182+
175183
/* _open_osfhandle will, on error, set errno accordingly */
176184
if ((fd = _open_osfhandle((intptr_t) h, fileFlags & O_APPEND)) < 0)
177185
CloseHandle(h); /* will not affect errno */

src/port/win32ntdll.c

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*-------------------------------------------------------------------------
2+
*
3+
* win32ntdll.c
4+
* Dynamically loaded Windows NT functions.
5+
*
6+
* Portions Copyright (c) 2021, PostgreSQL Global Development Group
7+
* Portions Copyright (c) 1994, Regents of the University of California
8+
*
9+
*
10+
* IDENTIFICATION
11+
* src/port/win32ntdll.c
12+
*
13+
*-------------------------------------------------------------------------
14+
*/
15+
16+
#define UMDF_USING_NTSTATUS
17+
18+
#include "c.h"
19+
20+
#include "port/win32ntdll.h"
21+
22+
RtlGetLastNtStatus_t pg_RtlGetLastNtStatus;
23+
24+
typedef struct NtDllRoutine
25+
{
26+
const char *name;
27+
pg_funcptr_t *address;
28+
} NtDllRoutine;
29+
30+
static const NtDllRoutine routines[] = {
31+
{"RtlGetLastNtStatus", (pg_funcptr_t *) &pg_RtlGetLastNtStatus}
32+
};
33+
34+
static bool initialized;
35+
36+
int
37+
initialize_ntdll(void)
38+
{
39+
HMODULE module;
40+
41+
if (initialized)
42+
return 0;
43+
44+
if (!(module = LoadLibraryEx("ntdll.dll", NULL, 0)))
45+
{
46+
_dosmaperr(GetLastError());
47+
return -1;
48+
}
49+
50+
for (int i = 0; i < lengthof(routines); ++i)
51+
{
52+
pg_funcptr_t address;
53+
54+
address = (pg_funcptr_t) GetProcAddress(module, routines[i].name);
55+
if (!address)
56+
{
57+
_dosmaperr(GetLastError());
58+
FreeLibrary(module);
59+
60+
return -1;
61+
}
62+
63+
*(pg_funcptr_t *) routines[i].address = address;
64+
}
65+
66+
initialized = true;
67+
68+
return 0;
69+
}

0 commit comments

Comments
 (0)