diff --git a/process/process_darwin.go b/process/process_darwin.go index 8b8b58a69..15aa9f988 100644 --- a/process/process_darwin.go +++ b/process/process_darwin.go @@ -38,7 +38,7 @@ type _Ctype_struct___0 struct { func pidsWithContext(ctx context.Context) ([]int32, error) { var ret []int32 - pids, err := callPsWithContext(ctx, "pid", 0, false) + pids, err := callPsWithContext(ctx, "pid", 0, false, false) if err != nil { return ret, err } @@ -55,7 +55,7 @@ func pidsWithContext(ctx context.Context) ([]int32, error) { } func (p *Process) PpidWithContext(ctx context.Context) (int32, error) { - r, err := callPsWithContext(ctx, "ppid", p.Pid, false) + r, err := callPsWithContext(ctx, "ppid", p.Pid, false, false) if err != nil { return 0, err } @@ -76,16 +76,16 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) { name := common.IntToString(k.Proc.P_comm[:]) if len(name) >= 15 { - cmdlineSlice, err := p.CmdlineSliceWithContext(ctx) + cmdName, err := p.CmdNameWithContext(ctx) if err != nil { return "", err } - if len(cmdlineSlice) > 0 { - extendedName := filepath.Base(cmdlineSlice[0]) + if len(cmdName) > 0 { + extendedName := filepath.Base(cmdName[0]) if strings.HasPrefix(extendedName, p.name) { name = extendedName } else { - name = cmdlineSlice[0] + name = cmdName[0] } } } @@ -94,20 +94,29 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) { } func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) { - r, err := callPsWithContext(ctx, "command", p.Pid, false) + 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) + if err != nil { + return nil, err + } + 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) + r, err := callPsWithContext(ctx, "command", p.Pid, false, false) if err != nil { return nil, err } @@ -115,7 +124,7 @@ func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) } func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) { - r, err := callPsWithContext(ctx, "etime", p.Pid, false) + r, err := callPsWithContext(ctx, "etime", p.Pid, false, false) if err != nil { return 0, err } @@ -163,7 +172,7 @@ func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) { } func (p *Process) StatusWithContext(ctx context.Context) (string, error) { - r, err := callPsWithContext(ctx, "state", p.Pid, false) + r, err := callPsWithContext(ctx, "state", p.Pid, false, false) if err != nil { return "", err } @@ -255,7 +264,7 @@ func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, e } func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) { - r, err := callPsWithContext(ctx, "utime,stime", p.Pid, true) + r, err := callPsWithContext(ctx, "utime,stime", p.Pid, true, false) if err != nil { return 0, err } @@ -309,7 +318,7 @@ func convertCPUTimes(s string) (ret float64, err error) { } func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) { - r, err := callPsWithContext(ctx, "utime,stime", p.Pid, false) + r, err := callPsWithContext(ctx, "utime,stime", p.Pid, false, false) if err != nil { return nil, err @@ -333,7 +342,7 @@ func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) } func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) { - r, err := callPsWithContext(ctx, "rss,vsize,pagein", p.Pid, false) + r, err := callPsWithContext(ctx, "rss,vsize,pagein", p.Pid, false, false) if err != nil { return nil, err } @@ -419,9 +428,9 @@ func (p *Process) getKProc() (*KinfoProc, error) { // call ps command. // Return value deletes Header line(you must not input wrong arg). -// And splited by Space. Caller have responsibility to manage. +// And split by space. Caller have responsibility to manage. // If passed arg pid is 0, get information from all process. -func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption bool) ([][]string, error) { +func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption bool, nameOption bool) ([][]string, error) { bin, err := exec.LookPath("ps") if err != nil { return [][]string{}, err @@ -435,6 +444,10 @@ func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption } else { cmd = []string{"-x", "-o", arg, "-p", strconv.Itoa(int(pid))} } + + if nameOption { + cmd = append(cmd, "-c") + } out, err := invoke.CommandWithContext(ctx, bin, cmd...) if err != nil { return [][]string{}, err @@ -443,13 +456,19 @@ func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption var ret [][]string for _, l := range lines[1:] { + var lr []string - for _, r := range strings.Split(l, " ") { - if r == "" { - continue + if nameOption { + lr = append(lr, l) + } else { + for _, r := range strings.Split(l, " ") { + if r == "" { + continue + } + lr = append(lr, strings.TrimSpace(r)) } - lr = append(lr, strings.TrimSpace(r)) } + if len(lr) != 0 { ret = append(ret, lr) } diff --git a/process/process_test.go b/process/process_test.go index 97827c016..0ea85eaba 100644 --- a/process/process_test.go +++ b/process/process_test.go @@ -312,6 +312,52 @@ func Test_Process_Name(t *testing.T) { t.Errorf("invalid Exe %s", n) } } + +func Test_Process_Long_Name_With_Spaces(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("unable to create temp dir %v", err) + } + defer os.RemoveAll(tmpdir) // clean up + tmpfilepath := filepath.Join(tmpdir, "loooong name with spaces.go") + tmpfile, err := os.Create(tmpfilepath) + if err != nil { + t.Fatalf("unable to create temp file %v", err) + } + + tmpfilecontent := []byte("package main\nimport(\n\"time\"\n)\nfunc main(){\nfor range time.Tick(time.Second) {}\n}") + if _, err := tmpfile.Write(tmpfilecontent); err != nil { + tmpfile.Close() + t.Fatalf("unable to write temp file %v", err) + } + if err := tmpfile.Close(); err != nil { + t.Fatalf("unable to close temp file %v", err) + } + + err = exec.Command("go", "build", "-o", tmpfile.Name()+".exe", tmpfile.Name()).Run() + if err != nil { + t.Fatalf("unable to build temp file %v", err) + } + + cmd := exec.Command(tmpfile.Name() + ".exe") + + assert.Nil(t, cmd.Start()) + time.Sleep(100 * time.Millisecond) + p, err := NewProcess(int32(cmd.Process.Pid)) + skipIfNotImplementedErr(t, err) + assert.Nil(t, err) + + n, err := p.Name() + skipIfNotImplementedErr(t, err) + if err != nil { + t.Fatalf("getting name error %v", err) + } + basename := filepath.Base(tmpfile.Name() + ".exe") + if basename != n { + t.Fatalf("%s != %s", basename, n) + } + cmd.Process.Kill() +} func Test_Process_Long_Name(t *testing.T) { tmpdir, err := ioutil.TempDir("", "") if err != nil { diff --git a/v3/process/process_darwin.go b/v3/process/process_darwin.go index a7c4115c5..7c20593ce 100644 --- a/v3/process/process_darwin.go +++ b/v3/process/process_darwin.go @@ -38,7 +38,7 @@ type _Ctype_struct___0 struct { func pidsWithContext(ctx context.Context) ([]int32, error) { var ret []int32 - pids, err := callPsWithContext(ctx, "pid", 0, false) + pids, err := callPsWithContext(ctx, "pid", 0, false, false) if err != nil { return ret, err } @@ -55,7 +55,7 @@ func pidsWithContext(ctx context.Context) ([]int32, error) { } func (p *Process) PpidWithContext(ctx context.Context) (int32, error) { - r, err := callPsWithContext(ctx, "ppid", p.Pid, false) + r, err := callPsWithContext(ctx, "ppid", p.Pid, false, false) if err != nil { return 0, err } @@ -76,16 +76,16 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) { name := common.IntToString(k.Proc.P_comm[:]) if len(name) >= 15 { - cmdlineSlice, err := p.CmdlineSliceWithContext(ctx) + cmdName, err := p.CmdNameWithContext(ctx) if err != nil { return "", err } - if len(cmdlineSlice) > 0 { - extendedName := filepath.Base(cmdlineSlice[0]) + if len(cmdName) > 0 { + extendedName := filepath.Base(cmdName[0]) if strings.HasPrefix(extendedName, p.name) { name = extendedName } else { - name = cmdlineSlice[0] + name = cmdName[0] } } } @@ -94,20 +94,29 @@ func (p *Process) NameWithContext(ctx context.Context) (string, error) { } func (p *Process) CmdlineWithContext(ctx context.Context) (string, error) { - r, err := callPsWithContext(ctx, "command", p.Pid, false) + 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) + if err != nil { + return nil, err + } + 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) + r, err := callPsWithContext(ctx, "command", p.Pid, false, false) if err != nil { return nil, err } @@ -115,7 +124,7 @@ func (p *Process) CmdlineSliceWithContext(ctx context.Context) ([]string, error) } func (p *Process) createTimeWithContext(ctx context.Context) (int64, error) { - r, err := callPsWithContext(ctx, "etime", p.Pid, false) + r, err := callPsWithContext(ctx, "etime", p.Pid, false, false) if err != nil { return 0, err } @@ -163,7 +172,7 @@ func (p *Process) ParentWithContext(ctx context.Context) (*Process, error) { } func (p *Process) StatusWithContext(ctx context.Context) ([]string, error) { - r, err := callPsWithContext(ctx, "state", p.Pid, false) + r, err := callPsWithContext(ctx, "state", p.Pid, false, false) if err != nil { return []string{""}, err } @@ -255,7 +264,7 @@ func (p *Process) IOCountersWithContext(ctx context.Context) (*IOCountersStat, e } func (p *Process) NumThreadsWithContext(ctx context.Context) (int32, error) { - r, err := callPsWithContext(ctx, "utime,stime", p.Pid, true) + r, err := callPsWithContext(ctx, "utime,stime", p.Pid, true, false) if err != nil { return 0, err } @@ -309,7 +318,7 @@ func convertCPUTimes(s string) (ret float64, err error) { } func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) { - r, err := callPsWithContext(ctx, "utime,stime", p.Pid, false) + r, err := callPsWithContext(ctx, "utime,stime", p.Pid, false, false) if err != nil { return nil, err @@ -333,7 +342,7 @@ func (p *Process) TimesWithContext(ctx context.Context) (*cpu.TimesStat, error) } func (p *Process) MemoryInfoWithContext(ctx context.Context) (*MemoryInfoStat, error) { - r, err := callPsWithContext(ctx, "rss,vsize,pagein", p.Pid, false) + r, err := callPsWithContext(ctx, "rss,vsize,pagein", p.Pid, false, false) if err != nil { return nil, err } @@ -421,7 +430,7 @@ func (p *Process) getKProc() (*KinfoProc, error) { // Return value deletes Header line(you must not input wrong arg). // And splited by Space. Caller have responsibility to manage. // If passed arg pid is 0, get information from all process. -func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption bool) ([][]string, error) { +func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption bool, nameOption bool) ([][]string, error) { bin, err := exec.LookPath("ps") if err != nil { return [][]string{}, err @@ -435,6 +444,9 @@ func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption } else { cmd = []string{"-x", "-o", arg, "-p", strconv.Itoa(int(pid))} } + if nameOption { + cmd = append(cmd, "-c") + } out, err := invoke.CommandWithContext(ctx, bin, cmd...) if err != nil { return [][]string{}, err @@ -444,11 +456,15 @@ func callPsWithContext(ctx context.Context, arg string, pid int32, threadOption var ret [][]string for _, l := range lines[1:] { var lr []string - for _, r := range strings.Split(l, " ") { - if r == "" { - continue + if nameOption { + lr = append(lr, l) + } else { + for _, r := range strings.Split(l, " ") { + if r == "" { + continue + } + lr = append(lr, strings.TrimSpace(r)) } - lr = append(lr, strings.TrimSpace(r)) } if len(lr) != 0 { ret = append(ret, lr) diff --git a/v3/process/process_test.go b/v3/process/process_test.go index 47045af24..df2faffcf 100644 --- a/v3/process/process_test.go +++ b/v3/process/process_test.go @@ -315,6 +315,51 @@ func Test_Process_Name(t *testing.T) { t.Errorf("invalid Exe %s", n) } } +func Test_Process_Long_Name_With_Spaces(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("unable to create temp dir %v", err) + } + defer os.RemoveAll(tmpdir) // clean up + tmpfilepath := filepath.Join(tmpdir, "loooong name with spaces.go") + tmpfile, err := os.Create(tmpfilepath) + if err != nil { + t.Fatalf("unable to create temp file %v", err) + } + + tmpfilecontent := []byte("package main\nimport(\n\"time\"\n)\nfunc main(){\nfor range time.Tick(time.Second) {}\n}") + if _, err := tmpfile.Write(tmpfilecontent); err != nil { + tmpfile.Close() + t.Fatalf("unable to write temp file %v", err) + } + if err := tmpfile.Close(); err != nil { + t.Fatalf("unable to close temp file %v", err) + } + + err = exec.Command("go", "build", "-o", tmpfile.Name()+".exe", tmpfile.Name()).Run() + if err != nil { + t.Fatalf("unable to build temp file %v", err) + } + + cmd := exec.Command(tmpfile.Name() + ".exe") + + assert.Nil(t, cmd.Start()) + time.Sleep(100 * time.Millisecond) + p, err := NewProcess(int32(cmd.Process.Pid)) + skipIfNotImplementedErr(t, err) + assert.Nil(t, err) + + n, err := p.Name() + skipIfNotImplementedErr(t, err) + if err != nil { + t.Fatalf("getting name error %v", err) + } + basename := filepath.Base(tmpfile.Name() + ".exe") + if basename != n { + t.Fatalf("%s != %s", basename, n) + } + cmd.Process.Kill() +} func Test_Process_Long_Name(t *testing.T) { tmpdir, err := ioutil.TempDir("", "") if err != nil {