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
|
package pidpicker
import (
"bytes"
"fmt"
"io/fs"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
)
// ProcessInfo is the metadata shown in the PID picker list.
type ProcessInfo struct {
Pid int
Comm string
Cmdline string
}
// ScanProcesses returns process metadata from /proc.
func ScanProcesses() ([]ProcessInfo, error) {
return scanProcessesFrom("/proc")
}
func scanProcessesFrom(procRoot string) ([]ProcessInfo, error) {
entries, err := os.ReadDir(procRoot)
if err != nil {
return nil, fmt.Errorf("read proc root %q: %w", procRoot, err)
}
processes := make([]ProcessInfo, 0, len(entries))
for _, entry := range entries {
process, ok := readProcessInfo(procRoot, entry)
if !ok {
continue
}
processes = append(processes, process)
}
sort.Slice(processes, func(i, j int) bool {
return processes[i].Pid < processes[j].Pid
})
return processes, nil
}
func readProcessInfo(procRoot string, entry fs.DirEntry) (ProcessInfo, bool) {
if !entry.IsDir() {
return ProcessInfo{}, false
}
pid, err := strconv.Atoi(entry.Name())
if err != nil {
return ProcessInfo{}, false
}
statPath := filepath.Join(procRoot, entry.Name(), "stat")
statData, err := os.ReadFile(statPath)
if err != nil {
return ProcessInfo{}, false
}
comm, err := parseCommFromStat(string(statData))
if err != nil {
return ProcessInfo{}, false
}
cmdlinePath := filepath.Join(procRoot, entry.Name(), "cmdline")
cmdlineData, err := os.ReadFile(cmdlinePath)
if err != nil {
cmdlineData = nil
}
return ProcessInfo{
Pid: pid,
Comm: comm,
Cmdline: normalizeCmdline(cmdlineData),
}, true
}
func parseCommFromStat(statLine string) (string, error) {
open := strings.IndexByte(statLine, '(')
close := strings.LastIndexByte(statLine, ')')
if open < 0 || close < 0 || close <= open+1 {
return "", fmt.Errorf("invalid stat line")
}
comm := statLine[open+1 : close]
if strings.TrimSpace(comm) == "" {
return "", fmt.Errorf("empty comm in stat line")
}
return comm, nil
}
func normalizeCmdline(raw []byte) string {
if len(raw) == 0 {
return ""
}
trimmed := bytes.TrimRight(raw, "\x00")
if len(trimmed) == 0 {
return ""
}
parts := bytes.Split(trimmed, []byte{0})
out := make([]string, 0, len(parts))
for _, part := range parts {
if len(part) == 0 {
continue
}
out = append(out, string(part))
}
return strings.Join(out, " ")
}
|