1// Copyright 2019 The Prometheus Authors 2// Licensed under the Apache License, Version 2.0 (the "License"); 3// you may not use this file except in compliance with the License. 4// You may obtain a copy of the License at 5// 6// http://www.apache.org/licenses/LICENSE-2.0 7// 8// Unless required by applicable law or agreed to in writing, software 9// distributed under the License is distributed on an "AS IS" BASIS, 10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11// See the License for the specific language governing permissions and 12// limitations under the License. 13 14package procfs 15 16import ( 17 "bufio" 18 "bytes" 19 "fmt" 20 "strconv" 21 "strings" 22 23 "github.com/prometheus/procfs/internal/util" 24) 25 26// A MountInfo is a type that describes the details, options 27// for each mount, parsed from /proc/self/mountinfo. 28// The fields described in each entry of /proc/self/mountinfo 29// is described in the following man page. 30// http://man7.org/linux/man-pages/man5/proc.5.html 31type MountInfo struct { 32 // Unique ID for the mount 33 MountID int 34 // The ID of the parent mount 35 ParentID int 36 // The value of `st_dev` for the files on this FS 37 MajorMinorVer string 38 // The pathname of the directory in the FS that forms 39 // the root for this mount 40 Root string 41 // The pathname of the mount point relative to the root 42 MountPoint string 43 // Mount options 44 Options map[string]string 45 // Zero or more optional fields 46 OptionalFields map[string]string 47 // The Filesystem type 48 FSType string 49 // FS specific information or "none" 50 Source string 51 // Superblock options 52 SuperOptions map[string]string 53} 54 55// Reads each line of the mountinfo file, and returns a list of formatted MountInfo structs. 56func parseMountInfo(info []byte) ([]*MountInfo, error) { 57 mounts := []*MountInfo{} 58 scanner := bufio.NewScanner(bytes.NewReader(info)) 59 for scanner.Scan() { 60 mountString := scanner.Text() 61 parsedMounts, err := parseMountInfoString(mountString) 62 if err != nil { 63 return nil, err 64 } 65 mounts = append(mounts, parsedMounts) 66 } 67 68 err := scanner.Err() 69 return mounts, err 70} 71 72// Parses a mountinfo file line, and converts it to a MountInfo struct. 73// An important check here is to see if the hyphen separator, as if it does not exist, 74// it means that the line is malformed. 75func parseMountInfoString(mountString string) (*MountInfo, error) { 76 var err error 77 78 mountInfo := strings.Split(mountString, " ") 79 mountInfoLength := len(mountInfo) 80 if mountInfoLength < 10 { 81 return nil, fmt.Errorf("couldn't find enough fields in mount string: %s", mountString) 82 } 83 84 if mountInfo[mountInfoLength-4] != "-" { 85 return nil, fmt.Errorf("couldn't find separator in expected field: %s", mountInfo[mountInfoLength-4]) 86 } 87 88 mount := &MountInfo{ 89 MajorMinorVer: mountInfo[2], 90 Root: mountInfo[3], 91 MountPoint: mountInfo[4], 92 Options: mountOptionsParser(mountInfo[5]), 93 OptionalFields: nil, 94 FSType: mountInfo[mountInfoLength-3], 95 Source: mountInfo[mountInfoLength-2], 96 SuperOptions: mountOptionsParser(mountInfo[mountInfoLength-1]), 97 } 98 99 mount.MountID, err = strconv.Atoi(mountInfo[0]) 100 if err != nil { 101 return nil, fmt.Errorf("failed to parse mount ID") 102 } 103 mount.ParentID, err = strconv.Atoi(mountInfo[1]) 104 if err != nil { 105 return nil, fmt.Errorf("failed to parse parent ID") 106 } 107 // Has optional fields, which is a space separated list of values. 108 // Example: shared:2 master:7 109 if mountInfo[6] != "" { 110 mount.OptionalFields, err = mountOptionsParseOptionalFields(mountInfo[6 : mountInfoLength-4]) 111 if err != nil { 112 return nil, err 113 } 114 } 115 return mount, nil 116} 117 118// mountOptionsIsValidField checks a string against a valid list of optional fields keys. 119func mountOptionsIsValidField(s string) bool { 120 switch s { 121 case 122 "shared", 123 "master", 124 "propagate_from", 125 "unbindable": 126 return true 127 } 128 return false 129} 130 131// mountOptionsParseOptionalFields parses a list of optional fields strings into a double map of strings. 132func mountOptionsParseOptionalFields(o []string) (map[string]string, error) { 133 optionalFields := make(map[string]string) 134 for _, field := range o { 135 optionSplit := strings.SplitN(field, ":", 2) 136 value := "" 137 if len(optionSplit) == 2 { 138 value = optionSplit[1] 139 } 140 if mountOptionsIsValidField(optionSplit[0]) { 141 optionalFields[optionSplit[0]] = value 142 } 143 } 144 return optionalFields, nil 145} 146 147// mountOptionsParser parses the mount options, superblock options. 148func mountOptionsParser(mountOptions string) map[string]string { 149 opts := make(map[string]string) 150 options := strings.Split(mountOptions, ",") 151 for _, opt := range options { 152 splitOption := strings.Split(opt, "=") 153 if len(splitOption) < 2 { 154 key := splitOption[0] 155 opts[key] = "" 156 } else { 157 key, value := splitOption[0], splitOption[1] 158 opts[key] = value 159 } 160 } 161 return opts 162} 163 164// GetMounts retrieves mountinfo information from `/proc/self/mountinfo`. 165func GetMounts() ([]*MountInfo, error) { 166 data, err := util.ReadFileNoStat("/proc/self/mountinfo") 167 if err != nil { 168 return nil, err 169 } 170 return parseMountInfo(data) 171} 172 173// GetProcMounts retrieves mountinfo information from a processes' `/proc/<pid>/mountinfo`. 174func GetProcMounts(pid int) ([]*MountInfo, error) { 175 data, err := util.ReadFileNoStat(fmt.Sprintf("/proc/%d/mountinfo", pid)) 176 if err != nil { 177 return nil, err 178 } 179 return parseMountInfo(data) 180} 181