1//go:build darwin && arm64
2
3package platform
4
5import (
6 "bytes"
7 "fmt"
8 "runtime"
9 "sync"
10 "syscall"
11 "time"
12 "unsafe"
13)
14
15const (
16 ctlKern = 1
17 kernProc = 14
18 kernProcAll = 0
19 kernProcPID = 1
20 kernProcArgs2 = 49
21 procInfoCallPIDInfo = 2
22 procPIDVnodePathInfo = 9
23 darwinKinfoProcSize = 0x288
24 darwinVnodePathInfoLen = 2352
25 maxDarwinPID = 1<<31 - 1
26 rawSysctlMaxRetries = 3
27)
28
29// ProcessInspector implements ports.ProcessInspector using Darwin sysctl and
30// proc_pidinfo interfaces.
31type ProcessInspector struct {
32 mu sync.Mutex
33 cachedRecords []processRecord
34 cachedRecordsUntil time.Time
35}
36
37// NewProcessInspector returns a Darwin process inspector.
38func NewProcessInspector() *ProcessInspector { return &ProcessInspector{} }
39
40func (p *ProcessInspector) Cwd(pid int) (string, error) { return ProcessCwd(pid) }
41func (p *ProcessInspector) Comm(pid int) (string, error) { return ProcessComm(pid) }
42func (p *ProcessInspector) Argv(pid int) ([]string, error) { return ProcessArgv(pid) }
43func (p *ProcessInspector) GroupArgv(pgid int, shellPid int) ([]string, error) {
44 recs, err := p.processRecords()
45 if err != nil {
46 return nil, err
47 }
48 pid, ok := selectProcessGroupPID(recs, pgid, shellPid)
49 if !ok {
50 return nil, nil
51 }
52 return p.Argv(pid)
53}
54
55func (p *ProcessInspector) processRecords() ([]processRecord, error) {
56 p.mu.Lock()
57 defer p.mu.Unlock()
58 now := time.Now()
59 if now.Before(p.cachedRecordsUntil) {
60 return p.cachedRecords, nil
61 }
62 recs, err := readDarwinProcessRecords()
63 if err != nil {
64 return nil, err
65 }
66 p.cachedRecords = recs
67 p.cachedRecordsUntil = now.Add(processRecordCacheTTL)
68 return recs, nil
69}
70
71// ProcessCwd returns the current working directory from proc_pidinfo.
72func ProcessCwd(pid int) (string, error) {
73 pid32, ok := darwinPID(pid)
74 if !ok {
75 return "", fmt.Errorf("process cwd: invalid pid %d", pid)
76 }
77 var info procVnodePathInfo
78 n, _, errno := syscall.Syscall6(
79 syscall.SYS_PROC_INFO,
80 procInfoCallPIDInfo,
81 uintptr(pid32),
82 procPIDVnodePathInfo,
83 0,
84 uintptr(unsafe.Pointer(&info)),
85 unsafe.Sizeof(info),
86 )
87 runtime.KeepAlive(&info)
88 if errno != 0 {
89 return "", fmt.Errorf("process cwd: proc_pidinfo pid %d: %w", pid, errno)
90 }
91 if n != unsafe.Sizeof(info) {
92 return "", fmt.Errorf("process cwd: proc_pidinfo pid %d returned %d bytes; want %d", pid, n, unsafe.Sizeof(info))
93 }
94 path := info.Cdir.Path[:]
95 if end := bytes.IndexByte(path, 0); end >= 0 {
96 path = path[:end]
97 }
98 if len(path) == 0 {
99 return "", fmt.Errorf("process cwd: empty cwd for pid %d", pid)
100 }
101 return string(path), nil
102}
103
104// ProcessComm returns the p_comm field from KERN_PROC_PID data.
105func ProcessComm(pid int) (string, error) {
106 pid32, ok := darwinPID(pid)
107 if !ok {
108 return "", fmt.Errorf("process comm: invalid pid %d", pid)
109 }
110 data, err := rawSysctl([]int32{ctlKern, kernProc, kernProcPID, pid32})
111 if err != nil {
112 return "", fmt.Errorf("process comm: sysctl pid %d: %w", pid, err)
113 }
114 if len(data) != darwinKinfoProcSize {
115 return "", fmt.Errorf("process comm: sysctl pid %d returned %d bytes; want %d", pid, len(data), darwinKinfoProcSize)
116 }
117 proc := *(*kinfoProc)(unsafe.Pointer(&data[0]))
118 runtime.KeepAlive(data)
119 comm := proc.Proc.Comm[:]
120 if end := bytes.IndexByte(comm, 0); end >= 0 {
121 comm = comm[:end]
122 }
123 if len(comm) == 0 {
124 return "", fmt.Errorf("process comm: empty comm for pid %d", pid)
125 }
126 return string(comm), nil
127}
128
129// ProcessArgv returns argv from KERN_PROCARGS2.
130func ProcessArgv(pid int) ([]string, error) {
131 pid32, ok := darwinPID(pid)
132 if !ok {
133 return nil, fmt.Errorf("process argv: invalid pid %d", pid)
134 }
135 data, err := rawSysctl([]int32{ctlKern, kernProcArgs2, pid32})
136 if err != nil {
137 return nil, fmt.Errorf("process argv: sysctl pid %d: %w", pid, err)
138 }
139 argv, err := parseKernProcArgs2(data)
140 if err != nil {
141 return nil, fmt.Errorf("process argv: pid %d: %w", pid, err)
142 }
143 return argv, nil
144}
145
146// ProcessGroupArgv returns argv for a restorable foreground process in pgid.
147// A bare pane shell (the only process in its foreground group) returns nil argv.
148func ProcessGroupArgv(pgid int, shellPid int) ([]string, error) {
149 return NewProcessInspector().GroupArgv(pgid, shellPid)
150}
151
152func readDarwinProcessRecords() ([]processRecord, error) {
153 data, err := rawSysctl([]int32{ctlKern, kernProc, kernProcAll})
154 if err != nil {
155 return nil, err
156 }
157 if len(data)%darwinKinfoProcSize != 0 {
158 return nil, fmt.Errorf("kern_proc_all returned %d bytes, not a multiple of %d", len(data), darwinKinfoProcSize)
159 }
160 recs := make([]processRecord, 0, len(data)/darwinKinfoProcSize)
161 for len(data) > 0 {
162 proc := (*kinfoProc)(unsafe.Pointer(&data[0]))
163 recs = append(recs, processRecord{pid: int(proc.Proc.Pid), pgrp: int(proc.Eproc.Pgid)})
164 data = data[darwinKinfoProcSize:]
165 }
166 runtime.KeepAlive(data)
167 return recs, nil
168}
169
170func darwinPID(pid int) (int32, bool) {
171 if pid <= 0 || pid > maxDarwinPID {
172 return 0, false
173 }
174 return int32(pid), true
175}
176
177// rawSysctl reads a binary sysctl MIB, retrying if a growing result races the
178// size query. It is the stdlib syscall equivalent of sysctl(3).
179func rawSysctl(mib []int32) ([]byte, error) {
180 return rawSysctlWith(mib, sysctl)
181}
182
183func rawSysctlWith(mib []int32, call func([]int32, *byte, *uintptr) error) ([]byte, error) {
184 for retry := 0; retry <= rawSysctlMaxRetries; retry++ {
185 var size uintptr
186 if err := call(mib, nil, &size); err != nil {
187 return nil, err
188 }
189 if size == 0 {
190 return nil, nil
191 }
192 buf := make([]byte, size)
193 n := size
194 if err := call(mib, &buf[0], &n); err != nil {
195 if err == syscall.ENOMEM && retry < rawSysctlMaxRetries {
196 continue
197 }
198 if err == syscall.ENOMEM {
199 return nil, fmt.Errorf("sysctl %v: result grew after %d retries: %w", mib, rawSysctlMaxRetries, err)
200 }
201 return nil, err
202 }
203 return buf[:n], nil
204 }
205 panic("unreachable")
206}
207
208func sysctl(mib []int32, old *byte, oldlen *uintptr) error {
209 _, _, errno := syscall.Syscall6(
210 syscall.SYS___SYSCTL,
211 uintptr(unsafe.Pointer(&mib[0])),
212 uintptr(len(mib)),
213 uintptr(unsafe.Pointer(old)),
214 uintptr(unsafe.Pointer(oldlen)),
215 0,
216 0,
217 )
218 runtime.KeepAlive(mib)
219 if errno != 0 {
220 return errno
221 }
222 return nil
223}
224
225// The following Darwin/arm64 ABI layouts are derived from Apple XNU
226// bsd/sys/proc.h and bsd/sys/proc_info.h, via golang.org/x/sys/unix
227// ztypes_darwin_arm64.go (BSD-3-Clause). Pointer fields are uintptr so this
228// package has no dependency on x/sys and never lets kernel pointers escape.
229type kinfoProc struct {
230 Proc externProc
231 Eproc eproc
232}
233
234type eproc struct {
235 Paddr uintptr
236 Sess uintptr
237 Pcred pcred
238 Ucred ucred
239 Vm vmspace
240 Ppid int32
241 Pgid int32
242 Jobc int16
243 Tdev int32
244 Tpgid int32
245 Tsess uintptr
246 Wmesg [8]byte
247 Xsize int32
248 Xrssize int16
249 Xccount int16
250 Xswrss int16
251 Flag int32
252 Login [12]byte
253 Spare [4]int32
254 _ [4]byte
255}
256
257type externProc struct {
258 Un [16]byte
259 Vmspace uintptr
260 Sigacts uintptr
261 Flag int32
262 Stat int8
263 Pid int32
264 Oppid int32
265 Dupfd int32
266 _ [4]byte
267 UserStack uintptr
268 ExitThread uintptr
269 Debugger int32
270 Sigwait int32
271 Estcpu uint32
272 Cpticks int32
273 Pctcpu uint32
274 _ [4]byte
275 Wchan uintptr
276 Wmesg uintptr
277 Swtime uint32
278 Slptime uint32
279 Realtimer itimerval
280 Rtime timeval
281 Uticks uint64
282 Sticks uint64
283 Iticks uint64
284 Traceflag int32
285 _ [4]byte
286 Tracep uintptr
287 Siglist int32
288 _ [4]byte
289 Textvp uintptr
290 Holdcnt int32
291 Sigmask uint32
292 Sigignore uint32
293 Sigcatch uint32
294 Priority uint8
295 Usrpri uint8
296 Nice int8
297 Comm [17]byte
298 _ [4]byte
299 Pgrp uintptr
300 Addr uintptr
301 Xstat uint16
302 Acflag uint16
303 _ [4]byte
304 Ru uintptr
305}
306
307type itimerval struct {
308 Interval timeval
309 Value timeval
310}
311
312type timeval struct {
313 Sec int64
314 Usec int32
315 _ [4]byte
316}
317
318type pcred struct {
319 Lock [72]int8
320 Ucred uintptr
321 Ruid uint32
322 Svuid uint32
323 Rgid uint32
324 Svgid uint32
325 Refcnt int32
326 _ [4]byte
327}
328
329type ucred struct {
330 Ref int32
331 UID uint32
332 Ngroups int16
333 Groups [16]uint32
334}
335
336type vmspace struct {
337 Dummy int32
338 Dummy2 uintptr
339 Dummy3 [5]int32
340 Dummy4 [3]uintptr
341}
342
343type vinfoStat struct {
344 Dev uint32
345 Mode uint16
346 Nlink uint16
347 Ino uint64
348 UID uint32
349 GID uint32
350 Atime int64
351 Atimensec int64
352 Mtime int64
353 Mtimensec int64
354 Ctime int64
355 Ctimensec int64
356 Birthtime int64
357 Birthtimensec int64
358 Size int64
359 Blocks int64
360 Blksize int32
361 Flags uint32
362 Gen uint32
363 Rdev uint32
364 Qspare [2]int64
365}
366
367type fsid struct{ Val [2]int32 }
368
369type vnodeInfo struct {
370 Stat vinfoStat
371 Type int32
372 Pad int32
373 Fsid fsid
374}
375
376type vnodeInfoPath struct {
377 Vi vnodeInfo
378 Path [1024]byte
379}
380
381type procVnodePathInfo struct {
382 Cdir vnodeInfoPath
383 Rdir vnodeInfoPath
384}
385
386func init() {
387 if unsafe.Sizeof(kinfoProc{}) != darwinKinfoProcSize {
388 panic(fmt.Sprintf("darwin kinfo_proc ABI size = %d; want %d", unsafe.Sizeof(kinfoProc{}), darwinKinfoProcSize))
389 }
390 if unsafe.Sizeof(procVnodePathInfo{}) != darwinVnodePathInfoLen {
391 panic(fmt.Sprintf("darwin proc_vnodepathinfo ABI size = %d; want %d", unsafe.Sizeof(procVnodePathInfo{}), darwinVnodePathInfoLen))
392 }
393}