1use crate::{RunContext, 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, _ctx: &RunContext) -> 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 Some(path_str) = device.to_str() else {
103 continue;
104 };
105 let Ok(cpath) = CString::new(path_str) else {
106 continue;
107 };
108 if device_scan(&cpath).is_ok() {
109 println!("registered '{}'", device.display());
110 registered += 1;
111 }
112 }
113
114 if registered == 0 {
115 println!("no btrfs devices found");
116 }
117
118 Ok(())
119}
120
121fn block_devices_from_proc_partitions() -> Result<Vec<PathBuf>> {
126 let contents = fs::read_to_string("/proc/partitions")
127 .context("failed to read /proc/partitions")?;
128
129 let mut devices = Vec::new();
130 for line in contents.lines().skip(2) {
131 let Some(name) = line.split_whitespace().nth(3) else {
132 continue;
133 };
134 devices.push(PathBuf::from(format!("/dev/{name}")));
135 }
136 Ok(devices)
137}
138
139fn scan_one(device: &Path) -> Result<()> {
140 let path_str = device.to_str().ok_or_else(|| {
141 anyhow::anyhow!("path is not valid UTF-8: '{}'", device.display())
142 })?;
143 let cpath = CString::new(path_str).with_context(|| {
144 format!("path contains a null byte: '{}'", device.display())
145 })?;
146 device_scan(&cpath).with_context(|| {
147 format!("failed to register '{}'", device.display())
148 })?;
149 Ok(())
150}
151
152fn forget_one(device: &Path) -> Result<()> {
153 let path_str = device.to_str().ok_or_else(|| {
154 anyhow::anyhow!("path is not valid UTF-8: '{}'", device.display())
155 })?;
156 let cpath = CString::new(path_str).with_context(|| {
157 format!("path contains a null byte: '{}'", device.display())
158 })?;
159 device_forget(Some(&cpath)).with_context(|| {
160 format!("failed to unregister '{}'", device.display())
161 })?;
162 Ok(())
163}