1use crate::raw::{
8 btrfs_ioc_get_features, btrfs_ioc_get_supported_features,
9 btrfs_ioc_set_features, btrfs_ioctl_feature_flags,
10};
11use bitflags::bitflags;
12use std::os::fd::{AsRawFd, BorrowedFd};
13
14bitflags! {
15 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
21 pub struct CompatRoFlags: u64 {
22 const FREE_SPACE_TREE =
24 crate::raw::BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE as u64;
25 const FREE_SPACE_TREE_VALID =
27 crate::raw::BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE_VALID as u64;
28 const VERITY =
30 crate::raw::BTRFS_FEATURE_COMPAT_RO_VERITY as u64;
31 const BLOCK_GROUP_TREE =
33 crate::raw::BTRFS_FEATURE_COMPAT_RO_BLOCK_GROUP_TREE as u64;
34 }
35}
36
37bitflags! {
38 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
43 pub struct IncompatFlags: u64 {
44 const MIXED_BACKREF =
46 crate::raw::BTRFS_FEATURE_INCOMPAT_MIXED_BACKREF as u64;
47 const DEFAULT_SUBVOL =
49 crate::raw::BTRFS_FEATURE_INCOMPAT_DEFAULT_SUBVOL as u64;
50 const MIXED_GROUPS =
52 crate::raw::BTRFS_FEATURE_INCOMPAT_MIXED_GROUPS as u64;
53 const COMPRESS_LZO =
55 crate::raw::BTRFS_FEATURE_INCOMPAT_COMPRESS_LZO as u64;
56 const COMPRESS_ZSTD =
58 crate::raw::BTRFS_FEATURE_INCOMPAT_COMPRESS_ZSTD as u64;
59 const BIG_METADATA =
61 crate::raw::BTRFS_FEATURE_INCOMPAT_BIG_METADATA as u64;
62 const EXTENDED_IREF =
64 crate::raw::BTRFS_FEATURE_INCOMPAT_EXTENDED_IREF as u64;
65 const RAID56 =
67 crate::raw::BTRFS_FEATURE_INCOMPAT_RAID56 as u64;
68 const SKINNY_METADATA =
71 crate::raw::BTRFS_FEATURE_INCOMPAT_SKINNY_METADATA as u64;
72 const NO_HOLES =
74 crate::raw::BTRFS_FEATURE_INCOMPAT_NO_HOLES as u64;
75 const METADATA_UUID =
78 crate::raw::BTRFS_FEATURE_INCOMPAT_METADATA_UUID as u64;
79 const RAID1C34 =
81 crate::raw::BTRFS_FEATURE_INCOMPAT_RAID1C34 as u64;
82 const ZONED =
84 crate::raw::BTRFS_FEATURE_INCOMPAT_ZONED as u64;
85 const EXTENT_TREE_V2 =
87 crate::raw::BTRFS_FEATURE_INCOMPAT_EXTENT_TREE_V2 as u64;
88 const RAID_STRIPE_TREE =
90 crate::raw::BTRFS_FEATURE_INCOMPAT_RAID_STRIPE_TREE as u64;
91 const SIMPLE_QUOTA =
93 crate::raw::BTRFS_FEATURE_INCOMPAT_SIMPLE_QUOTA as u64;
94 const REMAP_TREE =
96 crate::raw::BTRFS_FEATURE_INCOMPAT_REMAP_TREE as u64;
97 }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub struct FeatureFlags {
103 pub compat_ro: CompatRoFlags,
105 pub incompat: IncompatFlags,
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub struct SupportedFeatures {
116 pub compat_ro_supported: CompatRoFlags,
118 pub compat_ro_safe_set: CompatRoFlags,
120 pub compat_ro_safe_clear: CompatRoFlags,
122 pub incompat_supported: IncompatFlags,
124 pub incompat_safe_set: IncompatFlags,
126 pub incompat_safe_clear: IncompatFlags,
128}
129
130fn parse_feature_flags(raw: &btrfs_ioctl_feature_flags) -> FeatureFlags {
131 FeatureFlags {
132 compat_ro: CompatRoFlags::from_bits_truncate(raw.compat_ro_flags),
133 incompat: IncompatFlags::from_bits_truncate(raw.incompat_flags),
134 }
135}
136
137pub fn get_features(fd: BorrowedFd<'_>) -> nix::Result<FeatureFlags> {
143 let mut flags: btrfs_ioctl_feature_flags = unsafe { std::mem::zeroed() };
144 unsafe { btrfs_ioc_get_features(fd.as_raw_fd(), &raw mut flags) }?;
145 Ok(parse_feature_flags(&flags))
146}
147
148pub fn set_features(
164 fd: BorrowedFd<'_>,
165 flags: &FeatureFlags,
166 mask: &FeatureFlags,
167) -> nix::Result<()> {
168 let mut buf: [btrfs_ioctl_feature_flags; 2] = unsafe { std::mem::zeroed() };
169 buf[0].compat_ro_flags = flags.compat_ro.bits();
170 buf[0].incompat_flags = flags.incompat.bits();
171 buf[1].compat_ro_flags = mask.compat_ro.bits();
172 buf[1].incompat_flags = mask.incompat.bits();
173 unsafe { btrfs_ioc_set_features(fd.as_raw_fd(), &raw const buf) }?;
174 Ok(())
175}
176
177pub fn get_supported_features(
187 fd: BorrowedFd<'_>,
188) -> nix::Result<SupportedFeatures> {
189 let mut buf: [btrfs_ioctl_feature_flags; 3] = unsafe { std::mem::zeroed() };
190 unsafe { btrfs_ioc_get_supported_features(fd.as_raw_fd(), &raw mut buf) }?;
191
192 Ok(SupportedFeatures {
193 compat_ro_supported: CompatRoFlags::from_bits_truncate(
194 buf[0].compat_ro_flags,
195 ),
196 compat_ro_safe_set: CompatRoFlags::from_bits_truncate(
197 buf[1].compat_ro_flags,
198 ),
199 compat_ro_safe_clear: CompatRoFlags::from_bits_truncate(
200 buf[2].compat_ro_flags,
201 ),
202 incompat_supported: IncompatFlags::from_bits_truncate(
203 buf[0].incompat_flags,
204 ),
205 incompat_safe_set: IncompatFlags::from_bits_truncate(
206 buf[1].incompat_flags,
207 ),
208 incompat_safe_clear: IncompatFlags::from_bits_truncate(
209 buf[2].incompat_flags,
210 ),
211 })
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn parse_feature_flags_empty() {
220 let raw = btrfs_ioctl_feature_flags {
221 compat_flags: 0,
222 compat_ro_flags: 0,
223 incompat_flags: 0,
224 };
225 let flags = parse_feature_flags(&raw);
226 assert!(flags.compat_ro.is_empty());
227 assert!(flags.incompat.is_empty());
228 }
229
230 #[test]
231 fn parse_feature_flags_known_bits() {
232 let raw = btrfs_ioctl_feature_flags {
233 compat_flags: 0,
234 compat_ro_flags: crate::raw::BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE
235 as u64,
236 incompat_flags: crate::raw::BTRFS_FEATURE_INCOMPAT_NO_HOLES as u64
237 | crate::raw::BTRFS_FEATURE_INCOMPAT_SKINNY_METADATA as u64,
238 };
239 let flags = parse_feature_flags(&raw);
240 assert!(flags.compat_ro.contains(CompatRoFlags::FREE_SPACE_TREE));
241 assert!(flags.incompat.contains(IncompatFlags::NO_HOLES));
242 assert!(flags.incompat.contains(IncompatFlags::SKINNY_METADATA));
243 }
244
245 #[test]
246 fn parse_feature_flags_truncates_unknown() {
247 let raw = btrfs_ioctl_feature_flags {
248 compat_flags: 0,
249 compat_ro_flags: 0xFFFF_FFFF_FFFF_FFFF,
250 incompat_flags: 0xFFFF_FFFF_FFFF_FFFF,
251 };
252 let flags = parse_feature_flags(&raw);
253 assert!(!flags.compat_ro.is_empty());
255 assert!(!flags.incompat.is_empty());
256 }
257
258 #[test]
259 fn feature_flags_equality() {
260 let a = FeatureFlags {
261 compat_ro: CompatRoFlags::FREE_SPACE_TREE,
262 incompat: IncompatFlags::NO_HOLES,
263 };
264 let b = FeatureFlags {
265 compat_ro: CompatRoFlags::FREE_SPACE_TREE,
266 incompat: IncompatFlags::NO_HOLES,
267 };
268 assert_eq!(a, b);
269 }
270
271 #[test]
272 fn feature_flags_inequality() {
273 let a = FeatureFlags {
274 compat_ro: CompatRoFlags::FREE_SPACE_TREE,
275 incompat: IncompatFlags::NO_HOLES,
276 };
277 let b = FeatureFlags {
278 compat_ro: CompatRoFlags::empty(),
279 incompat: IncompatFlags::NO_HOLES,
280 };
281 assert_ne!(a, b);
282 }
283
284 #[test]
285 fn compat_ro_flags_debug() {
286 let flags = CompatRoFlags::FREE_SPACE_TREE
287 | CompatRoFlags::FREE_SPACE_TREE_VALID;
288 let s = format!("{flags:?}");
289 assert!(s.contains("FREE_SPACE_TREE"), "debug: {s}");
290 assert!(s.contains("FREE_SPACE_TREE_VALID"), "debug: {s}");
291 }
292
293 #[test]
294 fn incompat_flags_debug() {
295 let flags = IncompatFlags::NO_HOLES | IncompatFlags::ZONED;
296 let s = format!("{flags:?}");
297 assert!(s.contains("NO_HOLES"), "debug: {s}");
298 assert!(s.contains("ZONED"), "debug: {s}");
299 }
300
301 #[test]
302 fn supported_features_struct_fields() {
303 let supported = SupportedFeatures {
304 compat_ro_supported: CompatRoFlags::FREE_SPACE_TREE,
305 compat_ro_safe_set: CompatRoFlags::empty(),
306 compat_ro_safe_clear: CompatRoFlags::empty(),
307 incompat_supported: IncompatFlags::NO_HOLES,
308 incompat_safe_set: IncompatFlags::empty(),
309 incompat_safe_clear: IncompatFlags::empty(),
310 };
311 assert!(
312 supported
313 .compat_ro_supported
314 .contains(CompatRoFlags::FREE_SPACE_TREE)
315 );
316 assert!(
317 supported
318 .incompat_supported
319 .contains(IncompatFlags::NO_HOLES)
320 );
321 assert!(supported.incompat_safe_set.is_empty());
322 }
323}