From 9494ebdd946bd785b12a144b6f28b3b00dd14292 Mon Sep 17 00:00:00 2001 From: Ralph Schmieder Date: Mon, 10 May 2021 18:41:52 +0200 Subject: [PATCH] Additional Darwin CGO implementation - added CwdWithContext to get the current working dir of a process - added CmdLineWithContext natively w/ support for spaces etc. --- README.rst | 2 +- v3/process/process.go | 1 + v3/process/process_bsd.go | 5 -- v3/process/process_darwin.go | 21 ------ v3/process/process_darwin_cgo.go | 109 +++++++++++++++++++++++++++++ v3/process/process_darwin_nocgo.go | 27 +++++++ v3/process/process_freebsd.go | 4 ++ v3/process/process_openbsd.go | 4 ++ 8 files changed, 146 insertions(+), 27 deletions(-) diff --git a/README.rst b/README.rst index 3d3016c40..11b273e3a 100644 --- a/README.rst +++ b/README.rst @@ -218,7 +218,7 @@ name x x x x x cmdline x x x x create_time x x x status x x x x -cwd x +cwd x x exe x x x x uids x x x x gids x x x x diff --git a/v3/process/process.go b/v3/process/process.go index 1bd3d1c60..673db9ef2 100644 --- a/v3/process/process.go +++ b/v3/process/process.go @@ -20,6 +20,7 @@ var ( invoke common.Invoker = common.Invoke{} ErrorNoChildren = errors.New("process does not have children") ErrorProcessNotRunning = errors.New("process does not exist") + ErrorNotPermitted = errors.New("operation not permitted") ) type Process struct { diff --git a/v3/process/process_bsd.go b/v3/process/process_bsd.go index da90e2070..77e2dc69e 100644 --- a/v3/process/process_bsd.go +++ b/v3/process/process_bsd.go @@ -19,10 +19,6 @@ func (p *Process) TgidWithContext(ctx context.Context) (int32, error) { return 0, common.ErrNotImplementedError } -func (p *Process) CwdWithContext(ctx context.Context) (string, error) { - return "", common.ErrNotImplementedError -} - func (p *Process) IOniceWithContext(ctx context.Context) (int32, error) { return 0, common.ErrNotImplementedError } @@ -73,4 +69,3 @@ func parseKinfoProc(buf []byte) (KinfoProc, error) { err := common.Read(br, binary.LittleEndian, &k) return k, err } - diff --git a/v3/process/process_darwin.go b/v3/process/process_darwin.go index 5a575dc81..e43dbd875 100644 --- a/v3/process/process_darwin.go +++ b/v3/process/process_darwin.go @@ -100,14 +100,6 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) { return name, nil } -func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) { - r, err := callPsWithContext(ctx, "command", p.Pid, false, false) - if err != nil { - return "", err - } - return strings.Join(r[0], " "), err -} - // cmdNameWithContext returns the command name (including spaces) without any arguments func (p *Process) cmdNameWithContext(ctx context.Context) ([]string, error) { r, err := callPsWithContext(ctx, "command", p.Pid, false, true) @@ -117,19 +109,6 @@ func (p *Process) cmdNameWithContext(ctx context.Context) ([]string, error) { return r[0], err } -// CmdlineSliceWithContext returns the command line arguments of the process as a slice with each -// element being an argument. Because of current deficiencies in the way that the command -// line arguments are found, single arguments that have spaces in the will actually be -// reported as two separate items. In order to do something better CGO would be needed -// to use the native darwin functions. -func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) { - r, err := callPsWithContext(ctx, "command", p.Pid, false, false) - if err != nil { - return nil, err - } - return r[0], err -} - func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) { r, err := callPsWithContext(ctx, "etime", p.Pid, false, false) if err != nil { diff --git a/v3/process/process_darwin_cgo.go b/v3/process/process_darwin_cgo.go index a80817755..9b9d39345 100644 --- a/v3/process/process_darwin_cgo.go +++ b/v3/process/process_darwin_cgo.go @@ -5,13 +5,39 @@ package process // #include // #include +// #include +// #include +// #include +// #include import "C" import ( + "bytes" "context" "fmt" + "strings" + "syscall" "unsafe" ) +var argMax int + +func init() { + argMax = getArgMax() +} + +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 (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) @@ -28,3 +54,86 @@ func (p *Process) ExeWithContext(ctx context.Context) (string, error) { 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 := 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, fmt.Errorf("error: %d", retval) +} + +func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]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 +} + +func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) { + r, err := p.CmdlineSliceWithContext(ctx) + if err != nil { + return "", err + } + return strings.Join(r, " "), err +} diff --git a/v3/process/process_darwin_nocgo.go b/v3/process/process_darwin_nocgo.go index 3583e1987..91f2fc6a8 100644 --- a/v3/process/process_darwin_nocgo.go +++ b/v3/process/process_darwin_nocgo.go @@ -9,8 +9,14 @@ import ( "os/exec" "strconv" "strings" + + "github.com/shirou/gopsutil/v3/internal/common" ) +func (p *Process) CwdWithContext(ctx context.Context) (string, error) { + return "", common.ErrNotImplementedError +} + func (p *Process) ExeWithContext(ctx context.Context) (string, error) { lsof_bin, err := exec.LookPath("lsof") if err != nil { @@ -32,3 +38,24 @@ func (p *Process) ExeWithContext(ctx context.Context) (string, error) { } return "", fmt.Errorf("missing txt data returned by lsof") } + +func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) { + r, err := callPsWithContext(ctx, "command", p.Pid, false, false) + if err != nil { + return "", err + } + return strings.Join(r[0], " "), err +} + +// CmdlineSliceWithContext returns the command line arguments of the process as a slice with each +// element being an argument. Because of current deficiencies in the way that the command +// line arguments are found, single arguments that have spaces in the will actually be +// reported as two separate items. In order to do something better CGO would be needed +// to use the native darwin functions. +func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) { + r, err := callPsWithContext(ctx, "command", p.Pid, false, false) + if err != nil { + return nil, err + } + return r[0], err +} diff --git a/v3/process/process_freebsd.go b/v3/process/process_freebsd.go index 431a673df..63f0136f1 100644 --- a/v3/process/process_freebsd.go +++ b/v3/process/process_freebsd.go @@ -64,6 +64,10 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) { return name, nil } +func (p *Process) CwdWithContext(ctx context.Context) (string, error) { + return "", common.ErrNotImplementedError +} + func (p *Process) ExeWithContext(ctx context.Context) (string, error) { return "", common.ErrNotImplementedError } diff --git a/v3/process/process_openbsd.go b/v3/process/process_openbsd.go index 0977a11bc..9878fd7e3 100644 --- a/v3/process/process_openbsd.go +++ b/v3/process/process_openbsd.go @@ -69,6 +69,10 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) { return name, nil } +func (p *Process) CwdWithContext(ctx context.Context) (string, error) { + return "", common.ErrNotImplementedError +} + func (p *Process) ExeWithContext(ctx context.Context) (string, error) { return "", common.ErrNotImplementedError }