From: Nick Craig-Wood Date: Thu, 13 Dec 2012 21:02:39 +0000 (-0800) Subject: os: Improve the accuracy of os.Chtimes X-Git-Tag: go1.1rc2~1631 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=473441fc3086567f56473ba59a42cb8a4b859276;p=gostls13.git os: Improve the accuracy of os.Chtimes I've been writing some code which involves syncing files (like rsync) and it became apparent that under Linux I could read modification times (os.Lstat) with nanosecond precision but only write them with microsecond precision. This difference in precision is rather annoying when trying to discover whether files need syncing or not! I've patched syscall and os to increases the accuracy of of os.Chtimes for Linux and Windows. This involved exposing the utimensat system call under Linux and a bit of extra code under Windows. I decided not to expose the "at" bit of the system call as it is impossible to replicate under Windows, so the patch adds syscall.Utimens() to all architectures along with a ImplementsUtimens flag. If the utimensat syscall isn't available (utimensat was added to Linux in 2.6.22, Released, 8 July 2007) then it silently falls back to the microsecond accuracy version it uses now. The improved accuracy for Windows should be good for all versions of Windows. Unfortunately Darwin doesn't seem to have a utimensat system call that I could find so I couldn't implement it there. The BSDs do, but since they share their syscall implementation with Darwin I couldn't figure out how to define a syscall for *BSD and not Darwin. I've left this as a TODO in the code. In the process I implemented the missing methods for Timespec under Windows which I needed which just happened to round out the Timespec API for all platforms! ------------------------------------------------------------ Test code: http://play.golang.org/p/1xnGuYOi4b Linux Before (1000 ns precision) $ ./utimetest.linux.before z Setting mtime 1344937903123456789: 2012-08-14 10:51:43.123456789 +0100 BST Reading mtime 1344937903123457000: 2012-08-14 10:51:43.123457 +0100 BST Linux After (1 ns precision) $ ./utimetest.linux.after z Setting mtime 1344937903123456789: 2012-08-14 10:51:43.123456789 +0100 BST Reading mtime 1344937903123456789: 2012-08-14 10:51:43.123456789 +0100 BST Windows Before (1000 ns precision) X:\>utimetest.windows.before.exe c:\Test.txt Setting mtime 1344937903123456789: 2012-08-14 10:51:43.123456789 +0100 GMTDT Reading mtime 1344937903123456000: 2012-08-14 10:51:43.123456 +0100 GMTDT Windows After (100 ns precision) X:\>utimetest.windows.after.exe c:\Test.txt Setting mtime 1344937903123456789: 2012-08-14 10:51:43.123456789 +0100 GMTDT Reading mtime 1344937903123456700: 2012-08-14 10:51:43.1234567 +0100 GMTDT R=golang-dev, alex.brainman, rsc, bradfitz CC=golang-dev https://golang.org/cl/6905057 --- diff --git a/src/pkg/os/file_posix.go b/src/pkg/os/file_posix.go index 1ba3293154..b979fed97f 100644 --- a/src/pkg/os/file_posix.go +++ b/src/pkg/os/file_posix.go @@ -153,12 +153,10 @@ func (f *File) Sync() (err error) { // less precise time unit. // If there is an error, it will be of type *PathError. func Chtimes(name string, atime time.Time, mtime time.Time) error { - var utimes [2]syscall.Timeval - atime_ns := atime.Unix()*1e9 + int64(atime.Nanosecond()) - mtime_ns := mtime.Unix()*1e9 + int64(mtime.Nanosecond()) - utimes[0] = syscall.NsecToTimeval(atime_ns) - utimes[1] = syscall.NsecToTimeval(mtime_ns) - if e := syscall.Utimes(name, utimes[0:]); e != nil { + var utimes [2]syscall.Timespec + utimes[0] = syscall.NsecToTimespec(atime.UnixNano()) + utimes[1] = syscall.NsecToTimespec(mtime.UnixNano()) + if e := syscall.UtimesNano(name, utimes[0:]); e != nil { return &PathError{"chtimes", name, e} } return nil diff --git a/src/pkg/syscall/syscall_bsd.go b/src/pkg/syscall/syscall_bsd.go index 2f7007576b..0143e79a8b 100644 --- a/src/pkg/syscall/syscall_bsd.go +++ b/src/pkg/syscall/syscall_bsd.go @@ -609,6 +609,21 @@ func Utimes(path string, tv []Timeval) (err error) { return utimes(path, (*[2]Timeval)(unsafe.Pointer(&tv[0]))) } +func UtimesNano(path string, ts []Timespec) error { + // TODO: The BSDs can do utimensat with SYS_UTIMENSAT but it + // isn't supported by darwin so this uses utimes instead + if len(ts) != 2 { + return EINVAL + } + // Not as efficient as it could be because Timespec and + // Timeval have different types in the different OSes + tv := [2]Timeval{ + NsecToTimeval(TimespecToNsec(ts[0])), + NsecToTimeval(TimespecToNsec(ts[1])), + } + return utimes(path, (*[2]Timeval)(unsafe.Pointer(&tv[0]))) +} + //sys futimes(fd int, timeval *[2]Timeval) (err error) func Futimes(fd int, tv []Timeval) (err error) { if len(tv) != 2 { diff --git a/src/pkg/syscall/syscall_linux.go b/src/pkg/syscall/syscall_linux.go index b15f1fa965..61c64b5f8a 100644 --- a/src/pkg/syscall/syscall_linux.go +++ b/src/pkg/syscall/syscall_linux.go @@ -47,6 +47,25 @@ func Utimes(path string, tv []Timeval) (err error) { return utimes(path, (*[2]Timeval)(unsafe.Pointer(&tv[0]))) } +//sys utimensat(dirfd int, path string, times *[2]Timespec) (err error) +func UtimesNano(path string, ts []Timespec) (err error) { + if len(ts) != 2 { + return EINVAL + } + err = utimensat(_AT_FDCWD, path, (*[2]Timespec)(unsafe.Pointer(&ts[0]))) + if err != ENOSYS { + return err + } + // If the utimensat syscall isn't available (utimensat was added to Linux + // in 2.6.22, Released, 8 July 2007) then fall back to utimes + var tv [2]Timeval + for i := 0; i < 2; i++ { + tv[i].Sec = ts[i].Sec + tv[i].Usec = ts[i].Nsec / 1000 + } + return utimes(path, (*[2]Timeval)(unsafe.Pointer(&tv[0]))) +} + //sys futimesat(dirfd int, path *byte, times *[2]Timeval) (err error) func Futimesat(dirfd int, path string, tv []Timeval) (err error) { if len(tv) != 2 { diff --git a/src/pkg/syscall/syscall_windows.go b/src/pkg/syscall/syscall_windows.go index 9fe2da385a..5acb65dee1 100644 --- a/src/pkg/syscall/syscall_windows.go +++ b/src/pkg/syscall/syscall_windows.go @@ -451,6 +451,26 @@ func Utimes(path string, tv []Timeval) (err error) { return SetFileTime(h, nil, &a, &w) } +func UtimesNano(path string, ts []Timespec) (err error) { + if len(ts) != 2 { + return EINVAL + } + pathp, e := UTF16PtrFromString(path) + if e != nil { + return e + } + h, e := CreateFile(pathp, + FILE_WRITE_ATTRIBUTES, FILE_SHARE_WRITE, nil, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) + if e != nil { + return e + } + defer Close(h) + a := NsecToFiletime(TimespecToNsec(ts[0])) + w := NsecToFiletime(TimespecToNsec(ts[1])) + return SetFileTime(h, nil, &a, &w) +} + func Fsync(fd Handle) (err error) { return FlushFileBuffers(fd) } @@ -729,6 +749,14 @@ type Timespec struct { Nsec int64 } +func TimespecToNsec(ts Timespec) int64 { return int64(ts.Sec)*1e9 + int64(ts.Nsec) } + +func NsecToTimespec(nsec int64) (ts Timespec) { + ts.Sec = nsec / 1e9 + ts.Nsec = nsec % 1e9 + return +} + // TODO(brainman): fix all needed for net func Accept(fd Handle) (nfd Handle, sa Sockaddr, err error) { return 0, nil, EWINDOWS } diff --git a/src/pkg/syscall/types_linux.go b/src/pkg/syscall/types_linux.go index d222baa486..5aa957e781 100644 --- a/src/pkg/syscall/types_linux.go +++ b/src/pkg/syscall/types_linux.go @@ -348,6 +348,10 @@ type Ustat_t C.struct_ustat type EpollEvent C.struct_my_epoll_event +const ( + _AT_FDCWD = C.AT_FDCWD +) + // Terminal handling type Termios C.struct_termios diff --git a/src/pkg/syscall/zsyscall_linux_386.go b/src/pkg/syscall/zsyscall_linux_386.go index 789e7a535f..f1be0b3f85 100644 --- a/src/pkg/syscall/zsyscall_linux_386.go +++ b/src/pkg/syscall/zsyscall_linux_386.go @@ -64,6 +64,21 @@ func utimes(path string, times *[2]Timeval) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func utimensat(dirfd int, path string, times *[2]Timespec) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(path) + if err != nil { + return + } + _, _, e1 := Syscall(SYS_UTIMENSAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(times))) + if e1 != 0 { + err = e1 + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func futimesat(dirfd int, path *byte, times *[2]Timeval) (err error) { _, _, e1 := Syscall(SYS_FUTIMESAT, uintptr(dirfd), uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(times))) if e1 != 0 { diff --git a/src/pkg/syscall/zsyscall_linux_amd64.go b/src/pkg/syscall/zsyscall_linux_amd64.go index ed9deec9ad..11179648da 100644 --- a/src/pkg/syscall/zsyscall_linux_amd64.go +++ b/src/pkg/syscall/zsyscall_linux_amd64.go @@ -64,6 +64,21 @@ func utimes(path string, times *[2]Timeval) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func utimensat(dirfd int, path string, times *[2]Timespec) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(path) + if err != nil { + return + } + _, _, e1 := Syscall(SYS_UTIMENSAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(times))) + if e1 != 0 { + err = e1 + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func futimesat(dirfd int, path *byte, times *[2]Timeval) (err error) { _, _, e1 := Syscall(SYS_FUTIMESAT, uintptr(dirfd), uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(times))) if e1 != 0 { diff --git a/src/pkg/syscall/zsyscall_linux_arm.go b/src/pkg/syscall/zsyscall_linux_arm.go index 3288bcf950..2966bb60d6 100644 --- a/src/pkg/syscall/zsyscall_linux_arm.go +++ b/src/pkg/syscall/zsyscall_linux_arm.go @@ -64,6 +64,21 @@ func utimes(path string, times *[2]Timeval) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func utimensat(dirfd int, path string, times *[2]Timespec) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(path) + if err != nil { + return + } + _, _, e1 := Syscall(SYS_UTIMENSAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(times))) + if e1 != 0 { + err = e1 + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func futimesat(dirfd int, path *byte, times *[2]Timeval) (err error) { _, _, e1 := Syscall(SYS_FUTIMESAT, uintptr(dirfd), uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(times))) if e1 != 0 { diff --git a/src/pkg/syscall/ztypes_linux_386.go b/src/pkg/syscall/ztypes_linux_386.go index fcead2f97c..76d7b19484 100644 --- a/src/pkg/syscall/ztypes_linux_386.go +++ b/src/pkg/syscall/ztypes_linux_386.go @@ -501,6 +501,10 @@ type EpollEvent struct { Pad int32 } +const ( + _AT_FDCWD = -0x64 +) + type Termios struct { Iflag uint32 Oflag uint32 diff --git a/src/pkg/syscall/ztypes_linux_amd64.go b/src/pkg/syscall/ztypes_linux_amd64.go index 4077f038a5..0cb615d9c7 100644 --- a/src/pkg/syscall/ztypes_linux_amd64.go +++ b/src/pkg/syscall/ztypes_linux_amd64.go @@ -552,6 +552,10 @@ type EpollEvent struct { Pad int32 } +const ( + _AT_FDCWD = -0x64 +) + type Termios struct { Iflag uint32 Oflag uint32 diff --git a/src/pkg/syscall/ztypes_linux_arm.go b/src/pkg/syscall/ztypes_linux_arm.go index cd680c3ade..6b653b9bec 100644 --- a/src/pkg/syscall/ztypes_linux_arm.go +++ b/src/pkg/syscall/ztypes_linux_arm.go @@ -488,6 +488,10 @@ type EpollEvent struct { Pad int32 } +const ( + _AT_FDCWD = -0x64 +) + type Termios struct { Iflag uint32 Oflag uint32