Skip to main content

btrfs_cli/qgroup/
assign.rs

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