From 902f59ecb9fc4fd8c0874b85c50d209398bcdd17 Mon Sep 17 00:00:00 2001 From: Barret Rhoden Date: Thu, 12 Nov 2026 20:41:42 +0000 Subject: [PATCH] os: implement Chtimes on Linux Implement os.Chtimes on Linux using syscall.UtimesNano, preserving timestamps with UTIME_OMIT when a zero time.Time is passed. --- src/os/chtimes_linux.go | 29 +++++++++++++++++++++++++ src/os/chtimes_linux_test.go | 42 ++++++++++++++++++++++++++++++++++++ src/os/chtimes_other.go | 10 +++++++++ src/os/file_posix.go | 5 ----- 4 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 src/os/chtimes_linux.go create mode 100644 src/os/chtimes_linux_test.go create mode 100644 src/os/chtimes_other.go diff --git a/src/os/chtimes_linux.go b/src/os/chtimes_linux.go new file mode 100644 index 0000000000..afb0c38fa0 --- /dev/null +++ b/src/os/chtimes_linux.go @@ -0,0 +1,29 @@ +//go:build !baremetal && !nintendoswitch && !wasip1 && !wasm_unknown && !wasip2 + +package os + +import ( + "syscall" + "time" +) + +// See https://github.com/torvalds/linux/blob/master/include/linux/stat.h +const _UTIME_OMIT = (1 << 30) - 2 + +// Chtimes changes the access and modification times of the named file. +func Chtimes(name string, atime time.Time, mtime time.Time) error { + var utimes [2]syscall.Timespec + 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} + } + return nil +} diff --git a/src/os/chtimes_linux_test.go b/src/os/chtimes_linux_test.go new file mode 100644 index 0000000000..72cc8b974c --- /dev/null +++ b/src/os/chtimes_linux_test.go @@ -0,0 +1,42 @@ +//go:build !baremetal && !nintendoswitch && !wasip1 && !wasm_unknown && !wasip2 + +package os_test + +import ( + "os" + "path/filepath" + "testing" + "time" +) + +func TestChtimes(t *testing.T) { + path := filepath.Join(t.TempDir(), "file.txt") + if err := os.WriteFile(path, []byte("hello"), 0o600); err != nil { + t.Fatal(err) + } + + wantMtime := time.Date(2025, 3, 15, 12, 30, 45, 0, time.UTC) + if err := os.Chtimes(path, wantMtime, wantMtime); err != nil { + t.Fatalf("Chtimes failed: %v", err) + } + + fi, err := os.Stat(path) + if err != nil { + t.Fatal(err) + } + if !fi.ModTime().Equal(wantMtime) { + t.Errorf("ModTime() = %v, want %v", fi.ModTime(), wantMtime) + } + + // Zero time.Time should leave the modification time unchanged. + if err := os.Chtimes(path, time.Time{}, time.Time{}); err != nil { + t.Fatalf("Chtimes with zero times failed: %v", err) + } + fi, err = os.Stat(path) + if err != nil { + t.Fatal(err) + } + if !fi.ModTime().Equal(wantMtime) { + t.Errorf("ModTime() after zero Chtimes = %v, want %v", fi.ModTime(), wantMtime) + } +} diff --git a/src/os/chtimes_other.go b/src/os/chtimes_other.go new file mode 100644 index 0000000000..6b5a226bf2 --- /dev/null +++ b/src/os/chtimes_other.go @@ -0,0 +1,10 @@ +//go:build !linux || baremetal || nintendoswitch || wasip1 || wasm_unknown || wasip2 + +package os + +import "time" + +// Chtimes is a stub on non-Linux targets. +func Chtimes(name string, atime time.Time, mtime time.Time) error { + return ErrNotImplemented +} diff --git a/src/os/file_posix.go b/src/os/file_posix.go index a10ff49196..ca6004847b 100644 --- a/src/os/file_posix.go +++ b/src/os/file_posix.go @@ -6,11 +6,6 @@ import ( //TODO: re-implement the ErrNoDeadline error in the correct code path -// Chtimes is a stub, not yet implemented -func Chtimes(name string, atime time.Time, mtime time.Time) error { - return ErrNotImplemented -} - // setDeadline sets the read and write deadline. func (f *File) setDeadline(t time.Time) error { if t.IsZero() {