| /*------------------------------------------------------------------------- |
| * |
| * dirmod.c |
| * directory handling functions |
| * |
| * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * This includes replacement versions of functions that work on |
| * Windows. |
| * |
| * IDENTIFICATION |
| * src/port/dirmod.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| |
| #ifndef FRONTEND |
| #include "postgres.h" |
| #else |
| #include "postgres_fe.h" |
| #endif |
| |
| /* Don't modify declarations in system headers */ |
| #if defined(WIN32) || defined(__CYGWIN__) |
| #undef rename |
| #undef unlink |
| #endif |
| |
| #include <unistd.h> |
| #include <sys/stat.h> |
| |
| #if defined(WIN32) || defined(__CYGWIN__) |
| #ifndef __CYGWIN__ |
| #include <winioctl.h> |
| #else |
| #include <windows.h> |
| #include <w32api/winioctl.h> |
| #endif |
| #endif |
| |
| #if defined(WIN32) && !defined(__CYGWIN__) |
| #include "port/win32ntdll.h" |
| #endif |
| |
| #if defined(WIN32) || defined(__CYGWIN__) |
| |
| /* |
| * pgrename |
| */ |
| int |
| pgrename(const char *from, const char *to) |
| { |
| int loops = 0; |
| |
| /* |
| * We need to loop because even though PostgreSQL uses flags that allow |
| * rename while the file is open, other applications might have the file |
| * open without those flags. However, we won't wait indefinitely for |
| * someone else to close the file, as the caller might be holding locks |
| * and blocking other backends. |
| */ |
| #if defined(WIN32) && !defined(__CYGWIN__) |
| while (!MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING)) |
| #else |
| while (rename(from, to) < 0) |
| #endif |
| { |
| #if defined(WIN32) && !defined(__CYGWIN__) |
| DWORD err = GetLastError(); |
| |
| _dosmaperr(err); |
| |
| /* |
| * Modern NT-based Windows versions return ERROR_SHARING_VIOLATION if |
| * another process has the file open without FILE_SHARE_DELETE. |
| * ERROR_LOCK_VIOLATION has also been seen with some anti-virus |
| * software. This used to check for just ERROR_ACCESS_DENIED, so |
| * presumably you can get that too with some OS versions. We don't |
| * expect real permission errors where we currently use rename(). |
| */ |
| if (err != ERROR_ACCESS_DENIED && |
| err != ERROR_SHARING_VIOLATION && |
| err != ERROR_LOCK_VIOLATION) |
| return -1; |
| #else |
| if (errno != EACCES) |
| return -1; |
| #endif |
| |
| if (++loops > 100) /* time out after 10 sec */ |
| return -1; |
| pg_usleep(100000); /* us */ |
| } |
| return 0; |
| } |
| |
| /* |
| * Check if _pglstat64()'s reason for failure was STATUS_DELETE_PENDING. |
| * This doesn't apply to Cygwin, which has its own lstat() that would report |
| * the case as EACCES. |
| */ |
| static bool |
| lstat_error_was_status_delete_pending(void) |
| { |
| if (errno != ENOENT) |
| return false; |
| #if defined(WIN32) && !defined(__CYGWIN__) |
| if (pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING) |
| return true; |
| #endif |
| return false; |
| } |
| |
| /* |
| * pgunlink |
| */ |
| int |
| pgunlink(const char *path) |
| { |
| bool is_lnk; |
| int loops = 0; |
| struct stat st; |
| |
| /* |
| * This function might be called for a regular file or for a junction |
| * point (which we use to emulate symlinks). The latter must be unlinked |
| * with rmdir() on Windows. Before we worry about any of that, let's see |
| * if we can unlink directly, since that's expected to be the most common |
| * case. |
| */ |
| if (unlink(path) == 0) |
| return 0; |
| if (errno != EACCES) |
| return -1; |
| |
| /* |
| * EACCES is reported for many reasons including unlink() of a junction |
| * point. Check if that's the case so we can redirect to rmdir(). |
| * |
| * Note that by checking only once, we can't cope with a path that changes |
| * from regular file to junction point underneath us while we're retrying |
| * due to sharing violations, but that seems unlikely. We could perhaps |
| * prevent that by holding a file handle ourselves across the lstat() and |
| * the retry loop, but that seems like over-engineering for now. |
| * |
| * In the special case of a STATUS_DELETE_PENDING error (file already |
| * unlinked, but someone still has it open), we don't want to report |
| * ENOENT to the caller immediately, because rmdir(parent) would probably |
| * fail. We want to wait until the file truly goes away so that simple |
| * recursive directory unlink algorithms work. |
| */ |
| if (lstat(path, &st) < 0) |
| { |
| if (lstat_error_was_status_delete_pending()) |
| is_lnk = false; |
| else |
| return -1; |
| } |
| else |
| is_lnk = S_ISLNK(st.st_mode); |
| |
| /* |
| * We need to loop because even though PostgreSQL uses flags that allow |
| * unlink while the file is open, other applications might have the file |
| * open without those flags. However, we won't wait indefinitely for |
| * someone else to close the file, as the caller might be holding locks |
| * and blocking other backends. |
| */ |
| while ((is_lnk ? rmdir(path) : unlink(path)) < 0) |
| { |
| if (errno != EACCES) |
| return -1; |
| if (++loops > 100) /* time out after 10 sec */ |
| return -1; |
| pg_usleep(100000); /* us */ |
| } |
| return 0; |
| } |
| |
| /* We undefined these above; now redefine for possible use below */ |
| #define rename(from, to) pgrename(from, to) |
| #define unlink(path) pgunlink(path) |
| #endif /* defined(WIN32) || defined(__CYGWIN__) */ |
| |
| |
| #if defined(WIN32) && !defined(__CYGWIN__) /* Cygwin has its own symlinks */ |
| |
| /* |
| * pgsymlink support: |
| * |
| * This struct is a replacement for REPARSE_DATA_BUFFER which is defined in VC6 winnt.h |
| * but omitted in later SDK functions. |
| * We only need the SymbolicLinkReparseBuffer part of the original struct's union. |
| */ |
| typedef struct |
| { |
| DWORD ReparseTag; |
| WORD ReparseDataLength; |
| WORD Reserved; |
| /* SymbolicLinkReparseBuffer */ |
| WORD SubstituteNameOffset; |
| WORD SubstituteNameLength; |
| WORD PrintNameOffset; |
| WORD PrintNameLength; |
| WCHAR PathBuffer[FLEXIBLE_ARRAY_MEMBER]; |
| } REPARSE_JUNCTION_DATA_BUFFER; |
| |
| #define REPARSE_JUNCTION_DATA_BUFFER_HEADER_SIZE \ |
| FIELD_OFFSET(REPARSE_JUNCTION_DATA_BUFFER, SubstituteNameOffset) |
| |
| |
| /* |
| * pgsymlink - uses Win32 junction points |
| * |
| * For reference: http://www.codeproject.com/KB/winsdk/junctionpoints.aspx |
| */ |
| int |
| pgsymlink(const char *oldpath, const char *newpath) |
| { |
| HANDLE dirhandle; |
| DWORD len; |
| char buffer[MAX_PATH * sizeof(WCHAR) + offsetof(REPARSE_JUNCTION_DATA_BUFFER, PathBuffer)]; |
| char nativeTarget[MAX_PATH]; |
| char *p = nativeTarget; |
| REPARSE_JUNCTION_DATA_BUFFER *reparseBuf = (REPARSE_JUNCTION_DATA_BUFFER *) buffer; |
| |
| CreateDirectory(newpath, 0); |
| dirhandle = CreateFile(newpath, GENERIC_READ | GENERIC_WRITE, |
| 0, 0, OPEN_EXISTING, |
| FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0); |
| |
| if (dirhandle == INVALID_HANDLE_VALUE) |
| { |
| _dosmaperr(GetLastError()); |
| return -1; |
| } |
| |
| /* make sure we have an unparsed native win32 path */ |
| if (memcmp("\\??\\", oldpath, 4) != 0) |
| snprintf(nativeTarget, sizeof(nativeTarget), "\\??\\%s", oldpath); |
| else |
| strlcpy(nativeTarget, oldpath, sizeof(nativeTarget)); |
| |
| while ((p = strchr(p, '/')) != NULL) |
| *p++ = '\\'; |
| |
| len = strlen(nativeTarget) * sizeof(WCHAR); |
| reparseBuf->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; |
| reparseBuf->ReparseDataLength = len + 12; |
| reparseBuf->Reserved = 0; |
| reparseBuf->SubstituteNameOffset = 0; |
| reparseBuf->SubstituteNameLength = len; |
| reparseBuf->PrintNameOffset = len + sizeof(WCHAR); |
| reparseBuf->PrintNameLength = 0; |
| MultiByteToWideChar(CP_ACP, 0, nativeTarget, -1, |
| reparseBuf->PathBuffer, MAX_PATH); |
| |
| /* |
| * FSCTL_SET_REPARSE_POINT is coded differently depending on SDK version; |
| * we use our own definition |
| */ |
| if (!DeviceIoControl(dirhandle, |
| CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, FILE_ANY_ACCESS), |
| reparseBuf, |
| reparseBuf->ReparseDataLength + REPARSE_JUNCTION_DATA_BUFFER_HEADER_SIZE, |
| 0, 0, &len, 0)) |
| { |
| LPSTR msg; |
| int save_errno; |
| |
| _dosmaperr(GetLastError()); |
| save_errno = errno; |
| |
| FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | |
| FORMAT_MESSAGE_IGNORE_INSERTS | |
| FORMAT_MESSAGE_FROM_SYSTEM, |
| NULL, GetLastError(), |
| MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), |
| (LPSTR) &msg, 0, NULL); |
| #ifndef FRONTEND |
| ereport(ERROR, |
| (errcode_for_file_access(), |
| errmsg("could not set junction for \"%s\": %s", |
| nativeTarget, msg))); |
| #else |
| fprintf(stderr, _("could not set junction for \"%s\": %s\n"), |
| nativeTarget, msg); |
| #endif |
| LocalFree(msg); |
| |
| CloseHandle(dirhandle); |
| RemoveDirectory(newpath); |
| |
| errno = save_errno; |
| |
| return -1; |
| } |
| |
| CloseHandle(dirhandle); |
| |
| return 0; |
| } |
| |
| /* |
| * pgreadlink - uses Win32 junction points |
| */ |
| int |
| pgreadlink(const char *path, char *buf, size_t size) |
| { |
| DWORD attr; |
| HANDLE h; |
| char buffer[MAX_PATH * sizeof(WCHAR) + offsetof(REPARSE_JUNCTION_DATA_BUFFER, PathBuffer)]; |
| REPARSE_JUNCTION_DATA_BUFFER *reparseBuf = (REPARSE_JUNCTION_DATA_BUFFER *) buffer; |
| DWORD len; |
| int r; |
| |
| attr = GetFileAttributes(path); |
| if (attr == INVALID_FILE_ATTRIBUTES) |
| { |
| _dosmaperr(GetLastError()); |
| return -1; |
| } |
| if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) == 0) |
| { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| h = CreateFile(path, |
| GENERIC_READ, |
| FILE_SHARE_READ | FILE_SHARE_WRITE, |
| NULL, |
| OPEN_EXISTING, |
| FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, |
| 0); |
| if (h == INVALID_HANDLE_VALUE) |
| { |
| _dosmaperr(GetLastError()); |
| return -1; |
| } |
| |
| if (!DeviceIoControl(h, |
| FSCTL_GET_REPARSE_POINT, |
| NULL, |
| 0, |
| (LPVOID) reparseBuf, |
| sizeof(buffer), |
| &len, |
| NULL)) |
| { |
| LPSTR msg; |
| |
| errno = 0; |
| FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | |
| FORMAT_MESSAGE_IGNORE_INSERTS | |
| FORMAT_MESSAGE_FROM_SYSTEM, |
| NULL, GetLastError(), |
| MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), |
| (LPSTR) &msg, 0, NULL); |
| #ifndef FRONTEND |
| ereport(ERROR, |
| (errcode_for_file_access(), |
| errmsg("could not get junction for \"%s\": %s", |
| path, msg))); |
| #else |
| fprintf(stderr, _("could not get junction for \"%s\": %s\n"), |
| path, msg); |
| #endif |
| LocalFree(msg); |
| CloseHandle(h); |
| errno = EINVAL; |
| return -1; |
| } |
| CloseHandle(h); |
| |
| /* Got it, let's get some results from this */ |
| if (reparseBuf->ReparseTag != IO_REPARSE_TAG_MOUNT_POINT) |
| { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| r = WideCharToMultiByte(CP_ACP, 0, |
| reparseBuf->PathBuffer, -1, |
| buf, |
| size, |
| NULL, NULL); |
| |
| if (r <= 0) |
| { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| /* r includes the null terminator */ |
| r -= 1; |
| |
| /* |
| * If the path starts with "\??\" followed by a "drive absolute" path |
| * (known to Windows APIs as RtlPathTypeDriveAbsolute), then strip that |
| * prefix. This undoes some of the transformation performed by |
| * pgsymlink(), to get back to a format that users are used to seeing. We |
| * don't know how to transform other path types that might be encountered |
| * outside PGDATA, so we just return them directly. |
| */ |
| if (r >= 7 && |
| buf[0] == '\\' && |
| buf[1] == '?' && |
| buf[2] == '?' && |
| buf[3] == '\\' && |
| isalpha(buf[4]) && |
| buf[5] == ':' && |
| buf[6] == '\\') |
| { |
| memmove(buf, buf + 4, strlen(buf + 4) + 1); |
| r -= 4; |
| } |
| return r; |
| } |
| |
| #endif /* defined(WIN32) && !defined(__CYGWIN__) */ |