mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
pkg/mount: implement/use filter for mountinfo parsing
Functions `GetMounts()` and `parseMountTable()` return all the entries as read and parsed from /proc/self/mountinfo. In many cases the caller is only interested only one or a few entries, not all of them. One good example is `Mounted()` function, which looks for a specific entry only. Another example is `RecursiveUnmount()` which is only interested in mount under a specific path. This commit adds `filter` argument to `GetMounts()` to implement two things: 1. filter out entries a caller is not interested in 2. stop processing if a caller is found what it wanted `nil` can be passed to get a backward-compatible behavior, i.e. return all the entries. A few filters are implemented: - `PrefixFilter`: filters out all entries not under `prefix` - `SingleEntryFilter`: looks for a specific entry Finally, `Mounted()` is modified to use `SingleEntryFilter()`, and `RecursiveUnmount()` is using `PrefixFilter()`. Unit tests are added to check filters are working. [v2: ditch NoFilter, use nil] [v3: ditch GetMountsFiltered()] [v4: add unit test for filters] [v5: switch to gotestyourself] Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
This commit is contained in:
parent
e396b27b7f
commit
bb934c6aca
14 changed files with 104 additions and 34 deletions
|
@ -74,7 +74,7 @@ func (daemon *Daemon) cleanupMounts() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
infos, err := mount.GetMounts()
|
infos, err := mount.GetMounts(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "error reading mount table for cleanup")
|
return errors.Wrap(err, "error reading mount table for cleanup")
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,7 +145,7 @@ func lookupZfsDataset(rootdir string) (string, error) {
|
||||||
}
|
}
|
||||||
wantedDev := stat.Dev
|
wantedDev := stat.Dev
|
||||||
|
|
||||||
mounts, err := mount.GetMounts()
|
mounts, err := mount.GetMounts(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
@ -398,7 +398,7 @@ func getSourceMount(source string) (string, string, error) {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
mountinfos, err := mount.GetMounts()
|
mountinfos, err := mount.GetMounts(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
|
@ -260,7 +260,7 @@ func (s *DockerDaemonSuite) TestPluginVolumeRemoveOnRestart(c *check.C) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func existsMountpointWithPrefix(mountpointPrefix string) (bool, error) {
|
func existsMountpointWithPrefix(mountpointPrefix string) (bool, error) {
|
||||||
mounts, err := mount.GetMounts()
|
mounts, err := mount.GetMounts(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,26 +9,48 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetMounts retrieves a list of mounts for the current running process.
|
// FilterFunc is a type defining a callback function
|
||||||
func GetMounts() ([]*Info, error) {
|
// to filter out unwanted entries. It takes a pointer
|
||||||
return parseMountTable()
|
// to an Info struct (not fully populated, currently
|
||||||
|
// only Mountpoint is filled in), and returns two booleans:
|
||||||
|
// - skip: true if the entry should be skipped
|
||||||
|
// - stop: true if parsing should be stopped after the entry
|
||||||
|
type FilterFunc func(*Info) (skip, stop bool)
|
||||||
|
|
||||||
|
// PrefixFilter discards all entries whose mount points
|
||||||
|
// do not start with a prefix specified
|
||||||
|
func PrefixFilter(prefix string) FilterFunc {
|
||||||
|
return func(m *Info) (bool, bool) {
|
||||||
|
skip := !strings.HasPrefix(m.Mountpoint, prefix)
|
||||||
|
return skip, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SingleEntryFilter looks for a specific entry
|
||||||
|
func SingleEntryFilter(mp string) FilterFunc {
|
||||||
|
return func(m *Info) (bool, bool) {
|
||||||
|
if m.Mountpoint == mp {
|
||||||
|
return false, true // don't skip, stop now
|
||||||
|
}
|
||||||
|
return true, false // skip, keep going
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMounts retrieves a list of mounts for the current running process,
|
||||||
|
// with an optional filter applied (use nil for no filter).
|
||||||
|
func GetMounts(f FilterFunc) ([]*Info, error) {
|
||||||
|
return parseMountTable(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mounted determines if a specified mountpoint has been mounted.
|
// Mounted determines if a specified mountpoint has been mounted.
|
||||||
// On Linux it looks at /proc/self/mountinfo.
|
// On Linux it looks at /proc/self/mountinfo.
|
||||||
func Mounted(mountpoint string) (bool, error) {
|
func Mounted(mountpoint string) (bool, error) {
|
||||||
entries, err := parseMountTable()
|
entries, err := GetMounts(SingleEntryFilter(mountpoint))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search the table for the mountpoint
|
return len(entries) > 0, nil
|
||||||
for _, e := range entries {
|
|
||||||
if e.Mountpoint == mountpoint {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mount will mount filesystem according to the specified configuration, on the
|
// Mount will mount filesystem according to the specified configuration, on the
|
||||||
|
@ -66,7 +88,7 @@ func Unmount(target string) error {
|
||||||
// RecursiveUnmount unmounts the target and all mounts underneath, starting with
|
// RecursiveUnmount unmounts the target and all mounts underneath, starting with
|
||||||
// the deepsest mount first.
|
// the deepsest mount first.
|
||||||
func RecursiveUnmount(target string) error {
|
func RecursiveUnmount(target string) error {
|
||||||
mounts, err := GetMounts()
|
mounts, err := parseMountTable(PrefixFilter(target))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -77,9 +99,6 @@ func RecursiveUnmount(target string) error {
|
||||||
})
|
})
|
||||||
|
|
||||||
for i, m := range mounts {
|
for i, m := range mounts {
|
||||||
if !strings.HasPrefix(m.Mountpoint, target) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
logrus.Debugf("Trying to unmount %s", m.Mountpoint)
|
logrus.Debugf("Trying to unmount %s", m.Mountpoint)
|
||||||
err = unmount(m.Mountpoint, mntDetach)
|
err = unmount(m.Mountpoint, mntDetach)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -129,7 +129,7 @@ func TestMountReadonly(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetMounts(t *testing.T) {
|
func TestGetMounts(t *testing.T) {
|
||||||
mounts, err := GetMounts()
|
mounts, err := GetMounts(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,7 +121,7 @@ func ensureUnmount(t *testing.T, mnt string) {
|
||||||
|
|
||||||
// validateMount checks that mnt has the given options
|
// validateMount checks that mnt has the given options
|
||||||
func validateMount(t *testing.T, mnt string, opts, optional, vfs string) {
|
func validateMount(t *testing.T, mnt string, opts, optional, vfs string) {
|
||||||
info, err := GetMounts()
|
info, err := GetMounts(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
|
|
||||||
// Parse /proc/self/mountinfo because comparing Dev and ino does not work from
|
// Parse /proc/self/mountinfo because comparing Dev and ino does not work from
|
||||||
// bind mounts.
|
// bind mounts.
|
||||||
func parseMountTable() ([]*Info, error) {
|
func parseMountTable(filter FilterFunc) ([]*Info, error) {
|
||||||
var rawEntries *C.struct_statfs
|
var rawEntries *C.struct_statfs
|
||||||
|
|
||||||
count := int(C.getmntinfo(&rawEntries, C.MNT_WAIT))
|
count := int(C.getmntinfo(&rawEntries, C.MNT_WAIT))
|
||||||
|
@ -32,10 +32,24 @@ func parseMountTable() ([]*Info, error) {
|
||||||
var out []*Info
|
var out []*Info
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
var mountinfo Info
|
var mountinfo Info
|
||||||
|
var skip, stop bool
|
||||||
mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0])
|
mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0])
|
||||||
|
|
||||||
|
if filter != nil {
|
||||||
|
// filter out entries we're not interested in
|
||||||
|
skip, stop = filter(p)
|
||||||
|
if skip {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mountinfo.Source = C.GoString(&entry.f_mntfromname[0])
|
mountinfo.Source = C.GoString(&entry.f_mntfromname[0])
|
||||||
mountinfo.Fstype = C.GoString(&entry.f_fstypename[0])
|
mountinfo.Fstype = C.GoString(&entry.f_fstypename[0])
|
||||||
|
|
||||||
out = append(out, &mountinfo)
|
out = append(out, &mountinfo)
|
||||||
|
if stop {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,17 +28,17 @@ const (
|
||||||
|
|
||||||
// Parse /proc/self/mountinfo because comparing Dev and ino does not work from
|
// Parse /proc/self/mountinfo because comparing Dev and ino does not work from
|
||||||
// bind mounts
|
// bind mounts
|
||||||
func parseMountTable() ([]*Info, error) {
|
func parseMountTable(filter FilterFunc) ([]*Info, error) {
|
||||||
f, err := os.Open("/proc/self/mountinfo")
|
f, err := os.Open("/proc/self/mountinfo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
return parseInfoFile(f)
|
return parseInfoFile(f, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseInfoFile(r io.Reader) ([]*Info, error) {
|
func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
|
||||||
var (
|
var (
|
||||||
s = bufio.NewScanner(r)
|
s = bufio.NewScanner(r)
|
||||||
out = []*Info{}
|
out = []*Info{}
|
||||||
|
@ -53,6 +53,7 @@ func parseInfoFile(r io.Reader) ([]*Info, error) {
|
||||||
p = &Info{}
|
p = &Info{}
|
||||||
text = s.Text()
|
text = s.Text()
|
||||||
optionalFields string
|
optionalFields string
|
||||||
|
skip, stop bool
|
||||||
)
|
)
|
||||||
|
|
||||||
if _, err := fmt.Sscanf(text, mountinfoFormat,
|
if _, err := fmt.Sscanf(text, mountinfoFormat,
|
||||||
|
@ -60,6 +61,13 @@ func parseInfoFile(r io.Reader) ([]*Info, error) {
|
||||||
&p.Root, &p.Mountpoint, &p.Opts, &optionalFields); err != nil {
|
&p.Root, &p.Mountpoint, &p.Opts, &optionalFields); err != nil {
|
||||||
return nil, fmt.Errorf("Scanning '%s' failed: %s", text, err)
|
return nil, fmt.Errorf("Scanning '%s' failed: %s", text, err)
|
||||||
}
|
}
|
||||||
|
if filter != nil {
|
||||||
|
// filter out entries we're not interested in
|
||||||
|
skip, stop = filter(p)
|
||||||
|
if skip {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
// Safe as mountinfo encodes mountpoints with spaces as \040.
|
// Safe as mountinfo encodes mountpoints with spaces as \040.
|
||||||
index := strings.Index(text, " - ")
|
index := strings.Index(text, " - ")
|
||||||
postSeparatorFields := strings.Fields(text[index+3:])
|
postSeparatorFields := strings.Fields(text[index+3:])
|
||||||
|
@ -75,6 +83,9 @@ func parseInfoFile(r io.Reader) ([]*Info, error) {
|
||||||
p.Source = postSeparatorFields[1]
|
p.Source = postSeparatorFields[1]
|
||||||
p.VfsOpts = strings.Join(postSeparatorFields[2:], " ")
|
p.VfsOpts = strings.Join(postSeparatorFields[2:], " ")
|
||||||
out = append(out, p)
|
out = append(out, p)
|
||||||
|
if stop {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
@ -89,5 +100,5 @@ func PidMountInfo(pid int) ([]*Info, error) {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
return parseInfoFile(f)
|
return parseInfoFile(f, nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ package mount // import "github.com/docker/docker/pkg/mount"
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gotestyourself/gotestyourself/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -424,7 +426,7 @@ const (
|
||||||
|
|
||||||
func TestParseFedoraMountinfo(t *testing.T) {
|
func TestParseFedoraMountinfo(t *testing.T) {
|
||||||
r := bytes.NewBuffer([]byte(fedoraMountinfo))
|
r := bytes.NewBuffer([]byte(fedoraMountinfo))
|
||||||
_, err := parseInfoFile(r)
|
_, err := parseInfoFile(r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -432,7 +434,7 @@ func TestParseFedoraMountinfo(t *testing.T) {
|
||||||
|
|
||||||
func TestParseUbuntuMountinfo(t *testing.T) {
|
func TestParseUbuntuMountinfo(t *testing.T) {
|
||||||
r := bytes.NewBuffer([]byte(ubuntuMountInfo))
|
r := bytes.NewBuffer([]byte(ubuntuMountInfo))
|
||||||
_, err := parseInfoFile(r)
|
_, err := parseInfoFile(r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -440,7 +442,7 @@ func TestParseUbuntuMountinfo(t *testing.T) {
|
||||||
|
|
||||||
func TestParseGentooMountinfo(t *testing.T) {
|
func TestParseGentooMountinfo(t *testing.T) {
|
||||||
r := bytes.NewBuffer([]byte(gentooMountinfo))
|
r := bytes.NewBuffer([]byte(gentooMountinfo))
|
||||||
_, err := parseInfoFile(r)
|
_, err := parseInfoFile(r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -448,7 +450,7 @@ func TestParseGentooMountinfo(t *testing.T) {
|
||||||
|
|
||||||
func TestParseFedoraMountinfoFields(t *testing.T) {
|
func TestParseFedoraMountinfoFields(t *testing.T) {
|
||||||
r := bytes.NewBuffer([]byte(fedoraMountinfo))
|
r := bytes.NewBuffer([]byte(fedoraMountinfo))
|
||||||
infos, err := parseInfoFile(r)
|
infos, err := parseInfoFile(r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -474,3 +476,27 @@ func TestParseFedoraMountinfoFields(t *testing.T) {
|
||||||
t.Fatalf("expected %#v, got %#v", mi, infos[0])
|
t.Fatalf("expected %#v, got %#v", mi, infos[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseMountinfoFilters(t *testing.T) {
|
||||||
|
r := bytes.NewReader([]byte(fedoraMountinfo))
|
||||||
|
|
||||||
|
infos, err := parseInfoFile(r, SingleEntryFilter("/sys/fs/cgroup"))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, 1, len(infos))
|
||||||
|
|
||||||
|
r.Reset([]byte(fedoraMountinfo))
|
||||||
|
infos, err = parseInfoFile(r, SingleEntryFilter("nonexistent"))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, 0, len(infos))
|
||||||
|
|
||||||
|
r.Reset([]byte(fedoraMountinfo))
|
||||||
|
infos, err = parseInfoFile(r, PrefixFilter("/sys"))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
// there are 18 entries starting with /sys in fedoraMountinfo
|
||||||
|
assert.Equal(t, 18, len(infos))
|
||||||
|
|
||||||
|
r.Reset([]byte(fedoraMountinfo))
|
||||||
|
infos, err = parseInfoFile(r, PrefixFilter("nonexistent"))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Equal(t, 0, len(infos))
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,6 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseMountTable() ([]*Info, error) {
|
func parseMountTable(f FilterFunc) ([]*Info, error) {
|
||||||
return nil, fmt.Errorf("mount.parseMountTable is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
return nil, fmt.Errorf("mount.parseMountTable is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package mount // import "github.com/docker/docker/pkg/mount"
|
package mount // import "github.com/docker/docker/pkg/mount"
|
||||||
|
|
||||||
func parseMountTable() ([]*Info, error) {
|
func parseMountTable(f FilterFunc) ([]*Info, error) {
|
||||||
// Do NOT return an error!
|
// Do NOT return an error!
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ func New(scope string, rootIDs idtools.IDPair) (*Root, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
mountInfos, err := mount.GetMounts()
|
mountInfos, err := mount.GetMounts(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("error looking up mounts for local volume cleanup: %v", err)
|
logrus.Debugf("error looking up mounts for local volume cleanup: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -215,7 +215,7 @@ func TestCreateWithOpts(t *testing.T) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
mountInfos, err := mount.GetMounts()
|
mountInfos, err := mount.GetMounts(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue