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        /// Free space is tracked via a dedicated B-tree instead of bitmaps.
23        const FREE_SPACE_TREE =
24            crate::raw::BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE as u64;
25        /// The free space tree has been fully built and is valid.
26        const FREE_SPACE_TREE_VALID =
27            crate::raw::BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE_VALID as u64;
28        /// fs-verity support for individual files.
29        const VERITY =
30            crate::raw::BTRFS_FEATURE_COMPAT_RO_VERITY as u64;
31        /// Block groups are tracked in a dedicated B-tree for faster mount.
32        const BLOCK_GROUP_TREE =
33            crate::raw::BTRFS_FEATURE_COMPAT_RO_BLOCK_GROUP_TREE as u64;
34    }
35}
36
37bitflags! {
38    /// Incompatible feature flags (`incompat_flags`).
39    ///
40    /// A filesystem with an incompat flag set cannot be mounted by a kernel
41    /// that does not understand that flag.
42    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
43    pub struct IncompatFlags: u64 {
44        /// New-style backrefs with parent objectid and generation in the key.
45        const MIXED_BACKREF =
46            crate::raw::BTRFS_FEATURE_INCOMPAT_MIXED_BACKREF as u64;
47        /// A non-root subvolume has been set as the default mount target.
48        const DEFAULT_SUBVOL =
49            crate::raw::BTRFS_FEATURE_INCOMPAT_DEFAULT_SUBVOL as u64;
50        /// Data and metadata may share the same block group.
51        const MIXED_GROUPS =
52            crate::raw::BTRFS_FEATURE_INCOMPAT_MIXED_GROUPS as u64;
53        /// LZO compression is in use on the filesystem.
54        const COMPRESS_LZO =
55            crate::raw::BTRFS_FEATURE_INCOMPAT_COMPRESS_LZO as u64;
56        /// Zstandard compression is in use on the filesystem.
57        const COMPRESS_ZSTD =
58            crate::raw::BTRFS_FEATURE_INCOMPAT_COMPRESS_ZSTD as u64;
59        /// Metadata blocks larger than the page size are in use.
60        const BIG_METADATA =
61            crate::raw::BTRFS_FEATURE_INCOMPAT_BIG_METADATA as u64;
62        /// Extended inode refs allow more than 65536 hardlinks per inode.
63        const EXTENDED_IREF =
64            crate::raw::BTRFS_FEATURE_INCOMPAT_EXTENDED_IREF as u64;
65        /// RAID5/6 block group profiles are in use.
66        const RAID56 =
67            crate::raw::BTRFS_FEATURE_INCOMPAT_RAID56 as u64;
68        /// Tree block references use the type directly instead of embedding a
69        /// level indirection (reduces metadata overhead).
70        const SKINNY_METADATA =
71            crate::raw::BTRFS_FEATURE_INCOMPAT_SKINNY_METADATA as u64;
72        /// File extent items for holes are omitted, saving metadata space.
73        const NO_HOLES =
74            crate::raw::BTRFS_FEATURE_INCOMPAT_NO_HOLES as u64;
75        /// The filesystem has a separate `metadata_uuid` that differs from the
76        /// main `fsid`, allowing fsid changes without rewriting every tree block.
77        const METADATA_UUID =
78            crate::raw::BTRFS_FEATURE_INCOMPAT_METADATA_UUID as u64;
79        /// RAID1 profiles with 3 or 4 copies are in use.
80        const RAID1C34 =
81            crate::raw::BTRFS_FEATURE_INCOMPAT_RAID1C34 as u64;
82        /// Zoned block device support is enabled.
83        const ZONED =
84            crate::raw::BTRFS_FEATURE_INCOMPAT_ZONED as u64;
85        /// Version 2 of the extent tree (experimental).
86        const EXTENT_TREE_V2 =
87            crate::raw::BTRFS_FEATURE_INCOMPAT_EXTENT_TREE_V2 as u64;
88        /// RAID stripe tree for logical-to-physical mapping on zoned devices.
89        const RAID_STRIPE_TREE =
90            crate::raw::BTRFS_FEATURE_INCOMPAT_RAID_STRIPE_TREE as u64;
91        /// Simplified quota accounting that avoids full backref walks.
92        const SIMPLE_QUOTA =
93            crate::raw::BTRFS_FEATURE_INCOMPAT_SIMPLE_QUOTA as u64;
94        /// Remap tree support (experimental).
95        const REMAP_TREE =
96            crate::raw::BTRFS_FEATURE_INCOMPAT_REMAP_TREE as u64;
97    }
98}
99
100/// The set of feature flags active on a mounted filesystem.
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub struct FeatureFlags {
103    /// Compatible read-only feature flags.
104    pub compat_ro: CompatRoFlags,
105    /// Incompatible feature flags.
106    pub incompat: IncompatFlags,
107}
108
109/// The feature flags supported by the running kernel.
110///
111/// Each category has three sets: `supported` (kernel understands the flag),
112/// `safe_set` (can be enabled at runtime), and `safe_clear` (can be disabled
113/// at runtime).
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub struct SupportedFeatures {
116    /// `compat_ro` flags the kernel understands.
117    pub compat_ro_supported: CompatRoFlags,
118    /// `compat_ro` flags that can be enabled at runtime.
119    pub compat_ro_safe_set: CompatRoFlags,
120    /// `compat_ro` flags that can be disabled at runtime.
121    pub compat_ro_safe_clear: CompatRoFlags,
122    /// Incompat flags the kernel understands.
123    pub incompat_supported: IncompatFlags,
124    /// Incompat flags that can be enabled at runtime.
125    pub incompat_safe_set: IncompatFlags,
126    /// Incompat flags that can be disabled at runtime.
127    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
137/// Query the feature flags currently active on the filesystem.
138///
139/// # Errors
140///
141/// Returns `Err` if the ioctl fails.
142pub 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
148/// Set or clear feature flags on the filesystem.
149///
150/// The `flags` argument specifies the desired values, and `mask` specifies
151/// which bits to change. Only bits set in `mask` are modified: they are set
152/// to the corresponding value in `flags`. Bits not in the mask are left
153/// unchanged.
154///
155/// Use `get_supported_features` first to check which flags can be safely
156/// set or cleared at runtime.
157///
158/// Requires `CAP_SYS_ADMIN`. Returns `EPERM` without it.
159///
160/// # Errors
161///
162/// Returns `Err` if the ioctl fails.
163pub 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
177/// Query the feature flags supported by the running kernel.
178///
179/// Returns three sets per category (`compat_ro`, incompat): which flags the
180/// kernel understands, which can be enabled at runtime, and which can be
181/// disabled at runtime.
182///
183/// # Errors
184///
185/// Returns `Err` if the ioctl fails.
186pub 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        // Should not panic; unknown bits are silently dropped.
254        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}