Skip to main content

btrfs_uapi/
space.rs

1//! # Block group space usage: how much space each chunk type allocates and uses
2//!
3//! Reports the allocated and used byte counts for each combination of block
4//! group type (data, metadata, system) and RAID profile.  This is the data
5//! underlying the `btrfs filesystem df` command.
6
7use crate::raw::{
8    BTRFS_AVAIL_ALLOC_BIT_SINGLE, BTRFS_BLOCK_GROUP_DATA, BTRFS_BLOCK_GROUP_DUP,
9    BTRFS_BLOCK_GROUP_METADATA, BTRFS_BLOCK_GROUP_RAID0, BTRFS_BLOCK_GROUP_RAID1,
10    BTRFS_BLOCK_GROUP_RAID1C3, BTRFS_BLOCK_GROUP_RAID1C4, BTRFS_BLOCK_GROUP_RAID5,
11    BTRFS_BLOCK_GROUP_RAID6, BTRFS_BLOCK_GROUP_RAID10, BTRFS_BLOCK_GROUP_SYSTEM,
12    BTRFS_SPACE_INFO_GLOBAL_RSV, btrfs_ioc_space_info, btrfs_ioctl_space_args,
13    btrfs_ioctl_space_info,
14};
15use bitflags::bitflags;
16use std::{
17    fmt, mem,
18    os::{fd::AsRawFd, unix::io::BorrowedFd},
19};
20
21bitflags! {
22    /// Flags describing the type and RAID profile of a btrfs block group.
23    ///
24    /// The lower bits encode the chunk type (data, metadata, system) and the
25    /// upper bits encode the RAID profile. A `Display` implementation formats
26    /// the flags as `"<type>, <profile>"`, matching the output of
27    /// `btrfs filesystem df`.
28    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29    pub struct BlockGroupFlags: u64 {
30        // --- chunk types ---
31        const DATA            = BTRFS_BLOCK_GROUP_DATA as u64;
32        const SYSTEM          = BTRFS_BLOCK_GROUP_SYSTEM as u64;
33        const METADATA        = BTRFS_BLOCK_GROUP_METADATA as u64;
34
35        // --- RAID profiles ---
36        const RAID0           = BTRFS_BLOCK_GROUP_RAID0 as u64;
37        const RAID1           = BTRFS_BLOCK_GROUP_RAID1 as u64;
38        const DUP             = BTRFS_BLOCK_GROUP_DUP as u64;
39        const RAID10          = BTRFS_BLOCK_GROUP_RAID10 as u64;
40        const RAID5           = BTRFS_BLOCK_GROUP_RAID5 as u64;
41        const RAID6           = BTRFS_BLOCK_GROUP_RAID6 as u64;
42        const RAID1C3         = BTRFS_BLOCK_GROUP_RAID1C3 as u64;
43        const RAID1C4         = BTRFS_BLOCK_GROUP_RAID1C4 as u64;
44
45        // AVAIL_ALLOC_BIT_SINGLE is the explicit "single" marker (bit 48).
46        // When no profile bits are set the allocation is also single.
47        const SINGLE          = BTRFS_AVAIL_ALLOC_BIT_SINGLE;
48
49        // Pseudo-type used for the global reservation pool.
50        const GLOBAL_RSV      = BTRFS_SPACE_INFO_GLOBAL_RSV;
51    }
52}
53
54impl BlockGroupFlags {
55    /// Returns the human-readable chunk type name.
56    pub fn type_name(self) -> &'static str {
57        if self.contains(Self::GLOBAL_RSV) {
58            return "GlobalReserve";
59        }
60        let ty = self & (Self::DATA | Self::SYSTEM | Self::METADATA);
61        match ty {
62            t if t == Self::DATA => "Data",
63            t if t == Self::SYSTEM => "System",
64            t if t == Self::METADATA => "Metadata",
65            t if t == Self::DATA | Self::METADATA => "Data+Metadata",
66            _ => "unknown",
67        }
68    }
69
70    /// Returns the human-readable RAID profile name.
71    pub fn profile_name(self) -> &'static str {
72        let profile = self
73            & (Self::RAID0
74                | Self::RAID1
75                | Self::DUP
76                | Self::RAID10
77                | Self::RAID5
78                | Self::RAID6
79                | Self::RAID1C3
80                | Self::RAID1C4
81                | Self::SINGLE);
82        match profile {
83            p if p == Self::RAID0 => "RAID0",
84            p if p == Self::RAID1 => "RAID1",
85            p if p == Self::DUP => "DUP",
86            p if p == Self::RAID10 => "RAID10",
87            p if p == Self::RAID5 => "RAID5",
88            p if p == Self::RAID6 => "RAID6",
89            p if p == Self::RAID1C3 => "RAID1C3",
90            p if p == Self::RAID1C4 => "RAID1C4",
91            // Both explicit SINGLE and no-profile-bits mean "single".
92            _ => "single",
93        }
94    }
95}
96
97impl fmt::Display for BlockGroupFlags {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        write!(f, "{}, {}", self.type_name(), self.profile_name())
100    }
101}
102
103/// Space usage information for one block group type/profile combination.
104///
105/// Returned by [`space_info`]. The `flags` field describes the chunk type and
106/// RAID profile; `total_bytes` and `used_bytes` are the allocated and in-use
107/// byte counts respectively.
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub struct SpaceInfo {
110    pub flags: BlockGroupFlags,
111    pub total_bytes: u64,
112    pub used_bytes: u64,
113}
114
115impl From<btrfs_ioctl_space_info> for SpaceInfo {
116    fn from(raw: btrfs_ioctl_space_info) -> Self {
117        Self {
118            flags: BlockGroupFlags::from_bits_truncate(raw.flags),
119            total_bytes: raw.total_bytes,
120            used_bytes: raw.used_bytes,
121        }
122    }
123}
124
125/// Query space usage by block group type for the filesystem referred to by
126/// `fd`.
127///
128/// Returns one [`SpaceInfo`] entry per block group type/profile combination.
129///
130/// Uses a two-phase ioctl call: the first call with `space_slots = 0`
131/// retrieves the entry count, and the second call retrieves all entries.
132pub fn space_info(fd: BorrowedFd) -> nix::Result<Vec<SpaceInfo>> {
133    // Phase 1: query with space_slots = 0 to discover the number of entries.
134    let mut args: btrfs_ioctl_space_args = unsafe { mem::zeroed() };
135    unsafe { btrfs_ioc_space_info(fd.as_raw_fd(), &mut args) }?;
136    let count = args.total_spaces as usize;
137
138    if count == 0 {
139        return Ok(Vec::new());
140    }
141
142    // Phase 2: allocate a buffer large enough to hold the header plus all
143    // entries, then call again with space_slots set to the count.
144    //
145    // We use Vec<u64> rather than Vec<u8> to guarantee 8-byte alignment,
146    // matching the alignment requirement of btrfs_ioctl_space_args.
147    let base_size = mem::size_of::<btrfs_ioctl_space_args>();
148    let info_size = mem::size_of::<btrfs_ioctl_space_info>();
149    let total_bytes = base_size + count * info_size;
150    let num_u64s = total_bytes.div_ceil(mem::size_of::<u64>());
151    let mut buf = vec![0u64; num_u64s];
152
153    // SAFETY: buf is correctly sized and aligned for btrfs_ioctl_space_args.
154    // We write space_slots before the ioctl and read spaces[] only after the
155    // ioctl has populated them, keeping everything within the allocation.
156    unsafe {
157        let args_ptr = buf.as_mut_ptr() as *mut btrfs_ioctl_space_args;
158        (*args_ptr).space_slots = count as u64;
159        btrfs_ioc_space_info(fd.as_raw_fd(), &mut *args_ptr)?;
160        Ok((*args_ptr)
161            .spaces
162            .as_slice(count)
163            .iter()
164            .copied()
165            .map(SpaceInfo::from)
166            .collect())
167    }
168}