forked from cloudfoundry/gosigar
-
Notifications
You must be signed in to change notification settings - Fork 77
/
Copy pathutil.go
263 lines (222 loc) · 6.78 KB
/
util.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
package cgroup
import (
"bufio"
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
)
var (
// ErrCgroupsMissing indicates the /proc/cgroups was not found. This means
// that cgroups were disabled at compile time (CONFIG_CGROUPS=n) or that
// an invalid rootfs path was given.
ErrCgroupsMissing = errors.New("cgroups not found or unsupported by OS")
// ErrInvalidFormat indicates a malformed key/value pair on a line.
ErrInvalidFormat = errors.New("error invalid key/value format")
)
// mountinfo represents a subset of the fields containing /proc/[pid]/mountinfo.
type mountinfo struct {
mountpoint string
filesystemType string
superOptions []string
}
// Parses a cgroup param and returns the key name and value.
func parseCgroupParamKeyValue(t string) (string, uint64, error) {
parts := strings.Fields(t)
if len(parts) != 2 {
return "", 0, ErrInvalidFormat
}
value, err := parseUint([]byte(parts[1]))
if err != nil {
return "", 0, fmt.Errorf("unable to convert param value (%q) to uint64: %v", parts[1], err)
}
return parts[0], value, nil
}
// parseUintFromFile reads a single uint value from a file.
func parseUintFromFile(path ...string) (uint64, error) {
value, err := ioutil.ReadFile(filepath.Join(path...))
if err != nil {
// Not all features are implemented/enabled by each OS.
if os.IsNotExist(err) {
return 0, nil
}
return 0, err
}
return parseUint(value)
}
// parseUint reads a single uint value. It will trip any whitespace before
// attempting to parse string. If the value is negative it will return 0.
func parseUint(value []byte) (uint64, error) {
strValue := string(bytes.TrimSpace(value))
uintValue, err := strconv.ParseUint(strValue, 10, 64)
if err != nil {
// Munge negative values to 0.
intValue, intErr := strconv.ParseInt(strValue, 10, 64)
if intErr == nil && intValue < 0 {
return 0, nil
} else if intErr != nil && intErr.(*strconv.NumError).Err == strconv.ErrRange && intValue < 0 {
return 0, nil
}
return 0, err
}
return uintValue, nil
}
// parseMountinfoLine parses a line from the /proc/[pid]/mountinfo file on
// Linux. The format of the line is specified in section 3.5 of
// https://www.kernel.org/doc/Documentation/filesystems/proc.txt.
func parseMountinfoLine(line string) (mountinfo, error) {
mount := mountinfo{}
fields := strings.Fields(line)
if len(fields) < 10 {
return mount, fmt.Errorf("invalid mountinfo line, expected at least "+
"10 fields but got %d from line='%s'", len(fields), line)
}
mount.mountpoint = fields[4]
var seperatorIndex int
for i, value := range fields {
if value == "-" {
seperatorIndex = i
break
}
}
if fields[seperatorIndex] != "-" {
return mount, fmt.Errorf("invalid mountinfo line, separator ('-') not "+
"found in line='%s'", line)
}
if len(fields)-seperatorIndex-1 < 3 {
return mount, fmt.Errorf("invalid mountinfo line, expected at least "+
"3 fields after seperator but got %d from line='%s'",
len(fields)-seperatorIndex-1, line)
}
fields = fields[seperatorIndex+1:]
mount.filesystemType = fields[0]
mount.superOptions = strings.Split(fields[2], ",")
return mount, nil
}
// SupportedSubsystems returns the subsystems that are supported by the
// kernel. The returned map contains a entry for each subsystem.
func SupportedSubsystems(rootfsMountpoint string) (map[string]struct{}, error) {
if rootfsMountpoint == "" {
rootfsMountpoint = "/"
}
cgroups, err := os.Open(filepath.Join(rootfsMountpoint, "proc", "cgroups"))
if err != nil {
if os.IsNotExist(err) {
return nil, ErrCgroupsMissing
}
return nil, err
}
defer cgroups.Close()
subsystemSet := map[string]struct{}{}
sc := bufio.NewScanner(cgroups)
for sc.Scan() {
line := sc.Text()
// Ignore the header.
if len(line) > 0 && line[0] == '#' {
continue
}
// Parse the cgroup subsystems.
// Format: subsys_name hierarchy num_cgroups enabled
// Example: cpuset 4 1 1
fields := strings.Fields(line)
if len(fields) == 0 {
continue
}
// Check the enabled flag.
if len(fields) > 3 {
enabled := fields[3]
if enabled == "0" {
// Ignore cgroup subsystems that are disabled (via the
// cgroup_disable kernel command-line boot parameter).
continue
}
}
subsystem := fields[0]
subsystemSet[subsystem] = struct{}{}
}
return subsystemSet, sc.Err()
}
// SubsystemMountpoints returns the mountpoints for each of the given subsystems.
// The returned map contains the subsystem name as a key and the value is the
// mountpoint.
func SubsystemMountpoints(rootfsMountpoint string, subsystems map[string]struct{}) (map[string]string, error) {
if rootfsMountpoint == "" {
rootfsMountpoint = "/"
}
mountinfo, err := os.Open(filepath.Join(rootfsMountpoint, "proc", "self", "mountinfo"))
if err != nil {
return nil, err
}
defer mountinfo.Close()
mounts := map[string]string{}
sc := bufio.NewScanner(mountinfo)
for sc.Scan() {
// https://www.kernel.org/doc/Documentation/filesystems/proc.txt
// Example:
// 25 21 0:20 / /cgroup/cpu rw,relatime - cgroup cgroup rw,cpu
line := strings.TrimSpace(sc.Text())
if line == "" {
continue
}
mount, err := parseMountinfoLine(line)
if err != nil {
return nil, err
}
if mount.filesystemType != "cgroup" {
continue
}
if !strings.HasPrefix(mount.mountpoint, rootfsMountpoint) {
continue
}
for _, opt := range mount.superOptions {
// Sometimes the subsystem name is written like "name=blkio".
fields := strings.SplitN(opt, "=", 2)
if len(fields) > 1 {
opt = fields[1]
}
// Test if option is a subsystem name.
if _, found := subsystems[opt]; found {
// Add the subsystem mount if it does not already exist.
if _, exists := mounts[opt]; !exists {
mounts[opt] = mount.mountpoint
}
}
}
}
return mounts, sc.Err()
}
// ProcessCgroupPaths returns the cgroups to which a process belongs and the
// pathname of the cgroup relative to the mountpoint of the subsystem.
func ProcessCgroupPaths(rootfsMountpoint string, pid int) (map[string]string, error) {
if rootfsMountpoint == "" {
rootfsMountpoint = "/"
}
cgroup, err := os.Open(filepath.Join(rootfsMountpoint, "proc", strconv.Itoa(pid), "cgroup"))
if err != nil {
return nil, err
}
defer cgroup.Close()
paths := map[string]string{}
sc := bufio.NewScanner(cgroup)
for sc.Scan() {
// http://man7.org/linux/man-pages/man7/cgroups.7.html
// Format: hierarchy-ID:subsystem-list:cgroup-path
// Example:
// 2:cpu:/docker/b29faf21b7eff959f64b4192c34d5d67a707fe8561e9eaa608cb27693fba4242
line := sc.Text()
fields := strings.Split(line, ":")
if len(fields) != 3 {
continue
}
path := fields[2]
subsystems := strings.Split(fields[1], ",")
for _, subsystem := range subsystems {
paths[subsystem] = path
}
}
return paths, sc.Err()
}