Skip to main content

btrfs_uapi/
features.rs

1//! # Feature flags: querying and setting filesystem feature flags
2//!
3//! Wraps `BTRFS_IOC_GET_FEATURES`, `BTRFS_IOC_GET_SUPPORTED_FEATURES`,
4//! and `BTRFS_IOC_SET_FEATURES` to query and modify the feature flags of
5//! a mounted btrfs filesystem.
6
7use 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    /// Compatible read-only feature flags (`compat_ro_flags`).
17    ///
18    /// These features are backward-compatible for read operations: a kernel
19    /// that does not understand a compat_ro flag can still mount the
20    /// filesystem read-only.
21    #[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    /// Incompatible feature flags (`incompat_flags`).
36    ///
37    /// A filesystem with an incompat flag set cannot be mounted by a kernel
38    /// that does not understand that flag.
39    #[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/// The set of feature flags active on a mounted filesystem.
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub struct FeatureFlags {
81    /// Compatible read-only feature flags.
82    pub compat_ro: CompatRoFlags,
83    /// Incompatible feature flags.
84    pub incompat: IncompatFlags,
85}
86
87/// The feature flags supported by the running kernel.
88///
89/// Each category has three sets: `supported` (kernel understands the flag),
90/// `safe_set` (can be enabled at runtime), and `safe_clear` (can be disabled
91/// at runtime).
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub struct SupportedFeatures {
94    /// Compat_ro flags the kernel understands.
95    pub compat_ro_supported: CompatRoFlags,
96    /// Compat_ro flags that can be enabled at runtime.
97    pub compat_ro_safe_set: CompatRoFlags,
98    /// Compat_ro flags that can be disabled at runtime.
99    pub compat_ro_safe_clear: CompatRoFlags,
100    /// Incompat flags the kernel understands.
101    pub incompat_supported: IncompatFlags,
102    /// Incompat flags that can be enabled at runtime.
103    pub incompat_safe_set: IncompatFlags,
104    /// Incompat flags that can be disabled at runtime.
105    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
115/// Query the feature flags currently active on the filesystem.
116pub 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
122/// Set or clear feature flags on the filesystem.
123///
124/// The `flags` argument specifies the desired values, and `mask` specifies
125/// which bits to change. Only bits set in `mask` are modified: they are set
126/// to the corresponding value in `flags`. Bits not in the mask are left
127/// unchanged.
128///
129/// Use `get_supported_features` first to check which flags can be safely
130/// set or cleared at runtime.
131///
132/// Requires `CAP_SYS_ADMIN`. Returns `EPERM` without it.
133pub 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
147/// Query the feature flags supported by the running kernel.
148///
149/// Returns three sets per category (compat_ro, incompat): which flags the
150/// kernel understands, which can be enabled at runtime, and which can be
151/// disabled at runtime.
152pub 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        // Should not panic; unknown bits are silently dropped.
222        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}