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                && 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            let file = match File::open(parent).with_context(|| {
95                format!("failed to open '{}'", parent.display())
96            }) {
97                Ok(f) => f,
98                Err(e) => {
99                    eprintln!("error creating '{}': {e}", path.display());
100                    had_error = true;
101                    continue;
102                }
103            };
104
105            match subvolume_create(file.as_fd(), &cname, &qgroup_ids) {
106                Ok(()) => println!("Create subvolume '{}'", path.display()),
107                Err(e) => {
108                    eprintln!("error creating '{}': {e}", path.display());
109                    had_error = true;
110                }
111            }
112        }
113
114        if had_error {
115            anyhow::bail!("one or more subvolumes could not be created");
116        }
117
118        Ok(())
119    }
120}