Commit 473441fc authored by Nick Craig-Wood's avatar Nick Craig-Wood Committed by Brad Fitzpatrick

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
parent 7b4bb048
...@@ -153,12 +153,10 @@ func (f *File) Sync() (err error) { ...@@ -153,12 +153,10 @@ func (f *File) Sync() (err error) {
// less precise time unit. // less precise time unit.
// If there is an error, it will be of type *PathError. // If there is an error, it will be of type *PathError.
func Chtimes(name string, atime time.Time, mtime time.Time) error { func Chtimes(name string, atime time.Time, mtime time.Time) error {
var utimes [2]syscall.Timeval var utimes [2]syscall.Timespec
atime_ns := atime.Unix()*1e9 + int64(atime.Nanosecond()) utimes[0] = syscall.NsecToTimespec(atime.UnixNano())
mtime_ns := mtime.Unix()*1e9 + int64(mtime.Nanosecond()) utimes[1] = syscall.NsecToTimespec(mtime.UnixNano())
utimes[0] = syscall.NsecToTimeval(atime_ns) if e := syscall.UtimesNano(name, utimes[0:]); e != nil {
utimes[1] = syscall.NsecToTimeval(mtime_ns)
if e := syscall.Utimes(name, utimes[0:]); e != nil {
return &PathError{"chtimes", name, e} return &PathError{"chtimes", name, e}
} }
return nil return nil
......
...@@ -609,6 +609,21 @@ func Utimes(path string, tv []Timeval) (err error) { ...@@ -609,6 +609,21 @@ func Utimes(path string, tv []Timeval) (err error) {
return utimes(path, (*[2]Timeval)(unsafe.Pointer(&tv[0]))) 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) //sys futimes(fd int, timeval *[2]Timeval) (err error)
func Futimes(fd int, tv []Timeval) (err error) { func Futimes(fd int, tv []Timeval) (err error) {
if len(tv) != 2 { if len(tv) != 2 {
......
...@@ -47,6 +47,25 @@ func Utimes(path string, tv []Timeval) (err error) { ...@@ -47,6 +47,25 @@ func Utimes(path string, tv []Timeval) (err error) {
return utimes(path, (*[2]Timeval)(unsafe.Pointer(&tv[0]))) 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) //sys futimesat(dirfd int, path *byte, times *[2]Timeval) (err error)
func Futimesat(dirfd int, path string, tv []Timeval) (err error) { func Futimesat(dirfd int, path string, tv []Timeval) (err error) {
if len(tv) != 2 { if len(tv) != 2 {
......
...@@ -451,6 +451,26 @@ func Utimes(path string, tv []Timeval) (err error) { ...@@ -451,6 +451,26 @@ func Utimes(path string, tv []Timeval) (err error) {
return SetFileTime(h, nil, &a, &w) 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) { func Fsync(fd Handle) (err error) {
return FlushFileBuffers(fd) return FlushFileBuffers(fd)
} }
...@@ -729,6 +749,14 @@ type Timespec struct { ...@@ -729,6 +749,14 @@ type Timespec struct {
Nsec int64 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 // TODO(brainman): fix all needed for net
func Accept(fd Handle) (nfd Handle, sa Sockaddr, err error) { return 0, nil, EWINDOWS } func Accept(fd Handle) (nfd Handle, sa Sockaddr, err error) { return 0, nil, EWINDOWS }
......
...@@ -348,6 +348,10 @@ type Ustat_t C.struct_ustat ...@@ -348,6 +348,10 @@ type Ustat_t C.struct_ustat
type EpollEvent C.struct_my_epoll_event type EpollEvent C.struct_my_epoll_event
const (
_AT_FDCWD = C.AT_FDCWD
)
// Terminal handling // Terminal handling
type Termios C.struct_termios type Termios C.struct_termios
......
...@@ -64,6 +64,21 @@ func utimes(path string, times *[2]Timeval) (err error) { ...@@ -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 // 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) { 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))) _, _, e1 := Syscall(SYS_FUTIMESAT, uintptr(dirfd), uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(times)))
if e1 != 0 { if e1 != 0 {
......
...@@ -64,6 +64,21 @@ func utimes(path string, times *[2]Timeval) (err error) { ...@@ -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 // 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) { 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))) _, _, e1 := Syscall(SYS_FUTIMESAT, uintptr(dirfd), uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(times)))
if e1 != 0 { if e1 != 0 {
......
...@@ -64,6 +64,21 @@ func utimes(path string, times *[2]Timeval) (err error) { ...@@ -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 // 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) { 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))) _, _, e1 := Syscall(SYS_FUTIMESAT, uintptr(dirfd), uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(times)))
if e1 != 0 { if e1 != 0 {
......
...@@ -501,6 +501,10 @@ type EpollEvent struct { ...@@ -501,6 +501,10 @@ type EpollEvent struct {
Pad int32 Pad int32
} }
const (
_AT_FDCWD = -0x64
)
type Termios struct { type Termios struct {
Iflag uint32 Iflag uint32
Oflag uint32 Oflag uint32
......
...@@ -552,6 +552,10 @@ type EpollEvent struct { ...@@ -552,6 +552,10 @@ type EpollEvent struct {
Pad int32 Pad int32
} }
const (
_AT_FDCWD = -0x64
)
type Termios struct { type Termios struct {
Iflag uint32 Iflag uint32
Oflag uint32 Oflag uint32
......
...@@ -488,6 +488,10 @@ type EpollEvent struct { ...@@ -488,6 +488,10 @@ type EpollEvent struct {
Pad int32 Pad int32
} }
const (
_AT_FDCWD = -0x64
)
type Termios struct { type Termios struct {
Iflag uint32 Iflag uint32
Oflag uint32 Oflag uint32
......
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