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