From 76ccf0d2203da9f4a1c2ba5ab0c3a53b6a3eb65a Mon Sep 17 00:00:00 2001 From: Lomanic <5020919+Lomanic@users.noreply.github.com> Date: Sat, 7 Sep 2024 03:10:57 +0200 Subject: [PATCH] [process][darwin][freebsd][linux][openbsd] Make process.Children not reliant on pgrep pgrep -P $PID exits with status of 1 (and nothing in stdout nor stderr) both if a process doesn't exist or it doesn't have child processes, so we don't use it anymore on these OSes. We sort PIDs as pgrep did. Also deprecate the ErrorNoChildren error when there are no child processes, this is erroneous (simply check for the length of the returned slice, plus this is not an error per se), this was only returned on linux anyway. Fixes #1698 --- internal/common/common_unix.go | 20 -------------------- process/process.go | 2 +- process/process_darwin.go | 18 +++++++++++------- process/process_freebsd.go | 18 +++++++++++------- process/process_linux.go | 32 +++++++++++++++++++++++--------- process/process_openbsd.go | 18 +++++++++++------- 6 files changed, 57 insertions(+), 51 deletions(-) diff --git a/internal/common/common_unix.go b/internal/common/common_unix.go index 2715b890..c9f91b16 100644 --- a/internal/common/common_unix.go +++ b/internal/common/common_unix.go @@ -40,23 +40,3 @@ func CallLsofWithContext(ctx context.Context, invoke Invoker, pid int32, args .. } return ret, nil } - -func CallPgrepWithContext(ctx context.Context, invoke Invoker, pid int32) ([]int32, error) { - out, err := invoke.CommandWithContext(ctx, "pgrep", "-P", strconv.Itoa(int(pid))) - if err != nil { - return []int32{}, err - } - lines := strings.Split(string(out), "\n") - ret := make([]int32, 0, len(lines)) - for _, l := range lines { - if len(l) == 0 { - continue - } - i, err := strconv.ParseInt(l, 10, 32) - if err != nil { - continue - } - ret = append(ret, int32(i)) - } - return ret, nil -} diff --git a/process/process.go b/process/process.go index d73f1f97..70411c61 100644 --- a/process/process.go +++ b/process/process.go @@ -18,7 +18,7 @@ import ( var ( invoke common.Invoker = common.Invoke{} - ErrorNoChildren = errors.New("process does not have children") + ErrorNoChildren = errors.New("process does not have children") // Deprecated: ErrorNoChildren is never returned by process.Children(), check its returned []*Process slice length instead ErrorProcessNotRunning = errors.New("process does not exist") ErrorNotPermitted = errors.New("operation not permitted") ) diff --git a/process/process_darwin.go b/process/process_darwin.go index 66b3684e..119be2e3 100644 --- a/process/process_darwin.go +++ b/process/process_darwin.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "path/filepath" + "sort" "strconv" "strings" @@ -233,18 +234,21 @@ func convertCPUTimes(s string) (ret float64, err error) { } func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { - pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid) + procs, err := ProcessesWithContext(ctx) if err != nil { - return nil, err + return nil, nil } - ret := make([]*Process, 0, len(pids)) - for _, pid := range pids { - np, err := NewProcessWithContext(ctx, pid) + ret := make([]*Process, 0, len(procs)) + for _, proc := range procs { + ppid, err := proc.PpidWithContext(ctx) if err != nil { - return nil, err + continue + } + if ppid == p.Pid { + ret = append(ret, proc) } - ret = append(ret, np) } + sort.Slice(ret, func(i, j int) bool { return ret[i].Pid < ret[j].Pid }) return ret, nil } diff --git a/process/process_freebsd.go b/process/process_freebsd.go index 436dcf03..76373736 100644 --- a/process/process_freebsd.go +++ b/process/process_freebsd.go @@ -8,6 +8,7 @@ import ( "context" "errors" "path/filepath" + "sort" "strconv" "strings" @@ -269,18 +270,21 @@ func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, e } func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { - pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid) + procs, err := ProcessesWithContext(ctx) if err != nil { - return nil, err + return nil, nil } - ret := make([]*Process, 0, len(pids)) - for _, pid := range pids { - np, err := NewProcessWithContext(ctx, pid) + ret := make([]*Process, 0, len(procs)) + for _, proc := range procs { + ppid, err := proc.PpidWithContext(ctx) if err != nil { - return nil, err + continue + } + if ppid == p.Pid { + ret = append(ret, proc) } - ret = append(ret, np) } + sort.Slice(ret, func(i, j int) bool { return ret[i].Pid < ret[j].Pid }) return ret, nil } diff --git a/process/process_linux.go b/process/process_linux.go index 7aff0448..4f166934 100644 --- a/process/process_linux.go +++ b/process/process_linux.go @@ -12,6 +12,7 @@ import ( "math" "os" "path/filepath" + "sort" "strconv" "strings" @@ -338,21 +339,34 @@ func (p *Process) PageFaultsWithContext(ctx context.Context) (*PageFaultsStat, e } func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { - pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid) + statFiles, err := filepath.Glob(common.HostProcWithContext(ctx, "[0-9]*/stat")) if err != nil { return nil, err } - if len(pids) == 0 { - return nil, ErrorNoChildren - } - ret := make([]*Process, 0, len(pids)) - for _, pid := range pids { - np, err := NewProcessWithContext(ctx, pid) + ret := make([]*Process, 0, len(statFiles)) + for _, statFile := range statFiles { + statContents, err := os.ReadFile(statFile) if err != nil { - return nil, err + continue + } + fields := splitProcStat(statContents) + pid, err := strconv.ParseInt(fields[1], 10, 32) + if err != nil { + continue + } + ppid, err := strconv.ParseInt(fields[4], 10, 32) + if err != nil { + continue + } + if int32(ppid) == p.Pid { + np, err := NewProcessWithContext(ctx, int32(pid)) + if err != nil { + continue + } + ret = append(ret, np) } - ret = append(ret, np) } + sort.Slice(ret, func(i, j int) bool { return ret[i].Pid < ret[j].Pid }) return ret, nil } diff --git a/process/process_openbsd.go b/process/process_openbsd.go index e2d0ab46..5e8a9e0b 100644 --- a/process/process_openbsd.go +++ b/process/process_openbsd.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "path/filepath" + "sort" "strconv" "strings" "unsafe" @@ -286,18 +287,21 @@ func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, e } func (p *Process) ChildrenWithContext(ctx context.Context) ([]*Process, error) { - pids, err := common.CallPgrepWithContext(ctx, invoke, p.Pid) + procs, err := ProcessesWithContext(ctx) if err != nil { - return nil, err + return nil, nil } - ret := make([]*Process, 0, len(pids)) - for _, pid := range pids { - np, err := NewProcessWithContext(ctx, pid) + ret := make([]*Process, 0, len(procs)) + for _, proc := range procs { + ppid, err := proc.PpidWithContext(ctx) if err != nil { - return nil, err + continue + } + if ppid == p.Pid { + ret = append(ret, proc) } - ret = append(ret, np) } + sort.Slice(ret, func(i, j int) bool { return ret[i].Pid < ret[j].Pid }) return ret, nil }