From cd7a35ff924d2a4f24676da7719ae5ef1eb315ca Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas 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 #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 +#include + +#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 @@ -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