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