4. Dynamic Structures

Dynamic metadata are created on the fly when files and blocks are allocated to files.

4.1. Index Nodes

In a regular UNIX filesystem, the inode stores all the metadata pertaining to the file (time stamps, block maps, extended attributes, etc), not the directory entry. To find the information associated with a file, one must traverse the directory files to find the directory entry associated with a file, then load the inode to find the metadata for that file. ext4 appears to cheat (for performance reasons) a little bit by storing a copy of the file type (normally stored in the inode) in the directory entry. (Compare all this to FAT, which stores all the file information directly in the directory entry, but does not support hard links and is in general more seek-happy than ext4 due to its simpler block allocator and extensive use of linked lists.)

The inode table is a linear array of struct ext4_inode. The table is sized to have enough blocks to store at least sb.s_inode_size * sb.s_inodes_per_group bytes. The number of the block group containing an inode can be calculated as (inode_number - 1) / sb.s_inodes_per_group, and the offset into the group’s table is (inode_number - 1) % sb.s_inodes_per_group. There is no inode 0.

The inode checksum is calculated against the FS UUID, the inode number, and the inode structure itself.

The inode table entry is laid out in struct ext4_inode.

Offset Size Name Description
0x0 __le16 i_mode File mode. See the table i_mode below.
0x2 __le16 i_uid Lower 16-bits of Owner UID.
0x4 __le32 i_size_lo Lower 32-bits of size in bytes.
0x8 __le32 i_atime Last access time, in seconds since the epoch. However, if the EA_INODE inode flag is set, this inode stores an extended attribute value and this field contains the checksum of the value.
0xC __le32 i_ctime Last inode change time, in seconds since the epoch. However, if the EA_INODE inode flag is set, this inode stores an extended attribute value and this field contains the lower 32 bits of the attribute value’s reference count.
0x10 __le32 i_mtime Last data modification time, in seconds since the epoch. However, if the EA_INODE inode flag is set, this inode stores an extended attribute value and this field contains the number of the inode that owns the extended attribute.
0x14 __le32 i_dtime Deletion Time, in seconds since the epoch.
0x18 __le16 i_gid Lower 16-bits of GID.
0x1A __le16 i_links_count Hard link count. Normally, ext4 does not permit an inode to have more than 65,000 hard links. This applies to files as well as directories, which means that there cannot be more than 64,998 subdirectories in a directory (each subdirectory’s ‘..’ entry counts as a hard link, as does the ‘.’ entry in the directory itself). With the DIR_NLINK feature enabled, ext4 supports more than 64,998 subdirectories by setting this field to 1 to indicate that the number of hard links is not known.
0x1C __le32 i_blocks_lo Lower 32-bits of “block” count. If the huge_file feature flag is not set on the filesystem, the file consumes i_blocks_lo 512-byte blocks on disk. If huge_file is set and EXT4_HUGE_FILE_FL is NOT set in inode.i_flags, then the file consumes i_blocks_lo + (i_blocks_hi << 32) 512-byte blocks on disk. If huge_file is set and EXT4_HUGE_FILE_FL IS set in inode.i_flags, then this file consumes (i_blocks_lo + i_blocks_hi << 32) filesystem blocks on disk.
0x20 __le32 i_flags Inode flags. See the table i_flags below.
0x24 4 bytes i_osd1 See the table i_osd1 for more details.
0x28 60 bytes i_block[EXT4_N_BLOCKS=15] Block map or extent tree. See the section “The Contents of inode.i_block”.
0x64 __le32 i_generation File version (for NFS).
0x68 __le32 i_file_acl_lo Lower 32-bits of extended attribute block. ACLs are of course one of many possible extended attributes; I think the name of this field is a result of the first use of extended attributes being for ACLs.
0x6C __le32 i_size_high / i_dir_acl Upper 32-bits of file/directory size. In ext2/3 this field was named i_dir_acl, though it was usually set to zero and never used.
0x70 __le32 i_obso_faddr (Obsolete) fragment address.
0x74 12 bytes i_osd2 See the table i_osd2 for more details.
0x80 __le16 i_extra_isize Size of this inode - 128. Alternately, the size of the extended inode fields beyond the original ext2 inode, including this field.
0x82 __le16 i_checksum_hi Upper 16-bits of the inode checksum.
0x84 __le32 i_ctime_extra Extra change time bits. This provides sub-second precision. See Inode Timestamps section.
0x88 __le32 i_mtime_extra Extra modification time bits. This provides sub-second precision.
0x8C __le32 i_atime_extra Extra access time bits. This provides sub-second precision.
0x90 __le32 i_crtime File creation time, in seconds since the epoch.
0x94 __le32 i_crtime_extra Extra file creation time bits. This provides sub-second precision.
0x98 __le32 i_version_hi Upper 32-bits for version number.
0x9C __le32 i_projid Project ID.

The i_mode value is a combination of the following flags:

Value Description
0x1 S_IXOTH (Others may execute)
0x2 S_IWOTH (Others may write)
0x4 S_IROTH (Others may read)
0x8 S_IXGRP (Group members may execute)
0x10 S_IWGRP (Group members may write)
0x20 S_IRGRP (Group members may read)
0x40 S_IXUSR (Owner may execute)
0x80 S_IWUSR (Owner may write)
0x100 S_IRUSR (Owner may read)
0x200 S_ISVTX (Sticky bit)
0x400 S_ISGID (Set GID)
0x800 S_ISUID (Set UID)
  These are mutually-exclusive file types:
0x1000 S_IFIFO (FIFO)
0x2000 S_IFCHR (Character device)
0x4000 S_IFDIR (Directory)
0x6000 S_IFBLK (Block device)
0x8000 S_IFREG (Regular file)
0xA000 S_IFLNK (Symbolic link)
0xC000 S_IFSOCK (Socket)

The i_flags field is a combination of these values:

Value Description
0x1 This file requires secure deletion (EXT4_SECRM_FL). (not implemented)
0x2 This file should be preserved, should undeletion be desired (EXT4_UNRM_FL). (not implemented)
0x4 File is compressed (EXT4_COMPR_FL). (not really implemented)
0x8 All writes to the file must be synchronous (EXT4_SYNC_FL).
0x10 File is immutable (EXT4_IMMUTABLE_FL).
0x20 File can only be appended (EXT4_APPEND_FL).
0x40 The dump(1) utility should not dump this file (EXT4_NODUMP_FL).
0x80 Do not update access time (EXT4_NOATIME_FL).
0x100 Dirty compressed file (EXT4_DIRTY_FL). (not used)
0x200 File has one or more compressed clusters (EXT4_COMPRBLK_FL). (not used)
0x400 Do not compress file (EXT4_NOCOMPR_FL). (not used)
0x800 Encrypted inode (EXT4_ENCRYPT_FL). This bit value previously was EXT4_ECOMPR_FL (compression error), which was never used.
0x1000 Directory has hashed indexes (EXT4_INDEX_FL).
0x2000 AFS magic directory (EXT4_IMAGIC_FL).
0x4000 File data must always be written through the journal (EXT4_JOURNAL_DATA_FL).
0x8000 File tail should not be merged (EXT4_NOTAIL_FL). (not used by ext4)
0x10000 All directory entry data should be written synchronously (see dirsync) (EXT4_DIRSYNC_FL).
0x20000 Top of directory hierarchy (EXT4_TOPDIR_FL).
0x40000 This is a huge file (EXT4_HUGE_FILE_FL).
0x80000 Inode uses extents (EXT4_EXTENTS_FL).
0x100000 Verity protected file (EXT4_VERITY_FL).
0x200000 Inode stores a large extended attribute value in its data blocks (EXT4_EA_INODE_FL).
0x400000 This file has blocks allocated past EOF (EXT4_EOFBLOCKS_FL). (deprecated)
0x01000000 Inode is a snapshot (EXT4_SNAPFILE_FL). (not in mainline)
0x04000000 Snapshot is being deleted (EXT4_SNAPFILE_DELETED_FL). (not in mainline)
0x08000000 Snapshot shrink has completed (EXT4_SNAPFILE_SHRUNK_FL). (not in mainline)
0x10000000 Inode has inline data (EXT4_INLINE_DATA_FL).
0x20000000 Create children with the same project ID (EXT4_PROJINHERIT_FL).
0x80000000 Reserved for ext4 library (EXT4_RESERVED_FL).
  Aggregate flags:
0x705BDFFF User-visible flags.
0x604BC0FF User-modifiable flags. Note that while EXT4_JOURNAL_DATA_FL and EXT4_EXTENTS_FL can be set with setattr, they are not in the kernel’s EXT4_FL_USER_MODIFIABLE mask, since it needs to handle the setting of these flags in a special manner and they are masked out of the set of flags that are saved directly to i_flags.

The osd1 field has multiple meanings depending on the creator:

Linux:

Offset Size Name Description
0x0 __le32 l_i_version Inode version. However, if the EA_INODE inode flag is set, this inode stores an extended attribute value and this field contains the upper 32 bits of the attribute value’s reference count.

Hurd:

Offset Size Name Description
0x0 __le32 h_i_translator ??

Masix:

Offset Size Name Description
0x0 __le32 m_i_reserved ??

The osd2 field has multiple meanings depending on the filesystem creator:

Linux:

Offset Size Name Description
0x0 __le16 l_i_blocks_high Upper 16-bits of the block count. Please see the note attached to i_blocks_lo.
0x2 __le16 l_i_file_acl_high Upper 16-bits of the extended attribute block (historically, the file ACL location). See the Extended Attributes section below.
0x4 __le16 l_i_uid_high Upper 16-bits of the Owner UID.
0x6 __le16 l_i_gid_high Upper 16-bits of the GID.
0x8 __le16 l_i_checksum_lo Lower 16-bits of the inode checksum.
0xA __le16 l_i_reserved Unused.

Hurd:

Offset Size Name Description
0x0 __le16 h_i_reserved1 ??
0x2 __u16 h_i_mode_high Upper 16-bits of the file mode.
0x4 __le16 h_i_uid_high Upper 16-bits of the Owner UID.
0x6 __le16 h_i_gid_high Upper 16-bits of the GID.
0x8 __u32 h_i_author Author code?

Masix:

Offset Size Name Description
0x0 __le16 h_i_reserved1 ??
0x2 __u16 m_i_file_acl_high Upper 16-bits of the extended attribute block (historically, the file ACL location).
0x4 __u32 m_i_reserved2[2] ??

4.1.1. Inode Size

In ext2 and ext3, the inode structure size was fixed at 128 bytes (EXT2_GOOD_OLD_INODE_SIZE) and each inode had a disk record size of 128 bytes. Starting with ext4, it is possible to allocate a larger on-disk inode at format time for all inodes in the filesystem to provide space beyond the end of the original ext2 inode. The on-disk inode record size is recorded in the superblock as s_inode_size. The number of bytes actually used by struct ext4_inode beyond the original 128-byte ext2 inode is recorded in the i_extra_isize field for each inode, which allows struct ext4_inode to grow for a new kernel without having to upgrade all of the on-disk inodes. Access to fields beyond EXT2_GOOD_OLD_INODE_SIZE should be verified to be within i_extra_isize. By default, ext4 inode records are 256 bytes, and (as of August 2019) the inode structure is 160 bytes (i_extra_isize = 32). The extra space between the end of the inode structure and the end of the inode record can be used to store extended attributes. Each inode record can be as large as the filesystem block size, though this is not terribly efficient.

4.1.2. Finding an Inode

Each block group contains sb->s_inodes_per_group inodes. Because inode 0 is defined not to exist, this formula can be used to find the block group that an inode lives in: bg = (inode_num - 1) / sb->s_inodes_per_group. The particular inode can be found within the block group’s inode table at index = (inode_num - 1) % sb->s_inodes_per_group. To get the byte address within the inode table, use offset = index * sb->s_inode_size.

4.1.3. Inode Timestamps

Four timestamps are recorded in the lower 128 bytes of the inode structure – inode change time (ctime), access time (atime), data modification time (mtime), and deletion time (dtime). The four fields are 32-bit signed integers that represent seconds since the Unix epoch (1970-01-01 00:00:00 GMT), which means that the fields will overflow in January 2038. For inodes that are not linked from any directory but are still open (orphan inodes), the dtime field is overloaded for use with the orphan list. The superblock field s_last_orphan points to the first inode in the orphan list; dtime is then the number of the next orphaned inode, or zero if there are no more orphans.

If the inode structure size sb->s_inode_size is larger than 128 bytes and the i_inode_extra field is large enough to encompass the respective i_[cma]time_extra field, the ctime, atime, and mtime inode fields are widened to 64 bits. Within this “extra” 32-bit field, the lower two bits are used to extend the 32-bit seconds field to be 34 bit wide; the upper 30 bits are used to provide nanosecond timestamp accuracy. Therefore, timestamps should not overflow until May 2446. dtime was not widened. There is also a fifth timestamp to record inode creation time (crtime); this field is 64-bits wide and decoded in the same manner as 64-bit [cma]time. Neither crtime nor dtime are accessible through the regular stat() interface, though debugfs will report them.

We use the 32-bit signed time value plus (2^32 * (extra epoch bits)). In other words:

Extra epoch bits MSB of 32-bit time Adjustment for signed 32-bit to 64-bit tv_sec Decoded 64-bit tv_sec valid time range
0 0 1 0 -0x80000000 - -0x00000001 1901-12-13 to 1969-12-31
0 0 0 0 0x000000000 - 0x07fffffff 1970-01-01 to 2038-01-19
0 1 1 0x100000000 0x080000000 - 0x0ffffffff 2038-01-19 to 2106-02-07
0 1 0 0x100000000 0x100000000 - 0x17fffffff 2106-02-07 to 2174-02-25
1 0 1 0x200000000 0x180000000 - 0x1ffffffff 2174-02-25 to 2242-03-16
1 0 0 0x200000000 0x200000000 - 0x27fffffff 2242-03-16 to 2310-04-04
1 1 1 0x300000000 0x280000000 - 0x2ffffffff 2310-04-04 to 2378-04-22
1 1 0 0x300000000 0x300000000 - 0x37fffffff 2378-04-22 to 2446-05-10

This is a somewhat odd encoding since there are effectively seven times as many positive values as negative values. There have also been long-standing bugs decoding and encoding dates beyond 2038, which don’t seem to be fixed as of kernel 3.12 and e2fsprogs 1.42.8. 64-bit kernels incorrectly use the extra epoch bits 1,1 for dates between 1901 and 1970. At some point the kernel will be fixed and e2fsck will fix this situation, assuming that it is run before 2310.

4.2. The Contents of inode.i_block

Depending on the type of file an inode describes, the 60 bytes of storage in inode.i_block can be used in different ways. In general, regular files and directories will use it for file block indexing information, and special files will use it for special purposes.

4.2.2. Direct/Indirect Block Addressing

In ext2/3, file block numbers were mapped to logical block numbers by means of an (up to) three level 1-1 block map. To find the logical block that stores a particular file block, the code would navigate through this increasingly complicated structure. Notice that there is neither a magic number nor a checksum to provide any level of confidence that the block isn’t full of garbage.

i.i_block Offset Where It Points
0 to 11 Direct map to file blocks 0 to 11.
12

Indirect block: (file blocks 12 to ($block_size / 4) + 11, or 12 to 1035 if 4KiB blocks)

Indirect Block Offset Where It Points
0 to ($block_size / 4) Direct map to ($block_size / 4) blocks (1024 if 4KiB blocks)
13

Double-indirect block: (file blocks $block_size/4 + 12 to ($block_size / 4) ^ 2 + ($block_size / 4) + 11, or 1036 to 1049611 if 4KiB blocks)

Double Indirect Block Offset Where It Points
0 to ($block_size / 4)

Map to ($block_size / 4) indirect blocks (1024 if 4KiB blocks)

Indirect Block Offset Where It Points
0 to ($block_size / 4) Direct map to ($block_size / 4) blocks (1024 if 4KiB blocks)
14

Triple-indirect block: (file blocks ($block_size / 4) ^ 2 + ($block_size / 4) + 12 to ($block_size / 4) ^ 3 + ($block_size / 4) ^ 2 + ($block_size / 4) + 12, or 1049612 to 1074791436 if 4KiB blocks)

Triple Indirect Block Offset Where It Points
0 to ($block_size / 4)

Map to ($block_size / 4) double indirect blocks (1024 if 4KiB blocks)

Double Indirect Block Offset Where It Points
0 to ($block_size / 4)

Map to ($block_size / 4) indirect blocks (1024 if 4KiB blocks)

Indirect Block Offset Where It Points
0 to ($block_size / 4) Direct map to ($block_size / 4) blocks (1024 if 4KiB blocks)

Note that with this block mapping scheme, it is necessary to fill out a lot of mapping data even for a large contiguous file! This inefficiency led to the creation of the extent mapping scheme, discussed below.

Notice also that a file using this mapping scheme cannot be placed higher than 2^32 blocks.

4.2.3. Extent Tree

In ext4, the file to logical block map has been replaced with an extent tree. Under the old scheme, allocating a contiguous run of 1,000 blocks requires an indirect block to map all 1,000 entries; with extents, the mapping is reduced to a single struct ext4_extent with ee_len = 1000. If flex_bg is enabled, it is possible to allocate very large files with a single extent, at a considerable reduction in metadata block use, and some improvement in disk efficiency. The inode must have the extents flag (0x80000) flag set for this feature to be in use.

Extents are arranged as a tree. Each node of the tree begins with a struct ext4_extent_header. If the node is an interior node (eh.eh_depth > 0), the header is followed by eh.eh_entries instances of struct ext4_extent_idx; each of these index entries points to a block containing more nodes in the extent tree. If the node is a leaf node (eh.eh_depth == 0), then the header is followed by eh.eh_entries instances of struct ext4_extent; these instances point to the file’s data blocks. The root node of the extent tree is stored in inode.i_block, which allows for the first four extents to be recorded without the use of extra metadata blocks.

The extent tree header is recorded in struct ext4_extent_header, which is 12 bytes long:

Offset Size Name Description
0x0 __le16 eh_magic Magic number, 0xF30A.
0x2 __le16 eh_entries Number of valid entries following the header.
0x4 __le16 eh_max Maximum number of entries that could follow the header.
0x6 __le16 eh_depth Depth of this extent node in the extent tree. 0 = this extent node points to data blocks; otherwise, this extent node points to other extent nodes. The extent tree can be at most 5 levels deep: a logical block number can be at most 2^32, and the smallest n that satisfies 4*(((blocksize - 12)/12)^n) >= 2^32 is 5.
0x8 __le32 eh_generation Generation of the tree. (Used by Lustre, but not standard ext4).

Internal nodes of the extent tree, also known as index nodes, are recorded as struct ext4_extent_idx, and are 12 bytes long:

Offset Size Name Description
0x0 __le32 ei_block This index node covers file blocks from ‘block’ onward.
0x4 __le32 ei_leaf_lo Lower 32-bits of the block number of the extent node that is the next level lower in the tree. The tree node pointed to can be either another internal node or a leaf node, described below.
0x8 __le16 ei_leaf_hi Upper 16-bits of the previous field.
0xA __u16 ei_unused  

Leaf nodes of the extent tree are recorded as struct ext4_extent, and are also 12 bytes long:

Offset Size Name Description
0x0 __le32 ee_block First file block number that this extent covers.
0x4 __le16 ee_len Number of blocks covered by extent. If the value of this field is <= 32768, the extent is initialized. If the value of the field is > 32768, the extent is uninitialized and the actual extent length is ee_len - 32768. Therefore, the maximum length of a initialized extent is 32768 blocks, and the maximum length of an uninitialized extent is 32767.
0x6 __le16 ee_start_hi Upper 16-bits of the block number to which this extent points.
0x8 __le32 ee_start_lo Lower 32-bits of the block number to which this extent points.

Prior to the introduction of metadata checksums, the extent header + extent entries always left at least 4 bytes of unallocated space at the end of each extent tree data block (because (2^x % 12) >= 4). Therefore, the 32-bit checksum is inserted into this space. The 4 extents in the inode do not need checksumming, since the inode is already checksummed. The checksum is calculated against the FS UUID, the inode number, the inode generation, and the entire extent block leading up to (but not including) the checksum itself.

struct ext4_extent_tail is 4 bytes long:

Offset Size Name Description
0x0 __le32 eb_checksum Checksum of the extent block, crc32c(uuid+inum+igeneration+extentblock)

4.2.4. Inline Data

If the inline data feature is enabled for the filesystem and the flag is set for the inode, it is possible that the first 60 bytes of the file data are stored here.

4.3. Directory Entries

In an ext4 filesystem, a directory is more or less a flat file that maps an arbitrary byte string (usually ASCII) to an inode number on the filesystem. There can be many directory entries across the filesystem that reference the same inode number–these are known as hard links, and that is why hard links cannot reference files on other filesystems. As such, directory entries are found by reading the data block(s) associated with a directory file for the particular directory entry that is desired.

4.3.1. Linear (Classic) Directories

By default, each directory lists its entries in an “almost-linear” array. I write “almost” because it’s not a linear array in the memory sense because directory entries are not split across filesystem blocks. Therefore, it is more accurate to say that a directory is a series of data blocks and that each block contains a linear array of directory entries. The end of each per-block array is signified by reaching the end of the block; the last entry in the block has a record length that takes it all the way to the end of the block. The end of the entire directory is of course signified by reaching the end of the file. Unused directory entries are signified by inode = 0. By default the filesystem uses struct ext4_dir_entry_2 for directory entries unless the “filetype” feature flag is not set, in which case it uses struct ext4_dir_entry.

The original directory entry format is struct ext4_dir_entry, which is at most 263 bytes long, though on disk you’ll need to reference dirent.rec_len to know for sure.

Offset Size Name Description
0x0 __le32 inode Number of the inode that this directory entry points to.
0x4 __le16 rec_len Length of this directory entry. Must be a multiple of 4.
0x6 __le16 name_len Length of the file name.
0x8 char name[EXT4_NAME_LEN] File name.

Since file names cannot be longer than 255 bytes, the new directory entry format shortens the name_len field and uses the space for a file type flag, probably to avoid having to load every inode during directory tree traversal. This format is ext4_dir_entry_2, which is at most 263 bytes long, though on disk you’ll need to reference dirent.rec_len to know for sure.

Offset Size Name Description
0x0 __le32 inode Number of the inode that this directory entry points to.
0x4 __le16 rec_len Length of this directory entry.
0x6 __u8 name_len Length of the file name.
0x7 __u8 file_type File type code, see ftype table below.
0x8 char name[EXT4_NAME_LEN] File name.

The directory file type is one of the following values:

Value Description
0x0 Unknown.
0x1 Regular file.
0x2 Directory.
0x3 Character device file.
0x4 Block device file.
0x5 FIFO.
0x6 Socket.
0x7 Symbolic link.

In order to add checksums to these classic directory blocks, a phony struct ext4_dir_entry is placed at the end of each leaf block to hold the checksum. The directory entry is 12 bytes long. The inode number and name_len fields are set to zero to fool old software into ignoring an apparently empty directory entry, and the checksum is stored in the place where the name normally goes. The structure is struct ext4_dir_entry_tail:

Offset Size Name Description
0x0 __le32 det_reserved_zero1 Inode number, which must be zero.
0x4 __le16 det_rec_len Length of this directory entry, which must be 12.
0x6 __u8 det_reserved_zero2 Length of the file name, which must be zero.
0x7 __u8 det_reserved_ft File type, which must be 0xDE.
0x8 __le32 det_checksum Directory leaf block checksum.

The leaf directory block checksum is calculated against the FS UUID, the directory’s inode number, the directory’s inode generation number, and the entire directory entry block up to (but not including) the fake directory entry.

4.3.2. Hash Tree Directories

A linear array of directory entries isn’t great for performance, so a new feature was added to ext3 to provide a faster (but peculiar) balanced tree keyed off a hash of the directory entry name. If the EXT4_INDEX_FL (0x1000) flag is set in the inode, this directory uses a hashed btree (htree) to organize and find directory entries. For backwards read-only compatibility with ext2, this tree is actually hidden inside the directory file, masquerading as “empty” directory data blocks! It was stated previously that the end of the linear directory entry table was signified with an entry pointing to inode 0; this is (ab)used to fool the old linear-scan algorithm into thinking that the rest of the directory block is empty so that it moves on.

The root of the tree always lives in the first data block of the directory. By ext2 custom, the ‘.’ and ‘..’ entries must appear at the beginning of this first block, so they are put here as two struct ext4_dir_entry_2s and not stored in the tree. The rest of the root node contains metadata about the tree and finally a hash->block map to find nodes that are lower in the htree. If dx_root.info.indirect_levels is non-zero then the htree has two levels; the data block pointed to by the root node’s map is an interior node, which is indexed by a minor hash. Interior nodes in this tree contains a zeroed out struct ext4_dir_entry_2 followed by a minor_hash->block map to find leafe nodes. Leaf nodes contain a linear array of all struct ext4_dir_entry_2; all of these entries (presumably) hash to the same value. If there is an overflow, the entries simply overflow into the next leaf node, and the least-significant bit of the hash (in the interior node map) that gets us to this next leaf node is set.

To traverse the directory as a htree, the code calculates the hash of the desired file name and uses it to find the corresponding block number. If the tree is flat, the block is a linear array of directory entries that can be searched; otherwise, the minor hash of the file name is computed and used against this second block to find the corresponding third block number. That third block number will be a linear array of directory entries.

To traverse the directory as a linear array (such as the old code does), the code simply reads every data block in the directory. The blocks used for the htree will appear to have no entries (aside from ‘.’ and ‘..’) and so only the leaf nodes will appear to have any interesting content.

The root of the htree is in struct dx_root, which is the full length of a data block:

Offset Type Name Description
0x0 __le32 dot.inode inode number of this directory.
0x4 __le16 dot.rec_len Length of this record, 12.
0x6 u8 dot.name_len Length of the name, 1.
0x7 u8 dot.file_type File type of this entry, 0x2 (directory) (if the feature flag is set).
0x8 char dot.name[4] “.\0\0\0”
0xC __le32 dotdot.inode inode number of parent directory.
0x10 __le16 dotdot.rec_len block_size - 12. The record length is long enough to cover all htree data.
0x12 u8 dotdot.name_len Length of the name, 2.
0x13 u8 dotdot.file_type File type of this entry, 0x2 (directory) (if the feature flag is set).
0x14 char dotdot_name[4] “..\0\0”
0x18 __le32 struct dx_root_info.reserved_zero Zero.
0x1C u8 struct dx_root_info.hash_version Hash type, see dirhash table below.
0x1D u8 struct dx_root_info.info_length Length of the tree information, 0x8.
0x1E u8 struct dx_root_info.indirect_levels Depth of the htree. Cannot be larger than 3 if the INCOMPAT_LARGEDIR feature is set; cannot be larger than 2 otherwise.
0x1F u8 struct dx_root_info.unused_flags  
0x20 __le16 limit Maximum number of dx_entries that can follow this header, plus 1 for the header itself.
0x22 __le16 count Actual number of dx_entries that follow this header, plus 1 for the header itself.
0x24 __le32 block The block number (within the directory file) that goes with hash=0.
0x28 struct dx_entry entries[0] As many 8-byte struct dx_entry as fits in the rest of the data block.

The directory hash is one of the following values:

Value Description
0x0 Legacy.
0x1 Half MD4.
0x2 Tea.
0x3 Legacy, unsigned.
0x4 Half MD4, unsigned.
0x5 Tea, unsigned.

Interior nodes of an htree are recorded as struct dx_node, which is also the full length of a data block:

Offset Type Name Description
0x0 __le32 fake.inode Zero, to make it look like this entry is not in use.
0x4 __le16 fake.rec_len The size of the block, in order to hide all of the dx_node data.
0x6 u8 name_len Zero. There is no name for this “unused” directory entry.
0x7 u8 file_type Zero. There is no file type for this “unused” directory entry.
0x8 __le16 limit Maximum number of dx_entries that can follow this header, plus 1 for the header itself.
0xA __le16 count Actual number of dx_entries that follow this header, plus 1 for the header itself.
0xE __le32 block The block number (within the directory file) that goes with the lowest hash value of this block. This value is stored in the parent block.
0x12 struct dx_entry entries[0] As many 8-byte struct dx_entry as fits in the rest of the data block.

The hash maps that exist in both struct dx_root and struct dx_node are recorded as struct dx_entry, which is 8 bytes long:

Offset Type Name Description
0x0 __le32 hash Hash code.
0x4 __le32 block Block number (within the directory file, not filesystem blocks) of the next node in the htree.

(If you think this is all quite clever and peculiar, so does the author.)

If metadata checksums are enabled, the last 8 bytes of the directory block (precisely the length of one dx_entry) are used to store a struct dx_tail, which contains the checksum. The limit and count entries in the dx_root/dx_node structures are adjusted as necessary to fit the dx_tail into the block. If there is no space for the dx_tail, the user is notified to run e2fsck -D to rebuild the directory index (which will ensure that there’s space for the checksum. The dx_tail structure is 8 bytes long and looks like this:

Offset Type Name Description
0x0 u32 dt_reserved Zero.
0x4 __le32 dt_checksum Checksum of the htree directory block.

The checksum is calculated against the FS UUID, the htree index header (dx_root or dx_node), all of the htree indices (dx_entry) that are in use, and the tail block (dx_tail).

4.4. Extended Attributes

Extended attributes (xattrs) are typically stored in a separate data block on the disk and referenced from inodes via inode.i_file_acl*. The first use of extended attributes seems to have been for storing file ACLs and other security data (selinux). With the user_xattr mount option it is possible for users to store extended attributes so long as all attribute names begin with “user”; this restriction seems to have disappeared as of Linux 3.0.

There are two places where extended attributes can be found. The first place is between the end of each inode entry and the beginning of the next inode entry. For example, if inode.i_extra_isize = 28 and sb.inode_size = 256, then there are 256 - (128 + 28) = 100 bytes available for in-inode extended attribute storage. The second place where extended attributes can be found is in the block pointed to by inode.i_file_acl. As of Linux 3.11, it is not possible for this block to contain a pointer to a second extended attribute block (or even the remaining blocks of a cluster). In theory it is possible for each attribute’s value to be stored in a separate data block, though as of Linux 3.11 the code does not permit this.

Keys are generally assumed to be ASCIIZ strings, whereas values can be strings or binary data.

Extended attributes, when stored after the inode, have a header ext4_xattr_ibody_header that is 4 bytes long:

Offset Type Name Description
0x0 __le32 h_magic Magic number for identification, 0xEA020000. This value is set by the Linux driver, though e2fsprogs doesn’t seem to check it(?)

The beginning of an extended attribute block is in struct ext4_xattr_header, which is 32 bytes long:

Offset Type Name Description
0x0 __le32 h_magic Magic number for identification, 0xEA020000.
0x4 __le32 h_refcount Reference count.
0x8 __le32 h_blocks Number of disk blocks used.
0xC __le32 h_hash Hash value of all attributes.
0x10 __le32 h_checksum Checksum of the extended attribute block.
0x14 __u32 h_reserved[2] Zero.

The checksum is calculated against the FS UUID, the 64-bit block number of the extended attribute block, and the entire block (header + entries).

Following the struct ext4_xattr_header or struct ext4_xattr_ibody_header is an array of struct ext4_xattr_entry; each of these entries is at least 16 bytes long. When stored in an external block, the struct ext4_xattr_entry entries must be stored in sorted order. The sort order is e_name_index, then e_name_len, and finally e_name. Attributes stored inside an inode do not need be stored in sorted order.

Offset Type Name Description
0x0 __u8 e_name_len Length of name.
0x1 __u8 e_name_index Attribute name index. There is a discussion of this below.
0x2 __le16 e_value_offs Location of this attribute’s value on the disk block where it is stored. Multiple attributes can share the same value. For an inode attribute this value is relative to the start of the first entry; for a block this value is relative to the start of the block (i.e. the header).
0x4 __le32 e_value_inum The inode where the value is stored. Zero indicates the value is in the same block as this entry. This field is only used if the INCOMPAT_EA_INODE feature is enabled.
0x8 __le32 e_value_size Length of attribute value.
0xC __le32 e_hash Hash value of attribute name and attribute value. The kernel doesn’t update the hash for in-inode attributes, so for that case this value must be zero, because e2fsck validates any non-zero hash regardless of where the xattr lives.
0x10 char e_name[e_name_len] Attribute name. Does not include trailing NULL.

Attribute values can follow the end of the entry table. There appears to be a requirement that they be aligned to 4-byte boundaries. The values are stored starting at the end of the block and grow towards the xattr_header/xattr_entry table. When the two collide, the overflow is put into a separate disk block. If the disk block fills up, the filesystem returns -ENOSPC.

The first four fields of the ext4_xattr_entry are set to zero to mark the end of the key list.

4.4.1. Attribute Name Indices

Logically speaking, extended attributes are a series of key=value pairs. The keys are assumed to be NULL-terminated strings. To reduce the amount of on-disk space that the keys consume, the beginning of the key string is matched against the attribute name index. If a match is found, the attribute name index field is set, and matching string is removed from the key name. Here is a map of name index values to key prefixes:

Name Index Key Prefix
0 (no prefix)
1 “user.”
2 “system.posix_acl_access”
3 “system.posix_acl_default”
4 “trusted.”
6 “security.”
7 “system.” (inline_data only?)
8 “system.richacl” (SuSE kernels only?)

For example, if the attribute key is “user.fubar”, the attribute name index is set to 1 and the “fubar” name is recorded on disk.

4.4.2. POSIX ACLs

POSIX ACLs are stored in a reduced version of the Linux kernel (and libacl’s) internal ACL format. The key difference is that the version number is different (1) and the e_id field is only stored for named user and group ACLs.