Skip to main content

dav_server/
localfs.rs

1//! Local filesystem access.
2//!
3//! This implementation is stateless. So the easiest way to use it
4//! is to create a new instance in your handler every time
5//! you need one.
6
7use std::any::Any;
8use std::collections::VecDeque;
9use std::future::{self, Future};
10use std::io::{self, Read, Seek, SeekFrom, Write};
11use std::mem;
12#[cfg(unix)]
13use std::os::unix::{
14    ffi::OsStrExt,
15    fs::{DirBuilderExt, MetadataExt, OpenOptionsExt, PermissionsExt},
16};
17#[cfg(target_os = "windows")]
18use std::os::windows::prelude::*;
19use std::path::{Path, PathBuf};
20use std::pin::Pin;
21use std::pin::pin;
22use std::sync::Arc;
23use std::task::{Context, Poll};
24use std::time::{Duration, SystemTime, UNIX_EPOCH};
25
26use bytes::{Buf, Bytes, BytesMut};
27use futures_util::{FutureExt, Stream, future::BoxFuture};
28use tokio::task;
29
30use libc;
31use reflink_copy::reflink_or_copy;
32
33use crate::davpath::DavPath;
34use crate::fs::*;
35use crate::localfs_macos::DUCacheBuilder;
36
37// Run some code via block_in_place() or spawn_blocking().
38//
39// There's also a method on LocalFs for this, use the freestanding
40// function if you do not want the fs_access_guard() closure to be used.
41#[cfg(feature = "localfs")]
42#[inline]
43async fn blocking<F, R>(func: F) -> R
44where
45    F: FnOnce() -> R,
46    F: Send + 'static,
47    R: Send + 'static,
48{
49    match tokio::runtime::Handle::current().runtime_flavor() {
50        tokio::runtime::RuntimeFlavor::MultiThread => task::block_in_place(func),
51        _ => task::spawn_blocking(func).await.unwrap(),
52    }
53}
54
55#[derive(Debug, Clone)]
56struct LocalFsMetaData(std::fs::Metadata);
57
58/// Local Filesystem implementation.
59#[derive(Clone)]
60pub struct LocalFs {
61    pub(crate) inner: Arc<LocalFsInner>,
62}
63
64// inner struct.
65pub(crate) struct LocalFsInner {
66    pub basedir: PathBuf,
67    #[allow(dead_code)]
68    pub public: bool,
69    pub case_insensitive: bool,
70    pub macos: bool,
71    pub is_file: bool,
72    pub fs_access_guard: Option<Box<dyn Fn() -> Box<dyn Any> + Send + Sync + 'static>>,
73}
74
75#[derive(Debug)]
76struct LocalFsFile {
77    file: Option<std::fs::File>,
78    buf: BytesMut,
79}
80
81struct LocalFsReadDir {
82    fs: LocalFs,
83    do_meta: ReadDirMeta,
84    buffer: VecDeque<io::Result<LocalFsDirEntry>>,
85    dir_cache: Option<DUCacheBuilder>,
86    iterator: Option<std::fs::ReadDir>,
87    fut: Option<BoxFuture<'static, ReadDirBatch>>,
88}
89
90// a DirEntry either already has the metadata available, or a handle
91// to the filesystem so it can call fs.blocking()
92enum Meta {
93    Data(io::Result<std::fs::Metadata>),
94    Fs(LocalFs),
95}
96
97/// Helper function to create directory on basedir
98#[allow(unused)]
99fn helper_create_directory(basedir: &Path, _new_dir_name: &str) {
100    let new_path = basedir.join(_new_dir_name);
101    std::fs::create_dir_all(&new_path)
102        .expect("Failed to create default CalDAV directory; verify that 'basedir' is correct.");
103}
104
105// Items from the readdir stream.
106struct LocalFsDirEntry {
107    meta: Meta,
108    entry: std::fs::DirEntry,
109}
110
111impl LocalFs {
112    /// Create a new LocalFs DavFileSystem, serving "base".
113    ///
114    /// If "public" is set to true, all files and directories created will be
115    /// publically readable (mode 644/755), otherwise they will be private
116    /// (mode 600/700). Umask still overrides this.
117    ///
118    /// If "case_insensitive" is set to true, all filesystem lookups will
119    /// be case insensitive. Note that this has a _lot_ of overhead!
120    pub fn new<P: AsRef<Path>>(
121        base: P,
122        public: bool,
123        case_insensitive: bool,
124        macos: bool,
125    ) -> Box<LocalFs> {
126        let basedir = base.as_ref().to_path_buf();
127
128        #[cfg(feature = "caldav")]
129        helper_create_directory(&basedir, crate::caldav::DEFAULT_CALDAV_NAME);
130
131        #[cfg(feature = "carddav")]
132        helper_create_directory(&basedir, crate::carddav::DEFAULT_CARDDAV_NAME);
133
134        let inner = LocalFsInner {
135            basedir,
136            public,
137            macos,
138            case_insensitive,
139            is_file: false,
140            fs_access_guard: None,
141        };
142        Box::new({
143            LocalFs {
144                inner: Arc::new(inner),
145            }
146        })
147    }
148
149    /// Create a new LocalFs DavFileSystem, serving "file".
150    ///
151    /// This is like `new()`, but it always serves this single file.
152    /// The request path is ignored.
153    pub fn new_file<P: AsRef<Path>>(file: P, public: bool) -> Box<LocalFs> {
154        let inner = LocalFsInner {
155            basedir: file.as_ref().to_path_buf(),
156            public,
157            macos: false,
158            case_insensitive: false,
159            is_file: true,
160            fs_access_guard: None,
161        };
162        Box::new({
163            LocalFs {
164                inner: Arc::new(inner),
165            }
166        })
167    }
168
169    // Like new() but pass in a fs_access_guard hook.
170    #[doc(hidden)]
171    pub fn new_with_fs_access_guard<P: AsRef<Path>>(
172        base: P,
173        public: bool,
174        case_insensitive: bool,
175        macos: bool,
176        fs_access_guard: Option<Box<dyn Fn() -> Box<dyn Any> + Send + Sync + 'static>>,
177    ) -> Box<LocalFs> {
178        let basedir = base.as_ref().to_path_buf();
179
180        #[cfg(feature = "caldav")]
181        helper_create_directory(&basedir, crate::caldav::DEFAULT_CALDAV_NAME);
182
183        #[cfg(feature = "carddav")]
184        helper_create_directory(&basedir, crate::carddav::DEFAULT_CARDDAV_NAME);
185
186        let inner = LocalFsInner {
187            basedir,
188            public,
189            macos,
190            case_insensitive,
191            is_file: false,
192            fs_access_guard,
193        };
194        Box::new({
195            LocalFs {
196                inner: Arc::new(inner),
197            }
198        })
199    }
200
201    fn fspath_dbg(&self, path: &DavPath) -> PathBuf {
202        let mut pathbuf = self.inner.basedir.clone();
203        if !self.inner.is_file {
204            pathbuf.push(path.as_rel_ospath());
205        }
206        pathbuf
207    }
208
209    fn fspath(&self, path: &DavPath) -> PathBuf {
210        if self.inner.case_insensitive {
211            crate::localfs_windows::resolve(&self.inner.basedir, path)
212        } else {
213            let mut pathbuf = self.inner.basedir.clone();
214            if !self.inner.is_file {
215                pathbuf.push(path.as_rel_ospath());
216            }
217            pathbuf
218        }
219    }
220
221    // threadpool::blocking() adapter, also runs the before/after hooks.
222    #[doc(hidden)]
223    pub async fn blocking<F, R>(&self, func: F) -> R
224    where
225        F: FnOnce() -> R + Send + 'static,
226        R: Send + 'static,
227    {
228        let this = self.clone();
229        blocking(move || {
230            let _guard = this.inner.fs_access_guard.as_ref().map(|f| f());
231            func()
232        })
233        .await
234    }
235}
236
237// This implementation is basically a bunch of boilerplate to
238// wrap the std::fs call in self.blocking() calls.
239impl DavFileSystem for LocalFs {
240    fn metadata<'a>(&'a self, davpath: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>> {
241        async move {
242            if let Some(meta) = self.is_virtual(davpath) {
243                return Ok(meta);
244            }
245            let path = self.fspath(davpath);
246            if self.is_notfound(&path) {
247                return Err(FsError::NotFound);
248            }
249            self.blocking(move || match std::fs::metadata(path) {
250                Ok(meta) => Ok(Box::new(LocalFsMetaData(meta)) as Box<dyn DavMetaData>),
251                Err(e) => Err(e.into()),
252            })
253            .await
254        }
255        .boxed()
256    }
257
258    fn symlink_metadata<'a>(&'a self, davpath: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>> {
259        async move {
260            if let Some(meta) = self.is_virtual(davpath) {
261                return Ok(meta);
262            }
263            let path = self.fspath(davpath);
264            if self.is_notfound(&path) {
265                return Err(FsError::NotFound);
266            }
267            self.blocking(move || match std::fs::symlink_metadata(path) {
268                Ok(meta) => Ok(Box::new(LocalFsMetaData(meta)) as Box<dyn DavMetaData>),
269                Err(e) => Err(e.into()),
270            })
271            .await
272        }
273        .boxed()
274    }
275
276    // read_dir is a bit more involved - but not much - than a simple wrapper,
277    // because it returns a stream.
278    fn read_dir<'a>(
279        &'a self,
280        davpath: &'a DavPath,
281        meta: ReadDirMeta,
282    ) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>> {
283        async move {
284            trace!("FS: read_dir {:?}", self.fspath_dbg(davpath));
285            let path = self.fspath(davpath);
286            let path2 = path.clone();
287            let iter = self.blocking(move || std::fs::read_dir(&path)).await;
288            match iter {
289                Ok(iterator) => {
290                    let strm = LocalFsReadDir {
291                        fs: self.clone(),
292                        do_meta: meta,
293                        buffer: VecDeque::new(),
294                        dir_cache: self.dir_cache_builder(path2),
295                        iterator: Some(iterator),
296                        fut: None,
297                    };
298                    Ok(Box::pin(strm) as FsStream<Box<dyn DavDirEntry>>)
299                }
300                Err(e) => Err(e.into()),
301            }
302        }
303        .boxed()
304    }
305
306    fn open<'a>(
307        &'a self,
308        path: &'a DavPath,
309        options: OpenOptions,
310    ) -> FsFuture<'a, Box<dyn DavFile>> {
311        async move {
312            trace!("FS: open {:?}", self.fspath_dbg(path));
313            if self.is_forbidden(path) {
314                return Err(FsError::Forbidden);
315            }
316            #[cfg(unix)]
317            let mode = if self.inner.public { 0o644 } else { 0o600 };
318            let path = self.fspath(path);
319            self.blocking(move || {
320                #[cfg(unix)]
321                let res = std::fs::OpenOptions::new()
322                    .read(options.read)
323                    .write(options.write)
324                    .append(options.append)
325                    .truncate(options.truncate)
326                    .create(options.create)
327                    .create_new(options.create_new)
328                    .mode(mode)
329                    .open(path);
330                #[cfg(windows)]
331                let res = std::fs::OpenOptions::new()
332                    .read(options.read)
333                    .write(options.write)
334                    .append(options.append)
335                    .truncate(options.truncate)
336                    .create(options.create)
337                    .create_new(options.create_new)
338                    .open(path);
339                match res {
340                    Ok(file) => Ok(Box::new(LocalFsFile {
341                        file: Some(file),
342                        buf: BytesMut::new(),
343                    }) as Box<dyn DavFile>),
344                    Err(e) => Err(e.into()),
345                }
346            })
347            .await
348        }
349        .boxed()
350    }
351
352    fn create_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
353        async move {
354            trace!("FS: create_dir {:?}", self.fspath_dbg(path));
355            if self.is_forbidden(path) {
356                return Err(FsError::Forbidden);
357            }
358            #[cfg(unix)]
359            let mode = if self.inner.public { 0o755 } else { 0o700 };
360            let path = self.fspath(path);
361            self.blocking(move || {
362                #[cfg(unix)]
363                {
364                    std::fs::DirBuilder::new()
365                        .mode(mode)
366                        .create(path)
367                        .map_err(|e| e.into())
368                }
369                #[cfg(windows)]
370                {
371                    std::fs::DirBuilder::new()
372                        .create(path)
373                        .map_err(|e| e.into())
374                }
375            })
376            .await
377        }
378        .boxed()
379    }
380
381    fn remove_dir<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
382        async move {
383            trace!("FS: remove_dir {:?}", self.fspath_dbg(path));
384            let path = self.fspath(path);
385            self.blocking(move || std::fs::remove_dir(path).map_err(|e| e.into()))
386                .await
387        }
388        .boxed()
389    }
390
391    fn remove_file<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, ()> {
392        async move {
393            trace!("FS: remove_file {:?}", self.fspath_dbg(path));
394            if self.is_forbidden(path) {
395                return Err(FsError::Forbidden);
396            }
397            let path = self.fspath(path);
398            self.blocking(move || std::fs::remove_file(path).map_err(|e| e.into()))
399                .await
400        }
401        .boxed()
402    }
403
404    fn rename<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()> {
405        async move {
406            trace!(
407                "FS: rename {:?} {:?}",
408                self.fspath_dbg(from),
409                self.fspath_dbg(to)
410            );
411            if self.is_forbidden(from) || self.is_forbidden(to) {
412                return Err(FsError::Forbidden);
413            }
414            let frompath = self.fspath(from);
415            let topath = self.fspath(to);
416            self.blocking(move || {
417                match std::fs::rename(&frompath, &topath) {
418                    Ok(v) => Ok(v),
419                    Err(e) => {
420                        // webdav allows a rename from a directory to a file.
421                        // note that this check is racy, and I'm not quite sure what
422                        // we should do if the source is a symlink. anyway ...
423                        if e.raw_os_error() == Some(libc::ENOTDIR) && frompath.is_dir() {
424                            // remove and try again.
425                            let _ = std::fs::remove_file(&topath);
426                            std::fs::rename(frompath, topath).map_err(|e| e.into())
427                        } else {
428                            Err(e.into())
429                        }
430                    }
431                }
432            })
433            .await
434        }
435        .boxed()
436    }
437
438    fn copy<'a>(&'a self, from: &'a DavPath, to: &'a DavPath) -> FsFuture<'a, ()> {
439        async move {
440            trace!(
441                "FS: copy {:?} {:?}",
442                self.fspath_dbg(from),
443                self.fspath_dbg(to)
444            );
445            if self.is_forbidden(from) || self.is_forbidden(to) {
446                return Err(FsError::Forbidden);
447            }
448            let path_from = self.fspath(from);
449            let path_to = self.fspath(to);
450
451            match self
452                .blocking(move || reflink_or_copy(path_from, path_to))
453                .await
454            {
455                Ok(_) => Ok(()),
456                Err(e) => {
457                    debug!(
458                        "copy({:?}, {:?}) failed: {}",
459                        self.fspath_dbg(from),
460                        self.fspath_dbg(to),
461                        e
462                    );
463                    Err(e.into())
464                }
465            }
466        }
467        .boxed()
468    }
469}
470
471// read_batch() result.
472struct ReadDirBatch {
473    iterator: Option<std::fs::ReadDir>,
474    buffer: VecDeque<io::Result<LocalFsDirEntry>>,
475}
476
477// Read the next batch of LocalFsDirEntry structs (up to 256).
478// This is sync code, must be run in `blocking()`.
479fn read_batch(
480    iterator: Option<std::fs::ReadDir>,
481    fs: LocalFs,
482    do_meta: ReadDirMeta,
483) -> ReadDirBatch {
484    let mut buffer = VecDeque::new();
485    let mut iterator = match iterator {
486        Some(i) => i,
487        None => {
488            return ReadDirBatch {
489                buffer,
490                iterator: None,
491            };
492        }
493    };
494    let _guard = match do_meta {
495        ReadDirMeta::None => None,
496        _ => fs.inner.fs_access_guard.as_ref().map(|f| f()),
497    };
498    for _ in 0..256 {
499        match iterator.next() {
500            Some(Ok(entry)) => {
501                let meta = match do_meta {
502                    ReadDirMeta::Data => Meta::Data(std::fs::metadata(entry.path())),
503                    ReadDirMeta::DataSymlink => Meta::Data(entry.metadata()),
504                    ReadDirMeta::None => Meta::Fs(fs.clone()),
505                };
506                let d = LocalFsDirEntry { meta, entry };
507                buffer.push_back(Ok(d))
508            }
509            Some(Err(e)) => {
510                buffer.push_back(Err(e));
511                break;
512            }
513            None => break,
514        }
515    }
516    ReadDirBatch {
517        buffer,
518        iterator: Some(iterator),
519    }
520}
521
522impl LocalFsReadDir {
523    // Create a future that calls read_batch().
524    //
525    // The 'iterator' is moved into the future, and returned when it completes,
526    // together with a list of directory entries.
527    fn read_batch(&mut self) -> BoxFuture<'static, ReadDirBatch> {
528        let iterator = self.iterator.take();
529        let fs = self.fs.clone();
530        let do_meta = self.do_meta;
531
532        let fut: BoxFuture<ReadDirBatch> =
533            blocking(move || read_batch(iterator, fs, do_meta)).boxed();
534        fut
535    }
536}
537
538// The stream implementation tries to be smart and batch I/O operations
539impl Stream for LocalFsReadDir {
540    type Item = FsResult<Box<dyn DavDirEntry>>;
541
542    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
543        let this = Pin::into_inner(self);
544
545        // If the buffer is empty, fill it.
546        if this.buffer.is_empty() {
547            // If we have no pending future, create one.
548            if this.fut.is_none() {
549                if this.iterator.is_none() {
550                    return Poll::Ready(None);
551                }
552                this.fut = Some(this.read_batch());
553            }
554
555            // Poll the future.
556            let mut fut = pin!(this.fut.as_mut().unwrap());
557            match Pin::new(&mut fut).poll(cx) {
558                Poll::Ready(batch) => {
559                    this.fut.take();
560                    if let Some(ref mut nb) = this.dir_cache {
561                        batch.buffer.iter().for_each(|e| {
562                            if let Ok(e) = e {
563                                nb.add(e.entry.file_name());
564                            }
565                        });
566                    }
567                    this.buffer = batch.buffer;
568                    this.iterator = batch.iterator;
569                }
570                Poll::Pending => return Poll::Pending,
571            }
572        }
573
574        // we filled the buffer, now pop from the buffer.
575        match this.buffer.pop_front() {
576            Some(Ok(item)) => Poll::Ready(Some(Ok(Box::new(item)))),
577            Some(Err(err)) => {
578                // fuse the iterator.
579                this.iterator.take();
580                // finish the cache.
581                if let Some(ref mut nb) = this.dir_cache {
582                    nb.finish();
583                }
584                // return error of stream.
585                Poll::Ready(Some(Err(err.into())))
586            }
587            None => {
588                // fuse the iterator.
589                this.iterator.take();
590                // finish the cache.
591                if let Some(ref mut nb) = this.dir_cache {
592                    nb.finish();
593                }
594                // return end-of-stream.
595                Poll::Ready(None)
596            }
597        }
598    }
599}
600
601enum Is {
602    File,
603    Dir,
604    Symlink,
605}
606
607impl LocalFsDirEntry {
608    async fn is_a(&self, is: Is) -> FsResult<bool> {
609        match self.meta {
610            Meta::Data(Ok(ref meta)) => Ok(match is {
611                Is::File => meta.file_type().is_file(),
612                Is::Dir => meta.file_type().is_dir(),
613                Is::Symlink => meta.file_type().is_symlink(),
614            }),
615            Meta::Data(Err(ref e)) => Err(e.into()),
616            Meta::Fs(ref fs) => {
617                let fullpath = self.entry.path();
618                let ft = fs
619                    .blocking(move || std::fs::metadata(&fullpath))
620                    .await?
621                    .file_type();
622                Ok(match is {
623                    Is::File => ft.is_file(),
624                    Is::Dir => ft.is_dir(),
625                    Is::Symlink => ft.is_symlink(),
626                })
627            }
628        }
629    }
630}
631
632impl DavDirEntry for LocalFsDirEntry {
633    fn metadata(&'_ self) -> FsFuture<'_, Box<dyn DavMetaData>> {
634        match self.meta {
635            Meta::Data(ref meta) => {
636                let m = match meta {
637                    Ok(meta) => Ok(Box::new(LocalFsMetaData(meta.clone())) as Box<dyn DavMetaData>),
638                    Err(e) => Err(e.into()),
639                };
640                Box::pin(future::ready(m))
641            }
642            Meta::Fs(ref fs) => {
643                let fullpath = self.entry.path();
644                fs.blocking(move || match std::fs::metadata(&fullpath) {
645                    Ok(meta) => Ok(Box::new(LocalFsMetaData(meta)) as Box<dyn DavMetaData>),
646                    Err(e) => Err(e.into()),
647                })
648                .boxed()
649            }
650        }
651    }
652
653    #[cfg(unix)]
654    fn name(&self) -> Vec<u8> {
655        self.entry.file_name().as_bytes().to_vec()
656    }
657
658    #[cfg(windows)]
659    fn name(&self) -> Vec<u8> {
660        self.entry.file_name().to_str().unwrap().as_bytes().to_vec()
661    }
662
663    fn is_dir(&'_ self) -> FsFuture<'_, bool> {
664        Box::pin(self.is_a(Is::Dir))
665    }
666
667    fn is_file(&'_ self) -> FsFuture<'_, bool> {
668        Box::pin(self.is_a(Is::File))
669    }
670
671    fn is_symlink(&'_ self) -> FsFuture<'_, bool> {
672        Box::pin(self.is_a(Is::Symlink))
673    }
674}
675
676impl DavFile for LocalFsFile {
677    fn metadata(&'_ mut self) -> FsFuture<'_, Box<dyn DavMetaData>> {
678        async move {
679            let file = self.file.take().unwrap();
680            let (meta, file) = blocking(move || (file.metadata(), file)).await;
681            self.file = Some(file);
682            Ok(Box::new(LocalFsMetaData(meta?)) as Box<dyn DavMetaData>)
683        }
684        .boxed()
685    }
686
687    fn write_bytes(&'_ mut self, buf: Bytes) -> FsFuture<'_, ()> {
688        async move {
689            let mut file = self.file.take().unwrap();
690            let (res, file) = blocking(move || (file.write_all(&buf), file)).await;
691            self.file = Some(file);
692            res.map_err(|e| e.into())
693        }
694        .boxed()
695    }
696
697    fn write_buf(&'_ mut self, mut buf: Box<dyn Buf + Send>) -> FsFuture<'_, ()> {
698        async move {
699            let mut file = self.file.take().unwrap();
700            let (res, file) = blocking(move || {
701                while buf.remaining() > 0 {
702                    let n = match file.write(buf.chunk()) {
703                        Ok(n) => n,
704                        Err(e) => return (Err(e), file),
705                    };
706                    buf.advance(n);
707                }
708                (Ok(()), file)
709            })
710            .await;
711            self.file = Some(file);
712            res.map_err(|e| e.into())
713        }
714        .boxed()
715    }
716
717    fn read_bytes(&'_ mut self, count: usize) -> FsFuture<'_, Bytes> {
718        async move {
719            let mut file = self.file.take().unwrap();
720            let mut buf = mem::take(&mut self.buf);
721            let (res, file, buf) = blocking(move || {
722                buf.reserve(count);
723                let res = unsafe {
724                    buf.set_len(count);
725                    file.read(&mut buf).map(|n| {
726                        buf.set_len(n);
727                        buf.split().freeze()
728                    })
729                };
730                (res, file, buf)
731            })
732            .await;
733            self.file = Some(file);
734            self.buf = buf;
735            res.map_err(|e| e.into())
736        }
737        .boxed()
738    }
739
740    fn seek(&'_ mut self, pos: SeekFrom) -> FsFuture<'_, u64> {
741        async move {
742            let mut file = self.file.take().unwrap();
743            let (res, file) = blocking(move || (file.seek(pos), file)).await;
744            self.file = Some(file);
745            res.map_err(|e| e.into())
746        }
747        .boxed()
748    }
749
750    fn flush(&'_ mut self) -> FsFuture<'_, ()> {
751        async move {
752            let mut file = self.file.take().unwrap();
753            let (res, file) = blocking(move || (file.flush(), file)).await;
754            self.file = Some(file);
755            res.map_err(|e| e.into())
756        }
757        .boxed()
758    }
759}
760
761impl DavMetaData for LocalFsMetaData {
762    fn len(&self) -> u64 {
763        self.0.len()
764    }
765    fn created(&self) -> FsResult<SystemTime> {
766        self.0.created().map_err(|e| e.into())
767    }
768    fn modified(&self) -> FsResult<SystemTime> {
769        self.0.modified().map_err(|e| e.into())
770    }
771    fn accessed(&self) -> FsResult<SystemTime> {
772        self.0.accessed().map_err(|e| e.into())
773    }
774
775    #[cfg(unix)]
776    fn status_changed(&self) -> FsResult<SystemTime> {
777        Ok(UNIX_EPOCH + Duration::new(self.0.ctime() as u64, 0))
778    }
779
780    #[cfg(windows)]
781    fn status_changed(&self) -> FsResult<SystemTime> {
782        Ok(UNIX_EPOCH + Duration::from_nanos(self.0.creation_time() - 116444736000000000))
783    }
784
785    fn is_dir(&self) -> bool {
786        self.0.is_dir()
787    }
788    fn is_file(&self) -> bool {
789        self.0.is_file()
790    }
791    fn is_symlink(&self) -> bool {
792        self.0.file_type().is_symlink()
793    }
794    #[cfg(feature = "caldav")]
795    fn is_calendar(&self, path: &DavPath) -> bool {
796        crate::caldav::is_path_in_caldav_directory(path)
797    }
798    #[cfg(feature = "carddav")]
799    fn is_addressbook(&self, path: &DavPath) -> bool {
800        crate::carddav::is_path_in_carddav_directory(path)
801    }
802
803    #[cfg(unix)]
804    fn executable(&self) -> FsResult<bool> {
805        if self.0.is_file() {
806            return Ok((self.0.permissions().mode() & 0o100) > 0);
807        }
808        Err(FsError::NotImplemented)
809    }
810
811    #[cfg(windows)]
812    fn executable(&self) -> FsResult<bool> {
813        // FIXME: implement
814        Err(FsError::NotImplemented)
815    }
816
817    // same as the default apache etag.
818    #[cfg(unix)]
819    fn etag(&self) -> Option<String> {
820        let modified = self.0.modified().ok()?;
821        let t = modified.duration_since(UNIX_EPOCH).ok()?;
822        let t = t.as_secs() * 1000000 + t.subsec_nanos() as u64 / 1000;
823        if self.is_file() {
824            Some(format!("{:x}-{:x}-{:x}", self.0.ino(), self.0.len(), t))
825        } else {
826            Some(format!("{:x}-{:x}", self.0.ino(), t))
827        }
828    }
829
830    // same as the default apache etag.
831    #[cfg(windows)]
832    fn etag(&self) -> Option<String> {
833        let modified = self.0.modified().ok()?;
834        let t = modified.duration_since(UNIX_EPOCH).ok()?;
835        let t = t.as_secs() * 1000000 + t.subsec_nanos() as u64 / 1000;
836        if self.is_file() {
837            Some(format!("{:x}-{:x}", self.0.len(), t))
838        } else {
839            Some(format!("{:x}", t))
840        }
841    }
842}