postgresql/src/port/dirmod.c

356 lines
8.6 KiB
C

/*-------------------------------------------------------------------------
*
* dirmod.c
* directory handling functions
*
* Portions Copyright (c) 1996-2022, 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 <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__)
/*
* 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__) */