1use crate::{
2 RunContext, 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 #[allow(clippy::too_many_lines)]
41 fn run(&self, _ctx: &RunContext) -> 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 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 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 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}