feat(filer): add atime to FuseAttributes + TouchAccessTime RPC (#9556)

* feat(filer): add atime field and TouchAccessTime RPC to filer proto

Introduce POSIX-style access-time tracking on the filer:
- FuseAttributes gains atime (field 22) and atime_ns (field 23).
- New TouchAccessTime RPC (and Touch{Access,Time}{Request,Response})
  lets read paths bump atime without going through UpdateEntry's
  chunk-rewrite/EqualEntry short-circuit.

Additive proto changes only; zero atime is treated as unset and
existing clients are unaffected. Java client proto is kept in lock
step.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(filer): wire Atime through Attr codec with mtime fallback

Add Attr.Atime and round-trip it through EntryAttributeToPb /
EntryAttributeToExistingPb / PbToEntryAttribute. A zero proto atime
decodes as Mtime, so legacy entries report a sensible value and
freshly-created/updated entries default Atime to Mtime when callers
do not set it explicitly.

CreateEntry and UpdateEntry stamp Atime = Mtime (or Crtime) when it
is zero. TouchAccessTime later bypasses this path to write atime
alone via Store.UpdateEntry.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(filer): preserve atime in first epoch second on decode

The Atime decode branch previously treated any attr.Atime == 0 as
unset and overwrote it with Mtime, which drops valid timestamps in
the first second of the unix epoch where attr.Atime is 0 but
attr.AtimeNs > 0. Check both fields so we only fall back to Mtime
when both are zero.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Peter DoddandCursor authored and GitHub committed 2026-05-19 10:22:17 -07:00
1 parent b63610cf8f
commit 4476cb282b
8 files changed
+683 -370

No files matched your search

+23
View File
@@ -85,6 +85,8 @@ func EntryAttributeToPb(entry *Entry) *filer_pb.FuseAttributes {
MtimeNs: int32(entry.Attr.Mtime.Nanosecond()),
Ctime: entry.Attr.Ctime.Unix(),
CtimeNs: int32(entry.Attr.Ctime.Nanosecond()),
Atime: atimeSecondsForPb(entry.Attr),
AtimeNs: atimeNanosForPb(entry.Attr),
FileMode: uint32(entry.Attr.Mode),
Uid: entry.Uid,
Gid: entry.Gid,
@@ -100,6 +102,20 @@ func EntryAttributeToPb(entry *Entry) *filer_pb.FuseAttributes {
}
}
func atimeSecondsForPb(attr Attr) int64 {
if attr.Atime.IsZero() {
return 0
}
return attr.Atime.Unix()
}
func atimeNanosForPb(attr Attr) int32 {
if attr.Atime.IsZero() {
return 0
}
return int32(attr.Atime.Nanosecond())
}
// EntryAttributeToExistingPb fills an existing FuseAttributes to avoid allocation.
// Safe to call with nil attr (will return early without populating).
func EntryAttributeToExistingPb(entry *Entry, attr *filer_pb.FuseAttributes) {
@@ -111,6 +127,8 @@ func EntryAttributeToExistingPb(entry *Entry, attr *filer_pb.FuseAttributes) {
attr.MtimeNs = int32(entry.Attr.Mtime.Nanosecond())
attr.Ctime = entry.Attr.Ctime.Unix()
attr.CtimeNs = int32(entry.Attr.Ctime.Nanosecond())
attr.Atime = atimeSecondsForPb(entry.Attr)
attr.AtimeNs = atimeNanosForPb(entry.Attr)
attr.FileMode = uint32(entry.Attr.Mode)
attr.Uid = entry.Uid
attr.Gid = entry.Gid
@@ -140,6 +158,11 @@ func PbToEntryAttribute(attr *filer_pb.FuseAttributes) Attr {
} else {
t.Ctime = t.Mtime
}
if attr.Atime != 0 || attr.AtimeNs != 0 {
t.Atime = time.Unix(attr.Atime, int64(attr.AtimeNs))
} else {
t.Atime = t.Mtime
}
t.Mode = os.FileMode(attr.FileMode)
t.Uid = attr.Uid
t.Gid = attr.Gid