Commit 76c23039 authored by Jakob Unterwurzacher's avatar Jakob Unterwurzacher Committed by Han-Wen Nienhuys

darwin, loopback: emulate UTIME_OMIT

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
( https://github.com/rfjakob/gocryptfs/issues/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.
parent dc4ff97b
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"unsafe" "unsafe"
"github.com/hanwen/go-fuse/fuse" "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 { func (f *loopbackFile) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {
...@@ -65,8 +66,6 @@ func (f *loopbackFile) Allocate(off uint64, sz uint64, mode uint32) fuse.Status ...@@ -65,8 +66,6 @@ func (f *loopbackFile) Allocate(off uint64, sz uint64, mode uint32) fuse.Status
return fuse.OK return fuse.OK
} }
const _UTIME_OMIT = ((1 << 30) - 2)
// timeToTimeval - Convert time.Time to syscall.Timeval // timeToTimeval - Convert time.Time to syscall.Timeval
// //
// Note: This does not use syscall.NsecToTimespec because // Note: This does not use syscall.NsecToTimespec because
...@@ -79,22 +78,18 @@ func timeToTimeval(t *time.Time) syscall.Timeval { ...@@ -79,22 +78,18 @@ func timeToTimeval(t *time.Time) syscall.Timeval {
return tv return tv
} }
// OSX does not have the utimensat syscall neded to implement this properly. // MacOS before High Sierra lacks utimensat() and UTIME_OMIT.
// We do our best to emulate it using futimes. // We emulate using utimes() and extra GetAttr() calls.
func (f *loopbackFile) Utimens(a *time.Time, m *time.Time) fuse.Status { func (f *loopbackFile) Utimens(a *time.Time, m *time.Time) fuse.Status {
tv := make([]syscall.Timeval, 2) var attr fuse.Attr
if a == nil { if a == nil || m == nil {
tv[0].Usec = _UTIME_OMIT var status fuse.Status
} else { status = f.GetAttr(&attr)
tv[0] = timeToTimeval(a) if !status.Ok() {
} return status
}
if m == nil {
tv[1].Usec = _UTIME_OMIT
} else {
tv[1] = timeToTimeval(m)
} }
tv := utimens.Fill(a, m, &attr)
f.lock.Lock() f.lock.Lock()
err := syscall.Futimes(int(f.File.Fd()), tv) err := syscall.Futimes(int(f.File.Fd()), tv)
f.lock.Unlock() f.lock.Unlock()
......
...@@ -9,39 +9,21 @@ import ( ...@@ -9,39 +9,21 @@ import (
"time" "time"
"github.com/hanwen/go-fuse/fuse" "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 { func (fs *loopbackFileSystem) Utimens(path string, a *time.Time, m *time.Time, context *fuse.Context) fuse.Status {
tv := make([]syscall.Timeval, 2) // MacOS before High Sierra lacks utimensat() and UTIME_OMIT.
if a == nil { // We emulate using utimes() and extra GetAttr() calls.
tv[0].Usec = _UTIME_OMIT var attr *fuse.Attr
} else { if a == nil || m == nil {
tv[0] = timeToTimeval(a) var status fuse.Status
attr, status = fs.GetAttr(path, context)
if !status.Ok() {
return status
}
} }
tv := utimens.Fill(a, m, attr)
if m == nil {
tv[1].Usec = _UTIME_OMIT
} else {
tv[1] = timeToTimeval(m)
}
err := syscall.Utimes(fs.GetPath(path), tv) err := syscall.Utimes(fs.GetPath(path), tv)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
// 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
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment