Skip to main content

btrfs_cli/scrub/
limit.rs

1use crate::{
2    Format, Runnable,
3    filesystem::UnitMode,
4    util::{open_path, parse_size_with_suffix},
5};
6use anyhow::{Context, Result};
7use btrfs_uapi::{
8    device::device_info_all, filesystem::filesystem_info, sysfs::SysfsBtrfs,
9};
10use clap::Parser;
11use std::{os::unix::io::AsFd, path::PathBuf};
12
13/// Show or set the per-device scrub throughput limit
14///
15/// Without options, shows the current limit for each device. Use -l with
16/// either -a or -d to set a limit. Pass 0 to -l to remove a limit.
17#[derive(Parser, Debug)]
18#[command(group = clap::ArgGroup::new("target").args(["all", "devid"]).multiple(false))]
19pub struct ScrubLimitCommand {
20    /// Apply the limit to all devices
21    #[clap(long, short, requires = "limit")]
22    pub all: bool,
23
24    /// Select a single device by devid
25    #[clap(long, short, requires = "limit")]
26    pub devid: Option<u64>,
27
28    /// Set the throughput limit (e.g. 100m, 1g); 0 removes the limit
29    #[clap(long, short, value_name = "SIZE", value_parser = parse_size_with_suffix, requires = "target")]
30    pub limit: Option<u64>,
31
32    #[clap(flatten)]
33    pub units: UnitMode,
34
35    /// Path to a mounted btrfs filesystem
36    pub path: PathBuf,
37}
38
39impl Runnable for ScrubLimitCommand {
40    fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
41        let mode = self.units.resolve();
42        let file = open_path(&self.path)?;
43        let fd = file.as_fd();
44
45        let fs = filesystem_info(fd).with_context(|| {
46            format!(
47                "failed to get filesystem info for '{}'",
48                self.path.display()
49            )
50        })?;
51        let devices = device_info_all(fd, &fs).with_context(|| {
52            format!("failed to get device info for '{}'", self.path.display())
53        })?;
54
55        let sysfs = SysfsBtrfs::new(&fs.uuid);
56
57        println!("UUID: {}", fs.uuid.as_hyphenated());
58
59        if let Some(target_devid) = self.devid {
60            // Set limit for one specific device.
61            let dev = devices
62                .iter()
63                .find(|d| d.devid == target_devid)
64                .with_context(|| {
65                    format!("device with devid {target_devid} not found")
66                })?;
67            let new_limit = self.limit.unwrap();
68            let old_limit =
69                sysfs.scrub_speed_max_get(dev.devid).with_context(|| {
70                    format!(
71                        "failed to read scrub limit for devid {}",
72                        dev.devid
73                    )
74                })?;
75            println!(
76                "Set scrub limit of devid {} from {} to {}",
77                dev.devid,
78                super::format_limit(old_limit, &mode),
79                super::format_limit(new_limit, &mode),
80            );
81            sysfs
82                .scrub_speed_max_set(dev.devid, new_limit)
83                .with_context(|| {
84                    format!("failed to set scrub limit for devid {}", dev.devid)
85                })?;
86            return Ok(());
87        }
88
89        if self.all {
90            // Set limit for all devices.
91            let new_limit = self.limit.unwrap();
92            for dev in &devices {
93                let old_limit = sysfs
94                    .scrub_speed_max_get(dev.devid)
95                    .with_context(|| {
96                        format!(
97                            "failed to read scrub limit for devid {}",
98                            dev.devid
99                        )
100                    })?;
101                println!(
102                    "Set scrub limit of devid {} from {} to {}",
103                    dev.devid,
104                    super::format_limit(old_limit, &mode),
105                    super::format_limit(new_limit, &mode),
106                );
107                sysfs
108                    .scrub_speed_max_set(dev.devid, new_limit)
109                    .with_context(|| {
110                        format!(
111                            "failed to set scrub limit for devid {}",
112                            dev.devid
113                        )
114                    })?;
115            }
116            return Ok(());
117        }
118
119        // Read-only mode: print a table of current limits.
120        let id_w = "Id".len().max(
121            devices
122                .iter()
123                .map(|d| super::digits(d.devid))
124                .max()
125                .unwrap_or(0),
126        );
127        let limit_vals: Vec<String> = devices
128            .iter()
129            .map(|d| {
130                sysfs
131                    .scrub_speed_max_get(d.devid)
132                    .map(|v| super::format_limit(v, &mode))
133                    .unwrap_or_else(|_| "-".to_owned())
134            })
135            .collect();
136        let limit_w = "Limit"
137            .len()
138            .max(limit_vals.iter().map(|s| s.len()).max().unwrap_or(0));
139
140        println!("{:>id_w$}  {:>limit_w$}  Path", "Id", "Limit");
141        println!("{:->id_w$}  {:->limit_w$}  ----", "", "");
142        for (dev, limit_str) in devices.iter().zip(limit_vals.iter()) {
143            println!(
144                "{:>id_w$}  {:>limit_w$}  {}",
145                dev.devid, limit_str, dev.path
146            );
147        }
148
149        Ok(())
150    }
151}