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::{IndexedRepo, 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]")]
57 #[merge(strategy=conflate::option::overwrite_none)]
58 snapshot_path: Option<String>,
59
60 #[clap(short, long = "option", value_name = "OPTION")]
62 #[merge(strategy = conflate::vec::overwrite_empty)]
63 options: Vec<String>,
64}
65
66impl Override<RusticConfig> for MountCmd {
67 fn override_config(&self, mut config: RusticConfig) -> Result<RusticConfig, FrameworkError> {
71 let self_config = self
73 .clone()
74 .merge_from(config.mount)
75 .merge_from(Self::with_default_config());
76
77 if self_config.mount_point.is_none() {
79 return Err(ParseError
80 .context("Please specify a valid mount point!")
81 .into());
82 }
83
84 config.mount = self_config;
86
87 Ok(config)
88 }
89}
90
91impl Runnable for MountCmd {
92 fn run(&self) {
93 if let Err(err) = RUSTIC_APP
94 .config()
95 .repository
96 .run_indexed(|repo| self.inner_run(repo))
97 {
98 status_err!("{}", err);
99 RUSTIC_APP.shutdown(Shutdown::Crash);
100 };
101 }
102}
103
104impl MountCmd {
105 fn with_default_config() -> Self {
106 Self {
107 path_template: Some(String::from("[{hostname}]/[{label}]/{time}")),
108 time_template: Some(String::from("%Y-%m-%d_%H-%M-%S")),
109 options: vec![String::from("kernel_cache")],
110 ..Default::default()
111 }
112 }
113
114 fn inner_run(&self, repo: IndexedRepo) -> Result<()> {
115 let config = RUSTIC_APP.config();
116
117 let Some(path_template) = config.mount.path_template.clone() else {
121 bail!("Please specify a path template!");
122 };
123
124 let Some(time_template) = config.mount.time_template.clone() else {
125 bail!("Please specify a time template!");
126 };
127
128 let Some(mount_point) = config.mount.mount_point.clone() else {
129 bail!("Please specify a mount point!");
130 };
131
132 let vfs = if let Some(snap) = &config.mount.snapshot_path {
133 let node =
134 repo.node_from_snapshot_path(snap, |sn| config.snapshot_filter.matches(sn))?;
135 Vfs::from_dir_node(&node)
136 } else {
137 let snapshots = get_filtered_snapshots(&repo)?;
138 Vfs::from_snapshots(
139 snapshots,
140 &path_template,
141 &time_template,
142 Latest::AsLink,
143 IdenticalSnapshot::AsLink,
144 )?
145 };
146
147 let mut mount_options = config.mount.options.clone();
149
150 mount_options.push(format!("fsname=rusticfs:{}", repo.config().id));
151
152 if !config.mount.exclusive {
153 mount_options
154 .extend_from_slice(&["allow_other".to_string(), "default_permissions".to_string()]);
155 }
156
157 let file_access = config.mount.file_access.as_ref().map_or_else(
158 || {
159 if repo.config().is_hot == Some(true) {
160 FilePolicy::Forbidden
161 } else {
162 FilePolicy::Read
163 }
164 },
165 |s| *s,
166 );
167
168 let fs = FuseMT::new(FuseFS::new(repo, vfs, file_access), 1);
169
170 mount_options.sort_unstable();
172 mount_options.dedup();
173
174 let opt_string = format!("-o {}", mount_options.join(","));
178
179 mount(fs, mount_point, &[OsStr::new(&opt_string)])?;
180
181 Ok(())
182 }
183}