summaryrefslogtreecommitdiff
path: root/src/os
diff options
context:
space:
mode:
authorConstantin Konstantinidis <constantinkonstantinidis@gmail.com>2020-10-19 19:19:17 +0200
committerGopher Robot <gobot@golang.org>2023-05-11 18:19:17 +0000
commit5a9b6432ec8b9199ce9fce9387e94195138b313f (patch)
tree48ff3ce1365fe5c46f099e36418d1b5c313768f2 /src/os
parent96add980ad27faed627f26ef1ab09e8fe45d6bd1 (diff)
downloadgo-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.go7
-rw-r--r--src/os/file_posix.go12
-rw-r--r--src/os/file_unix.go2
-rw-r--r--src/os/file_windows.go2
-rw-r--r--src/os/os_test.go122
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