1use crate::{Format, Runnable};
2use anyhow::{Context, Result};
3use btrfs_uapi::device::{device_forget, device_scan};
4use clap::Parser;
5use std::{ffi::CString, fs, path::PathBuf};
6
7#[derive(Parser, Debug)]
15pub struct DeviceScanCommand {
16 #[clap(long, short = 'u', alias = "forget")]
21 pub forget: bool,
22
23 pub devices: Vec<PathBuf>,
25}
26
27impl Runnable for DeviceScanCommand {
28 fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
29 if self.forget {
30 if self.devices.is_empty() {
31 device_forget(None).context(
32 "failed to unregister stale devices from kernel",
33 )?;
34 println!("unregistered all stale devices");
35 } else {
36 let mut had_error = false;
37 for device in &self.devices {
38 match forget_one(device) {
39 Ok(()) => {
40 println!("unregistered '{}'", device.display())
41 }
42 Err(e) => {
43 eprintln!(
44 "error unregistering '{}': {e}",
45 device.display()
46 );
47 had_error = true;
48 }
49 }
50 }
51 if had_error {
52 anyhow::bail!(
53 "one or more devices could not be unregistered"
54 );
55 }
56 }
57 } else if self.devices.is_empty() {
58 scan_all()?;
59 } else {
60 let mut had_error = false;
61 for device in &self.devices {
62 match scan_one(device) {
63 Ok(()) => println!("registered '{}'", device.display()),
64 Err(e) => {
65 eprintln!(
66 "error registering '{}': {e}",
67 device.display()
68 );
69 had_error = true;
70 }
71 }
72 }
73 if had_error {
74 anyhow::bail!("one or more devices could not be registered");
75 }
76 }
77
78 Ok(())
79 }
80}
81
82fn scan_all() -> Result<()> {
88 let devices = block_devices_from_proc_partitions()
89 .context("failed to enumerate block devices from /proc/partitions")?;
90
91 if devices.is_empty() {
92 println!("no block devices found");
93 return Ok(());
94 }
95
96 let mut registered = 0u32;
97 for device in &devices {
98 let path_str = match device.to_str() {
99 Some(s) => s,
100 None => continue,
101 };
102 let cpath = match CString::new(path_str) {
103 Ok(c) => c,
104 Err(_) => continue,
105 };
106 if device_scan(&cpath).is_ok() {
107 println!("registered '{}'", device.display());
108 registered += 1;
109 }
110 }
111
112 if registered == 0 {
113 println!("no btrfs devices found");
114 }
115
116 Ok(())
117}
118
119fn block_devices_from_proc_partitions() -> Result<Vec<PathBuf>> {
124 let contents = fs::read_to_string("/proc/partitions")
125 .context("failed to read /proc/partitions")?;
126
127 let mut devices = Vec::new();
128 for line in contents.lines().skip(2) {
129 let name = match line.split_whitespace().nth(3) {
130 Some(n) => n,
131 None => continue,
132 };
133 devices.push(PathBuf::from(format!("/dev/{name}")));
134 }
135 Ok(devices)
136}
137
138fn scan_one(device: &PathBuf) -> Result<()> {
139 let path_str = device.to_str().ok_or_else(|| {
140 anyhow::anyhow!("path is not valid UTF-8: '{}'", device.display())
141 })?;
142 let cpath = CString::new(path_str).with_context(|| {
143 format!("path contains a null byte: '{}'", device.display())
144 })?;
145 device_scan(&cpath).with_context(|| {
146 format!("failed to register '{}'", device.display())
147 })?;
148 Ok(())
149}
150
151fn forget_one(device: &PathBuf) -> Result<()> {
152 let path_str = device.to_str().ok_or_else(|| {
153 anyhow::anyhow!("path is not valid UTF-8: '{}'", device.display())
154 })?;
155 let cpath = CString::new(path_str).with_context(|| {
156 format!("path contains a null byte: '{}'", device.display())
157 })?;
158 device_forget(Some(&cpath)).with_context(|| {
159 format!("failed to unregister '{}'", device.display())
160 })?;
161 Ok(())
162}