diff --git a/mountinfo.go b/mountinfo.go index a011edc6..9cb3397d 100644 --- a/mountinfo.go +++ b/mountinfo.go @@ -72,17 +72,18 @@ func parseMountInfo(info []byte) ([]*MountInfo, error) { // Parses a mountinfo file line, and converts it to a MountInfo struct. // An important check here is to see if the hyphen separator, as if it does not exist, // it means that the line is malformed. +// See: https://man7.org/linux/man-pages/man5/proc_pid_mountinfo.5.html func parseMountInfoString(mountString string) (*MountInfo, error) { var err error - mountInfo := strings.Split(mountString, " ") - mountInfoLength := len(mountInfo) - if mountInfoLength < 10 { - return nil, fmt.Errorf("%w: Too few fields in mount string: %s", ErrFileParse, mountString) + fields := strings.Split(mountString, " - ") + if len(fields) != 2 { + return nil, fmt.Errorf("%w: Could not split hyphen separator: %s", ErrFileParse, mountString) } - if mountInfo[mountInfoLength-4] != "-" { - return nil, fmt.Errorf("%w: couldn't find separator in expected field: %s", ErrFileParse, mountInfo[mountInfoLength-4]) + mountInfo := strings.Split(fields[0], " ") + if len(mountInfo) < 6 { + return nil, fmt.Errorf("%w: Too few fields in mount string: %s", ErrFileParse, mountString) } mount := &MountInfo{ @@ -90,10 +91,7 @@ func parseMountInfoString(mountString string) (*MountInfo, error) { Root: mountInfo[3], MountPoint: mountInfo[4], Options: mountOptionsParser(mountInfo[5]), - OptionalFields: nil, - FSType: mountInfo[mountInfoLength-3], - Source: mountInfo[mountInfoLength-2], - SuperOptions: mountOptionsParser(mountInfo[mountInfoLength-1]), + OptionalFields: map[string]string{}, } mount.MountID, err = strconv.Atoi(mountInfo[0]) @@ -104,14 +102,25 @@ func parseMountInfoString(mountString string) (*MountInfo, error) { if err != nil { return nil, fmt.Errorf("%w: parent ID: %q", ErrFileParse, mountInfo[1]) } + // Has optional fields, which is a space separated list of values. // Example: shared:2 master:7 - if mountInfo[6] != "" { - mount.OptionalFields, err = mountOptionsParseOptionalFields(mountInfo[6 : mountInfoLength-4]) + if len(mountInfo) > 6 { + mount.OptionalFields, err = mountOptionsParseOptionalFields(mountInfo[6:]) if err != nil { return nil, fmt.Errorf("%w: %w", ErrFileParse, err) } } + + mountInfo = strings.SplitN(fields[1], " ", 3) + if len(mountInfo) != 3 { + return nil, fmt.Errorf("%w: Too few fields after separator: %s", ErrFileParse, mountString) + } + + mount.FSType = mountInfo[0] + mount.Source = mountInfo[1] + mount.SuperOptions = mountOptionsParser(mountInfo[2]) + return mount, nil } @@ -148,13 +157,11 @@ func mountOptionsParseOptionalFields(o []string) (map[string]string, error) { func mountOptionsParser(mountOptions string) map[string]string { opts := make(map[string]string) for opt := range strings.SplitSeq(mountOptions, ",") { - splitOption := strings.Split(opt, "=") + splitOption := strings.SplitN(opt, "=", 2) if len(splitOption) < 2 { - key := splitOption[0] - opts[key] = "" + opts[splitOption[0]] = "" } else { - key, value := splitOption[0], splitOption[1] - opts[key] = value + opts[splitOption[0]] = splitOption[1] } } return opts diff --git a/mountinfo_test.go b/mountinfo_test.go index 832edac2..8c7ae9ed 100644 --- a/mountinfo_test.go +++ b/mountinfo_test.go @@ -106,7 +106,7 @@ func TestMountInfo(t *testing.T) { Root: "/", MountPoint: "/run/user/112", Options: map[string]string{"rw": "", "nosuid": "", "nodev": "", "relatime": ""}, - OptionalFields: nil, + OptionalFields: map[string]string{}, FSType: "tmpfs", Source: "tmpfs", SuperOptions: map[string]string{"rw": "", "size": "405096k", "mode": "700", "uid": "112", "gid": "116"}, @@ -196,6 +196,32 @@ func TestMountInfo(t *testing.T) { SuperOptions: map[string]string{"rw": "", "data": "ordered"}, }, }, + { + name: "9p Docker Desktop mount with space in super option path", + s: `3196 80 0:172 / /Docker/host rw,noatime - 9p C:\134Program\040Files\134Docker\134Docker\134resources rw,aname=drvfs;path=C:\Program Files\Docker\Docker\resources;symlinkroot=/mnt/,cache=5,access=client,msize=65536,trans=fd,rfd=3,wfd=3`, + mount: &MountInfo{ + MountID: 3196, + ParentID: 80, + MajorMinorVer: "0:172", + Root: "/", + MountPoint: "/Docker/host", + Options: map[string]string{"rw": "", "noatime": ""}, + OptionalFields: map[string]string{}, + FSType: "9p", + Source: `C:\134Program\040Files\134Docker\134Docker\134resources`, + SuperOptions: map[string]string{ + "rw": "", + "aname": `drvfs;path=C:\Program Files\Docker\Docker\resources;symlinkroot=/mnt/`, + "cache": "5", + "access": "client", + "msize": "65536", + "trans": "fd", + "rfd": "3", + "wfd": "3", + }, + }, + invalid: false, + }, } for i, test := range tests {