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
14// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
15
16package procfs
17
18import (
19	"bufio"
20	"fmt"
21	"os"
22	"strconv"
23	"strings"
24
25	"golang.org/x/sys/unix"
26)
27
28// ProcMapPermissions contains permission settings read from /proc/[pid]/maps
29type ProcMapPermissions struct {
30	// mapping has the [R]ead flag set
31	Read bool
32	// mapping has the [W]rite flag set
33	Write bool
34	// mapping has the [X]ecutable flag set
35	Execute bool
36	// mapping has the [S]hared flag set
37	Shared bool
38	// mapping is marked as [P]rivate (copy on write)
39	Private bool
40}
41
42// ProcMap contains the process memory-mappings of the process,
43// read from /proc/[pid]/maps
44type ProcMap struct {
45	// The start address of current mapping.
46	StartAddr uintptr
47	// The end address of the current mapping
48	EndAddr uintptr
49	// The permissions for this mapping
50	Perms *ProcMapPermissions
51	// The current offset into the file/fd (e.g., shared libs)
52	Offset int64
53	// Device owner of this mapping (major:minor) in Mkdev format.
54	Dev uint64
55	// The inode of the device above
56	Inode uint64
57	// The file or psuedofile (or empty==anonymous)
58	Pathname string
59}
60
61// parseDevice parses the device token of a line and converts it to a dev_t
62// (mkdev) like structure.
63func parseDevice(s string) (uint64, error) {
64	toks := strings.Split(s, ":")
65	if len(toks) < 2 {
66		return 0, fmt.Errorf("unexpected number of fields")
67	}
68
69	major, err := strconv.ParseUint(toks[0], 16, 0)
70	if err != nil {
71		return 0, err
72	}
73
74	minor, err := strconv.ParseUint(toks[1], 16, 0)
75	if err != nil {
76		return 0, err
77	}
78
79	return unix.Mkdev(uint32(major), uint32(minor)), nil
80}
81
82// parseAddress just converts a hex-string to a uintptr
83func parseAddress(s string) (uintptr, error) {
84	a, err := strconv.ParseUint(s, 16, 0)
85	if err != nil {
86		return 0, err
87	}
88
89	return uintptr(a), nil
90}
91
92// parseAddresses parses the start-end address
93func parseAddresses(s string) (uintptr, uintptr, error) {
94	toks := strings.Split(s, "-")
95	if len(toks) < 2 {
96		return 0, 0, fmt.Errorf("invalid address")
97	}
98
99	saddr, err := parseAddress(toks[0])
100	if err != nil {
101		return 0, 0, err
102	}
103
104	eaddr, err := parseAddress(toks[1])
105	if err != nil {
106		return 0, 0, err
107	}
108
109	return saddr, eaddr, nil
110}
111
112// parsePermissions parses a token and returns any that are set.
113func parsePermissions(s string) (*ProcMapPermissions, error) {
114	if len(s) < 4 {
115		return nil, fmt.Errorf("invalid permissions token")
116	}
117
118	perms := ProcMapPermissions{}
119	for _, ch := range s {
120		switch ch {
121		case 'r':
122			perms.Read = true
123		case 'w':
124			perms.Write = true
125		case 'x':
126			perms.Execute = true
127		case 'p':
128			perms.Private = true
129		case 's':
130			perms.Shared = true
131		}
132	}
133
134	return &perms, nil
135}
136
137// parseProcMap will attempt to parse a single line within a proc/[pid]/maps
138// buffer.
139func parseProcMap(text string) (*ProcMap, error) {
140	fields := strings.Fields(text)
141	if len(fields) < 5 {
142		return nil, fmt.Errorf("truncated procmap entry")
143	}
144
145	saddr, eaddr, err := parseAddresses(fields[0])
146	if err != nil {
147		return nil, err
148	}
149
150	perms, err := parsePermissions(fields[1])
151	if err != nil {
152		return nil, err
153	}
154
155	offset, err := strconv.ParseInt(fields[2], 16, 0)
156	if err != nil {
157		return nil, err
158	}
159
160	device, err := parseDevice(fields[3])
161	if err != nil {
162		return nil, err
163	}
164
165	inode, err := strconv.ParseUint(fields[4], 10, 0)
166	if err != nil {
167		return nil, err
168	}
169
170	pathname := ""
171
172	if len(fields) >= 5 {
173		pathname = strings.Join(fields[5:], " ")
174	}
175
176	return &ProcMap{
177		StartAddr: saddr,
178		EndAddr:   eaddr,
179		Perms:     perms,
180		Offset:    offset,
181		Dev:       device,
182		Inode:     inode,
183		Pathname:  pathname,
184	}, nil
185}
186
187// ProcMaps reads from /proc/[pid]/maps to get the memory-mappings of the
188// process.
189func (p Proc) ProcMaps() ([]*ProcMap, error) {
190	file, err := os.Open(p.path("maps"))
191	if err != nil {
192		return nil, err
193	}
194	defer file.Close()
195
196	maps := []*ProcMap{}
197	scan := bufio.NewScanner(file)
198
199	for scan.Scan() {
200		m, err := parseProcMap(scan.Text())
201		if err != nil {
202			return nil, err
203		}
204
205		maps = append(maps, m)
206	}
207
208	return maps, nil
209}
210