Description: Fix ext4 block group handling Backported distantly from grub2, particularly: https://git.savannah.gnu.org/cgit/grub.git/commit/?id=e20aa39ea4298011ba716087713cff26c6c52006 Author: Colin Watson Bug-Debian: https://bugs.debian.org/511121 Last-Update: 2018-10-25 Index: b/stage2/fsys_ext2fs.c =================================================================== --- a/stage2/fsys_ext2fs.c +++ b/stage2/fsys_ext2fs.c @@ -41,6 +41,8 @@ typedef unsigned short __u16; typedef __signed__ int __s32; typedef unsigned int __u32; +typedef __signed__ long long __s64; +typedef unsigned long long __u64; /* * Constants relative to the data blocks, from ext2_fs.h @@ -51,6 +53,13 @@ #define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1) #define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1) +/* Superblock filesystem feature flags (RO compatible) */ +#define EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001 + +/* Superblock filesystem feature flags (back-incompatible) */ +#define EXT2_FEATURE_INCOMPAT_META_BG 0x0010 +#define EXT4_FEATURE_INCOMPAT_64BIT 0x0080 + /* Inode flags */ #define EXT4_EXTENTS_FL 0x00080000 /* Inode uses extents */ @@ -122,7 +131,7 @@ __u32 s_hash_seed[4]; /* HTREE hash seed */ __u8 s_def_hash_version; /* Default hash version to use */ __u8 s_jnl_backup_type; /* Default type of journal backup */ - __u16 s_reserved_word_pad; + __u16 s_desc_size; /* Group desc. size: INCOMPAT_64BIT */ __u32 s_default_mount_opts; __u32 s_first_meta_bg; /* First metablock group */ __u32 s_mkfs_time; /* When the filesystem was created */ @@ -284,13 +293,20 @@ #define PATH_MAX 1024 /* include/linux/limits.h */ #define MAX_LINK_COUNT 5 /* number of symbolic links to follow */ +/* Information about a "mounted" ext2 filesystem. */ +struct ext2_data + { + int log_group_desc_size; + }; + /* made up, these are pointers into FSYS_BUF */ /* read once, always stays there: */ #define SUPERBLOCK \ ((struct ext2_super_block *)(FSYS_BUF)) +#define FS_DATA \ + ((struct ext2_data *)((int)SUPERBLOCK + sizeof(struct ext2_super_block))) #define GROUP_DESC \ - ((struct ext2_group_desc *) \ - ((int)SUPERBLOCK + sizeof(struct ext2_super_block))) + ((int)((int)FS_DATA + sizeof(struct ext2_data))) #define INODE \ ((struct ext2_inode *)((int)GROUP_DESC + EXT2_BLOCK_SIZE(SUPERBLOCK))) #define DATABLOCK1 \ @@ -357,6 +373,20 @@ || SUPERBLOCK->s_magic != EXT2_SUPER_MAGIC) retval = 0; + if (SUPERBLOCK->s_rev_level != EXT2_GOOD_OLD_REV + && (SUPERBLOCK->s_feature_incompat & EXT4_FEATURE_INCOMPAT_64BIT) + && SUPERBLOCK->s_desc_size != 0 + && (SUPERBLOCK->s_desc_size & (SUPERBLOCK->s_desc_size - 1)) == 0 + && (SUPERBLOCK->s_desc_size & 0x1fe0)) + { + __u16 b = SUPERBLOCK->s_desc_size; + for (FS_DATA->log_group_desc_size = 0; + b != (1 << FS_DATA->log_group_desc_size); + FS_DATA->log_group_desc_size++); + } + else + FS_DATA->log_group_desc_size = 5; + return retval; } @@ -623,6 +653,32 @@ return INODE->i_blocks == ea_blocks; } +static int +is_power_of (__u64 a, __u64 b) +{ + __u64 c; + /* Prevent overflow assuming b < 8. */ + if (a >= (1LL << 60)) + return 0; + for (c = 1; c <= a; c *= b); + return (c == a); +} + +static int +ext2fs_group_has_super_block (__u64 group) +{ + if (!(SUPERBLOCK->s_feature_ro_compat & EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER)) + return 1; + /* Algorithm looked up in Linux source. */ + if (group <= 1) + return 1; + /* Even number is never a power of odd number. */ + if (!(group & 1)) + return 0; + return (is_power_of (group, 7) || is_power_of (group, 5) || + is_power_of (group, 3)); +} + /* preconditions: ext2fs_mount already executed, therefore supblk in buffer * known as SUPERBLOCK * returns: 0 if error, nonzero iff we were able to find the file successfully @@ -635,8 +691,9 @@ { int current_ino = EXT2_ROOT_INO; /* start at the root */ int updir_ino = current_ino; /* the parent of the current directory */ - int group_id; /* which group the inode is in */ - int group_desc; /* fs pointer to that group */ + __u64 full_offset; + __u64 group_id; /* which group the inode is in */ + __u64 group_desc; /* fs pointer to that group */ int desc; /* index within that group */ int ino_blk; /* fs pointer of the inode's information */ int str_chk = 0; /* used to hold the results of a string compare */ @@ -675,23 +732,37 @@ /* look up an inode */ group_id = (current_ino - 1) / (SUPERBLOCK->s_inodes_per_group); - group_desc = group_id >> log2 (EXT2_DESC_PER_BLOCK (SUPERBLOCK)); - desc = group_id & (EXT2_DESC_PER_BLOCK (SUPERBLOCK) - 1); + full_offset = group_id << FS_DATA->log_group_desc_size; + group_desc = full_offset >> EXT2_BLOCK_SIZE_BITS (SUPERBLOCK); + desc = full_offset & (EXT2_BLOCK_SIZE (SUPERBLOCK) - 1); + if ((SUPERBLOCK->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG) + && group_desc >= SUPERBLOCK->s_first_meta_bg) + { + __u64 first_block_group; + /* Find the first block group for which a descriptor is stored in + the given block. */ + first_block_group = (group_desc << (EXT2_BLOCK_SIZE_BITS (SUPERBLOCK) + - FS_DATA->log_group_desc_size)); + group_desc = first_block_group * SUPERBLOCK->s_blocks_per_group; + if (ext2fs_group_has_super_block (first_block_group)) + group_desc++; + } + else + /* Superblock. */ + group_desc++; #ifdef E2DEBUG printf ("ipg=%d, dpb=%d\n", SUPERBLOCK->s_inodes_per_group, EXT2_DESC_PER_BLOCK (SUPERBLOCK)); printf ("group_id=%d group_desc=%d desc=%d\n", group_id, group_desc, desc); #endif /* E2DEBUG */ - if (!ext2_rdfsb ( - (WHICH_SUPER + group_desc + SUPERBLOCK->s_first_data_block), - (int) GROUP_DESC)) + if (!ext2_rdfsb (SUPERBLOCK->s_first_data_block + group_desc, GROUP_DESC)) { return 0; } - gdp = GROUP_DESC; + gdp = (struct ext2_group_desc *)(GROUP_DESC + desc); inodes_per_block = EXT2_BLOCK_SIZE (SUPERBLOCK) / EXT2_INODE_SIZE(SUPERBLOCK); inode_offset = ((current_ino - 1) % (SUPERBLOCK->s_inodes_per_group)); - ino_blk = gdp[desc].bg_inode_table + (inode_offset / inodes_per_block); + ino_blk = gdp->bg_inode_table + (inode_offset / inodes_per_block); #ifdef E2DEBUG printf ("inode table fsblock=%d\n", ino_blk); #endif /* E2DEBUG */