From b8c40ac83c71bb08c277d6cb58ab9b234df33019 Mon Sep 17 00:00:00 2001 From: Chris Gilling Date: Sat, 27 Feb 2016 17:52:24 -0800 Subject: [PATCH 1/3] process: add CmdlineSlice function for linux + freebsd This allows for getting more exact information about each argument especially if there are arguments that have spaces in them. This was not implemented for darwin or for windows because they both currently have not way of properly parsing the cmdline string. Darwin parses the output of 'ps' which is already whitespace segmented, and windows just has the cmdline string. --- process/process_darwin.go | 3 +++ process/process_freebsd.go | 19 +++++++++++++++++++ process/process_linux.go | 30 ++++++++++++++++++++++++++++++ process/process_test.go | 13 +++++++++++++ process/process_windows.go | 4 +++- 5 files changed, 68 insertions(+), 1 deletion(-) diff --git a/process/process_darwin.go b/process/process_darwin.go index 414ea98..f2bd0a8 100644 --- a/process/process_darwin.go +++ b/process/process_darwin.go @@ -88,6 +88,9 @@ func (p *Process) Cmdline() (string, error) { } return strings.Join(r[0], " "), err } +func (p *Process) CmdlineSlice() ([]string, error) { + return nil, common.NotImplementedError +} func (p *Process) CreateTime() (int64, error) { return 0, common.NotImplementedError } diff --git a/process/process_freebsd.go b/process/process_freebsd.go index 9835bc9..4989867 100644 --- a/process/process_freebsd.go +++ b/process/process_freebsd.go @@ -54,6 +54,7 @@ func (p *Process) Name() (string, error) { func (p *Process) Exe() (string, error) { return "", common.NotImplementedError } + func (p *Process) Cmdline() (string, error) { mib := []int32{CTLKern, KernProc, KernProcArgs, p.Pid} buf, _, err := common.CallSyscall(mib) @@ -69,6 +70,24 @@ func (p *Process) Cmdline() (string, error) { return strings.Join(ret, " "), nil } + +func (p *Process) CmdlineSlice() ([]string, error) { + mib := []int32{CTLKern, KernProc, KernProcArgs, p.Pid} + buf, _, err := common.CallSyscall(mib) + if err != nil { + return nil, err + } + if buf[len(buf)-1] == 0 { + buf = buf[:len(buf)-1] + } + parts := bytes.Split(buf, []byte{0}) + var strParts []string + for _, p := range parts { + strParts = append(strParts, string(p)) + } + + return strParts, nil +} func (p *Process) CreateTime() (int64, error) { return 0, common.NotImplementedError } diff --git a/process/process_linux.go b/process/process_linux.go index 1783d2f..7c6bb29 100644 --- a/process/process_linux.go +++ b/process/process_linux.go @@ -3,6 +3,7 @@ package process import ( + "bytes" "encoding/json" "errors" "fmt" @@ -84,9 +85,19 @@ func (p *Process) Name() (string, error) { func (p *Process) Exe() (string, error) { return p.fillFromExe() } + +// Cmdline returns the command line arguments of the process as a string with +// each argument separated by 0x20 ascii character. func (p *Process) Cmdline() (string, error) { return p.fillFromCmdline() } + +// CmdlineSlice returns the command line arguments of the process as a slice with each +// element being an argument. +func (p *Process) CmdlineSlice() ([]string, error) { + return p.fillSliceFromCmdline() +} + func (p *Process) CreateTime() (int64, error) { _, _, _, createTime, _, err := p.fillFromStat() if err != nil { @@ -403,6 +414,25 @@ func (p *Process) fillFromCmdline() (string, error) { return strings.Join(ret, " "), nil } +func (p *Process) fillSliceFromCmdline() ([]string, error) { + pid := p.Pid + cmdPath := common.HostProc(strconv.Itoa(int(pid)), "cmdline") + cmdline, err := ioutil.ReadFile(cmdPath) + if err != nil { + return nil, err + } + if cmdline[len(cmdline)-1] == 0 { + cmdline = cmdline[:len(cmdline)-1] + } + parts := bytes.Split(cmdline, []byte{0}) + var strParts []string + for _, p := range parts { + strParts = append(strParts, string(p)) + } + + return strParts, nil +} + // Get IO status from /proc/(pid)/io func (p *Process) fillFromIO() (*IOCountersStat, error) { pid := p.Pid diff --git a/process/process_test.go b/process/process_test.go index 129204f..c5aac11 100644 --- a/process/process_test.go +++ b/process/process_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "os/user" + "reflect" "runtime" "strings" "sync" @@ -120,6 +121,18 @@ func Test_Process_CmdLine(t *testing.T) { } } +func Test_Process_CmdLineSlice(t *testing.T) { + p := testGetProcess() + + v, err := p.CmdlineSlice() + if err != nil { + t.Fatalf("geting cmdline slice error %v", err) + } + if !reflect.DeepEqual(v, os.Args) { + t.Errorf("returned cmdline slice not as expected:\nexp: %v\ngot: %v", os.Args, v) + } +} + func Test_Process_Ppid(t *testing.T) { p := testGetProcess() diff --git a/process/process_windows.go b/process/process_windows.go index 51a0ee1..6fd972e 100644 --- a/process/process_windows.go +++ b/process/process_windows.go @@ -144,7 +144,9 @@ func (p *Process) Cmdline() (string, error) { } return *dst[0].CommandLine, nil } - +func (p *Process) CmdlineSlice() ([]string, error) { + return nil, common.NotImplementedError +} func (p *Process) CreateTime() (int64, error) { dst, err := GetWin32Proc(p.Pid) if err != nil { From d098bf135ffefecb49f3a41d8919dd7185f317c1 Mon Sep 17 00:00:00 2001 From: Chris Gilling Date: Sat, 27 Feb 2016 18:29:24 -0800 Subject: [PATCH 2/3] process: fix bug in CmdlineSlice where cmdline is empty Sometimes (at least on linux) cmdline is empty, this was causing a panic. --- process/process_freebsd.go | 3 +++ process/process_linux.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/process/process_freebsd.go b/process/process_freebsd.go index 4989867..3242ff6 100644 --- a/process/process_freebsd.go +++ b/process/process_freebsd.go @@ -77,6 +77,9 @@ func (p *Process) CmdlineSlice() ([]string, error) { if err != nil { return nil, err } + if len(buf) == 0 { + return nil, nil + } if buf[len(buf)-1] == 0 { buf = buf[:len(buf)-1] } diff --git a/process/process_linux.go b/process/process_linux.go index 7c6bb29..2bb003e 100644 --- a/process/process_linux.go +++ b/process/process_linux.go @@ -421,6 +421,9 @@ func (p *Process) fillSliceFromCmdline() ([]string, error) { if err != nil { return nil, err } + if len(cmdline) == 0 { + return nil, nil + } if cmdline[len(cmdline)-1] == 0 { cmdline = cmdline[:len(cmdline)-1] } From 8c186ae593caed7822791080456c1eb959d723d5 Mon Sep 17 00:00:00 2001 From: Chris Gilling Date: Sun, 28 Feb 2016 19:49:50 -0800 Subject: [PATCH 3/3] process: add CmdlineSlice for darwin + windows darwin will not perform correctly if there are spaces in the actual arguments, in which case a single argument will be reported as multple. Some CGO would be needed to get around this I think. I couldn't find any good documentation on how windows handles command line arguments with spaces inside the actual arguments, so this implementation merely just splits on spaces. --- process/process_darwin.go | 15 ++++++++++++++- process/process_windows.go | 12 +++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/process/process_darwin.go b/process/process_darwin.go index f2bd0a8..c728bcd 100644 --- a/process/process_darwin.go +++ b/process/process_darwin.go @@ -81,6 +81,9 @@ func (p *Process) Name() (string, error) { func (p *Process) Exe() (string, error) { return "", common.NotImplementedError } + +// Cmdline returns the command line arguments of the process as a string with +// each argument separated by 0x20 ascii character. func (p *Process) Cmdline() (string, error) { r, err := callPs("command", p.Pid, false) if err != nil { @@ -88,8 +91,18 @@ func (p *Process) Cmdline() (string, error) { } return strings.Join(r[0], " "), err } + +// CmdlineSlice 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) CmdlineSlice() ([]string, error) { - return nil, common.NotImplementedError + r, err := callPs("command", p.Pid, false) + if err != nil { + return nil, err + } + return r[0], err } func (p *Process) CreateTime() (int64, error) { return 0, common.NotImplementedError diff --git a/process/process_windows.go b/process/process_windows.go index 6fd972e..f4fe119 100644 --- a/process/process_windows.go +++ b/process/process_windows.go @@ -5,6 +5,7 @@ package process import ( "errors" "fmt" + "strings" "syscall" "time" "unsafe" @@ -144,9 +145,18 @@ func (p *Process) Cmdline() (string, error) { } return *dst[0].CommandLine, nil } + +// CmdlineSlice returns the command line arguments of the process as a slice with each +// element being an argument. This merely returns the CommandLine informations passed +// to the process split on the 0x20 ASCII character. func (p *Process) CmdlineSlice() ([]string, error) { - return nil, common.NotImplementedError + cmdline, err := p.Cmdline() + if err != nil { + return nil, err + } + return strings.Split(cmdline, " "), nil } + func (p *Process) CreateTime() (int64, error) { dst, err := GetWin32Proc(p.Pid) if err != nil {