btrfs_cli/subvolume/
create.rs1use 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#[derive(Parser, Debug)]
12pub struct SubvolumeCreateCommand {
13 #[clap(short = 'i', value_name = "QGROUPID", action = clap::ArgAction::Append)]
15 pub qgroups: Vec<String>,
16
17 #[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}