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

loopback: fix Utimens on symlinks

The problem was that os.Chtimes() operates on the target of the
symlink and not on the symlink itself.
When tar extracts an archive containing a symlink, it actually wants
to set the times on the symlink. This can also be tested using
"touch -h".

This patch adds a wrapper for the Linux utimensat(2) syscall
that allows passing flags along with a unit test.

It uses the flag AT_SYMLINK_NOFOLLOW to implement
loopbackFileSystem.Utimens() properly.

It also add handling for UTIME_NOW and UTIME_OMIT that was already
present in loopbackFile.Utimens() but missing in loopbackFileSystem.

Fixes issue 81 ( https://github.com/hanwen/go-fuse/issues/81 ).
parent 55aa1802
......@@ -204,6 +204,7 @@ func (f *loopbackFile) GetAttr(a *fuse.Attr) fuse.Status {
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
......
......@@ -118,16 +118,27 @@ func (fs *loopbackFileSystem) Truncate(path string, offset uint64, context *fuse
return fuse.ToStatus(os.Truncate(fs.GetPath(path), int64(offset)))
}
func (fs *loopbackFileSystem) Utimens(path string, Atime *time.Time, Mtime *time.Time, context *fuse.Context) (code fuse.Status) {
var a time.Time
if Atime != nil {
a = *Atime
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].Sec = a.Unix()
}
var m time.Time
if Mtime != nil {
m = *Mtime
if m == nil {
ts[1].Nsec = _UTIME_OMIT
} else {
ts[1].Sec = m.Unix()
}
return fuse.ToStatus(os.Chtimes(fs.GetPath(path), a, m))
err := sysUtimensat(0, fs.GetPath(path), &ts, _AT_SYMLINK_NOFOLLOW)
return fuse.ToStatus(err)
}
func (fs *loopbackFileSystem) Readlink(name string, context *fuse.Context) (out string, code fuse.Status) {
......
......@@ -116,3 +116,26 @@ func sysListxattr(path string, dest []byte) (sz int, err error) {
func sysSetxattr(path string, attr string, val []byte, flag int) error {
return syscall.Setxattr(path, attr, val, flag)
}
const _AT_SYMLINK_NOFOLLOW = 0x100
// Linux kernel syscall utimensat(2)
//
// Needed to implement SetAttr on symlinks correctly as only utimensat provides
// AT_SYMLINK_NOFOLLOW.
func sysUtimensat(dirfd int, pathname string, times *[2]syscall.Timespec, flags int) (err error) {
// Null-terminated version of pathname
p0, err := syscall.BytePtrFromString(pathname)
if err != nil {
return err
}
_, _, e1 := syscall.Syscall6(syscall.SYS_UTIMENSAT,
uintptr(dirfd), uintptr(unsafe.Pointer(p0)), uintptr(unsafe.Pointer(times)), uintptr(flags), 0, 0)
if e1 != 0 {
err = syscall.Errno(e1)
}
return
}
package pathfs
import (
"reflect"
"os"
"testing"
"syscall"
)
func TestSysUtimensat(t *testing.T) {
symlink := "/tmp/TestSysUtimensat"
os.Remove(symlink)
err := os.Symlink("/nonexisting/file", symlink)
if err != nil {
t.Fatal(err)
}
var ts [2]syscall.Timespec
// Atime
ts[0].Nsec = 1111
ts[0].Sec = 2222
// Mtime
ts[1].Nsec = 3333
ts[1].Sec = 4444
err = sysUtimensat(0, symlink, &ts, _AT_SYMLINK_NOFOLLOW)
if err != nil {
t.Fatal(err)
}
var st syscall.Stat_t
err = syscall.Lstat(symlink, &st)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(st.Atim, ts[0]) {
t.Errorf("Wrong atime: %v", st.Atim)
}
if !reflect.DeepEqual(st.Mtim, ts[1]) {
t.Errorf("Wrong mtime: %v", st.Mtim)
}
}
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