Make scan paths required operands; add -x/--one-file-system
All checks were successful
check / check (push) Successful in 4s

scan now takes one or more PATH operands (directories or regular
files) via cobra flags instead of the -root flag with its /srv
default; invoking scan with no operand is a usage error and a
nonexistent operand is fatal. Filesystem boundaries are crossed by
default; the new -x/--one-file-system flag (GNU du/rsync convention)
stops the walk at each operand's filesystem, implemented by comparing
lstat device IDs with build-tagged helpers for darwin's int32 Dev.
Verified against a real mounted disk image: default crosses, -x does
not, --one-file-system is identical to -x.
This commit is contained in:
2026-07-23 08:55:17 +07:00
parent 3391207f8d
commit 4b1c3cbf70
7 changed files with 262 additions and 84 deletions

View File

@@ -129,7 +129,7 @@ func TestWalkPass(t *testing.T) {
t.Fatal(err)
}
paths, errs := walkPass(dir)
paths, errs := walkPass([]string{dir}, false)
if errs != 0 {
t.Fatalf("errs = %d, want 0", errs)
}
@@ -141,6 +141,93 @@ func TestWalkPass(t *testing.T) {
}
}
func TestWalkPassMultipleRoots(t *testing.T) {
t.Parallel()
rootA := t.TempDir()
rootB := t.TempDir()
want := []string{
writeFile(t, rootA, "a1", []byte("1")),
writeFile(t, rootA, "sub/a2", []byte("2")),
writeFile(t, rootB, "b1", []byte("3")),
}
paths, errs := walkPass([]string{rootA, rootB}, false)
if errs != 0 {
t.Fatalf("errs = %d, want 0", errs)
}
// Operands are walked in the order given.
if !slices.Equal(paths, want) {
t.Fatalf("paths = %q, want %q", paths, want)
}
}
func TestWalkPassFileAndSymlinkOperands(t *testing.T) {
t.Parallel()
dir := t.TempDir()
f := writeFile(t, dir, "plain", []byte("data"))
link := filepath.Join(dir, "link")
err := os.Symlink(f, link)
if err != nil {
t.Fatal(err)
}
// A regular-file operand is emitted as itself.
paths, errs := walkPass([]string{f}, false)
if errs != 0 || !slices.Equal(paths, []string{f}) {
t.Fatalf("file operand: paths = %q, errs = %d", paths, errs)
}
// A symlink operand is not followed and yields nothing.
paths, errs = walkPass([]string{link}, false)
if errs != 0 || len(paths) != 0 {
t.Fatalf("symlink operand: paths = %q, errs = %d", paths, errs)
}
}
func TestWalkPassOneFilesystemSameFS(t *testing.T) {
t.Parallel()
// Everything in one filesystem: -x must not skip anything.
dir := t.TempDir()
want := []string{
writeFile(t, dir, "a", []byte("a")),
writeFile(t, dir, "sub/deep/b", []byte("b")),
}
paths, errs := walkPass([]string{dir}, true)
if errs != 0 {
t.Fatalf("errs = %d, want 0", errs)
}
slices.Sort(paths)
if !slices.Equal(paths, want) {
t.Fatalf("paths = %q, want %q", paths, want)
}
}
func TestDeviceOf(t *testing.T) {
t.Parallel()
dir := t.TempDir()
dev1, ok1 := deviceOf(dir)
dev2, ok2 := deviceOf(dir)
if !ok1 || !ok2 || dev1 != dev2 {
t.Fatalf("deviceOf unstable: %d/%v vs %d/%v", dev1, ok1, dev2, ok2)
}
if _, ok := deviceOf(filepath.Join(dir, "missing")); ok {
t.Fatal("deviceOf reported ok for a missing path")
}
}
func TestStatPass(t *testing.T) {
t.Parallel()
@@ -234,7 +321,7 @@ func buildSmokeTree(t *testing.T) string {
func scanToRecords(t *testing.T, dir string) []scanRec {
t.Helper()
paths, walkErrs := walkPass(dir)
paths, walkErrs := walkPass([]string{dir}, false)
if walkErrs != 0 {
t.Fatalf("walk errors: %d", walkErrs)
}