1// +build openbsd
2
3package process
4
5import (
6	"bytes"
7	"context"
8	"encoding/binary"
9	"fmt"
10	"io"
11	"os/exec"
12	"path/filepath"
13	"strconv"
14	"strings"
15	"unsafe"
16
17	cpu "github.com/shirou/gopsutil/v3/cpu"
18	"github.com/shirou/gopsutil/v3/internal/common"
19	mem "github.com/shirou/gopsutil/v3/mem"
20	net "github.com/shirou/gopsutil/v3/net"
21	"golang.org/x/sys/unix"
22)
23
24func pidsWithContext(ctx context.Context) ([]int32, error) {
25	var ret []int32
26	procs, err := ProcessesWithContext(ctx)
27	if err != nil {
28		return ret, nil
29	}
30
31	for _, p := range procs {
32		ret = append(ret, p.Pid)
33	}
34
35	return ret, nil
36}
37
38func (p *Process) PpidWithContext(ctx context.Context) (int32, error) {
39	k, err := p.getKProc()
40	if err != nil {
41		return 0, err
42	}
43
44	return k.Ppid, nil
45}
46
47func (p *Process) NameWithContext(ctx context.Context) (string, error) {
48	k, err := p.getKProc()
49	if err != nil {
50		return "", err
51	}
52	name := common.IntToString(k.Comm[:])
53
54	if len(name) >= 15 {
55		cmdlineSlice, err := p.CmdlineSliceWithContext(ctx)
56		if err != nil {
57			return "", err
58		}
59		if len(cmdlineSlice) > 0 {
60			extendedName := filepath.Base(cmdlineSlice[0])
61			if strings.HasPrefix(extendedName, p.name) {
62				name = extendedName
63			} else {
64				name = cmdlineSlice[0]
65			}
66		}
67	}
68
69	return name, nil
70}
71
72func (p *Process) ExeWithContext(ctx context.Context) (string, error) {
73	return "", common.ErrNotImplementedError
74}
75
76func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) {
77	mib := []int32{CTLKern, KernProcArgs, p.Pid, KernProcArgv}
78	buf, _, err := common.CallSyscall(mib)
79
80	if err != nil {
81		return nil, err
82	}
83
84	/* From man sysctl(2):
85	The buffer pointed to by oldp is filled with an array of char
86	pointers followed by the strings themselves. The last char
87	pointer is a NULL pointer. */
88	var strParts []string
89	r := bytes.NewReader(buf)
90	baseAddr := uintptr(unsafe.Pointer(&buf[0]))
91	for {
92		argvp, err := readPtr(r)
93		if err != nil {
94			return nil, err
95		}
96		if argvp == 0 { // check for a NULL pointer
97			break
98		}
99		offset := argvp - baseAddr
100		length := uintptr(bytes.IndexByte(buf[offset:], 0))
101		str := string(buf[offset : offset+length])
102		strParts = append(strParts, str)
103	}
104
105	return strParts, nil
106}
107
108// readPtr reads a pointer data from a given reader. WARNING: only little
109// endian architectures are supported.
110func readPtr(r io.Reader) (uintptr, error) {
111	switch sizeofPtr {
112	case 4:
113		var p uint32
114		if err := binary.Read(r, binary.LittleEndian, &p); err != nil {
115			return 0, err
116		}
117		return uintptr(p), nil
118	case 8:
119		var p uint64
120		if err := binary.Read(r, binary.LittleEndian, &p); err != nil {
121			return 0, err
122		}
123		return uintptr(p), nil
124	default:
125		return 0, fmt.Errorf("unsupported pointer size")
126	}
127}
128
129func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) {
130	argv, err := p.CmdlineSliceWithContext(ctx)
131	if err != nil {
132		return "", err
133	}
134	return strings.Join(argv, " "), nil
135}
136
137func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) {
138	return 0, common.ErrNotImplementedError
139}
140
141func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) {
142	return nil, common.ErrNotImplementedError
143}
144
145func (p *Process) StatusWithContext(ctx context.Context) ([]string, error) {
146	k, err := p.getKProc()
147	if err != nil {
148		return []string{""}, err
149	}
150	var s string
151	switch k.Stat {
152	case SIDL:
153	case SRUN:
154	case SONPROC:
155		s = Running
156	case SSLEEP:
157		s = Sleep
158	case SSTOP:
159		s = Stop
160	case SDEAD:
161		s = Zombie
162	}
163
164	return []string{s}, nil
165}
166
167func (p *Process) ForegroundWithContext(ctx context.Context) (bool, error) {
168	// see https://github.com/shirou/gopsutil/issues/596#issuecomment-432707831 for implementation details
169	pid := p.Pid
170	ps, err := exec.LookPath("ps")
171	if err != nil {
172		return false, err
173	}
174	out, err := invoke.CommandWithContext(ctx, ps, "-o", "stat=", "-p", strconv.Itoa(int(pid)))
175	if err != nil {
176		return false, err
177	}
178	return strings.IndexByte(string(out), '+') != -1, nil
179}
180
181func (p *Process) UidsWithContext(ctx context.Context) ([]int32, error) {
182	k, err := p.getKProc()
183	if err != nil {
184		return nil, err
185	}
186
187	uids := make([]int32, 0, 3)
188
189	uids = append(uids, int32(k.Ruid), int32(k.Uid), int32(k.Svuid))
190
191	return uids, nil
192}
193
194func (p *Process) GidsWithContext(ctx context.Context) ([]int32, error) {
195	k, err := p.getKProc()
196	if err != nil {
197		return nil, err
198	}
199
200	gids := make([]int32, 0, 3)
201	gids = append(gids, int32(k.Rgid), int32(k.Ngroups), int32(k.Svgid))
202
203	return gids, nil
204}
205
206func (p *Process) GroupsWithContext(ctx context.Context) ([]int32, error) {
207	k, err := p.getKProc()
208	if err != nil {
209		return nil, err
210	}
211
212	groups := make([]int32, k.Ngroups)
213	for i := int16(0); i < k.Ngroups; i++ {
214		groups[i] = int32(k.Groups[i])
215	}
216
217	return groups, nil
218}
219
220func (p *Process) TerminalWithContext(ctx context.Context) (string, error) {
221	k, err := p.getKProc()
222	if err != nil {
223		return "", err
224	}
225
226	ttyNr := uint64(k.Tdev)
227
228	termmap, err := getTerminalMap()
229	if err != nil {
230		return "", err
231	}
232
233	return termmap[ttyNr], nil
234}
235
236func (p *Process) NiceWithContext(ctx context.Context) (int32, error) {
237	k, err := p.getKProc()
238	if err != nil {
239		return 0, err
240	}
241	return int32(k.Nice), nil
242}
243
244func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, error) {
245	k, err := p.getKProc()
246	if err != nil {
247		return nil, err
248	}
249	return &IOCountersStat{
250		ReadCount:  uint64(k.Uru_inblock),
251		WriteCount: uint64(k.Uru_oublock),
252	}, nil
253}
254
255func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) {
256	/* not supported, just return 1 */
257	return 1, nil
258}
259
260func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) {
261	k, err := p.getKProc()
262	if err != nil {
263		return nil, err
264	}
265	return &cpu.TimesStat{
266		CPU:    "cpu",
267		User:   float64(k.Uutime_sec) + float64(k.Uutime_usec)/1000000,
268		System: float64(k.Ustime_sec) + float64(k.Ustime_usec)/1000000,
269	}, nil
270}
271
272func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) {
273	k, err := p.getKProc()
274	if err != nil {
275		return nil, err
276	}
277	pageSize, err := mem.GetPageSizeWithContext(ctx)
278	if err != nil {
279		return nil, err
280	}
281
282	return &MemoryInfoStat{
283		RSS: uint64(k.Vm_rssize) * pageSize,
284		VMS: uint64(k.Vm_tsize) + uint64(k.Vm_dsize) +
285			uint64(k.Vm_ssize),
286	}, nil
287}
288
289func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) {
290	pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid)
291	if err != nil {
292		return nil, err
293	}
294	ret := make([]*Process, 0, len(pids))
295	for _, pid := range pids {
296		np, err := NewProcessWithContext(ctx, pid)
297		if err != nil {
298			return nil, err
299		}
300		ret = append(ret, np)
301	}
302	return ret, nil
303}
304
305func (p *Process) ConnectionsWithContext(ctx context.Context) ([]net.ConnectionStat, error) {
306	return nil, common.ErrNotImplementedError
307}
308
309func (p *Process) ConnectionsMaxWithContext(ctx context.Context, max int) ([]net.ConnectionStat, error) {
310	return nil, common.ErrNotImplementedError
311}
312
313func ProcessesWithContext(ctx context.Context) ([]*Process, error) {
314	results := []*Process{}
315
316	buf, length, err := callKernProcSyscall(KernProcAll, 0)
317
318	if err != nil {
319		return results, err
320	}
321
322	// get kinfo_proc size
323	count := int(length / uint64(sizeOfKinfoProc))
324
325	// parse buf to procs
326	for i := 0; i < count; i++ {
327		b := buf[i*sizeOfKinfoProc : (i+1)*sizeOfKinfoProc]
328		k, err := parseKinfoProc(b)
329		if err != nil {
330			continue
331		}
332		p, err := NewProcessWithContext(ctx, int32(k.Pid))
333		if err != nil {
334			continue
335		}
336
337		results = append(results, p)
338	}
339
340	return results, nil
341}
342
343func (p *Process) getKProc() (*KinfoProc, error) {
344	buf, length, err := callKernProcSyscall(KernProcPID, p.Pid)
345	if err != nil {
346		return nil, err
347	}
348	if length != sizeOfKinfoProc {
349		return nil, err
350	}
351
352	k, err := parseKinfoProc(buf)
353	if err != nil {
354		return nil, err
355	}
356	return &k, nil
357}
358
359func callKernProcSyscall(op int32, arg int32) ([]byte, uint64, error) {
360	mib := []int32{CTLKern, KernProc, op, arg, sizeOfKinfoProc, 0}
361	mibptr := unsafe.Pointer(&mib[0])
362	miblen := uint64(len(mib))
363	length := uint64(0)
364	_, _, err := unix.Syscall6(
365		unix.SYS___SYSCTL,
366		uintptr(mibptr),
367		uintptr(miblen),
368		0,
369		uintptr(unsafe.Pointer(&length)),
370		0,
371		0)
372	if err != 0 {
373		return nil, length, err
374	}
375
376	count := int32(length / uint64(sizeOfKinfoProc))
377	mib = []int32{CTLKern, KernProc, op, arg, sizeOfKinfoProc, count}
378	mibptr = unsafe.Pointer(&mib[0])
379	miblen = uint64(len(mib))
380	// get proc info itself
381	buf := make([]byte, length)
382	_, _, err = unix.Syscall6(
383		unix.SYS___SYSCTL,
384		uintptr(mibptr),
385		uintptr(miblen),
386		uintptr(unsafe.Pointer(&buf[0])),
387		uintptr(unsafe.Pointer(&length)),
388		0,
389		0)
390	if err != 0 {
391		return buf, length, err
392	}
393
394	return buf, length, nil
395}
396