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