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