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 #[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 #[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 _ => "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#[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 #[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 #[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 #[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 #[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
282pub fn space_info(fd: BorrowedFd) -> nix::Result<Vec<SpaceInfo>> {
294 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 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 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}