Keep in sync with the latest kernel
dfeab2e95a75 ("erofs: add multiple device support")

Signed-off-by: Gao Xiang <[email protected]>
---
 dump/main.c              |  2 +-
 fuse/main.c              | 10 ++++++
 include/erofs/defs.h     | 32 +++++++++++++++++++
 include/erofs/internal.h | 19 +++++++++++-
 include/erofs_fs.h       | 22 +++++++++++--
 lib/data.c               | 67 +++++++++++++++++++++++++++++++++-------
 lib/super.c              | 43 ++++++++++++++++++++++++--
 7 files changed, 177 insertions(+), 18 deletions(-)

diff --git a/dump/main.c b/dump/main.c
index 401e6841aefb..912d98f70525 100644
--- a/dump/main.c
+++ b/dump/main.c
@@ -609,7 +609,7 @@ static void erofsdump_show_superblock(void)
        fprintf(stdout, "Filesystem magic number:                      
0x%04X\n",
                        EROFS_SUPER_MAGIC_V1);
        fprintf(stdout, "Filesystem blocks:                            %llu\n",
-                       sbi.blocks | 0ULL);
+                       sbi.total_blocks | 0ULL);
        fprintf(stdout, "Filesystem inode metadata start block:        %u\n",
                        sbi.meta_blkaddr);
        fprintf(stdout, "Filesystem shared xattr metadata start block: %u\n",
diff --git a/fuse/main.c b/fuse/main.c
index 8137421b1dea..21939cd15923 100644
--- a/fuse/main.c
+++ b/fuse/main.c
@@ -113,6 +113,7 @@ static struct options {
 static const struct fuse_opt option_spec[] = {
        OPTION("--dbglevel=%u", debug_lvl),
        OPTION("--help", show_help),
+       FUSE_OPT_KEY("--device=", 1),
        FUSE_OPT_END
 };
 
@@ -148,7 +149,15 @@ static void erofsfuse_dumpcfg(void)
 static int optional_opt_func(void *data, const char *arg, int key,
                             struct fuse_args *outargs)
 {
+       int ret;
+
        switch (key) {
+       case 1:
+               ret = blob_open_ro(arg);
+               if (ret)
+                       return -1;
+               ++sbi.extra_devices;
+               return 0;
        case FUSE_OPT_KEY_NONOPT:
                if (fusecfg.mountpoint)
                        return -1; /* Too many args */
@@ -237,6 +246,7 @@ int main(int argc, char *argv[])
 
        ret = fuse_main(args.argc, args.argv, &erofs_ops, NULL);
 err_dev_close:
+       blob_closeall();
        dev_close();
 err_fuse_free_args:
        fuse_opt_free_args(&args);
diff --git a/include/erofs/defs.h b/include/erofs/defs.h
index 96bbb6574ff3..5283f5d3c6f3 100644
--- a/include/erofs/defs.h
+++ b/include/erofs/defs.h
@@ -252,6 +252,38 @@ static inline u32 get_unaligned_le32(const u8 *p)
        (n) & (1ULL <<  1) ?  1 : 0     \
 )
 
+static inline unsigned int fls_long(unsigned long x)
+{
+       return x ? sizeof(x) * 8 - __builtin_clz(x) : 0;
+}
+
+/**
+ * __roundup_pow_of_two() - round up to nearest power of two
+ * @n: value to round up
+ */
+static inline __attribute__((const))
+unsigned long __roundup_pow_of_two(unsigned long n)
+{
+       return 1UL << fls_long(n - 1);
+}
+
+/**
+ * roundup_pow_of_two - round the given value up to nearest power of two
+ * @n: parameter
+ *
+ * round the given value up to the nearest power of two
+ * - the result is undefined when n == 0
+ * - this can be used to initialise global variables from constant data
+ */
+#define roundup_pow_of_two(n)                  \
+(                                              \
+       __builtin_constant_p(n) ? (             \
+               ((n) == 1) ? 1 :                \
+               (1UL << (ilog2((n) - 1) + 1))   \
+                                  ) :          \
+       __roundup_pow_of_two(n)                 \
+ )
+
 #ifndef __always_inline
 #define __always_inline        inline
 #endif
diff --git a/include/erofs/internal.h b/include/erofs/internal.h
index f84e6b4f125d..974c069baa4f 100644
--- a/include/erofs/internal.h
+++ b/include/erofs/internal.h
@@ -60,8 +60,16 @@ typedef u32 erofs_blk_t;
 
 struct erofs_buffer_head;
 
+struct erofs_device_info {
+       u32 blocks;
+       u32 mapped_blkaddr;
+};
+
 struct erofs_sb_info {
-       u64 blocks;
+       struct erofs_device_info *devs;
+
+       u64 total_blocks;
+       u64 primarydevice_blocks;
 
        erofs_blk_t meta_blkaddr;
        erofs_blk_t xattr_blkaddr;
@@ -84,6 +92,8 @@ struct erofs_sb_info {
        u16 lz4_max_distance;
 
        u32 checksum;
+       u16 extra_devices;
+       u16 device_id_mask;
 };
 
 /* global sbi */
@@ -112,6 +122,7 @@ EROFS_FEATURE_FUNCS(lz4_0padding, incompat, 
INCOMPAT_LZ4_0PADDING)
 EROFS_FEATURE_FUNCS(compr_cfgs, incompat, INCOMPAT_COMPR_CFGS)
 EROFS_FEATURE_FUNCS(big_pcluster, incompat, INCOMPAT_BIG_PCLUSTER)
 EROFS_FEATURE_FUNCS(chunked_file, incompat, INCOMPAT_CHUNKED_FILE)
+EROFS_FEATURE_FUNCS(device_table, incompat, INCOMPAT_DEVICE_TABLE)
 EROFS_FEATURE_FUNCS(sb_chksum, compat, COMPAT_SB_CHKSUM)
 
 #define EROFS_I_EA_INITED      (1 << 0)
@@ -257,6 +268,7 @@ struct erofs_map_blocks {
        erofs_off_t m_pa, m_la;
        u64 m_plen, m_llen;
 
+       unsigned short m_deviceid;
        unsigned int m_flags;
        erofs_blk_t index;
 };
@@ -267,6 +279,11 @@ struct erofs_map_blocks {
  */
 #define EROFS_GET_BLOCKS_FIEMAP        0x0002
 
+struct erofs_map_dev {
+       erofs_off_t m_pa;
+       unsigned int m_deviceid;
+};
+
 /* super.c */
 int erofs_read_superblock(void);
 
diff --git a/include/erofs_fs.h b/include/erofs_fs.h
index 4291970753a8..9a918775750c 100644
--- a/include/erofs_fs.h
+++ b/include/erofs_fs.h
@@ -22,14 +22,27 @@
 #define EROFS_FEATURE_INCOMPAT_COMPR_CFGS      0x00000002
 #define EROFS_FEATURE_INCOMPAT_BIG_PCLUSTER    0x00000002
 #define EROFS_FEATURE_INCOMPAT_CHUNKED_FILE    0x00000004
+#define EROFS_FEATURE_INCOMPAT_DEVICE_TABLE    0x00000008
 #define EROFS_ALL_FEATURE_INCOMPAT             \
        (EROFS_FEATURE_INCOMPAT_LZ4_0PADDING | \
         EROFS_FEATURE_INCOMPAT_COMPR_CFGS | \
         EROFS_FEATURE_INCOMPAT_BIG_PCLUSTER | \
-        EROFS_FEATURE_INCOMPAT_CHUNKED_FILE)
+        EROFS_FEATURE_INCOMPAT_CHUNKED_FILE | \
+        EROFS_FEATURE_INCOMPAT_DEVICE_TABLE)
 
 #define EROFS_SB_EXTSLOT_SIZE  16
 
+struct erofs_deviceslot {
+       union {
+               u8 uuid[16];            /* used for device manager later */
+               u8 userdata[64];        /* digest(sha256), etc. */
+       } u;
+       __le32 blocks;                  /* total fs blocks of this device */
+       __le32 mapped_blkaddr;          /* map starting at mapped_blkaddr */
+       u8 reserved[56];
+};
+#define EROFS_DEVT_SLOT_SIZE   sizeof(struct erofs_deviceslot)
+
 /* erofs on-disk super block (currently 128 bytes) */
 struct erofs_super_block {
        __le32 magic;           /* file system magic number */
@@ -55,7 +68,9 @@ struct erofs_super_block {
                /* customized sliding window size instead of 64k by default */
                __le16 lz4_max_distance;
        } __packed u1;
-       __u8 reserved2[42];
+       __le16 extra_devices;   /* # of devices besides the primary device */
+       __le16 devt_slotoff;    /* startoff = devt_slotoff * devt_slotsize */
+       __u8 reserved2[38];
 };
 
 /*
@@ -239,7 +254,7 @@ static inline unsigned int erofs_xattr_entry_size(struct 
erofs_xattr_entry *e)
 /* 8-byte inode chunk indexes */
 struct erofs_inode_chunk_index {
        __le16 advise;          /* always 0, don't care for now */
-       __le16 device_id;       /* back-end storage id, always 0 for now */
+       __le16 device_id;       /* back-end storage id (with bits masked) */
        __le32 blkaddr;         /* start block address of this inode chunk */
 };
 
@@ -404,6 +419,7 @@ static inline void 
erofs_check_ondisk_layout_definitions(void)
        /* keep in sync between 2 index structures for better extendibility */
        BUILD_BUG_ON(sizeof(struct erofs_inode_chunk_index) !=
                     sizeof(struct z_erofs_vle_decompressed_index));
+       BUILD_BUG_ON(sizeof(struct erofs_deviceslot) != 128);
 
        BUILD_BUG_ON(BIT(Z_EROFS_VLE_DI_CLUSTER_TYPE_BITS) <
                     Z_EROFS_VLE_CLUSTER_TYPE_MAX - 1);
diff --git a/lib/data.c b/lib/data.c
index b83cbff3e731..c90b66fca628 100644
--- a/lib/data.c
+++ b/lib/data.c
@@ -72,6 +72,7 @@ int erofs_map_blocks(struct erofs_inode *inode,
        erofs_off_t pos;
        int err = 0;
 
+       map->m_deviceid = 0;
        if (map->m_la >= inode->i_size) {
                /* leave out-of-bound access unmapped */
                map->m_flags = 0;
@@ -118,14 +119,8 @@ int erofs_map_blocks(struct erofs_inode *inode,
                map->m_flags = 0;
                break;
        default:
-               /* only one device is supported for now */
-               if (idx->device_id) {
-                       erofs_err("invalid device id %u @ %" PRIu64 " for nid 
%llu",
-                                 le16_to_cpu(idx->device_id),
-                                 chunknr, vi->nid | 0ULL);
-                       err = -EFSCORRUPTED;
-                       goto out;
-               }
+               map->m_deviceid = le16_to_cpu(idx->device_id) &
+                       sbi.device_id_mask;
                map->m_pa = blknr_to_addr(le32_to_cpu(idx->blkaddr));
                map->m_flags = EROFS_MAP_MAPPED;
                break;
@@ -135,12 +130,42 @@ out:
        return err;
 }
 
+int erofs_map_dev(struct erofs_sb_info *sbi, struct erofs_map_dev *map)
+{
+       struct erofs_device_info *dif;
+       int id;
+
+       if (map->m_deviceid) {
+               if (sbi->extra_devices < map->m_deviceid)
+                       return -ENODEV;
+       } else if (sbi->extra_devices) {
+               for (id = 0; id < sbi->extra_devices; ++id) {
+                       erofs_off_t startoff, length;
+
+                       dif = sbi->devs + id;
+
+                       if (!dif->mapped_blkaddr)
+                               continue;
+                       startoff = blknr_to_addr(dif->mapped_blkaddr);
+                       length = blknr_to_addr(dif->blocks);
+
+                       if (map->m_pa >= startoff &&
+                           map->m_pa < startoff + length) {
+                               map->m_pa -= startoff;
+                               break;
+                       }
+               }
+       }
+       return 0;
+}
+
 static int erofs_read_raw_data(struct erofs_inode *inode, char *buffer,
                               erofs_off_t size, erofs_off_t offset)
 {
        struct erofs_map_blocks map = {
                .index = UINT_MAX,
        };
+       struct erofs_map_dev mdev;
        int ret;
        erofs_off_t ptr = offset;
 
@@ -155,6 +180,14 @@ static int erofs_read_raw_data(struct erofs_inode *inode, 
char *buffer,
 
                DBG_BUGON(map.m_plen != map.m_llen);
 
+               mdev = (struct erofs_map_dev) {
+                       .m_deviceid = map.m_deviceid,
+                       .m_pa = map.m_pa,
+               };
+               ret = erofs_map_dev(&sbi, &mdev);
+               if (ret)
+                       return ret;
+
                /* trim extent */
                eend = min(offset + size, map.m_la + map.m_llen);
                DBG_BUGON(ptr < map.m_la);
@@ -172,11 +205,12 @@ static int erofs_read_raw_data(struct erofs_inode *inode, 
char *buffer,
                }
 
                if (ptr > map.m_la) {
-                       map.m_pa += ptr - map.m_la;
+                       mdev.m_pa += ptr - map.m_la;
                        map.m_la = ptr;
                }
 
-               ret = dev_read(0, estart, map.m_pa, eend - map.m_la);
+               ret = dev_read(mdev.m_deviceid, estart, mdev.m_pa,
+                              eend - map.m_la);
                if (ret < 0)
                        return -EIO;
                ptr = eend;
@@ -191,6 +225,7 @@ static int z_erofs_read_data(struct erofs_inode *inode, 
char *buffer,
        struct erofs_map_blocks map = {
                .index = UINT_MAX,
        };
+       struct erofs_map_dev mdev;
        bool partial;
        unsigned int algorithmformat, bufsize;
        char *raw = NULL;
@@ -205,6 +240,16 @@ static int z_erofs_read_data(struct erofs_inode *inode, 
char *buffer,
                if (ret)
                        break;
 
+               /* no device id here, thus it will always succeed */
+               mdev = (struct erofs_map_dev) {
+                       .m_pa = map.m_pa,
+               };
+               ret = erofs_map_dev(&sbi, &mdev);
+               if (ret) {
+                       DBG_BUGON(1);
+                       break;
+               }
+
                /*
                 * trim to the needed size if the returned extent is quite
                 * larger than requested, and set up partial flag as well.
@@ -240,7 +285,7 @@ static int z_erofs_read_data(struct erofs_inode *inode, 
char *buffer,
                                break;
                        }
                }
-               ret = dev_read(0, raw, map.m_pa, map.m_plen);
+               ret = dev_read(mdev.m_deviceid, raw, mdev.m_pa, map.m_plen);
                if (ret < 0)
                        break;
 
diff --git a/lib/super.c b/lib/super.c
index bf4d4318a321..3ccc551f18cf 100644
--- a/lib/super.c
+++ b/lib/super.c
@@ -23,6 +23,45 @@ static bool check_layout_compatibility(struct erofs_sb_info 
*sbi,
        return true;
 }
 
+static int erofs_init_devices(struct erofs_sb_info *sbi,
+                             struct erofs_super_block *dsb)
+{
+       unsigned int ondisk_extradevs, i;
+       erofs_off_t pos;
+
+       sbi->total_blocks = sbi->primarydevice_blocks;
+
+       if (!erofs_sb_has_device_table())
+               ondisk_extradevs = 0;
+       else
+               ondisk_extradevs = le16_to_cpu(dsb->extra_devices);
+
+       if (ondisk_extradevs != sbi->extra_devices) {
+               erofs_err("extra devices don't match (ondisk %u, given %u)",
+                         ondisk_extradevs, sbi->extra_devices);
+               return -EINVAL;
+       }
+       if (!ondisk_extradevs)
+               return 0;
+
+       sbi->device_id_mask = roundup_pow_of_two(ondisk_extradevs + 1) - 1;
+       sbi->devs = calloc(ondisk_extradevs, sizeof(*sbi->devs));
+       pos = le16_to_cpu(dsb->devt_slotoff) * EROFS_DEVT_SLOT_SIZE;
+       for (i = 0; i < ondisk_extradevs; ++i) {
+               struct erofs_deviceslot dis;
+               int ret;
+
+               ret = dev_read(0, &dis, pos, sizeof(dis));
+               if (ret < 0)
+                       return ret;
+
+               sbi->devs[i].mapped_blkaddr = dis.mapped_blkaddr;
+               sbi->total_blocks += dis.blocks;
+               pos += EROFS_DEVT_SLOT_SIZE;
+       }
+       return 0;
+}
+
 int erofs_read_superblock(void)
 {
        char data[EROFS_BLKSIZ];
@@ -56,7 +95,7 @@ int erofs_read_superblock(void)
        if (!check_layout_compatibility(&sbi, dsb))
                return ret;
 
-       sbi.blocks = le32_to_cpu(dsb->blocks);
+       sbi.primarydevice_blocks = le32_to_cpu(dsb->blocks);
        sbi.meta_blkaddr = le32_to_cpu(dsb->meta_blkaddr);
        sbi.xattr_blkaddr = le32_to_cpu(dsb->xattr_blkaddr);
        sbi.islotbits = EROFS_ISLOTBITS;
@@ -68,5 +107,5 @@ int erofs_read_superblock(void)
        sbi.build_time_nsec = le32_to_cpu(dsb->build_time_nsec);
 
        memcpy(&sbi.uuid, dsb->uuid, sizeof(dsb->uuid));
-       return 0;
+       return erofs_init_devices(&sbi, dsb);
 }
-- 
2.24.4

Reply via email to