Repository: rsteube/carapace
Pull request #490 (2022-04-10 10:13:39)

embed go-ps


Patch:
diff --git a/go.mod b/go.mod
index 253ccb8..5355eae 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 5e87c26..ec06252 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 a03c92c..2bec36a 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 0000000..2298515
--- /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 0000000..2b5e8ed
--- /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 0000000..3b417a0
--- /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_freebsd.go b/third_party/github.com/mitchellh/go-ps/process_freebsd.go
new file mode 100644
index 0000000..48115a4
--- /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 0000000..c8218a0
--- /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 0000000..c4a86d0
--- /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_unix.go b/third_party/github.com/mitchellh/go-ps/process_unix.go
new file mode 100644
index 0000000..faec61a
--- /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_windows.go b/third_party/github.com/mitchellh/go-ps/process_windows.go
new file mode 100644
index 0000000..a930f90
--- /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
+}


Test patch:
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 0000000..5cbf1ae
--- /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_test.go b/third_party/github.com/mitchellh/go-ps/process_test.go
new file mode 100644
index 0000000..1bcbc32
--- /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_test.go b/third_party/github.com/mitchellh/go-ps/process_unix_test.go
new file mode 100644
index 0000000..0e53426
--- /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)
+}