Skip to main content

btrfs_cli/scrub/
limit.rs

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