Skip to content

Commit fb464b9

Browse files
committed
mount: replace mountinfo handling with moby/sys/mountinfo
Trying to reduce duplicated effort in maintaining a mountinfo parser, this patch replaces the local implementation with the implementation in github.com/moby/sys, which is actively maintained and contains various optimizations. Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent 7c6d710 commit fb464b9

20 files changed

Lines changed: 856 additions & 842 deletions

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ require (
3737
github.com/hashicorp/go-multierror v1.0.0
3838
github.com/imdario/mergo v0.3.10
3939
github.com/klauspost/compress v1.11.3
40+
github.com/moby/sys/mountinfo v0.4.0
4041
github.com/moby/sys/symlink v0.1.0
4142
github.com/opencontainers/go-digest v1.0.0
4243
github.com/opencontainers/image-spec v1.0.1

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,8 @@ github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go
345345
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
346346
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
347347
github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o=
348+
github.com/moby/sys/mountinfo v0.4.0 h1:1KInV3Huv18akCu58V7lzNlt+jFmqlu1EaErnEHE/VM=
349+
github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
348350
github.com/moby/sys/symlink v0.1.0 h1:MTFZ74KtNI6qQQpuBxU+uKCim4WtOMokr03hCfJcazE=
349351
github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
350352
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
@@ -611,6 +613,7 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
611613
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
612614
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
613615
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
616+
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
614617
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
615618
golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
616619
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3 h1:kzM6+9dur93BcC2kVlYl34cHU+TYZLanmpSJHVMmL64=

mount/lookup_unix.go

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,34 +20,30 @@ package mount
2020

2121
import (
2222
"path/filepath"
23-
"sort"
24-
"strings"
2523

24+
"github.com/moby/sys/mountinfo"
2625
"github.com/pkg/errors"
2726
)
2827

2928
// Lookup returns the mount info corresponds to the path.
3029
func Lookup(dir string) (Info, error) {
3130
dir = filepath.Clean(dir)
3231

33-
mounts, err := Self()
32+
m, err := mountinfo.GetMounts(mountinfo.ParentsFilter(dir))
3433
if err != nil {
35-
return Info{}, err
34+
return Info{}, errors.Wrapf(err, "failed to find the mount info for %q", dir)
35+
}
36+
if len(m) == 0 {
37+
return Info{}, errors.Errorf("failed to find the mount info for %q", dir)
3638
}
3739

38-
// Sort descending order by Info.Mountpoint
39-
sort.SliceStable(mounts, func(i, j int) bool {
40-
return mounts[j].Mountpoint < mounts[i].Mountpoint
41-
})
42-
for _, m := range mounts {
43-
// Note that m.{Major, Minor} are generally unreliable for our purpose here
44-
// https://www.spinics.net/lists/linux-btrfs/msg58908.html
45-
// Note that device number is not checked here, because for overlayfs files
46-
// may have different device number with the mountpoint.
47-
if strings.HasPrefix(dir, m.Mountpoint) {
48-
return m, nil
40+
// find the longest matching mount point
41+
var idx, maxlen int
42+
for i := range m {
43+
if len(m[i].Mountpoint) > maxlen {
44+
maxlen = len(m[i].Mountpoint)
45+
idx = i
4946
}
5047
}
51-
52-
return Info{}, errors.Errorf("failed to find the mount info for %q", dir)
48+
return *m[idx], nil
5349
}

mount/mountinfo.go

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,41 +16,16 @@
1616

1717
package mount
1818

19+
import "github.com/moby/sys/mountinfo"
20+
1921
// Info reveals information about a particular mounted filesystem. This
2022
// struct is populated from the content in the /proc/<pid>/mountinfo file.
21-
type Info struct {
22-
// ID is a unique identifier of the mount (may be reused after umount).
23-
ID int
24-
25-
// Parent indicates the ID of the mount parent (or of self for the top of the
26-
// mount tree).
27-
Parent int
28-
29-
// Major indicates one half of the device ID which identifies the device class.
30-
Major int
31-
32-
// Minor indicates one half of the device ID which identifies a specific
33-
// instance of device.
34-
Minor int
35-
36-
// Root of the mount within the filesystem.
37-
Root string
38-
39-
// Mountpoint indicates the mount point relative to the process's root.
40-
Mountpoint string
41-
42-
// Options represents mount-specific options.
43-
Options string
44-
45-
// Optional represents optional fields.
46-
Optional string
47-
48-
// FSType indicates the type of filesystem, such as EXT3.
49-
FSType string
50-
51-
// Source indicates filesystem specific information or "none".
52-
Source string
53-
54-
// VFSOptions represents per super block options.
55-
VFSOptions string
23+
type Info = mountinfo.Info
24+
25+
func fromMountinfoSlice(infos []*mountinfo.Info) []Info {
26+
out := make([]Info, len(infos))
27+
for i, v := range infos {
28+
out[i] = *v
29+
}
30+
return out
5631
}

mount/mountinfo_bsd.go

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,46 +18,20 @@
1818

1919
package mount
2020

21-
/*
22-
#include <sys/param.h>
23-
#include <sys/ucred.h>
24-
#include <sys/mount.h>
25-
*/
26-
import "C"
27-
2821
import (
2922
"fmt"
30-
"reflect"
31-
"unsafe"
23+
"runtime"
24+
25+
"github.com/moby/sys/mountinfo"
3226
)
3327

3428
// Self retrieves a list of mounts for the current running process.
3529
func Self() ([]Info, error) {
36-
var rawEntries *C.struct_statfs
37-
38-
count := int(C.getmntinfo(&rawEntries, C.MNT_WAIT))
39-
if count == 0 {
40-
return nil, fmt.Errorf("Failed to call getmntinfo")
41-
}
42-
43-
var entries []C.struct_statfs
44-
header := (*reflect.SliceHeader)(unsafe.Pointer(&entries))
45-
header.Cap = count
46-
header.Len = count
47-
header.Data = uintptr(unsafe.Pointer(rawEntries))
48-
49-
var out []Info
50-
for _, entry := range entries {
51-
var mountinfo Info
52-
mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0])
53-
mountinfo.Source = C.GoString(&entry.f_mntfromname[0])
54-
mountinfo.FSType = C.GoString(&entry.f_fstypename[0])
55-
out = append(out, mountinfo)
56-
}
57-
return out, nil
30+
m, err := mountinfo.GetMounts(nil)
31+
return fromMountinfoSlice(m), err
5832
}
5933

6034
// PID collects the mounts for a specific process ID.
6135
func PID(pid int) ([]Info, error) {
62-
return nil, fmt.Errorf("mountinfo.PID is not implemented on freebsd")
36+
return nil, fmt.Errorf("mountinfo.PID is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
6337
}

mount/mountinfo_linux.go

Lines changed: 5 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -19,127 +19,19 @@
1919
package mount
2020

2121
import (
22-
"bufio"
23-
"fmt"
24-
"io"
25-
"os"
26-
"strconv"
27-
"strings"
28-
29-
"github.com/pkg/errors"
22+
"github.com/moby/sys/mountinfo"
3023
)
3124

3225
// Self retrieves a list of mounts for the current running process.
3326
func Self() ([]Info, error) {
34-
f, err := os.Open("/proc/self/mountinfo")
35-
if err != nil {
36-
return nil, err
37-
}
38-
defer f.Close()
39-
40-
return parseInfoFile(f)
41-
}
42-
43-
func parseInfoFile(r io.Reader) ([]Info, error) {
44-
s := bufio.NewScanner(r)
45-
out := []Info{}
46-
var err error
47-
for s.Scan() {
48-
/*
49-
See http://man7.org/linux/man-pages/man5/proc.5.html
50-
51-
36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
52-
(1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
53-
(1) mount ID: unique identifier of the mount (may be reused after umount)
54-
(2) parent ID: ID of parent (or of self for the top of the mount tree)
55-
(3) major:minor: value of st_dev for files on filesystem
56-
(4) root: root of the mount within the filesystem
57-
(5) mount point: mount point relative to the process's root
58-
(6) mount options: per mount options
59-
(7) optional fields: zero or more fields of the form "tag[:value]"
60-
(8) separator: marks the end of the optional fields
61-
(9) filesystem type: name of filesystem of the form "type[.subtype]"
62-
(10) mount source: filesystem specific information or "none"
63-
(11) super options: per super block options
64-
*/
65-
66-
text := s.Text()
67-
fields := strings.Split(text, " ")
68-
numFields := len(fields)
69-
if numFields < 10 {
70-
// should be at least 10 fields
71-
return nil, errors.Errorf("parsing '%s' failed: not enough fields (%d)", text, numFields)
72-
}
73-
p := Info{}
74-
// ignore any numbers parsing errors, as there should not be any
75-
p.ID, _ = strconv.Atoi(fields[0])
76-
p.Parent, _ = strconv.Atoi(fields[1])
77-
mm := strings.Split(fields[2], ":")
78-
if len(mm) != 2 {
79-
return nil, errors.Errorf("parsing '%s' failed: unexpected minor:major pair %s", text, mm)
80-
}
81-
p.Major, _ = strconv.Atoi(mm[0])
82-
p.Minor, _ = strconv.Atoi(mm[1])
83-
84-
p.Root, err = strconv.Unquote(`"` + strings.Replace(fields[3], `"`, `\"`, -1) + `"`)
85-
if err != nil {
86-
return nil, errors.Wrapf(err, "parsing '%s' failed: unable to unquote root field", fields[3])
87-
}
88-
p.Mountpoint, err = strconv.Unquote(`"` + strings.Replace(fields[4], `"`, `\"`, -1) + `"`)
89-
if err != nil {
90-
return nil, errors.Wrapf(err, "parsing '%s' failed: unable to unquote mount point field", fields[4])
91-
}
92-
p.Options = fields[5]
93-
94-
// one or more optional fields, when a separator (-)
95-
i := 6
96-
for ; i < numFields && fields[i] != "-"; i++ {
97-
switch i {
98-
case 6:
99-
p.Optional = fields[6]
100-
default:
101-
/* NOTE there might be more optional fields before the separator
102-
such as fields[7]...fields[N] (where N < separatorIndex),
103-
although as of Linux kernel 4.15 the only known ones are
104-
mount propagation flags in fields[6]. The correct
105-
behavior is to ignore any unknown optional fields.
106-
*/
107-
}
108-
}
109-
if i == numFields {
110-
return nil, errors.Errorf("parsing '%s' failed: missing separator ('-')", text)
111-
}
112-
// There should be 3 fields after the separator...
113-
if i+4 > numFields {
114-
return nil, errors.Errorf("parsing '%s' failed: not enough fields after a separator", text)
115-
}
116-
// ... but in Linux <= 3.9 mounting a cifs with spaces in a share name
117-
// (like "//serv/My Documents") _may_ end up having a space in the last field
118-
// of mountinfo (like "unc=//serv/My Documents"). Since kernel 3.10-rc1, cifs
119-
// option unc= is ignored, so a space should not appear. In here we ignore
120-
// those "extra" fields caused by extra spaces.
121-
p.FSType = fields[i+1]
122-
p.Source = fields[i+2]
123-
p.VFSOptions = fields[i+3]
124-
125-
out = append(out, p)
126-
}
127-
if err = s.Err(); err != nil {
128-
return nil, err
129-
}
130-
131-
return out, nil
27+
m, err := mountinfo.GetMounts(nil)
28+
return fromMountinfoSlice(m), err
13229
}
13330

13431
// PID collects the mounts for a specific process ID. If the process
13532
// ID is unknown, it is better to use `Self` which will inspect
13633
// "/proc/self/mountinfo" instead.
13734
func PID(pid int) ([]Info, error) {
138-
f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
139-
if err != nil {
140-
return nil, err
141-
}
142-
defer f.Close()
143-
144-
return parseInfoFile(f)
35+
m, err := mountinfo.PidMountInfo(pid)
36+
return fromMountinfoSlice(m), err
14537
}

0 commit comments

Comments
 (0)