423 lines
11 KiB
C
423 lines
11 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* dirmod.c
|
|
* directory handling functions
|
|
*
|
|
* Portions Copyright (c) 1996-2024, 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__) */
|