Skip to content

Commit e98632a

Browse files
authored
Fix mountinfo parsing #153 (#154)
1 parent 374174e commit e98632a

File tree

2 files changed

+267
-15
lines changed

2 files changed

+267
-15
lines changed

mounts_linux.go

Lines changed: 94 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,35 @@ import (
1313
"golang.org/x/sys/unix"
1414
)
1515

16+
const (
17+
// A line of self/mountinfo has the following structure:
18+
// 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
19+
// (0) (1) (2) (3) (4) (5) (6) (7) (8) (9) (10)
20+
//
21+
// (0) mount ID: unique identifier of the mount (may be reused after umount).
22+
//mountinfoMountID = 0
23+
// (1) parent ID: ID of parent (or of self for the top of the mount tree).
24+
//mountinfoParentID = 1
25+
// (2) major:minor: value of st_dev for files on filesystem.
26+
//mountinfoMajorMinor = 2
27+
// (3) root: root of the mount within the filesystem.
28+
//mountinfoRoot = 3
29+
// (4) mount point: mount point relative to the process's root.
30+
mountinfoMountPoint = 4
31+
// (5) mount options: per mount options.
32+
mountinfoMountOpts = 5
33+
// (6) optional fields: zero or more fields terminated by "-".
34+
mountinfoOptionalFields = 6
35+
// (7) separator between optional fields.
36+
//mountinfoSeparator = 7
37+
// (8) filesystem type: name of filesystem of the form.
38+
mountinfoFsType = 8
39+
// (9) mount source: filesystem specific information or "none".
40+
mountinfoMountSource = 9
41+
// (10) super options: per super block options.
42+
//mountinfoSuperOptions = 10
43+
)
44+
1645
func (m *Mount) Stat() unix.Statfs_t {
1746
return m.Metadata.(unix.Statfs_t)
1847
}
@@ -28,24 +57,23 @@ func mounts() ([]Mount, []string, error) {
2857

2958
ret := make([]Mount, 0, len(lines))
3059
for _, line := range lines {
31-
// a line of self/mountinfo has the following structure:
32-
// 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
33-
// (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11)
34-
35-
// split the mountinfo line by the separator hyphen
36-
parts := strings.Split(line, " - ")
37-
if len(parts) != 2 {
38-
return nil, nil, fmt.Errorf("found invalid mountinfo line in file %s: %s", filename, line)
60+
nb, fields := parseMountInfoLine(line)
61+
if nb == 0 {
62+
continue
3963
}
4064

41-
fields := strings.Fields(parts[0])
42-
// blockDeviceID := fields[2]
43-
mountPoint := unescapeFstab(fields[4])
44-
mountOpts := fields[5]
65+
// if the number of fields does not match the structure of mountinfo,
66+
// emit a warning and ignore the line.
67+
if nb < 10 || nb > 11 {
68+
warnings = append(warnings, fmt.Sprintf("found invalid mountinfo line: %s", line))
69+
continue
70+
}
4571

46-
fields = strings.Fields(parts[1])
47-
fstype := unescapeFstab(fields[0])
48-
device := unescapeFstab(fields[1])
72+
// blockDeviceID := fields[mountinfoMountID]
73+
mountPoint := fields[mountinfoMountPoint]
74+
mountOpts := fields[mountinfoMountOpts]
75+
fstype := fields[mountinfoFsType]
76+
device := fields[mountinfoMountSource]
4977

5078
var stat unix.Statfs_t
5179
err := unix.Statfs(mountPoint, &stat)
@@ -90,3 +118,54 @@ func mounts() ([]Mount, []string, error) {
90118

91119
return ret, warnings, nil
92120
}
121+
122+
// parseMountInfoLine parses a line of /proc/self/mountinfo and returns the
123+
// amount of parsed fields and their values.
124+
func parseMountInfoLine(line string) (int, [11]string) {
125+
var fields [11]string
126+
127+
if len(line) == 0 || line[0] == '#' {
128+
// ignore comments and empty lines
129+
return 0, fields
130+
}
131+
132+
var i int
133+
for _, f := range strings.Fields(line) {
134+
// when parsing the optional fields, loop until we find the separator
135+
if i == mountinfoOptionalFields {
136+
// (6) optional fields: zero or more fields of the form
137+
// "tag[:value]"; see below.
138+
// (7) separator: the end of the optional fields is marked
139+
// by a single hyphen.
140+
if f != "-" {
141+
if fields[i] == "" {
142+
fields[i] += f
143+
} else {
144+
fields[i] += " " + f
145+
}
146+
147+
// keep reading until we reach the separator
148+
continue
149+
}
150+
151+
// separator found, continue parsing
152+
i++
153+
}
154+
155+
switch i {
156+
case mountinfoMountPoint:
157+
fallthrough
158+
case mountinfoMountSource:
159+
fallthrough
160+
case mountinfoFsType:
161+
fields[i] = unescapeFstab(f)
162+
163+
default:
164+
fields[i] = f
165+
}
166+
167+
i++
168+
}
169+
170+
return i, fields
171+
}

mounts_linux_test.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
//go:build linux
2+
// +build linux
3+
4+
package main
5+
6+
import (
7+
"reflect"
8+
"testing"
9+
)
10+
11+
func TestGetFields(t *testing.T) {
12+
var tt = []struct {
13+
input string
14+
number int
15+
expected [11]string
16+
}{
17+
// Empty lines
18+
{
19+
input: "",
20+
number: 0,
21+
},
22+
{
23+
input: " ",
24+
number: 0,
25+
},
26+
{
27+
input: " ",
28+
number: 0,
29+
},
30+
{
31+
input: " ",
32+
number: 0,
33+
},
34+
35+
// Comments
36+
{
37+
input: "#",
38+
number: 0,
39+
},
40+
{
41+
input: "# ",
42+
number: 0,
43+
},
44+
{
45+
input: "# ",
46+
number: 0,
47+
},
48+
{
49+
input: "# I'm a lazy dog",
50+
number: 0,
51+
},
52+
53+
// Bad fields
54+
{
55+
input: "1 2",
56+
number: 2,
57+
expected: [11]string{"1", "2"},
58+
},
59+
{
60+
input: "1 2",
61+
number: 2,
62+
expected: [11]string{"1", "2"},
63+
},
64+
{
65+
input: "1 2 3",
66+
number: 3,
67+
expected: [11]string{"1", "2", "3"},
68+
},
69+
{
70+
input: "1 2 3 4",
71+
number: 4,
72+
expected: [11]string{"1", "2", "3", "4"},
73+
},
74+
75+
// No optional separator or no options
76+
{
77+
input: "1 2 3 4 5 6 7 NotASeparator 9 10 11",
78+
number: 6,
79+
expected: [11]string{"1", "2", "3", "4", "5", "6", "7 NotASeparator 9 10 11"},
80+
},
81+
{
82+
input: "1 2 3 4 5 6 7 8 9 10 11",
83+
number: 6,
84+
expected: [11]string{"1", "2", "3", "4", "5", "6", "7 8 9 10 11"},
85+
},
86+
{
87+
input: "1 2 3 4 5 6 - 9 10 11",
88+
number: 11,
89+
expected: [11]string{"1", "2", "3", "4", "5", "6", "", "-", "9", "10", "11"},
90+
},
91+
92+
// Normal mount table line
93+
{
94+
input: "22 27 0:21 / /proc rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw",
95+
number: 11,
96+
expected: [11]string{"22", "27", "0:21", "/", "/proc", "rw,nosuid,nodev,noexec,relatime", "shared:5", "-", "proc", "proc", "rw"},
97+
},
98+
{
99+
input: "31 23 0:27 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime shared:9 - cgroup2 cgroup2 rw,nsdelegate,memory_recursiveprot",
100+
number: 11,
101+
expected: [11]string{"31", "23", "0:27", "/", "/sys/fs/cgroup", "rw,nosuid,nodev,noexec,relatime", "shared:9", "-", "cgroup2", "cgroup2", "rw,nsdelegate,memory_recursiveprot"},
102+
},
103+
{
104+
input: "40 27 0:33 / /tmp rw,nosuid,nodev shared:18 - tmpfs tmpfs",
105+
number: 10,
106+
expected: [11]string{"40", "27", "0:33", "/", "/tmp", "rw,nosuid,nodev", "shared:18", "-", "tmpfs", "tmpfs"},
107+
},
108+
{
109+
input: "40 27 0:33 / /tmp rw,nosuid,nodev shared:18 shared:22 - tmpfs tmpfs",
110+
number: 10,
111+
expected: [11]string{"40", "27", "0:33", "/", "/tmp", "rw,nosuid,nodev", "shared:18 shared:22", "-", "tmpfs", "tmpfs"},
112+
},
113+
{
114+
input: "50 27 0:33 / /tmp rw,nosuid,nodev - tmpfs tmpfs",
115+
number: 10,
116+
expected: [11]string{"50", "27", "0:33", "/", "/tmp", "rw,nosuid,nodev", "", "-", "tmpfs", "tmpfs"},
117+
},
118+
119+
// Exceptional mount table lines
120+
{
121+
input: "328 27 0:73 / /mnt/a rw,relatime shared:206 - tmpfs - rw,inode64",
122+
number: 11,
123+
expected: [11]string{"328", "27", "0:73", "/", "/mnt/a", "rw,relatime", "shared:206", "-", "tmpfs", "-", "rw,inode64"},
124+
},
125+
{
126+
input: "330 27 0:73 / /mnt/a rw,relatime shared:206 - tmpfs 👾 rw,inode64",
127+
number: 11,
128+
expected: [11]string{"330", "27", "0:73", "/", "/mnt/a", "rw,relatime", "shared:206", "-", "tmpfs", "👾", "rw,inode64"},
129+
},
130+
{
131+
input: "335 27 0:73 / /mnt/👾 rw,relatime shared:206 - tmpfs 👾 rw,inode64",
132+
number: 11,
133+
expected: [11]string{"335", "27", "0:73", "/", "/mnt/👾", "rw,relatime", "shared:206", "-", "tmpfs", "👾", "rw,inode64"},
134+
},
135+
{
136+
input: "509 27 0:78 / /mnt/- rw,relatime shared:223 - tmpfs 👾 rw,inode64",
137+
number: 11,
138+
expected: [11]string{"509", "27", "0:78", "/", "/mnt/-", "rw,relatime", "shared:223", "-", "tmpfs", "👾", "rw,inode64"},
139+
},
140+
{
141+
input: "362 27 0:76 / /mnt/a\\040b rw,relatime shared:215 - tmpfs 👾 rw,inode64",
142+
number: 11,
143+
expected: [11]string{"362", "27", "0:76", "/", "/mnt/a b", "rw,relatime", "shared:215", "-", "tmpfs", "👾", "rw,inode64"},
144+
},
145+
{
146+
input: "1 2 3:3 / /mnt/\\011 rw shared:7 - tmpfs - rw,inode64",
147+
number: 11,
148+
expected: [11]string{"1", "2", "3:3", "/", "/mnt/\t", "rw", "shared:7", "-", "tmpfs", "-", "rw,inode64"},
149+
},
150+
{
151+
input: "11 2 3:3 / /mnt/a\\012b rw shared:7 - tmpfs - rw,inode64",
152+
number: 11,
153+
expected: [11]string{"11", "2", "3:3", "/", "/mnt/a\nb", "rw", "shared:7", "-", "tmpfs", "-", "rw,inode64"},
154+
},
155+
{
156+
input: "111 2 3:3 / /mnt/a\\134b rw shared:7 - tmpfs - rw,inode64",
157+
number: 11,
158+
expected: [11]string{"111", "2", "3:3", "/", "/mnt/a\\b", "rw", "shared:7", "-", "tmpfs", "-", "rw,inode64"},
159+
},
160+
{
161+
input: "1111 2 3:3 / /mnt/a\\042b rw shared:7 - tmpfs - rw,inode64",
162+
number: 11,
163+
expected: [11]string{"1111", "2", "3:3", "/", "/mnt/a\"b", "rw", "shared:7", "-", "tmpfs", "-", "rw,inode64"},
164+
},
165+
}
166+
167+
for _, tc := range tt {
168+
nb, actual := parseMountInfoLine(tc.input)
169+
if nb != tc.number || !reflect.DeepEqual(actual, tc.expected) {
170+
t.Errorf("\nparseMountInfoLine(%q) == \n(%d) %q, \nexpected (%d) %q", tc.input, nb, actual, tc.number, tc.expected)
171+
}
172+
}
173+
}

0 commit comments

Comments
 (0)