This repository was archived by the owner on Jan 23, 2023. It is now read-only.
forked from alicebob/procspy
-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathproc.go
138 lines (117 loc) · 2.8 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
126
127
128
129
130
131
132
133
134
135
136
137
138
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 {
fh, err := os.Open(filepath.Join(base, "/comm"))
if err != nil {
return ""
}
name := make([]byte, 64)
l, err := fh.Read(name)
fh.Close()
if err != nil {
return ""
}
if l < 2 {
return ""
}
// drop trailing "\n"
return string(name[:l-1])
}
// 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
}