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,
9    BTRFS_BLOCK_GROUP_DUP, BTRFS_BLOCK_GROUP_METADATA, BTRFS_BLOCK_GROUP_RAID0,
10    BTRFS_BLOCK_GROUP_RAID1, BTRFS_BLOCK_GROUP_RAID1C3,
11    BTRFS_BLOCK_GROUP_RAID1C4, BTRFS_BLOCK_GROUP_RAID5,
12    BTRFS_BLOCK_GROUP_RAID6, BTRFS_BLOCK_GROUP_RAID10,
13    BTRFS_BLOCK_GROUP_SYSTEM, BTRFS_SPACE_INFO_GLOBAL_RSV,
14    btrfs_ioc_space_info, btrfs_ioctl_space_args, btrfs_ioctl_space_info,
15};
16use bitflags::bitflags;
17use std::{
18    fmt, mem,
19    os::{fd::AsRawFd, unix::io::BorrowedFd},
20};
21
22bitflags! {
23    /// Flags describing the type and RAID profile of a btrfs block group.
24    ///
25    /// The lower bits encode the chunk type (data, metadata, system) and the
26    /// upper bits encode the RAID profile. A `Display` implementation formats
27    /// the flags as `"<type>, <profile>"`, matching the output of
28    /// `btrfs filesystem df`.
29    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30    pub struct BlockGroupFlags: u64 {
31        // --- chunk types ---
32        const DATA            = BTRFS_BLOCK_GROUP_DATA as u64;
33        const SYSTEM          = BTRFS_BLOCK_GROUP_SYSTEM as u64;
34        const METADATA        = BTRFS_BLOCK_GROUP_METADATA as u64;
35
36        // --- RAID profiles ---
37        const RAID0           = BTRFS_BLOCK_GROUP_RAID0 as u64;
38        const RAID1           = BTRFS_BLOCK_GROUP_RAID1 as u64;
39        const DUP             = BTRFS_BLOCK_GROUP_DUP as u64;
40        const RAID10          = BTRFS_BLOCK_GROUP_RAID10 as u64;
41        const RAID5           = BTRFS_BLOCK_GROUP_RAID5 as u64;
42        const RAID6           = BTRFS_BLOCK_GROUP_RAID6 as u64;
43        const RAID1C3         = BTRFS_BLOCK_GROUP_RAID1C3 as u64;
44        const RAID1C4         = BTRFS_BLOCK_GROUP_RAID1C4 as u64;
45
46        // AVAIL_ALLOC_BIT_SINGLE is the explicit "single" marker (bit 48).
47        // When no profile bits are set the allocation is also single.
48        const SINGLE          = BTRFS_AVAIL_ALLOC_BIT_SINGLE;
49
50        // Pseudo-type used for the global reservation pool.
51        const GLOBAL_RSV      = BTRFS_SPACE_INFO_GLOBAL_RSV;
52    }
53}
54
55impl BlockGroupFlags {
56    /// Returns the human-readable chunk type name.
57    pub fn type_name(self) -> &'static str {
58        if self.contains(Self::GLOBAL_RSV) {
59            return "GlobalReserve";
60        }
61        let ty = self & (Self::DATA | Self::SYSTEM | Self::METADATA);
62        match ty {
63            t if t == Self::DATA => "Data",
64            t if t == Self::SYSTEM => "System",
65            t if t == Self::METADATA => "Metadata",
66            t if t == Self::DATA | Self::METADATA => "Data+Metadata",
67            _ => "unknown",
68        }
69    }
70
71    /// Returns the human-readable RAID profile name.
72    pub fn profile_name(self) -> &'static str {
73        let profile = self
74            & (Self::RAID0
75                | Self::RAID1
76                | Self::DUP
77                | Self::RAID10
78                | Self::RAID5
79                | Self::RAID6
80                | Self::RAID1C3
81                | Self::RAID1C4
82                | Self::SINGLE);
83        match profile {
84            p if p == Self::RAID0 => "RAID0",
85            p if p == Self::RAID1 => "RAID1",
86            p if p == Self::DUP => "DUP",
87            p if p == Self::RAID10 => "RAID10",
88            p if p == Self::RAID5 => "RAID5",
89            p if p == Self::RAID6 => "RAID6",
90            p if p == Self::RAID1C3 => "RAID1C3",
91            p if p == Self::RAID1C4 => "RAID1C4",
92            // Both explicit SINGLE and no-profile-bits mean "single".
93            _ => "single",
94        }
95    }
96}
97
98impl fmt::Display for BlockGroupFlags {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        write!(f, "{}, {}", self.type_name(), self.profile_name())
101    }
102}
103
104/// Space usage information for one block group type/profile combination.
105///
106/// Returned by [`space_info`]. The `flags` field describes the chunk type and
107/// RAID profile; `total_bytes` and `used_bytes` are the allocated and in-use
108/// byte counts respectively.
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub struct SpaceInfo {
111    pub flags: BlockGroupFlags,
112    pub total_bytes: u64,
113    pub used_bytes: u64,
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    // --- type_name ---
121
122    #[test]
123    fn type_name_data() {
124        assert_eq!(BlockGroupFlags::DATA.type_name(), "Data");
125    }
126
127    #[test]
128    fn type_name_metadata() {
129        assert_eq!(BlockGroupFlags::METADATA.type_name(), "Metadata");
130    }
131
132    #[test]
133    fn type_name_system() {
134        assert_eq!(BlockGroupFlags::SYSTEM.type_name(), "System");
135    }
136
137    #[test]
138    fn type_name_data_metadata() {
139        let flags = BlockGroupFlags::DATA | BlockGroupFlags::METADATA;
140        assert_eq!(flags.type_name(), "Data+Metadata");
141    }
142
143    #[test]
144    fn type_name_global_rsv() {
145        assert_eq!(BlockGroupFlags::GLOBAL_RSV.type_name(), "GlobalReserve");
146    }
147
148    #[test]
149    fn type_name_global_rsv_takes_precedence() {
150        let flags = BlockGroupFlags::GLOBAL_RSV | BlockGroupFlags::METADATA;
151        assert_eq!(flags.type_name(), "GlobalReserve");
152    }
153
154    // --- profile_name ---
155
156    #[test]
157    fn profile_name_single_no_bits() {
158        assert_eq!(BlockGroupFlags::DATA.profile_name(), "single");
159    }
160
161    #[test]
162    fn profile_name_single_explicit() {
163        assert_eq!(
164            (BlockGroupFlags::DATA | BlockGroupFlags::SINGLE).profile_name(),
165            "single"
166        );
167    }
168
169    #[test]
170    fn profile_name_raid0() {
171        assert_eq!(
172            (BlockGroupFlags::DATA | BlockGroupFlags::RAID0).profile_name(),
173            "RAID0"
174        );
175    }
176
177    #[test]
178    fn profile_name_raid1() {
179        assert_eq!(
180            (BlockGroupFlags::DATA | BlockGroupFlags::RAID1).profile_name(),
181            "RAID1"
182        );
183    }
184
185    #[test]
186    fn profile_name_dup() {
187        assert_eq!(
188            (BlockGroupFlags::METADATA | BlockGroupFlags::DUP).profile_name(),
189            "DUP"
190        );
191    }
192
193    #[test]
194    fn profile_name_raid10() {
195        assert_eq!(
196            (BlockGroupFlags::DATA | BlockGroupFlags::RAID10).profile_name(),
197            "RAID10"
198        );
199    }
200
201    #[test]
202    fn profile_name_raid5() {
203        assert_eq!(
204            (BlockGroupFlags::DATA | BlockGroupFlags::RAID5).profile_name(),
205            "RAID5"
206        );
207    }
208
209    #[test]
210    fn profile_name_raid6() {
211        assert_eq!(
212            (BlockGroupFlags::DATA | BlockGroupFlags::RAID6).profile_name(),
213            "RAID6"
214        );
215    }
216
217    #[test]
218    fn profile_name_raid1c3() {
219        assert_eq!(
220            (BlockGroupFlags::DATA | BlockGroupFlags::RAID1C3).profile_name(),
221            "RAID1C3"
222        );
223    }
224
225    #[test]
226    fn profile_name_raid1c4() {
227        assert_eq!(
228            (BlockGroupFlags::DATA | BlockGroupFlags::RAID1C4).profile_name(),
229            "RAID1C4"
230        );
231    }
232
233    // --- Display ---
234
235    #[test]
236    fn display_data_single() {
237        assert_eq!(format!("{}", BlockGroupFlags::DATA), "Data, single");
238    }
239
240    #[test]
241    fn display_metadata_dup() {
242        let flags = BlockGroupFlags::METADATA | BlockGroupFlags::DUP;
243        assert_eq!(format!("{flags}"), "Metadata, DUP");
244    }
245
246    #[test]
247    fn display_system_raid1() {
248        let flags = BlockGroupFlags::SYSTEM | BlockGroupFlags::RAID1;
249        assert_eq!(format!("{flags}"), "System, RAID1");
250    }
251
252    #[test]
253    fn display_global_rsv() {
254        let flags = BlockGroupFlags::GLOBAL_RSV | BlockGroupFlags::METADATA;
255        assert_eq!(format!("{flags}"), "GlobalReserve, single");
256    }
257
258    // --- from_bits_truncate ---
259
260    #[test]
261    fn from_bits_preserves_known_flags() {
262        let raw =
263            BTRFS_BLOCK_GROUP_DATA as u64 | BTRFS_BLOCK_GROUP_RAID1 as u64;
264        let flags = BlockGroupFlags::from_bits_truncate(raw);
265        assert!(flags.contains(BlockGroupFlags::DATA));
266        assert!(flags.contains(BlockGroupFlags::RAID1));
267    }
268}
269
270impl From<btrfs_ioctl_space_info> for SpaceInfo {
271    fn from(raw: btrfs_ioctl_space_info) -> Self {
272        Self {
273            flags: BlockGroupFlags::from_bits_truncate(raw.flags),
274            total_bytes: raw.total_bytes,
275            used_bytes: raw.used_bytes,
276        }
277    }
278}
279
280/// Query space usage by block group type for the filesystem referred to by
281/// `fd`.
282///
283/// Returns one [`SpaceInfo`] entry per block group type/profile combination.
284///
285/// Uses a two-phase ioctl call: the first call with `space_slots = 0`
286/// retrieves the entry count, and the second call retrieves all entries.
287pub fn space_info(fd: BorrowedFd) -> nix::Result<Vec<SpaceInfo>> {
288    // Phase 1: query with space_slots = 0 to discover the number of entries.
289    let mut args: btrfs_ioctl_space_args = unsafe { mem::zeroed() };
290    unsafe { btrfs_ioc_space_info(fd.as_raw_fd(), &mut args) }?;
291    let count = args.total_spaces as usize;
292
293    if count == 0 {
294        return Ok(Vec::new());
295    }
296
297    // Phase 2: allocate a buffer large enough to hold the header plus all
298    // entries, then call again with space_slots set to the count.
299    //
300    // We use Vec<u64> rather than Vec<u8> to guarantee 8-byte alignment,
301    // matching the alignment requirement of btrfs_ioctl_space_args.
302    let base_size = mem::size_of::<btrfs_ioctl_space_args>();
303    let info_size = mem::size_of::<btrfs_ioctl_space_info>();
304    let total_bytes = base_size + count * info_size;
305    let num_u64s = total_bytes.div_ceil(mem::size_of::<u64>());
306    let mut buf = vec![0u64; num_u64s];
307
308    // SAFETY: buf is correctly sized and aligned for btrfs_ioctl_space_args.
309    // We write space_slots before the ioctl and read spaces[] only after the
310    // ioctl has populated them, keeping everything within the allocation.
311    unsafe {
312        let args_ptr = buf.as_mut_ptr() as *mut btrfs_ioctl_space_args;
313        (*args_ptr).space_slots = count as u64;
314        btrfs_ioc_space_info(fd.as_raw_fd(), &mut *args_ptr)?;
315        Ok((*args_ptr)
316            .spaces
317            .as_slice(count)
318            .iter()
319            .copied()
320            .map(SpaceInfo::from)
321            .collect())
322    }
323}