[PATCH v1] erofs-utils: mkfs,oci: support tarindex mode with zinfo for OCI

ChengyuZhu6 hudson at cyzhu.com
Wed Oct 1 02:45:22 AEST 2025


From: Chengyu Zhu <hudsonzhu at tencent.com>

Introduce OCI tarindex mode and optional zinfo generation.

e.g.:
mkfs.erofs --oci=i,platform=linux/amd64,layer=3 \
--gzinfo=golang.zinfo golang.erofs golang:1.22.8

Signed-off-by: Chengyu Zhu <hudsonzhu at tencent.com>
---
 lib/remotes/oci.c | 57 ++++++++++++++++++++++++++++++++++++++---
 mkfs/main.c       | 65 ++++++++++++++++++++++++++++-------------------
 2 files changed, 92 insertions(+), 30 deletions(-)

diff --git a/lib/remotes/oci.c b/lib/remotes/oci.c
index b25e0b2..349e080 100644
--- a/lib/remotes/oci.c
+++ b/lib/remotes/oci.c
@@ -27,6 +27,7 @@
 #include "liberofs_base64.h"
 #include "liberofs_oci.h"
 #include "liberofs_private.h"
+#include "liberofs_gzran.h"
 
 #ifdef OCIEROFS_ENABLED
 
@@ -840,14 +841,33 @@ out:
 	return ret;
 }
 
-static int ocierofs_process_tar_stream(struct erofs_importer *importer, int fd)
+static int ocierofs_process_tar_stream(struct erofs_importer *importer, int fd,
+				       const struct ocierofs_config *config,
+				       u64 *tar_offset_out)
 {
 	struct erofs_tarfile tarfile = {};
-	int ret;
+	int ret, decoder, zinfo_fd;
+	struct erofs_vfile vf;
 
 	init_list_head(&tarfile.global.xattrs);
 
-	ret = erofs_iostream_open(&tarfile.ios, fd, EROFS_IOS_DECODER_GZIP);
+	/*
+	 * Choose decoder based on config:
+	 * - tarindex + zinfo  → tar.gzip (GZRAN decoder)
+	 * - tarindex only → tar (no decoder, raw)
+	 * - neither → default gzip decoder
+	 */
+	if (config && config->tarindex_path) {
+		tarfile.index_mode = true;
+		if (config->zinfo_path)
+			decoder = EROFS_IOS_DECODER_GZRAN;
+		else
+			decoder = EROFS_IOS_DECODER_NONE;
+	} else {
+		decoder = EROFS_IOS_DECODER_GZIP;
+	}
+
+	ret = erofs_iostream_open(&tarfile.ios, fd, decoder);
 	if (ret) {
 		erofs_err("failed to initialize tar stream: %s",
 			  erofs_strerror(ret));
@@ -858,6 +878,25 @@ static int ocierofs_process_tar_stream(struct erofs_importer *importer, int fd)
 		ret = tarerofs_parse_tar(importer, &tarfile);
 		/* Continue parsing until end of archive */
 	} while (!ret);
+
+	if (decoder == EROFS_IOS_DECODER_GZRAN) {
+		zinfo_fd = open(config->zinfo_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+		if (zinfo_fd < 0) {
+			ret = -errno;
+		} else {
+			vf = (struct erofs_vfile){ .fd = zinfo_fd };
+			ret = erofs_gzran_builder_export_zinfo(tarfile.ios.gb, &vf);
+			close(zinfo_fd);
+			if (ret < 0) {
+				erofs_err("failed to export zinfo: %s",
+					  erofs_strerror(ret));
+			}
+		}
+	}
+
+	if (tar_offset_out)
+		*tar_offset_out = tarfile.offset;
+
 	erofs_iostream_close(&tarfile.ios);
 
 	if (ret < 0 && ret != -ENODATA) {
@@ -1230,6 +1269,7 @@ int ocierofs_build_trees(struct erofs_importer *importer,
 {
 	struct ocierofs_ctx ctx = {};
 	int ret, i, end, fd;
+	u64 tar_offset = 0;
 
 	ret = ocierofs_init(&ctx, config);
 	if (ret) {
@@ -1250,6 +1290,12 @@ int ocierofs_build_trees(struct erofs_importer *importer,
 		end = ctx.layer_count;
 	}
 
+	if (config->tarindex_path && (end - i) != 1) {
+		erofs_err("tarindex mode requires exactly one layer (use blob= or layer= option)");
+		ret = -EINVAL;
+		goto out;
+	}
+
 	while (i < end) {
 		char *trimmed = erofs_trim_for_progressinfo(ctx.layers[i]->digest,
 				sizeof("Extracting layer  ...") - 1);
@@ -1263,7 +1309,7 @@ int ocierofs_build_trees(struct erofs_importer *importer,
 			ret = fd;
 			break;
 		}
-		ret = ocierofs_process_tar_stream(importer, fd);
+		ret = ocierofs_process_tar_stream(importer, fd, config, &tar_offset);
 		close(fd);
 		if (ret) {
 			erofs_err("failed to process tar stream for layer %s: %s",
@@ -1273,6 +1319,9 @@ int ocierofs_build_trees(struct erofs_importer *importer,
 		i++;
 	}
 out:
+	if (config->tarindex_path && importer->sbi)
+		importer->sbi->devs[0].blocks = BLK_ROUND_UP(importer->sbi, tar_offset);
+
 	ocierofs_ctx_cleanup(&ctx);
 	return ret;
 }
diff --git a/mkfs/main.c b/mkfs/main.c
index 1c37576..c7359f6 100644
--- a/mkfs/main.c
+++ b/mkfs/main.c
@@ -218,6 +218,8 @@ static void usage(int argc, char **argv)
 		"   [,blob=Y]           Y=blob digest to extract (omit to extract all layers)\n"
 		"   [,username=Z]       Z=username for authentication (optional)\n"
 		"   [,password=W]       W=password for authentication (optional)\n"
+		"   [,i]                generate tarindex file (requires layer or blob selection)\n"
+
 #endif
 		" --tar=X               generate a full or index-only image from a tarball(-ish) source\n"
 		"                       (X = f|i|headerball; f=full mode, i=index mode,\n"
@@ -285,7 +287,7 @@ static struct erofs_s3 s3cfg;
 
 #ifdef OCIEROFS_ENABLED
 static struct ocierofs_config ocicfg;
-static char *mkfs_oci_options;
+static bool mkfs_oci_tarindex_mode;
 #endif
 
 enum {
@@ -727,7 +729,7 @@ static int mkfs_parse_s3_cfg(char *cfg_str)
  * @options_str: comma-separated options string
  *
  * Parse OCI options string containing comma-separated key=value pairs.
- * Supported options include platform, blob, layer, username, and password.
+ * Supported options include platform, blob, layer, username, password, i (tarindex mode), and zinfo.
  *
  * Return: 0 on success, negative errno on failure
  */
@@ -745,6 +747,7 @@ static int mkfs_parse_oci_options(struct ocierofs_config *oci_cfg, char *options
 		if (q)
 			*q = '\0';
 
+
 		p = strstr(opt, "platform=");
 		if (p) {
 			p += strlen("platform=");
@@ -790,19 +793,23 @@ static int mkfs_parse_oci_options(struct ocierofs_config *oci_cfg, char *options
 						oci_cfg->username = strdup(p);
 						if (!oci_cfg->username)
 							return -ENOMEM;
+					} else {
+						p = strstr(opt, "password=");
+						if (p) {
+							p += strlen("password=");
+							free(oci_cfg->password);
+							oci_cfg->password = strdup(p);
+							if (!oci_cfg->password)
+								return -ENOMEM;
+						} else {
+							if (!strcmp(opt, "i"))
+								mkfs_oci_tarindex_mode = true;
+							else {
+								erofs_err("mkfs: invalid --oci value %s", opt);
+								return -EINVAL;
+							}
+						}
 					}
-
-					p = strstr(opt, "password=");
-					if (p) {
-						p += strlen("password=");
-						free(oci_cfg->password);
-						oci_cfg->password = strdup(p);
-						if (!oci_cfg->password)
-							return -ENOMEM;
-					}
-
-					erofs_err("mkfs: invalid --oci value %s", opt);
-					return -EINVAL;
 				}
 			}
 		}
@@ -1378,10 +1385,13 @@ static int mkfs_parse_options_cfg(struct erofs_importer_params *params,
 			break;
 #endif
 #ifdef OCIEROFS_ENABLED
-		case 534:
-			mkfs_oci_options = optarg;
+		case 534: {
 			source_mode = EROFS_MKFS_SOURCE_OCI;
+			err = mkfs_parse_oci_options(&ocicfg, optarg);
+			if (err)
+				return err;
 			break;
+		}
 #endif
 		case 535:
 			if (optarg)
@@ -1757,6 +1767,9 @@ int main(int argc, char **argv)
 			goto exit;
 		}
 		mkfs_blkszbits = src->blkszbits;
+	} else if (mkfs_oci_tarindex_mode) {
+		mkfs_blkszbits = 9;
+		tar_index_512b = true;
 	}
 
 	if (!incremental_mode)
@@ -1883,13 +1896,11 @@ int main(int argc, char **argv)
 #endif
 #ifdef OCIEROFS_ENABLED
 		} else if (source_mode == EROFS_MKFS_SOURCE_OCI) {
-			ocicfg.blob_digest = NULL;
-			ocicfg.layer_index = -1;
-
-			err = mkfs_parse_oci_options(&ocicfg, mkfs_oci_options);
-			if (err)
-				goto exit;
 			ocicfg.image_ref = cfg.c_src_path;
+			if (mkfs_oci_tarindex_mode)
+				ocicfg.tarindex_path = strdup(cfg.c_src_path);
+			if (!ocicfg.zinfo_path)
+				ocicfg.zinfo_path = mkfs_aws_zinfo_file;
 
 			if (incremental_mode ||
 			    dataimport_mode == EROFS_MKFS_DATA_IMPORT_RVSP ||
@@ -1914,10 +1925,12 @@ int main(int argc, char **argv)
 		if (!g_sbi.extra_devices) {
 			DBG_BUGON(1);
 		} else {
-			if (cfg.c_src_path)
-				g_sbi.devs[0].src_path = strdup(cfg.c_src_path);
-			g_sbi.devs[0].blocks =
-				BLK_ROUND_UP(&g_sbi, erofstar.offset);
+			if (source_mode != EROFS_MKFS_SOURCE_OCI) {
+				if (cfg.c_src_path)
+					g_sbi.devs[0].src_path = strdup(cfg.c_src_path);
+				g_sbi.devs[0].blocks =
+					BLK_ROUND_UP(&g_sbi, erofstar.offset);
+			}
 		}
 	}
 
-- 
2.47.1



More information about the Linux-erofs mailing list