From e16ef256c003422000b60298649c0649c90f69c0 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Mon, 26 Oct 2026 12:15:34 -0700 Subject: [PATCH 1/3] mountinfo: add TestParentsFilter ParentsFilter is only tested against the fedoraMountinfo fixture. Add a table-driven unit test for it, similar to TestPrefixFilter. Signed-off-by: Kir Kolyshkin --- mountinfo/mountinfo_filters_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/mountinfo/mountinfo_filters_test.go b/mountinfo/mountinfo_filters_test.go index cf5b35e0..b0d041d2 100644 --- a/mountinfo/mountinfo_filters_test.go +++ b/mountinfo/mountinfo_filters_test.go @@ -29,3 +29,30 @@ func TestPrefixFilter(t *testing.T) { } } } + +func TestParentsFilter(t *testing.T) { + tests := []struct { + path string + mountPoint string + shouldSkip bool + }{ + {path: "/a/b", mountPoint: "/", shouldSkip: false}, + {path: "/a/b", mountPoint: "/a", shouldSkip: false}, + {path: "/a/b", mountPoint: "/a/b", shouldSkip: false}, + {path: "/a/b", mountPoint: "/a/b/c", shouldSkip: true}, + {path: "/a/b", mountPoint: "/b", shouldSkip: true}, + {path: "/", mountPoint: "/", shouldSkip: false}, + {path: "/", mountPoint: "/a", shouldSkip: true}, + } + for _, tc := range tests { + filter := ParentsFilter(tc.path) + skip, _ := filter(&Info{Mountpoint: tc.mountPoint}) + if skip != tc.shouldSkip { + if tc.shouldSkip { + t.Errorf("path %q: expected %q to be skipped", tc.path, tc.mountPoint) + } else { + t.Errorf("path %q: expected %q not to be skipped", tc.path, tc.mountPoint) + } + } + } +} From 894e3552eb85c4a4b4264a78cc29fed80d77607e Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Mon, 26 Oct 2026 12:15:46 -0700 Subject: [PATCH 2/3] mountinfo: fix ParentsFilter() being too greedy ParentsFilter does a plain string prefix match, so, for example, a mount point of "/a" is considered to be a parent of "/aa". Fix it to match on path components instead, similar to what commit f11a7ca did for PrefixFilter. The root mount point has to be special-cased, as it already ends with a slash. Document the requirements for the path argument, same as for PrefixFilter. Signed-off-by: Kir Kolyshkin --- mountinfo/mountinfo_filters.go | 8 ++++++-- mountinfo/mountinfo_filters_test.go | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mountinfo/mountinfo_filters.go b/mountinfo/mountinfo_filters.go index 16079c3c..0c2b3a30 100644 --- a/mountinfo/mountinfo_filters.go +++ b/mountinfo/mountinfo_filters.go @@ -40,12 +40,16 @@ func SingleEntryFilter(mp string) FilterFunc { // ParentsFilter returns all entries whose mount points // can be parents of a path specified, discarding others. +// The path must be absolute, have all symlinks resolved, +// and cleaned (i.e. no extra slashes or dots). // // For example, given /var/lib/docker/something, entries -// like /var/lib/docker, /var and / are returned. +// like /var/lib/docker, /var and / are returned, while +// /var/lib/dock is discarded. func ParentsFilter(path string) FilterFunc { return func(m *Info) (bool, bool) { - skip := !strings.HasPrefix(path, m.Mountpoint) + mp := m.Mountpoint + skip := mp != "/" && mp != path && !strings.HasPrefix(path, mp+"/") return skip, false } } diff --git a/mountinfo/mountinfo_filters_test.go b/mountinfo/mountinfo_filters_test.go index b0d041d2..545ef499 100644 --- a/mountinfo/mountinfo_filters_test.go +++ b/mountinfo/mountinfo_filters_test.go @@ -41,6 +41,8 @@ func TestParentsFilter(t *testing.T) { {path: "/a/b", mountPoint: "/a/b", shouldSkip: false}, {path: "/a/b", mountPoint: "/a/b/c", shouldSkip: true}, {path: "/a/b", mountPoint: "/b", shouldSkip: true}, + {path: "/aa", mountPoint: "/a", shouldSkip: true}, + {path: "/a/bb", mountPoint: "/a/b", shouldSkip: true}, {path: "/", mountPoint: "/", shouldSkip: false}, {path: "/", mountPoint: "/a", shouldSkip: true}, } From 44e556e49c80ae1683226ba58909ab342f2bad60 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Mon, 26 Oct 2026 12:15:53 -0700 Subject: [PATCH 3/3] mountinfo: fix PrefixFilter("/") PrefixFilter appends a slash to both the prefix and the mount point before comparing them. For the root prefix, this results in "//", which only matches the root mount itself, so PrefixFilter("/") returns a single entry rather than all of them. Since every mount point is under "/", special-case it. Signed-off-by: Kir Kolyshkin --- mountinfo/mountinfo_filters.go | 2 +- mountinfo/mountinfo_filters_test.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/mountinfo/mountinfo_filters.go b/mountinfo/mountinfo_filters.go index 0c2b3a30..037f47ba 100644 --- a/mountinfo/mountinfo_filters.go +++ b/mountinfo/mountinfo_filters.go @@ -23,7 +23,7 @@ type FilterFunc func(*Info) (skip, stop bool) // "/foo" and "/foo/bar", and discards "/foobar". func PrefixFilter(prefix string) FilterFunc { return func(m *Info) (bool, bool) { - skip := !strings.HasPrefix(m.Mountpoint+"/", prefix+"/") + skip := prefix != "/" && !strings.HasPrefix(m.Mountpoint+"/", prefix+"/") return skip, false } } diff --git a/mountinfo/mountinfo_filters_test.go b/mountinfo/mountinfo_filters_test.go index 545ef499..ade53ed1 100644 --- a/mountinfo/mountinfo_filters_test.go +++ b/mountinfo/mountinfo_filters_test.go @@ -12,6 +12,9 @@ func TestPrefixFilter(t *testing.T) { {prefix: "/a", mountPoint: "/a/b", shouldSkip: false}, {prefix: "/a", mountPoint: "/aa", shouldSkip: true}, {prefix: "/a", mountPoint: "/aa/b", shouldSkip: true}, + {prefix: "/", mountPoint: "/", shouldSkip: false}, + {prefix: "/", mountPoint: "/a", shouldSkip: false}, + {prefix: "/", mountPoint: "/a/b", shouldSkip: false}, // invalid prefix: prefix path must be cleaned and have no trailing slash {prefix: "/a/", mountPoint: "/a", shouldSkip: true},