// Copyright 2019 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package procfs import ( "bufio" "fmt" "io" "os" "strconv" "strings" ) var validOptionalFields = map[string]bool{ "shared": true, "master": true, "propagate_from": true, "unbindable": true, } // A MountInfo is a type that describes the details, options // for each mount, parsed from /proc/self/mountinfo. // The fields described in each entry of /proc/self/mountinfo // is described in the following man page. // http://man7.org/linux/man-pages/man5/proc.5.html type MountInfo struct { // Unique Id for the mount MountId int // The Id of the parent mount ParentId int // The value of `st_dev` for the files on this FS MajorMinorVer string // The pathname of the directory in the FS that forms // the root for this mount Root string // The pathname of the mount point relative to the root MountPoint string // Mount options Options map[string]string // Zero or more optional fields OptionalFields map[string]string // The Filesystem type FSType string // FS specific information or "none" Source string // Superblock options SuperOptions map[string]string } // Returns part of the mountinfo line, if it exists, else an empty string. func getStringSliceElement(parts []string, idx int, defaultValue string) string { if idx >= len(parts) { return defaultValue } return parts[idx] } // Reads each line of the mountinfo file, and returns a list of formatted MountInfo structs. func parseMountInfo(r io.Reader) ([]*MountInfo, error) { mounts := []*MountInfo{} scanner := bufio.NewScanner(r) for scanner.Scan() { mountString := scanner.Text() parsedMounts, err := parseMountInfoString(mountString) if err != nil { return nil, err } mounts = append(mounts, parsedMounts) } err := scanner.Err() return mounts, err } // 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. func parseMountInfoString(mountString string) (*MountInfo, error) { var err error // OptionalFields can be zero, hence these checks to ensure we do not populate the wrong values in the wrong spots separatorIndex := strings.Index(mountString, "-") if separatorIndex == -1 { return nil, fmt.Errorf("no separator found in mountinfo string: %s", mountString) } beforeFields := strings.Fields(mountString[:separatorIndex]) afterFields := strings.Fields(mountString[separatorIndex+1:]) if (len(beforeFields) + len(afterFields)) < 7 { return nil, fmt.Errorf("too few fields") } mount := &MountInfo{ MajorMinorVer: getStringSliceElement(beforeFields, 2, ""), Root: getStringSliceElement(beforeFields, 3, ""), MountPoint: getStringSliceElement(beforeFields, 4, ""), Options: mountOptionsParser(getStringSliceElement(beforeFields, 5, "")), OptionalFields: nil, FSType: getStringSliceElement(afterFields, 0, ""), Source: getStringSliceElement(afterFields, 1, ""), SuperOptions: mountOptionsParser(getStringSliceElement(afterFields, 2, "")), } mount.MountId, err = strconv.Atoi(getStringSliceElement(beforeFields, 0, "")) if err != nil { return nil, fmt.Errorf("failed to parse mount ID") } mount.ParentId, err = strconv.Atoi(getStringSliceElement(beforeFields, 1, "")) if err != nil { return nil, fmt.Errorf("failed to parse parent ID") } // Has optional fields, which is a space separated list of values. // Example: shared:2 master:7 if len(beforeFields) > 6 { mount.OptionalFields = make(map[string]string) optionalFields := beforeFields[6:] for _, field := range optionalFields { optionSplit := strings.Split(field, ":") target, value := optionSplit[0], "" if len(optionSplit) == 2 { value = optionSplit[1] } // Checks if the 'keys' in the optional fields in the mountinfo line are acceptable. // Allowed 'keys' are shared, master, propagate_from, unbindable. if _, ok := validOptionalFields[target]; ok { mount.OptionalFields[target] = value } } } return mount, nil } // Parses the mount options, superblock options. func mountOptionsParser(mountOptions string) map[string]string { opts := make(map[string]string) options := strings.Split(mountOptions, ",") for _, opt := range options { splitOption := strings.Split(opt, "=") if len(splitOption) < 2 { key := splitOption[0] opts[key] = "" } else { key, value := splitOption[0], splitOption[1] opts[key] = value } } return opts } // Retrieves mountinfo information from `/proc/self/mountinfo`. func GetMounts() ([]*MountInfo, error) { f, err := os.Open("/proc/self/mountinfo") if err != nil { return nil, err } defer f.Close() return parseMountInfo(f) } // Retrieves mountinfo information from a processes' `/proc//mountinfo`. func GetProcMounts(pid int) ([]*MountInfo, error) { f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid)) if err != nil { return nil, err } defer f.Close() return parseMountInfo(f) }