Fix WSL2 mountinfo parsing

The WSL2 mountinfo doesn't correctly escape paths in the super options
list. Refactor the parser to split the line in stages before and after
the ` - ` separator. This makes the parsing logic simpler since we don't
need to track the separator index anymore.
* Fix a small bug in the option parser that doesn't deal with multiple `=`
  chars in the comma-separated option strings.
* Fix `OptionalFields` to always return an empty map rather than nil for
  no optional fields.

Fixes: https://github.com/prometheus/procfs/issues/872

Signed-off-by: SuperQ <superq@gmail.com>
diff --git a/mountinfo.go b/mountinfo.go
index a011edc..9cb3397 100644
--- a/mountinfo.go
+++ b/mountinfo.go
@@ -72,17 +72,18 @@
 // 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 @@
 		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 @@
 	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 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 832edac..8c7ae9e 100644
--- a/mountinfo_test.go
+++ b/mountinfo_test.go
@@ -106,7 +106,7 @@
 				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 @@
 				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 {