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_ioc_space_info, btrfs_ioctl_space_args, btrfs_ioctl_space_info,
9};
10pub use btrfs_disk::items::BlockGroupFlags;
11use std::{
12    mem,
13    os::{fd::AsRawFd, unix::io::BorrowedFd},
14};
15
16/// Space usage information for one block group type/profile combination.
17///
18/// Returned by [`space_info`]. The `flags` field describes the chunk type and
19/// RAID profile; `total_bytes` and `used_bytes` are the allocated and in-use
20/// byte counts respectively.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct SpaceInfo {
23    /// Block group type and RAID profile flags for this space category.
24    pub flags: BlockGroupFlags,
25    /// Total bytes allocated to block groups of this type/profile.
26    pub total_bytes: u64,
27    /// Bytes actually in use within those block groups.
28    pub used_bytes: u64,
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    #[test]
36    fn from_bits_preserves_known_flags() {
37        let raw = BlockGroupFlags::DATA.bits() | BlockGroupFlags::RAID1.bits();
38        let flags = BlockGroupFlags::from_bits_truncate(raw);
39        assert!(flags.contains(BlockGroupFlags::DATA));
40        assert!(flags.contains(BlockGroupFlags::RAID1));
41    }
42}
43
44impl From<btrfs_ioctl_space_info> for SpaceInfo {
45    fn from(raw: btrfs_ioctl_space_info) -> Self {
46        Self {
47            flags: BlockGroupFlags::from_bits_truncate(raw.flags),
48            total_bytes: raw.total_bytes,
49            used_bytes: raw.used_bytes,
50        }
51    }
52}
53
54/// Query space usage by block group type for the filesystem referred to by
55/// `fd`.
56///
57/// Returns one [`SpaceInfo`] entry per block group type/profile combination.
58///
59/// Uses a two-phase ioctl call: the first call with `space_slots = 0`
60/// retrieves the entry count, and the second call retrieves all entries.
61/// The entry count can change between calls if the kernel allocates new
62/// block groups concurrently; this is benign (the kernel fills at most
63/// `space_slots` entries and the second call will simply return fewer
64/// than expected).
65///
66/// # Errors
67///
68/// Returns `Err` if the ioctl fails.
69#[allow(clippy::cast_possible_truncation)] // space count always fits in usize
70pub fn space_info(fd: BorrowedFd) -> nix::Result<Vec<SpaceInfo>> {
71    // Phase 1: query with space_slots = 0 to discover the number of entries.
72    let mut args: btrfs_ioctl_space_args = unsafe { mem::zeroed() };
73    unsafe { btrfs_ioc_space_info(fd.as_raw_fd(), &raw mut args) }?;
74    let count = args.total_spaces as usize;
75
76    if count == 0 {
77        return Ok(Vec::new());
78    }
79
80    // Phase 2: allocate a buffer large enough to hold the header plus all
81    // entries, then call again with space_slots set to the count.
82    //
83    // We use Vec<u64> rather than Vec<u8> to guarantee 8-byte alignment,
84    // matching the alignment requirement of btrfs_ioctl_space_args.
85    let base_size = mem::size_of::<btrfs_ioctl_space_args>();
86    let info_size = mem::size_of::<btrfs_ioctl_space_info>();
87    let total_bytes = base_size + count * info_size;
88    let num_u64s = total_bytes.div_ceil(mem::size_of::<u64>());
89    let mut buf = vec![0u64; num_u64s];
90
91    // SAFETY: buf is correctly sized and aligned for btrfs_ioctl_space_args.
92    // We write space_slots before the ioctl and read spaces[] only after the
93    // ioctl has populated them, keeping everything within the allocation.
94    unsafe {
95        let args_ptr = buf.as_mut_ptr().cast::<btrfs_ioctl_space_args>();
96        (*args_ptr).space_slots = count as u64;
97        btrfs_ioc_space_info(fd.as_raw_fd(), &raw mut *args_ptr)?;
98        Ok((*args_ptr)
99            .spaces
100            .as_slice(count)
101            .iter()
102            .copied()
103            .map(SpaceInfo::from)
104            .collect())
105    }
106}