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