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.

This patch fixes the failing utimens tests.
  • Loading branch information
rfjakob authored and hanwen committed May 8, 2018
1 parent dc4ff97 commit 76c2303
Show file tree
Hide file tree
Showing 3 changed files with 62 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.
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)
// MacOS before High Sierra lacks utimensat() and UTIME_OMIT.
// We emulate using utimes() and extra GetAttr() calls.
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)
}
40 changes: 40 additions & 0 deletions internal/utimens/utimens_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2018 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 converts time.Time to syscall.Timeval
func timeToTimeval(t *time.Time) 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
var tv syscall.Timeval
tv.Usec = int32(t.Nanosecond() / 1000)
tv.Sec = t.Unix()
return tv
}

// Fill converts a and m to a syscall.Timeval slice that can be passed
// to syscall.Utimes. Missing values (if any) are taken from attr
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 76c2303

Please sign in to comment.