[PATCH v2] erofs-utils: add NBD-backed OCI image mounting

Gao Xiang hsiangkao at linux.alibaba.com
Mon Sep 8 18:35:40 AEST 2025


Hi Chengyu,

On 2025/9/5 22:30, ChengyuZhu6 wrote:
> From: Chengyu Zhu <hudsonzhu at tencent.com>
> 
> - Add HTTP range downloads for OCI blobs
> - Introduce ocierofs_iostream for virtual file I/O
> - Add oci option for OCI image mounting with NBD backend
> 
> New mount.erofs -t erofs.nbd option: -o=[options] source-image mountpoint
> 
> Supported oci options:
> - oci.platform=os/arch (default: linux/amd64)
> - oci=N (extract specific layer, default: all layers)
> - oci.username/oci.password (basic authentication)
> 
> e.g.:
> sudo mount.erofs -t erofs.nbd  -o 'oci=0,oci.platform=linux/amd64' \
> quay.io/chengyuzhu6/golang:1.22.8-erofs /mnt
> 
> Signed-off-by: Chengyu Zhu <hudsonzhu at tencent.com>

Sorry for late reply.

> ---
>   lib/liberofs_oci.h |   8 +-
>   lib/remotes/oci.c  | 247 ++++++++++++++++++++++++++++++++++++++++++++-
>   mount/Makefile.am  |   2 +-
>   mount/main.c       | 196 +++++++++++++++++++++++++++--------
>   4 files changed, 406 insertions(+), 47 deletions(-)
> 
> diff --git a/lib/liberofs_oci.h b/lib/liberofs_oci.h
> index 2896308..873a560 100644
> --- a/lib/liberofs_oci.h
> +++ b/lib/liberofs_oci.h
> @@ -55,7 +55,11 @@ struct ocierofs_ctx {
>   	int layer_count;
>   };
>   
> -int ocierofs_init(struct ocierofs_ctx *ctx, const struct ocierofs_config *config);
> +struct ocierofs_iostream {
> +	struct ocierofs_ctx *ctx;
> +	struct erofs_vfile vf;

Why need this?

> +	u64 offset;
> +};
>   
>   /*
>    * ocierofs_build_trees - Build file trees from OCI container image layers
> @@ -67,6 +71,8 @@ int ocierofs_init(struct ocierofs_ctx *ctx, const struct ocierofs_config *config
>   int ocierofs_build_trees(struct erofs_importer *importer,
>   			 const struct ocierofs_config *cfg);
>   
> +int ocierofs_io_open(struct erofs_vfile *vf, const struct ocierofs_config *cfg);
> +
>   #ifdef __cplusplus
>   }
>   #endif
> diff --git a/lib/remotes/oci.c b/lib/remotes/oci.c
> index f2b08b2..ba01a0e 100644
> --- a/lib/remotes/oci.c
> +++ b/lib/remotes/oci.c
> @@ -16,6 +16,7 @@
>   #include <json-c/json.h>
>   #include "erofs/importer.h"
>   #include "erofs/internal.h"
> +#include "erofs/io.h"
>   #include "erofs/print.h"
>   #include "erofs/tar.h"
>   #include "liberofs_oci.h"
> @@ -33,6 +34,8 @@
>   #define OCI_MEDIATYPE_MANIFEST "application/vnd.oci.image.manifest.v1+json"
>   #define OCI_MEDIATYPE_INDEX "application/vnd.oci.image.index.v1+json"
>   
> +#define OCIEROFS_IO_CHUNK_SIZE 32768
> +
>   struct ocierofs_request {
>   	char *url;
>   	struct curl_slist *headers;
> @@ -1032,7 +1035,7 @@ static int ocierofs_parse_ref(struct ocierofs_ctx *ctx, const char *ref_str)
>    *
>    * Return: 0 on success, negative errno on failure
>    */
> -int ocierofs_init(struct ocierofs_ctx *ctx, const struct ocierofs_config *config)
> +static int ocierofs_init(struct ocierofs_ctx *ctx, const struct ocierofs_config *config)
>   {
>   	int ret;
>   
> @@ -1226,3 +1229,245 @@ int ocierofs_build_trees(struct erofs_importer *importer,
>   	ocierofs_ctx_cleanup(&ctx);
>   	return ret;
>   }
> +
> +static int ocierofs_download_blob_range(struct ocierofs_ctx *ctx, off_t offset, size_t length,
> +					void **out_buf, size_t *out_size)
> +{
> +	struct ocierofs_request req = {};
> +	struct ocierofs_response resp = {};
> +	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;
> +	size_t available;
> +	size_t copy_size;
> +
> +	if (offset < 0)
> +		return -EINVAL;
> +
> +	if (offset >= blob_size)

It's possible that NBD reads offset >= blob_size here, so I think you
should

	if (offset >= blob_size) {
		*out_size = 0;
		return 0;
	}

here.

> +		return 0;
> +
> +	if (length && offset + (off_t)length > blob_size)


		why `(off_t)length`? since length is already `off_t`.


> +		length = (size_t)(blob_size - offset);


	if (offset + length > blob_size)
		length = blob_size - 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)
> +		return -ENOMEM;
> +
> +	if (length)
> +		snprintf(rangehdr, sizeof(rangehdr), "Range: bytes=%lld-%lld",
> +			 (long long)offset, (long long)(offset + (off_t)length - 1));
> +	else
> +		snprintf(rangehdr, sizeof(rangehdr), "Range: bytes=%lld-",
> +			 (long long)offset);
> +
> +	if (ctx->auth_header && strstr(ctx->auth_header, "Bearer"))
> +		req.headers = curl_slist_append(req.headers, ctx->auth_header);
> +	req.headers = curl_slist_append(req.headers, rangehdr);
> +
> +	curl_easy_reset(ctx->curl);
> +
> +	ret = ocierofs_curl_setup_common_options(ctx->curl);
> +	if (ret)
> +		goto out;
> +
> +	ret = ocierofs_curl_setup_rq(ctx->curl, req.url, OCIEROFS_HTTP_GET,
> +				     req.headers,
> +				     ocierofs_write_callback,
> +				     &resp, NULL, NULL);
> +	if (ret)
> +		goto out;
> +
> +	ret = ocierofs_curl_perform(ctx->curl, &http_code);
> +	if (ret)
> +		goto out;
> +
> +	if (http_code == 206) {
> +		*out_buf = resp.data;
> +		*out_size = resp.size;
> +		resp.data = NULL;
> +		ret = 0;
> +	} else if (http_code == 200) {

		ret = 0;

> +		if (!offset) {
> +			*out_buf = resp.data;
> +			*out_size = resp.size;
> +			resp.data = NULL;
> +			ret = 0;
> +		} else {

		} else if (offset < resp.size) {
			available = resp.size - offset;
			...
		}

> +			if (offset < resp.size) {
> +				available = resp.size - offset;
> +				copy_size = length ? min_t(size_t, length, available) : available;
> +
> +				*out_buf = malloc(copy_size);
> +				if (!*out_buf) {
> +					ret = -ENOMEM;
> +					goto out;
> +				}
> +				memcpy(*out_buf, resp.data + offset, copy_size);
> +				*out_size = copy_size;
> +				ret = 0;
> +			} else {
> +				ret = 0;
> +			}
> +		}
> +	} else {
> +		erofs_err("HTTP range request failed with code %ld", http_code);
> +		ret = -EIO;
> +	}
> +
> +out:
> +	if (req.headers)
> +		curl_slist_free_all(req.headers);
> +	free(req.url);
> +	free(resp.data);
> +	return ret;
> +}
> +

...

> diff --git a/mount/Makefile.am b/mount/Makefile.am
> index d93f3f4..0b4447f 100644
> --- a/mount/Makefile.am
> +++ b/mount/Makefile.am
> @@ -9,5 +9,5 @@ mount_erofs_SOURCES = main.c
>   mount_erofs_CFLAGS = -Wall -I$(top_srcdir)/include
>   mount_erofs_LDADD = $(top_builddir)/lib/liberofs.la ${libselinux_LIBS} \
>   	${liblz4_LIBS} ${liblzma_LIBS} ${zlib_LIBS} ${libdeflate_LIBS} \
> -	${libzstd_LIBS} ${libqpl_LIBS} ${libxxhash_LIBS} ${libnl3_LIBS}
> +	${libzstd_LIBS} ${libqpl_LIBS} ${libxxhash_LIBS} ${libnl3_LIBS} ${openssl_LIBS}
>   endif
> diff --git a/mount/main.c b/mount/main.c
> index 2826dac..359dbbf 100644
> --- a/mount/main.c
> +++ b/mount/main.c
> @@ -15,6 +15,7 @@

...


> +

>   
>   			err = IS_ERR(id) ? PTR_ERR(id) :
> @@ -789,9 +887,19 @@ int main(int argc, char *argv[])
>   	}
>   
>   	if (mountcfg.backend == EROFSNBD) {
> -		err = erofsmount_nbd(mountcfg.device, mountcfg.target,
> -				     mountcfg.fstype, mountcfg.flags,
> -				     mountcfg.options);
> +		if (mountcfg.use_oci) {
> +			struct erofs_vfile vfile = {};

Could you just move this to
	ocierofs_io_open() instead?

e.g.
	vfile = (struct erofs_vfile){.ops = &ocierofs_io_vfops};
	*(struct ocierofs_iostream **)vfile->payload = oci_iostream;

> +
> +			ocicfg.image_ref = mountcfg.device;
> +			src.vf = &vfile;
> +			err = erofsmount_nbd(src, EROFSNBD_SOURCE_OCI, mountcfg.target,
> +					     mountcfg.fstype, mountcfg.flags, mountcfg.options);
> +		} else {
> +			src.device_path = mountcfg.device;
> +			err = erofsmount_nbd(src, EROFSNBD_SOURCE_LOCAL, mountcfg.target,
> +					     mountcfg.fstype, mountcfg.flags,
> +					     mountcfg.options);
> +		}

I think you could just

		struct erofs_vfile vfile;
		int sourcetype;

		if (mountcfg.use_oci) {
			sourcetype = EROFSNBD_SOURCE_OCI;
			ocicfg.image_ref = mountcfg.device;
			src.vf = &vfile;
		} else {
			sourcetype = EROFSNBD_SOURCE_LOCAL;
			src.device_path = mountcfg.device;
		}

		err = erofsmount_nbd(src, sourcetype, mountcfg.target,
				     mountcfg.fstype, mountcfg.flags,
				     mountcfg.options);

Thanks,
Gao Xiang

>   		goto exit;
>   	}
>   



More information about the Linux-erofs mailing list