#pragma author endes #pragma description ext4 volume layout parser (until inodes) // Decodes the ext4 superblock, group descriptors and inodes. // Does not decode the directory entries, inode data, jornal or superblock backups. // Heavily based on the linux kernel documentation: // https://www.kernel.org/doc/html/latest/filesystems/ext4/ #pragma endian little #pragma magic [53 EF] @ 0x438 #pragma pattern_limit 0x90000 import type.time; import type.size; import type.magic; import std.core; import std.mem; import std.math; enum ext4_super_state : u16 { Cleanlyumounted = 0x01, Errorsdetected = 0x02, Orphansbeingrecovered = 0x03 }; enum ext4_super_errors : u16 { Continue = 0x01, RemountReadOnly = 0x02, Panic = 0x03 }; enum ext4_super_creator : u32 { Linux = 0x00, Hurd = 0x01, Masix = 0x02, FreeBSD = 0x03, Lites = 0x04 }; enum ext4_super_revision : u32 { ORIGINAL = 0x00, V2_DYNAMIC_REV = 0x01 }; bitfield ext4_super_compat { COMPAT_DIR_PREALLOC : 1 [[comment("Directory preallocation.")]]; COMPAT_IMAGIC_INODES : 1 [[comment("“imagic inodes”. Not clear from the code what this does.")]]; COMPAT_HAS_JOURNAL : 1 [[comment("Has a journal.")]]; COMPAT_EXT_ATTR : 1 [[comment("Supports extended attributes.")]]; COMPAT_RESIZE_INODE : 1 [[comment("Has reserved GDT blocks for filesystem expansion.")]]; COMPAT_DIR_INDEX : 1 [[comment("Has directory indices.")]]; COMPAT_LAZY_BG : 1 [[comment("“Lazy BG”. Not in Linux kernel, seems to have been for uninitialized block groups?.")]]; COMPAT_EXCLUDE_INODE : 1 [[comment("“Exclude inode”. Not used.")]]; COMPAT_EXCLUDE_BITMAP : 1 [[comment("“Exclude bitmap”. Seems to be used to indicate the presence of snapshot-related exclude bitmaps? Not defined in linux kernel or used in e2fsprogs.")]]; COMPAT_SPARSE_SUPER2 : 1 [[comment("Sparse Super Block, v2. If this flag is set, the SB field s_backup_bgs points to the two block groups that contain backup superblocks.")]]; COMPAT_FAST_COMMIT : 1 [[comment("Journal fast commits supported.")]]; padding : 1; RO_COMPAT_ORPHAN_PRESENT : 1 [[comment("Orphan file allocated. This is the special file for more efficient tracking of unlinked but still open inodes.")]]; }; bitfield ext4_super_incompat { INCOMPAT_COMPRESSION : 1; INCOMPAT_FILETYPE : 1 [[comment("Directory entries record the file type.")]]; INCOMPAT_RECOVER : 1 [[comment("Filesystem needs recovery.")]]; INCOMPAT_JOURNAL_DEV : 1 [[comment("Filesystem has a separate journal device.")]]; INCOMPAT_META_BG : 1 [[comment("Meta block groups.")]]; padding : 1; INCOMPAT_EXTENTS : 1 [[comment("Files in this filesystem use extents.")]]; INCOMPAT_64BIT : 1 [[comment("Enable a filesystem size of 2^64 blocks.")]]; INCOMPAT_MMP : 1 [[comment("Multiple mount protection.")]]; INCOMPAT_FLEX_BG : 1 [[comment("Flexible block groups.")]]; INCOMPAT_EA_INODE : 1 [[comment("Inodes can be used to store large extended attribute values.")]]; padding : 1 [[comment("Data in directory entry.")]]; INCOMPAT_DIRDATA : 1; INCOMPAT_CSUM_SEED : 1 [[comment("Metadata checksum seed is stored in the superblock.")]]; INCOMPAT_LARGEDIR : 1 [[comment("Large directory >2GB or 3-level htree.")]]; INCOMPAT_INLINE_DATA : 1 [[comment("Data in inode.")]]; INCOMPAT_ENCRYPT : 1 [[comment("Encrypted inodes are present on the filesystem.")]]; }; bitfield ext4_super_compat_ro { RO_COMPAT_SPARSE_SUPER : 1 [[comment("Sparse superblocks.")]]; RO_COMPAT_LARGE_FILE : 1 [[comment("This filesystem has been used to store a file greater than 2GiB.")]]; RO_COMPAT_BTREE_DIR : 1 [[comment("Not used in linux kernel or e2fsprogs.")]]; RO_COMPAT_HUGE_FILE : 1 [[comment("This filesystem has files whose sizes are represented in units of logical blocks, not 512-byte sectors. This implies a very large file indeed!.")]]; RO_COMPAT_GDT_CSUM : 1 [[comment("Group descriptors have checksums.")]]; RO_COMPAT_DIR_NLINK : 1 [[comment("Indicates that the old ext3 32,000 subdirectory limit no longer applies.")]]; RO_COMPAT_EXTRA_ISIZE : 1 [[comment("Indicates that large inodes exist on this filesystem.")]]; RO_COMPAT_HAS_SNAPSHOT : 1 [[comment("This filesystem has a snapshot.")]]; RO_COMPAT_QUOTA : 1; RO_COMPAT_BIGALLOC : 1 [[comment("This filesystem supports “bigalloc”, which means that file extents are tracked in units of clusters (of blocks) instead of blocks.")]]; RO_COMPAT_METADATA_CSUM : 1 [[comment("This filesystem supports metadata checksumming.")]]; RO_COMPAT_REPLICA : 1 [[comment("Filesystem supports replicas. This feature is neither in the kernel nor e2fsprogs.")]]; RO_COMPAT_READONLY : 1 [[comment("Read-only filesystem image.")]]; RO_COMPAT_PROJECT : 1 [[comment("Filesystem tracks project quotas.")]]; padding : 1; RO_COMPAT_VERITY : 1 [[comment("Verity inodes may be present on the filesystem.")]]; RO_COMPAT_ORPHAN_PRESENT : 1 [[comment("Indicates orphan file may have valid orphan entries and thus we need to clean them up when mounting the filesystem.")]]; }; enum ext4_super_def_hash : u8 { LEGACY = 0x00, HALF_MD4 = 0x01, TEA = 0x02, LEGACY_UNSIGNED = 0x03, HALF_MD4_UNSIGNED = 0x04, TEA_UNSIGNED = 0x05 }; bitfield ext4_super_mountopts { EXT4_DEFM_DEBUG : 1 [[comment("Print debugging info upon (re)mount.")]]; EXT4_DEFM_BSDGROUPS : 1 [[comment("New files take the gid of the containing directory (instead of the fsgid of the current process).")]]; EXT4_DEFM_XATTR_USER : 1 [[comment("Support userspace-provided extended attributes.")]]; EXT4_DEFM_ACL : 1 [[comment("Support POSIX access control lists (ACLs).")]]; EXT4_DEFM_UID16 : 1 [[comment("Do not support 32-bit UIDs.")]]; EXT4_DEFM_JMODE_DATA : 1 [[comment("All data and metadata are committed to the journal.")]]; EXT4_DEFM_JMODE_ORDER : 1 [[comment("All data are flushed to the disk before metadata are committed to the journal.")]]; padding : 1; EXT4_DEFM_NOBARRIER : 1 [[comment("Disable write flushes.")]]; EXT4_DEFM_BLOCK_VALIDITY : 1 [[comment("Track which blocks in a filesystem are metadata and therefore should not be used as data blocks.")]]; EXT4_DEFM_DISCARD : 1 [[comment("Enable DISCARD support, where the storage device is told about blocks becoming unused.")]]; EXT4_DEFM_NODELALLOC : 1 [[comment("Disable delayed allocation.")]]; }; bitfield ext4_super_flags { SIGNED_HASH_DIRECTORY : 1; UNSIGNED_HASH_DIRECTORY : 1; TEST_DEV_CODE : 1; }; enum ext4_super_encrypt_algos : u8 { ENCRYPTION_MODE_INVALID = 0x00, ENCRYPTION_MODE_AES_256_XTS = 0x01, ENCRYPTION_MODE_AES_256_GCM = 0x02, ENCRYPTION_MODE_AES_256_CBC = 0x03, }; struct ext4_super_block { u32 s_inodes_count [[comment("Total inode count.")]]; u32 s_blocks_count_lo [[comment("Total block count.")]]; u32 s_r_blocks_count_lo [[comment("This number of blocks can only be allocated by the super-user.")]]; u32 s_free_blocks_count_lo [[comment("Free block count.")]]; u32 s_free_inodes_count [[comment("Free inode count.")]]; u32 s_first_data_block [[comment("First data block. This must be at least 1 for 1k-block filesystems and is typically 0 for all other block sizes.")]]; u32 s_log_block_size; u32 s_log_cluster_size; u64 block_size = std::math::pow(2, 10+s_log_block_size); u64 cluster_size = std::math::pow(2, 10+s_log_cluster_size); u32 s_blocks_per_group [[comment("Blocks per group.")]]; u32 s_clusters_per_group [[comment("Clusters per group, if bigalloc is enabled. Otherwise s_clusters_per_group must equal s_blocks_per_group.")]]; u32 s_inodes_per_group [[comment("Inodes per group.")]]; type::time32_t s_mtime [[comment("Last mount time, in seconds since the epoch.")]]; type::time32_t s_wtime [[comment("Last write time, in seconds since the epoch.")]]; u16 s_mnt_count [[comment("Number of mounts since the last fsck.")]]; u16 s_max_mnt_count [[comment("Number of mounts beyond which a fsck is needed.")]]; type::Magic<"\x53\xEF"> s_magic; ext4_super_state s_state [[comment("File system state.")]]; ext4_super_errors s_errors [[comment("Behaviour when detecting errors.")]]; u16 s_minor_rev_level [[comment("Minor revision level.")]]; type::time32_t s_lastcheck [[comment("Time of last check, in seconds since the epoch.")]]; u32 s_checkinterval [[comment("Maximum time between checks, in seconds.")]]; ext4_super_creator s_creator_os [[comment("Creator OS.")]]; ext4_super_revision s_rev_level [[comment("Revision level.")]]; u16 s_def_resuid [[comment("Default uid for reserved blocks.")]]; u16 s_def_resgid [[comment("Default gid for reserved blocks.")]]; // EXT2_DYNAMIC_REV superblock if (s_rev_level >= ext4_super_revision::V2_DYNAMIC_REV) { u32 s_first_ino [[comment("First non-reserved inode.")]]; u16 s_inode_size [[comment("Size of inode structure, in bytes.")]]; u16 s_block_group_nr [[comment("Block group number of this superblock.")]]; ext4_super_compat s_feature_compat [[comment("Compatible feature set flags. Kernel can still read/write this fs even if it doesn’t understand a flag; fsck should not do that.")]]; padding[2]; ext4_super_incompat s_feature_incompat [[comment("Incompatible feature set. If the kernel or fsck doesn’t understand one of these bits, it should stop.")]]; padding[1]; ext4_super_compat_ro s_feature_ro_compat [[comment("Readonly-compatible feature set. If the kernel doesn’t understand one of these bits, it can still mount read-only.")]]; padding[1]; u8 s_uuid[16] [[comment("128-bit UUID for volume.")]]; char s_volume_name[16] [[comment("Volume label.")]]; char s_last_mounted[64] [[comment("Directory where filesystem was last mounted.")]]; if (s_feature_incompat.INCOMPAT_COMPRESSION) { u32 s_algorithm_usage_bitmap; } else { padding[4]; } // Performance hints if (s_feature_compat.COMPAT_DIR_PREALLOC) { u8 s_prealloc_blocks [[comment("Number of blocks to try to preallocate for ... files? (Not used in e2fsprogs/Linux).")]]; u8 s_prealloc_dir_blocks [[comment("Number of blocks to preallocate for directories. (Not used in e2fsprogs/Linux).")]]; } else { padding[2]; } u16 s_reserved_gdt_blocks [[comment("Number of reserved GDT entries for future filesystem expansion.")]]; // Journaling support if (s_feature_compat.COMPAT_HAS_JOURNAL) { u8 s_journal_uuid[16] [[comment("UUID of journal superblock.")]]; u32 s_journal_inum [[comment("Inode number of journal file.")]]; if (s_feature_incompat.INCOMPAT_JOURNAL_DEV) { u32 s_journal_dev [[comment("Device number of journal file.")]]; } else { padding[4]; } } else { padding[24]; } u32 s_last_orphan [[comment("Inode start of list of orphaned inodes to delete.")]]; u32 s_hash_seed[4] [[comment("HTREE hash seed.")]]; ext4_super_def_hash s_def_hash_version [[comment("Default hash algorithm to use for directory hashes.")]]; u8 s_jnl_backup_type [[comment("If this value is 0 or EXT3_JNL_BACKUP_BLOCKS (1), then the s_jnl_blocks field contains a duplicate copy of the inode’s i_block[] array and i_size.")]]; if (s_feature_incompat.INCOMPAT_64BIT) { u16 s_desc_size [[comment("Size of group descriptors, in bytes, if the 64bit incompat feature flag is set.")]]; } else { padding[2]; } ext4_super_mountopts s_default_mount_opts [[comment("Default mount options.")]]; padding[2]; if (s_feature_incompat.INCOMPAT_META_BG) { u32 s_first_meta_bg [[comment("First metablock block group, if the meta_bg feature is enabled.")]]; } else { padding[4]; } type::time32_t s_mkfs_time [[comment("When the filesystem was created, in seconds since the epoch.")]]; u32 s_jnl_blocks[17] [[comment("Backup copy of the journal inode’s i_block[] array in the first 15 elements and i_size_high and i_size in the 16th and 17th elements, respectively.")]]; if (s_feature_incompat.INCOMPAT_64BIT) { u32 s_blocks_count_hi [[comment("High 32-bits of the block count.")]]; u32 s_r_blocks_count_hi [[comment("High 32-bits of the reserved block count.")]]; u32 s_free_blocks_count_hi [[comment("High 32-bits of the free block count.")]]; u64 s_blocks_count = (u64(s_blocks_count_hi) << 32) + s_blocks_count_lo; u64 s_r_blocks_count = (u64(s_r_blocks_count_hi) << 32) + s_r_blocks_count_lo; u64 s_free_blocks_count = (u64(s_free_blocks_count_hi) << 32) + s_free_blocks_count_lo; u64 groups_count = std::math::ceil(s_blocks_count/float(s_blocks_per_group)); } else { padding[12]; u64 s_blocks_count = s_blocks_count_lo; u64 s_r_blocks_count = s_r_blocks_count_lo; u64 s_free_blocks_count = s_free_blocks_count_lo; u64 groups_count = std::math::ceil(s_blocks_count/float(s_blocks_per_group)); } if (s_feature_ro_compat.RO_COMPAT_EXTRA_ISIZE) { u16 s_min_extra_isize [[comment("All inodes have at least # bytes.")]]; u16 s_want_extra_isize [[comment("New inodes should reserve # bytes.")]]; } else { padding[4]; } ext4_super_flags s_flags [[comment("Miscellaneous flags.")]]; padding[3]; u16 s_raid_stride [[comment("RAID stride. This is the number of logical blocks read from or written to the disk before moving to the next disk. This affects the placement of filesystem metadata.")]]; if (s_feature_incompat.INCOMPAT_MMP) { u16 s_mmp_interval [[comment("Number of seconds to wait in multi-mount prevention (MMP) checking.")]]; u64 s_mmp_block [[comment("Block number for multi-mount protection data.")]]; } else { padding[10]; } u32 s_raid_stripe_width [[comment("RAID stripe width. This is the number of logical blocks read from or written to the disk before coming back to the current disk.")]]; if (s_feature_incompat.INCOMPAT_FLEX_BG) { u8 s_log_groups_per_flex; u64 groups_per_flex = std::math::pow(2, s_log_groups_per_flex); } else { padding[1]; } if (s_feature_ro_compat.RO_COMPAT_METADATA_CSUM) { u8 s_checksum_type [[comment("Metadata checksum algorithm type.")]]; } else { padding[1]; } padding[2]; u64 s_kbytes_written [[comment("Number of KiB written to this filesystem over its lifetime.")]]; if (s_feature_ro_compat.RO_COMPAT_HAS_SNAPSHOT) { u32 s_snapshot_inum [[comment("inode number of active snapshot. (Not used in e2fsprogs/Linux.)")]]; u32 s_snapshot_id [[comment("Sequential ID of active snapshot. (Not used in e2fsprogs/Linux.)")]]; u64 s_snapshot_r_blocks_count [[comment("Number of blocks reserved for active snapshot’s future use. (Not used in e2fsprogs/Linux.)")]]; u32 s_snapshot_list [[comment("inode number of the head of the on-disk snapshot list. (Not used in e2fsprogs/Linux.)")]]; } else { padding[20]; } u32 s_error_count [[comment("Number of errors seen.")]]; if (s_error_count > 0) { type::time32_t s_first_error_time [[comment("First time an error happened.")]]; u32 s_first_error_ino [[comment("inode involved in first error.")]]; u64 s_first_error_block [[comment("Number of block involved of first error.")]]; char s_first_error_func[32] [[comment("Name of function where the error happened.")]]; u32 s_first_error_line [[comment("Line number where error happened.")]]; type::time32_t s_last_error_time [[comment("Last time an error happened.")]]; u32 s_last_error_ino [[comment("inode involved in most recent error.")]]; u32 s_last_error_line [[comment("Line number where most recent error happened.")]]; u64 s_last_error_block [[comment("Number of block involved in most recent error.")]]; char s_last_error_func[32] [[comment("Name of function where the most recent error happened.")]]; } else { padding[104]; } char s_mount_opts[64] [[comment("ASCIIZ string of mount options.")]]; if (s_feature_ro_compat.RO_COMPAT_QUOTA) { u32 s_usr_quota_inum [[comment("Inode number of user quota file.")]]; u32 s_grp_quota_inum [[comment("Inode number of group quota file.")]]; } else { padding[8]; } u32 s_overhead_blocks [[comment("Overhead blocks/clusters in fs. (Huh? This field is always zero, which means that the linux kernel calculates it dynamically.)")]]; if (s_feature_compat.COMPAT_SPARSE_SUPER2) { u32 s_backup_bgs[2] [[comment("Block groups containing superblock backups.")]]; } else { padding[8]; } if (s_feature_incompat.INCOMPAT_ENCRYPT) { ext4_super_encrypt_algos s_encrypt_algos[4] [[comment("Encryption algorithms in use. There can be up to four algorithms in use at any time.")]]; u8 s_encrypt_pw_salt[16] [[comment("Salt for the string2key algorithm for encryption.")]]; } else { padding[20]; } u32 s_lpf_ino [[comment("Inode number of lost+found.")]]; if (s_feature_ro_compat.RO_COMPAT_PROJECT) { u32 s_prj_quota_inum [[comment("Inode that tracks project quotas.")]]; } else { padding[4]; } if (s_feature_ro_compat.RO_COMPAT_METADATA_CSUM) { u32 s_checksum_seed [[comment("Checksum seed used for metadata_csum calculations. This value is crc32c(~0, $orig_fs_uuid).")]]; } else { padding[4]; } u8 s_wtime_hi [[comment("Upper 8 bits of the s_wtime field.")]]; u8 s_mtime_hi [[comment("Upper 8 bits of the s_mtime field.")]]; u8 s_mkfs_time_hi [[comment("Upper 8 bits of the s_mkfs_time field.")]]; u8 s_lastcheck_hi [[comment("Upper 8 bits of the s_lastcheck field.")]]; u8 s_first_error_time_hi [[comment("Upper 8 bits of the s_first_error_time field.")]]; u8 s_last_error_time_hi [[comment("Upper 8 bits of the s_last_error_time field.")]]; padding[2]; u16 s_encoding [[comment("Filename charset encoding.")]]; u16 s_encoding_flags [[comment("Filename charset encoding flags.")]]; if (s_feature_compat.RO_COMPAT_ORPHAN_PRESENT) { u32 s_orphan_file_inum [[comment("Orphan file inode number.")]]; } else { padding[4]; } padding[376]; if (s_feature_ro_compat.RO_COMPAT_METADATA_CSUM) { u32 s_checksum [[comment("Superblock checksum.")]]; } else { padding[4]; } } }; ext4_super_block super_block @ 0x400; fn block_to_address(u32 block) { return super_block.block_size * block; }; fn block_pointer_to_address(u32 block) { return block_to_address(block) - block; }; struct ext4_bitmap { u8 data[super_block.block_size]; }; bitfield ext4_i_mode { S_IXOTH : 1 [[comment("Others may execute.")]]; S_IWOTH : 1 [[comment("Others may write.")]]; S_IROTH : 1 [[comment("Others may read.")]]; S_IXGRP : 1 [[comment("Group members may execute.")]]; S_IWGRP : 1 [[comment("Group members may write.")]]; S_IRGRP : 1 [[comment("Group members may read.")]]; S_IXUSR : 1 [[comment("Owner may execute.")]]; S_IWUSR : 1 [[comment("Owner may write.")]]; S_IRUSR : 1 [[comment("Owner may read.")]]; S_ISVTX : 1 [[comment("Sticky bit.")]]; S_ISGID : 1 [[comment("Set GID.")]]; S_ISUID : 1 [[comment("Set UID.")]]; S_IFIFO : 1 [[comment("FIFO.")]]; S_IFCHR : 1 [[comment("Character device.")]]; S_IFDIR : 1 [[comment("Directory.")]]; S_IFREG : 1 [[comment("Regular file.")]]; }; bitfield ext4_i_flags { EXT4_SECRM_FL : 1 [[comment("This file requires secure deletion.")]]; EXT4_UNRM_FL : 1 [[comment("This file should be preserved, should undeletion be desired.")]]; EXT4_COMPR_FL : 1 [[comment("File is compressed.")]]; EXT4_SYNC_FL : 1 [[comment("All writes to the file must be synchronous.")]]; EXT4_IMMUTABLE_FL : 1 [[comment("File is immutable.")]]; EXT4_APPEND_FL : 1 [[comment("File can only be appended.")]]; EXT4_NODUMP_FL : 1 [[comment("The dump utility should not dump this file.")]]; EXT4_NOATIME_FL : 1 [[comment("Do not update access time.")]]; EXT4_DIRTY_FL : 1 [[comment("Dirty compressed file.")]]; EXT4_COMPRBLK_FL : 1 [[comment("File has one or more compressed clusters.")]]; EXT4_NOCOMPR_FL : 1 [[comment("Do not compress file.")]]; EXT4_ENCRYPT_FL : 1 [[comment("Encrypted inode.")]]; EXT4_INDEX_FL : 1 [[comment("Directory has hashed indexes.")]]; EXT4_IMAGIC_FL : 1 [[comment("AFS magic directory.")]]; EXT4_JOURNAL_DATA_FL : 1 [[comment("File data must always be written through the journal.")]]; EXT4_NOTAIL_FL : 1 [[comment("File tail should not be merged.")]]; EXT4_DIRSYNC_FL : 1 [[comment("All directory entry data should be written synchronously.")]]; EXT4_TOPDIR_FL : 1 [[comment("Top of directory hierarchy.")]]; EXT4_HUGE_FILE_FL : 1 [[comment("This is a huge file.")]]; EXT4_EXTENTS_FL : 1 [[comment("Inode uses extents.")]]; EXT4_VERITY_FL : 1 [[comment("Verity protected file.")]]; EXT4_EA_INODE_FL : 1 [[comment("Inode stores a large extended attribute value in its data blocks.")]]; EXT4_EOFBLOCKS_FL : 1 [[comment("This file has blocks allocated past EOF.")]]; padding : 1; EXT4_SNAPFILE_FL : 1 [[comment("Inode is a snapshot.")]]; padding : 1; EXT4_SNAPFILE_DELETED_FL : 1 [[comment("Snapshot is being deleted.")]]; EXT4_SNAPFILE_SHRUNK_FL : 1 [[comment("Snapshot shrink has completed.")]]; EXT4_INLINE_DATA_FL : 1 [[comment("Inode has inline data.")]]; EXT4_PROJINHERIT_FL : 1 [[comment("Create children with the same project ID.")]]; padding : 1; EXT4_RESERVED_FL : 1 [[comment("Reserved for ext4 library.")]]; }; struct ext4_inode { ext4_i_mode i_mode [[comment("File mode.")]]; u16 i_uid [[comment("Lower 16-bits of Owner UID.")]]; u32 i_size [[comment("Lower 32-bits of size in bytes.")]]; type::time32_t i_atime [[comment("Last access time.")]]; type::time32_t i_ctime [[comment("Last inode change time.")]]; type::time32_t i_mtime [[comment("Last data modification time.")]]; type::time32_t i_dtime [[comment("Deletion Time.")]]; u16 i_gid [[comment("Lower 16-bits of GID.")]]; u16 i_links_count [[comment("Hard link count.")]]; u32 i_blocks_lo [[comment("Lower 32-bits of “block” count.")]]; ext4_i_flags i_flags [[comment("Inode flags.")]]; u32 i_osd1 [[comment("Depends of the OS.")]]; u32 i_block[15] [[comment("Block map or extent tree.")]]; u32 i_generation [[comment("File version (for NFS).")]]; u32 i_file_acl [[comment("Lower 32-bits of extended attribute block.")]]; u32 i_dir_acl [[comment("Upper 32-bits of file/directory size.")]]; u32 i_faddr [[comment("Fragment address.")]]; u8 i_osd2[12] [[comment("Depends of the OS.")]]; if (super_block.s_rev_level >= ext4_super_revision::V2_DYNAMIC_REV && super_block.s_inode_size > 0x80) { u16 i_extra_isize [[comment("Size of this inode - 128.")]]; u16 i_checksum_hi [[comment("Upper 16-bits of the inode checksum.")]]; if (super_block.s_inode_size > 0x84) { u32 i_ctime_extra [[comment("Extra change time bits.")]]; u32 i_mtime_extra [[comment("Extra modification time bits.")]]; u32 i_atime_extra [[comment("Extra access time bits.")]]; u32 i_crtime [[comment("File creation time.")]]; u32 i_crtime_extra [[comment("Extra file creation time bits.")]]; if (super_block.s_inode_size > 0x98) { u32 i_version_hi [[comment("Upper 32-bits for version number.")]]; if (super_block.s_inode_size > 0x9C) { u32 i_projid [[comment("Project ID.")]]; if (super_block.s_inode_size > 0xA0) { padding[super_block.s_inode_size - 0xA0]; } } } } } }; bitfield ext4_bg_flags { EXT4_BG_INODE_UNINIT : 1 [[comment("Inode table and bitmap are not initialized.")]]; EXT4_BG_BLOCK_UNINIT : 1 [[comment("Block bitmap is not initialized.")]]; EXT4_BG_INODE_ZEROED : 1 [[comment("Inode table is zeroed.")]]; }; struct ext4_group_desc { ext4_bitmap *bg_block_bitmap : u32 [[pointer_base("block_pointer_to_address"), comment("Lower 32-bits of location of block bitmap.")]]; ext4_bitmap *bg_inode_bitmap : u32 [[pointer_base("block_pointer_to_address"), comment("Lower 32-bits of location of inode bitmap.")]]; ext4_inode *bg_inode_table[super_block.s_inodes_per_group] : u32 [[pointer_base("block_pointer_to_address"), comment("Lower 32-bits of location of inode table.")]]; u16 bg_free_blocks_count [[comment("Lower 16-bits of free block count.")]]; u16 bg_free_inodes_count [[comment("Lower 16-bits of free inode count.")]]; u16 bg_used_dirs_count [[comment("Lower 16-bits of directory count.")]]; ext4_bg_flags bg_flags [[comment("Block group flags.")]]; padding[1]; u32 bg_exclude_bitmap_lo [[comment("Lower 32-bits of location of snapshot exclusion bitmap.")]]; u16 bg_block_bitmap_csum_lo [[comment("Lower 16-bits of the block bitmap checksum.")]]; u16 bg_inode_bitmap_csum_lo [[comment("Lower 16-bits of the inode bitmap checksum.")]]; u16 bg_itable_unused_lo [[comment("Lower 16-bits of unused inode count.")]]; u16 bg_checksum [[comment("Group descriptor checksum.")]]; }; struct ext4_group_desc_64_bit : ext4_group_desc { u32 bg_block_bitmap_hi [[comment("Upper 32-bits of location of block bitmap.")]]; u32 bg_inode_bitmap_hi [[comment("Upper 32-bits of location of inodes bitmap.")]]; u32 bg_inode_table_hi [[comment("Upper 32-bits of location of inodes table.")]]; u16 bg_free_blocks_count_hi [[comment("Upper 16-bits of free block count.")]]; u16 bg_free_inodes_count_hi [[comment("Upper 16-bits of free inode count.")]]; u16 bg_used_dirs_count_hi [[comment("Upper 16-bits of directory count.")]]; u16 bg_itable_unused_hi [[comment("Upper 16-bits of unused inode count.")]]; u32 bg_exclude_bitmap_hi [[comment("Upper 32-bits of location of snapshot exclusion bitmap.")]]; u16 bg_block_bitmap_csum_hi [[comment("Upper 16-bits of the block bitmap checksum.")]]; u16 bg_inode_bitmap_csum_hi [[comment("Upper 16-bits of the inode bitmap checksum.")]]; padding[4]; }; struct ext4_group_descriptors { if (super_block.s_rev_level >= ext4_super_revision::V2_DYNAMIC_REV && super_block.s_feature_incompat.INCOMPAT_64BIT) { ext4_group_desc_64_bit group_desc[super_block.groups_count]; } else { ext4_group_desc group_desc[super_block.groups_count]; } }; ext4_group_descriptors group_descs @ block_to_address(super_block.s_first_data_block + 1);