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)]
12#[allow(clippy::doc_markdown)]
13pub struct SubvolumeCreateCommand {
14 #[clap(short = 'i', value_name = "QGROUPID", action = clap::ArgAction::Append)]
16 pub qgroups: Vec<String>,
17
18 #[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, _format: Format, _dry_run: bool) -> 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}