X-Git-Url: https://code.delx.au/refind/blobdiff_plain/e562783225622cbbcd3c527c6582ae5432eaf185..c5bc1ce13e5c8f0f9e264313726a8d3a28d4b70d:/filesystems/fsw_ext4.c diff --git a/filesystems/fsw_ext4.c b/filesystems/fsw_ext4.c index d5c4d95..7557629 100644 --- a/filesystems/fsw_ext4.c +++ b/filesystems/fsw_ext4.c @@ -72,6 +72,34 @@ struct fsw_fstype_table FSW_FSTYPE_TABLE_NAME(ext4) = { fsw_ext4_readlink, }; + +static __inline int test_root(fsw_u32 a, int b) +{ + fsw_u32 num = b; + + while (a > num) + num *= b; + return num == a; +} + +static int fsw_ext4_group_sparse(fsw_u32 group) +{ + if (group <= 1) + return 1; + if (!(group & 1)) + return 0; + return (test_root(group, 7) || test_root(group, 5) || + test_root(group, 3)); +} + +/* calculate the first block number of the group */ +static __inline fsw_u64 +fsw_ext4_group_first_block_no(struct ext4_super_block *sb, fsw_u32 group_no) +{ + return group_no * (fsw_u64)EXT4_BLOCKS_PER_GROUP(sb) + + sb->s_first_data_block; +} + /** * Mount an ext4 volume. Reads the superblock and constructs the * root directory dnode. @@ -82,7 +110,8 @@ static fsw_status_t fsw_ext4_volume_mount(struct fsw_ext4_volume *vol) fsw_status_t status; void *buffer; fsw_u32 blocksize; - fsw_u32 groupcnt, groupno, gdesc_per_block, gdesc_bno, gdesc_index; + fsw_u32 groupcnt, groupno, gdesc_per_block, gdesc_index, metabg_of_gdesc; + fsw_u64 gdesc_bno; struct ext4_group_desc *gdesc; int i; struct fsw_string s; @@ -111,10 +140,10 @@ static fsw_status_t fsw_ext4_volume_mount(struct fsw_ext4_volume *vol) if (vol->sb->s_rev_level == EXT4_DYNAMIC_REV && (vol->sb->s_feature_incompat & ~(EXT4_FEATURE_INCOMPAT_FILETYPE | EXT4_FEATURE_INCOMPAT_RECOVER | - EXT4_FEATURE_INCOMPAT_EXTENTS))) + EXT4_FEATURE_INCOMPAT_EXTENTS | EXT4_FEATURE_INCOMPAT_FLEX_BG | + EXT4_FEATURE_INCOMPAT_64BIT | EXT4_FEATURE_INCOMPAT_META_BG))) return FSW_UNSUPPORTED; - if (vol->sb->s_rev_level == EXT4_DYNAMIC_REV && (vol->sb->s_feature_incompat & EXT4_FEATURE_INCOMPAT_RECOVER)) { @@ -146,29 +175,55 @@ static fsw_status_t fsw_ext4_volume_mount(struct fsw_ext4_volume *vol) // size of group descriptor depends on feature.... if (!(vol->sb->s_feature_incompat & EXT4_FEATURE_INCOMPAT_64BIT)) { - // Default minimal group descriptor size... + // Default minimal group descriptor size... (this might not be set in old ext2 filesystems, therefor set it!) vol->sb->s_desc_size = EXT4_MIN_DESC_SIZE; } // Calculate group descriptor count the way the kernel does it... groupcnt = (vol->sb->s_blocks_count_lo - vol->sb->s_first_data_block + vol->sb->s_blocks_per_group - 1) / vol->sb->s_blocks_per_group; - // Descriptors in one block... s_desc_size needs to be set! + + // Descriptors in one block... s_desc_size needs to be set! (Usually 128 since normal block + // descriptors are 32 byte and block size is 4096) gdesc_per_block = EXT4_DESC_PER_BLOCK(vol->sb); // Read the group descriptors to get inode table offsets - status = fsw_alloc(sizeof(fsw_u32) * groupcnt, &vol->inotab_bno); + status = fsw_alloc(sizeof(fsw_u64) * groupcnt, &vol->inotab_bno); if (status) return status; + + // Loop through all block group descriptors in order to get inode table locations for (groupno = 0; groupno < groupcnt; groupno++) { - // get the block group descriptor - gdesc_bno = (vol->sb->s_first_data_block + 1) + groupno / gdesc_per_block; + + // Calculate the block number which contains the block group descriptor we look for + if(vol->sb->s_feature_incompat & EXT4_FEATURE_INCOMPAT_META_BG && groupno >= vol->sb->s_first_meta_bg) + { + // If option meta_bg is set, the block group descriptor is in meta block group... + metabg_of_gdesc = (fsw_u32)(groupno / gdesc_per_block) * gdesc_per_block; + gdesc_bno = fsw_ext4_group_first_block_no(vol->sb, metabg_of_gdesc); + // We need to know if the block group in questition has a super block, if yes, the + // block group descriptors are in the next block number + if(!(vol->sb->s_feature_ro_compat & EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER) || fsw_ext4_group_sparse(metabg_of_gdesc)) + gdesc_bno += 1; + } + else + { + // All group descriptors follow the super block (+1) + gdesc_bno = (vol->sb->s_first_data_block + 1) + groupno / gdesc_per_block; + } gdesc_index = groupno % gdesc_per_block; + + // Get block if necessary... status = fsw_block_get(vol, gdesc_bno, 1, (void **)&buffer); if (status) return status; - gdesc = (struct ext4_group_desc *)(buffer + gdesc_index * vol->sb->s_desc_size); + + // Get group descriptor table and block number of inode table... + gdesc = (struct ext4_group_desc *)((char *)buffer + gdesc_index * vol->sb->s_desc_size); vol->inotab_bno[groupno] = gdesc->bg_inode_table_lo; + if (vol->sb->s_desc_size >= EXT4_MIN_DESC_SIZE_64BIT) + vol->inotab_bno[groupno] |= (fsw_u64)gdesc->bg_inode_table_hi << 32; + fsw_block_release(vol, gdesc_bno, buffer); } @@ -202,8 +257,18 @@ static void fsw_ext4_volume_free(struct fsw_ext4_volume *vol) static fsw_status_t fsw_ext4_volume_stat(struct fsw_ext4_volume *vol, struct fsw_volume_stat *sb) { - sb->total_bytes = (fsw_u64)vol->sb->s_blocks_count_lo * vol->g.log_blocksize; - sb->free_bytes = (fsw_u64)vol->sb->s_free_blocks_count_lo * vol->g.log_blocksize; + fsw_u64 count; + + count = vol->sb->s_blocks_count_lo; + if (vol->sb->s_desc_size >= EXT4_MIN_DESC_SIZE_64BIT) + count |= (fsw_u64)vol->sb->s_blocks_count_hi << 32; + sb->total_bytes = count * vol->g.log_blocksize; + + count = vol->sb->s_free_blocks_count_lo; + if (vol->sb->s_desc_size >= EXT4_MIN_DESC_SIZE_64BIT) + count |= (fsw_u64)vol->sb->s_free_blocks_count_hi << 32; + sb->free_bytes = count * vol->g.log_blocksize; + return FSW_SUCCESS; } @@ -218,7 +283,8 @@ static fsw_status_t fsw_ext4_volume_stat(struct fsw_ext4_volume *vol, struct fsw static fsw_status_t fsw_ext4_dnode_fill(struct fsw_ext4_volume *vol, struct fsw_ext4_dnode *dno) { fsw_status_t status; - fsw_u32 groupno, ino_in_group, ino_bno, ino_index; + fsw_u32 groupno, ino_in_group, ino_index; + fsw_u64 ino_bno; fsw_u8 *buffer; if (dno->raw) @@ -226,8 +292,8 @@ static fsw_status_t fsw_ext4_dnode_fill(struct fsw_ext4_volume *vol, struct fsw_ // read the inode block - groupno = (dno->g.dnode_id - 1) / vol->sb->s_inodes_per_group; - ino_in_group = (dno->g.dnode_id - 1) % vol->sb->s_inodes_per_group; + groupno = (fsw_u32) (dno->g.dnode_id - 1) / vol->sb->s_inodes_per_group; + ino_in_group = (fsw_u32) (dno->g.dnode_id - 1) % vol->sb->s_inodes_per_group; ino_bno = vol->inotab_bno[groupno] + ino_in_group / (vol->g.phys_blocksize / vol->inode_size); ino_index = ino_in_group % (vol->g.phys_blocksize / vol->inode_size); @@ -282,10 +348,10 @@ static fsw_status_t fsw_ext4_dnode_stat(struct fsw_ext4_volume *vol, struct fsw_ struct fsw_dnode_stat *sb) { sb->used_bytes = dno->raw->i_blocks_lo * EXT4_BLOCK_SIZE(vol->sb); // very, very strange... - sb->store_time_posix(sb, FSW_DNODE_STAT_CTIME, dno->raw->i_ctime); - sb->store_time_posix(sb, FSW_DNODE_STAT_ATIME, dno->raw->i_atime); - sb->store_time_posix(sb, FSW_DNODE_STAT_MTIME, dno->raw->i_mtime); - sb->store_attr_posix(sb, dno->raw->i_mode); + fsw_store_time_posix(sb, FSW_DNODE_STAT_CTIME, dno->raw->i_ctime); + fsw_store_time_posix(sb, FSW_DNODE_STAT_ATIME, dno->raw->i_atime); + fsw_store_time_posix(sb, FSW_DNODE_STAT_MTIME, dno->raw->i_mtime); + fsw_store_attr_posix(sb, dno->raw->i_mode); return FSW_SUCCESS; } @@ -308,7 +374,7 @@ static fsw_status_t fsw_ext4_get_extent(struct fsw_ext4_volume *vol, struct fsw_ // Preconditions: The caller has checked that the requested logical block // is within the file's size. The dnode has complete information, i.e. // fsw_ext4_dnode_read_info was called successfully on it. - FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_extent: inode flags %x\n"), dno->raw->i_flags)); + FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_extent: inode %d, block %d\n"), dno->g.dnode_id, extent->log_start)); extent->type = FSW_EXTENT_TYPE_PHYSBLOCK; extent->log_count = 1; @@ -331,52 +397,71 @@ static fsw_status_t fsw_ext4_get_extent(struct fsw_ext4_volume *vol, struct fsw_ static fsw_status_t fsw_ext4_get_by_extent(struct fsw_ext4_volume *vol, struct fsw_ext4_dnode *dno, struct fsw_extent *extent) { - fsw_status_t status; - fsw_u32 bno, release_bno, buf_bcnt, buf_offset, file_bcnt; - int i; - fsw_u8 *buffer; + fsw_status_t status; + fsw_u32 bno, buf_offset; + int ext_cnt; + void *buffer; + struct ext4_extent_header *ext4_extent_header; + struct ext4_extent_idx *ext4_extent_idx; struct ext4_extent *ext4_extent; // Logical block requested by core... bno = extent->log_start; - // First buffer is the i_block field from inde... - buffer = dno->raw->i_block; - buf_bcnt = EXT4_NDIR_BLOCKS; + // First buffer is the i_block field from inode... + buffer = (void *)dno->raw->i_block; buf_offset = 0; - - ext4_extent_header = (struct ext4_extent_header *)buffer + buf_offset; - buf_offset += sizeof(struct ext4_extent_header); - FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_by_extent: extent header magic %x\n"), - ext4_extent_header->eh_magic)); - if(ext4_extent_header->eh_magic != EXT4_EXT_MAGIC) - return FSW_VOLUME_CORRUPTED; - - if(ext4_extent_header->eh_depth == 0) - { - // Leaf node, the header follows actual extents - FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_by_extent: leaf extent with %d extents\n"), + while(1) { + ext4_extent_header = (struct ext4_extent_header *)((char *)buffer + buf_offset); + buf_offset += sizeof(struct ext4_extent_header); + FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_by_extent: extent header with %d entries\n"), ext4_extent_header->eh_entries)); - for(i = 0;i < ext4_extent_header->eh_entries;i++) + if(ext4_extent_header->eh_magic != EXT4_EXT_MAGIC) + return FSW_VOLUME_CORRUPTED; + + for(ext_cnt = 0;ext_cnt < ext4_extent_header->eh_entries;ext_cnt++) { - ext4_extent = (struct ext4_extent *)(buffer + buf_offset); - FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_by_extent: extent with %d len\n"), ext4_extent->ee_len)); - FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_by_extent: extent with %d start_hi\n"), ext4_extent->ee_start_hi)); - FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_by_extent: extent with %d start_lo\n"), ext4_extent->ee_start_lo)); - if(bno >= ext4_extent->ee_block && bno < ext4_extent->ee_block + ext4_extent->ee_len) + if(ext4_extent_header->eh_depth == 0) { - extent->phys_start = ext4_extent->ee_start_lo; + // Leaf node, the header follows actual extents + ext4_extent = (struct ext4_extent *)((char *)buffer + buf_offset); + buf_offset += sizeof(struct ext4_extent); + FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_by_extent: extent node cover %d...\n"), ext4_extent->ee_block)); + + // Is the requested block in this extent? + if(bno >= ext4_extent->ee_block && bno < ext4_extent->ee_block + ext4_extent->ee_len) + { + extent->phys_start = ((fsw_u64)ext4_extent->ee_start_hi << 32) | ext4_extent->ee_start_lo; + extent->phys_start += (bno - ext4_extent->ee_block); + extent->log_count = ext4_extent->ee_len - (bno - ext4_extent->ee_block); + return FSW_SUCCESS; + } + } + else + { + FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_by_extent: index extents, depth %d\n"), + ext4_extent_header->eh_depth)); + ext4_extent_idx = (struct ext4_extent_idx *)((char *)buffer + buf_offset); + buf_offset += sizeof(struct ext4_extent_idx); + + FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_by_extent: index node covers block %d...\n"), + ext4_extent_idx->ei_block)); + if(bno >= ext4_extent_idx->ei_block) + { + // Follow extent tree... + fsw_u64 phys_bno = ((fsw_u64)ext4_extent_idx->ei_leaf_hi << 32) | ext4_extent_idx->ei_leaf_lo; + status = fsw_block_get(vol, phys_bno, 1, (void **)&buffer); + if (status) + return status; + buf_offset = 0; + break; + } } - buf_offset += sizeof(struct ext4_extent); } } - else - { - return FSW_NOT_FOUND; - } - - return FSW_SUCCESS; + + return FSW_NOT_FOUND; } /**