mkfs support for the new chunk-based uncompressed files,
including:
 * chunk-based files with 4-byte block address array;
 * chunk-based files with 8-byte inode chunk indexes.

Signed-off-by: Gao Xiang <[email protected]>
---
 include/erofs/blobchunk.h |  18 ++++
 include/erofs/config.h    |   1 +
 include/erofs/defs.h      |  77 ++++++++++++++
 include/erofs/hashtable.h |  77 --------------
 include/erofs/internal.h  |   1 +
 include/erofs/io.h        |   2 +
 lib/Makefile.am           |   2 +-
 lib/blobchunk.c           | 217 ++++++++++++++++++++++++++++++++++++++
 lib/inode.c               |  36 +++++--
 lib/io.c                  |   2 +-
 man/mkfs.erofs.1          |   3 +
 mkfs/main.c               |  38 +++++++
 12 files changed, 389 insertions(+), 85 deletions(-)
 create mode 100644 include/erofs/blobchunk.h
 create mode 100644 lib/blobchunk.c

diff --git a/include/erofs/blobchunk.h b/include/erofs/blobchunk.h
new file mode 100644
index 000000000000..b418227e0ef8
--- /dev/null
+++ b/include/erofs/blobchunk.h
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * erofs-utils/lib/blobchunk.h
+ *
+ * Copyright (C) 2021, Alibaba Cloud
+ */
+#ifndef __EROFS_BLOBCHUNK_H
+#define __EROFS_BLOBCHUNK_H
+
+#include "erofs/internal.h"
+
+int erofs_blob_write_chunk_indexes(struct erofs_inode *inode, erofs_off_t off);
+int erofs_blob_write_chunked_file(struct erofs_inode *inode);
+int erofs_blob_remap(void);
+void erofs_blob_exit(void);
+int erofs_blob_init(void);
+
+#endif
diff --git a/include/erofs/config.h b/include/erofs/config.h
index d5d9b5a751c0..574dd52be12d 100644
--- a/include/erofs/config.h
+++ b/include/erofs/config.h
@@ -42,6 +42,7 @@ struct erofs_configure {
        bool c_random_pclusterblks;
 #endif
        char c_timeinherit;
+       char c_chunkbits;
        bool c_noinline_data;
 
 #ifdef HAVE_LIBSELINUX
diff --git a/include/erofs/defs.h b/include/erofs/defs.h
index 6e0a7774871c..96bbb6574ff3 100644
--- a/include/erofs/defs.h
+++ b/include/erofs/defs.h
@@ -175,6 +175,83 @@ static inline u32 get_unaligned_le32(const u8 *p)
        return p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
 }
 
+/**
+ * ilog2 - log of base 2 of 32-bit or a 64-bit unsigned value
+ * @n - parameter
+ *
+ * constant-capable log of base 2 calculation
+ * - this can be used to initialise global variables from constant data, hence
+ *   the massive ternary operator construction
+ *
+ * selects the appropriately-sized optimised version depending on sizeof(n)
+ */
+#define ilog2(n)                       \
+(                                      \
+       (n) & (1ULL << 63) ? 63 :       \
+       (n) & (1ULL << 62) ? 62 :       \
+       (n) & (1ULL << 61) ? 61 :       \
+       (n) & (1ULL << 60) ? 60 :       \
+       (n) & (1ULL << 59) ? 59 :       \
+       (n) & (1ULL << 58) ? 58 :       \
+       (n) & (1ULL << 57) ? 57 :       \
+       (n) & (1ULL << 56) ? 56 :       \
+       (n) & (1ULL << 55) ? 55 :       \
+       (n) & (1ULL << 54) ? 54 :       \
+       (n) & (1ULL << 53) ? 53 :       \
+       (n) & (1ULL << 52) ? 52 :       \
+       (n) & (1ULL << 51) ? 51 :       \
+       (n) & (1ULL << 50) ? 50 :       \
+       (n) & (1ULL << 49) ? 49 :       \
+       (n) & (1ULL << 48) ? 48 :       \
+       (n) & (1ULL << 47) ? 47 :       \
+       (n) & (1ULL << 46) ? 46 :       \
+       (n) & (1ULL << 45) ? 45 :       \
+       (n) & (1ULL << 44) ? 44 :       \
+       (n) & (1ULL << 43) ? 43 :       \
+       (n) & (1ULL << 42) ? 42 :       \
+       (n) & (1ULL << 41) ? 41 :       \
+       (n) & (1ULL << 40) ? 40 :       \
+       (n) & (1ULL << 39) ? 39 :       \
+       (n) & (1ULL << 38) ? 38 :       \
+       (n) & (1ULL << 37) ? 37 :       \
+       (n) & (1ULL << 36) ? 36 :       \
+       (n) & (1ULL << 35) ? 35 :       \
+       (n) & (1ULL << 34) ? 34 :       \
+       (n) & (1ULL << 33) ? 33 :       \
+       (n) & (1ULL << 32) ? 32 :       \
+       (n) & (1ULL << 31) ? 31 :       \
+       (n) & (1ULL << 30) ? 30 :       \
+       (n) & (1ULL << 29) ? 29 :       \
+       (n) & (1ULL << 28) ? 28 :       \
+       (n) & (1ULL << 27) ? 27 :       \
+       (n) & (1ULL << 26) ? 26 :       \
+       (n) & (1ULL << 25) ? 25 :       \
+       (n) & (1ULL << 24) ? 24 :       \
+       (n) & (1ULL << 23) ? 23 :       \
+       (n) & (1ULL << 22) ? 22 :       \
+       (n) & (1ULL << 21) ? 21 :       \
+       (n) & (1ULL << 20) ? 20 :       \
+       (n) & (1ULL << 19) ? 19 :       \
+       (n) & (1ULL << 18) ? 18 :       \
+       (n) & (1ULL << 17) ? 17 :       \
+       (n) & (1ULL << 16) ? 16 :       \
+       (n) & (1ULL << 15) ? 15 :       \
+       (n) & (1ULL << 14) ? 14 :       \
+       (n) & (1ULL << 13) ? 13 :       \
+       (n) & (1ULL << 12) ? 12 :       \
+       (n) & (1ULL << 11) ? 11 :       \
+       (n) & (1ULL << 10) ? 10 :       \
+       (n) & (1ULL <<  9) ?  9 :       \
+       (n) & (1ULL <<  8) ?  8 :       \
+       (n) & (1ULL <<  7) ?  7 :       \
+       (n) & (1ULL <<  6) ?  6 :       \
+       (n) & (1ULL <<  5) ?  5 :       \
+       (n) & (1ULL <<  4) ?  4 :       \
+       (n) & (1ULL <<  3) ?  3 :       \
+       (n) & (1ULL <<  2) ?  2 :       \
+       (n) & (1ULL <<  1) ?  1 : 0     \
+)
+
 #ifndef __always_inline
 #define __always_inline        inline
 #endif
diff --git a/include/erofs/hashtable.h b/include/erofs/hashtable.h
index a71cb0044816..90eb84ee8598 100644
--- a/include/erofs/hashtable.h
+++ b/include/erofs/hashtable.h
@@ -262,83 +262,6 @@ static __always_inline u32 hash_64(u64 val, unsigned int 
bits)
 #endif
 }
 
-/**
- * ilog2 - log of base 2 of 32-bit or a 64-bit unsigned value
- * @n - parameter
- *
- * constant-capable log of base 2 calculation
- * - this can be used to initialise global variables from constant data, hence
- *   the massive ternary operator construction
- *
- * selects the appropriately-sized optimised version depending on sizeof(n)
- */
-#define ilog2(n)                               \
-(                                                              \
-       (n) & (1ULL << 63) ? 63 :       \
-       (n) & (1ULL << 62) ? 62 :       \
-       (n) & (1ULL << 61) ? 61 :       \
-       (n) & (1ULL << 60) ? 60 :       \
-       (n) & (1ULL << 59) ? 59 :       \
-       (n) & (1ULL << 58) ? 58 :       \
-       (n) & (1ULL << 57) ? 57 :       \
-       (n) & (1ULL << 56) ? 56 :       \
-       (n) & (1ULL << 55) ? 55 :       \
-       (n) & (1ULL << 54) ? 54 :       \
-       (n) & (1ULL << 53) ? 53 :       \
-       (n) & (1ULL << 52) ? 52 :       \
-       (n) & (1ULL << 51) ? 51 :       \
-       (n) & (1ULL << 50) ? 50 :       \
-       (n) & (1ULL << 49) ? 49 :       \
-       (n) & (1ULL << 48) ? 48 :       \
-       (n) & (1ULL << 47) ? 47 :       \
-       (n) & (1ULL << 46) ? 46 :       \
-       (n) & (1ULL << 45) ? 45 :       \
-       (n) & (1ULL << 44) ? 44 :       \
-       (n) & (1ULL << 43) ? 43 :       \
-       (n) & (1ULL << 42) ? 42 :       \
-       (n) & (1ULL << 41) ? 41 :       \
-       (n) & (1ULL << 40) ? 40 :       \
-       (n) & (1ULL << 39) ? 39 :       \
-       (n) & (1ULL << 38) ? 38 :       \
-       (n) & (1ULL << 37) ? 37 :       \
-       (n) & (1ULL << 36) ? 36 :       \
-       (n) & (1ULL << 35) ? 35 :       \
-       (n) & (1ULL << 34) ? 34 :       \
-       (n) & (1ULL << 33) ? 33 :       \
-       (n) & (1ULL << 32) ? 32 :       \
-       (n) & (1ULL << 31) ? 31 :       \
-       (n) & (1ULL << 30) ? 30 :       \
-       (n) & (1ULL << 29) ? 29 :       \
-       (n) & (1ULL << 28) ? 28 :       \
-       (n) & (1ULL << 27) ? 27 :       \
-       (n) & (1ULL << 26) ? 26 :       \
-       (n) & (1ULL << 25) ? 25 :       \
-       (n) & (1ULL << 24) ? 24 :       \
-       (n) & (1ULL << 23) ? 23 :       \
-       (n) & (1ULL << 22) ? 22 :       \
-       (n) & (1ULL << 21) ? 21 :       \
-       (n) & (1ULL << 20) ? 20 :       \
-       (n) & (1ULL << 19) ? 19 :       \
-       (n) & (1ULL << 18) ? 18 :       \
-       (n) & (1ULL << 17) ? 17 :       \
-       (n) & (1ULL << 16) ? 16 :       \
-       (n) & (1ULL << 15) ? 15 :       \
-       (n) & (1ULL << 14) ? 14 :       \
-       (n) & (1ULL << 13) ? 13 :       \
-       (n) & (1ULL << 12) ? 12 :       \
-       (n) & (1ULL << 11) ? 11 :       \
-       (n) & (1ULL << 10) ? 10 :       \
-       (n) & (1ULL <<  9) ?  9 :       \
-       (n) & (1ULL <<  8) ?  8 :       \
-       (n) & (1ULL <<  7) ?  7 :       \
-       (n) & (1ULL <<  6) ?  6 :       \
-       (n) & (1ULL <<  5) ?  5 :       \
-       (n) & (1ULL <<  4) ?  4 :       \
-       (n) & (1ULL <<  3) ?  3 :       \
-       (n) & (1ULL <<  2) ?  2 :       \
-       (n) & (1ULL <<  1) ?  1 : 0     \
-)
-
 #define DEFINE_HASHTABLE(name, bits)                                   \
        struct hlist_head name[1 << (bits)] =                           \
                        { [0 ... ((1 << (bits)) - 1)] = HLIST_HEAD_INIT }
diff --git a/include/erofs/internal.h b/include/erofs/internal.h
index 8621f3426410..8b154edb9f88 100644
--- a/include/erofs/internal.h
+++ b/include/erofs/internal.h
@@ -165,6 +165,7 @@ struct erofs_inode {
 
        union {
                void *compressmeta;
+               void *chunkindexes;
                struct {
                        uint16_t z_advise;
                        uint8_t  z_algorithmtype[2];
diff --git a/include/erofs/io.h b/include/erofs/io.h
index 2597bf48a1c4..2597c5c0eb96 100644
--- a/include/erofs/io.h
+++ b/include/erofs/io.h
@@ -25,6 +25,8 @@ int dev_fsync(void);
 int dev_resize(erofs_blk_t nblocks);
 u64 dev_length(void);
 
+extern int erofs_devfd;
+
 int erofs_copy_file_range(int fd_in, erofs_off_t *off_in,
                           int fd_out, erofs_off_t *off_out,
                           size_t length);
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 2638a109c29c..b64d90b3e144 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -22,7 +22,7 @@ noinst_HEADERS = $(top_srcdir)/include/erofs_fs.h \
 noinst_HEADERS += compressor.h
 liberofs_la_SOURCES = config.c io.c cache.c super.c inode.c xattr.c exclude.c \
                      namei.c data.c compress.c compressor.c zmap.c 
decompress.c \
-                     compress_hints.c hashmap.c sha256.c
+                     compress_hints.c hashmap.c sha256.c blobchunk.c
 liberofs_la_CFLAGS = -Wall -Werror -I$(top_srcdir)/include
 if ENABLE_LZ4
 liberofs_la_CFLAGS += ${LZ4_CFLAGS}
diff --git a/lib/blobchunk.c b/lib/blobchunk.c
new file mode 100644
index 000000000000..e05d0cb08252
--- /dev/null
+++ b/lib/blobchunk.c
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * erofs-utils/lib/blobchunk.c
+ *
+ * Copyright (C) 2021, Alibaba Cloud
+ */
+#define _GNU_SOURCE
+#include "erofs/hashmap.h"
+#include "erofs/blobchunk.h"
+#include "erofs/cache.h"
+#include "erofs/io.h"
+#include <unistd.h>
+
+void erofs_sha256(const unsigned char *in, unsigned long in_size,
+                 unsigned char out[32]);
+
+struct erofs_blobchunk {
+       struct hashmap_entry ent;
+       char            sha256[32];
+       unsigned int    chunksize;
+       erofs_blk_t     blkaddr;
+};
+
+static struct hashmap blob_hashmap;
+static FILE *blobfile;
+static erofs_blk_t remapped_base;
+
+static struct erofs_blobchunk *erofs_blob_getchunk(int fd,
+               unsigned int chunksize)
+{
+       static u8 zeroed[EROFS_BLKSIZ];
+       u8 *chunkdata, sha256[32];
+       int ret;
+       unsigned int hash;
+       erofs_off_t blkpos;
+       struct erofs_blobchunk *chunk;
+
+       chunkdata = malloc(chunksize);
+       if (!chunkdata)
+               return ERR_PTR(-ENOMEM);
+
+       ret = read(fd, chunkdata, chunksize);
+       if (ret < chunksize) {
+               chunk = ERR_PTR(-EIO);
+               goto out;
+       }
+       erofs_sha256(chunkdata, chunksize, sha256);
+       hash = memhash(sha256, sizeof(sha256));
+       chunk = hashmap_get_from_hash(&blob_hashmap, hash, sha256);
+       if (chunk) {
+               DBG_BUGON(chunksize != chunk->chunksize);
+               goto out;
+       }
+       chunk = malloc(sizeof(struct erofs_blobchunk));
+       if (!chunk) {
+               chunk = ERR_PTR(-ENOMEM);
+               goto out;
+       }
+
+       chunk->chunksize = chunksize;
+       blkpos = ftell(blobfile);
+       DBG_BUGON(erofs_blkoff(blkpos));
+       chunk->blkaddr = erofs_blknr(blkpos);
+       memcpy(chunk->sha256, sha256, sizeof(sha256));
+       hashmap_entry_init(&chunk->ent, hash);
+       hashmap_add(&blob_hashmap, chunk);
+
+       erofs_dbg("Writing chunk (%u bytes) to %u", chunksize, chunk->blkaddr);
+       ret = fwrite(chunkdata, chunksize, 1, blobfile);
+       if (ret == 1 && erofs_blkoff(chunksize))
+               ret = fwrite(zeroed, EROFS_BLKSIZ - erofs_blkoff(chunksize),
+                            1, blobfile);
+       if (ret < 1) {
+               struct hashmap_entry key;
+
+               hashmap_entry_init(&key, hash);
+               hashmap_remove(&blob_hashmap, &key, sha256);
+               chunk = ERR_PTR(-ENOSPC);
+               goto out;
+       }
+out:
+       free(chunkdata);
+       return chunk;
+}
+
+static int erofs_blob_hashmap_cmp(const void *a, const void *b,
+                                 const void *key)
+{
+       const struct erofs_blobchunk *ec1 =
+                       container_of((struct hashmap_entry *)a,
+                                    struct erofs_blobchunk, ent);
+       const struct erofs_blobchunk *ec2 =
+                       container_of((struct hashmap_entry *)b,
+                                    struct erofs_blobchunk, ent);
+
+       return memcmp(ec1->sha256, key ? key : ec2->sha256,
+                     sizeof(ec1->sha256));
+}
+
+int erofs_blob_write_chunk_indexes(struct erofs_inode *inode,
+                                  erofs_off_t off)
+{
+       struct erofs_inode_chunk_index idx = {0};
+       unsigned int dst, src, unit;
+
+       if (inode->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES)
+               unit = sizeof(struct erofs_inode_chunk_index);
+       else
+               unit = EROFS_BLOCK_MAP_ENTRY_SIZE;
+
+       for (dst = src = 0; dst < inode->extent_isize;
+            src += sizeof(void *), dst += unit) {
+               struct erofs_blobchunk *chunk;
+
+               chunk = *(void **)(inode->chunkindexes + src);
+
+               idx.blkaddr = chunk->blkaddr + remapped_base;
+               if (unit == EROFS_BLOCK_MAP_ENTRY_SIZE)
+                       memcpy(inode->chunkindexes + dst, &idx.blkaddr, unit);
+               else
+                       memcpy(inode->chunkindexes + dst, &idx, sizeof(idx));
+       }
+       off = roundup(off, unit);
+
+       return dev_write(inode->chunkindexes, off, inode->extent_isize);
+}
+
+int erofs_blob_write_chunked_file(struct erofs_inode *inode)
+{
+       unsigned int chunksize = 1 << cfg.c_chunkbits;
+       unsigned int count = DIV_ROUND_UP(inode->i_size, chunksize);
+       struct erofs_inode_chunk_index *idx;
+       erofs_off_t pos, len;
+       unsigned int unit;
+       int fd, ret;
+
+       inode->u.chunkformat |= inode->u.chunkbits - LOG_BLOCK_SIZE;
+
+       if (inode->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES)
+               unit = sizeof(struct erofs_inode_chunk_index);
+       else
+               unit = EROFS_BLOCK_MAP_ENTRY_SIZE;
+
+       inode->extent_isize = count * unit;
+       idx = malloc(count * max(sizeof(*idx), sizeof(void *)));
+       if (!idx)
+               return -ENOMEM;
+       inode->chunkindexes = idx;
+
+       fd = open(inode->i_srcpath, O_RDONLY | O_BINARY);
+       if (fd < 0) {
+               ret = -errno;
+               goto err;
+       }
+
+       for (pos = 0; pos < inode->i_size; pos += len) {
+               struct erofs_blobchunk *chunk;
+
+               len = min_t(u64, inode->i_size - pos, chunksize);
+               chunk = erofs_blob_getchunk(fd, len);
+               if (IS_ERR(chunk)) {
+                       ret = PTR_ERR(chunk);
+                       close(fd);
+                       goto err;
+               }
+               *(void **)idx++ = chunk;
+       }
+       inode->datalayout = EROFS_INODE_CHUNK_BASED;
+       close(fd);
+       return 0;
+err:
+       free(inode->chunkindexes);
+       inode->chunkindexes = NULL;
+       return ret;
+}
+
+int erofs_blob_remap(void)
+{
+       struct erofs_buffer_head *bh;
+       ssize_t length;
+       erofs_off_t pos_in, pos_out;
+       int ret;
+
+       fflush(blobfile);
+       length = ftell(blobfile);
+       bh = erofs_balloc(DATA, length, 0, 0);
+       if (IS_ERR(bh))
+               return PTR_ERR(bh);
+
+       erofs_mapbh(bh->block);
+       pos_out = erofs_btell(bh, false);
+       pos_in = 0;
+       remapped_base = erofs_blknr(pos_out);
+       ret = erofs_copy_file_range(fileno(blobfile), &pos_in,
+                                   erofs_devfd, &pos_out, length);
+       bh->op = &erofs_skip_write_bhops;
+       erofs_bdrop(bh, false);
+       return ret < length ? -EIO : 0;
+}
+
+void erofs_blob_exit(void)
+{
+       if (blobfile)
+               fclose(blobfile);
+
+       hashmap_free(&blob_hashmap, 1);
+}
+
+int erofs_blob_init(void)
+{
+       blobfile = tmpfile64();
+       if (!blobfile)
+               return -ENOMEM;
+
+       hashmap_init(&blob_hashmap, erofs_blob_hashmap_cmp, 0);
+       return 0;
+}
diff --git a/lib/inode.c b/lib/inode.c
index 4c40c348aa4b..26ffa4b2bb38 100644
--- a/lib/inode.c
+++ b/lib/inode.c
@@ -24,6 +24,7 @@
 #include "erofs/exclude.h"
 #include "erofs/block_list.h"
 #include "erofs/compress_hints.h"
+#include "erofs/blobchunk.h"
 
 #define S_SHIFT                 12
 static unsigned char erofs_ftype_by_mode[S_IFMT >> S_SHIFT] = {
@@ -387,6 +388,12 @@ int erofs_write_file(struct erofs_inode *inode)
                return 0;
        }
 
+       if (cfg.c_chunkbits) {
+               inode->u.chunkbits = cfg.c_chunkbits;
+               inode->u.chunkformat = EROFS_CHUNK_FORMAT_INDEXES;
+               return erofs_blob_write_chunked_file(inode);
+       }
+
        if (cfg.c_compr_alg_master && erofs_file_is_compressible(inode)) {
                ret = erofs_write_compressed_file(inode);
 
@@ -440,6 +447,10 @@ static bool erofs_bh_flush_write_inode(struct 
erofs_buffer_head *bh)
                        if (is_inode_layout_compression(inode))
                                u.dic.i_u.compressed_blocks =
                                        cpu_to_le32(inode->u.i_blocks);
+                       else if (inode->datalayout ==
+                                       EROFS_INODE_CHUNK_BASED)
+                               u.dic.i_u.c.format =
+                                       cpu_to_le16(inode->u.chunkformat);
                        else
                                u.dic.i_u.raw_blkaddr =
                                        cpu_to_le32(inode->u.i_blkaddr);
@@ -473,6 +484,10 @@ static bool erofs_bh_flush_write_inode(struct 
erofs_buffer_head *bh)
                        if (is_inode_layout_compression(inode))
                                u.die.i_u.compressed_blocks =
                                        cpu_to_le32(inode->u.i_blocks);
+                       else if (inode->datalayout ==
+                                       EROFS_INODE_CHUNK_BASED)
+                               u.die.i_u.c.format =
+                                       cpu_to_le16(inode->u.chunkformat);
                        else
                                u.die.i_u.raw_blkaddr =
                                        cpu_to_le32(inode->u.i_blkaddr);
@@ -505,12 +520,19 @@ static bool erofs_bh_flush_write_inode(struct 
erofs_buffer_head *bh)
        }
 
        if (inode->extent_isize) {
-               /* write compression metadata */
-               off = Z_EROFS_VLE_EXTENT_ALIGN(off);
-               ret = dev_write(inode->compressmeta, off, inode->extent_isize);
-               if (ret)
-                       return false;
-               free(inode->compressmeta);
+               if (inode->datalayout == EROFS_INODE_CHUNK_BASED) {
+                       ret = erofs_blob_write_chunk_indexes(inode, off);
+                       if (ret)
+                               return false;
+               } else {
+                       /* write compression metadata */
+                       off = Z_EROFS_VLE_EXTENT_ALIGN(off);
+                       ret = dev_write(inode->compressmeta, off,
+                                       inode->extent_isize);
+                       if (ret)
+                               return false;
+                       free(inode->compressmeta);
+               }
        }
 
        inode->bh = NULL;
@@ -565,6 +587,8 @@ static int erofs_prepare_inode_buffer(struct erofs_inode 
*inode)
 
        if (is_inode_layout_compression(inode))
                goto noinline;
+       if (inode->datalayout == EROFS_INODE_CHUNK_BASED)
+               goto noinline;
 
        if (cfg.c_noinline_data && S_ISREG(inode->i_mode)) {
                inode->datalayout = EROFS_INODE_FLAT_PLAIN;
diff --git a/lib/io.c b/lib/io.c
index 504a69e4bdc1..03c7e3355089 100644
--- a/lib/io.c
+++ b/lib/io.c
@@ -24,7 +24,7 @@
 #include "erofs/print.h"
 
 static const char *erofs_devname;
-static int erofs_devfd = -1;
+int erofs_devfd = -1;
 static u64 erofs_devsz;
 
 int dev_get_blkdev_size(int fd, u64 *bytes)
diff --git a/man/mkfs.erofs.1 b/man/mkfs.erofs.1
index 1446cb56db30..3c250c118168 100644
--- a/man/mkfs.erofs.1
+++ b/man/mkfs.erofs.1
@@ -83,6 +83,9 @@ Set all file gids to \fIGID\fR.
 .B \-\-all-root
 Make all files owned by root.
 .TP
+.BI "\-\-chunksize " #
+Generate chunk-based files with #-byte chunks.
+.TP
 .B \-\-help
 Display this help and exit.
 .TP
diff --git a/mkfs/main.c b/mkfs/main.c
index addefcefea38..b61205dac91a 100644
--- a/mkfs/main.c
+++ b/mkfs/main.c
@@ -22,6 +22,7 @@
 #include "erofs/exclude.h"
 #include "erofs/block_list.h"
 #include "erofs/compress_hints.h"
+#include "erofs/blobchunk.h"
 
 #ifdef HAVE_LIBUUID
 #include <uuid.h>
@@ -44,6 +45,7 @@ static struct option long_options[] = {
 #endif
        {"max-extent-bytes", required_argument, NULL, 9},
        {"compress-hints", required_argument, NULL, 10},
+       {"chunksize", required_argument, NULL, 11},
 #ifdef WITH_ANDROID
        {"mount-point", required_argument, NULL, 512},
        {"product-out", required_argument, NULL, 513},
@@ -79,6 +81,7 @@ static void usage(void)
 #ifdef HAVE_LIBUUID
              " -UX                   use a given filesystem UUID\n"
 #endif
+             " --chunksize=X         generate chunk-based files with X-byte 
chunks\n"
              " --exclude-path=X      avoid including file X (X = exact literal 
path)\n"
              " --exclude-regex=X     avoid including files that match X (X = 
regular expression)\n"
 #ifdef HAVE_LIBSELINUX
@@ -321,6 +324,26 @@ static int mkfs_parse_options_cfg(int argc, char *argv[])
                        cfg.c_pclusterblks_max = i / EROFS_BLKSIZ;
                        cfg.c_pclusterblks_def = cfg.c_pclusterblks_max;
                        break;
+               case 11:
+                       i = strtol(optarg, &endptr, 0);
+                       if (*endptr != '\0') {
+                               erofs_err("invalid chunksize %s", optarg);
+                               return -EINVAL;
+                       }
+                       cfg.c_chunkbits = ilog2(i);
+                       if ((1 << cfg.c_chunkbits) != i) {
+                               erofs_err("chunksize %s must be a power of two",
+                                         optarg);
+                               return -EINVAL;
+                       }
+                       if (i < EROFS_BLKSIZ) {
+                               erofs_err("chunksize %s must be larger than 
block size",
+                                         optarg);
+                               return -EINVAL;
+                       }
+                       erofs_sb_set_chunked_file();
+                       erofs_warn("EXPERIMENTAL chunked file feature in use. 
Use at your own risk!");
+                       break;
 
                case 1:
                        usage();
@@ -528,6 +551,12 @@ int main(int argc, char **argv)
                return 1;
        }
 
+       if (cfg.c_chunkbits) {
+               err = erofs_blob_init();
+               if (err)
+                       return 1;
+       }
+
        err = lstat64(cfg.c_src_path, &st);
        if (err)
                return 1;
@@ -622,6 +651,13 @@ int main(int argc, char **argv)
        root_nid = erofs_lookupnid(root_inode);
        erofs_iput(root_inode);
 
+       if (cfg.c_chunkbits) {
+               erofs_info("total metadata: %u blocks", erofs_mapbh(NULL));
+               err = erofs_blob_remap();
+               if (err)
+                       goto exit;
+       }
+
        err = erofs_mkfs_update_super_block(sb_bh, root_nid, &nblocks);
        if (err)
                goto exit;
@@ -642,6 +678,8 @@ exit:
        dev_close();
        erofs_cleanup_compress_hints();
        erofs_cleanup_exclude_rules();
+       if (cfg.c_chunkbits)
+               erofs_blob_exit();
        erofs_exit_configure();
 
        if (err) {
-- 
2.24.4

Reply via email to