Skip to content

Commit 5a9b643

Browse files
iwdgogopherbot
authored andcommitted
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 <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Reviewed-by: Cherry Mui <[email protected]>
1 parent 96add98 commit 5a9b643

18 files changed

+232
-8
lines changed

src/internal/syscall/unix/at_aix.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ package unix
1111
const (
1212
AT_REMOVEDIR = 0x1
1313
AT_SYMLINK_NOFOLLOW = 0x1
14+
UTIME_OMIT = -0x3
1415
)

src/internal/syscall/unix/at_js.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package unix
6+
7+
const (
8+
// UTIME_OMIT is the sentinel value to indicate that a time value should not
9+
// be changed. It is useful for example to indicate for example with UtimesNano
10+
// to avoid changing AccessTime or ModifiedTime.
11+
// Its value must match syscall/fs_js.go
12+
UTIME_OMIT = -0x2
13+
)

src/internal/syscall/unix/at_solaris.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@ func syscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err
1616
const (
1717
AT_REMOVEDIR = 0x1
1818
AT_SYMLINK_NOFOLLOW = 0x1000
19+
20+
UTIME_OMIT = -0x2
1921
)

src/internal/syscall/unix/at_sysnum_darwin.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ package unix
66

77
const AT_REMOVEDIR = 0x80
88
const AT_SYMLINK_NOFOLLOW = 0x0020
9+
10+
const UTIME_OMIT = -0x2

src/internal/syscall/unix/at_sysnum_dragonfly.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ const fstatatTrap uintptr = syscall.SYS_FSTATAT
1212

1313
const AT_REMOVEDIR = 0x2
1414
const AT_SYMLINK_NOFOLLOW = 0x1
15+
16+
const UTIME_OMIT = -0x1

src/internal/syscall/unix/at_sysnum_freebsd.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const (
1010
AT_REMOVEDIR = 0x800
1111
AT_SYMLINK_NOFOLLOW = 0x200
1212

13+
UTIME_OMIT = -0x2
14+
1315
unlinkatTrap uintptr = syscall.SYS_UNLINKAT
1416
openatTrap uintptr = syscall.SYS_OPENAT
1517
posixFallocateTrap uintptr = syscall.SYS_POSIX_FALLOCATE

src/internal/syscall/unix/at_sysnum_linux.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,6 @@ const (
1414
AT_FDCWD = -0x64
1515
AT_REMOVEDIR = 0x200
1616
AT_SYMLINK_NOFOLLOW = 0x100
17+
18+
UTIME_OMIT = 0x3ffffffe
1719
)

src/internal/syscall/unix/at_sysnum_netbsd.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ const fstatatTrap uintptr = syscall.SYS_FSTATAT
1212

1313
const AT_REMOVEDIR = 0x800
1414
const AT_SYMLINK_NOFOLLOW = 0x200
15+
16+
const UTIME_OMIT = (1 << 30) - 2

src/internal/syscall/unix/at_sysnum_openbsd.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ const fstatatTrap uintptr = syscall.SYS_FSTATAT
1212

1313
const AT_REMOVEDIR = 0x08
1414
const AT_SYMLINK_NOFOLLOW = 0x02
15+
16+
const UTIME_OMIT = -0x1
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package unix
6+
7+
const (
8+
// UTIME_OMIT is the sentinel value to indicate that a time value should not
9+
// be changed. It is useful for example to indicate for example with UtimesNano
10+
// to avoid changing AccessTime or ModifiedTime.
11+
// Its value must match syscall/fs_wasip1.go
12+
UTIME_OMIT = -0x2
13+
)

src/os/file_plan9.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,7 @@ func chmod(name string, mode FileMode) error {
447447

448448
// Chtimes changes the access and modification times of the named
449449
// file, similar to the Unix utime() or utimes() functions.
450+
// A zero time.Time value will leave the corresponding file time unchanged.
450451
//
451452
// The underlying filesystem may truncate or round the values to a
452453
// less precise time unit.
@@ -457,6 +458,12 @@ func Chtimes(name string, atime time.Time, mtime time.Time) error {
457458
d.Null()
458459
d.Atime = uint32(atime.Unix())
459460
d.Mtime = uint32(mtime.Unix())
461+
if atime.IsZero() {
462+
d.Atime = 0xFFFFFFFF
463+
}
464+
if mtime.IsZero() {
465+
d.Mtime = 0xFFFFFFFF
466+
}
460467

461468
var buf [syscall.STATFIXLEN]byte
462469
n, err := d.Marshal(buf[:])

src/os/file_posix.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,14 +173,22 @@ func (f *File) Sync() error {
173173

174174
// Chtimes changes the access and modification times of the named
175175
// file, similar to the Unix utime() or utimes() functions.
176+
// A zero time.Time value will leave the corresponding file time unchanged.
176177
//
177178
// The underlying filesystem may truncate or round the values to a
178179
// less precise time unit.
179180
// If there is an error, it will be of type *PathError.
180181
func Chtimes(name string, atime time.Time, mtime time.Time) error {
181182
var utimes [2]syscall.Timespec
182-
utimes[0] = syscall.NsecToTimespec(atime.UnixNano())
183-
utimes[1] = syscall.NsecToTimespec(mtime.UnixNano())
183+
set := func(i int, t time.Time) {
184+
if t.IsZero() {
185+
utimes[i] = syscall.Timespec{Sec: _UTIME_OMIT, Nsec: _UTIME_OMIT}
186+
} else {
187+
utimes[i] = syscall.NsecToTimespec(t.UnixNano())
188+
}
189+
}
190+
set(0, atime)
191+
set(1, mtime)
184192
if e := syscall.UtimesNano(fixLongPath(name), utimes[0:]); e != nil {
185193
return &PathError{Op: "chtimes", Path: name, Err: e}
186194
}

src/os/file_unix.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"syscall"
1515
)
1616

17+
const _UTIME_OMIT = unix.UTIME_OMIT
18+
1719
// fixLongPath is a noop on non-Windows platforms.
1820
func fixLongPath(path string) string {
1921
return path

src/os/file_windows.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import (
1515
"unsafe"
1616
)
1717

18+
const _UTIME_OMIT = 0
19+
1820
// file is the real representation of *File.
1921
// The extra level of indirection ensures that no clients of os
2022
// can overwrite this data, which could cause the finalizer

src/os/os_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,6 +1386,128 @@ func TestChtimes(t *testing.T) {
13861386
testChtimes(t, f.Name())
13871387
}
13881388

1389+
func TestChtimesWithZeroTimes(t *testing.T) {
1390+
file := newFile("chtimes-with-zero", t)
1391+
_, err := file.Write([]byte("hello, world\n"))
1392+
if err != nil {
1393+
t.Fatalf("Write: %s", err)
1394+
}
1395+
fName := file.Name()
1396+
defer Remove(file.Name())
1397+
err = file.Close()
1398+
if err != nil {
1399+
t.Errorf("%v", err)
1400+
}
1401+
fs, err := Stat(fName)
1402+
if err != nil {
1403+
t.Fatal(err)
1404+
}
1405+
startAtime := Atime(fs)
1406+
startMtime := fs.ModTime()
1407+
switch runtime.GOOS {
1408+
case "js":
1409+
startAtime = startAtime.Truncate(time.Second)
1410+
startMtime = startMtime.Truncate(time.Second)
1411+
}
1412+
at0 := startAtime
1413+
mt0 := startMtime
1414+
t0 := startMtime.Truncate(time.Second).Add(1 * time.Hour)
1415+
1416+
tests := []struct {
1417+
aTime time.Time
1418+
mTime time.Time
1419+
wantATime time.Time
1420+
wantMTime time.Time
1421+
}{
1422+
{
1423+
aTime: time.Time{},
1424+
mTime: time.Time{},
1425+
wantATime: startAtime,
1426+
wantMTime: startMtime,
1427+
},
1428+
{
1429+
aTime: t0.Add(200 * time.Second),
1430+
mTime: time.Time{},
1431+
wantATime: t0.Add(200 * time.Second),
1432+
wantMTime: startMtime,
1433+
},
1434+
{
1435+
aTime: time.Time{},
1436+
mTime: t0.Add(100 * time.Second),
1437+
wantATime: t0.Add(200 * time.Second),
1438+
wantMTime: t0.Add(100 * time.Second),
1439+
},
1440+
{
1441+
aTime: t0.Add(300 * time.Second),
1442+
mTime: t0.Add(100 * time.Second),
1443+
wantATime: t0.Add(300 * time.Second),
1444+
wantMTime: t0.Add(100 * time.Second),
1445+
},
1446+
}
1447+
1448+
for _, tt := range tests {
1449+
// Now change the times accordingly.
1450+
if err := Chtimes(fName, tt.aTime, tt.mTime); err != nil {
1451+
t.Error(err)
1452+
}
1453+
1454+
// Finally verify the expectations.
1455+
fs, err = Stat(fName)
1456+
if err != nil {
1457+
t.Error(err)
1458+
}
1459+
at0 = Atime(fs)
1460+
mt0 = fs.ModTime()
1461+
1462+
if got, want := at0, tt.wantATime; !got.Equal(want) {
1463+
errormsg := fmt.Sprintf("AccessTime mismatch with values ATime:%q-MTime:%q\ngot: %q\nwant: %q", tt.aTime, tt.mTime, got, want)
1464+
switch runtime.GOOS {
1465+
case "plan9":
1466+
// Mtime is the time of the last change of
1467+
// content. Similarly, atime is set whenever
1468+
// the contents are accessed; also, it is set
1469+
// whenever mtime is set.
1470+
case "windows":
1471+
t.Error(errormsg)
1472+
default: // unix's
1473+
if got, want := at0, tt.wantATime; !got.Equal(want) {
1474+
mounts, err := ReadFile("/bin/mounts")
1475+
if err != nil {
1476+
mounts, err = ReadFile("/etc/mtab")
1477+
}
1478+
if strings.Contains(string(mounts), "noatime") {
1479+
t.Log(errormsg)
1480+
t.Log("A filesystem is mounted with noatime; ignoring.")
1481+
} else {
1482+
switch runtime.GOOS {
1483+
case "netbsd", "dragonfly":
1484+
// On a 64-bit implementation, birth time is generally supported and cannot be changed.
1485+
// When supported, atime update is restricted and depends on the file system and on the
1486+
// OS configuration.
1487+
if strings.Contains(runtime.GOARCH, "64") {
1488+
t.Log(errormsg)
1489+
t.Log("Filesystem might not support atime changes; ignoring.")
1490+
}
1491+
default:
1492+
t.Error(errormsg)
1493+
}
1494+
}
1495+
}
1496+
}
1497+
}
1498+
if got, want := mt0, tt.wantMTime; !got.Equal(want) {
1499+
errormsg := fmt.Sprintf("ModTime mismatch with values ATime:%q-MTime:%q\ngot: %q\nwant: %q", tt.aTime, tt.mTime, got, want)
1500+
switch runtime.GOOS {
1501+
case "dragonfly":
1502+
t.Log(errormsg)
1503+
t.Log("Mtime is always updated; ignoring.")
1504+
default:
1505+
t.Error(errormsg)
1506+
}
1507+
}
1508+
}
1509+
}
1510+
13891511
// Use TempDir (via newDir) to make sure we're on a local file system,
13901512
// so that timings are not distorted by latency and caching.
13911513
// On NFS, timings can be off due to caching of meta-data on

src/syscall/fs_js.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@ func Lchown(path string, uid, gid int) error {
273273
}
274274

275275
func UtimesNano(path string, ts []Timespec) error {
276+
// UTIME_OMIT value must match internal/syscall/unix/at_js.go
277+
const UTIME_OMIT = -0x2
276278
if err := checkPath(path); err != nil {
277279
return err
278280
}
@@ -281,6 +283,18 @@ func UtimesNano(path string, ts []Timespec) error {
281283
}
282284
atime := ts[0].Sec
283285
mtime := ts[1].Sec
286+
if atime == UTIME_OMIT || mtime == UTIME_OMIT {
287+
var st Stat_t
288+
if err := Stat(path, &st); err != nil {
289+
return err
290+
}
291+
if atime == UTIME_OMIT {
292+
atime = st.Atime
293+
}
294+
if mtime == UTIME_OMIT {
295+
mtime = st.Mtime
296+
}
297+
}
284298
_, err := fsCall("utimes", path, atime, mtime)
285299
return err
286300
}

src/syscall/fs_wasip1.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -663,17 +663,33 @@ func Lchown(path string, uid, gid int) error {
663663
}
664664

665665
func UtimesNano(path string, ts []Timespec) error {
666+
// UTIME_OMIT value must match internal/syscall/unix/at_wasip1.go
667+
const UTIME_OMIT = -0x2
666668
if path == "" {
667669
return EINVAL
668670
}
669671
dirFd, pathPtr, pathLen := preparePath(path)
672+
atime := TimespecToNsec(ts[0])
673+
mtime := TimespecToNsec(ts[1])
674+
if ts[0].Nsec == UTIME_OMIT || ts[1].Nsec == UTIME_OMIT {
675+
var st Stat_t
676+
if err := Stat(path, &st); err != nil {
677+
return err
678+
}
679+
if ts[0].Nsec == UTIME_OMIT {
680+
atime = int64(st.Atime)
681+
}
682+
if ts[1].Nsec == UTIME_OMIT {
683+
mtime = int64(st.Mtime)
684+
}
685+
}
670686
errno := path_filestat_set_times(
671687
dirFd,
672688
LOOKUP_SYMLINK_FOLLOW,
673689
pathPtr,
674690
pathLen,
675-
timestamp(TimespecToNsec(ts[0])),
676-
timestamp(TimespecToNsec(ts[1])),
691+
timestamp(atime),
692+
timestamp(mtime),
677693
FILESTAT_SET_ATIM|FILESTAT_SET_MTIM,
678694
)
679695
return errnoErr(errno)

src/syscall/syscall_windows.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -635,8 +635,14 @@ func Utimes(path string, tv []Timeval) (err error) {
635635
return e
636636
}
637637
defer Close(h)
638-
a := NsecToFiletime(tv[0].Nanoseconds())
639-
w := NsecToFiletime(tv[1].Nanoseconds())
638+
a := Filetime{}
639+
w := Filetime{}
640+
if tv[0].Nanoseconds() != 0 {
641+
a = NsecToFiletime(tv[0].Nanoseconds())
642+
}
643+
if tv[0].Nanoseconds() != 0 {
644+
w = NsecToFiletime(tv[1].Nanoseconds())
645+
}
640646
return SetFileTime(h, nil, &a, &w)
641647
}
642648

@@ -655,8 +661,14 @@ func UtimesNano(path string, ts []Timespec) (err error) {
655661
return e
656662
}
657663
defer Close(h)
658-
a := NsecToFiletime(TimespecToNsec(ts[0]))
659-
w := NsecToFiletime(TimespecToNsec(ts[1]))
664+
a := Filetime{}
665+
w := Filetime{}
666+
if TimespecToNsec(ts[0]) != 0 {
667+
a = NsecToFiletime(TimespecToNsec(ts[0]))
668+
}
669+
if TimespecToNsec(ts[1]) != 0 {
670+
w = NsecToFiletime(TimespecToNsec(ts[1]))
671+
}
660672
return SetFileTime(h, nil, &a, &w)
661673
}
662674

0 commit comments

Comments
 (0)