[PATCH v2 4/5] erofs-utils: mfks: add rebuild FULLDATA for combined EROFS images

Lucas Karpinski lkarpinski at nvidia.com
Tue Mar 10 03:38:20 AEDT 2026


This patch introduces experimental support for merging multiple source
images in mkfs. Each source image becomes a directory directly under root
and keeps its UUID stored as a device table tag. The raw block data from
each source is copied using erofs_copy_file_range. We preserve the file
metadata and layout (FLAT_PLAIN and FLAT_INLINE). Symlink paths are handled
by reading and copy link targets.

This does not yet support chunk-based files at this time or compressed
images.

Signed-off-by: Lucas Karpinski <lkarpinski at nvidia.com>
---
 lib/cache.c            |   6 +++
 lib/liberofs_cache.h   |   1 +
 lib/liberofs_rebuild.h |   4 ++
 lib/rebuild.c          | 112 +++++++++++++++++++++++++++++++++++++++++++++++--
 mkfs/main.c            |  44 ++++++++++++++-----
 5 files changed, 154 insertions(+), 13 deletions(-)

diff --git a/lib/cache.c b/lib/cache.c
index 4c7c386..49742bc 100644
--- a/lib/cache.c
+++ b/lib/cache.c
@@ -544,6 +544,12 @@ erofs_blk_t erofs_total_metablocks(struct erofs_bufmgr *bmgr)
 	return bmgr->metablkcnt;
 }
 
+void erofs_bset_tail(struct erofs_bufmgr *bmgr, erofs_blk_t blkaddr)
+{
+	if (blkaddr > bmgr->tail_blkaddr)
+		bmgr->tail_blkaddr = blkaddr;
+}
+
 void erofs_buffer_exit(struct erofs_bufmgr *bmgr)
 {
 	DBG_BUGON(__erofs_bflush(bmgr, NULL, true));
diff --git a/lib/liberofs_cache.h b/lib/liberofs_cache.h
index baac609..55e8f25 100644
--- a/lib/liberofs_cache.h
+++ b/lib/liberofs_cache.h
@@ -138,6 +138,7 @@ int erofs_bflush(struct erofs_bufmgr *bmgr,
 		 struct erofs_buffer_block *bb);
 
 void erofs_bdrop(struct erofs_buffer_head *bh, bool tryrevoke);
+void erofs_bset_tail(struct erofs_bufmgr *bmgr, erofs_blk_t blkaddr);
 void erofs_buffer_exit(struct erofs_bufmgr *bmgr);
 
 #ifdef __cplusplus
diff --git a/lib/liberofs_rebuild.h b/lib/liberofs_rebuild.h
index d8c4c8a..fba7f39 100644
--- a/lib/liberofs_rebuild.h
+++ b/lib/liberofs_rebuild.h
@@ -17,6 +17,10 @@ int erofs_rebuild_load_tree(struct erofs_inode *root, struct erofs_sb_info *sbi,
 			    enum erofs_rebuild_datamode mode,
 			    erofs_blk_t uniaddr_offset);
 
+int erofs_rebuild_copy_src(struct erofs_sb_info *sbi,
+			   struct erofs_sb_info *src,
+			   struct erofs_device_info *dev);
+
 int erofs_rebuild_load_basedir(struct erofs_inode *dir, u64 *nr_subdirs,
 			       unsigned int *i_nlink);
 #endif
diff --git a/lib/rebuild.c b/lib/rebuild.c
index 7e62bc9..451307a 100644
--- a/lib/rebuild.c
+++ b/lib/rebuild.c
@@ -14,8 +14,10 @@
 #include "erofs/xattr.h"
 #include "erofs/blobchunk.h"
 #include "erofs/internal.h"
+#include "erofs/io.h"
 #include "liberofs_rebuild.h"
 #include "liberofs_uuid.h"
+#include "liberofs_cache.h"
 
 #ifdef HAVE_LINUX_AUFS_TYPE_H
 #include <linux/aufs_type.h>
@@ -221,9 +223,60 @@ err:
 	return ret;
 }
 
+static int erofs_rebuild_write_full_data(struct erofs_inode *inode,
+					 erofs_blk_t uniaddr_offset)
+{
+	struct erofs_sb_info *src_sbi = inode->sbi;
+	int err = 0;
+
+	if (inode->datalayout == EROFS_INODE_FLAT_PLAIN) {
+		if (inode->u.i_blkaddr != EROFS_NULL_ADDR)
+			inode->u.i_blkaddr += uniaddr_offset;
+	} else if (inode->datalayout == EROFS_INODE_FLAT_INLINE) {
+		erofs_blk_t nblocks = erofs_blknr(src_sbi, inode->i_size);
+		unsigned int inline_size = inode->i_size % erofs_blksiz(src_sbi);
+
+		if (nblocks > 0 && inode->u.i_blkaddr != EROFS_NULL_ADDR)
+			inode->u.i_blkaddr += uniaddr_offset;
+
+		inode->idata_size = inline_size;
+		if (inline_size > 0) {
+			struct erofs_vfile vf;
+			erofs_off_t tail_offset = erofs_pos(src_sbi, nblocks);
+
+			inode->idata = malloc(inline_size);
+			if (!inode->idata)
+				return -ENOMEM;
+			err = erofs_iopen(&vf, inode);
+			if (err) {
+				free(inode->idata);
+				inode->idata = NULL;
+				return err;
+			}
+			err = erofs_pread(&vf, inode->idata, inline_size,
+					  tail_offset);
+			if (err) {
+				free(inode->idata);
+				inode->idata = NULL;
+				return err;
+			}
+		}
+	} else if (inode->datalayout == EROFS_INODE_CHUNK_BASED) {
+		erofs_err("chunk-based files not yet supported: %s",
+			  inode->i_srcpath);
+		err = -EOPNOTSUPP;
+	} else if (is_inode_layout_compression(inode)) {
+		erofs_err("compressed files not yet supported: %s",
+			  inode->i_srcpath);
+		err = -EOPNOTSUPP;
+	}
+	return err;
+}
+
 static int erofs_rebuild_update_inode(struct erofs_sb_info *dst_sb,
 				      struct erofs_inode *inode,
-				      enum erofs_rebuild_datamode datamode)
+				      enum erofs_rebuild_datamode datamode,
+				      erofs_blk_t uniaddr_offset)
 {
 	int err = 0;
 
@@ -265,6 +318,8 @@ static int erofs_rebuild_update_inode(struct erofs_sb_info *dst_sb,
 			err = erofs_rebuild_write_blob_index(dst_sb, inode);
 		else if (datamode == EROFS_REBUILD_DATA_RESVSP)
 			inode->datasource = EROFS_INODE_DATA_SOURCE_RESVSP;
+		else if (datamode == EROFS_REBUILD_DATA_FULL)
+			err = erofs_rebuild_write_full_data(inode, uniaddr_offset);
 		else
 			err = -EOPNOTSUPP;
 		break;
@@ -387,7 +442,8 @@ static int erofs_rebuild_dirent_iter(struct erofs_dir_context *ctx)
 			inode->i_nlink = 1;
 
 			ret = erofs_rebuild_update_inode(&g_sbi, inode,
-							 rctx->datamode);
+							 rctx->datamode,
+							 rctx->uniaddr_offset);
 			if (ret) {
 				erofs_iput(inode);
 				goto out;
@@ -425,6 +481,7 @@ int erofs_rebuild_load_tree(struct erofs_inode *root, struct erofs_sb_info *sbi,
 {
 	struct erofs_inode inode = {};
 	struct erofs_rebuild_dir_context ctx;
+	struct erofs_inode *mergedir;
 	char uuid_str[37];
 	char *fsid = sbi->devname;
 	int ret;
@@ -447,16 +504,19 @@ int erofs_rebuild_load_tree(struct erofs_inode *root, struct erofs_sb_info *sbi,
 		erofs_err("failed to read root inode of %s", fsid);
 		return ret;
 	}
+
+	mergedir = root;
 	inode.i_srcpath = strdup("/");
 
 	ctx = (struct erofs_rebuild_dir_context) {
 		.ctx.dir = &inode,
 		.ctx.cb = erofs_rebuild_dirent_iter,
-		.mergedir = root,
+		.mergedir = mergedir,
 		.datamode = mode,
 		.uniaddr_offset = uniaddr_offset,
 	};
 	ret = erofs_iterate_dir(&ctx.ctx, false);
+
 	free(inode.i_srcpath);
 	return ret;
 }
@@ -556,3 +616,49 @@ int erofs_rebuild_load_basedir(struct erofs_inode *dir, u64 *nr_subdirs,
 	};
 	return erofs_iterate_dir(&ctx.ctx, false);
 }
+
+int erofs_rebuild_copy_src(struct erofs_sb_info *sbi,
+			   struct erofs_sb_info *src,
+			   struct erofs_device_info *dev)
+{
+	erofs_blk_t cur = sbi->primarydevice_blocks;
+	u64 src_off = 0, dst_off, len;
+	int src_fd, dst_fd;
+	int ret;
+
+	ret = erofs_read_superblock(src);
+	if (ret) {
+		erofs_err("failed to read superblock of %s: %s",
+			  src->devname, erofs_strerror(ret));
+		return ret;
+	}
+
+	dev->blocks = src->primarydevice_blocks;
+	dev->uniaddr = cur;
+
+	erofs_info("Copying %s: %u blocks at unified address %u",
+		   src->devname, dev->blocks, cur);
+
+	src_fd = src->bdev.fd;
+	dst_fd = sbi->bdev.fd;
+	if (src_fd < 0 || dst_fd < 0) {
+		erofs_err("failed to get file descriptors");
+		return -EINVAL;
+	}
+	dst_off = erofs_pos(sbi, cur);
+	len = erofs_pos(src, dev->blocks);
+	while (len > 0) {
+		ssize_t copied = erofs_copy_file_range(src_fd, &src_off,
+						       dst_fd, &dst_off, len);
+		if (copied < 0) {
+			erofs_err("failed to copy data from %s: %s",
+				  src->devname, erofs_strerror(-copied));
+			return copied;
+		}
+		if (copied == 0)
+			break;
+		len -= copied;
+	}
+	sbi->primarydevice_blocks += dev->blocks;
+	return 0;
+}
diff --git a/mkfs/main.c b/mkfs/main.c
index 48da20f..4ac835f 100644
--- a/mkfs/main.c
+++ b/mkfs/main.c
@@ -15,9 +15,11 @@
 #include <getopt.h>
 #include "erofs/config.h"
 #include "erofs/print.h"
+#include "erofs/io.h"
 #include "erofs/importer.h"
 #include "erofs/diskbuf.h"
 #include "erofs/inode.h"
+#include "erofs/dir.h"
 #include "erofs/tar.h"
 #include "erofs/dedupe.h"
 #include "erofs/xattr.h"
@@ -30,6 +32,7 @@
 #include "../lib/liberofs_metabox.h"
 #include "../lib/liberofs_oci.h"
 #include "../lib/liberofs_private.h"
+#include "../lib/liberofs_cache.h"
 #include "../lib/liberofs_rebuild.h"
 #include "../lib/liberofs_s3.h"
 #include "../lib/liberofs_uuid.h"
@@ -1717,7 +1720,7 @@ static int erofs_mkfs_rebuild_load_trees(struct erofs_inode *root)
 	struct erofs_sb_info *src;
 	unsigned int extra_devices = 0;
 	erofs_blk_t nblocks;
-	int ret, idx;
+	int ret, idx = 0;
 	enum erofs_rebuild_datamode datamode;
 
 	switch (dataimport_mode) {
@@ -1734,9 +1737,33 @@ static int erofs_mkfs_rebuild_load_trees(struct erofs_inode *root)
 		return -EINVAL;
 	}
 
+	if (datamode != EROFS_REBUILD_DATA_RESVSP) {
+		ret = erofs_mkfs_init_devices(&g_sbi, rebuild_src_count);
+		if (ret) {
+			erofs_err("failed to initialize devices: %s",
+				  erofs_strerror(ret));
+			return ret;
+		}
+		devs = g_sbi.devs;
+	}
+
 	list_for_each_entry(src, &rebuild_src_list, list) {
+		erofs_blk_t uniaddr = 0;
+
+		if (datamode == EROFS_REBUILD_DATA_FULL) {
+			/* Copy source data blocks */
+			ret = erofs_rebuild_copy_src(&g_sbi, src, &devs[idx]);
+			if (ret)
+				return ret;
+
+			uniaddr = devs[idx].uniaddr;
+
+			/* Advance buffer manager past copied data */
+			erofs_bset_tail(g_sbi.bmgr, g_sbi.primarydevice_blocks);
+		}
+
 		src->xamgr = g_sbi.xamgr;
-		ret = erofs_rebuild_load_tree(root, src, datamode, 0);
+		ret = erofs_rebuild_load_tree(root, src, datamode, uniaddr);
 		src->xamgr = NULL;
 		if (ret) {
 			erofs_err("failed to load %s", src->devname);
@@ -1748,9 +1775,10 @@ static int erofs_mkfs_rebuild_load_trees(struct erofs_inode *root)
 			return -EOPNOTSUPP;
 		}
 		extra_devices += src->extra_devices;
+		idx++;
 	}
 
-	if (datamode != EROFS_REBUILD_DATA_BLOB_INDEX)
+	if (datamode == EROFS_REBUILD_DATA_RESVSP)
 		return 0;
 
 	/* Each blob has either no extra device or only one device for TarFS */
@@ -1760,11 +1788,6 @@ static int erofs_mkfs_rebuild_load_trees(struct erofs_inode *root)
 		return -EOPNOTSUPP;
 	}
 
-	ret = erofs_mkfs_init_devices(&g_sbi, rebuild_src_count);
-	if (ret)
-		return ret;
-
-	devs = g_sbi.devs;
 	list_for_each_entry(src, &rebuild_src_list, list) {
 		u8 *tag = NULL;
 
@@ -1775,14 +1798,15 @@ static int erofs_mkfs_rebuild_load_trees(struct erofs_inode *root)
 			tag = src->devs[0].tag;
 		} else {
 			nblocks = src->primarydevice_blocks;
-			devs[idx].src_path = strdup(src->devname);
+			if (datamode == EROFS_REBUILD_DATA_BLOB_INDEX)
+				devs[idx].src_path = strdup(src->devname);
 		}
 		devs[idx].blocks = nblocks;
 		if (tag && *tag)
 			memcpy(devs[idx].tag, tag, sizeof(devs[0].tag));
 		else
 			/* convert UUID of the source image to a hex string */
-			erofs_uuid_unparse_as_tag(src->uuid, (char *)g_sbi.devs[idx].tag);
+			erofs_uuid_unparse_as_tag(src->uuid, (char *)devs[idx].tag);
 	}
 	return 0;
 }

-- 
Git-155)


More information about the Linux-erofs mailing list