Skip to main content

btrfs_uapi/
balance.rs

1//! # Balance operations: redistributing data and metadata across devices
2//!
3//! A balance rewrites chunks across the filesystem's devices according to
4//! optional filter criteria (usage threshold, device selection, profile
5//! conversion). It is also used to change the RAID profile of a block group
6//! type, or to reduce the number of devices in a filesystem.
7//!
8//! Requires `CAP_SYS_ADMIN`.
9
10use crate::raw::*;
11use bitflags::bitflags;
12use nix::libc::c_int;
13use std::os::{fd::AsRawFd, unix::io::BorrowedFd};
14
15bitflags! {
16    /// Top-level flags for a balance operation (`btrfs_ioctl_balance_args.flags`).
17    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
18    pub struct BalanceFlags: u64 {
19        /// Balance data chunks.
20        const DATA     = BTRFS_BALANCE_DATA as u64;
21        /// Balance system chunks.
22        const SYSTEM   = BTRFS_BALANCE_SYSTEM as u64;
23        /// Balance metadata chunks.
24        const METADATA = BTRFS_BALANCE_METADATA as u64;
25        /// Force a balance even if the device is busy.
26        const FORCE    = BTRFS_BALANCE_FORCE as u64;
27        /// Resume a previously paused balance.
28        const RESUME   = BTRFS_BALANCE_RESUME as u64;
29    }
30}
31
32bitflags! {
33    /// Per-chunk-type filter flags (`btrfs_balance_args.flags`).
34    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
35    pub struct BalanceArgsFlags: u64 {
36        /// Filter by chunk profiles.
37        const PROFILES       = BTRFS_BALANCE_ARGS_PROFILES as u64;
38        /// Filter by usage (single value, 0..N percent).
39        const USAGE          = BTRFS_BALANCE_ARGS_USAGE as u64;
40        /// Filter by usage range (min..max percent).
41        const USAGE_RANGE    = BTRFS_BALANCE_ARGS_USAGE_RANGE as u64;
42        /// Filter by device ID.
43        const DEVID          = BTRFS_BALANCE_ARGS_DEVID as u64;
44        /// Filter by physical byte range on device.
45        const DRANGE         = BTRFS_BALANCE_ARGS_DRANGE as u64;
46        /// Filter by virtual byte range.
47        const VRANGE         = BTRFS_BALANCE_ARGS_VRANGE as u64;
48        /// Limit number of chunks processed (single value).
49        const LIMIT          = BTRFS_BALANCE_ARGS_LIMIT as u64;
50        /// Limit number of chunks processed (min..max range).
51        const LIMIT_RANGE    = BTRFS_BALANCE_ARGS_LIMIT_RANGE as u64;
52        /// Filter by stripe count range.
53        const STRIPES_RANGE  = BTRFS_BALANCE_ARGS_STRIPES_RANGE as u64;
54        /// Convert chunks to a different profile.
55        const CONVERT        = BTRFS_BALANCE_ARGS_CONVERT as u64;
56        /// Soft convert: skip chunks already on the target profile.
57        const SOFT           = BTRFS_BALANCE_ARGS_SOFT as u64;
58    }
59}
60
61bitflags! {
62    /// State flags returned by the kernel (`btrfs_ioctl_balance_args.state`).
63    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
64    pub struct BalanceState: u64 {
65        /// A balance is currently running.
66        const RUNNING    = BTRFS_BALANCE_STATE_RUNNING as u64;
67        /// A pause has been requested.
68        const PAUSE_REQ  = BTRFS_BALANCE_STATE_PAUSE_REQ as u64;
69        /// A cancellation has been requested.
70        const CANCEL_REQ = BTRFS_BALANCE_STATE_CANCEL_REQ as u64;
71    }
72}
73
74/// Per-type filter arguments for a balance operation, corresponding to
75/// `btrfs_balance_args`.
76///
77/// Construct one with [`BalanceArgs::new`] and chain the setter methods to
78/// enable filters. Each setter automatically sets the corresponding flag bit.
79#[derive(Clone)]
80pub struct BalanceArgs {
81    raw: btrfs_balance_args,
82}
83
84impl std::fmt::Debug for BalanceArgs {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        // btrfs_balance_args is __attribute__((packed)), so we must copy fields
87        // to locals before taking references to them.
88        let flags = self.raw.flags;
89        let profiles = self.raw.profiles;
90        let devid = self.raw.devid;
91        let pstart = self.raw.pstart;
92        let pend = self.raw.pend;
93        let vstart = self.raw.vstart;
94        let vend = self.raw.vend;
95        let target = self.raw.target;
96        let stripes_min = self.raw.stripes_min;
97        let stripes_max = self.raw.stripes_max;
98        f.debug_struct("BalanceArgs")
99            .field("flags", &flags)
100            .field("profiles", &profiles)
101            .field("devid", &devid)
102            .field("pstart", &pstart)
103            .field("pend", &pend)
104            .field("vstart", &vstart)
105            .field("vend", &vend)
106            .field("target", &target)
107            .field("stripes_min", &stripes_min)
108            .field("stripes_max", &stripes_max)
109            .finish()
110    }
111}
112
113impl Default for BalanceArgs {
114    fn default() -> Self {
115        Self {
116            raw: unsafe { std::mem::zeroed() },
117        }
118    }
119}
120
121impl BalanceArgs {
122    /// Create a new `BalanceArgs` with no filters enabled.
123    pub fn new() -> Self {
124        Self::default()
125    }
126
127    /// Filter by chunk profile bitmask. The value is a bitmask of
128    /// `BTRFS_BLOCK_GROUP_*` profile flags.
129    pub fn profiles(mut self, profiles: u64) -> Self {
130        self.raw.profiles = profiles;
131        self.raw.flags |= BalanceArgsFlags::PROFILES.bits();
132        self
133    }
134
135    /// Filter chunks whose usage is below `percent` (0..100).
136    pub fn usage(mut self, percent: u64) -> Self {
137        // SAFETY: the union field `usage` and `usage_min`/`usage_max` overlap;
138        // setting `usage` covers the whole 8-byte field.
139        self.raw.__bindgen_anon_1.usage = percent;
140        self.raw.flags |= BalanceArgsFlags::USAGE.bits();
141        self
142    }
143
144    /// Filter chunks whose usage falls in `min..=max` percent.
145    pub fn usage_range(mut self, min: u32, max: u32) -> Self {
146        self.raw.__bindgen_anon_1.__bindgen_anon_1.usage_min = min;
147        self.raw.__bindgen_anon_1.__bindgen_anon_1.usage_max = max;
148        self.raw.flags |= BalanceArgsFlags::USAGE_RANGE.bits();
149        self
150    }
151
152    /// Filter chunks that reside on the given device ID.
153    pub fn devid(mut self, devid: u64) -> Self {
154        self.raw.devid = devid;
155        self.raw.flags |= BalanceArgsFlags::DEVID.bits();
156        self
157    }
158
159    /// Filter chunks whose physical range on-disk overlaps `start..end`.
160    pub fn drange(mut self, start: u64, end: u64) -> Self {
161        self.raw.pstart = start;
162        self.raw.pend = end;
163        self.raw.flags |= BalanceArgsFlags::DRANGE.bits();
164        self
165    }
166
167    /// Filter chunks whose virtual address range overlaps `start..end`.
168    pub fn vrange(mut self, start: u64, end: u64) -> Self {
169        self.raw.vstart = start;
170        self.raw.vend = end;
171        self.raw.flags |= BalanceArgsFlags::VRANGE.bits();
172        self
173    }
174
175    /// Process at most `limit` chunks.
176    pub fn limit(mut self, limit: u64) -> Self {
177        self.raw.__bindgen_anon_2.limit = limit;
178        self.raw.flags |= BalanceArgsFlags::LIMIT.bits();
179        self
180    }
181
182    /// Process between `min` and `max` chunks.
183    pub fn limit_range(mut self, min: u32, max: u32) -> Self {
184        self.raw.__bindgen_anon_2.__bindgen_anon_1.limit_min = min;
185        self.raw.__bindgen_anon_2.__bindgen_anon_1.limit_max = max;
186        self.raw.flags |= BalanceArgsFlags::LIMIT_RANGE.bits();
187        self
188    }
189
190    /// Filter chunks that span between `min` and `max` stripes.
191    pub fn stripes_range(mut self, min: u32, max: u32) -> Self {
192        self.raw.stripes_min = min;
193        self.raw.stripes_max = max;
194        self.raw.flags |= BalanceArgsFlags::STRIPES_RANGE.bits();
195        self
196    }
197
198    /// Convert balanced chunks to the given profile.
199    pub fn convert(mut self, profile: u64) -> Self {
200        self.raw.target = profile;
201        self.raw.flags |= BalanceArgsFlags::CONVERT.bits();
202        self
203    }
204
205    /// When converting, skip chunks already on the target profile.
206    pub fn soft(mut self) -> Self {
207        self.raw.flags |= BalanceArgsFlags::SOFT.bits();
208        self
209    }
210}
211
212/// Progress counters returned by the kernel for an in-progress balance.
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214pub struct BalanceProgress {
215    /// Estimated number of chunks that will be relocated.
216    pub expected: u64,
217    /// Number of chunks considered so far.
218    pub considered: u64,
219    /// Number of chunks relocated so far.
220    pub completed: u64,
221}
222
223/// Control command for [`balance_ctl`].
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
225pub enum BalanceCtl {
226    /// Pause the running balance at the next safe point.
227    Pause,
228    /// Cancel the running balance.
229    Cancel,
230}
231
232impl BalanceCtl {
233    fn as_raw(self) -> c_int {
234        match self {
235            BalanceCtl::Pause => BTRFS_BALANCE_CTL_PAUSE as c_int,
236            BalanceCtl::Cancel => BTRFS_BALANCE_CTL_CANCEL as c_int,
237        }
238    }
239}
240
241/// Start or resume a balance operation on the filesystem referred to by `fd`.
242///
243/// `flags` controls which chunk types are balanced and whether to force or
244/// resume. `data`, `meta`, and `sys` are optional per-type filter arguments;
245/// pass `None` to use defaults for that type.
246///
247/// On success, returns the progress counters as reported by the kernel after
248/// the operation completes (or is paused/interrupted).
249pub fn balance(
250    fd: BorrowedFd,
251    flags: BalanceFlags,
252    data: Option<BalanceArgs>,
253    meta: Option<BalanceArgs>,
254    sys: Option<BalanceArgs>,
255) -> nix::Result<BalanceProgress> {
256    let mut args: btrfs_ioctl_balance_args = unsafe { std::mem::zeroed() };
257
258    args.flags = flags.bits();
259
260    if let Some(a) = data {
261        args.data = a.raw;
262    }
263    if let Some(a) = meta {
264        args.meta = a.raw;
265    }
266    if let Some(a) = sys {
267        args.sys = a.raw;
268    }
269
270    unsafe {
271        btrfs_ioc_balance_v2(fd.as_raw_fd(), &mut args)?;
272    }
273
274    Ok(BalanceProgress {
275        expected: args.stat.expected,
276        considered: args.stat.considered,
277        completed: args.stat.completed,
278    })
279}
280
281/// Send a control command to a running balance operation on the filesystem
282/// referred to by `fd`.
283///
284/// Use [`BalanceCtl::Pause`] to pause or [`BalanceCtl::Cancel`] to cancel.
285pub fn balance_ctl(fd: BorrowedFd, cmd: BalanceCtl) -> nix::Result<()> {
286    unsafe {
287        btrfs_ioc_balance_ctl(fd.as_raw_fd(), cmd.as_raw() as u64)?;
288    }
289    Ok(())
290}
291
292/// Query the current balance state and progress on the filesystem referred to
293/// by `fd`.
294///
295/// Returns a [`BalanceState`] bitflags value indicating whether a balance is
296/// running, paused, or being cancelled, along with a [`BalanceProgress`] with
297/// the current counters.
298pub fn balance_progress(fd: BorrowedFd) -> nix::Result<(BalanceState, BalanceProgress)> {
299    let mut args: btrfs_ioctl_balance_args = unsafe { std::mem::zeroed() };
300
301    unsafe {
302        btrfs_ioc_balance_progress(fd.as_raw_fd(), &mut args)?;
303    }
304
305    let state = BalanceState::from_bits_truncate(args.state);
306    let progress = BalanceProgress {
307        expected: args.stat.expected,
308        considered: args.stat.considered,
309        completed: args.stat.completed,
310    };
311
312    Ok((state, progress))
313}