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#[derive(Parser, Debug)]
18#[command(group = clap::ArgGroup::new("target").args(["all", "devid"]).multiple(false))]
19pub struct ScrubLimitCommand {
20 #[clap(long, short, requires = "limit")]
22 pub all: bool,
23
24 #[clap(long, short, requires = "limit")]
26 pub devid: Option<u64>,
27
28 #[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 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 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 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 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.scrub_speed_max_get(d.devid).map_or_else(
131 |_| "-".to_owned(),
132 |v| super::format_limit(v, &mode),
133 )
134 })
135 .collect();
136 let limit_w = "Limit".len().max(
137 limit_vals
138 .iter()
139 .map(std::string::String::len)
140 .max()
141 .unwrap_or(0),
142 );
143
144 println!("{:>id_w$} {:>limit_w$} Path", "Id", "Limit");
145 println!("{:->id_w$} {:->limit_w$} ----", "", "");
146 for (dev, limit_str) in devices.iter().zip(limit_vals.iter()) {
147 println!(
148 "{:>id_w$} {:>limit_w$} {}",
149 dev.devid, limit_str, dev.path
150 );
151 }
152
153 Ok(())
154 }
155}