[PATCH 3/3] erofs-utils: mkfs: enable incremental builds for local files
Gao Xiang
hsiangkao at linux.alibaba.com
Sun Sep 28 16:32:32 AEST 2025
EROFS supports incremental-style builds without rewriting the entire
filesystem metadata, since it does not have classic centralized inode
tables or SquashFS-style directory tables.
This commit adds incremental build support for local files:
$ mkfs.erofs foo.erofs layer0/
$ mkfs.erofs --incremental=data foo.erofs layer1/
...
OverlayFS whiteouts are supported for replacing specific directory
entries. Additionally, these extra whiteouts can be dropped from the
final image using `--ovlfs-strip`
Signed-off-by: Gao Xiang <hsiangkao at linux.alibaba.com>
---
lib/inode.c | 54 +++++++++++++++++++++++++++---------------
lib/liberofs_rebuild.h | 4 ++--
lib/rebuild.c | 26 +++++++++++++-------
mkfs/main.c | 1 -
4 files changed, 54 insertions(+), 31 deletions(-)
diff --git a/lib/inode.c b/lib/inode.c
index 810ffc2..74c9645 100644
--- a/lib/inode.c
+++ b/lib/inode.c
@@ -1294,6 +1294,7 @@ struct erofs_inode *erofs_new_inode(struct erofs_sb_info *sbi)
static struct erofs_inode *erofs_iget_from_local(struct erofs_importer *im,
const char *path)
{
+ const struct erofs_importer_params *params = im->params;
struct erofs_sb_info *sbi = im->sbi;
struct erofs_inode *inode;
struct stat st;
@@ -1308,7 +1309,7 @@ static struct erofs_inode *erofs_iget_from_local(struct erofs_importer *im,
* hard-link, just return it. Also don't lookup for directories
* since hard-link directory isn't allowed.
*/
- if (!S_ISDIR(st.st_mode) && !im->params->hard_dereference) {
+ if (!S_ISDIR(st.st_mode) && !params->hard_dereference) {
inode = erofs_iget(st.st_dev, st.st_ino);
if (inode)
return inode;
@@ -1669,6 +1670,8 @@ static int erofs_mkfs_import_localdir(struct erofs_importer *im, struct erofs_in
ret = PTR_ERR(inode);
goto err_closedir;
}
+ if (!dir->whiteouts && erofs_inode_is_whiteout(inode))
+ dir->whiteouts = true;
d->inode = inode;
d->type = erofs_mode_to_ftype(inode->i_mode);
__nlink += S_ISDIR(inode->i_mode);
@@ -1712,16 +1715,15 @@ static void erofs_dentry_kill(struct erofs_dentry *d)
free(d);
}
-static int erofs_mkfs_handle_directory(struct erofs_importer *im,
- struct erofs_inode *dir,
- bool rebuild,
- bool incremental)
+static int erofs_prepare_dir_inode(struct erofs_importer *im,
+ struct erofs_inode *dir,
+ bool rebuild,
+ bool incremental)
{
struct erofs_sb_info *sbi = im->sbi;
struct erofs_dentry *d, *n;
unsigned int i_nlink;
u64 nr_subdirs;
- bool delwht = im->params->ovlfs_strip && dir->whiteouts;
int ret;
nr_subdirs = 0;
@@ -1733,11 +1735,6 @@ static int erofs_mkfs_handle_directory(struct erofs_importer *im,
erofs_dentry_kill(d);
continue;
}
- if (delwht && erofs_dentry_is_wht(sbi, d)) {
- erofs_dbg("remove whiteout %s", d->inode->i_srcpath);
- erofs_dentry_kill(d);
- continue;
- }
i_nlink += (d->type == EROFS_FT_DIR);
++nr_subdirs;
}
@@ -1749,6 +1746,22 @@ static int erofs_mkfs_handle_directory(struct erofs_importer *im,
return ret;
}
+ if (incremental && dir->dev == sbi->dev && !dir->opaque) {
+ ret = erofs_rebuild_load_basedir(dir, &nr_subdirs, &i_nlink);
+ if (ret)
+ return ret;
+ }
+ if (im->params->ovlfs_strip && dir->whiteouts) {
+ list_for_each_entry_safe(d, n, &dir->i_subdirs, d_child) {
+ if (erofs_dentry_is_wht(sbi, d)) {
+ erofs_dbg("remove whiteout %s",
+ d->inode->i_srcpath);
+ erofs_dentry_kill(d);
+ --nr_subdirs;
+ continue;
+ }
+ }
+ }
DBG_BUGON(nr_subdirs + 2 < i_nlink);
ret = erofs_prepare_dir_file(im, dir, nr_subdirs);
if (ret)
@@ -1769,8 +1782,7 @@ static int erofs_mkfs_handle_directory(struct erofs_importer *im,
else
dir->i_nlink = 1;
}
-
- return erofs_mkfs_go(im, EROFS_MKFS_JOB_DIR, &dir, sizeof(dir));
+ return 0;
}
static int erofs_mkfs_begin_nondirectory(struct erofs_importer *im,
@@ -1830,10 +1842,9 @@ static int erofs_mkfs_handle_inode(struct erofs_importer *im,
inode->inode_isize = sizeof(struct erofs_inode_compact);
}
- if (incremental && S_ISDIR(inode->i_mode) &&
- inode->dev == inode->sbi->dev && !inode->opaque) {
- ret = erofs_rebuild_load_basedir(inode);
- if (ret)
+ if (S_ISDIR(inode->i_mode)) {
+ ret = erofs_prepare_dir_inode(im, inode, rebuild, incremental);
+ if (ret < 0)
return ret;
}
@@ -1856,8 +1867,8 @@ static int erofs_mkfs_handle_inode(struct erofs_importer *im,
if (!S_ISDIR(inode->i_mode)) {
ret = erofs_mkfs_begin_nondirectory(im, inode);
} else {
- ret = erofs_mkfs_handle_directory(im, inode,
- rebuild, incremental);
+ ret = erofs_mkfs_go(im, EROFS_MKFS_JOB_DIR, &inode,
+ sizeof(inode));
}
erofs_info("file %s dumped (mode %05o)", *relpath ? relpath : "/",
inode->i_mode);
@@ -2071,6 +2082,11 @@ fail:
int erofs_importer_load_tree(struct erofs_importer *im, bool rebuild,
bool incremental)
{
+ if (__erofs_unlikely(incremental && erofs_sb_has_metabox(im->sbi))) {
+ erofs_err("Metadata-compressed filesystems don't support incremental builds for now");
+ return -EOPNOTSUPP;
+ }
+
return erofs_mkfs_build_tree(&((struct erofs_mkfs_buildtree_ctx) {
.im = im,
.rebuild = rebuild,
diff --git a/lib/liberofs_rebuild.h b/lib/liberofs_rebuild.h
index 1eb79cf..69802fb 100644
--- a/lib/liberofs_rebuild.h
+++ b/lib/liberofs_rebuild.h
@@ -16,6 +16,6 @@ struct erofs_dentry *erofs_rebuild_get_dentry(struct erofs_inode *pwd,
int erofs_rebuild_load_tree(struct erofs_inode *root, struct erofs_sb_info *sbi,
enum erofs_rebuild_datamode mode);
-int erofs_rebuild_load_basedir(struct erofs_inode *dir);
-
+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 83e30fd..c5b44d5 100644
--- a/lib/rebuild.c
+++ b/lib/rebuild.c
@@ -274,15 +274,18 @@ static int erofs_rebuild_update_inode(struct erofs_sb_info *dst_sb,
return err;
}
-/*
- * @mergedir: parent directory in the merged tree
- * @ctx.dir: parent directory when itering erofs_iterate_dir()
- * @datamode: indicate how to import inode data
- */
struct erofs_rebuild_dir_context {
+ /* @ctx.dir: parent directory when itering erofs_iterate_dir() */
struct erofs_dir_context ctx;
- struct erofs_inode *mergedir;
- enum erofs_rebuild_datamode datamode;
+ struct erofs_inode *mergedir; /* parent directory in the merged tree */
+ union {
+ /* indicate how to import inode data */
+ enum erofs_rebuild_datamode datamode;
+ struct {
+ u64 *nr_subdirs;
+ unsigned int *i_nlink;
+ };
+ };
};
static int erofs_rebuild_dirent_iter(struct erofs_dir_context *ctx)
@@ -458,8 +461,8 @@ int erofs_rebuild_load_tree(struct erofs_inode *root, struct erofs_sb_info *sbi,
static int erofs_rebuild_basedir_dirent_iter(struct erofs_dir_context *ctx)
{
struct erofs_rebuild_dir_context *rctx = (void *)ctx;
- struct erofs_inode *dir = ctx->dir;
struct erofs_inode *mergedir = rctx->mergedir;
+ struct erofs_inode *dir = ctx->dir;
struct erofs_dentry *d;
char *dname;
bool dumb;
@@ -484,6 +487,8 @@ static int erofs_rebuild_basedir_dirent_iter(struct erofs_dir_context *ctx)
d->validnid = true;
if (!mergedir->whiteouts && erofs_dentry_is_wht(dir->sbi, d))
mergedir->whiteouts = true;
+ *rctx->i_nlink += (ctx->de_ftype == EROFS_FT_DIR);
+ ++*rctx->nr_subdirs;
} else if (__erofs_unlikely(d->validnid)) {
/* The base image appears to be corrupted */
DBG_BUGON(1);
@@ -508,7 +513,8 @@ out:
return ret;
}
-int erofs_rebuild_load_basedir(struct erofs_inode *dir)
+int erofs_rebuild_load_basedir(struct erofs_inode *dir, u64 *nr_subdirs,
+ unsigned int *i_nlink)
{
struct erofs_inode fakeinode = {
.sbi = dir->sbi,
@@ -540,6 +546,8 @@ int erofs_rebuild_load_basedir(struct erofs_inode *dir)
.ctx.dir = &fakeinode,
.ctx.cb = erofs_rebuild_basedir_dirent_iter,
.mergedir = dir,
+ .nr_subdirs = nr_subdirs,
+ .i_nlink = i_nlink,
};
return erofs_iterate_dir(&ctx.ctx, false);
}
diff --git a/mkfs/main.c b/mkfs/main.c
index 7a538bd..f3cf24e 100644
--- a/mkfs/main.c
+++ b/mkfs/main.c
@@ -1835,7 +1835,6 @@ int main(int argc, char **argv)
err = PTR_ERR(root);
goto exit;
}
- incremental_mode = false;
} else {
root = erofs_rebuild_make_root(&g_sbi);
if (IS_ERR(root)) {
--
2.43.5
More information about the Linux-erofs
mailing list