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 nix::libc::c_int;
13use std::os::fd::{AsRawFd, BorrowedFd};
14
15bitflags! {
16 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
22 pub struct CompatRoFlags: u64 {
23 const FREE_SPACE_TREE =
24 crate::raw::BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE as u64;
25 const FREE_SPACE_TREE_VALID =
26 crate::raw::BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE_VALID as u64;
27 const VERITY =
28 crate::raw::BTRFS_FEATURE_COMPAT_RO_VERITY as u64;
29 const BLOCK_GROUP_TREE =
30 crate::raw::BTRFS_FEATURE_COMPAT_RO_BLOCK_GROUP_TREE as u64;
31 }
32}
33
34bitflags! {
35 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
40 pub struct IncompatFlags: u64 {
41 const MIXED_BACKREF =
42 crate::raw::BTRFS_FEATURE_INCOMPAT_MIXED_BACKREF as u64;
43 const DEFAULT_SUBVOL =
44 crate::raw::BTRFS_FEATURE_INCOMPAT_DEFAULT_SUBVOL as u64;
45 const MIXED_GROUPS =
46 crate::raw::BTRFS_FEATURE_INCOMPAT_MIXED_GROUPS as u64;
47 const COMPRESS_LZO =
48 crate::raw::BTRFS_FEATURE_INCOMPAT_COMPRESS_LZO as u64;
49 const COMPRESS_ZSTD =
50 crate::raw::BTRFS_FEATURE_INCOMPAT_COMPRESS_ZSTD as u64;
51 const BIG_METADATA =
52 crate::raw::BTRFS_FEATURE_INCOMPAT_BIG_METADATA as u64;
53 const EXTENDED_IREF =
54 crate::raw::BTRFS_FEATURE_INCOMPAT_EXTENDED_IREF as u64;
55 const RAID56 =
56 crate::raw::BTRFS_FEATURE_INCOMPAT_RAID56 as u64;
57 const SKINNY_METADATA =
58 crate::raw::BTRFS_FEATURE_INCOMPAT_SKINNY_METADATA as u64;
59 const NO_HOLES =
60 crate::raw::BTRFS_FEATURE_INCOMPAT_NO_HOLES as u64;
61 const METADATA_UUID =
62 crate::raw::BTRFS_FEATURE_INCOMPAT_METADATA_UUID as u64;
63 const RAID1C34 =
64 crate::raw::BTRFS_FEATURE_INCOMPAT_RAID1C34 as u64;
65 const ZONED =
66 crate::raw::BTRFS_FEATURE_INCOMPAT_ZONED as u64;
67 const EXTENT_TREE_V2 =
68 crate::raw::BTRFS_FEATURE_INCOMPAT_EXTENT_TREE_V2 as u64;
69 const RAID_STRIPE_TREE =
70 crate::raw::BTRFS_FEATURE_INCOMPAT_RAID_STRIPE_TREE as u64;
71 const SIMPLE_QUOTA =
72 crate::raw::BTRFS_FEATURE_INCOMPAT_SIMPLE_QUOTA as u64;
73 const REMAP_TREE =
74 crate::raw::BTRFS_FEATURE_INCOMPAT_REMAP_TREE as u64;
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub struct FeatureFlags {
81 pub compat_ro: CompatRoFlags,
83 pub incompat: IncompatFlags,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub struct SupportedFeatures {
94 pub compat_ro_supported: CompatRoFlags,
96 pub compat_ro_safe_set: CompatRoFlags,
98 pub compat_ro_safe_clear: CompatRoFlags,
100 pub incompat_supported: IncompatFlags,
102 pub incompat_safe_set: IncompatFlags,
104 pub incompat_safe_clear: IncompatFlags,
106}
107
108fn parse_feature_flags(raw: &btrfs_ioctl_feature_flags) -> FeatureFlags {
109 FeatureFlags {
110 compat_ro: CompatRoFlags::from_bits_truncate(raw.compat_ro_flags),
111 incompat: IncompatFlags::from_bits_truncate(raw.incompat_flags),
112 }
113}
114
115pub fn get_features(fd: BorrowedFd<'_>) -> nix::Result<FeatureFlags> {
117 let mut flags: btrfs_ioctl_feature_flags = unsafe { std::mem::zeroed() };
118 unsafe { btrfs_ioc_get_features(fd.as_raw_fd() as c_int, &mut flags) }?;
119 Ok(parse_feature_flags(&flags))
120}
121
122pub fn set_features(
134 fd: BorrowedFd<'_>,
135 flags: &FeatureFlags,
136 mask: &FeatureFlags,
137) -> nix::Result<()> {
138 let mut buf: [btrfs_ioctl_feature_flags; 2] = unsafe { std::mem::zeroed() };
139 buf[0].compat_ro_flags = flags.compat_ro.bits();
140 buf[0].incompat_flags = flags.incompat.bits();
141 buf[1].compat_ro_flags = mask.compat_ro.bits();
142 buf[1].incompat_flags = mask.incompat.bits();
143 unsafe { btrfs_ioc_set_features(fd.as_raw_fd() as c_int, &buf) }?;
144 Ok(())
145}
146
147pub fn get_supported_features(
153 fd: BorrowedFd<'_>,
154) -> nix::Result<SupportedFeatures> {
155 let mut buf: [btrfs_ioctl_feature_flags; 3] = unsafe { std::mem::zeroed() };
156 unsafe {
157 btrfs_ioc_get_supported_features(fd.as_raw_fd() as c_int, &mut buf)
158 }?;
159
160 Ok(SupportedFeatures {
161 compat_ro_supported: CompatRoFlags::from_bits_truncate(
162 buf[0].compat_ro_flags,
163 ),
164 compat_ro_safe_set: CompatRoFlags::from_bits_truncate(
165 buf[1].compat_ro_flags,
166 ),
167 compat_ro_safe_clear: CompatRoFlags::from_bits_truncate(
168 buf[2].compat_ro_flags,
169 ),
170 incompat_supported: IncompatFlags::from_bits_truncate(
171 buf[0].incompat_flags,
172 ),
173 incompat_safe_set: IncompatFlags::from_bits_truncate(
174 buf[1].incompat_flags,
175 ),
176 incompat_safe_clear: IncompatFlags::from_bits_truncate(
177 buf[2].incompat_flags,
178 ),
179 })
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn parse_feature_flags_empty() {
188 let raw = btrfs_ioctl_feature_flags {
189 compat_flags: 0,
190 compat_ro_flags: 0,
191 incompat_flags: 0,
192 };
193 let flags = parse_feature_flags(&raw);
194 assert!(flags.compat_ro.is_empty());
195 assert!(flags.incompat.is_empty());
196 }
197
198 #[test]
199 fn parse_feature_flags_known_bits() {
200 let raw = btrfs_ioctl_feature_flags {
201 compat_flags: 0,
202 compat_ro_flags: crate::raw::BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE
203 as u64,
204 incompat_flags: crate::raw::BTRFS_FEATURE_INCOMPAT_NO_HOLES as u64
205 | crate::raw::BTRFS_FEATURE_INCOMPAT_SKINNY_METADATA as u64,
206 };
207 let flags = parse_feature_flags(&raw);
208 assert!(flags.compat_ro.contains(CompatRoFlags::FREE_SPACE_TREE));
209 assert!(flags.incompat.contains(IncompatFlags::NO_HOLES));
210 assert!(flags.incompat.contains(IncompatFlags::SKINNY_METADATA));
211 }
212
213 #[test]
214 fn parse_feature_flags_truncates_unknown() {
215 let raw = btrfs_ioctl_feature_flags {
216 compat_flags: 0,
217 compat_ro_flags: 0xFFFF_FFFF_FFFF_FFFF,
218 incompat_flags: 0xFFFF_FFFF_FFFF_FFFF,
219 };
220 let flags = parse_feature_flags(&raw);
221 assert!(!flags.compat_ro.is_empty());
223 assert!(!flags.incompat.is_empty());
224 }
225
226 #[test]
227 fn feature_flags_equality() {
228 let a = FeatureFlags {
229 compat_ro: CompatRoFlags::FREE_SPACE_TREE,
230 incompat: IncompatFlags::NO_HOLES,
231 };
232 let b = FeatureFlags {
233 compat_ro: CompatRoFlags::FREE_SPACE_TREE,
234 incompat: IncompatFlags::NO_HOLES,
235 };
236 assert_eq!(a, b);
237 }
238
239 #[test]
240 fn feature_flags_inequality() {
241 let a = FeatureFlags {
242 compat_ro: CompatRoFlags::FREE_SPACE_TREE,
243 incompat: IncompatFlags::NO_HOLES,
244 };
245 let b = FeatureFlags {
246 compat_ro: CompatRoFlags::empty(),
247 incompat: IncompatFlags::NO_HOLES,
248 };
249 assert_ne!(a, b);
250 }
251
252 #[test]
253 fn compat_ro_flags_debug() {
254 let flags = CompatRoFlags::FREE_SPACE_TREE
255 | CompatRoFlags::FREE_SPACE_TREE_VALID;
256 let s = format!("{flags:?}");
257 assert!(s.contains("FREE_SPACE_TREE"), "debug: {s}");
258 assert!(s.contains("FREE_SPACE_TREE_VALID"), "debug: {s}");
259 }
260
261 #[test]
262 fn incompat_flags_debug() {
263 let flags = IncompatFlags::NO_HOLES | IncompatFlags::ZONED;
264 let s = format!("{flags:?}");
265 assert!(s.contains("NO_HOLES"), "debug: {s}");
266 assert!(s.contains("ZONED"), "debug: {s}");
267 }
268
269 #[test]
270 fn supported_features_struct_fields() {
271 let supported = SupportedFeatures {
272 compat_ro_supported: CompatRoFlags::FREE_SPACE_TREE,
273 compat_ro_safe_set: CompatRoFlags::empty(),
274 compat_ro_safe_clear: CompatRoFlags::empty(),
275 incompat_supported: IncompatFlags::NO_HOLES,
276 incompat_safe_set: IncompatFlags::empty(),
277 incompat_safe_clear: IncompatFlags::empty(),
278 };
279 assert!(
280 supported
281 .compat_ro_supported
282 .contains(CompatRoFlags::FREE_SPACE_TREE)
283 );
284 assert!(
285 supported
286 .incompat_supported
287 .contains(IncompatFlags::NO_HOLES)
288 );
289 assert!(supported.incompat_safe_set.is_empty());
290 }
291}