Skip to main content

btrfs_cli/filesystem/
resize.rs

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