Fix loss of precision for setting timestamps so roundtrip happens properly
This commit is contained in:
parent
25a1b96537
commit
19668ac18f
4 changed files with 96 additions and 36 deletions
14
chtimes.go
Normal file
14
chtimes.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// Default implementation of Chtimes
|
||||||
|
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||||
|
return os.Chtimes(name, atime, mtime)
|
||||||
|
}
|
78
chtimes_linux.go
Normal file
78
chtimes_linux.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
// An implementation of Chtimes which preserves nanosecond precision under linux
|
||||||
|
//
|
||||||
|
// Should go in standard library, but here for now
|
||||||
|
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// COPIED from syscall
|
||||||
|
// byteSliceFromString returns a NUL-terminated slice of bytes
|
||||||
|
// containing the text of s. If s contains a NUL byte at any
|
||||||
|
// location, it returns (nil, EINVAL).
|
||||||
|
func byteSliceFromString(s string) ([]byte, error) {
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if s[i] == 0 {
|
||||||
|
return nil, syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a := make([]byte, len(s)+1)
|
||||||
|
copy(a, s)
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// COPIED from syscall
|
||||||
|
// bytePtrFromString returns a pointer to a NUL-terminated array of
|
||||||
|
// bytes containing the text of s. If s contains a NUL byte at any
|
||||||
|
// location, it returns (nil, EINVAL).
|
||||||
|
func bytePtrFromString(s string) (*byte, error) {
|
||||||
|
a, err := byteSliceFromString(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &a[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// COPIED from syscall auto generated code modified from utimes
|
||||||
|
func utimensat(dirfd int, path string, times *[2]syscall.Timespec) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = bytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _, e1 := syscall.Syscall(syscall.SYS_UTIMENSAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(times)))
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME needs defining properly!
|
||||||
|
const AT_FDCWD = -100
|
||||||
|
|
||||||
|
// COPIED from syscall and modified
|
||||||
|
//sys utimes(path string, times *[2]Timeval) (err error)
|
||||||
|
func Utimensat(dirfd int, path string, ts []syscall.Timespec) (err error) {
|
||||||
|
if len(ts) != 2 {
|
||||||
|
return syscall.EINVAL
|
||||||
|
}
|
||||||
|
return utimensat(dirfd, path, (*[2]syscall.Timespec)(unsafe.Pointer(&ts[0])))
|
||||||
|
}
|
||||||
|
|
||||||
|
// COPIED from syscall and modified
|
||||||
|
func Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||||
|
var utimes [2]syscall.Timespec
|
||||||
|
utimes[0] = syscall.NsecToTimespec(atime.UnixNano())
|
||||||
|
utimes[1] = syscall.NsecToTimespec(mtime.UnixNano())
|
||||||
|
if e := Utimensat(AT_FDCWD, name, utimes[0:]); e != nil {
|
||||||
|
return &os.PathError{"chtimes", name, e}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
38
notes.txt
38
notes.txt
|
@ -48,40 +48,8 @@ Need to make directory objects otherwise can't upload an empty directory
|
||||||
|
|
||||||
Make a fs.Errorf and count errors and log them at a different level
|
Make a fs.Errorf and count errors and log them at a different level
|
||||||
|
|
||||||
Seem to be able to read stats to ns precision (using struct stat
|
Submit chtimes_linux.go to standard library
|
||||||
defined in asm-generic/stat.h)
|
* Can also fix Futime which isn't using the syscall under linux
|
||||||
|
* Also for all the *bsd so likely they could be fixed too
|
||||||
|
|
||||||
https://www.kernel.org/doc/man-pages/online/pages/man2/stat.2.html
|
|
||||||
|
|
||||||
but only write them to us precision.
|
|
||||||
This is confirmed by looking at the source for Chtimes
|
|
||||||
|
|
||||||
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 {
|
|
||||||
return &PathError{"chtimes", name, e}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
And
|
|
||||||
|
|
||||||
func NsecToTimeval(nsec int64) (tv Timeval) {
|
|
||||||
nsec += 999 // round up to microsecond
|
|
||||||
tv.Sec = nsec / 1e9
|
|
||||||
tv.Usec = nsec % 1e9 / 1e3
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Seems likely Go should be using
|
|
||||||
utimensat() and futimens()
|
|
||||||
Instead of utimes() - the syscall is defined already
|
|
||||||
SYS_UTIMENSAT
|
|
||||||
Also for all the *bsd so likely they could be fixed too
|
|
||||||
|
|
||||||
Can also fix Futime which isn't using the syscall under linux
|
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,7 @@ func (fs *FsObject) md5sum() (string, error) {
|
||||||
|
|
||||||
// Sets the modification time of the local fs object
|
// Sets the modification time of the local fs object
|
||||||
func (fs *FsObject) SetModTime(modTime time.Time) {
|
func (fs *FsObject) SetModTime(modTime time.Time) {
|
||||||
err := os.Chtimes(fs.path, modTime, modTime)
|
err := Chtimes(fs.path, modTime, modTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fs.Debugf("Failed to set mtime on file: %s", err)
|
fs.Debugf("Failed to set mtime on file: %s", err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue