diff mbox series

[v3] sstate: fix access to a cached object rewriting its modification time

Message ID 20260911070121.39436-1-michael.haener@siemens.com
State Under Review
Headers show
Series [v3] sstate: fix access to a cached object rewriting its modification time | expand

Commit Message

Michael Haener Sept. 11, 2026, 7:01 a.m. UTC
Marking an object in the sstate cache as in use is done with a plain
touch, which writes the modification time along with the access time.
Every build that merely reads an object therefore makes it look newly
created.

scripts/sstate-cache-management.py --remove-duplicated keeps the entry
with the newest modification time. As every use rewrites it, building an
older configuration last keeps its object and removes the newer one.

Refresh only the access time and leave the modification time alone. The
modification time then tells when an object was created, the access time
when it was last used.

Signed-off-by: Michael Haener <michael.haener@siemens.com>
Reviewed-by: Adrian Freihofer <adrian.freihofer@siemens.com>
Reviewed-by: Peter Marko <peter.marko@siemens.com>
---
v3 (after review by Zhan Xusheng):
- restored the original error handling: PermissionError and EROFS are
  ignored, everything else propagates as before
- use os.utime(ns=...) to round trip the modification time exactly
- replaced the vague motivation with the concrete effect on
  sstate-cache-management.py --remove-duplicated

v2:
- dropped SSTATE_ATIME_UPDATE_AFTER, refreshing only the access time is now
  unconditional
- renamed the helper to sstate_touch_atime()
- reduced the scope to the timestamp fix

 meta/classes-global/sstate.bbclass | 39 ++++++++++++++++++------------
 1 file changed, 24 insertions(+), 15 deletions(-)
diff mbox series

Patch

diff --git a/meta/classes-global/sstate.bbclass b/meta/classes-global/sstate.bbclass
index b2fa93650a..76544eb954 100644
--- a/meta/classes-global/sstate.bbclass
+++ b/meta/classes-global/sstate.bbclass
@@ -699,15 +699,7 @@  def sstate_package(ss, d):
     if not os.path.exists(siginfo):
         bb.siggen.dump_this_task(siginfo, d)
     else:
-        try:
-            os.utime(siginfo, None)
-        except PermissionError:
-            pass
-        except OSError as e:
-            # Handle read-only file systems gracefully
-            import errno
-            if e.errno != errno.EROFS:
-                raise e
+        sstate_touch_atime(siginfo)
 
     return
 
@@ -802,7 +794,10 @@  python sstate_create_and_sign_package () {
     # Best effort touch
     def touch(file):
         try:
-            file.touch()
+            if file.exists():
+                sstate_touch_atime(file)
+            else:
+                file.touch()
         except:
             pass
 
@@ -936,13 +931,27 @@  sstate_unpack_package () {
 	# Update both any file and any symlink pointing to the file for sigs as well as the file
 	for file in ${SSTATE_PKG} ${SSTATE_PKG}.sig ${SSTATE_PKG}.siginfo
 	do
-		[ ! -e $file ] || touch $file 2>/dev/null || true
-		[ ! -e $file ] || touch --no-dereference $file 2>/dev/null || true
+		[ ! -e $file ] || touch -a $file 2>/dev/null || true
+		[ ! -e $file ] || touch -a --no-dereference $file 2>/dev/null || true
 	done
 }
 
 BB_HASHCHECK_FUNCTION = "sstate_checkhashes"
 
+def sstate_touch_atime(path):
+    import errno
+    import time
+    try:
+        stat_info = os.stat(path)
+        # nanoseconds avoid shifting the modification time through float rounding
+        os.utime(path, ns=(time.time_ns(), stat_info.st_mtime_ns))
+    except PermissionError:
+        pass
+    except OSError as e:
+        # Handle read-only file systems gracefully
+        if e.errno != errno.EROFS:
+            raise e
+
 def sstate_checkhashes(sq_data, d, siginfo=False, currentcount=0, summary=True, **kwargs):
     import itertools
 
@@ -978,10 +987,10 @@  def sstate_checkhashes(sq_data, d, siginfo=False, currentcount=0, summary=True,
         sstatefile = d.expand("${SSTATE_DIR}/" + getsstatefile(tid, siginfo, d))
 
         if os.path.exists(sstatefile):
-            oe.utils.touch(sstatefile)
+            sstate_touch_atime(sstatefile)
             for ext in ['.sig', '.siginfo']:
                 if os.path.exists(sstatefile + ext):
-                    oe.utils.touch(sstatefile + ext)
+                    sstate_touch_atime(sstatefile + ext)
             found.add(tid)
             bb.debug(2, "SState: Found valid sstate file %s" % sstatefile)
         else:
@@ -1223,7 +1232,7 @@  python sstate_eventhandler() {
         if not os.path.exists(siginfo):
             bb.siggen.dump_this_task(siginfo, d)
         else:
-            oe.utils.touch(siginfo)
+            sstate_touch_atime(siginfo)
 }
 
 SSTATE_PRUNE_OBSOLETEWORKDIR ?= "1"