diff --git a/fuse/misc.go b/fuse/misc.go index 44ad46ad5..839fdfcd3 100644 --- a/fuse/misc.go +++ b/fuse/misc.go @@ -12,6 +12,7 @@ import ( "os" "reflect" "syscall" + "time" "unsafe" ) @@ -86,3 +87,21 @@ func init() { log.Panicf("page size incorrect: %d", p) } } + +const _UTIME_OMIT = ((1 << 30) - 2) + +// UtimeToTimespec converts a "Time" pointer as passed to Utimens to a +// "Timespec" that can be passed to the utimensat syscall. +// A nil pointer is converted to the special UTIME_OMIT value. +func UtimeToTimespec(t *time.Time) (ts syscall.Timespec) { + if t == nil { + ts.Nsec = _UTIME_OMIT + } else { + ts = syscall.NsecToTimespec(t.UnixNano()) + // Go bug https://github.com/golang/go/issues/12777 + if ts.Nsec < 0 { + ts.Nsec = 0 + } + } + return ts +} diff --git a/fuse/nodefs/files_darwin.go b/fuse/nodefs/files_darwin.go index 02e1495d6..3b4cba603 100644 --- a/fuse/nodefs/files_darwin.go +++ b/fuse/nodefs/files_darwin.go @@ -65,7 +65,6 @@ func (f *loopbackFile) Allocate(off uint64, sz uint64, mode uint32) fuse.Status return fuse.OK } -const _UTIME_NOW = ((1 << 30) - 1) const _UTIME_OMIT = ((1 << 30) - 2) // timeToTimeval - Convert time.Time to syscall.Timeval diff --git a/fuse/nodefs/files_linux.go b/fuse/nodefs/files_linux.go index a4bf28249..fdee5dbd0 100644 --- a/fuse/nodefs/files_linux.go +++ b/fuse/nodefs/files_linux.go @@ -21,27 +21,11 @@ func (f *loopbackFile) Allocate(off uint64, sz uint64, mode uint32) fuse.Status return fuse.OK } -const _UTIME_NOW = ((1 << 30) - 1) -const _UTIME_OMIT = ((1 << 30) - 2) - // Utimens - file handle based version of loopbackFileSystem.Utimens() func (f *loopbackFile) Utimens(a *time.Time, m *time.Time) fuse.Status { var ts [2]syscall.Timespec - - if a == nil { - ts[0].Nsec = _UTIME_OMIT - } else { - ts[0] = syscall.NsecToTimespec(a.UnixNano()) - ts[0].Nsec = 0 - } - - if m == nil { - ts[1].Nsec = _UTIME_OMIT - } else { - ts[1] = syscall.NsecToTimespec(a.UnixNano()) - ts[1].Nsec = 0 - } - + ts[0] = fuse.UtimeToTimespec(a) + ts[1] = fuse.UtimeToTimespec(m) f.lock.Lock() err := futimens(int(f.File.Fd()), &ts) f.lock.Unlock() diff --git a/fuse/pathfs/loopback_linux.go b/fuse/pathfs/loopback_linux.go index 4ca37ee44..079948366 100644 --- a/fuse/pathfs/loopback_linux.go +++ b/fuse/pathfs/loopback_linux.go @@ -39,27 +39,11 @@ func (fs *loopbackFileSystem) SetXAttr(name string, attr string, data []byte, fl return fuse.ToStatus(err) } -const _UTIME_NOW = ((1 << 30) - 1) -const _UTIME_OMIT = ((1 << 30) - 2) - // Utimens - path based version of loopbackFile.Utimens() func (fs *loopbackFileSystem) Utimens(path string, a *time.Time, m *time.Time, context *fuse.Context) (code fuse.Status) { var ts [2]syscall.Timespec - - if a == nil { - ts[0].Nsec = _UTIME_OMIT - } else { - ts[0] = syscall.NsecToTimespec(a.UnixNano()) - ts[0].Nsec = 0 - } - - if m == nil { - ts[1].Nsec = _UTIME_OMIT - } else { - ts[1] = syscall.NsecToTimespec(m.UnixNano()) - ts[1].Nsec = 0 - } - + ts[0] = fuse.UtimeToTimespec(a) + ts[1] = fuse.UtimeToTimespec(m) err := sysUtimensat(0, fs.GetPath(path), &ts, _AT_SYMLINK_NOFOLLOW) return fuse.ToStatus(err) } diff --git a/fuse/test/loopback_linux_test.go b/fuse/test/loopback_linux_test.go index 5e6ec2f0c..1a1a3c720 100644 --- a/fuse/test/loopback_linux_test.go +++ b/fuse/test/loopback_linux_test.go @@ -70,6 +70,41 @@ func TestNegativeTime(t *testing.T) { } } +// Setting nanoseconds should work for dates after 1970 +func TestUtimesNano(t *testing.T) { + tc := NewTestCase(t) + defer tc.Cleanup() + + path := tc.mountFile + err := ioutil.WriteFile(path, []byte("xyz"), 0600) + if err != nil { + t.Fatal(err) + } + ts := make([]syscall.Timespec, 2) + // atime + ts[0].Sec = 1 + ts[0].Nsec = 2 + // mtime + ts[1].Sec = 3 + ts[1].Nsec = 4 + err = syscall.UtimesNano(path, ts) + if err != nil { + t.Fatal(err) + } + + var st syscall.Stat_t + err = syscall.Stat(path, &st) + if err != nil { + t.Fatal(err) + } + if st.Atim != ts[0] { + t.Errorf("Wrong atime: %v, want: %v", st.Atim, ts[0]) + } + if st.Mtim != ts[1] { + t.Errorf("Wrong mtime: %v, want: %v", st.Mtim, ts[1]) + } +} + func clearStatfs(s *syscall.Statfs_t) { empty := syscall.Statfs_t{} s.Type = 0