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
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    // --- type_name ---
120
121    #[test]
122    fn type_name_data() {
123        assert_eq!(BlockGroupFlags::DATA.type_name(), "Data");
124    }
125
126    #[test]
127    fn type_name_metadata() {
128        assert_eq!(BlockGroupFlags::METADATA.type_name(), "Metadata");
129    }
130
131    #[test]
132    fn type_name_system() {
133        assert_eq!(BlockGroupFlags::SYSTEM.type_name(), "System");
134    }
135
136    #[test]
137    fn type_name_data_metadata() {
138        let flags = BlockGroupFlags::DATA | BlockGroupFlags::METADATA;
139        assert_eq!(flags.type_name(), "Data+Metadata");
140    }
141
142    #[test]
143    fn type_name_global_rsv() {
144        assert_eq!(BlockGroupFlags::GLOBAL_RSV.type_name(), "GlobalReserve");
145    }
146
147    #[test]
148    fn type_name_global_rsv_takes_precedence() {
149        let flags = BlockGroupFlags::GLOBAL_RSV | BlockGroupFlags::METADATA;
150        assert_eq!(flags.type_name(), "GlobalReserve");
151    }
152
153    // --- profile_name ---
154
155    #[test]
156    fn profile_name_single_no_bits() {
157        assert_eq!(BlockGroupFlags::DATA.profile_name(), "single");
158    }
159
160    #[test]
161    fn profile_name_single_explicit() {
162        assert_eq!(
163            (BlockGroupFlags::DATA | BlockGroupFlags::SINGLE).profile_name(),
164            "single"
165        );
166    }
167
168    #[test]
169    fn profile_name_raid0() {
170        assert_eq!(
171            (BlockGroupFlags::DATA | BlockGroupFlags::RAID0).profile_name(),
172            "RAID0"
173        );
174    }
175
176    #[test]
177    fn profile_name_raid1() {
178        assert_eq!(
179            (BlockGroupFlags::DATA | BlockGroupFlags::RAID1).profile_name(),
180            "RAID1"
181        );
182    }
183
184    #[test]
185    fn profile_name_dup() {
186        assert_eq!(
187            (BlockGroupFlags::METADATA | BlockGroupFlags::DUP).profile_name(),
188            "DUP"
189        );
190    }
191
192    #[test]
193    fn profile_name_raid10() {
194        assert_eq!(
195            (BlockGroupFlags::DATA | BlockGroupFlags::RAID10).profile_name(),
196            "RAID10"
197        );
198    }
199
200    #[test]
201    fn profile_name_raid5() {
202        assert_eq!(
203            (BlockGroupFlags::DATA | BlockGroupFlags::RAID5).profile_name(),
204            "RAID5"
205        );
206    }
207
208    #[test]
209    fn profile_name_raid6() {
210        assert_eq!(
211            (BlockGroupFlags::DATA | BlockGroupFlags::RAID6).profile_name(),
212            "RAID6"
213        );
214    }
215
216    #[test]
217    fn profile_name_raid1c3() {
218        assert_eq!(
219            (BlockGroupFlags::DATA | BlockGroupFlags::RAID1C3).profile_name(),
220            "RAID1C3"
221        );
222    }
223
224    #[test]
225    fn profile_name_raid1c4() {
226        assert_eq!(
227            (BlockGroupFlags::DATA | BlockGroupFlags::RAID1C4).profile_name(),
228            "RAID1C4"
229        );
230    }
231
232    // --- Display ---
233
234    #[test]
235    fn display_data_single() {
236        assert_eq!(format!("{}", BlockGroupFlags::DATA), "Data, single");
237    }
238
239    #[test]
240    fn display_metadata_dup() {
241        let flags = BlockGroupFlags::METADATA | BlockGroupFlags::DUP;
242        assert_eq!(format!("{flags}"), "Metadata, DUP");
243    }
244
245    #[test]
246    fn display_system_raid1() {
247        let flags = BlockGroupFlags::SYSTEM | BlockGroupFlags::RAID1;
248        assert_eq!(format!("{flags}"), "System, RAID1");
249    }
250
251    #[test]
252    fn display_global_rsv() {
253        let flags = BlockGroupFlags::GLOBAL_RSV | BlockGroupFlags::METADATA;
254        assert_eq!(format!("{flags}"), "GlobalReserve, single");
255    }
256
257    // --- from_bits_truncate ---
258
259    #[test]
260    fn from_bits_preserves_known_flags() {
261        let raw = BTRFS_BLOCK_GROUP_DATA as u64 | BTRFS_BLOCK_GROUP_RAID1 as u64;
262        let flags = BlockGroupFlags::from_bits_truncate(raw);
263        assert!(flags.contains(BlockGroupFlags::DATA));
264        assert!(flags.contains(BlockGroupFlags::RAID1));
265    }
266}
267
268impl From<btrfs_ioctl_space_info> for SpaceInfo {
269    fn from(raw: btrfs_ioctl_space_info) -> Self {
270        Self {
271            flags: BlockGroupFlags::from_bits_truncate(raw.flags),
272            total_bytes: raw.total_bytes,
273            used_bytes: raw.used_bytes,
274        }
275    }
276}
277
278/// Query space usage by block group type for the filesystem referred to by
279/// `fd`.
280///
281/// Returns one [`SpaceInfo`] entry per block group type/profile combination.
282///
283/// Uses a two-phase ioctl call: the first call with `space_slots = 0`
284/// retrieves the entry count, and the second call retrieves all entries.
285pub fn space_info(fd: BorrowedFd) -> nix::Result<Vec<SpaceInfo>> {
286    // Phase 1: query with space_slots = 0 to discover the number of entries.
287    let mut args: btrfs_ioctl_space_args = unsafe { mem::zeroed() };
288    unsafe { btrfs_ioc_space_info(fd.as_raw_fd(), &mut args) }?;
289    let count = args.total_spaces as usize;
290
291    if count == 0 {
292        return Ok(Vec::new());
293    }
294
295    // Phase 2: allocate a buffer large enough to hold the header plus all
296    // entries, then call again with space_slots set to the count.
297    //
298    // We use Vec<u64> rather than Vec<u8> to guarantee 8-byte alignment,
299    // matching the alignment requirement of btrfs_ioctl_space_args.
300    let base_size = mem::size_of::<btrfs_ioctl_space_args>();
301    let info_size = mem::size_of::<btrfs_ioctl_space_info>();
302    let total_bytes = base_size + count * info_size;
303    let num_u64s = total_bytes.div_ceil(mem::size_of::<u64>());
304    let mut buf = vec![0u64; num_u64s];
305
306    // SAFETY: buf is correctly sized and aligned for btrfs_ioctl_space_args.
307    // We write space_slots before the ioctl and read spaces[] only after the
308    // ioctl has populated them, keeping everything within the allocation.
309    unsafe {
310        let args_ptr = buf.as_mut_ptr() as *mut btrfs_ioctl_space_args;
311        (*args_ptr).space_slots = count as u64;
312        btrfs_ioc_space_info(fd.as_raw_fd(), &mut *args_ptr)?;
313        Ok((*args_ptr)
314            .spaces
315            .as_slice(count)
316            .iter()
317            .copied()
318            .map(SpaceInfo::from)
319            .collect())
320    }
321}