Skip to main content

btrfs_uapi/
resize.rs

1//! # Device resizing: growing or shrinking a device within a mounted filesystem
2//!
3//! Resizing adjusts how much of a block device's capacity btrfs uses, without
4//! unmounting.  A device can be grown up to its physical size, shrunk to the
5//! minimum space currently occupied, or set to an explicit byte count.
6
7use crate::raw::{btrfs_ioc_resize, btrfs_ioctl_vol_args};
8use nix::libc::c_char;
9use std::{
10    mem,
11    os::{fd::AsRawFd, unix::io::BorrowedFd},
12};
13
14/// The target size for a resize operation.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ResizeAmount {
17    /// Cancel an in-progress resize.
18    Cancel,
19    /// Grow the device to its maximum available size.
20    Max,
21    /// Set the device to exactly this many bytes.
22    Set(u64),
23    /// Add this many bytes to the current device size.
24    Add(u64),
25    /// Subtract this many bytes from the current device size.
26    Sub(u64),
27}
28
29impl ResizeAmount {
30    fn to_string(&self) -> String {
31        match self {
32            Self::Cancel => "cancel".to_owned(),
33            Self::Max => "max".to_owned(),
34            Self::Set(n) => n.to_string(),
35            Self::Add(n) => format!("+{n}"),
36            Self::Sub(n) => format!("-{n}"),
37        }
38    }
39}
40
41/// Arguments for a resize operation.
42///
43/// `devid` selects which device within the filesystem to resize. When `None`,
44/// the kernel defaults to device ID 1 (the first device).
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct ResizeArgs {
47    pub devid: Option<u64>,
48    pub amount: ResizeAmount,
49}
50
51impl ResizeArgs {
52    pub fn new(amount: ResizeAmount) -> Self {
53        Self {
54            devid: None,
55            amount,
56        }
57    }
58
59    pub fn with_devid(mut self, devid: u64) -> Self {
60        self.devid = Some(devid);
61        self
62    }
63
64    /// Format into the string that `BTRFS_IOC_RESIZE` expects in
65    /// `btrfs_ioctl_vol_args.name`: `[<devid>:]<amount>`.
66    fn format_name(&self) -> String {
67        let amount = self.amount.to_string();
68        match self.devid {
69            Some(devid) => format!("{devid}:{amount}"),
70            None => amount,
71        }
72    }
73}
74
75/// Resize a device within the btrfs filesystem referred to by `fd`.
76///
77/// `fd` must be an open file descriptor to a directory on the mounted
78/// filesystem. Use [`ResizeArgs`] to specify the target device and amount.
79pub fn resize(fd: BorrowedFd, args: ResizeArgs) -> nix::Result<()> {
80    let name = args.format_name();
81    let name_bytes = name.as_bytes();
82
83    // BTRFS_PATH_NAME_MAX is 4087; the name field is [c_char; 4088].
84    // A well-formed resize string (devid + colon + u64 digits) is at most
85    // ~23 characters, so this can only fail if the caller constructs a
86    // pathological devid.
87    if name_bytes.len() >= 4088 {
88        return Err(nix::errno::Errno::EINVAL);
89    }
90
91    let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
92    for (i, &b) in name_bytes.iter().enumerate() {
93        raw.name[i] = b as c_char;
94    }
95
96    unsafe { btrfs_ioc_resize(fd.as_raw_fd(), &mut raw) }?;
97    Ok(())
98}