btrfs_cli/subvolume/
snapshot.rs1use crate::{RunContext, Runnable, util::parse_qgroupid};
2use anyhow::{Context, Result};
3use btrfs_uapi::subvolume::snapshot_create;
4use clap::Parser;
5use std::{ffi::CString, fs::File, os::unix::io::AsFd, path::PathBuf};
6
7#[derive(Parser, Debug)]
9pub struct SubvolumeSnapshotCommand {
10 #[clap(short, long)]
12 pub readonly: bool,
13
14 #[clap(short = 'i', value_name = "QGROUPID", action = clap::ArgAction::Append)]
16 pub qgroups: Vec<String>,
17
18 pub source: PathBuf,
20
21 pub dest: PathBuf,
23}
24
25impl Runnable for SubvolumeSnapshotCommand {
26 fn run(&self, _ctx: &RunContext) -> Result<()> {
27 let qgroup_ids: Vec<u64> = self
28 .qgroups
29 .iter()
30 .map(|s| parse_qgroupid(s))
31 .collect::<Result<_>>()?;
32
33 let (dest_parent, name_os) =
34 if self.dest.is_dir() {
35 let name_os = self.source.file_name().ok_or_else(|| {
36 anyhow::anyhow!("source has no file name")
37 })?;
38 (self.dest.as_path(), name_os)
39 } else {
40 let dest_parent = self.dest.parent().ok_or_else(|| {
41 anyhow::anyhow!("destination has no parent")
42 })?;
43 let name_os = self.dest.file_name().ok_or_else(|| {
44 anyhow::anyhow!("destination has no name")
45 })?;
46 (dest_parent, name_os)
47 };
48
49 let name_str = name_os.to_str().ok_or_else(|| {
50 anyhow::anyhow!("snapshot name is not valid UTF-8")
51 })?;
52
53 let cname = CString::new(name_str).with_context(|| {
54 format!("snapshot name contains a null byte: '{name_str}'")
55 })?;
56
57 let source_file = File::open(&self.source).with_context(|| {
58 format!("failed to open source '{}'", self.source.display())
59 })?;
60
61 let parent_file = File::open(dest_parent).with_context(|| {
62 format!(
63 "failed to open destination parent '{}'",
64 dest_parent.display()
65 )
66 })?;
67
68 snapshot_create(
69 parent_file.as_fd(),
70 source_file.as_fd(),
71 &cname,
72 self.readonly,
73 &qgroup_ids,
74 )
75 .with_context(|| {
76 format!(
77 "failed to create snapshot of '{}' in '{}/{}'",
78 self.source.display(),
79 dest_parent.display(),
80 name_str,
81 )
82 })?;
83
84 if self.readonly {
85 println!(
86 "Create readonly snapshot of '{}' in '{}/{}'",
87 self.source.display(),
88 dest_parent.display(),
89 name_str,
90 );
91 } else {
92 println!(
93 "Create snapshot of '{}' in '{}/{}'",
94 self.source.display(),
95 dest_parent.display(),
96 name_str,
97 );
98 }
99
100 Ok(())
101 }
102}