cli/connhelper: remove dependency on pkg/process
This package will not be included in the api or client modules, and
we're currently only using a single function of it, and only the
unix implementation, so let's fork it for now (although the package
may be moved to moby/sys).
This removes the last dependency on github.com/docker/docker.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 2abcbf842f)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
@@ -4,12 +4,17 @@ package commandconn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/process"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
@@ -51,16 +56,16 @@ func TestCloseRunningCommand(t *testing.T) {
|
||||
c, err := New(ctx, "sh", "-c", "while true; do sleep 1; done")
|
||||
assert.NilError(t, err)
|
||||
cmdConn := c.(*commandConn)
|
||||
assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
|
||||
assert.Check(t, processAlive(cmdConn.cmd.Process.Pid))
|
||||
|
||||
n, err := c.Write([]byte("hello"))
|
||||
assert.Check(t, is.Equal(len("hello"), n))
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
|
||||
assert.Check(t, processAlive(cmdConn.cmd.Process.Pid))
|
||||
|
||||
err = cmdConn.Close()
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid))
|
||||
assert.Check(t, !processAlive(cmdConn.cmd.Process.Pid))
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
@@ -79,7 +84,7 @@ func TestCloseTwice(t *testing.T) {
|
||||
c, err := New(ctx, "sh", "-c", "echo hello; sleep 1; exit 0")
|
||||
assert.NilError(t, err)
|
||||
cmdConn := c.(*commandConn)
|
||||
assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
|
||||
assert.Check(t, processAlive(cmdConn.cmd.Process.Pid))
|
||||
|
||||
b := make([]byte, 32)
|
||||
n, err := c.Read(b)
|
||||
@@ -88,11 +93,11 @@ func TestCloseTwice(t *testing.T) {
|
||||
|
||||
err = cmdConn.Close()
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid))
|
||||
assert.Check(t, !processAlive(cmdConn.cmd.Process.Pid))
|
||||
|
||||
err = cmdConn.Close()
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid))
|
||||
assert.Check(t, !processAlive(cmdConn.cmd.Process.Pid))
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
@@ -111,7 +116,7 @@ func TestEOFTimeout(t *testing.T) {
|
||||
c, err := New(ctx, "sh", "-c", "sleep 20")
|
||||
assert.NilError(t, err)
|
||||
cmdConn := c.(*commandConn)
|
||||
assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
|
||||
assert.Check(t, processAlive(cmdConn.cmd.Process.Pid))
|
||||
|
||||
cmdConn.stdout = mockStdoutEOF{}
|
||||
|
||||
@@ -148,7 +153,7 @@ func TestCloseWhileWriting(t *testing.T) {
|
||||
c, err := New(ctx, "sh", "-c", "while true; do sleep 1; done")
|
||||
assert.NilError(t, err)
|
||||
cmdConn := c.(*commandConn)
|
||||
assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
|
||||
assert.Check(t, processAlive(cmdConn.cmd.Process.Pid))
|
||||
|
||||
writeErrC := make(chan error)
|
||||
go func() {
|
||||
@@ -164,7 +169,7 @@ func TestCloseWhileWriting(t *testing.T) {
|
||||
|
||||
err = c.Close()
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid))
|
||||
assert.Check(t, !processAlive(cmdConn.cmd.Process.Pid))
|
||||
|
||||
writeErr := <-writeErrC
|
||||
assert.ErrorContains(t, writeErr, "file already closed")
|
||||
@@ -176,7 +181,7 @@ func TestCloseWhileReading(t *testing.T) {
|
||||
c, err := New(ctx, "sh", "-c", "while true; do sleep 1; done")
|
||||
assert.NilError(t, err)
|
||||
cmdConn := c.(*commandConn)
|
||||
assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
|
||||
assert.Check(t, processAlive(cmdConn.cmd.Process.Pid))
|
||||
|
||||
readErrC := make(chan error)
|
||||
go func() {
|
||||
@@ -193,8 +198,37 @@ func TestCloseWhileReading(t *testing.T) {
|
||||
|
||||
err = cmdConn.Close()
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid))
|
||||
assert.Check(t, !processAlive(cmdConn.cmd.Process.Pid))
|
||||
|
||||
readErr := <-readErrC
|
||||
assert.Check(t, is.ErrorIs(readErr, fs.ErrClosed))
|
||||
}
|
||||
|
||||
// processAlive returns true if a process with a given pid is running. It only considers
|
||||
// positive PIDs; 0 (all processes in the current process group), -1 (all processes
|
||||
// with a PID larger than 1), and negative (-n, all processes in process group
|
||||
// "n") values for pid are never considered to be alive.
|
||||
//
|
||||
// It was forked from https://github.com/moby/moby/blob/v28.3.3/pkg/process/process_unix.go#L17-L42
|
||||
func processAlive(pid int) bool {
|
||||
if pid < 1 {
|
||||
return false
|
||||
}
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
// OS X does not have a proc filesystem. Use kill -0 pid to judge if the
|
||||
// process exists. From KILL(2): https://www.freebsd.org/cgi/man.cgi?query=kill&sektion=2&manpath=OpenDarwin+7.2.1
|
||||
//
|
||||
// Sig may be one of the signals specified in sigaction(2) or it may
|
||||
// be 0, in which case error checking is performed but no signal is
|
||||
// actually sent. This can be used to check the validity of pid.
|
||||
err := syscall.Kill(pid, 0)
|
||||
|
||||
// Either the PID was found (no error), or we get an EPERM, which means
|
||||
// the PID exists, but we don't have permissions to signal it.
|
||||
return err == nil || errors.Is(err, syscall.EPERM)
|
||||
default:
|
||||
_, err := os.Stat(filepath.Join("/proc", strconv.Itoa(pid)))
|
||||
return err == nil
|
||||
}
|
||||
}
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
// Package process provides a set of basic functions to manage individual
|
||||
// processes.
|
||||
package process
|
||||
-82
@@ -1,82 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Alive returns true if process with a given pid is running. It only considers
|
||||
// positive PIDs; 0 (all processes in the current process group), -1 (all processes
|
||||
// with a PID larger than 1), and negative (-n, all processes in process group
|
||||
// "n") values for pid are never considered to be alive.
|
||||
func Alive(pid int) bool {
|
||||
if pid < 1 {
|
||||
return false
|
||||
}
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
// OS X does not have a proc filesystem. Use kill -0 pid to judge if the
|
||||
// process exists. From KILL(2): https://www.freebsd.org/cgi/man.cgi?query=kill&sektion=2&manpath=OpenDarwin+7.2.1
|
||||
//
|
||||
// Sig may be one of the signals specified in sigaction(2) or it may
|
||||
// be 0, in which case error checking is performed but no signal is
|
||||
// actually sent. This can be used to check the validity of pid.
|
||||
err := unix.Kill(pid, 0)
|
||||
|
||||
// Either the PID was found (no error) or we get an EPERM, which means
|
||||
// the PID exists, but we don't have permissions to signal it.
|
||||
return err == nil || errors.Is(err, unix.EPERM)
|
||||
default:
|
||||
_, err := os.Stat(filepath.Join("/proc", strconv.Itoa(pid)))
|
||||
return err == nil
|
||||
}
|
||||
}
|
||||
|
||||
// Kill force-stops a process. It only considers positive PIDs; 0 (all processes
|
||||
// in the current process group), -1 (all processes with a PID larger than 1),
|
||||
// and negative (-n, all processes in process group "n") values for pid are
|
||||
// ignored. Refer to [KILL(2)] for details.
|
||||
//
|
||||
// [KILL(2)]: https://man7.org/linux/man-pages/man2/kill.2.html
|
||||
func Kill(pid int) error {
|
||||
if pid < 1 {
|
||||
return fmt.Errorf("invalid PID (%d): only positive PIDs are allowed", pid)
|
||||
}
|
||||
err := unix.Kill(pid, unix.SIGKILL)
|
||||
if err != nil && !errors.Is(err, unix.ESRCH) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Zombie return true if process has a state with "Z". It only considers positive
|
||||
// PIDs; 0 (all processes in the current process group), -1 (all processes with
|
||||
// a PID larger than 1), and negative (-n, all processes in process group "n")
|
||||
// values for pid are ignored. Refer to [PROC(5)] for details.
|
||||
//
|
||||
// [PROC(5)]: https://man7.org/linux/man-pages/man5/proc.5.html
|
||||
func Zombie(pid int) (bool, error) {
|
||||
if pid < 1 {
|
||||
return false, nil
|
||||
}
|
||||
data, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if cols := bytes.SplitN(data, []byte(" "), 4); len(cols) >= 3 && string(cols[2]) == "Z" {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// Alive returns true if process with a given pid is running.
|
||||
func Alive(pid int) bool {
|
||||
h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
var c uint32
|
||||
err = windows.GetExitCodeProcess(h, &c)
|
||||
_ = windows.CloseHandle(h)
|
||||
if err != nil {
|
||||
// From the GetExitCodeProcess function (processthreadsapi.h) API docs:
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess
|
||||
//
|
||||
// The GetExitCodeProcess function returns a valid error code defined by the
|
||||
// application only after the thread terminates. Therefore, an application should
|
||||
// not use STILL_ACTIVE (259) as an error code (STILL_ACTIVE is a macro for
|
||||
// STATUS_PENDING (minwinbase.h)). If a thread returns STILL_ACTIVE (259) as
|
||||
// an error code, then applications that test for that value could interpret it
|
||||
// to mean that the thread is still running, and continue to test for the
|
||||
// completion of the thread after the thread has terminated, which could put
|
||||
// the application into an infinite loop.
|
||||
return c == uint32(windows.STATUS_PENDING)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Kill force-stops a process.
|
||||
func Kill(pid int) error {
|
||||
p, err := os.FindProcess(pid)
|
||||
if err == nil {
|
||||
err = p.Kill()
|
||||
if err != nil && err != os.ErrProcessDone {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Vendored
-1
@@ -96,7 +96,6 @@ github.com/docker/docker/internal/lazyregexp
|
||||
github.com/docker/docker/internal/multierror
|
||||
github.com/docker/docker/pkg/homedir
|
||||
github.com/docker/docker/pkg/jsonmessage
|
||||
github.com/docker/docker/pkg/process
|
||||
github.com/docker/docker/pkg/progress
|
||||
github.com/docker/docker/pkg/stdcopy
|
||||
github.com/docker/docker/pkg/streamformatter
|
||||
|
||||
Reference in New Issue
Block a user