/*------------------------------------------------------------------------- * * dirmod.c * directory handling functions * * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * This includes replacement versions of functions that work on * Win32 (NT4 and newer). * * 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 #include #if defined(WIN32) || defined(__CYGWIN__) #ifndef __CYGWIN__ #include #else #include #include #endif #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; } /* * pgunlink */ int pgunlink(const char *path) { int loops = 0; /* * 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 (unlink(path)) { 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) 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; 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 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); 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; } /* * If the path starts with "\??\", which it will do in most (all?) cases, * strip those out. */ if (r > 4 && strncmp(buf, "\\??\\", 4) == 0) { memmove(buf, buf + 4, strlen(buf + 4) + 1); r -= 4; } return r; } /* * Assumes the file exists, so will return false if it doesn't * (since a nonexistent file is not a junction) */ bool pgwin32_is_junction(const char *path) { DWORD attr = GetFileAttributes(path); if (attr == INVALID_FILE_ATTRIBUTES) { _dosmaperr(GetLastError()); return false; } return ((attr & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT); } #endif /* defined(WIN32) && !defined(__CYGWIN__) */