]> Cypherpunks repositories - gostls13.git/commitdiff
os: Improve the accuracy of os.Chtimes
authorNick Craig-Wood <nick@craig-wood.com>
Thu, 13 Dec 2012 21:02:39 +0000 (13:02 -0800)
committerBrad Fitzpatrick <bradfitz@golang.org>
Thu, 13 Dec 2012 21:02:39 +0000 (13:02 -0800)
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

src/pkg/os/file_posix.go
src/pkg/syscall/syscall_bsd.go
src/pkg/syscall/syscall_linux.go
src/pkg/syscall/syscall_windows.go
src/pkg/syscall/types_linux.go
src/pkg/syscall/zsyscall_linux_386.go
src/pkg/syscall/zsyscall_linux_amd64.go
src/pkg/syscall/zsyscall_linux_arm.go
src/pkg/syscall/ztypes_linux_386.go
src/pkg/syscall/ztypes_linux_amd64.go
src/pkg/syscall/ztypes_linux_arm.go

index 1ba32931541efa8a01b919f4ae77700babe7d17d..b979fed97f7f9c77a336b4aff2e791c5d9b2e035 100644 (file)
@@ -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
index 2f7007576bfeb3cb7a2b997e12b5caf6070e00f8..0143e79a8b3354062e2c8123d2477c47a0f13dec 100644 (file)
@@ -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 {
index b15f1fa965324ff2620929c79ee690aee8db3fdb..61c64b5f8a5ffd88de0dc10a0ffca965e0d35fdd 100644 (file)
@@ -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 {
index 9fe2da385ab4a9df100df1de8bee4040be4c3e60..5acb65dee144ca55e340ad8f9e6962c0f6fed712 100644 (file)
@@ -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 }
index d222baa48696d1242e7374d1e55b5c4514958dd0..5aa957e7815c38867d4d404089c464b94ba62cc7 100644 (file)
@@ -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
index 789e7a535f0cf8f4836161dc2c56a8ba77c9a596..f1be0b3f85705c438c0030076a8103ceb8b15659 100644 (file)
@@ -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 {
index ed9deec9ad3ee39bc372d6a9a8802eaab0180f88..11179648da0538bf9a274e2b2075500c8517dae7 100644 (file)
@@ -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 {
index 3288bcf950f36feb7274a635929138c690052703..2966bb60d6b0840f8c7176a4a6dd789442f1904e 100644 (file)
@@ -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 {
index fcead2f97c14a3637da08803860f811e8798688a..76d7b194840312baa419974dada647dfd6830c85 100644 (file)
@@ -501,6 +501,10 @@ type EpollEvent struct {
        Pad    int32
 }
 
+const (
+       _AT_FDCWD = -0x64
+)
+
 type Termios struct {
        Iflag     uint32
        Oflag     uint32
index 4077f038a578e4499315630747a235baece233cb..0cb615d9c71f751be9f0a34ec9d6613bcd1b0df3 100644 (file)
@@ -552,6 +552,10 @@ type EpollEvent struct {
        Pad    int32
 }
 
+const (
+       _AT_FDCWD = -0x64
+)
+
 type Termios struct {
        Iflag     uint32
        Oflag     uint32
index cd680c3ade9fa0deeeeaf852d59ee3ac125afffd..6b653b9bec5693d7d315a4cbd6c906a92f9c0333 100644 (file)
@@ -488,6 +488,10 @@ type EpollEvent struct {
        Pad    int32
 }
 
+const (
+       _AT_FDCWD = -0x64
+)
+
 type Termios struct {
        Iflag     uint32
        Oflag     uint32