On Thu, Aug 4, 2022 at 9:42 AM Thomas Munro <thomas.mu...@gmail.com> wrote: > On Thu, Aug 4, 2022 at 9:28 AM Andrew Dunstan <and...@dunslane.net> wrote: > > On 2022-08-01 Mo 16:06, Andrew Dunstan wrote: > > > I'll try it out on fairywren/drongo. > > > They are happy with patches 2, 3, and 4. > > Thanks for testing! > > If there are no objections, I'll go ahead and commit these later today.
Hmm, POSIX says st_link should contain the length of a symlink's target path, so I suppose we should probably set that even though we never consult it. Here's a version that does that. I also removed the rest of the now redundant #ifdef S_ISLNK conditions.
From 6e57c91b7b780ea51fa45e63cb201ff4240a416b Mon Sep 17 00:00:00 2001 From: Melih Mutlu <memu...@microsoft.com> Date: Mon, 21 Feb 2022 14:46:05 +0300 Subject: [PATCH v4 1/4] Added Windows with MinGW environment in Cirrus CI --- .cirrus.yml | 79 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 14 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 81eb8a9996..9820915f35 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -23,7 +23,6 @@ env: CHECKFLAGS: -Otarget PROVE_FLAGS: --timer PGCTLTIMEOUT: 120 # avoids spurious failures during parallel tests - TEMP_CONFIG: ${CIRRUS_WORKING_DIR}/src/tools/ci/pg_ci_base.conf PG_TEST_EXTRA: kerberos ldap ssl @@ -338,13 +337,30 @@ task: cores_script: src/tools/ci/cores_backtrace.sh macos "${HOME}/cores" +WINDOWS_ENVIRONMENT_BASE: &WINDOWS_ENVIRONMENT_BASE + env: + # Half the allowed per-user CPU cores + CPUS: 4 + # The default working dir is in a directory msbuild complains about + CIRRUS_WORKING_DIR: "c:/cirrus" + TEMP_CONFIG: ${CIRRUS_WORKING_DIR}/src/tools/ci/pg_ci_base.conf + + # Avoids port conflicts between concurrent tap test runs + PG_TEST_USE_UNIX_SOCKETS: 1 + + only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*' || $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:[^\n]*windows.*' + + sysinfo_script: | + chcp + systeminfo + powershell -Command get-psdrive -psprovider filesystem + set + task: + << : *WINDOWS_ENVIRONMENT_BASE name: Windows - Server 2019, VS 2019 env: - # Half the allowed per-user CPU cores - CPUS: 4 - # Our windows infrastructure doesn't have test concurrency above the level # of a single vcregress test target. Due to that, it's useful to run prove # with multiple jobs. For the other tasks it isn't, because two sources @@ -354,15 +370,11 @@ task: # likely can be improved upon further. PROVE_FLAGS: -j10 --timer - # The default cirrus working dir is in a directory msbuild complains about - CIRRUS_WORKING_DIR: "c:/cirrus" # Avoid re-installing over and over NO_TEMP_INSTALL: 1 # git's tar doesn't deal with drive letters, see # https://postgr.es/m/b6782dc3-a7b0-ed56-175f-f8f54cb08d67%40dunslane.net TAR: "c:/windows/system32/tar.exe" - # Avoids port conflicts between concurrent tap test runs - PG_TEST_USE_UNIX_SOCKETS: 1 PG_REGRESS_SOCK_DIR: "c:/cirrus/" # -m enables parallelism # verbosity:minimal + Summary reduce verbosity, while keeping a summary of @@ -393,12 +405,6 @@ task: cpu: $CPUS memory: 4G - sysinfo_script: | - chcp - systeminfo - powershell -Command get-psdrive -psprovider filesystem - set - setup_additional_packages_script: | REM choco install -y --no-progress ... @@ -456,6 +462,51 @@ task: path: "crashlog-*.txt" type: text/plain +task: + << : *WINDOWS_ENVIRONMENT_BASE + name: Windows - Server 2019, MinGW64 + windows_container: + image: $CONTAINER_REPO/windows_ci_mingw64:latest + cpu: $CPUS + memory: 4G + env: + CCACHE_DIR: C:/msys64/ccache + BUILD_DIR: "%CIRRUS_WORKING_DIR%/build" + + ccache_cache: + folder: ${CCACHE_DIR} + + mingw_info_script: + - C:\msys64\usr\bin\bash.exe -lc "where gcc" + - C:\msys64\usr\bin\bash.exe -lc "gcc --version" + - C:\msys64\usr\bin\bash.exe -lc "where perl" + - C:\msys64\usr\bin\bash.exe -lc "perl --version" + + configure_script: + - C:\msys64\usr\bin\bash.exe -lc "mkdir %BUILD_DIR% && + cd %BUILD_DIR% && + %CIRRUS_WORKING_DIR%/configure + --host=x86_64-w64-mingw32 + --enable-cassert + --enable-tap-tests + --with-icu + --with-libxml + --with-libxslt + --with-lz4 + --enable-debug + CC='ccache gcc' + CXX='ccache g++'" + + build_script: + C:\msys64\usr\bin\bash.exe -lc "cd %BUILD_DIR% && make -s world-bin -j${CPUS}" + + upload_caches: ccache + + tests_script: + - set "NoDefaultCurrentDirectoryInExePath=0" + - C:\msys64\usr\bin\bash.exe -lc "cd %BUILD_DIR% && make -s ${CHECK} ${CHECKFLAGS} -j${CPUS} TMPDIR=%BUILD_DIR%/tmp_install" + + on_failure: *on_failure task: name: CompilerWarnings -- 2.37.1
From f8be96a1c712b7ffe42445108022b9978825ce97 Mon Sep 17 00:00:00 2001 From: Thomas Munro <tmu...@postgresql.org> Date: Fri, 5 Aug 2022 16:41:34 +1200 Subject: [PATCH v4 2/4] Provide lstat() for Windows. Junction points will be reported with S_ISLNK(x.st_mode), simulating POSIX lstat(). stat() will follow pseudo-symlinks, like in POSIX (but only one level before giving up, unlike in POSIX). Tested-by: Andrew Dunstan <and...@dunslane.net> Discussion: https://postgr.es/m/CA%2BhUKGLfOOeyZpm5ByVcAt7x5Pn-%3DxGRNCvgiUPVVzjFLtnY0w%40mail.gmail.com --- src/include/port/win32_port.h | 18 +++++- src/port/win32stat.c | 104 ++++++++++++++++++++++++++++++++-- 2 files changed, 117 insertions(+), 5 deletions(-) diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h index 0eaa97561a..c27b34de5b 100644 --- a/src/include/port/win32_port.h +++ b/src/include/port/win32_port.h @@ -278,10 +278,11 @@ struct stat /* This should match struct __stat64 */ extern int _pgfstat64(int fileno, struct stat *buf); extern int _pgstat64(const char *name, struct stat *buf); +extern int _pglstat64(const char *name, struct stat *buf); #define fstat(fileno, sb) _pgfstat64(fileno, sb) #define stat(path, sb) _pgstat64(path, sb) -#define lstat(path, sb) _pgstat64(path, sb) +#define lstat(path, sb) _pglstat64(path, sb) /* These macros are not provided by older MinGW, nor by MSVC */ #ifndef S_IRUSR @@ -327,6 +328,21 @@ extern int _pgstat64(const char *name, struct stat *buf); #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #endif +/* + * In order for lstat() to be able to report junction points as symlinks, we + * need to hijack a bit in st_mode, since neither MSVC nor MinGW provides + * S_ISLNK and there aren't any spare bits. We'll steal the one for character + * devices, because we don't otherwise make use of those. + */ +#ifdef S_ISLNK +#error "S_ISLNK is already defined" +#endif +#ifdef S_IFLNK +#error "S_IFLNK is already defined" +#endif +#define S_IFLNK S_IFCHR +#define S_ISLNK(m) (((m) & S_IFLNK) == S_IFLNK) + /* * Supplement to <fcntl.h>. * This is the same value as _O_NOINHERIT in the MS header file. This is diff --git a/src/port/win32stat.c b/src/port/win32stat.c index e03ed5f35c..b10e35886e 100644 --- a/src/port/win32stat.c +++ b/src/port/win32stat.c @@ -15,7 +15,11 @@ #ifdef WIN32 +#define UMDF_USING_NTSTATUS + #include "c.h" +#include "port/win32ntdll.h" + #include <windows.h> /* @@ -107,12 +111,10 @@ fileinfo_to_stat(HANDLE hFile, struct stat *buf) } /* - * Windows implementation of stat(). - * - * This currently also implements lstat(), though perhaps that should change. + * Windows implementation of lstat(). */ int -_pgstat64(const char *name, struct stat *buf) +_pglstat64(const char *name, struct stat *buf) { /* * Our open wrapper will report STATUS_DELETE_PENDING as ENOENT. We @@ -129,10 +131,104 @@ _pgstat64(const char *name, struct stat *buf) ret = fileinfo_to_stat(hFile, buf); + /* + * Junction points appear as directories to fileinfo_to_stat(), so we'll + * need to do a bit more work to distinguish them. + */ + if (ret == 0 && S_ISDIR(buf->st_mode)) + { + char next[MAXPGPATH]; + ssize_t size; + + /* + * POSIX says we need to put the length of the target path into + * st_size. Use readlink() to get it, or learn that this is not a + * junction point. + */ + size = readlink(name, next, sizeof(next)); + if (size < 0) + { + if (errno == EACCES && + pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING) + { + /* Unlinked underneath us. */ + errno = ENOENT; + ret = -1; + } + else if (errno == EINVAL) + { + /* It's not a junction point, nothing to do. */ + } + else + { + /* Some other failure. */ + ret = -1; + } + } + else + { + /* It's a junction point, so report it as a symlink. */ + buf->st_mode &= ~S_IFDIR; + buf->st_mode |= S_IFLNK; + buf->st_size = size; + } + } + CloseHandle(hFile); return ret; } +/* + * Windows implementation of stat(). + */ +int +_pgstat64(const char *name, struct stat *buf) +{ + int ret; + + ret = _pglstat64(name, buf); + + /* Do we need to follow a symlink (junction point)? */ + if (ret == 0 && S_ISLNK(buf->st_mode)) + { + char next[MAXPGPATH]; + ssize_t size; + + size = readlink(name, next, sizeof(next)); + if (size < 0) + { + if (errno == EACCES && + pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING) + { + /* Unlinked underneath us. */ + errno = ENOENT; + } + return -1; + } + if (size >= sizeof(next)) + { + errno = ENAMETOOLONG; + return -1; + } + next[size] = 0; + + ret = _pglstat64(next, buf); + if (ret == 0 && S_ISLNK(buf->st_mode)) + { + /* + * We're only prepared to go one hop, because we only expect to + * deal with the simple cases that we create. The error for too + * many symlinks is supposed to be ELOOP, but Windows hasn't got + * it. + */ + errno = EIO; + return -1; + } + } + + return ret; +} + /* * Windows implementation of fstat(). */ -- 2.37.1
From 9fa720ed83ea394a5c701a9ec4aee3336d23b6b0 Mon Sep 17 00:00:00 2001 From: Thomas Munro <tmu...@postgresql.org> Date: Fri, 5 Aug 2022 09:52:26 +1200 Subject: [PATCH v4 4/4] Replace pgwin32_is_junction() with lstat(). Now that lstat() reports junction points with S_IFLNK and unlink() can unlink them, there is no need for conditional code for Windows in a few places. Sometimes that was expressed by testing for WIN32, other times by testing for S_ISLNK. The coding around pgwin32_is_junction() was a bit suspect anyway, as we never checked for errors, and we also know that errors can be spuriously reported because of transient sharing violations on this OS. The lstat()-based code has handling for that. This also reverts 4fc6b6ee on master only. That was done only because lstat() didn't previously work for symlinks (junction points), but isn't needed anymore. Tested-by: Andrew Dunstan <and...@dunslane.net> Discussion: https://postgr.es/m/CA%2BhUKGLfOOeyZpm5ByVcAt7x5Pn-%3DxGRNCvgiUPVVzjFLtnY0w%40mail.gmail.com --- src/backend/commands/tablespace.c | 7 +------ src/backend/replication/basebackup.c | 12 +----------- src/backend/storage/file/fd.c | 5 ----- src/backend/utils/adt/misc.c | 7 ------- src/bin/pg_checksums/pg_checksums.c | 4 ---- src/bin/pg_rewind/file_ops.c | 4 ---- src/common/file_utils.c | 23 ----------------------- src/include/port.h | 1 - src/include/port/win32_port.h | 1 - src/port/dirmod.c | 16 ---------------- 10 files changed, 2 insertions(+), 78 deletions(-) diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c index 526e82e388..f260b484fc 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -792,8 +792,7 @@ destroy_tablespace_directories(Oid tablespaceoid, bool redo) /* * Try to remove the symlink. We must however deal with the possibility * that it's a directory instead of a symlink --- this could happen during - * WAL replay (see TablespaceCreateDbspace), and it is also the case on - * Windows where junction points lstat() as directories. + * WAL replay (see TablespaceCreateDbspace). * * Note: in the redo case, we'll return true if this final step fails; * there's no point in retrying it. Also, ENOENT should provoke no more @@ -823,7 +822,6 @@ remove_symlink: linkloc))); } } -#ifdef S_ISLNK else if (S_ISLNK(st.st_mode)) { if (unlink(linkloc) < 0) @@ -836,7 +834,6 @@ remove_symlink: linkloc))); } } -#endif else { /* Refuse to remove anything that's not a directory or symlink */ @@ -914,7 +911,6 @@ remove_tablespace_symlink(const char *linkloc) errmsg("could not remove directory \"%s\": %m", linkloc))); } -#ifdef S_ISLNK else if (S_ISLNK(st.st_mode)) { if (unlink(linkloc) < 0 && errno != ENOENT) @@ -923,7 +919,6 @@ remove_tablespace_symlink(const char *linkloc) errmsg("could not remove symbolic link \"%s\": %m", linkloc))); } -#endif else { /* Refuse to remove anything that's not a directory or symlink */ diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c index 5e457f9be9..deeddd09a9 100644 --- a/src/backend/replication/basebackup.c +++ b/src/backend/replication/basebackup.c @@ -1322,13 +1322,7 @@ sendDir(bbsink *sink, const char *path, int basepathlen, bool sizeonly, } /* Allow symbolic links in pg_tblspc only */ - if (strcmp(path, "./pg_tblspc") == 0 && -#ifndef WIN32 - S_ISLNK(statbuf.st_mode) -#else - pgwin32_is_junction(pathbuf) -#endif - ) + if (strcmp(path, "./pg_tblspc") == 0 && S_ISLNK(statbuf.st_mode)) { char linkpath[MAXPGPATH]; int rllen; @@ -1798,11 +1792,7 @@ static void convert_link_to_directory(const char *pathbuf, struct stat *statbuf) { /* If symlink, write it as a directory anyway */ -#ifndef WIN32 if (S_ISLNK(statbuf->st_mode)) -#else - if (pgwin32_is_junction(pathbuf)) -#endif statbuf->st_mode = S_IFDIR | pg_dir_create_mode; } diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index ccb540d617..efb34d4dcb 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -3365,7 +3365,6 @@ SyncDataDirectory(void) */ xlog_is_symlink = false; -#ifndef WIN32 { struct stat st; @@ -3377,10 +3376,6 @@ SyncDataDirectory(void) else if (S_ISLNK(st.st_mode)) xlog_is_symlink = true; } -#else - if (pgwin32_is_junction("pg_wal")) - xlog_is_symlink = true; -#endif #ifdef HAVE_SYNCFS if (recovery_init_sync_method == RECOVERY_INIT_SYNC_METHOD_SYNCFS) diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c index af0d924459..d35b5d1f4f 100644 --- a/src/backend/utils/adt/misc.c +++ b/src/backend/utils/adt/misc.c @@ -283,9 +283,7 @@ pg_tablespace_location(PG_FUNCTION_ARGS) char sourcepath[MAXPGPATH]; char targetpath[MAXPGPATH]; int rllen; -#ifndef WIN32 struct stat st; -#endif /* * It's useful to apply this function to pg_class.reltablespace, wherein @@ -314,10 +312,6 @@ pg_tablespace_location(PG_FUNCTION_ARGS) * created with allow_in_place_tablespaces enabled. If a directory is * found, a relative path to the data directory is returned. */ -#ifdef WIN32 - if (!pgwin32_is_junction(sourcepath)) - PG_RETURN_TEXT_P(cstring_to_text(sourcepath)); -#else if (lstat(sourcepath, &st) < 0) { ereport(ERROR, @@ -328,7 +322,6 @@ pg_tablespace_location(PG_FUNCTION_ARGS) if (!S_ISLNK(st.st_mode)) PG_RETURN_TEXT_P(cstring_to_text(sourcepath)); -#endif /* * In presence of a link or a junction point, return the path pointing to. diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c index dc20122c89..324ccf7783 100644 --- a/src/bin/pg_checksums/pg_checksums.c +++ b/src/bin/pg_checksums/pg_checksums.c @@ -384,11 +384,7 @@ scan_directory(const char *basedir, const char *subdir, bool sizeonly) if (!sizeonly) scan_file(fn, segmentno); } -#ifndef WIN32 else if (S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)) -#else - else if (S_ISDIR(st.st_mode) || pgwin32_is_junction(fn)) -#endif { /* * If going through the entries of pg_tblspc, we assume to operate diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c index 5e6d8b89c4..db190bcba7 100644 --- a/src/bin/pg_rewind/file_ops.c +++ b/src/bin/pg_rewind/file_ops.c @@ -431,11 +431,7 @@ recurse_dir(const char *datadir, const char *parentpath, /* recurse to handle subdirectories */ recurse_dir(datadir, path, callback); } -#ifndef WIN32 else if (S_ISLNK(fst.st_mode)) -#else - else if (pgwin32_is_junction(fullpath)) -#endif { char link_target[MAXPGPATH]; int len; diff --git a/src/common/file_utils.c b/src/common/file_utils.c index 966b987d64..df4d6d240c 100644 --- a/src/common/file_utils.c +++ b/src/common/file_utils.c @@ -79,7 +79,6 @@ fsync_pgdata(const char *pg_data, */ xlog_is_symlink = false; -#ifndef WIN32 { struct stat st; @@ -88,10 +87,6 @@ fsync_pgdata(const char *pg_data, else if (S_ISLNK(st.st_mode)) xlog_is_symlink = true; } -#else - if (pgwin32_is_junction(pg_wal)) - xlog_is_symlink = true; -#endif /* * If possible, hint to the kernel that we're soon going to fsync the data @@ -459,27 +454,9 @@ get_dirent_type(const char *path, result = PGFILETYPE_REG; else if (S_ISDIR(fst.st_mode)) result = PGFILETYPE_DIR; -#ifdef S_ISLNK else if (S_ISLNK(fst.st_mode)) result = PGFILETYPE_LNK; -#endif } -#if defined(WIN32) && !defined(_MSC_VER) - - /* - * If we're on native Windows (not Cygwin, which has its own POSIX - * symlinks), but not using the MSVC compiler, then we're using a - * readdir() emulation provided by the MinGW runtime that has no d_type. - * Since the lstat() fallback code reports junction points as directories, - * we need an extra system call to check if we should report them as - * symlinks instead, following our convention. - */ - if (result == PGFILETYPE_DIR && - !look_through_symlinks && - pgwin32_is_junction(path)) - result = PGFILETYPE_LNK; -#endif - return result; } diff --git a/src/include/port.h b/src/include/port.h index 14b640fe33..feb2ae840d 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -284,7 +284,6 @@ 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(const char *path); #define symlink(oldpath, newpath) pgsymlink(oldpath, newpath) #define readlink(path, buf, size) pgreadlink(path, buf, size) diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h index c27b34de5b..a65996cce5 100644 --- a/src/include/port/win32_port.h +++ b/src/include/port/win32_port.h @@ -230,7 +230,6 @@ int setitimer(int which, const struct itimerval *value, struct itimerval *oval */ 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(const char *path); #define symlink(oldpath, newpath) pgsymlink(oldpath, newpath) #define readlink(path, buf, size) pgreadlink(path, buf, size) diff --git a/src/port/dirmod.c b/src/port/dirmod.c index ea191e99c6..2818bfd2e9 100644 --- a/src/port/dirmod.c +++ b/src/port/dirmod.c @@ -362,20 +362,4 @@ pgreadlink(const char *path, char *buf, size_t size) 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__) */ -- 2.37.1
From af3e1f2e5fd505299d9062a8aedc6765983aebc2 Mon Sep 17 00:00:00 2001 From: Thomas Munro <tmu...@postgresql.org> Date: Fri, 5 Aug 2022 16:41:50 +1200 Subject: [PATCH v4 3/4] Make unlink() work for junction points on Windows. To support harmonization of Windows and Unix code, teach our unlink() wrapper that junction points need to be unlinked with rmdir() on Windows. Tested-by: Andrew Dunstan <and...@dunslane.net> Discussion: https://postgr.es/m/CA%2BhUKGLfOOeyZpm5ByVcAt7x5Pn-%3DxGRNCvgiUPVVzjFLtnY0w%40mail.gmail.com --- src/port/dirmod.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/port/dirmod.c b/src/port/dirmod.c index 7ce042e75d..ea191e99c6 100644 --- a/src/port/dirmod.c +++ b/src/port/dirmod.c @@ -99,6 +99,32 @@ int pgunlink(const char *path) { 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. + */ + if (lstat(path, &st) < 0) + return -1; /* * We need to loop because even though PostgreSQL uses flags that allow @@ -107,7 +133,7 @@ pgunlink(const char *path) * someone else to close the file, as the caller might be holding locks * and blocking other backends. */ - while (unlink(path)) + while ((S_ISLNK(st.st_mode) ? rmdir(path) : unlink(path)) < 0) { if (errno != EACCES) return -1; -- 2.37.1