1use 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 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29 pub struct BlockGroupFlags: u64 {
30 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 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 const SINGLE = BTRFS_AVAIL_ALLOC_BIT_SINGLE;
48
49 const GLOBAL_RSV = BTRFS_SPACE_INFO_GLOBAL_RSV;
51 }
52}
53
54impl BlockGroupFlags {
55 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 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 _ => "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#[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 #[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 #[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 #[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 #[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
278pub fn space_info(fd: BorrowedFd) -> nix::Result<Vec<SpaceInfo>> {
286 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 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 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}