diff --git a/go.mod b/go.mod index 253ccb8d6..5355eaedf 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/rsteube/carapace go 1.14 require ( - github.com/mitchellh/go-ps v1.0.0 github.com/spf13/cobra v1.4.0 github.com/spf13/pflag v1.0.5 golang.org/x/sys v0.0.0-20211205182925-97ca703d548d diff --git a/go.sum b/go.sum index 5e87c26b7..ec062526a 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go index a03c92cb6..2bec36a4c 100644 --- a/pkg/ps/ps.go +++ b/pkg/ps/ps.go @@ -5,7 +5,7 @@ import ( "os" "strings" - "github.com/mitchellh/go-ps" + "github.com/rsteube/carapace/third_party/github.com/mitchellh/go-ps" ) // DetermineShell determines shell by parent process name diff --git a/third_party/github.com/mitchellh/go-ps/LICENSE.md b/third_party/github.com/mitchellh/go-ps/LICENSE.md new file mode 100644 index 000000000..229851590 --- /dev/null +++ b/third_party/github.com/mitchellh/go-ps/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Mitchell Hashimoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/third_party/github.com/mitchellh/go-ps/process.go b/third_party/github.com/mitchellh/go-ps/process.go new file mode 100644 index 000000000..2b5e8ed23 --- /dev/null +++ b/third_party/github.com/mitchellh/go-ps/process.go @@ -0,0 +1,40 @@ +// ps provides an API for finding and listing processes in a platform-agnostic +// way. +// +// NOTE: If you're reading these docs online via GoDocs or some other system, +// you might only see the Unix docs. This project makes heavy use of +// platform-specific implementations. We recommend reading the source if you +// are interested. +package ps + +// Process is the generic interface that is implemented on every platform +// and provides common operations for processes. +type Process interface { + // Pid is the process ID for this process. + Pid() int + + // PPid is the parent process ID for this process. + PPid() int + + // Executable name running this process. This is not a path to the + // executable. + Executable() string +} + +// Processes returns all processes. +// +// This of course will be a point-in-time snapshot of when this method was +// called. Some operating systems don't provide snapshot capability of the +// process table, in which case the process table returned might contain +// ephemeral entities that happened to be running when this was called. +func Processes() ([]Process, error) { + return processes() +} + +// FindProcess looks up a single process by pid. +// +// Process will be nil and error will be nil if a matching process is +// not found. +func FindProcess(pid int) (Process, error) { + return findProcess(pid) +} diff --git a/third_party/github.com/mitchellh/go-ps/process_darwin.go b/third_party/github.com/mitchellh/go-ps/process_darwin.go new file mode 100644 index 000000000..3b417a079 --- /dev/null +++ b/third_party/github.com/mitchellh/go-ps/process_darwin.go @@ -0,0 +1,139 @@ +//go:build darwin +// +build darwin + +package ps + +import ( + "bytes" + "encoding/binary" + "syscall" + "unsafe" +) + +type DarwinProcess struct { + pid int + ppid int + binary string +} + +func (p *DarwinProcess) Pid() int { + return p.pid +} + +func (p *DarwinProcess) PPid() int { + return p.ppid +} + +func (p *DarwinProcess) Executable() string { + return p.binary +} + +func findProcess(pid int) (Process, error) { + ps, err := processes() + if err != nil { + return nil, err + } + + for _, p := range ps { + if p.Pid() == pid { + return p, nil + } + } + + return nil, nil +} + +func processes() ([]Process, error) { + buf, err := darwinSyscall() + if err != nil { + return nil, err + } + + procs := make([]*kinfoProc, 0, 50) + k := 0 + for i := _KINFO_STRUCT_SIZE; i < buf.Len(); i += _KINFO_STRUCT_SIZE { + proc := &kinfoProc{} + err = binary.Read(bytes.NewBuffer(buf.Bytes()[k:i]), binary.LittleEndian, proc) + if err != nil { + return nil, err + } + + k = i + procs = append(procs, proc) + } + + darwinProcs := make([]Process, len(procs)) + for i, p := range procs { + darwinProcs[i] = &DarwinProcess{ + pid: int(p.Pid), + ppid: int(p.PPid), + binary: darwinCstring(p.Comm), + } + } + + return darwinProcs, nil +} + +func darwinCstring(s [16]byte) string { + i := 0 + for _, b := range s { + if b != 0 { + i++ + } else { + break + } + } + + return string(s[:i]) +} + +func darwinSyscall() (*bytes.Buffer, error) { + mib := [4]int32{_CTRL_KERN, _KERN_PROC, _KERN_PROC_ALL, 0} + size := uintptr(0) + + _, _, errno := syscall.Syscall6( + syscall.SYS___SYSCTL, + uintptr(unsafe.Pointer(&mib[0])), + 4, + 0, + uintptr(unsafe.Pointer(&size)), + 0, + 0) + + if errno != 0 { + return nil, errno + } + + bs := make([]byte, size) + _, _, errno = syscall.Syscall6( + syscall.SYS___SYSCTL, + uintptr(unsafe.Pointer(&mib[0])), + 4, + uintptr(unsafe.Pointer(&bs[0])), + uintptr(unsafe.Pointer(&size)), + 0, + 0) + + if errno != 0 { + return nil, errno + } + + return bytes.NewBuffer(bs[0:size]), nil +} + +const ( + _CTRL_KERN = 1 + _KERN_PROC = 14 + _KERN_PROC_ALL = 0 + _KINFO_STRUCT_SIZE = 648 +) + +type kinfoProc struct { + _ [40]byte + Pid int32 + _ [199]byte + Comm [16]byte + _ [301]byte + PPid int32 + _ [84]byte +} diff --git a/third_party/github.com/mitchellh/go-ps/process_darwin_test.go b/third_party/github.com/mitchellh/go-ps/process_darwin_test.go new file mode 100644 index 000000000..5cbf1ae74 --- /dev/null +++ b/third_party/github.com/mitchellh/go-ps/process_darwin_test.go @@ -0,0 +1,12 @@ +//go:build darwin +// +build darwin + +package ps + +import ( + "testing" +) + +func TestDarwinProcess_impl(t *testing.T) { + var _ Process = new(DarwinProcess) +} diff --git a/third_party/github.com/mitchellh/go-ps/process_freebsd.go b/third_party/github.com/mitchellh/go-ps/process_freebsd.go new file mode 100644 index 000000000..48115a4c1 --- /dev/null +++ b/third_party/github.com/mitchellh/go-ps/process_freebsd.go @@ -0,0 +1,261 @@ +//go:build freebsd +// +build freebsd + +package ps + +import ( + "bytes" + "encoding/binary" + "syscall" + "unsafe" +) + +// copied from sys/sysctl.h +const ( + CTL_KERN = 1 // "high kernel": proc, limits + KERN_PROC = 14 // struct: process entries + KERN_PROC_PID = 1 // by process id + KERN_PROC_PROC = 8 // only return procs + KERN_PROC_PATHNAME = 12 // path to executable +) + +// copied from sys/user.h +type Kinfo_proc struct { + Ki_structsize int32 + Ki_layout int32 + Ki_args int64 + Ki_paddr int64 + Ki_addr int64 + Ki_tracep int64 + Ki_textvp int64 + Ki_fd int64 + Ki_vmspace int64 + Ki_wchan int64 + Ki_pid int32 + Ki_ppid int32 + Ki_pgid int32 + Ki_tpgid int32 + Ki_sid int32 + Ki_tsid int32 + Ki_jobc [2]byte + Ki_spare_short1 [2]byte + Ki_tdev int32 + Ki_siglist [16]byte + Ki_sigmask [16]byte + Ki_sigignore [16]byte + Ki_sigcatch [16]byte + Ki_uid int32 + Ki_ruid int32 + Ki_svuid int32 + Ki_rgid int32 + Ki_svgid int32 + Ki_ngroups [2]byte + Ki_spare_short2 [2]byte + Ki_groups [64]byte + Ki_size int64 + Ki_rssize int64 + Ki_swrss int64 + Ki_tsize int64 + Ki_dsize int64 + Ki_ssize int64 + Ki_xstat [2]byte + Ki_acflag [2]byte + Ki_pctcpu int32 + Ki_estcpu int32 + Ki_slptime int32 + Ki_swtime int32 + Ki_cow int32 + Ki_runtime int64 + Ki_start [16]byte + Ki_childtime [16]byte + Ki_flag int64 + Ki_kiflag int64 + Ki_traceflag int32 + Ki_stat [1]byte + Ki_nice [1]byte + Ki_lock [1]byte + Ki_rqindex [1]byte + Ki_oncpu [1]byte + Ki_lastcpu [1]byte + Ki_ocomm [17]byte + Ki_wmesg [9]byte + Ki_login [18]byte + Ki_lockname [9]byte + Ki_comm [20]byte + Ki_emul [17]byte + Ki_sparestrings [68]byte + Ki_spareints [36]byte + Ki_cr_flags int32 + Ki_jid int32 + Ki_numthreads int32 + Ki_tid int32 + Ki_pri int32 + Ki_rusage [144]byte + Ki_rusage_ch [144]byte + Ki_pcb int64 + Ki_kstack int64 + Ki_udata int64 + Ki_tdaddr int64 + Ki_spareptrs [48]byte + Ki_spareint64s [96]byte + Ki_sflag int64 + Ki_tdflags int64 +} + +// UnixProcess is an implementation of Process that contains Unix-specific +// fields and information. +type UnixProcess struct { + pid int + ppid int + state rune + pgrp int + sid int + + binary string +} + +func (p *UnixProcess) Pid() int { + return p.pid +} + +func (p *UnixProcess) PPid() int { + return p.ppid +} + +func (p *UnixProcess) Executable() string { + return p.binary +} + +// Refresh reloads all the data associated with this process. +func (p *UnixProcess) Refresh() error { + + mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PID, int32(p.pid)} + + buf, length, err := call_syscall(mib) + if err != nil { + return err + } + proc_k := Kinfo_proc{} + if length != uint64(unsafe.Sizeof(proc_k)) { + return err + } + + k, err := parse_kinfo_proc(buf) + if err != nil { + return err + } + + p.ppid, p.pgrp, p.sid, p.binary = copy_params(&k) + return nil +} + +func copy_params(k *Kinfo_proc) (int, int, int, string) { + n := -1 + for i, b := range k.Ki_comm { + if b == 0 { + break + } + n = i + 1 + } + comm := string(k.Ki_comm[:n]) + + return int(k.Ki_ppid), int(k.Ki_pgid), int(k.Ki_sid), comm +} + +func findProcess(pid int) (Process, error) { + mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, int32(pid)} + + _, _, err := call_syscall(mib) + if err != nil { + return nil, err + } + + return newUnixProcess(pid) +} + +func processes() ([]Process, error) { + results := make([]Process, 0, 50) + + mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0} + buf, length, err := call_syscall(mib) + if err != nil { + return results, err + } + + // get kinfo_proc size + k := Kinfo_proc{} + procinfo_len := int(unsafe.Sizeof(k)) + count := int(length / uint64(procinfo_len)) + + // parse buf to procs + for i := 0; i < count; i++ { + b := buf[i*procinfo_len : i*procinfo_len+procinfo_len] + k, err := parse_kinfo_proc(b) + if err != nil { + continue + } + p, err := newUnixProcess(int(k.Ki_pid)) + if err != nil { + continue + } + p.ppid, p.pgrp, p.sid, p.binary = copy_params(&k) + + results = append(results, p) + } + + return results, nil +} + +func parse_kinfo_proc(buf []byte) (Kinfo_proc, error) { + var k Kinfo_proc + br := bytes.NewReader(buf) + err := binary.Read(br, binary.LittleEndian, &k) + if err != nil { + return k, err + } + + return k, nil +} + +func call_syscall(mib []int32) ([]byte, uint64, error) { + miblen := uint64(len(mib)) + + // get required buffer size + length := uint64(0) + _, _, err := syscall.RawSyscall6( + syscall.SYS___SYSCTL, + uintptr(unsafe.Pointer(&mib[0])), + uintptr(miblen), + 0, + uintptr(unsafe.Pointer(&length)), + 0, + 0) + if err != 0 { + b := make([]byte, 0) + return b, length, err + } + if length == 0 { + b := make([]byte, 0) + return b, length, err + } + // get proc info itself + buf := make([]byte, length) + _, _, err = syscall.RawSyscall6( + syscall.SYS___SYSCTL, + uintptr(unsafe.Pointer(&mib[0])), + uintptr(miblen), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(unsafe.Pointer(&length)), + 0, + 0) + if err != 0 { + return buf, length, err + } + + return buf, length, nil +} + +func newUnixProcess(pid int) (*UnixProcess, error) { + p := &UnixProcess{pid: pid} + return p, p.Refresh() +} diff --git a/third_party/github.com/mitchellh/go-ps/process_linux.go b/third_party/github.com/mitchellh/go-ps/process_linux.go new file mode 100644 index 000000000..c8218a0f9 --- /dev/null +++ b/third_party/github.com/mitchellh/go-ps/process_linux.go @@ -0,0 +1,36 @@ +//go:build linux +// +build linux + +package ps + +import ( + "fmt" + "io/ioutil" + "strings" +) + +// Refresh reloads all the data associated with this process. +func (p *UnixProcess) Refresh() error { + statPath := fmt.Sprintf("/proc/%d/stat", p.pid) + dataBytes, err := ioutil.ReadFile(statPath) + if err != nil { + return err + } + + // First, parse out the image name + data := string(dataBytes) + binStart := strings.IndexRune(data, '(') + 1 + binEnd := strings.IndexRune(data[binStart:], ')') + p.binary = data[binStart : binStart+binEnd] + + // Move past the image name and start parsing the rest + data = data[binStart+binEnd+2:] + _, err = fmt.Sscanf(data, + "%c %d %d %d", + &p.state, + &p.ppid, + &p.pgrp, + &p.sid) + + return err +} diff --git a/third_party/github.com/mitchellh/go-ps/process_solaris.go b/third_party/github.com/mitchellh/go-ps/process_solaris.go new file mode 100644 index 000000000..c4a86d065 --- /dev/null +++ b/third_party/github.com/mitchellh/go-ps/process_solaris.go @@ -0,0 +1,97 @@ +//go:build solaris +// +build solaris + +package ps + +import ( + "encoding/binary" + "fmt" + "os" +) + +type ushort_t uint16 + +type id_t int32 +type pid_t int32 +type uid_t int32 +type gid_t int32 + +type dev_t uint64 +type size_t uint64 +type uintptr_t uint64 + +type timestruc_t [16]byte + +// This is copy from /usr/include/sys/procfs.h +type psinfo_t struct { + Pr_flag int32 /* process flags (DEPRECATED; do not use) */ + Pr_nlwp int32 /* number of active lwps in the process */ + Pr_pid pid_t /* unique process id */ + Pr_ppid pid_t /* process id of parent */ + Pr_pgid pid_t /* pid of process group leader */ + Pr_sid pid_t /* session id */ + Pr_uid uid_t /* real user id */ + Pr_euid uid_t /* effective user id */ + Pr_gid gid_t /* real group id */ + Pr_egid gid_t /* effective group id */ + Pr_addr uintptr_t /* address of process */ + Pr_size size_t /* size of process image in Kbytes */ + Pr_rssize size_t /* resident set size in Kbytes */ + Pr_pad1 size_t + Pr_ttydev dev_t /* controlling tty device (or PRNODEV) */ + + // Guess this following 2 ushort_t values require a padding to properly + // align to the 64bit mark. + Pr_pctcpu ushort_t /* % of recent cpu time used by all lwps */ + Pr_pctmem ushort_t /* % of system memory used by process */ + Pr_pad64bit [4]byte + + Pr_start timestruc_t /* process start time, from the epoch */ + Pr_time timestruc_t /* usr+sys cpu time for this process */ + Pr_ctime timestruc_t /* usr+sys cpu time for reaped children */ + Pr_fname [16]byte /* name of execed file */ + Pr_psargs [80]byte /* initial characters of arg list */ + Pr_wstat int32 /* if zombie, the wait() status */ + Pr_argc int32 /* initial argument count */ + Pr_argv uintptr_t /* address of initial argument vector */ + Pr_envp uintptr_t /* address of initial environment vector */ + Pr_dmodel [1]byte /* data model of the process */ + Pr_pad2 [3]byte + Pr_taskid id_t /* task id */ + Pr_projid id_t /* project id */ + Pr_nzomb int32 /* number of zombie lwps in the process */ + Pr_poolid id_t /* pool id */ + Pr_zoneid id_t /* zone id */ + Pr_contract id_t /* process contract */ + Pr_filler int32 /* reserved for future use */ + Pr_lwp [128]byte /* information for representative lwp */ +} + +func (p *UnixProcess) Refresh() error { + var psinfo psinfo_t + + path := fmt.Sprintf("/proc/%d/psinfo", p.pid) + fh, err := os.Open(path) + if err != nil { + return err + } + defer fh.Close() + + err = binary.Read(fh, binary.LittleEndian, &psinfo) + if err != nil { + return err + } + + p.ppid = int(psinfo.Pr_ppid) + p.binary = toString(psinfo.Pr_fname[:], 16) + return nil +} + +func toString(array []byte, len int) string { + for i := 0; i < len; i++ { + if array[i] == 0 { + return string(array[:i]) + } + } + return string(array[:]) +} diff --git a/third_party/github.com/mitchellh/go-ps/process_test.go b/third_party/github.com/mitchellh/go-ps/process_test.go new file mode 100644 index 000000000..1bcbc3279 --- /dev/null +++ b/third_party/github.com/mitchellh/go-ps/process_test.go @@ -0,0 +1,45 @@ +package ps + +import ( + "os" + "testing" +) + +func TestFindProcess(t *testing.T) { + p, err := FindProcess(os.Getpid()) + if err != nil { + t.Fatalf("err: %s", err) + } + if p == nil { + t.Fatal("should have process") + } + + if p.Pid() != os.Getpid() { + t.Fatalf("bad: %#v", p.Pid()) + } +} + +func TestProcesses(t *testing.T) { + // This test works because there will always be SOME processes + // running. + p, err := Processes() + if err != nil { + t.Fatalf("err: %s", err) + } + + if len(p) <= 0 { + t.Fatal("should have processes") + } + + found := false + for _, p1 := range p { + if p1.Executable() == "go" || p1.Executable() == "go.exe" { + found = true + break + } + } + + if !found { + t.Fatal("should have Go") + } +} diff --git a/third_party/github.com/mitchellh/go-ps/process_unix.go b/third_party/github.com/mitchellh/go-ps/process_unix.go new file mode 100644 index 000000000..faec61a8d --- /dev/null +++ b/third_party/github.com/mitchellh/go-ps/process_unix.go @@ -0,0 +1,96 @@ +//go:build linux || solaris +// +build linux solaris + +package ps + +import ( + "fmt" + "io" + "os" + "strconv" +) + +// UnixProcess is an implementation of Process that contains Unix-specific +// fields and information. +type UnixProcess struct { + pid int + ppid int + state rune + pgrp int + sid int + + binary string +} + +func (p *UnixProcess) Pid() int { + return p.pid +} + +func (p *UnixProcess) PPid() int { + return p.ppid +} + +func (p *UnixProcess) Executable() string { + return p.binary +} + +func findProcess(pid int) (Process, error) { + dir := fmt.Sprintf("/proc/%d", pid) + _, err := os.Stat(dir) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + + return nil, err + } + + return newUnixProcess(pid) +} + +func processes() ([]Process, error) { + d, err := os.Open("/proc") + if err != nil { + return nil, err + } + defer d.Close() + + results := make([]Process, 0, 50) + for { + names, err := d.Readdirnames(10) + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + + for _, name := range names { + // We only care if the name starts with a numeric + if name[0] < '0' || name[0] > '9' { + continue + } + + // From this point forward, any errors we just ignore, because + // it might simply be that the process doesn't exist anymore. + pid, err := strconv.ParseInt(name, 10, 0) + if err != nil { + continue + } + + p, err := newUnixProcess(int(pid)) + if err != nil { + continue + } + + results = append(results, p) + } + } + + return results, nil +} + +func newUnixProcess(pid int) (*UnixProcess, error) { + p := &UnixProcess{pid: pid} + return p, p.Refresh() +} diff --git a/third_party/github.com/mitchellh/go-ps/process_unix_test.go b/third_party/github.com/mitchellh/go-ps/process_unix_test.go new file mode 100644 index 000000000..0e53426a2 --- /dev/null +++ b/third_party/github.com/mitchellh/go-ps/process_unix_test.go @@ -0,0 +1,12 @@ +//go:build linux || solaris +// +build linux solaris + +package ps + +import ( + "testing" +) + +func TestUnixProcess_impl(t *testing.T) { + var _ Process = new(UnixProcess) +} diff --git a/third_party/github.com/mitchellh/go-ps/process_windows.go b/third_party/github.com/mitchellh/go-ps/process_windows.go new file mode 100644 index 000000000..a930f90b8 --- /dev/null +++ b/third_party/github.com/mitchellh/go-ps/process_windows.go @@ -0,0 +1,120 @@ +//go:build windows +// +build windows + +package ps + +import ( + "fmt" + "syscall" + "unsafe" +) + +// Windows API functions +var ( + modKernel32 = syscall.NewLazyDLL("kernel32.dll") + procCloseHandle = modKernel32.NewProc("CloseHandle") + procCreateToolhelp32Snapshot = modKernel32.NewProc("CreateToolhelp32Snapshot") + procProcess32First = modKernel32.NewProc("Process32FirstW") + procProcess32Next = modKernel32.NewProc("Process32NextW") +) + +// Some constants from the Windows API +const ( + ERROR_NO_MORE_FILES = 0x12 + MAX_PATH = 260 +) + +// PROCESSENTRY32 is the Windows API structure that contains a process's +// information. +type PROCESSENTRY32 struct { + Size uint32 + CntUsage uint32 + ProcessID uint32 + DefaultHeapID uintptr + ModuleID uint32 + CntThreads uint32 + ParentProcessID uint32 + PriorityClassBase int32 + Flags uint32 + ExeFile [MAX_PATH]uint16 +} + +// WindowsProcess is an implementation of Process for Windows. +type WindowsProcess struct { + pid int + ppid int + exe string +} + +func (p *WindowsProcess) Pid() int { + return p.pid +} + +func (p *WindowsProcess) PPid() int { + return p.ppid +} + +func (p *WindowsProcess) Executable() string { + return p.exe +} + +func newWindowsProcess(e *PROCESSENTRY32) *WindowsProcess { + // Find when the string ends for decoding + end := 0 + for { + if e.ExeFile[end] == 0 { + break + } + end++ + } + + return &WindowsProcess{ + pid: int(e.ProcessID), + ppid: int(e.ParentProcessID), + exe: syscall.UTF16ToString(e.ExeFile[:end]), + } +} + +func findProcess(pid int) (Process, error) { + ps, err := processes() + if err != nil { + return nil, err + } + + for _, p := range ps { + if p.Pid() == pid { + return p, nil + } + } + + return nil, nil +} + +func processes() ([]Process, error) { + handle, _, _ := procCreateToolhelp32Snapshot.Call( + 0x00000002, + 0) + if handle < 0 { + return nil, syscall.GetLastError() + } + defer procCloseHandle.Call(handle) + + var entry PROCESSENTRY32 + entry.Size = uint32(unsafe.Sizeof(entry)) + ret, _, _ := procProcess32First.Call(handle, uintptr(unsafe.Pointer(&entry))) + if ret == 0 { + return nil, fmt.Errorf("Error retrieving process info.") + } + + results := make([]Process, 0, 50) + for { + results = append(results, newWindowsProcess(&entry)) + + ret, _, _ := procProcess32Next.Call(handle, uintptr(unsafe.Pointer(&entry))) + if ret == 0 { + break + } + } + + return results, nil +}