Skip to main content

btrfs_disk/
items.rs

1//! # Typed Rust structs for btrfs tree item payloads
2//!
3//! Each on-disk item type has a corresponding struct with a `parse` method
4//! that reads from a raw byte buffer using safe LE reader helpers. These
5//! structs are the public API for item data; display formatting lives in
6//! the `cli` crate.
7
8/// Read a UUID (16 bytes) from a `Buf` cursor, advancing it by 16 bytes.
9use crate::util::get_uuid;
10use crate::{
11    raw,
12    tree::{DiskKey, ObjectId},
13    util::raw_crc32c,
14};
15use bytes::{Buf, BufMut};
16use std::{fmt, mem};
17use uuid::Uuid;
18
19bitflags::bitflags! {
20    /// Block group / chunk type flags: the combination of chunk type
21    /// (DATA, SYSTEM, METADATA) and RAID profile stored in on-disk chunk
22    /// items and block group items.
23    ///
24    /// Display produces the dump-tree format: `DATA|DUP`, `METADATA|single`, etc.
25    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26    pub struct BlockGroupFlags: u64 {
27        const DATA     = raw::BTRFS_BLOCK_GROUP_DATA as u64;
28        const SYSTEM   = raw::BTRFS_BLOCK_GROUP_SYSTEM as u64;
29        const METADATA = raw::BTRFS_BLOCK_GROUP_METADATA as u64;
30        const RAID0    = raw::BTRFS_BLOCK_GROUP_RAID0 as u64;
31        const RAID1    = raw::BTRFS_BLOCK_GROUP_RAID1 as u64;
32        const DUP      = raw::BTRFS_BLOCK_GROUP_DUP as u64;
33        const RAID10   = raw::BTRFS_BLOCK_GROUP_RAID10 as u64;
34        const RAID5    = raw::BTRFS_BLOCK_GROUP_RAID5 as u64;
35        const RAID6    = raw::BTRFS_BLOCK_GROUP_RAID6 as u64;
36        const RAID1C3  = raw::BTRFS_BLOCK_GROUP_RAID1C3 as u64;
37        const RAID1C4  = raw::BTRFS_BLOCK_GROUP_RAID1C4 as u64;
38
39        /// Explicit "single" marker (bit 48). When no profile bits are
40        /// set, the allocation is also single.
41        const SINGLE     = raw::BTRFS_AVAIL_ALLOC_BIT_SINGLE;
42
43        /// Pseudo-type used for the global reservation pool.
44        const GLOBAL_RSV = raw::BTRFS_SPACE_INFO_GLOBAL_RSV;
45    }
46}
47
48impl BlockGroupFlags {
49    /// Returns the human-readable chunk type name.
50    #[must_use]
51    pub fn type_name(self) -> &'static str {
52        if self.contains(Self::GLOBAL_RSV) {
53            return "GlobalReserve";
54        }
55        let ty = self & (Self::DATA | Self::SYSTEM | Self::METADATA);
56        match ty {
57            t if t == Self::DATA => "Data",
58            t if t == Self::SYSTEM => "System",
59            t if t == Self::METADATA => "Metadata",
60            t if t == Self::DATA | Self::METADATA => "Data+Metadata",
61            _ => "unknown",
62        }
63    }
64
65    /// Returns the RAID profile name, or `"single"` when no profile bit is set.
66    #[must_use]
67    pub fn profile_name(self) -> &'static str {
68        let profile = self
69            & (Self::RAID0
70                | Self::RAID1
71                | Self::DUP
72                | Self::RAID10
73                | Self::RAID5
74                | Self::RAID6
75                | Self::RAID1C3
76                | Self::RAID1C4
77                | Self::SINGLE);
78        match profile {
79            p if p == Self::RAID0 => "RAID0",
80            p if p == Self::RAID1 => "RAID1",
81            p if p == Self::DUP => "DUP",
82            p if p == Self::RAID10 => "RAID10",
83            p if p == Self::RAID5 => "RAID5",
84            p if p == Self::RAID6 => "RAID6",
85            p if p == Self::RAID1C3 => "RAID1C3",
86            p if p == Self::RAID1C4 => "RAID1C4",
87            // Both explicit SINGLE and no-profile-bits mean "single".
88            _ => "single",
89        }
90    }
91}
92
93bitflags::bitflags! {
94    /// Inode item flags stored in `btrfs_inode_item::flags`.
95    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
96    pub struct InodeFlags: u64 {
97        const NODATASUM      = raw::BTRFS_INODE_NODATASUM as u64;
98        const NODATACOW      = raw::BTRFS_INODE_NODATACOW as u64;
99        const READONLY       = raw::BTRFS_INODE_READONLY as u64;
100        const NOCOMPRESS     = raw::BTRFS_INODE_NOCOMPRESS as u64;
101        const PREALLOC       = raw::BTRFS_INODE_PREALLOC as u64;
102        const SYNC           = raw::BTRFS_INODE_SYNC as u64;
103        const IMMUTABLE      = raw::BTRFS_INODE_IMMUTABLE as u64;
104        const APPEND         = raw::BTRFS_INODE_APPEND as u64;
105        const NODUMP         = raw::BTRFS_INODE_NODUMP as u64;
106        const NOATIME        = raw::BTRFS_INODE_NOATIME as u64;
107        const DIRSYNC        = raw::BTRFS_INODE_DIRSYNC as u64;
108        const COMPRESS       = raw::BTRFS_INODE_COMPRESS as u64;
109        const ROOT_ITEM_INIT = raw::BTRFS_INODE_ROOT_ITEM_INIT as u64;
110        // Preserve unknown bits from the on-disk value.
111        const _ = !0;
112    }
113}
114
115impl fmt::Display for InodeFlags {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        const NAMES: &[(InodeFlags, &str)] = &[
118            (InodeFlags::NODATASUM, "NODATASUM"),
119            (InodeFlags::NODATACOW, "NODATACOW"),
120            (InodeFlags::READONLY, "READONLY"),
121            (InodeFlags::NOCOMPRESS, "NOCOMPRESS"),
122            (InodeFlags::PREALLOC, "PREALLOC"),
123            (InodeFlags::SYNC, "SYNC"),
124            (InodeFlags::IMMUTABLE, "IMMUTABLE"),
125            (InodeFlags::APPEND, "APPEND"),
126            (InodeFlags::NODUMP, "NODUMP"),
127            (InodeFlags::NOATIME, "NOATIME"),
128            (InodeFlags::DIRSYNC, "DIRSYNC"),
129            (InodeFlags::COMPRESS, "COMPRESS"),
130            (InodeFlags::ROOT_ITEM_INIT, "ROOT_ITEM_INIT"),
131        ];
132        let known: InodeFlags = NAMES
133            .iter()
134            .fold(InodeFlags::empty(), |a, &(flag, _)| a | flag);
135        let mut parts: Vec<String> = NAMES
136            .iter()
137            .filter(|&&(flag, _)| self.contains(flag))
138            .map(|&(_, name)| name.to_string())
139            .collect();
140        let unknown = *self & !known;
141        if !unknown.is_empty() {
142            parts.push(format!("UNKNOWN: 0x{:x}", unknown.bits()));
143        }
144        if parts.is_empty() {
145            write!(f, "none")
146        } else {
147            write!(f, "{}", parts.join("|"))
148        }
149    }
150}
151/// Btrfs timestamp (seconds + nanoseconds since Unix epoch).
152#[derive(Debug, Clone, Copy)]
153pub struct Timespec {
154    /// Seconds since 1970-01-01 00:00:00 UTC.
155    pub sec: u64,
156    /// Nanosecond component (`0..999_999_999`).
157    pub nsec: u32,
158}
159
160impl Timespec {
161    fn parse(buf: &mut &[u8]) -> Self {
162        Self {
163            sec: buf.get_u64_le(),
164            nsec: buf.get_u32_le(),
165        }
166    }
167}
168
169/// Compression type for file extents.
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171/// See also `btrfs_uapi::defrag::CompressType` which omits `None`/`Unknown`
172/// for use in ioctl requests.
173pub enum CompressionType {
174    /// No compression.
175    None,
176    /// Zlib (deflate) compression.
177    Zlib,
178    /// LZO compression (btrfs per-sector format).
179    Lzo,
180    /// Zstandard compression.
181    Zstd,
182    /// Unrecognized compression type byte.
183    Unknown(u8),
184}
185
186impl CompressionType {
187    /// Convert a raw on-disk compression type byte to a `CompressionType` variant.
188    #[must_use]
189    pub fn from_raw(v: u8) -> Self {
190        match v {
191            0 => Self::None,
192            1 => Self::Zlib,
193            2 => Self::Lzo,
194            3 => Self::Zstd,
195            _ => Self::Unknown(v),
196        }
197    }
198
199    /// Return the human-readable name of this compression type.
200    #[must_use]
201    pub fn name(&self) -> &'static str {
202        match self {
203            Self::None => "none",
204            Self::Zlib => "zlib",
205            Self::Lzo => "lzo",
206            Self::Zstd => "zstd",
207            Self::Unknown(_) => "unknown",
208        }
209    }
210
211    /// Convert back to the raw on-disk byte value.
212    #[must_use]
213    pub fn to_raw(self) -> u8 {
214        match self {
215            Self::None => 0,
216            Self::Zlib => 1,
217            Self::Lzo => 2,
218            Self::Zstd => 3,
219            Self::Unknown(v) => v,
220        }
221    }
222}
223
224/// File extent type.
225#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226pub enum FileExtentType {
227    /// Data stored directly in the tree leaf (small files or file tails).
228    Inline,
229    /// Data stored in a separate disk extent, referenced by logical address.
230    Regular,
231    /// Preallocated extent (reserved but not yet written).
232    Prealloc,
233    /// Unrecognized extent type byte.
234    Unknown(u8),
235}
236
237impl FileExtentType {
238    /// Convert a raw on-disk extent type byte to a `FileExtentType` variant.
239    #[must_use]
240    pub fn from_raw(v: u8) -> Self {
241        match u32::from(v) {
242            raw::BTRFS_FILE_EXTENT_INLINE => Self::Inline,
243            raw::BTRFS_FILE_EXTENT_REG => Self::Regular,
244            raw::BTRFS_FILE_EXTENT_PREALLOC => Self::Prealloc,
245            _ => Self::Unknown(v),
246        }
247    }
248
249    /// Return the human-readable name of this extent type.
250    #[must_use]
251    pub fn name(&self) -> &'static str {
252        match self {
253            Self::Inline => "inline",
254            Self::Regular => "regular",
255            Self::Prealloc => "prealloc",
256            Self::Unknown(_) => "unknown",
257        }
258    }
259
260    /// Convert back to the raw on-disk byte value.
261    #[must_use]
262    #[allow(clippy::cast_possible_truncation)]
263    pub fn to_raw(self) -> u8 {
264        match self {
265            Self::Inline => raw::BTRFS_FILE_EXTENT_INLINE as u8,
266            Self::Regular => raw::BTRFS_FILE_EXTENT_REG as u8,
267            Self::Prealloc => raw::BTRFS_FILE_EXTENT_PREALLOC as u8,
268            Self::Unknown(v) => v,
269        }
270    }
271}
272
273/// Directory entry file type, stored in `btrfs_dir_item::type`.
274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
275pub enum FileType {
276    /// Unknown file type (0).
277    Unknown,
278    /// Regular file.
279    RegFile,
280    /// Directory.
281    Dir,
282    /// Character device.
283    Chrdev,
284    /// Block device.
285    Blkdev,
286    /// Named pipe (FIFO).
287    Fifo,
288    /// Unix domain socket.
289    Sock,
290    /// Symbolic link.
291    Symlink,
292    /// Extended attribute (used in `XATTR_ITEM` entries).
293    Xattr,
294    /// Unrecognized file type byte.
295    Other(u8),
296}
297
298impl FileType {
299    /// Convert a raw on-disk file type byte to a `FileType` variant.
300    #[must_use]
301    pub fn from_raw(v: u8) -> Self {
302        match u32::from(v) {
303            raw::BTRFS_FT_UNKNOWN => Self::Unknown,
304            raw::BTRFS_FT_REG_FILE => Self::RegFile,
305            raw::BTRFS_FT_DIR => Self::Dir,
306            raw::BTRFS_FT_CHRDEV => Self::Chrdev,
307            raw::BTRFS_FT_BLKDEV => Self::Blkdev,
308            raw::BTRFS_FT_FIFO => Self::Fifo,
309            raw::BTRFS_FT_SOCK => Self::Sock,
310            raw::BTRFS_FT_SYMLINK => Self::Symlink,
311            raw::BTRFS_FT_XATTR => Self::Xattr,
312            _ => Self::Other(v),
313        }
314    }
315
316    /// Return the human-readable name of this file type (matches btrfs-progs output).
317    #[must_use]
318    pub fn name(&self) -> &'static str {
319        match self {
320            Self::Unknown | Self::Other(_) => "UNKNOWN",
321            Self::RegFile => "FILE",
322            Self::Dir => "DIR",
323            Self::Chrdev => "CHRDEV",
324            Self::Blkdev => "BLKDEV",
325            Self::Fifo => "FIFO",
326            Self::Sock => "SOCK",
327            Self::Symlink => "SYMLINK",
328            Self::Xattr => "XATTR",
329        }
330    }
331}
332
333/// Inode metadata, stored as `INODE_ITEM` in the FS tree.
334///
335/// Contains POSIX attributes (uid, gid, mode, timestamps) plus btrfs-specific
336/// fields (flags, sequence number, block group hint).
337#[derive(Debug, Clone)]
338pub struct InodeItem {
339    /// Generation when this inode was created.
340    pub generation: u64,
341    /// Transaction ID of the last modification.
342    pub transid: u64,
343    /// Logical file size in bytes.
344    pub size: u64,
345    /// Total on-disk bytes used (including all copies for RAID).
346    pub nbytes: u64,
347    /// Block group hint for new allocations.
348    pub block_group: u64,
349    /// Hard link count.
350    pub nlink: u32,
351    /// Owner user ID.
352    pub uid: u32,
353    /// Owner group ID.
354    pub gid: u32,
355    /// POSIX file mode (type + permissions).
356    pub mode: u32,
357    /// Device number (for character/block device inodes).
358    pub rdev: u64,
359    /// Inode flags (NODATASUM, COMPRESS, etc.).
360    pub flags: InodeFlags,
361    /// NFS-compatible change sequence number.
362    pub sequence: u64,
363    /// Last access time.
364    pub atime: Timespec,
365    /// Last change time (inode metadata).
366    pub ctime: Timespec,
367    /// Last modification time (file data).
368    pub mtime: Timespec,
369    /// Creation time.
370    pub otime: Timespec,
371}
372
373impl InodeItem {
374    /// Parse an inode item from a raw byte buffer. Returns `None` if the
375    /// buffer is too small.
376    #[must_use]
377    pub fn parse(data: &[u8]) -> Option<Self> {
378        if data.len() < mem::size_of::<raw::btrfs_inode_item>() {
379            return None;
380        }
381        let mut buf = data;
382        Some(Self {
383            generation: buf.get_u64_le(),
384            transid: buf.get_u64_le(),
385            size: buf.get_u64_le(),
386            nbytes: buf.get_u64_le(),
387            block_group: buf.get_u64_le(),
388            nlink: buf.get_u32_le(),
389            uid: buf.get_u32_le(),
390            gid: buf.get_u32_le(),
391            mode: buf.get_u32_le(),
392            rdev: buf.get_u64_le(),
393            flags: InodeFlags::from_bits_truncate(buf.get_u64_le()),
394            sequence: buf.get_u64_le(),
395            // Skip reserved[4] (4 x u64 = 32 bytes)
396            atime: {
397                buf.advance(32);
398                Timespec::parse(&mut buf)
399            },
400            ctime: Timespec::parse(&mut buf),
401            mtime: Timespec::parse(&mut buf),
402            otime: Timespec::parse(&mut buf),
403        })
404    }
405}
406
407/// Hard link reference from an inode to a directory entry.
408///
409/// Key: `(inode_number, INODE_REF, parent_dir_inode)`. Multiple refs can be
410/// packed into a single item when an inode has several hard links in the same
411/// parent directory.
412#[derive(Debug, Clone)]
413pub struct InodeRef {
414    /// Index in the parent directory (matches a `DIR_INDEX` key offset).
415    pub index: u64,
416    /// Filename component (raw bytes, typically UTF-8).
417    pub name: Vec<u8>,
418}
419
420impl InodeRef {
421    /// Parse all packed inode refs from a single item's data buffer.
422    #[must_use]
423    pub fn parse_all(data: &[u8]) -> Vec<Self> {
424        let mut result = Vec::new();
425        let mut buf = data;
426        while buf.remaining() >= 10 {
427            let index = buf.get_u64_le();
428            let name_len = buf.get_u16_le() as usize;
429            if buf.remaining() < name_len {
430                break;
431            }
432            let name = buf[..name_len].to_vec();
433            buf.advance(name_len);
434            result.push(Self { index, name });
435        }
436        result
437    }
438}
439
440/// Extended inode reference, used when the `EXTREF` feature is enabled.
441///
442/// Unlike `InodeRef`, the parent directory objectid is stored in the struct
443/// rather than the key offset, allowing references from different parent
444/// directories to coexist.
445#[derive(Debug, Clone)]
446pub struct InodeExtref {
447    /// Parent directory inode number.
448    pub parent: u64,
449    /// Index in the parent directory.
450    pub index: u64,
451    /// Filename component (raw bytes, typically UTF-8).
452    pub name: Vec<u8>,
453}
454
455impl InodeExtref {
456    /// Parse all packed extended inode refs from a single item's data buffer.
457    #[must_use]
458    pub fn parse_all(data: &[u8]) -> Vec<Self> {
459        let mut result = Vec::new();
460        let mut buf = data;
461        while buf.remaining() >= 18 {
462            let parent = buf.get_u64_le();
463            let index = buf.get_u64_le();
464            let name_len = buf.get_u16_le() as usize;
465            if buf.remaining() < name_len {
466                break;
467            }
468            let name = buf[..name_len].to_vec();
469            buf.advance(name_len);
470            result.push(Self {
471                parent,
472                index,
473                name,
474            });
475        }
476        result
477    }
478}
479
480/// Directory entry, stored as `DIR_ITEM` (hashed by name) or `DIR_INDEX`
481/// (sequential index) in the FS tree.
482///
483/// Multiple entries can be packed into a single item when names hash to the
484/// same value (for `DIR_ITEM`) or when processing xattrs (`XATTR_ITEM`).
485#[derive(Debug, Clone)]
486pub struct DirItem {
487    /// Key of the target inode (objectid = inode number, type = `INODE_ITEM`).
488    pub location: DiskKey,
489    /// Transaction ID when this entry was created.
490    pub transid: u64,
491    /// Type of the referenced inode (file, directory, symlink, etc.).
492    pub file_type: FileType,
493    /// Filename or xattr name (raw bytes).
494    pub name: Vec<u8>,
495    /// Xattr value (empty for regular directory entries).
496    pub data: Vec<u8>,
497}
498
499impl DirItem {
500    /// Parse all packed directory entries from a single item's data buffer.
501    #[must_use]
502    pub fn parse_all(data: &[u8]) -> Vec<Self> {
503        let mut result = Vec::new();
504        let dir_item_size = mem::size_of::<raw::btrfs_dir_item>();
505        let mut buf = data;
506
507        while buf.remaining() >= dir_item_size {
508            let location = DiskKey::parse(buf, 0);
509            buf.advance(17); // skip past DiskKey (u64 + u8 + u64)
510            let transid = buf.get_u64_le();
511            let data_len = buf.get_u16_le() as usize;
512            let name_len = buf.get_u16_le() as usize;
513            let file_type = FileType::from_raw(buf.get_u8());
514
515            if buf.remaining() < name_len + data_len {
516                break;
517            }
518            let name = buf[..name_len].to_vec();
519            buf.advance(name_len);
520            let item_data = buf[..data_len].to_vec();
521            buf.advance(data_len);
522            result.push(Self {
523                location,
524                transid,
525                file_type,
526                name,
527                data: item_data,
528            });
529        }
530        result
531    }
532}
533
534bitflags::bitflags! {
535    /// Root item flags stored in `btrfs_root_item::flags`.
536    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
537    pub struct RootItemFlags: u64 {
538        const RDONLY = raw::BTRFS_ROOT_SUBVOL_RDONLY as u64;
539        const DEAD   = raw::BTRFS_ROOT_SUBVOL_DEAD;
540        // Preserve unknown bits from the on-disk value.
541        const _ = !0;
542    }
543}
544
545impl fmt::Display for RootItemFlags {
546    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
547        if self.contains(Self::RDONLY) {
548            write!(f, "RDONLY")
549        } else {
550            write!(f, "none")
551        }
552    }
553}
554
555/// Root item describing a tree (subvolume, snapshot, or internal tree).
556///
557/// Stored in the root tree with key `(tree_objectid, ROOT_ITEM, 0)`. Contains
558/// the root block pointer, subvolume UUIDs, and transaction timestamps needed
559/// for snapshot management and send/receive.
560#[derive(Debug, Clone)]
561pub struct RootItem {
562    /// Generation when this root was last modified.
563    pub generation: u64,
564    /// Objectid of the root directory inode (always 256 for FS trees).
565    pub root_dirid: u64,
566    /// Logical bytenr of this tree's root block.
567    pub bytenr: u64,
568    /// Quota byte limit (0 = unlimited).
569    pub byte_limit: u64,
570    /// Bytes used by this tree.
571    pub bytes_used: u64,
572    /// Generation of the last snapshot taken from this subvolume.
573    pub last_snapshot: u64,
574    /// Root flags (RDONLY for read-only snapshots).
575    pub flags: RootItemFlags,
576    /// Reference count.
577    pub refs: u32,
578    /// Progress key for in-progress drop operations.
579    pub drop_progress: DiskKey,
580    /// Tree level of the drop progress.
581    pub drop_level: u8,
582    /// B-tree level of this tree's root block.
583    pub level: u8,
584    /// Extended generation (v2 root items, matches `generation` in practice).
585    pub generation_v2: u64,
586    /// UUID of this subvolume.
587    pub uuid: Uuid,
588    /// UUID of the parent subvolume (for snapshots).
589    pub parent_uuid: Uuid,
590    /// UUID of the subvolume this was received from (for send/receive).
591    pub received_uuid: Uuid,
592    /// Transaction ID of the last change to this subvolume.
593    pub ctransid: u64,
594    /// Transaction ID when this subvolume was created.
595    pub otransid: u64,
596    /// Transaction ID when this subvolume was sent.
597    pub stransid: u64,
598    /// Transaction ID when this subvolume was received.
599    pub rtransid: u64,
600    /// Time of the last change.
601    pub ctime: Timespec,
602    /// Creation time.
603    pub otime: Timespec,
604    /// Time when sent.
605    pub stime: Timespec,
606    /// Time when received.
607    pub rtime: Timespec,
608}
609
610impl RootItem {
611    /// Parse a root item from a raw byte buffer. Handles both v1 (shorter)
612    /// and v2 (full) root item formats gracefully, defaulting missing fields
613    /// to zero/nil.
614    #[must_use]
615    #[allow(clippy::too_many_lines)]
616    pub fn parse(data: &[u8]) -> Option<Self> {
617        let inode_size = mem::size_of::<raw::btrfs_inode_item>();
618        if data.len() < inode_size + 8 {
619            return None;
620        }
621
622        let mut buf = &data[inode_size..];
623        let generation = buf.get_u64_le();
624        let root_dirid = buf.get_u64_le();
625        let bytenr = buf.get_u64_le();
626        let byte_limit = buf.get_u64_le();
627        let bytes_used = buf.get_u64_le();
628        let last_snapshot = buf.get_u64_le();
629        let flags = RootItemFlags::from_bits_truncate(buf.get_u64_le());
630        let refs = buf.get_u32_le();
631
632        let dp_off = inode_size + 60;
633        let drop_progress = if dp_off + 17 <= data.len() {
634            DiskKey::parse(data, dp_off)
635        } else {
636            DiskKey::parse(&[0; 17], 0)
637        };
638        let drop_level = if dp_off + 17 < data.len() {
639            data[dp_off + 17]
640        } else {
641            0
642        };
643
644        let level_off = mem::offset_of!(raw::btrfs_root_item, level);
645        let level = if level_off < data.len() {
646            data[level_off]
647        } else {
648            0
649        };
650        let generation_v2 = if level_off + 1 + 8 <= data.len() {
651            let mut b = &data[level_off + 1..];
652            b.get_u64_le()
653        } else {
654            0
655        };
656
657        let uuid_off = mem::offset_of!(raw::btrfs_root_item, uuid);
658        let uuid = if uuid_off + 16 <= data.len() {
659            let mut b = &data[uuid_off..];
660            get_uuid(&mut b)
661        } else {
662            Uuid::nil()
663        };
664        let parent_uuid = if uuid_off + 32 <= data.len() {
665            let mut b = &data[uuid_off + 16..];
666            get_uuid(&mut b)
667        } else {
668            Uuid::nil()
669        };
670        let received_uuid = if uuid_off + 48 <= data.len() {
671            let mut b = &data[uuid_off + 32..];
672            get_uuid(&mut b)
673        } else {
674            Uuid::nil()
675        };
676
677        let ct_off = mem::offset_of!(raw::btrfs_root_item, ctransid);
678        let ctransid = if ct_off + 8 <= data.len() {
679            let mut b = &data[ct_off..];
680            b.get_u64_le()
681        } else {
682            0
683        };
684        let otransid = if ct_off + 16 <= data.len() {
685            let mut b = &data[ct_off + 8..];
686            b.get_u64_le()
687        } else {
688            0
689        };
690        let stransid = if ct_off + 24 <= data.len() {
691            let mut b = &data[ct_off + 16..];
692            b.get_u64_le()
693        } else {
694            0
695        };
696        let rtransid = if ct_off + 32 <= data.len() {
697            let mut b = &data[ct_off + 24..];
698            b.get_u64_le()
699        } else {
700            0
701        };
702
703        let ctime_off = mem::offset_of!(raw::btrfs_root_item, ctime);
704        let ts_size = mem::size_of::<raw::btrfs_timespec>();
705        let ctime = if ctime_off + ts_size <= data.len() {
706            let mut b = &data[ctime_off..];
707            Timespec::parse(&mut b)
708        } else {
709            Timespec { sec: 0, nsec: 0 }
710        };
711        let otime = if ctime_off + 2 * ts_size <= data.len() {
712            let mut b = &data[ctime_off + ts_size..];
713            Timespec::parse(&mut b)
714        } else {
715            Timespec { sec: 0, nsec: 0 }
716        };
717        let stime = if ctime_off + 3 * ts_size <= data.len() {
718            let mut b = &data[ctime_off + 2 * ts_size..];
719            Timespec::parse(&mut b)
720        } else {
721            Timespec { sec: 0, nsec: 0 }
722        };
723        let rtime = if ctime_off + 4 * ts_size <= data.len() {
724            let mut b = &data[ctime_off + 3 * ts_size..];
725            Timespec::parse(&mut b)
726        } else {
727            Timespec { sec: 0, nsec: 0 }
728        };
729
730        Some(Self {
731            generation,
732            root_dirid,
733            bytenr,
734            byte_limit,
735            bytes_used,
736            last_snapshot,
737            flags,
738            refs,
739            drop_progress,
740            drop_level,
741            level,
742            generation_v2,
743            uuid,
744            parent_uuid,
745            received_uuid,
746            ctransid,
747            otransid,
748            stransid,
749            rtransid,
750            ctime,
751            otime,
752            stime,
753            rtime,
754        })
755    }
756}
757
758/// Reference linking a subvolume to its parent directory.
759///
760/// `ROOT_REF` keys (parent → child) and `ROOT_BACKREF` keys (child → parent)
761/// use the same on-disk format.
762#[derive(Debug, Clone)]
763pub struct RootRef {
764    /// Inode number of the directory containing the subvolume entry.
765    pub dirid: u64,
766    /// Directory sequence number (matches the `DIR_INDEX` offset).
767    pub sequence: u64,
768    /// Name of the subvolume entry in the parent directory.
769    pub name: Vec<u8>,
770}
771
772impl RootRef {
773    /// Parse a root ref (or root backref) from a raw byte buffer.
774    #[must_use]
775    pub fn parse(data: &[u8]) -> Option<Self> {
776        if data.len() < mem::size_of::<raw::btrfs_root_ref>() {
777            return None;
778        }
779        let mut buf = data;
780        let dirid = buf.get_u64_le();
781        let sequence = buf.get_u64_le();
782        let name_len = buf.get_u16_le() as usize;
783        let name_start = mem::size_of::<raw::btrfs_root_ref>();
784        let name = if name_start + name_len <= data.len() {
785            data[name_start..name_start + name_len].to_vec()
786        } else {
787            Vec::new()
788        };
789        Some(Self {
790            dirid,
791            sequence,
792            name,
793        })
794    }
795}
796
797/// File extent descriptor, stored as `EXTENT_DATA` in the FS tree.
798///
799/// Key: `(inode, EXTENT_DATA, file_offset)`. Describes how a range of file
800/// bytes maps to on-disk storage. Extents can be inline (data embedded in the
801/// item), regular (referencing a disk extent), or prealloc (reserved but
802/// unwritten).
803#[derive(Debug, Clone)]
804pub struct FileExtentItem {
805    /// Generation when this extent was allocated.
806    pub generation: u64,
807    /// Uncompressed size of the data in this extent.
808    pub ram_bytes: u64,
809    /// Compression algorithm applied to the on-disk data.
810    pub compression: CompressionType,
811    /// Whether the extent is inline, regular, or preallocated.
812    pub extent_type: FileExtentType,
813    /// Type-specific extent location.
814    pub body: FileExtentBody,
815}
816
817/// Body of a file extent: either inline data or a reference to a disk extent.
818#[derive(Debug, Clone)]
819pub enum FileExtentBody {
820    /// Data is stored directly in the tree leaf (small files/tails).
821    Inline {
822        /// Number of bytes of inline data following the extent header.
823        inline_size: usize,
824    },
825    /// Data is stored in a separate disk extent.
826    Regular {
827        /// Logical byte address of the extent on disk (0 = hole/sparse).
828        disk_bytenr: u64,
829        /// Size of the on-disk extent in bytes (compressed size if compressed).
830        disk_num_bytes: u64,
831        /// Byte offset into the extent where this file range starts.
832        offset: u64,
833        /// Number of logical file bytes this extent covers.
834        num_bytes: u64,
835    },
836}
837
838impl FileExtentItem {
839    /// Parse a file extent item from a raw byte buffer.
840    #[must_use]
841    pub fn parse(data: &[u8]) -> Option<Self> {
842        if data.len() < 21 {
843            return None;
844        }
845        let mut buf = data;
846        let generation = buf.get_u64_le();
847        let ram_bytes = buf.get_u64_le();
848        let compression = CompressionType::from_raw(buf.get_u8());
849        buf.advance(3); // skip encryption, other_encoding
850        let extent_type = FileExtentType::from_raw(buf.get_u8());
851
852        let body = if extent_type == FileExtentType::Inline {
853            FileExtentBody::Inline {
854                inline_size: buf.remaining(),
855            }
856        } else if buf.remaining() >= 32 {
857            FileExtentBody::Regular {
858                disk_bytenr: buf.get_u64_le(),
859                disk_num_bytes: buf.get_u64_le(),
860                offset: buf.get_u64_le(),
861                num_bytes: buf.get_u64_le(),
862            }
863        } else {
864            return None;
865        };
866
867        Some(Self {
868            generation,
869            ram_bytes,
870            compression,
871            extent_type,
872            body,
873        })
874    }
875}
876
877/// Compute the hash used for `EXTENT_DATA_REF` keys, matching the kernel's
878/// `hash_extent_data_ref()`. Uses two independent CRC32C computations
879/// combined into a single u64.
880fn extent_data_ref_hash(root: u64, objectid: u64, offset: u64) -> u64 {
881    let high_crc = raw_crc32c(!0u32, &root.to_le_bytes());
882    let low_crc = raw_crc32c(!0u32, &objectid.to_le_bytes());
883    let low_crc = raw_crc32c(low_crc, &offset.to_le_bytes());
884    (u64::from(high_crc) << 31) ^ u64::from(low_crc)
885}
886
887/// Inline reference types found inside `EXTENT_ITEM`/`METADATA_ITEM`.
888#[derive(Debug, Clone)]
889pub enum InlineRef {
890    /// Direct backref from a metadata extent to the tree that owns it.
891    /// The `root` field is the tree objectid (e.g. 5 for `FS_TREE`).
892    TreeBlockBackref {
893        /// Raw offset value from the inline ref header (equals `root`).
894        ref_offset: u64,
895        /// Tree objectid that owns this metadata block.
896        root: u64,
897    },
898    /// Shared backref from a metadata extent via a parent tree block.
899    /// Used when a tree block is shared between snapshots.
900    SharedBlockBackref {
901        /// Raw offset value from the inline ref header (equals `parent`).
902        ref_offset: u64,
903        /// Logical bytenr of the parent tree block that references this extent.
904        parent: u64,
905    },
906    /// Backref from a data extent to a specific file inode. Stores the
907    /// owning root, inode number, file offset, and reference count.
908    ExtentDataBackref {
909        /// Computed hash of (root, objectid, offset) for display.
910        ref_offset: u64,
911        /// Tree objectid that owns the referencing inode.
912        root: u64,
913        /// Inode number that references this data extent.
914        objectid: u64,
915        /// File byte offset where this extent is referenced.
916        offset: u64,
917        /// Number of references from this (root, objectid, offset) triple.
918        count: u32,
919    },
920    /// Shared backref from a data extent via a parent tree block.
921    /// Used when data extents are shared between snapshots.
922    SharedDataBackref {
923        /// Raw offset value from the inline ref header (equals `parent`).
924        ref_offset: u64,
925        /// Logical bytenr of the parent tree block that references this extent.
926        parent: u64,
927        /// Number of references from the parent block.
928        count: u32,
929    },
930    /// Simple ownership reference for an extent (`simple_quota` feature).
931    /// Records which tree root owns the extent.
932    ExtentOwnerRef {
933        /// Raw offset value from the inline ref header (equals `root`).
934        ref_offset: u64,
935        /// Tree objectid that owns this extent.
936        root: u64,
937    },
938}
939
940impl InlineRef {
941    /// The raw type byte for this inline ref.
942    #[must_use]
943    #[allow(clippy::cast_possible_truncation)]
944    pub fn raw_type(&self) -> u8 {
945        match self {
946            Self::TreeBlockBackref { .. } => {
947                raw::BTRFS_TREE_BLOCK_REF_KEY as u8
948            }
949            Self::SharedBlockBackref { .. } => {
950                raw::BTRFS_SHARED_BLOCK_REF_KEY as u8
951            }
952            Self::ExtentDataBackref { .. } => {
953                raw::BTRFS_EXTENT_DATA_REF_KEY as u8
954            }
955            Self::SharedDataBackref { .. } => {
956                raw::BTRFS_SHARED_DATA_REF_KEY as u8
957            }
958            Self::ExtentOwnerRef { .. } => {
959                raw::BTRFS_EXTENT_OWNER_REF_KEY as u8
960            }
961        }
962    }
963
964    /// The offset value from the inline ref header.
965    #[must_use]
966    pub fn raw_offset(&self) -> u64 {
967        match self {
968            Self::TreeBlockBackref { ref_offset, .. }
969            | Self::SharedBlockBackref { ref_offset, .. }
970            | Self::ExtentDataBackref { ref_offset, .. }
971            | Self::SharedDataBackref { ref_offset, .. }
972            | Self::ExtentOwnerRef { ref_offset, .. } => *ref_offset,
973        }
974    }
975}
976
977bitflags::bitflags! {
978    /// Extent item flags stored in `btrfs_extent_item::flags`.
979    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
980    pub struct ExtentFlags: u64 {
981        const DATA         = raw::BTRFS_EXTENT_FLAG_DATA as u64;
982        const TREE_BLOCK   = raw::BTRFS_EXTENT_FLAG_TREE_BLOCK as u64;
983        const FULL_BACKREF = raw::BTRFS_BLOCK_FLAG_FULL_BACKREF as u64;
984        // Preserve unknown bits from the on-disk value.
985        const _ = !0;
986    }
987}
988
989impl fmt::Display for ExtentFlags {
990    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
991        let mut parts = Vec::new();
992        if self.contains(Self::DATA) {
993            parts.push("DATA");
994        }
995        if self.contains(Self::TREE_BLOCK) {
996            parts.push("TREE_BLOCK");
997        }
998        if self.contains(Self::FULL_BACKREF) {
999            parts.push("FULL_BACKREF");
1000        }
1001        write!(f, "{}", parts.join("|"))
1002    }
1003}
1004
1005/// Extent allocation record from the extent tree.
1006///
1007/// Tracks reference counts, ownership, and backreferences for a contiguous
1008/// range of allocated disk space. Used for both data extents (`EXTENT_ITEM`)
1009/// and metadata blocks (`METADATA_ITEM` with skinny metadata).
1010#[derive(Debug, Clone)]
1011pub struct ExtentItem {
1012    /// Number of references to this extent.
1013    pub refs: u64,
1014    /// Generation when this extent was allocated.
1015    pub generation: u64,
1016    /// Whether this extent holds data or a tree block.
1017    pub flags: ExtentFlags,
1018    /// For non-skinny tree block extents: the first key in the block.
1019    pub tree_block_key: Option<DiskKey>,
1020    /// For non-skinny tree block extents: the block's tree level.
1021    pub tree_block_level: Option<u8>,
1022    /// For skinny metadata items: the tree level (from the key offset).
1023    pub skinny_level: Option<u64>,
1024    /// Inline backreferences packed after the extent header.
1025    pub inline_refs: Vec<InlineRef>,
1026}
1027
1028impl ExtentItem {
1029    /// Returns true if this extent holds file data.
1030    #[must_use]
1031    pub fn is_data(&self) -> bool {
1032        self.flags.contains(ExtentFlags::DATA)
1033    }
1034
1035    /// Returns true if this extent holds a metadata tree block.
1036    #[must_use]
1037    pub fn is_tree_block(&self) -> bool {
1038        self.flags.contains(ExtentFlags::TREE_BLOCK)
1039    }
1040
1041    /// Parse an extent item from a raw byte buffer, using the item key to
1042    /// determine whether this is a skinny metadata item or a full extent item.
1043    #[must_use]
1044    pub fn parse(data: &[u8], key: &DiskKey) -> Option<Self> {
1045        use crate::tree::KeyType;
1046
1047        if data.len() < mem::size_of::<raw::btrfs_extent_item>() {
1048            return None;
1049        }
1050        let mut buf = data;
1051        let refs = buf.get_u64_le();
1052        let generation = buf.get_u64_le();
1053        let flags = ExtentFlags::from_bits_truncate(buf.get_u64_le());
1054
1055        let is_tree_block = flags.contains(ExtentFlags::TREE_BLOCK);
1056
1057        let mut tree_block_key = None;
1058        let mut tree_block_level = None;
1059        if is_tree_block
1060            && key.key_type == KeyType::ExtentItem
1061            && buf.remaining() > 17
1062        {
1063            tree_block_key = Some(DiskKey::parse(buf, 0));
1064            buf.advance(17); // skip DiskKey
1065            tree_block_level = Some(buf.get_u8());
1066        }
1067
1068        let skinny_level =
1069            if key.key_type == KeyType::MetadataItem && is_tree_block {
1070                Some(key.offset)
1071            } else {
1072                None
1073            };
1074
1075        let mut inline_refs = Vec::new();
1076        while buf.remaining() > 0 {
1077            let ref_type = buf.get_u8();
1078            let ref_offset = if buf.remaining() >= 8 {
1079                buf.get_u64_le()
1080            } else {
1081                0
1082            };
1083
1084            match u32::from(ref_type) {
1085                raw::BTRFS_TREE_BLOCK_REF_KEY => {
1086                    inline_refs.push(InlineRef::TreeBlockBackref {
1087                        ref_offset,
1088                        root: ref_offset,
1089                    });
1090                }
1091                raw::BTRFS_SHARED_BLOCK_REF_KEY => {
1092                    inline_refs.push(InlineRef::SharedBlockBackref {
1093                        ref_offset,
1094                        parent: ref_offset,
1095                    });
1096                }
1097                raw::BTRFS_EXTENT_DATA_REF_KEY => {
1098                    // EXTENT_DATA_REF has no 8-byte offset field; the struct
1099                    // starts directly after the type byte. The 8 bytes we
1100                    // speculatively consumed are actually the first field
1101                    // (root) of the struct, so reinterpret them.
1102                    let root = ref_offset; // already read as u64_le
1103                    if buf.remaining() >= 20 {
1104                        let oid = buf.get_u64_le();
1105                        let off = buf.get_u64_le();
1106                        let count = buf.get_u32_le();
1107                        // The C tool prints a CRC hash for the display offset;
1108                        // compute it the same way: hash(root, objectid, offset).
1109                        let hash = extent_data_ref_hash(root, oid, off);
1110                        inline_refs.push(InlineRef::ExtentDataBackref {
1111                            ref_offset: hash,
1112                            root,
1113                            objectid: oid,
1114                            offset: off,
1115                            count,
1116                        });
1117                    } else {
1118                        break;
1119                    }
1120                }
1121                raw::BTRFS_SHARED_DATA_REF_KEY => {
1122                    if buf.remaining() >= 4 {
1123                        let count = buf.get_u32_le();
1124                        inline_refs.push(InlineRef::SharedDataBackref {
1125                            ref_offset,
1126                            parent: ref_offset,
1127                            count,
1128                        });
1129                    } else {
1130                        break;
1131                    }
1132                }
1133                raw::BTRFS_EXTENT_OWNER_REF_KEY => {
1134                    inline_refs.push(InlineRef::ExtentOwnerRef {
1135                        ref_offset,
1136                        root: ref_offset,
1137                    });
1138                }
1139                _ => break,
1140            }
1141        }
1142
1143        Some(Self {
1144            refs,
1145            generation,
1146            flags,
1147            tree_block_key,
1148            tree_block_level,
1149            skinny_level,
1150            inline_refs,
1151        })
1152    }
1153}
1154
1155/// Standalone data extent backreference (non-inline).
1156///
1157/// Key: `(extent_bytenr, EXTENT_DATA_REF, hash)`. Records which file inode
1158/// references a given data extent.
1159#[derive(Debug, Clone)]
1160pub struct ExtentDataRef {
1161    /// Root tree objectid that owns the referencing inode.
1162    pub root: u64,
1163    /// Inode number that references this extent.
1164    pub objectid: u64,
1165    /// File offset where this extent is referenced.
1166    pub offset: u64,
1167    /// Number of references from this (root, objectid, offset) triple.
1168    pub count: u32,
1169}
1170
1171impl ExtentDataRef {
1172    /// Parse a standalone extent data ref from a raw byte buffer.
1173    #[must_use]
1174    pub fn parse(data: &[u8]) -> Option<Self> {
1175        if data.len() < mem::size_of::<raw::btrfs_extent_data_ref>() {
1176            return None;
1177        }
1178        let mut buf = data;
1179        Some(Self {
1180            root: buf.get_u64_le(),
1181            objectid: buf.get_u64_le(),
1182            offset: buf.get_u64_le(),
1183            count: buf.get_u32_le(),
1184        })
1185    }
1186}
1187
1188/// Shared data extent backreference (for snapshot-shared extents).
1189///
1190/// Key: `(extent_bytenr, SHARED_DATA_REF, parent_bytenr)`.
1191#[derive(Debug, Clone)]
1192pub struct SharedDataRef {
1193    /// Number of references from the parent block.
1194    pub count: u32,
1195}
1196
1197impl SharedDataRef {
1198    /// Parse a shared data ref from a raw byte buffer.
1199    #[must_use]
1200    pub fn parse(data: &[u8]) -> Option<Self> {
1201        if data.len() < 4 {
1202            return None;
1203        }
1204        let mut buf = data;
1205        Some(Self {
1206            count: buf.get_u32_le(),
1207        })
1208    }
1209}
1210
1211/// Block group descriptor, tracking space usage for a chunk.
1212///
1213/// Key: `(logical_offset, BLOCK_GROUP_ITEM, length)`.
1214#[derive(Debug, Clone)]
1215pub struct BlockGroupItem {
1216    /// Bytes used within this block group.
1217    pub used: u64,
1218    /// Objectid of the chunk that backs this block group.
1219    pub chunk_objectid: u64,
1220    /// Type and RAID profile flags (DATA, METADATA, SYSTEM, DUP, RAID*, etc.).
1221    pub flags: BlockGroupFlags,
1222}
1223
1224impl BlockGroupItem {
1225    /// Parse a block group item from a raw byte buffer.
1226    #[must_use]
1227    pub fn parse(data: &[u8]) -> Option<Self> {
1228        if data.len() < mem::size_of::<raw::btrfs_block_group_item>() {
1229            return None;
1230        }
1231        let mut buf = data;
1232        Some(Self {
1233            used: buf.get_u64_le(),
1234            chunk_objectid: buf.get_u64_le(),
1235            flags: BlockGroupFlags::from_bits_truncate(buf.get_u64_le()),
1236        })
1237    }
1238}
1239
1240/// Chunk item mapping logical addresses to physical device locations.
1241///
1242/// Key: `(FIRST_CHUNK_TREE, CHUNK_ITEM, logical_offset)`. Each chunk maps a
1243/// contiguous range of logical addresses to one or more device stripes.
1244#[derive(Debug, Clone)]
1245pub struct ChunkItem {
1246    /// Length of this chunk in bytes.
1247    pub length: u64,
1248    /// Owner of this chunk (always `BTRFS_FIRST_CHUNK_TREE_OBJECTID`).
1249    pub owner: u64,
1250    /// Stripe length for striped profiles.
1251    pub stripe_len: u64,
1252    /// Type and RAID profile flags.
1253    pub chunk_type: BlockGroupFlags,
1254    /// I/O alignment requirement.
1255    pub io_align: u32,
1256    /// I/O width requirement.
1257    pub io_width: u32,
1258    /// Sector size of the underlying devices.
1259    pub sector_size: u32,
1260    /// Number of stripes (device copies) for this chunk.
1261    pub num_stripes: u16,
1262    /// Number of sub-stripes (for RAID10).
1263    pub sub_stripes: u16,
1264    /// Physical device locations for each stripe.
1265    pub stripes: Vec<ChunkStripe>,
1266}
1267
1268/// A single physical stripe within a chunk.
1269#[derive(Debug, Clone)]
1270pub struct ChunkStripe {
1271    /// Device ID where this stripe lives.
1272    pub devid: u64,
1273    /// Physical byte offset on the device.
1274    pub offset: u64,
1275    /// UUID of the device.
1276    pub dev_uuid: Uuid,
1277}
1278
1279impl ChunkItem {
1280    /// Parse a chunk item (with stripes) from a raw byte buffer.
1281    #[must_use]
1282    pub fn parse(data: &[u8]) -> Option<Self> {
1283        let chunk_base_size = mem::offset_of!(raw::btrfs_chunk, stripe);
1284        if data.len() < chunk_base_size {
1285            return None;
1286        }
1287        let mut buf = data;
1288        let length = buf.get_u64_le();
1289        let owner = buf.get_u64_le();
1290        let stripe_len = buf.get_u64_le();
1291        let chunk_type = BlockGroupFlags::from_bits_truncate(buf.get_u64_le());
1292        let io_align = buf.get_u32_le();
1293        let io_width = buf.get_u32_le();
1294        let sector_size = buf.get_u32_le();
1295        let num_stripes = buf.get_u16_le();
1296        let sub_stripes = buf.get_u16_le();
1297        let stripe_size = mem::size_of::<raw::btrfs_stripe>();
1298        let mut stripes = Vec::with_capacity(num_stripes as usize);
1299        let mut sbuf = &data[chunk_base_size..];
1300        for i in 0..num_stripes as usize {
1301            let s_off = chunk_base_size + i * stripe_size;
1302            if s_off + stripe_size > data.len() {
1303                break;
1304            }
1305            let devid = sbuf.get_u64_le();
1306            let offset = sbuf.get_u64_le();
1307            let dev_uuid = get_uuid(&mut sbuf);
1308            stripes.push(ChunkStripe {
1309                devid,
1310                offset,
1311                dev_uuid,
1312            });
1313        }
1314        Some(Self {
1315            length,
1316            owner,
1317            stripe_len,
1318            chunk_type,
1319            io_align,
1320            io_width,
1321            sector_size,
1322            num_stripes,
1323            sub_stripes,
1324            stripes,
1325        })
1326    }
1327}
1328
1329/// Device item describing a single device in the filesystem.
1330///
1331/// Stored in the device tree and embedded in the superblock. Contains the
1332/// device's size, usage, and identifying UUIDs.
1333#[derive(Debug, Clone)]
1334pub struct DeviceItem {
1335    /// Unique device ID within this filesystem.
1336    pub devid: u64,
1337    /// Total size of the device in bytes.
1338    pub total_bytes: u64,
1339    /// Bytes allocated on this device.
1340    pub bytes_used: u64,
1341    /// I/O alignment requirement.
1342    pub io_align: u32,
1343    /// I/O width requirement.
1344    pub io_width: u32,
1345    /// Sector size of this device.
1346    pub sector_size: u32,
1347    /// Device type (reserved, always 0).
1348    pub dev_type: u64,
1349    /// Generation when this device was last updated.
1350    pub generation: u64,
1351    /// Start offset for allocations on this device.
1352    pub start_offset: u64,
1353    /// Device group (reserved, always 0).
1354    pub dev_group: u32,
1355    /// Seek speed hint (0 = not set).
1356    pub seek_speed: u8,
1357    /// Bandwidth hint (0 = not set).
1358    pub bandwidth: u8,
1359    /// UUID of this device.
1360    pub uuid: Uuid,
1361    /// Filesystem UUID that this device belongs to.
1362    pub fsid: Uuid,
1363}
1364
1365impl DeviceItem {
1366    /// Serialize this device item to a `BufMut` in on-disk little-endian format.
1367    pub fn write_bytes(&self, buf: &mut impl BufMut) {
1368        buf.put_u64_le(self.devid);
1369        buf.put_u64_le(self.total_bytes);
1370        buf.put_u64_le(self.bytes_used);
1371        buf.put_u32_le(self.io_align);
1372        buf.put_u32_le(self.io_width);
1373        buf.put_u32_le(self.sector_size);
1374        buf.put_u64_le(self.dev_type);
1375        buf.put_u64_le(self.generation);
1376        buf.put_u64_le(self.start_offset);
1377        buf.put_u32_le(self.dev_group);
1378        buf.put_u8(self.seek_speed);
1379        buf.put_u8(self.bandwidth);
1380        buf.put_slice(self.uuid.as_bytes());
1381        buf.put_slice(self.fsid.as_bytes());
1382    }
1383
1384    /// Parse a device item from a raw byte buffer.
1385    #[must_use]
1386    pub fn parse(data: &[u8]) -> Option<Self> {
1387        if data.len() < mem::size_of::<raw::btrfs_dev_item>() {
1388            return None;
1389        }
1390        let mut buf = data;
1391        let devid = buf.get_u64_le();
1392        let total_bytes = buf.get_u64_le();
1393        let bytes_used = buf.get_u64_le();
1394        let io_align = buf.get_u32_le();
1395        let io_width = buf.get_u32_le();
1396        let sector_size = buf.get_u32_le();
1397        let dev_type = buf.get_u64_le();
1398        let generation = buf.get_u64_le();
1399        let start_offset = buf.get_u64_le();
1400        let dev_group = buf.get_u32_le();
1401        let seek_speed = buf.get_u8();
1402        let bandwidth = buf.get_u8();
1403        let uuid = get_uuid(&mut buf);
1404        let fsid = get_uuid(&mut buf);
1405        Some(Self {
1406            devid,
1407            total_bytes,
1408            bytes_used,
1409            io_align,
1410            io_width,
1411            sector_size,
1412            dev_type,
1413            generation,
1414            start_offset,
1415            dev_group,
1416            seek_speed,
1417            bandwidth,
1418            uuid,
1419            fsid,
1420        })
1421    }
1422}
1423
1424/// Device extent, mapping a physical range on a device to a chunk.
1425///
1426/// Key: `(devid, DEV_EXTENT, physical_offset)`. The inverse of a chunk
1427/// stripe: given a device and physical offset, find the owning chunk.
1428#[derive(Debug, Clone)]
1429pub struct DeviceExtent {
1430    /// Objectid of the chunk tree (always 3).
1431    pub chunk_tree: u64,
1432    /// Objectid of the owning chunk.
1433    pub chunk_objectid: u64,
1434    /// Logical offset of the owning chunk.
1435    pub chunk_offset: u64,
1436    /// Length of this device extent in bytes.
1437    pub length: u64,
1438    /// UUID of the chunk tree.
1439    pub chunk_tree_uuid: Uuid,
1440}
1441
1442impl DeviceExtent {
1443    /// Parse a device extent from a raw byte buffer.
1444    #[must_use]
1445    pub fn parse(data: &[u8]) -> Option<Self> {
1446        if data.len() < mem::size_of::<raw::btrfs_dev_extent>() {
1447            return None;
1448        }
1449        let mut buf = data;
1450        let chunk_tree = buf.get_u64_le();
1451        let chunk_objectid = buf.get_u64_le();
1452        let chunk_offset = buf.get_u64_le();
1453        let length = buf.get_u64_le();
1454        let chunk_tree_uuid = get_uuid(&mut buf);
1455        Some(Self {
1456            chunk_tree,
1457            chunk_objectid,
1458            chunk_offset,
1459            length,
1460            chunk_tree_uuid,
1461        })
1462    }
1463}
1464
1465bitflags::bitflags! {
1466    /// Free space info flags stored in `btrfs_free_space_info::flags`.
1467    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1468    pub struct FreeSpaceInfoFlags: u32 {
1469        const USING_BITMAPS = raw::BTRFS_FREE_SPACE_USING_BITMAPS;
1470        // Preserve unknown bits from the on-disk value.
1471        const _ = !0;
1472    }
1473}
1474
1475impl fmt::Display for FreeSpaceInfoFlags {
1476    // The C reference prints this field as an unsigned decimal integer (%u).
1477    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1478        write!(f, "{}", self.bits())
1479    }
1480}
1481
1482/// Free space info for a block group in the free space tree.
1483///
1484/// Key: `(block_group_offset, FREE_SPACE_INFO, block_group_length)`.
1485#[derive(Debug, Clone)]
1486pub struct FreeSpaceInfo {
1487    /// Number of free extents (or bitmap entries) in this block group.
1488    pub extent_count: u32,
1489    /// Flags indicating whether this block group uses bitmaps.
1490    pub flags: FreeSpaceInfoFlags,
1491}
1492
1493impl FreeSpaceInfo {
1494    /// Parse a free space info item from a raw byte buffer.
1495    #[must_use]
1496    pub fn parse(data: &[u8]) -> Option<Self> {
1497        if data.len() < 8 {
1498            return None;
1499        }
1500        let mut buf = data;
1501        Some(Self {
1502            extent_count: buf.get_u32_le(),
1503            flags: FreeSpaceInfoFlags::from_bits_truncate(buf.get_u32_le()),
1504        })
1505    }
1506}
1507
1508/// Quota group status, stored in the quota tree.
1509///
1510/// Key: `(0, QGROUP_STATUS, 0)`. Tracks the overall state of quota accounting.
1511#[derive(Debug, Clone)]
1512pub struct QgroupStatus {
1513    /// Qgroup on-disk format version.
1514    pub version: u64,
1515    /// Generation when quotas were last consistent.
1516    pub generation: u64,
1517    /// Status flags (e.g. rescan in progress).
1518    pub flags: u64,
1519    /// Progress objectid for an in-progress rescan.
1520    pub scan: u64,
1521    /// Generation when quotas were enabled (kernel 6.8+, absent on older formats).
1522    pub enable_gen: Option<u64>,
1523}
1524
1525impl QgroupStatus {
1526    /// Parse a qgroup status item from a raw byte buffer.
1527    #[must_use]
1528    pub fn parse(data: &[u8]) -> Option<Self> {
1529        if data.len() < 32 {
1530            return None;
1531        }
1532        let mut buf = data;
1533        let version = buf.get_u64_le();
1534        let generation = buf.get_u64_le();
1535        let flags = buf.get_u64_le();
1536        let scan = buf.get_u64_le();
1537        let enable_gen = if buf.remaining() >= 8 {
1538            Some(buf.get_u64_le())
1539        } else {
1540            None
1541        };
1542        Some(Self {
1543            version,
1544            generation,
1545            flags,
1546            scan,
1547            enable_gen,
1548        })
1549    }
1550}
1551
1552/// Quota group accounting info.
1553///
1554/// Key: `(level/subvolid, QGROUP_INFO, 0)`. Tracks how much space a qgroup
1555/// references and how much is exclusive to it.
1556#[derive(Debug, Clone)]
1557pub struct QgroupInfo {
1558    /// Generation when this info was last updated.
1559    pub generation: u64,
1560    /// Total bytes referenced by this qgroup (shared + exclusive).
1561    pub referenced: u64,
1562    /// Referenced bytes after compression.
1563    pub referenced_compressed: u64,
1564    /// Bytes used exclusively by this qgroup.
1565    pub exclusive: u64,
1566    /// Exclusive bytes after compression.
1567    pub exclusive_compressed: u64,
1568}
1569
1570impl QgroupInfo {
1571    /// Parse a qgroup info item from a raw byte buffer.
1572    #[must_use]
1573    pub fn parse(data: &[u8]) -> Option<Self> {
1574        if data.len() < mem::size_of::<raw::btrfs_qgroup_info_item>() {
1575            return None;
1576        }
1577        let mut buf = data;
1578        Some(Self {
1579            generation: buf.get_u64_le(),
1580            referenced: buf.get_u64_le(),
1581            referenced_compressed: buf.get_u64_le(),
1582            exclusive: buf.get_u64_le(),
1583            exclusive_compressed: buf.get_u64_le(),
1584        })
1585    }
1586}
1587
1588/// Quota group limits.
1589///
1590/// Key: `(level/subvolid, QGROUP_LIMIT, 0)`. Caps referenced and/or exclusive
1591/// space usage for a qgroup.
1592#[derive(Debug, Clone)]
1593pub struct QgroupLimit {
1594    /// Bitmask of which limits are active.
1595    pub flags: u64,
1596    /// Maximum referenced bytes (0 = unlimited).
1597    pub max_referenced: u64,
1598    /// Maximum exclusive bytes (0 = unlimited).
1599    pub max_exclusive: u64,
1600    /// Reserved referenced bytes.
1601    pub rsv_referenced: u64,
1602    /// Reserved exclusive bytes.
1603    pub rsv_exclusive: u64,
1604}
1605
1606impl QgroupLimit {
1607    /// Parse a qgroup limit item from a raw byte buffer.
1608    #[must_use]
1609    pub fn parse(data: &[u8]) -> Option<Self> {
1610        if data.len() < mem::size_of::<raw::btrfs_qgroup_limit_item>() {
1611            return None;
1612        }
1613        let mut buf = data;
1614        Some(Self {
1615            flags: buf.get_u64_le(),
1616            max_referenced: buf.get_u64_le(),
1617            max_exclusive: buf.get_u64_le(),
1618            rsv_referenced: buf.get_u64_le(),
1619            rsv_exclusive: buf.get_u64_le(),
1620        })
1621    }
1622}
1623
1624/// Per-device I/O error statistics.
1625///
1626/// Key: `(DEV_STATS, PERSISTENT_ITEM, devid)`. Stored as an array of u64
1627/// counters for write errors, read errors, flush errors, corruption errors,
1628/// and generation mismatches.
1629#[derive(Debug, Clone)]
1630pub struct DeviceStats {
1631    /// Named counters: `(stat_name, count)`.
1632    pub values: Vec<(String, u64)>,
1633}
1634
1635impl DeviceStats {
1636    /// Parse device statistics from a raw byte buffer. Reads up to 5 u64
1637    /// counters (`write_errs`, `read_errs`, `flush_errs`, `corruption_errs`, generation).
1638    #[must_use]
1639    pub fn parse(data: &[u8]) -> Self {
1640        let stat_names = [
1641            "write_errs",
1642            "read_errs",
1643            "flush_errs",
1644            "corruption_errs",
1645            "generation",
1646        ];
1647        let mut buf = data;
1648        let mut values = Vec::new();
1649        for name in &stat_names {
1650            if buf.remaining() >= 8 {
1651                values.push((name.to_string(), buf.get_u64_le()));
1652            }
1653        }
1654        DeviceStats { values }
1655    }
1656}
1657
1658/// UUID tree entry mapping a subvolume UUID to its objectid(s).
1659///
1660/// Key: `(upper_half_of_uuid, UUID_KEY_SUBVOL, lower_half_of_uuid)`.
1661#[derive(Debug, Clone)]
1662pub struct UuidItem {
1663    /// Subvolume objectids associated with this UUID.
1664    pub subvol_ids: Vec<u64>,
1665}
1666
1667impl UuidItem {
1668    /// Parse a UUID tree item from a raw byte buffer.
1669    #[must_use]
1670    pub fn parse(data: &[u8]) -> Self {
1671        let mut buf = data;
1672        let mut subvol_ids = Vec::new();
1673        while buf.remaining() >= 8 {
1674            subvol_ids.push(buf.get_u64_le());
1675        }
1676        Self { subvol_ids }
1677    }
1678}
1679
1680/// Parsed item payload: the typed result of parsing a leaf item's raw data
1681/// based on its key type.
1682///
1683/// Returned by [`parse_item_payload`]. Each variant wraps the corresponding
1684/// item struct. `Unknown` holds the raw bytes for unrecognized key types.
1685pub enum ItemPayload {
1686    /// Inode metadata (POSIX attributes, timestamps, flags).
1687    InodeItem(InodeItem),
1688    /// One or more hard-link references packed in a single item.
1689    InodeRef(Vec<InodeRef>),
1690    /// One or more extended inode references.
1691    InodeExtref(Vec<InodeExtref>),
1692    /// One or more directory entries (also used for `DIR_INDEX` and `XATTR_ITEM`).
1693    DirItem(Vec<DirItem>),
1694    /// Directory log item with the logged range end offset.
1695    DirLogItem {
1696        /// End of the logged directory range.
1697        end: u64,
1698    },
1699    /// Orphan marker (no data payload).
1700    OrphanItem,
1701    /// Tree root descriptor (subvolume, snapshot, or internal tree).
1702    RootItem(RootItem),
1703    /// Root forward or back reference (`ROOT_REF` / `ROOT_BACKREF`).
1704    RootRef(RootRef),
1705    /// File extent descriptor.
1706    FileExtentItem(FileExtentItem),
1707    /// Raw extent checksum data.
1708    ExtentCsum {
1709        /// Raw checksum bytes (array of per-sector checksums).
1710        data: Vec<u8>,
1711    },
1712    /// Extent allocation record (`EXTENT_ITEM` or `METADATA_ITEM`).
1713    ExtentItem(ExtentItem),
1714    /// Standalone tree block backref (no data payload; the key offset is the root).
1715    TreeBlockRef,
1716    /// Standalone shared block backref (no data payload; the key offset is the parent bytenr).
1717    SharedBlockRef,
1718    /// Standalone data extent backref.
1719    ExtentDataRef(ExtentDataRef),
1720    /// Standalone shared data extent backref.
1721    SharedDataRef(SharedDataRef),
1722    /// Simple ownership reference for an extent.
1723    ExtentOwnerRef {
1724        /// Tree objectid that owns this extent.
1725        root: u64,
1726    },
1727    /// Block group descriptor.
1728    BlockGroupItem(BlockGroupItem),
1729    /// Free space info for a block group.
1730    FreeSpaceInfo(FreeSpaceInfo),
1731    /// Free space extent (no data payload; key encodes start and length).
1732    FreeSpaceExtent,
1733    /// Free space bitmap (data payload is the bitmap).
1734    FreeSpaceBitmap,
1735    /// Chunk item mapping logical to physical addresses.
1736    ChunkItem(ChunkItem),
1737    /// Device item describing a single device.
1738    DeviceItem(DeviceItem),
1739    /// Physical extent mapping on a device.
1740    DeviceExtent(DeviceExtent),
1741    /// Quota group status.
1742    QgroupStatus(QgroupStatus),
1743    /// Quota group accounting info.
1744    QgroupInfo(QgroupInfo),
1745    /// Quota group limits.
1746    QgroupLimit(QgroupLimit),
1747    /// Quota group relation (no data payload; parent/child encoded in key).
1748    QgroupRelation,
1749    /// Per-device I/O error statistics.
1750    DeviceStats(DeviceStats),
1751    /// Balance status item.
1752    BalanceItem {
1753        /// Balance flags from the first 8 bytes of the item data.
1754        flags: u64,
1755    },
1756    /// Device replace status.
1757    DeviceReplace(DeviceReplaceItem),
1758    /// UUID tree entry mapping a UUID to subvolume objectids.
1759    UuidItem(UuidItem),
1760    /// String item (typically the superblock label).
1761    StringItem(Vec<u8>),
1762    /// RAID stripe extent mapping.
1763    RaidStripe(RaidStripeItem),
1764    /// Unrecognized item type; raw data preserved.
1765    Unknown(Vec<u8>),
1766}
1767
1768/// Device replace status, persisted across reboots.
1769///
1770/// Key: `(DEV_REPLACE, PERSISTENT_ITEM, 0)`.
1771#[derive(Debug, Clone)]
1772pub struct DeviceReplaceItem {
1773    /// Device ID of the source device being replaced.
1774    pub src_devid: u64,
1775    /// Left cursor position (bytes processed from left).
1776    pub cursor_left: u64,
1777    /// Right cursor position.
1778    pub cursor_right: u64,
1779    /// Replace mode (continuous = 0 or legacy).
1780    pub replace_mode: u64,
1781    /// Current state (not started, started, suspended, etc.).
1782    pub replace_state: u64,
1783    /// Unix timestamp when the replace operation started.
1784    pub time_started: u64,
1785    /// Unix timestamp when the replace operation completed or was cancelled.
1786    pub time_stopped: u64,
1787    /// Number of write errors during replace.
1788    pub num_write_errors: u64,
1789    /// Number of uncorrectable read errors during replace.
1790    pub num_uncorrectable_read_errors: u64,
1791}
1792
1793impl DeviceReplaceItem {
1794    /// Parse a device replace item from a raw byte buffer.
1795    #[must_use]
1796    pub fn parse(data: &[u8]) -> Option<Self> {
1797        if data.len() < 80 {
1798            return None;
1799        }
1800        let mut buf = data;
1801        Some(Self {
1802            src_devid: buf.get_u64_le(),
1803            cursor_left: buf.get_u64_le(),
1804            cursor_right: buf.get_u64_le(),
1805            replace_mode: buf.get_u64_le(),
1806            replace_state: buf.get_u64_le(),
1807            time_started: buf.get_u64_le(),
1808            time_stopped: buf.get_u64_le(),
1809            num_write_errors: buf.get_u64_le(),
1810            num_uncorrectable_read_errors: buf.get_u64_le(),
1811        })
1812    }
1813}
1814
1815/// RAID stripe extent mapping (for the raid-stripe-tree feature).
1816///
1817/// Key: `(logical_offset, RAID_STRIPE, length)`.
1818#[derive(Debug, Clone)]
1819pub struct RaidStripeItem {
1820    /// RAID encoding type.
1821    pub encoding: u64,
1822    /// Per-device stripe entries.
1823    pub stripes: Vec<RaidStripeEntry>,
1824}
1825
1826/// A single device stripe within a RAID stripe item.
1827#[derive(Debug, Clone)]
1828pub struct RaidStripeEntry {
1829    /// Device ID for this stripe.
1830    pub devid: u64,
1831    /// Physical byte offset on the device.
1832    pub physical: u64,
1833}
1834
1835impl RaidStripeItem {
1836    /// Parse a RAID stripe item from a raw byte buffer.
1837    #[must_use]
1838    pub fn parse(data: &[u8]) -> Option<Self> {
1839        if data.len() < 8 {
1840            return None;
1841        }
1842        let mut buf = data;
1843        let encoding = buf.get_u64_le();
1844        let mut stripes = Vec::new();
1845        while buf.remaining() >= 16 {
1846            stripes.push(RaidStripeEntry {
1847                devid: buf.get_u64_le(),
1848                physical: buf.get_u64_le(),
1849            });
1850        }
1851        Some(Self { encoding, stripes })
1852    }
1853}
1854
1855/// Parse an item's raw data into a typed payload based on its key type.
1856#[must_use]
1857#[allow(clippy::too_many_lines)]
1858pub fn parse_item_payload(key: &DiskKey, data: &[u8]) -> ItemPayload {
1859    use crate::tree::KeyType;
1860
1861    match key.key_type {
1862        KeyType::InodeItem => match InodeItem::parse(data) {
1863            Some(v) => ItemPayload::InodeItem(v),
1864            None => ItemPayload::Unknown(data.to_vec()),
1865        },
1866        KeyType::InodeRef => ItemPayload::InodeRef(InodeRef::parse_all(data)),
1867        KeyType::InodeExtref => {
1868            ItemPayload::InodeExtref(InodeExtref::parse_all(data))
1869        }
1870        KeyType::DirItem | KeyType::DirIndex | KeyType::XattrItem => {
1871            ItemPayload::DirItem(DirItem::parse_all(data))
1872        }
1873        KeyType::DirLogItem | KeyType::DirLogIndex => {
1874            let end = if data.len() >= 8 {
1875                let mut buf = data;
1876                buf.get_u64_le()
1877            } else {
1878                0
1879            };
1880            ItemPayload::DirLogItem { end }
1881        }
1882        KeyType::OrphanItem => ItemPayload::OrphanItem,
1883        KeyType::RootItem => match RootItem::parse(data) {
1884            Some(v) => ItemPayload::RootItem(v),
1885            None => ItemPayload::Unknown(data.to_vec()),
1886        },
1887        KeyType::RootRef | KeyType::RootBackref => match RootRef::parse(data) {
1888            Some(v) => ItemPayload::RootRef(v),
1889            None => ItemPayload::Unknown(data.to_vec()),
1890        },
1891        KeyType::ExtentData => match FileExtentItem::parse(data) {
1892            Some(v) => ItemPayload::FileExtentItem(v),
1893            None => ItemPayload::Unknown(data.to_vec()),
1894        },
1895        KeyType::ExtentCsum => ItemPayload::ExtentCsum {
1896            data: data.to_vec(),
1897        },
1898        KeyType::ExtentItem | KeyType::MetadataItem => {
1899            match ExtentItem::parse(data, key) {
1900                Some(v) => ItemPayload::ExtentItem(v),
1901                None => ItemPayload::Unknown(data.to_vec()),
1902            }
1903        }
1904        KeyType::TreeBlockRef => ItemPayload::TreeBlockRef,
1905        KeyType::SharedBlockRef => ItemPayload::SharedBlockRef,
1906        KeyType::ExtentDataRef => match ExtentDataRef::parse(data) {
1907            Some(v) => ItemPayload::ExtentDataRef(v),
1908            None => ItemPayload::Unknown(data.to_vec()),
1909        },
1910        KeyType::SharedDataRef => match SharedDataRef::parse(data) {
1911            Some(v) => ItemPayload::SharedDataRef(v),
1912            None => ItemPayload::Unknown(data.to_vec()),
1913        },
1914        KeyType::ExtentOwnerRef => {
1915            if data.len() >= 8 {
1916                let mut buf = data;
1917                ItemPayload::ExtentOwnerRef {
1918                    root: buf.get_u64_le(),
1919                }
1920            } else {
1921                ItemPayload::Unknown(data.to_vec())
1922            }
1923        }
1924        KeyType::BlockGroupItem => match BlockGroupItem::parse(data) {
1925            Some(v) => ItemPayload::BlockGroupItem(v),
1926            None => ItemPayload::Unknown(data.to_vec()),
1927        },
1928        KeyType::FreeSpaceInfo => match FreeSpaceInfo::parse(data) {
1929            Some(v) => ItemPayload::FreeSpaceInfo(v),
1930            None => ItemPayload::Unknown(data.to_vec()),
1931        },
1932        KeyType::FreeSpaceExtent => ItemPayload::FreeSpaceExtent,
1933        KeyType::FreeSpaceBitmap => ItemPayload::FreeSpaceBitmap,
1934        KeyType::ChunkItem => match ChunkItem::parse(data) {
1935            Some(v) => ItemPayload::ChunkItem(v),
1936            None => ItemPayload::Unknown(data.to_vec()),
1937        },
1938        KeyType::DeviceItem => match DeviceItem::parse(data) {
1939            Some(v) => ItemPayload::DeviceItem(v),
1940            None => ItemPayload::Unknown(data.to_vec()),
1941        },
1942        KeyType::DeviceExtent => match DeviceExtent::parse(data) {
1943            Some(v) => ItemPayload::DeviceExtent(v),
1944            None => ItemPayload::Unknown(data.to_vec()),
1945        },
1946        KeyType::QgroupStatus => match QgroupStatus::parse(data) {
1947            Some(v) => ItemPayload::QgroupStatus(v),
1948            None => ItemPayload::Unknown(data.to_vec()),
1949        },
1950        KeyType::QgroupInfo => match QgroupInfo::parse(data) {
1951            Some(v) => ItemPayload::QgroupInfo(v),
1952            None => ItemPayload::Unknown(data.to_vec()),
1953        },
1954        KeyType::QgroupLimit => match QgroupLimit::parse(data) {
1955            Some(v) => ItemPayload::QgroupLimit(v),
1956            None => ItemPayload::Unknown(data.to_vec()),
1957        },
1958        KeyType::QgroupRelation => ItemPayload::QgroupRelation,
1959        KeyType::PersistentItem => {
1960            if key.objectid == u64::from(raw::BTRFS_DEV_STATS_OBJECTID) {
1961                ItemPayload::DeviceStats(DeviceStats::parse(data))
1962            } else {
1963                ItemPayload::Unknown(data.to_vec())
1964            }
1965        }
1966        KeyType::TemporaryItem => {
1967            if ObjectId::from_raw(key.objectid) == ObjectId::Balance
1968                && data.len() >= 8
1969            {
1970                ItemPayload::BalanceItem {
1971                    flags: {
1972                        let mut buf = data;
1973                        buf.get_u64_le()
1974                    },
1975                }
1976            } else {
1977                ItemPayload::Unknown(data.to_vec())
1978            }
1979        }
1980        KeyType::DeviceReplace => match DeviceReplaceItem::parse(data) {
1981            Some(v) => ItemPayload::DeviceReplace(v),
1982            None => ItemPayload::Unknown(data.to_vec()),
1983        },
1984        KeyType::UuidKeySubvol | KeyType::UuidKeyReceivedSubvol => {
1985            ItemPayload::UuidItem(UuidItem::parse(data))
1986        }
1987        KeyType::StringItem => ItemPayload::StringItem(data.to_vec()),
1988        KeyType::RaidStripe => match RaidStripeItem::parse(data) {
1989            Some(v) => ItemPayload::RaidStripe(v),
1990            None => ItemPayload::Unknown(data.to_vec()),
1991        },
1992        _ => ItemPayload::Unknown(data.to_vec()),
1993    }
1994}
1995
1996#[cfg(test)]
1997mod tests {
1998    use super::*;
1999
2000    // ── Enum round-trips ──────────────────────────────────────────────
2001
2002    #[test]
2003    fn compression_type_round_trip() {
2004        for v in 0..=3 {
2005            let ct = CompressionType::from_raw(v);
2006            assert_eq!(ct.to_raw(), v);
2007        }
2008        assert_eq!(CompressionType::from_raw(0), CompressionType::None);
2009        assert_eq!(CompressionType::from_raw(1), CompressionType::Zlib);
2010        assert_eq!(CompressionType::from_raw(2), CompressionType::Lzo);
2011        assert_eq!(CompressionType::from_raw(3), CompressionType::Zstd);
2012        assert_eq!(CompressionType::from_raw(99), CompressionType::Unknown(99));
2013        assert_eq!(CompressionType::Unknown(99).to_raw(), 99);
2014    }
2015
2016    #[test]
2017    fn compression_type_names() {
2018        assert_eq!(CompressionType::None.name(), "none");
2019        assert_eq!(CompressionType::Zlib.name(), "zlib");
2020        assert_eq!(CompressionType::Lzo.name(), "lzo");
2021        assert_eq!(CompressionType::Zstd.name(), "zstd");
2022        assert_eq!(CompressionType::Unknown(42).name(), "unknown");
2023    }
2024
2025    #[test]
2026    fn file_extent_type_round_trip() {
2027        assert_eq!(FileExtentType::from_raw(0), FileExtentType::Inline);
2028        assert_eq!(FileExtentType::from_raw(1), FileExtentType::Regular);
2029        assert_eq!(FileExtentType::from_raw(2), FileExtentType::Prealloc);
2030        assert_eq!(FileExtentType::from_raw(77), FileExtentType::Unknown(77));
2031        for v in 0..=2 {
2032            let ft = FileExtentType::from_raw(v);
2033            assert_eq!(ft.to_raw(), v);
2034        }
2035        assert_eq!(FileExtentType::Unknown(77).to_raw(), 77);
2036    }
2037
2038    #[test]
2039    fn file_extent_type_names() {
2040        assert_eq!(FileExtentType::Inline.name(), "inline");
2041        assert_eq!(FileExtentType::Regular.name(), "regular");
2042        assert_eq!(FileExtentType::Prealloc.name(), "prealloc");
2043        assert_eq!(FileExtentType::Unknown(5).name(), "unknown");
2044    }
2045
2046    #[test]
2047    fn file_type_from_raw_all_variants() {
2048        assert_eq!(FileType::from_raw(0), FileType::Unknown);
2049        assert_eq!(FileType::from_raw(1), FileType::RegFile);
2050        assert_eq!(FileType::from_raw(2), FileType::Dir);
2051        assert_eq!(FileType::from_raw(3), FileType::Chrdev);
2052        assert_eq!(FileType::from_raw(4), FileType::Blkdev);
2053        assert_eq!(FileType::from_raw(5), FileType::Fifo);
2054        assert_eq!(FileType::from_raw(6), FileType::Sock);
2055        assert_eq!(FileType::from_raw(7), FileType::Symlink);
2056        assert_eq!(FileType::from_raw(8), FileType::Xattr);
2057        assert_eq!(FileType::from_raw(99), FileType::Other(99));
2058    }
2059
2060    #[test]
2061    fn file_type_names() {
2062        assert_eq!(FileType::Unknown.name(), "UNKNOWN");
2063        assert_eq!(FileType::RegFile.name(), "FILE");
2064        assert_eq!(FileType::Dir.name(), "DIR");
2065        assert_eq!(FileType::Chrdev.name(), "CHRDEV");
2066        assert_eq!(FileType::Blkdev.name(), "BLKDEV");
2067        assert_eq!(FileType::Fifo.name(), "FIFO");
2068        assert_eq!(FileType::Sock.name(), "SOCK");
2069        assert_eq!(FileType::Symlink.name(), "SYMLINK");
2070        assert_eq!(FileType::Xattr.name(), "XATTR");
2071        assert_eq!(FileType::Other(200).name(), "UNKNOWN");
2072    }
2073
2074    // ── Simple struct parsers ─────────────────────────────────────────
2075
2076    #[test]
2077    fn block_group_item_parse() {
2078        let mut buf = Vec::new();
2079        buf.extend_from_slice(&1000u64.to_le_bytes()); // used
2080        buf.extend_from_slice(&256u64.to_le_bytes()); // chunk_objectid
2081        buf.extend_from_slice(
2082            &(raw::BTRFS_BLOCK_GROUP_DATA as u64).to_le_bytes(),
2083        );
2084        let item = BlockGroupItem::parse(&buf).unwrap();
2085        assert_eq!(item.used, 1000);
2086        assert_eq!(item.chunk_objectid, 256);
2087        assert_eq!(item.flags, BlockGroupFlags::DATA);
2088    }
2089
2090    #[test]
2091    fn block_group_item_too_short() {
2092        assert!(BlockGroupItem::parse(&[0; 23]).is_none());
2093    }
2094
2095    #[test]
2096    fn free_space_info_parse() {
2097        let mut buf = Vec::new();
2098        buf.extend_from_slice(&42u32.to_le_bytes());
2099        buf.extend_from_slice(&7u32.to_le_bytes());
2100        let info = FreeSpaceInfo::parse(&buf).unwrap();
2101        assert_eq!(info.extent_count, 42);
2102        assert_eq!(info.flags, FreeSpaceInfoFlags::from_bits_truncate(7));
2103    }
2104
2105    #[test]
2106    fn free_space_info_too_short() {
2107        assert!(FreeSpaceInfo::parse(&[0; 7]).is_none());
2108    }
2109
2110    #[test]
2111    fn dev_extent_parse() {
2112        let size = mem::size_of::<raw::btrfs_dev_extent>();
2113        let mut buf = vec![0u8; size];
2114        buf[0..8].copy_from_slice(&3u64.to_le_bytes()); // chunk_tree
2115        buf[8..16].copy_from_slice(&256u64.to_le_bytes()); // chunk_objectid
2116        buf[16..24].copy_from_slice(&0x10000u64.to_le_bytes()); // chunk_offset
2117        buf[24..32].copy_from_slice(&0x40000u64.to_le_bytes()); // length
2118        // chunk_tree_uuid at offset 32
2119        buf[32..48].copy_from_slice(&[0xAB; 16]);
2120        let de = DeviceExtent::parse(&buf).unwrap();
2121        assert_eq!(de.chunk_tree, 3);
2122        assert_eq!(de.chunk_objectid, 256);
2123        assert_eq!(de.chunk_offset, 0x10000);
2124        assert_eq!(de.length, 0x40000);
2125        assert_eq!(de.chunk_tree_uuid.as_bytes(), &[0xAB; 16]);
2126    }
2127
2128    #[test]
2129    fn dev_extent_too_short() {
2130        let size = mem::size_of::<raw::btrfs_dev_extent>();
2131        assert!(DeviceExtent::parse(&vec![0u8; size - 1]).is_none());
2132    }
2133
2134    #[test]
2135    fn extent_data_ref_parse() {
2136        let size = mem::size_of::<raw::btrfs_extent_data_ref>();
2137        let mut buf = vec![0u8; size];
2138        buf[0..8].copy_from_slice(&5u64.to_le_bytes()); // root
2139        buf[8..16].copy_from_slice(&256u64.to_le_bytes()); // objectid
2140        buf[16..24].copy_from_slice(&0u64.to_le_bytes()); // offset
2141        buf[24..28].copy_from_slice(&1u32.to_le_bytes()); // count
2142        let edr = ExtentDataRef::parse(&buf).unwrap();
2143        assert_eq!(edr.root, 5);
2144        assert_eq!(edr.objectid, 256);
2145        assert_eq!(edr.offset, 0);
2146        assert_eq!(edr.count, 1);
2147    }
2148
2149    #[test]
2150    fn extent_data_ref_too_short() {
2151        assert!(ExtentDataRef::parse(&[0; 27]).is_none());
2152    }
2153
2154    #[test]
2155    fn shared_data_ref_parse() {
2156        let buf = 17u32.to_le_bytes();
2157        let sdr = SharedDataRef::parse(&buf).unwrap();
2158        assert_eq!(sdr.count, 17);
2159    }
2160
2161    #[test]
2162    fn shared_data_ref_too_short() {
2163        assert!(SharedDataRef::parse(&[0; 3]).is_none());
2164    }
2165
2166    #[test]
2167    fn qgroup_info_parse() {
2168        let mut buf = Vec::new();
2169        buf.extend_from_slice(&100u64.to_le_bytes()); // generation
2170        buf.extend_from_slice(&4096u64.to_le_bytes()); // referenced
2171        buf.extend_from_slice(&4096u64.to_le_bytes()); // referenced_compressed
2172        buf.extend_from_slice(&2048u64.to_le_bytes()); // exclusive
2173        buf.extend_from_slice(&2048u64.to_le_bytes()); // exclusive_compressed
2174        let qi = QgroupInfo::parse(&buf).unwrap();
2175        assert_eq!(qi.generation, 100);
2176        assert_eq!(qi.referenced, 4096);
2177        assert_eq!(qi.referenced_compressed, 4096);
2178        assert_eq!(qi.exclusive, 2048);
2179        assert_eq!(qi.exclusive_compressed, 2048);
2180    }
2181
2182    #[test]
2183    fn qgroup_info_too_short() {
2184        assert!(QgroupInfo::parse(&[0; 39]).is_none());
2185    }
2186
2187    #[test]
2188    fn qgroup_limit_parse() {
2189        let mut buf = Vec::new();
2190        buf.extend_from_slice(&3u64.to_le_bytes()); // flags
2191        buf.extend_from_slice(&1_000_000u64.to_le_bytes()); // max_referenced
2192        buf.extend_from_slice(&500_000u64.to_le_bytes()); // max_exclusive
2193        buf.extend_from_slice(&0u64.to_le_bytes()); // rsv_referenced
2194        buf.extend_from_slice(&0u64.to_le_bytes()); // rsv_exclusive
2195        let ql = QgroupLimit::parse(&buf).unwrap();
2196        assert_eq!(ql.flags, 3);
2197        assert_eq!(ql.max_referenced, 1_000_000);
2198        assert_eq!(ql.max_exclusive, 500_000);
2199        assert_eq!(ql.rsv_referenced, 0);
2200        assert_eq!(ql.rsv_exclusive, 0);
2201    }
2202
2203    #[test]
2204    fn qgroup_limit_too_short() {
2205        assert!(QgroupLimit::parse(&[0; 39]).is_none());
2206    }
2207
2208    #[test]
2209    fn qgroup_status_parse_minimal() {
2210        let mut buf = Vec::new();
2211        buf.extend_from_slice(&1u64.to_le_bytes()); // version
2212        buf.extend_from_slice(&50u64.to_le_bytes()); // generation
2213        buf.extend_from_slice(&2u64.to_le_bytes()); // flags
2214        buf.extend_from_slice(&0u64.to_le_bytes()); // scan
2215        let qs = QgroupStatus::parse(&buf).unwrap();
2216        assert_eq!(qs.version, 1);
2217        assert_eq!(qs.generation, 50);
2218        assert_eq!(qs.flags, 2);
2219        assert_eq!(qs.scan, 0);
2220        assert!(qs.enable_gen.is_none());
2221    }
2222
2223    #[test]
2224    fn qgroup_status_parse_with_enable_gen() {
2225        let mut buf = Vec::new();
2226        buf.extend_from_slice(&1u64.to_le_bytes());
2227        buf.extend_from_slice(&50u64.to_le_bytes());
2228        buf.extend_from_slice(&2u64.to_le_bytes());
2229        buf.extend_from_slice(&0u64.to_le_bytes());
2230        buf.extend_from_slice(&99u64.to_le_bytes()); // enable_gen
2231        let qs = QgroupStatus::parse(&buf).unwrap();
2232        assert_eq!(qs.enable_gen, Some(99));
2233    }
2234
2235    #[test]
2236    fn qgroup_status_too_short() {
2237        assert!(QgroupStatus::parse(&[0; 31]).is_none());
2238    }
2239
2240    #[test]
2241    fn dev_replace_item_parse() {
2242        let mut buf = vec![0u8; 80];
2243        buf[0..8].copy_from_slice(&1u64.to_le_bytes()); // src_devid
2244        buf[8..16].copy_from_slice(&0x1000u64.to_le_bytes()); // cursor_left
2245        buf[16..24].copy_from_slice(&0x2000u64.to_le_bytes()); // cursor_right
2246        buf[24..32].copy_from_slice(&0u64.to_le_bytes()); // replace_mode
2247        buf[32..40].copy_from_slice(&2u64.to_le_bytes()); // replace_state
2248        buf[40..48].copy_from_slice(&1700000000u64.to_le_bytes()); // time_started
2249        buf[48..56].copy_from_slice(&1700000100u64.to_le_bytes()); // time_stopped
2250        buf[56..64].copy_from_slice(&3u64.to_le_bytes()); // num_write_errors
2251        buf[64..72].copy_from_slice(&5u64.to_le_bytes()); // num_uncorrectable_read_errors
2252        let dri = DeviceReplaceItem::parse(&buf).unwrap();
2253        assert_eq!(dri.src_devid, 1);
2254        assert_eq!(dri.cursor_left, 0x1000);
2255        assert_eq!(dri.cursor_right, 0x2000);
2256        assert_eq!(dri.replace_state, 2);
2257        assert_eq!(dri.time_started, 1700000000);
2258        assert_eq!(dri.time_stopped, 1700000100);
2259        assert_eq!(dri.num_write_errors, 3);
2260        assert_eq!(dri.num_uncorrectable_read_errors, 5);
2261    }
2262
2263    #[test]
2264    fn dev_replace_item_too_short() {
2265        assert!(DeviceReplaceItem::parse(&[0; 79]).is_none());
2266    }
2267
2268    #[test]
2269    fn raid_stripe_item_parse() {
2270        let mut buf = Vec::new();
2271        buf.extend_from_slice(&1u64.to_le_bytes()); // encoding
2272        // stripe 1
2273        buf.extend_from_slice(&1u64.to_le_bytes()); // devid
2274        buf.extend_from_slice(&0x10000u64.to_le_bytes()); // physical
2275        // stripe 2
2276        buf.extend_from_slice(&2u64.to_le_bytes());
2277        buf.extend_from_slice(&0x20000u64.to_le_bytes());
2278        let rsi = RaidStripeItem::parse(&buf).unwrap();
2279        assert_eq!(rsi.encoding, 1);
2280        assert_eq!(rsi.stripes.len(), 2);
2281        assert_eq!(rsi.stripes[0].devid, 1);
2282        assert_eq!(rsi.stripes[0].physical, 0x10000);
2283        assert_eq!(rsi.stripes[1].devid, 2);
2284        assert_eq!(rsi.stripes[1].physical, 0x20000);
2285    }
2286
2287    #[test]
2288    fn raid_stripe_item_no_stripes() {
2289        let buf = 42u64.to_le_bytes();
2290        let rsi = RaidStripeItem::parse(&buf).unwrap();
2291        assert_eq!(rsi.encoding, 42);
2292        assert!(rsi.stripes.is_empty());
2293    }
2294
2295    #[test]
2296    fn raid_stripe_item_too_short() {
2297        assert!(RaidStripeItem::parse(&[0; 7]).is_none());
2298    }
2299
2300    // ── Variable-length parsers ───────────────────────────────────────
2301
2302    #[test]
2303    fn inode_ref_parse_single() {
2304        let mut buf = Vec::new();
2305        buf.extend_from_slice(&42u64.to_le_bytes()); // index
2306        buf.extend_from_slice(&4u16.to_le_bytes()); // name_len
2307        buf.extend_from_slice(b"test");
2308        let refs = InodeRef::parse_all(&buf);
2309        assert_eq!(refs.len(), 1);
2310        assert_eq!(refs[0].index, 42);
2311        assert_eq!(refs[0].name, b"test");
2312    }
2313
2314    #[test]
2315    fn inode_ref_parse_multiple() {
2316        let mut buf = Vec::new();
2317        // entry 1
2318        buf.extend_from_slice(&1u64.to_le_bytes());
2319        buf.extend_from_slice(&3u16.to_le_bytes());
2320        buf.extend_from_slice(b"abc");
2321        // entry 2
2322        buf.extend_from_slice(&2u64.to_le_bytes());
2323        buf.extend_from_slice(&2u16.to_le_bytes());
2324        buf.extend_from_slice(b"xy");
2325        let refs = InodeRef::parse_all(&buf);
2326        assert_eq!(refs.len(), 2);
2327        assert_eq!(refs[0].index, 1);
2328        assert_eq!(refs[0].name, b"abc");
2329        assert_eq!(refs[1].index, 2);
2330        assert_eq!(refs[1].name, b"xy");
2331    }
2332
2333    #[test]
2334    fn inode_ref_parse_truncated() {
2335        // Header present but name extends past buffer end.
2336        let mut buf = Vec::new();
2337        buf.extend_from_slice(&1u64.to_le_bytes());
2338        buf.extend_from_slice(&10u16.to_le_bytes()); // claims 10 bytes
2339        buf.extend_from_slice(b"abc"); // only 3 available
2340        let refs = InodeRef::parse_all(&buf);
2341        assert!(refs.is_empty());
2342    }
2343
2344    #[test]
2345    fn inode_extref_parse_single() {
2346        let mut buf = Vec::new();
2347        buf.extend_from_slice(&256u64.to_le_bytes()); // parent
2348        buf.extend_from_slice(&3u64.to_le_bytes()); // index
2349        buf.extend_from_slice(&5u16.to_le_bytes()); // name_len
2350        buf.extend_from_slice(b"hello");
2351        let refs = InodeExtref::parse_all(&buf);
2352        assert_eq!(refs.len(), 1);
2353        assert_eq!(refs[0].parent, 256);
2354        assert_eq!(refs[0].index, 3);
2355        assert_eq!(refs[0].name, b"hello");
2356    }
2357
2358    #[test]
2359    fn dir_item_parse_single() {
2360        let dir_item_size = mem::size_of::<raw::btrfs_dir_item>();
2361        let mut buf = vec![0u8; dir_item_size];
2362        // location: DiskKey at offset 0 (17 bytes: u64 objectid + u8 type + u64 offset)
2363        buf[0..8].copy_from_slice(&256u64.to_le_bytes()); // objectid
2364        buf[8] = 1; // key type
2365        buf[9..17].copy_from_slice(&0u64.to_le_bytes()); // offset
2366        // transid at offset 17
2367        buf[17..25].copy_from_slice(&100u64.to_le_bytes());
2368        // data_len at offset 25
2369        buf[25..27].copy_from_slice(&0u16.to_le_bytes());
2370        // name_len at offset 27
2371        buf[27..29].copy_from_slice(&4u16.to_le_bytes());
2372        // file_type at offset 29
2373        buf[29] = 1; // FT_REG_FILE
2374        // Append name
2375        buf.extend_from_slice(b"file");
2376        let items = DirItem::parse_all(&buf);
2377        assert_eq!(items.len(), 1);
2378        assert_eq!(items[0].transid, 100);
2379        assert_eq!(items[0].file_type, FileType::RegFile);
2380        assert_eq!(items[0].name, b"file");
2381        assert!(items[0].data.is_empty());
2382    }
2383
2384    #[test]
2385    fn root_ref_parse() {
2386        let hdr_size = mem::size_of::<raw::btrfs_root_ref>();
2387        let mut buf = vec![0u8; hdr_size];
2388        buf[0..8].copy_from_slice(&256u64.to_le_bytes()); // dirid
2389        buf[8..16].copy_from_slice(&7u64.to_le_bytes()); // sequence
2390        buf[16..18].copy_from_slice(&6u16.to_le_bytes()); // name_len
2391        buf.extend_from_slice(b"subvol");
2392        let rr = RootRef::parse(&buf).unwrap();
2393        assert_eq!(rr.dirid, 256);
2394        assert_eq!(rr.sequence, 7);
2395        assert_eq!(rr.name, b"subvol");
2396    }
2397
2398    #[test]
2399    fn root_ref_too_short() {
2400        let hdr_size = mem::size_of::<raw::btrfs_root_ref>();
2401        assert!(RootRef::parse(&vec![0u8; hdr_size - 1]).is_none());
2402    }
2403
2404    #[test]
2405    fn uuid_item_parse() {
2406        let mut buf = Vec::new();
2407        buf.extend_from_slice(&256u64.to_le_bytes());
2408        buf.extend_from_slice(&257u64.to_le_bytes());
2409        buf.extend_from_slice(&258u64.to_le_bytes());
2410        let ui = UuidItem::parse(&buf);
2411        assert_eq!(ui.subvol_ids, vec![256, 257, 258]);
2412    }
2413
2414    #[test]
2415    fn uuid_item_empty() {
2416        let ui = UuidItem::parse(&[]);
2417        assert!(ui.subvol_ids.is_empty());
2418    }
2419
2420    #[test]
2421    fn dev_stats_parse() {
2422        let mut buf = Vec::new();
2423        buf.extend_from_slice(&1u64.to_le_bytes()); // write_errs
2424        buf.extend_from_slice(&2u64.to_le_bytes()); // read_errs
2425        buf.extend_from_slice(&3u64.to_le_bytes()); // flush_errs
2426        buf.extend_from_slice(&4u64.to_le_bytes()); // corruption_errs
2427        buf.extend_from_slice(&5u64.to_le_bytes()); // generation
2428        let ds = DeviceStats::parse(&buf);
2429        assert_eq!(ds.values.len(), 5);
2430        assert_eq!(ds.values[0], ("write_errs".to_string(), 1));
2431        assert_eq!(ds.values[1], ("read_errs".to_string(), 2));
2432        assert_eq!(ds.values[2], ("flush_errs".to_string(), 3));
2433        assert_eq!(ds.values[3], ("corruption_errs".to_string(), 4));
2434        assert_eq!(ds.values[4], ("generation".to_string(), 5));
2435    }
2436
2437    #[test]
2438    fn dev_stats_partial() {
2439        // Only 2 values available.
2440        let mut buf = Vec::new();
2441        buf.extend_from_slice(&10u64.to_le_bytes());
2442        buf.extend_from_slice(&20u64.to_le_bytes());
2443        let ds = DeviceStats::parse(&buf);
2444        assert_eq!(ds.values.len(), 2);
2445        assert_eq!(ds.values[0].1, 10);
2446        assert_eq!(ds.values[1].1, 20);
2447    }
2448
2449    // ── FileExtentItem ────────────────────────────────────────────────
2450
2451    #[test]
2452    fn file_extent_item_inline() {
2453        let mut buf = vec![0u8; 21 + 10]; // 21 header + 10 inline data
2454        buf[0..8].copy_from_slice(&7u64.to_le_bytes()); // generation
2455        buf[8..16].copy_from_slice(&10u64.to_le_bytes()); // ram_bytes
2456        buf[16] = 0; // compression = none
2457        // bytes 17-19 are encryption/other_encoding (unused)
2458        buf[20] = 0; // extent_type = inline
2459        buf[21..31].copy_from_slice(&[0xAA; 10]); // inline data
2460        let fei = FileExtentItem::parse(&buf).unwrap();
2461        assert_eq!(fei.generation, 7);
2462        assert_eq!(fei.ram_bytes, 10);
2463        assert_eq!(fei.compression, CompressionType::None);
2464        assert_eq!(fei.extent_type, FileExtentType::Inline);
2465        match fei.body {
2466            FileExtentBody::Inline { inline_size } => {
2467                assert_eq!(inline_size, 10)
2468            }
2469            _ => panic!("expected inline body"),
2470        }
2471    }
2472
2473    #[test]
2474    fn file_extent_item_regular() {
2475        let mut buf = vec![0u8; 53];
2476        buf[0..8].copy_from_slice(&100u64.to_le_bytes()); // generation
2477        buf[8..16].copy_from_slice(&4096u64.to_le_bytes()); // ram_bytes
2478        buf[16] = 1; // compression = zlib
2479        buf[20] = 1; // extent_type = regular
2480        buf[21..29].copy_from_slice(&0x100000u64.to_le_bytes()); // disk_bytenr
2481        buf[29..37].copy_from_slice(&4096u64.to_le_bytes()); // disk_num_bytes
2482        buf[37..45].copy_from_slice(&0u64.to_le_bytes()); // offset
2483        buf[45..53].copy_from_slice(&4096u64.to_le_bytes()); // num_bytes
2484        let fei = FileExtentItem::parse(&buf).unwrap();
2485        assert_eq!(fei.generation, 100);
2486        assert_eq!(fei.compression, CompressionType::Zlib);
2487        assert_eq!(fei.extent_type, FileExtentType::Regular);
2488        match fei.body {
2489            FileExtentBody::Regular {
2490                disk_bytenr,
2491                disk_num_bytes,
2492                offset,
2493                num_bytes,
2494            } => {
2495                assert_eq!(disk_bytenr, 0x100000);
2496                assert_eq!(disk_num_bytes, 4096);
2497                assert_eq!(offset, 0);
2498                assert_eq!(num_bytes, 4096);
2499            }
2500            _ => panic!("expected regular body"),
2501        }
2502    }
2503
2504    #[test]
2505    fn file_extent_item_too_short() {
2506        assert!(FileExtentItem::parse(&[0; 20]).is_none());
2507    }
2508
2509    #[test]
2510    fn file_extent_item_regular_too_short() {
2511        // 21 bytes is enough for inline but not for regular.
2512        let mut buf = vec![0u8; 21];
2513        buf[20] = 1; // extent_type = regular
2514        assert!(FileExtentItem::parse(&buf).is_none());
2515    }
2516
2517    // ── Helper functions ──────────────────────────────────────────────
2518
2519    #[test]
2520    fn raw_crc32c_known_value() {
2521        // The raw CRC32C of an empty buffer with seed 0 should be 0.
2522        assert_eq!(raw_crc32c(0, &[]), 0);
2523        // Verify that raw_crc32c differs from the standard CRC32C.
2524        // Standard CRC32C of "123456789" is 0xE3069283.
2525        let raw = raw_crc32c(0, b"123456789");
2526        let standard = crc32c::crc32c(b"123456789");
2527        assert_eq!(standard, 0xE3069283);
2528        assert_ne!(raw, standard);
2529        // raw_crc32c is deterministic.
2530        assert_eq!(raw, raw_crc32c(0, b"123456789"));
2531        // Chaining: raw_crc32c with a nonzero seed produces different results.
2532        let chained = raw_crc32c(raw, b"more");
2533        assert_ne!(chained, raw);
2534    }
2535
2536    #[test]
2537    fn extent_data_ref_hash_deterministic() {
2538        let h1 = extent_data_ref_hash(5, 256, 0);
2539        let h2 = extent_data_ref_hash(5, 256, 0);
2540        assert_eq!(h1, h2);
2541        // Different inputs produce different hashes.
2542        let h3 = extent_data_ref_hash(5, 256, 4096);
2543        assert_ne!(h1, h3);
2544    }
2545
2546    #[test]
2547    fn block_group_flags_type_name() {
2548        assert_eq!(BlockGroupFlags::DATA.type_name(), "Data");
2549        assert_eq!(BlockGroupFlags::METADATA.type_name(), "Metadata");
2550        assert_eq!(BlockGroupFlags::SYSTEM.type_name(), "System");
2551        assert_eq!(
2552            (BlockGroupFlags::DATA | BlockGroupFlags::METADATA).type_name(),
2553            "Data+Metadata"
2554        );
2555        assert_eq!(BlockGroupFlags::GLOBAL_RSV.type_name(), "GlobalReserve");
2556    }
2557
2558    #[test]
2559    fn block_group_flags_profile_name() {
2560        assert_eq!(BlockGroupFlags::DATA.profile_name(), "single");
2561        assert_eq!(
2562            (BlockGroupFlags::DATA | BlockGroupFlags::DUP).profile_name(),
2563            "DUP"
2564        );
2565        assert_eq!(
2566            (BlockGroupFlags::DATA | BlockGroupFlags::RAID0).profile_name(),
2567            "RAID0"
2568        );
2569        assert_eq!(
2570            (BlockGroupFlags::DATA | BlockGroupFlags::RAID1).profile_name(),
2571            "RAID1"
2572        );
2573        assert_eq!(
2574            (BlockGroupFlags::DATA | BlockGroupFlags::RAID10).profile_name(),
2575            "RAID10"
2576        );
2577        assert_eq!(
2578            (BlockGroupFlags::DATA | BlockGroupFlags::RAID5).profile_name(),
2579            "RAID5"
2580        );
2581        assert_eq!(
2582            (BlockGroupFlags::DATA | BlockGroupFlags::RAID6).profile_name(),
2583            "RAID6"
2584        );
2585        assert_eq!(
2586            (BlockGroupFlags::DATA | BlockGroupFlags::RAID1C3).profile_name(),
2587            "RAID1C3"
2588        );
2589        assert_eq!(
2590            (BlockGroupFlags::DATA | BlockGroupFlags::RAID1C4).profile_name(),
2591            "RAID1C4"
2592        );
2593    }
2594}