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