Skip to main content

btrfs_disk/
tree.rs

1//! # Parsing btrfs B-tree nodes and leaves from raw blocks
2//!
3//! A btrfs filesystem is organized as a collection of B-trees. Each tree is
4//! stored as a hierarchy of blocks (nodesize bytes each, typically 16 KiB).
5//! Internal nodes contain key pointers to child blocks; leaves contain items
6//! with their associated data payloads.
7//!
8//! This module provides typed Rust structs for all tree block components and
9//! enums for key types and well-known object IDs, with safe LE parsing from
10//! raw byte buffers.
11
12use crate::{raw, util::get_uuid};
13use bytes::Buf;
14use std::{fmt, mem};
15use uuid::Uuid;
16
17/// Btrfs item key type, identifying what kind of item a key refers to.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub enum KeyType {
20    /// Inode metadata (POSIX attributes, timestamps, flags). Key:
21    /// `(ino, INODE_ITEM, 0)`. Exactly one per inode in the FS tree.
22    InodeItem,
23    /// Hard-link reference from an inode to a parent directory. Key:
24    /// `(ino, INODE_REF, parent_dir_ino)`. Multiple refs may be packed
25    /// into one item when the inode has several links in the same parent.
26    InodeRef,
27    /// Extended inode reference (when the `extref` feature is enabled).
28    /// Key: `(ino, INODE_EXTREF, hash(parent_ino, name))`. Stores the
29    /// parent inode number inside the struct rather than in the key offset.
30    InodeExtref,
31    /// Extended attribute stored as a directory-entry-like item. Key:
32    /// `(ino, XATTR_ITEM, crc32c(name))`. Data contains the xattr name
33    /// and value.
34    XattrItem,
35    /// fs-verity descriptor item. Key: `(ino, VERITY_DESC_ITEM, 0)`.
36    /// Stores the fs-verity descriptor for a verity-protected file.
37    VerityDescItem,
38    /// fs-verity Merkle tree block. Key: `(ino, VERITY_MERKLE_ITEM, offset)`.
39    /// Stores a block of the Merkle tree used for fs-verity verification.
40    VerityMerkleItem,
41    /// Orphan inode marker. Key: `(ORPHAN_OBJECTID, ORPHAN_ITEM, ino)`.
42    /// Created when an inode is unlinked while still open; cleaned up on
43    /// mount or by orphan cleanup.
44    OrphanItem,
45    /// Directory log item (tree-log only). Key:
46    /// `(dir_ino, DIR_LOG_ITEM, end_range)`. Used during log replay to
47    /// track which directory entries have been logged.
48    DirLogItem,
49    /// Directory log index (tree-log only). Key:
50    /// `(dir_ino, DIR_LOG_INDEX, end_range)`. Index counterpart of
51    /// `DIR_LOG_ITEM`.
52    DirLogIndex,
53    /// Directory entry keyed by name hash. Key:
54    /// `(dir_ino, DIR_ITEM, crc32c(name))`. Multiple entries may share
55    /// the same item when names hash-collide.
56    DirItem,
57    /// Directory entry keyed by sequential index. Key:
58    /// `(dir_ino, DIR_INDEX, seq)`. Provides ordered directory iteration
59    /// independent of the name hash.
60    DirIndex,
61    /// File extent descriptor. Key: `(ino, EXTENT_DATA, file_offset)`.
62    /// Describes how a range of file bytes maps to on-disk storage (inline,
63    /// regular, or preallocated).
64    ExtentData,
65    /// Data checksum. Key: `(EXTENT_CSUM, EXTENT_CSUM, logical_bytenr)`.
66    /// Stores an array of per-sector CRC32C checksums for a contiguous
67    /// range of data blocks.
68    ExtentCsum,
69    /// Tree root descriptor. Key: `(tree_objectid, ROOT_ITEM, 0)`. Stored
70    /// in the root tree; contains the root block pointer, generation,
71    /// subvolume UUIDs, and timestamps.
72    RootItem,
73    /// Backreference from a child subvolume to its parent. Key:
74    /// `(child_tree_id, ROOT_BACKREF, parent_tree_id)`. Contains the
75    /// directory inode and name where the subvolume is mounted.
76    RootBackref,
77    /// Forward reference from a parent subvolume to a child. Key:
78    /// `(parent_tree_id, ROOT_REF, child_tree_id)`. Same on-disk format
79    /// as `ROOT_BACKREF`.
80    RootRef,
81    /// Non-skinny extent allocation record. Key:
82    /// `(bytenr, EXTENT_ITEM, length)`. Tracks reference counts and
83    /// backreferences for a contiguous range of allocated disk space.
84    ExtentItem,
85    /// Skinny metadata extent record (when `skinny_metadata` feature is
86    /// enabled). Key: `(bytenr, METADATA_ITEM, level)`. Like `EXTENT_ITEM`
87    /// but the tree block info is encoded in the key offset instead of an
88    /// extra header.
89    MetadataItem,
90    /// Simple subvolume ownership reference for an extent. Key:
91    /// `(bytenr, EXTENT_OWNER_REF, root_objectid)`. Used with the
92    /// `simple_quota` feature for fast ownership tracking.
93    ExtentOwnerRef,
94    /// Direct backref from a metadata extent to the owning tree. Key:
95    /// `(bytenr, TREE_BLOCK_REF, root_objectid)`. The key offset
96    /// identifies which tree root owns the block.
97    TreeBlockRef,
98    /// Backref from a data extent to a specific file inode. Key:
99    /// `(bytenr, EXTENT_DATA_REF, hash(root, ino, offset))`. Stores
100    /// the root, inode, file offset, and reference count.
101    ExtentDataRef,
102    /// Shared backref from a metadata extent via a parent block. Key:
103    /// `(bytenr, SHARED_BLOCK_REF, parent_bytenr)`. Used for
104    /// snapshot-shared tree blocks.
105    SharedBlockRef,
106    /// Shared backref from a data extent via a parent tree block. Key:
107    /// `(bytenr, SHARED_DATA_REF, parent_bytenr)`. Used for
108    /// snapshot-shared data extents; stores a reference count.
109    SharedDataRef,
110    /// Block group descriptor tracking space usage for a chunk. Key:
111    /// `(logical_offset, BLOCK_GROUP_ITEM, length)`. Contains bytes used
112    /// and the chunk type/RAID profile flags.
113    BlockGroupItem,
114    /// Free space info for a block group in the free space tree. Key:
115    /// `(block_group_offset, FREE_SPACE_INFO, block_group_length)`.
116    /// Describes whether the block group uses bitmaps or extents.
117    FreeSpaceInfo,
118    /// Free space extent in the free space tree. Key:
119    /// `(start, FREE_SPACE_EXTENT, length)`. Represents a contiguous
120    /// free range; the item has no data payload.
121    FreeSpaceExtent,
122    /// Free space bitmap in the free space tree. Key:
123    /// `(start, FREE_SPACE_BITMAP, length)`. A bitmap covering the
124    /// block group's address range; each bit represents one sector.
125    FreeSpaceBitmap,
126    /// Physical extent mapping on a device. Key:
127    /// `(devid, DEV_EXTENT, physical_offset)`. The inverse of a chunk
128    /// stripe: maps a physical range back to the owning chunk.
129    DeviceExtent,
130    /// Device item describing a single device. Key:
131    /// `(DEV_ITEMS, DEV_ITEM, devid)`. Contains size, usage, and UUIDs.
132    /// Also embedded in the superblock.
133    DeviceItem,
134    /// Chunk item mapping logical to physical addresses. Key:
135    /// `(FIRST_CHUNK_TREE, CHUNK_ITEM, logical_offset)`. Contains the
136    /// chunk length, RAID profile, and per-device stripe locations.
137    ChunkItem,
138    /// RAID stripe extent mapping (for the raid-stripe-tree feature). Key:
139    /// `(logical_offset, RAID_STRIPE, length)`. Contains per-device
140    /// physical offsets for the stripe.
141    RaidStripe,
142    /// Quota group status. Key: `(0, QGROUP_STATUS, 0)`. Tracks the
143    /// overall quota accounting state, rescan progress, and enable
144    /// generation.
145    QgroupStatus,
146    /// Quota group accounting info. Key:
147    /// `(level/subvolid, QGROUP_INFO, 0)`. Tracks referenced and
148    /// exclusive bytes for a qgroup.
149    QgroupInfo,
150    /// Quota group limits. Key: `(level/subvolid, QGROUP_LIMIT, 0)`.
151    /// Caps referenced and/or exclusive space usage for a qgroup.
152    QgroupLimit,
153    /// Quota group relation. Key:
154    /// `(child_qgroupid, QGROUP_RELATION, parent_qgroupid)`. Defines
155    /// the parent-child relationship between qgroups.
156    QgroupRelation,
157    /// Temporary item (value 248). `BTRFS_BALANCE_ITEM_KEY` and
158    /// `BTRFS_TEMPORARY_ITEM_KEY` share this value. Used for balance
159    /// status persistence.
160    TemporaryItem,
161    /// Persistent item (value 249). `BTRFS_DEV_STATS_KEY` and
162    /// `BTRFS_PERSISTENT_ITEM_KEY` share this value. Used for device
163    /// statistics and device replace status.
164    PersistentItem,
165    /// Device replace status. Key: `(DEV_REPLACE, DEV_REPLACE, 0)`.
166    /// Persists the state of an in-progress device replace operation
167    /// across reboots.
168    DeviceReplace,
169    /// UUID tree entry for a subvolume. Key:
170    /// `(upper_uuid_half, UUID_KEY_SUBVOL, lower_uuid_half)`. Maps a
171    /// subvolume UUID to its tree objectid for fast lookup.
172    UuidKeySubvol,
173    /// UUID tree entry for a received subvolume. Key:
174    /// `(upper_uuid_half, UUID_KEY_RECEIVED_SUBVOL, lower_uuid_half)`.
175    /// Maps a received UUID to the subvolume objectid.
176    UuidKeyReceivedSubvol,
177    /// String item (typically the superblock's label). Key:
178    /// `(BTRFS_FREE_SPACE_OBJECTID, STRING_ITEM, 0)`. The data payload
179    /// is a raw byte string.
180    StringItem,
181    /// Unrecognized key type byte value.
182    Unknown(u8),
183}
184
185impl KeyType {
186    /// Convert a raw on-disk key type byte to a `KeyType` variant.
187    #[must_use]
188    pub fn from_raw(value: u8) -> Self {
189        match u32::from(value) {
190            raw::BTRFS_INODE_ITEM_KEY => Self::InodeItem,
191            raw::BTRFS_INODE_REF_KEY => Self::InodeRef,
192            raw::BTRFS_INODE_EXTREF_KEY => Self::InodeExtref,
193            raw::BTRFS_XATTR_ITEM_KEY => Self::XattrItem,
194            raw::BTRFS_VERITY_DESC_ITEM_KEY => Self::VerityDescItem,
195            raw::BTRFS_VERITY_MERKLE_ITEM_KEY => Self::VerityMerkleItem,
196            raw::BTRFS_ORPHAN_ITEM_KEY => Self::OrphanItem,
197            raw::BTRFS_DIR_LOG_ITEM_KEY => Self::DirLogItem,
198            raw::BTRFS_DIR_LOG_INDEX_KEY => Self::DirLogIndex,
199            raw::BTRFS_DIR_ITEM_KEY => Self::DirItem,
200            raw::BTRFS_DIR_INDEX_KEY => Self::DirIndex,
201            raw::BTRFS_EXTENT_DATA_KEY => Self::ExtentData,
202            raw::BTRFS_EXTENT_CSUM_KEY => Self::ExtentCsum,
203            raw::BTRFS_ROOT_ITEM_KEY => Self::RootItem,
204            raw::BTRFS_ROOT_BACKREF_KEY => Self::RootBackref,
205            raw::BTRFS_ROOT_REF_KEY => Self::RootRef,
206            raw::BTRFS_EXTENT_ITEM_KEY => Self::ExtentItem,
207            raw::BTRFS_METADATA_ITEM_KEY => Self::MetadataItem,
208            raw::BTRFS_EXTENT_OWNER_REF_KEY => Self::ExtentOwnerRef,
209            raw::BTRFS_TREE_BLOCK_REF_KEY => Self::TreeBlockRef,
210            raw::BTRFS_EXTENT_DATA_REF_KEY => Self::ExtentDataRef,
211            raw::BTRFS_SHARED_BLOCK_REF_KEY => Self::SharedBlockRef,
212            raw::BTRFS_SHARED_DATA_REF_KEY => Self::SharedDataRef,
213            raw::BTRFS_BLOCK_GROUP_ITEM_KEY => Self::BlockGroupItem,
214            raw::BTRFS_FREE_SPACE_INFO_KEY => Self::FreeSpaceInfo,
215            raw::BTRFS_FREE_SPACE_EXTENT_KEY => Self::FreeSpaceExtent,
216            raw::BTRFS_FREE_SPACE_BITMAP_KEY => Self::FreeSpaceBitmap,
217            raw::BTRFS_DEV_EXTENT_KEY => Self::DeviceExtent,
218            raw::BTRFS_DEV_ITEM_KEY => Self::DeviceItem,
219            raw::BTRFS_CHUNK_ITEM_KEY => Self::ChunkItem,
220            raw::BTRFS_RAID_STRIPE_KEY => Self::RaidStripe,
221            raw::BTRFS_QGROUP_STATUS_KEY => Self::QgroupStatus,
222            raw::BTRFS_QGROUP_INFO_KEY => Self::QgroupInfo,
223            raw::BTRFS_QGROUP_LIMIT_KEY => Self::QgroupLimit,
224            raw::BTRFS_QGROUP_RELATION_KEY => Self::QgroupRelation,
225            // 248 = BTRFS_BALANCE_ITEM_KEY = BTRFS_TEMPORARY_ITEM_KEY
226            raw::BTRFS_TEMPORARY_ITEM_KEY => Self::TemporaryItem,
227            // 249 = BTRFS_DEV_STATS_KEY = BTRFS_PERSISTENT_ITEM_KEY
228            raw::BTRFS_PERSISTENT_ITEM_KEY => Self::PersistentItem,
229            raw::BTRFS_DEV_REPLACE_KEY => Self::DeviceReplace,
230            raw::BTRFS_UUID_KEY_SUBVOL => Self::UuidKeySubvol,
231            raw::BTRFS_UUID_KEY_RECEIVED_SUBVOL => Self::UuidKeyReceivedSubvol,
232            raw::BTRFS_STRING_ITEM_KEY => Self::StringItem,
233            _ => Self::Unknown(value),
234        }
235    }
236
237    /// Return the raw u8 key type value.
238    // All btrfs key type constants are defined to fit in u8 (they're the
239    // on-disk key type field); the u32 bindgen type is wider than necessary.
240    #[must_use]
241    #[allow(clippy::cast_possible_truncation)]
242    pub fn to_raw(self) -> u8 {
243        match self {
244            Self::InodeItem => raw::BTRFS_INODE_ITEM_KEY as u8,
245            Self::InodeRef => raw::BTRFS_INODE_REF_KEY as u8,
246            Self::InodeExtref => raw::BTRFS_INODE_EXTREF_KEY as u8,
247            Self::XattrItem => raw::BTRFS_XATTR_ITEM_KEY as u8,
248            Self::VerityDescItem => raw::BTRFS_VERITY_DESC_ITEM_KEY as u8,
249            Self::VerityMerkleItem => raw::BTRFS_VERITY_MERKLE_ITEM_KEY as u8,
250            Self::OrphanItem => raw::BTRFS_ORPHAN_ITEM_KEY as u8,
251            Self::DirLogItem => raw::BTRFS_DIR_LOG_ITEM_KEY as u8,
252            Self::DirLogIndex => raw::BTRFS_DIR_LOG_INDEX_KEY as u8,
253            Self::DirItem => raw::BTRFS_DIR_ITEM_KEY as u8,
254            Self::DirIndex => raw::BTRFS_DIR_INDEX_KEY as u8,
255            Self::ExtentData => raw::BTRFS_EXTENT_DATA_KEY as u8,
256            Self::ExtentCsum => raw::BTRFS_EXTENT_CSUM_KEY as u8,
257            Self::RootItem => raw::BTRFS_ROOT_ITEM_KEY as u8,
258            Self::RootBackref => raw::BTRFS_ROOT_BACKREF_KEY as u8,
259            Self::RootRef => raw::BTRFS_ROOT_REF_KEY as u8,
260            Self::ExtentItem => raw::BTRFS_EXTENT_ITEM_KEY as u8,
261            Self::MetadataItem => raw::BTRFS_METADATA_ITEM_KEY as u8,
262            Self::ExtentOwnerRef => raw::BTRFS_EXTENT_OWNER_REF_KEY as u8,
263            Self::TreeBlockRef => raw::BTRFS_TREE_BLOCK_REF_KEY as u8,
264            Self::ExtentDataRef => raw::BTRFS_EXTENT_DATA_REF_KEY as u8,
265            Self::SharedBlockRef => raw::BTRFS_SHARED_BLOCK_REF_KEY as u8,
266            Self::SharedDataRef => raw::BTRFS_SHARED_DATA_REF_KEY as u8,
267            Self::BlockGroupItem => raw::BTRFS_BLOCK_GROUP_ITEM_KEY as u8,
268            Self::FreeSpaceInfo => raw::BTRFS_FREE_SPACE_INFO_KEY as u8,
269            Self::FreeSpaceExtent => raw::BTRFS_FREE_SPACE_EXTENT_KEY as u8,
270            Self::FreeSpaceBitmap => raw::BTRFS_FREE_SPACE_BITMAP_KEY as u8,
271            Self::DeviceExtent => raw::BTRFS_DEV_EXTENT_KEY as u8,
272            Self::DeviceItem => raw::BTRFS_DEV_ITEM_KEY as u8,
273            Self::ChunkItem => raw::BTRFS_CHUNK_ITEM_KEY as u8,
274            Self::RaidStripe => raw::BTRFS_RAID_STRIPE_KEY as u8,
275            Self::QgroupStatus => raw::BTRFS_QGROUP_STATUS_KEY as u8,
276            Self::QgroupInfo => raw::BTRFS_QGROUP_INFO_KEY as u8,
277            Self::QgroupLimit => raw::BTRFS_QGROUP_LIMIT_KEY as u8,
278            Self::QgroupRelation => raw::BTRFS_QGROUP_RELATION_KEY as u8,
279            Self::TemporaryItem => raw::BTRFS_TEMPORARY_ITEM_KEY as u8,
280            Self::PersistentItem => raw::BTRFS_PERSISTENT_ITEM_KEY as u8,
281            Self::DeviceReplace => raw::BTRFS_DEV_REPLACE_KEY as u8,
282            Self::UuidKeySubvol => raw::BTRFS_UUID_KEY_SUBVOL as u8,
283            Self::UuidKeyReceivedSubvol => {
284                raw::BTRFS_UUID_KEY_RECEIVED_SUBVOL as u8
285            }
286            Self::StringItem => raw::BTRFS_STRING_ITEM_KEY as u8,
287            Self::Unknown(v) => v,
288        }
289    }
290}
291
292impl fmt::Display for KeyType {
293    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294        match self {
295            Self::InodeItem => write!(f, "INODE_ITEM"),
296            Self::InodeRef => write!(f, "INODE_REF"),
297            Self::InodeExtref => write!(f, "INODE_EXTREF"),
298            Self::XattrItem => write!(f, "XATTR_ITEM"),
299            Self::VerityDescItem => write!(f, "VERITY_DESC_ITEM"),
300            Self::VerityMerkleItem => write!(f, "VERITY_MERKLE_ITEM"),
301            Self::OrphanItem => write!(f, "ORPHAN_ITEM"),
302            Self::DirLogItem => write!(f, "DIR_LOG_ITEM"),
303            Self::DirLogIndex => write!(f, "DIR_LOG_INDEX"),
304            Self::DirItem => write!(f, "DIR_ITEM"),
305            Self::DirIndex => write!(f, "DIR_INDEX"),
306            Self::ExtentData => write!(f, "EXTENT_DATA"),
307            Self::ExtentCsum => write!(f, "EXTENT_CSUM"),
308            Self::RootItem => write!(f, "ROOT_ITEM"),
309            Self::RootBackref => write!(f, "ROOT_BACKREF"),
310            Self::RootRef => write!(f, "ROOT_REF"),
311            Self::ExtentItem => write!(f, "EXTENT_ITEM"),
312            Self::MetadataItem => write!(f, "METADATA_ITEM"),
313            Self::ExtentOwnerRef => write!(f, "EXTENT_OWNER_REF"),
314            Self::TreeBlockRef => write!(f, "TREE_BLOCK_REF"),
315            Self::ExtentDataRef => write!(f, "EXTENT_DATA_REF"),
316            Self::SharedBlockRef => write!(f, "SHARED_BLOCK_REF"),
317            Self::SharedDataRef => write!(f, "SHARED_DATA_REF"),
318            Self::BlockGroupItem => write!(f, "BLOCK_GROUP_ITEM"),
319            Self::FreeSpaceInfo => write!(f, "FREE_SPACE_INFO"),
320            Self::FreeSpaceExtent => write!(f, "FREE_SPACE_EXTENT"),
321            Self::FreeSpaceBitmap => write!(f, "FREE_SPACE_BITMAP"),
322            Self::DeviceExtent => write!(f, "DEV_EXTENT"),
323            Self::DeviceItem => write!(f, "DEV_ITEM"),
324            Self::ChunkItem => write!(f, "CHUNK_ITEM"),
325            Self::RaidStripe => write!(f, "RAID_STRIPE"),
326            Self::QgroupStatus => write!(f, "QGROUP_STATUS"),
327            Self::QgroupInfo => write!(f, "QGROUP_INFO"),
328            Self::QgroupLimit => write!(f, "QGROUP_LIMIT"),
329            Self::QgroupRelation => write!(f, "QGROUP_RELATION"),
330            Self::TemporaryItem => write!(f, "TEMPORARY_ITEM"),
331            Self::PersistentItem => write!(f, "PERSISTENT_ITEM"),
332            Self::DeviceReplace => write!(f, "DEV_REPLACE"),
333            Self::UuidKeySubvol => write!(f, "UUID_KEY_SUBVOL"),
334            Self::UuidKeyReceivedSubvol => {
335                write!(f, "UUID_KEY_RECEIVED_SUBVOL")
336            }
337            Self::StringItem => write!(f, "STRING_ITEM"),
338            Self::Unknown(v) => write!(f, "UNKNOWN.{v}"),
339        }
340    }
341}
342
343/// Well-known btrfs object IDs used as tree IDs, namespace roots, and
344/// special-purpose objectids in item keys.
345#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
346pub enum ObjectId {
347    /// The root tree (objectid 1). Contains `ROOT_ITEM` entries pointing
348    /// to the root block of every other tree in the filesystem.
349    RootTree,
350    /// The extent tree (objectid 2). Tracks all allocated extents
351    /// (metadata tree blocks and data extents) with backreferences to
352    /// their owners.
353    ExtentTree,
354    /// The chunk tree (objectid 3). Maps logical address ranges to
355    /// physical device stripes. Bootstrapped from the superblock's
356    /// `sys_chunk_array`.
357    ChunkTree,
358    /// The device tree (objectid 4). Contains per-device extent
359    /// allocation records (`DEV_EXTENT` items).
360    DevTree,
361    /// The default FS tree (objectid 5). Holds the filesystem content
362    /// (inodes, directory entries, file extents) for the top-level
363    /// subvolume. Additional subvolumes/snapshots have objectids >= 256.
364    FsTree,
365    /// The root tree directory (objectid 6). A virtual directory entry
366    /// in the root tree that links to the default subvolume.
367    RootTreeDir,
368    /// The checksum tree (objectid 7). Stores per-block data checksums
369    /// as `EXTENT_CSUM` items.
370    CsumTree,
371    /// The quota tree (objectid 8). Contains qgroup status, info, limit,
372    /// and relation items for space accounting.
373    QuotaTree,
374    /// The UUID tree (objectid 9). Provides fast UUID-to-subvolume
375    /// lookups for send/receive operations.
376    UuidTree,
377    /// The free space tree (objectid 10). Tracks free space per block
378    /// group using extent or bitmap items, replacing the older free
379    /// space cache (v1).
380    FreeSpaceTree,
381    /// The block group tree (objectid 11). Separates block group items
382    /// from the extent tree for faster mount times (requires the
383    /// `block_group_tree` compat-ro feature).
384    BlockGroupTree,
385    /// The RAID stripe tree (objectid 12). Maps logical extents to
386    /// per-device physical stripe offsets for the `raid-stripe-tree`
387    /// feature.
388    RaidStripeTree,
389    /// The remap tree (objectid 13). Reserved for future use by the
390    /// extent-tree-v2 feature.
391    RemapTree,
392    /// Device statistics objectid (0). Used with `PERSISTENT_ITEM_KEY`
393    /// to store per-device I/O error counters.
394    DeviceStats,
395    /// Balance status objectid (-4 as u64). Stored in the root tree
396    /// as a `TEMPORARY_ITEM` to persist in-progress balance state.
397    Balance,
398    /// Orphan objectid (-5 as u64). Used as the objectid for
399    /// `ORPHAN_ITEM` keys that mark inodes pending cleanup.
400    Orphan,
401    /// Tree log objectid (-6 as u64). The tree log records recent
402    /// fsync'd changes for fast replay after a crash.
403    TreeLog,
404    /// Tree log fixup objectid (-7 as u64). Temporary items created
405    /// during log replay to resolve backreferences.
406    TreeLogFixup,
407    /// Tree relocation objectid (-8 as u64). Used during balance to
408    /// hold relocated tree blocks before they are committed to their
409    /// final location.
410    TreeReloc,
411    /// Data relocation tree objectid (-9 as u64). A temporary tree
412    /// used during balance to hold relocated data extents.
413    DataRelocTree,
414    /// Extent checksum objectid (-10 as u64). Used as the objectid in
415    /// `EXTENT_CSUM` keys in the checksum tree.
416    ExtentCsum,
417    /// Free space objectid (-11 as u64). Used in the older free space
418    /// cache (v1) inode items.
419    FreeSpace,
420    /// Free inode objectid (-12 as u64). Used for free inode number
421    /// tracking within FS trees.
422    FreeIno,
423    /// Checksum change objectid (-13 as u64). Used during online
424    /// checksum algorithm conversion.
425    CsumChange,
426    /// Multiple objectids sentinel (-255 as u64). Used internally to
427    /// indicate that an extent is referenced by multiple trees.
428    Multiple,
429    /// First user-accessible objectid (256). Inode numbers in FS trees
430    /// start at this value; the root directory inode is always 256.
431    FirstFree,
432    /// A numeric objectid that doesn't match any well-known value.
433    Id(u64),
434}
435
436impl ObjectId {
437    /// Convert a raw 64-bit objectid to an `ObjectId` variant.
438    // Negative objectid constants (e.g. BTRFS_BALANCE_OBJECTID = -4) are i32
439    // in bindgen output; casting them to u64 intentionally produces the kernel's
440    // two's-complement representation (e.g. 0xFFFFFFFF_FFFFFFFC).
441    #[must_use]
442    #[allow(clippy::cast_sign_loss, clippy::cast_lossless)]
443    pub fn from_raw(value: u64) -> Self {
444        // Positive well-known objectids (bindgen produces u32 for these)
445        match value {
446            v if v == raw::BTRFS_ROOT_TREE_OBJECTID as u64 => Self::RootTree,
447            v if v == raw::BTRFS_EXTENT_TREE_OBJECTID as u64 => {
448                Self::ExtentTree
449            }
450            v if v == raw::BTRFS_CHUNK_TREE_OBJECTID as u64 => Self::ChunkTree,
451            v if v == raw::BTRFS_DEV_TREE_OBJECTID as u64 => Self::DevTree,
452            v if v == raw::BTRFS_FS_TREE_OBJECTID as u64 => Self::FsTree,
453            v if v == raw::BTRFS_ROOT_TREE_DIR_OBJECTID as u64 => {
454                Self::RootTreeDir
455            }
456            v if v == raw::BTRFS_CSUM_TREE_OBJECTID as u64 => Self::CsumTree,
457            v if v == raw::BTRFS_QUOTA_TREE_OBJECTID as u64 => Self::QuotaTree,
458            v if v == raw::BTRFS_UUID_TREE_OBJECTID as u64 => Self::UuidTree,
459            v if v == raw::BTRFS_FREE_SPACE_TREE_OBJECTID as u64 => {
460                Self::FreeSpaceTree
461            }
462            v if v == raw::BTRFS_BLOCK_GROUP_TREE_OBJECTID as u64 => {
463                Self::BlockGroupTree
464            }
465            v if v == raw::BTRFS_RAID_STRIPE_TREE_OBJECTID as u64 => {
466                Self::RaidStripeTree
467            }
468            v if v == raw::BTRFS_REMAP_TREE_OBJECTID as u64 => Self::RemapTree,
469            // Negative objectids: cast i32 to u64 to get the kernel representation
470            v if v == raw::BTRFS_BALANCE_OBJECTID as u64 => Self::Balance,
471            v if v == raw::BTRFS_ORPHAN_OBJECTID as u64 => Self::Orphan,
472            v if v == raw::BTRFS_TREE_LOG_OBJECTID as u64 => Self::TreeLog,
473            v if v == raw::BTRFS_TREE_LOG_FIXUP_OBJECTID as u64 => {
474                Self::TreeLogFixup
475            }
476            v if v == raw::BTRFS_TREE_RELOC_OBJECTID as u64 => Self::TreeReloc,
477            v if v == raw::BTRFS_DATA_RELOC_TREE_OBJECTID as u64 => {
478                Self::DataRelocTree
479            }
480            v if v == raw::BTRFS_EXTENT_CSUM_OBJECTID as u64 => {
481                Self::ExtentCsum
482            }
483            v if v == raw::BTRFS_FREE_SPACE_OBJECTID as u64 => Self::FreeSpace,
484            v if v == raw::BTRFS_FREE_INO_OBJECTID as u64 => Self::FreeIno,
485            v if v == raw::BTRFS_CSUM_CHANGE_OBJECTID as u64 => {
486                Self::CsumChange
487            }
488            v if v == raw::BTRFS_MULTIPLE_OBJECTIDS as u64 => Self::Multiple,
489            _ => Self::Id(value),
490        }
491    }
492
493    /// Return the raw u64 objectid value.
494    #[must_use]
495    #[allow(clippy::cast_sign_loss, clippy::cast_lossless)]
496    pub fn to_raw(self) -> u64 {
497        match self {
498            Self::RootTree => raw::BTRFS_ROOT_TREE_OBJECTID as u64,
499            Self::ExtentTree => raw::BTRFS_EXTENT_TREE_OBJECTID as u64,
500            Self::ChunkTree => raw::BTRFS_CHUNK_TREE_OBJECTID as u64,
501            Self::DevTree => raw::BTRFS_DEV_TREE_OBJECTID as u64,
502            Self::FsTree => raw::BTRFS_FS_TREE_OBJECTID as u64,
503            Self::RootTreeDir => raw::BTRFS_ROOT_TREE_DIR_OBJECTID as u64,
504            Self::CsumTree => raw::BTRFS_CSUM_TREE_OBJECTID as u64,
505            Self::QuotaTree => raw::BTRFS_QUOTA_TREE_OBJECTID as u64,
506            Self::UuidTree => raw::BTRFS_UUID_TREE_OBJECTID as u64,
507            Self::FreeSpaceTree => raw::BTRFS_FREE_SPACE_TREE_OBJECTID as u64,
508            Self::BlockGroupTree => raw::BTRFS_BLOCK_GROUP_TREE_OBJECTID as u64,
509            Self::RaidStripeTree => raw::BTRFS_RAID_STRIPE_TREE_OBJECTID as u64,
510            Self::RemapTree => raw::BTRFS_REMAP_TREE_OBJECTID as u64,
511            Self::DeviceStats => raw::BTRFS_DEV_STATS_OBJECTID as u64,
512            Self::Balance => raw::BTRFS_BALANCE_OBJECTID as u64,
513            Self::Orphan => raw::BTRFS_ORPHAN_OBJECTID as u64,
514            Self::TreeLog => raw::BTRFS_TREE_LOG_OBJECTID as u64,
515            Self::TreeLogFixup => raw::BTRFS_TREE_LOG_FIXUP_OBJECTID as u64,
516            Self::TreeReloc => raw::BTRFS_TREE_RELOC_OBJECTID as u64,
517            Self::DataRelocTree => raw::BTRFS_DATA_RELOC_TREE_OBJECTID as u64,
518            Self::ExtentCsum => raw::BTRFS_EXTENT_CSUM_OBJECTID as u64,
519            Self::FreeSpace => raw::BTRFS_FREE_SPACE_OBJECTID as u64,
520            Self::FreeIno => raw::BTRFS_FREE_INO_OBJECTID as u64,
521            Self::CsumChange => raw::BTRFS_CSUM_CHANGE_OBJECTID as u64,
522            Self::Multiple => raw::BTRFS_MULTIPLE_OBJECTIDS as u64,
523            Self::FirstFree => raw::BTRFS_FIRST_FREE_OBJECTID as u64,
524            Self::Id(v) => v,
525        }
526    }
527
528    /// Display an objectid with context-dependent disambiguation.
529    ///
530    /// Some objectid values have different meanings depending on the key type.
531    /// For example, objectid 1 is `ROOT_TREE` in general but `DEV_ITEMS`
532    /// when used with `DEV_ITEM_KEY`, and `0` is `DEV_STATS` when used
533    /// with `PERSISTENT_ITEM_KEY`.
534    #[must_use]
535    #[allow(clippy::cast_lossless)]
536    pub fn display_with_type(self, key_type: KeyType) -> String {
537        let raw = self.to_raw();
538        // Special disambiguations from the C reference print_objectid()
539        if raw == raw::BTRFS_DEV_ITEMS_OBJECTID as u64
540            && key_type == KeyType::DeviceItem
541        {
542            return "DEV_ITEMS".to_string();
543        }
544        if raw == raw::BTRFS_DEV_STATS_OBJECTID as u64
545            && key_type == KeyType::PersistentItem
546        {
547            return "DEV_STATS".to_string();
548        }
549        if raw == raw::BTRFS_FIRST_CHUNK_TREE_OBJECTID as u64
550            && key_type == KeyType::ChunkItem
551        {
552            return "FIRST_CHUNK_TREE".to_string();
553        }
554        // DEV_EXTENT objectids are device IDs, not tree IDs — print as numbers
555        if key_type == KeyType::DeviceExtent {
556            return raw.to_string();
557        }
558        self.to_string()
559    }
560
561    /// Parse a tree name string (for CLI `-t` flag) into an `ObjectId`.
562    pub fn from_tree_name(name: &str) -> Option<Self> {
563        match name.to_lowercase().as_str() {
564            "root" => Some(Self::RootTree),
565            "extent" => Some(Self::ExtentTree),
566            "chunk" => Some(Self::ChunkTree),
567            "device" | "dev" => Some(Self::DevTree),
568            "fs" => Some(Self::FsTree),
569            "root_tree_dir" => Some(Self::RootTreeDir),
570            "csum" | "checksum" => Some(Self::CsumTree),
571            "quota" => Some(Self::QuotaTree),
572            "uuid" => Some(Self::UuidTree),
573            "free_space" | "free-space" => Some(Self::FreeSpaceTree),
574            "block_group" | "block-group" => Some(Self::BlockGroupTree),
575            "raid_stripe" | "raid-stripe" => Some(Self::RaidStripeTree),
576            "remap" => Some(Self::RemapTree),
577            "tree_log" | "tree-log" => Some(Self::TreeLog),
578            "tree_log_fixup" | "tree-log-fixup" => Some(Self::TreeLogFixup),
579            "tree_reloc" | "tree-reloc" => Some(Self::TreeReloc),
580            "data_reloc" | "data-reloc" => Some(Self::DataRelocTree),
581            _ => name.parse::<u64>().ok().map(Self::from_raw),
582        }
583    }
584}
585
586impl fmt::Display for ObjectId {
587    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
588        match self {
589            Self::RootTree => write!(f, "ROOT_TREE"),
590            Self::ExtentTree => write!(f, "EXTENT_TREE"),
591            Self::ChunkTree => write!(f, "CHUNK_TREE"),
592            Self::DevTree => write!(f, "DEV_TREE"),
593            Self::FsTree => write!(f, "FS_TREE"),
594            Self::RootTreeDir => write!(f, "ROOT_TREE_DIR"),
595            Self::CsumTree => write!(f, "CSUM_TREE"),
596            Self::QuotaTree => write!(f, "QUOTA_TREE"),
597            Self::UuidTree => write!(f, "UUID_TREE"),
598            Self::FreeSpaceTree => write!(f, "FREE_SPACE_TREE"),
599            Self::BlockGroupTree => write!(f, "BLOCK_GROUP_TREE"),
600            Self::RaidStripeTree => write!(f, "RAID_STRIPE_TREE"),
601            Self::RemapTree => write!(f, "REMAP_TREE"),
602            Self::DeviceStats => write!(f, "DEV_STATS"),
603            Self::Balance => write!(f, "BALANCE"),
604            Self::Orphan => write!(f, "ORPHAN"),
605            Self::TreeLog => write!(f, "TREE_LOG"),
606            Self::TreeLogFixup => write!(f, "TREE_LOG_FIXUP"),
607            Self::TreeReloc => write!(f, "TREE_RELOC"),
608            Self::DataRelocTree => write!(f, "DATA_RELOC_TREE"),
609            Self::ExtentCsum => write!(f, "EXTENT_CSUM"),
610            Self::FreeSpace => write!(f, "FREE_SPACE"),
611            Self::FreeIno => write!(f, "FREE_INO"),
612            Self::CsumChange => write!(f, "CSUM_CHANGE"),
613            Self::Multiple => write!(f, "MULTIPLE"),
614            Self::FirstFree => write!(f, "256"),
615            Self::Id(v) => write!(f, "{v}"),
616        }
617    }
618}
619
620/// A parsed btrfs disk key (objectid, type, offset).
621///
622/// Every item and key pointer in a B-tree is addressed by this three-part key.
623/// Keys are sorted lexicographically: first by objectid, then type, then offset.
624#[derive(Debug, Clone, Copy, PartialEq, Eq)]
625pub struct DiskKey {
626    /// Object this key belongs to (e.g. inode number, tree ID, device ID).
627    pub objectid: u64,
628    /// Discriminates what kind of data this key addresses.
629    pub key_type: KeyType,
630    /// Type-specific offset (e.g. byte offset for extents, directory index).
631    pub offset: u64,
632}
633
634impl DiskKey {
635    /// Parse a disk key from `buf` at byte offset `off`.
636    /// The on-disk layout is: objectid (le64), type (u8), offset (le64) = 17 bytes.
637    #[must_use]
638    pub fn parse(buf: &[u8], off: usize) -> Self {
639        let mut buf = &buf[off..];
640        let objectid = buf.get_u64_le();
641        let key_type = KeyType::from_raw(buf.get_u8());
642        let offset = buf.get_u64_le();
643        Self {
644            objectid,
645            key_type,
646            offset,
647        }
648    }
649}
650
651/// Format a key as `(OBJECTID TYPE OFFSET)` matching the C reference output.
652///
653/// Special formatting rules:
654/// - Qgroup keys: objectid and offset are formatted as `LEVEL/SUBVOLID`
655/// - UUID keys: objectid is formatted as `0x<16-char-hex>`
656/// - Offset of `u64::MAX` is formatted as `-1`
657#[must_use]
658pub fn format_key(key: &DiskKey) -> String {
659    let objectid_str = format_key_objectid(key);
660    let type_str = format_key_type(key);
661    let offset_str = format_key_offset(key);
662    format!("({objectid_str} {type_str} {offset_str})")
663}
664
665fn format_key_objectid(key: &DiskKey) -> String {
666    match key.key_type {
667        KeyType::QgroupRelation
668        | KeyType::QgroupInfo
669        | KeyType::QgroupLimit => {
670            let level = key.objectid >> 48;
671            let subvolid = key.objectid & ((1u64 << 48) - 1);
672            format!("{level}/{subvolid}")
673        }
674        KeyType::UuidKeySubvol | KeyType::UuidKeyReceivedSubvol => {
675            format!("0x{:016x}", key.objectid)
676        }
677        _ => {
678            let oid = ObjectId::from_raw(key.objectid);
679            oid.display_with_type(key.key_type)
680        }
681    }
682}
683
684#[allow(clippy::cast_sign_loss)]
685fn format_key_type(key: &DiskKey) -> String {
686    // Special case: type 0 with FREE_SPACE objectid means UNTYPED
687    if key.key_type == KeyType::Unknown(0)
688        && key.objectid == raw::BTRFS_FREE_SPACE_OBJECTID as u64
689    {
690        return "UNTYPED".to_string();
691    }
692    key.key_type.to_string()
693}
694
695fn format_key_offset(key: &DiskKey) -> String {
696    match key.key_type {
697        KeyType::QgroupRelation
698        | KeyType::QgroupStatus
699        | KeyType::QgroupInfo
700        | KeyType::QgroupLimit => {
701            let level = key.offset >> 48;
702            let subvolid = key.offset & ((1u64 << 48) - 1);
703            format!("{level}/{subvolid}")
704        }
705        KeyType::UuidKeySubvol | KeyType::UuidKeyReceivedSubvol => {
706            format!("0x{:016x}", key.offset)
707        }
708        _ => format!("{}", key.offset),
709    }
710}
711
712bitflags::bitflags! {
713    /// Tree block header flags stored in `btrfs_header::flags` (lower 56 bits;
714    /// the upper 8 bits hold the backref revision).
715    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
716    pub struct HeaderFlags: u64 {
717        const WRITTEN = raw::BTRFS_HEADER_FLAG_WRITTEN as u64;
718        const RELOC   = raw::BTRFS_HEADER_FLAG_RELOC as u64;
719        // Preserve unknown bits from the on-disk value.
720        const _ = !0;
721    }
722}
723
724impl fmt::Display for HeaderFlags {
725    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
726        let known = Self::WRITTEN | Self::RELOC;
727        let mut parts = Vec::new();
728        if self.contains(Self::WRITTEN) {
729            parts.push("WRITTEN");
730        }
731        if self.contains(Self::RELOC) {
732            parts.push("RELOC");
733        }
734        if !(*self & !known).is_empty() {
735            parts.push("UNKNOWN");
736        }
737        if parts.is_empty() {
738            write!(f, "0x0")
739        } else {
740            write!(f, "{}", parts.join("|"))
741        }
742    }
743}
744
745/// Parsed header of a btrfs tree block (shared by nodes and leaves).
746///
747/// Every tree block (node or leaf) begins with this 101-byte header. It
748/// identifies the block's position in the tree, its owning tree, and contains
749/// a checksum covering the entire block.
750#[derive(Debug, Clone)]
751pub struct Header {
752    /// Checksum of everything past this field to the end of the tree block.
753    pub csum: [u8; 32],
754    /// Filesystem UUID (must match the superblock's fsid or `metadata_uuid`).
755    pub fsid: Uuid,
756    /// Logical byte offset where this block is stored.
757    pub bytenr: u64,
758    /// Header flags (lower 56 bits) and backref revision (upper 8 bits).
759    pub flags: HeaderFlags,
760    /// UUID of the chunk tree that maps this block's logical address.
761    pub chunk_tree_uuid: Uuid,
762    /// Transaction generation when this block was last written.
763    pub generation: u64,
764    /// Objectid of the tree that owns this block (e.g. 1 = root tree, 5 = FS tree).
765    pub owner: u64,
766    /// Number of items (leaf) or key pointers (node) in this block.
767    pub nritems: u32,
768    /// Tree level: 0 for leaves, >0 for internal nodes.
769    pub level: u8,
770}
771
772/// Size of the on-disk header in bytes.
773const HEADER_SIZE: usize = mem::size_of::<raw::btrfs_header>();
774
775impl Header {
776    /// Parse a tree block header from the start of `buf`.
777    ///
778    /// # Panics
779    ///
780    /// Panics if `buf` is smaller than the on-disk header size (101 bytes).
781    #[must_use]
782    pub fn parse(buf: &[u8]) -> Self {
783        assert!(
784            buf.len() >= HEADER_SIZE,
785            "buffer too small for btrfs_header: {} < {HEADER_SIZE}",
786            buf.len()
787        );
788        let mut b = buf;
789        let mut csum = [0u8; 32];
790        csum.copy_from_slice(&b[..32]);
791        b.advance(32);
792        let fsid = get_uuid(&mut b);
793        let bytenr = b.get_u64_le();
794        let flags = HeaderFlags::from_bits_truncate(b.get_u64_le());
795        let chunk_tree_uuid = get_uuid(&mut b);
796        let generation = b.get_u64_le();
797        let owner = b.get_u64_le();
798        let nritems = b.get_u32_le();
799        let level = b.get_u8();
800        Self {
801            csum,
802            fsid,
803            bytenr,
804            flags,
805            chunk_tree_uuid,
806            generation,
807            owner,
808            nritems,
809            level,
810        }
811    }
812
813    /// Return the backref revision from the flags field.
814    #[must_use]
815    pub fn backref_rev(&self) -> u64 {
816        self.flags.bits() >> raw::BTRFS_BACKREF_REV_SHIFT
817    }
818
819    /// Return the block flags with the backref revision bits masked out.
820    #[must_use]
821    pub fn block_flags(&self) -> HeaderFlags {
822        let mask = (u64::from(raw::BTRFS_BACKREF_REV_MAX) - 1)
823            << raw::BTRFS_BACKREF_REV_SHIFT;
824        HeaderFlags::from_bits_truncate(self.flags.bits() & !mask)
825    }
826}
827
828/// A key pointer from an internal tree node, pointing to a child block.
829#[derive(Debug, Clone, Copy)]
830pub struct KeyPtr {
831    /// The lowest key in the child subtree.
832    pub key: DiskKey,
833    /// Logical byte address of the child tree block.
834    pub blockptr: u64,
835    /// Generation of the child block (used for consistency checks).
836    pub generation: u64,
837}
838
839/// Size of a key pointer on disk.
840const KEY_PTR_SIZE: usize = mem::size_of::<raw::btrfs_key_ptr>();
841
842impl KeyPtr {
843    /// Parse a key pointer from `buf` at byte offset `off`.
844    fn parse(buf: &[u8], off: usize) -> Self {
845        let key = DiskKey::parse(buf, off);
846        let mut buf = &buf[off + 17..];
847        Self {
848            key,
849            blockptr: buf.get_u64_le(),
850            generation: buf.get_u64_le(),
851        }
852    }
853}
854
855/// A leaf item descriptor: key + offset/size into the leaf's data area.
856#[derive(Debug, Clone, Copy)]
857pub struct Item {
858    /// The three-part key that identifies this item (objectid, type, offset).
859    pub key: DiskKey,
860    /// Byte offset of this item's data, relative to the end of the item array
861    /// (i.e. relative to `HEADER_SIZE + nritems * ITEM_SIZE`... but actually
862    /// it's an offset from the start of the leaf data area in the C code,
863    /// which starts right after the header). See `Leaf::item_data()`.
864    pub offset: u32,
865    /// Size of this item's data in bytes.
866    pub size: u32,
867}
868
869/// Size of an item descriptor on disk.
870const ITEM_SIZE: usize = mem::size_of::<raw::btrfs_item>();
871
872impl Item {
873    /// Parse an item descriptor from `buf` at byte offset `off`.
874    fn parse(buf: &[u8], off: usize) -> Self {
875        let key = DiskKey::parse(buf, off);
876        let mut buf = &buf[off + 17..];
877        Self {
878            key,
879            offset: buf.get_u32_le(),
880            size: buf.get_u32_le(),
881        }
882    }
883}
884
885/// A parsed btrfs tree block: either an internal node or a leaf.
886///
887/// Nodes (level > 0) contain sorted key pointers to child blocks. Leaves
888/// (level == 0) contain sorted items whose data payloads can be parsed with
889/// [`crate::items::parse_item_payload`].
890pub enum TreeBlock {
891    /// Internal node (level > 0): contains key pointers to child blocks.
892    Node {
893        /// The tree block header.
894        header: Header,
895        /// Sorted key pointers to child blocks.
896        ptrs: Vec<KeyPtr>,
897    },
898    /// Leaf node (level == 0): contains items with data payloads.
899    Leaf {
900        /// The tree block header.
901        header: Header,
902        /// Sorted item descriptors (key + offset/size into the data area).
903        items: Vec<Item>,
904        /// The full block data, so item formatters can extract payloads.
905        data: Vec<u8>,
906    },
907}
908
909impl TreeBlock {
910    /// Parse a tree block from a nodesize-length buffer.
911    #[must_use]
912    pub fn parse(buf: &[u8]) -> Self {
913        let header = Header::parse(buf);
914        let nritems = header.nritems as usize;
915
916        if header.level > 0 {
917            let mut ptrs = Vec::with_capacity(nritems);
918            for i in 0..nritems {
919                let off = HEADER_SIZE + i * KEY_PTR_SIZE;
920                ptrs.push(KeyPtr::parse(buf, off));
921            }
922            Self::Node { header, ptrs }
923        } else {
924            let mut items = Vec::with_capacity(nritems);
925            for i in 0..nritems {
926                let off = HEADER_SIZE + i * ITEM_SIZE;
927                items.push(Item::parse(buf, off));
928            }
929            Self::Leaf {
930                header,
931                items,
932                data: buf.to_vec(),
933            }
934        }
935    }
936
937    /// Return a reference to the header.
938    #[must_use]
939    pub fn header(&self) -> &Header {
940        match self {
941            Self::Node { header, .. } | Self::Leaf { header, .. } => header,
942        }
943    }
944
945    /// For a leaf block, get the data slice for item at `index`.
946    ///
947    /// The item's `offset` field is relative to the start of the data area,
948    /// which begins right after the header. So the absolute offset in the
949    /// block buffer is `HEADER_SIZE + item.offset`.
950    #[must_use]
951    pub fn item_data(&self, index: usize) -> Option<&[u8]> {
952        match self {
953            Self::Leaf { items, data, .. } => {
954                let item = items.get(index)?;
955                let start = HEADER_SIZE + item.offset as usize;
956                let end = start + item.size as usize;
957                if end <= data.len() {
958                    Some(&data[start..end])
959                } else {
960                    None
961                }
962            }
963            Self::Node { .. } => None,
964        }
965    }
966}
967
968#[cfg(test)]
969mod tests {
970    use super::*;
971
972    // Helper to build a minimal LE buffer for a disk key
973    fn make_disk_key(objectid: u64, key_type: u8, offset: u64) -> [u8; 17] {
974        let mut buf = [0u8; 17];
975        buf[0..8].copy_from_slice(&objectid.to_le_bytes());
976        buf[8] = key_type;
977        buf[9..17].copy_from_slice(&offset.to_le_bytes());
978        buf
979    }
980
981    // Helper to build a minimal tree block header
982    fn make_header(
983        bytenr: u64,
984        generation: u64,
985        owner: u64,
986        nritems: u32,
987        level: u8,
988    ) -> Vec<u8> {
989        let mut buf = vec![0u8; HEADER_SIZE];
990        // csum: leave as zeros
991        // fsid: leave as zeros
992        buf[48..56].copy_from_slice(&bytenr.to_le_bytes());
993        // flags: WRITTEN
994        buf[56..64].copy_from_slice(
995            &(raw::BTRFS_HEADER_FLAG_WRITTEN as u64).to_le_bytes(),
996        );
997        // chunk_tree_uuid: leave as zeros
998        buf[80..88].copy_from_slice(&generation.to_le_bytes());
999        buf[88..96].copy_from_slice(&owner.to_le_bytes());
1000        buf[96..100].copy_from_slice(&nritems.to_le_bytes());
1001        buf[100] = level;
1002        buf
1003    }
1004
1005    #[test]
1006    fn key_type_round_trip() {
1007        for raw_val in 0..=255u8 {
1008            let kt = KeyType::from_raw(raw_val);
1009            assert_eq!(kt.to_raw(), raw_val);
1010        }
1011    }
1012
1013    #[test]
1014    fn key_type_display() {
1015        assert_eq!(KeyType::InodeItem.to_string(), "INODE_ITEM");
1016        assert_eq!(KeyType::ChunkItem.to_string(), "CHUNK_ITEM");
1017        assert_eq!(KeyType::Unknown(99).to_string(), "UNKNOWN.99");
1018    }
1019
1020    #[test]
1021    fn objectid_round_trip() {
1022        let cases: &[u64] = &[
1023            1,
1024            2,
1025            3,
1026            4,
1027            5,
1028            6,
1029            7,
1030            8,
1031            9,
1032            10,
1033            11,
1034            12,
1035            13,
1036            256,
1037            1000,
1038            // Negative objectids cast to u64
1039            raw::BTRFS_BALANCE_OBJECTID as u64,
1040            raw::BTRFS_ORPHAN_OBJECTID as u64,
1041            raw::BTRFS_TREE_LOG_OBJECTID as u64,
1042            raw::BTRFS_DATA_RELOC_TREE_OBJECTID as u64,
1043        ];
1044        for &v in cases {
1045            let oid = ObjectId::from_raw(v);
1046            assert_eq!(oid.to_raw(), v, "round-trip failed for {v}");
1047        }
1048    }
1049
1050    #[test]
1051    fn objectid_display() {
1052        assert_eq!(ObjectId::RootTree.to_string(), "ROOT_TREE");
1053        assert_eq!(ObjectId::FsTree.to_string(), "FS_TREE");
1054        assert_eq!(ObjectId::Id(256).to_string(), "256");
1055        assert_eq!(ObjectId::Id(u64::MAX).to_string(), "18446744073709551615");
1056    }
1057
1058    #[test]
1059    fn objectid_display_with_type() {
1060        // objectid 1 is normally ROOT_TREE but DEV_ITEMS with DeviceItem key
1061        let oid = ObjectId::from_raw(1);
1062        assert_eq!(oid.display_with_type(KeyType::RootItem), "ROOT_TREE");
1063        assert_eq!(oid.display_with_type(KeyType::DeviceItem), "DEV_ITEMS");
1064    }
1065
1066    #[test]
1067    fn objectid_from_tree_name() {
1068        assert_eq!(ObjectId::from_tree_name("root"), Some(ObjectId::RootTree));
1069        assert_eq!(
1070            ObjectId::from_tree_name("CHUNK"),
1071            Some(ObjectId::ChunkTree)
1072        );
1073        assert_eq!(
1074            ObjectId::from_tree_name("free-space"),
1075            Some(ObjectId::FreeSpaceTree)
1076        );
1077        assert_eq!(ObjectId::from_tree_name("5"), Some(ObjectId::FsTree));
1078        assert_eq!(ObjectId::from_tree_name("256"), Some(ObjectId::Id(256)));
1079        assert_eq!(ObjectId::from_tree_name("nosuch"), None);
1080    }
1081
1082    #[test]
1083    fn parse_disk_key() {
1084        let buf = make_disk_key(42, raw::BTRFS_INODE_ITEM_KEY as u8, 100);
1085        let key = DiskKey::parse(&buf, 0);
1086        assert_eq!(key.objectid, 42);
1087        assert_eq!(key.key_type, KeyType::InodeItem);
1088        assert_eq!(key.offset, 100);
1089    }
1090
1091    #[test]
1092    fn parse_header() {
1093        let buf = make_header(65536, 7, 5, 10, 0);
1094        let hdr = Header::parse(&buf);
1095        assert_eq!(hdr.bytenr, 65536);
1096        assert_eq!(hdr.generation, 7);
1097        assert_eq!(hdr.owner, 5);
1098        assert_eq!(hdr.nritems, 10);
1099        assert_eq!(hdr.level, 0);
1100        assert_eq!(
1101            hdr.block_flags(),
1102            HeaderFlags::from_bits_truncate(
1103                raw::BTRFS_HEADER_FLAG_WRITTEN as u64
1104            )
1105        );
1106    }
1107
1108    #[test]
1109    fn header_flags_display_written() {
1110        let flags = HeaderFlags::from_bits_truncate(
1111            raw::BTRFS_HEADER_FLAG_WRITTEN as u64,
1112        );
1113        assert_eq!(flags.to_string(), "WRITTEN");
1114    }
1115
1116    #[test]
1117    fn header_flags_display_multiple() {
1118        let flags = HeaderFlags::from_bits_truncate(
1119            raw::BTRFS_HEADER_FLAG_WRITTEN as u64
1120                | raw::BTRFS_HEADER_FLAG_RELOC as u64,
1121        );
1122        assert_eq!(flags.to_string(), "WRITTEN|RELOC");
1123    }
1124
1125    #[test]
1126    fn parse_leaf_block() {
1127        let nodesize = 4096usize;
1128        let nritems = 2u32;
1129        let mut buf = vec![0u8; nodesize];
1130
1131        // Write header (level 0 = leaf)
1132        let hdr = make_header(65536, 7, 5, nritems, 0);
1133        buf[..HEADER_SIZE].copy_from_slice(&hdr);
1134
1135        // Write two item descriptors
1136        // Item 0: key=(256, INODE_ITEM, 0), offset=3800, size=160
1137        let key0 = make_disk_key(256, raw::BTRFS_INODE_ITEM_KEY as u8, 0);
1138        let item0_off = HEADER_SIZE;
1139        buf[item0_off..item0_off + 17].copy_from_slice(&key0);
1140        buf[item0_off + 17..item0_off + 21]
1141            .copy_from_slice(&3800u32.to_le_bytes());
1142        buf[item0_off + 21..item0_off + 25]
1143            .copy_from_slice(&160u32.to_le_bytes());
1144
1145        // Item 1: key=(256, DIR_ITEM, 100), offset=3700, size=50
1146        let key1 = make_disk_key(256, raw::BTRFS_DIR_ITEM_KEY as u8, 100);
1147        let item1_off = HEADER_SIZE + ITEM_SIZE;
1148        buf[item1_off..item1_off + 17].copy_from_slice(&key1);
1149        buf[item1_off + 17..item1_off + 21]
1150            .copy_from_slice(&3700u32.to_le_bytes());
1151        buf[item1_off + 21..item1_off + 25]
1152            .copy_from_slice(&50u32.to_le_bytes());
1153
1154        // Write some recognizable data at the item data offsets
1155        // Item 0 data at HEADER_SIZE + 3800
1156        let data0_start = HEADER_SIZE + 3800;
1157        buf[data0_start] = 0xAA;
1158        // Item 1 data at HEADER_SIZE + 3700
1159        let data1_start = HEADER_SIZE + 3700;
1160        buf[data1_start] = 0xBB;
1161
1162        let block = TreeBlock::parse(&buf);
1163
1164        match &block {
1165            TreeBlock::Leaf { header, items, .. } => {
1166                assert_eq!(header.level, 0);
1167                assert_eq!(items.len(), 2);
1168                assert_eq!(items[0].key.key_type, KeyType::InodeItem);
1169                assert_eq!(items[1].key.key_type, KeyType::DirItem);
1170            }
1171            TreeBlock::Node { .. } => panic!("expected leaf"),
1172        }
1173
1174        let data0 = block.item_data(0).unwrap();
1175        assert_eq!(data0[0], 0xAA);
1176        assert_eq!(data0.len(), 160);
1177
1178        let data1 = block.item_data(1).unwrap();
1179        assert_eq!(data1[0], 0xBB);
1180        assert_eq!(data1.len(), 50);
1181    }
1182
1183    #[test]
1184    fn parse_node_block() {
1185        let nodesize = 4096usize;
1186        let nritems = 3u32;
1187        let mut buf = vec![0u8; nodesize];
1188
1189        // Write header (level 1 = node)
1190        let hdr = make_header(131072, 10, 2, nritems, 1);
1191        buf[..HEADER_SIZE].copy_from_slice(&hdr);
1192
1193        // Write three key pointers
1194        for i in 0..3u64 {
1195            let off = HEADER_SIZE + i as usize * KEY_PTR_SIZE;
1196            let key = make_disk_key(i + 1, raw::BTRFS_ROOT_ITEM_KEY as u8, 0);
1197            buf[off..off + 17].copy_from_slice(&key);
1198            let blockptr = (i + 1) * 65536;
1199            buf[off + 17..off + 25].copy_from_slice(&blockptr.to_le_bytes());
1200            let generation = 10 - i;
1201            buf[off + 25..off + 33].copy_from_slice(&generation.to_le_bytes());
1202        }
1203
1204        let block = TreeBlock::parse(&buf);
1205
1206        match &block {
1207            TreeBlock::Node { header, ptrs } => {
1208                assert_eq!(header.level, 1);
1209                assert_eq!(header.bytenr, 131072);
1210                assert_eq!(ptrs.len(), 3);
1211                assert_eq!(ptrs[0].blockptr, 65536);
1212                assert_eq!(ptrs[1].blockptr, 131072);
1213                assert_eq!(ptrs[2].blockptr, 196608);
1214                assert_eq!(ptrs[0].generation, 10);
1215                assert_eq!(ptrs[2].generation, 8);
1216            }
1217            TreeBlock::Leaf { .. } => panic!("expected node"),
1218        }
1219
1220        // item_data should return None for nodes
1221        assert!(block.item_data(0).is_none());
1222    }
1223
1224    #[test]
1225    fn format_key_basic() {
1226        let key = DiskKey {
1227            objectid: 256,
1228            key_type: KeyType::InodeItem,
1229            offset: 0,
1230        };
1231        assert_eq!(format_key(&key), "(256 INODE_ITEM 0)");
1232    }
1233
1234    #[test]
1235    fn format_key_well_known_objectid() {
1236        let key = DiskKey {
1237            objectid: raw::BTRFS_FS_TREE_OBJECTID as u64,
1238            key_type: KeyType::RootItem,
1239            offset: u64::MAX,
1240        };
1241        assert_eq!(
1242            format_key(&key),
1243            "(FS_TREE ROOT_ITEM 18446744073709551615)"
1244        );
1245    }
1246
1247    #[test]
1248    fn format_key_qgroup() {
1249        let key = DiskKey {
1250            objectid: 0, // level=0, subvolid=0
1251            key_type: KeyType::QgroupInfo,
1252            offset: (0u64 << 48) | 256, // level=0, subvolid=256
1253        };
1254        assert_eq!(format_key(&key), "(0/0 QGROUP_INFO 0/256)");
1255    }
1256
1257    #[test]
1258    fn format_key_uuid() {
1259        let key = DiskKey {
1260            objectid: 0xdeadbeef12345678,
1261            key_type: KeyType::UuidKeySubvol,
1262            offset: 0xabcdef0123456789,
1263        };
1264        assert_eq!(
1265            format_key(&key),
1266            "(0xdeadbeef12345678 UUID_KEY_SUBVOL 0xabcdef0123456789)"
1267        );
1268    }
1269
1270    #[test]
1271    fn format_key_dev_items() {
1272        let key = DiskKey {
1273            objectid: 1,
1274            key_type: KeyType::DeviceItem,
1275            offset: 1,
1276        };
1277        assert_eq!(format_key(&key), "(DEV_ITEMS DEV_ITEM 1)");
1278    }
1279}