diff options
author | Constantin Konstantinidis <constantinkonstantinidis@gmail.com> | 2020-10-19 19:19:17 +0200 |
---|---|---|
committer | Gopher Robot <gobot@golang.org> | 2023-05-11 18:19:17 +0000 |
commit | 5a9b6432ec8b9199ce9fce9387e94195138b313f (patch) | |
tree | 48ff3ce1365fe5c46f099e36418d1b5c313768f2 /src/os | |
parent | 96add980ad27faed627f26ef1ab09e8fe45d6bd1 (diff) | |
download | go-git-5a9b6432ec8b9199ce9fce9387e94195138b313f.tar.gz |
os: make Chtimes accept empty time values to skip file time modification
Empty time value time.Time{} leaves the corresponding time of the file
unchanged.
Fixes #32558
Change-Id: I1aff42f30668ff505ecec2e9509d8f2b8e4b1b6a
Reviewed-on: https://go-review.googlesource.com/c/go/+/219638
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Ian Lance Taylor <iant@google.com>
Run-TryBot: Ian Lance Taylor <iant@google.com>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Diffstat (limited to 'src/os')
-rw-r--r-- | src/os/file_plan9.go | 7 | ||||
-rw-r--r-- | src/os/file_posix.go | 12 | ||||
-rw-r--r-- | src/os/file_unix.go | 2 | ||||
-rw-r--r-- | src/os/file_windows.go | 2 | ||||
-rw-r--r-- | src/os/os_test.go | 122 |
5 files changed, 143 insertions, 2 deletions
diff --git a/src/os/file_plan9.go b/src/os/file_plan9.go index 6e05df160e..8336487c14 100644 --- a/src/os/file_plan9.go +++ b/src/os/file_plan9.go @@ -447,6 +447,7 @@ func chmod(name string, mode FileMode) error { // Chtimes changes the access and modification times of the named // file, similar to the Unix utime() or utimes() functions. +// A zero time.Time value will leave the corresponding file time unchanged. // // The underlying filesystem may truncate or round the values to a // less precise time unit. @@ -457,6 +458,12 @@ func Chtimes(name string, atime time.Time, mtime time.Time) error { d.Null() d.Atime = uint32(atime.Unix()) d.Mtime = uint32(mtime.Unix()) + if atime.IsZero() { + d.Atime = 0xFFFFFFFF + } + if mtime.IsZero() { + d.Mtime = 0xFFFFFFFF + } var buf [syscall.STATFIXLEN]byte n, err := d.Marshal(buf[:]) diff --git a/src/os/file_posix.go b/src/os/file_posix.go index 4e0f7c1d80..e06ab1b7b9 100644 --- a/src/os/file_posix.go +++ b/src/os/file_posix.go @@ -173,14 +173,22 @@ func (f *File) Sync() error { // Chtimes changes the access and modification times of the named // file, similar to the Unix utime() or utimes() functions. +// A zero time.Time value will leave the corresponding file time unchanged. // // The underlying filesystem may truncate or round the values to a // 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.Timespec - utimes[0] = syscall.NsecToTimespec(atime.UnixNano()) - utimes[1] = syscall.NsecToTimespec(mtime.UnixNano()) + set := func(i int, t time.Time) { + if t.IsZero() { + utimes[i] = syscall.Timespec{Sec: _UTIME_OMIT, Nsec: _UTIME_OMIT} + } else { + utimes[i] = syscall.NsecToTimespec(t.UnixNano()) + } + } + set(0, atime) + set(1, mtime) if e := syscall.UtimesNano(fixLongPath(name), utimes[0:]); e != nil { return &PathError{Op: "chtimes", Path: name, Err: e} } diff --git a/src/os/file_unix.go b/src/os/file_unix.go index a14295cfff..f0e5d3cd4f 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -14,6 +14,8 @@ import ( "syscall" ) +const _UTIME_OMIT = unix.UTIME_OMIT + // fixLongPath is a noop on non-Windows platforms. func fixLongPath(path string) string { return path diff --git a/src/os/file_windows.go b/src/os/file_windows.go index 7e495069ef..f5a436e235 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -15,6 +15,8 @@ import ( "unsafe" ) +const _UTIME_OMIT = 0 + // file is the real representation of *File. // The extra level of indirection ensures that no clients of os // can overwrite this data, which could cause the finalizer diff --git a/src/os/os_test.go b/src/os/os_test.go index a0d9411b6e..3f4fbabb2d 100644 --- a/src/os/os_test.go +++ b/src/os/os_test.go @@ -1386,6 +1386,128 @@ func TestChtimes(t *testing.T) { testChtimes(t, f.Name()) } +func TestChtimesWithZeroTimes(t *testing.T) { + file := newFile("chtimes-with-zero", t) + _, err := file.Write([]byte("hello, world\n")) + if err != nil { + t.Fatalf("Write: %s", err) + } + fName := file.Name() + defer Remove(file.Name()) + err = file.Close() + if err != nil { + t.Errorf("%v", err) + } + fs, err := Stat(fName) + if err != nil { + t.Fatal(err) + } + startAtime := Atime(fs) + startMtime := fs.ModTime() + switch runtime.GOOS { + case "js": + startAtime = startAtime.Truncate(time.Second) + startMtime = startMtime.Truncate(time.Second) + } + at0 := startAtime + mt0 := startMtime + t0 := startMtime.Truncate(time.Second).Add(1 * time.Hour) + + tests := []struct { + aTime time.Time + mTime time.Time + wantATime time.Time + wantMTime time.Time + }{ + { + aTime: time.Time{}, + mTime: time.Time{}, + wantATime: startAtime, + wantMTime: startMtime, + }, + { + aTime: t0.Add(200 * time.Second), + mTime: time.Time{}, + wantATime: t0.Add(200 * time.Second), + wantMTime: startMtime, + }, + { + aTime: time.Time{}, + mTime: t0.Add(100 * time.Second), + wantATime: t0.Add(200 * time.Second), + wantMTime: t0.Add(100 * time.Second), + }, + { + aTime: t0.Add(300 * time.Second), + mTime: t0.Add(100 * time.Second), + wantATime: t0.Add(300 * time.Second), + wantMTime: t0.Add(100 * time.Second), + }, + } + + for _, tt := range tests { + // Now change the times accordingly. + if err := Chtimes(fName, tt.aTime, tt.mTime); err != nil { + t.Error(err) + } + + // Finally verify the expectations. + fs, err = Stat(fName) + if err != nil { + t.Error(err) + } + at0 = Atime(fs) + mt0 = fs.ModTime() + + if got, want := at0, tt.wantATime; !got.Equal(want) { + errormsg := fmt.Sprintf("AccessTime mismatch with values ATime:%q-MTime:%q\ngot: %q\nwant: %q", tt.aTime, tt.mTime, got, want) + switch runtime.GOOS { + case "plan9": + // Mtime is the time of the last change of + // content. Similarly, atime is set whenever + // the contents are accessed; also, it is set + // whenever mtime is set. + case "windows": + t.Error(errormsg) + default: // unix's + if got, want := at0, tt.wantATime; !got.Equal(want) { + mounts, err := ReadFile("/bin/mounts") + if err != nil { + mounts, err = ReadFile("/etc/mtab") + } + if strings.Contains(string(mounts), "noatime") { + t.Log(errormsg) + t.Log("A filesystem is mounted with noatime; ignoring.") + } else { + switch runtime.GOOS { + case "netbsd", "dragonfly": + // On a 64-bit implementation, birth time is generally supported and cannot be changed. + // When supported, atime update is restricted and depends on the file system and on the + // OS configuration. + if strings.Contains(runtime.GOARCH, "64") { + t.Log(errormsg) + t.Log("Filesystem might not support atime changes; ignoring.") + } + default: + t.Error(errormsg) + } + } + } + } + } + if got, want := mt0, tt.wantMTime; !got.Equal(want) { + errormsg := fmt.Sprintf("ModTime mismatch with values ATime:%q-MTime:%q\ngot: %q\nwant: %q", tt.aTime, tt.mTime, got, want) + switch runtime.GOOS { + case "dragonfly": + t.Log(errormsg) + t.Log("Mtime is always updated; ignoring.") + default: + t.Error(errormsg) + } + } + } +} + // Use TempDir (via newDir) to make sure we're on a local file system, // so that timings are not distorted by latency and caching. // On NFS, timings can be off due to caching of meta-data on |