From bc54aa9fe40d6d1ffa6f80a1e04a18689ddbc54c Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Fri, 9 Jan 2026 14:39:39 +0100 Subject: pkg/osutil: fix CreationTime We return Ctime from CreationTime. But "C" does not stand for "creation", it stands for "status change" (inode update). It may or may not be the creation time. Use Btime (birth time) for creation time. Fixes #6547 --- pkg/osutil/osutil.go | 7 +++---- pkg/osutil/osutil_linux.go | 17 ++++++++++++++--- pkg/osutil/osutil_nonlinux.go | 10 ++++++++-- 3 files changed, 25 insertions(+), 9 deletions(-) (limited to 'pkg/osutil') diff --git a/pkg/osutil/osutil.go b/pkg/osutil/osutil.go index fa963a3fb..d56682732 100644 --- a/pkg/osutil/osutil.go +++ b/pkg/osutil/osutil.go @@ -351,10 +351,9 @@ func Abs(path string) string { return filepath.Clean(path) } -// CreationTime returns file creation time. -// May return zero time, if not known. -func CreationTime(fi os.FileInfo) time.Time { - return creationTime(fi) +// FileTimes returns file creation and modification times. +func FileTimes(file string) (time.Time, time.Time, error) { + return fileTimes(file) } // MonotonicNano returns monotonic time in nanoseconds from some unspecified point in time. diff --git a/pkg/osutil/osutil_linux.go b/pkg/osutil/osutil_linux.go index 50be9b047..5ab639280 100644 --- a/pkg/osutil/osutil_linux.go +++ b/pkg/osutil/osutil_linux.go @@ -18,9 +18,20 @@ import ( "golang.org/x/sys/unix" ) -func creationTime(fi os.FileInfo) time.Time { - st := fi.Sys().(*syscall.Stat_t) - return time.Unix(int64(st.Ctim.Sec), int64(st.Ctim.Nsec)) // nolint: unconvert +func fileTimes(file string) (time.Time, time.Time, error) { + // Btime stands for "birth" time, which is creation time. + var statx unix.Statx_t + err := unix.Statx(unix.AT_FDCWD, file, unix.AT_SYMLINK_NOFOLLOW, unix.STATX_BTIME|unix.STATX_MTIME, &statx) + if err != nil { + return time.Time{}, time.Time{}, err + } + modTime := time.Unix(statx.Mtime.Sec, int64(statx.Mtime.Nsec)) + // Some filesystems may not store the birth time. + creationTime := modTime + if statx.Mask&unix.STATX_BTIME != 0 { + creationTime = time.Unix(statx.Btime.Sec, int64(statx.Btime.Nsec)) + } + return creationTime, modTime, nil } // RemoveAll is similar to os.RemoveAll, but can handle more cases. diff --git a/pkg/osutil/osutil_nonlinux.go b/pkg/osutil/osutil_nonlinux.go index 1d3ee8b82..bc696d3fc 100644 --- a/pkg/osutil/osutil_nonlinux.go +++ b/pkg/osutil/osutil_nonlinux.go @@ -12,8 +12,14 @@ import ( "time" ) -func creationTime(fi os.FileInfo) time.Time { - return time.Time{} +func fileTimes(file string) (time.Time, time.Time, error) { + stat, err := os.Stat(file) + if err != nil { + return time.Time{}, time.Time{}, err + } + // Creation time is not present in stat, so we use modification time for both. + modTime := stat.ModTime() + return modTime, modTime, nil } func RemoveAll(dir string) error { -- cgit mrf-deployment