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