1use crate::{Format, Runnable, util::check_device_for_overwrite};
2use anyhow::{Context, Result};
3use btrfs_uapi::{
4 device::device_add, filesystem::filesystem_info, sysfs::SysfsBtrfs,
5};
6use clap::Parser;
7use std::{ffi::CString, fs, os::unix::io::AsFd, path::PathBuf};
8
9#[derive(Parser, Debug)]
14#[allow(clippy::doc_markdown)]
15pub struct DeviceAddCommand {
16 #[clap(short = 'f', long)]
18 pub force: bool,
19
20 #[clap(short = 'K', long)]
22 pub nodiscard: bool,
23
24 #[clap(long)]
26 pub enqueue: bool,
27
28 #[clap(required = true, num_args = 1..)]
30 pub devices: Vec<PathBuf>,
31
32 pub target: PathBuf,
34}
35
36impl Runnable for DeviceAddCommand {
37 fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
38 let file = fs::File::open(&self.target).with_context(|| {
39 format!("failed to open '{}'", self.target.display())
40 })?;
41 let fd = file.as_fd();
42
43 if self.enqueue {
45 let info = filesystem_info(fd).with_context(|| {
46 format!(
47 "failed to get filesystem info for '{}'",
48 self.target.display()
49 )
50 })?;
51 let sysfs = SysfsBtrfs::new(&info.uuid);
52 let op =
53 sysfs.wait_for_exclusive_operation().with_context(|| {
54 format!(
55 "failed to check exclusive operation on '{}'",
56 self.target.display()
57 )
58 })?;
59 if op != "none" {
60 eprintln!("waited for exclusive operation '{op}' to finish");
61 }
62 }
63
64 let mut had_error = false;
65
66 for device in &self.devices {
67 if let Err(e) = check_device_for_overwrite(device, self.force) {
70 eprintln!("error: {e:#}");
71 had_error = true;
72 continue;
73 }
74
75 if !self.nodiscard {
77 match fs::OpenOptions::new().write(true).open(device) {
78 Ok(tgtfile) => {
79 match btrfs_uapi::blkdev::discard_whole_device(
80 tgtfile.as_fd(),
81 ) {
82 Ok(0) => {}
83 Ok(_) => eprintln!(
84 "discarded device '{}'",
85 device.display()
86 ),
87 Err(e) => {
88 eprintln!(
89 "warning: discard failed on '{}': {e}; continuing anyway",
90 device.display()
91 );
92 }
93 }
94 }
95 Err(e) => {
96 eprintln!(
97 "warning: could not open '{}' for discard: {e}; continuing anyway",
98 device.display()
99 );
100 }
101 }
102 }
103
104 let path_str = device.to_str().ok_or_else(|| {
105 anyhow::anyhow!(
106 "device path is not valid UTF-8: '{}'",
107 device.display()
108 )
109 })?;
110
111 let cpath = CString::new(path_str).with_context(|| {
112 format!(
113 "device path contains a null byte: '{}'",
114 device.display()
115 )
116 })?;
117
118 match device_add(fd, &cpath) {
119 Ok(()) => println!("added device '{}'", device.display()),
120 Err(e) => {
121 eprintln!(
122 "error adding device '{}': {e}",
123 device.display()
124 );
125 had_error = true;
126 }
127 }
128 }
129
130 if had_error {
131 anyhow::bail!("one or more devices could not be added");
132 }
133
134 Ok(())
135 }
136}