rustic_rs/commands/
mount.rs1#![allow(clippy::doc_markdown)]
5
6mod fusefs;
7use fusefs::FuseFS;
8
9use abscissa_core::{
10 Command, FrameworkError, FrameworkErrorKind::ParseError, Runnable, Shutdown, config::Override,
11};
12use anyhow::{Result, bail};
13use clap::Parser;
14use conflate::{Merge, MergeFrom};
15use fuse_mt::{FuseMT, mount};
16use rustic_core::vfs::{FilePolicy, IdenticalSnapshot, Latest, Vfs};
17use std::{ffi::OsStr, path::PathBuf};
18
19use crate::{
20 Application, RUSTIC_APP, RusticConfig,
21 repository::{CliIndexedRepo, get_filtered_snapshots},
22 status_err,
23};
24
25#[derive(Clone, Debug, Default, Command, Parser, Merge, serde::Serialize, serde::Deserialize)]
26#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
27pub struct MountCmd {
28 #[clap(long)]
30 #[merge(strategy=conflate::option::overwrite_none)]
31 path_template: Option<String>,
32
33 #[clap(long)]
35 #[merge(strategy=conflate::option::overwrite_none)]
36 time_template: Option<String>,
37
38 #[clap(short, long)]
40 #[merge(strategy=conflate::bool::overwrite_false)]
41 exclusive: bool,
42
43 #[clap(long)]
45 #[merge(strategy=conflate::option::overwrite_none)]
46 file_access: Option<FilePolicy>,
47
48 #[clap(value_name = "PATH")]
50 #[merge(strategy=conflate::option::overwrite_none)]
51 mount_point: Option<PathBuf>,
52
53 #[clap(value_name = "SNAPSHOT[:PATH]")]
55 #[merge(strategy=conflate::option::overwrite_none)]
56 snapshot_path: Option<String>,
57
58 #[clap(short, long = "option", value_name = "OPTION")]
60 #[merge(strategy = conflate::vec::overwrite_empty)]
61 options: Vec<String>,
62}
63
64impl Override<RusticConfig> for MountCmd {
65 fn override_config(&self, mut config: RusticConfig) -> Result<RusticConfig, FrameworkError> {
69 let self_config = self
71 .clone()
72 .merge_from(config.mount)
73 .merge_from(Self::with_default_config());
74
75 if self_config.mount_point.is_none() {
77 return Err(ParseError
78 .context("Please specify a valid mount point!")
79 .into());
80 }
81
82 config.mount = self_config;
84
85 Ok(config)
86 }
87}
88
89impl Runnable for MountCmd {
90 fn run(&self) {
91 if let Err(err) = RUSTIC_APP
92 .config()
93 .repository
94 .run_indexed(|repo| self.inner_run(repo))
95 {
96 status_err!("{}", err);
97 RUSTIC_APP.shutdown(Shutdown::Crash);
98 };
99 }
100}
101
102impl MountCmd {
103 fn with_default_config() -> Self {
104 Self {
105 path_template: Some(String::from("[{hostname}]/[{label}]/{time}")),
106 time_template: Some(String::from("%Y-%m-%d_%H-%M-%S")),
107 options: vec![String::from("kernel_cache")],
108 ..Default::default()
109 }
110 }
111
112 fn inner_run(&self, repo: CliIndexedRepo) -> Result<()> {
113 let config = RUSTIC_APP.config();
114
115 let Some(path_template) = config.mount.path_template.clone() else {
119 bail!("Please specify a path template!");
120 };
121
122 let Some(time_template) = config.mount.time_template.clone() else {
123 bail!("Please specify a time template!");
124 };
125
126 let Some(mount_point) = config.mount.mount_point.clone() else {
127 bail!("Please specify a mount point!");
128 };
129
130 let vfs = if let Some(snap) = &config.mount.snapshot_path {
131 let node =
132 repo.node_from_snapshot_path(snap, |sn| config.snapshot_filter.matches(sn))?;
133 Vfs::from_dir_node(&node)
134 } else {
135 let snapshots = get_filtered_snapshots(&repo)?;
136 Vfs::from_snapshots(
137 snapshots,
138 &path_template,
139 &time_template,
140 Latest::AsLink,
141 IdenticalSnapshot::AsLink,
142 )?
143 };
144
145 let mut mount_options = config.mount.options.clone();
147
148 mount_options.push(format!("fsname=rusticfs:{}", repo.config().id));
149
150 if !config.mount.exclusive {
151 mount_options
152 .extend_from_slice(&["allow_other".to_string(), "default_permissions".to_string()]);
153 }
154
155 let file_access = config.mount.file_access.as_ref().map_or_else(
156 || {
157 if repo.config().is_hot == Some(true) {
158 FilePolicy::Forbidden
159 } else {
160 FilePolicy::Read
161 }
162 },
163 |s| *s,
164 );
165
166 let fs = FuseMT::new(FuseFS::new(repo, vfs, file_access), 1);
167
168 mount_options.sort_unstable();
170 mount_options.dedup();
171
172 let opt_string = format!("-o {}", mount_options.join(","));
176
177 mount(fs, mount_point, &[OsStr::new(&opt_string)])?;
178
179 Ok(())
180 }
181}