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