forked from weaveworks/procspy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
proc.go
125 lines (107 loc) · 2.67 KB
/
proc.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
package procspy
// /proc-based implementation.
import (
"bytes"
"os"
"path/filepath"
"strconv"
"syscall"
)
var (
procRoot = "/proc"
)
// SetProcRoot sets the location of the proc filesystem.
func SetProcRoot(root string) {
procRoot = root
}
// walkProcPid walks over all numerical (PID) /proc entries, and sees if their
// ./fd/* files are symlink to sockets. Returns a map from socket ID (inode)
// to PID. Will return an error if /proc isn't there.
func walkProcPid(buf *bytes.Buffer) (map[uint64]Proc, error) {
fh, err := os.Open(procRoot)
if err != nil {
return nil, err
}
dirNames, err := fh.Readdirnames(-1)
fh.Close()
if err != nil {
return nil, err
}
var (
res = map[uint64]Proc{}
namespaces = map[uint64]struct{}{}
stat syscall.Stat_t
)
for _, dirName := range dirNames {
pid, err := strconv.ParseUint(dirName, 10, 0)
if err != nil {
// Not a number, so not a PID subdir.
continue
}
fdBase := filepath.Join(procRoot, dirName, "fd")
dfh, err := os.Open(fdBase)
if err != nil {
// Process is be gone by now, or we don't have access.
continue
}
fdNames, err := dfh.Readdirnames(-1)
dfh.Close()
if err != nil {
continue
}
// Read network namespace, and if we haven't seen it before,
// read /proc/<pid>/net/tcp
err = syscall.Lstat(filepath.Join(procRoot, dirName, "/ns/net"), &stat)
if err != nil {
continue
}
if _, ok := namespaces[stat.Ino]; !ok {
namespaces[stat.Ino] = struct{}{}
readFile(filepath.Join(procRoot, dirName, "/net/tcp"), buf)
readFile(filepath.Join(procRoot, dirName, "/net/tcp6"), buf)
}
var name string
for _, fdName := range fdNames {
// Direct use of syscall.Stat() to save garbage.
err = syscall.Stat(filepath.Join(fdBase, fdName), &stat)
if err != nil {
continue
}
// We want sockets only.
if stat.Mode&syscall.S_IFMT != syscall.S_IFSOCK {
continue
}
if name == "" {
if name = procName(filepath.Join(procRoot, dirName)); name == "" {
// Process might be gone by now
break
}
}
res[stat.Ino] = Proc{
PID: uint(pid),
Name: name,
}
}
}
return res, nil
}
// procName does a pid->name lookup.
func procName(base string) string {
b, err := os.ReadFile(filepath.Join(base, "/comm"))
if err == nil && len(b) > 1 {
return string(b[:len(b)-1])
}
return ""
}
// readFile reads an arbitrary file into a buffer. It's a variable so it can
// be overwritten for benchmarks. That's bad practice and we should change it
// to be a dependency.
var readFile = func(filename string, buf *bytes.Buffer) error {
f, err := os.Open(filename)
if err != nil {
return err
}
_, err = buf.ReadFrom(f)
f.Close()
return err
}