---
 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)




Reply via email to