0001-Fix-Windows-basebackup-by-renaming-before-unlinking.patch
text/x-patch
Filename: 0001-Fix-Windows-basebackup-by-renaming-before-unlinking.patch
Type: text/x-patch
Part: 0
Patch
Format: format-patch
Series: patch 0001
Subject: Fix Windows basebackup by renaming before unlinking.
| File | + | − |
|---|---|---|
| src/backend/replication/basebackup.c | 7 | 0 |
| src/backend/storage/file/fd.c | 35 | 3 |
| src/backend/storage/smgr/md.c | 3 | 3 |
| src/include/storage/fd.h | 1 | 0 |
From 5990d14bb4f9a0ca83fd1b6c7c6e0a6a23786a0d Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Sun, 5 Sep 2021 17:07:44 +1200
Subject: [PATCH] Fix Windows basebackup by renaming before unlinking.
Rename files before unlinking on Windows. This solves a problem where
basebackup could fail because other backends have a file handle to a
deleted file, so stat() and open() fail with ERROR_ACCESS_DENIED.
Suggested by Kyotaro Horiguchi <horikyota.ntt@gmail.com>.
XXX This is only a rough sketch to try the idea out!
Discussion: https://postgr.es/m/CA%2BhUKGJz_pZTF9mckn6XgSv69%2BjGwdgLkxZ6b3NWGLBCVjqUZA%40mail.gmail.com
---
src/backend/replication/basebackup.c | 7 +++++
src/backend/storage/file/fd.c | 38 +++++++++++++++++++++++++---
src/backend/storage/smgr/md.c | 6 ++---
src/include/storage/fd.h | 1 +
4 files changed, 46 insertions(+), 6 deletions(-)
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index e71d7406ed..7961a7041b 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -19,6 +19,7 @@
#include "access/xlog_internal.h" /* for pg_start/stop_backup */
#include "catalog/pg_type.h"
#include "common/file_perm.h"
+#include "common/string.h"
#include "commands/progress.h"
#include "lib/stringinfo.h"
#include "libpq/libpq.h"
@@ -1268,6 +1269,12 @@ sendDir(const char *path, int basepathlen, bool sizeonly, List *tablespaces,
strlen(PG_TEMP_FILE_PREFIX)) == 0)
continue;
+#ifdef WIN32
+ /* Skip unlinked files */
+ if (pg_str_endswith(de->d_name, ".unlinked"))
+ continue;
+#endif
+
/*
* Check if the postmaster has signaled us to exit, and abort with an
* error in that case. The error handler further up will call
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 501652654e..43f24d4ceb 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -72,6 +72,7 @@
#include "postgres.h"
+#include <common/string.h>
#include <dirent.h>
#include <sys/file.h>
#include <sys/param.h>
@@ -683,6 +684,35 @@ pg_truncate(const char *path, off_t length)
#endif
}
+/*
+ * Unlink a file. On Windows, rename to a temporary filename before unlinking
+ * so that "path" is available for new files immediately even if other backends
+ * hold descriptors to the one we unlink.
+ */
+int
+pg_unlink(const char *path)
+{
+#ifdef WIN32
+ for (;;)
+ {
+ char tmp_path[MAXPGPATH];
+
+ snprintf(tmp_path, sizeof(tmp_path), "%s.%ld.unlinked", path, random());
+ if (!MoveFileEx(path, tmp_path, 0))
+ {
+ if (GetLastError() == ERROR_FILE_EXISTS)
+ continue; /* try a new random number */
+ _dosmaperr(GetLastError());
+ return -1;
+ }
+
+ return unlink(tmp_path);
+ }
+#else
+ return unlink(path);
+#endif
+}
+
/*
* fsync_fname -- fsync a file or directory, handling errors properly
*
@@ -3269,8 +3299,9 @@ CleanupTempFiles(bool isCommit, bool isProcExit)
* postmaster session
*
* This should be called during postmaster startup. It will forcibly
- * remove any leftover files created by OpenTemporaryFile and any leftover
- * temporary relation files created by mdcreate.
+ * remove any leftover files created by OpenTemporaryFile, any leftover
+ * temporary relation files created by mdcreate, and anything left
+ * behind by pg_unlink() on crash.
*
* During post-backend-crash restart cycle, this routine is called when
* remove_temp_files_after_crash GUC is enabled. Multiple crashes while
@@ -3448,7 +3479,8 @@ RemovePgTempRelationFilesInDbspace(const char *dbspacedirname)
while ((de = ReadDirExtended(dbspace_dir, dbspacedirname, LOG)) != NULL)
{
- if (!looks_like_temp_rel_name(de->d_name))
+ if (!looks_like_temp_rel_name(de->d_name) &&
+ !pg_str_endswith(de->d_name, ".unlinked"))
continue;
snprintf(rm_path, sizeof(rm_path), "%s/%s",
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 8592d47e95..d519855d59 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -374,7 +374,7 @@ mdunlinkfork(RelFileNodeBackend rnode, ForkNumber forkNum, bool isRedo)
/* Next unlink the file, unless it was already found to be missing */
if (ret == 0 || errno != ENOENT)
{
- ret = unlink(path);
+ ret = pg_unlink(path);
if (ret < 0 && errno != ENOENT)
ereport(WARNING,
(errcode_for_file_access(),
@@ -422,7 +422,7 @@ mdunlinkfork(RelFileNodeBackend rnode, ForkNumber forkNum, bool isRedo)
register_forget_request(rnode, forkNum, segno);
}
- if (unlink(segpath) < 0)
+ if (pg_unlink(segpath) < 0)
{
/* ENOENT is expected after the last segment... */
if (errno != ENOENT)
@@ -1753,7 +1753,7 @@ mdunlinkfiletag(const FileTag *ftag, char *path)
pfree(p);
/* Try to unlink the file. */
- return unlink(path);
+ return pg_unlink(path);
}
/*
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 329c7eba9a..be38f6a979 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -189,6 +189,7 @@ extern ssize_t pg_pwritev_with_retry(int fd,
int iovcnt,
off_t offset);
extern int pg_truncate(const char *path, off_t length);
+extern int pg_unlink(const char *path);
extern void fsync_fname(const char *fname, bool isdir);
extern int fsync_fname_ext(const char *fname, bool isdir, bool ignore_perm, int elevel);
extern int durable_rename(const char *oldfile, const char *newfile, int loglevel);
--
2.30.2