[PATCH v2] erofs-utils: add OCI registry support
Gao Xiang
hsiangkao at linux.alibaba.com
Fri Aug 22 13:17:48 AEST 2025
On 2025/8/22 10:45, ChengyuZhu6 wrote:
> From: Chengyu Zhu <hudson at cyzhu.com>
>
> This patch adds support for building EROFS filesystems from
> OCI-compliant 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]
Could you just write down the full command line? e,g.
mkfs.erofs --oci=registry/repo:tag[,options] <IMAGE> <?>
what's the meaning of <?>? since users already pass in
registry/repo:tag and layer=N
>
> Supported options:
> - platform=os/arch (default: linux/amd64)
> - layer=N (extract specific layer, default: all layers)
> - anonymous (use anonymous access)
> - username/password (basic authentication)
>
> Signed-off-by: Changzhi Xie <sa.z at qq.com>
> Signed-off-by: Chengyu Zhu <hudson at cyzhu.com>
..
> +
> +#endif /* __EROFS_OCI_H */
> diff --git a/lib/remotes/oci.c b/lib/remotes/oci.c
> new file mode 100644
> index 0000000..0c14f90
> --- /dev/null
> +++ b/lib/remotes/oci.c
> @@ -0,0 +1,826 @@
> +/* 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"
> +
> +#define OCI_AUTH_HEADER_MAX_LEN 1024
> +#define OCI_TEMP_FILENAME_MAX_LEN 256
> +
> +#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"
> +
> +#define DOCKER_REGISTRY "docker.io"
> +#define DOCKER_API_REGISTRY "registry-1.docker.io"
> +#define QUAY_REGISTRY "quay.io"
> +
> +struct erofs_oci_request {
> + char *url;
> + struct curl_slist *headers;
> +};
> +
> +struct erofs_oci_response {
> + char *data;
> + size_t size;
> + long http_code;
> +};
> +
> +struct oci_stream {
struct erofs_oci_stream {
It's prefered to use `erofs_` prefix even the structure
is internal-only or static.
> + struct erofs_tarfile tarfile;
> + FILE *temp_file;
> + char temp_filename[OCI_TEMP_FILENAME_MAX_LEN];
I think you could just leave a fd for this?
and use
erofs_tmpfile().
> + 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)
same here.
ocierofs_write_callback
> +{
> + size_t realsize = size * nmemb;
> + struct erofs_oci_response *resp = userp;
> + char *ptr;
> +
> + if (!resp || !contents)
> + return 0;
> +
> + ptr = realloc(resp->data, resp->size + realsize + 1);
> + if (!ptr) {
> + erofs_err("failed to allocate memory for response data");
> + 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)
ocierofs_
> +{
> + 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;
> +}
> +
> +static int oci_curl_setup_common_options(CURL *curl)
ocierofs_
> +{
> + if (!curl)
> + return -EINVAL;
Since it's an internal helper, I think it's unneeded to
check the invalid argument.
> +
> + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
> + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L);
> + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120L);
> + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
> + curl_easy_setopt(curl, CURLOPT_USERAGENT, "ocierofs/" PACKAGE_VERSION);
> +
> + return 0;
> +}
> +
> +static int oci_curl_setup_basic_auth(CURL *curl, const char *username, const char *password)
ocierofs_
> +{
> + char *userpwd = NULL;
> +
> + if (!curl || !username || !password)
> + return -EINVAL;
Same here too.
> +
> + if (asprintf(&userpwd, "%s:%s", username, password) == -1)
> + return -ENOMEM;
> +
> + curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd);
> + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
> +
> + free(userpwd);
> + return 0;
> +}
> +
> +static int oci_curl_clear_auth(CURL *curl)
> +{
> + if (!curl)
> + return -EINVAL;
Same here too.
> +
> + curl_easy_setopt(curl, CURLOPT_USERPWD, NULL);
> + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_NONE);
> +
> + return 0;
> +}
> +
> +static int oci_curl_setup_request(CURL *curl, const char *url, size_t (*write_func)(void *, size_t, size_t, void *),
> + void *write_data, struct curl_slist *headers)
> +{
> + if (!curl || !url || !write_func)
> + return -EINVAL;
Same here too.
> +
> + curl_easy_setopt(curl, CURLOPT_URL, url);
> + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_func);
> + curl_easy_setopt(curl, CURLOPT_WRITEDATA, write_data);
> +
> + if (headers)
> + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
> +
> + return 0;
> +}
> +
> +static int oci_request_perform(struct erofs_oci *oci, struct erofs_oci_request *req,
> + struct erofs_oci_response *resp)
> +{
> + CURLcode res;
> + int ret;
> +
> + ret = oci_curl_setup_request(oci->curl, req->url, oci_write_callback, resp, req->headers);
> + if (ret)
> + return ret;
> +
> + res = curl_easy_perform(oci->curl);
> + if (res != CURLE_OK) {
> + erofs_err("curl request failed: %s", curl_easy_strerror(res));
> + return -EIO;
> + }
> +
> + res = curl_easy_getinfo(oci->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;
> +}
> +
> +static char *oci_get_auth_token(struct erofs_oci *oci, const char *registry,
> + const char *repository, const char *username,
> + const char *password)
It seems your tab style is incorrect: one tab should be 8 spaces.
> +{
...
> +
> + if (!resp.data)
> + {
brace should follow `if (!resp.data)`
if (!resp.data) {
erofs-utils follows linux kernel style: K&R C style.
> + erofs_err("empty response from auth server");
> + ret = -EINVAL;
> + goto out_url;
> + }
> +
> + root = json_tokener_parse(resp.data);
> + if (!root)
> + {
here.
> + erofs_err("failed to parse auth response");
> + ret = -EINVAL;
> + goto out_url;
> + }
> +
> + if (!json_object_object_get_ex(root, "token", &token_obj))
> + {
here.
There are still many broken braces below, I think let's
just fix the coding style first.
> + erofs_err("no token found in auth response");
> + ret = -EINVAL;
> + goto out_json;
> + }
> +
> + token = json_object_get_string(token_obj);
> + if (!token)
> + {
> + erofs_err("invalid token in auth response");
> + ret = -EINVAL;
> + goto out_json;
> + }
> +
> + if (asprintf(&auth_header, "Authorization: Bearer %s", token) == -1)
> + {
> + ret = -ENOMEM;
> + goto out_json;
> + }
> +
> +out_json:
> + json_object_put(root);
> +out_url:
> + free(req.url);
> + free(resp.data);
> + return ret ? ERR_PTR(ret) : auth_header;
> +}
> +
Thanks,
Gao Xiang
More information about the Linux-erofs
mailing list