blob: 61fa618874c4a9605d061a1ae042536bd7876c81 [file] [log] [blame]
kesavand2cde6582020-06-22 04:56:23 -04001// 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 "fmt"
19 "io"
20 "os"
21 "strconv"
22 "strings"
23)
24
25var validOptionalFields = map[string]bool{
26 "shared": true,
27 "master": true,
28 "propagate_from": true,
29 "unbindable": true,
30}
31
32// A MountInfo is a type that describes the details, options
33// for each mount, parsed from /proc/self/mountinfo.
34// The fields described in each entry of /proc/self/mountinfo
35// is described in the following man page.
36// http://man7.org/linux/man-pages/man5/proc.5.html
37type MountInfo struct {
38 // Unique Id for the mount
39 MountId int
40 // The Id of the parent mount
41 ParentId int
42 // The value of `st_dev` for the files on this FS
43 MajorMinorVer string
44 // The pathname of the directory in the FS that forms
45 // the root for this mount
46 Root string
47 // The pathname of the mount point relative to the root
48 MountPoint string
49 // Mount options
50 Options map[string]string
51 // Zero or more optional fields
52 OptionalFields map[string]string
53 // The Filesystem type
54 FSType string
55 // FS specific information or "none"
56 Source string
57 // Superblock options
58 SuperOptions map[string]string
59}
60
61// Returns part of the mountinfo line, if it exists, else an empty string.
62func getStringSliceElement(parts []string, idx int, defaultValue string) string {
63 if idx >= len(parts) {
64 return defaultValue
65 }
66 return parts[idx]
67}
68
69// Reads each line of the mountinfo file, and returns a list of formatted MountInfo structs.
70func parseMountInfo(r io.Reader) ([]*MountInfo, error) {
71 mounts := []*MountInfo{}
72 scanner := bufio.NewScanner(r)
73 for scanner.Scan() {
74 mountString := scanner.Text()
75 parsedMounts, err := parseMountInfoString(mountString)
76 if err != nil {
77 return nil, err
78 }
79 mounts = append(mounts, parsedMounts)
80 }
81
82 err := scanner.Err()
83 return mounts, err
84}
85
86// Parses a mountinfo file line, and converts it to a MountInfo struct.
87// An important check here is to see if the hyphen separator, as if it does not exist,
88// it means that the line is malformed.
89func parseMountInfoString(mountString string) (*MountInfo, error) {
90 var err error
91
92 // OptionalFields can be zero, hence these checks to ensure we do not populate the wrong values in the wrong spots
93 separatorIndex := strings.Index(mountString, "-")
94 if separatorIndex == -1 {
95 return nil, fmt.Errorf("no separator found in mountinfo string: %s", mountString)
96 }
97 beforeFields := strings.Fields(mountString[:separatorIndex])
98 afterFields := strings.Fields(mountString[separatorIndex+1:])
99 if (len(beforeFields) + len(afterFields)) < 7 {
100 return nil, fmt.Errorf("too few fields")
101 }
102
103 mount := &MountInfo{
104 MajorMinorVer: getStringSliceElement(beforeFields, 2, ""),
105 Root: getStringSliceElement(beforeFields, 3, ""),
106 MountPoint: getStringSliceElement(beforeFields, 4, ""),
107 Options: mountOptionsParser(getStringSliceElement(beforeFields, 5, "")),
108 OptionalFields: nil,
109 FSType: getStringSliceElement(afterFields, 0, ""),
110 Source: getStringSliceElement(afterFields, 1, ""),
111 SuperOptions: mountOptionsParser(getStringSliceElement(afterFields, 2, "")),
112 }
113
114 mount.MountId, err = strconv.Atoi(getStringSliceElement(beforeFields, 0, ""))
115 if err != nil {
116 return nil, fmt.Errorf("failed to parse mount ID")
117 }
118 mount.ParentId, err = strconv.Atoi(getStringSliceElement(beforeFields, 1, ""))
119 if err != nil {
120 return nil, fmt.Errorf("failed to parse parent ID")
121 }
122 // Has optional fields, which is a space separated list of values.
123 // Example: shared:2 master:7
124 if len(beforeFields) > 6 {
125 mount.OptionalFields = make(map[string]string)
126 optionalFields := beforeFields[6:]
127 for _, field := range optionalFields {
128 optionSplit := strings.Split(field, ":")
129 target, value := optionSplit[0], ""
130 if len(optionSplit) == 2 {
131 value = optionSplit[1]
132 }
133 // Checks if the 'keys' in the optional fields in the mountinfo line are acceptable.
134 // Allowed 'keys' are shared, master, propagate_from, unbindable.
135 if _, ok := validOptionalFields[target]; ok {
136 mount.OptionalFields[target] = value
137 }
138 }
139 }
140 return mount, nil
141}
142
143// Parses the mount options, superblock options.
144func mountOptionsParser(mountOptions string) map[string]string {
145 opts := make(map[string]string)
146 options := strings.Split(mountOptions, ",")
147 for _, opt := range options {
148 splitOption := strings.Split(opt, "=")
149 if len(splitOption) < 2 {
150 key := splitOption[0]
151 opts[key] = ""
152 } else {
153 key, value := splitOption[0], splitOption[1]
154 opts[key] = value
155 }
156 }
157 return opts
158}
159
160// Retrieves mountinfo information from `/proc/self/mountinfo`.
161func GetMounts() ([]*MountInfo, error) {
162 f, err := os.Open("/proc/self/mountinfo")
163 if err != nil {
164 return nil, err
165 }
166 defer f.Close()
167 return parseMountInfo(f)
168}
169
170// Retrieves mountinfo information from a processes' `/proc/<pid>/mountinfo`.
171func GetProcMounts(pid int) ([]*MountInfo, error) {
172 f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
173 if err != nil {
174 return nil, err
175 }
176 defer f.Close()
177 return parseMountInfo(f)
178}