[RFC PATCH v2 RESEND 03/10] umbra: add patch 2 umfile physical file manager and metadata storage primitives - Mailing list pgsql-hackers

From Mingwei Jia
Subject [RFC PATCH v2 RESEND 03/10] umbra: add patch 2 umfile physical file manager and metadata storage primitives
Date
Msg-id 20260601233340.67949-2-i@nayishan.top
Whole thread
List 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)




pgsql-hackers by date:

Previous
From: Andres Freund
Date:
Subject: Re: Heads Up: cirrus-ci is shutting down June 1st
Next
From: Mingwei Jia
Date:
Subject: [RFC PATCH v2 RESEND 08/10] umbra: add patch 7 checkpoint-boundary FPW replacement and block-reference remap