//go:build darwin && cgo // +build darwin,cgo package process // #include // #include // #include // #include // #include // #include // #include import "C" import ( "bytes" "context" "fmt" "strings" "syscall" "unsafe" "github.com/shirou/gopsutil/v3/cpu" ) var ( argMax int timescaleToNanoSeconds float64 ) func init() { argMax = getArgMax() timescaleToNanoSeconds = getTimeScaleToNanoSeconds() } func getArgMax() int { var ( mib = [...]C.int{C.CTL_KERN, C.KERN_ARGMAX} argmax C.int size C.size_t = C.ulong(unsafe.Sizeof(argmax)) ) retval := C.sysctl(&mib[0], 2, unsafe.Pointer(&argmax), &size, C.NULL, 0) if retval == 0 { return int(argmax) } return 0 } func getTimeScaleToNanoSeconds() float64 { var timeBaseInfo C.struct_mach_timebase_info C.mach_timebase_info(&timeBaseInfo) return float64(timeBaseInfo.numer) / float64(timeBaseInfo.denom) } func (p *Process) ExeWithContext(ctx context.Context) (string, error) { var c C.char // need a var for unsafe.Sizeof need a var const bufsize = C.PROC_PIDPATHINFO_MAXSIZE * unsafe.Sizeof(c) buffer := (*C.char)(C.malloc(C.size_t(bufsize))) defer C.free(unsafe.Pointer(buffer)) ret, err := C.proc_pidpath(C.int(p.Pid), unsafe.Pointer(buffer), C.uint32_t(bufsize)) if err != nil { return "", err } if ret <= 0 { return "", fmt.Errorf("unknown error: proc_pidpath returned %d", ret) } return C.GoString(buffer), nil } // CwdWithContext retrieves the Current Working Directory for the given process. // It uses the proc_pidinfo from libproc and will only work for processes the // EUID can access. Otherwise "operation not permitted" will be returned as the // error. // Note: This might also work for other *BSD OSs. func (p *Process) CwdWithContext(ctx context.Context) (string, error) { const vpiSize = C.sizeof_struct_proc_vnodepathinfo vpi := (*C.struct_proc_vnodepathinfo)(C.malloc(vpiSize)) defer C.free(unsafe.Pointer(vpi)) ret, err := C.proc_pidinfo(C.int(p.Pid), C.PROC_PIDVNODEPATHINFO, 0, unsafe.Pointer(vpi), vpiSize) if err != nil { // fmt.Printf("ret: %d %T\n", ret, err) if err == syscall.EPERM { return "", ErrorNotPermitted } return "", err } if ret <= 0 { return "", fmt.Errorf("unknown error: proc_pidinfo returned %d", ret) } if ret != C.sizeof_struct_proc_vnodepathinfo { return "", fmt.Errorf("too few bytes; expected %d, got %d", vpiSize, ret) } return C.GoString(&vpi.pvi_cdir.vip_path[0]), err } func procArgs(pid int32) ([]byte, int, error) { var ( mib = [...]C.int{C.CTL_KERN, C.KERN_PROCARGS2, C.int(pid)} size C.size_t = C.ulong(argMax) nargs C.int result []byte ) procargs := (*C.char)(C.malloc(C.ulong(argMax))) defer C.free(unsafe.Pointer(procargs)) retval, err := C.sysctl(&mib[0], 3, unsafe.Pointer(procargs), &size, C.NULL, 0) if retval == 0 { C.memcpy(unsafe.Pointer(&nargs), unsafe.Pointer(procargs), C.sizeof_int) result = C.GoBytes(unsafe.Pointer(procargs), C.int(size)) // fmt.Printf("size: %d %d\n%s\n", size, nargs, hex.Dump(result)) return result, int(nargs), nil } return nil, 0, err } func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) { return p.cmdlineSliceWithContext(ctx, true) } func (p *Process) cmdlineSliceWithContext(ctx context.Context, fallback bool) ([]string, error) { pargs, nargs, err := procArgs(p.Pid) if err != nil { return nil, err } // The first bytes hold the nargs int, skip it. args := bytes.Split((pargs)[C.sizeof_int:], []byte{0}) var argStr string // The first element is the actual binary/command path. // command := args[0] var argSlice []string // var envSlice []string // All other, non-zero elements are arguments. The first "nargs" elements // are the arguments. Everything else in the slice is then the environment // of the process. for _, arg := range args[1:] { argStr = string(arg[:]) if len(argStr) > 0 { if nargs > 0 { argSlice = append(argSlice, argStr) nargs-- continue } break // envSlice = append(envSlice, argStr) } } return argSlice, err } // cmdNameWithContext returns the command name (including spaces) without any arguments func (p *Process) cmdNameWithContext(ctx context.Context) (string, error) { r, err := p.cmdlineSliceWithContext(ctx, false) if err != nil { return "", err } if len(r) == 0 { return "", nil } return r[0], err } func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) { r, err := p.CmdlineSliceWithContext(ctx) if err != nil { return "", err } return strings.Join(r, " "), err } func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) { const tiSize = C.sizeof_struct_proc_taskinfo ti := (*C.struct_proc_taskinfo)(C.malloc(tiSize)) defer C.free(unsafe.Pointer(ti)) _, err := C.proc_pidinfo(C.int(p.Pid), C.PROC_PIDTASKINFO, 0, unsafe.Pointer(ti), tiSize) if err != nil { return 0, err } return int32(ti.pti_threadnum), nil } func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) { const tiSize = C.sizeof_struct_proc_taskinfo ti := (*C.struct_proc_taskinfo)(C.malloc(tiSize)) defer C.free(unsafe.Pointer(ti)) _, err := C.proc_pidinfo(C.int(p.Pid), C.PROC_PIDTASKINFO, 0, unsafe.Pointer(ti), tiSize) if err != nil { return nil, err } ret := &cpu.TimesStat{ CPU: "cpu", User: float64(ti.pti_total_user) * timescaleToNanoSeconds / 1e9, System: float64(ti.pti_total_system) * timescaleToNanoSeconds / 1e9, } return ret, nil } func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) { const tiSize = C.sizeof_struct_proc_taskinfo ti := (*C.struct_proc_taskinfo)(C.malloc(tiSize)) defer C.free(unsafe.Pointer(ti)) _, err := C.proc_pidinfo(C.int(p.Pid), C.PROC_PIDTASKINFO, 0, unsafe.Pointer(ti), tiSize) if err != nil { return nil, err } ret := &MemoryInfoStat{ RSS: uint64(ti.pti_resident_size), VMS: uint64(ti.pti_virtual_size), Swap: uint64(ti.pti_pageins), } return ret, nil }