On 2025/8/22 10:45, ChengyuZhu6 wrote:
From: Chengyu Zhu <hud...@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 <s...@qq.com>
Signed-off-by: Chengyu Zhu <hud...@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

Reply via email to