Ext3 stores a file’s name separately from most of the information needed to manage it. The name belongs to a directory entry, while the file’s metadata and block mapping are kept in an inode. Understanding how these two structures work is essential when examining an Ext3 volume or attempting filesystem data recovery.
How Ext3 organizes inodes
The inode bitmap provides a quick record of which inodes in a block group are allocated and which are free. Each inode is represented by one bit. The actual inode data is stored in the inode table, which consists of a large number of consecutive inode structures.
In Ext3, an inode stores almost everything associated with a file except its name. Inode tables are located after the inode bitmap, and every block group has its own inode table. Each regular file and each directory uses one inode.

Information held in an inode includes the file’s link count, size, timestamps, owning user ID, owning group ID, and pointers to the blocks containing the file’s data. These values are commonly referred to as metadata.
The inode table begins in the block immediately following the inode bitmap. The superblock records the total number of inodes in the filesystem, the number of inodes in each block group, and the inode size.

0x00–0x03</td>
<td style="text-align: right;">4 bytes</td>
<td>Total number of inodes</td>
</tr>
<tr>
<td>0x28–0x2B</td>
<td style="text-align: right;">4 bytes</td>
<td>Number of inodes per block group</td>
</tr>
<tr>
<td>0x58–0x59</td>
<td style="text-align: right;">2 bytes</td>
<td>Inode size</td>
</tr>
</tbody>
</table>
The traditional Ext3 inode size is 128 bytes. When the superblock identifies the filesystem as using the dynamic revision, the actual size is taken from offsets 0x58–0x59. This makes it possible to use larger inodes.
A larger inode can hold extended attributes directly, which can improve workloads that make extensive use of such attributes, including SELinux or Samba v4. Some Ext4 capabilities also require inode sizes of at least 256 bytes. For this reason, Ext3 filesystems created with later migration to Ext4 in mind are often formatted with 256-byte inodes, avoiding the need to recreate the filesystem solely to change the inode size.
Every inode has a number, beginning with inode 1. Inodes 1 through 10 are reserved by the filesystem. The superblock records the first non-reserved inode, which is commonly inode 11. These first ten inodes are marked as allocated in the inode bitmap. In general:
- Inode 1 is used to describe bad blocks.
- Inode 2 belongs to the root directory.
- Inode 8 is normally associated with the journal.
Once an inode number and the number of inodes per block group are known, the block group containing that inode can be calculated as:
(inode number - 1) / inodes per block group
Ext3 inode layout

The standard 128-byte portion of an Ext3 inode is organized as follows:
<table> <thead> <tr> <th>Byte offset</th> <th style="text-align: right;">Length</th> <th>Field</th> </tr> </thead> <tbody> <tr> <td>0x00–0x01</td>
<td style="text-align: right;">2 bytes</td>
<td>File mode</td>
</tr>
<tr>
<td>0x02–0x03</td>
<td style="text-align: right;">2 bytes</td>
<td>Lower 16 bits of user UID</td>
</tr>
<tr>
<td>0x04–0x07</td>
<td style="text-align: right;">4 bytes</td>
<td>Lower 32 bits of size in bytes</td>
</tr>
<tr>
<td>0x08–0x0B</td>
<td style="text-align: right;">4 bytes</td>
<td>Last access time</td>
</tr>
<tr>
<td>0x0C–0x0F</td>
<td style="text-align: right;">4 bytes</td>
<td>Inode change time</td>
</tr>
<tr>
<td>0x10–0x13</td>
<td style="text-align: right;">4 bytes</td>
<td>Last modification time</td>
</tr>
<tr>
<td>0x14–0x17</td>
<td style="text-align: right;">4 bytes</td>
<td>Deletion time</td>
</tr>
<tr>
<td>0x18–0x19</td>
<td style="text-align: right;">2 bytes</td>
<td>Lower 16 bits of group ID</td>
</tr>
<tr>
<td>0x1A–0x1B</td>
<td style="text-align: right;">2 bytes</td>
<td>Hard-link count</td>
</tr>
<tr>
<td>0x1C–0x1F</td>
<td style="text-align: right;">4 bytes</td>
<td>Number of sectors occupied, using 512-byte sectors</td>
</tr>
<tr>
<td>0x20–0x23</td>
<td style="text-align: right;">4 bytes</td>
<td>Flags</td>
</tr>
<tr>
<td>0x24–0x27</td>
<td style="text-align: right;">4 bytes</td>
<td>Unused</td>
</tr>
<tr>
<td>0x28–0x2B</td>
<td style="text-align: right;">4 bytes</td>
<td>Direct block pointer 1</td>
</tr>
<tr>
<td>0x2C–0x2F</td>
<td style="text-align: right;">4 bytes</td>
<td>Direct block pointer 2</td>
</tr>
<tr>
<td>0x30–0x33</td>
<td style="text-align: right;">4 bytes</td>
<td>Direct block pointer 3</td>
</tr>
<tr>
<td>0x34–0x37</td>
<td style="text-align: right;">4 bytes</td>
<td>Direct block pointer 4</td>
</tr>
<tr>
<td>0x38–0x3B</td>
<td style="text-align: right;">4 bytes</td>
<td>Direct block pointer 5</td>
</tr>
<tr>
<td>0x3C–0x3F</td>
<td style="text-align: right;">4 bytes</td>
<td>Direct block pointer 6</td>
</tr>
<tr>
<td>0x40–0x43</td>
<td style="text-align: right;">4 bytes</td>
<td>Direct block pointer 7</td>
</tr>
<tr>
<td>0x44–0x47</td>
<td style="text-align: right;">4 bytes</td>
<td>Direct block pointer 8</td>
</tr>
<tr>
<td>0x48–0x4B</td>
<td style="text-align: right;">4 bytes</td>
<td>Direct block pointer 9</td>
</tr>
<tr>
<td>0x4C–0x4F</td>
<td style="text-align: right;">4 bytes</td>
<td>Direct block pointer 10</td>
</tr>
<tr>
<td>0x50–0x53</td>
<td style="text-align: right;">4 bytes</td>
<td>Direct block pointer 11</td>
</tr>
<tr>
<td>0x54–0x57</td>
<td style="text-align: right;">4 bytes</td>
<td>Direct block pointer 12</td>
</tr>
<tr>
<td>0x58–0x5B</td>
<td style="text-align: right;">4 bytes</td>
<td>Indirect block pointer</td>
</tr>
<tr>
<td>0x5C–0x5F</td>
<td style="text-align: right;">4 bytes</td>
<td>Double-indirect block pointer</td>
</tr>
<tr>
<td>0x60–0x63</td>
<td style="text-align: right;">4 bytes</td>
<td>Triple-indirect block pointer</td>
</tr>
<tr>
<td>0x64–0x67</td>
<td style="text-align: right;">4 bytes</td>
<td>File version, used by NFS</td>
</tr>
<tr>
<td>0x68–0x6B</td>
<td style="text-align: right;">4 bytes or more</td>
<td>Lower 32 bits of extended-attribute block address</td>
</tr>
<tr>
<td>0x6C–0x6F</td>
<td style="text-align: right;">4 bytes</td>
<td>Upper 32 bits of size in bytes</td>
</tr>
<tr>
<td>0x70–0x73</td>
<td style="text-align: right;">4 bytes</td>
<td>Fragment data block address</td>
</tr>
<tr>
<td>0x74</td>
<td style="text-align: right;">1 byte</td>
<td>Fragment index</td>
</tr>
<tr>
<td>0x75</td>
<td style="text-align: right;">1 byte</td>
<td>Fragment size</td>
</tr>
<tr>
<td>0x76–0x77</td>
<td style="text-align: right;">2 bytes</td>
<td>Unused</td>
</tr>
<tr>
<td>0x78–0x79</td>
<td style="text-align: right;">2 bytes</td>
<td>Upper 16 bits of UID</td>
</tr>
<tr>
<td>0x7A–0x7B</td>
<td style="text-align: right;">2 bytes</td>
<td>Upper 16 bits of GID</td>
</tr>
<tr>
<td>0x7C–0x7F</td>
<td style="text-align: right;">4 bytes</td>
<td>Unused</td>
</tr>
</tbody>
</table>
When the inode is larger than 128 bytes, the additional area can contain extended attributes and higher-precision or extended timestamp information:
<table> <thead> <tr> <th>Byte offset</th> <th style="text-align: right;">Length</th> <th>Field</th> </tr> </thead> <tbody> <tr> <td>0x80–0xFF</td>
<td style="text-align: right;">128 bytes</td>
<td>Additional attribute area</td>
</tr>
<tr>
<td>0x80–0x81</td>
<td style="text-align: right;">2 bytes</td>
<td>Size of this attribute section</td>
</tr>
<tr>
<td>0x82–0x83</td>
<td style="text-align: right;">2 bytes</td>
<td>Upper 16 bits of the inode checksum</td>
</tr>
<tr>
<td>0x84–0x87</td>
<td style="text-align: right;">4 bytes</td>
<td>Inode change time with subsecond precision</td>
</tr>
<tr>
<td>0x88–0x8B</td>
<td style="text-align: right;">4 bytes</td>
<td>Modification time with subsecond precision</td>
</tr>
<tr>
<td>0x8C–0x8F</td>
<td style="text-align: right;">4 bytes</td>
<td>Access time with subsecond precision</td>
</tr>
<tr>
<td>0x90–0x93</td>
<td style="text-align: right;">4 bytes</td>
<td>File creation time</td>
</tr>
<tr>
<td>0x94–0x97</td>
<td style="text-align: right;">4 bytes</td>
<td>Subsecond portion of creation time</td>
</tr>
<tr>
<td>0x98–0x9B</td>
<td style="text-align: right;">4 bytes</td>
<td>Upper 32 bits of the version number</td>
</tr>
<tr>
<td>0x9C–0x9F</td>
<td style="text-align: right;">4 bytes</td>
<td>Project ID</td>
</tr>
<tr>
<td>0xA0–0xFF</td>
<td style="text-align: right;">96 bytes</td>
<td>Unused, available for other extended attributes</td>
</tr>
</tbody>
</table>
Interpreting the inode fields
File mode: 0x00–0x01
The i_mode field occupies two bytes. Its 16 bits have three roles. Bits 0 through 8 describe permissions, bits 9 through 11 define special behavior for executable files and directories, and bits 12 through 15 identify the file type.
Linux permissions are applied to three classes of users: the owner recorded by the inode’s UID, members of the group recorded by its GID, and everyone else. Each class can have read, write, and execute permission.
The common permission and special-mode values are:
<table> <thead> <tr> <th>Value</th> <th>Meaning</th> </tr> </thead> <tbody> <tr> <td>0x1</td>
<td>S_IXOTH, execute permission for others</td>
</tr>
<tr>
<td>0x2</td>
<td>S_IWOTH, write permission for others</td>
</tr>
<tr>
<td>0x4</td>
<td>S_IROTH, read permission for others</td>
</tr>
<tr>
<td>0x8</td>
<td>S_IXGRP, execute permission for the group</td>
</tr>
<tr>
<td>0x10</td>
<td>S_IWGRP, write permission for the group</td>
</tr>
<tr>
<td>0x20</td>
<td>S_IRGRP, read permission for the group</td>
</tr>
<tr>
<td>0x40</td>
<td>S_IXUSR, execute permission for the owner</td>
</tr>
<tr>
<td>0x80</td>
<td>S_IWUSR, write permission for the owner</td>
</tr>
<tr>
<td>0x100</td>
<td>S_IRUSR, read permission for the owner</td>
</tr>
<tr>
<td>0x200</td>
<td>S_ISVTX, sticky bit</td>
</tr>
<tr>
<td>0x400</td>
<td>S_ISGID, set-group-ID</td>
</tr>
<tr>
<td>0x800</td>
<td>S_ISUID, set-user-ID</td>
</tr>
</tbody>
</table>
The file-type values are mutually exclusive rather than combinable:
<table> <thead> <tr> <th>Value</th> <th>Meaning</th> </tr> </thead> <tbody> <tr> <td>0x1000</td>
<td>S_IFIFO, FIFO or pipe</td>
</tr>
<tr>
<td>0x2000</td>
<td>S_IFCHR, character device</td>
</tr>
<tr>
<td>0x4000</td>
<td>S_IFDIR, directory</td>
</tr>
<tr>
<td>0x6000</td>
<td>S_IFBLK, block device</td>
</tr>
<tr>
<td>0x8000</td>
<td>S_IFREG, regular file</td>
</tr>
<tr>
<td>0xA000</td>
<td>S_IFLNK, symbolic link</td>
</tr>
<tr>
<td>0xC000</td>
<td>S_IFSOCK, socket</td>
</tr>
</tbody>
</table>
A FIFO, also called a pipe, transfers data between processes. One process can open it to receive information written by another process; the data is held in memory rather than stored on disk.
A character device, sometimes called a raw device, is used for devices that do not need to be read one whole block at a time, such as a keyboard. Block devices, in contrast, are accessed in block-sized units, as with a hard disk. Reading from a disk requires at least one sector to be read, even when a program requests less data; the operating system then returns only the requested portion.
A symbolic link is a special file that points to another file or directory and acts as a filesystem-level shortcut. A socket provides bidirectional communication between processes. Like a named pipe, socket data is not stored on disk as ordinary file content.
Ownership, size, and timestamps
The fields at 0x02–0x03 and 0x18–0x19 hold the lower 16 bits of the UID and GID. Their upper portions are stored later in the inode at 0x78–0x79 and 0x7A–0x7B.
The size field at 0x04–0x07 contains the lower 32 bits of the byte count. For a regular file, this is the file size. For a directory, it represents the size of the directory’s data area. Ext and Ext2 use a 32-bit size field and therefore have a nominal 4 GB limit. Ext3 extends the size to 64 bits by storing the upper 32 bits at 0x6C–0x6F.
Four traditional timestamp fields are present:
0x08–0x0B: last access time0x0C–0x0F: inode change time0x10–0x13: last modification time0x14–0x17: deletion time
These timestamps are measured from midnight on January 1, 1970, using Greenwich Mean Time in the traditional representation. The original 32-bit format reaches its upper limit on January 18, 2038. Larger inodes provide additional attribute space for extended timestamp information, including subsecond precision and creation time.
When a file is created, its access time, inode change time, and modification time are initially set to the creation time. The deletion-time field remains unset until the file is removed.
The hard-link count at 0x1A–0x1B records how many directory entries refer to the inode. The sector-count field at 0x1C–0x1F records the number of 512-byte sectors allocated to the file.
Inode flags
The four-byte flag field at 0x20–0x23 records additional file attributes. The defined values include the following:
0x1</td>
<td>Secure deletion requested (EXT4_SECRM_FL, not implemented)</td>
</tr>
<tr>
<td>0x2</td>
<td>Undelete protection (EXT4_UNRM_FL, not implemented)</td>
</tr>
<tr>
<td>0x4</td>
<td>File compressed (EXT4_COMPR_FL, not fully implemented)</td>
</tr>
<tr>
<td>0x8</td>
<td>All writes must be synchronous (EXT4_SYNC_FL)</td>
</tr>
<tr>
<td>0x10</td>
<td>Immutable file (EXT4_IMMUTABLE_FL)</td>
</tr>
<tr>
<td>0x20</td>
<td>Append-only file (EXT4_APPEND_FL)</td>
</tr>
<tr>
<td>0x40</td>
<td>Do not include in dump output (EXT4_NODUMP_FL)</td>
</tr>
<tr>
<td>0x80</td>
<td>Do not update access time (EXT4_NOATIME_FL)</td>
</tr>
<tr>
<td>0x100</td>
<td>Dirty compressed file (EXT4_DIRTY_FL, unused)</td>
</tr>
<tr>
<td>0x200</td>
<td>File has compressed clusters (EXT4_COMPRBLK_FL, unused)</td>
</tr>
<tr>
<td>0x400</td>
<td>File must not be compressed (EXT4_NOCOMPR_FL, unused)</td>
</tr>
<tr>
<td>0x800</td>
<td>Encrypted inode (EXT4_ENCRYPT_FL)</td>
</tr>
<tr>
<td>0x1000</td>
<td>Directory uses a hash index (EXT4_INDEX_FL)</td>
</tr>
<tr>
<td>0x2000</td>
<td>AFS magic directory (EXT4_IMAGIC_FL)</td>
</tr>
<tr>
<td>0x4000</td>
<td>File data must always be journaled (EXT4_JOURNAL_DATA_FL)</td>
</tr>
<tr>
<td>0x8000</td>
<td>Do not merge the file tail (EXT4_NOTAIL_FL, unused in Ext4)</td>
</tr>
<tr>
<td>0x10000</td>
<td>Directory entries must be written synchronously (EXT4_DIRSYNC_FL)</td>
</tr>
<tr>
<td>0x20000</td>
<td>Top-level directory (EXT4_TOPDIR_FL)</td>
</tr>
<tr>
<td>0x40000</td>
<td>Very large file (EXT4_HUGE_FILE_FL)</td>
</tr>
<tr>
<td>0x80000</td>
<td>Inode uses extents (EXT4_EXTENTS_FL)</td>
</tr>
<tr>
<td>0x100000</td>
<td>File protected by verity (EXT4_VERITY_FL)</td>
</tr>
<tr>
<td>0x200000</td>
<td>Inode stores large extended-attribute values in data blocks (EXT4_EA_INODE_FL)</td>
</tr>
<tr>
<td>0x400000</td>
<td>Blocks allocated after EOF (EXT4_EOFBLOCKS_FL, deprecated)</td>
</tr>
<tr>
<td>0x01000000</td>
<td>Inode is a snapshot (EXT4_SNAPFILE_FL, not in mainline)</td>
</tr>
<tr>
<td>0x04000000</td>
<td>Snapshot is being deleted (EXT4_SNAPFILE_DELETED_FL, not in mainline)</td>
</tr>
<tr>
<td>0x08000000</td>
<td>Snapshot shrinking is complete (EXT4_SNAPFILE_SHRUNK_FL, not in mainline)</td>
</tr>
<tr>
<td>0x10000000</td>
<td>Inode contains inline data (EXT4_INLINE_DATA_FL)</td>
</tr>
<tr>
<td>0x20000000</td>
<td>Child objects inherit the same project ID (EXT4_PROJINHERIT_FL)</td>
</tr>
<tr>
<td>0x80000000</td>
<td>Reserved for the Ext4 library (EXT4_RESERVED_FL)</td>
</tr>
</tbody>
</table>
Two aggregate masks are also defined:
0x705BDFFF: flags visible to users0x604BC0FF: flags users may modify
The four bytes at 0x24–0x27 are unused in the basic inode layout.
Direct and indirect block pointers
The block-pointer region runs from 0x28 through 0x63. The first twelve entries are direct pointers. They point straight to blocks containing file data, so i_block[0–11] can be followed directly to locate those blocks.
If a file needs more than twelve data blocks, the thirteenth pointer is an indirect pointer. It points to a block containing additional block addresses rather than file data itself. The fourteenth pointer is a double-indirect pointer: its target contains pointers to indirect blocks. The fifteenth is a triple-indirect pointer, whose target contains double-indirect pointers.

This layered arrangement lets a relatively small inode address files much larger than the twelve directly referenced blocks, while keeping small files efficient to access.
The remaining fields in the traditional inode are:
0x64–0x67: file version, used by NFS0x68–0x6B: lower 32 bits of the extended-attribute block address; ACL information is one possible use of this area0x6C–0x6F: upper 32 bits of the file size0x70–0x73: fragment data-block address0x74: fragment index0x75: fragment size0x76–0x77: unused0x78–0x79: upper 16 bits of the UID0x7A–0x7B: upper 16 bits of the GID0x7C–0x7F: unused
Directory entries in Ext3
An inode does not normally contain the file name. Names are kept in directory entries, which are stored in the data blocks allocated to a directory. A directory’s inode contains the block pointers needed to locate those blocks.
Each directory entry records an inode number, the length of the entry, the length of the name, the file type, and the name itself.

Any padding bytes after the name are not included in the recorded file-name length.
<table> <thead> <tr> <th>Byte offset</th> <th style="text-align: right;">Length</th> <th>Field</th> </tr> </thead> <tbody> <tr> <td>0x00–0x03</td>
<td style="text-align: right;">4 bytes</td>
<td>Inode number</td>
</tr>
<tr>
<td>0x04–0x05</td>
<td style="text-align: right;">2 bytes</td>
<td>Directory-entry length</td>
</tr>
<tr>
<td>0x06</td>
<td style="text-align: right;">1 byte</td>
<td>File-name length</td>
</tr>
<tr>
<td>0x07</td>
<td style="text-align: right;">1 byte</td>
<td>File type</td>
</tr>
<tr>
<td>0x08–</td>
<td style="text-align: right;">Variable</td>
<td>File name</td>
</tr>
</tbody>
</table>
The file-type field uses values such as:
<table> <thead> <tr> <th>Type value</th> <th>Meaning</th> </tr> </thead> <tbody> <tr> <td>0x01</td>
<td>Regular file</td>
</tr>
<tr>
<td>0x02</td>
<td>Directory</td>
</tr>
<tr>
<td>0x07</td>
<td>Symbolic link</td>
</tr>
</tbody>
</table>
Ext3 directory entries have several properties that matter when parsing raw data:
- They are stored in the directory’s data area, whose location is reached through the directory inode’s block pointers.
- Their size is variable because it depends on the file-name length. A name can be up to 255 characters and is stored using ASCII in this layout.
- Although entries are variable-length, each entry occupies a multiple of four bytes. Every entry therefore begins at a directory-relative offset divisible by four.
- If the name does not end on a four-byte boundary, zero padding is added after the name until the boundary is reached. These padding bytes are not part of the name length.
- The first two entries in a directory are normally
.and... The first refers to the current directory, while the second refers to its parent. - The length field of an entry identifies where the next entry begins. For the final entry in a block, its length extends to the end of that block rather than merely covering the visible name and fixed fields.
Consequently, recovering a directory listing requires more than searching for printable names. The parser must read the inode number, honor the recorded entry length, distinguish the name length from alignment padding, identify the file type, and stop at the end of the directory block. Once the directory entry supplies an inode number, the inode can then be examined to determine ownership, timestamps, size, flags, and the blocks containing the file’s data.