main ece7682

process_darwin_arm64.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close