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