rustic_rs/commands/webdav/
webdavfs.rs1#[cfg(not(windows))]
2use std::os::unix::ffi::OsStrExt;
3use std::{
4 fmt::{Debug, Formatter},
5 io::SeekFrom,
6 sync::{Arc, OnceLock},
7 time::SystemTime,
8};
9
10use bytes::{Buf, Bytes};
11use dav_server::{
12 davpath::DavPath,
13 fs::{
14 DavDirEntry, DavFile, DavFileSystem, DavMetaData, FsError, FsFuture, FsResult, FsStream,
15 OpenOptions, ReadDirMeta,
16 },
17};
18use futures::FutureExt;
19use rustic_core::{
20 repofile::Node,
21 vfs::{FilePolicy, OpenFile, Vfs},
22};
23use tokio::task::spawn_blocking;
24
25use crate::repository::IndexedRepo;
26
27fn now() -> SystemTime {
28 static NOW: OnceLock<SystemTime> = OnceLock::new();
29 *NOW.get_or_init(SystemTime::now)
30}
31
32struct DavFsInner {
34 repo: IndexedRepo,
36
37 vfs: Vfs,
39
40 file_policy: FilePolicy,
42}
43
44impl Debug for DavFsInner {
45 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
46 write!(f, "DavFS")
47 }
48}
49
50#[derive(Debug)]
55pub struct WebDavFS {
56 inner: Arc<DavFsInner>,
57}
58
59impl WebDavFS {
60 pub(crate) fn new(repo: IndexedRepo, vfs: Vfs, file_policy: FilePolicy) -> Self {
72 let inner = DavFsInner {
73 repo,
74 vfs,
75 file_policy,
76 };
77
78 Self {
79 inner: Arc::new(inner),
80 }
81 }
82
83 async fn node_from_path(&self, path: &DavPath) -> Result<Node, FsError> {
99 let inner = self.inner.clone();
100 let path = path.as_pathbuf();
101 spawn_blocking(move || {
102 inner
103 .vfs
104 .node_from_path(&inner.repo, &path)
105 .map_err(|_| FsError::GeneralFailure)
106 })
107 .await
108 .map_err(|_| FsError::GeneralFailure)?
109 }
110
111 async fn dir_entries_from_path(&self, path: &DavPath) -> Result<Vec<Node>, FsError> {
127 let inner = self.inner.clone();
128 let path = path.as_pathbuf();
129 spawn_blocking(move || {
130 inner
131 .vfs
132 .dir_entries_from_path(&inner.repo, &path)
133 .map_err(|_| FsError::GeneralFailure)
134 })
135 .await
136 .map_err(|_| FsError::GeneralFailure)?
137 }
138}
139
140impl Clone for WebDavFS {
141 fn clone(&self) -> Self {
142 Self {
143 inner: self.inner.clone(),
144 }
145 }
146}
147
148impl DavFileSystem for WebDavFS {
149 fn metadata<'a>(&'a self, davpath: &'a DavPath) -> FsFuture<'_, Box<dyn DavMetaData>> {
150 self.symlink_metadata(davpath)
151 }
152
153 fn symlink_metadata<'a>(&'a self, davpath: &'a DavPath) -> FsFuture<'_, Box<dyn DavMetaData>> {
154 async move {
155 let node = self.node_from_path(davpath).await?;
156 let meta: Box<dyn DavMetaData> = Box::new(DavFsMetaData(node));
157 Ok(meta)
158 }
159 .boxed()
160 }
161
162 fn read_dir<'a>(
163 &'a self,
164 davpath: &'a DavPath,
165 _meta: ReadDirMeta,
166 ) -> FsFuture<'_, FsStream<Box<dyn DavDirEntry>>> {
167 async move {
168 let entries = self.dir_entries_from_path(davpath).await?;
169 let entry_iter = entries.into_iter().map(|e| {
170 let entry: Box<dyn DavDirEntry> = Box::new(DavFsDirEntry(e));
171 Ok(entry)
172 });
173 let strm: FsStream<Box<dyn DavDirEntry>> = Box::pin(futures::stream::iter(entry_iter));
174 Ok(strm)
175 }
176 .boxed()
177 }
178
179 fn open<'a>(
180 &'a self,
181 path: &'a DavPath,
182 options: OpenOptions,
183 ) -> FsFuture<'_, Box<dyn DavFile>> {
184 async move {
185 if options.write
186 || options.append
187 || options.truncate
188 || options.create
189 || options.create_new
190 {
191 return Err(FsError::Forbidden);
192 }
193
194 let node = self.node_from_path(path).await?;
195 if matches!(self.inner.file_policy, FilePolicy::Forbidden) {
196 return Err(FsError::Forbidden);
197 }
198
199 let inner = self.inner.clone();
200 let node_copy = node.clone();
201 let open = spawn_blocking(move || {
202 inner
203 .repo
204 .open_file(&node_copy)
205 .map_err(|_err| FsError::GeneralFailure)
206 })
207 .await
208 .map_err(|_| FsError::GeneralFailure)??;
209
210 let file: Box<dyn DavFile> = Box::new(DavFsFile {
211 node,
212 open: Arc::new(open),
213 fs: self.inner.clone(),
214 seek: 0,
215 });
216 Ok(file)
217 }
218 .boxed()
219 }
220}
221
222#[derive(Clone, Debug)]
224struct DavFsDirEntry(Node);
225
226impl DavDirEntry for DavFsDirEntry {
227 fn metadata(&self) -> FsFuture<'_, Box<dyn DavMetaData>> {
228 async move {
229 let meta: Box<dyn DavMetaData> = Box::new(DavFsMetaData(self.0.clone()));
230 Ok(meta)
231 }
232 .boxed()
233 }
234
235 #[cfg(not(windows))]
236 fn name(&self) -> Vec<u8> {
237 self.0.name().as_bytes().to_vec()
238 }
239
240 #[cfg(windows)]
241 fn name(&self) -> Vec<u8> {
242 self.0
243 .name()
244 .as_os_str()
245 .to_string_lossy()
246 .to_string()
247 .into_bytes()
248 }
249}
250
251struct DavFsFile {
255 node: Node,
257
258 open: Arc<OpenFile>,
260
261 fs: Arc<DavFsInner>,
263
264 seek: usize,
266}
267
268impl Debug for DavFsFile {
269 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
270 write!(f, "DavFile")
271 }
272}
273
274impl DavFile for DavFsFile {
275 fn metadata(&mut self) -> FsFuture<'_, Box<dyn DavMetaData>> {
276 async move {
277 let meta: Box<dyn DavMetaData> = Box::new(DavFsMetaData(self.node.clone()));
278 Ok(meta)
279 }
280 .boxed()
281 }
282
283 fn write_bytes(&mut self, _buf: Bytes) -> FsFuture<'_, ()> {
284 async move { Err(FsError::Forbidden) }.boxed()
285 }
286
287 fn write_buf(&mut self, _buf: Box<dyn Buf + Send>) -> FsFuture<'_, ()> {
288 async move { Err(FsError::Forbidden) }.boxed()
289 }
290
291 fn read_bytes(&mut self, count: usize) -> FsFuture<'_, Bytes> {
292 let fs = self.fs.clone();
293 let seek = self.seek;
294 let open = self.open.clone();
295 async move {
296 let data = spawn_blocking(move || {
297 fs.repo
298 .read_file_at(&open, seek, count)
299 .map_err(|_err| FsError::GeneralFailure)
300 })
301 .await
302 .map_err(|_| FsError::GeneralFailure)??;
303 self.seek += data.len();
304 Ok(data)
305 }
306 .boxed()
307 }
308
309 fn seek(&mut self, pos: SeekFrom) -> FsFuture<'_, u64> {
310 async move {
311 match pos {
312 SeekFrom::Start(start) => {
313 self.seek = usize::try_from(start).expect("usize overflow should not happen");
314 }
315 SeekFrom::Current(delta) => {
316 self.seek = usize::try_from(
317 i64::try_from(self.seek).expect("i64 wrapped around") + delta,
318 )
319 .expect("usize overflow should not happen");
320 }
321 SeekFrom::End(end) => {
322 self.seek = usize::try_from(
323 i64::try_from(self.node.meta.size).expect("i64 wrapped around") + end,
324 )
325 .expect("usize overflow should not happen");
326 }
327 }
328
329 Ok(self.seek as u64)
330 }
331 .boxed()
332 }
333
334 fn flush(&mut self) -> FsFuture<'_, ()> {
335 async move { Ok(()) }.boxed()
336 }
337}
338
339#[derive(Clone, Debug)]
341struct DavFsMetaData(Node);
342
343impl DavMetaData for DavFsMetaData {
344 fn len(&self) -> u64 {
345 self.0.meta.size
346 }
347 fn created(&self) -> FsResult<SystemTime> {
348 Ok(now())
349 }
350 fn modified(&self) -> FsResult<SystemTime> {
351 Ok(self.0.meta.mtime.map_or_else(now, SystemTime::from))
352 }
353 fn accessed(&self) -> FsResult<SystemTime> {
354 Ok(self.0.meta.atime.map_or_else(now, SystemTime::from))
355 }
356
357 fn status_changed(&self) -> FsResult<SystemTime> {
358 Ok(self.0.meta.ctime.map_or_else(now, SystemTime::from))
359 }
360
361 fn is_dir(&self) -> bool {
362 self.0.is_dir()
363 }
364 fn is_file(&self) -> bool {
365 self.0.is_file()
366 }
367 fn is_symlink(&self) -> bool {
368 self.0.is_symlink()
369 }
370 fn executable(&self) -> FsResult<bool> {
371 if self.0.is_file() {
372 let Some(mode) = self.0.meta.mode else {
373 return Ok(false);
374 };
375 return Ok((mode & 0o100) > 0);
376 }
377 Err(FsError::NotImplemented)
378 }
379}