Skip to main content

btrfs_cli/qgroup/
assign.rs

1use crate::{
2    Format, Runnable,
3    util::{open_path, parse_qgroupid},
4};
5use anyhow::{Context, Result, bail};
6use btrfs_uapi::quota::qgroupid_level;
7use clap::Parser;
8use nix::errno::Errno;
9use std::{os::unix::io::AsFd, path::PathBuf};
10
11/// Assign a qgroup as the child of another qgroup
12#[derive(Parser, Debug)]
13pub struct QgroupAssignCommand {
14    /// Source qgroup id (e.g. "0/5")
15    pub src: String,
16    /// Destination qgroup id (e.g. "1/0")
17    pub dst: String,
18    /// Path to a mounted btrfs filesystem
19    pub path: PathBuf,
20    /// Schedule a quota rescan if needed (default)
21    #[clap(long, conflicts_with = "no_rescan")]
22    pub rescan: bool,
23    /// Do not schedule a quota rescan
24    #[clap(long)]
25    pub no_rescan: bool,
26}
27
28impl Runnable for QgroupAssignCommand {
29    fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
30        let src = parse_qgroupid(&self.src)?;
31        let dst = parse_qgroupid(&self.dst)?;
32
33        if qgroupid_level(src) >= qgroupid_level(dst) {
34            bail!(
35                "source qgroup '{}' must be at a lower level than destination qgroup '{}'",
36                self.src,
37                self.dst
38            );
39        }
40
41        let file = open_path(&self.path)?;
42        let fd = file.as_fd();
43
44        let needs_rescan = match btrfs_uapi::quota::qgroup_assign(fd, src, dst)
45        {
46            Ok(needs_rescan) => needs_rescan,
47            Err(Errno::ENOTCONN) => {
48                bail!("quota not enabled on '{}'", self.path.display())
49            }
50            Err(e) => {
51                return Err(e).with_context(|| {
52                    format!(
53                        "failed to assign qgroup '{}' to '{}' on '{}'",
54                        self.src,
55                        self.dst,
56                        self.path.display()
57                    )
58                });
59            }
60        };
61
62        // Default behaviour (neither flag given) is to rescan.
63        let do_rescan = !self.no_rescan;
64
65        if needs_rescan {
66            if do_rescan {
67                btrfs_uapi::quota::quota_rescan(fd).with_context(|| {
68                    format!(
69                        "failed to schedule quota rescan on '{}'",
70                        self.path.display()
71                    )
72                })?;
73                println!("Quota data changed, rescan scheduled");
74            } else {
75                eprintln!(
76                    "WARNING: quotas may be inconsistent, rescan needed on '{}'",
77                    self.path.display()
78                );
79            }
80        }
81
82        Ok(())
83    }
84}