From: zhaoyifan <zhaoyifa...@huawei.com>

This patch introduces configuration options for the upcoming experimental S3
support, including configuration parsing and passwd_file reading logic.

User could specify the following options:
- S3 service endpoint (Compulsory)
- S3 credentials file, in the format of "$ak:%sk" (Optional)
- S3 API calling style (Optional)
- S3 API signature version, only sigV2 supported yet (Optional)

Signed-off-by: Yifan Zhao <zhaoyifa...@huawei.com>
---
change since v1:
- rename: include/erofs/s3.h => lib/liberofs_s3.h
- add liberofs_s3.h in this patch rather than previous one

 lib/liberofs_s3.h |  40 +++++++++
 lib/remotes/s3.c  |   3 +-
 mkfs/main.c       | 220 ++++++++++++++++++++++++++++++++++++++++------
 3 files changed, 233 insertions(+), 30 deletions(-)
 create mode 100644 lib/liberofs_s3.h

diff --git a/lib/liberofs_s3.h b/lib/liberofs_s3.h
new file mode 100644
index 0000000..16a06c9
--- /dev/null
+++ b/lib/liberofs_s3.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 */
+/*
+ * Copyright (C) 2025 HUAWEI, Inc.
+ *             http://www.huawei.com/
+ * Created by Yifan Zhao <zhaoyifa...@huawei.com>
+ */
+#ifndef __EROFS_S3_H
+#define __EROFS_S3_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum s3erofs_url_style {
+    S3EROFS_URL_STYLE_PATH,          // Path style: 
https://s3.amazonaws.com/bucket/object
+    S3EROFS_URL_STYLE_VIRTUAL_HOST,  // Virtual host style: 
https://bucket.s3.amazonaws.com/object
+};
+
+enum s3erofs_signature_version {
+       S3EROFS_SIGNATURE_VERSION_2,
+       S3EROFS_SIGNATURE_VERSION_4,
+};
+
+#define S3_ACCESS_KEY_LEN 256
+#define S3_SECRET_KEY_LEN 256
+
+struct erofs_s3 {
+       const char *endpoint, *bucket;
+       char access_key[S3_ACCESS_KEY_LEN + 1];
+       char secret_key[S3_SECRET_KEY_LEN + 1];
+
+       enum s3erofs_url_style url_style;
+       enum s3erofs_signature_version sig;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
\ No newline at end of file
diff --git a/lib/remotes/s3.c b/lib/remotes/s3.c
index ed2b023..358ee91 100644
--- a/lib/remotes/s3.c
+++ b/lib/remotes/s3.c
@@ -3,4 +3,5 @@
  * Copyright (C) 2025 HUAWEI, Inc.
  *             http://www.huawei.com/
  * Created by Yifan Zhao <zhaoyifa...@huawei.com>
- */
\ No newline at end of file
+ */
+#include "liberofs_s3.h"
\ No newline at end of file
diff --git a/mkfs/main.c b/mkfs/main.c
index 3aa1421..f524f45 100644
--- a/mkfs/main.c
+++ b/mkfs/main.c
@@ -31,6 +31,7 @@
 #include "../lib/liberofs_private.h"
 #include "../lib/liberofs_uuid.h"
 #include "../lib/liberofs_metabox.h"
+#include "../lib/liberofs_s3.h"
 #include "../lib/compressor.h"
 
 static struct option long_options[] = {
@@ -59,6 +60,9 @@ static struct option long_options[] = {
        {"gid-offset", required_argument, NULL, 17},
        {"tar", optional_argument, NULL, 20},
        {"aufs", no_argument, NULL, 21},
+#ifdef HAVE_S3
+       {"s3", required_argument, NULL, 22},
+#endif
        {"mount-point", required_argument, NULL, 512},
        {"xattr-prefix", required_argument, NULL, 19},
 #ifdef WITH_ANDROID
@@ -197,6 +201,12 @@ static void usage(int argc, char **argv)
                " --root-xattr-isize=#  ensure the inline xattr size of the 
root directory is # bytes at least\n"
                " --aufs                replace aufs special files with 
overlayfs metadata\n"
                " --sort=<path,none>    data sorting order for tarballs as 
input (default: path)\n"
+#ifdef HAVE_S3
+               " --s3=X                generate an index-only image from 
s3-compatible object store backend\n"
+               "   [,passwd_file=Y]    X=endpoint, Y=s3 credentials file\n"
+               "   [,style=Z]          S3 API calling style (Z = vhost|path) 
(default: vhost)\n"
+               "   [,sig=<2,4>]        S3 API signature version (default: 2)\n"
+#endif
                " --tar=X               generate a full or index-only image 
from a tarball(-ish) source\n"
                "                       (X = f|i|headerball; f=full mode, 
i=index mode,\n"
                "                                            headerball=file 
data is omited in the source stream)\n"
@@ -247,6 +257,10 @@ static struct erofs_tarfile erofstar = {
 static bool incremental_mode;
 static u8 metabox_algorithmid;
 
+#ifdef HAVE_S3
+static struct erofs_s3 s3cfg;
+#endif
+
 enum {
        EROFS_MKFS_DATA_IMPORT_DEFAULT,
        EROFS_MKFS_DATA_IMPORT_FULLDATA,
@@ -257,6 +271,9 @@ enum {
 enum {
        EROFS_MKFS_SOURCE_DEFAULT,
        EROFS_MKFS_SOURCE_TAR,
+#ifdef HAVE_S3
+       EROFS_MKFS_SOURCE_S3,
+#endif
        EROFS_MKFS_SOURCE_REBUILD,
 } source_mode;
 
@@ -522,6 +539,139 @@ static void mkfs_parse_tar_cfg(char *cfg)
                erofstar.index_mode = true;
 }
 
+#ifdef HAVE_S3
+static int mkfs_parse_s3_cfg_passwd(const char *filepath, char *ak, char *sk)
+{
+       struct stat file_stat;
+       int fd, n, ret = 0;
+       char buf[S3_ACCESS_KEY_LEN + S3_SECRET_KEY_LEN + 3];
+       char *colon;
+
+       fd = open(filepath, O_RDONLY);
+       if (fd < 0) {
+               erofs_err("failed to open passwd_file %s", filepath);
+               return -errno;
+       }
+
+       if (fstat(fd, &file_stat) != 0) {
+               ret = -errno;
+               goto err;
+       }
+
+       if (!S_ISREG(file_stat.st_mode)) {
+               erofs_err("%s is not a regular file", filepath);
+               ret = -EINVAL;
+               goto err;
+       }
+
+       if ((file_stat.st_mode & (S_IXUSR | S_IROTH | S_IWOTH | S_IXOTH | 
S_IRGRP |
+                                 S_IWGRP | S_IXGRP)) != 0) {
+               erofs_err("%s should not have others/group permission", 
filepath);
+               ret = -EPERM;
+               goto err;
+       }
+
+       if (file_stat.st_size > S3_ACCESS_KEY_LEN + S3_SECRET_KEY_LEN + 3) {
+               erofs_err("passwd_file %s with size %lu is too big", filepath,
+                         file_stat.st_size);
+               ret = -EINVAL;
+               goto err;
+       }
+
+       n = read(fd, buf, file_stat.st_size);
+       if (n < 0) {
+               ret = -errno;
+               goto err;
+       }
+       buf[n] = '\0';
+
+       while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == '\r'))
+               buf[--n] = '\0';
+
+       colon = strchr(buf, ':');
+       if (!colon) {
+               ret = -EINVAL;
+               goto err;
+       }
+       *colon = '\0';
+
+       strcpy(ak, buf);
+       strcpy(sk, colon + 1);
+
+err:
+       close(fd);
+       return ret;
+}
+
+static int mkfs_parse_s3_cfg(char *cfg_str)
+{
+       char *p, *q, *opt;
+       int ret = 0;
+
+       if (source_mode != EROFS_MKFS_SOURCE_DEFAULT) {
+               erofs_warn("generation image from s3 conflicting with other "
+                          "options, ignoring it");
+               return 0;
+       }
+       source_mode = EROFS_MKFS_SOURCE_S3;
+
+       if (!cfg_str) {
+               erofs_err("s3: missing parameter");
+               return -EINVAL;
+       }
+
+       s3cfg.url_style = S3EROFS_URL_STYLE_VIRTUAL_HOST;
+       s3cfg.sig = S3EROFS_SIGNATURE_VERSION_2;
+
+       p = strchr(cfg_str, ',');
+       if (p) {
+               s3cfg.endpoint = strndup(cfg_str, p - cfg_str);
+       } else {
+               s3cfg.endpoint = strdup(cfg_str);
+               return 0;
+       }
+
+       opt = p + 1;
+       while (opt) {
+               q = strchr(opt, ',');
+               if (q)
+                       *q = '\0';
+
+               if ((p = strstr(opt, "passwd_file="))) {
+                       p += strlen("passwd_file=");
+                       ret = mkfs_parse_s3_cfg_passwd(p, s3cfg.access_key,
+                                                      s3cfg.secret_key);
+                       if (ret)
+                               return ret;
+               } else if ((p = strstr(opt, "style="))) {
+                       p += strlen("style=");
+                       if (strncmp(p, "vhost", 5) == 0) {
+                               s3cfg.url_style = 
S3EROFS_URL_STYLE_VIRTUAL_HOST;
+                       } else if (strncmp(p, "path", 4) == 0) {
+                               s3cfg.url_style = S3EROFS_URL_STYLE_PATH;
+                       } else {
+                               erofs_err("invalid s3 style %s", p);
+                               return -EINVAL;
+                       }
+               } else if ((p = strstr(opt, "sig="))) {
+                       p += strlen("sig=");
+                       if (strncmp(p, "4", 1) == 0) {
+                               erofs_warn("AWS Signature Version 4 is not 
supported yet, using Version 2");
+                       } else if (strncmp(p, "2", 1) == 0) {
+                               s3cfg.sig = S3EROFS_SIGNATURE_VERSION_2;
+                       } else {
+                               erofs_err("invalid s3 sig %s", p);
+                               return -EINVAL;
+                       }
+               }
+
+               opt = q ? q + 1 : NULL;
+       }
+
+       return 0;
+}
+#endif
+
 static int mkfs_parse_one_compress_alg(char *alg,
                                       struct erofs_compr_opts *copts)
 {
@@ -670,6 +820,13 @@ static int mkfs_parse_sources(int argc, char *argv[], int 
optind)
                        erofstar.ios.dumpfd = fd;
                }
                break;
+#ifdef HAVE_S3
+       case EROFS_MKFS_SOURCE_S3:
+               s3cfg.bucket = strdup(argv[optind++]);
+               if (!s3cfg.bucket)
+                       return -ENOMEM;
+               break;
+#endif
        case EROFS_MKFS_SOURCE_REBUILD:
        default:
                erofs_err("unexpected source_mode: %d", source_mode);
@@ -944,6 +1101,13 @@ static int mkfs_parse_options_cfg(int argc, char *argv[])
                case 21:
                        erofstar.aufs = true;
                        break;
+#ifdef HAVE_S3
+               case 22:
+                       err = mkfs_parse_s3_cfg(optarg);
+                       if (err)
+                               return err;
+                       break;
+#endif
                case 516:
                        if (!optarg || !strcmp(optarg, "1"))
                                cfg.c_ovlfs_strip = true;
@@ -1539,35 +1703,7 @@ int main(int argc, char **argv)
 
        erofs_inode_manager_init();
 
-       if (source_mode == EROFS_MKFS_SOURCE_TAR) {
-               root = erofs_rebuild_make_root(&g_sbi);
-               if (IS_ERR(root)) {
-                       err = PTR_ERR(root);
-                       goto exit;
-               }
-
-               while (!(err = tarerofs_parse_tar(root, &erofstar)));
-
-               if (err < 0)
-                       goto exit;
-
-               err = erofs_rebuild_dump_tree(root, incremental_mode);
-               if (err < 0)
-                       goto exit;
-       } else if (source_mode == EROFS_MKFS_SOURCE_REBUILD) {
-               root = erofs_rebuild_make_root(&g_sbi);
-               if (IS_ERR(root)) {
-                       err = PTR_ERR(root);
-                       goto exit;
-               }
-
-               err = erofs_mkfs_rebuild_load_trees(root);
-               if (err)
-                       goto exit;
-               err = erofs_rebuild_dump_tree(root, incremental_mode);
-               if (err)
-                       goto exit;
-       } else {
+       if (source_mode == EROFS_MKFS_SOURCE_DEFAULT) {
                err = erofs_build_shared_xattrs_from_path(&g_sbi, 
cfg.c_src_path);
                if (err) {
                        erofs_err("failed to build shared xattrs: %s",
@@ -1584,6 +1720,32 @@ int main(int argc, char **argv)
                        root = NULL;
                        goto exit;
                }
+       } else {
+               root = erofs_rebuild_make_root(&g_sbi);
+               if (IS_ERR(root)) {
+                       err = PTR_ERR(root);
+                       goto exit;
+               }
+
+               if (source_mode == EROFS_MKFS_SOURCE_TAR) {
+                       while (!(err = tarerofs_parse_tar(root, &erofstar)))
+                               ;
+                       if (err < 0)
+                               goto exit;
+               } else if (source_mode == EROFS_MKFS_SOURCE_REBUILD) {
+                       err = erofs_mkfs_rebuild_load_trees(root);
+                       if (err)
+                               goto exit;
+#ifdef HAVE_S3
+               } else if (source_mode == EROFS_MKFS_SOURCE_S3) {
+                       err = -EOPNOTSUPP;
+                       goto exit;
+#endif
+               }
+
+               err = erofs_rebuild_dump_tree(root, incremental_mode);
+               if (err)
+                       goto exit;
        }
 
        if (tar_index_512b) {
-- 
2.46.0


Reply via email to