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}