Skip to content

Commit

Permalink
darwin, loopback: emulate UTIME_OMIT
Browse files Browse the repository at this point in the history
All but the newest MacOS versions (xnu-4570.1.46 / High Sierra)
lack utimensat() and UTIME_OMIT, see:
https://github.com/apple/darwin-xnu/blame/0a798f6738bc1db01281fc08ae024145e84df927/bsd/sys/stat.h#L537

The UTIME_OMIT value is intepreted literally by MacOS, resulting
in all files getting a 1970 timestamp
( rfjakob/gocryptfs#229 ).

Emulate UTIME_OMIT by filling in the missing values
using an extra GetAttr call.

To not duplicate the logic in pathfs and nodefs, an
internal "utimens" helper package is created.
  • Loading branch information
rfjakob committed May 6, 2018
1 parent bb848eb commit d51692e
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 45 deletions.
27 changes: 11 additions & 16 deletions fuse/nodefs/files_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"unsafe"

"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/utimens"
)

func (f *loopbackFile) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {
Expand Down Expand Up @@ -65,8 +66,6 @@ func (f *loopbackFile) Allocate(off uint64, sz uint64, mode uint32) fuse.Status
return fuse.OK
}

const _UTIME_OMIT = ((1 << 30) - 2)

// timeToTimeval - Convert time.Time to syscall.Timeval
//
// Note: This does not use syscall.NsecToTimespec because
Expand All @@ -79,22 +78,18 @@ func timeToTimeval(t *time.Time) syscall.Timeval {
return tv
}

// OSX does not have the utimensat syscall neded to implement this properly.
// We do our best to emulate it using futimes.
// MacOS before High Sierra lacks utimensat() and UTIME_OMIT.
// We emulate using utimes() and extra GetAttr() calls.
func (f *loopbackFile) Utimens(a *time.Time, m *time.Time) fuse.Status {
tv := make([]syscall.Timeval, 2)
if a == nil {
tv[0].Usec = _UTIME_OMIT
} else {
tv[0] = timeToTimeval(a)
}

if m == nil {
tv[1].Usec = _UTIME_OMIT
} else {
tv[1] = timeToTimeval(m)
var attr fuse.Attr
if a == nil || m == nil {
var status fuse.Status
status = f.GetAttr(&attr)
if !status.Ok() {
return status
}
}

tv := utimens.Fill(a, m, &attr)
f.lock.Lock()
err := syscall.Futimes(int(f.File.Fd()), tv)
f.lock.Unlock()
Expand Down
40 changes: 11 additions & 29 deletions fuse/pathfs/loopback_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,21 @@ import (
"time"

"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/utimens"
)

const _UTIME_NOW = ((1 << 30) - 1)
const _UTIME_OMIT = ((1 << 30) - 2)

// timeToTimeval - Convert time.Time to syscall.Timeval
//
// Note: This does not use syscall.NsecToTimespec because
// that does not work properly for times before 1970,
// see https://github.com/golang/go/issues/12777
func timeToTimeval(t *time.Time) syscall.Timeval {
var tv syscall.Timeval
tv.Usec = int32(t.Nanosecond() / 1000)
tv.Sec = t.Unix()
return tv
}

// OSX does not have the utimensat syscall neded to implement this properly.
// We do our best to emulate it using futimes.
// MacOS before High Sierra lacks utimensat() and UTIME_OMIT.
// We emulate using utimes() and extra GetAttr() calls.
func (fs *loopbackFileSystem) Utimens(path string, a *time.Time, m *time.Time, context *fuse.Context) fuse.Status {
tv := make([]syscall.Timeval, 2)
if a == nil {
tv[0].Usec = _UTIME_OMIT
} else {
tv[0] = timeToTimeval(a)
var attr *fuse.Attr
if a == nil || m == nil {
var status fuse.Status
attr, status = fs.GetAttr(path, context)
if !status.Ok() {
return status
}
}

if m == nil {
tv[1].Usec = _UTIME_OMIT
} else {
tv[1] = timeToTimeval(m)
}

tv := utimens.Fill(a, m, attr)
err := syscall.Utimes(fs.GetPath(path), tv)
return fuse.ToStatus(err)
}
42 changes: 42 additions & 0 deletions internal/utimens/utimens_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package utimens

import (
"syscall"
"time"

"github.com/hanwen/go-fuse/fuse"
)

// timeToTimeval - convert time.Time to syscall.Timeval
//
// Note: This does not use syscall.NsecToTimespec because
// that does not work properly for times before 1970,
// see https://github.com/golang/go/issues/12777
func timeToTimeval(t *time.Time) syscall.Timeval {
var tv syscall.Timeval
tv.Usec = int32(t.Nanosecond() / 1000)
tv.Sec = t.Unix()
return tv
}

// Fill - fill the missing timestamp value (if any) from attr, and pack
// both into a syscall.Timeval slice that can be passed to syscall.Utimes()
// or syscall.Futimes().
func Fill(a *time.Time, m *time.Time, attr *fuse.Attr) []syscall.Timeval {
if a == nil {
a2 := time.Unix(int64(attr.Atime), int64(attr.Atimensec))
a = &a2
}
if m == nil {
m2 := time.Unix(int64(attr.Mtime), int64(attr.Mtimensec))
m = &m2
}
tv := make([]syscall.Timeval, 2)
tv[0] = timeToTimeval(a)
tv[1] = timeToTimeval(m)
return tv
}

0 comments on commit d51692e

Please sign in to comment.