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