Skip to content

Commit

Permalink
Fix mountinfo parsing #153 (#154)
Browse files Browse the repository at this point in the history
  • Loading branch information
IGLOU-EU authored Feb 7, 2022
1 parent 374174e commit e98632a
Show file tree
Hide file tree
Showing 2 changed files with 267 additions and 15 deletions.
109 changes: 94 additions & 15 deletions mounts_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,35 @@ import (
"golang.org/x/sys/unix"
)

const (
// A line of self/mountinfo has the following structure:
// 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
// (0) (1) (2) (3) (4) (5) (6) (7) (8) (9) (10)
//
// (0) mount ID: unique identifier of the mount (may be reused after umount).
//mountinfoMountID = 0
// (1) parent ID: ID of parent (or of self for the top of the mount tree).
//mountinfoParentID = 1
// (2) major:minor: value of st_dev for files on filesystem.
//mountinfoMajorMinor = 2
// (3) root: root of the mount within the filesystem.
//mountinfoRoot = 3
// (4) mount point: mount point relative to the process's root.
mountinfoMountPoint = 4
// (5) mount options: per mount options.
mountinfoMountOpts = 5
// (6) optional fields: zero or more fields terminated by "-".
mountinfoOptionalFields = 6
// (7) separator between optional fields.
//mountinfoSeparator = 7
// (8) filesystem type: name of filesystem of the form.
mountinfoFsType = 8
// (9) mount source: filesystem specific information or "none".
mountinfoMountSource = 9
// (10) super options: per super block options.
//mountinfoSuperOptions = 10
)

func (m *Mount) Stat() unix.Statfs_t {
return m.Metadata.(unix.Statfs_t)
}
Expand All @@ -28,24 +57,23 @@ func mounts() ([]Mount, []string, error) {

ret := make([]Mount, 0, len(lines))
for _, line := range lines {
// a line of self/mountinfo has the following structure:
// 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
// (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11)

// split the mountinfo line by the separator hyphen
parts := strings.Split(line, " - ")
if len(parts) != 2 {
return nil, nil, fmt.Errorf("found invalid mountinfo line in file %s: %s", filename, line)
nb, fields := parseMountInfoLine(line)
if nb == 0 {
continue
}

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

fields = strings.Fields(parts[1])
fstype := unescapeFstab(fields[0])
device := unescapeFstab(fields[1])
// blockDeviceID := fields[mountinfoMountID]
mountPoint := fields[mountinfoMountPoint]
mountOpts := fields[mountinfoMountOpts]
fstype := fields[mountinfoFsType]
device := fields[mountinfoMountSource]

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

return ret, warnings, nil
}

// parseMountInfoLine parses a line of /proc/self/mountinfo and returns the
// amount of parsed fields and their values.
func parseMountInfoLine(line string) (int, [11]string) {
var fields [11]string

if len(line) == 0 || line[0] == '#' {
// ignore comments and empty lines
return 0, fields
}

var i int
for _, f := range strings.Fields(line) {
// when parsing the optional fields, loop until we find the separator
if i == mountinfoOptionalFields {
// (6) optional fields: zero or more fields of the form
// "tag[:value]"; see below.
// (7) separator: the end of the optional fields is marked
// by a single hyphen.
if f != "-" {
if fields[i] == "" {
fields[i] += f
} else {
fields[i] += " " + f
}

// keep reading until we reach the separator
continue
}

// separator found, continue parsing
i++
}

switch i {
case mountinfoMountPoint:
fallthrough
case mountinfoMountSource:
fallthrough
case mountinfoFsType:
fields[i] = unescapeFstab(f)

default:
fields[i] = f
}

i++
}

return i, fields
}
173 changes: 173 additions & 0 deletions mounts_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//go:build linux
// +build linux

package main

import (
"reflect"
"testing"
)

func TestGetFields(t *testing.T) {
var tt = []struct {
input string
number int
expected [11]string
}{
// Empty lines
{
input: "",
number: 0,
},
{
input: " ",
number: 0,
},
{
input: " ",
number: 0,
},
{
input: " ",
number: 0,
},

// Comments
{
input: "#",
number: 0,
},
{
input: "# ",
number: 0,
},
{
input: "# ",
number: 0,
},
{
input: "# I'm a lazy dog",
number: 0,
},

// Bad fields
{
input: "1 2",
number: 2,
expected: [11]string{"1", "2"},
},
{
input: "1 2",
number: 2,
expected: [11]string{"1", "2"},
},
{
input: "1 2 3",
number: 3,
expected: [11]string{"1", "2", "3"},
},
{
input: "1 2 3 4",
number: 4,
expected: [11]string{"1", "2", "3", "4"},
},

// No optional separator or no options
{
input: "1 2 3 4 5 6 7 NotASeparator 9 10 11",
number: 6,
expected: [11]string{"1", "2", "3", "4", "5", "6", "7 NotASeparator 9 10 11"},
},
{
input: "1 2 3 4 5 6 7 8 9 10 11",
number: 6,
expected: [11]string{"1", "2", "3", "4", "5", "6", "7 8 9 10 11"},
},
{
input: "1 2 3 4 5 6 - 9 10 11",
number: 11,
expected: [11]string{"1", "2", "3", "4", "5", "6", "", "-", "9", "10", "11"},
},

// Normal mount table line
{
input: "22 27 0:21 / /proc rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw",
number: 11,
expected: [11]string{"22", "27", "0:21", "/", "/proc", "rw,nosuid,nodev,noexec,relatime", "shared:5", "-", "proc", "proc", "rw"},
},
{
input: "31 23 0:27 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime shared:9 - cgroup2 cgroup2 rw,nsdelegate,memory_recursiveprot",
number: 11,
expected: [11]string{"31", "23", "0:27", "/", "/sys/fs/cgroup", "rw,nosuid,nodev,noexec,relatime", "shared:9", "-", "cgroup2", "cgroup2", "rw,nsdelegate,memory_recursiveprot"},
},
{
input: "40 27 0:33 / /tmp rw,nosuid,nodev shared:18 - tmpfs tmpfs",
number: 10,
expected: [11]string{"40", "27", "0:33", "/", "/tmp", "rw,nosuid,nodev", "shared:18", "-", "tmpfs", "tmpfs"},
},
{
input: "40 27 0:33 / /tmp rw,nosuid,nodev shared:18 shared:22 - tmpfs tmpfs",
number: 10,
expected: [11]string{"40", "27", "0:33", "/", "/tmp", "rw,nosuid,nodev", "shared:18 shared:22", "-", "tmpfs", "tmpfs"},
},
{
input: "50 27 0:33 / /tmp rw,nosuid,nodev - tmpfs tmpfs",
number: 10,
expected: [11]string{"50", "27", "0:33", "/", "/tmp", "rw,nosuid,nodev", "", "-", "tmpfs", "tmpfs"},
},

// Exceptional mount table lines
{
input: "328 27 0:73 / /mnt/a rw,relatime shared:206 - tmpfs - rw,inode64",
number: 11,
expected: [11]string{"328", "27", "0:73", "/", "/mnt/a", "rw,relatime", "shared:206", "-", "tmpfs", "-", "rw,inode64"},
},
{
input: "330 27 0:73 / /mnt/a rw,relatime shared:206 - tmpfs 👾 rw,inode64",
number: 11,
expected: [11]string{"330", "27", "0:73", "/", "/mnt/a", "rw,relatime", "shared:206", "-", "tmpfs", "👾", "rw,inode64"},
},
{
input: "335 27 0:73 / /mnt/👾 rw,relatime shared:206 - tmpfs 👾 rw,inode64",
number: 11,
expected: [11]string{"335", "27", "0:73", "/", "/mnt/👾", "rw,relatime", "shared:206", "-", "tmpfs", "👾", "rw,inode64"},
},
{
input: "509 27 0:78 / /mnt/- rw,relatime shared:223 - tmpfs 👾 rw,inode64",
number: 11,
expected: [11]string{"509", "27", "0:78", "/", "/mnt/-", "rw,relatime", "shared:223", "-", "tmpfs", "👾", "rw,inode64"},
},
{
input: "362 27 0:76 / /mnt/a\\040b rw,relatime shared:215 - tmpfs 👾 rw,inode64",
number: 11,
expected: [11]string{"362", "27", "0:76", "/", "/mnt/a b", "rw,relatime", "shared:215", "-", "tmpfs", "👾", "rw,inode64"},
},
{
input: "1 2 3:3 / /mnt/\\011 rw shared:7 - tmpfs - rw,inode64",
number: 11,
expected: [11]string{"1", "2", "3:3", "/", "/mnt/\t", "rw", "shared:7", "-", "tmpfs", "-", "rw,inode64"},
},
{
input: "11 2 3:3 / /mnt/a\\012b rw shared:7 - tmpfs - rw,inode64",
number: 11,
expected: [11]string{"11", "2", "3:3", "/", "/mnt/a\nb", "rw", "shared:7", "-", "tmpfs", "-", "rw,inode64"},
},
{
input: "111 2 3:3 / /mnt/a\\134b rw shared:7 - tmpfs - rw,inode64",
number: 11,
expected: [11]string{"111", "2", "3:3", "/", "/mnt/a\\b", "rw", "shared:7", "-", "tmpfs", "-", "rw,inode64"},
},
{
input: "1111 2 3:3 / /mnt/a\\042b rw shared:7 - tmpfs - rw,inode64",
number: 11,
expected: [11]string{"1111", "2", "3:3", "/", "/mnt/a\"b", "rw", "shared:7", "-", "tmpfs", "-", "rw,inode64"},
},
}

for _, tc := range tt {
nb, actual := parseMountInfoLine(tc.input)
if nb != tc.number || !reflect.DeepEqual(actual, tc.expected) {
t.Errorf("\nparseMountInfoLine(%q) == \n(%d) %q, \nexpected (%d) %q", tc.input, nb, actual, tc.number, tc.expected)
}
}
}

0 comments on commit e98632a

Please sign in to comment.