Skip to main content

btrfs_cli/subvolume/
create.rs

1use crate::{RunContext, Runnable, util::parse_qgroupid};
2use anyhow::{Context, Result};
3use btrfs_uapi::subvolume::subvolume_create;
4use clap::Parser;
5use std::{ffi::CString, fs::File, os::unix::io::AsFd, path::PathBuf};
6
7/// Create a new subvolume at each given path.
8///
9/// The parent directory must already exist and be on a btrfs filesystem
10/// (unless -p is given). Requires CAP_SYS_ADMIN.
11#[derive(Parser, Debug)]
12#[allow(clippy::doc_markdown)]
13pub struct SubvolumeCreateCommand {
14    /// Add the newly created subvolume to a qgroup (can be given multiple times)
15    #[clap(short = 'i', value_name = "QGROUPID", action = clap::ArgAction::Append)]
16    pub qgroups: Vec<String>,
17
18    /// Create any missing parent directories (like mkdir -p)
19    #[clap(short = 'p', long = "parents")]
20    pub parents: bool,
21
22    #[clap(required = true)]
23    pub paths: Vec<PathBuf>,
24}
25
26impl Runnable for SubvolumeCreateCommand {
27    fn run(&self, _ctx: &RunContext) -> Result<()> {
28        let qgroup_ids: Vec<u64> = self
29            .qgroups
30            .iter()
31            .map(|s| parse_qgroupid(s))
32            .collect::<Result<_>>()?;
33
34        let mut had_error = false;
35
36        for path in &self.paths {
37            let parent = match path.parent().ok_or_else(|| {
38                anyhow::anyhow!("'{}' has no parent directory", path.display())
39            }) {
40                Ok(p) => p,
41                Err(e) => {
42                    eprintln!("error creating '{}': {e}", path.display());
43                    had_error = true;
44                    continue;
45                }
46            };
47
48            let name_os = match path.file_name().ok_or_else(|| {
49                anyhow::anyhow!("'{}' has no file name", path.display())
50            }) {
51                Ok(n) => n,
52                Err(e) => {
53                    eprintln!("error creating '{}': {e}", path.display());
54                    had_error = true;
55                    continue;
56                }
57            };
58
59            let name_str = match name_os.to_str().ok_or_else(|| {
60                anyhow::anyhow!("'{}' is not valid UTF-8", path.display())
61            }) {
62                Ok(s) => s,
63                Err(e) => {
64                    eprintln!("error creating '{}': {e}", path.display());
65                    had_error = true;
66                    continue;
67                }
68            };
69
70            let cname = match CString::new(name_str).with_context(|| {
71                format!("subvolume name contains a null byte: '{name_str}'")
72            }) {
73                Ok(c) => c,
74                Err(e) => {
75                    eprintln!("error creating '{}': {e}", path.display());
76                    had_error = true;
77                    continue;
78                }
79            };
80
81            if self.parents
82                && let Err(e) =
83                    std::fs::create_dir_all(parent).with_context(|| {
84                        format!(
85                            "failed to create parent directories for '{}'",
86                            parent.display()
87                        )
88                    })
89            {
90                eprintln!("error creating '{}': {e}", path.display());
91                had_error = true;
92                continue;
93            }
94
95            let file = match File::open(parent).with_context(|| {
96                format!("failed to open '{}'", parent.display())
97            }) {
98                Ok(f) => f,
99                Err(e) => {
100                    eprintln!("error creating '{}': {e}", path.display());
101                    had_error = true;
102                    continue;
103                }
104            };
105
106            match subvolume_create(file.as_fd(), &cname, &qgroup_ids) {
107                Ok(()) => println!("Create subvolume '{}'", path.display()),
108                Err(e) => {
109                    eprintln!("error creating '{}': {e}", path.display());
110                    had_error = true;
111                }
112            }
113        }
114
115        if had_error {
116            anyhow::bail!("one or more subvolumes could not be created");
117        }
118
119        Ok(())
120    }
121}