Skip to main content

btrfs_cli/filesystem/
resize.rs

1use crate::{
2    Format, Runnable,
3    util::{open_path, parse_size_with_suffix},
4};
5use anyhow::{Context, Result};
6use btrfs_uapi::filesystem::{ResizeAmount, ResizeArgs, resize};
7use clap::Parser;
8use std::{os::unix::io::AsFd, path::PathBuf};
9
10/// Resize a mounted btrfs filesystem
11#[derive(Parser, Debug)]
12pub struct FilesystemResizeCommand {
13    /// Wait if there is another exclusive operation running, otherwise error
14    #[clap(long)]
15    pub enqueue: bool,
16
17    /// Resize a filesystem stored in a file image (unmounted)
18    #[clap(long)]
19    pub offline: bool,
20
21    /// New size for the filesystem, e.g. "1G", "+512M", "-1G", "max", "cancel",
22    /// or "devid:ID:SIZE" to target a specific device
23    pub size: String,
24
25    pub path: PathBuf,
26}
27
28fn parse_resize_amount(s: &str) -> Result<ResizeAmount> {
29    if s == "cancel" {
30        return Ok(ResizeAmount::Cancel);
31    }
32    if s == "max" {
33        return Ok(ResizeAmount::Max);
34    }
35    let (modifier, rest) = if let Some(r) = s.strip_prefix('+') {
36        (1i32, r)
37    } else if let Some(r) = s.strip_prefix('-') {
38        (-1i32, r)
39    } else {
40        (0i32, s)
41    };
42    let bytes = parse_size_with_suffix(rest)?;
43    Ok(match modifier {
44        1 => ResizeAmount::Add(bytes),
45        -1 => ResizeAmount::Sub(bytes),
46        _ => ResizeAmount::Set(bytes),
47    })
48}
49
50fn parse_resize_args(s: &str) -> Result<ResizeArgs> {
51    if let Some(colon) = s.find(':')
52        && let Ok(devid) = s[..colon].parse::<u64>()
53    {
54        let amount = parse_resize_amount(&s[colon + 1..])?;
55        return Ok(ResizeArgs::new(amount).with_devid(devid));
56    }
57    Ok(ResizeArgs::new(parse_resize_amount(s)?))
58}
59
60impl Runnable for FilesystemResizeCommand {
61    fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
62        if self.offline {
63            anyhow::bail!("--offline is not yet implemented");
64        }
65
66        if self.enqueue {
67            anyhow::bail!("--enqueue is not yet implemented");
68        }
69
70        let args = parse_resize_args(&self.size).with_context(|| {
71            format!("invalid resize argument: '{}'", self.size)
72        })?;
73
74        let file = open_path(&self.path)?;
75
76        resize(file.as_fd(), args).with_context(|| {
77            format!("resize failed on '{}'", self.path.display())
78        })?;
79
80        Ok(())
81    }
82}