179 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			179 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
| // 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/<pid>/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)
 | |
| }
 |