>> + 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. Done.
>> + 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. > > ? Done. >> + 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; > ... > } Done. >> 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; Done. >> + >> + 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); > Done. Thanks, Chengyu > 2025年9月8日 16:35,Gao Xiang <hsiang...@linux.alibaba.com> 写道: > > Hi Chengyu, > > On 2025/9/5 22:30, ChengyuZhu6 wrote: >> From: Chengyu Zhu <hudson...@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 <hudson...@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; >> } >>