Repository: zargony/fuse-rs (language: rust)

Issue:
time::Timespec or std::time::SystemTime (fuse::FileAttr)
Would it be a good idea to patch to this? That would be a breaking change to fuse::FileAttr.

(The need would be reduced significantly if Timespec implemented From<SystemTime>)


Discussion / hints:
I like this, especially because the time crate seems to be deprecated now that std has some time types. However it seems to be hard to construct a SystemTime from arbitrary values (like time_t integer values) and most filesystem implementations probably store some kind of integer for timestamps. Maybe we could use std::sys::unix::time::SystemTime directly instead (not sure about this, I'm not very familiar with std::time and just had a quick look to the source)
That's the ugly part - but it probably optimises away ok. Here's going backward with duration_since:
```rust
fn timespec_from(st: &SystemTime) -> Timespec {
    if let Ok(dur_since_epoch) = st.duration_since(std::time::UNIX_EPOCH) {
        Timespec::new(dur_since_epoch.as_secs() as i64,
                      dur_since_epoch.subsec_nanos() as i32)
    } else {
        Timespec::new(0, 0)
    }
}
```
@zargony I'd like to work on this, since I want to remove the dependency on the deprecated `time` crate. Do you have any suggestions on a possible solution?

I tried replacing all `Timespec` with `std::time::Duration` structs, and surprisingly enough all the tests still passed! [See the diff here](https://github.com/zargony/rust-fuse/compare/master...acheronfail:remove-deprecated-time-crate?expand=1).

^^ Although, it's not the best solution since it involves a lot of casting between `i64` and `u64`, etc. 

More than happy to work on this, any pointers for me? :slightly_smiling_face: 
Looks good to me. I like the idea to get rid of the `time` crate. As far as I remember, there was no `std::time` back when these structs were created.

`Duration` however doesn't sound right to me. As far as I know, `Duration` is meant to measure a period of time rather than a point in time which we need here. `std::fs::Metadata` uses `std::time::SystemTime` therefore (which actually wraps a `Duration`, but has a different semantic meaning).
(This reminds me of the idea that we should actually have a low-level and a high-level interface like libfuse provides.)

Also it would be nice to keep the file information statically in the hello example (it was meant to show its simplicity). But we can't express a static time yet in stable Rust, right? (does stable Rust have const fns already?)

I wonder why the atime/mtime/ctime fields are signed anyway. Looking at the original fuse_kernel.h makes me think this should be actually unsigned integers.

>  As far as I know, Duration is meant to measure a period of time rather than a point in time which we need here. std::fs::Metadata uses std::time::SystemTime therefore (which actually wraps a Duration, but has a different semantic meaning).

I agree with that. I haven't used `SystemTime` before, but it seems it doesn't expose a way to pull out the seconds and nanoseconds unless you convert it to a `Duration` first... I'll look into it.

> Also it would be nice to keep the file information statically in the hello example (it was meant to show its simplicity). But we can't express a static time yet in stable Rust, right? (does stable Rust have const fns already?)

I know, that was the part I disliked the most. Rust doesn't have const fns yet, no. (At least not in stable).

> I wonder why the atime/mtime/ctime fields are signed anyway. Looking at the original fuse_kernel.h makes me think this should be actually unsigned integers.

I was actually confused by this as well!
Surely we should just convert them all to unsigned integers? I can't think of a reason why we'd need them to be signed.

Fix patch:
diff --git a/fuse-sys/src/abi.rs b/fuse-sys/src/abi.rs
index a336684..8e72945 100644
--- a/fuse-sys/src/abi.rs
+++ b/fuse-sys/src/abi.rs
@@ -31,16 +31,16 @@ pub struct fuse_attr {
     pub ino: u64,
     pub size: u64,
     pub blocks: u64,
-    pub atime: i64,
-    pub mtime: i64,
-    pub ctime: i64,
+    pub atime: u64,
+    pub mtime: u64,
+    pub ctime: u64,
     #[cfg(target_os = "macos")]
-    pub crtime: i64,
-    pub atimensec: i32,
-    pub mtimensec: i32,
-    pub ctimensec: i32,
+    pub crtime: u64,
+    pub atimensec: u32,
+    pub mtimensec: u32,
+    pub ctimensec: u32,
     #[cfg(target_os = "macos")]
-    pub crtimensec: i32,
+    pub crtimensec: u32,
     pub mode: u32,
     pub nlink: u32,
     pub uid: u32,
@@ -221,10 +221,10 @@ impl fuse_opcode {
 pub struct fuse_entry_out {
     pub nodeid: u64,
     pub generation: u64,
-    pub entry_valid: i64,
-    pub attr_valid: i64,
-    pub entry_valid_nsec: i32,
-    pub attr_valid_nsec: i32,
+    pub entry_valid: u64,
+    pub attr_valid: u64,
+    pub entry_valid_nsec: u32,
+    pub attr_valid_nsec: u32,
     pub attr: fuse_attr,
 }
 
@@ -237,8 +237,8 @@ pub struct fuse_forget_in {
 #[repr(C)]
 #[derive(Debug)]
 pub struct fuse_attr_out {
-    pub attr_valid: i64,
-    pub attr_valid_nsec: i32,
+    pub attr_valid: u64,
+    pub attr_valid_nsec: u32,
     pub dummy: u32,
     pub attr: fuse_attr,
 }
@@ -247,10 +247,10 @@ pub struct fuse_attr_out {
 #[repr(C)]
 #[derive(Debug)]
 pub struct fuse_getxtimes_out {
-    pub bkuptime: i64,
-    pub crtime: i64,
-    pub bkuptimensec: i32,
-    pub crtimensec: i32,
+    pub bkuptime: u64,
+    pub crtime: u64,
+    pub bkuptimensec: u32,
+    pub crtimensec: u32,
 }
 
 #[repr(C)]
@@ -296,11 +296,11 @@ pub struct fuse_setattr_in {
     pub fh: u64,
     pub size: u64,
     pub unused1: u64,
-    pub atime: i64,
-    pub mtime: i64,
+    pub atime: u64,
+    pub mtime: u64,
     pub unused2: u64,
-    pub atimensec: i32,
-    pub mtimensec: i32,
+    pub atimensec: u32,
+    pub mtimensec: u32,
     pub unused3: u32,
     pub mode: u32,
     pub unused4: u32,
@@ -308,17 +308,17 @@ pub struct fuse_setattr_in {
     pub gid: u32,
     pub unused5: u32,
     #[cfg(target_os = "macos")]
-    pub bkuptime: i64,
+    pub bkuptime: u64,
     #[cfg(target_os = "macos")]
-    pub chgtime: i64,
+    pub chgtime: u64,
     #[cfg(target_os = "macos")]
-    pub crtime: i64,
+    pub crtime: u64,
     #[cfg(target_os = "macos")]
-    pub bkuptimensec: i32,
+    pub bkuptimensec: u32,
     #[cfg(target_os = "macos")]
-    pub chgtimensec: i32,
+    pub chgtimensec: u32,
     #[cfg(target_os = "macos")]
-    pub crtimensec: i32,
+    pub crtimensec: u32,
     #[cfg(target_os = "macos")]
     pub flags: u32,                                     // see chflags(2)
 }
diff --git a/fuse/Cargo.toml b/fuse/Cargo.toml
index 00cc49a..a8125ff 100644
--- a/fuse/Cargo.toml
+++ b/fuse/Cargo.toml
@@ -18,8 +18,8 @@ travis-ci = { repository = "zargony/rust-fuse" }
 fuse-sys = { path = "../fuse-sys", version = "0.4.0-alpha" }
 libc = "0.2"
 log = "0.3"
-time = "0.1"
 thread-scoped = "1"
 
 [dev-dependencies]
 env_logger = "0.5"
+lazy_static = "1.2.0"
diff --git a/fuse/examples/hello.rs b/fuse/examples/hello.rs
index 84d7c87..61a954c 100644
--- a/fuse/examples/hello.rs
+++ b/fuse/examples/hello.rs
@@ -1,53 +1,58 @@
 extern crate env_logger;
+#[macro_use]
+extern crate lazy_static;
 extern crate fuse;
 extern crate libc;
-extern crate time;
 
 use std::env;
 use std::ffi::OsStr;
+use std::time::{Duration, SystemTime, UNIX_EPOCH};
 use libc::ENOENT;
-use time::Timespec;
 use fuse::{FileType, FileAttr, Filesystem, Request, ReplyData, ReplyEntry, ReplyAttr, ReplyDirectory};
 
-const TTL: Timespec = Timespec { sec: 1, nsec: 0 };                     // 1 second
+lazy_static! {
+    // 1 second
+    static ref TTL: SystemTime = UNIX_EPOCH + Duration::new(1, 0);
 
-const CREATE_TIME: Timespec = Timespec { sec: 1381237736, nsec: 0 };    // 2013-10-08 08:56
+    // 2013-10-08 08:56
+    static ref CREATE_TIME: SystemTime = UNIX_EPOCH + Duration::new(1381237736, 0);
 
-const HELLO_DIR_ATTR: FileAttr = FileAttr {
-    ino: 1,
-    size: 0,
-    blocks: 0,
-    atime: CREATE_TIME,
-    mtime: CREATE_TIME,
-    ctime: CREATE_TIME,
-    crtime: CREATE_TIME,
-    kind: FileType::Directory,
-    perm: 0o755,
-    nlink: 2,
-    uid: 501,
-    gid: 20,
-    rdev: 0,
-    flags: 0,
-};
+    static ref HELLO_DIR_ATTR: FileAttr = FileAttr {
+        ino: 1,
+        size: 0,
+        blocks: 0,
+        atime: *CREATE_TIME,
+        mtime: *CREATE_TIME,
+        ctime: *CREATE_TIME,
+        crtime: *CREATE_TIME,
+        kind: FileType::Directory,
+        perm: 0o755,
+        nlink: 2,
+        uid: 501,
+        gid: 20,
+        rdev: 0,
+        flags: 0,
+    };
 
-const HELLO_TXT_CONTENT: &'static str = "Hello World!\n";
+    static ref HELLO_TXT_CONTENT: &'static str = "Hello World!\n";
 
-const HELLO_TXT_ATTR: FileAttr = FileAttr {
-    ino: 2,
-    size: 13,
-    blocks: 1,
-    atime: CREATE_TIME,
-    mtime: CREATE_TIME,
-    ctime: CREATE_TIME,
-    crtime: CREATE_TIME,
-    kind: FileType::RegularFile,
-    perm: 0o644,
-    nlink: 1,
-    uid: 501,
-    gid: 20,
-    rdev: 0,
-    flags: 0,
-};
+    static ref HELLO_TXT_ATTR: FileAttr = FileAttr {
+        ino: 2,
+        size: 13,
+        blocks: 1,
+        atime: *CREATE_TIME,
+        mtime: *CREATE_TIME,
+        ctime: *CREATE_TIME,
+        crtime: *CREATE_TIME,
+        kind: FileType::RegularFile,
+        perm: 0o644,
+        nlink: 1,
+        uid: 501,
+        gid: 20,
+        rdev: 0,
+        flags: 0,
+    };
+}
 
 struct HelloFS;
 
diff --git a/fuse/src/lib.rs b/fuse/src/lib.rs
index 1d41555..674217d 100644
--- a/fuse/src/lib.rs
+++ b/fuse/src/lib.rs
@@ -10,7 +10,6 @@ extern crate fuse_sys;
 extern crate libc;
 #[macro_use]
 extern crate log;
-extern crate time;
 extern crate thread_scoped;
 
 use std::convert::AsRef;
@@ -18,7 +17,7 @@ use std::io;
 use std::ffi::OsStr;
 use std::path::Path;
 use libc::{c_int, ENOSYS};
-use time::Timespec;
+use std::time::SystemTime;
 
 pub use fuse_sys::abi::FUSE_ROOT_ID;
 pub use fuse_sys::abi::consts;
@@ -65,13 +64,13 @@ pub struct FileAttr {
     /// Size in blocks
     pub blocks: u64,
     /// Time of last access
-    pub atime: Timespec,
+    pub atime: SystemTime,
     /// Time of last modification
-    pub mtime: Timespec,
+    pub mtime: SystemTime,
     /// Time of last change
-    pub ctime: Timespec,
+    pub ctime: SystemTime,
     /// Time of creation (macOS only)
-    pub crtime: Timespec,
+    pub crtime: SystemTime,
     /// Kind of file (directory, file, pipe, etc)
     pub kind: FileType,
     /// Permissions
@@ -125,7 +124,7 @@ pub trait Filesystem {
     }
 
     /// Set file attributes.
-    fn setattr(&mut self, _req: &Request, _ino: u64, _mode: Option<u32>, _uid: Option<u32>, _gid: Option<u32>, _size: Option<u64>, _atime: Option<Timespec>, _mtime: Option<Timespec>, _fh: Option<u64>, _crtime: Option<Timespec>, _chgtime: Option<Timespec>, _bkuptime: Option<Timespec>, _flags: Option<u32>, reply: ReplyAttr) {
+    fn setattr(&mut self, _req: &Request, _ino: u64, _mode: Option<u32>, _uid: Option<u32>, _gid: Option<u32>, _size: Option<u64>, _atime: Option<SystemTime>, _mtime: Option<SystemTime>, _fh: Option<u64>, _crtime: Option<SystemTime>, _chgtime: Option<SystemTime>, _bkuptime: Option<SystemTime>, _flags: Option<u32>, reply: ReplyAttr) {
         reply.error(ENOSYS);
     }
 
diff --git a/fuse/src/reply.rs b/fuse/src/reply.rs
index 9f9f302..2e4e8c2 100644
--- a/fuse/src/reply.rs
+++ b/fuse/src/reply.rs
@@ -19,7 +19,7 @@ use fuse_sys::abi::fuse_getxattr_out;
 use fuse_sys::abi::fuse_getxtimes_out;
 use fuse_sys::abi::{fuse_out_header, fuse_dirent};
 use libc::{c_int, S_IFIFO, S_IFCHR, S_IFBLK, S_IFDIR, S_IFREG, S_IFLNK, S_IFSOCK, EIO};
-use time::Timespec;
+use std::time::{SystemTime, SystemTimeError, UNIX_EPOCH};
 
 use {FileType, FileAttr};
 
@@ -54,6 +54,11 @@ fn as_bytes<T, U, F: FnOnce(&[&[u8]]) -> U>(data: &T, f: F) -> U {
     }
 }
 
+fn time_from_system_time(system_time: &SystemTime) -> Result<(u64, u32), SystemTimeError> {
+    let duration = system_time.duration_since(UNIX_EPOCH)?;
+    Ok((duration.as_secs(), duration.subsec_nanos()))
+}
+
 // Some platforms like Linux x86_64 have mode_t = u32, and lint warns of a trivial_numeric_casts.
 // But others like macOS x86_64 have mode_t = u16, requiring a typecast.  So, just silence lint.
 #[allow(trivial_numeric_casts)]
@@ -73,18 +78,23 @@ fn mode_from_kind_and_perm(kind: FileType, perm: u16) -> u32 {
 /// Returns a fuse_attr from FileAttr
 #[cfg(target_os = "macos")]
 fn fuse_attr_from_attr(attr: &FileAttr) -> fuse_attr {
+    let (atime_secs, atime_nanos) = time_from_system_time(&attr.atime).unwrap();
+    let (mtime_secs, mtime_nanos) = time_from_system_time(&attr.mtime).unwrap();
+    let (ctime_secs, ctime_nanos) = time_from_system_time(&attr.ctime).unwrap();
+    let (crtime_secs, crtime_nanos) = time_from_system_time(&attr.crtime).unwrap();
+
     fuse_attr {
         ino: attr.ino,
         size: attr.size,
         blocks: attr.blocks,
-        atime: attr.atime.sec,
-        mtime: attr.mtime.sec,
-        ctime: attr.ctime.sec,
-        crtime: attr.crtime.sec,
-        atimensec: attr.atime.nsec,
-        mtimensec: attr.mtime.nsec,
-        ctimensec: attr.ctime.nsec,
-        crtimensec: attr.crtime.nsec,
+        atime: atime_secs,
+        mtime: mtime_secs,
+        ctime: ctime_secs,
+        crtime: crtime_secs,
+        atimensec: atime_nanos,
+        mtimensec: mtime_nanos,
+        ctimensec: ctime_nanos,
+        crtimensec: crtime_nanos,
         mode: mode_from_kind_and_perm(attr.kind, attr.perm),
         nlink: attr.nlink,
         uid: attr.uid,
@@ -97,16 +107,20 @@ fn fuse_attr_from_attr(attr: &FileAttr) -> fuse_attr {
 /// Returns a fuse_attr from FileAttr
 #[cfg(not(target_os = "macos"))]
 fn fuse_attr_from_attr(attr: &FileAttr) -> fuse_attr {
+    let (atime_secs, atime_nanos) = time_from_system_time(&attr.atime).unwrap();
+    let (mtime_secs, mtime_nanos) = time_from_system_time(&attr.mtime).unwrap();
+    let (ctime_secs, ctime_nanos) = time_from_system_time(&attr.ctime).unwrap();
+
     fuse_attr {
         ino: attr.ino,
         size: attr.size,
         blocks: attr.blocks,
-        atime: attr.atime.sec,
-        mtime: attr.mtime.sec,
-        ctime: attr.ctime.sec,
-        atimensec: attr.atime.nsec,
-        mtimensec: attr.mtime.nsec,
-        ctimensec: attr.ctime.nsec,
+        atime: atime_secs,
+        mtime: mtime_secs,
+        ctime: ctime_secs,
+        atimensec: atime_nanos,
+        mtimensec: mtime_nanos,
+        ctimensec: ctime_nanos,
         mode: mode_from_kind_and_perm(attr.kind, attr.perm),
         nlink: attr.nlink,
         uid: attr.uid,
@@ -245,14 +259,15 @@ impl Reply for ReplyEntry {
 
 impl ReplyEntry {
     /// Reply to a request with the given entry
-    pub fn entry(self, ttl: &Timespec, attr: &FileAttr, generation: u64) {
+    pub fn entry(self, ttl: &SystemTime, attr: &FileAttr, generation: u64) {
+        let (ttl_secs, ttl_nanos) = time_from_system_time(ttl).unwrap();
         self.reply.ok(&fuse_entry_out {
             nodeid: attr.ino,
             generation: generation,
-            entry_valid: ttl.sec,
-            attr_valid: ttl.sec,
-            entry_valid_nsec: ttl.nsec,
-            attr_valid_nsec: ttl.nsec,
+            entry_valid: ttl_secs,
+            attr_valid: ttl_secs,
+            entry_valid_nsec: ttl_nanos,
+            attr_valid_nsec: ttl_nanos,
             attr: fuse_attr_from_attr(attr),
         });
     }
@@ -279,10 +294,11 @@ impl Reply for ReplyAttr {
 
 impl ReplyAttr {
     /// Reply to a request with the given attribute
-    pub fn attr(self, ttl: &Timespec, attr: &FileAttr) {
+    pub fn attr(self, ttl: &SystemTime, attr: &FileAttr) {
+        let (ttl_secs, ttl_nanos) = time_from_system_time(ttl).unwrap();
         self.reply.ok(&fuse_attr_out {
-            attr_valid: ttl.sec,
-            attr_valid_nsec: ttl.nsec,
+            attr_valid: ttl_secs,
+            attr_valid_nsec: ttl_nanos,
             dummy: 0,
             attr: fuse_attr_from_attr(attr),
         });
@@ -313,12 +329,14 @@ impl Reply for ReplyXTimes {
 #[cfg(target_os = "macos")]
 impl ReplyXTimes {
     /// Reply to a request with the given xtimes
-    pub fn xtimes(self, bkuptime: Timespec, crtime: Timespec) {
+    pub fn xtimes(self, bkuptime: SystemTime, crtime: SystemTime) {
+        let (bkuptime_secs, bkuptime_nanos) = time_from_system_time(&bkuptime).unwrap();
+        let (crtime_secs, crtime_nanos) = time_from_system_time(&crtime).unwrap();
         self.reply.ok(&fuse_getxtimes_out {
-            bkuptime: bkuptime.sec,
-            crtime: crtime.sec,
-            bkuptimensec: bkuptime.nsec,
-            crtimensec: crtime.nsec,
+            bkuptime: bkuptime_secs,
+            crtime: crtime_secs,
+            bkuptimensec: bkuptime_nanos,
+            crtimensec: crtime_nanos,
         });
     }
 
@@ -442,14 +460,15 @@ impl Reply for ReplyCreate {
 
 impl ReplyCreate {
     /// Reply to a request with the given entry
-    pub fn created(self, ttl: &Timespec, attr: &FileAttr, generation: u64, fh: u64, flags: u32) {
+    pub fn created(self, ttl: &SystemTime, attr: &FileAttr, generation: u64, fh: u64, flags: u32) {
+        let (ttl_secs, ttl_nanos) = time_from_system_time(ttl).unwrap();
         self.reply.ok(&(fuse_entry_out {
             nodeid: attr.ino,
             generation: generation,
-            entry_valid: ttl.sec,
-            attr_valid: ttl.sec,
-            entry_valid_nsec: ttl.nsec,
-            attr_valid_nsec: ttl.nsec,
+            entry_valid: ttl_secs,
+            attr_valid: ttl_secs,
+            entry_valid_nsec: ttl_nanos,
+            attr_valid_nsec: ttl_nanos,
             attr: fuse_attr_from_attr(attr),
         }, fuse_open_out {
             fh: fh,
@@ -618,7 +637,7 @@ impl ReplyXattr {
 mod test {
     use std::thread;
     use std::sync::mpsc::{channel, Sender};
-    use time::Timespec;
+    use std::time::{Duration, UNIX_EPOCH};
     use super::as_bytes;
     use super::{Reply, ReplyRaw, ReplyEmpty, ReplyData, ReplyEntry, ReplyAttr, ReplyOpen};
     use super::{ReplyWrite, ReplyStatfs, ReplyCreate, ReplyLock, ReplyBmap, ReplyDirectory};
@@ -752,7 +771,7 @@ mod test {
             }
         };
         let reply: ReplyEntry = Reply::new(0xdeadbeef, sender);
-        let time = Timespec::new(0x1234, 0x5678);
+        let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678);
         let attr = FileAttr { ino: 0x11, size: 0x22, blocks: 0x33, atime: time, mtime: time, ctime: time, crtime: time,
             kind: FileType::RegularFile, perm: 0o644, nlink: 0x55, uid: 0x66, gid: 0x77, rdev: 0x88, flags: 0x99 };
         reply.entry(&time, &attr, 0xaa);
@@ -785,7 +804,7 @@ mod test {
             }
         };
         let reply: ReplyAttr = Reply::new(0xdeadbeef, sender);
-        let time = Timespec::new(0x1234, 0x5678);
+        let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678);
         let attr = FileAttr { ino: 0x11, size: 0x22, blocks: 0x33, atime: time, mtime: time, ctime: time, crtime: time,
             kind: FileType::RegularFile, perm: 0o644, nlink: 0x55, uid: 0x66, gid: 0x77, rdev: 0x88, flags: 0x99 };
         reply.attr(&time, &attr);
@@ -802,7 +821,7 @@ mod test {
             ]
         };
         let reply: ReplyXTimes = Reply::new(0xdeadbeef, sender);
-        let time = Timespec::new(0x1234, 0x5678);
+        let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678);
         reply.xtimes(time, time);
     }
 
@@ -879,7 +898,7 @@ mod test {
             }
         };
         let reply: ReplyCreate = Reply::new(0xdeadbeef, sender);
-        let time = Timespec::new(0x1234, 0x5678);
+        let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678);
         let attr = FileAttr { ino: 0x11, size: 0x22, blocks: 0x33, atime: time, mtime: time, ctime: time, crtime: time,
             kind: FileType::RegularFile, perm: 0o644, nlink: 0x55, uid: 0x66, gid: 0x77, rdev: 0x88, flags: 0x99 };
         reply.created(&time, &attr, 0xaa, 0xbb, 0xcc);
diff --git a/fuse/src/request.rs b/fuse/src/request.rs
index e2c5f00..77c03b4 100644
--- a/fuse/src/request.rs
+++ b/fuse/src/request.rs
@@ -5,7 +5,7 @@
 
 use std::mem;
 use libc::{EIO, ENOSYS, EPROTO};
-use time::Timespec;
+use std::time::{Duration, SystemTime, UNIX_EPOCH};
 use fuse_sys::abi::*;
 use fuse_sys::abi::consts::*;
 use fuse_sys::abi::fuse_opcode::*;
@@ -177,11 +177,11 @@ impl<'a> Request<'a> {
                 };
                 let atime = match arg.valid & FATTR_ATIME {
                     0 => None,
-                    _ => Some(Timespec::new(arg.atime, arg.atimensec)),
+                    _ => Some(UNIX_EPOCH + Duration::new(arg.atime, arg.atimensec)),
                 };
                 let mtime = match arg.valid & FATTR_MTIME {
                     0 => None,
-                    _ => Some(Timespec::new(arg.mtime, arg.mtimensec)),
+                    _ => Some(UNIX_EPOCH + Duration::new(arg.mtime, arg.mtimensec)),
                 };
                 let fh = match arg.valid & FATTR_FH {
                     0 => None,
@@ -189,18 +189,18 @@ impl<'a> Request<'a> {
                 };
                 #[cfg(target_os = "macos")]
                 #[inline]
-                fn get_macos_setattr(arg: &fuse_setattr_in) -> (Option<Timespec>, Option<Timespec>, Option<Timespec>, Option<u32>) {
+                fn get_macos_setattr(arg: &fuse_setattr_in) -> (Option<SystemTime>, Option<SystemTime>, Option<SystemTime>, Option<u32>) {
                     let crtime = match arg.valid & FATTR_CRTIME {
                         0 => None,
-                        _ => Some(Timespec::new(arg.crtime, arg.crtimensec)),
+                        _ => Some(UNIX_EPOCH + Duration::new(arg.crtime, arg.crtimensec)),
                     };
                     let chgtime = match arg.valid & FATTR_CHGTIME {
                         0 => None,
-                        _ => Some(Timespec::new(arg.chgtime, arg.chgtimensec)),
+                        _ => Some(UNIX_EPOCH + Duration::new(arg.chgtime, arg.chgtimensec)),
                     };
                     let bkuptime = match arg.valid & FATTR_BKUPTIME {
                         0 => None,
-                        _ => Some(Timespec::new(arg.bkuptime, arg.bkuptimensec)),
+                        _ => Some(UNIX_EPOCH + Duration::new(arg.bkuptime, arg.bkuptimensec)),
                     };
                     let flags = match arg.valid & FATTR_FLAGS {
                         0 => None,
@@ -210,7 +210,7 @@ impl<'a> Request<'a> {
                 }
                 #[cfg(not(target_os = "macos"))]
                 #[inline]
-                fn get_macos_setattr(_arg: &fuse_setattr_in) -> (Option<Timespec>, Option<Timespec>, Option<Timespec>, Option<u32>) {
+                fn get_macos_setattr(_arg: &fuse_setattr_in) -> (Option<SystemTime>, Option<SystemTime>, Option<SystemTime>, Option<u32>) {
                     (None, None, None, None)
                 }
                 let (crtime, chgtime, bkuptime, flags) = get_macos_setattr(arg);


Test patch: