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