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
//go:build linux
package devsup
import (
"bytes"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
)
type procInfo struct {
pid int
ppid int
pgid int
ticks uint64
state byte
}
func readProcInfo(pid int) (procInfo, bool) {
b, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "stat"))
if err != nil {
return procInfo{}, false
}
i := bytes.LastIndexByte(b, ')')
if i < 0 {
return procInfo{}, false
}
f := strings.Fields(string(b[i+1:]))
if len(f) < 20 || len(f[0]) != 1 {
return procInfo{}, false
}
ppid, _ := strconv.Atoi(f[1])
pgid, _ := strconv.Atoi(f[2])
ticks, _ := strconv.ParseUint(f[19], 10, 64)
return procInfo{pid: pid, ppid: ppid, pgid: pgid, ticks: ticks, state: f[0][0]}, true
}
func ownPIDs() []int {
entries, err := os.ReadDir("/proc")
if err != nil {
return nil
}
uid := uint32(os.Getuid())
var out []int
for _, e := range entries {
pid, err := strconv.Atoi(e.Name())
if err != nil {
continue
}
fi, err := e.Info()
if err != nil {
continue
}
st, ok := fi.Sys().(*syscall.Stat_t)
if !ok || st.Uid != uid {
continue
}
out = append(out, pid)
}
return out
}
func procTable() []procInfo {
var out []procInfo
for _, pid := range ownPIDs() {
if p, ok := readProcInfo(pid); ok && p.state != 'Z' && p.state != 'X' {
out = append(out, p)
}
}
return out
}
func procEnv(pid int, keys ...string) map[string]string {
b, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "environ"))
if err != nil {
return nil
}
out := map[string]string{}
for kv := range bytes.SplitSeq(b, []byte{0}) {
for _, k := range keys {
if bytes.HasPrefix(kv, []byte(k+"=")) {
out[k] = string(kv[len(k)+1:])
}
}
}
return out
}
func childrenOf(pid int) []int {
tasks, err := os.ReadDir(filepath.Join("/proc", strconv.Itoa(pid), "task"))
if err != nil {
return nil
}
var out []int
for _, t := range tasks {
b, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "task", t.Name(), "children"))
if err != nil {
continue
}
for f := range strings.FieldsSeq(string(b)) {
if n, err := strconv.Atoi(f); err == nil {
out = append(out, n)
}
}
}
return out
}
type attribution struct {
unit string
invocation string
}
func attributedTo(devsupID string) map[int]attribution {
out := map[int]attribution{}
for _, p := range procTable() {
env := procEnv(p.pid, EnvDevsup, EnvUnit, EnvInvocation)
if env[EnvDevsup] != devsupID || env[EnvUnit] == "" {
continue
}
out[p.pid] = attribution{unit: env[EnvUnit], invocation: env[EnvInvocation]}
}
return out
}