diff options
-rw-r--r-- | internal/fusefrontend/file.go | 6 | ||||
-rw-r--r-- | internal/fusefrontend/fs.go | 5 | ||||
-rw-r--r-- | internal/syscallcompat/sys_darwin.go | 87 | ||||
-rw-r--r-- | internal/syscallcompat/sys_linux.go | 23 |
4 files changed, 113 insertions, 8 deletions
diff --git a/internal/fusefrontend/file.go b/internal/fusefrontend/file.go index a0b82ba..531274a 100644 --- a/internal/fusefrontend/file.go +++ b/internal/fusefrontend/file.go @@ -44,8 +44,6 @@ type File struct { qIno openfiletable.QIno // Entry in the open file table fileTableEntry *openfiletable.Entry - // go-fuse nodefs.loopbackFile - loopbackFile nodefs.File // Store where the last byte was written lastWrittenOffset int64 // The opCount is used to judge whether "lastWrittenOffset" is still @@ -75,7 +73,6 @@ func NewFile(fd *os.File, fs *FS) (*File, fuse.Status) { contentEnc: fs.contentEnc, qIno: qi, fileTableEntry: e, - loopbackFile: nodefs.NewLoopbackFile(fd), fs: fs, File: nodefs.NewDefaultFile(), }, fuse.OK @@ -474,5 +471,6 @@ func (f *File) GetAttr(a *fuse.Attr) fuse.Status { func (f *File) Utimens(a *time.Time, m *time.Time) fuse.Status { f.fdLock.RLock() defer f.fdLock.RUnlock() - return f.loopbackFile.Utimens(a, m) + err := syscallcompat.FutimesNano(f.intFd(), a, m) + return fuse.ToStatus(err) } diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go index 4c8af2d..096e663 100644 --- a/internal/fusefrontend/fs.go +++ b/internal/fusefrontend/fs.go @@ -372,10 +372,7 @@ func (fs *FS) Utimens(path string, a *time.Time, m *time.Time, context *fuse.Con return fuse.ToStatus(err) } defer syscall.Close(dirfd) - ts := make([]unix.Timespec, 2) - ts[0] = unix.Timespec(fuse.UtimeToTimespec(a)) - ts[1] = unix.Timespec(fuse.UtimeToTimespec(m)) - err = unix.UtimesNanoAt(dirfd, cName, ts, unix.AT_SYMLINK_NOFOLLOW) + err = syscallcompat.UtimesNanoAtNofollow(dirfd, cName, a, m) return fuse.ToStatus(err) } diff --git a/internal/syscallcompat/sys_darwin.go b/internal/syscallcompat/sys_darwin.go index ebda80f..699e38c 100644 --- a/internal/syscallcompat/sys_darwin.go +++ b/internal/syscallcompat/sys_darwin.go @@ -2,8 +2,11 @@ package syscallcompat import ( "log" + "path/filepath" "runtime" "syscall" + "time" + "unsafe" "golang.org/x/sys/unix" @@ -33,6 +36,24 @@ func pthread_setugid_np(uid uint32, gid uint32) (err error) { return } +// Unfortunately fsetattrlist does not have a syscall wrapper yet. +func fsetattrlist(fd int, list unsafe.Pointer, buf unsafe.Pointer, size uintptr, options int) (err error) { + _, _, e1 := syscall.Syscall6(syscall.SYS_FSETATTRLIST, uintptr(fd), uintptr(list), uintptr(buf), uintptr(size), uintptr(options), 0) + if e1 != 0 { + err = e1 + } + return +} + +// Setattrlist already has a syscall wrapper, but it is not exported. +func setattrlist(path *byte, list unsafe.Pointer, buf unsafe.Pointer, size uintptr, options int) (err error) { + _, _, e1 := syscall.Syscall6(syscall.SYS_SETATTRLIST, uintptr(unsafe.Pointer(path)), uintptr(list), uintptr(buf), uintptr(size), uintptr(options), 0) + if e1 != 0 { + err = e1 + } + return +} + // Sorry, fallocate is not available on OSX at all and // fcntl F_PREALLOCATE is not accessible from Go. // See https://github.com/rfjakob/gocryptfs/issues/18 if you want to help. @@ -125,6 +146,72 @@ func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (er return Mkdirat(dirfd, path, mode) } +type attrList struct { + bitmapCount uint16 + _ uint16 + CommonAttr uint32 + VolAttr uint32 + DirAttr uint32 + FileAttr uint32 + Forkattr uint32 +} + +func timesToAttrList(a *time.Time, m *time.Time) (attrList attrList, attributes [2]unix.Timespec) { + attrList.bitmapCount = unix.ATTR_BIT_MAP_COUNT + attrList.CommonAttr = 0 + i := 0 + if m != nil { + attributes[i] = unix.Timespec(fuse.UtimeToTimespec(m)) + attrList.CommonAttr |= unix.ATTR_CMN_MODTIME + i += 1 + } + if a != nil { + attributes[i] = unix.Timespec(fuse.UtimeToTimespec(a)) + attrList.CommonAttr |= unix.ATTR_CMN_ACCTIME + i += 1 + } + return attrList, attributes +} + +// FutimesNano syscall. +func FutimesNano(fd int, a *time.Time, m *time.Time) (err error) { + attrList, attributes := timesToAttrList(a, m) + return fsetattrlist(fd, unsafe.Pointer(&attrList), unsafe.Pointer(&attributes), + unsafe.Sizeof(attributes), 0) +} + +// UtimesNanoAtNofollow is like UtimesNanoAt but never follows symlinks. +// +// Unfortunately we cannot use unix.UtimesNanoAt since it is broken and just +// ignores the provided 'dirfd'. In addition, it also lacks handling of 'nil' +// pointers (used to preserve one of both timestamps). +func UtimesNanoAtNofollow(dirfd int, path string, a *time.Time, m *time.Time) (err error) { + if !filepath.IsAbs(path) { + chdirMutex.Lock() + defer chdirMutex.Unlock() + var cwd int + cwd, err = syscall.Open(".", syscall.O_RDONLY, 0) + if err != nil { + return err + } + defer syscall.Close(cwd) + err = syscall.Fchdir(dirfd) + if err != nil { + return err + } + defer syscall.Fchdir(cwd) + } + + _p0, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + + attrList, attributes := timesToAttrList(a, m) + return setattrlist(_p0, unsafe.Pointer(&attrList), unsafe.Pointer(&attributes), + unsafe.Sizeof(attributes), unix.FSOPT_NOFOLLOW) +} + func Getdents(fd int) ([]fuse.DirEntry, error) { return emulateGetdents(fd) } diff --git a/internal/syscallcompat/sys_linux.go b/internal/syscallcompat/sys_linux.go index 625e136..92d8183 100644 --- a/internal/syscallcompat/sys_linux.go +++ b/internal/syscallcompat/sys_linux.go @@ -6,6 +6,7 @@ import ( "runtime" "sync" "syscall" + "time" "golang.org/x/sys/unix" @@ -191,6 +192,28 @@ func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (er return Mkdirat(dirfd, path, mode) } +func timesToTimespec(a *time.Time, m *time.Time) []unix.Timespec { + ts := make([]unix.Timespec, 2) + ts[0] = unix.Timespec(fuse.UtimeToTimespec(a)) + ts[1] = unix.Timespec(fuse.UtimeToTimespec(m)) + return ts +} + +// FutimesNano syscall. +func FutimesNano(fd int, a *time.Time, m *time.Time) (err error) { + ts := timesToTimespec(a, m) + // To avoid introducing a separate syscall wrapper for futimens() + // (as done in go-fuse, for example), we instead use the /proc/self/fd trick. + procPath := fmt.Sprintf("/proc/self/fd/%d", fd) + return unix.UtimesNanoAt(unix.AT_FDCWD, procPath, ts, 0) +} + +// UtimesNanoAtNofollow is like UtimesNanoAt but never follows symlinks. +func UtimesNanoAtNofollow(dirfd int, path string, a *time.Time, m *time.Time) (err error) { + ts := timesToTimespec(a, m) + return unix.UtimesNanoAt(dirfd, path, ts, unix.AT_SYMLINK_NOFOLLOW) +} + // Getdents syscall. func Getdents(fd int) ([]fuse.DirEntry, error) { return getdents(fd) |