1#![warn(clippy::pedantic)]
23#![allow(clippy::module_name_repetitions)]
24#![allow(clippy::doc_markdown)]
25#![allow(clippy::struct_excessive_bools)]
26#![cfg_attr(
29 test,
30 allow(
31 clippy::cast_lossless,
32 clippy::cast_possible_truncation,
33 clippy::cast_possible_wrap,
34 clippy::cast_sign_loss,
35 clippy::identity_op,
36 clippy::match_wildcard_for_single_variants,
37 clippy::semicolon_if_nothing_returned,
38 clippy::uninlined_format_args,
39 clippy::unreadable_literal,
40 )
41)]
42
43use anyhow::Result;
44use clap::{ArgAction, Parser, ValueEnum};
45
46mod balance;
47mod check;
48mod device;
49mod filesystem;
50mod inspect;
51mod property;
52mod qgroup;
53mod quota;
54mod receive;
55mod replace;
56mod rescue;
57mod restore;
58mod scrub;
59mod send;
60mod subvolume;
61mod util;
62
63pub use crate::{
64 balance::*, check::*, device::*, filesystem::*, inspect::*, property::*,
65 qgroup::*, quota::*, receive::*, replace::*, rescue::*, restore::*,
66 scrub::*, send::*, subvolume::*,
67};
68
69#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
71pub enum Format {
72 #[default]
73 Text,
74 Json,
75 Modern,
76}
77
78impl std::str::FromStr for Format {
79 type Err = String;
80
81 fn from_str(s: &str) -> Result<Self, Self::Err> {
82 <Self as clap::ValueEnum>::from_str(s, true).map_err(|e| e.clone())
83 }
84}
85
86#[derive(
88 Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum,
89)]
90pub enum Level {
91 Debug,
92 #[default]
93 Info,
94 Warn,
95 Error,
96}
97
98#[derive(Parser, Debug)]
108#[allow(clippy::doc_markdown)]
109#[clap(version, infer_subcommands = true, arg_required_else_help = true)]
110pub struct Arguments {
111 #[clap(flatten)]
112 pub global: GlobalOptions,
113
114 #[clap(subcommand)]
115 pub command: Command,
116}
117
118const GLOBAL_OPTIONS: &str = "Global options";
119
120#[derive(Parser, Debug)]
122pub struct GlobalOptions {
123 #[clap(global = true, short, long, action = ArgAction::Count, help_heading = GLOBAL_OPTIONS)]
125 pub verbose: u8,
126
127 #[clap(global = true, short, long, help_heading = GLOBAL_OPTIONS)]
129 pub quiet: bool,
130
131 #[clap(global = true, long, help_heading = GLOBAL_OPTIONS)]
133 pub dry_run: bool,
134
135 #[clap(global = true, long, help_heading = GLOBAL_OPTIONS)]
137 pub log: Option<Level>,
138
139 #[clap(global = true, long, help_heading = GLOBAL_OPTIONS)]
141 pub format: Option<Format>,
142}
143
144pub struct RunContext {
146 pub format: Format,
148 pub dry_run: bool,
150 pub quiet: bool,
152}
153
154pub trait Runnable {
156 fn run(&self, ctx: &RunContext) -> Result<()>;
162
163 fn supported_formats(&self) -> &[Format] {
168 &[Format::Text, Format::Modern]
169 }
170
171 fn supports_dry_run(&self) -> bool {
177 false
178 }
179}
180
181pub trait CommandGroup {
187 fn leaf(&self) -> &dyn Runnable;
188}
189
190impl<T: CommandGroup> Runnable for T {
191 fn run(&self, ctx: &RunContext) -> Result<()> {
192 self.leaf().run(ctx)
193 }
194
195 fn supported_formats(&self) -> &[Format] {
196 self.leaf().supported_formats()
197 }
198
199 fn supports_dry_run(&self) -> bool {
200 self.leaf().supports_dry_run()
201 }
202}
203
204#[derive(Parser, Debug)]
205pub enum Command {
206 Balance(BalanceCommand),
207 Check(CheckCommand),
208 Device(DeviceCommand),
209 Filesystem(FilesystemCommand),
210 #[command(alias = "inspect-internal")]
211 Inspect(InspectCommand),
212 #[cfg(feature = "mkfs")]
213 Mkfs(btrfs_mkfs::args::Arguments),
214 Property(PropertyCommand),
215 Qgroup(QgroupCommand),
216 Quota(QuotaCommand),
217 Receive(ReceiveCommand),
218 Replace(ReplaceCommand),
219 Rescue(RescueCommand),
220 Restore(RestoreCommand),
221 Scrub(ScrubCommand),
222 Send(SendCommand),
223 Subvolume(SubvolumeCommand),
224 #[cfg(feature = "tune")]
225 Tune(btrfs_tune::args::Arguments),
226}
227
228#[cfg(feature = "mkfs")]
229impl Runnable for btrfs_mkfs::args::Arguments {
230 fn run(&self, _ctx: &RunContext) -> Result<()> {
231 btrfs_mkfs::run::run(self)
232 }
233}
234
235#[cfg(feature = "tune")]
236impl Runnable for btrfs_tune::args::Arguments {
237 fn run(&self, _ctx: &RunContext) -> Result<()> {
238 btrfs_tune::run::run(self)
239 }
240}
241
242impl CommandGroup for Command {
243 fn leaf(&self) -> &dyn Runnable {
244 match self {
245 Command::Balance(cmd) => cmd,
246 Command::Check(cmd) => cmd,
247 Command::Device(cmd) => cmd,
248 Command::Filesystem(cmd) => cmd,
249 Command::Inspect(cmd) => cmd,
250 #[cfg(feature = "mkfs")]
251 Command::Mkfs(cmd) => cmd,
252 Command::Property(cmd) => cmd,
253 Command::Qgroup(cmd) => cmd,
254 Command::Quota(cmd) => cmd,
255 Command::Receive(cmd) => cmd,
256 Command::Replace(cmd) => cmd,
257 Command::Rescue(cmd) => cmd,
258 Command::Restore(cmd) => cmd,
259 Command::Scrub(cmd) => cmd,
260 Command::Send(cmd) => cmd,
261 Command::Subvolume(cmd) => cmd,
262 #[cfg(feature = "tune")]
263 Command::Tune(cmd) => cmd,
264 }
265 }
266}
267
268impl Arguments {
269 pub fn run(&self) -> Result<()> {
275 let level = if let Some(explicit) = self.global.log {
276 match explicit {
277 Level::Debug => log::LevelFilter::Debug,
278 Level::Info => log::LevelFilter::Info,
279 Level::Warn => log::LevelFilter::Warn,
280 Level::Error => log::LevelFilter::Error,
281 }
282 } else if self.global.quiet {
283 log::LevelFilter::Error
284 } else {
285 match self.global.verbose {
286 0 => log::LevelFilter::Warn,
287 1 => log::LevelFilter::Info,
288 2 => log::LevelFilter::Debug,
289 _ => log::LevelFilter::Trace,
290 }
291 };
292 env_logger::Builder::new().filter_level(level).init();
293
294 if self.global.dry_run && !self.command.supports_dry_run() {
295 anyhow::bail!(
296 "the --dry-run option is not supported by this command"
297 );
298 }
299
300 let format = self
301 .global
302 .format
303 .or_else(|| {
308 std::env::var("BTRFS_OUTPUT_FORMAT")
309 .ok()
310 .and_then(|s| s.parse().ok())
311 })
312 .unwrap_or_default();
313 if !self.command.supported_formats().contains(&format) {
314 anyhow::bail!(
315 "the --format {format:?} option is not supported by this command",
316 );
317 }
318
319 let ctx = RunContext {
320 format,
321 dry_run: self.global.dry_run,
322 quiet: self.global.quiet,
323 };
324 self.command.run(&ctx)
325 }
326}