| From: | Mingwei Jia <i(at)nayishan(dot)top> |
|---|---|
| To: | pgsql-hackers(at)lists(dot)postgresql(dot)org |
| Subject: | [RFC PATCH v2 RESEND 03/10] umbra: add patch 2 umfile physical file manager and metadata storage primitives |
| Date: | 2026-06-01 23:33:33 |
| Message-ID: | 20260601233340.67949-2-i@nayishan.top |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
---
src/backend/storage/smgr/Makefile | 1 +
src/backend/storage/smgr/meson.build | 1 +
src/backend/storage/smgr/umbra.c | 106 ++-
src/backend/storage/smgr/umfile.c | 1146 ++++++++++++++++++++++++++
src/include/storage/um_defs.h | 49 ++
src/include/storage/umbra.h | 12 +
src/include/storage/umfile.h | 101 +++
7 files changed, 1411 insertions(+), 5 deletions(-)
create mode 100644 src/backend/storage/smgr/umfile.c
create mode 100644 src/include/storage/um_defs.h
create mode 100644 src/include/storage/umfile.h
diff --git a/src/backend/storage/smgr/Makefile b/src/backend/storage/smgr/Makefile
index 537e7b65f4..32d72d8831 100644
--- a/src/backend/storage/smgr/Makefile
+++ b/src/backend/storage/smgr/Makefile
@@ -19,6 +19,7 @@ OBJS = \
ifeq ($(with_umbra), yes)
OBJS += \
+ umfile.o \
umbra.o
endif
diff --git a/src/backend/storage/smgr/meson.build b/src/backend/storage/smgr/meson.build
index ba28e59f2f..00313617bf 100644
--- a/src/backend/storage/smgr/meson.build
+++ b/src/backend/storage/smgr/meson.build
@@ -8,6 +8,7 @@ backend_sources += files(
if get_option('umbra').enabled()
backend_sources += files(
+ 'umfile.c',
'umbra.c',
)
endif
diff --git a/src/backend/storage/smgr/umbra.c b/src/backend/storage/smgr/umbra.c
index 4c4fd28dbf..2c08231587 100644
--- a/src/backend/storage/smgr/umbra.c
+++ b/src/backend/storage/smgr/umbra.c
@@ -4,8 +4,8 @@
* Umbra storage manager skeleton.
*
* This file establishes Umbra as a separate smgr implementation from md.c.
- * The initial implementation preserves md semantics by forwarding relation
- * file operations to md.c.
+ * Data-fork operations remain md-backed here, while relation-local metadata
+ * file operations go through umfile.
*
* src/backend/storage/smgr/umbra.c
*
@@ -15,17 +15,87 @@
#include "storage/md.h"
#include "storage/smgr.h"
+#include "storage/umfile.h"
#include "storage/umbra.h"
#include "utils/memutils.h"
typedef struct UmbraSmgrRelationState
{
- bool initialized;
+ UmbraFileContext *filectx;
} UmbraSmgrRelationState;
+static UmbraFileContext *um_relation_filectx(SMgrRelation reln);
+
+bool
+UmMetadataExists(SMgrRelation reln)
+{
+ return umfile_exists(um_relation_filectx(reln),
+ UMBRA_METADATA_FORKNUM,
+ UMFILE_EXISTS_DENSE);
+}
+
+bool
+UmMetadataOpenOrCreate(SMgrRelation reln, bool isRedo, bool *created)
+{
+ return umfile_open_or_create(um_relation_filectx(reln),
+ UMBRA_METADATA_FORKNUM,
+ isRedo,
+ created);
+}
+
+BlockNumber
+UmMetadataNblocks(SMgrRelation reln)
+{
+ return umfile_nblocks(um_relation_filectx(reln),
+ UMBRA_METADATA_FORKNUM,
+ UMFILE_NBLOCKS_DENSE);
+}
+
+void
+UmMetadataRead(SMgrRelation reln, BlockNumber blkno, void *buffer)
+{
+ void *buffers[1];
+
+ buffers[0] = buffer;
+ umfile_readv(um_relation_filectx(reln), UMBRA_METADATA_FORKNUM, blkno,
+ buffers, 1);
+}
+
+void
+UmMetadataWrite(SMgrRelation reln, BlockNumber blkno, const void *buffer,
+ bool skipFsync)
+{
+ const void *buffers[1];
+
+ buffers[0] = buffer;
+ umfile_writev(um_relation_filectx(reln), UMBRA_METADATA_FORKNUM, blkno,
+ buffers, 1, skipFsync);
+}
+
+void
+UmMetadataExtend(SMgrRelation reln, BlockNumber blkno, const void *buffer,
+ bool skipFsync)
+{
+ umfile_extend(um_relation_filectx(reln), UMBRA_METADATA_FORKNUM, blkno,
+ buffer, skipFsync);
+}
+
+void
+UmMetadataImmediateSync(SMgrRelation reln)
+{
+ umfile_immedsync(um_relation_filectx(reln), UMBRA_METADATA_FORKNUM);
+}
+
+void
+UmMetadataUnlink(RelFileLocatorBackend rlocator, bool isRedo)
+{
+ umfile_unlink(rlocator, UMBRA_METADATA_FORKNUM, isRedo);
+}
+
void
uminit(void)
{
+ umfile_init();
}
void
@@ -37,7 +107,7 @@ umopen(SMgrRelation reln)
state = MemoryContextAllocZero(TopMemoryContext,
sizeof(UmbraSmgrRelationState));
- state->initialized = true;
+ state->filectx = umfile_ctx_acquire(reln->smgr_rlocator);
reln->smgr_private = state;
mdopen(reln);
@@ -54,9 +124,10 @@ umdestroy(SMgrRelation reln)
{
UmbraSmgrRelationState *state = reln->smgr_private;
+ umfile_ctx_forget(reln->smgr_rlocator);
+
if (state != NULL)
{
- Assert(state->initialized);
pfree(state);
reln->smgr_private = NULL;
}
@@ -77,6 +148,17 @@ umexists(SMgrRelation reln, ForkNumber forknum)
void
umunlink(RelFileLocatorBackend rlocator, ForkNumber forknum, bool isRedo)
{
+ umfile_ctx_forget(rlocator);
+
+ if (forknum == UMBRA_METADATA_FORKNUM)
+ {
+ UmMetadataUnlink(rlocator, isRedo);
+ return;
+ }
+
+ if (forknum == MAIN_FORKNUM || forknum == InvalidForkNumber)
+ UmMetadataUnlink(rlocator, isRedo);
+
mdunlink(rlocator, forknum, isRedo);
}
@@ -165,3 +247,17 @@ umfd(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, uint32 *off)
{
return mdfd(reln, forknum, blocknum, off);
}
+
+static UmbraFileContext *
+um_relation_filectx(SMgrRelation reln)
+{
+ UmbraSmgrRelationState *state = reln->smgr_private;
+
+ if (state == NULL)
+ return umfile_ctx_acquire(reln->smgr_rlocator);
+
+ if (state->filectx == NULL)
+ state->filectx = umfile_ctx_acquire(reln->smgr_rlocator);
+
+ return state->filectx;
+}
diff --git a/src/backend/storage/smgr/umfile.c b/src/backend/storage/smgr/umfile.c
new file mode 100644
index 0000000000..f8d1140840
--- /dev/null
+++ b/src/backend/storage/smgr/umfile.c
@@ -0,0 +1,1146 @@
+/*-------------------------------------------------------------------------
+ *
+ * umfile.c
+ * Umbra backend-local file/segment helpers.
+ *
+ * This layer owns backend-local file contexts keyed by RelFileLocatorBackend
+ * and provides physical fork/segment management beneath Umbra metadata and
+ * mapping code.
+ *
+ * src/backend/storage/smgr/umfile.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "access/xlogutils.h"
+#include "commands/tablespace.h"
+#include "common/relpath.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "storage/um_defs.h"
+#include "storage/umfile.h"
+#include "utils/hsearch.h"
+#include "utils/memutils.h"
+#include "utils/wait_event.h"
+
+/* Behavior flags for segment open helpers. */
+#define UM_EXTENSION_FAIL (1 << 0)
+#define UM_EXTENSION_RETURN_NULL (1 << 1)
+#define UM_EXTENSION_CREATE (1 << 2)
+#define UM_EXTENSION_CREATE_RECOVERY (1 << 3)
+#define UM_EXTENSION_DONT_OPEN (1 << 5)
+
+typedef struct UmCtxRegistryEntry
+{
+ RelFileLocatorBackend rlocator;
+ UmbraFileContext *ctx;
+} UmCtxRegistryEntry;
+
+typedef struct UmfdVec
+{
+ File umfd_vfd;
+ BlockNumber umfd_segno;
+} UmfdVec;
+
+struct UmbraFileContext
+{
+ RelFileLocatorBackend rlocator;
+ int num_open_segs[UMBRA_FORK_SLOTS];
+ UmfdVec *seg_fds[UMBRA_FORK_SLOTS];
+ uint32 refcount;
+};
+
+static MemoryContext UmFileCxt = NULL;
+static HTAB *UmFileContextHash = NULL;
+
+static void umfile_ctx_registry_init(void);
+static UmbraFileContext *umfile_ctx_create(RelFileLocatorBackend rlocator);
+static void umfile_ctx_destroy(UmbraFileContext *ctx);
+static void umfile_close_open_segments(UmbraFileContext *ctx,
+ ForkNumber forknum);
+static bool umfile_create(UmbraFileContext *ctx, ForkNumber forknum,
+ bool isRedo);
+static int umfile_open_flags(void);
+static void umfile_fdvec_resize(UmbraFileContext *ctx, ForkNumber forknum,
+ int nseg);
+static inline UmfdVec *umfile_v_get(UmbraFileContext *ctx,
+ ForkNumber forknum, int segindex);
+static BlockNumber umfile_nblocks_in_seg(File vfd);
+static RelPathStr umfile_segpath(RelFileLocatorBackend rlocator,
+ ForkNumber forknum, BlockNumber segno);
+static UmfdVec *umfile_openseg(UmbraFileContext *ctx,
+ RelFileLocatorBackend rlocator,
+ ForkNumber forknum,
+ BlockNumber segno, int oflags);
+static UmfdVec *umfile_openfork(UmbraFileContext *ctx,
+ RelFileLocatorBackend rlocator,
+ ForkNumber forknum, int behavior);
+static UmfdVec *umfile_getseg(UmbraFileContext *ctx,
+ RelFileLocatorBackend rlocator,
+ ForkNumber forknum, BlockNumber blkno,
+ bool skipFsync, int behavior);
+static bool umfile_fork_has_open_segment(UmbraFileContext *ctx,
+ ForkNumber forknum);
+static bool umfile_fork_has_open_segment_on_disk(UmbraFileContext *ctx,
+ RelFileLocatorBackend rlocator,
+ ForkNumber forknum);
+static inline bool umfile_seg_entry_is_open(const UmfdVec *seg);
+static inline void umfile_seg_entry_reset(UmfdVec *seg);
+
+void
+umfile_init(void)
+{
+ HASHCTL ctl;
+
+ if (UmFileContextHash != NULL)
+ return;
+
+ UmFileCxt = AllocSetContextCreate(TopMemoryContext,
+ "UmFile",
+ ALLOCSET_DEFAULT_SIZES);
+ MemoryContextAllowInCriticalSection(UmFileCxt, true);
+
+ memset(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(RelFileLocatorBackend);
+ ctl.entrysize = sizeof(UmCtxRegistryEntry);
+ ctl.hcxt = UmFileCxt;
+
+ UmFileContextHash = hash_create("Umbra file context registry",
+ 256,
+ &ctl,
+ HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+}
+
+UmbraFileContext *
+umfile_ctx_lookup(RelFileLocatorBackend rlocator)
+{
+ UmCtxRegistryEntry *entry;
+
+ umfile_ctx_registry_init();
+ entry = hash_search(UmFileContextHash, &rlocator, HASH_FIND, NULL);
+ if (entry == NULL)
+ return NULL;
+
+ return entry->ctx;
+}
+
+UmbraFileContext *
+umfile_ctx_acquire(RelFileLocatorBackend rlocator)
+{
+ UmCtxRegistryEntry *entry;
+ bool found;
+
+ umfile_ctx_registry_init();
+ entry = hash_search(UmFileContextHash, &rlocator, HASH_ENTER, &found);
+ if (!found)
+ entry->ctx = umfile_ctx_create(rlocator);
+ entry->ctx->refcount++;
+
+ return entry->ctx;
+}
+
+UmbraFileContext *
+umfile_ctx_create_temporary(RelFileLocatorBackend rlocator)
+{
+ umfile_ctx_registry_init();
+ return umfile_ctx_create(rlocator);
+}
+
+void
+umfile_ctx_destroy_temporary(UmbraFileContext *ctx)
+{
+ if (ctx == NULL)
+ return;
+
+ umfile_ctx_destroy(ctx);
+}
+
+void
+umfile_ctx_release(RelFileLocatorBackend rlocator)
+{
+ UmCtxRegistryEntry *entry;
+ UmbraFileContext *ctx;
+
+ if (UmFileContextHash == NULL)
+ return;
+
+ entry = hash_search(UmFileContextHash, &rlocator, HASH_FIND, NULL);
+ if (entry == NULL)
+ return;
+
+ ctx = entry->ctx;
+ Assert(ctx->refcount > 0);
+ ctx->refcount--;
+
+ if (ctx->refcount == 0)
+ {
+ umfile_ctx_destroy(ctx);
+ (void) hash_search(UmFileContextHash, &rlocator, HASH_REMOVE, NULL);
+ }
+}
+
+void
+umfile_ctx_forget(RelFileLocatorBackend rlocator)
+{
+ UmCtxRegistryEntry *entry;
+ UmbraFileContext *ctx;
+
+ if (UmFileContextHash == NULL)
+ return;
+
+ entry = hash_search(UmFileContextHash, &rlocator, HASH_FIND, NULL);
+ if (entry == NULL)
+ return;
+
+ ctx = entry->ctx;
+ for (ForkNumber forknum = 0; forknum <= UMBRA_METADATA_FORKNUM; forknum++)
+ umfile_close_open_segments(ctx, forknum);
+
+ if (ctx->refcount == 0)
+ {
+ umfile_ctx_destroy(ctx);
+ (void) hash_search(UmFileContextHash, &rlocator, HASH_REMOVE, NULL);
+ }
+}
+
+void
+umfile_ctx_close_fork(UmbraFileContext *ctx, ForkNumber forknum)
+{
+ if (ctx == NULL)
+ return;
+
+ umfile_close_open_segments(ctx, forknum);
+}
+
+bool
+umfile_ctx_fork_exists(UmbraFileContext *ctx, ForkNumber forknum,
+ UmFileExistsMode mode)
+{
+ if (ctx == NULL)
+ return false;
+
+ return umfile_exists(ctx, forknum, mode);
+}
+
+BlockNumber
+umfile_ctx_get_nblocks(UmbraFileContext *ctx, ForkNumber forknum,
+ UmFileNblocksMode mode)
+{
+ Assert(ctx != NULL);
+ return umfile_nblocks(ctx, forknum, mode);
+}
+
+void
+umfile_ctx_read(UmbraFileContext *ctx, ForkNumber forknum, BlockNumber blkno,
+ char *buffer, int nbytes)
+{
+ UmfdVec *seg;
+ off_t offset;
+ ssize_t got;
+
+ Assert(ctx != NULL);
+ Assert(buffer != NULL);
+ Assert(nbytes > 0 && nbytes <= BLCKSZ);
+
+ seg = umfile_getseg(ctx, ctx->rlocator, forknum, blkno,
+ false,
+ UM_EXTENSION_FAIL | UM_EXTENSION_CREATE_RECOVERY);
+ offset = (off_t) BLCKSZ * (blkno % ((BlockNumber) RELSEG_SIZE));
+ got = FileRead(seg->umfd_vfd, buffer, nbytes, offset,
+ WAIT_EVENT_DATA_FILE_READ);
+ if (got < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read block %u in file \"%s\": %m",
+ blkno, FilePathName(seg->umfd_vfd))));
+ if (got != nbytes)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("could not read block %u in file \"%s\"",
+ blkno, FilePathName(seg->umfd_vfd)),
+ errdetail("Read only %zd of %d bytes.", got, nbytes)));
+}
+
+void
+umfile_ctx_write(UmbraFileContext *ctx, ForkNumber forknum, BlockNumber blkno,
+ const char *buffer, int nbytes, bool skipFsync)
+{
+ UmfdVec *seg;
+ BlockNumber nblocks;
+ off_t offset;
+ ssize_t wrote;
+
+ Assert(ctx != NULL);
+ Assert(buffer != NULL);
+ Assert(nbytes > 0 && nbytes <= BLCKSZ);
+
+ nblocks = umfile_nblocks(ctx, forknum, UMFILE_NBLOCKS_DENSE);
+ if (blkno >= nblocks)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("cannot overwrite block %u in relation %u/%u/%u fork %d",
+ blkno,
+ ctx->rlocator.locator.spcOid,
+ ctx->rlocator.locator.dbOid,
+ ctx->rlocator.locator.relNumber,
+ forknum),
+ errdetail("Current fork size is %u blocks.", nblocks)));
+
+ seg = umfile_getseg(ctx, ctx->rlocator, forknum, blkno,
+ skipFsync,
+ UM_EXTENSION_FAIL | UM_EXTENSION_CREATE_RECOVERY);
+ offset = (off_t) BLCKSZ * (blkno % ((BlockNumber) RELSEG_SIZE));
+ wrote = FileWrite(seg->umfd_vfd, buffer, nbytes, offset,
+ WAIT_EVENT_DATA_FILE_WRITE);
+ if (wrote < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write block %u in file \"%s\": %m",
+ blkno, FilePathName(seg->umfd_vfd))));
+ if (wrote != nbytes)
+ ereport(ERROR,
+ (errcode(ERRCODE_DISK_FULL),
+ errmsg("could not write block %u in file \"%s\"",
+ blkno, FilePathName(seg->umfd_vfd)),
+ errdetail("Wrote only %zd of %d bytes.", wrote, nbytes)));
+
+ /*
+ * Sync policy is explicit at this layer: callers use
+ * umfile_registersync()/umfile_immedsync() for durable requests.
+ */
+ (void) skipFsync;
+}
+
+void
+umfile_ctx_extend(UmbraFileContext *ctx, ForkNumber forknum, BlockNumber blkno,
+ const char *buffer)
+{
+ BlockNumber nblocks;
+
+ Assert(ctx != NULL);
+ Assert(buffer != NULL);
+
+ (void) umfile_open_or_create(ctx, forknum, false, NULL);
+ nblocks = umfile_nblocks(ctx, forknum, UMFILE_NBLOCKS_DENSE);
+ if (blkno != nblocks)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("cannot extend relation %u/%u/%u fork %d at block %u",
+ ctx->rlocator.locator.spcOid,
+ ctx->rlocator.locator.dbOid,
+ ctx->rlocator.locator.relNumber,
+ forknum, blkno),
+ errdetail("Expected next block %u.", nblocks)));
+
+ umfile_extend(ctx, forknum, blkno, buffer, true);
+}
+
+void
+umfile_ctx_unlinkfork(RelFileLocatorBackend rlocator, ForkNumber forknum,
+ bool isRedo)
+{
+ umfile_unlink(rlocator, forknum, isRedo);
+}
+
+bool
+umfile_metadata_exists(UmbraFileContext *ctx)
+{
+ return umfile_exists(ctx, UMBRA_METADATA_FORKNUM, UMFILE_EXISTS_DENSE);
+}
+
+bool
+umfile_metadata_open_or_create(UmbraFileContext *ctx, bool isRedo, bool *created)
+{
+ return umfile_open_or_create(ctx, UMBRA_METADATA_FORKNUM, isRedo, created);
+}
+
+BlockNumber
+umfile_metadata_nblocks(UmbraFileContext *ctx)
+{
+ return umfile_nblocks(ctx, UMBRA_METADATA_FORKNUM, UMFILE_NBLOCKS_DENSE);
+}
+
+void
+umfile_metadata_read(UmbraFileContext *ctx, BlockNumber blkno, void *buffer)
+{
+ void *buffers[1];
+
+ buffers[0] = buffer;
+ umfile_readv(ctx, UMBRA_METADATA_FORKNUM, blkno, buffers, 1);
+}
+
+void
+umfile_metadata_write(UmbraFileContext *ctx, BlockNumber blkno, const void *buffer)
+{
+ const void *buffers[1];
+
+ buffers[0] = buffer;
+ umfile_writev(ctx, UMBRA_METADATA_FORKNUM, blkno, buffers, 1, false);
+}
+
+void
+umfile_metadata_extend(UmbraFileContext *ctx, BlockNumber blkno, const void *buffer)
+{
+ umfile_extend(ctx, UMBRA_METADATA_FORKNUM, blkno, buffer, false);
+}
+
+void
+umfile_metadata_immedsync(UmbraFileContext *ctx)
+{
+ umfile_immedsync(ctx, UMBRA_METADATA_FORKNUM);
+}
+
+void
+umfile_metadata_unlink(RelFileLocatorBackend rlocator, bool isRedo)
+{
+ umfile_unlink(rlocator, UMBRA_METADATA_FORKNUM, isRedo);
+}
+
+bool
+umfile_exists(UmbraFileContext *ctx, ForkNumber forknum, UmFileExistsMode mode)
+{
+ Assert(ctx != NULL);
+ (void) mode;
+
+ if (umfile_fork_has_open_segment(ctx, forknum))
+ {
+ if (umfile_fork_has_open_segment_on_disk(ctx, ctx->rlocator, forknum))
+ return true;
+
+ umfile_close_open_segments(ctx, forknum);
+ }
+
+ return (umfile_openfork(ctx, ctx->rlocator, forknum,
+ UM_EXTENSION_RETURN_NULL) != NULL);
+}
+
+bool
+umfile_open_or_create(UmbraFileContext *ctx, ForkNumber forknum,
+ bool isRedo, bool *created)
+{
+ UmfdVec *seg;
+ bool was_created;
+
+ Assert(ctx != NULL);
+
+ if (created != NULL)
+ *created = false;
+
+ seg = umfile_openfork(ctx, ctx->rlocator, forknum,
+ UM_EXTENSION_RETURN_NULL);
+ if (seg != NULL)
+ return true;
+
+ was_created = umfile_create(ctx, forknum, isRedo);
+ if (created != NULL)
+ *created = was_created;
+
+ return true;
+}
+
+BlockNumber
+umfile_nblocks(UmbraFileContext *ctx, ForkNumber forknum, UmFileNblocksMode mode)
+{
+ UmfdVec *seg;
+ BlockNumber segno;
+ BlockNumber nblocks;
+
+ Assert(ctx != NULL);
+ (void) mode;
+
+ if (umfile_openfork(ctx, ctx->rlocator, forknum,
+ UM_EXTENSION_RETURN_NULL) == NULL)
+ return 0;
+
+ Assert(ctx->num_open_segs[forknum] > 0);
+ segno = ctx->num_open_segs[forknum] - 1;
+ seg = umfile_v_get(ctx, forknum, segno);
+
+ for (;;)
+ {
+ nblocks = umfile_nblocks_in_seg(seg->umfd_vfd);
+ if (nblocks > (BlockNumber) RELSEG_SIZE)
+ elog(FATAL, "Umbra segment too big");
+ if (nblocks < (BlockNumber) RELSEG_SIZE)
+ return (segno * ((BlockNumber) RELSEG_SIZE)) + nblocks;
+
+ segno++;
+ seg = umfile_openseg(ctx, ctx->rlocator, forknum, segno, 0);
+ if (seg == NULL)
+ return segno * ((BlockNumber) RELSEG_SIZE);
+ }
+}
+
+void
+umfile_readv(UmbraFileContext *ctx, ForkNumber forknum, BlockNumber blocknum,
+ void **buffers, BlockNumber nblocks)
+{
+ for (BlockNumber i = 0; i < nblocks; i++)
+ umfile_ctx_read(ctx, forknum, blocknum + i, buffers[i], BLCKSZ);
+}
+
+void
+umfile_writev(UmbraFileContext *ctx, ForkNumber forknum, BlockNumber blocknum,
+ const void **buffers, BlockNumber nblocks, bool skipFsync)
+{
+ for (BlockNumber i = 0; i < nblocks; i++)
+ umfile_ctx_write(ctx, forknum, blocknum + i, buffers[i], BLCKSZ,
+ skipFsync);
+}
+
+void
+umfile_extend(UmbraFileContext *ctx, ForkNumber forknum, BlockNumber blocknum,
+ const void *buffer, bool skipFsync)
+{
+ UmfdVec *seg;
+ off_t offset;
+ ssize_t wrote;
+
+ Assert(ctx != NULL);
+ Assert(buffer != NULL);
+
+ seg = umfile_getseg(ctx, ctx->rlocator, forknum, blocknum,
+ skipFsync,
+ UM_EXTENSION_FAIL | UM_EXTENSION_CREATE);
+ offset = (off_t) BLCKSZ * (blocknum % ((BlockNumber) RELSEG_SIZE));
+ wrote = FileWrite(seg->umfd_vfd, buffer, BLCKSZ, offset,
+ WAIT_EVENT_DATA_FILE_EXTEND);
+ if (wrote < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not extend file \"%s\": %m",
+ FilePathName(seg->umfd_vfd))));
+ if (wrote != BLCKSZ)
+ ereport(ERROR,
+ (errcode(ERRCODE_DISK_FULL),
+ errmsg("could not extend file \"%s\" at block %u",
+ FilePathName(seg->umfd_vfd), blocknum),
+ errdetail("Wrote only %zd of %d bytes.", wrote, BLCKSZ)));
+
+ (void) skipFsync;
+}
+
+void
+umfile_zeroextend(UmbraFileContext *ctx, ForkNumber forknum,
+ BlockNumber blocknum, int nblocks, bool skipFsync)
+{
+ Assert(ctx != NULL);
+ Assert(nblocks >= 0);
+
+ while (nblocks > 0)
+ {
+ UmfdVec *seg;
+ BlockNumber nblocks_this_segment;
+ off_t offset;
+ int ret;
+
+ seg = umfile_getseg(ctx, ctx->rlocator, forknum, blocknum,
+ skipFsync,
+ UM_EXTENSION_FAIL | UM_EXTENSION_CREATE);
+ offset = (off_t) BLCKSZ * (blocknum % ((BlockNumber) RELSEG_SIZE));
+ nblocks_this_segment =
+ Min((BlockNumber) nblocks,
+ ((BlockNumber) RELSEG_SIZE) -
+ (blocknum % ((BlockNumber) RELSEG_SIZE)));
+
+ ret = FileZero(seg->umfd_vfd,
+ offset,
+ (off_t) BLCKSZ * nblocks_this_segment,
+ WAIT_EVENT_DATA_FILE_EXTEND);
+ if (ret < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not zero-extend file \"%s\": %m",
+ FilePathName(seg->umfd_vfd))));
+
+ nblocks -= nblocks_this_segment;
+ blocknum += nblocks_this_segment;
+ }
+}
+
+void
+umfile_truncate(UmbraFileContext *ctx, ForkNumber forknum,
+ BlockNumber old_blocks, BlockNumber nblocks)
+{
+ int curopensegs;
+
+ Assert(ctx != NULL);
+
+ if (nblocks > old_blocks)
+ {
+ if (InRecovery)
+ return;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("cannot truncate relation %u/%u/%u fork %d to %u blocks: current size is only %u blocks",
+ ctx->rlocator.locator.spcOid,
+ ctx->rlocator.locator.dbOid,
+ ctx->rlocator.locator.relNumber,
+ forknum,
+ nblocks,
+ old_blocks)));
+ }
+
+ if (nblocks == old_blocks)
+ return;
+
+ /*
+ * Bring all dense segments into the local array first, then trim from the
+ * tail. This keeps the truncate contract local to the file manager.
+ */
+ (void) umfile_nblocks(ctx, forknum, UMFILE_NBLOCKS_DENSE);
+ curopensegs = ctx->num_open_segs[forknum];
+
+ while (curopensegs > 0)
+ {
+ UmfdVec *seg;
+ BlockNumber priorblocks;
+
+ priorblocks = (curopensegs - 1) * ((BlockNumber) RELSEG_SIZE);
+ seg = umfile_v_get(ctx, forknum, curopensegs - 1);
+
+ if (priorblocks >= nblocks)
+ {
+ if (FileTruncate(seg->umfd_vfd, 0, WAIT_EVENT_DATA_FILE_TRUNCATE) < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not truncate file \"%s\": %m",
+ FilePathName(seg->umfd_vfd))));
+
+ if (seg != umfile_v_get(ctx, forknum, 0))
+ {
+ FileClose(seg->umfd_vfd);
+ umfile_fdvec_resize(ctx, forknum, curopensegs - 1);
+ }
+ }
+ else if (priorblocks + ((BlockNumber) RELSEG_SIZE) > nblocks)
+ {
+ BlockNumber lastsegblocks;
+
+ lastsegblocks = nblocks - priorblocks;
+ if (FileTruncate(seg->umfd_vfd,
+ (off_t) lastsegblocks * BLCKSZ,
+ WAIT_EVENT_DATA_FILE_TRUNCATE) < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not truncate file \"%s\" to %u blocks: %m",
+ FilePathName(seg->umfd_vfd),
+ nblocks)));
+ }
+ else
+ break;
+
+ curopensegs--;
+ }
+}
+
+void
+umfile_immedsync(UmbraFileContext *ctx, ForkNumber forknum)
+{
+ int segno;
+ int min_inactive_seg;
+
+ Assert(ctx != NULL);
+
+ (void) umfile_nblocks(ctx, forknum, UMFILE_NBLOCKS_DENSE);
+ min_inactive_seg = segno = ctx->num_open_segs[forknum];
+
+ while (umfile_openseg(ctx, ctx->rlocator, forknum, segno, 0) != NULL)
+ segno++;
+
+ while (segno > 0)
+ {
+ UmfdVec *seg = umfile_v_get(ctx, forknum, segno - 1);
+
+ if (FileSync(seg->umfd_vfd, WAIT_EVENT_DATA_FILE_IMMEDIATE_SYNC) < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not fsync file \"%s\": %m",
+ FilePathName(seg->umfd_vfd))));
+
+ if (segno > min_inactive_seg)
+ {
+ FileClose(seg->umfd_vfd);
+ umfile_fdvec_resize(ctx, forknum, segno - 1);
+ }
+
+ segno--;
+ }
+}
+
+void
+umfile_registersync(UmbraFileContext *ctx, ForkNumber forknum)
+{
+ /*
+ * Registering durability at this boundary is implemented as an immediate
+ * fsync.
+ */
+ umfile_immedsync(ctx, forknum);
+}
+
+void
+umfile_unlink(RelFileLocatorBackend rlocator, ForkNumber forknum, bool isRedo)
+{
+ if (forknum == InvalidForkNumber)
+ {
+ for (forknum = 0; forknum <= UMBRA_METADATA_FORKNUM; forknum++)
+ umfile_unlink(rlocator, forknum, isRedo);
+ return;
+ }
+
+ for (BlockNumber segno = 0;; segno++)
+ {
+ RelPathStr path;
+
+ path = umfile_segpath(rlocator, forknum, segno);
+ if (unlink(path.str) < 0)
+ {
+ if (FILE_POSSIBLY_DELETED(errno))
+ {
+ if (segno == 0 && isRedo)
+ return;
+ break;
+ }
+
+ ereport(WARNING,
+ (errcode_for_file_access(),
+ errmsg("could not remove file \"%s\": %m", path.str)));
+ break;
+ }
+ }
+}
+
+static void
+umfile_ctx_registry_init(void)
+{
+ if (UmFileContextHash == NULL)
+ umfile_init();
+
+ Assert(UmFileContextHash != NULL);
+}
+
+static UmbraFileContext *
+umfile_ctx_create(RelFileLocatorBackend rlocator)
+{
+ UmbraFileContext *ctx;
+
+ Assert(UmFileCxt != NULL);
+
+ ctx = MemoryContextAllocZero(UmFileCxt, sizeof(UmbraFileContext));
+ ctx->rlocator = rlocator;
+
+ for (ForkNumber forknum = 0; forknum <= UMBRA_METADATA_FORKNUM; forknum++)
+ {
+ ctx->num_open_segs[forknum] = 0;
+ ctx->seg_fds[forknum] = NULL;
+ }
+
+ return ctx;
+}
+
+static void
+umfile_ctx_destroy(UmbraFileContext *ctx)
+{
+ if (ctx == NULL)
+ return;
+
+ for (ForkNumber forknum = 0; forknum <= UMBRA_METADATA_FORKNUM; forknum++)
+ umfile_close_open_segments(ctx, forknum);
+
+ pfree(ctx);
+}
+
+static void
+umfile_close_open_segments(UmbraFileContext *ctx, ForkNumber forknum)
+{
+ int nopensegs;
+
+ Assert(ctx != NULL);
+
+ nopensegs = ctx->num_open_segs[forknum];
+ while (nopensegs > 0)
+ {
+ UmfdVec *seg = umfile_v_get(ctx, forknum, nopensegs - 1);
+
+ if (umfile_seg_entry_is_open(seg))
+ FileClose(seg->umfd_vfd);
+ umfile_fdvec_resize(ctx, forknum, nopensegs - 1);
+ nopensegs--;
+ }
+}
+
+static bool
+umfile_create(UmbraFileContext *ctx, ForkNumber forknum, bool isRedo)
+{
+ RelPathStr path;
+ File fd;
+ UmfdVec *seg;
+ bool created = false;
+
+ Assert(ctx != NULL);
+
+ if (isRedo && ctx->num_open_segs[forknum] > 0)
+ return false;
+
+ if (ctx->num_open_segs[forknum] > 0)
+ umfile_close_open_segments(ctx, forknum);
+
+ TablespaceCreateDbspace(ctx->rlocator.locator.spcOid,
+ ctx->rlocator.locator.dbOid,
+ isRedo);
+
+ path = umfile_segpath(ctx->rlocator, forknum, 0);
+ fd = PathNameOpenFile(path.str, umfile_open_flags() | O_CREAT | O_EXCL);
+ if (fd < 0)
+ {
+ int save_errno = errno;
+
+ if (isRedo)
+ fd = PathNameOpenFile(path.str, umfile_open_flags());
+ if (fd < 0)
+ {
+ errno = save_errno;
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not create file \"%s\": %m", path.str)));
+ }
+ }
+ else
+ created = true;
+
+ umfile_fdvec_resize(ctx, forknum, 1);
+ seg = umfile_v_get(ctx, forknum, 0);
+ seg->umfd_vfd = fd;
+ seg->umfd_segno = 0;
+
+ return created;
+}
+
+static int
+umfile_open_flags(void)
+{
+ int flags = O_RDWR | PG_BINARY;
+
+ if (io_direct_flags & IO_DIRECT_DATA)
+ flags |= PG_O_DIRECT;
+
+ return flags;
+}
+
+static void
+umfile_fdvec_resize(UmbraFileContext *ctx, ForkNumber forknum, int nseg)
+{
+ Assert(ctx != NULL);
+ Assert(nseg >= 0);
+
+ if (nseg == 0)
+ {
+ if (ctx->num_open_segs[forknum] > 0)
+ pfree(ctx->seg_fds[forknum]);
+ ctx->seg_fds[forknum] = NULL;
+ ctx->num_open_segs[forknum] = 0;
+ return;
+ }
+
+ if (ctx->num_open_segs[forknum] == 0)
+ {
+ ctx->seg_fds[forknum] =
+ MemoryContextAlloc(UmFileCxt, sizeof(UmfdVec) * nseg);
+ }
+ else if (nseg > ctx->num_open_segs[forknum])
+ {
+ ctx->seg_fds[forknum] =
+ repalloc(ctx->seg_fds[forknum], sizeof(UmfdVec) * nseg);
+ }
+
+ ctx->num_open_segs[forknum] = nseg;
+}
+
+static inline UmfdVec *
+umfile_v_get(UmbraFileContext *ctx, ForkNumber forknum, int segindex)
+{
+ Assert(ctx != NULL);
+ Assert(segindex >= 0);
+ Assert(segindex < ctx->num_open_segs[forknum]);
+ return &ctx->seg_fds[forknum][segindex];
+}
+
+static BlockNumber
+umfile_nblocks_in_seg(File vfd)
+{
+ pgoff_t size;
+
+ size = FileSize(vfd);
+ if (size < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not determine size of file \"%s\": %m",
+ FilePathName(vfd))));
+ if ((size % BLCKSZ) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("file \"%s\" has partial block contents",
+ FilePathName(vfd)),
+ errdetail("File size %lld is not a multiple of %d bytes.",
+ (long long) size, BLCKSZ)));
+
+ return (BlockNumber) (size / BLCKSZ);
+}
+
+static RelPathStr
+umfile_segpath(RelFileLocatorBackend rlocator, ForkNumber forknum,
+ BlockNumber segno)
+{
+ RelPathStr base;
+ RelPathStr fullpath;
+
+ if (forknum == UMBRA_METADATA_FORKNUM)
+ base = UmMetadataRelPathBackend(rlocator);
+ else
+ base = relpath(rlocator, forknum);
+
+ if (segno == 0)
+ return base;
+
+ snprintf(fullpath.str, sizeof(fullpath.str), "%s.%u", base.str, segno);
+ return fullpath;
+}
+
+static UmfdVec *
+umfile_openseg(UmbraFileContext *ctx, RelFileLocatorBackend rlocator,
+ ForkNumber forknum, BlockNumber segno, int oflags)
+{
+ UmfdVec *seg;
+ RelPathStr path;
+ File fd;
+ int old_nseg;
+
+ Assert(ctx != NULL);
+
+ old_nseg = ctx->num_open_segs[forknum];
+ if (segno < (BlockNumber) old_nseg)
+ {
+ seg = umfile_v_get(ctx, forknum, (int) segno);
+ if (umfile_seg_entry_is_open(seg))
+ return seg;
+ }
+
+ path = umfile_segpath(rlocator, forknum, segno);
+ fd = PathNameOpenFile(path.str, umfile_open_flags() | oflags);
+ if (fd < 0)
+ return NULL;
+
+ if (segno >= (BlockNumber) old_nseg)
+ {
+ umfile_fdvec_resize(ctx, forknum, segno + 1);
+ for (int i = old_nseg; i < ctx->num_open_segs[forknum]; i++)
+ umfile_seg_entry_reset(umfile_v_get(ctx, forknum, i));
+ }
+
+ seg = umfile_v_get(ctx, forknum, (int) segno);
+ seg->umfd_vfd = fd;
+ seg->umfd_segno = segno;
+
+ Assert(umfile_nblocks_in_seg(seg->umfd_vfd) <= (BlockNumber) RELSEG_SIZE);
+ return seg;
+}
+
+static UmfdVec *
+umfile_openfork(UmbraFileContext *ctx, RelFileLocatorBackend rlocator,
+ ForkNumber forknum, int behavior)
+{
+ RelPathStr path;
+ File fd;
+ UmfdVec *seg;
+
+ Assert(ctx != NULL);
+
+ if (ctx->num_open_segs[forknum] > 0)
+ {
+ seg = umfile_v_get(ctx, forknum, 0);
+ if (umfile_seg_entry_is_open(seg))
+ return seg;
+ }
+
+ path = umfile_segpath(rlocator, forknum, 0);
+ fd = PathNameOpenFile(path.str, umfile_open_flags());
+ if (fd < 0)
+ {
+ if ((behavior & UM_EXTENSION_RETURN_NULL) &&
+ FILE_POSSIBLY_DELETED(errno))
+ return NULL;
+
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\": %m", path.str)));
+ }
+
+ if (ctx->num_open_segs[forknum] == 0)
+ umfile_fdvec_resize(ctx, forknum, 1);
+ seg = umfile_v_get(ctx, forknum, 0);
+ seg->umfd_vfd = fd;
+ seg->umfd_segno = 0;
+
+ Assert(umfile_nblocks_in_seg(seg->umfd_vfd) <= (BlockNumber) RELSEG_SIZE);
+ return seg;
+}
+
+static UmfdVec *
+umfile_getseg(UmbraFileContext *ctx, RelFileLocatorBackend rlocator,
+ ForkNumber forknum, BlockNumber blkno,
+ bool skipFsync, int behavior)
+{
+ UmfdVec *seg;
+ BlockNumber targetseg;
+ BlockNumber nextsegno;
+
+ Assert(ctx != NULL);
+ Assert(behavior &
+ (UM_EXTENSION_FAIL | UM_EXTENSION_CREATE |
+ UM_EXTENSION_RETURN_NULL | UM_EXTENSION_DONT_OPEN));
+
+ targetseg = blkno / ((BlockNumber) RELSEG_SIZE);
+
+ if (targetseg < (BlockNumber) ctx->num_open_segs[forknum])
+ {
+ seg = umfile_v_get(ctx, forknum, (int) targetseg);
+ if (umfile_seg_entry_is_open(seg))
+ return seg;
+ }
+
+ if (behavior & UM_EXTENSION_DONT_OPEN)
+ return NULL;
+
+ if (ctx->num_open_segs[forknum] > 0)
+ seg = umfile_v_get(ctx, forknum, ctx->num_open_segs[forknum] - 1);
+ else
+ {
+ seg = umfile_openfork(ctx, rlocator, forknum, behavior);
+ if (seg == NULL)
+ return NULL;
+ }
+
+ for (nextsegno = ctx->num_open_segs[forknum];
+ nextsegno <= targetseg;
+ nextsegno++)
+ {
+ BlockNumber nblocks;
+ int flags = 0;
+
+ Assert(nextsegno == seg->umfd_segno + 1);
+
+ nblocks = umfile_nblocks_in_seg(seg->umfd_vfd);
+ if (nblocks > (BlockNumber) RELSEG_SIZE)
+ elog(FATAL, "Umbra segment too big");
+
+ if ((behavior & UM_EXTENSION_CREATE) ||
+ (InRecovery && (behavior & UM_EXTENSION_CREATE_RECOVERY)))
+ {
+ if (nblocks < (BlockNumber) RELSEG_SIZE)
+ {
+ char *zerobuf;
+
+ zerobuf = palloc_aligned(BLCKSZ, PG_IO_ALIGN_SIZE,
+ MCXT_ALLOC_ZERO);
+ umfile_extend(ctx, forknum,
+ nextsegno * ((BlockNumber) RELSEG_SIZE) - 1,
+ zerobuf, skipFsync);
+ pfree(zerobuf);
+ }
+ flags = O_CREAT;
+ }
+ else if (nblocks < (BlockNumber) RELSEG_SIZE)
+ {
+ if (behavior & UM_EXTENSION_RETURN_NULL)
+ {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\" (target block %u): previous segment is only %u blocks",
+ umfile_segpath(rlocator, forknum, nextsegno).str,
+ blkno, nblocks)));
+ }
+
+ seg = umfile_openseg(ctx, rlocator, forknum, nextsegno, flags);
+ if (seg == NULL)
+ {
+ if ((behavior & UM_EXTENSION_RETURN_NULL) &&
+ FILE_POSSIBLY_DELETED(errno))
+ return NULL;
+
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\" (target block %u): %m",
+ umfile_segpath(rlocator, forknum, nextsegno).str,
+ blkno)));
+ }
+ }
+
+ return seg;
+}
+
+static bool
+umfile_fork_has_open_segment(UmbraFileContext *ctx, ForkNumber forknum)
+{
+ Assert(ctx != NULL);
+
+ for (int i = 0; i < ctx->num_open_segs[forknum]; i++)
+ {
+ if (umfile_seg_entry_is_open(umfile_v_get(ctx, forknum, i)))
+ return true;
+ }
+
+ return false;
+}
+
+static bool
+umfile_fork_has_open_segment_on_disk(UmbraFileContext *ctx,
+ RelFileLocatorBackend rlocator,
+ ForkNumber forknum)
+{
+ bool have_live = false;
+
+ Assert(ctx != NULL);
+
+ for (int i = 0; i < ctx->num_open_segs[forknum]; i++)
+ {
+ UmfdVec *seg = umfile_v_get(ctx, forknum, i);
+ RelPathStr path;
+
+ if (!umfile_seg_entry_is_open(seg))
+ continue;
+
+ path = umfile_segpath(rlocator, forknum, seg->umfd_segno);
+ if (access(path.str, F_OK) == 0)
+ {
+ have_live = true;
+ continue;
+ }
+
+ FileClose(seg->umfd_vfd);
+ umfile_seg_entry_reset(seg);
+ }
+
+ return have_live;
+}
+
+static inline bool
+umfile_seg_entry_is_open(const UmfdVec *seg)
+{
+ return (seg != NULL && seg->umfd_vfd >= 0);
+}
+
+static inline void
+umfile_seg_entry_reset(UmfdVec *seg)
+{
+ seg->umfd_vfd = -1;
+ seg->umfd_segno = InvalidBlockNumber;
+}
diff --git a/src/include/storage/um_defs.h b/src/include/storage/um_defs.h
new file mode 100644
index 0000000000..3b567a397e
--- /dev/null
+++ b/src/include/storage/um_defs.h
@@ -0,0 +1,49 @@
+/*-------------------------------------------------------------------------
+ *
+ * um_defs.h
+ * Umbra low-level fork and metadata path definitions.
+ *
+ * This header contains storage-layout facts shared by Umbra submodules.
+ *
+ * src/include/storage/um_defs.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef UM_DEFS_H
+#define UM_DEFS_H
+
+#include <stdio.h>
+
+#include "common/relpath.h"
+#include "storage/relfilelocator.h"
+
+/*
+ * Umbra reserves an extra fork slot for relation-local metadata. This lives
+ * outside PostgreSQL's built-in fork numbering so ordinary smgr loops do not
+ * try to process it implicitly.
+ */
+#define UMBRA_METADATA_FORKNUM ((ForkNumber) (INIT_FORKNUM + 1))
+#define UMBRA_FORK_SLOTS (UMBRA_METADATA_FORKNUM + 1)
+
+static inline RelPathStr
+UmMetadataRelPathBackend(RelFileLocatorBackend rlocator)
+{
+ RelPathStr base;
+ RelPathStr path;
+
+ base = relpath(rlocator, MAIN_FORKNUM);
+ snprintf(path.str, sizeof(path.str), "%s_map", base.str);
+ return path;
+}
+
+static inline RelPathStr
+UmMetadataRelPathPerm(RelFileLocator rlocator)
+{
+ RelFileLocatorBackend backend_rlocator;
+
+ backend_rlocator.locator = rlocator;
+ backend_rlocator.backend = INVALID_PROC_NUMBER;
+ return UmMetadataRelPathBackend(backend_rlocator);
+}
+
+#endif /* UM_DEFS_H */
diff --git a/src/include/storage/umbra.h b/src/include/storage/umbra.h
index 9a2873f96d..30e033fcf0 100644
--- a/src/include/storage/umbra.h
+++ b/src/include/storage/umbra.h
@@ -17,6 +17,18 @@
#include "storage/block.h"
#include "storage/relfilelocator.h"
#include "storage/smgr.h"
+#include "storage/um_defs.h"
+
+extern bool UmMetadataExists(SMgrRelation reln);
+extern bool UmMetadataOpenOrCreate(SMgrRelation reln, bool isRedo, bool *created);
+extern BlockNumber UmMetadataNblocks(SMgrRelation reln);
+extern void UmMetadataRead(SMgrRelation reln, BlockNumber blkno, void *buffer);
+extern void UmMetadataWrite(SMgrRelation reln, BlockNumber blkno,
+ const void *buffer, bool skipFsync);
+extern void UmMetadataExtend(SMgrRelation reln, BlockNumber blkno,
+ const void *buffer, bool skipFsync);
+extern void UmMetadataImmediateSync(SMgrRelation reln);
+extern void UmMetadataUnlink(RelFileLocatorBackend rlocator, bool isRedo);
extern void uminit(void);
extern void umopen(SMgrRelation reln);
diff --git a/src/include/storage/umfile.h b/src/include/storage/umfile.h
new file mode 100644
index 0000000000..56936aa697
--- /dev/null
+++ b/src/include/storage/umfile.h
@@ -0,0 +1,101 @@
+/*-------------------------------------------------------------------------
+ *
+ * umfile.h
+ * Umbra backend-local file/context helpers.
+ *
+ * This layer owns backend-local file contexts keyed by RelFileLocatorBackend.
+ * It is the low-level file access boundary beneath Umbra metadata and mapping
+ * code.
+ *
+ * src/include/storage/umfile.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef UMFILE_H
+#define UMFILE_H
+
+#include "storage/aio_types.h"
+#include "storage/block.h"
+#include "storage/fd.h"
+#include "storage/relfilelocator.h"
+#include "storage/um_defs.h"
+
+typedef struct UmbraFileContext UmbraFileContext;
+
+typedef enum UmFileNblocksMode
+{
+ UMFILE_NBLOCKS_DENSE,
+ UMFILE_NBLOCKS_SPARSE
+} UmFileNblocksMode;
+
+typedef enum UmFileExistsMode
+{
+ UMFILE_EXISTS_DENSE,
+ UMFILE_EXISTS_SPARSE
+} UmFileExistsMode;
+
+extern void umfile_init(void);
+
+extern UmbraFileContext *umfile_ctx_lookup(RelFileLocatorBackend rlocator);
+extern UmbraFileContext *umfile_ctx_acquire(RelFileLocatorBackend rlocator);
+extern UmbraFileContext *umfile_ctx_create_temporary(RelFileLocatorBackend rlocator);
+extern void umfile_ctx_destroy_temporary(UmbraFileContext *ctx);
+extern void umfile_ctx_release(RelFileLocatorBackend rlocator);
+extern void umfile_ctx_forget(RelFileLocatorBackend rlocator);
+extern void umfile_ctx_close_fork(UmbraFileContext *ctx, ForkNumber forknum);
+
+extern bool umfile_ctx_fork_exists(UmbraFileContext *ctx, ForkNumber forknum,
+ UmFileExistsMode mode);
+extern BlockNumber umfile_ctx_get_nblocks(UmbraFileContext *ctx,
+ ForkNumber forknum,
+ UmFileNblocksMode mode);
+extern void umfile_ctx_read(UmbraFileContext *ctx, ForkNumber forknum,
+ BlockNumber blkno, char *buffer, int nbytes);
+extern void umfile_ctx_write(UmbraFileContext *ctx, ForkNumber forknum,
+ BlockNumber blkno, const char *buffer,
+ int nbytes, bool skipFsync);
+extern void umfile_ctx_extend(UmbraFileContext *ctx, ForkNumber forknum,
+ BlockNumber blkno, const char *buffer);
+extern void umfile_ctx_unlinkfork(RelFileLocatorBackend rlocator,
+ ForkNumber forknum, bool isRedo);
+
+extern bool umfile_exists(UmbraFileContext *ctx, ForkNumber forknum,
+ UmFileExistsMode mode);
+extern bool umfile_open_or_create(UmbraFileContext *ctx, ForkNumber forknum,
+ bool isRedo, bool *created);
+extern BlockNumber umfile_nblocks(UmbraFileContext *ctx, ForkNumber forknum,
+ UmFileNblocksMode mode);
+extern void umfile_readv(UmbraFileContext *ctx, ForkNumber forknum,
+ BlockNumber blocknum, void **buffers,
+ BlockNumber nblocks);
+extern void umfile_writev(UmbraFileContext *ctx, ForkNumber forknum,
+ BlockNumber blocknum, const void **buffers,
+ BlockNumber nblocks, bool skipFsync);
+extern void umfile_extend(UmbraFileContext *ctx, ForkNumber forknum,
+ BlockNumber blocknum, const void *buffer,
+ bool skipFsync);
+extern void umfile_zeroextend(UmbraFileContext *ctx, ForkNumber forknum,
+ BlockNumber blocknum, int nblocks,
+ bool skipFsync);
+extern void umfile_truncate(UmbraFileContext *ctx, ForkNumber forknum,
+ BlockNumber old_blocks, BlockNumber nblocks);
+extern void umfile_immedsync(UmbraFileContext *ctx, ForkNumber forknum);
+extern void umfile_registersync(UmbraFileContext *ctx, ForkNumber forknum);
+extern void umfile_unlink(RelFileLocatorBackend rlocator, ForkNumber forknum,
+ bool isRedo);
+
+/* Metadata-only convenience wrappers over the generic umfile surface. */
+extern bool umfile_metadata_exists(UmbraFileContext *ctx);
+extern bool umfile_metadata_open_or_create(UmbraFileContext *ctx,
+ bool isRedo, bool *created);
+extern BlockNumber umfile_metadata_nblocks(UmbraFileContext *ctx);
+extern void umfile_metadata_read(UmbraFileContext *ctx, BlockNumber blkno,
+ void *buffer);
+extern void umfile_metadata_write(UmbraFileContext *ctx, BlockNumber blkno,
+ const void *buffer);
+extern void umfile_metadata_extend(UmbraFileContext *ctx, BlockNumber blkno,
+ const void *buffer);
+extern void umfile_metadata_immedsync(UmbraFileContext *ctx);
+extern void umfile_metadata_unlink(RelFileLocatorBackend rlocator, bool isRedo);
+
+#endif /* UMFILE_H */
--
2.50.1 (Apple Git-155)
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Mingwei Jia | 2026-06-01 23:33:34 | [RFC PATCH v2 RESEND 04/10] umbra: add patch 3 metadata disk format and identity mapping bootstrap |
| Previous Message | Mingwei Jia | 2026-06-01 23:33:32 | [RFC PATCH v2 RESEND 02/10] umbra: add patch 1 smgr implementation boundary |