4. 動態結構

動態元資料是在將檔案和塊分配給檔案時即時建立的。

4.1. 索引節點

在常規的 UNIX 檔案系統中,inode 儲存與檔案相關的所有元資料(時間戳、塊對映、擴充套件屬性等),而不是目錄條目。要查詢與檔案關聯的資訊,必須遍歷目錄檔案以查詢與檔案關聯的目錄條目,然後載入 inode 以查詢該檔案的元資料。ext4 似乎透過在目錄條目中儲存檔案型別的副本(通常儲存在 inode 中)來作弊(出於效能原因)。(將所有這些與 FAT 進行比較,FAT 將所有檔案資訊直接儲存在目錄條目中,但不支援硬連結,並且由於其更簡單的塊分配器和對連結串列的廣泛使用,通常比 ext4 更容易進行尋道。)

inode 表是 struct ext4_inode 的線性陣列。表的大小設定為具有足夠的塊來儲存至少 sb.s_inode_size * sb.s_inodes_per_group 位元組。包含 inode 的塊組的編號可以計算為 (inode_number - 1) / sb.s_inodes_per_group,並且組的表中的偏移量為 (inode_number - 1) % sb.s_inodes_per_group。沒有 inode 0。

inode 校驗和是根據 FS UUID、inode 編號和 inode 結構本身計算的。

inode 表條目以 struct ext4_inode 形式佈局。

偏移量

大小

名稱

描述

0x0

__le16

i_mode

檔案模式。請參閱下面的 i_mode 表。

0x2

__le16

i_uid

所有者 UID 的低 16 位。

0x4

__le32

i_size_lo

以位元組為單位的大小的低 32 位。

0x8

__le32

i_atime

上次訪問時間,以自 epoch 以來的秒數為單位。但是,如果設定了 EA_INODE inode 標誌,則此 inode 儲存擴充套件屬性值,並且此欄位包含該值的校驗和。

0xC

__le32

i_ctime

上次 inode 更改時間,以自 epoch 以來的秒數為單位。但是,如果設定了 EA_INODE inode 標誌,則此 inode 儲存擴充套件屬性值,並且此欄位包含屬性值的引用計數的低 32 位。

0x10

__le32

i_mtime

上次資料修改時間,以自 epoch 以來的秒數為單位。但是,如果設定了 EA_INODE inode 標誌,則此 inode 儲存擴充套件屬性值,並且此欄位包含擁有擴充套件屬性的 inode 的編號。

0x14

__le32

i_dtime

刪除時間,以自 epoch 以來的秒數為單位。

0x18

__le16

i_gid

GID 的低 16 位。

0x1A

__le16

i_links_count

硬連結計數。通常,ext4 不允許 inode 具有超過 65,000 個硬連結。這適用於檔案和目錄,這意味著一個目錄中不能有超過 64,998 個子目錄(每個子目錄的“..”條目計為一個硬連結,目錄本身的“.”條目也計為一個硬連結)。啟用 DIR_NLINK 功能後,ext4 透過將此欄位設定為 1 來指示硬連結的數量未知,從而支援超過 64,998 個子目錄。

0x1C

__le32

i_blocks_lo

“塊”計數的低 32 位。如果在檔案系統上未設定 huge_file 功能標誌,則該檔案在磁碟上佔用 i_blocks_lo 512 位元組的塊。如果設定了 huge_file 並且在 inode.i_flags 中未設定 EXT4_HUGE_FILE_FL,則該檔案在磁碟上佔用 i_blocks_lo + (i_blocks_hi << 32) 512 位元組的塊。如果設定了 huge_file 並且在 inode.i_flags 中設定了 EXT4_HUGE_FILE_FL,則該檔案在磁碟上佔用 (i_blocks_lo + i_blocks_hi << 32) 檔案系統塊。

0x20

__le32

i_flags

Inode 標誌。請參閱下面的 i_flags 表。

0x24

4 位元組

i_osd1

有關更多詳細資訊,請參見 i_osd1 表。

0x28

60 位元組

i_block[EXT4_N_BLOCKS=15]

塊對映或 extent 樹。請參閱“inode.i_block 的內容”部分。

0x64

__le32

i_generation

檔案版本(對於 NFS)。

0x68

__le32

i_file_acl_lo

擴充套件屬性塊的低 32 位。當然,ACL 只是許多可能的擴充套件屬性之一;我認為此欄位的名稱是擴充套件屬性的第一個用途是用於 ACL 的結果。

0x6C

__le32

i_size_high / i_dir_acl

檔案/目錄大小的高 32 位。在 ext2/3 中,此欄位名為 i_dir_acl,儘管它通常設定為零並且從未使用過。

0x70

__le32

i_obso_faddr

(已過時)片段地址。

0x74

12 位元組

i_osd2

有關更多詳細資訊,請參見 i_osd2 表。

0x80

__le16

i_extra_isize

此 inode 的大小 - 128。或者,原始 ext2 inode 之外的擴充套件 inode 欄位的大小,包括此欄位。

0x82

__le16

i_checksum_hi

inode 校驗和的高 16 位。

0x84

__le32

i_ctime_extra

額外的更改時間位。這提供了亞秒級的精度。請參閱 Inode 時間戳部分。

0x88

__le32

i_mtime_extra

額外的修改時間位。這提供了亞秒級的精度。

0x8C

__le32

i_atime_extra

額外的訪問時間位。這提供了亞秒級的精度。

0x90

__le32

i_crtime

檔案建立時間,以自 epoch 以來的秒數為單位。

0x94

__le32

i_crtime_extra

額外的檔案建立時間位。這提供了亞秒級的精度。

0x98

__le32

i_version_hi

版本號的高 32 位。

0x9C

__le32

i_projid

專案 ID。

i_mode 值是以下標誌的組合

描述

0x1

S_IXOTH(其他人可以執行)

0x2

S_IWOTH(其他人可以寫入)

0x4

S_IROTH(其他人可以讀取)

0x8

S_IXGRP(組成員可以執行)

0x10

S_IWGRP(組成員可以寫入)

0x20

S_IRGRP(組成員可以讀取)

0x40

S_IXUSR(所有者可以執行)

0x80

S_IWUSR(所有者可以寫入)

0x100

S_IRUSR(所有者可以讀取)

0x200

S_ISVTX(粘滯位)

0x400

S_ISGID(設定 GID)

0x800

S_ISUID(設定 UID)

這些是互斥的檔案型別

0x1000

S_IFIFO(FIFO)

0x2000

S_IFCHR(字元裝置)

0x4000

S_IFDIR(目錄)

0x6000

S_IFBLK(塊裝置)

0x8000

S_IFREG(常規檔案)

0xA000

S_IFLNK(符號連結)

0xC000

S_IFSOCK(套接字)

i_flags 欄位是這些值的組合

描述

0x1

此檔案需要安全刪除 (EXT4_SECRM_FL)。(未實現)

0x2

如果需要取消刪除,則應保留此檔案 (EXT4_UNRM_FL)。(未實現)

0x4

檔案已壓縮 (EXT4_COMPR_FL)。(未真正實現)

0x8

對檔案的所有寫入必須是同步的 (EXT4_SYNC_FL)。

0x10

檔案是不可變的 (EXT4_IMMUTABLE_FL)。

0x20

檔案只能追加 (EXT4_APPEND_FL)。

0x40

dump(1) 實用程式不應轉儲此檔案 (EXT4_NODUMP_FL)。

0x80

不要更新訪問時間 (EXT4_NOATIME_FL)。

0x100

髒壓縮檔案 (EXT4_DIRTY_FL)。 (未使用)

0x200

檔案具有一個或多個壓縮簇 (EXT4_COMPRBLK_FL)。 (未使用)

0x400

不要壓縮檔案 (EXT4_NOCOMPR_FL)。 (未使用)

0x800

加密的 inode (EXT4_ENCRYPT_FL)。此位值以前是 EXT4_ECOMPR_FL(壓縮錯誤),從未被使用過。

0x1000

目錄具有雜湊索引 (EXT4_INDEX_FL)。

0x2000

AFS 魔術目錄 (EXT4_IMAGIC_FL)。

0x4000

檔案資料必須始終透過日誌寫入 (EXT4_JOURNAL_DATA_FL)。

0x8000

不應合併檔案尾部 (EXT4_NOTAIL_FL)。 (ext4 未使用)

0x10000

所有目錄條目資料都應同步寫入(請參見 dirsync)(EXT4_DIRSYNC_FL)。

0x20000

目錄層次結構的頂部 (EXT4_TOPDIR_FL)。

0x40000

這是一個巨大的檔案 (EXT4_HUGE_FILE_FL)。

0x80000

Inode 使用 extent (EXT4_EXTENTS_FL)。

0x100000

Verity 保護的檔案 (EXT4_VERITY_FL)。

0x200000

Inode 在其資料塊中儲存一個大的擴充套件屬性值 (EXT4_EA_INODE_FL)。

0x400000

此檔案已在 EOF 之後分配了塊 (EXT4_EOFBLOCKS_FL)。 (已棄用)

0x01000000

Inode 是快照 (EXT4_SNAPFILE_FL)。 (不在主線中)

0x04000000

正在刪除快照 (EXT4_SNAPFILE_DELETED_FL)。 (不在主線中)

0x08000000

快照收縮已完成 (EXT4_SNAPFILE_SHRUNK_FL)。 (不在主線中)

0x10000000

Inode 具有內聯資料 (EXT4_INLINE_DATA_FL)。

0x20000000

使用相同的專案 ID 建立子級 (EXT4_PROJINHERIT_FL)。

0x80000000

為 ext4 庫保留 (EXT4_RESERVED_FL)。

聚合標誌

0x705BDFFF

使用者可見的標誌。

0x604BC0FF

使用者可修改的標誌。請注意,雖然可以使用 setattr 設定 EXT4_JOURNAL_DATA_FL 和 EXT4_EXTENTS_FL,但它們不在核心的 EXT4_FL_USER_MODIFIABLE 掩碼中,因為它需要以特殊方式處理這些標誌的設定,並且它們已從直接儲存到 i_flags 的標誌集中遮蔽掉。

osd1 欄位具有多個含義,具體取決於建立者

Linux

偏移量

大小

名稱

描述

0x0

__le32

l_i_version

Inode 版本。但是,如果設定了 EA_INODE inode 標誌,則此 inode 儲存擴充套件屬性值,並且此欄位包含屬性值的引用計數的高 32 位。

Hurd

偏移量

大小

名稱

描述

0x0

__le32

h_i_translator

??

Masix

偏移量

大小

名稱

描述

0x0

__le32

m_i_reserved

??

osd2 欄位具有多個含義,具體取決於檔案系統建立者

Linux

偏移量

大小

名稱

描述

0x0

__le16

l_i_blocks_high

塊計數的高 16 位。請參閱附加到 i_blocks_lo 的註釋。

0x2

__le16

l_i_file_acl_high

擴充套件屬性塊的高 16 位(歷史上是檔案 ACL 位置)。請參見下面的擴充套件屬性部分。

0x4

__le16

l_i_uid_high

所有者 UID 的高 16 位。

0x6

__le16

l_i_gid_high

GID 的高 16 位。

0x8

__le16

l_i_checksum_lo

inode 校驗和的低 16 位。

0xA

__le16

l_i_reserved

未使用。

Hurd

偏移量

大小

名稱

描述

0x0

__le16

h_i_reserved1

??

0x2

__u16

h_i_mode_high

檔案模式的高 16 位。

0x4

__le16

h_i_uid_high

所有者 UID 的高 16 位。

0x6

__le16

h_i_gid_high

GID 的高 16 位。

0x8

__u32

h_i_author

作者程式碼?

Masix

偏移量

大小

名稱

描述

0x0

__le16

h_i_reserved1

??

0x2

__u16

m_i_file_acl_high

擴充套件屬性塊的高 16 位(歷史上是檔案 ACL 位置)。

0x4

__u32

m_i_reserved2[2]

??

4.1.1. Inode 大小

在 ext2 和 ext3 中,inode 結構大小固定為 128 位元組 (EXT2_GOOD_OLD_INODE_SIZE),並且每個 inode 的磁碟記錄大小為 128 位元組。從 ext4 開始,可以在格式化時為檔案系統中的所有 inode 分配一個更大的磁碟 inode,以便在原始 ext2 inode 的末尾之外提供空間。磁碟上的 inode 記錄大小記錄在超級塊中,為 s_inode_size。struct ext4_inode 實際使用的位元組數超過原始 128 位元組的 ext2 inode 記錄在每個 inode 的 i_extra_isize 欄位中,這允許 struct ext4_inode 為新核心增長,而無需升級所有磁碟上的 inode。對 EXT2_GOOD_OLD_INODE_SIZE 之外的欄位的訪問應驗證是否在 i_extra_isize 範圍內。預設情況下,ext4 inode 記錄為 256 位元組,並且(截至 2019 年 8 月)inode 結構為 160 位元組 (i_extra_isize = 32)。inode 結構末尾和 inode 記錄末尾之間的額外空間可用於儲存擴充套件屬性。每個 inode 記錄可以與檔案系統塊大小一樣大,儘管這效率不高。

4.1.2. 查詢 Inode

每個塊組包含 sb->s_inodes_per_group 個 inode。因為 inode 0 被定義為不存在,所以可以使用以下公式來查詢 inode 所在的塊組:bg = (inode_num - 1) / sb->s_inodes_per_group。可以在塊組的 inode 表中找到特定的 inode,其位置為 index = (inode_num - 1) % sb->s_inodes_per_group。要獲取 inode 表中的位元組地址,請使用 offset = index * sb->s_inode_size

4.1.3. Inode 時間戳

在 inode 結構的低 128 位元組中記錄了四個時間戳——inode 更改時間 (ctime)、訪問時間 (atime)、資料修改時間 (mtime) 和刪除時間 (dtime)。這四個欄位是 32 位有符號整數,表示自 Unix epoch(1970-01-01 00:00:00 GMT)以來的秒數,這意味著這些欄位將在 2038 年 1 月溢位。如果檔案系統沒有 orphan_file 功能,則未從任何目錄連結但仍處於開啟狀態的 inode(孤立 inode)會將 dtime 欄位過載以用於孤立列表。超級塊欄位 s_last_orphan 指向孤立列表中的第一個 inode;然後 dtime 是下一個孤立 inode 的編號,如果沒有更多孤立 inode,則為零。

如果 inode 結構大小 sb->s_inode_size 大於 128 位元組並且 i_inode_extra 欄位足夠大以包含相應的 i_[cma]time_extra 欄位,則 ctime、atime 和 mtime inode 欄位將擴充套件為 64 位。在這個“extra” 32 位欄位中,較低的兩位用於將 32 位秒欄位擴充套件為 34 位寬;較高的 30 位用於提供納秒級的時間戳精度。因此,時間戳直到 2446 年 5 月才會溢位。dtime 沒有擴充套件。還有一個第五個時間戳來記錄 inode 建立時間 (crtime);此欄位為 64 位寬,並以與 64 位 [cma]time 相同的方式解碼。crtime 和 dtime 都無法透過常規的 stat() 介面訪問,但 debugfs 會報告它們。

我們使用 32 位有符號時間值加上 (2^32 * (額外 epoch 位))。換句話說

額外的 epoch 位

32 位時間的 MSB

將有符號 32 位調整為 64 位 tv_sec

解碼後的 64 位 tv_sec

有效時間範圍

0 0

1

0

-0x80000000 - -0x00000001

1901-12-13 至 1969-12-31

0 0

0

0

0x000000000 - 0x07fffffff

1970-01-01 至 2038-01-19

0 1

1

0x100000000

0x080000000 - 0x0ffffffff

2038-01-19 至 2106-02-07

0 1

0

0x100000000

0x100000000 - 0x17fffffff

2106-02-07 至 2174-02-25

1 0

1

0x200000000

0x180000000 - 0x1ffffffff

2174-02-25 至 2242-03-16

1 0

0

0x200000000

0x200000000 - 0x27fffffff

2242-03-16 至 2310-04-04

1 1

1

0x300000000

0x280000000 - 0x2ffffffff

2310-04-04 至 2378-04-22

1 1

0

0x300000000

0x300000000 - 0x37fffffff

2378-04-22 至 2446-05-10

這是一種有些奇怪的編碼,因為正值的數量實際上是負值的七倍。解碼和編碼超出 2038 年的日期也存在長期存在的錯誤,截至核心 3.12 和 e2fsprogs 1.42.8,這些錯誤似乎尚未修復。 64 位核心錯誤地使用額外的 epoch 位 1,1 來表示 1901 年到 1970 年之間的日期。在某個時候,核心將被修復,e2fsck 將修復這種情況,前提是在 2310 年之前執行它。

4.2. inode.i_block 的內容

根據 inode 描述的檔案型別,inode.i_block 中的 60 個位元組的儲存空間可以以不同的方式使用。一般來說,常規檔案和目錄將使用它來儲存檔案塊索引資訊,而特殊檔案將使用它來儲存特殊用途。

4.2.2. 直接/間接塊定址

在 ext2/3 中,檔案塊號透過(最多)三級 1-1 塊對映對映到邏輯塊號。要查詢儲存特定檔案塊的邏輯塊,程式碼將遍歷這個越來越複雜的結構。請注意,既沒有幻數也沒有校驗和來提供任何程度的信心,證明該塊不是充滿了垃圾。

i.i_block 偏移量

它指向哪裡

0 到 11

直接對映到檔案塊 0 到 11。

12

間接塊:(檔案塊 12 到 ($block_size / 4) + 11,或者如果 4KiB 塊則為 12 到 1035)

間接塊偏移量

它指向哪裡

0 到 ($block_size / 4)

直接對映到 ($block_size / 4) 個塊(如果 4KiB 塊則為 1024)

13

雙重間接塊:(檔案塊 $block_size/4 + 12 到 ($block_size / 4) ^ 2 + ($block_size / 4) + 11,或者如果 4KiB 塊則為 1036 到 1049611)

雙重間接塊偏移量

它指向哪裡

0 到 ($block_size / 4)

對映到 ($block_size / 4) 個間接塊(如果 4KiB 塊則為 1024)

間接塊偏移量

它指向哪裡

0 到 ($block_size / 4)

直接對映到 ($block_size / 4) 個塊(如果 4KiB 塊則為 1024)

14

三重間接塊:(檔案塊 ($block_size / 4) ^ 2 + ($block_size / 4) + 12 到 ($block_size / 4) ^ 3 + ($block_size / 4) ^ 2 + ($block_size / 4) + 12,或者如果 4KiB 塊則為 1049612 到 1074791436)

三重間接塊偏移量

它指向哪裡

0 到 ($block_size / 4)

對映到 ($block_size / 4) 個雙重間接塊(如果 4KiB 塊則為 1024)

雙重間接塊偏移量

它指向哪裡

0 到 ($block_size / 4)

對映到 ($block_size / 4) 個間接塊(如果 4KiB 塊則為 1024)

間接塊偏移量

它指向哪裡

0 到 ($block_size / 4)

直接對映到 ($block_size / 4) 個塊(如果 4KiB 塊則為 1024)

請注意,使用這種塊對映方案,即使對於一個大的連續檔案,也需要填寫大量的對映資料!這種效率低下導致了 extent 對映方案的建立,如下所述。

另請注意,使用此對映方案的檔案不能放置在高於 2^32 個塊的位置。

4.2.3. Extent 樹

在 ext4 中,檔案到邏輯塊的對映已被 extent 樹取代。在舊方案下,分配 1,000 個塊的連續執行需要一個間接塊來對映所有 1,000 個條目;使用 extent,對映減少到單個 struct ext4_extent,其中 ee_len = 1000。如果啟用了 flex_bg,則可以使用單個 extent 分配非常大的檔案,從而大大減少元資料塊的使用,並提高磁碟效率。inode 必須設定 extent 標誌 (0x80000) 才能使用此功能。

Extent 被排列成樹。樹的每個節點都以 struct ext4_extent_header 開頭。如果節點是內部節點 (eh.eh_depth > 0),則標頭後跟 eh.eh_entriesstruct ext4_extent_idx 的例項;這些索引條目中的每一個都指向一個包含 extent 樹中更多節點的塊。如果節點是葉節點 (eh.eh_depth == 0),則標頭後跟 eh.eh_entriesstruct ext4_extent 的例項;這些例項指向檔案的資料塊。extent 樹的根節點儲存在 inode.i_block 中,這允許記錄前四個 extent,而無需使用額外的元資料塊。

extent 樹標頭記錄在 struct ext4_extent_header 中,長度為 12 位元組

偏移量

大小

名稱

描述

0x0

__le16

eh_magic

幻數,0xF30A。

0x2

__le16

eh_entries

標頭後有效條目的數量。

0x4

__le16

eh_max

可以跟隨標頭的最大條目數。

0x6

__le16

eh_depth

extent 節點在 extent 樹中的深度。 0 = 此 extent 節點指向資料塊;否則,此 extent 節點指向其他 extent 節點。 extent 樹最多可以有 5 層深:邏輯塊號最多可以是 2^32,滿足 4*(((blocksize - 12)/12)^n) >= 2^32 的最小 n 是 5。

0x8

__le32

eh_generation

樹的世代。(由 Lustre 使用,但不是標準 ext4)。

extent 樹的內部節點,也稱為索引節點,記錄為 struct ext4_extent_idx,長度為 12 位元組

偏移量

大小

名稱

描述

0x0

__le32

ei_block

此索引節點涵蓋從“塊”開始的檔案塊。

0x4

__le32

ei_leaf_lo

樹中下一層較低的 extent 節點的塊號的低 32 位。 指向的樹節點可以是另一個內部節點,也可以是葉節點,如下所述。

0x8

__le16

ei_leaf_hi

前一個欄位的高 16 位。

0xA

__u16

ei_unused

extent 樹的葉節點記錄為 struct ext4_extent,長度也為 12 位元組

偏移量

大小

名稱

描述

0x0

__le32

ee_block

此 extent 涵蓋的第一個檔案塊號。

0x4

__le16

ee_len

extent 覆蓋的塊數。如果此欄位的值 <= 32768,則 extent 已初始化。如果該欄位的值 > 32768,則 extent 未初始化,實際的 extent 長度為 ee_len - 32768。因此,已初始化 extent 的最大長度為 32768 個塊,未初始化 extent 的最大長度為 32767 個塊。

0x6

__le16

ee_start_hi

此 extent 指向的塊號的高 16 位。

0x8

__le32

ee_start_lo

此 extent 指向的塊號的低 32 位。

在引入元資料校驗和之前,extent 標頭 + extent 條目始終在每個 extent 樹資料塊的末尾留下至少 4 個位元組的未分配空間(因為 (2^x % 12) >= 4)。因此,32 位校驗和被插入到這個空間中。 inode 中的 4 個 extent 不需要校驗和,因為 inode 已經過校驗和。校驗和是根據 FS UUID、inode 編號、inode 世代和直到(但不包括)校驗和本身的整個 extent 塊計算得出的。

struct ext4_extent_tail 長度為 4 位元組

偏移量

大小

名稱

描述

0x0

__le32

eb_checksum

extent 塊的校驗和,crc32c(uuid+inum+igeneration+extentblock)

4.2.4. 內聯資料

如果為檔案系統啟用了內聯資料功能,並且為 inode 設定了標誌,則可以將檔案資料的前 60 個位元組儲存在此處。

4.3. 目錄條目

在 ext4 檔案系統中,目錄或多或少是一個平面檔案,它將任意位元組字串(通常是 ASCII)對映到檔案系統上的 inode 編號。檔案系統中可能有許多目錄條目引用相同的 inode 編號——這些條目稱為硬連結,這也是硬連結無法引用其他檔案系統上的檔案的原因。因此,透過讀取與目錄檔案關聯的資料塊以查詢所需的特定目錄條目來找到目錄條目。

4.3.1. 線性(經典)目錄

預設情況下,每個目錄都以“幾乎線性”的陣列列出其條目。 我寫“幾乎”是因為它不是記憶體意義上的線性陣列,因為目錄條目不會跨檔案系統塊拆分。 因此,更準確的說法是,目錄是一系列資料塊,每個塊都包含一個線性目錄條目陣列。 每個每個塊陣列的結尾都透過到達塊的末尾來表示; 塊中的最後一個條目的記錄長度使其一直延伸到塊的末尾。 整個目錄的結尾當然是透過到達檔案結尾來表示的。 未使用的目錄條目由 inode = 0 表示。 預設情況下,檔案系統使用 struct ext4_dir_entry_2 作為目錄條目,除非未設定“filetype”功能標誌,在這種情況下,它使用 struct ext4_dir_entry

原始目錄條目格式是 struct ext4_dir_entry,最多 263 位元組長,儘管在磁碟上您需要引用 dirent.rec_len 才能確定。

偏移量

大小

名稱

描述

0x0

__le32

inode

此目錄條目指向的 inode 的編號。

0x4

__le16

rec_len

此目錄條目的長度。 必須是 4 的倍數。

0x6

__le16

name_len

檔名的長度。

0x8

char

name[EXT4_NAME_LEN]

檔名。

由於檔名不能超過 255 個位元組,因此新的目錄條目格式縮短了 name_len 欄位,並將該空間用於檔案型別標誌,可能是為了避免在目錄樹遍歷期間必須載入每個 inode。 此格式為 ext4_dir_entry_2,最多 263 位元組長,儘管在磁碟上您需要引用 dirent.rec_len 才能確定。

偏移量

大小

名稱

描述

0x0

__le32

inode

此目錄條目指向的 inode 的編號。

0x4

__le16

rec_len

此目錄條目的長度。

0x6

__u8

name_len

檔名的長度。

0x7

__u8

file_type

檔案型別程式碼,請參見下面的 ftype 表。

0x8

char

name[EXT4_NAME_LEN]

檔名。

目錄檔案型別是以下值之一

描述

0x0

未知。

0x1

常規檔案。

0x2

目錄。

0x3

字元裝置檔案。

0x4

塊裝置檔案。

0x5

FIFO。

0x6

套接字。

0x7

符號連結。

為了支援既加密又區分大小寫的目錄,我們還必須在目錄條目中包含雜湊資訊。 我們將 ext4_extended_dir_entry_2 附加到 ext4_dir_entry_2,但點和點點的條目除外,它們保持不變。 該結構緊跟在 name 之後,幷包含在 rec_len 列出的大小中。 如果目錄條目使用此擴充套件,則最多可為 271 位元組。

偏移量

大小

名稱

描述

0x0

__le32

hash

目錄名稱的雜湊值

0x4

__le32

minor_hash

目錄名稱的次要雜湊值

為了將校驗和新增到這些經典目錄塊,一個偽造的 struct ext4_dir_entry 被放置在每個葉塊的末尾以儲存校驗和。 目錄條目長 12 位元組。 inode 編號和 name_len 欄位設定為零,以欺騙舊軟體忽略表面上為空的目錄條目,並且校驗和儲存在名稱通常所在的位置。 該結構為 struct ext4_dir_entry_tail

偏移量

大小

名稱

描述

0x0

__le32

det_reserved_zero1

Inode 編號,必須為零。

0x4

__le16

det_rec_len

此目錄條目的長度,必須為 12。

0x6

__u8

det_reserved_zero2

檔名長度,必須為零。

0x7

__u8

det_reserved_ft

檔案型別,必須為 0xDE。

0x8

__le32

det_checksum

目錄葉塊校驗和。

葉目錄塊校驗和是針對 FS UUID、目錄的 inode 編號、目錄的 inode 世代編號和整個目錄條目塊(直到但不包括)偽造的目錄條目計算得出的。

4.3.2. 雜湊樹目錄

目錄條目的線性陣列對於效能而言並不是很好,因此向 ext3 添加了一個新功能,以提供基於目錄條目名稱雜湊的更快(但很奇怪)的平衡樹。 如果 inode 中設定了 EXT4_INDEX_FL (0x1000) 標誌,則此目錄使用雜湊 btree (htree) 來組織和查詢目錄條目。 為了向後與 ext2 只讀相容,此樹實際上隱藏在目錄檔案中,偽裝成“空”目錄資料塊! 先前說過,線性目錄條目表的末尾用指向 inode 0 的條目表示; 這(被濫用)來欺騙舊的線性掃描演算法,使其認為目錄塊的其餘部分是空的,從而繼續前進。

樹的根始終位於目錄的第一個資料塊中。 根據 ext2 慣例,“.”和“..”條目必須出現在第一個塊的開頭,因此將它們作為兩個 struct ext4_dir_entry_2 s 放置在此處,而不是儲存在樹中。 根節點的其餘部分包含有關樹的元資料,最後包含雜湊 -> 塊對映以查詢 htree 中較低的節點。 如果 dx_root.info.indirect_levels 為非零,則 htree 有兩層; 根節點的對映指向的資料塊是一個內部節點,該節點由次要雜湊索引。 此樹中的內部節點包含一個歸零的 struct ext4_dir_entry_2,後跟一個次要雜湊 -> 塊對映以查詢葉節點。 葉節點包含所有 struct ext4_dir_entry_2 的線性陣列; 所有這些條目(大概)都雜湊到相同的值。 如果存在溢位,則條目會簡單地溢位到下一個葉節點中,並且(在內部節點對映中)獲得此下一個葉節點的雜湊的最低有效位已設定。

要將目錄作為 htree 遍歷,程式碼會計算所需檔名的雜湊值,並使用它來查詢相應的塊號。 如果樹是平坦的,則該塊是可以搜尋的目錄條目的線性陣列; 否則,將計算檔名的次要雜湊,並將其與第二個塊進行比較以查詢相應的第三個塊號。 第三個塊號將是目錄條目的線性陣列。

要將目錄作為線性陣列遍歷(例如舊程式碼),程式碼只需讀取目錄中的每個資料塊。 用於 htree 的塊將看起來沒有條目(除了“.”和“..”),因此只有葉節點似乎具有任何有趣的內容。

htree 的根在 struct dx_root 中,該結構是資料塊的完整長度

偏移量

Type

名稱

描述

0x0

__le32

dot.inode

此目錄的 inode 編號。

0x4

__le16

dot.rec_len

此記錄的長度,12。

0x6

u8

dot.name_len

名稱的長度,1。

0x7

u8

dot.file_type

此條目的檔案型別,0x2(目錄)(如果設定了功能標誌)。

0x8

char

dot.name[4]

“.000”

0xC

__le32

dotdot.inode

父目錄的 inode 編號。

0x10

__le16

dotdot.rec_len

block_size - 12。 記錄長度足夠長,可以覆蓋所有 htree 資料。

0x12

u8

dotdot.name_len

名稱的長度,2。

0x13

u8

dotdot.file_type

此條目的檔案型別,0x2(目錄)(如果設定了功能標誌)。

0x14

char

dotdot_name[4]

“..00”

0x18

__le32

struct dx_root_info.reserved_zero

零。

0x1C

u8

struct dx_root_info.hash_version

雜湊型別,請參見下面的 dirhash 表。

0x1D

u8

struct dx_root_info.info_length

樹資訊的長度,0x8。

0x1E

u8

struct dx_root_info.indirect_levels

htree 的深度。 如果設定了 INCOMPAT_LARGEDIR 功能,則不能大於 3; 否則不能大於 2。

0x1F

u8

struct dx_root_info.unused_flags

0x20

__le16

limit

可以跟隨此標頭的 dx_entries 的最大數量,外加 1 個標頭本身。

0x22

__le16

count

跟隨此標頭的 dx_entries 的實際數量,外加 1 個標頭本身。

0x24

__le32

block

與 hash=0 一起的塊號(在目錄檔案中)。

0x28

struct dx_entry

entries[0]

儘可能多的 8 位元組 struct dx_entry 適合資料塊的其餘部分。

目錄雜湊是以下值之一

描述

0x0

舊版。

0x1

半 MD4。

0x2

Tea。

0x3

舊版,無符號。

0x4

半 MD4,無符號。

0x5

Tea,無簽名。

0x6

Siphash。

htree 的內部節點記錄為 struct dx_node,它也是資料塊的完整長度

偏移量

Type

名稱

描述

0x0

__le32

fake.inode

零,使其看起來好像未使用此條目。

0x4

__le16

fake.rec_len

塊的大小,以隱藏所有 dx_node 資料。

0x6

u8

name_len

零。 此“未使用”目錄條目沒有名稱。

0x7

u8

file_type

零。 此“未使用”目錄條目沒有檔案型別。

0x8

__le16

limit

可以跟隨此標頭的 dx_entries 的最大數量,外加 1 個標頭本身。

0xA

__le16

count

跟隨此標頭的 dx_entries 的實際數量,外加 1 個標頭本身。

0xE

__le32

block

與此塊的最低雜湊值一起使用的塊號(在目錄檔案中)。 此值儲存在父塊中。

0x12

struct dx_entry

entries[0]

儘可能多的 8 位元組 struct dx_entry 適合資料塊的其餘部分。

存在於 struct dx_rootstruct dx_node 中的雜湊對映記錄為 struct dx_entry,長度為 8 位元組

偏移量

Type

名稱

描述

0x0

__le32

hash

雜湊碼。

0x4

__le32

block

htree 中下一個節點的塊號(在目錄檔案中,而不是檔案系統塊)。

(如果您認為這一切都非常聰明和奇怪,作者也是這麼認為的。)

如果啟用了元資料校驗和,則目錄塊的最後 8 個位元組(恰好是一個 dx_entry 的長度)用於儲存 struct dx_tail,其中包含校驗和。 根據需要調整 dx_root/dx_node 結構中的 limitcount 條目,以將 dx_tail 放入塊中。 如果沒有空間用於 dx_tail,則會通知使用者執行 e2fsck -D 以重建目錄索引(這將確保有空間用於校驗和。 dx_tail 結構長 8 個位元組,如下所示

偏移量

Type

名稱

描述

0x0

u32

dt_reserved

零。

0x4

__le32

dt_checksum

htree 目錄塊的校驗和。

校驗和是針對 FS UUID、htree 索引標頭(dx_root 或 dx_node)、所有正在使用的 htree 索引 (dx_entry) 和尾塊 (dx_tail) 計算得出的。

4.4. 擴充套件屬性

擴充套件屬性 (xattrs) 通常儲存在磁碟上的單獨資料塊中,並透過 inode.i_file_acl* 從 inode 引用。 擴充套件屬性的第一個用途似乎是儲存檔案 ACL 和其他安全資料 (selinux)。 使用 user_xattr 掛載選項,使用者可以儲存擴充套件屬性,只要所有屬性名稱都以“user”開頭即可; 從 Linux 3.0 開始,此限制似乎已消失。

可以在兩個地方找到擴充套件屬性。 第一個地方是在每個 inode 條目的末尾和下一個 inode 條目的開頭之間。 例如,如果 inode.i_extra_isize = 28 且 sb.inode_size = 256,則有 256 - (128 + 28) = 100 個位元組可用於 inode 內擴充套件屬性儲存。 可以在其中找到擴充套件屬性的第二個位置是在 inode.i_file_acl 指向的塊中。 從 Linux 3.11 開始,此塊不可能包含指向第二個擴充套件屬性塊(甚至群集的其餘塊)的指標。 理論上,每個屬性的值都可以儲存在單獨的資料塊中,但從 Linux 3.11 開始,程式碼不允許這樣做。

通常假定鍵是 ASCIIZ 字串,而值可以是字串或二進位制資料。

擴充套件屬性(儲存在 inode 之後)具有一個長度為 4 位元組的標頭 ext4_xattr_ibody_header

偏移量

Type

名稱

描述

0x0

__le32

h_magic

用於標識的幻數,0xEA020000。 此值由 Linux 驅動程式設定,儘管 e2fsprogs 似乎不檢查它(?)

擴充套件屬性塊的起始位置位於 struct ext4_xattr_header 中,其長度為 32 位元組。

偏移量

Type

名稱

描述

0x0

__le32

h_magic

用於識別的魔數,為 0xEA020000。

0x4

__le32

h_refcount

引用計數。

0x8

__le32

h_blocks

使用的磁碟塊數量。

0xC

__le32

h_hash

所有屬性的雜湊值。

0x10

__le32

h_checksum

擴充套件屬性塊的校驗和。

0x14

__u32

h_reserved[3]

零。

校驗和是根據 FS UUID、擴充套件屬性塊的 64 位塊號以及整個塊(header + entries)計算得出的。

struct ext4_xattr_headerstruct ext4_xattr_ibody_header 之後是一個 struct ext4_xattr_entry 陣列;這些條目中的每一個都至少有 16 位元組長。當儲存在外部塊中時,struct ext4_xattr_entry 條目必須按排序順序儲存。排序順序為 e_name_index,然後是 e_name_len,最後是 e_name。儲存在 inode 中的屬性不需要按排序順序儲存。

偏移量

Type

名稱

描述

0x0

__u8

e_name_len

名稱的長度。

0x1

__u8

e_name_index

屬性名稱索引。 下面會討論。

0x2

__le16

e_value_offs

此屬性的值在其儲存的磁碟塊上的位置。 多個屬性可以共享相同的值。 對於 inode 屬性,此值相對於第一個條目的起始位置;對於塊,此值相對於塊的起始位置(即 header)。

0x4

__le32

e_value_inum

儲存值的 inode。 零表示該值與此條目位於同一塊中。 僅當啟用了 INCOMPAT_EA_INODE 功能時才使用此欄位。

0x8

__le32

e_value_size

屬性值的長度。

0xC

__le32

e_hash

屬性名稱和屬性值的雜湊值。 核心不會更新 inode 內屬性的雜湊值,因此對於這種情況,該值必須為零,因為 e2fsck 會驗證任何非零雜湊值,無論 xattr 位於何處。

0x10

char

e_name[e_name_len]

屬性名稱。 不包括尾隨 NULL。

屬性值可以位於條目表之後。 似乎需要將它們對齊到 4 位元組邊界。 這些值從塊的末尾開始儲存,並向 xattr_header/xattr_entry 表增長。 當兩者發生衝突時,溢位會被放入單獨的磁碟塊中。 如果磁碟塊已滿,則檔案系統將返回 -ENOSPC。

ext4_xattr_entry 的前四個欄位設定為零以標記鍵列表的結尾。

4.4.1. 屬性名稱索引

從邏輯上講,擴充套件屬性是一系列鍵=值對。 假設鍵是 NULL 結尾的字串。 為了減少鍵佔用的磁碟空間量,鍵字串的開頭與屬性名稱索引匹配。 如果找到匹配項,則設定屬性名稱索引欄位,並從鍵名中刪除匹配的字串。 以下是將名稱索引值對映到鍵字首的對映

名稱索引

鍵字首

0

(無字首)

1

“user.”

2

“system.posix_acl_access”

3

“system.posix_acl_default”

4

“trusted.”

6

“security.”

7

“system.” (僅限 inline_data?)

8

“system.richacl” (僅限 SuSE 核心?)

例如,如果屬性鍵為 “user.fubar”,則屬性名稱索引設定為 1,並且 “fubar” 名稱記錄在磁碟上。

4.4.2. POSIX ACL

POSIX ACL 儲存在 Linux 核心(和 libacl)內部 ACL 格式的簡化版本中。 主要區別在於版本號不同 (1),並且僅為命名的使用者和組 ACL 儲存 e_id 欄位。