| author | Alberto Bertogli
<albertito@blitiri.com.ar> 2026-03-18 01:06:35 UTC |
| committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2026-03-18 01:23:13 UTC |
| parent | a71d138e4ae92fa508a4e3f6d641a62f0c6304ec |
| test/onefilesystem.t | +15 | -0 |
| test/onefilesystem.tt | +40 | -0 |
| walk.go | +13 | -5 |
diff --git a/test/onefilesystem.t b/test/onefilesystem.t new file mode 100644 index 0000000..146cd53 --- /dev/null +++ b/test/onefilesystem.t @@ -0,0 +1,15 @@ + +Tests for -x (one filesystem) flag. + +We use unshare to create a user+mount namespace, mount a tmpfs inside the test +tree, and verify that -x prevents crossing the boundary. + +Skip if we can't create user+mount namespaces (e.g. CI environments). + + $ unshare --user --mount --map-root-user true 2>/dev/null || exit 80 + +Run the rest of the test inside the namespace, so we can mount a tmpfs. + + $ unshare --user --mount --map-root-user cram3 $TESTDIR/onefilesystem.tt + . + # Ran 1 tests, 0 skipped, 0 failed. diff --git a/test/onefilesystem.tt b/test/onefilesystem.tt new file mode 100644 index 0000000..e5270cc --- /dev/null +++ b/test/onefilesystem.tt @@ -0,0 +1,40 @@ + +Note this runs inside unshare so we can mount things. + +Prepare the directories, files and mounts. + + $ mkdir -p root/mnt root/dir + $ echo aaa > root/file1 + $ echo bbb > root/dir/file2 + $ mount -t tmpfs none root/mnt + $ echo ccc > root/mnt/file3 + $ mkdir root/mnt/subdir + $ echo ddd > root/mnt/subdir/file4 + +Generate with -x: only root device files should get checksums. + + $ alias summer="$TESTDIR/../summer" + $ summer -v --parallel=1 -x generate root + "root/dir/file2": writing checksum \(checksum:\w+, mtime:\d+\) (re) + "root/file1": writing checksum \(checksum:\w+, mtime:\d+\) (re) + 0s: 0 matched, 0 modified, 2 new, 0 corrupted + +Verify with -x: should see only root device files. + + $ summer -v --parallel=1 -x verify root + "root/dir/file2": match \(checksum:\w+, mtime:\d+\) (re) + "root/file1": match \(checksum:\w+, mtime:\d+\) (re) + 0s: 2 matched, 0 modified, 0 new, 0 corrupted + +Verify without -x: root device files match, mount files are missing. + + $ summer -v --parallel=1 verify root + "root/dir/file2": match \(checksum:\w+, mtime:\d+\) (re) + "root/file1": match \(checksum:\w+, mtime:\d+\) (re) + "root/mnt/file3": missing checksum attribute + "root/mnt/subdir/file4": missing checksum attribute + 0s: 2 matched, 0 modified, 2 new, 0 corrupted + +Unmount so the test directory can be gracefully cleaned up. + + $ umount root/mnt diff --git a/walk.go b/walk.go index f8f0383..d41b829 100644 --- a/walk.go +++ b/walk.go @@ -22,6 +22,19 @@ func openAndInfo(path string, d fs.DirEntry, err error, rootDev deviceID) (bool, if err != nil { return false, nil, nil, err } + + // Check filesystem boundaries for directories, so we skip entire + // subtrees on other filesystems without descending into them. + if options.oneFilesystem && d.IsDir() { + info, err := d.Info() + if err != nil { + return false, nil, nil, err + } + if rootDev != getDevice(info) { + return false, nil, nil, fs.SkipDir + } + } + if d.IsDir() || !d.Type().IsRegular() { return false, nil, nil, nil } @@ -45,11 +58,6 @@ func openAndInfo(path string, d fs.DirEntry, err error, rootDev deviceID) (bool, return true, nil, nil, err } - if options.oneFilesystem && rootDev != getDevice(info) { - fd.Close() - return false, nil, nil, fs.SkipDir - } - return true, fd, info, nil }