fix: populate ctime from platform-specific syscall data
All checks were successful
check / check (pull_request) Successful in 4m19s
All checks were successful
check / check (pull_request) Successful in 4m19s
The scanner was setting CTime to info.ModTime() as a placeholder since afero's FileInfo interface doesn't expose ctime directly. This change extracts the actual ctime from the underlying syscall.Stat_t via platform-specific build files: - macOS (Darwin): uses Birthtimespec (file creation/birth time) - Linux: uses Ctim (inode change time) - Other platforms: falls back to mtime Also adds: - Documentation of ctime semantics in README.md (new 'file metadata' section) - Platform differences table (macOS birth time vs Linux inode change time) - Note that ctime is recorded but not restored (not settable via standard APIs) - Updated README schema to match actual schema (adds ctime, source_path, link_target) - Doc comment on CTime field in database model closes #13
This commit is contained in:
23
internal/snapshot/ctime_darwin.go
Normal file
23
internal/snapshot/ctime_darwin.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// getCTime extracts the file creation time (birth time) from os.FileInfo.
|
||||
//
|
||||
// On macOS (Darwin), this returns the birth time (Birthtimespec) from the
|
||||
// underlying syscall.Stat_t. macOS HFS+ and APFS filesystems natively track
|
||||
// file creation time, making this a true "created at" timestamp.
|
||||
//
|
||||
// Falls back to modification time if the underlying Sys() data is not a
|
||||
// *syscall.Stat_t (e.g. when using in-memory filesystems for testing).
|
||||
func getCTime(info os.FileInfo) time.Time {
|
||||
stat, ok := info.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return info.ModTime()
|
||||
}
|
||||
return time.Unix(stat.Birthtimespec.Sec, stat.Birthtimespec.Nsec).UTC()
|
||||
}
|
||||
29
internal/snapshot/ctime_linux.go
Normal file
29
internal/snapshot/ctime_linux.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// getCTime extracts the inode change time (ctime) from os.FileInfo.
|
||||
//
|
||||
// On Linux, this returns the inode change time (Ctim) from the underlying
|
||||
// syscall.Stat_t. Linux ctime is updated whenever file metadata (permissions,
|
||||
// ownership, link count) or content changes. It is NOT the file creation
|
||||
// (birth) time.
|
||||
//
|
||||
// Note: Linux ext4 (kernel 4.11+) and btrfs do track birth time via the
|
||||
// statx() syscall, but this is not exposed through Go's os.FileInfo.Sys().
|
||||
// The inode change time is the best available approximation through standard
|
||||
// Go APIs.
|
||||
//
|
||||
// Falls back to modification time if the underlying Sys() data is not a
|
||||
// *syscall.Stat_t (e.g. when using in-memory filesystems for testing).
|
||||
func getCTime(info os.FileInfo) time.Time {
|
||||
stat, ok := info.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return info.ModTime()
|
||||
}
|
||||
return time.Unix(stat.Ctim.Sec, stat.Ctim.Nsec).UTC()
|
||||
}
|
||||
15
internal/snapshot/ctime_other.go
Normal file
15
internal/snapshot/ctime_other.go
Normal file
@@ -0,0 +1,15 @@
|
||||
//go:build !darwin && !linux
|
||||
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// getCTime returns the file's modification time as a fallback on unsupported
|
||||
// platforms. See ctime_darwin.go and ctime_linux.go for platform-specific
|
||||
// implementations that extract actual ctime/birth time from syscall data.
|
||||
func getCTime(info os.FileInfo) time.Time {
|
||||
return info.ModTime()
|
||||
}
|
||||
@@ -728,7 +728,7 @@ func (s *Scanner) checkFileInMemory(path string, info os.FileInfo, knownFiles ma
|
||||
Path: types.FilePath(path),
|
||||
SourcePath: types.SourcePath(s.currentSourcePath), // Store source directory for restore path stripping
|
||||
MTime: info.ModTime(),
|
||||
CTime: info.ModTime(), // afero doesn't provide ctime
|
||||
CTime: getCTime(info),
|
||||
Size: info.Size(),
|
||||
Mode: uint32(info.Mode()),
|
||||
UID: uid,
|
||||
|
||||
Reference in New Issue
Block a user