]> git.ipfire.org Git - thirdparty/rsync.git/commitdiff
runtests: write valgrind logs to a world-writable subdir
authorAndrew Tridgell <andrew@tridgell.net>
Sat, 13 Jun 2026 22:02:20 +0000 (08:02 +1000)
committerAndrew Tridgell <andrew@tridgell.net>
Sat, 13 Jun 2026 22:33:03 +0000 (08:33 +1000)
Under --valgrind some tests run rsync with reduced privileges: partial_nowrite
wraps it in "setpriv --inh-caps -all --bounding-set -all" to force EACCES, and
chdir-symlink-race's daemon drops to the module's uid.  Such a child cannot
create valgrind's --log-file in a root-owned scratchbase, so valgrind aborts at
startup and the test fails (seen only in the root + --use-tcp cell).

Put the logs in a 1777 valgrind-logs/ subdir so a privilege-dropped child can
always write them.  Scan and cleanup are unchanged; the logs just move one
directory down.

runtests.py

index 2c7a7f9b5298a557bfa3b1d6cf5a7c51b672a6c5..809276e30eb9ee96a567e45cb66bec0ac2adbc36 100755 (executable)
@@ -266,7 +266,14 @@ def build_rsync_cmd(rsync_bin, args, scratchbase):
     """Build the RSYNC command string for tests."""
     parts = []
     if args.valgrind:
-        vlog = os.path.join(scratchbase, 'valgrind.%p.log')
+        # Logs go in a world-writable+sticky subdir so that rsync children
+        # which drop privileges (the setpriv cap-drop in partial_nowrite, a
+        # daemon dropping to the module's uid) can still create their log file
+        # even when scratchbase itself is root-owned.
+        vgdir = os.path.join(scratchbase, 'valgrind-logs')
+        os.makedirs(vgdir, exist_ok=True)
+        os.chmod(vgdir, 0o1777)
+        vlog = os.path.join(vgdir, 'valgrind.%p.log')
         vopts = f'--log-file={vlog}'
         supp = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                             'testsuite', 'valgrind.supp')
@@ -454,7 +461,7 @@ def main():
     print(f'    os={subprocess.check_output(["uname", "-a"], text=True).strip()}')
     print(f'    preserve_scratch={"yes" if args.preserve_scratch else "no"}')
     if args.valgrind:
-        print(f'    valgrind=enabled (logs in valgrind.*.log)')
+        print(f'    valgrind=enabled (logs in valgrind-logs/valgrind.*.log)')
     if args.parallel > 1:
         print(f'    parallel={args.parallel}')
     print(f'    daemon_transport={"tcp (loopback)" if args.use_tcp else "pipe (secure default)"}')
@@ -620,7 +627,7 @@ def main():
     # Check valgrind logs for errors
     vg_errors = 0
     if args.valgrind:
-        for vlog in sorted(glob.glob(os.path.join(scratchbase, 'valgrind.*.log'))):
+        for vlog in sorted(glob.glob(os.path.join(scratchbase, 'valgrind-logs', 'valgrind.*.log'))):
             try:
                 with open(vlog) as f:
                     content = f.read()
@@ -644,7 +651,7 @@ def main():
     if skipped > 0:
         print(f'      {skipped} skipped')
     if vg_errors > 0:
-        print(f'      {vg_errors} valgrind error(s) found (see logs in {scratchbase})')
+        print(f'      {vg_errors} valgrind error(s) found (see logs in {os.path.join(scratchbase, "valgrind-logs")})')
 
     if expect is not None:
         # Version-mixing mode: the run is judged purely on whether each test's