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, KeyType, ObjectId},
13    util::{raw_crc32c, write_disk_key},
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    /// Serialize to 12 bytes (8-byte sec + 4-byte nsec).
169    fn write_to(&self, buf: &mut Vec<u8>) {
170        buf.put_u64_le(self.sec);
171        buf.put_u32_le(self.nsec);
172    }
173}
174
175/// Compression type for file extents.
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177/// See also `btrfs_uapi::defrag::CompressType` which omits `None`/`Unknown`
178/// for use in ioctl requests.
179pub enum CompressionType {
180    /// No compression.
181    None,
182    /// Zlib (deflate) compression.
183    Zlib,
184    /// LZO compression (btrfs per-sector format).
185    Lzo,
186    /// Zstandard compression.
187    Zstd,
188    /// Unrecognized compression type byte.
189    Unknown(u8),
190}
191
192impl CompressionType {
193    /// Convert a raw on-disk compression type byte to a `CompressionType` variant.
194    #[must_use]
195    pub fn from_raw(v: u8) -> Self {
196        match v {
197            0 => Self::None,
198            1 => Self::Zlib,
199            2 => Self::Lzo,
200            3 => Self::Zstd,
201            _ => Self::Unknown(v),
202        }
203    }
204
205    /// Return the human-readable name of this compression type.
206    #[must_use]
207    pub fn name(&self) -> &'static str {
208        match self {
209            Self::None => "none",
210            Self::Zlib => "zlib",
211            Self::Lzo => "lzo",
212            Self::Zstd => "zstd",
213            Self::Unknown(_) => "unknown",
214        }
215    }
216
217    /// Convert back to the raw on-disk byte value.
218    #[must_use]
219    pub fn to_raw(self) -> u8 {
220        match self {
221            Self::None => 0,
222            Self::Zlib => 1,
223            Self::Lzo => 2,
224            Self::Zstd => 3,
225            Self::Unknown(v) => v,
226        }
227    }
228}
229
230/// File extent type.
231#[derive(Debug, Clone, Copy, PartialEq, Eq)]
232pub enum FileExtentType {
233    /// Data stored directly in the tree leaf (small files or file tails).
234    Inline,
235    /// Data stored in a separate disk extent, referenced by logical address.
236    Regular,
237    /// Preallocated extent (reserved but not yet written).
238    Prealloc,
239    /// Unrecognized extent type byte.
240    Unknown(u8),
241}
242
243impl FileExtentType {
244    /// Convert a raw on-disk extent type byte to a `FileExtentType` variant.
245    #[must_use]
246    pub fn from_raw(v: u8) -> Self {
247        match u32::from(v) {
248            raw::BTRFS_FILE_EXTENT_INLINE => Self::Inline,
249            raw::BTRFS_FILE_EXTENT_REG => Self::Regular,
250            raw::BTRFS_FILE_EXTENT_PREALLOC => Self::Prealloc,
251            _ => Self::Unknown(v),
252        }
253    }
254
255    /// Return the human-readable name of this extent type.
256    #[must_use]
257    pub fn name(&self) -> &'static str {
258        match self {
259            Self::Inline => "inline",
260            Self::Regular => "regular",
261            Self::Prealloc => "prealloc",
262            Self::Unknown(_) => "unknown",
263        }
264    }
265
266    /// Convert back to the raw on-disk byte value.
267    #[must_use]
268    #[allow(clippy::cast_possible_truncation)]
269    pub fn to_raw(self) -> u8 {
270        match self {
271            Self::Inline => raw::BTRFS_FILE_EXTENT_INLINE as u8,
272            Self::Regular => raw::BTRFS_FILE_EXTENT_REG as u8,
273            Self::Prealloc => raw::BTRFS_FILE_EXTENT_PREALLOC as u8,
274            Self::Unknown(v) => v,
275        }
276    }
277}
278
279/// Directory entry file type, stored in `btrfs_dir_item::type`.
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
281pub enum FileType {
282    /// Unknown file type (0).
283    Unknown,
284    /// Regular file.
285    RegFile,
286    /// Directory.
287    Dir,
288    /// Character device.
289    Chrdev,
290    /// Block device.
291    Blkdev,
292    /// Named pipe (FIFO).
293    Fifo,
294    /// Unix domain socket.
295    Sock,
296    /// Symbolic link.
297    Symlink,
298    /// Extended attribute (used in `XATTR_ITEM` entries).
299    Xattr,
300    /// Unrecognized file type byte.
301    Other(u8),
302}
303
304impl FileType {
305    /// Convert a raw on-disk file type byte to a `FileType` variant.
306    #[must_use]
307    pub fn from_raw(v: u8) -> Self {
308        match u32::from(v) {
309            raw::BTRFS_FT_UNKNOWN => Self::Unknown,
310            raw::BTRFS_FT_REG_FILE => Self::RegFile,
311            raw::BTRFS_FT_DIR => Self::Dir,
312            raw::BTRFS_FT_CHRDEV => Self::Chrdev,
313            raw::BTRFS_FT_BLKDEV => Self::Blkdev,
314            raw::BTRFS_FT_FIFO => Self::Fifo,
315            raw::BTRFS_FT_SOCK => Self::Sock,
316            raw::BTRFS_FT_SYMLINK => Self::Symlink,
317            raw::BTRFS_FT_XATTR => Self::Xattr,
318            _ => Self::Other(v),
319        }
320    }
321
322    /// Return the human-readable name of this file type (matches btrfs-progs output).
323    #[must_use]
324    pub fn name(&self) -> &'static str {
325        match self {
326            Self::Unknown | Self::Other(_) => "UNKNOWN",
327            Self::RegFile => "FILE",
328            Self::Dir => "DIR",
329            Self::Chrdev => "CHRDEV",
330            Self::Blkdev => "BLKDEV",
331            Self::Fifo => "FIFO",
332            Self::Sock => "SOCK",
333            Self::Symlink => "SYMLINK",
334            Self::Xattr => "XATTR",
335        }
336    }
337}
338
339/// Inode metadata, stored as `INODE_ITEM` in the FS tree.
340///
341/// Contains POSIX attributes (uid, gid, mode, timestamps) plus btrfs-specific
342/// fields (flags, sequence number, block group hint).
343#[derive(Debug, Clone)]
344pub struct InodeItem {
345    /// Generation when this inode was created.
346    pub generation: u64,
347    /// Transaction ID of the last modification.
348    pub transid: u64,
349    /// Logical file size in bytes.
350    pub size: u64,
351    /// Total on-disk bytes used (including all copies for RAID).
352    pub nbytes: u64,
353    /// Block group hint for new allocations.
354    pub block_group: u64,
355    /// Hard link count.
356    pub nlink: u32,
357    /// Owner user ID.
358    pub uid: u32,
359    /// Owner group ID.
360    pub gid: u32,
361    /// POSIX file mode (type + permissions).
362    pub mode: u32,
363    /// Device number (for character/block device inodes).
364    pub rdev: u64,
365    /// Inode flags (NODATASUM, COMPRESS, etc.).
366    pub flags: InodeFlags,
367    /// NFS-compatible change sequence number.
368    pub sequence: u64,
369    /// Last access time.
370    pub atime: Timespec,
371    /// Last change time (inode metadata).
372    pub ctime: Timespec,
373    /// Last modification time (file data).
374    pub mtime: Timespec,
375    /// Creation time.
376    pub otime: Timespec,
377}
378
379impl InodeItem {
380    /// Parse an inode item from a raw byte buffer. Returns `None` if the
381    /// buffer is too small.
382    #[must_use]
383    pub fn parse(data: &[u8]) -> Option<Self> {
384        if data.len() < mem::size_of::<raw::btrfs_inode_item>() {
385            return None;
386        }
387        let mut buf = data;
388        Some(Self {
389            generation: buf.get_u64_le(),
390            transid: buf.get_u64_le(),
391            size: buf.get_u64_le(),
392            nbytes: buf.get_u64_le(),
393            block_group: buf.get_u64_le(),
394            nlink: buf.get_u32_le(),
395            uid: buf.get_u32_le(),
396            gid: buf.get_u32_le(),
397            mode: buf.get_u32_le(),
398            rdev: buf.get_u64_le(),
399            flags: InodeFlags::from_bits_truncate(buf.get_u64_le()),
400            sequence: buf.get_u64_le(),
401            // Skip reserved[4] (4 x u64 = 32 bytes)
402            atime: {
403                buf.advance(32);
404                Timespec::parse(&mut buf)
405            },
406            ctime: Timespec::parse(&mut buf),
407            mtime: Timespec::parse(&mut buf),
408            otime: Timespec::parse(&mut buf),
409        })
410    }
411}
412
413/// Parameters for creating an inode item.
414pub struct InodeItemArgs {
415    /// Generation and transid.
416    pub generation: u64,
417    /// Logical file size.
418    pub size: u64,
419    /// On-disk bytes used.
420    pub nbytes: u64,
421    /// Hard link count.
422    pub nlink: u32,
423    /// Owner user ID.
424    pub uid: u32,
425    /// Owner group ID.
426    pub gid: u32,
427    /// POSIX file mode (type + permissions).
428    pub mode: u32,
429    /// Timestamp for atime/ctime/mtime/otime.
430    pub time: Timespec,
431}
432
433impl InodeItemArgs {
434    /// Serialize to the 160-byte on-disk `btrfs_inode_item` representation.
435    #[must_use]
436    pub fn to_bytes(&self) -> Vec<u8> {
437        let mut buf = Vec::with_capacity(160);
438        buf.put_u64_le(self.generation);
439        buf.put_u64_le(self.generation); // transid = generation
440        buf.put_u64_le(self.size);
441        buf.put_u64_le(self.nbytes);
442        buf.put_u64_le(0); // block_group
443        buf.put_u32_le(self.nlink);
444        buf.put_u32_le(self.uid);
445        buf.put_u32_le(self.gid);
446        buf.put_u32_le(self.mode);
447        buf.put_u64_le(0); // rdev
448        buf.put_u64_le(0); // flags
449        buf.put_u64_le(0); // sequence
450        buf.extend_from_slice(&[0u8; 32]); // reserved
451        for _ in 0..4 {
452            self.time.write_to(&mut buf);
453        }
454        debug_assert_eq!(buf.len(), 160);
455        buf
456    }
457}
458
459/// Hard link reference from an inode to a directory entry.
460///
461/// Key: `(inode_number, INODE_REF, parent_dir_inode)`. Multiple refs can be
462/// packed into a single item when an inode has several hard links in the same
463/// parent directory.
464#[derive(Debug, Clone)]
465pub struct InodeRef {
466    /// Index in the parent directory (matches a `DIR_INDEX` key offset).
467    pub index: u64,
468    /// Filename component (raw bytes, typically UTF-8).
469    pub name: Vec<u8>,
470}
471
472impl InodeRef {
473    /// Parse all packed inode refs from a single item's data buffer.
474    #[must_use]
475    pub fn parse_all(data: &[u8]) -> Vec<Self> {
476        let mut result = Vec::new();
477        let mut buf = data;
478        while buf.remaining() >= 10 {
479            let index = buf.get_u64_le();
480            let name_len = buf.get_u16_le() as usize;
481            if buf.remaining() < name_len {
482                break;
483            }
484            let name = buf[..name_len].to_vec();
485            buf.advance(name_len);
486            result.push(Self { index, name });
487        }
488        result
489    }
490
491    /// Serialize a single inode ref entry.
492    ///
493    /// On-disk layout: index (8) + `name_len` (2) + name.
494    #[must_use]
495    pub fn serialize(index: u64, name: &[u8]) -> Vec<u8> {
496        let mut buf = Vec::with_capacity(10 + name.len());
497        buf.put_u64_le(index);
498        #[allow(clippy::cast_possible_truncation)]
499        buf.put_u16_le(name.len() as u16);
500        buf.extend_from_slice(name);
501        buf
502    }
503}
504
505/// Extended inode reference, used when the `EXTREF` feature is enabled.
506///
507/// Unlike `InodeRef`, the parent directory objectid is stored in the struct
508/// rather than the key offset, allowing references from different parent
509/// directories to coexist.
510#[derive(Debug, Clone)]
511pub struct InodeExtref {
512    /// Parent directory inode number.
513    pub parent: u64,
514    /// Index in the parent directory.
515    pub index: u64,
516    /// Filename component (raw bytes, typically UTF-8).
517    pub name: Vec<u8>,
518}
519
520impl InodeExtref {
521    /// Parse all packed extended inode refs from a single item's data buffer.
522    #[must_use]
523    pub fn parse_all(data: &[u8]) -> Vec<Self> {
524        let mut result = Vec::new();
525        let mut buf = data;
526        while buf.remaining() >= 18 {
527            let parent = buf.get_u64_le();
528            let index = buf.get_u64_le();
529            let name_len = buf.get_u16_le() as usize;
530            if buf.remaining() < name_len {
531                break;
532            }
533            let name = buf[..name_len].to_vec();
534            buf.advance(name_len);
535            result.push(Self {
536                parent,
537                index,
538                name,
539            });
540        }
541        result
542    }
543}
544
545/// Directory entry, stored as `DIR_ITEM` (hashed by name) or `DIR_INDEX`
546/// (sequential index) in the FS tree.
547///
548/// Multiple entries can be packed into a single item when names hash to the
549/// same value (for `DIR_ITEM`) or when processing xattrs (`XATTR_ITEM`).
550#[derive(Debug, Clone)]
551pub struct DirItem {
552    /// Key of the target inode (objectid = inode number, type = `INODE_ITEM`).
553    pub location: DiskKey,
554    /// Transaction ID when this entry was created.
555    pub transid: u64,
556    /// Type of the referenced inode (file, directory, symlink, etc.).
557    pub file_type: FileType,
558    /// Filename or xattr name (raw bytes).
559    pub name: Vec<u8>,
560    /// Xattr value (empty for regular directory entries).
561    pub data: Vec<u8>,
562}
563
564impl DirItem {
565    /// Parse all packed directory entries from a single item's data buffer.
566    #[must_use]
567    pub fn parse_all(data: &[u8]) -> Vec<Self> {
568        let mut result = Vec::new();
569        let dir_item_size = mem::size_of::<raw::btrfs_dir_item>();
570        let mut buf = data;
571
572        while buf.remaining() >= dir_item_size {
573            let location = DiskKey::parse(buf, 0);
574            buf.advance(17); // skip past DiskKey (u64 + u8 + u64)
575            let transid = buf.get_u64_le();
576            let data_len = buf.get_u16_le() as usize;
577            let name_len = buf.get_u16_le() as usize;
578            let file_type = FileType::from_raw(buf.get_u8());
579
580            if buf.remaining() < name_len + data_len {
581                break;
582            }
583            let name = buf[..name_len].to_vec();
584            buf.advance(name_len);
585            let item_data = buf[..data_len].to_vec();
586            buf.advance(data_len);
587            result.push(Self {
588                location,
589                transid,
590                file_type,
591                name,
592                data: item_data,
593            });
594        }
595        result
596    }
597
598    /// Serialize a directory entry.
599    ///
600    /// On-disk layout: location key (17) + transid (8) + `data_len` (2) +
601    /// `name_len` (2) + type (1) + name + data = 30 + `name.len()` + `data.len()`.
602    #[must_use]
603    pub fn serialize(
604        location: &DiskKey,
605        transid: u64,
606        file_type: u8,
607        name: &[u8],
608    ) -> Vec<u8> {
609        let mut buf = Vec::with_capacity(30 + name.len());
610        let key_off = buf.len();
611        buf.extend_from_slice(&[0u8; 17]);
612        write_disk_key(&mut buf[key_off..], 0, location);
613        buf.put_u64_le(transid);
614        buf.put_u16_le(0); // data_len (no xattr data for regular entries)
615        #[allow(clippy::cast_possible_truncation)]
616        buf.put_u16_le(name.len() as u16);
617        buf.put_u8(file_type);
618        buf.extend_from_slice(name);
619        buf
620    }
621}
622
623bitflags::bitflags! {
624    /// Root item flags stored in `btrfs_root_item::flags`.
625    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
626    pub struct RootItemFlags: u64 {
627        const RDONLY = raw::BTRFS_ROOT_SUBVOL_RDONLY as u64;
628        const DEAD   = raw::BTRFS_ROOT_SUBVOL_DEAD;
629        // Preserve unknown bits from the on-disk value.
630        const _ = !0;
631    }
632}
633
634impl fmt::Display for RootItemFlags {
635    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
636        if self.contains(Self::RDONLY) {
637            write!(f, "RDONLY")
638        } else {
639            write!(f, "none")
640        }
641    }
642}
643
644/// Root item describing a tree (subvolume, snapshot, or internal tree).
645///
646/// Stored in the root tree with key `(tree_objectid, ROOT_ITEM, 0)`. Contains
647/// the root block pointer, subvolume UUIDs, and transaction timestamps needed
648/// for snapshot management and send/receive.
649#[derive(Debug, Clone)]
650pub struct RootItem {
651    /// Raw bytes of the embedded `btrfs_inode_item` (160 bytes).
652    ///
653    /// The on-disk `ROOT_ITEM` starts with an embedded inode describing the
654    /// root directory of the subvolume. This is preserved as raw bytes to
655    /// avoid losing data during parse/serialize round-trips, since the
656    /// embedded inode fields are not otherwise tracked by this struct.
657    pub inode_data: Vec<u8>,
658    /// Generation when this root was last modified.
659    pub generation: u64,
660    /// Objectid of the root directory inode (always 256 for FS trees).
661    pub root_dirid: u64,
662    /// Logical bytenr of this tree's root block.
663    pub bytenr: u64,
664    /// Quota byte limit (0 = unlimited).
665    pub byte_limit: u64,
666    /// Bytes used by this tree.
667    pub bytes_used: u64,
668    /// Generation of the last snapshot taken from this subvolume.
669    pub last_snapshot: u64,
670    /// Root flags (RDONLY for read-only snapshots).
671    pub flags: RootItemFlags,
672    /// Reference count.
673    pub refs: u32,
674    /// Progress key for in-progress drop operations.
675    pub drop_progress: DiskKey,
676    /// Tree level of the drop progress.
677    pub drop_level: u8,
678    /// B-tree level of this tree's root block.
679    pub level: u8,
680    /// Extended generation (v2 root items, matches `generation` in practice).
681    pub generation_v2: u64,
682    /// UUID of this subvolume.
683    pub uuid: Uuid,
684    /// UUID of the parent subvolume (for snapshots).
685    pub parent_uuid: Uuid,
686    /// UUID of the subvolume this was received from (for send/receive).
687    pub received_uuid: Uuid,
688    /// Transaction ID of the last change to this subvolume.
689    pub ctransid: u64,
690    /// Transaction ID when this subvolume was created.
691    pub otransid: u64,
692    /// Transaction ID when this subvolume was sent.
693    pub stransid: u64,
694    /// Transaction ID when this subvolume was received.
695    pub rtransid: u64,
696    /// Time of the last change.
697    pub ctime: Timespec,
698    /// Creation time.
699    pub otime: Timespec,
700    /// Time when sent.
701    pub stime: Timespec,
702    /// Time when received.
703    pub rtime: Timespec,
704}
705
706impl RootItem {
707    /// Parse a root item from a raw byte buffer. Handles both v1 (shorter)
708    /// and v2 (full) root item formats gracefully, defaulting missing fields
709    /// to zero/nil.
710    #[must_use]
711    #[allow(clippy::too_many_lines)]
712    pub fn parse(data: &[u8]) -> Option<Self> {
713        let inode_size = mem::size_of::<raw::btrfs_inode_item>();
714        if data.len() < inode_size + 8 {
715            return None;
716        }
717
718        let inode_data = data[..inode_size].to_vec();
719        let mut buf = &data[inode_size..];
720        let generation = buf.get_u64_le();
721        let root_dirid = buf.get_u64_le();
722        let bytenr = buf.get_u64_le();
723        let byte_limit = buf.get_u64_le();
724        let bytes_used = buf.get_u64_le();
725        let last_snapshot = buf.get_u64_le();
726        let flags = RootItemFlags::from_bits_truncate(buf.get_u64_le());
727        let refs = buf.get_u32_le();
728
729        let dp_off = inode_size + 60;
730        let drop_progress = if dp_off + 17 <= data.len() {
731            DiskKey::parse(data, dp_off)
732        } else {
733            DiskKey::parse(&[0; 17], 0)
734        };
735        let drop_level = if dp_off + 17 < data.len() {
736            data[dp_off + 17]
737        } else {
738            0
739        };
740
741        let level_off = mem::offset_of!(raw::btrfs_root_item, level);
742        let level = if level_off < data.len() {
743            data[level_off]
744        } else {
745            0
746        };
747        let generation_v2 = if level_off + 1 + 8 <= data.len() {
748            let mut b = &data[level_off + 1..];
749            b.get_u64_le()
750        } else {
751            0
752        };
753
754        let uuid_off = mem::offset_of!(raw::btrfs_root_item, uuid);
755        let uuid = if uuid_off + 16 <= data.len() {
756            let mut b = &data[uuid_off..];
757            get_uuid(&mut b)
758        } else {
759            Uuid::nil()
760        };
761        let parent_uuid = if uuid_off + 32 <= data.len() {
762            let mut b = &data[uuid_off + 16..];
763            get_uuid(&mut b)
764        } else {
765            Uuid::nil()
766        };
767        let received_uuid = if uuid_off + 48 <= data.len() {
768            let mut b = &data[uuid_off + 32..];
769            get_uuid(&mut b)
770        } else {
771            Uuid::nil()
772        };
773
774        let ct_off = mem::offset_of!(raw::btrfs_root_item, ctransid);
775        let ctransid = if ct_off + 8 <= data.len() {
776            let mut b = &data[ct_off..];
777            b.get_u64_le()
778        } else {
779            0
780        };
781        let otransid = if ct_off + 16 <= data.len() {
782            let mut b = &data[ct_off + 8..];
783            b.get_u64_le()
784        } else {
785            0
786        };
787        let stransid = if ct_off + 24 <= data.len() {
788            let mut b = &data[ct_off + 16..];
789            b.get_u64_le()
790        } else {
791            0
792        };
793        let rtransid = if ct_off + 32 <= data.len() {
794            let mut b = &data[ct_off + 24..];
795            b.get_u64_le()
796        } else {
797            0
798        };
799
800        let ctime_off = mem::offset_of!(raw::btrfs_root_item, ctime);
801        let ts_size = mem::size_of::<raw::btrfs_timespec>();
802        let ctime = if ctime_off + ts_size <= data.len() {
803            let mut b = &data[ctime_off..];
804            Timespec::parse(&mut b)
805        } else {
806            Timespec { sec: 0, nsec: 0 }
807        };
808        let otime = if ctime_off + 2 * ts_size <= data.len() {
809            let mut b = &data[ctime_off + ts_size..];
810            Timespec::parse(&mut b)
811        } else {
812            Timespec { sec: 0, nsec: 0 }
813        };
814        let stime = if ctime_off + 3 * ts_size <= data.len() {
815            let mut b = &data[ctime_off + 2 * ts_size..];
816            Timespec::parse(&mut b)
817        } else {
818            Timespec { sec: 0, nsec: 0 }
819        };
820        let rtime = if ctime_off + 4 * ts_size <= data.len() {
821            let mut b = &data[ctime_off + 3 * ts_size..];
822            Timespec::parse(&mut b)
823        } else {
824            Timespec { sec: 0, nsec: 0 }
825        };
826
827        Some(Self {
828            inode_data,
829            generation,
830            root_dirid,
831            bytenr,
832            byte_limit,
833            bytes_used,
834            last_snapshot,
835            flags,
836            refs,
837            drop_progress,
838            drop_level,
839            level,
840            generation_v2,
841            uuid,
842            parent_uuid,
843            received_uuid,
844            ctransid,
845            otransid,
846            stransid,
847            rtransid,
848            ctime,
849            otime,
850            stime,
851            rtime,
852        })
853    }
854
855    /// Create a minimal root item for internal trees (not subvolumes).
856    ///
857    /// Sets generation, bytenr, level, and refs=1. All other fields are
858    /// zeroed/nil.
859    #[must_use]
860    pub fn new_internal(generation: u64, bytenr: u64, level: u8) -> Self {
861        Self {
862            inode_data: vec![0u8; 160],
863            generation,
864            root_dirid: 0,
865            bytenr,
866            byte_limit: 0,
867            bytes_used: 0,
868            last_snapshot: 0,
869            flags: RootItemFlags::empty(),
870            refs: 1,
871            drop_progress: DiskKey {
872                objectid: 0,
873                key_type: KeyType::from_raw(0),
874                offset: 0,
875            },
876            drop_level: 0,
877            level,
878            generation_v2: generation,
879            uuid: Uuid::nil(),
880            parent_uuid: Uuid::nil(),
881            received_uuid: Uuid::nil(),
882            ctransid: 0,
883            otransid: 0,
884            stransid: 0,
885            rtransid: 0,
886            ctime: Timespec { sec: 0, nsec: 0 },
887            otime: Timespec { sec: 0, nsec: 0 },
888            stime: Timespec { sec: 0, nsec: 0 },
889            rtime: Timespec { sec: 0, nsec: 0 },
890        }
891    }
892
893    /// Serialize to the on-disk byte representation (496 bytes).
894    ///
895    /// Starts with a 160-byte zeroed `inode_item`, followed by root item
896    /// fields, padded with zeros to 496 bytes total.
897    #[must_use]
898    pub fn to_bytes(&self) -> Vec<u8> {
899        let mut buf = Vec::with_capacity(496);
900
901        // btrfs_inode_item (160 bytes) — preserved from the original on-disk
902        // data, or zeroed for newly created root items.
903        buf.extend_from_slice(&self.inode_data);
904
905        buf.put_u64_le(self.generation);
906        buf.put_u64_le(self.root_dirid);
907        buf.put_u64_le(self.bytenr);
908        buf.put_u64_le(self.byte_limit);
909        buf.put_u64_le(self.bytes_used);
910        buf.put_u64_le(self.last_snapshot);
911        buf.put_u64_le(self.flags.bits());
912        buf.put_u32_le(self.refs);
913
914        let key_off = buf.len();
915        buf.extend_from_slice(&[0u8; 17]);
916        write_disk_key(&mut buf[key_off..], 0, &self.drop_progress);
917
918        buf.put_u8(self.drop_level);
919        buf.put_u8(self.level);
920        buf.put_u64_le(self.generation_v2);
921
922        buf.extend_from_slice(self.uuid.as_bytes());
923        buf.extend_from_slice(self.parent_uuid.as_bytes());
924        buf.extend_from_slice(self.received_uuid.as_bytes());
925
926        buf.put_u64_le(self.ctransid);
927        buf.put_u64_le(self.otransid);
928        buf.put_u64_le(self.stransid);
929        buf.put_u64_le(self.rtransid);
930
931        self.ctime.write_to(&mut buf);
932        self.otime.write_to(&mut buf);
933        self.stime.write_to(&mut buf);
934        self.rtime.write_to(&mut buf);
935
936        // Pad to 439 bytes (sizeof(btrfs_root_item): 160 inode + 279 root
937        // fields including reserved[8]).
938        buf.resize(mem::size_of::<raw::btrfs_root_item>(), 0);
939        buf
940    }
941}
942
943/// Reference linking a subvolume to its parent directory.
944///
945/// `ROOT_REF` keys (parent → child) and `ROOT_BACKREF` keys (child → parent)
946/// use the same on-disk format.
947#[derive(Debug, Clone)]
948pub struct RootRef {
949    /// Inode number of the directory containing the subvolume entry.
950    pub dirid: u64,
951    /// Directory sequence number (matches the `DIR_INDEX` offset).
952    pub sequence: u64,
953    /// Name of the subvolume entry in the parent directory.
954    pub name: Vec<u8>,
955}
956
957impl RootRef {
958    /// Parse a root ref (or root backref) from a raw byte buffer.
959    #[must_use]
960    pub fn parse(data: &[u8]) -> Option<Self> {
961        if data.len() < mem::size_of::<raw::btrfs_root_ref>() {
962            return None;
963        }
964        let mut buf = data;
965        let dirid = buf.get_u64_le();
966        let sequence = buf.get_u64_le();
967        let name_len = buf.get_u16_le() as usize;
968        let name_start = mem::size_of::<raw::btrfs_root_ref>();
969        let name = if name_start + name_len <= data.len() {
970            data[name_start..name_start + name_len].to_vec()
971        } else {
972            Vec::new()
973        };
974        Some(Self {
975            dirid,
976            sequence,
977            name,
978        })
979    }
980
981    /// Serialize a `(dirid, sequence, name)` tuple to the on-disk
982    /// `btrfs_root_ref` byte representation: 18-byte fixed header
983    /// (`dirid` u64 + `sequence` u64 + `name_len` u16, all
984    /// little-endian) followed by the raw name bytes.
985    ///
986    /// # Panics
987    ///
988    /// Panics if `name.len()` does not fit in a `u16` (the on-disk
989    /// `name_len` field is 16 bits). Practical names are always far
990    /// below 65535 bytes.
991    #[must_use]
992    pub fn serialize(dirid: u64, sequence: u64, name: &[u8]) -> Vec<u8> {
993        let header = mem::size_of::<raw::btrfs_root_ref>();
994        let mut buf = Vec::with_capacity(header + name.len());
995        buf.put_u64_le(dirid);
996        buf.put_u64_le(sequence);
997        buf.put_u16_le(
998            u16::try_from(name.len())
999                .expect("RootRef::serialize: name length does not fit in u16"),
1000        );
1001        buf.put_slice(name);
1002        buf
1003    }
1004}
1005
1006/// File extent descriptor, stored as `EXTENT_DATA` in the FS tree.
1007///
1008/// Key: `(inode, EXTENT_DATA, file_offset)`. Describes how a range of file
1009/// bytes maps to on-disk storage. Extents can be inline (data embedded in the
1010/// item), regular (referencing a disk extent), or prealloc (reserved but
1011/// unwritten).
1012#[derive(Debug, Clone)]
1013pub struct FileExtentItem {
1014    /// Generation when this extent was allocated.
1015    pub generation: u64,
1016    /// Uncompressed size of the data in this extent.
1017    pub ram_bytes: u64,
1018    /// Compression algorithm applied to the on-disk data.
1019    pub compression: CompressionType,
1020    /// Whether the extent is inline, regular, or preallocated.
1021    pub extent_type: FileExtentType,
1022    /// Type-specific extent location.
1023    pub body: FileExtentBody,
1024}
1025
1026/// Body of a file extent: either inline data or a reference to a disk extent.
1027#[derive(Debug, Clone)]
1028pub enum FileExtentBody {
1029    /// Data is stored directly in the tree leaf (small files/tails).
1030    Inline {
1031        /// Number of bytes of inline data following the extent header.
1032        inline_size: usize,
1033    },
1034    /// Data is stored in a separate disk extent.
1035    Regular {
1036        /// Logical byte address of the extent on disk (0 = hole/sparse).
1037        disk_bytenr: u64,
1038        /// Size of the on-disk extent in bytes (compressed size if compressed).
1039        disk_num_bytes: u64,
1040        /// Byte offset into the extent where this file range starts.
1041        offset: u64,
1042        /// Number of logical file bytes this extent covers.
1043        num_bytes: u64,
1044    },
1045}
1046
1047impl FileExtentItem {
1048    /// Parse a file extent item from a raw byte buffer.
1049    #[must_use]
1050    pub fn parse(data: &[u8]) -> Option<Self> {
1051        if data.len() < 21 {
1052            return None;
1053        }
1054        let mut buf = data;
1055        let generation = buf.get_u64_le();
1056        let ram_bytes = buf.get_u64_le();
1057        let compression = CompressionType::from_raw(buf.get_u8());
1058        buf.advance(3); // skip encryption, other_encoding
1059        let extent_type = FileExtentType::from_raw(buf.get_u8());
1060
1061        let body = if extent_type == FileExtentType::Inline {
1062            FileExtentBody::Inline {
1063                inline_size: buf.remaining(),
1064            }
1065        } else if buf.remaining() >= 32 {
1066            FileExtentBody::Regular {
1067                disk_bytenr: buf.get_u64_le(),
1068                disk_num_bytes: buf.get_u64_le(),
1069                offset: buf.get_u64_le(),
1070                num_bytes: buf.get_u64_le(),
1071            }
1072        } else {
1073            return None;
1074        };
1075
1076        Some(Self {
1077            generation,
1078            ram_bytes,
1079            compression,
1080            extent_type,
1081            body,
1082        })
1083    }
1084
1085    /// Size of the fixed `btrfs_file_extent_item` header (the bytes before
1086    /// `disk_bytenr` / inline data).
1087    pub const HEADER_SIZE: usize = 21;
1088
1089    /// Size of a regular or prealloc `EXTENT_DATA` item: 21-byte header plus
1090    /// 32-byte body (`disk_bytenr`, `disk_num_bytes`, `offset`, `num_bytes`).
1091    pub const REGULAR_SIZE: usize = 53;
1092
1093    /// Serialize a regular or prealloc `EXTENT_DATA` item (53 bytes).
1094    ///
1095    /// `prealloc` selects `BTRFS_FILE_EXTENT_PREALLOC` instead of the default
1096    /// `BTRFS_FILE_EXTENT_REG`. `disk_bytenr` of 0 represents a hole.
1097    #[must_use]
1098    #[allow(clippy::too_many_arguments)]
1099    pub fn to_bytes_regular(
1100        generation: u64,
1101        ram_bytes: u64,
1102        compression: CompressionType,
1103        prealloc: bool,
1104        disk_bytenr: u64,
1105        disk_num_bytes: u64,
1106        offset: u64,
1107        num_bytes: u64,
1108    ) -> Vec<u8> {
1109        let extent_type = if prealloc {
1110            FileExtentType::Prealloc
1111        } else {
1112            FileExtentType::Regular
1113        };
1114        let mut buf = Vec::with_capacity(Self::REGULAR_SIZE);
1115        buf.put_u64_le(generation);
1116        buf.put_u64_le(ram_bytes);
1117        buf.put_u8(compression.to_raw());
1118        buf.put_u8(0); // encryption
1119        buf.put_u16_le(0); // other_encoding
1120        buf.put_u8(extent_type.to_raw());
1121        buf.put_u64_le(disk_bytenr);
1122        buf.put_u64_le(disk_num_bytes);
1123        buf.put_u64_le(offset);
1124        buf.put_u64_le(num_bytes);
1125        debug_assert_eq!(buf.len(), Self::REGULAR_SIZE);
1126        buf
1127    }
1128
1129    /// Serialize an inline `EXTENT_DATA` item: 21-byte header followed by
1130    /// `data` bytes (raw or already-compressed/framed payload).
1131    ///
1132    /// `ram_bytes` is the uncompressed file size covered by this inline
1133    /// extent. `compression` indicates whether `data` is compressed.
1134    #[must_use]
1135    pub fn to_bytes_inline(
1136        generation: u64,
1137        ram_bytes: u64,
1138        compression: CompressionType,
1139        data: &[u8],
1140    ) -> Vec<u8> {
1141        let mut buf = Vec::with_capacity(Self::HEADER_SIZE + data.len());
1142        buf.put_u64_le(generation);
1143        buf.put_u64_le(ram_bytes);
1144        buf.put_u8(compression.to_raw());
1145        buf.put_u8(0); // encryption
1146        buf.put_u16_le(0); // other_encoding
1147        buf.put_u8(FileExtentType::Inline.to_raw());
1148        debug_assert_eq!(buf.len(), Self::HEADER_SIZE);
1149        buf.extend_from_slice(data);
1150        buf
1151    }
1152}
1153
1154/// Compute the hash used for `EXTENT_DATA_REF` keys, matching the kernel's
1155/// `hash_extent_data_ref()`. Uses two independent CRC32C computations
1156/// combined into a single u64.
1157#[must_use]
1158pub fn extent_data_ref_hash(root: u64, objectid: u64, offset: u64) -> u64 {
1159    let high_crc = raw_crc32c(!0u32, &root.to_le_bytes());
1160    let low_crc = raw_crc32c(!0u32, &objectid.to_le_bytes());
1161    let low_crc = raw_crc32c(low_crc, &offset.to_le_bytes());
1162    (u64::from(high_crc) << 31) ^ u64::from(low_crc)
1163}
1164
1165/// Inline reference types found inside `EXTENT_ITEM`/`METADATA_ITEM`.
1166#[derive(Debug, Clone)]
1167pub enum InlineRef {
1168    /// Direct backref from a metadata extent to the tree that owns it.
1169    /// The `root` field is the tree objectid (e.g. 5 for `FS_TREE`).
1170    TreeBlockBackref {
1171        /// Raw offset value from the inline ref header (equals `root`).
1172        ref_offset: u64,
1173        /// Tree objectid that owns this metadata block.
1174        root: u64,
1175    },
1176    /// Shared backref from a metadata extent via a parent tree block.
1177    /// Used when a tree block is shared between snapshots.
1178    SharedBlockBackref {
1179        /// Raw offset value from the inline ref header (equals `parent`).
1180        ref_offset: u64,
1181        /// Logical bytenr of the parent tree block that references this extent.
1182        parent: u64,
1183    },
1184    /// Backref from a data extent to a specific file inode. Stores the
1185    /// owning root, inode number, file offset, and reference count.
1186    ExtentDataBackref {
1187        /// Computed hash of (root, objectid, offset) for display.
1188        ref_offset: u64,
1189        /// Tree objectid that owns the referencing inode.
1190        root: u64,
1191        /// Inode number that references this data extent.
1192        objectid: u64,
1193        /// File byte offset where this extent is referenced.
1194        offset: u64,
1195        /// Number of references from this (root, objectid, offset) triple.
1196        count: u32,
1197    },
1198    /// Shared backref from a data extent via a parent tree block.
1199    /// Used when data extents are shared between snapshots.
1200    SharedDataBackref {
1201        /// Raw offset value from the inline ref header (equals `parent`).
1202        ref_offset: u64,
1203        /// Logical bytenr of the parent tree block that references this extent.
1204        parent: u64,
1205        /// Number of references from the parent block.
1206        count: u32,
1207    },
1208    /// Simple ownership reference for an extent (`simple_quota` feature).
1209    /// Records which tree root owns the extent.
1210    ExtentOwnerRef {
1211        /// Raw offset value from the inline ref header (equals `root`).
1212        ref_offset: u64,
1213        /// Tree objectid that owns this extent.
1214        root: u64,
1215    },
1216}
1217
1218impl InlineRef {
1219    /// The raw type byte for this inline ref.
1220    #[must_use]
1221    #[allow(clippy::cast_possible_truncation)]
1222    pub fn raw_type(&self) -> u8 {
1223        match self {
1224            Self::TreeBlockBackref { .. } => {
1225                raw::BTRFS_TREE_BLOCK_REF_KEY as u8
1226            }
1227            Self::SharedBlockBackref { .. } => {
1228                raw::BTRFS_SHARED_BLOCK_REF_KEY as u8
1229            }
1230            Self::ExtentDataBackref { .. } => {
1231                raw::BTRFS_EXTENT_DATA_REF_KEY as u8
1232            }
1233            Self::SharedDataBackref { .. } => {
1234                raw::BTRFS_SHARED_DATA_REF_KEY as u8
1235            }
1236            Self::ExtentOwnerRef { .. } => {
1237                raw::BTRFS_EXTENT_OWNER_REF_KEY as u8
1238            }
1239        }
1240    }
1241
1242    /// The offset value from the inline ref header.
1243    #[must_use]
1244    pub fn raw_offset(&self) -> u64 {
1245        match self {
1246            Self::TreeBlockBackref { ref_offset, .. }
1247            | Self::SharedBlockBackref { ref_offset, .. }
1248            | Self::ExtentDataBackref { ref_offset, .. }
1249            | Self::SharedDataBackref { ref_offset, .. }
1250            | Self::ExtentOwnerRef { ref_offset, .. } => *ref_offset,
1251        }
1252    }
1253}
1254
1255/// On-disk size in bytes of an inline backref record (including its
1256/// 1-byte type tag) for a given inline ref type byte.
1257///
1258/// Returns `None` if `type_byte` is not a recognized inline ref type.
1259///
1260/// - `TREE_BLOCK_REF`/`EXTENT_OWNER_REF`: 1 (tag) + 8 (root) = 9
1261/// - `SHARED_BLOCK_REF`: 1 (tag) + 8 (parent) = 9
1262/// - `EXTENT_DATA_REF`: 1 (tag) + 28 (`btrfs_extent_data_ref`) = 29
1263/// - `SHARED_DATA_REF`: 1 (tag) + 8 (parent) + 4 (count) = 13
1264#[must_use]
1265#[allow(clippy::cast_possible_truncation)]
1266pub fn inline_ref_size(type_byte: u8) -> Option<usize> {
1267    match u32::from(type_byte) {
1268        raw::BTRFS_TREE_BLOCK_REF_KEY | raw::BTRFS_EXTENT_OWNER_REF_KEY => {
1269            Some(9)
1270        }
1271        raw::BTRFS_SHARED_BLOCK_REF_KEY => Some(9),
1272        raw::BTRFS_EXTENT_DATA_REF_KEY => Some(29),
1273        raw::BTRFS_SHARED_DATA_REF_KEY => Some(13),
1274        _ => None,
1275    }
1276}
1277
1278bitflags::bitflags! {
1279    /// Extent item flags stored in `btrfs_extent_item::flags`.
1280    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1281    pub struct ExtentFlags: u64 {
1282        const DATA         = raw::BTRFS_EXTENT_FLAG_DATA as u64;
1283        const TREE_BLOCK   = raw::BTRFS_EXTENT_FLAG_TREE_BLOCK as u64;
1284        const FULL_BACKREF = raw::BTRFS_BLOCK_FLAG_FULL_BACKREF as u64;
1285        // Preserve unknown bits from the on-disk value.
1286        const _ = !0;
1287    }
1288}
1289
1290impl fmt::Display for ExtentFlags {
1291    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1292        let mut parts = Vec::new();
1293        if self.contains(Self::DATA) {
1294            parts.push("DATA");
1295        }
1296        if self.contains(Self::TREE_BLOCK) {
1297            parts.push("TREE_BLOCK");
1298        }
1299        if self.contains(Self::FULL_BACKREF) {
1300            parts.push("FULL_BACKREF");
1301        }
1302        write!(f, "{}", parts.join("|"))
1303    }
1304}
1305
1306/// Extent allocation record from the extent tree.
1307///
1308/// Tracks reference counts, ownership, and backreferences for a contiguous
1309/// range of allocated disk space. Used for both data extents (`EXTENT_ITEM`)
1310/// and metadata blocks (`METADATA_ITEM` with skinny metadata).
1311#[derive(Debug, Clone)]
1312pub struct ExtentItem {
1313    /// Number of references to this extent.
1314    pub refs: u64,
1315    /// Generation when this extent was allocated.
1316    pub generation: u64,
1317    /// Whether this extent holds data or a tree block.
1318    pub flags: ExtentFlags,
1319    /// For non-skinny tree block extents: the first key in the block.
1320    pub tree_block_key: Option<DiskKey>,
1321    /// For non-skinny tree block extents: the block's tree level.
1322    pub tree_block_level: Option<u8>,
1323    /// For skinny metadata items: the tree level (from the key offset).
1324    pub skinny_level: Option<u64>,
1325    /// Inline backreferences packed after the extent header.
1326    pub inline_refs: Vec<InlineRef>,
1327}
1328
1329impl ExtentItem {
1330    /// Returns true if this extent holds file data.
1331    #[must_use]
1332    pub fn is_data(&self) -> bool {
1333        self.flags.contains(ExtentFlags::DATA)
1334    }
1335
1336    /// Returns true if this extent holds a metadata tree block.
1337    #[must_use]
1338    pub fn is_tree_block(&self) -> bool {
1339        self.flags.contains(ExtentFlags::TREE_BLOCK)
1340    }
1341
1342    /// Parse an extent item from a raw byte buffer, using the item key to
1343    /// determine whether this is a skinny metadata item or a full extent item.
1344    #[must_use]
1345    pub fn parse(data: &[u8], key: &DiskKey) -> Option<Self> {
1346        use crate::tree::KeyType;
1347
1348        if data.len() < mem::size_of::<raw::btrfs_extent_item>() {
1349            return None;
1350        }
1351        let mut buf = data;
1352        let refs = buf.get_u64_le();
1353        let generation = buf.get_u64_le();
1354        let flags = ExtentFlags::from_bits_truncate(buf.get_u64_le());
1355
1356        let is_tree_block = flags.contains(ExtentFlags::TREE_BLOCK);
1357
1358        let mut tree_block_key = None;
1359        let mut tree_block_level = None;
1360        if is_tree_block
1361            && key.key_type == KeyType::ExtentItem
1362            && buf.remaining() > 17
1363        {
1364            tree_block_key = Some(DiskKey::parse(buf, 0));
1365            buf.advance(17); // skip DiskKey
1366            tree_block_level = Some(buf.get_u8());
1367        }
1368
1369        let skinny_level =
1370            if key.key_type == KeyType::MetadataItem && is_tree_block {
1371                Some(key.offset)
1372            } else {
1373                None
1374            };
1375
1376        let mut inline_refs = Vec::new();
1377        while buf.remaining() > 0 {
1378            let ref_type = buf.get_u8();
1379            let ref_offset = if buf.remaining() >= 8 {
1380                buf.get_u64_le()
1381            } else {
1382                0
1383            };
1384
1385            match u32::from(ref_type) {
1386                raw::BTRFS_TREE_BLOCK_REF_KEY => {
1387                    inline_refs.push(InlineRef::TreeBlockBackref {
1388                        ref_offset,
1389                        root: ref_offset,
1390                    });
1391                }
1392                raw::BTRFS_SHARED_BLOCK_REF_KEY => {
1393                    inline_refs.push(InlineRef::SharedBlockBackref {
1394                        ref_offset,
1395                        parent: ref_offset,
1396                    });
1397                }
1398                raw::BTRFS_EXTENT_DATA_REF_KEY => {
1399                    // EXTENT_DATA_REF has no 8-byte offset field; the struct
1400                    // starts directly after the type byte. The 8 bytes we
1401                    // speculatively consumed are actually the first field
1402                    // (root) of the struct, so reinterpret them.
1403                    let root = ref_offset; // already read as u64_le
1404                    if buf.remaining() >= 20 {
1405                        let oid = buf.get_u64_le();
1406                        let off = buf.get_u64_le();
1407                        let count = buf.get_u32_le();
1408                        // The C tool prints a CRC hash for the display offset;
1409                        // compute it the same way: hash(root, objectid, offset).
1410                        let hash = extent_data_ref_hash(root, oid, off);
1411                        inline_refs.push(InlineRef::ExtentDataBackref {
1412                            ref_offset: hash,
1413                            root,
1414                            objectid: oid,
1415                            offset: off,
1416                            count,
1417                        });
1418                    } else {
1419                        break;
1420                    }
1421                }
1422                raw::BTRFS_SHARED_DATA_REF_KEY => {
1423                    if buf.remaining() >= 4 {
1424                        let count = buf.get_u32_le();
1425                        inline_refs.push(InlineRef::SharedDataBackref {
1426                            ref_offset,
1427                            parent: ref_offset,
1428                            count,
1429                        });
1430                    } else {
1431                        break;
1432                    }
1433                }
1434                raw::BTRFS_EXTENT_OWNER_REF_KEY => {
1435                    inline_refs.push(InlineRef::ExtentOwnerRef {
1436                        ref_offset,
1437                        root: ref_offset,
1438                    });
1439                }
1440                _ => break,
1441            }
1442        }
1443
1444        Some(Self {
1445            refs,
1446            generation,
1447            flags,
1448            tree_block_key,
1449            tree_block_level,
1450            skinny_level,
1451            inline_refs,
1452        })
1453    }
1454
1455    /// Size of a skinny metadata extent item with one `TREE_BLOCK_REF`.
1456    ///
1457    /// Layout: extent header (24) + inline ref type (1) + offset (8) = 33.
1458    pub const SKINNY_SIZE: usize = 33;
1459
1460    /// Size of a non-skinny metadata extent item with `tree_block_info`
1461    /// and one `TREE_BLOCK_REF`.
1462    ///
1463    /// Layout: extent header (24) + `tree_block_info` (18) + inline ref (9) = 51.
1464    pub const NON_SKINNY_SIZE: usize = 51;
1465
1466    /// Serialize a skinny metadata extent item (`METADATA_ITEM`) with a
1467    /// single `TREE_BLOCK_REF` inline backref (33 bytes).
1468    #[must_use]
1469    pub fn to_bytes_skinny(
1470        refs: u64,
1471        generation: u64,
1472        root_id: u64,
1473    ) -> Vec<u8> {
1474        let mut buf = Vec::with_capacity(Self::SKINNY_SIZE);
1475        buf.put_u64_le(refs);
1476        buf.put_u64_le(generation);
1477        buf.put_u64_le(ExtentFlags::TREE_BLOCK.bits());
1478        buf.put_u8(KeyType::TreeBlockRef.to_raw());
1479        buf.put_u64_le(root_id);
1480        debug_assert_eq!(buf.len(), Self::SKINNY_SIZE);
1481        buf
1482    }
1483
1484    /// Serialize a non-skinny metadata extent item (`EXTENT_ITEM`) with
1485    /// `tree_block_info` and a `TREE_BLOCK_REF` inline backref (51 bytes).
1486    #[must_use]
1487    pub fn to_bytes_non_skinny(
1488        refs: u64,
1489        generation: u64,
1490        root_id: u64,
1491        first_key: &DiskKey,
1492        level: u8,
1493    ) -> Vec<u8> {
1494        let mut buf = Vec::with_capacity(Self::NON_SKINNY_SIZE);
1495        buf.put_u64_le(refs);
1496        buf.put_u64_le(generation);
1497        buf.put_u64_le(ExtentFlags::TREE_BLOCK.bits());
1498        // tree_block_info: first key + level
1499        let key_off = buf.len();
1500        buf.extend_from_slice(&[0u8; 17]);
1501        write_disk_key(&mut buf[key_off..], 0, first_key);
1502        buf.put_u8(level);
1503        buf.put_u8(KeyType::TreeBlockRef.to_raw());
1504        buf.put_u64_le(root_id);
1505        debug_assert_eq!(buf.len(), Self::NON_SKINNY_SIZE);
1506        buf
1507    }
1508
1509    /// Size of a data extent item with one inline `EXTENT_DATA_REF`.
1510    ///
1511    /// Layout: extent header (24) + inline ref type (1) + data ref (28) = 53.
1512    pub const DATA_INLINE_SIZE: usize = 53;
1513
1514    /// Serialize a data extent item (`EXTENT_ITEM`) with a single inline
1515    /// `EXTENT_DATA_REF` backref (53 bytes).
1516    #[must_use]
1517    pub fn to_bytes_data(
1518        refs: u64,
1519        generation: u64,
1520        root: u64,
1521        objectid: u64,
1522        offset: u64,
1523        count: u32,
1524    ) -> Vec<u8> {
1525        let mut buf = Vec::with_capacity(Self::DATA_INLINE_SIZE);
1526        buf.put_u64_le(refs); // extent header: refs
1527        buf.put_u64_le(generation); // extent header: generation
1528        buf.put_u64_le(ExtentFlags::DATA.bits()); // flags
1529        buf.put_u8(KeyType::ExtentDataRef.to_raw()); // inline type tag
1530        buf.put_u64_le(root); // btrfs_extent_data_ref.root
1531        buf.put_u64_le(objectid); // btrfs_extent_data_ref.objectid
1532        buf.put_u64_le(offset); // btrfs_extent_data_ref.offset
1533        buf.put_u32_le(count); // btrfs_extent_data_ref.count
1534        debug_assert_eq!(buf.len(), Self::DATA_INLINE_SIZE);
1535        buf
1536    }
1537}
1538
1539/// Standalone data extent backreference (non-inline).
1540///
1541/// Key: `(extent_bytenr, EXTENT_DATA_REF, hash)`. Records which file inode
1542/// references a given data extent.
1543#[derive(Debug, Clone)]
1544pub struct ExtentDataRef {
1545    /// Root tree objectid that owns the referencing inode.
1546    pub root: u64,
1547    /// Inode number that references this extent.
1548    pub objectid: u64,
1549    /// File offset where this extent is referenced.
1550    pub offset: u64,
1551    /// Number of references from this (root, objectid, offset) triple.
1552    pub count: u32,
1553}
1554
1555impl ExtentDataRef {
1556    /// Parse a standalone extent data ref from a raw byte buffer.
1557    #[must_use]
1558    pub fn parse(data: &[u8]) -> Option<Self> {
1559        if data.len() < mem::size_of::<raw::btrfs_extent_data_ref>() {
1560            return None;
1561        }
1562        let mut buf = data;
1563        Some(Self {
1564            root: buf.get_u64_le(),
1565            objectid: buf.get_u64_le(),
1566            offset: buf.get_u64_le(),
1567            count: buf.get_u32_le(),
1568        })
1569    }
1570}
1571
1572/// Shared data extent backreference (for snapshot-shared extents).
1573///
1574/// Key: `(extent_bytenr, SHARED_DATA_REF, parent_bytenr)`.
1575#[derive(Debug, Clone)]
1576pub struct SharedDataRef {
1577    /// Number of references from the parent block.
1578    pub count: u32,
1579}
1580
1581impl SharedDataRef {
1582    /// Parse a shared data ref from a raw byte buffer.
1583    #[must_use]
1584    pub fn parse(data: &[u8]) -> Option<Self> {
1585        if data.len() < 4 {
1586            return None;
1587        }
1588        let mut buf = data;
1589        Some(Self {
1590            count: buf.get_u32_le(),
1591        })
1592    }
1593}
1594
1595/// Block group descriptor, tracking space usage for a chunk.
1596///
1597/// Key: `(logical_offset, BLOCK_GROUP_ITEM, length)`.
1598#[derive(Debug, Clone)]
1599pub struct BlockGroupItem {
1600    /// Bytes used within this block group.
1601    pub used: u64,
1602    /// Objectid of the chunk that backs this block group.
1603    pub chunk_objectid: u64,
1604    /// Type and RAID profile flags (DATA, METADATA, SYSTEM, DUP, RAID*, etc.).
1605    pub flags: BlockGroupFlags,
1606}
1607
1608impl BlockGroupItem {
1609    /// Parse a block group item from a raw byte buffer.
1610    #[must_use]
1611    pub fn parse(data: &[u8]) -> Option<Self> {
1612        if data.len() < mem::size_of::<raw::btrfs_block_group_item>() {
1613            return None;
1614        }
1615        let mut buf = data;
1616        Some(Self {
1617            used: buf.get_u64_le(),
1618            chunk_objectid: buf.get_u64_le(),
1619            flags: BlockGroupFlags::from_bits_truncate(buf.get_u64_le()),
1620        })
1621    }
1622
1623    /// Serialize to the 24-byte on-disk representation.
1624    #[must_use]
1625    pub fn to_bytes(&self) -> Vec<u8> {
1626        let mut buf = Vec::with_capacity(24);
1627        buf.put_u64_le(self.used);
1628        buf.put_u64_le(self.chunk_objectid);
1629        buf.put_u64_le(self.flags.bits());
1630        buf
1631    }
1632}
1633
1634/// Chunk item mapping logical addresses to physical device locations.
1635///
1636/// Key: `(FIRST_CHUNK_TREE, CHUNK_ITEM, logical_offset)`. Each chunk maps a
1637/// contiguous range of logical addresses to one or more device stripes.
1638#[derive(Debug, Clone)]
1639pub struct ChunkItem {
1640    /// Length of this chunk in bytes.
1641    pub length: u64,
1642    /// Owner of this chunk (always `BTRFS_FIRST_CHUNK_TREE_OBJECTID`).
1643    pub owner: u64,
1644    /// Stripe length for striped profiles.
1645    pub stripe_len: u64,
1646    /// Type and RAID profile flags.
1647    pub chunk_type: BlockGroupFlags,
1648    /// I/O alignment requirement.
1649    pub io_align: u32,
1650    /// I/O width requirement.
1651    pub io_width: u32,
1652    /// Sector size of the underlying devices.
1653    pub sector_size: u32,
1654    /// Number of stripes (device copies) for this chunk.
1655    pub num_stripes: u16,
1656    /// Number of sub-stripes (for RAID10).
1657    pub sub_stripes: u16,
1658    /// Physical device locations for each stripe.
1659    pub stripes: Vec<ChunkStripe>,
1660}
1661
1662/// A single physical stripe within a chunk.
1663#[derive(Debug, Clone)]
1664pub struct ChunkStripe {
1665    /// Device ID where this stripe lives.
1666    pub devid: u64,
1667    /// Physical byte offset on the device.
1668    pub offset: u64,
1669    /// UUID of the device.
1670    pub dev_uuid: Uuid,
1671}
1672
1673impl ChunkItem {
1674    /// Parse a chunk item (with stripes) from a raw byte buffer.
1675    #[must_use]
1676    pub fn parse(data: &[u8]) -> Option<Self> {
1677        let chunk_base_size = mem::offset_of!(raw::btrfs_chunk, stripe);
1678        if data.len() < chunk_base_size {
1679            return None;
1680        }
1681        let mut buf = data;
1682        let length = buf.get_u64_le();
1683        let owner = buf.get_u64_le();
1684        let stripe_len = buf.get_u64_le();
1685        let chunk_type = BlockGroupFlags::from_bits_truncate(buf.get_u64_le());
1686        let io_align = buf.get_u32_le();
1687        let io_width = buf.get_u32_le();
1688        let sector_size = buf.get_u32_le();
1689        let num_stripes = buf.get_u16_le();
1690        let sub_stripes = buf.get_u16_le();
1691        let stripe_size = mem::size_of::<raw::btrfs_stripe>();
1692        let mut stripes = Vec::with_capacity(num_stripes as usize);
1693        let mut sbuf = &data[chunk_base_size..];
1694        for i in 0..num_stripes as usize {
1695            let s_off = chunk_base_size + i * stripe_size;
1696            if s_off + stripe_size > data.len() {
1697                break;
1698            }
1699            let devid = sbuf.get_u64_le();
1700            let offset = sbuf.get_u64_le();
1701            let dev_uuid = get_uuid(&mut sbuf);
1702            stripes.push(ChunkStripe {
1703                devid,
1704                offset,
1705                dev_uuid,
1706            });
1707        }
1708        Some(Self {
1709            length,
1710            owner,
1711            stripe_len,
1712            chunk_type,
1713            io_align,
1714            io_width,
1715            sector_size,
1716            num_stripes,
1717            sub_stripes,
1718            stripes,
1719        })
1720    }
1721}
1722
1723impl ChunkItem {
1724    /// Convert to a `ChunkMapping` for use with `chunk_item_bytes` and
1725    /// `ChunkTreeCache`.
1726    #[must_use]
1727    pub fn to_mapping(&self, logical: u64) -> crate::chunk::ChunkMapping {
1728        crate::chunk::ChunkMapping {
1729            logical,
1730            length: self.length,
1731            stripe_len: self.stripe_len,
1732            chunk_type: self.chunk_type.bits(),
1733            num_stripes: self.num_stripes,
1734            sub_stripes: self.sub_stripes,
1735            stripes: self
1736                .stripes
1737                .iter()
1738                .map(|s| crate::chunk::Stripe {
1739                    devid: s.devid,
1740                    offset: s.offset,
1741                    dev_uuid: s.dev_uuid,
1742                })
1743                .collect(),
1744        }
1745    }
1746}
1747
1748/// Device item describing a single device in the filesystem.
1749///
1750/// Stored in the device tree and embedded in the superblock. Contains the
1751/// device's size, usage, and identifying UUIDs.
1752#[derive(Debug, Clone)]
1753pub struct DeviceItem {
1754    /// Unique device ID within this filesystem.
1755    pub devid: u64,
1756    /// Total size of the device in bytes.
1757    pub total_bytes: u64,
1758    /// Bytes allocated on this device.
1759    pub bytes_used: u64,
1760    /// I/O alignment requirement.
1761    pub io_align: u32,
1762    /// I/O width requirement.
1763    pub io_width: u32,
1764    /// Sector size of this device.
1765    pub sector_size: u32,
1766    /// Device type (reserved, always 0).
1767    pub dev_type: u64,
1768    /// Generation when this device was last updated.
1769    pub generation: u64,
1770    /// Start offset for allocations on this device.
1771    pub start_offset: u64,
1772    /// Device group (reserved, always 0).
1773    pub dev_group: u32,
1774    /// Seek speed hint (0 = not set).
1775    pub seek_speed: u8,
1776    /// Bandwidth hint (0 = not set).
1777    pub bandwidth: u8,
1778    /// UUID of this device.
1779    pub uuid: Uuid,
1780    /// Filesystem UUID that this device belongs to.
1781    pub fsid: Uuid,
1782}
1783
1784impl DeviceItem {
1785    /// Serialize this device item to a `BufMut` in on-disk little-endian format.
1786    pub fn write_bytes(&self, buf: &mut impl BufMut) {
1787        buf.put_u64_le(self.devid);
1788        buf.put_u64_le(self.total_bytes);
1789        buf.put_u64_le(self.bytes_used);
1790        buf.put_u32_le(self.io_align);
1791        buf.put_u32_le(self.io_width);
1792        buf.put_u32_le(self.sector_size);
1793        buf.put_u64_le(self.dev_type);
1794        buf.put_u64_le(self.generation);
1795        buf.put_u64_le(self.start_offset);
1796        buf.put_u32_le(self.dev_group);
1797        buf.put_u8(self.seek_speed);
1798        buf.put_u8(self.bandwidth);
1799        buf.put_slice(self.uuid.as_bytes());
1800        buf.put_slice(self.fsid.as_bytes());
1801    }
1802
1803    /// Parse a device item from a raw byte buffer.
1804    #[must_use]
1805    pub fn parse(data: &[u8]) -> Option<Self> {
1806        if data.len() < mem::size_of::<raw::btrfs_dev_item>() {
1807            return None;
1808        }
1809        let mut buf = data;
1810        let devid = buf.get_u64_le();
1811        let total_bytes = buf.get_u64_le();
1812        let bytes_used = buf.get_u64_le();
1813        let io_align = buf.get_u32_le();
1814        let io_width = buf.get_u32_le();
1815        let sector_size = buf.get_u32_le();
1816        let dev_type = buf.get_u64_le();
1817        let generation = buf.get_u64_le();
1818        let start_offset = buf.get_u64_le();
1819        let dev_group = buf.get_u32_le();
1820        let seek_speed = buf.get_u8();
1821        let bandwidth = buf.get_u8();
1822        let uuid = get_uuid(&mut buf);
1823        let fsid = get_uuid(&mut buf);
1824        Some(Self {
1825            devid,
1826            total_bytes,
1827            bytes_used,
1828            io_align,
1829            io_width,
1830            sector_size,
1831            dev_type,
1832            generation,
1833            start_offset,
1834            dev_group,
1835            seek_speed,
1836            bandwidth,
1837            uuid,
1838            fsid,
1839        })
1840    }
1841}
1842
1843/// Device extent, mapping a physical range on a device to a chunk.
1844///
1845/// Key: `(devid, DEV_EXTENT, physical_offset)`. The inverse of a chunk
1846/// stripe: given a device and physical offset, find the owning chunk.
1847#[derive(Debug, Clone)]
1848pub struct DeviceExtent {
1849    /// Objectid of the chunk tree (always 3).
1850    pub chunk_tree: u64,
1851    /// Objectid of the owning chunk.
1852    pub chunk_objectid: u64,
1853    /// Logical offset of the owning chunk.
1854    pub chunk_offset: u64,
1855    /// Length of this device extent in bytes.
1856    pub length: u64,
1857    /// UUID of the chunk tree.
1858    pub chunk_tree_uuid: Uuid,
1859}
1860
1861impl DeviceExtent {
1862    /// Parse a device extent from a raw byte buffer.
1863    #[must_use]
1864    pub fn parse(data: &[u8]) -> Option<Self> {
1865        if data.len() < mem::size_of::<raw::btrfs_dev_extent>() {
1866            return None;
1867        }
1868        let mut buf = data;
1869        let chunk_tree = buf.get_u64_le();
1870        let chunk_objectid = buf.get_u64_le();
1871        let chunk_offset = buf.get_u64_le();
1872        let length = buf.get_u64_le();
1873        let chunk_tree_uuid = get_uuid(&mut buf);
1874        Some(Self {
1875            chunk_tree,
1876            chunk_objectid,
1877            chunk_offset,
1878            length,
1879            chunk_tree_uuid,
1880        })
1881    }
1882}
1883
1884bitflags::bitflags! {
1885    /// Free space info flags stored in `btrfs_free_space_info::flags`.
1886    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1887    pub struct FreeSpaceInfoFlags: u32 {
1888        const USING_BITMAPS = raw::BTRFS_FREE_SPACE_USING_BITMAPS;
1889        // Preserve unknown bits from the on-disk value.
1890        const _ = !0;
1891    }
1892}
1893
1894impl fmt::Display for FreeSpaceInfoFlags {
1895    // The C reference prints this field as an unsigned decimal integer (%u).
1896    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1897        write!(f, "{}", self.bits())
1898    }
1899}
1900
1901/// Free space info for a block group in the free space tree.
1902///
1903/// Key: `(block_group_offset, FREE_SPACE_INFO, block_group_length)`.
1904#[derive(Debug, Clone)]
1905pub struct FreeSpaceInfo {
1906    /// Number of free extents (or bitmap entries) in this block group.
1907    pub extent_count: u32,
1908    /// Flags indicating whether this block group uses bitmaps.
1909    pub flags: FreeSpaceInfoFlags,
1910}
1911
1912impl FreeSpaceInfo {
1913    /// Parse a free space info item from a raw byte buffer.
1914    #[must_use]
1915    pub fn parse(data: &[u8]) -> Option<Self> {
1916        if data.len() < 8 {
1917            return None;
1918        }
1919        let mut buf = data;
1920        Some(Self {
1921            extent_count: buf.get_u32_le(),
1922            flags: FreeSpaceInfoFlags::from_bits_truncate(buf.get_u32_le()),
1923        })
1924    }
1925}
1926
1927/// Quota group status, stored in the quota tree.
1928///
1929/// Key: `(0, QGROUP_STATUS, 0)`. Tracks the overall state of quota accounting.
1930#[derive(Debug, Clone)]
1931pub struct QgroupStatus {
1932    /// Qgroup on-disk format version.
1933    pub version: u64,
1934    /// Generation when quotas were last consistent.
1935    pub generation: u64,
1936    /// Status flags (e.g. rescan in progress).
1937    pub flags: u64,
1938    /// Progress objectid for an in-progress rescan.
1939    pub scan: u64,
1940    /// Generation when quotas were enabled (kernel 6.8+, absent on older formats).
1941    pub enable_gen: Option<u64>,
1942}
1943
1944impl QgroupStatus {
1945    /// Parse a qgroup status item from a raw byte buffer.
1946    #[must_use]
1947    pub fn parse(data: &[u8]) -> Option<Self> {
1948        if data.len() < 32 {
1949            return None;
1950        }
1951        let mut buf = data;
1952        let version = buf.get_u64_le();
1953        let generation = buf.get_u64_le();
1954        let flags = buf.get_u64_le();
1955        let scan = buf.get_u64_le();
1956        let enable_gen = if buf.remaining() >= 8 {
1957            Some(buf.get_u64_le())
1958        } else {
1959            None
1960        };
1961        Some(Self {
1962            version,
1963            generation,
1964            flags,
1965            scan,
1966            enable_gen,
1967        })
1968    }
1969}
1970
1971/// Quota group accounting info.
1972///
1973/// Key: `(level/subvolid, QGROUP_INFO, 0)`. Tracks how much space a qgroup
1974/// references and how much is exclusive to it.
1975#[derive(Debug, Clone)]
1976pub struct QgroupInfo {
1977    /// Generation when this info was last updated.
1978    pub generation: u64,
1979    /// Total bytes referenced by this qgroup (shared + exclusive).
1980    pub referenced: u64,
1981    /// Referenced bytes after compression.
1982    pub referenced_compressed: u64,
1983    /// Bytes used exclusively by this qgroup.
1984    pub exclusive: u64,
1985    /// Exclusive bytes after compression.
1986    pub exclusive_compressed: u64,
1987}
1988
1989impl QgroupInfo {
1990    /// Parse a qgroup info item from a raw byte buffer.
1991    #[must_use]
1992    pub fn parse(data: &[u8]) -> Option<Self> {
1993        if data.len() < mem::size_of::<raw::btrfs_qgroup_info_item>() {
1994            return None;
1995        }
1996        let mut buf = data;
1997        Some(Self {
1998            generation: buf.get_u64_le(),
1999            referenced: buf.get_u64_le(),
2000            referenced_compressed: buf.get_u64_le(),
2001            exclusive: buf.get_u64_le(),
2002            exclusive_compressed: buf.get_u64_le(),
2003        })
2004    }
2005}
2006
2007/// Quota group limits.
2008///
2009/// Key: `(level/subvolid, QGROUP_LIMIT, 0)`. Caps referenced and/or exclusive
2010/// space usage for a qgroup.
2011#[derive(Debug, Clone)]
2012pub struct QgroupLimit {
2013    /// Bitmask of which limits are active.
2014    pub flags: u64,
2015    /// Maximum referenced bytes (0 = unlimited).
2016    pub max_referenced: u64,
2017    /// Maximum exclusive bytes (0 = unlimited).
2018    pub max_exclusive: u64,
2019    /// Reserved referenced bytes.
2020    pub rsv_referenced: u64,
2021    /// Reserved exclusive bytes.
2022    pub rsv_exclusive: u64,
2023}
2024
2025impl QgroupLimit {
2026    /// Parse a qgroup limit item from a raw byte buffer.
2027    #[must_use]
2028    pub fn parse(data: &[u8]) -> Option<Self> {
2029        if data.len() < mem::size_of::<raw::btrfs_qgroup_limit_item>() {
2030            return None;
2031        }
2032        let mut buf = data;
2033        Some(Self {
2034            flags: buf.get_u64_le(),
2035            max_referenced: buf.get_u64_le(),
2036            max_exclusive: buf.get_u64_le(),
2037            rsv_referenced: buf.get_u64_le(),
2038            rsv_exclusive: buf.get_u64_le(),
2039        })
2040    }
2041}
2042
2043/// Per-device I/O error statistics.
2044///
2045/// Key: `(DEV_STATS, PERSISTENT_ITEM, devid)`. Stored as an array of u64
2046/// counters for write errors, read errors, flush errors, corruption errors,
2047/// and generation mismatches.
2048#[derive(Debug, Clone)]
2049pub struct DeviceStats {
2050    /// Named counters: `(stat_name, count)`.
2051    pub values: Vec<(String, u64)>,
2052}
2053
2054impl DeviceStats {
2055    /// Parse device statistics from a raw byte buffer. Reads up to 5 u64
2056    /// counters (`write_errs`, `read_errs`, `flush_errs`, `corruption_errs`, generation).
2057    #[must_use]
2058    pub fn parse(data: &[u8]) -> Self {
2059        let stat_names = [
2060            "write_errs",
2061            "read_errs",
2062            "flush_errs",
2063            "corruption_errs",
2064            "generation",
2065        ];
2066        let mut buf = data;
2067        let mut values = Vec::new();
2068        for name in &stat_names {
2069            if buf.remaining() >= 8 {
2070                values.push((name.to_string(), buf.get_u64_le()));
2071            }
2072        }
2073        DeviceStats { values }
2074    }
2075}
2076
2077/// UUID tree entry mapping a subvolume UUID to its objectid(s).
2078///
2079/// Key: `(upper_half_of_uuid, UUID_KEY_SUBVOL, lower_half_of_uuid)`.
2080#[derive(Debug, Clone)]
2081pub struct UuidItem {
2082    /// Subvolume objectids associated with this UUID.
2083    pub subvol_ids: Vec<u64>,
2084}
2085
2086impl UuidItem {
2087    /// Parse a UUID tree item from a raw byte buffer.
2088    #[must_use]
2089    pub fn parse(data: &[u8]) -> Self {
2090        let mut buf = data;
2091        let mut subvol_ids = Vec::new();
2092        while buf.remaining() >= 8 {
2093            subvol_ids.push(buf.get_u64_le());
2094        }
2095        Self { subvol_ids }
2096    }
2097}
2098
2099/// Parsed item payload: the typed result of parsing a leaf item's raw data
2100/// based on its key type.
2101///
2102/// Returned by [`parse_item_payload`]. Each variant wraps the corresponding
2103/// item struct. `Unknown` holds the raw bytes for unrecognized key types.
2104pub enum ItemPayload {
2105    /// Inode metadata (POSIX attributes, timestamps, flags).
2106    InodeItem(InodeItem),
2107    /// One or more hard-link references packed in a single item.
2108    InodeRef(Vec<InodeRef>),
2109    /// One or more extended inode references.
2110    InodeExtref(Vec<InodeExtref>),
2111    /// One or more directory entries (also used for `DIR_INDEX` and `XATTR_ITEM`).
2112    DirItem(Vec<DirItem>),
2113    /// Directory log item with the logged range end offset.
2114    DirLogItem {
2115        /// End of the logged directory range.
2116        end: u64,
2117    },
2118    /// Orphan marker (no data payload).
2119    OrphanItem,
2120    /// Tree root descriptor (subvolume, snapshot, or internal tree).
2121    RootItem(RootItem),
2122    /// Root forward or back reference (`ROOT_REF` / `ROOT_BACKREF`).
2123    RootRef(RootRef),
2124    /// File extent descriptor.
2125    FileExtentItem(FileExtentItem),
2126    /// Raw extent checksum data.
2127    ExtentCsum {
2128        /// Raw checksum bytes (array of per-sector checksums).
2129        data: Vec<u8>,
2130    },
2131    /// Extent allocation record (`EXTENT_ITEM` or `METADATA_ITEM`).
2132    ExtentItem(ExtentItem),
2133    /// Standalone tree block backref (no data payload; the key offset is the root).
2134    TreeBlockRef,
2135    /// Standalone shared block backref (no data payload; the key offset is the parent bytenr).
2136    SharedBlockRef,
2137    /// Standalone data extent backref.
2138    ExtentDataRef(ExtentDataRef),
2139    /// Standalone shared data extent backref.
2140    SharedDataRef(SharedDataRef),
2141    /// Simple ownership reference for an extent.
2142    ExtentOwnerRef {
2143        /// Tree objectid that owns this extent.
2144        root: u64,
2145    },
2146    /// Block group descriptor.
2147    BlockGroupItem(BlockGroupItem),
2148    /// Free space info for a block group.
2149    FreeSpaceInfo(FreeSpaceInfo),
2150    /// Free space extent (no data payload; key encodes start and length).
2151    FreeSpaceExtent,
2152    /// Free space bitmap (data payload is the bitmap).
2153    FreeSpaceBitmap,
2154    /// Chunk item mapping logical to physical addresses.
2155    ChunkItem(ChunkItem),
2156    /// Device item describing a single device.
2157    DeviceItem(DeviceItem),
2158    /// Physical extent mapping on a device.
2159    DeviceExtent(DeviceExtent),
2160    /// Quota group status.
2161    QgroupStatus(QgroupStatus),
2162    /// Quota group accounting info.
2163    QgroupInfo(QgroupInfo),
2164    /// Quota group limits.
2165    QgroupLimit(QgroupLimit),
2166    /// Quota group relation (no data payload; parent/child encoded in key).
2167    QgroupRelation,
2168    /// Per-device I/O error statistics.
2169    DeviceStats(DeviceStats),
2170    /// Balance status item.
2171    BalanceItem {
2172        /// Balance flags from the first 8 bytes of the item data.
2173        flags: u64,
2174    },
2175    /// Device replace status.
2176    DeviceReplace(DeviceReplaceItem),
2177    /// UUID tree entry mapping a UUID to subvolume objectids.
2178    UuidItem(UuidItem),
2179    /// String item (typically the superblock label).
2180    StringItem(Vec<u8>),
2181    /// RAID stripe extent mapping.
2182    RaidStripe(RaidStripeItem),
2183    /// Unrecognized item type; raw data preserved.
2184    Unknown(Vec<u8>),
2185}
2186
2187/// Device replace status, persisted across reboots.
2188///
2189/// Key: `(DEV_REPLACE, PERSISTENT_ITEM, 0)`.
2190#[derive(Debug, Clone)]
2191pub struct DeviceReplaceItem {
2192    /// Device ID of the source device being replaced.
2193    pub src_devid: u64,
2194    /// Left cursor position (bytes processed from left).
2195    pub cursor_left: u64,
2196    /// Right cursor position.
2197    pub cursor_right: u64,
2198    /// Replace mode (continuous = 0 or legacy).
2199    pub replace_mode: u64,
2200    /// Current state (not started, started, suspended, etc.).
2201    pub replace_state: u64,
2202    /// Unix timestamp when the replace operation started.
2203    pub time_started: u64,
2204    /// Unix timestamp when the replace operation completed or was cancelled.
2205    pub time_stopped: u64,
2206    /// Number of write errors during replace.
2207    pub num_write_errors: u64,
2208    /// Number of uncorrectable read errors during replace.
2209    pub num_uncorrectable_read_errors: u64,
2210}
2211
2212impl DeviceReplaceItem {
2213    /// Parse a device replace item from a raw byte buffer.
2214    #[must_use]
2215    pub fn parse(data: &[u8]) -> Option<Self> {
2216        if data.len() < 80 {
2217            return None;
2218        }
2219        let mut buf = data;
2220        Some(Self {
2221            src_devid: buf.get_u64_le(),
2222            cursor_left: buf.get_u64_le(),
2223            cursor_right: buf.get_u64_le(),
2224            replace_mode: buf.get_u64_le(),
2225            replace_state: buf.get_u64_le(),
2226            time_started: buf.get_u64_le(),
2227            time_stopped: buf.get_u64_le(),
2228            num_write_errors: buf.get_u64_le(),
2229            num_uncorrectable_read_errors: buf.get_u64_le(),
2230        })
2231    }
2232}
2233
2234/// RAID stripe extent mapping (for the raid-stripe-tree feature).
2235///
2236/// Key: `(logical_offset, RAID_STRIPE, length)`.
2237#[derive(Debug, Clone)]
2238pub struct RaidStripeItem {
2239    /// RAID encoding type.
2240    pub encoding: u64,
2241    /// Per-device stripe entries.
2242    pub stripes: Vec<RaidStripeEntry>,
2243}
2244
2245/// A single device stripe within a RAID stripe item.
2246#[derive(Debug, Clone)]
2247pub struct RaidStripeEntry {
2248    /// Device ID for this stripe.
2249    pub devid: u64,
2250    /// Physical byte offset on the device.
2251    pub physical: u64,
2252}
2253
2254impl RaidStripeItem {
2255    /// Parse a RAID stripe item from a raw byte buffer.
2256    #[must_use]
2257    pub fn parse(data: &[u8]) -> Option<Self> {
2258        if data.len() < 8 {
2259            return None;
2260        }
2261        let mut buf = data;
2262        let encoding = buf.get_u64_le();
2263        let mut stripes = Vec::new();
2264        while buf.remaining() >= 16 {
2265            stripes.push(RaidStripeEntry {
2266                devid: buf.get_u64_le(),
2267                physical: buf.get_u64_le(),
2268            });
2269        }
2270        Some(Self { encoding, stripes })
2271    }
2272}
2273
2274/// Parse an item's raw data into a typed payload based on its key type.
2275#[must_use]
2276#[allow(clippy::too_many_lines)]
2277pub fn parse_item_payload(key: &DiskKey, data: &[u8]) -> ItemPayload {
2278    use crate::tree::KeyType;
2279
2280    match key.key_type {
2281        KeyType::InodeItem => match InodeItem::parse(data) {
2282            Some(v) => ItemPayload::InodeItem(v),
2283            None => ItemPayload::Unknown(data.to_vec()),
2284        },
2285        KeyType::InodeRef => ItemPayload::InodeRef(InodeRef::parse_all(data)),
2286        KeyType::InodeExtref => {
2287            ItemPayload::InodeExtref(InodeExtref::parse_all(data))
2288        }
2289        KeyType::DirItem | KeyType::DirIndex | KeyType::XattrItem => {
2290            ItemPayload::DirItem(DirItem::parse_all(data))
2291        }
2292        KeyType::DirLogItem | KeyType::DirLogIndex => {
2293            let end = if data.len() >= 8 {
2294                let mut buf = data;
2295                buf.get_u64_le()
2296            } else {
2297                0
2298            };
2299            ItemPayload::DirLogItem { end }
2300        }
2301        KeyType::OrphanItem => ItemPayload::OrphanItem,
2302        KeyType::RootItem => match RootItem::parse(data) {
2303            Some(v) => ItemPayload::RootItem(v),
2304            None => ItemPayload::Unknown(data.to_vec()),
2305        },
2306        KeyType::RootRef | KeyType::RootBackref => match RootRef::parse(data) {
2307            Some(v) => ItemPayload::RootRef(v),
2308            None => ItemPayload::Unknown(data.to_vec()),
2309        },
2310        KeyType::ExtentData => match FileExtentItem::parse(data) {
2311            Some(v) => ItemPayload::FileExtentItem(v),
2312            None => ItemPayload::Unknown(data.to_vec()),
2313        },
2314        KeyType::ExtentCsum => ItemPayload::ExtentCsum {
2315            data: data.to_vec(),
2316        },
2317        KeyType::ExtentItem | KeyType::MetadataItem => {
2318            match ExtentItem::parse(data, key) {
2319                Some(v) => ItemPayload::ExtentItem(v),
2320                None => ItemPayload::Unknown(data.to_vec()),
2321            }
2322        }
2323        KeyType::TreeBlockRef => ItemPayload::TreeBlockRef,
2324        KeyType::SharedBlockRef => ItemPayload::SharedBlockRef,
2325        KeyType::ExtentDataRef => match ExtentDataRef::parse(data) {
2326            Some(v) => ItemPayload::ExtentDataRef(v),
2327            None => ItemPayload::Unknown(data.to_vec()),
2328        },
2329        KeyType::SharedDataRef => match SharedDataRef::parse(data) {
2330            Some(v) => ItemPayload::SharedDataRef(v),
2331            None => ItemPayload::Unknown(data.to_vec()),
2332        },
2333        KeyType::ExtentOwnerRef => {
2334            if data.len() >= 8 {
2335                let mut buf = data;
2336                ItemPayload::ExtentOwnerRef {
2337                    root: buf.get_u64_le(),
2338                }
2339            } else {
2340                ItemPayload::Unknown(data.to_vec())
2341            }
2342        }
2343        KeyType::BlockGroupItem => match BlockGroupItem::parse(data) {
2344            Some(v) => ItemPayload::BlockGroupItem(v),
2345            None => ItemPayload::Unknown(data.to_vec()),
2346        },
2347        KeyType::FreeSpaceInfo => match FreeSpaceInfo::parse(data) {
2348            Some(v) => ItemPayload::FreeSpaceInfo(v),
2349            None => ItemPayload::Unknown(data.to_vec()),
2350        },
2351        KeyType::FreeSpaceExtent => ItemPayload::FreeSpaceExtent,
2352        KeyType::FreeSpaceBitmap => ItemPayload::FreeSpaceBitmap,
2353        KeyType::ChunkItem => match ChunkItem::parse(data) {
2354            Some(v) => ItemPayload::ChunkItem(v),
2355            None => ItemPayload::Unknown(data.to_vec()),
2356        },
2357        KeyType::DeviceItem => match DeviceItem::parse(data) {
2358            Some(v) => ItemPayload::DeviceItem(v),
2359            None => ItemPayload::Unknown(data.to_vec()),
2360        },
2361        KeyType::DeviceExtent => match DeviceExtent::parse(data) {
2362            Some(v) => ItemPayload::DeviceExtent(v),
2363            None => ItemPayload::Unknown(data.to_vec()),
2364        },
2365        KeyType::QgroupStatus => match QgroupStatus::parse(data) {
2366            Some(v) => ItemPayload::QgroupStatus(v),
2367            None => ItemPayload::Unknown(data.to_vec()),
2368        },
2369        KeyType::QgroupInfo => match QgroupInfo::parse(data) {
2370            Some(v) => ItemPayload::QgroupInfo(v),
2371            None => ItemPayload::Unknown(data.to_vec()),
2372        },
2373        KeyType::QgroupLimit => match QgroupLimit::parse(data) {
2374            Some(v) => ItemPayload::QgroupLimit(v),
2375            None => ItemPayload::Unknown(data.to_vec()),
2376        },
2377        KeyType::QgroupRelation => ItemPayload::QgroupRelation,
2378        KeyType::PersistentItem => {
2379            if key.objectid == u64::from(raw::BTRFS_DEV_STATS_OBJECTID) {
2380                ItemPayload::DeviceStats(DeviceStats::parse(data))
2381            } else {
2382                ItemPayload::Unknown(data.to_vec())
2383            }
2384        }
2385        KeyType::TemporaryItem => {
2386            if ObjectId::from_raw(key.objectid) == ObjectId::Balance
2387                && data.len() >= 8
2388            {
2389                ItemPayload::BalanceItem {
2390                    flags: {
2391                        let mut buf = data;
2392                        buf.get_u64_le()
2393                    },
2394                }
2395            } else {
2396                ItemPayload::Unknown(data.to_vec())
2397            }
2398        }
2399        KeyType::DeviceReplace => match DeviceReplaceItem::parse(data) {
2400            Some(v) => ItemPayload::DeviceReplace(v),
2401            None => ItemPayload::Unknown(data.to_vec()),
2402        },
2403        KeyType::UuidKeySubvol | KeyType::UuidKeyReceivedSubvol => {
2404            ItemPayload::UuidItem(UuidItem::parse(data))
2405        }
2406        KeyType::StringItem => ItemPayload::StringItem(data.to_vec()),
2407        KeyType::RaidStripe => match RaidStripeItem::parse(data) {
2408            Some(v) => ItemPayload::RaidStripe(v),
2409            None => ItemPayload::Unknown(data.to_vec()),
2410        },
2411        _ => ItemPayload::Unknown(data.to_vec()),
2412    }
2413}
2414
2415#[cfg(test)]
2416mod tests {
2417    use super::*;
2418
2419    // ── Enum round-trips ──────────────────────────────────────────────
2420
2421    #[test]
2422    fn compression_type_round_trip() {
2423        for v in 0..=3 {
2424            let ct = CompressionType::from_raw(v);
2425            assert_eq!(ct.to_raw(), v);
2426        }
2427        assert_eq!(CompressionType::from_raw(0), CompressionType::None);
2428        assert_eq!(CompressionType::from_raw(1), CompressionType::Zlib);
2429        assert_eq!(CompressionType::from_raw(2), CompressionType::Lzo);
2430        assert_eq!(CompressionType::from_raw(3), CompressionType::Zstd);
2431        assert_eq!(CompressionType::from_raw(99), CompressionType::Unknown(99));
2432        assert_eq!(CompressionType::Unknown(99).to_raw(), 99);
2433    }
2434
2435    #[test]
2436    fn compression_type_names() {
2437        assert_eq!(CompressionType::None.name(), "none");
2438        assert_eq!(CompressionType::Zlib.name(), "zlib");
2439        assert_eq!(CompressionType::Lzo.name(), "lzo");
2440        assert_eq!(CompressionType::Zstd.name(), "zstd");
2441        assert_eq!(CompressionType::Unknown(42).name(), "unknown");
2442    }
2443
2444    #[test]
2445    fn file_extent_type_round_trip() {
2446        assert_eq!(FileExtentType::from_raw(0), FileExtentType::Inline);
2447        assert_eq!(FileExtentType::from_raw(1), FileExtentType::Regular);
2448        assert_eq!(FileExtentType::from_raw(2), FileExtentType::Prealloc);
2449        assert_eq!(FileExtentType::from_raw(77), FileExtentType::Unknown(77));
2450        for v in 0..=2 {
2451            let ft = FileExtentType::from_raw(v);
2452            assert_eq!(ft.to_raw(), v);
2453        }
2454        assert_eq!(FileExtentType::Unknown(77).to_raw(), 77);
2455    }
2456
2457    #[test]
2458    fn file_extent_type_names() {
2459        assert_eq!(FileExtentType::Inline.name(), "inline");
2460        assert_eq!(FileExtentType::Regular.name(), "regular");
2461        assert_eq!(FileExtentType::Prealloc.name(), "prealloc");
2462        assert_eq!(FileExtentType::Unknown(5).name(), "unknown");
2463    }
2464
2465    #[test]
2466    fn file_type_from_raw_all_variants() {
2467        assert_eq!(FileType::from_raw(0), FileType::Unknown);
2468        assert_eq!(FileType::from_raw(1), FileType::RegFile);
2469        assert_eq!(FileType::from_raw(2), FileType::Dir);
2470        assert_eq!(FileType::from_raw(3), FileType::Chrdev);
2471        assert_eq!(FileType::from_raw(4), FileType::Blkdev);
2472        assert_eq!(FileType::from_raw(5), FileType::Fifo);
2473        assert_eq!(FileType::from_raw(6), FileType::Sock);
2474        assert_eq!(FileType::from_raw(7), FileType::Symlink);
2475        assert_eq!(FileType::from_raw(8), FileType::Xattr);
2476        assert_eq!(FileType::from_raw(99), FileType::Other(99));
2477    }
2478
2479    #[test]
2480    fn file_type_names() {
2481        assert_eq!(FileType::Unknown.name(), "UNKNOWN");
2482        assert_eq!(FileType::RegFile.name(), "FILE");
2483        assert_eq!(FileType::Dir.name(), "DIR");
2484        assert_eq!(FileType::Chrdev.name(), "CHRDEV");
2485        assert_eq!(FileType::Blkdev.name(), "BLKDEV");
2486        assert_eq!(FileType::Fifo.name(), "FIFO");
2487        assert_eq!(FileType::Sock.name(), "SOCK");
2488        assert_eq!(FileType::Symlink.name(), "SYMLINK");
2489        assert_eq!(FileType::Xattr.name(), "XATTR");
2490        assert_eq!(FileType::Other(200).name(), "UNKNOWN");
2491    }
2492
2493    // ── Simple struct parsers ─────────────────────────────────────────
2494
2495    #[test]
2496    fn block_group_item_parse() {
2497        let mut buf = Vec::new();
2498        buf.extend_from_slice(&1000u64.to_le_bytes()); // used
2499        buf.extend_from_slice(&256u64.to_le_bytes()); // chunk_objectid
2500        buf.extend_from_slice(
2501            &(raw::BTRFS_BLOCK_GROUP_DATA as u64).to_le_bytes(),
2502        );
2503        let item = BlockGroupItem::parse(&buf).unwrap();
2504        assert_eq!(item.used, 1000);
2505        assert_eq!(item.chunk_objectid, 256);
2506        assert_eq!(item.flags, BlockGroupFlags::DATA);
2507    }
2508
2509    #[test]
2510    fn block_group_item_too_short() {
2511        assert!(BlockGroupItem::parse(&[0; 23]).is_none());
2512    }
2513
2514    #[test]
2515    fn free_space_info_parse() {
2516        let mut buf = Vec::new();
2517        buf.extend_from_slice(&42u32.to_le_bytes());
2518        buf.extend_from_slice(&7u32.to_le_bytes());
2519        let info = FreeSpaceInfo::parse(&buf).unwrap();
2520        assert_eq!(info.extent_count, 42);
2521        assert_eq!(info.flags, FreeSpaceInfoFlags::from_bits_truncate(7));
2522    }
2523
2524    #[test]
2525    fn free_space_info_too_short() {
2526        assert!(FreeSpaceInfo::parse(&[0; 7]).is_none());
2527    }
2528
2529    #[test]
2530    fn dev_extent_parse() {
2531        let size = mem::size_of::<raw::btrfs_dev_extent>();
2532        let mut buf = vec![0u8; size];
2533        buf[0..8].copy_from_slice(&3u64.to_le_bytes()); // chunk_tree
2534        buf[8..16].copy_from_slice(&256u64.to_le_bytes()); // chunk_objectid
2535        buf[16..24].copy_from_slice(&0x10000u64.to_le_bytes()); // chunk_offset
2536        buf[24..32].copy_from_slice(&0x40000u64.to_le_bytes()); // length
2537        // chunk_tree_uuid at offset 32
2538        buf[32..48].copy_from_slice(&[0xAB; 16]);
2539        let de = DeviceExtent::parse(&buf).unwrap();
2540        assert_eq!(de.chunk_tree, 3);
2541        assert_eq!(de.chunk_objectid, 256);
2542        assert_eq!(de.chunk_offset, 0x10000);
2543        assert_eq!(de.length, 0x40000);
2544        assert_eq!(de.chunk_tree_uuid.as_bytes(), &[0xAB; 16]);
2545    }
2546
2547    #[test]
2548    fn dev_extent_too_short() {
2549        let size = mem::size_of::<raw::btrfs_dev_extent>();
2550        assert!(DeviceExtent::parse(&vec![0u8; size - 1]).is_none());
2551    }
2552
2553    #[test]
2554    fn extent_data_ref_parse() {
2555        let size = mem::size_of::<raw::btrfs_extent_data_ref>();
2556        let mut buf = vec![0u8; size];
2557        buf[0..8].copy_from_slice(&5u64.to_le_bytes()); // root
2558        buf[8..16].copy_from_slice(&256u64.to_le_bytes()); // objectid
2559        buf[16..24].copy_from_slice(&0u64.to_le_bytes()); // offset
2560        buf[24..28].copy_from_slice(&1u32.to_le_bytes()); // count
2561        let edr = ExtentDataRef::parse(&buf).unwrap();
2562        assert_eq!(edr.root, 5);
2563        assert_eq!(edr.objectid, 256);
2564        assert_eq!(edr.offset, 0);
2565        assert_eq!(edr.count, 1);
2566    }
2567
2568    #[test]
2569    fn extent_data_ref_too_short() {
2570        assert!(ExtentDataRef::parse(&[0; 27]).is_none());
2571    }
2572
2573    #[test]
2574    fn shared_data_ref_parse() {
2575        let buf = 17u32.to_le_bytes();
2576        let sdr = SharedDataRef::parse(&buf).unwrap();
2577        assert_eq!(sdr.count, 17);
2578    }
2579
2580    #[test]
2581    fn shared_data_ref_too_short() {
2582        assert!(SharedDataRef::parse(&[0; 3]).is_none());
2583    }
2584
2585    #[test]
2586    fn qgroup_info_parse() {
2587        let mut buf = Vec::new();
2588        buf.extend_from_slice(&100u64.to_le_bytes()); // generation
2589        buf.extend_from_slice(&4096u64.to_le_bytes()); // referenced
2590        buf.extend_from_slice(&4096u64.to_le_bytes()); // referenced_compressed
2591        buf.extend_from_slice(&2048u64.to_le_bytes()); // exclusive
2592        buf.extend_from_slice(&2048u64.to_le_bytes()); // exclusive_compressed
2593        let qi = QgroupInfo::parse(&buf).unwrap();
2594        assert_eq!(qi.generation, 100);
2595        assert_eq!(qi.referenced, 4096);
2596        assert_eq!(qi.referenced_compressed, 4096);
2597        assert_eq!(qi.exclusive, 2048);
2598        assert_eq!(qi.exclusive_compressed, 2048);
2599    }
2600
2601    #[test]
2602    fn qgroup_info_too_short() {
2603        assert!(QgroupInfo::parse(&[0; 39]).is_none());
2604    }
2605
2606    #[test]
2607    fn qgroup_limit_parse() {
2608        let mut buf = Vec::new();
2609        buf.extend_from_slice(&3u64.to_le_bytes()); // flags
2610        buf.extend_from_slice(&1_000_000u64.to_le_bytes()); // max_referenced
2611        buf.extend_from_slice(&500_000u64.to_le_bytes()); // max_exclusive
2612        buf.extend_from_slice(&0u64.to_le_bytes()); // rsv_referenced
2613        buf.extend_from_slice(&0u64.to_le_bytes()); // rsv_exclusive
2614        let ql = QgroupLimit::parse(&buf).unwrap();
2615        assert_eq!(ql.flags, 3);
2616        assert_eq!(ql.max_referenced, 1_000_000);
2617        assert_eq!(ql.max_exclusive, 500_000);
2618        assert_eq!(ql.rsv_referenced, 0);
2619        assert_eq!(ql.rsv_exclusive, 0);
2620    }
2621
2622    #[test]
2623    fn qgroup_limit_too_short() {
2624        assert!(QgroupLimit::parse(&[0; 39]).is_none());
2625    }
2626
2627    #[test]
2628    fn qgroup_status_parse_minimal() {
2629        let mut buf = Vec::new();
2630        buf.extend_from_slice(&1u64.to_le_bytes()); // version
2631        buf.extend_from_slice(&50u64.to_le_bytes()); // generation
2632        buf.extend_from_slice(&2u64.to_le_bytes()); // flags
2633        buf.extend_from_slice(&0u64.to_le_bytes()); // scan
2634        let qs = QgroupStatus::parse(&buf).unwrap();
2635        assert_eq!(qs.version, 1);
2636        assert_eq!(qs.generation, 50);
2637        assert_eq!(qs.flags, 2);
2638        assert_eq!(qs.scan, 0);
2639        assert!(qs.enable_gen.is_none());
2640    }
2641
2642    #[test]
2643    fn qgroup_status_parse_with_enable_gen() {
2644        let mut buf = Vec::new();
2645        buf.extend_from_slice(&1u64.to_le_bytes());
2646        buf.extend_from_slice(&50u64.to_le_bytes());
2647        buf.extend_from_slice(&2u64.to_le_bytes());
2648        buf.extend_from_slice(&0u64.to_le_bytes());
2649        buf.extend_from_slice(&99u64.to_le_bytes()); // enable_gen
2650        let qs = QgroupStatus::parse(&buf).unwrap();
2651        assert_eq!(qs.enable_gen, Some(99));
2652    }
2653
2654    #[test]
2655    fn qgroup_status_too_short() {
2656        assert!(QgroupStatus::parse(&[0; 31]).is_none());
2657    }
2658
2659    #[test]
2660    fn dev_replace_item_parse() {
2661        let mut buf = vec![0u8; 80];
2662        buf[0..8].copy_from_slice(&1u64.to_le_bytes()); // src_devid
2663        buf[8..16].copy_from_slice(&0x1000u64.to_le_bytes()); // cursor_left
2664        buf[16..24].copy_from_slice(&0x2000u64.to_le_bytes()); // cursor_right
2665        buf[24..32].copy_from_slice(&0u64.to_le_bytes()); // replace_mode
2666        buf[32..40].copy_from_slice(&2u64.to_le_bytes()); // replace_state
2667        buf[40..48].copy_from_slice(&1700000000u64.to_le_bytes()); // time_started
2668        buf[48..56].copy_from_slice(&1700000100u64.to_le_bytes()); // time_stopped
2669        buf[56..64].copy_from_slice(&3u64.to_le_bytes()); // num_write_errors
2670        buf[64..72].copy_from_slice(&5u64.to_le_bytes()); // num_uncorrectable_read_errors
2671        let dri = DeviceReplaceItem::parse(&buf).unwrap();
2672        assert_eq!(dri.src_devid, 1);
2673        assert_eq!(dri.cursor_left, 0x1000);
2674        assert_eq!(dri.cursor_right, 0x2000);
2675        assert_eq!(dri.replace_state, 2);
2676        assert_eq!(dri.time_started, 1700000000);
2677        assert_eq!(dri.time_stopped, 1700000100);
2678        assert_eq!(dri.num_write_errors, 3);
2679        assert_eq!(dri.num_uncorrectable_read_errors, 5);
2680    }
2681
2682    #[test]
2683    fn dev_replace_item_too_short() {
2684        assert!(DeviceReplaceItem::parse(&[0; 79]).is_none());
2685    }
2686
2687    #[test]
2688    fn raid_stripe_item_parse() {
2689        let mut buf = Vec::new();
2690        buf.extend_from_slice(&1u64.to_le_bytes()); // encoding
2691        // stripe 1
2692        buf.extend_from_slice(&1u64.to_le_bytes()); // devid
2693        buf.extend_from_slice(&0x10000u64.to_le_bytes()); // physical
2694        // stripe 2
2695        buf.extend_from_slice(&2u64.to_le_bytes());
2696        buf.extend_from_slice(&0x20000u64.to_le_bytes());
2697        let rsi = RaidStripeItem::parse(&buf).unwrap();
2698        assert_eq!(rsi.encoding, 1);
2699        assert_eq!(rsi.stripes.len(), 2);
2700        assert_eq!(rsi.stripes[0].devid, 1);
2701        assert_eq!(rsi.stripes[0].physical, 0x10000);
2702        assert_eq!(rsi.stripes[1].devid, 2);
2703        assert_eq!(rsi.stripes[1].physical, 0x20000);
2704    }
2705
2706    #[test]
2707    fn raid_stripe_item_no_stripes() {
2708        let buf = 42u64.to_le_bytes();
2709        let rsi = RaidStripeItem::parse(&buf).unwrap();
2710        assert_eq!(rsi.encoding, 42);
2711        assert!(rsi.stripes.is_empty());
2712    }
2713
2714    #[test]
2715    fn raid_stripe_item_too_short() {
2716        assert!(RaidStripeItem::parse(&[0; 7]).is_none());
2717    }
2718
2719    // ── Variable-length parsers ───────────────────────────────────────
2720
2721    #[test]
2722    fn inode_ref_parse_single() {
2723        let mut buf = Vec::new();
2724        buf.extend_from_slice(&42u64.to_le_bytes()); // index
2725        buf.extend_from_slice(&4u16.to_le_bytes()); // name_len
2726        buf.extend_from_slice(b"test");
2727        let refs = InodeRef::parse_all(&buf);
2728        assert_eq!(refs.len(), 1);
2729        assert_eq!(refs[0].index, 42);
2730        assert_eq!(refs[0].name, b"test");
2731    }
2732
2733    #[test]
2734    fn inode_ref_parse_multiple() {
2735        let mut buf = Vec::new();
2736        // entry 1
2737        buf.extend_from_slice(&1u64.to_le_bytes());
2738        buf.extend_from_slice(&3u16.to_le_bytes());
2739        buf.extend_from_slice(b"abc");
2740        // entry 2
2741        buf.extend_from_slice(&2u64.to_le_bytes());
2742        buf.extend_from_slice(&2u16.to_le_bytes());
2743        buf.extend_from_slice(b"xy");
2744        let refs = InodeRef::parse_all(&buf);
2745        assert_eq!(refs.len(), 2);
2746        assert_eq!(refs[0].index, 1);
2747        assert_eq!(refs[0].name, b"abc");
2748        assert_eq!(refs[1].index, 2);
2749        assert_eq!(refs[1].name, b"xy");
2750    }
2751
2752    #[test]
2753    fn inode_ref_parse_truncated() {
2754        // Header present but name extends past buffer end.
2755        let mut buf = Vec::new();
2756        buf.extend_from_slice(&1u64.to_le_bytes());
2757        buf.extend_from_slice(&10u16.to_le_bytes()); // claims 10 bytes
2758        buf.extend_from_slice(b"abc"); // only 3 available
2759        let refs = InodeRef::parse_all(&buf);
2760        assert!(refs.is_empty());
2761    }
2762
2763    #[test]
2764    fn inode_extref_parse_single() {
2765        let mut buf = Vec::new();
2766        buf.extend_from_slice(&256u64.to_le_bytes()); // parent
2767        buf.extend_from_slice(&3u64.to_le_bytes()); // index
2768        buf.extend_from_slice(&5u16.to_le_bytes()); // name_len
2769        buf.extend_from_slice(b"hello");
2770        let refs = InodeExtref::parse_all(&buf);
2771        assert_eq!(refs.len(), 1);
2772        assert_eq!(refs[0].parent, 256);
2773        assert_eq!(refs[0].index, 3);
2774        assert_eq!(refs[0].name, b"hello");
2775    }
2776
2777    #[test]
2778    fn dir_item_parse_single() {
2779        let dir_item_size = mem::size_of::<raw::btrfs_dir_item>();
2780        let mut buf = vec![0u8; dir_item_size];
2781        // location: DiskKey at offset 0 (17 bytes: u64 objectid + u8 type + u64 offset)
2782        buf[0..8].copy_from_slice(&256u64.to_le_bytes()); // objectid
2783        buf[8] = 1; // key type
2784        buf[9..17].copy_from_slice(&0u64.to_le_bytes()); // offset
2785        // transid at offset 17
2786        buf[17..25].copy_from_slice(&100u64.to_le_bytes());
2787        // data_len at offset 25
2788        buf[25..27].copy_from_slice(&0u16.to_le_bytes());
2789        // name_len at offset 27
2790        buf[27..29].copy_from_slice(&4u16.to_le_bytes());
2791        // file_type at offset 29
2792        buf[29] = 1; // FT_REG_FILE
2793        // Append name
2794        buf.extend_from_slice(b"file");
2795        let items = DirItem::parse_all(&buf);
2796        assert_eq!(items.len(), 1);
2797        assert_eq!(items[0].transid, 100);
2798        assert_eq!(items[0].file_type, FileType::RegFile);
2799        assert_eq!(items[0].name, b"file");
2800        assert!(items[0].data.is_empty());
2801    }
2802
2803    #[test]
2804    fn root_ref_parse() {
2805        let hdr_size = mem::size_of::<raw::btrfs_root_ref>();
2806        let mut buf = vec![0u8; hdr_size];
2807        buf[0..8].copy_from_slice(&256u64.to_le_bytes()); // dirid
2808        buf[8..16].copy_from_slice(&7u64.to_le_bytes()); // sequence
2809        buf[16..18].copy_from_slice(&6u16.to_le_bytes()); // name_len
2810        buf.extend_from_slice(b"subvol");
2811        let rr = RootRef::parse(&buf).unwrap();
2812        assert_eq!(rr.dirid, 256);
2813        assert_eq!(rr.sequence, 7);
2814        assert_eq!(rr.name, b"subvol");
2815    }
2816
2817    #[test]
2818    fn root_ref_too_short() {
2819        let hdr_size = mem::size_of::<raw::btrfs_root_ref>();
2820        assert!(RootRef::parse(&vec![0u8; hdr_size - 1]).is_none());
2821    }
2822
2823    #[test]
2824    fn root_ref_serialize_round_trip() {
2825        let bytes = RootRef::serialize(256, 42, b"snapshot-1");
2826        let parsed = RootRef::parse(&bytes).unwrap();
2827        assert_eq!(parsed.dirid, 256);
2828        assert_eq!(parsed.sequence, 42);
2829        assert_eq!(parsed.name, b"snapshot-1");
2830        // Header is 18 bytes (8 + 8 + 2) plus the name.
2831        assert_eq!(bytes.len(), mem::size_of::<raw::btrfs_root_ref>() + 10);
2832    }
2833
2834    #[test]
2835    fn root_ref_serialize_empty_name() {
2836        let bytes = RootRef::serialize(7, 0, b"");
2837        let parsed = RootRef::parse(&bytes).unwrap();
2838        assert_eq!(parsed.dirid, 7);
2839        assert_eq!(parsed.sequence, 0);
2840        assert!(parsed.name.is_empty());
2841    }
2842
2843    #[test]
2844    fn uuid_item_parse() {
2845        let mut buf = Vec::new();
2846        buf.extend_from_slice(&256u64.to_le_bytes());
2847        buf.extend_from_slice(&257u64.to_le_bytes());
2848        buf.extend_from_slice(&258u64.to_le_bytes());
2849        let ui = UuidItem::parse(&buf);
2850        assert_eq!(ui.subvol_ids, vec![256, 257, 258]);
2851    }
2852
2853    #[test]
2854    fn uuid_item_empty() {
2855        let ui = UuidItem::parse(&[]);
2856        assert!(ui.subvol_ids.is_empty());
2857    }
2858
2859    #[test]
2860    fn dev_stats_parse() {
2861        let mut buf = Vec::new();
2862        buf.extend_from_slice(&1u64.to_le_bytes()); // write_errs
2863        buf.extend_from_slice(&2u64.to_le_bytes()); // read_errs
2864        buf.extend_from_slice(&3u64.to_le_bytes()); // flush_errs
2865        buf.extend_from_slice(&4u64.to_le_bytes()); // corruption_errs
2866        buf.extend_from_slice(&5u64.to_le_bytes()); // generation
2867        let ds = DeviceStats::parse(&buf);
2868        assert_eq!(ds.values.len(), 5);
2869        assert_eq!(ds.values[0], ("write_errs".to_string(), 1));
2870        assert_eq!(ds.values[1], ("read_errs".to_string(), 2));
2871        assert_eq!(ds.values[2], ("flush_errs".to_string(), 3));
2872        assert_eq!(ds.values[3], ("corruption_errs".to_string(), 4));
2873        assert_eq!(ds.values[4], ("generation".to_string(), 5));
2874    }
2875
2876    #[test]
2877    fn dev_stats_partial() {
2878        // Only 2 values available.
2879        let mut buf = Vec::new();
2880        buf.extend_from_slice(&10u64.to_le_bytes());
2881        buf.extend_from_slice(&20u64.to_le_bytes());
2882        let ds = DeviceStats::parse(&buf);
2883        assert_eq!(ds.values.len(), 2);
2884        assert_eq!(ds.values[0].1, 10);
2885        assert_eq!(ds.values[1].1, 20);
2886    }
2887
2888    // ── FileExtentItem ────────────────────────────────────────────────
2889
2890    #[test]
2891    fn file_extent_item_inline() {
2892        let mut buf = vec![0u8; 21 + 10]; // 21 header + 10 inline data
2893        buf[0..8].copy_from_slice(&7u64.to_le_bytes()); // generation
2894        buf[8..16].copy_from_slice(&10u64.to_le_bytes()); // ram_bytes
2895        buf[16] = 0; // compression = none
2896        // bytes 17-19 are encryption/other_encoding (unused)
2897        buf[20] = 0; // extent_type = inline
2898        buf[21..31].copy_from_slice(&[0xAA; 10]); // inline data
2899        let fei = FileExtentItem::parse(&buf).unwrap();
2900        assert_eq!(fei.generation, 7);
2901        assert_eq!(fei.ram_bytes, 10);
2902        assert_eq!(fei.compression, CompressionType::None);
2903        assert_eq!(fei.extent_type, FileExtentType::Inline);
2904        match fei.body {
2905            FileExtentBody::Inline { inline_size } => {
2906                assert_eq!(inline_size, 10)
2907            }
2908            _ => panic!("expected inline body"),
2909        }
2910    }
2911
2912    #[test]
2913    fn file_extent_item_regular() {
2914        let mut buf = vec![0u8; 53];
2915        buf[0..8].copy_from_slice(&100u64.to_le_bytes()); // generation
2916        buf[8..16].copy_from_slice(&4096u64.to_le_bytes()); // ram_bytes
2917        buf[16] = 1; // compression = zlib
2918        buf[20] = 1; // extent_type = regular
2919        buf[21..29].copy_from_slice(&0x100000u64.to_le_bytes()); // disk_bytenr
2920        buf[29..37].copy_from_slice(&4096u64.to_le_bytes()); // disk_num_bytes
2921        buf[37..45].copy_from_slice(&0u64.to_le_bytes()); // offset
2922        buf[45..53].copy_from_slice(&4096u64.to_le_bytes()); // num_bytes
2923        let fei = FileExtentItem::parse(&buf).unwrap();
2924        assert_eq!(fei.generation, 100);
2925        assert_eq!(fei.compression, CompressionType::Zlib);
2926        assert_eq!(fei.extent_type, FileExtentType::Regular);
2927        match fei.body {
2928            FileExtentBody::Regular {
2929                disk_bytenr,
2930                disk_num_bytes,
2931                offset,
2932                num_bytes,
2933            } => {
2934                assert_eq!(disk_bytenr, 0x100000);
2935                assert_eq!(disk_num_bytes, 4096);
2936                assert_eq!(offset, 0);
2937                assert_eq!(num_bytes, 4096);
2938            }
2939            _ => panic!("expected regular body"),
2940        }
2941    }
2942
2943    #[test]
2944    fn file_extent_item_too_short() {
2945        assert!(FileExtentItem::parse(&[0; 20]).is_none());
2946    }
2947
2948    #[test]
2949    fn file_extent_item_regular_too_short() {
2950        // 21 bytes is enough for inline but not for regular.
2951        let mut buf = vec![0u8; 21];
2952        buf[20] = 1; // extent_type = regular
2953        assert!(FileExtentItem::parse(&buf).is_none());
2954    }
2955
2956    #[test]
2957    fn file_extent_item_to_bytes_regular_round_trip() {
2958        let bytes = FileExtentItem::to_bytes_regular(
2959            42,
2960            65536,
2961            CompressionType::Zstd,
2962            false,
2963            0x200000,
2964            4096,
2965            0,
2966            65536,
2967        );
2968        assert_eq!(bytes.len(), FileExtentItem::REGULAR_SIZE);
2969        let parsed = FileExtentItem::parse(&bytes).unwrap();
2970        assert_eq!(parsed.generation, 42);
2971        assert_eq!(parsed.ram_bytes, 65536);
2972        assert_eq!(parsed.compression, CompressionType::Zstd);
2973        assert_eq!(parsed.extent_type, FileExtentType::Regular);
2974        match parsed.body {
2975            FileExtentBody::Regular {
2976                disk_bytenr,
2977                disk_num_bytes,
2978                offset,
2979                num_bytes,
2980            } => {
2981                assert_eq!(disk_bytenr, 0x200000);
2982                assert_eq!(disk_num_bytes, 4096);
2983                assert_eq!(offset, 0);
2984                assert_eq!(num_bytes, 65536);
2985            }
2986            _ => panic!("expected regular body"),
2987        }
2988    }
2989
2990    #[test]
2991    fn file_extent_item_to_bytes_regular_prealloc_flag() {
2992        let bytes = FileExtentItem::to_bytes_regular(
2993            1,
2994            4096,
2995            CompressionType::None,
2996            true,
2997            0x10000,
2998            4096,
2999            0,
3000            4096,
3001        );
3002        let parsed = FileExtentItem::parse(&bytes).unwrap();
3003        assert_eq!(parsed.extent_type, FileExtentType::Prealloc);
3004    }
3005
3006    #[test]
3007    fn file_extent_item_to_bytes_inline_round_trip() {
3008        let payload = b"hello inline";
3009        let bytes = FileExtentItem::to_bytes_inline(
3010            7,
3011            payload.len() as u64,
3012            CompressionType::None,
3013            payload,
3014        );
3015        assert_eq!(bytes.len(), FileExtentItem::HEADER_SIZE + payload.len());
3016        let parsed = FileExtentItem::parse(&bytes).unwrap();
3017        assert_eq!(parsed.generation, 7);
3018        assert_eq!(parsed.ram_bytes, payload.len() as u64);
3019        assert_eq!(parsed.compression, CompressionType::None);
3020        assert_eq!(parsed.extent_type, FileExtentType::Inline);
3021        match parsed.body {
3022            FileExtentBody::Inline { inline_size } => {
3023                assert_eq!(inline_size, payload.len());
3024            }
3025            _ => panic!("expected inline body"),
3026        }
3027        // Inline payload bytes must be preserved verbatim.
3028        assert_eq!(&bytes[FileExtentItem::HEADER_SIZE..], payload);
3029    }
3030
3031    // ── Helper functions ──────────────────────────────────────────────
3032
3033    #[test]
3034    fn raw_crc32c_known_value() {
3035        // The raw CRC32C of an empty buffer with seed 0 should be 0.
3036        assert_eq!(raw_crc32c(0, &[]), 0);
3037        // Verify that raw_crc32c differs from the standard CRC32C.
3038        // Standard CRC32C of "123456789" is 0xE3069283.
3039        let raw = raw_crc32c(0, b"123456789");
3040        let standard = crc32c::crc32c(b"123456789");
3041        assert_eq!(standard, 0xE3069283);
3042        assert_ne!(raw, standard);
3043        // raw_crc32c is deterministic.
3044        assert_eq!(raw, raw_crc32c(0, b"123456789"));
3045        // Chaining: raw_crc32c with a nonzero seed produces different results.
3046        let chained = raw_crc32c(raw, b"more");
3047        assert_ne!(chained, raw);
3048    }
3049
3050    #[test]
3051    fn extent_data_ref_hash_deterministic() {
3052        let h1 = extent_data_ref_hash(5, 256, 0);
3053        let h2 = extent_data_ref_hash(5, 256, 0);
3054        assert_eq!(h1, h2);
3055        // Different inputs produce different hashes.
3056        let h3 = extent_data_ref_hash(5, 256, 4096);
3057        assert_ne!(h1, h3);
3058    }
3059
3060    #[test]
3061    fn block_group_flags_type_name() {
3062        assert_eq!(BlockGroupFlags::DATA.type_name(), "Data");
3063        assert_eq!(BlockGroupFlags::METADATA.type_name(), "Metadata");
3064        assert_eq!(BlockGroupFlags::SYSTEM.type_name(), "System");
3065        assert_eq!(
3066            (BlockGroupFlags::DATA | BlockGroupFlags::METADATA).type_name(),
3067            "Data+Metadata"
3068        );
3069        assert_eq!(BlockGroupFlags::GLOBAL_RSV.type_name(), "GlobalReserve");
3070    }
3071
3072    #[test]
3073    fn block_group_flags_profile_name() {
3074        assert_eq!(BlockGroupFlags::DATA.profile_name(), "single");
3075        assert_eq!(
3076            (BlockGroupFlags::DATA | BlockGroupFlags::DUP).profile_name(),
3077            "DUP"
3078        );
3079        assert_eq!(
3080            (BlockGroupFlags::DATA | BlockGroupFlags::RAID0).profile_name(),
3081            "RAID0"
3082        );
3083        assert_eq!(
3084            (BlockGroupFlags::DATA | BlockGroupFlags::RAID1).profile_name(),
3085            "RAID1"
3086        );
3087        assert_eq!(
3088            (BlockGroupFlags::DATA | BlockGroupFlags::RAID10).profile_name(),
3089            "RAID10"
3090        );
3091        assert_eq!(
3092            (BlockGroupFlags::DATA | BlockGroupFlags::RAID5).profile_name(),
3093            "RAID5"
3094        );
3095        assert_eq!(
3096            (BlockGroupFlags::DATA | BlockGroupFlags::RAID6).profile_name(),
3097            "RAID6"
3098        );
3099        assert_eq!(
3100            (BlockGroupFlags::DATA | BlockGroupFlags::RAID1C3).profile_name(),
3101            "RAID1C3"
3102        );
3103        assert_eq!(
3104            (BlockGroupFlags::DATA | BlockGroupFlags::RAID1C4).profile_name(),
3105            "RAID1C4"
3106        );
3107    }
3108
3109    #[test]
3110    fn extent_item_skinny_size() {
3111        let bytes = ExtentItem::to_bytes_skinny(1, 42, 5);
3112        assert_eq!(bytes.len(), ExtentItem::SKINNY_SIZE);
3113        assert_eq!(bytes.len(), 33);
3114    }
3115
3116    #[test]
3117    fn extent_item_non_skinny_size() {
3118        let key = DiskKey {
3119            objectid: 256,
3120            key_type: KeyType::InodeItem,
3121            offset: 0,
3122        };
3123        let bytes = ExtentItem::to_bytes_non_skinny(1, 42, 5, &key, 0);
3124        assert_eq!(bytes.len(), ExtentItem::NON_SKINNY_SIZE);
3125        assert_eq!(bytes.len(), 51);
3126    }
3127
3128    #[test]
3129    fn extent_item_skinny_non_skinny_header_match() {
3130        let skinny = ExtentItem::to_bytes_skinny(1, 42, 5);
3131        let key = DiskKey {
3132            objectid: 0,
3133            key_type: KeyType::from_raw(0),
3134            offset: 0,
3135        };
3136        let non_skinny = ExtentItem::to_bytes_non_skinny(1, 42, 5, &key, 0);
3137        // First 24 bytes (refs + generation + flags) identical
3138        assert_eq!(&skinny[..24], &non_skinny[..24]);
3139    }
3140
3141    #[test]
3142    fn extent_item_flags_are_tree_block() {
3143        let bytes = ExtentItem::to_bytes_skinny(1, 42, 5);
3144        let flags = u64::from_le_bytes(bytes[16..24].try_into().unwrap());
3145        assert_eq!(flags, ExtentFlags::TREE_BLOCK.bits());
3146    }
3147
3148    #[test]
3149    fn root_item_to_bytes_round_trip() {
3150        let original = RootItem::new_internal(42, 65536, 0);
3151        let bytes = original.to_bytes();
3152        assert_eq!(bytes.len(), 439);
3153        let parsed = RootItem::parse(&bytes).expect("parse failed");
3154        assert_eq!(parsed.generation, 42);
3155        assert_eq!(parsed.bytenr, 65536);
3156        assert_eq!(parsed.level, 0);
3157        assert_eq!(parsed.refs, 1);
3158    }
3159
3160    #[test]
3161    fn block_group_item_to_bytes_round_trip() {
3162        let bg = BlockGroupItem {
3163            used: 1024 * 1024,
3164            chunk_objectid: 256,
3165            flags: BlockGroupFlags::METADATA | BlockGroupFlags::DUP,
3166        };
3167        let bytes = bg.to_bytes();
3168        assert_eq!(bytes.len(), 24);
3169        let parsed = BlockGroupItem::parse(&bytes).unwrap();
3170        assert_eq!(parsed.used, bg.used);
3171        assert_eq!(parsed.chunk_objectid, bg.chunk_objectid);
3172        assert_eq!(parsed.flags, bg.flags);
3173    }
3174
3175    #[test]
3176    fn inode_item_args_to_bytes_size() {
3177        let args = InodeItemArgs {
3178            generation: 7,
3179            size: 42,
3180            nbytes: 4096,
3181            nlink: 1,
3182            uid: 1000,
3183            gid: 1000,
3184            mode: 0o100644,
3185            time: Timespec { sec: 100, nsec: 0 },
3186        };
3187        let bytes = args.to_bytes();
3188        assert_eq!(bytes.len(), 160);
3189        let parsed = InodeItem::parse(&bytes).unwrap();
3190        assert_eq!(parsed.generation, 7);
3191        assert_eq!(parsed.size, 42);
3192        assert_eq!(parsed.nlink, 1);
3193    }
3194
3195    #[test]
3196    fn dir_item_serialize_round_trip() {
3197        let location = DiskKey {
3198            objectid: 257,
3199            key_type: KeyType::InodeItem,
3200            offset: 0,
3201        };
3202        let bytes = DirItem::serialize(
3203            &location,
3204            7,
3205            raw::BTRFS_FT_REG_FILE as u8,
3206            b"hello.txt",
3207        );
3208        let items = DirItem::parse_all(&bytes);
3209        assert_eq!(items.len(), 1);
3210        assert_eq!(items[0].location.objectid, 257);
3211        assert_eq!(items[0].transid, 7);
3212        assert_eq!(items[0].name, b"hello.txt");
3213    }
3214
3215    #[test]
3216    fn inode_ref_serialize_round_trip() {
3217        let bytes = InodeRef::serialize(2, b"hello.txt");
3218        let refs = InodeRef::parse_all(&bytes);
3219        assert_eq!(refs.len(), 1);
3220        assert_eq!(refs[0].index, 2);
3221        assert_eq!(refs[0].name, b"hello.txt");
3222    }
3223}