[PATCH v4] erofs-utils: oci: add support for indexing by layer digest

Gao Xiang hsiangkao at linux.alibaba.com
Tue Sep 23 13:19:03 AEST 2025



On 2025/9/23 10:55, ChengyuZhu6 wrote:
> From: Chengyu Zhu <hudsonzhu at tencent.com>
> 
> Add support for indexing by layer_digest string for more precise
> and reliable OCI layer identification. This change affects both mkfs.erofs
> and mount.erofs tools.
> 
> Signed-off-by: Chengyu Zhu <hudsonzhu at tencent.com>
> ---
>   lib/liberofs_oci.h |   6 +-
>   lib/remotes/oci.c  |  87 +++++++++++++++++++-------
>   mkfs/main.c        |  78 ++++++++++++++---------
>   mount/main.c       | 153 ++++++++++++++++++++++++++++++++-------------
>   4 files changed, 228 insertions(+), 96 deletions(-)
> 
> diff --git a/lib/liberofs_oci.h b/lib/liberofs_oci.h
> index aa41141..621eb2b 100644
> --- a/lib/liberofs_oci.h
> +++ b/lib/liberofs_oci.h
> @@ -21,7 +21,8 @@ struct erofs_importer;
>    * @platform: target platform in "os/arch" format (e.g., "linux/amd64")
>    * @username: username for authentication (optional)
>    * @password: password for authentication (optional)
> - * @layer_index: specific layer to extract (-1 for all layers)
> + * @layer_digest: specific layer digest to extract (NULL for all layers)

blob_digest?

> + * @layer_index: specific layer index to extract (negative for all layers)
>    *
>    * Configuration structure for OCI image parameters including registry
>    * location, image identification, platform specification, and authentication
> @@ -32,6 +33,7 @@ struct ocierofs_config {
>   	char *platform;
>   	char *username;
>   	char *password;
> +	char *layer_digest;

blob_digest?

>   	int layer_index;
>   };
>   
> @@ -51,7 +53,7 @@ struct ocierofs_ctx {
>   	char *tag;
>   	char *manifest_digest;
>   	struct ocierofs_layer_info **layers;
> -	int layer_index;
> +	char *layer_digest;

blob_digest?

>   	int layer_count;
>   };
>   
> diff --git a/lib/remotes/oci.c b/lib/remotes/oci.c
> index 26aec27..b6118da 100644
> --- a/lib/remotes/oci.c
> +++ b/lib/remotes/oci.c
> @@ -898,6 +898,20 @@ static int ocierofs_prepare_auth(struct ocierofs_ctx *ctx,
>   	return 0;
>   }
>   
> +static int ocierofs_find_layer_by_digest(struct ocierofs_ctx *ctx, const char *digest)
> +{
> +	int i;
> +
> +	for (i = 0; i < ctx->layer_count; i++) {
> +		DBG_BUGON(!ctx->layers[i]);
> +		DBG_BUGON(!ctx->layers[i]->digest);
> +
> +		if (!strcmp(ctx->layers[i]->digest, digest))
> +			return i;
> +	}
> +	return -1;
> +}
> +
>   static int ocierofs_prepare_layers(struct ocierofs_ctx *ctx,
>   				   const struct ocierofs_config *config)
>   {
> @@ -925,16 +939,34 @@ static int ocierofs_prepare_layers(struct ocierofs_ctx *ctx,
>   		goto out_manifest;
>   	}
>   
> -	if (ctx->layer_index >= ctx->layer_count) {
> -		erofs_err("layer index %d exceeds available layers (%d)",
> -			  ctx->layer_index, ctx->layer_count);
> -		ret = -EINVAL;
> -		goto out_layers;
> +	if (!ctx->layer_digest && config->layer_index >= 0) {
> +		if (config->layer_index >= ctx->layer_count) {
> +			erofs_err("layer index %d out of range (0..%d)",
> +				  config->layer_index, ctx->layer_count - 1);
> +			ret = -EINVAL;
> +			goto out_layers;
> +		}
> +		DBG_BUGON(!ctx->layers[config->layer_index]);
> +		DBG_BUGON(!ctx->layers[config->layer_index]->digest);
> +		ctx->layer_digest = strdup(ctx->layers[config->layer_index]->digest);
> +		if (!ctx->layer_digest) {
> +			ret = -ENOMEM;
> +			goto out_layers;
> +		}
> +	}
> +
> +	if (ctx->layer_digest) {
> +		if (ocierofs_find_layer_by_digest(ctx, ctx->layer_digest) < 0) {
> +			erofs_err("layer digest %s not found in image layers",
> +				  ctx->layer_digest);
> +			ret = -ENOENT;
> +			goto out_layers;
> +		}
>   	}
>   	return 0;
>   
>   out_layers:
> -	free(ctx->layers);
> +	ocierofs_free_layers_info(ctx->layers, ctx->layer_count);
>   	ctx->layers = NULL;
>   out_manifest:
>   	free(ctx->manifest_digest);
> @@ -1054,10 +1086,10 @@ static int ocierofs_init(struct ocierofs_ctx *ctx, const struct ocierofs_config
>   	if (ocierofs_curl_setup_common_options(ctx->curl))
>   		return -EIO;
>   
> -	if (config->layer_index >= 0)
> -		ctx->layer_index = config->layer_index;
> +	if (config->layer_digest)
> +		ctx->layer_digest = strdup(config->layer_digest);
>   	else
> -		ctx->layer_index = -1;
> +		ctx->layer_digest = NULL;
>   	ctx->registry = strdup("registry-1.docker.io");
>   	ctx->tag = strdup("latest");
>   	if (config->platform)
> @@ -1190,6 +1222,7 @@ static void ocierofs_ctx_cleanup(struct ocierofs_ctx *ctx)
>   	free(ctx->tag);
>   	free(ctx->platform);
>   	free(ctx->manifest_digest);
> +	free(ctx->layer_digest);
>   }
>   
>   int ocierofs_build_trees(struct erofs_importer *importer,
> @@ -1204,8 +1237,13 @@ int ocierofs_build_trees(struct erofs_importer *importer,
>   		return ret;
>   	}
>   
> -	if (ctx.layer_index >= 0) {
> -		i = ctx.layer_index;
> +	if (ctx.layer_digest) {
> +		i = ocierofs_find_layer_by_digest(&ctx, ctx.layer_digest);
> +		if (i < 0) {
> +			erofs_err("layer digest %s not found", ctx.layer_digest);
> +			ret = -ENOENT;
> +			goto out;
> +		}
>   		end = i + 1;
>   	} else {
>   		i = 0;
> @@ -1215,25 +1253,26 @@ int ocierofs_build_trees(struct erofs_importer *importer,
>   	while (i < end) {
>   		char *trimmed = erofs_trim_for_progressinfo(ctx.layers[i]->digest,
>   				sizeof("Extracting layer  ...") - 1);
> -		erofs_update_progressinfo("Extracting layer %d: %s ...", i,
> -				  trimmed);
> +		erofs_update_progressinfo("Extracting layer %s ...", trimmed);
>   		free(trimmed);
>   		fd = ocierofs_extract_layer(&ctx, ctx.layers[i]->digest,
>   					    ctx.auth_header);
>   		if (fd < 0) {
> -			erofs_err("failed to extract layer %d: %s", i,
> -				  erofs_strerror(fd));
> +			erofs_err("failed to extract layer %s: %s",
> +				  ctx.layers[i]->digest, erofs_strerror(fd));
> +			ret = fd;
>   			break;
>   		}
>   		ret = ocierofs_process_tar_stream(importer, fd);
>   		close(fd);
>   		if (ret) {
> -			erofs_err("failed to process tar stream for layer %d: %s", i,
> -				  erofs_strerror(ret));
> +			erofs_err("failed to process tar stream for layer %s: %s",
> +				  ctx.layers[i]->digest, erofs_strerror(ret));
>   			break;
>   		}
>   		i++;
>   	}
> +out:
>   	ocierofs_ctx_cleanup(&ctx);
>   	return ret;
>   }
> @@ -1246,12 +1285,18 @@ static int ocierofs_download_blob_range(struct ocierofs_ctx *ctx, off_t offset,
>   	const char *api_registry;
>   	char rangehdr[64];
>   	long http_code = 0;
> -	int ret;
> -	int index = ctx->layer_index;
> -	u64 blob_size = ctx->layers[index]->size;
> +	int ret, index;
> +	const char *digest;
> +	u64 blob_size;
>   	size_t available;
>   	size_t copy_size;
>   
> +	index = ocierofs_find_layer_by_digest(ctx, ctx->layer_digest);
> +	if (index < 0)
> +		return -ENOENT;
> +	digest = ctx->layer_digest;
> +	blob_size = ctx->layers[index]->size;
> +
>   	if (offset < 0)
>   		return -EINVAL;
>   
> @@ -1265,7 +1310,7 @@ static int ocierofs_download_blob_range(struct ocierofs_ctx *ctx, off_t offset,
>   
>   	api_registry = ocierofs_get_api_registry(ctx->registry);
>   	if (asprintf(&req.url, "https://%s/v2/%s/blobs/%s",
> -	     api_registry, ctx->repository, ctx->layers[index]->digest) == -1)
> +	     api_registry, ctx->repository, digest) == -1)
>   		return -ENOMEM;
>   
>   	if (length)
> diff --git a/mkfs/main.c b/mkfs/main.c
> index 50e2bdb..6eb4203 100644
> --- a/mkfs/main.c
> +++ b/mkfs/main.c
> @@ -215,9 +215,10 @@ static void usage(int argc, char **argv)
>   #endif
>   #ifdef OCIEROFS_ENABLED
>   		" --oci[=platform=X]    X=platform (default: linux/amd64)\n"
> -		"   [,layer=Y]          Y=layer index to extract (0-based; omit to extract all layers)\n"
> -		"   [,username=Z]       Z=username for authentication (optional)\n"
> -		"   [,password=W]       W=password for authentication (optional)\n"
> +		"   [,layer_index=Y]    Y=layer index to extract (0-based; omit to extract all layers)\n"
> +		"   [,layer_digest=Z]   Z=layer digest to extract (omit to extract all layers)\n"

Can we use
		"   [,layer=#]          #=layer index to extract (0-based; omit to extract all layers)\n"
		"   [,blob=Y]           Y=layer digest to extract (omit to extract all layers)\n"

instead?

> +		"   [,username=W]       W=username for authentication (optional)\n"
> +		"   [,password=V]       V=password for authentication (optional)\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"
> @@ -707,13 +708,14 @@ 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, layer, username, and password.
> + * Supported options include platform, layer_digest, layer_index, username, and password.
>    *
>    * Return: 0 on success, negative errno on failure
>    */
>   static int mkfs_parse_oci_options(struct ocierofs_config *oci_cfg, char *options_str)
>   {
>   	char *opt, *q, *p;
> +	long idx;
>   
>   	if (!options_str)
>   		return 0;
> @@ -732,40 +734,57 @@ static int mkfs_parse_oci_options(struct ocierofs_config *oci_cfg, char *options
>   			if (!oci_cfg->platform)
>   				return -ENOMEM;
>   		} else {
> -			p = strstr(opt, "layer=");
> +			p = strstr(opt, "layer_digest=");


layer=

>   			if (p) {
> -				p += strlen("layer=");
> -				{
> -					char *endptr;
> -					unsigned long v = strtoul(p, &endptr, 10);
> -
> -					if (endptr == p || *endptr != '\0') {
> -						erofs_err("invalid layer index %s",
> -						  p);
> -						return -EINVAL;
> -					}
> -					oci_cfg->layer_index = (int)v;
> +				p += strlen("layer_digest=");


blob=

> +				free(oci_cfg->layer_digest);
> +
> +				if (oci_cfg->layer_index >= 0) {
> +					erofs_err("invalid --oci: layer_digest and layer_index cannot be set together");
> +					return -EINVAL;
> +				}
> +
> +				if (strncmp(p, "sha256:", 7) != 0) {
> +					if (asprintf(&oci_cfg->layer_digest, "sha256:%s", p) < 0)
> +						return -ENOMEM;
> +				} else {
> +					oci_cfg->layer_digest = strdup(p);
> +					if (!oci_cfg->layer_digest)
> +						return -ENOMEM;
>   				}
>   			} else {
> -				p = strstr(opt, "username=");
> +				p = strstr(opt, "layer_index=");
>   				if (p) {
> -					p += strlen("username=");
> -					free(oci_cfg->username);
> -					oci_cfg->username = strdup(p);
> -					if (!oci_cfg->username)
> -						return -ENOMEM;
> +					p += strlen("layer_index=");
> +					if (oci_cfg->layer_digest) {
> +						erofs_err("invalid --oci: layer_index and layer_digest cannot be set together");
> +						return -EINVAL;
> +					}
> +					idx = strtol(p, NULL, 10);
> +					if (idx < 0)
> +						return -EINVAL;
> +					oci_cfg->layer_index = (int)idx;
>   				} else {
> +					p = strstr(opt, "username=");
> +					if (p) {
> +						p += strlen("username=");
> +						free(oci_cfg->username);
> +						oci_cfg->username = strdup(p);
> +						if (!oci_cfg->username)
> +							return -ENOMEM;
> +					}
> +
>   					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 {
> -						erofs_err("mkfs: invalid --oci value %s", opt);
> -						return -EINVAL;
> +						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;
>   				}
>   			}
>   		}
> @@ -1850,6 +1869,7 @@ int main(int argc, char **argv)
>   #endif
>   #ifdef OCIEROFS_ENABLED
>   		} else if (source_mode == EROFS_MKFS_SOURCE_OCI) {
> +			ocicfg.layer_digest = NULL;
>   			ocicfg.layer_index = -1;
>   
>   			err = mkfs_parse_oci_options(&ocicfg, mkfs_oci_options);
> diff --git a/mount/main.c b/mount/main.c
> index f368746..323d1de 100644
> --- a/mount/main.c
> +++ b/mount/main.c
> @@ -81,51 +81,76 @@ static int erofsmount_parse_oci_option(const char *option)
>   {
>   	struct ocierofs_config *oci_cfg = &nbdsrc.ocicfg;
>   	char *p;
> +	long idx;
>   
> -	p = strstr(option, "oci.layer=");
> +	if (oci_cfg->layer_index == 0 && !oci_cfg->layer_digest &&
> +	    !oci_cfg->platform && !oci_cfg->username && !oci_cfg->password)
> +		oci_cfg->layer_index = -1;
> +
> +	p = strstr(option, "oci.layer_digest=");
>   	if (p != NULL) {
> -		p += strlen("oci.layer=");
> -		{
> -			char *endptr;
> -			unsigned long v = strtoul(p, &endptr, 10);
> +		p += strlen("oci.layer_digest=");

oci.blob=

> +		free(oci_cfg->layer_digest);
>   
> -			if (endptr == p || *endptr != '\0')
> -				return -EINVAL;
> -			oci_cfg->layer_index = (int)v;
> +		if (oci_cfg->layer_index >= 0) {
> +			erofs_err("invalid options: oci.layer_digest and oci.layer_index cannot be set together");
> +			return -EINVAL;
> +		}
> +
> +		if (strncmp(p, "sha256:", 7) != 0) {
> +			if (asprintf(&oci_cfg->layer_digest, "sha256:%s", p) < 0)
> +				return -ENOMEM;
> +		} else {
> +			oci_cfg->layer_digest = strdup(p);
> +			if (!oci_cfg->layer_digest)
> +				return -ENOMEM;
>   		}
>   	} else {
> -		p = strstr(option, "oci.platform=");
> +		p = strstr(option, "oci.layer_index=");

oci.layer =


Thanks,
Gao Xiang


More information about the Linux-erofs mailing list