Provide a better error message for misplaced dispatch options.
[pgsql.git] / src / port / dirmod.c
blobf98d5a7bf2864d44daae803f2974be0910f2f4c7
1 /*-------------------------------------------------------------------------
3 * dirmod.c
4 * directory handling functions
6 * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
9 * This includes replacement versions of functions that work on
10 * Windows.
12 * IDENTIFICATION
13 * src/port/dirmod.c
15 *-------------------------------------------------------------------------
18 #ifndef FRONTEND
19 #include "postgres.h"
20 #else
21 #include "postgres_fe.h"
22 #endif
24 /* Don't modify declarations in system headers */
25 #if defined(WIN32) || defined(__CYGWIN__)
26 #undef rename
27 #undef unlink
28 #endif
30 #include <unistd.h>
31 #include <sys/stat.h>
33 #if defined(WIN32) || defined(__CYGWIN__)
34 #ifndef __CYGWIN__
35 #include <winioctl.h>
36 #else
37 #include <windows.h>
38 #include <w32api/winioctl.h>
39 #endif
40 #endif
42 #if defined(WIN32) && !defined(__CYGWIN__)
43 #include "port/win32ntdll.h"
44 #endif
46 #if defined(WIN32) || defined(__CYGWIN__)
49 * pgrename
51 int
52 pgrename(const char *from, const char *to)
54 int loops = 0;
57 * We need to loop because even though PostgreSQL uses flags that allow
58 * rename while the file is open, other applications might have the file
59 * open without those flags. However, we won't wait indefinitely for
60 * someone else to close the file, as the caller might be holding locks
61 * and blocking other backends.
63 #if defined(WIN32) && !defined(__CYGWIN__)
64 while (!MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING))
65 #else
66 while (rename(from, to) < 0)
67 #endif
69 #if defined(WIN32) && !defined(__CYGWIN__)
70 DWORD err = GetLastError();
72 _dosmaperr(err);
75 * Modern NT-based Windows versions return ERROR_SHARING_VIOLATION if
76 * another process has the file open without FILE_SHARE_DELETE.
77 * ERROR_LOCK_VIOLATION has also been seen with some anti-virus
78 * software. This used to check for just ERROR_ACCESS_DENIED, so
79 * presumably you can get that too with some OS versions. We don't
80 * expect real permission errors where we currently use rename().
82 if (err != ERROR_ACCESS_DENIED &&
83 err != ERROR_SHARING_VIOLATION &&
84 err != ERROR_LOCK_VIOLATION)
85 return -1;
86 #else
87 if (errno != EACCES)
88 return -1;
89 #endif
91 if (++loops > 100) /* time out after 10 sec */
92 return -1;
93 pg_usleep(100000); /* us */
95 return 0;
99 * Check if _pglstat64()'s reason for failure was STATUS_DELETE_PENDING.
100 * This doesn't apply to Cygwin, which has its own lstat() that would report
101 * the case as EACCES.
103 static bool
104 lstat_error_was_status_delete_pending(void)
106 if (errno != ENOENT)
107 return false;
108 #if defined(WIN32) && !defined(__CYGWIN__)
109 if (pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING)
110 return true;
111 #endif
112 return false;
116 * pgunlink
119 pgunlink(const char *path)
121 bool is_lnk;
122 int loops = 0;
123 struct stat st;
126 * This function might be called for a regular file or for a junction
127 * point (which we use to emulate symlinks). The latter must be unlinked
128 * with rmdir() on Windows. Before we worry about any of that, let's see
129 * if we can unlink directly, since that's expected to be the most common
130 * case.
132 if (unlink(path) == 0)
133 return 0;
134 if (errno != EACCES)
135 return -1;
138 * EACCES is reported for many reasons including unlink() of a junction
139 * point. Check if that's the case so we can redirect to rmdir().
141 * Note that by checking only once, we can't cope with a path that changes
142 * from regular file to junction point underneath us while we're retrying
143 * due to sharing violations, but that seems unlikely. We could perhaps
144 * prevent that by holding a file handle ourselves across the lstat() and
145 * the retry loop, but that seems like over-engineering for now.
147 * In the special case of a STATUS_DELETE_PENDING error (file already
148 * unlinked, but someone still has it open), we don't want to report
149 * ENOENT to the caller immediately, because rmdir(parent) would probably
150 * fail. We want to wait until the file truly goes away so that simple
151 * recursive directory unlink algorithms work.
153 if (lstat(path, &st) < 0)
155 if (lstat_error_was_status_delete_pending())
156 is_lnk = false;
157 else
158 return -1;
160 else
161 is_lnk = S_ISLNK(st.st_mode);
164 * We need to loop because even though PostgreSQL uses flags that allow
165 * unlink while the file is open, other applications might have the file
166 * open without those flags. However, we won't wait indefinitely for
167 * someone else to close the file, as the caller might be holding locks
168 * and blocking other backends.
170 while ((is_lnk ? rmdir(path) : unlink(path)) < 0)
172 if (errno != EACCES)
173 return -1;
174 if (++loops > 100) /* time out after 10 sec */
175 return -1;
176 pg_usleep(100000); /* us */
178 return 0;
181 /* We undefined these above; now redefine for possible use below */
182 #define rename(from, to) pgrename(from, to)
183 #define unlink(path) pgunlink(path)
184 #endif /* defined(WIN32) || defined(__CYGWIN__) */
187 #if defined(WIN32) && !defined(__CYGWIN__) /* Cygwin has its own symlinks */
190 * pgsymlink support:
192 * This struct is a replacement for REPARSE_DATA_BUFFER which is defined in VC6 winnt.h
193 * but omitted in later SDK functions.
194 * We only need the SymbolicLinkReparseBuffer part of the original struct's union.
196 typedef struct
198 DWORD ReparseTag;
199 WORD ReparseDataLength;
200 WORD Reserved;
201 /* SymbolicLinkReparseBuffer */
202 WORD SubstituteNameOffset;
203 WORD SubstituteNameLength;
204 WORD PrintNameOffset;
205 WORD PrintNameLength;
206 WCHAR PathBuffer[FLEXIBLE_ARRAY_MEMBER];
207 } REPARSE_JUNCTION_DATA_BUFFER;
209 #define REPARSE_JUNCTION_DATA_BUFFER_HEADER_SIZE \
210 FIELD_OFFSET(REPARSE_JUNCTION_DATA_BUFFER, SubstituteNameOffset)
214 * pgsymlink - uses Win32 junction points
216 * For reference: http://www.codeproject.com/KB/winsdk/junctionpoints.aspx
219 pgsymlink(const char *oldpath, const char *newpath)
221 HANDLE dirhandle;
222 DWORD len;
223 char buffer[MAX_PATH * sizeof(WCHAR) + offsetof(REPARSE_JUNCTION_DATA_BUFFER, PathBuffer)];
224 char nativeTarget[MAX_PATH];
225 char *p = nativeTarget;
226 REPARSE_JUNCTION_DATA_BUFFER *reparseBuf = (REPARSE_JUNCTION_DATA_BUFFER *) buffer;
228 CreateDirectory(newpath, 0);
229 dirhandle = CreateFile(newpath, GENERIC_READ | GENERIC_WRITE,
230 0, 0, OPEN_EXISTING,
231 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0);
233 if (dirhandle == INVALID_HANDLE_VALUE)
235 _dosmaperr(GetLastError());
236 return -1;
239 /* make sure we have an unparsed native win32 path */
240 if (memcmp("\\??\\", oldpath, 4) != 0)
241 snprintf(nativeTarget, sizeof(nativeTarget), "\\??\\%s", oldpath);
242 else
243 strlcpy(nativeTarget, oldpath, sizeof(nativeTarget));
245 while ((p = strchr(p, '/')) != NULL)
246 *p++ = '\\';
248 len = strlen(nativeTarget) * sizeof(WCHAR);
249 reparseBuf->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
250 reparseBuf->ReparseDataLength = len + 12;
251 reparseBuf->Reserved = 0;
252 reparseBuf->SubstituteNameOffset = 0;
253 reparseBuf->SubstituteNameLength = len;
254 reparseBuf->PrintNameOffset = len + sizeof(WCHAR);
255 reparseBuf->PrintNameLength = 0;
256 MultiByteToWideChar(CP_ACP, 0, nativeTarget, -1,
257 reparseBuf->PathBuffer, MAX_PATH);
260 * FSCTL_SET_REPARSE_POINT is coded differently depending on SDK version;
261 * we use our own definition
263 if (!DeviceIoControl(dirhandle,
264 CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, FILE_ANY_ACCESS),
265 reparseBuf,
266 reparseBuf->ReparseDataLength + REPARSE_JUNCTION_DATA_BUFFER_HEADER_SIZE,
267 0, 0, &len, 0))
269 LPSTR msg;
270 int save_errno;
272 _dosmaperr(GetLastError());
273 save_errno = errno;
275 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
276 FORMAT_MESSAGE_IGNORE_INSERTS |
277 FORMAT_MESSAGE_FROM_SYSTEM,
278 NULL, GetLastError(),
279 MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT),
280 (LPSTR) &msg, 0, NULL);
281 #ifndef FRONTEND
282 ereport(ERROR,
283 (errcode_for_file_access(),
284 errmsg("could not set junction for \"%s\": %s",
285 nativeTarget, msg)));
286 #else
287 fprintf(stderr, _("could not set junction for \"%s\": %s\n"),
288 nativeTarget, msg);
289 #endif
290 LocalFree(msg);
292 CloseHandle(dirhandle);
293 RemoveDirectory(newpath);
295 errno = save_errno;
297 return -1;
300 CloseHandle(dirhandle);
302 return 0;
306 * pgreadlink - uses Win32 junction points
309 pgreadlink(const char *path, char *buf, size_t size)
311 DWORD attr;
312 HANDLE h;
313 char buffer[MAX_PATH * sizeof(WCHAR) + offsetof(REPARSE_JUNCTION_DATA_BUFFER, PathBuffer)];
314 REPARSE_JUNCTION_DATA_BUFFER *reparseBuf = (REPARSE_JUNCTION_DATA_BUFFER *) buffer;
315 DWORD len;
316 int r;
318 attr = GetFileAttributes(path);
319 if (attr == INVALID_FILE_ATTRIBUTES)
321 _dosmaperr(GetLastError());
322 return -1;
324 if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) == 0)
326 errno = EINVAL;
327 return -1;
330 h = CreateFile(path,
331 GENERIC_READ,
332 FILE_SHARE_READ | FILE_SHARE_WRITE,
333 NULL,
334 OPEN_EXISTING,
335 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
337 if (h == INVALID_HANDLE_VALUE)
339 _dosmaperr(GetLastError());
340 return -1;
343 if (!DeviceIoControl(h,
344 FSCTL_GET_REPARSE_POINT,
345 NULL,
347 (LPVOID) reparseBuf,
348 sizeof(buffer),
349 &len,
350 NULL))
352 LPSTR msg;
354 errno = 0;
355 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
356 FORMAT_MESSAGE_IGNORE_INSERTS |
357 FORMAT_MESSAGE_FROM_SYSTEM,
358 NULL, GetLastError(),
359 MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT),
360 (LPSTR) &msg, 0, NULL);
361 #ifndef FRONTEND
362 ereport(ERROR,
363 (errcode_for_file_access(),
364 errmsg("could not get junction for \"%s\": %s",
365 path, msg)));
366 #else
367 fprintf(stderr, _("could not get junction for \"%s\": %s\n"),
368 path, msg);
369 #endif
370 LocalFree(msg);
371 CloseHandle(h);
372 errno = EINVAL;
373 return -1;
375 CloseHandle(h);
377 /* Got it, let's get some results from this */
378 if (reparseBuf->ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)
380 errno = EINVAL;
381 return -1;
384 r = WideCharToMultiByte(CP_ACP, 0,
385 reparseBuf->PathBuffer, -1,
386 buf,
387 size,
388 NULL, NULL);
390 if (r <= 0)
392 errno = EINVAL;
393 return -1;
396 /* r includes the null terminator */
397 r -= 1;
400 * If the path starts with "\??\" followed by a "drive absolute" path
401 * (known to Windows APIs as RtlPathTypeDriveAbsolute), then strip that
402 * prefix. This undoes some of the transformation performed by
403 * pgsymlink(), to get back to a format that users are used to seeing. We
404 * don't know how to transform other path types that might be encountered
405 * outside PGDATA, so we just return them directly.
407 if (r >= 7 &&
408 buf[0] == '\\' &&
409 buf[1] == '?' &&
410 buf[2] == '?' &&
411 buf[3] == '\\' &&
412 isalpha(buf[4]) &&
413 buf[5] == ':' &&
414 buf[6] == '\\')
416 memmove(buf, buf + 4, strlen(buf + 4) + 1);
417 r -= 4;
419 return r;
422 #endif /* defined(WIN32) && !defined(__CYGWIN__) */