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