Skip to main content

btrfs_cli/qgroup/
limit.rs

1use crate::{
2    Format, Runnable,
3    util::{parse_qgroupid, parse_size_with_suffix},
4};
5use anyhow::{Context, Result};
6use btrfs_uapi::quota::QgroupLimitFlags;
7use clap::Parser;
8use nix::errno::Errno;
9use std::{fs::File, os::unix::io::AsFd, path::PathBuf};
10
11/// A size limit that is either a byte count or "none" to remove the limit.
12#[derive(Debug, Clone)]
13pub enum QgroupLimitSize {
14    Bytes(u64),
15    None,
16}
17
18impl std::str::FromStr for QgroupLimitSize {
19    type Err = anyhow::Error;
20
21    fn from_str(s: &str) -> Result<Self> {
22        if s.eq_ignore_ascii_case("none") {
23            Ok(Self::None)
24        } else {
25            parse_size_with_suffix(s).map(Self::Bytes)
26        }
27    }
28}
29
30/// Set limits on a subvolume quota group
31#[derive(Parser, Debug)]
32pub struct QgroupLimitCommand {
33    /// Size limit in bytes (use suffix K/M/G/T), or "none" to remove the limit
34    pub size: QgroupLimitSize,
35
36    /// Qgroup ID (e.g. "0/5") or path if qgroupid is omitted
37    #[clap(value_name = "QGROUPID_OR_PATH")]
38    pub target: String,
39
40    /// Path to the filesystem (required when qgroupid is given)
41    pub path: Option<PathBuf>,
42
43    /// Limit amount of data after compression
44    #[clap(short = 'c', long)]
45    pub compress: bool,
46
47    /// Limit space exclusively assigned to this qgroup
48    #[clap(short = 'e', long)]
49    pub exclusive: bool,
50}
51
52impl Runnable for QgroupLimitCommand {
53    fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
54        let (fs_path, qgroupid) = match &self.path {
55            None => {
56                // target is the path; apply limit to the subvolume that fd refers to (qgroupid 0)
57                let path = PathBuf::from(&self.target);
58                (path, 0u64)
59            }
60            Some(path) => {
61                // target is the qgroup ID string
62                let qgroupid = parse_qgroupid(&self.target)?;
63                (path.clone(), qgroupid)
64            }
65        };
66
67        let size = match self.size {
68            QgroupLimitSize::Bytes(n) => n,
69            QgroupLimitSize::None => u64::MAX,
70        };
71
72        let mut flags = QgroupLimitFlags::empty();
73        let mut max_rfer = u64::MAX;
74        let mut max_excl = u64::MAX;
75
76        if self.compress {
77            flags |= QgroupLimitFlags::RFER_CMPR | QgroupLimitFlags::EXCL_CMPR;
78        }
79
80        if self.exclusive {
81            flags |= QgroupLimitFlags::MAX_EXCL;
82            max_excl = size;
83        } else {
84            flags |= QgroupLimitFlags::MAX_RFER;
85            max_rfer = size;
86        }
87
88        let file = File::open(&fs_path).with_context(|| {
89            format!("failed to open '{}'", fs_path.display())
90        })?;
91
92        match btrfs_uapi::quota::qgroup_limit(
93            file.as_fd(),
94            qgroupid,
95            flags,
96            max_rfer,
97            max_excl,
98        ) {
99            Ok(()) => Ok(()),
100            Err(Errno::ENOTCONN) => {
101                anyhow::bail!("quota not enabled on '{}'", fs_path.display())
102            }
103            Err(e) => Err(e).with_context(|| {
104                format!("failed to set qgroup limit on '{}'", fs_path.display())
105            }),
106        }
107    }
108}