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    #[allow(clippy::too_many_lines)]
41    fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
42        let mode = self.units.resolve();
43        let file = open_path(&self.path)?;
44        let fd = file.as_fd();
45
46        let fs = filesystem_info(fd).with_context(|| {
47            format!(
48                "failed to get filesystem info for '{}'",
49                self.path.display()
50            )
51        })?;
52        let devices = device_info_all(fd, &fs).with_context(|| {
53            format!("failed to get device info for '{}'", self.path.display())
54        })?;
55
56        let sysfs = SysfsBtrfs::new(&fs.uuid);
57
58        println!("UUID: {}", fs.uuid.as_hyphenated());
59
60        if let Some(target_devid) = self.devid {
61            // Set limit for one specific device.
62            let dev = devices
63                .iter()
64                .find(|d| d.devid == target_devid)
65                .with_context(|| {
66                    format!("device with devid {target_devid} not found")
67                })?;
68            let new_limit = self.limit.unwrap();
69            let old_limit =
70                sysfs.scrub_speed_max_get(dev.devid).with_context(|| {
71                    format!(
72                        "failed to read scrub limit for devid {}",
73                        dev.devid
74                    )
75                })?;
76            println!(
77                "Set scrub limit of devid {} from {} to {}",
78                dev.devid,
79                super::format_limit(old_limit, &mode),
80                super::format_limit(new_limit, &mode),
81            );
82            sysfs
83                .scrub_speed_max_set(dev.devid, new_limit)
84                .with_context(|| {
85                    format!("failed to set scrub limit for devid {}", dev.devid)
86                })?;
87            return Ok(());
88        }
89
90        if self.all {
91            // Set limit for all devices.
92            let new_limit = self.limit.unwrap();
93            for dev in &devices {
94                let old_limit = sysfs
95                    .scrub_speed_max_get(dev.devid)
96                    .with_context(|| {
97                        format!(
98                            "failed to read scrub limit for devid {}",
99                            dev.devid
100                        )
101                    })?;
102                println!(
103                    "Set scrub limit of devid {} from {} to {}",
104                    dev.devid,
105                    super::format_limit(old_limit, &mode),
106                    super::format_limit(new_limit, &mode),
107                );
108                sysfs
109                    .scrub_speed_max_set(dev.devid, new_limit)
110                    .with_context(|| {
111                        format!(
112                            "failed to set scrub limit for devid {}",
113                            dev.devid
114                        )
115                    })?;
116            }
117            return Ok(());
118        }
119
120        // Read-only mode: print a table of current limits.
121        let id_w = "Id".len().max(
122            devices
123                .iter()
124                .map(|d| super::digits(d.devid))
125                .max()
126                .unwrap_or(0),
127        );
128        let limit_vals: Vec<String> = devices
129            .iter()
130            .map(|d| {
131                sysfs.scrub_speed_max_get(d.devid).map_or_else(
132                    |_| "-".to_owned(),
133                    |v| super::format_limit(v, &mode),
134                )
135            })
136            .collect();
137        let limit_w = "Limit".len().max(
138            limit_vals
139                .iter()
140                .map(std::string::String::len)
141                .max()
142                .unwrap_or(0),
143        );
144
145        println!("{:>id_w$}  {:>limit_w$}  Path", "Id", "Limit");
146        println!("{:->id_w$}  {:->limit_w$}  ----", "", "");
147        for (dev, limit_str) in devices.iter().zip(limit_vals.iter()) {
148            println!(
149                "{:>id_w$}  {:>limit_w$}  {}",
150                dev.devid, limit_str, dev.path
151            );
152        }
153
154        Ok(())
155    }
156}