diff --git a/src/include/port.h b/src/include/port.h index 2f5abdd33486d22148952fdf4e141c2b95f3798e..7ad464c07dc9417c806bb66511573b3ea82401c8 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -284,8 +284,11 @@ extern int pgunlink(const char *path); */ #if defined(WIN32) && !defined(__CYGWIN__) extern int pgsymlink(const char *oldpath, const char *newpath); +extern int pgreadlink(const char *path, char *buf, size_t size); +extern bool pgwin32_is_junction(char *path); #define symlink(oldpath, newpath) pgsymlink(oldpath, newpath) +#define readlink(path, buf, size) pgreadlink(path, buf, size) #endif extern bool rmtree(const char *path, bool rmtopdir); diff --git a/src/port/dirmod.c b/src/port/dirmod.c index 0d373a428bcc301d204c2d9fabcd46a36368d1ae..b6a5564c2c49373a39d2beebffc28836013a734c 100644 --- a/src/port/dirmod.c +++ b/src/port/dirmod.c @@ -297,6 +297,124 @@ pgsymlink(const char *oldpath, const char *newpath) 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) + sizeof(REPARSE_JUNCTION_DATA_BUFFER)]; + 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_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 nonexistant file is not a junction) + */ +bool +pgwin32_is_junction(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__) */