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