public inbox for linux-erofs@ozlabs.org
 help / color / mirror / Atom feed
* [PATCH 1/1] erofs-utils: lib: oci: support reading credentials from docker config
@ 2026-02-09  7:53 ChengyuZhu6
  2026-02-09  9:06 ` Gao Xiang
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: ChengyuZhu6 @ 2026-02-09  7:53 UTC (permalink / raw)
  To: linux-erofs; +Cc: hsiangkao, Chengyu Zhu

From: Chengyu Zhu <hudsonzhu@tencent.com>

This patch adds support for reading authentication credentials from
Docker's `config.json` (typically in `~/.docker/config.json` or via
`DOCKER_CONFIG`).

If no username/password is provided via command-line arguments, the
implementation will attempt to look up the registry in the docker config
file and use the stored credentials if found.

Signed-off-by: Chengyu Zhu <hudsonzhu@tencent.com>
---
 lib/Makefile.am             |   2 +-
 lib/liberofs_dockerconfig.h |  28 +++++
 lib/remotes/docker_config.c | 243 ++++++++++++++++++++++++++++++++++++
 lib/remotes/oci.c           |  18 ++-
 4 files changed, 286 insertions(+), 5 deletions(-)
 create mode 100644 lib/liberofs_dockerconfig.h
 create mode 100644 lib/remotes/docker_config.c

diff --git a/lib/Makefile.am b/lib/Makefile.am
index 817f69a..7a65b2c 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -95,7 +95,7 @@ liberofs_la_LDFLAGS += ${liburing_LIBS}
 liberofs_la_SOURCES += backends/ublk.c
 endif
 endif
-liberofs_la_SOURCES += remotes/oci.c
+liberofs_la_SOURCES += remotes/oci.c remotes/docker_config.c
 liberofs_la_CFLAGS += ${json_c_CFLAGS}
 liberofs_la_LDFLAGS += ${json_c_LIBS}
 liberofs_la_SOURCES += gzran.c
diff --git a/lib/liberofs_dockerconfig.h b/lib/liberofs_dockerconfig.h
new file mode 100644
index 0000000..9a84592
--- /dev/null
+++ b/lib/liberofs_dockerconfig.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */
+/*
+ * Copyright (C) 2026 Tencent, Inc.
+ *             http://www.tencent.com/
+ */
+#ifndef __EROFS_DOCKER_CONFIG_H
+#define __EROFS_DOCKER_CONFIG_H
+
+#define DOCKER_REGISTRY "docker.io"
+#define DOCKER_API_REGISTRY "registry-1.docker.io"
+#define DOCKER_HUB_AUTH_KEY "https://index.docker.io/v1/"
+
+struct erofs_docker_credential {
+	char *username;
+	char *password;
+};
+
+/**
+ * erofs_docker_config_lookup - look up registry credentials from Docker config
+ * @registry: the registry hostname (e.g. "index.docker.io")
+ * @cred: output credential structure
+ */
+int erofs_docker_config_lookup(const char *registry,
+			       struct erofs_docker_credential *cred);
+
+void erofs_docker_credential_free(struct erofs_docker_credential *cred);
+
+#endif
diff --git a/lib/remotes/docker_config.c b/lib/remotes/docker_config.c
new file mode 100644
index 0000000..9f039e4
--- /dev/null
+++ b/lib/remotes/docker_config.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
+/*
+ * Copyright (C) 2026 Tencent, Inc.
+ *             http://www.tencent.com/
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_JSON_C_JSON_H
+#include <json-c/json.h>
+#endif
+
+#include "erofs/defs.h"
+#include "erofs/print.h"
+#include "liberofs_base64.h"
+#include "liberofs_dockerconfig.h"
+
+#ifndef HAVE_JSON_C_JSON_H
+
+int erofs_docker_config_lookup(const char *registry,
+			       struct erofs_docker_credential *cred)
+{
+	(void)registry;
+	(void)cred;
+	return -EOPNOTSUPP;
+}
+
+void erofs_docker_credential_free(struct erofs_docker_credential *cred)
+{
+	(void)cred;
+}
+
+#else /* HAVE_JSON_C_JSON_H */
+
+static char *docker_config_path(void)
+{
+	const char *dir;
+	char *path = NULL;
+
+	dir = getenv("DOCKER_CONFIG");
+	if (dir && *dir) {
+		if (asprintf(&path, "%s/config.json", dir) < 0)
+			return NULL;
+		return path;
+	}
+
+	dir = getenv("HOME");
+	if (!dir || !*dir) {
+		erofs_dbg("HOME is not set, cannot locate docker config");
+		return NULL;
+	}
+
+	if (asprintf(&path, "%s/.docker/config.json", dir) < 0)
+		return NULL;
+	return path;
+}
+
+static char *read_file_to_string(const char *path)
+{
+	FILE *fp;
+	struct stat st;
+	char *buf;
+	size_t nread;
+
+	if (stat(path, &st) < 0)
+		return NULL;
+
+	if (st.st_size <= 0 || st.st_size > (1 << 22))
+		return NULL;
+
+	fp = fopen(path, "r");
+	if (!fp)
+		return NULL;
+
+	buf = malloc(st.st_size + 1);
+	if (!buf) {
+		fclose(fp);
+		return NULL;
+	}
+
+	nread = fread(buf, 1, st.st_size, fp);
+	fclose(fp);
+
+	if ((off_t)nread != st.st_size) {
+		free(buf);
+		return NULL;
+	}
+	buf[nread] = '\0';
+	return buf;
+}
+
+/*
+ * Check if @key (an auths entry key) matches @registry.
+ *
+ * For Docker Hub: @registry is docker.io or registry-1.docker.io.
+ * The auths key in config.json is always "https://index.docker.io/v1/".
+ * For other registries: the auths key is an exact match against @registry.
+ */
+static bool registry_match(const char *key, const char *registry)
+{
+	if (!key || !registry)
+		return false;
+
+	if (!strcasecmp(registry, DOCKER_REGISTRY) ||
+	    !strcasecmp(registry, DOCKER_API_REGISTRY))
+		return !strcmp(key, DOCKER_HUB_AUTH_KEY);
+
+	return !strcasecmp(key, registry);
+}
+
+static int decode_auth_field(const char *b64, char **out_user, char **out_pass)
+{
+	int b64_len = strlen(b64);
+	int decoded_max = b64_len;
+	u8 *decoded;
+	int decoded_len;
+	char *colon;
+
+	decoded = malloc(decoded_max + 1);
+	if (!decoded)
+		return -ENOMEM;
+
+	decoded_len = erofs_base64_decode(b64, b64_len, decoded);
+	if (decoded_len <= 0) {
+		free(decoded);
+		return -EINVAL;
+	}
+	decoded[decoded_len] = '\0';
+
+	colon = strchr((char *)decoded, ':');
+	if (!colon) {
+		memset(decoded, 0, decoded_len);
+		free(decoded);
+		return -EINVAL;
+	}
+
+	*colon = '\0';
+	*out_user = strdup((char *)decoded);
+	*out_pass = strdup(colon + 1);
+
+	memset(decoded, 0, decoded_len);
+	free(decoded);
+
+	if (!*out_user || !*out_pass) {
+		free(*out_user);
+		free(*out_pass);
+		*out_user = NULL;
+		*out_pass = NULL;
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+int erofs_docker_config_lookup(const char *registry,
+			       struct erofs_docker_credential *cred)
+{
+	char *path = NULL;
+	char *content = NULL;
+	struct json_object *root = NULL, *auths_obj = NULL;
+	int ret = -ENOENT;
+
+	memset(cred, 0, sizeof(*cred));
+
+	path = docker_config_path();
+	if (!path)
+		return -ENOENT;
+
+	content = read_file_to_string(path);
+	if (!content) {
+		erofs_dbg("cannot read docker config: %s", path);
+		free(path);
+		return -ENOENT;
+	}
+	free(path);
+
+	root = json_tokener_parse(content);
+	memset(content, 0, strlen(content));
+	free(content);
+
+	if (!root) {
+		erofs_warn("failed to parse docker config.json");
+		return -EINVAL;
+	}
+
+	if (!json_object_object_get_ex(root, "auths", &auths_obj)) {
+		erofs_dbg("no \"auths\" in docker config.json");
+		json_object_put(root);
+		return -ENOENT;
+	}
+
+	struct json_object_iterator it = json_object_iter_begin(auths_obj);
+	struct json_object_iterator end = json_object_iter_end(auths_obj);
+
+	while (!json_object_iter_equal(&it, &end)) {
+		const char *key = json_object_iter_peek_name(&it);
+		struct json_object *entry, *auth_field;
+		const char *b64;
+
+		if (!registry_match(key, registry)) {
+			json_object_iter_next(&it);
+			continue;
+		}
+
+		entry = json_object_iter_peek_value(&it);
+		if (json_object_object_get_ex(entry, "auth", &auth_field)) {
+			b64 = json_object_get_string(auth_field);
+			if (b64 && *b64) {
+				ret = decode_auth_field(b64, &cred->username,
+							&cred->password);
+				if (!ret)
+					erofs_dbg("found docker credentials for %s",
+						  registry);
+			}
+		}
+		break;
+	}
+
+	json_object_put(root);
+	return ret;
+}
+
+void erofs_docker_credential_free(struct erofs_docker_credential *cred)
+{
+	if (cred->username) {
+		memset(cred->username, 0, strlen(cred->username));
+		free(cred->username);
+		cred->username = NULL;
+	}
+	if (cred->password) {
+		memset(cred->password, 0, strlen(cred->password));
+		free(cred->password);
+		cred->password = NULL;
+	}
+}
+
+#endif /* HAVE_JSON_C_JSON_H */
diff --git a/lib/remotes/oci.c b/lib/remotes/oci.c
index fe5df29..48bf37f 100644
--- a/lib/remotes/oci.c
+++ b/lib/remotes/oci.c
@@ -30,6 +30,7 @@
 #include "erofs/tar.h"
 #include "liberofs_base64.h"
 #include "liberofs_oci.h"
+#include "liberofs_dockerconfig.h"
 #include "liberofs_private.h"
 #include "liberofs_gzran.h"
 
@@ -39,9 +40,6 @@
 
 #ifdef OCIEROFS_ENABLED
 
-#define DOCKER_REGISTRY "docker.io"
-#define DOCKER_API_REGISTRY "registry-1.docker.io"
-
 #define DOCKER_MEDIATYPE_MANIFEST_V2 \
 	"application/vnd.docker.distribution.manifest.v2+json"
 #define DOCKER_MEDIATYPE_MANIFEST_V1 \
@@ -985,9 +983,21 @@ static int ocierofs_find_layer_by_digest(struct ocierofs_ctx *ctx, const char *d
 static int ocierofs_prepare_layers(struct ocierofs_ctx *ctx,
 				   const struct ocierofs_config *config)
 {
+	struct erofs_docker_credential dcred = { NULL, NULL };
+	const char *username = config->username;
+	const char *password = config->password;
 	int ret;
 
-	ret = ocierofs_prepare_auth(ctx, config->username, config->password);
+	/* Fallback to Docker config.json if no CLI credentials provided */
+	if ((!username || !*username) && (!password || !*password)) {
+		if (!erofs_docker_config_lookup(ctx->registry, &dcred)) {
+			username = dcred.username;
+			password = dcred.password;
+		}
+	}
+
+	ret = ocierofs_prepare_auth(ctx, username, password);
+	erofs_docker_credential_free(&dcred);
 	if (ret)
 		return ret;
 
-- 
2.51.0



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/1] erofs-utils: lib: oci: support reading credentials from docker config
  2026-02-09  7:53 [PATCH 1/1] erofs-utils: lib: oci: support reading credentials from docker config ChengyuZhu6
@ 2026-02-09  9:06 ` Gao Xiang
  2026-02-10  9:37 ` [PATCH v2] " ChengyuZhu6
  2026-02-12  2:03 ` [PATCH v3] " ChengyuZhu6
  2 siblings, 0 replies; 6+ messages in thread
From: Gao Xiang @ 2026-02-09  9:06 UTC (permalink / raw)
  To: ChengyuZhu6, linux-erofs; +Cc: Chengyu Zhu



On 2026/2/9 15:53, ChengyuZhu6 wrote:

..

> --- /dev/null
> +++ b/lib/remotes/docker_config.c
> @@ -0,0 +1,243 @@
> +// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
> +/*
> + * Copyright (C) 2026 Tencent, Inc.
> + *             http://www.tencent.com/
> + */
> +#ifdef HAVE_CONFIG_H
> +#include "config.h"
> +#endif
> +
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/stat.h>
> +
> +#ifdef HAVE_JSON_C_JSON_H
> +#include <json-c/json.h>
> +#endif
> +
> +#include "erofs/defs.h"
> +#include "erofs/print.h"
> +#include "liberofs_base64.h"
> +#include "liberofs_dockerconfig.h"
> +
> +#ifndef HAVE_JSON_C_JSON_H
> +
> +int erofs_docker_config_lookup(const char *registry,
> +			       struct erofs_docker_credential *cred)
> +{
> +	(void)registry;
> +	(void)cred;
> +	return -EOPNOTSUPP;
> +}
> +
> +void erofs_docker_credential_free(struct erofs_docker_credential *cred)
> +{
> +	(void)cred;
> +}
> +
> +#else /* HAVE_JSON_C_JSON_H */
> +
> +static char *docker_config_path(void)
> +{
> +	const char *dir;
> +	char *path = NULL;
> +
> +	dir = getenv("DOCKER_CONFIG");
> +	if (dir && *dir) {
> +		if (asprintf(&path, "%s/config.json", dir) < 0)
> +			return NULL;
> +		return path;
> +	}

Is it possible to make DOCKER_CONFIG as "" to ignore
$HOME/.docker/config.json for script usage?

> +
> +	dir = getenv("HOME");
> +	if (!dir || !*dir) {
> +		erofs_dbg("HOME is not set, cannot locate docker config");
> +		return NULL;
> +	}
> +
> +	if (asprintf(&path, "%s/.docker/config.json", dir) < 0)
> +		return NULL;
> +	return path;
> +}
> +
> +static char *read_file_to_string(const char *path)
> +{
> +	FILE *fp;
> +	struct stat st;
> +	char *buf;
> +	size_t nread;
> +
> +	if (stat(path, &st) < 0)
> +		return NULL;
> +
> +	if (st.st_size <= 0 || st.st_size > (1 << 22))
> +		return NULL;
> +
> +	fp = fopen(path, "r");
> +	if (!fp)
> +		return NULL;
> +
> +	buf = malloc(st.st_size + 1);
> +	if (!buf) {
> +		fclose(fp);
> +		return NULL;
> +	}
> +
> +	nread = fread(buf, 1, st.st_size, fp);
> +	fclose(fp);
> +
> +	if ((off_t)nread != st.st_size) {
> +		free(buf);
> +		return NULL;
> +	}
> +	buf[nread] = '\0';
> +	return buf;
> +}
> +
> +/*
> + * Check if @key (an auths entry key) matches @registry.
> + *
> + * For Docker Hub: @registry is docker.io or registry-1.docker.io.
> + * The auths key in config.json is always "https://index.docker.io/v1/".
> + * For other registries: the auths key is an exact match against @registry.
> + */
> +static bool registry_match(const char *key, const char *registry)
> +{
> +	if (!key || !registry)
> +		return false;
> +
> +	if (!strcasecmp(registry, DOCKER_REGISTRY) ||
> +	    !strcasecmp(registry, DOCKER_API_REGISTRY))
> +		return !strcmp(key, DOCKER_HUB_AUTH_KEY);
> +
> +	return !strcasecmp(key, registry);
> +}
> +
> +static int decode_auth_field(const char *b64, char **out_user, char **out_pass)
> +{
> +	int b64_len = strlen(b64);
> +	int decoded_max = b64_len;
> +	u8 *decoded;
> +	int decoded_len;
> +	char *colon;
> +
> +	decoded = malloc(decoded_max + 1);
> +	if (!decoded)
> +		return -ENOMEM;
> +
> +	decoded_len = erofs_base64_decode(b64, b64_len, decoded);
> +	if (decoded_len <= 0) {
> +		free(decoded);
> +		return -EINVAL;
> +	}
> +	decoded[decoded_len] = '\0';
> +
> +	colon = strchr((char *)decoded, ':');
> +	if (!colon) {
> +		memset(decoded, 0, decoded_len);
> +		free(decoded);
> +		return -EINVAL;
> +	}
> +
> +	*colon = '\0';
> +	*out_user = strdup((char *)decoded);
> +	*out_pass = strdup(colon + 1);
> +
> +	memset(decoded, 0, decoded_len);
> +	free(decoded);
> +
> +	if (!*out_user || !*out_pass) {
> +		free(*out_user);
> +		free(*out_pass);
> +		*out_user = NULL;
> +		*out_pass = NULL;
> +		return -ENOMEM;
> +	}
> +	return 0;
> +}
> +
> +int erofs_docker_config_lookup(const char *registry,
> +			       struct erofs_docker_credential *cred)
> +{
> +	char *path = NULL;
> +	char *content = NULL;
> +	struct json_object *root = NULL, *auths_obj = NULL;
> +	int ret = -ENOENT;
> +
> +	memset(cred, 0, sizeof(*cred));
> +
> +	path = docker_config_path();
> +	if (!path)
> +		return -ENOENT;
> +
> +	content = read_file_to_string(path);
> +	if (!content) {
> +		erofs_dbg("cannot read docker config: %s", path);
> +		free(path);
> +		return -ENOENT;
> +	}
> +	free(path);
> +
> +	root = json_tokener_parse(content);
> +	memset(content, 0, strlen(content));
> +	free(content);
> +
> +	if (!root) {
> +		erofs_warn("failed to parse docker config.json");
> +		return -EINVAL;
> +	}
> +
> +	if (!json_object_object_get_ex(root, "auths", &auths_obj)) {
> +		erofs_dbg("no \"auths\" in docker config.json");
> +		json_object_put(root);
> +		return -ENOENT;
> +	}
> +
> +	struct json_object_iterator it = json_object_iter_begin(auths_obj);
> +	struct json_object_iterator end = json_object_iter_end(auths_obj);
> +
> +	while (!json_object_iter_equal(&it, &end)) {
> +		const char *key = json_object_iter_peek_name(&it);
> +		struct json_object *entry, *auth_field;
> +		const char *b64;
> +
> +		if (!registry_match(key, registry)) {
> +			json_object_iter_next(&it);
> +			continue;
> +		}
> +
> +		entry = json_object_iter_peek_value(&it);
> +		if (json_object_object_get_ex(entry, "auth", &auth_field)) {
> +			b64 = json_object_get_string(auth_field);
> +			if (b64 && *b64) {
> +				ret = decode_auth_field(b64, &cred->username,
> +							&cred->password);
> +				if (!ret)
> +					erofs_dbg("found docker credentials for %s",
> +						  registry);
> +			}
> +		}
> +		break;
> +	}
> +
> +	json_object_put(root);
> +	return ret;
> +}
> +
> +void erofs_docker_credential_free(struct erofs_docker_credential *cred)
> +{
> +	if (cred->username) {
> +		memset(cred->username, 0, strlen(cred->username));
> +		free(cred->username);
> +		cred->username = NULL;
> +	}
> +	if (cred->password) {
> +		memset(cred->password, 0, strlen(cred->password));
> +		free(cred->password);
> +		cred->password = NULL;
> +	}

maybe introduce erofs_free_sensitive() for these usage.

Thanks,
Gao Xiang


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH v2] erofs-utils: lib: oci: support reading credentials from docker config
  2026-02-09  7:53 [PATCH 1/1] erofs-utils: lib: oci: support reading credentials from docker config ChengyuZhu6
  2026-02-09  9:06 ` Gao Xiang
@ 2026-02-10  9:37 ` ChengyuZhu6
  2026-02-12  1:27   ` Gao Xiang
  2026-02-12  2:03 ` [PATCH v3] " ChengyuZhu6
  2 siblings, 1 reply; 6+ messages in thread
From: ChengyuZhu6 @ 2026-02-10  9:37 UTC (permalink / raw)
  To: linux-erofs; +Cc: hsiangkao, Chengyu Zhu

From: Chengyu Zhu <hudsonzhu@tencent.com>

This patch adds support for reading authentication credentials from
Docker's `config.json` (typically in `~/.docker/config.json` or via
`DOCKER_CONFIG`).

If no username/password is provided via command-line arguments, the
implementation will attempt to look up the registry in the docker config
file and use the stored credentials if found.

Signed-off-by: Chengyu Zhu <hudsonzhu@tencent.com>
---
 include/erofs/internal.h    |  10 ++
 lib/Makefile.am             |   2 +-
 lib/liberofs_dockerconfig.h |  30 +++++
 lib/remotes/docker_config.c | 240 ++++++++++++++++++++++++++++++++++++
 lib/remotes/oci.c           |  18 ++-
 5 files changed, 295 insertions(+), 5 deletions(-)
 create mode 100644 lib/liberofs_dockerconfig.h
 create mode 100644 lib/remotes/docker_config.c

diff --git a/include/erofs/internal.h b/include/erofs/internal.h
index ef019a5..3f1e4ff 100644
--- a/include/erofs/internal.h
+++ b/include/erofs/internal.h
@@ -25,6 +25,8 @@ typedef unsigned short umode_t;
 #ifdef HAVE_PTHREAD_H
 #include <pthread.h>
 #endif
+#include <stdlib.h>
+#include <string.h>
 #include "atomic.h"
 #include "io.h"
 
@@ -542,6 +544,14 @@ static inline int erofs_blk_read(struct erofs_sb_info *sbi, int device_id,
 			      erofs_pos(sbi, nblocks));
 }
 
+static inline void erofs_free_sensitive(void *ptr, size_t len)
+{
+	if (!ptr)
+		return;
+	memset(ptr, 0, len);
+	free(ptr);
+}
+
 /* vmdk.c */
 int erofs_dump_vmdk_desc(FILE *f, struct erofs_sb_info *sbi);
 
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 817f69a..7a65b2c 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -95,7 +95,7 @@ liberofs_la_LDFLAGS += ${liburing_LIBS}
 liberofs_la_SOURCES += backends/ublk.c
 endif
 endif
-liberofs_la_SOURCES += remotes/oci.c
+liberofs_la_SOURCES += remotes/oci.c remotes/docker_config.c
 liberofs_la_CFLAGS += ${json_c_CFLAGS}
 liberofs_la_LDFLAGS += ${json_c_LIBS}
 liberofs_la_SOURCES += gzran.c
diff --git a/lib/liberofs_dockerconfig.h b/lib/liberofs_dockerconfig.h
new file mode 100644
index 0000000..1580e1c
--- /dev/null
+++ b/lib/liberofs_dockerconfig.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */
+/*
+ * Copyright (C) 2026 Tencent, Inc.
+ *             http://www.tencent.com/
+ */
+#ifndef __EROFS_DOCKER_CONFIG_H
+#define __EROFS_DOCKER_CONFIG_H
+
+#include "erofs/internal.h"
+
+#define DOCKER_REGISTRY "docker.io"
+#define DOCKER_API_REGISTRY "registry-1.docker.io"
+#define DOCKER_HUB_AUTH_KEY "https://index.docker.io/v1/"
+
+struct erofs_docker_credential {
+	char *username;
+	char *password;
+};
+
+/**
+ * erofs_docker_config_lookup - look up registry credentials from Docker config
+ * @registry: the registry hostname (e.g. "index.docker.io")
+ * @cred: output credential structure
+ */
+int erofs_docker_config_lookup(const char *registry,
+			       struct erofs_docker_credential *cred);
+
+void erofs_docker_credential_free(struct erofs_docker_credential *cred);
+
+#endif
diff --git a/lib/remotes/docker_config.c b/lib/remotes/docker_config.c
new file mode 100644
index 0000000..89fb826
--- /dev/null
+++ b/lib/remotes/docker_config.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
+/*
+ * Copyright (C) 2026 Tencent, Inc.
+ *             http://www.tencent.com/
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_JSON_C_JSON_H
+#include <json-c/json.h>
+#endif
+
+#include "erofs/defs.h"
+#include "erofs/print.h"
+#include "liberofs_base64.h"
+#include "liberofs_dockerconfig.h"
+
+#ifndef HAVE_JSON_C_JSON_H
+
+int erofs_docker_config_lookup(const char *registry,
+			       struct erofs_docker_credential *cred)
+{
+	(void)registry;
+	(void)cred;
+	return -EOPNOTSUPP;
+}
+
+void erofs_docker_credential_free(struct erofs_docker_credential *cred)
+{
+	(void)cred;
+}
+
+#else /* HAVE_JSON_C_JSON_H */
+
+static char *docker_config_path(void)
+{
+	const char *dir;
+	char *path = NULL;
+
+	dir = getenv("DOCKER_CONFIG");
+	if (dir) {
+		if (!*dir)
+			return NULL;
+		if (asprintf(&path, "%s/config.json", dir) < 0)
+			return NULL;
+		return path;
+	}
+
+	dir = getenv("HOME");
+	if (!dir || !*dir) {
+		erofs_dbg("HOME is not set, cannot locate docker config");
+		return NULL;
+	}
+
+	if (asprintf(&path, "%s/.docker/config.json", dir) < 0)
+		return NULL;
+	return path;
+}
+
+static char *read_file_to_string(const char *path)
+{
+	FILE *fp;
+	struct stat st;
+	char *buf;
+	size_t nread;
+
+	if (stat(path, &st) < 0)
+		return NULL;
+
+	if (st.st_size <= 0 || st.st_size > (1 << 22))
+		return NULL;
+
+	fp = fopen(path, "r");
+	if (!fp)
+		return NULL;
+
+	buf = malloc(st.st_size + 1);
+	if (!buf) {
+		fclose(fp);
+		return NULL;
+	}
+
+	nread = fread(buf, 1, st.st_size, fp);
+	fclose(fp);
+
+	if ((off_t)nread != st.st_size) {
+		free(buf);
+		return NULL;
+	}
+	buf[nread] = '\0';
+	return buf;
+}
+
+/*
+ * Check if @key (an auths entry key) matches @registry.
+ *
+ * For Docker Hub: @registry is docker.io or registry-1.docker.io.
+ * The auths key in config.json is always "https://index.docker.io/v1/".
+ * For other registries: the auths key is an exact match against @registry.
+ */
+static bool registry_match(const char *key, const char *registry)
+{
+	if (!key || !registry)
+		return false;
+
+	if (!strcasecmp(registry, DOCKER_REGISTRY) ||
+	    !strcasecmp(registry, DOCKER_API_REGISTRY))
+		return !strcmp(key, DOCKER_HUB_AUTH_KEY);
+
+	return !strcasecmp(key, registry);
+}
+
+static int decode_auth_field(const char *b64, char **out_user, char **out_pass)
+{
+	int b64_len = strlen(b64);
+	int decoded_max = b64_len;
+	u8 *decoded;
+	int decoded_len;
+	char *colon;
+
+	decoded = malloc(decoded_max + 1);
+	if (!decoded)
+		return -ENOMEM;
+
+	decoded_len = erofs_base64_decode(b64, b64_len, decoded);
+	if (decoded_len <= 0) {
+		free(decoded);
+		return -EINVAL;
+	}
+	decoded[decoded_len] = '\0';
+
+	colon = strchr((char *)decoded, ':');
+	if (!colon) {
+		erofs_free_sensitive(decoded, decoded_len);
+		return -EINVAL;
+	}
+
+	*colon = '\0';
+	*out_user = strdup((char *)decoded);
+	*out_pass = strdup(colon + 1);
+
+	erofs_free_sensitive(decoded, decoded_len);
+
+	if (!*out_user || !*out_pass) {
+		free(*out_user);
+		free(*out_pass);
+		*out_user = NULL;
+		*out_pass = NULL;
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+int erofs_docker_config_lookup(const char *registry,
+			       struct erofs_docker_credential *cred)
+{
+	char *path = NULL;
+	char *content = NULL;
+	struct json_object *root = NULL, *auths_obj = NULL;
+	int ret = -ENOENT;
+
+	memset(cred, 0, sizeof(*cred));
+
+	path = docker_config_path();
+	if (!path)
+		return -ENOENT;
+
+	content = read_file_to_string(path);
+	if (!content) {
+		erofs_dbg("cannot read docker config: %s", path);
+		free(path);
+		return -ENOENT;
+	}
+	free(path);
+
+	root = json_tokener_parse(content);
+	erofs_free_sensitive(content, strlen(content));
+
+	if (!root) {
+		erofs_warn("failed to parse docker config.json");
+		return -EINVAL;
+	}
+
+	if (!json_object_object_get_ex(root, "auths", &auths_obj)) {
+		erofs_dbg("no \"auths\" in docker config.json");
+		json_object_put(root);
+		return -ENOENT;
+	}
+
+	struct json_object_iterator it = json_object_iter_begin(auths_obj);
+	struct json_object_iterator end = json_object_iter_end(auths_obj);
+
+	while (!json_object_iter_equal(&it, &end)) {
+		const char *key = json_object_iter_peek_name(&it);
+		struct json_object *entry, *auth_field;
+		const char *b64;
+
+		if (!registry_match(key, registry)) {
+			json_object_iter_next(&it);
+			continue;
+		}
+
+		entry = json_object_iter_peek_value(&it);
+		if (json_object_object_get_ex(entry, "auth", &auth_field)) {
+			b64 = json_object_get_string(auth_field);
+			if (b64 && *b64) {
+				ret = decode_auth_field(b64, &cred->username,
+							&cred->password);
+				if (!ret)
+					erofs_dbg("found docker credentials for %s",
+						  registry);
+			}
+		}
+		break;
+	}
+
+	json_object_put(root);
+	return ret;
+}
+
+void erofs_docker_credential_free(struct erofs_docker_credential *cred)
+{
+	if (cred->username) {
+		erofs_free_sensitive(cred->username, strlen(cred->username));
+		cred->username = NULL;
+	}
+	if (cred->password) {
+		erofs_free_sensitive(cred->password, strlen(cred->password));
+		cred->password = NULL;
+	}
+}
+
+#endif /* HAVE_JSON_C_JSON_H */
diff --git a/lib/remotes/oci.c b/lib/remotes/oci.c
index fe5df29..48bf37f 100644
--- a/lib/remotes/oci.c
+++ b/lib/remotes/oci.c
@@ -30,6 +30,7 @@
 #include "erofs/tar.h"
 #include "liberofs_base64.h"
 #include "liberofs_oci.h"
+#include "liberofs_dockerconfig.h"
 #include "liberofs_private.h"
 #include "liberofs_gzran.h"
 
@@ -39,9 +40,6 @@
 
 #ifdef OCIEROFS_ENABLED
 
-#define DOCKER_REGISTRY "docker.io"
-#define DOCKER_API_REGISTRY "registry-1.docker.io"
-
 #define DOCKER_MEDIATYPE_MANIFEST_V2 \
 	"application/vnd.docker.distribution.manifest.v2+json"
 #define DOCKER_MEDIATYPE_MANIFEST_V1 \
@@ -985,9 +983,21 @@ static int ocierofs_find_layer_by_digest(struct ocierofs_ctx *ctx, const char *d
 static int ocierofs_prepare_layers(struct ocierofs_ctx *ctx,
 				   const struct ocierofs_config *config)
 {
+	struct erofs_docker_credential dcred = { NULL, NULL };
+	const char *username = config->username;
+	const char *password = config->password;
 	int ret;
 
-	ret = ocierofs_prepare_auth(ctx, config->username, config->password);
+	/* Fallback to Docker config.json if no CLI credentials provided */
+	if ((!username || !*username) && (!password || !*password)) {
+		if (!erofs_docker_config_lookup(ctx->registry, &dcred)) {
+			username = dcred.username;
+			password = dcred.password;
+		}
+	}
+
+	ret = ocierofs_prepare_auth(ctx, username, password);
+	erofs_docker_credential_free(&dcred);
 	if (ret)
 		return ret;
 
-- 
2.51.0



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH v2] erofs-utils: lib: oci: support reading credentials from docker config
  2026-02-10  9:37 ` [PATCH v2] " ChengyuZhu6
@ 2026-02-12  1:27   ` Gao Xiang
  2026-02-12  1:52     ` hudsonZhu
  0 siblings, 1 reply; 6+ messages in thread
From: Gao Xiang @ 2026-02-12  1:27 UTC (permalink / raw)
  To: ChengyuZhu6; +Cc: linux-erofs, hsiangkao, Chengyu Zhu

Hi Chengyu,

On Tue, Feb 10, 2026 at 05:37:26PM +0800, ChengyuZhu6 wrote:
> From: Chengyu Zhu <hudsonzhu@tencent.com>
> 
> This patch adds support for reading authentication credentials from
> Docker's `config.json` (typically in `~/.docker/config.json` or via
> `DOCKER_CONFIG`).
> 
> If no username/password is provided via command-line arguments, the
> implementation will attempt to look up the registry in the docker config
> file and use the stored credentials if found.
> 
> Signed-off-by: Chengyu Zhu <hudsonzhu@tencent.com>

I cannot apply this patch, which commit is this commit based on?

Could you try to base on the latest -experimental or -dev commit?

Thanks,
Gao Xiang


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v2] erofs-utils: lib: oci: support reading credentials from docker config
  2026-02-12  1:27   ` Gao Xiang
@ 2026-02-12  1:52     ` hudsonZhu
  0 siblings, 0 replies; 6+ messages in thread
From: hudsonZhu @ 2026-02-12  1:52 UTC (permalink / raw)
  To: Gao Xiang; +Cc: linux-erofs, hsiangkao, Chengyu Zhu

My bad. Let me see what’s going on.

Thanks,
Chengyu

> 2026年2月12日 09:27,Gao Xiang <xiang@kernel.org> 写道:
> 
> Hi Chengyu,
> 
> On Tue, Feb 10, 2026 at 05:37:26PM +0800, ChengyuZhu6 wrote:
>> From: Chengyu Zhu <hudsonzhu@tencent.com>
>> 
>> This patch adds support for reading authentication credentials from
>> Docker's `config.json` (typically in `~/.docker/config.json` or via
>> `DOCKER_CONFIG`).
>> 
>> If no username/password is provided via command-line arguments, the
>> implementation will attempt to look up the registry in the docker config
>> file and use the stored credentials if found.
>> 
>> Signed-off-by: Chengyu Zhu <hudsonzhu@tencent.com>
> 
> I cannot apply this patch, which commit is this commit based on?
> 
> Could you try to base on the latest -experimental or -dev commit?
> 
> Thanks,
> Gao Xiang



^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH v3] erofs-utils: lib: oci: support reading credentials from docker config
  2026-02-09  7:53 [PATCH 1/1] erofs-utils: lib: oci: support reading credentials from docker config ChengyuZhu6
  2026-02-09  9:06 ` Gao Xiang
  2026-02-10  9:37 ` [PATCH v2] " ChengyuZhu6
@ 2026-02-12  2:03 ` ChengyuZhu6
  2 siblings, 0 replies; 6+ messages in thread
From: ChengyuZhu6 @ 2026-02-12  2:03 UTC (permalink / raw)
  To: linux-erofs; +Cc: hsiangkao, Chengyu Zhu

From: Chengyu Zhu <hudsonzhu@tencent.com>

This patch adds support for reading authentication credentials from
Docker's `config.json` (typically in `~/.docker/config.json` or via
`DOCKER_CONFIG`).

If no username/password is provided via command-line arguments, the
implementation will attempt to look up the registry in the docker config
file and use the stored credentials if found.

Signed-off-by: Chengyu Zhu <hudsonzhu@tencent.com>
---
 include/erofs/internal.h    |  10 ++
 lib/Makefile.am             |   2 +-
 lib/liberofs_dockerconfig.h |  30 +++++
 lib/remotes/docker_config.c | 240 ++++++++++++++++++++++++++++++++++++
 lib/remotes/oci.c           |  18 ++-
 5 files changed, 295 insertions(+), 5 deletions(-)
 create mode 100644 lib/liberofs_dockerconfig.h
 create mode 100644 lib/remotes/docker_config.c

diff --git a/include/erofs/internal.h b/include/erofs/internal.h
index ef019a5..3f1e4ff 100644
--- a/include/erofs/internal.h
+++ b/include/erofs/internal.h
@@ -25,6 +25,8 @@ typedef unsigned short umode_t;
 #ifdef HAVE_PTHREAD_H
 #include <pthread.h>
 #endif
+#include <stdlib.h>
+#include <string.h>
 #include "atomic.h"
 #include "io.h"
 
@@ -542,6 +544,14 @@ static inline int erofs_blk_read(struct erofs_sb_info *sbi, int device_id,
 			      erofs_pos(sbi, nblocks));
 }
 
+static inline void erofs_free_sensitive(void *ptr, size_t len)
+{
+	if (!ptr)
+		return;
+	memset(ptr, 0, len);
+	free(ptr);
+}
+
 /* vmdk.c */
 int erofs_dump_vmdk_desc(FILE *f, struct erofs_sb_info *sbi);
 
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 1721039..77f6fd8 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -89,7 +89,7 @@ liberofs_la_CFLAGS += ${libnl3_CFLAGS}
 liberofs_la_LDFLAGS += ${libnl3_LIBS}
 liberofs_la_SOURCES += backends/nbd.c
 endif
-liberofs_la_SOURCES += remotes/oci.c
+liberofs_la_SOURCES += remotes/oci.c remotes/docker_config.c
 liberofs_la_CFLAGS += ${json_c_CFLAGS}
 liberofs_la_LDFLAGS += ${json_c_LIBS}
 liberofs_la_SOURCES += gzran.c
diff --git a/lib/liberofs_dockerconfig.h b/lib/liberofs_dockerconfig.h
new file mode 100644
index 0000000..1580e1c
--- /dev/null
+++ b/lib/liberofs_dockerconfig.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */
+/*
+ * Copyright (C) 2026 Tencent, Inc.
+ *             http://www.tencent.com/
+ */
+#ifndef __EROFS_DOCKER_CONFIG_H
+#define __EROFS_DOCKER_CONFIG_H
+
+#include "erofs/internal.h"
+
+#define DOCKER_REGISTRY "docker.io"
+#define DOCKER_API_REGISTRY "registry-1.docker.io"
+#define DOCKER_HUB_AUTH_KEY "https://index.docker.io/v1/"
+
+struct erofs_docker_credential {
+	char *username;
+	char *password;
+};
+
+/**
+ * erofs_docker_config_lookup - look up registry credentials from Docker config
+ * @registry: the registry hostname (e.g. "index.docker.io")
+ * @cred: output credential structure
+ */
+int erofs_docker_config_lookup(const char *registry,
+			       struct erofs_docker_credential *cred);
+
+void erofs_docker_credential_free(struct erofs_docker_credential *cred);
+
+#endif
diff --git a/lib/remotes/docker_config.c b/lib/remotes/docker_config.c
new file mode 100644
index 0000000..89fb826
--- /dev/null
+++ b/lib/remotes/docker_config.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
+/*
+ * Copyright (C) 2026 Tencent, Inc.
+ *             http://www.tencent.com/
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_JSON_C_JSON_H
+#include <json-c/json.h>
+#endif
+
+#include "erofs/defs.h"
+#include "erofs/print.h"
+#include "liberofs_base64.h"
+#include "liberofs_dockerconfig.h"
+
+#ifndef HAVE_JSON_C_JSON_H
+
+int erofs_docker_config_lookup(const char *registry,
+			       struct erofs_docker_credential *cred)
+{
+	(void)registry;
+	(void)cred;
+	return -EOPNOTSUPP;
+}
+
+void erofs_docker_credential_free(struct erofs_docker_credential *cred)
+{
+	(void)cred;
+}
+
+#else /* HAVE_JSON_C_JSON_H */
+
+static char *docker_config_path(void)
+{
+	const char *dir;
+	char *path = NULL;
+
+	dir = getenv("DOCKER_CONFIG");
+	if (dir) {
+		if (!*dir)
+			return NULL;
+		if (asprintf(&path, "%s/config.json", dir) < 0)
+			return NULL;
+		return path;
+	}
+
+	dir = getenv("HOME");
+	if (!dir || !*dir) {
+		erofs_dbg("HOME is not set, cannot locate docker config");
+		return NULL;
+	}
+
+	if (asprintf(&path, "%s/.docker/config.json", dir) < 0)
+		return NULL;
+	return path;
+}
+
+static char *read_file_to_string(const char *path)
+{
+	FILE *fp;
+	struct stat st;
+	char *buf;
+	size_t nread;
+
+	if (stat(path, &st) < 0)
+		return NULL;
+
+	if (st.st_size <= 0 || st.st_size > (1 << 22))
+		return NULL;
+
+	fp = fopen(path, "r");
+	if (!fp)
+		return NULL;
+
+	buf = malloc(st.st_size + 1);
+	if (!buf) {
+		fclose(fp);
+		return NULL;
+	}
+
+	nread = fread(buf, 1, st.st_size, fp);
+	fclose(fp);
+
+	if ((off_t)nread != st.st_size) {
+		free(buf);
+		return NULL;
+	}
+	buf[nread] = '\0';
+	return buf;
+}
+
+/*
+ * Check if @key (an auths entry key) matches @registry.
+ *
+ * For Docker Hub: @registry is docker.io or registry-1.docker.io.
+ * The auths key in config.json is always "https://index.docker.io/v1/".
+ * For other registries: the auths key is an exact match against @registry.
+ */
+static bool registry_match(const char *key, const char *registry)
+{
+	if (!key || !registry)
+		return false;
+
+	if (!strcasecmp(registry, DOCKER_REGISTRY) ||
+	    !strcasecmp(registry, DOCKER_API_REGISTRY))
+		return !strcmp(key, DOCKER_HUB_AUTH_KEY);
+
+	return !strcasecmp(key, registry);
+}
+
+static int decode_auth_field(const char *b64, char **out_user, char **out_pass)
+{
+	int b64_len = strlen(b64);
+	int decoded_max = b64_len;
+	u8 *decoded;
+	int decoded_len;
+	char *colon;
+
+	decoded = malloc(decoded_max + 1);
+	if (!decoded)
+		return -ENOMEM;
+
+	decoded_len = erofs_base64_decode(b64, b64_len, decoded);
+	if (decoded_len <= 0) {
+		free(decoded);
+		return -EINVAL;
+	}
+	decoded[decoded_len] = '\0';
+
+	colon = strchr((char *)decoded, ':');
+	if (!colon) {
+		erofs_free_sensitive(decoded, decoded_len);
+		return -EINVAL;
+	}
+
+	*colon = '\0';
+	*out_user = strdup((char *)decoded);
+	*out_pass = strdup(colon + 1);
+
+	erofs_free_sensitive(decoded, decoded_len);
+
+	if (!*out_user || !*out_pass) {
+		free(*out_user);
+		free(*out_pass);
+		*out_user = NULL;
+		*out_pass = NULL;
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+int erofs_docker_config_lookup(const char *registry,
+			       struct erofs_docker_credential *cred)
+{
+	char *path = NULL;
+	char *content = NULL;
+	struct json_object *root = NULL, *auths_obj = NULL;
+	int ret = -ENOENT;
+
+	memset(cred, 0, sizeof(*cred));
+
+	path = docker_config_path();
+	if (!path)
+		return -ENOENT;
+
+	content = read_file_to_string(path);
+	if (!content) {
+		erofs_dbg("cannot read docker config: %s", path);
+		free(path);
+		return -ENOENT;
+	}
+	free(path);
+
+	root = json_tokener_parse(content);
+	erofs_free_sensitive(content, strlen(content));
+
+	if (!root) {
+		erofs_warn("failed to parse docker config.json");
+		return -EINVAL;
+	}
+
+	if (!json_object_object_get_ex(root, "auths", &auths_obj)) {
+		erofs_dbg("no \"auths\" in docker config.json");
+		json_object_put(root);
+		return -ENOENT;
+	}
+
+	struct json_object_iterator it = json_object_iter_begin(auths_obj);
+	struct json_object_iterator end = json_object_iter_end(auths_obj);
+
+	while (!json_object_iter_equal(&it, &end)) {
+		const char *key = json_object_iter_peek_name(&it);
+		struct json_object *entry, *auth_field;
+		const char *b64;
+
+		if (!registry_match(key, registry)) {
+			json_object_iter_next(&it);
+			continue;
+		}
+
+		entry = json_object_iter_peek_value(&it);
+		if (json_object_object_get_ex(entry, "auth", &auth_field)) {
+			b64 = json_object_get_string(auth_field);
+			if (b64 && *b64) {
+				ret = decode_auth_field(b64, &cred->username,
+							&cred->password);
+				if (!ret)
+					erofs_dbg("found docker credentials for %s",
+						  registry);
+			}
+		}
+		break;
+	}
+
+	json_object_put(root);
+	return ret;
+}
+
+void erofs_docker_credential_free(struct erofs_docker_credential *cred)
+{
+	if (cred->username) {
+		erofs_free_sensitive(cred->username, strlen(cred->username));
+		cred->username = NULL;
+	}
+	if (cred->password) {
+		erofs_free_sensitive(cred->password, strlen(cred->password));
+		cred->password = NULL;
+	}
+}
+
+#endif /* HAVE_JSON_C_JSON_H */
diff --git a/lib/remotes/oci.c b/lib/remotes/oci.c
index 4c4568f..193464b 100644
--- a/lib/remotes/oci.c
+++ b/lib/remotes/oci.c
@@ -26,14 +26,12 @@
 #include "erofs/tar.h"
 #include "liberofs_base64.h"
 #include "liberofs_oci.h"
+#include "liberofs_dockerconfig.h"
 #include "liberofs_private.h"
 #include "liberofs_gzran.h"
 
 #ifdef OCIEROFS_ENABLED
 
-#define DOCKER_REGISTRY "docker.io"
-#define DOCKER_API_REGISTRY "registry-1.docker.io"
-
 #define DOCKER_MEDIATYPE_MANIFEST_V2 \
 	"application/vnd.docker.distribution.manifest.v2+json"
 #define DOCKER_MEDIATYPE_MANIFEST_V1 \
@@ -964,9 +962,21 @@ static int ocierofs_find_layer_by_digest(struct ocierofs_ctx *ctx, const char *d
 static int ocierofs_prepare_layers(struct ocierofs_ctx *ctx,
 				   const struct ocierofs_config *config)
 {
+	struct erofs_docker_credential dcred = { NULL, NULL };
+	const char *username = config->username;
+	const char *password = config->password;
 	int ret;
 
-	ret = ocierofs_prepare_auth(ctx, config->username, config->password);
+	/* Fallback to Docker config.json if no CLI credentials provided */
+	if ((!username || !*username) && (!password || !*password)) {
+		if (!erofs_docker_config_lookup(ctx->registry, &dcred)) {
+			username = dcred.username;
+			password = dcred.password;
+		}
+	}
+
+	ret = ocierofs_prepare_auth(ctx, username, password);
+	erofs_docker_credential_free(&dcred);
 	if (ret)
 		return ret;
 
-- 
2.51.0



^ permalink raw reply related	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-02-12  2:04 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-09  7:53 [PATCH 1/1] erofs-utils: lib: oci: support reading credentials from docker config ChengyuZhu6
2026-02-09  9:06 ` Gao Xiang
2026-02-10  9:37 ` [PATCH v2] " ChengyuZhu6
2026-02-12  1:27   ` Gao Xiang
2026-02-12  1:52     ` hudsonZhu
2026-02-12  2:03 ` [PATCH v3] " ChengyuZhu6

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox