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#[derive(Parser, Debug)]
14#[command(group = clap::ArgGroup::new("target").args(["all", "devid"]).multiple(false))]
15pub struct ScrubLimitCommand {
16 #[clap(long, short, requires = "limit")]
18 pub all: bool,
19
20 #[clap(long, short, requires = "limit")]
22 pub devid: Option<u64>,
23
24 #[clap(long, short, value_name = "SIZE", value_parser = parse_size_with_suffix, requires = "target")]
26 pub limit: Option<u64>,
27
28 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 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 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 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}