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 && 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}