Skip to main content

btrfs_cli/
lib.rs

1//! # btrfs-cli: the btrfs command-line tool
2//!
3//! This crate provides the `btrfs` command-line binary, an alternative
4//! implementation of btrfs-progs written in Rust. It is built on top of `btrfs-uapi` for kernel communication,
5//! `btrfs-disk` for direct on-disk structure parsing, and `btrfs-stream` for
6//! send/receive stream processing.
7//!
8//! Not all commands from btrfs-progs are implemented yet. Run `btrfs help` to
9//! see what is available. Most commands require root privileges or
10//! `CAP_SYS_ADMIN`.
11
12#![warn(clippy::pedantic)]
13#![allow(clippy::module_name_repetitions)]
14#![allow(clippy::doc_markdown)]
15#![allow(clippy::struct_excessive_bools)]
16
17use anyhow::Result;
18use clap::{ArgAction, Parser, ValueEnum};
19
20mod balance;
21mod check;
22mod device;
23mod filesystem;
24mod inspect;
25mod property;
26mod qgroup;
27mod quota;
28mod receive;
29mod replace;
30mod rescue;
31mod restore;
32mod scrub;
33mod send;
34mod subvolume;
35mod util;
36
37pub use crate::{
38    balance::*, check::*, device::*, filesystem::*, inspect::*, property::*,
39    qgroup::*, quota::*, receive::*, replace::*, rescue::*, restore::*,
40    scrub::*, send::*, subvolume::*,
41};
42
43/// Output format for commands that support structured output.
44#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
45pub enum Format {
46    #[default]
47    Text,
48    Json,
49}
50
51/// Log verbosity level, ordered from most to least verbose.
52#[derive(
53    Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum,
54)]
55pub enum Level {
56    Debug,
57    #[default]
58    Info,
59    Warn,
60    Error,
61}
62
63/// User-space command-line tool for managing Btrfs filesystems.
64///
65/// btrfs is a modern copy-on-write filesystem for Linux that provides advanced features
66/// including subvolumes, snapshots, RAID support, compression, quotas, and checksumming.
67/// This tool allows you to create and manage filesystems, devices, subvolumes, snapshots,
68/// quotas, and perform various maintenance operations.
69///
70/// Most operations require CAP_SYS_ADMIN (root privileges) or special permissions for
71/// the specific filesystem.
72#[derive(Parser, Debug)]
73#[allow(clippy::doc_markdown)]
74#[clap(version, infer_subcommands = true)]
75pub struct Arguments {
76    #[clap(flatten)]
77    pub global: GlobalOptions,
78
79    #[clap(subcommand)]
80    pub command: Command,
81}
82
83const GLOBAL_OPTIONS: &str = "Global options";
84
85/// Flags shared across all subcommands (verbosity, dry-run, output format).
86#[derive(Parser, Debug)]
87pub struct GlobalOptions {
88    /// Increase verbosity (repeat for more: -v, -vv, -vvv)
89    #[clap(global = true, short, long, action = ArgAction::Count, help_heading = GLOBAL_OPTIONS)]
90    pub verbose: u8,
91
92    /// Print only errors
93    #[clap(global = true, short, long, help_heading = GLOBAL_OPTIONS)]
94    pub quiet: bool,
95
96    /// If supported, do not do any active/changing actions
97    #[clap(global = true, long, help_heading = GLOBAL_OPTIONS)]
98    pub dry_run: bool,
99
100    /// Set log level
101    #[clap(global = true, long, help_heading = GLOBAL_OPTIONS)]
102    pub log: Option<Level>,
103
104    /// If supported, print subcommand output in that format
105    #[clap(global = true, long, help_heading = GLOBAL_OPTIONS)]
106    pub format: Option<Format>,
107}
108
109/// A CLI subcommand that can be executed.
110pub trait Runnable {
111    /// Execute this command.
112    ///
113    /// # Errors
114    ///
115    /// Returns an error if the command fails.
116    fn run(&self, format: Format, dry_run: bool) -> Result<()>;
117}
118
119#[derive(Parser, Debug)]
120pub enum Command {
121    Balance(BalanceCommand),
122    Check(CheckCommand),
123    Device(DeviceCommand),
124    Filesystem(FilesystemCommand),
125    #[command(alias = "inspect-internal")]
126    Inspect(InspectCommand),
127    #[cfg(feature = "mkfs")]
128    Mkfs(btrfs_mkfs::args::Arguments),
129    Property(PropertyCommand),
130    Qgroup(QgroupCommand),
131    Quota(QuotaCommand),
132    Receive(ReceiveCommand),
133    Replace(ReplaceCommand),
134    Rescue(RescueCommand),
135    Restore(RestoreCommand),
136    Scrub(ScrubCommand),
137    Send(SendCommand),
138    Subvolume(SubvolumeCommand),
139    #[cfg(feature = "tune")]
140    Tune(btrfs_tune::args::Arguments),
141}
142
143impl Runnable for Command {
144    fn run(&self, format: Format, dry_run: bool) -> Result<()> {
145        match self {
146            Command::Balance(cmd) => cmd.run(format, dry_run),
147            Command::Check(cmd) => cmd.run(format, dry_run),
148            Command::Device(cmd) => cmd.run(format, dry_run),
149            Command::Filesystem(cmd) => cmd.run(format, dry_run),
150            Command::Inspect(cmd) => cmd.run(format, dry_run),
151            #[cfg(feature = "mkfs")]
152            Command::Mkfs(args) => btrfs_mkfs::run::run(args),
153            Command::Property(cmd) => cmd.run(format, dry_run),
154            Command::Qgroup(cmd) => cmd.run(format, dry_run),
155            Command::Quota(cmd) => cmd.run(format, dry_run),
156            Command::Receive(cmd) => cmd.run(format, dry_run),
157            Command::Replace(cmd) => cmd.run(format, dry_run),
158            Command::Rescue(cmd) => cmd.run(format, dry_run),
159            Command::Restore(cmd) => cmd.run(format, dry_run),
160            Command::Scrub(cmd) => cmd.run(format, dry_run),
161            Command::Send(cmd) => cmd.run(format, dry_run),
162            Command::Subvolume(cmd) => cmd.run(format, dry_run),
163            #[cfg(feature = "tune")]
164            Command::Tune(args) => btrfs_tune::run::run(args),
165        }
166    }
167}
168
169impl Arguments {
170    /// Parse and run the CLI command.
171    ///
172    /// # Errors
173    ///
174    /// Returns an error if the command fails.
175    pub fn run(&self) -> Result<()> {
176        let level = if let Some(explicit) = self.global.log {
177            match explicit {
178                Level::Debug => log::LevelFilter::Debug,
179                Level::Info => log::LevelFilter::Info,
180                Level::Warn => log::LevelFilter::Warn,
181                Level::Error => log::LevelFilter::Error,
182            }
183        } else if self.global.quiet {
184            log::LevelFilter::Error
185        } else {
186            match self.global.verbose {
187                0 => log::LevelFilter::Warn,
188                1 => log::LevelFilter::Info,
189                2 => log::LevelFilter::Debug,
190                _ => log::LevelFilter::Trace,
191            }
192        };
193        env_logger::Builder::new().filter_level(level).init();
194        self.command
195            .run(self.global.format.unwrap_or_default(), self.global.dry_run)
196    }
197}