Skip to main content

btrfs_cli/subvolume/
create.rs

1use crate::{Format, 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)]
12pub struct SubvolumeCreateCommand {
13    /// Add the newly created subvolume to a qgroup (can be given multiple times)
14    #[clap(short = 'i', value_name = "QGROUPID", action = clap::ArgAction::Append)]
15    pub qgroups: Vec<String>,
16
17    /// Create any missing parent directories (like mkdir -p)
18    #[clap(short = 'p', long = "parents")]
19    pub parents: bool,
20
21    #[clap(required = true)]
22    pub paths: Vec<PathBuf>,
23}
24
25impl Runnable for SubvolumeCreateCommand {
26    fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
27        let qgroup_ids: Vec<u64> = self
28            .qgroups
29            .iter()
30            .map(|s| parse_qgroupid(s))
31            .collect::<Result<_>>()?;
32
33        let mut had_error = false;
34
35        for path in &self.paths {
36            let parent = match path.parent().ok_or_else(|| {
37                anyhow::anyhow!("'{}' has no parent directory", path.display())
38            }) {
39                Ok(p) => p,
40                Err(e) => {
41                    eprintln!("error creating '{}': {e}", path.display());
42                    had_error = true;
43                    continue;
44                }
45            };
46
47            let name_os = match path.file_name().ok_or_else(|| {
48                anyhow::anyhow!("'{}' has no file name", path.display())
49            }) {
50                Ok(n) => n,
51                Err(e) => {
52                    eprintln!("error creating '{}': {e}", path.display());
53                    had_error = true;
54                    continue;
55                }
56            };
57
58            let name_str = match name_os.to_str().ok_or_else(|| {
59                anyhow::anyhow!("'{}' is not valid UTF-8", path.display())
60            }) {
61                Ok(s) => s,
62                Err(e) => {
63                    eprintln!("error creating '{}': {e}", path.display());
64                    had_error = true;
65                    continue;
66                }
67            };
68
69            let cname = match CString::new(name_str).with_context(|| {
70                format!("subvolume name contains a null byte: '{}'", name_str)
71            }) {
72                Ok(c) => c,
73                Err(e) => {
74                    eprintln!("error creating '{}': {e}", path.display());
75                    had_error = true;
76                    continue;
77                }
78            };
79
80            if self.parents {
81                if let Err(e) =
82                    std::fs::create_dir_all(parent).with_context(|| {
83                        format!(
84                            "failed to create parent directories for '{}'",
85                            parent.display()
86                        )
87                    })
88                {
89                    eprintln!("error creating '{}': {e}", path.display());
90                    had_error = true;
91                    continue;
92                }
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}