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::Future;
10use std::io::{self, Read, Seek, SeekFrom, Write};
11#[cfg(unix)]
12use std::os::unix::{
13    ffi::OsStrExt,
14    fs::{DirBuilderExt, MetadataExt, OpenOptionsExt, PermissionsExt},
15};
16#[cfg(target_os = "windows")]
17use std::os::windows::prelude::*;
18use std::path::{Path, PathBuf};
19use std::pin::Pin;
20use std::sync::atomic::{AtomicU32, Ordering};
21use std::sync::Arc;
22use std::task::{Context, Poll};
23use std::time::{Duration, SystemTime, UNIX_EPOCH};
24
25use bytes::{Buf, Bytes, BytesMut};
26use futures_util::{future, future::BoxFuture, FutureExt, Stream};
27use pin_utils::pin_mut;
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
37const RUNTIME_TYPE_BASIC: u32 = 1;
38const RUNTIME_TYPE_THREADPOOL: u32 = 2;
39static RUNTIME_TYPE: AtomicU32 = AtomicU32::new(0);
40
41#[derive(Clone, Copy)]
42#[repr(u32)]
43enum RuntimeType {
44    Basic = RUNTIME_TYPE_BASIC,
45    ThreadPool = RUNTIME_TYPE_THREADPOOL,
46}
47
48impl RuntimeType {
49    #[inline]
50    fn get() -> RuntimeType {
51        match RUNTIME_TYPE.load(Ordering::Relaxed) {
52            RUNTIME_TYPE_BASIC => RuntimeType::Basic,
53            RUNTIME_TYPE_THREADPOOL => RuntimeType::ThreadPool,
54            _ => {
55                let dbg = format!("{:?}", tokio::runtime::Handle::current());
56                let rt = if dbg.contains("ThreadPool") {
57                    RuntimeType::ThreadPool
58                } else {
59                    RuntimeType::Basic
60                };
61                RUNTIME_TYPE.store(rt as u32, Ordering::SeqCst);
62                rt
63            }
64        }
65    }
66}
67
68// Run some code via block_in_place() or spawn_blocking().
69//
70// There's also a method on LocalFs for this, use the freestanding
71// function if you do not want the fs_access_guard() closure to be used.
72#[inline]
73async fn blocking<F, R>(func: F) -> R
74where
75    F: FnOnce() -> R,
76    F: Send + 'static,
77    R: Send + 'static,
78{
79    match RuntimeType::get() {
80        RuntimeType::Basic => task::spawn_blocking(func).await.unwrap(),
81        RuntimeType::ThreadPool => task::block_in_place(func),
82    }
83}
84
85#[derive(Debug, Clone)]
86struct LocalFsMetaData(std::fs::Metadata);
87
88/// Local Filesystem implementation.
89#[derive(Clone)]
90pub struct LocalFs {
91    pub(crate) inner: Arc<LocalFsInner>,
92}
93
94// inner struct.
95pub(crate) struct LocalFsInner {
96    pub basedir: PathBuf,
97    #[allow(dead_code)]
98    pub public: bool,
99    pub case_insensitive: bool,
100    pub macos: bool,
101    pub is_file: bool,
102    pub fs_access_guard: Option<Box<dyn Fn() -> Box<dyn Any> + Send + Sync + 'static>>,
103}
104
105#[derive(Debug)]
106struct LocalFsFile(Option<std::fs::File>);
107
108struct LocalFsReadDir {
109    fs: LocalFs,
110    do_meta: ReadDirMeta,
111    buffer: VecDeque<io::Result<LocalFsDirEntry>>,
112    dir_cache: Option<DUCacheBuilder>,
113    iterator: Option<std::fs::ReadDir>,
114    fut: Option<BoxFuture<'static, ReadDirBatch>>,
115}
116
117// a DirEntry either already has the metadata available, or a handle
118// to the filesystem so it can call fs.blocking()
119enum Meta {
120    Data(io::Result<std::fs::Metadata>),
121    Fs(LocalFs),
122}
123
124// Items from the readdir stream.
125struct LocalFsDirEntry {
126    meta: Meta,
127    entry: std::fs::DirEntry,
128}
129
130impl LocalFs {
131    /// Create a new LocalFs DavFileSystem, serving "base".
132    ///
133    /// If "public" is set to true, all files and directories created will be
134    /// publically readable (mode 644/755), otherwise they will be private
135    /// (mode 600/700). Umask still overrides this.
136    ///
137    /// If "case_insensitive" is set to true, all filesystem lookups will
138    /// be case insensitive. Note that this has a _lot_ of overhead!
139    pub fn new<P: AsRef<Path>>(
140        base: P,
141        public: bool,
142        case_insensitive: bool,
143        macos: bool,
144    ) -> Box<LocalFs> {
145        let inner = LocalFsInner {
146            basedir: base.as_ref().to_path_buf(),
147            public,
148            macos,
149            case_insensitive,
150            is_file: false,
151            fs_access_guard: None,
152        };
153        Box::new({
154            LocalFs {
155                inner: Arc::new(inner),
156            }
157        })
158    }
159
160    /// Create a new LocalFs DavFileSystem, serving "file".
161    ///
162    /// This is like `new()`, but it always serves this single file.
163    /// The request path is ignored.
164    pub fn new_file<P: AsRef<Path>>(file: P, public: bool) -> Box<LocalFs> {
165        let inner = LocalFsInner {
166            basedir: file.as_ref().to_path_buf(),
167            public,
168            macos: false,
169            case_insensitive: false,
170            is_file: true,
171            fs_access_guard: None,
172        };
173        Box::new({
174            LocalFs {
175                inner: Arc::new(inner),
176            }
177        })
178    }
179
180    // Like new() but pass in a fs_access_guard hook.
181    #[doc(hidden)]
182    pub fn new_with_fs_access_guard<P: AsRef<Path>>(
183        base: P,
184        public: bool,
185        case_insensitive: bool,
186        macos: bool,
187        fs_access_guard: Option<Box<dyn Fn() -> Box<dyn Any> + Send + Sync + 'static>>,
188    ) -> Box<LocalFs> {
189        let inner = LocalFsInner {
190            basedir: base.as_ref().to_path_buf(),
191            public,
192            macos,
193            case_insensitive,
194            is_file: false,
195            fs_access_guard,
196        };
197        Box::new({
198            LocalFs {
199                inner: Arc::new(inner),
200            }
201        })
202    }
203
204    fn fspath_dbg(&self, path: &DavPath) -> PathBuf {
205        let mut pathbuf = self.inner.basedir.clone();
206        if !self.inner.is_file {
207            pathbuf.push(path.as_rel_ospath());
208        }
209        pathbuf
210    }
211
212    fn fspath(&self, path: &DavPath) -> PathBuf {
213        if self.inner.case_insensitive {
214            crate::localfs_windows::resolve(&self.inner.basedir, path)
215        } else {
216            let mut pathbuf = self.inner.basedir.clone();
217            if !self.inner.is_file {
218                pathbuf.push(path.as_rel_ospath());
219            }
220            pathbuf
221        }
222    }
223
224    // threadpool::blocking() adapter, also runs the before/after hooks.
225    #[doc(hidden)]
226    pub async fn blocking<F, R>(&self, func: F) -> R
227    where
228        F: FnOnce() -> R + Send + 'static,
229        R: Send + 'static,
230    {
231        let this = self.clone();
232        blocking(move || {
233            let _guard = this.inner.fs_access_guard.as_ref().map(|f| f());
234            func()
235        })
236        .await
237    }
238}
239
240// This implementation is basically a bunch of boilerplate to
241// wrap the std::fs call in self.blocking() calls.
242impl DavFileSystem for LocalFs {
243    fn metadata<'a>(&'a self, davpath: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>> {
244        async move {
245            if let Some(meta) = self.is_virtual(davpath) {
246                return Ok(meta);
247            }
248            let path = self.fspath(davpath);
249            if self.is_notfound(&path) {
250                return Err(FsError::NotFound);
251            }
252            self.blocking(move || match std::fs::metadata(path) {
253                Ok(meta) => Ok(Box::new(LocalFsMetaData(meta)) as Box<dyn DavMetaData>),
254                Err(e) => Err(e.into()),
255            })
256            .await
257        }
258        .boxed()
259    }
260
261    fn symlink_metadata<'a>(&'a self, davpath: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>> {
262        async move {
263            if let Some(meta) = self.is_virtual(davpath) {
264                return Ok(meta);
265            }
266            let path = self.fspath(davpath);
267            if self.is_notfound(&path) {
268                return Err(FsError::NotFound);
269            }
270            self.blocking(move || match std::fs::symlink_metadata(path) {
271                Ok(meta) => Ok(Box::new(LocalFsMetaData(meta)) as Box<dyn DavMetaData>),
272                Err(e) => Err(e.into()),
273            })
274            .await
275        }
276        .boxed()
277    }
278
279    // read_dir is a bit more involved - but not much - than a simple wrapper,
280    // because it returns a stream.
281    fn read_dir<'a>(
282        &'a self,
283        davpath: &'a DavPath,
284        meta: ReadDirMeta,
285    ) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>> {
286        async move {
287            trace!("FS: read_dir {:?}", self.fspath_dbg(davpath));
288            let path = self.fspath(davpath);
289            let path2 = path.clone();
290            let iter = self.blocking(move || std::fs::read_dir(&path)).await;
291            match iter {
292                Ok(iterator) => {
293                    let strm = LocalFsReadDir {
294                        fs: self.clone(),
295                        do_meta: meta,
296                        buffer: VecDeque::new(),
297                        dir_cache: self.dir_cache_builder(path2),
298                        iterator: Some(iterator),
299                        fut: None,
300                    };
301                    Ok(Box::pin(strm) as FsStream<Box<dyn DavDirEntry>>)
302                }
303                Err(e) => Err(e.into()),
304            }
305        }
306        .boxed()
307    }
308
309    fn open<'a>(
310        &'a self,
311        path: &'a DavPath,
312        options: OpenOptions,
313    ) -> FsFuture<'a, Box<dyn DavFile>> {
314        async move {
315            trace!("FS: open {:?}", self.fspath_dbg(path));
316            if self.is_forbidden(path) {
317                return Err(FsError::Forbidden);
318            }
319            #[cfg(unix)]
320            let mode = if self.inner.public { 0o644 } else { 0o600 };
321            let path = self.fspath(path);
322            self.blocking(move || {
323                #[cfg(unix)]
324                let res = std::fs::OpenOptions::new()
325                    .read(options.read)
326                    .write(options.write)
327                    .append(options.append)
328                    .truncate(options.truncate)
329                    .create(options.create)
330                    .create_new(options.create_new)
331                    .mode(mode)
332                    .open(path);
333                #[cfg(windows)]
334                let res = std::fs::OpenOptions::new()
335                    .read(options.read)
336                    .write(options.write)
337                    .append(options.append)
338                    .truncate(options.truncate)
339                    .create(options.create)
340                    .create_new(options.create_new)
341                    .open(path);
342                match res {
343                    Ok(file) => Ok(Box::new(LocalFsFile(Some(file))) 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 fut = this.fut.as_mut().unwrap();
557            pin_mut!(fut);
558            match Pin::new(&mut fut).poll(cx) {
559                Poll::Ready(batch) => {
560                    this.fut.take();
561                    if let Some(ref mut nb) = this.dir_cache {
562                        batch.buffer.iter().for_each(|e| {
563                            if let Ok(ref e) = e {
564                                nb.add(e.entry.file_name());
565                            }
566                        });
567                    }
568                    this.buffer = batch.buffer;
569                    this.iterator = batch.iterator;
570                }
571                Poll::Pending => return Poll::Pending,
572            }
573        }
574
575        // we filled the buffer, now pop from the buffer.
576        match this.buffer.pop_front() {
577            Some(Ok(item)) => Poll::Ready(Some(Ok(Box::new(item)))),
578            Some(Err(err)) => {
579                // fuse the iterator.
580                this.iterator.take();
581                // finish the cache.
582                if let Some(ref mut nb) = this.dir_cache {
583                    nb.finish();
584                }
585                // return error of stream.
586                Poll::Ready(Some(Err(err.into())))
587            }
588            None => {
589                // fuse the iterator.
590                this.iterator.take();
591                // finish the cache.
592                if let Some(ref mut nb) = this.dir_cache {
593                    nb.finish();
594                }
595                // return end-of-stream.
596                Poll::Ready(None)
597            }
598        }
599    }
600}
601
602enum Is {
603    File,
604    Dir,
605    Symlink,
606}
607
608impl LocalFsDirEntry {
609    async fn is_a(&self, is: Is) -> FsResult<bool> {
610        match self.meta {
611            Meta::Data(Ok(ref meta)) => Ok(match is {
612                Is::File => meta.file_type().is_file(),
613                Is::Dir => meta.file_type().is_dir(),
614                Is::Symlink => meta.file_type().is_symlink(),
615            }),
616            Meta::Data(Err(ref e)) => Err(e.into()),
617            Meta::Fs(ref fs) => {
618                let fullpath = self.entry.path();
619                let ft = fs
620                    .blocking(move || std::fs::metadata(&fullpath))
621                    .await?
622                    .file_type();
623                Ok(match is {
624                    Is::File => ft.is_file(),
625                    Is::Dir => ft.is_dir(),
626                    Is::Symlink => ft.is_symlink(),
627                })
628            }
629        }
630    }
631}
632
633impl DavDirEntry for LocalFsDirEntry {
634    fn metadata(&self) -> FsFuture<Box<dyn DavMetaData>> {
635        match self.meta {
636            Meta::Data(ref meta) => {
637                let m = match meta {
638                    Ok(meta) => Ok(Box::new(LocalFsMetaData(meta.clone())) as Box<dyn DavMetaData>),
639                    Err(e) => Err(e.into()),
640                };
641                Box::pin(future::ready(m))
642            }
643            Meta::Fs(ref fs) => {
644                let fullpath = self.entry.path();
645                fs.blocking(move || match std::fs::metadata(&fullpath) {
646                    Ok(meta) => Ok(Box::new(LocalFsMetaData(meta)) as Box<dyn DavMetaData>),
647                    Err(e) => Err(e.into()),
648                })
649                .boxed()
650            }
651        }
652    }
653
654    #[cfg(unix)]
655    fn name(&self) -> Vec<u8> {
656        self.entry.file_name().as_bytes().to_vec()
657    }
658
659    #[cfg(windows)]
660    fn name(&self) -> Vec<u8> {
661        self.entry.file_name().to_str().unwrap().as_bytes().to_vec()
662    }
663
664    fn is_dir(&self) -> FsFuture<bool> {
665        Box::pin(self.is_a(Is::Dir))
666    }
667
668    fn is_file(&self) -> FsFuture<bool> {
669        Box::pin(self.is_a(Is::File))
670    }
671
672    fn is_symlink(&self) -> FsFuture<bool> {
673        Box::pin(self.is_a(Is::Symlink))
674    }
675}
676
677impl DavFile for LocalFsFile {
678    fn metadata(&mut self) -> FsFuture<Box<dyn DavMetaData>> {
679        async move {
680            let file = self.0.take().unwrap();
681            let (meta, file) = blocking(move || (file.metadata(), file)).await;
682            self.0 = Some(file);
683            Ok(Box::new(LocalFsMetaData(meta?)) as Box<dyn DavMetaData>)
684        }
685        .boxed()
686    }
687
688    fn write_bytes(&mut self, buf: Bytes) -> FsFuture<()> {
689        async move {
690            let mut file = self.0.take().unwrap();
691            let (res, file) = blocking(move || (file.write_all(&buf), file)).await;
692            self.0 = Some(file);
693            res.map_err(|e| e.into())
694        }
695        .boxed()
696    }
697
698    fn write_buf(&mut self, mut buf: Box<dyn Buf + Send>) -> FsFuture<()> {
699        async move {
700            let mut file = self.0.take().unwrap();
701            let (res, file) = blocking(move || {
702                while buf.remaining() > 0 {
703                    let n = match file.write(buf.chunk()) {
704                        Ok(n) => n,
705                        Err(e) => return (Err(e), file),
706                    };
707                    buf.advance(n);
708                }
709                (Ok(()), file)
710            })
711            .await;
712            self.0 = Some(file);
713            res.map_err(|e| e.into())
714        }
715        .boxed()
716    }
717
718    fn read_bytes(&mut self, count: usize) -> FsFuture<Bytes> {
719        async move {
720            let mut file = self.0.take().unwrap();
721            let (res, file) = blocking(move || {
722                let mut buf = BytesMut::with_capacity(count);
723                let res = unsafe {
724                    buf.set_len(count);
725                    file.read(&mut buf).map(|n| {
726                        buf.set_len(n);
727                        buf.freeze()
728                    })
729                };
730                (res, file)
731            })
732            .await;
733            self.0 = Some(file);
734            res.map_err(|e| e.into())
735        }
736        .boxed()
737    }
738
739    fn seek(&mut self, pos: SeekFrom) -> FsFuture<u64> {
740        async move {
741            let mut file = self.0.take().unwrap();
742            let (res, file) = blocking(move || (file.seek(pos), file)).await;
743            self.0 = Some(file);
744            res.map_err(|e| e.into())
745        }
746        .boxed()
747    }
748
749    fn flush(&mut self) -> FsFuture<()> {
750        async move {
751            let mut file = self.0.take().unwrap();
752            let (res, file) = blocking(move || (file.flush(), file)).await;
753            self.0 = Some(file);
754            res.map_err(|e| e.into())
755        }
756        .boxed()
757    }
758}
759
760impl DavMetaData for LocalFsMetaData {
761    fn len(&self) -> u64 {
762        self.0.len()
763    }
764    fn created(&self) -> FsResult<SystemTime> {
765        self.0.created().map_err(|e| e.into())
766    }
767    fn modified(&self) -> FsResult<SystemTime> {
768        self.0.modified().map_err(|e| e.into())
769    }
770    fn accessed(&self) -> FsResult<SystemTime> {
771        self.0.accessed().map_err(|e| e.into())
772    }
773
774    #[cfg(unix)]
775    fn status_changed(&self) -> FsResult<SystemTime> {
776        Ok(UNIX_EPOCH + Duration::new(self.0.ctime() as u64, 0))
777    }
778
779    #[cfg(windows)]
780    fn status_changed(&self) -> FsResult<SystemTime> {
781        Ok(UNIX_EPOCH + Duration::from_nanos(self.0.creation_time() - 116444736000000000))
782    }
783
784    fn is_dir(&self) -> bool {
785        self.0.is_dir()
786    }
787    fn is_file(&self) -> bool {
788        self.0.is_file()
789    }
790    fn is_symlink(&self) -> bool {
791        self.0.file_type().is_symlink()
792    }
793
794    #[cfg(unix)]
795    fn executable(&self) -> FsResult<bool> {
796        if self.0.is_file() {
797            return Ok((self.0.permissions().mode() & 0o100) > 0);
798        }
799        Err(FsError::NotImplemented)
800    }
801
802    #[cfg(windows)]
803    fn executable(&self) -> FsResult<bool> {
804        // FIXME: implement
805        Err(FsError::NotImplemented)
806    }
807
808    // same as the default apache etag.
809    #[cfg(unix)]
810    fn etag(&self) -> Option<String> {
811        let modified = self.0.modified().ok()?;
812        let t = modified.duration_since(UNIX_EPOCH).ok()?;
813        let t = t.as_secs() * 1000000 + t.subsec_nanos() as u64 / 1000;
814        if self.is_file() {
815            Some(format!("{:x}-{:x}-{:x}", self.0.ino(), self.0.len(), t))
816        } else {
817            Some(format!("{:x}-{:x}", self.0.ino(), t))
818        }
819    }
820
821    // same as the default apache etag.
822    #[cfg(windows)]
823    fn etag(&self) -> Option<String> {
824        let modified = self.0.modified().ok()?;
825        let t = modified.duration_since(UNIX_EPOCH).ok()?;
826        let t = t.as_secs() * 1000000 + t.subsec_nanos() as u64 / 1000;
827        if self.is_file() {
828            Some(format!("{:x}-{:x}", self.0.len(), t))
829        } else {
830            Some(format!("{:x}", t))
831        }
832    }
833}