From cd7a35ff924d2a4f24676da7719ae5ef1eb315ca Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Sun, 5 Jul 2026 19:42:22 +0300
Subject: [PATCH 2/5] Move some functions from fd.c to file_utils.c

The idea is that the Virtual File Descriptor abstraction lives in fd.c
and general file utilities that work directly with raw fds or file
names go into file_utils.c.
---
 contrib/basic_archive/basic_archive.c         |   1 +
 contrib/pg_prewarm/autoprewarm.c              |   1 +
 contrib/pg_stash_advice/stashpersist.c        |   1 +
 .../pg_stat_statements/pg_stat_statements.c   |   1 +
 src/backend/access/heap/rewriteheap.c         |   1 +
 src/backend/access/transam/slru.c             |   1 +
 src/backend/access/transam/timeline.c         |   1 +
 src/backend/access/transam/twophase.c         |   1 +
 src/backend/access/transam/xlog.c             |   1 +
 src/backend/access/transam/xlogarchive.c      |   1 +
 src/backend/access/transam/xlogrecovery.c     |   1 +
 src/backend/backup/basebackup_server.c        |   1 +
 src/backend/commands/dbcommands.c             |   1 +
 src/backend/commands/extension.c              |   1 +
 src/backend/jit/jit.c                         |   2 +-
 src/backend/postmaster/walsummarizer.c        |   1 +
 src/backend/replication/logical/origin.c      |   1 +
 src/backend/replication/logical/snapbuild.c   |   1 +
 src/backend/replication/slot.c                |   1 +
 src/backend/storage/file/Makefile             |   1 +
 src/backend/storage/file/copydir.c            |   1 +
 src/backend/storage/file/fd.c                 | 644 +----------------
 src/backend/storage/file/file_utils.c         | 670 ++++++++++++++++++
 src/backend/storage/file/meson.build          |   1 +
 src/backend/storage/file/reinit.c             |   1 +
 src/backend/storage/smgr/md.c                 |   1 +
 src/backend/storage/sync/sync.c               |   2 +-
 src/backend/utils/activity/pgstat.c           |   1 +
 src/backend/utils/cache/relmapper.c           |   1 +
 src/backend/utils/fmgr/dfmgr.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   1 +
 src/backend/utils/misc/guc.c                  |   1 +
 src/backend/utils/misc/guc_tables.c           |   1 +
 src/backend/utils/misc/injection_point.c      |   2 +-
 src/common/controldata_utils.c                |   1 +
 src/include/common/file_utils.h               |  10 +-
 src/include/storage/fd.h                      |  26 -
 src/include/storage/file_utils.h              |  52 ++
 38 files changed, 763 insertions(+), 676 deletions(-)
 create mode 100644 src/backend/storage/file/file_utils.c
 create mode 100644 src/include/storage/file_utils.h

diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 914a0b56d16..19a0ca457c9 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -35,6 +35,7 @@
 #include "miscadmin.h"
 #include "storage/copydir.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "utils/guc.h"
 
 PG_MODULE_MAGIC_EXT(
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index deb4c2671b5..3dd33c0c0ec 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -37,6 +37,7 @@
 #include "storage/dsm.h"
 #include "storage/dsm_registry.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
 #include "storage/lwlock.h"
diff --git a/contrib/pg_stash_advice/stashpersist.c b/contrib/pg_stash_advice/stashpersist.c
index 5bdf4bddaae..f81aafa60ad 100644
--- a/contrib/pg_stash_advice/stashpersist.c
+++ b/contrib/pg_stash_advice/stashpersist.c
@@ -19,6 +19,7 @@
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
 #include "storage/proc.h"
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 92315627916..ec8ffb8d923 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -60,6 +60,7 @@
 #include "parser/analyze.h"
 #include "pgstat.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/ipc.h"
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 5a5398a76ae..a1ce8876968 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -119,6 +119,7 @@
 #include "storage/bufmgr.h"
 #include "storage/bulk_write.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/procarray.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index 47dd52d6749..e3e5e2be539 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -69,6 +69,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/shmem.h"
 #include "storage/shmem_internal.h"
 #include "utils/guc.h"
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 68e5f692d26..56342fe83d2 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -41,6 +41,7 @@
 #include "access/xlogdefs.h"
 #include "pgstat.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "utils/wait_event.h"
 
 /*
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index 1035e8b3fc7..b718d1aac21 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -97,6 +97,7 @@
 #include "replication/origin.h"
 #include "replication/syncrep.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/ipc.h"
 #include "storage/md.h"
 #include "storage/predicate.h"
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index a81912b7441..dd5e333b40c 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -87,6 +87,7 @@
 #include "replication/walsender.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/ipc.h"
 #include "storage/large_object.h"
 #include "storage/latch.h"
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 9a0c8097cb1..55ce3de1096 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -30,6 +30,7 @@
 #include "postmaster/startup.h"
 #include "replication/walsender.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/ipc.h"
 #include "utils/wait_event.h"
 
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c0ae4d3f63f..110d9614d62 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -53,6 +53,7 @@
 #include "replication/slotsync.h"
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
 #include "storage/pmsignal.h"
diff --git a/src/backend/backup/basebackup_server.c b/src/backend/backup/basebackup_server.c
index 3d44bf71d19..1cd2c130f2b 100644
--- a/src/backend/backup/basebackup_server.c
+++ b/src/backend/backup/basebackup_server.c
@@ -15,6 +15,7 @@
 #include "catalog/pg_authid.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "utils/acl.h"
 #include "utils/wait_event.h"
 
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index f0819d15ab7..27c4a0eaefa 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -56,6 +56,7 @@
 #include "replication/slot.h"
 #include "storage/copydir.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/md.h"
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index d073585c421..52b4300250c 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -58,6 +58,7 @@
 #include "nodes/pg_list.h"
 #include "nodes/queryjumble.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "tcop/utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
diff --git a/src/backend/jit/jit.c b/src/backend/jit/jit.c
index 3dc82b7b268..7666bf9401f 100644
--- a/src/backend/jit/jit.c
+++ b/src/backend/jit/jit.c
@@ -26,7 +26,7 @@
 #include "miscadmin.h"
 #include "nodes/execnodes.h"
 #include "portability/instr_time.h"
-#include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "utils/fmgrprotos.h"
 
 /* GUCs */
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index 4f12eaf2c85..e24088483ee 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -41,6 +41,7 @@
 #include "replication/walreceiver.h"
 #include "storage/aio_subsys.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
 #include "storage/lwlock.h"
diff --git a/src/backend/replication/logical/origin.c b/src/backend/replication/logical/origin.c
index c9dfb094c2b..b1b7b0caa14 100644
--- a/src/backend/replication/logical/origin.c
+++ b/src/backend/replication/logical/origin.c
@@ -86,6 +86,7 @@
 #include "replication/slot.h"
 #include "storage/condition_variable.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/subsystems.h"
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index b8992234924..b8e34702d62 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -136,6 +136,7 @@
 #include "replication/snapbuild.h"
 #include "replication/snapbuild_internal.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index d7fb9f5a67f..89969baa5db 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -52,6 +52,7 @@
 #include "replication/slot.h"
 #include "replication/walsender_private.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
diff --git a/src/backend/storage/file/Makefile b/src/backend/storage/file/Makefile
index 660ac51807e..af50f7cdda5 100644
--- a/src/backend/storage/file/Makefile
+++ b/src/backend/storage/file/Makefile
@@ -17,6 +17,7 @@ OBJS = \
 	copydir.o \
 	fd.o \
 	fileset.o \
+	file_utils.o \
 	reinit.o \
 	sharedfileset.o
 
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index 5ee141f13a5..f76538c8991 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -29,6 +29,7 @@
 #include "pgstat.h"
 #include "storage/copydir.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "utils/wait_event.h"
 
 /* GUCs */
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 733017cca5f..c8c223240f3 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -86,7 +86,6 @@
 #include <fcntl.h>
 
 #include "access/xact.h"
-#include "access/xlog.h"
 #include "catalog/pg_tablespace.h"
 #include "common/file_perm.h"
 #include "common/file_utils.h"
@@ -96,6 +95,7 @@
 #include "postmaster/startup.h"
 #include "storage/aio.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/ipc.h"
 #include "utils/guc.h"
 #include "utils/guc_hooks.h"
@@ -159,9 +159,6 @@ int			max_files_per_process = 1000;
  */
 int			max_safe_fds = FD_MINFREE;	/* default if not changed */
 
-/* Whether it is safe to continue running after fsync() fails. */
-bool		data_sync_retry = false;
-
 /* How SyncDataDirectory() should do its job. */
 int			recovery_init_sync_method = DATA_DIR_SYNC_METHOD_FSYNC;
 
@@ -348,8 +345,6 @@ static void pre_sync_fname(const char *fname, bool isdir, int elevel);
 static void datadir_fsync_fname(const char *fname, bool isdir, int elevel);
 static void unlink_if_exists_fname(const char *fname, bool isdir, int elevel);
 
-static int	fsync_parent_path(const char *fname, int elevel);
-
 
 /* ResourceOwner callbacks to hold virtual file descriptors */
 static void ResOwnerReleaseFile(Datum res);
@@ -376,514 +371,6 @@ ResourceOwnerForgetFile(ResourceOwner owner, File file)
 	ResourceOwnerForget(owner, Int32GetDatum(file), &file_resowner_desc);
 }
 
-/*
- * pg_fsync --- do fsync with or without writethrough
- */
-int
-pg_fsync(int fd)
-{
-#if !defined(WIN32) && defined(USE_ASSERT_CHECKING)
-	struct stat st;
-
-	/*
-	 * Some operating system implementations of fsync() have requirements
-	 * about the file access modes that were used when their file descriptor
-	 * argument was opened, and these requirements differ depending on whether
-	 * the file descriptor is for a directory.
-	 *
-	 * For any file descriptor that may eventually be handed to fsync(), we
-	 * should have opened it with access modes that are compatible with
-	 * fsync() on all supported systems, otherwise the code may not be
-	 * portable, even if it runs ok on the current system.
-	 *
-	 * We assert here that a descriptor for a file was opened with write
-	 * permissions (i.e., not O_RDONLY) and for a directory without write
-	 * permissions (O_RDONLY).  Notice that the assertion check is made even
-	 * if fsync() is disabled.
-	 *
-	 * If fstat() fails, ignore it and let the follow-up fsync() complain.
-	 */
-	if (fstat(fd, &st) == 0)
-	{
-		int			desc_flags = fcntl(fd, F_GETFL);
-
-		desc_flags &= O_ACCMODE;
-
-		if (S_ISDIR(st.st_mode))
-			Assert(desc_flags == O_RDONLY);
-		else
-			Assert(desc_flags != O_RDONLY);
-	}
-	errno = 0;
-#endif
-
-	/* #if is to skip the wal_sync_method test if there's no need for it */
-#if defined(HAVE_FSYNC_WRITETHROUGH)
-	if (wal_sync_method == WAL_SYNC_METHOD_FSYNC_WRITETHROUGH)
-		return pg_fsync_writethrough(fd);
-	else
-#endif
-		return pg_fsync_no_writethrough(fd);
-}
-
-
-/*
- * pg_fsync_no_writethrough --- same as fsync except does nothing if
- *	enableFsync is off
- */
-int
-pg_fsync_no_writethrough(int fd)
-{
-	int			rc;
-
-	if (!enableFsync)
-		return 0;
-
-retry:
-	rc = fsync(fd);
-
-	if (rc == -1 && errno == EINTR)
-		goto retry;
-
-	return rc;
-}
-
-/*
- * pg_fsync_writethrough
- */
-int
-pg_fsync_writethrough(int fd)
-{
-	if (enableFsync)
-	{
-#if defined(F_FULLFSYNC)
-		return (fcntl(fd, F_FULLFSYNC, 0) == -1) ? -1 : 0;
-#else
-		errno = ENOSYS;
-		return -1;
-#endif
-	}
-	else
-		return 0;
-}
-
-/*
- * pg_fdatasync --- same as fdatasync except does nothing if enableFsync is off
- */
-int
-pg_fdatasync(int fd)
-{
-	int			rc;
-
-	if (!enableFsync)
-		return 0;
-
-retry:
-	rc = fdatasync(fd);
-
-	if (rc == -1 && errno == EINTR)
-		goto retry;
-
-	return rc;
-}
-
-/*
- * pg_file_exists -- check that a file exists.
- *
- * This requires an absolute path to the file.  Returns true if the file is
- * not a directory, false otherwise.
- */
-bool
-pg_file_exists(const char *name)
-{
-	struct stat st;
-
-	Assert(name != NULL);
-
-	if (stat(name, &st) == 0)
-		return !S_ISDIR(st.st_mode);
-	else if (!(errno == ENOENT || errno == ENOTDIR || errno == EACCES))
-		ereport(ERROR,
-				(errcode_for_file_access(),
-				 errmsg("could not access file \"%s\": %m", name)));
-
-	return false;
-}
-
-/*
- * pg_flush_data --- advise OS that the described dirty data should be flushed
- *
- * offset of 0 with nbytes 0 means that the entire file should be flushed
- */
-void
-pg_flush_data(int fd, pgoff_t offset, pgoff_t nbytes)
-{
-	/*
-	 * Right now file flushing is primarily used to avoid making later
-	 * fsync()/fdatasync() calls have less impact. Thus don't trigger flushes
-	 * if fsyncs are disabled - that's a decision we might want to make
-	 * configurable at some point.
-	 */
-	if (!enableFsync)
-		return;
-
-	/*
-	 * We compile all alternatives that are supported on the current platform,
-	 * to find portability problems more easily.
-	 */
-#if defined(HAVE_SYNC_FILE_RANGE)
-	{
-		int			rc;
-		static bool not_implemented_by_kernel = false;
-
-		if (not_implemented_by_kernel)
-			return;
-
-retry:
-
-		/*
-		 * sync_file_range(SYNC_FILE_RANGE_WRITE), currently linux specific,
-		 * tells the OS that writeback for the specified blocks should be
-		 * started, but that we don't want to wait for completion.  Note that
-		 * this call might block if too much dirty data exists in the range.
-		 * This is the preferable method on OSs supporting it, as it works
-		 * reliably when available (contrast to msync()) and doesn't flush out
-		 * clean data (like FADV_DONTNEED).
-		 */
-		rc = sync_file_range(fd, offset, nbytes,
-							 SYNC_FILE_RANGE_WRITE);
-		if (rc != 0)
-		{
-			int			elevel;
-
-			if (errno == EINTR)
-				goto retry;
-
-			/*
-			 * For systems that don't have an implementation of
-			 * sync_file_range() such as Windows WSL, generate only one
-			 * warning and then suppress all further attempts by this process.
-			 */
-			if (errno == ENOSYS)
-			{
-				elevel = WARNING;
-				not_implemented_by_kernel = true;
-			}
-			else
-				elevel = data_sync_elevel(WARNING);
-
-			ereport(elevel,
-					(errcode_for_file_access(),
-					 errmsg("could not flush dirty data: %m")));
-		}
-
-		return;
-	}
-#endif
-#if !defined(WIN32) && defined(MS_ASYNC)
-	{
-		void	   *p;
-		static int	pagesize = 0;
-
-		/*
-		 * On several OSs msync(MS_ASYNC) on a mmap'ed file triggers
-		 * writeback. On linux it only does so if MS_SYNC is specified, but
-		 * then it does the writeback synchronously. Luckily all common linux
-		 * systems have sync_file_range().  This is preferable over
-		 * FADV_DONTNEED because it doesn't flush out clean data.
-		 *
-		 * We map the file (mmap()), tell the kernel to sync back the contents
-		 * (msync()), and then remove the mapping again (munmap()).
-		 */
-
-		/* mmap() needs actual length if we want to map whole file */
-		if (offset == 0 && nbytes == 0)
-		{
-			nbytes = lseek(fd, 0, SEEK_END);
-			if (nbytes < 0)
-			{
-				ereport(WARNING,
-						(errcode_for_file_access(),
-						 errmsg("could not determine dirty data size: %m")));
-				return;
-			}
-		}
-
-		/*
-		 * Some platforms reject partial-page mmap() attempts.  To deal with
-		 * that, just truncate the request to a page boundary.  If any extra
-		 * bytes don't get flushed, well, it's only a hint anyway.
-		 */
-
-		/* fetch pagesize only once */
-		if (pagesize == 0)
-			pagesize = sysconf(_SC_PAGESIZE);
-
-		/* align length to pagesize, dropping any fractional page */
-		if (pagesize > 0)
-			nbytes = (nbytes / pagesize) * pagesize;
-
-		/* fractional-page request is a no-op */
-		if (nbytes <= 0)
-			return;
-
-		/*
-		 * mmap could well fail, particularly on 32-bit platforms where there
-		 * may simply not be enough address space.  If so, silently fall
-		 * through to the next implementation.
-		 */
-		if (nbytes <= (pgoff_t) SSIZE_MAX)
-			p = mmap(NULL, nbytes, PROT_READ, MAP_SHARED, fd, offset);
-		else
-			p = MAP_FAILED;
-
-		if (p != MAP_FAILED)
-		{
-			int			rc;
-
-			rc = msync(p, (size_t) nbytes, MS_ASYNC);
-			if (rc != 0)
-			{
-				ereport(data_sync_elevel(WARNING),
-						(errcode_for_file_access(),
-						 errmsg("could not flush dirty data: %m")));
-				/* NB: need to fall through to munmap()! */
-			}
-
-			rc = munmap(p, (size_t) nbytes);
-			if (rc != 0)
-			{
-				/* FATAL error because mapping would remain */
-				ereport(FATAL,
-						(errcode_for_file_access(),
-						 errmsg("could not munmap() while flushing data: %m")));
-			}
-
-			return;
-		}
-	}
-#endif
-#if defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_DONTNEED)
-	{
-		int			rc;
-
-		/*
-		 * Signal the kernel that the passed in range should not be cached
-		 * anymore. This has the, desired, side effect of writing out dirty
-		 * data, and the, undesired, side effect of likely discarding useful
-		 * clean cached blocks.  For the latter reason this is the least
-		 * preferable method.
-		 */
-
-		rc = posix_fadvise(fd, offset, nbytes, POSIX_FADV_DONTNEED);
-
-		if (rc != 0)
-		{
-			/* don't error out, this is just a performance optimization */
-			ereport(WARNING,
-					(errcode_for_file_access(),
-					 errmsg("could not flush dirty data: %m")));
-		}
-
-		return;
-	}
-#endif
-}
-
-/*
- * Truncate an open file to a given length.
- */
-static int
-pg_ftruncate(int fd, pgoff_t length)
-{
-	int			ret;
-
-retry:
-	ret = ftruncate(fd, length);
-
-	if (ret == -1 && errno == EINTR)
-		goto retry;
-
-	return ret;
-}
-
-/*
- * Truncate a file to a given length by name.
- */
-int
-pg_truncate(const char *path, pgoff_t length)
-{
-	int			ret;
-#ifdef WIN32
-	int			save_errno;
-	int			fd;
-
-	fd = OpenTransientFile(path, O_RDWR | PG_BINARY);
-	if (fd >= 0)
-	{
-		ret = pg_ftruncate(fd, length);
-		save_errno = errno;
-		CloseTransientFile(fd);
-		errno = save_errno;
-	}
-	else
-		ret = -1;
-#else
-
-retry:
-	ret = truncate(path, length);
-
-	if (ret == -1 && errno == EINTR)
-		goto retry;
-#endif
-
-	return ret;
-}
-
-/*
- * fsync_fname -- fsync a file or directory, handling errors properly
- *
- * Try to fsync a file or directory. When doing the latter, ignore errors that
- * indicate the OS just doesn't allow/require fsyncing directories.
- */
-void
-fsync_fname(const char *fname, bool isdir)
-{
-	fsync_fname_ext(fname, isdir, false, data_sync_elevel(ERROR));
-}
-
-/*
- * durable_rename -- rename(2) wrapper, issuing fsyncs required for durability
- *
- * This routine ensures that, after returning, the effect of renaming file
- * persists in case of a crash. A crash while this routine is running will
- * leave you with either the pre-existing or the moved file in place of the
- * new file; no mixed state or truncated files are possible.
- *
- * It does so by using fsync on the old filename and the possibly existing
- * target filename before the rename, and the target file and directory after.
- *
- * Note that rename() cannot be used across arbitrary directories, as they
- * might not be on the same filesystem. Therefore this routine does not
- * support renaming across directories.
- *
- * Log errors with the caller specified severity.
- *
- * Returns 0 if the operation succeeded, -1 otherwise. Note that errno is not
- * valid upon return.
- */
-int
-durable_rename(const char *oldfile, const char *newfile, int elevel)
-{
-	int			fd;
-
-	/*
-	 * First fsync the old and target path (if it exists), to ensure that they
-	 * are properly persistent on disk. Syncing the target file is not
-	 * strictly necessary, but it makes it easier to reason about crashes;
-	 * because it's then guaranteed that either source or target file exists
-	 * after a crash.
-	 */
-	if (fsync_fname_ext(oldfile, false, false, elevel) != 0)
-		return -1;
-
-	fd = OpenTransientFile(newfile, PG_BINARY | O_RDWR);
-	if (fd < 0)
-	{
-		if (errno != ENOENT)
-		{
-			ereport(elevel,
-					(errcode_for_file_access(),
-					 errmsg("could not open file \"%s\": %m", newfile)));
-			return -1;
-		}
-	}
-	else
-	{
-		if (pg_fsync(fd) != 0)
-		{
-			int			save_errno;
-
-			/* close file upon error, might not be in transaction context */
-			save_errno = errno;
-			CloseTransientFile(fd);
-			errno = save_errno;
-
-			ereport(elevel,
-					(errcode_for_file_access(),
-					 errmsg("could not fsync file \"%s\": %m", newfile)));
-			return -1;
-		}
-
-		if (CloseTransientFile(fd) != 0)
-		{
-			ereport(elevel,
-					(errcode_for_file_access(),
-					 errmsg("could not close file \"%s\": %m", newfile)));
-			return -1;
-		}
-	}
-
-	/* Time to do the real deal... */
-	if (rename(oldfile, newfile) < 0)
-	{
-		ereport(elevel,
-				(errcode_for_file_access(),
-				 errmsg("could not rename file \"%s\" to \"%s\": %m",
-						oldfile, newfile)));
-		return -1;
-	}
-
-	/*
-	 * To guarantee renaming the file is persistent, fsync the file with its
-	 * new name, and its containing directory.
-	 */
-	if (fsync_fname_ext(newfile, false, false, elevel) != 0)
-		return -1;
-
-	if (fsync_parent_path(newfile, elevel) != 0)
-		return -1;
-
-	return 0;
-}
-
-/*
- * durable_unlink -- remove a file in a durable manner
- *
- * This routine ensures that, after returning, the effect of removing file
- * persists in case of a crash. A crash while this routine is running will
- * leave the system in no mixed state.
- *
- * It does so by using fsync on the parent directory of the file after the
- * actual removal is done.
- *
- * Log errors with the severity specified by caller.
- *
- * Returns 0 if the operation succeeded, -1 otherwise. Note that errno is not
- * valid upon return.
- */
-int
-durable_unlink(const char *fname, int elevel)
-{
-	if (unlink(fname) < 0)
-	{
-		ereport(elevel,
-				(errcode_for_file_access(),
-				 errmsg("could not remove file \"%s\": %m",
-						fname)));
-		return -1;
-	}
-
-	/*
-	 * To guarantee that the removal of the file is persistent, fsync its
-	 * parent directory.
-	 */
-	if (fsync_parent_path(fname, elevel) != 0)
-		return -1;
-
-	return 0;
-}
-
 /*
  * InitFileAccess --- initialize this module during backend startup
  *
@@ -3800,112 +3287,6 @@ unlink_if_exists_fname(const char *fname, bool isdir, int elevel)
 	}
 }
 
-/*
- * fsync_fname_ext -- Try to fsync a file or directory
- *
- * If ignore_perm is true, ignore errors upon trying to open unreadable
- * files. Logs other errors at a caller-specified level.
- *
- * Returns 0 if the operation succeeded, -1 otherwise.
- */
-int
-fsync_fname_ext(const char *fname, bool isdir, bool ignore_perm, int elevel)
-{
-	int			fd;
-	int			flags;
-	int			returncode;
-
-	/*
-	 * Some OSs require directories to be opened read-only whereas other
-	 * systems don't allow us to fsync files opened read-only; so we need both
-	 * cases here.  Using O_RDWR will cause us to fail to fsync files that are
-	 * not writable by our userid, but we assume that's OK.
-	 */
-	flags = PG_BINARY;
-	if (!isdir)
-		flags |= O_RDWR;
-	else
-		flags |= O_RDONLY;
-
-	fd = OpenTransientFile(fname, flags);
-
-	/*
-	 * Some OSs don't allow us to open directories at all (Windows returns
-	 * EACCES), just ignore the error in that case.  If desired also silently
-	 * ignoring errors about unreadable files. Log others.
-	 */
-	if (fd < 0 && isdir && (errno == EISDIR || errno == EACCES))
-		return 0;
-	else if (fd < 0 && ignore_perm && errno == EACCES)
-		return 0;
-	else if (fd < 0)
-	{
-		ereport(elevel,
-				(errcode_for_file_access(),
-				 errmsg("could not open file \"%s\": %m", fname)));
-		return -1;
-	}
-
-	returncode = pg_fsync(fd);
-
-	/*
-	 * Some OSes don't allow us to fsync directories at all, so we can ignore
-	 * those errors. Anything else needs to be logged.
-	 */
-	if (returncode != 0 && !(isdir && (errno == EBADF || errno == EINVAL)))
-	{
-		int			save_errno;
-
-		/* close file upon error, might not be in transaction context */
-		save_errno = errno;
-		(void) CloseTransientFile(fd);
-		errno = save_errno;
-
-		ereport(elevel,
-				(errcode_for_file_access(),
-				 errmsg("could not fsync file \"%s\": %m", fname)));
-		return -1;
-	}
-
-	if (CloseTransientFile(fd) != 0)
-	{
-		ereport(elevel,
-				(errcode_for_file_access(),
-				 errmsg("could not close file \"%s\": %m", fname)));
-		return -1;
-	}
-
-	return 0;
-}
-
-/*
- * fsync_parent_path -- fsync the parent path of a file or directory
- *
- * This is aimed at making file operations persistent on disk in case of
- * an OS crash or power failure.
- */
-static int
-fsync_parent_path(const char *fname, int elevel)
-{
-	char		parentpath[MAXPGPATH];
-
-	strlcpy(parentpath, fname, MAXPGPATH);
-	get_parent_directory(parentpath);
-
-	/*
-	 * get_parent_directory() returns an empty string if the input argument is
-	 * just a file name (see comments in path.c), so handle that as being the
-	 * current directory.
-	 */
-	if (strlen(parentpath) == 0)
-		strlcpy(parentpath, ".", MAXPGPATH);
-
-	if (fsync_fname_ext(parentpath, true, false, elevel) != 0)
-		return -1;
-
-	return 0;
-}
-
 /*
  * Create a PostgreSQL data sub-directory
  *
@@ -3930,29 +3311,6 @@ MakePGDirectory(const char *directoryName)
 	return mkdir(directoryName, pg_dir_create_mode);
 }
 
-/*
- * Return the passed-in error level, or PANIC if data_sync_retry is off.
- *
- * Failure to fsync any data file is cause for immediate panic, unless
- * data_sync_retry is enabled.  Data may have been written to the operating
- * system and removed from our buffer pool already, and if we are running on
- * an operating system that forgets dirty data on write-back failure, there
- * may be only one copy of the data remaining: in the WAL.  A later attempt to
- * fsync again might falsely report success.  Therefore we must not allow any
- * further checkpoints to be attempted.  data_sync_retry can in theory be
- * enabled on systems known not to drop dirty buffered data on write-back
- * failure (with the likely outcome that checkpoints will continue to fail
- * until the underlying problem is fixed).
- *
- * Any code that reports a failure from fsync() or related functions should
- * filter the error level with this function.
- */
-int
-data_sync_elevel(int elevel)
-{
-	return data_sync_retry ? elevel : PANIC;
-}
-
 bool
 check_debug_io_direct(char **newval, void **extra, GucSource source)
 {
diff --git a/src/backend/storage/file/file_utils.c b/src/backend/storage/file/file_utils.c
new file mode 100644
index 00000000000..cc29498f05a
--- /dev/null
+++ b/src/backend/storage/file/file_utils.c
@@ -0,0 +1,670 @@
+/*-------------------------------------------------------------------------
+ *
+ * file_utils.c
+ *	  Assorted portability and utility functions to work on files
+ *
+ * There's a file with the same name in src/common.  It contains
+ * frontend versions of some of these functions.
+ *
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/storage/file/file_utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "access/xlog.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "storage/file_utils.h"
+
+/* GUC: Whether it is safe to continue running after fsync() fails. */
+bool		data_sync_retry = false;
+
+static int	fsync_parent_path(const char *fname, int elevel);
+
+/*
+ * Return the passed-in error level, or PANIC if data_sync_retry is off.
+ *
+ * Failure to fsync any data file is cause for immediate panic, unless
+ * data_sync_retry is enabled.  Data may have been written to the operating
+ * system and removed from our buffer pool already, and if we are running on
+ * an operating system that forgets dirty data on write-back failure, there
+ * may be only one copy of the data remaining: in the WAL.  A later attempt to
+ * fsync again might falsely report success.  Therefore we must not allow any
+ * further checkpoints to be attempted.  data_sync_retry can in theory be
+ * enabled on systems known not to drop dirty buffered data on write-back
+ * failure (with the likely outcome that checkpoints will continue to fail
+ * until the underlying problem is fixed).
+ *
+ * Any code that reports a failure from fsync() or related functions should
+ * filter the error level with this function.
+ */
+int
+data_sync_elevel(int elevel)
+{
+	return data_sync_retry ? elevel : PANIC;
+}
+
+/*
+ * pg_fsync --- do fsync with or without writethrough
+ */
+int
+pg_fsync(int fd)
+{
+#if !defined(WIN32) && defined(USE_ASSERT_CHECKING)
+	struct stat st;
+
+	/*
+	 * Some operating system implementations of fsync() have requirements
+	 * about the file access modes that were used when their file descriptor
+	 * argument was opened, and these requirements differ depending on whether
+	 * the file descriptor is for a directory.
+	 *
+	 * For any file descriptor that may eventually be handed to fsync(), we
+	 * should have opened it with access modes that are compatible with
+	 * fsync() on all supported systems, otherwise the code may not be
+	 * portable, even if it runs ok on the current system.
+	 *
+	 * We assert here that a descriptor for a file was opened with write
+	 * permissions (i.e., not O_RDONLY) and for a directory without write
+	 * permissions (O_RDONLY).  Notice that the assertion check is made even
+	 * if fsync() is disabled.
+	 *
+	 * If fstat() fails, ignore it and let the follow-up fsync() complain.
+	 */
+	if (fstat(fd, &st) == 0)
+	{
+		int			desc_flags = fcntl(fd, F_GETFL);
+
+		desc_flags &= O_ACCMODE;
+
+		if (S_ISDIR(st.st_mode))
+			Assert(desc_flags == O_RDONLY);
+		else
+			Assert(desc_flags != O_RDONLY);
+	}
+	errno = 0;
+#endif
+
+	/* #if is to skip the wal_sync_method test if there's no need for it */
+#if defined(HAVE_FSYNC_WRITETHROUGH)
+	if (wal_sync_method == WAL_SYNC_METHOD_FSYNC_WRITETHROUGH)
+		return pg_fsync_writethrough(fd);
+	else
+#endif
+		return pg_fsync_no_writethrough(fd);
+}
+
+
+/*
+ * pg_fsync_no_writethrough --- same as fsync except does nothing if
+ *	enableFsync is off
+ */
+int
+pg_fsync_no_writethrough(int fd)
+{
+	int			rc;
+
+	if (!enableFsync)
+		return 0;
+
+retry:
+	rc = fsync(fd);
+
+	if (rc == -1 && errno == EINTR)
+		goto retry;
+
+	return rc;
+}
+
+/*
+ * pg_fsync_writethrough
+ */
+int
+pg_fsync_writethrough(int fd)
+{
+	if (enableFsync)
+	{
+#if defined(F_FULLFSYNC)
+		return (fcntl(fd, F_FULLFSYNC, 0) == -1) ? -1 : 0;
+#else
+		errno = ENOSYS;
+		return -1;
+#endif
+	}
+	else
+		return 0;
+}
+
+/*
+ * pg_fdatasync --- same as fdatasync except does nothing if enableFsync is off
+ */
+int
+pg_fdatasync(int fd)
+{
+	int			rc;
+
+	if (!enableFsync)
+		return 0;
+
+retry:
+	rc = fdatasync(fd);
+
+	if (rc == -1 && errno == EINTR)
+		goto retry;
+
+	return rc;
+}
+
+/*
+ * pg_file_exists -- check that a file exists.
+ *
+ * This requires an absolute path to the file.  Returns true if the file is
+ * not a directory, false otherwise.
+ */
+bool
+pg_file_exists(const char *name)
+{
+	struct stat st;
+
+	Assert(name != NULL);
+
+	if (stat(name, &st) == 0)
+		return !S_ISDIR(st.st_mode);
+	else if (!(errno == ENOENT || errno == ENOTDIR || errno == EACCES))
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not access file \"%s\": %m", name)));
+
+	return false;
+}
+
+/*
+ * pg_flush_data --- advise OS that the described dirty data should be flushed
+ *
+ * offset of 0 with nbytes 0 means that the entire file should be flushed
+ */
+void
+pg_flush_data(int fd, pgoff_t offset, pgoff_t nbytes)
+{
+	/*
+	 * Right now file flushing is primarily used to avoid making later
+	 * fsync()/fdatasync() calls have less impact. Thus don't trigger flushes
+	 * if fsyncs are disabled - that's a decision we might want to make
+	 * configurable at some point.
+	 */
+	if (!enableFsync)
+		return;
+
+	/*
+	 * We compile all alternatives that are supported on the current platform,
+	 * to find portability problems more easily.
+	 */
+#if defined(HAVE_SYNC_FILE_RANGE)
+	{
+		int			rc;
+		static bool not_implemented_by_kernel = false;
+
+		if (not_implemented_by_kernel)
+			return;
+
+retry:
+
+		/*
+		 * sync_file_range(SYNC_FILE_RANGE_WRITE), currently linux specific,
+		 * tells the OS that writeback for the specified blocks should be
+		 * started, but that we don't want to wait for completion.  Note that
+		 * this call might block if too much dirty data exists in the range.
+		 * This is the preferable method on OSs supporting it, as it works
+		 * reliably when available (contrast to msync()) and doesn't flush out
+		 * clean data (like FADV_DONTNEED).
+		 */
+		rc = sync_file_range(fd, offset, nbytes,
+							 SYNC_FILE_RANGE_WRITE);
+		if (rc != 0)
+		{
+			int			elevel;
+
+			if (errno == EINTR)
+				goto retry;
+
+			/*
+			 * For systems that don't have an implementation of
+			 * sync_file_range() such as Windows WSL, generate only one
+			 * warning and then suppress all further attempts by this process.
+			 */
+			if (errno == ENOSYS)
+			{
+				elevel = WARNING;
+				not_implemented_by_kernel = true;
+			}
+			else
+				elevel = data_sync_elevel(WARNING);
+
+			ereport(elevel,
+					(errcode_for_file_access(),
+					 errmsg("could not flush dirty data: %m")));
+		}
+
+		return;
+	}
+#endif
+#if !defined(WIN32) && defined(MS_ASYNC)
+	{
+		void	   *p;
+		static int	pagesize = 0;
+
+		/*
+		 * On several OSs msync(MS_ASYNC) on a mmap'ed file triggers
+		 * writeback. On linux it only does so if MS_SYNC is specified, but
+		 * then it does the writeback synchronously. Luckily all common linux
+		 * systems have sync_file_range().  This is preferable over
+		 * FADV_DONTNEED because it doesn't flush out clean data.
+		 *
+		 * We map the file (mmap()), tell the kernel to sync back the contents
+		 * (msync()), and then remove the mapping again (munmap()).
+		 */
+
+		/* mmap() needs actual length if we want to map whole file */
+		if (offset == 0 && nbytes == 0)
+		{
+			nbytes = lseek(fd, 0, SEEK_END);
+			if (nbytes < 0)
+			{
+				ereport(WARNING,
+						(errcode_for_file_access(),
+						 errmsg("could not determine dirty data size: %m")));
+				return;
+			}
+		}
+
+		/*
+		 * Some platforms reject partial-page mmap() attempts.  To deal with
+		 * that, just truncate the request to a page boundary.  If any extra
+		 * bytes don't get flushed, well, it's only a hint anyway.
+		 */
+
+		/* fetch pagesize only once */
+		if (pagesize == 0)
+			pagesize = sysconf(_SC_PAGESIZE);
+
+		/* align length to pagesize, dropping any fractional page */
+		if (pagesize > 0)
+			nbytes = (nbytes / pagesize) * pagesize;
+
+		/* fractional-page request is a no-op */
+		if (nbytes <= 0)
+			return;
+
+		/*
+		 * mmap could well fail, particularly on 32-bit platforms where there
+		 * may simply not be enough address space.  If so, silently fall
+		 * through to the next implementation.
+		 */
+		if (nbytes <= (pgoff_t) SSIZE_MAX)
+			p = mmap(NULL, nbytes, PROT_READ, MAP_SHARED, fd, offset);
+		else
+			p = MAP_FAILED;
+
+		if (p != MAP_FAILED)
+		{
+			int			rc;
+
+			rc = msync(p, (size_t) nbytes, MS_ASYNC);
+			if (rc != 0)
+			{
+				ereport(data_sync_elevel(WARNING),
+						(errcode_for_file_access(),
+						 errmsg("could not flush dirty data: %m")));
+				/* NB: need to fall through to munmap()! */
+			}
+
+			rc = munmap(p, (size_t) nbytes);
+			if (rc != 0)
+			{
+				/* FATAL error because mapping would remain */
+				ereport(FATAL,
+						(errcode_for_file_access(),
+						 errmsg("could not munmap() while flushing data: %m")));
+			}
+
+			return;
+		}
+	}
+#endif
+#if defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_DONTNEED)
+	{
+		int			rc;
+
+		/*
+		 * Signal the kernel that the passed in range should not be cached
+		 * anymore. This has the, desired, side effect of writing out dirty
+		 * data, and the, undesired, side effect of likely discarding useful
+		 * clean cached blocks.  For the latter reason this is the least
+		 * preferable method.
+		 */
+
+		rc = posix_fadvise(fd, offset, nbytes, POSIX_FADV_DONTNEED);
+
+		if (rc != 0)
+		{
+			/* don't error out, this is just a performance optimization */
+			ereport(WARNING,
+					(errcode_for_file_access(),
+					 errmsg("could not flush dirty data: %m")));
+		}
+
+		return;
+	}
+#endif
+}
+
+/*
+ * Truncate an open file to a given length.
+ */
+int
+pg_ftruncate(int fd, pgoff_t length)
+{
+	int			ret;
+
+retry:
+	ret = ftruncate(fd, length);
+
+	if (ret == -1 && errno == EINTR)
+		goto retry;
+
+	return ret;
+}
+
+/*
+ * Truncate a file to a given length by name.
+ */
+int
+pg_truncate(const char *path, pgoff_t length)
+{
+	int			ret;
+#ifdef WIN32
+	int			save_errno;
+	int			fd;
+
+	fd = OpenTransientFile(path, O_RDWR | PG_BINARY);
+	if (fd >= 0)
+	{
+		ret = pg_ftruncate(fd, length);
+		save_errno = errno;
+		CloseTransientFile(fd);
+		errno = save_errno;
+	}
+	else
+		ret = -1;
+#else
+
+retry:
+	ret = truncate(path, length);
+
+	if (ret == -1 && errno == EINTR)
+		goto retry;
+#endif
+
+	return ret;
+}
+
+/*
+ * fsync_fname -- fsync a file or directory, handling errors properly
+ *
+ * Try to fsync a file or directory. When doing the latter, ignore errors that
+ * indicate the OS just doesn't allow/require fsyncing directories.
+ */
+void
+fsync_fname(const char *fname, bool isdir)
+{
+	fsync_fname_ext(fname, isdir, false, data_sync_elevel(ERROR));
+}
+
+/*
+ * fsync_fname_ext -- Try to fsync a file or directory
+ *
+ * If ignore_perm is true, ignore errors upon trying to open unreadable
+ * files. Logs other errors at a caller-specified level.
+ *
+ * Returns 0 if the operation succeeded, -1 otherwise.
+ */
+int
+fsync_fname_ext(const char *fname, bool isdir, bool ignore_perm, int elevel)
+{
+	int			fd;
+	int			flags;
+	int			returncode;
+
+	/*
+	 * Some OSs require directories to be opened read-only whereas other
+	 * systems don't allow us to fsync files opened read-only; so we need both
+	 * cases here.  Using O_RDWR will cause us to fail to fsync files that are
+	 * not writable by our userid, but we assume that's OK.
+	 */
+	flags = PG_BINARY;
+	if (!isdir)
+		flags |= O_RDWR;
+	else
+		flags |= O_RDONLY;
+
+	fd = OpenTransientFile(fname, flags);
+
+	/*
+	 * Some OSs don't allow us to open directories at all (Windows returns
+	 * EACCES), just ignore the error in that case.  If desired also silently
+	 * ignoring errors about unreadable files. Log others.
+	 */
+	if (fd < 0 && isdir && (errno == EISDIR || errno == EACCES))
+		return 0;
+	else if (fd < 0 && ignore_perm && errno == EACCES)
+		return 0;
+	else if (fd < 0)
+	{
+		ereport(elevel,
+				(errcode_for_file_access(),
+				 errmsg("could not open file \"%s\": %m", fname)));
+		return -1;
+	}
+
+	returncode = pg_fsync(fd);
+
+	/*
+	 * Some OSes don't allow us to fsync directories at all, so we can ignore
+	 * those errors. Anything else needs to be logged.
+	 */
+	if (returncode != 0 && !(isdir && (errno == EBADF || errno == EINVAL)))
+	{
+		int			save_errno;
+
+		/* close file upon error, might not be in transaction context */
+		save_errno = errno;
+		(void) CloseTransientFile(fd);
+		errno = save_errno;
+
+		ereport(elevel,
+				(errcode_for_file_access(),
+				 errmsg("could not fsync file \"%s\": %m", fname)));
+		return -1;
+	}
+
+	if (CloseTransientFile(fd) != 0)
+	{
+		ereport(elevel,
+				(errcode_for_file_access(),
+				 errmsg("could not close file \"%s\": %m", fname)));
+		return -1;
+	}
+
+	return 0;
+}
+
+
+/*
+ * durable_rename -- rename(2) wrapper, issuing fsyncs required for durability
+ *
+ * This routine ensures that, after returning, the effect of renaming file
+ * persists in case of a crash. A crash while this routine is running will
+ * leave you with either the pre-existing or the moved file in place of the
+ * new file; no mixed state or truncated files are possible.
+ *
+ * It does so by using fsync on the old filename and the possibly existing
+ * target filename before the rename, and the target file and directory after.
+ *
+ * Note that rename() cannot be used across arbitrary directories, as they
+ * might not be on the same filesystem. Therefore this routine does not
+ * support renaming across directories.
+ *
+ * Log errors with the caller specified severity.
+ *
+ * Returns 0 if the operation succeeded, -1 otherwise. Note that errno is not
+ * valid upon return.
+ */
+int
+durable_rename(const char *oldfile, const char *newfile, int elevel)
+{
+	int			fd;
+
+	/*
+	 * First fsync the old and target path (if it exists), to ensure that they
+	 * are properly persistent on disk. Syncing the target file is not
+	 * strictly necessary, but it makes it easier to reason about crashes;
+	 * because it's then guaranteed that either source or target file exists
+	 * after a crash.
+	 */
+	if (fsync_fname_ext(oldfile, false, false, elevel) != 0)
+		return -1;
+
+	fd = OpenTransientFile(newfile, PG_BINARY | O_RDWR);
+	if (fd < 0)
+	{
+		if (errno != ENOENT)
+		{
+			ereport(elevel,
+					(errcode_for_file_access(),
+					 errmsg("could not open file \"%s\": %m", newfile)));
+			return -1;
+		}
+	}
+	else
+	{
+		if (pg_fsync(fd) != 0)
+		{
+			int			save_errno;
+
+			/* close file upon error, might not be in transaction context */
+			save_errno = errno;
+			CloseTransientFile(fd);
+			errno = save_errno;
+
+			ereport(elevel,
+					(errcode_for_file_access(),
+					 errmsg("could not fsync file \"%s\": %m", newfile)));
+			return -1;
+		}
+
+		if (CloseTransientFile(fd) != 0)
+		{
+			ereport(elevel,
+					(errcode_for_file_access(),
+					 errmsg("could not close file \"%s\": %m", newfile)));
+			return -1;
+		}
+	}
+
+	/* Time to do the real deal... */
+	if (rename(oldfile, newfile) < 0)
+	{
+		ereport(elevel,
+				(errcode_for_file_access(),
+				 errmsg("could not rename file \"%s\" to \"%s\": %m",
+						oldfile, newfile)));
+		return -1;
+	}
+
+	/*
+	 * To guarantee renaming the file is persistent, fsync the file with its
+	 * new name, and its containing directory.
+	 */
+	if (fsync_fname_ext(newfile, false, false, elevel) != 0)
+		return -1;
+
+	if (fsync_parent_path(newfile, elevel) != 0)
+		return -1;
+
+	return 0;
+}
+
+/*
+ * durable_unlink -- remove a file in a durable manner
+ *
+ * This routine ensures that, after returning, the effect of removing file
+ * persists in case of a crash. A crash while this routine is running will
+ * leave the system in no mixed state.
+ *
+ * It does so by using fsync on the parent directory of the file after the
+ * actual removal is done.
+ *
+ * Log errors with the severity specified by caller.
+ *
+ * Returns 0 if the operation succeeded, -1 otherwise. Note that errno is not
+ * valid upon return.
+ */
+int
+durable_unlink(const char *fname, int elevel)
+{
+	if (unlink(fname) < 0)
+	{
+		ereport(elevel,
+				(errcode_for_file_access(),
+				 errmsg("could not remove file \"%s\": %m",
+						fname)));
+		return -1;
+	}
+
+	/*
+	 * To guarantee that the removal of the file is persistent, fsync its
+	 * parent directory.
+	 */
+	if (fsync_parent_path(fname, elevel) != 0)
+		return -1;
+
+	return 0;
+}
+
+/*
+ * fsync_parent_path -- fsync the parent path of a file or directory
+ *
+ * This is aimed at making file operations persistent on disk in case of
+ * an OS crash or power failure.
+ */
+static int
+fsync_parent_path(const char *fname, int elevel)
+{
+	char		parentpath[MAXPGPATH];
+
+	strlcpy(parentpath, fname, MAXPGPATH);
+	get_parent_directory(parentpath);
+
+	/*
+	 * get_parent_directory() returns an empty string if the input argument is
+	 * just a file name (see comments in path.c), so handle that as being the
+	 * current directory.
+	 */
+	if (strlen(parentpath) == 0)
+		strlcpy(parentpath, ".", MAXPGPATH);
+
+	if (fsync_fname_ext(parentpath, true, false, elevel) != 0)
+		return -1;
+
+	return 0;
+}
diff --git a/src/backend/storage/file/meson.build b/src/backend/storage/file/meson.build
index 795402589b0..7366f971e9d 100644
--- a/src/backend/storage/file/meson.build
+++ b/src/backend/storage/file/meson.build
@@ -5,6 +5,7 @@ backend_sources += files(
   'copydir.c',
   'fd.c',
   'fileset.c',
+  'file_utils.c',
   'reinit.c',
   'sharedfileset.c',
 )
diff --git a/src/backend/storage/file/reinit.c b/src/backend/storage/file/reinit.c
index 25fa2151309..0d285679c80 100644
--- a/src/backend/storage/file/reinit.c
+++ b/src/backend/storage/file/reinit.c
@@ -20,6 +20,7 @@
 #include "postmaster/startup.h"
 #include "storage/copydir.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/reinit.h"
 #include "utils/hsearch.h"
 #include "utils/memutils.h"
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index dee29037b16..5031db96e16 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -35,6 +35,7 @@
 #include "storage/aio.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/md.h"
 #include "storage/relfilelocator.h"
 #include "storage/smgr.h"
diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c
index 2c964b6f3d9..7db3c66547e 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -26,7 +26,7 @@
 #include "pgstat.h"
 #include "portability/instr_time.h"
 #include "postmaster/bgwriter.h"
-#include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/latch.h"
 #include "storage/md.h"
 #include "utils/hsearch.h"
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index c4fa14f138f..545125ef2b5 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -108,6 +108,7 @@
 #include "lib/dshash.h"
 #include "pgstat.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/ipc.h"
 #include "storage/lwlock.h"
 #include "utils/guc_hooks.h"
diff --git a/src/backend/utils/cache/relmapper.c b/src/backend/utils/cache/relmapper.c
index 3aaf466868d..ff71c699f86 100644
--- a/src/backend/utils/cache/relmapper.c
+++ b/src/backend/utils/cache/relmapper.c
@@ -51,6 +51,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/lwlock.h"
 #include "utils/inval.h"
 #include "utils/relmapper.h"
diff --git a/src/backend/utils/fmgr/dfmgr.c b/src/backend/utils/fmgr/dfmgr.c
index e636cc81cf8..fd4c57813da 100644
--- a/src/backend/utils/fmgr/dfmgr.c
+++ b/src/backend/utils/fmgr/dfmgr.c
@@ -23,7 +23,7 @@
 #include "fmgr.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
-#include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/shmem.h"
 #include "utils/hsearch.h"
 
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 7ffc808073a..fbfac106772 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -42,6 +42,7 @@
 #include "postmaster/postmaster.h"
 #include "replication/slotsync.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
 #include "storage/pg_shmem.h"
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 774bbc9be5f..5c6ed106a49 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -42,6 +42,7 @@
 #include "parser/scansup.h"
 #include "port/pg_bitutils.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
 #include "tcop/tcopprot.h"
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 90aa374b3ec..d025126ea7f 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -81,6 +81,7 @@
 #include "storage/bufpage.h"
 #include "storage/copydir.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/io_worker.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
diff --git a/src/backend/utils/misc/injection_point.c b/src/backend/utils/misc/injection_point.c
index 272ef5e578a..eefd15b5665 100644
--- a/src/backend/utils/misc/injection_point.c
+++ b/src/backend/utils/misc/injection_point.c
@@ -25,7 +25,7 @@
 
 #include "fmgr.h"
 #include "miscadmin.h"
-#include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
 #include "storage/subsystems.h"
diff --git a/src/common/controldata_utils.c b/src/common/controldata_utils.c
index 4ab116afcde..dfc037a1a2c 100644
--- a/src/common/controldata_utils.c
+++ b/src/common/controldata_utils.c
@@ -37,6 +37,7 @@
 #ifndef FRONTEND
 #include "pgstat.h"
 #include "storage/fd.h"
+#include "storage/file_utils.h"
 #include "utils/wait_event.h"
 #endif
 
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index d6415424b60..e67d24ec7d8 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -10,8 +10,8 @@
  *
  *-------------------------------------------------------------------------
  */
-#ifndef FILE_UTILS_H
-#define FILE_UTILS_H
+#ifndef COMMON_FILE_UTILS_H
+#define COMMON_FILE_UTILS_H
 
 #include <dirent.h>
 
@@ -32,6 +32,10 @@ typedef enum DataDirSyncMethod
 
 struct iovec;					/* avoid including port/pg_iovec.h here */
 
+/*
+ * These functions are frontend-only.  For backend code, they have
+ * counterparts in storage/file_utils.h
+ */
 #ifdef FRONTEND
 extern int	pre_sync_fname(const char *fname, bool isdir);
 extern int	fsync_fname(const char *fname, bool isdir);
@@ -63,4 +67,4 @@ extern ssize_t pg_pwrite_zeros(int fd, size_t size, pgoff_t offset);
 #define PG_TEMP_FILES_DIR "pgsql_tmp"
 #define PG_TEMP_FILE_PREFIX "pgsql_tmp"
 
-#endif							/* FILE_UTILS_H */
+#endif							/* COMMON_FILE_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 8ac466fd346..f02d0f34468 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -68,7 +68,6 @@ enum FileExtendMethod
 
 /* GUC parameter */
 extern PGDLLIMPORT int max_files_per_process;
-extern PGDLLIMPORT bool data_sync_retry;
 extern PGDLLIMPORT int recovery_init_sync_method;
 extern PGDLLIMPORT int io_direct_flags;
 extern PGDLLIMPORT int file_extend_method;
@@ -78,19 +77,6 @@ extern PGDLLIMPORT int file_extend_method;
  */
 extern PGDLLIMPORT int max_safe_fds;
 
-/*
- * On Windows, we have to interpret EACCES as possibly meaning the same as
- * ENOENT, because if a file is unlinked-but-not-yet-gone on that platform,
- * that's what you get.  Ugh.  This code is designed so that we don't
- * actually believe these cases are okay without further evidence (namely,
- * a pending fsync request getting canceled ... see ProcessSyncRequests).
- */
-#ifndef WIN32
-#define FILE_POSSIBLY_DELETED(err)	((err) == ENOENT)
-#else
-#define FILE_POSSIBLY_DELETED(err)	((err) == ENOENT || (err) == EACCES)
-#endif
-
 /*
  * O_DIRECT is not standard, but almost every Unix has it.  We translate it
  * to the appropriate Windows flag in src/port/open.c.  We simulate it with
@@ -207,19 +193,7 @@ extern void RemovePgTempFilesInDir(const char *tmpdirname, bool missing_ok,
 								   bool unlink_all);
 extern bool looks_like_temp_rel_name(const char *name);
 
-extern int	pg_fsync(int fd);
-extern int	pg_fsync_no_writethrough(int fd);
-extern int	pg_fsync_writethrough(int fd);
-extern int	pg_fdatasync(int fd);
-extern bool pg_file_exists(const char *name);
-extern void pg_flush_data(int fd, pgoff_t offset, pgoff_t nbytes);
-extern int	pg_truncate(const char *path, pgoff_t length);
-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 elevel);
-extern int	durable_unlink(const char *fname, int elevel);
 extern void SyncDataDirectory(void);
-extern int	data_sync_elevel(int elevel);
 
 static inline ssize_t
 FileRead(File file, void *buffer, size_t amount, pgoff_t offset,
diff --git a/src/include/storage/file_utils.h b/src/include/storage/file_utils.h
new file mode 100644
index 00000000000..70c96c04cb8
--- /dev/null
+++ b/src/include/storage/file_utils.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * file_utils.h
+ *	  Assorted utility functions to work on files
+ *
+ * See also common/file_utils.h for similar functions for frontend
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/file_utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_FILE_UTILS_H
+#define STORAGE_FILE_UTILS_H
+
+/* GUC parameter */
+extern PGDLLIMPORT bool data_sync_retry;
+
+/*
+ * On Windows, we have to interpret EACCES as possibly meaning the same as
+ * ENOENT, because if a file is unlinked-but-not-yet-gone on that platform,
+ * that's what you get.  Ugh.  This code is designed so that we don't
+ * actually believe these cases are okay without further evidence (namely,
+ * a pending fsync request getting canceled ... see ProcessSyncRequests).
+ */
+#ifndef WIN32
+#define FILE_POSSIBLY_DELETED(err)	((err) == ENOENT)
+#else
+#define FILE_POSSIBLY_DELETED(err)	((err) == ENOENT || (err) == EACCES)
+#endif
+
+extern int	pg_fsync(int fd);
+extern int	pg_fsync_no_writethrough(int fd);
+extern int	pg_fsync_writethrough(int fd);
+extern int	pg_fdatasync(int fd);
+extern bool pg_file_exists(const char *name);
+extern void pg_flush_data(int fd, pgoff_t offset, pgoff_t nbytes);
+extern int	pg_ftruncate(int fd, pgoff_t length);
+extern int	pg_truncate(const char *path, pgoff_t length);
+extern int	data_sync_elevel(int elevel);
+
+/*
+ * These functions have counterparts in common/file_utils.h for frontend code
+ */
+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 elevel);
+extern int	durable_unlink(const char *fname, int elevel);
+
+#endif							/* STORAGE_FILE_UTILS_H */
-- 
2.47.3

