Hi Xiang, > The author doesn't seem to be updated, you could > > `git commit --amend --author="Chengyu Zhu <hud...@cyzhu.com>”` Done.
>> This patch adds support for building EROFS filesystems from OCI-compliant > > 72-char per line. Fixed. > > Is it possible to enable anonymous access if username/password > are not specified? Yes, the new patch works. > Your `Signed-off-by` needs to the last line of the commit message > since you sent out the email: Fixed. > Can we make those string as dynamic allocated? > Hard-coded strings look dangerous. Done. > is it possible to rename it as `struct erofs_oci_params`? Done. > What's the difference between > OCI_AUTH_ANONYMOUS > and > anonymous_access? There isn’t a functional difference. anonymous_access was only used to capture the “anonymous” input, which duplicated OCI_AUTH_ANONYMOUS. I’ve removed anonymous_access and now rely solely on OCI_AUTH_ANONYMOUS. > and then make > > struct erofs_oci { > CURL *curl; > struct erofs_oci_params *params; > }; Done. >> +/* Layer information */ > > The comment can be droped since it's obvious. Removed. >> + } else { >> + snprintf(req.url, sizeof(req.url), >> + "https://%s/token?service=%s&scope=repository:%s:pull", >> + registry, registry, repository); >> + } > > You could use asprintf to allocate dynamic strings… Done. >> + struct erofs_tarfile tarfile; > > what's the purpose of `struct erofs_tarfile`? It's used by the tarerofs_parse_tar() function to iterate through tar archive entries and extract file information. Thanks, Chengyu Zhu > > 2025年8月21日 15:52,Gao Xiang <hsiang...@linux.alibaba.com> 写道: > > Hi Chengyu, > > On 2025/8/21 15:32, ChengyuZhu6 wrote: > > Thanks for the patch! > > The author doesn't seem to be updated, you could > > `git commit --amend --author="Chengyu Zhu <hud...@cyzhu.com>"` > > or just use `git config` for global update. > >> This patch adds support for building EROFS filesystems from OCI-compliant > > 72-char per line. > >> container registries, enabling users to create EROFS images directly from >> container images stored in registries like Docker Hub, Quay.io, etc. >> The implementation includes: >> - OCI remote backend with registry authentication support >> - Manifest parsing for Docker v2 and OCI v1 formats >> - Layer extraction and tar processing integration >> - Multi-platform image selection capability >> - Both anonymous and authenticated registry access >> - Comprehensive build system integration >> New mkfs.erofs option: --oci=registry/repo:tag[,options] >> Supported options: >> - platform=os/arch (default: linux/amd64) >> - layer=N (extract specific layer, default: all layers) >> - anonymous (use anonymous access) > > Is it possible to enable anonymous access if username/password > are not specified? > >> - username/password (basic authentication) >> Signed-off-by: Chengyu Zhu <hud...@cyzhu.com> >> Signed-off-by: Changzhi Xie <s...@qq.com> > > Your `Signed-off-by` needs to the last line of the commit message > since you sent out the email: > > Signed-off-by: Changzhi Xie <s...@qq.com> > Signed-off-by: Chengyu Zhu <hud...@cyzhu.com> > >> --- > > ... > > >> diff --git a/lib/liberofs_oci.h b/lib/liberofs_oci.h >> new file mode 100644 >> index 0000000..8ed98d7 >> --- /dev/null >> +++ b/lib/liberofs_oci.h >> @@ -0,0 +1,73 @@ >> +/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ >> +/* >> + * Copyright (C) 2025 Tencent, Inc. >> + * http://www.tencent.com/ >> + */ >> +#ifndef __EROFS_OCI_H >> +#define __EROFS_OCI_H >> + >> +#include <stdbool.h> >> + >> +#ifdef __cplusplus >> +extern "C" { >> +#endif >> + >> +struct erofs_inode; >> + >> +/* OCI authentication modes */ >> +enum oci_auth_mode { >> + OCI_AUTH_ANONYMOUS, /* No authentication */ >> + OCI_AUTH_TOKEN, /* Bearer token authentication */ >> + OCI_AUTH_BASIC, /* Basic authentication */ >> +}; >> + >> +/* Maximum lengths for OCI parameters */ >> +#define OCI_REGISTRY_LEN 256 >> +#define OCI_REPOSITORY_LEN 256 >> +#define OCI_TAG_LEN 64 >> +#define OCI_PLATFORM_LEN 32 >> +#define OCI_USERNAME_LEN 64 >> +#define OCI_PASSWORD_LEN 256 > > Can we make those string as dynamic allocated? > Hard-coded strings look dangerous. > >> + >> +/** >> + * struct erofs_oci - OCI configuration >> + * @registry: registry hostname (e.g., "registry-1.docker.io") >> + * @repository: image repository (e.g., "library/ubuntu") >> + * @tag: image tag or digest (e.g., "latest" or sha256:...) >> + * @platform: target platform in "os/arch" format (e.g., >> "linux/amd64") >> + * @username: username for basic authentication >> + * @password: password for basic authentication >> + * @auth_mode: authentication mode to use >> + * @layer_index: specific layer to extract (-1 for all layers) >> + * @anonymous_access: whether to use anonymous access >> + */ >> +struct erofs_oci { > > is it possible to rename it as `struct erofs_oci_params`? > >> + char registry[OCI_REGISTRY_LEN + 1]; >> + char repository[OCI_REPOSITORY_LEN + 1]; >> + char tag[OCI_TAG_LEN + 1]; >> + char platform[OCI_PLATFORM_LEN + 1]; >> + char username[OCI_USERNAME_LEN + 1]; >> + char password[OCI_PASSWORD_LEN + 1]; >> + >> + enum oci_auth_mode auth_mode; > > What's the difference between > OCI_AUTH_ANONYMOUS > and > anonymous_access? > >> + int layer_index; >> + bool anonymous_access; >> +}; >> + >> +/** >> + * ocierofs_build_trees - Build file trees from OCI container image layers >> + * @root: root inode to build the file tree under >> + * @oci: OCI configuration >> + * @ref: reference string (unused, kept for API compatibility) >> + * @fillzero: if true, only create inodes without downloading actual data >> + * >> + * Return: 0 on success, negative errno on failure >> + */ >> +int ocierofs_build_trees(struct erofs_inode *root, struct erofs_oci *oci, >> + const char *ref, bool fillzero); >> + >> +#ifdef __cplusplus >> +} >> +#endif >> + >> +#endif /* __EROFS_OCI_H */ >> diff --git a/lib/remotes/oci.c b/lib/remotes/oci.c >> new file mode 100644 >> index 0000000..e86af50 >> --- /dev/null >> +++ b/lib/remotes/oci.c >> @@ -0,0 +1,665 @@ >> +/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */ >> +/* >> + * Copyright (C) 2025 Tencent, Inc. >> + * http://www.tencent.com/ >> + */ >> +#define _GNU_SOURCE >> +#include <stdio.h> >> +#include <stdlib.h> >> +#include <string.h> >> +#include <unistd.h> >> +#include <fcntl.h> >> +#include <sys/types.h> >> +#include <sys/stat.h> >> +#include <errno.h> >> +#include <curl/curl.h> >> +#include <json-c/json.h> >> +#include "erofs/internal.h" >> +#include "erofs/print.h" >> +#include "erofs/inode.h" >> +#include "erofs/blobchunk.h" >> +#include "erofs/diskbuf.h" >> +#include "erofs/rebuild.h" >> +#include "erofs/tar.h" >> +#include "liberofs_oci.h" >> + >> +/* Constants */ >> +#define OCI_URL_MAX_LEN 8192 >> +#define OCI_AUTH_HEADER_MAX_LEN 1024 >> +#define OCI_TEMP_FILENAME_MAX_LEN 256 >> + >> +/* Media types */ >> +#define DOCKER_MEDIATYPE_MANIFEST_V2 >> "application/vnd.docker.distribution.manifest.v2+json" >> +#define DOCKER_MEDIATYPE_MANIFEST_V1 >> "application/vnd.docker.distribution.manifest.v1+json" >> +#define DOCKER_MEDIATYPE_MANIFEST_LIST >> "application/vnd.docker.distribution.manifest.list.v2+json" >> +#define OCI_MEDIATYPE_MANIFEST >> "application/vnd.oci.image.manifest.v1+json" >> +#define OCI_MEDIATYPE_INDEX >> "application/vnd.oci.image.index.v1+json" >> + >> +/* Registry constants */ >> +#define DOCKER_REGISTRY "docker.io" >> +#define DOCKER_API_REGISTRY "registry-1.docker.io" >> +#define QUAY_REGISTRY "quay.io" >> + >> +/* Global CURL handle */ >> +static CURL *g_curl; > > and then make > > struct erofs_oci { > CURL *curl; > struct erofs_oci_params *params; > }; > > As a OCI context (it can be used for multiple instance usage > such as runtime lazy loading.) > >> +> +/* HTTP request/response structures */ >> +struct oci_request { >> + char url[OCI_URL_MAX_LEN]; >> + struct curl_slist *headers; >> +}; > > struct erofs_oci_request { > }; > >> + >> +struct oci_response { > > erofs_oci_response... > >> + char *data; >> + size_t size; >> + long http_code; >> +}; >> + >> +/* Layer information */ > > The comment can be droped since it's obvious. > >> +struct oci_layer { > > erofs_oci_layer { > >> + char *digest; >> + u64 size; >> + const char *media_type; >> +}; >> + >> +/* Layer streaming context */ >> +struct oci_stream { > > Same here. > >> + struct erofs_tarfile tarfile; > > what's the purpose of `struct erofs_tarfile`? > >> + FILE *temp_file; >> + char temp_filename[OCI_TEMP_FILENAME_MAX_LEN]; >> + int layer_index; >> +}; >> + >> +/* Callback for writing response data to memory */ >> +static size_t oci_write_callback(void *contents, size_t size, size_t nmemb, >> void *userp) >> +{ >> + size_t realsize = size * nmemb; >> + struct oci_response *resp = userp; >> + char *ptr; >> + >> + ptr = realloc(resp->data, resp->size + realsize + 1); >> + if (!ptr) >> + return 0; >> + >> + resp->data = ptr; >> + memcpy(&resp->data[resp->size], contents, realsize); >> + resp->size += realsize; >> + resp->data[resp->size] = '\0'; >> + return realsize; >> +} >> + >> +/* Callback for writing layer data to file */ >> +static size_t oci_layer_write_callback(void *contents, size_t size, size_t >> nmemb, void *userp) >> +{ >> + struct oci_stream *stream = userp; >> + size_t realsize = size * nmemb; >> + >> + if (!stream->temp_file) >> + return 0; >> + >> + if (fwrite(contents, 1, realsize, stream->temp_file) != realsize) { >> + erofs_err("failed to write layer data for layer %d", >> stream->layer_index); >> + return 0; >> + } >> + >> + return realsize; >> +} >> + >> +/* Perform HTTP request */ >> +static int oci_request_perform(struct oci_request *req, struct oci_response >> *resp) >> +{ >> + CURLcode res; >> + >> + erofs_dbg("requesting URL: %s", req->url); >> + >> + curl_easy_setopt(g_curl, CURLOPT_URL, req->url); >> + curl_easy_setopt(g_curl, CURLOPT_WRITEDATA, resp); >> + curl_easy_setopt(g_curl, CURLOPT_WRITEFUNCTION, oci_write_callback); >> + >> + if (req->headers) >> + curl_easy_setopt(g_curl, CURLOPT_HTTPHEADER, req->headers); >> + >> + res = curl_easy_perform(g_curl); >> + if (res != CURLE_OK) { >> + erofs_err("curl request failed: %s", curl_easy_strerror(res)); >> + return -EIO; >> + } >> + >> + res = curl_easy_getinfo(g_curl, CURLINFO_RESPONSE_CODE, >> &resp->http_code); >> + if (res != CURLE_OK) { >> + erofs_err("failed to get HTTP response code: %s", >> curl_easy_strerror(res)); >> + return -EIO; >> + } >> + >> + if (resp->http_code < 200 || resp->http_code >= 300) { >> + erofs_err("HTTP request failed with code %ld", resp->http_code); >> + return -EIO; >> + } >> + >> + return 0; >> +} >> + >> +/* Get authentication token */ >> +static char *oci_get_auth_token(const char *registry, const char >> *repository, >> + const char *username, const char *password) >> +{ >> + struct oci_request req = {}; >> + struct oci_response resp = {}; >> + json_object *root, *token_obj; >> + const char *token; >> + char *auth_header = NULL; >> + int ret; >> + >> + if (!registry || !repository) >> + return ERR_PTR(-EINVAL); >> + >> + if (!strcmp(registry, DOCKER_API_REGISTRY) || !strcmp(registry, >> DOCKER_REGISTRY)) { >> + snprintf(req.url, sizeof(req.url), >> + >> "https://auth.docker.io/token?service=registry.docker.io&scope=repository:%s:pull", >> + repository); >> + } else if (!strcmp(registry, QUAY_REGISTRY)) { >> + snprintf(req.url, sizeof(req.url), >> + >> "https://%s/v2/auth?service=%s&scope=repository:%s:pull", >> + QUAY_REGISTRY, QUAY_REGISTRY, repository); >> + } else { >> + snprintf(req.url, sizeof(req.url), >> + "https://%s/token?service=%s&scope=repository:%s:pull", >> + registry, registry, repository); >> + } > > You could use asprintf to allocate dynamic strings... > > > ... > >> case 'V': >> version(); >> @@ -1638,7 +1775,8 @@ int main(int argc, char **argv) >> erofs_uuid_generate(g_sbi.uuid); >> if ((source_mode == EROFS_MKFS_SOURCE_TAR && !erofstar.index_mode) || >> - (source_mode == EROFS_MKFS_SOURCE_S3)) { >> + (source_mode == EROFS_MKFS_SOURCE_S3) || >> + (source_mode == EROFS_MKFS_SOURCE_OCI)) { >> err = erofs_diskbuf_init(1); >> if (err) { >> erofs_err("failed to initialize diskbuf: %s", >> @@ -1756,12 +1894,27 @@ int main(int argc, char **argv) >> dataimport_mode == >> EROFS_MKFS_DATA_IMPORT_ZEROFILL); >> if (err) >> goto exit; >> +#endif >> +#ifdef OCIEROFS_ENABLED >> + } else if (source_mode == EROFS_MKFS_SOURCE_OCI) { >> + if (incremental_mode || >> + dataimport_mode == EROFS_MKFS_DATA_IMPORT_RVSP) >> + err = -EOPNOTSUPP; >> + else >> + err = ocierofs_build_trees(root, &ocicfg, >> + cfg.c_src_path, >> + dataimport_mode == >> EROFS_MKFS_DATA_IMPORT_ZEROFILL); >> + if (err) >> + goto exit; >> + erofs_info("OCI build_trees completed, starting >> filesystem construction"); >> #endif >> } >> + erofs_info("Starting erofs_rebuild_dump_tree..."); > > The debugging info can be removed.. > >> err = erofs_rebuild_dump_tree(root, incremental_mode); >> if (err) >> goto exit; >> + erofs_info("erofs_rebuild_dump_tree completed"); > > Same here. > > Thanks, > Gao Xiang