rustic_rs/commands/mount/
fusefs.rs1#[cfg(not(windows))]
2use std::os::unix::prelude::OsStrExt;
3use std::{
4 collections::BTreeMap,
5 ffi::{CString, OsStr},
6 path::Path,
7 sync::RwLock,
8 time::{Duration, SystemTime},
9};
10
11use rustic_core::{
12 IndexedFull, Repository,
13 repofile::{Node, NodeType},
14 vfs::{FilePolicy, OpenFile, Vfs},
15};
16
17use fuse_mt::{
18 CallbackResult, DirectoryEntry, FileAttr, FileType, FilesystemMT, RequestInfo, ResultData,
19 ResultEmpty, ResultEntry, ResultOpen, ResultReaddir, ResultSlice, ResultXattr, Xattr,
20};
21use itertools::Itertools;
22
23pub struct FuseFS<P, S> {
24 repo: Repository<P, S>,
25 vfs: Vfs,
26 open_files: RwLock<BTreeMap<u64, OpenFile>>,
27 now: SystemTime,
28 file_policy: FilePolicy,
29}
30
31impl<P, S: IndexedFull> FuseFS<P, S> {
32 pub(crate) fn new(repo: Repository<P, S>, vfs: Vfs, file_policy: FilePolicy) -> Self {
33 let open_files = RwLock::new(BTreeMap::new());
34
35 Self {
36 repo,
37 vfs,
38 open_files,
39 now: SystemTime::now(),
40 file_policy,
41 }
42 }
43
44 fn node_from_path(&self, path: &Path) -> Result<Node, i32> {
45 self.vfs
46 .node_from_path(&self.repo, path)
47 .map_err(|_| libc::ENOENT)
48 }
49
50 fn dir_entries_from_path(&self, path: &Path) -> Result<Vec<Node>, i32> {
51 self.vfs
52 .dir_entries_from_path(&self.repo, path)
53 .map_err(|_| libc::ENOENT)
54 }
55}
56
57fn node_to_filetype(node: &Node) -> FileType {
58 match node.node_type {
59 NodeType::File => FileType::RegularFile,
60 NodeType::Dir => FileType::Directory,
61 NodeType::Symlink { .. } => FileType::Symlink,
62 NodeType::Chardev { .. } => FileType::CharDevice,
63 NodeType::Dev { .. } => FileType::BlockDevice,
64 NodeType::Fifo => FileType::NamedPipe,
65 NodeType::Socket => FileType::Socket,
66 }
67}
68
69fn node_type_to_rdev(tpe: &NodeType) -> u32 {
70 u32::try_from(match tpe {
71 NodeType::Dev { device } | NodeType::Chardev { device } => *device,
72 _ => 0,
73 })
74 .unwrap()
75}
76
77fn node_to_linktarget(node: &Node) -> Option<&OsStr> {
78 if node.is_symlink() {
79 Some(node.node_type.to_link().as_os_str())
80 } else {
81 None
82 }
83}
84
85fn node_to_file_attr(node: &Node, now: SystemTime) -> FileAttr {
86 FileAttr {
87 size: node.meta.size,
89 blocks: 0,
91 atime: node.meta.atime.map(SystemTime::from).unwrap_or(now),
93 mtime: node.meta.mtime.map(SystemTime::from).unwrap_or(now),
95 ctime: node.meta.ctime.map(SystemTime::from).unwrap_or(now),
97 crtime: now,
99 kind: node_to_filetype(node),
101 perm: node.meta.mode.unwrap_or(0o755) as u16,
103 nlink: node.meta.links.try_into().unwrap_or(1),
105 uid: node.meta.uid.unwrap_or(0),
107 gid: node.meta.gid.unwrap_or(0),
109 rdev: node_type_to_rdev(&node.node_type),
111 flags: 0,
113 }
114}
115
116impl<P, S: IndexedFull> FilesystemMT for FuseFS<P, S> {
117 fn getattr(&self, _req: RequestInfo, path: &Path, _fh: Option<u64>) -> ResultEntry {
118 let node = self.node_from_path(path)?;
119 Ok((Duration::from_secs(1), node_to_file_attr(&node, self.now)))
120 }
121
122 #[cfg(not(windows))]
123 fn readlink(&self, _req: RequestInfo, path: &Path) -> ResultData {
124 let target = node_to_linktarget(&self.node_from_path(path)?)
125 .ok_or(libc::ENOSYS)?
126 .as_bytes()
127 .to_vec();
128
129 Ok(target)
130 }
131
132 fn open(&self, _req: RequestInfo, path: &Path, _flags: u32) -> ResultOpen {
133 if matches!(self.file_policy, FilePolicy::Forbidden) {
134 return Err(libc::ENOTSUP);
135 }
136 let node = self.node_from_path(path)?;
137 let open = self.repo.open_file(&node).map_err(|_| libc::ENOSYS)?;
138 let fh = {
139 let mut open_files = self.open_files.write().unwrap();
140 let fh = open_files.last_key_value().map_or(0, |(fh, _)| *fh + 1);
141 _ = open_files.insert(fh, open);
142 fh
143 };
144 Ok((fh, 0))
145 }
146
147 fn release(
148 &self,
149 _req: RequestInfo,
150 _path: &Path,
151 fh: u64,
152 _flags: u32,
153 _lock_owner: u64,
154 _flush: bool,
155 ) -> ResultEmpty {
156 _ = self.open_files.write().unwrap().remove(&fh);
157 Ok(())
158 }
159
160 fn read(
161 &self,
162 _req: RequestInfo,
163 _path: &Path,
164 fh: u64,
165 offset: u64,
166 size: u32,
167 callback: impl FnOnce(ResultSlice<'_>) -> CallbackResult,
168 ) -> CallbackResult {
169 if let Some(open_file) = self.open_files.read().unwrap().get(&fh) {
170 if let Ok(data) =
171 self.repo
172 .read_file_at(open_file, offset.try_into().unwrap(), size as usize)
173 {
174 return callback(Ok(&data));
175 }
176 }
177 callback(Err(libc::ENOSYS))
178 }
179
180 fn opendir(&self, _req: RequestInfo, _path: &Path, _flags: u32) -> ResultOpen {
181 Ok((0, 0))
182 }
183
184 fn readdir(&self, _req: RequestInfo, path: &Path, _fh: u64) -> ResultReaddir {
185 let nodes = self.dir_entries_from_path(path)?;
186
187 let result = nodes
188 .into_iter()
189 .map(|node| DirectoryEntry {
190 name: node.name(),
191 kind: node_to_filetype(&node),
192 })
193 .collect();
194 Ok(result)
195 }
196
197 fn releasedir(&self, _req: RequestInfo, _path: &Path, _fh: u64, _flags: u32) -> ResultEmpty {
198 Ok(())
199 }
200
201 fn listxattr(&self, _req: RequestInfo, path: &Path, size: u32) -> ResultXattr {
202 let node = self.node_from_path(path)?;
203 let xattrs = node
204 .meta
205 .extended_attributes
206 .into_iter()
207 .map(|a| CString::new(a.name).unwrap().into_bytes_with_nul())
209 .concat();
210
211 if size == 0 {
212 Ok(Xattr::Size(u32::try_from(xattrs.len()).unwrap()))
213 } else {
214 Ok(Xattr::Data(xattrs))
215 }
216 }
217
218 fn getxattr(&self, _req: RequestInfo, path: &Path, name: &OsStr, size: u32) -> ResultXattr {
219 let node = self.node_from_path(path)?;
220 match node
221 .meta
222 .extended_attributes
223 .into_iter()
224 .find(|a| name == OsStr::new(&a.name))
225 {
226 None => Err(libc::ENOSYS),
227 Some(attr) => {
228 let value = attr.value.unwrap_or_default();
229 if size == 0 {
230 Ok(Xattr::Size(u32::try_from(value.len()).unwrap()))
231 } else {
232 Ok(Xattr::Data(value))
233 }
234 }
235 }
236 }
237}