]> Cypherpunks.ru repositories - gostls13.git/commitdiff
os: make Chtimes accept empty time values to skip file time modification
authorConstantin Konstantinidis <constantinkonstantinidis@gmail.com>
Mon, 19 Oct 2020 17:19:17 +0000 (19:19 +0200)
committerGopher Robot <gobot@golang.org>
Thu, 11 May 2023 18:19:17 +0000 (18:19 +0000)
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>
18 files changed:
src/internal/syscall/unix/at_aix.go
src/internal/syscall/unix/at_js.go [new file with mode: 0644]
src/internal/syscall/unix/at_solaris.go
src/internal/syscall/unix/at_sysnum_darwin.go
src/internal/syscall/unix/at_sysnum_dragonfly.go
src/internal/syscall/unix/at_sysnum_freebsd.go
src/internal/syscall/unix/at_sysnum_linux.go
src/internal/syscall/unix/at_sysnum_netbsd.go
src/internal/syscall/unix/at_sysnum_openbsd.go
src/internal/syscall/unix/at_wasip1.go [new file with mode: 0644]
src/os/file_plan9.go
src/os/file_posix.go
src/os/file_unix.go
src/os/file_windows.go
src/os/os_test.go
src/syscall/fs_js.go
src/syscall/fs_wasip1.go
src/syscall/syscall_windows.go

index 425df982118ae22d9be0b3a7cfe7a1ac166f7cb7..3fe3285ce21257c527eda5307614526f723a3112 100644 (file)
@@ -11,4 +11,5 @@ package unix
 const (
        AT_REMOVEDIR        = 0x1
        AT_SYMLINK_NOFOLLOW = 0x1
+       UTIME_OMIT          = -0x3
 )
diff --git a/src/internal/syscall/unix/at_js.go b/src/internal/syscall/unix/at_js.go
new file mode 100644 (file)
index 0000000..d05ccce
--- /dev/null
@@ -0,0 +1,13 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package unix
+
+const (
+       // UTIME_OMIT is the sentinel value to indicate that a time value should not
+       // be changed. It is useful for example to indicate for example with UtimesNano
+       // to avoid changing AccessTime or ModifiedTime.
+       // Its value must match syscall/fs_js.go
+       UTIME_OMIT = -0x2
+)
index e917c4fc9be11bb74f048655fa9df14349cf2d3b..4ab224d670b9faaa8c0ba86368a059bd72f34e95 100644 (file)
@@ -16,4 +16,6 @@ func syscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err
 const (
        AT_REMOVEDIR        = 0x1
        AT_SYMLINK_NOFOLLOW = 0x1000
+
+       UTIME_OMIT = -0x2
 )
index aaaaa4751c0f0d01bf5f1e9e7bfbb11cd24266f1..208ff34d038eb514aaa71e9e25a668294a96aa58 100644 (file)
@@ -6,3 +6,5 @@ package unix
 
 const AT_REMOVEDIR = 0x80
 const AT_SYMLINK_NOFOLLOW = 0x0020
+
+const UTIME_OMIT = -0x2
index cec9abce6a28e82236d9f3df4f439dea5a087c67..b7ed3f732b8b88db2542ee88c7bcad990e3ffa87 100644 (file)
@@ -12,3 +12,5 @@ const fstatatTrap uintptr = syscall.SYS_FSTATAT
 
 const AT_REMOVEDIR = 0x2
 const AT_SYMLINK_NOFOLLOW = 0x1
+
+const UTIME_OMIT = -0x1
index 530f5c2a2b8fe08f9693c2eed6e04bf883bc0f4d..9cd5da6ce32231a07ab2f5e2745e03666de422a9 100644 (file)
@@ -10,6 +10,8 @@ const (
        AT_REMOVEDIR        = 0x800
        AT_SYMLINK_NOFOLLOW = 0x200
 
+       UTIME_OMIT = -0x2
+
        unlinkatTrap       uintptr = syscall.SYS_UNLINKAT
        openatTrap         uintptr = syscall.SYS_OPENAT
        posixFallocateTrap uintptr = syscall.SYS_POSIX_FALLOCATE
index b9b8495e32ac3ceb7a03c86378c13664f148e348..7c3b15c30302c87252c4e6e24701b21fe20da2f9 100644 (file)
@@ -14,4 +14,6 @@ const (
        AT_FDCWD            = -0x64
        AT_REMOVEDIR        = 0x200
        AT_SYMLINK_NOFOLLOW = 0x100
+
+       UTIME_OMIT = 0x3ffffffe
 )
index fe45e296d7ec200603dae002b782846a106ba8cb..becc1bdf8233e769907c6c5009a9e891bcdfcd15 100644 (file)
@@ -12,3 +12,5 @@ const fstatatTrap uintptr = syscall.SYS_FSTATAT
 
 const AT_REMOVEDIR = 0x800
 const AT_SYMLINK_NOFOLLOW = 0x200
+
+const UTIME_OMIT = (1 << 30) - 2
index c2d48b9914d36e12eb3aa0c8e807822d918228a3..fd389477ec33d624820abb6d719071a730d6aab9 100644 (file)
@@ -12,3 +12,5 @@ const fstatatTrap uintptr = syscall.SYS_FSTATAT
 
 const AT_REMOVEDIR = 0x08
 const AT_SYMLINK_NOFOLLOW = 0x02
+
+const UTIME_OMIT = -0x1
diff --git a/src/internal/syscall/unix/at_wasip1.go b/src/internal/syscall/unix/at_wasip1.go
new file mode 100644 (file)
index 0000000..3d47d7e
--- /dev/null
@@ -0,0 +1,13 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package unix
+
+const (
+       // UTIME_OMIT is the sentinel value to indicate that a time value should not
+       // be changed. It is useful for example to indicate for example with UtimesNano
+       // to avoid changing AccessTime or ModifiedTime.
+       // Its value must match syscall/fs_wasip1.go
+       UTIME_OMIT = -0x2
+)
index 6e05df160ec44eb5f09c078ef5db69fa256c57f1..8336487c147f856f07ec7a35721b3c7eb05a3f66 100644 (file)
@@ -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[:])
index 4e0f7c1d80d3ea99d98ed7941481c2fbaa2eff5b..e06ab1b7b979d668f73b37bed7ace9a9d136ccbb 100644 (file)
@@ -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}
        }
index a14295cfff78c1cde0aa845408d5446a5e73e644..f0e5d3cd4f70d70b8eaf2faaf80c1368f9c2028e 100644 (file)
@@ -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
index 7e495069ef7f69ad03b6d0a795323f81b3fc7201..f5a436e2353a3cf821ca0a29836f68c3296c3ea5 100644 (file)
@@ -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
index a0d9411b6ec4ab3aa1bd359dbb59b82a20c4c00e..3f4fbabb2d35b2f68e36841fb3755ec2a488da42 100644 (file)
@@ -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
index ce0aa88828ff7456c89c3c82310b68acf9dccd71..793b9a2d41c38e337759fdeccc161f1a4fd2e379 100644 (file)
@@ -273,6 +273,8 @@ func Lchown(path string, uid, gid int) error {
 }
 
 func UtimesNano(path string, ts []Timespec) error {
+       // UTIME_OMIT value must match internal/syscall/unix/at_js.go
+       const UTIME_OMIT = -0x2
        if err := checkPath(path); err != nil {
                return err
        }
@@ -281,6 +283,18 @@ func UtimesNano(path string, ts []Timespec) error {
        }
        atime := ts[0].Sec
        mtime := ts[1].Sec
+       if atime == UTIME_OMIT || mtime == UTIME_OMIT {
+               var st Stat_t
+               if err := Stat(path, &st); err != nil {
+                       return err
+               }
+               if atime == UTIME_OMIT {
+                       atime = st.Atime
+               }
+               if mtime == UTIME_OMIT {
+                       mtime = st.Mtime
+               }
+       }
        _, err := fsCall("utimes", path, atime, mtime)
        return err
 }
index fa7c5c888531764b75b49c13bcc72aea6ffb7054..84c65c070f6da1fc70d991f8c3632e649f5da263 100644 (file)
@@ -663,17 +663,33 @@ func Lchown(path string, uid, gid int) error {
 }
 
 func UtimesNano(path string, ts []Timespec) error {
+       // UTIME_OMIT value must match internal/syscall/unix/at_wasip1.go
+       const UTIME_OMIT = -0x2
        if path == "" {
                return EINVAL
        }
        dirFd, pathPtr, pathLen := preparePath(path)
+       atime := TimespecToNsec(ts[0])
+       mtime := TimespecToNsec(ts[1])
+       if ts[0].Nsec == UTIME_OMIT || ts[1].Nsec == UTIME_OMIT {
+               var st Stat_t
+               if err := Stat(path, &st); err != nil {
+                       return err
+               }
+               if ts[0].Nsec == UTIME_OMIT {
+                       atime = int64(st.Atime)
+               }
+               if ts[1].Nsec == UTIME_OMIT {
+                       mtime = int64(st.Mtime)
+               }
+       }
        errno := path_filestat_set_times(
                dirFd,
                LOOKUP_SYMLINK_FOLLOW,
                pathPtr,
                pathLen,
-               timestamp(TimespecToNsec(ts[0])),
-               timestamp(TimespecToNsec(ts[1])),
+               timestamp(atime),
+               timestamp(mtime),
                FILESTAT_SET_ATIM|FILESTAT_SET_MTIM,
        )
        return errnoErr(errno)
index cf6049a2f2a3cf39fd626c51ac5e076602942e85..1f7753663b5aa0cd0251deecf8f046e91b6dbc2b 100644 (file)
@@ -635,8 +635,14 @@ func Utimes(path string, tv []Timeval) (err error) {
                return e
        }
        defer Close(h)
-       a := NsecToFiletime(tv[0].Nanoseconds())
-       w := NsecToFiletime(tv[1].Nanoseconds())
+       a := Filetime{}
+       w := Filetime{}
+       if tv[0].Nanoseconds() != 0 {
+               a = NsecToFiletime(tv[0].Nanoseconds())
+       }
+       if tv[0].Nanoseconds() != 0 {
+               w = NsecToFiletime(tv[1].Nanoseconds())
+       }
        return SetFileTime(h, nil, &a, &w)
 }
 
@@ -655,8 +661,14 @@ func UtimesNano(path string, ts []Timespec) (err error) {
                return e
        }
        defer Close(h)
-       a := NsecToFiletime(TimespecToNsec(ts[0]))
-       w := NsecToFiletime(TimespecToNsec(ts[1]))
+       a := Filetime{}
+       w := Filetime{}
+       if TimespecToNsec(ts[0]) != 0 {
+               a = NsecToFiletime(TimespecToNsec(ts[0]))
+       }
+       if TimespecToNsec(ts[1]) != 0 {
+               w = NsecToFiletime(TimespecToNsec(ts[1]))
+       }
        return SetFileTime(h, nil, &a, &w)
 }