mirror of
https://github.com/containers/podman.git
synced 2025-05-17 15:18:43 +08:00
Merge pull request #25906 from jankaluza/25104-pidfs
Verify the ExecSession pid before killing it.
This commit is contained in:
@ -16,6 +16,7 @@ import (
|
||||
"github.com/containers/common/pkg/util"
|
||||
"github.com/containers/podman/v5/libpod/define"
|
||||
"github.com/containers/podman/v5/libpod/events"
|
||||
"github.com/containers/podman/v5/pkg/pidhandle"
|
||||
"github.com/containers/storage/pkg/stringid"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
@ -101,6 +102,10 @@ type ExecSession struct {
|
||||
// Config is the configuration of this exec session.
|
||||
// Cannot be empty.
|
||||
Config *ExecConfig `json:"config"`
|
||||
|
||||
// PIDData is a string uniquely identifying the PID. The value is platform
|
||||
// specific. It is generated by pidhandle package and used by isSessionAlive.
|
||||
PIDData string `json:"pidData,omitempty"`
|
||||
}
|
||||
|
||||
// ID returns the ID of an exec session.
|
||||
@ -251,6 +256,27 @@ func (c *Container) ExecCreate(config *ExecConfig) (string, error) {
|
||||
return session.Id, nil
|
||||
}
|
||||
|
||||
// Returns a serialized representation of the pid using the PIDHandle package.
|
||||
// This string can be passed to NewPIDHandleFromString to recreate a PIDHandle
|
||||
// that reliably refers to the same process as the original.
|
||||
//
|
||||
// NOTE that there is a still race in generating the PIDData on start but this
|
||||
// is as good as we can do currently. Long term we could change conmon to
|
||||
// send pidfd back directly.
|
||||
func getPidData(pid int) string {
|
||||
pidHandle, err := pidhandle.NewPIDHandle(pid)
|
||||
if err != nil {
|
||||
logrus.Debugf("getting the PID handle for pid %d: %v", pid, err)
|
||||
return ""
|
||||
}
|
||||
defer pidHandle.Close()
|
||||
pidData, err := pidHandle.String()
|
||||
if err != nil {
|
||||
logrus.Debugf("generating PIDData (%d) failed: %v", pid, err)
|
||||
}
|
||||
return pidData
|
||||
}
|
||||
|
||||
// ExecStart starts an exec session in the container, but does not attach to it.
|
||||
// Returns immediately upon starting the exec session, unlike other ExecStart
|
||||
// functions, which will only return when the exec session exits.
|
||||
@ -296,6 +322,7 @@ func (c *Container) ExecStart(sessionID string) error {
|
||||
// Update and save session to reflect PID/running
|
||||
session.PID = pid
|
||||
session.State = define.ExecStateRunning
|
||||
session.PIDData = getPidData(pid)
|
||||
|
||||
return c.save()
|
||||
}
|
||||
@ -354,6 +381,7 @@ func (c *Container) execStartAndAttach(sessionID string, streams *define.AttachS
|
||||
// Update and save session to reflect PID/running
|
||||
session.PID = pid
|
||||
session.State = define.ExecStateRunning
|
||||
session.PIDData = getPidData(pid)
|
||||
|
||||
if err := c.save(); err != nil {
|
||||
lastErr = err
|
||||
@ -535,6 +563,7 @@ func (c *Container) ExecHTTPStartAndAttach(sessionID string, r *http.Request, w
|
||||
|
||||
session.PID = pid
|
||||
session.State = define.ExecStateRunning
|
||||
session.PIDData = getPidData(pid)
|
||||
|
||||
if err := c.save(); err != nil {
|
||||
lastErr = err
|
||||
@ -1057,17 +1086,17 @@ func (c *Container) readExecExitCode(sessionID string) (int, error) {
|
||||
}
|
||||
|
||||
// getExecSessionPID gets the PID of an active exec session
|
||||
func (c *Container) getExecSessionPID(sessionID string) (int, error) {
|
||||
func (c *Container) getExecSessionPID(sessionID string) (int, string, error) {
|
||||
session, ok := c.state.ExecSessions[sessionID]
|
||||
if ok {
|
||||
return session.PID, nil
|
||||
return session.PID, session.PIDData, nil
|
||||
}
|
||||
oldSession, ok := c.state.LegacyExecSessions[sessionID]
|
||||
if ok {
|
||||
return oldSession.PID, nil
|
||||
return oldSession.PID, "", nil
|
||||
}
|
||||
|
||||
return -1, fmt.Errorf("no exec session with ID %s found in container %s: %w", sessionID, c.ID(), define.ErrNoSuchExecSession)
|
||||
return -1, "", fmt.Errorf("no exec session with ID %s found in container %s: %w", sessionID, c.ID(), define.ErrNoSuchExecSession)
|
||||
}
|
||||
|
||||
// getKnownExecSessions gets a list of all exec sessions we think are running,
|
||||
@ -1128,6 +1157,7 @@ func (c *Container) getActiveExecSessions() ([]string, error) {
|
||||
}
|
||||
session.ExitCode = exitCode
|
||||
session.PID = 0
|
||||
session.PIDData = ""
|
||||
session.State = define.ExecStateStopped
|
||||
|
||||
c.newExecDiedEvent(session.ID(), exitCode)
|
||||
@ -1262,6 +1292,7 @@ func justWriteExecExitCode(c *Container, sessionID string, exitCode int, emitEve
|
||||
session.State = define.ExecStateStopped
|
||||
session.ExitCode = exitCode
|
||||
session.PID = 0
|
||||
session.PIDData = ""
|
||||
|
||||
// Finally, save our changes.
|
||||
return c.save()
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"github.com/containers/podman/v5/libpod/define"
|
||||
"github.com/containers/podman/v5/pkg/errorhandling"
|
||||
"github.com/containers/podman/v5/pkg/lookup"
|
||||
"github.com/containers/podman/v5/pkg/pidhandle"
|
||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
@ -214,26 +215,32 @@ func (r *ConmonOCIRuntime) ExecAttachResize(ctr *Container, sessionID string, ne
|
||||
|
||||
// ExecStopContainer stops a given exec session in a running container.
|
||||
func (r *ConmonOCIRuntime) ExecStopContainer(ctr *Container, sessionID string, timeout uint) error {
|
||||
pid, err := ctr.getExecSessionPID(sessionID)
|
||||
pid, pidData, err := ctr.getExecSessionPID(sessionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Debugf("Going to stop container %s exec session %s", ctr.ID(), sessionID)
|
||||
|
||||
// Is the session dead?
|
||||
// Ping the PID with signal 0 to see if it still exists.
|
||||
if err := unix.Kill(pid, 0); err != nil {
|
||||
if err == unix.ESRCH {
|
||||
return nil
|
||||
pidHandle, err := pidhandle.NewPIDHandleFromString(pid, pidData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting the PID handle for pid %d from '%s': %w", pid, pidData, err)
|
||||
}
|
||||
return fmt.Errorf("pinging container %s exec session %s PID %d with signal 0: %w", ctr.ID(), sessionID, pid, err)
|
||||
defer pidHandle.Close()
|
||||
|
||||
// Is the session dead?
|
||||
sessionAlive, err := pidHandle.IsAlive()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting the process status for pid %d: %w", pid, err)
|
||||
}
|
||||
if !sessionAlive {
|
||||
return nil
|
||||
}
|
||||
|
||||
if timeout > 0 {
|
||||
// Use SIGTERM by default, then SIGSTOP after timeout.
|
||||
logrus.Debugf("Killing exec session %s (PID %d) of container %s with SIGTERM", sessionID, pid, ctr.ID())
|
||||
if err := unix.Kill(pid, unix.SIGTERM); err != nil {
|
||||
if err := pidHandle.Kill(unix.SIGTERM); err != nil {
|
||||
if err == unix.ESRCH {
|
||||
return nil
|
||||
}
|
||||
@ -251,7 +258,7 @@ func (r *ConmonOCIRuntime) ExecStopContainer(ctr *Container, sessionID string, t
|
||||
|
||||
// SIGTERM did not work. On to SIGKILL.
|
||||
logrus.Debugf("Killing exec session %s (PID %d) of container %s with SIGKILL", sessionID, pid, ctr.ID())
|
||||
if err := unix.Kill(pid, unix.SIGTERM); err != nil {
|
||||
if err := pidHandle.Kill(unix.SIGTERM); err != nil {
|
||||
if err == unix.ESRCH {
|
||||
return nil
|
||||
}
|
||||
@ -268,23 +275,26 @@ func (r *ConmonOCIRuntime) ExecStopContainer(ctr *Container, sessionID string, t
|
||||
|
||||
// ExecUpdateStatus checks if the given exec session is still running.
|
||||
func (r *ConmonOCIRuntime) ExecUpdateStatus(ctr *Container, sessionID string) (bool, error) {
|
||||
pid, err := ctr.getExecSessionPID(sessionID)
|
||||
pid, pidData, err := ctr.getExecSessionPID(sessionID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
logrus.Debugf("Checking status of container %s exec session %s", ctr.ID(), sessionID)
|
||||
|
||||
// Is the session dead?
|
||||
// Ping the PID with signal 0 to see if it still exists.
|
||||
if err := unix.Kill(pid, 0); err != nil {
|
||||
if err == unix.ESRCH {
|
||||
return false, nil
|
||||
pidHandle, err := pidhandle.NewPIDHandleFromString(pid, pidData)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("getting the PID handle for pid %d from '%s': %w", pid, pidData, err)
|
||||
}
|
||||
return false, fmt.Errorf("pinging container %s exec session %s PID %d with signal 0: %w", ctr.ID(), sessionID, pid, err)
|
||||
defer pidHandle.Close()
|
||||
|
||||
// Is the session dead?
|
||||
sessionAlive, err := pidHandle.IsAlive()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("getting the process status for pid %d: %w", pid, err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
return sessionAlive, nil
|
||||
}
|
||||
|
||||
// ExecAttachSocketPath is the path to a container's exec session attach socket.
|
||||
|
140
pkg/pidhandle/pidhandle.go
Normal file
140
pkg/pidhandle/pidhandle.go
Normal file
@ -0,0 +1,140 @@
|
||||
//go:build !windows
|
||||
|
||||
// Package for handling processes and PIDs.
|
||||
package pidhandle
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/process"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// PIDHandle defines an interface for working with operating system processes
|
||||
// in a reliable way. OS-specific implementations include additional logic
|
||||
// to try to ensure that operations (e.g., sending signals) are performed
|
||||
// on the exact same process that was originally referenced when the PIDHandle
|
||||
// was created via NewPIDHandle or NewPIDHandleFromString.
|
||||
//
|
||||
// This prevents accidental interaction with a different process in scenarios
|
||||
// where the original process has exited and its PID has been reused by
|
||||
// the system for an unrelated process.
|
||||
type PIDHandle interface {
|
||||
// Returns the PID associated with this PIDHandle.
|
||||
PID() int
|
||||
// Releases the PIDHandle resources.
|
||||
Close() error
|
||||
// Sends the signal to process.
|
||||
Kill(signal unix.Signal) error
|
||||
// Returns true in case the process is still alive.
|
||||
IsAlive() (bool, error)
|
||||
// Returns a serialized representation of the PIDHandle.
|
||||
// This string can be passed to NewPIDHandleFromString to recreate
|
||||
// a PIDHandle that reliably refers to the same process as the original.
|
||||
String() (string, error)
|
||||
}
|
||||
|
||||
// The pidData value used when no process with this PID exists when creating
|
||||
// the PIDHandle.
|
||||
const noSuchProcessID = "no-proc"
|
||||
|
||||
// The pidData prefix used when only the process start time (creation time)
|
||||
// is supported when creating the PIDHandle to uniquely identify the process.
|
||||
const startTimePrefix = "start-time:"
|
||||
|
||||
type pidHandle struct {
|
||||
pid int
|
||||
pidData string
|
||||
}
|
||||
|
||||
// Returns the PID.
|
||||
func (h *pidHandle) PID() int {
|
||||
return h.pid
|
||||
}
|
||||
|
||||
// Close releases the PIDHandle resource.
|
||||
func (h *pidHandle) Close() error {
|
||||
// No resources for the default PIDHandle implementation.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sends the signal to process.
|
||||
func (h *pidHandle) Kill(signal unix.Signal) error {
|
||||
if h.pidData == noSuchProcessID {
|
||||
// The process did not exist when we created the PIDHandle, so return
|
||||
// ESRCH error.
|
||||
return unix.ESRCH
|
||||
}
|
||||
|
||||
// Get the start-time of the process and check if it is the same as
|
||||
// the one we store in pidData. If it is not, we know that the PID
|
||||
// has been recycled and return ESRCH error.
|
||||
startTime, found := strings.CutPrefix(h.pidData, startTimePrefix)
|
||||
if found {
|
||||
p, err := process.NewProcess(int32(h.pid))
|
||||
if err != nil {
|
||||
if err == process.ErrorProcessNotRunning {
|
||||
return unix.ESRCH
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
ctime, err := p.CreateTime()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strconv.FormatInt(ctime, 10) != startTime {
|
||||
return unix.ESRCH
|
||||
}
|
||||
}
|
||||
|
||||
return unix.Kill(h.pid, signal)
|
||||
}
|
||||
|
||||
// Returns true in case the process is still alive.
|
||||
func (h *pidHandle) IsAlive() (bool, error) {
|
||||
err := h.Kill(0)
|
||||
if err != nil {
|
||||
if err == unix.ESRCH {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Returns a serialized representation of the PIDHandle.
|
||||
// This string can be passed to NewPIDHandleFromString to recreate
|
||||
// a PIDHandle that reliably refers to the same process as the original.
|
||||
func (h *pidHandle) String() (string, error) {
|
||||
if len(h.pidData) != 0 {
|
||||
return h.pidData, nil
|
||||
}
|
||||
|
||||
// Get the start-time of the process and return it as string.
|
||||
p, err := process.NewProcess(int32(h.pid))
|
||||
if err != nil {
|
||||
if err == process.ErrorProcessNotRunning {
|
||||
return noSuchProcessID, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
ctime, err := p.CreateTime()
|
||||
if err != nil {
|
||||
// The process existed, but we cannot get its start-time. There is
|
||||
// either an issue with getting it, or the process terminated in the
|
||||
// mean-time. We have no way to find out what actually happened, so
|
||||
// in this case, we just fallback to an empty string. This will mean
|
||||
// that Kill or IsAlive might kill wrong process in rare situation
|
||||
// when CreateTime() failed for different reason than the process
|
||||
// terminated...
|
||||
logrus.Debugf("Getting CreateTime for process (%d) failed: %v", h.pid, err)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return startTimePrefix + strconv.FormatInt(ctime, 10), nil
|
||||
}
|
201
pkg/pidhandle/pidhandle_linux.go
Normal file
201
pkg/pidhandle/pidhandle_linux.go
Normal file
@ -0,0 +1,201 @@
|
||||
//go:build linux
|
||||
|
||||
// Package for handling processes and PIDs.
|
||||
package pidhandle
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type pidfdHandle struct {
|
||||
pidfd int
|
||||
normalHandle pidHandle
|
||||
}
|
||||
|
||||
// Store the "unix." methods in variables so we can mock them
|
||||
// in the unit-tests and test out different return value.
|
||||
var (
|
||||
pidfdOpen = unix.PidfdOpen
|
||||
newFileHandle = unix.NewFileHandle
|
||||
openByHandleAt = unix.OpenByHandleAt
|
||||
nameToHandleAt = unix.NameToHandleAt
|
||||
pidfdSendSignal = unix.PidfdSendSignal
|
||||
)
|
||||
|
||||
// The pidData prefix used when the pidfd and name_to_handle is supported
|
||||
// when creating the PIDHandle to uniquely identify the process.
|
||||
const nameToHandlePrefix = "name-to-handle:"
|
||||
|
||||
// Creates new PIDHandle for a given process pid.
|
||||
//
|
||||
// Note that there still can be a race condition if the process terminates
|
||||
// *before* the PIDHandle is created. It is a caller's responsibility
|
||||
// to ensure that this either cannot happen or accept this risk.
|
||||
func NewPIDHandle(pid int) (PIDHandle, error) {
|
||||
// Use the pidfd to obtain the file-descriptor pointing to the process.
|
||||
pidData := ""
|
||||
pidfd, err := pidfdOpen(pid, 0)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case unix.ENOSYS:
|
||||
// Do not fail if PidFdOpen is not supported, we will
|
||||
// fallback to process start-time later.
|
||||
|
||||
case unix.ESRCH:
|
||||
// The process does not exist, so any future call of Kill
|
||||
// or IsAlive should return unix.ESRCH, even if the pid is
|
||||
// recycled in the future. Let's note it in the pidData.
|
||||
pidData = noSuchProcessID
|
||||
|
||||
case unix.EINVAL:
|
||||
// The PidfdOpen returns EINVAL if pid is invalid or if it refers
|
||||
// to a thread and not to process. This is not a valid PID for
|
||||
// PIDHandle and it most likely means the pid has been recycled
|
||||
// (or there is a programming error). We therefore store
|
||||
// noSuchProcessID into pidData to return unix.ESRCH in
|
||||
// the future Kill or IsAlive calls.
|
||||
pidData = noSuchProcessID
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("pidfdOpen failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
h := pidfdHandle{
|
||||
pidfd: pidfd,
|
||||
normalHandle: pidHandle{pid: pid, pidData: pidData},
|
||||
}
|
||||
|
||||
pidData, err = h.String()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h.normalHandle.pidData = pidData
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
// Creates new PIDHandle for a given process pid using the pidData
|
||||
// originally obtained from PIDHandle.String().
|
||||
func NewPIDHandleFromString(pid int, pidData string) (PIDHandle, error) {
|
||||
h := pidfdHandle{
|
||||
pidfd: -1,
|
||||
normalHandle: pidHandle{pid: pid, pidData: pidData},
|
||||
}
|
||||
|
||||
// Open the pidfd encoded in pidData.
|
||||
data, found := strings.CutPrefix(pidData, nameToHandlePrefix)
|
||||
if found {
|
||||
// Split the data.
|
||||
parts := strings.SplitN(data, " ", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid format, expected 2 parts")
|
||||
}
|
||||
|
||||
// Parse fhType.
|
||||
fhTypeInt, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fhType := int32(fhTypeInt)
|
||||
|
||||
// Decode hex string to bytes.
|
||||
bytes, err := hex.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create FileHandle and open it.
|
||||
fh := newFileHandle(fhType, bytes)
|
||||
fd, err := pidfdOpen(os.Getpid(), 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer unix.Close(fd)
|
||||
pidfd, err := openByHandleAt(fd, fh, 0)
|
||||
if err != nil {
|
||||
if err == unix.ESTALE {
|
||||
h.normalHandle.pidData = noSuchProcessID
|
||||
return &h, nil
|
||||
}
|
||||
return nil, fmt.Errorf("openByHandleAt failed: %w", err)
|
||||
}
|
||||
h.pidfd = pidfd
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
// Returns the PID associated with this PIDHandle.
|
||||
func (h *pidfdHandle) PID() int {
|
||||
return h.normalHandle.PID()
|
||||
}
|
||||
|
||||
// Close releases the pidfd resource.
|
||||
func (h *pidfdHandle) Close() error {
|
||||
if h.pidfd != 0 {
|
||||
err := unix.Close(h.pidfd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to close pidfd: %w", err)
|
||||
}
|
||||
h.pidfd = 0
|
||||
}
|
||||
return h.normalHandle.Close()
|
||||
}
|
||||
|
||||
// Sends the signal to process.
|
||||
func (h *pidfdHandle) Kill(signal unix.Signal) error {
|
||||
if h.pidfd > -1 {
|
||||
return pidfdSendSignal(h.pidfd, signal, nil, 0)
|
||||
}
|
||||
|
||||
return h.normalHandle.Kill(signal)
|
||||
}
|
||||
|
||||
// Returns true in case the process is still alive.
|
||||
func (h *pidfdHandle) IsAlive() (bool, error) {
|
||||
err := h.Kill(0)
|
||||
if err != nil {
|
||||
if err == unix.ESRCH {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Returns a serialized representation of the PIDHandle.
|
||||
// This string can be passed to NewPIDHandleFromString to recreate
|
||||
// a PIDHandle that reliably refers to the same process as the original.
|
||||
func (h *pidfdHandle) String() (string, error) {
|
||||
if len(h.normalHandle.pidData) != 0 {
|
||||
return h.normalHandle.pidData, nil
|
||||
}
|
||||
|
||||
// Serialize the pidfd to string if possible.
|
||||
if h.pidfd > -1 {
|
||||
fh, _, err := nameToHandleAt(h.pidfd, "", unix.AT_EMPTY_PATH)
|
||||
if err != nil {
|
||||
// Do not fail if NameToHandleAt is not supported, we will
|
||||
// fallback to process start-time later.
|
||||
if err == unix.ENOTSUP {
|
||||
logrus.Debugf("NameToHandleAt(%d) failed: %v", h.pidfd, err)
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
hexStr := hex.EncodeToString(fh.Bytes())
|
||||
return nameToHandlePrefix + strconv.Itoa(int(fh.Type())) + " " + hexStr, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to default String().
|
||||
return h.normalHandle.String()
|
||||
}
|
235
pkg/pidhandle/pidhandle_linux_test.go
Normal file
235
pkg/pidhandle/pidhandle_linux_test.go
Normal file
@ -0,0 +1,235 @@
|
||||
//go:build linux
|
||||
|
||||
package pidhandle
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func TestNewPIDHandle(t *testing.T) {
|
||||
// Mock the pidfdOpen
|
||||
original := pidfdOpen
|
||||
defer func() { pidfdOpen = original }()
|
||||
pidfdOpen = func(pid int, flags int) (int, error) {
|
||||
return 255, nil
|
||||
}
|
||||
|
||||
// Mock the nameToHandleAt
|
||||
original_nameToHandleAt := nameToHandleAt
|
||||
defer func() { nameToHandleAt = original_nameToHandleAt }()
|
||||
nameToHandleAt = func(dirfd int, path string, flags int) (handle unix.FileHandle, mountID int, err error) {
|
||||
return newFileHandle(254, []byte("test")), 1, nil
|
||||
}
|
||||
|
||||
h, err := NewPIDHandle(os.Getpid())
|
||||
assert.NoError(t, err)
|
||||
defer h.Close()
|
||||
|
||||
pidData, err := h.String()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, nameToHandlePrefix+"254 74657374", pidData)
|
||||
assert.Equal(t, h.PID(), os.Getpid())
|
||||
}
|
||||
|
||||
func TestNewPIDHandlepidfdOpenNotSupported(t *testing.T) {
|
||||
// Mock the pidfdOpen
|
||||
original := pidfdOpen
|
||||
defer func() { pidfdOpen = original }()
|
||||
pidfdOpen = func(pid int, flags int) (int, error) {
|
||||
return -1, unix.ENOSYS
|
||||
}
|
||||
|
||||
h, err := NewPIDHandle(os.Getpid())
|
||||
assert.NoError(t, err)
|
||||
defer h.Close()
|
||||
|
||||
pidData, err := h.String()
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, pidData, startTimePrefix)
|
||||
assert.Equal(t, h.PID(), os.Getpid())
|
||||
}
|
||||
|
||||
func TestPIDHandleStringnameToHandleAtNotSupported(t *testing.T) {
|
||||
// Mock the pidfdOpen
|
||||
original := pidfdOpen
|
||||
defer func() { pidfdOpen = original }()
|
||||
pidfdOpen = func(pid int, flags int) (int, error) {
|
||||
return 254, nil
|
||||
}
|
||||
|
||||
// Mock the nameToHandleAt
|
||||
original_nameToHandleAt := nameToHandleAt
|
||||
defer func() { nameToHandleAt = original_nameToHandleAt }()
|
||||
nameToHandleAt = func(dirfd int, path string, flags int) (handle unix.FileHandle, mountID int, err error) {
|
||||
return newFileHandle(-1, []byte("")), 1, unix.ENOTSUP
|
||||
}
|
||||
|
||||
h, err := NewPIDHandle(os.Getpid())
|
||||
assert.NoError(t, err)
|
||||
defer h.Close()
|
||||
|
||||
pidData, err := h.String()
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, pidData, startTimePrefix)
|
||||
}
|
||||
|
||||
func TestNewPIDHandleFromString(t *testing.T) {
|
||||
// Mock the pidfdOpen
|
||||
original := pidfdOpen
|
||||
defer func() { pidfdOpen = original }()
|
||||
pidfdOpen = func(pid int, flags int) (int, error) {
|
||||
return 254, nil
|
||||
}
|
||||
|
||||
// Mock the newFileHandle
|
||||
original_newFileHandle := newFileHandle
|
||||
defer func() { newFileHandle = original_newFileHandle }()
|
||||
newFileHandle = func(fhType int32, bytes []byte) unix.FileHandle {
|
||||
return unix.NewFileHandle(254, []byte("test"))
|
||||
}
|
||||
|
||||
// Mock the openByHandleAt
|
||||
original_openByHandleAt := openByHandleAt
|
||||
defer func() { openByHandleAt = original_openByHandleAt }()
|
||||
openByHandleAt = func(mountFD int, handle unix.FileHandle, flags int) (fd int, err error) {
|
||||
return 255, nil
|
||||
}
|
||||
|
||||
// Mock the nameToHandleAt
|
||||
original_nameToHandleAt := nameToHandleAt
|
||||
defer func() { nameToHandleAt = original_nameToHandleAt }()
|
||||
nameToHandleAt = func(dirfd int, path string, flags int) (handle unix.FileHandle, mountID int, err error) {
|
||||
return newFileHandle(255, []byte("test")), 1, nil
|
||||
}
|
||||
|
||||
h, err := NewPIDHandleFromString(os.Getpid(), nameToHandlePrefix+"254 74657374")
|
||||
assert.NoError(t, err)
|
||||
defer h.Close()
|
||||
|
||||
pidData, err := h.String()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, nameToHandlePrefix+"254 74657374", pidData)
|
||||
assert.Equal(t, h.PID(), os.Getpid())
|
||||
}
|
||||
|
||||
func TestNewPIDHandleFromStringWrongPidData(t *testing.T) {
|
||||
// Mock the pidfdOpen
|
||||
original := pidfdOpen
|
||||
defer func() { pidfdOpen = original }()
|
||||
pidfdOpen = func(pid int, flags int) (int, error) {
|
||||
return 254, nil
|
||||
}
|
||||
|
||||
// Mock the newFileHandle
|
||||
original_newFileHandle := newFileHandle
|
||||
defer func() { newFileHandle = original_newFileHandle }()
|
||||
newFileHandle = func(fhType int32, bytes []byte) unix.FileHandle {
|
||||
return unix.NewFileHandle(254, []byte("test"))
|
||||
}
|
||||
|
||||
// Mock the openByHandleAt
|
||||
original_openByHandleAt := openByHandleAt
|
||||
defer func() { openByHandleAt = original_openByHandleAt }()
|
||||
openByHandleAt = func(mountFD int, handle unix.FileHandle, flags int) (fd int, err error) {
|
||||
return 255, nil
|
||||
}
|
||||
|
||||
values := []string{
|
||||
nameToHandlePrefix + "foo",
|
||||
nameToHandlePrefix + "254 foo",
|
||||
nameToHandlePrefix + "254 foo bar",
|
||||
nameToHandlePrefix + "foo 1245",
|
||||
}
|
||||
|
||||
for _, s := range values {
|
||||
_, err := NewPIDHandleFromString(os.Getpid(), s)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPIDHandlePidfdStartTime(t *testing.T) {
|
||||
h, err := NewPIDHandleFromString(os.Getpid(), "start-time:1234567890")
|
||||
assert.NoError(t, err)
|
||||
defer h.Close()
|
||||
}
|
||||
|
||||
func TestPIDHandleKill(t *testing.T) {
|
||||
// Mock the pidfdOpen
|
||||
original := pidfdOpen
|
||||
defer func() { pidfdOpen = original }()
|
||||
pidfdOpen = func(pid int, flags int) (int, error) {
|
||||
return 254, nil
|
||||
}
|
||||
|
||||
// Mock the pidfdSendSignal
|
||||
original_pidfdSendSignal := pidfdSendSignal
|
||||
defer func() { pidfdSendSignal = original_pidfdSendSignal }()
|
||||
pidfdSendSignal = func(pidfd int, sig unix.Signal, info *unix.Siginfo, flags int) (err error) {
|
||||
return unix.ESRCH
|
||||
}
|
||||
|
||||
// Mock the nameToHandleAt
|
||||
original_nameToHandleAt := nameToHandleAt
|
||||
defer func() { nameToHandleAt = original_nameToHandleAt }()
|
||||
nameToHandleAt = func(dirfd int, path string, flags int) (handle unix.FileHandle, mountID int, err error) {
|
||||
return newFileHandle(254, []byte("test")), 1, nil
|
||||
}
|
||||
|
||||
h, err := NewPIDHandle(os.Getpid())
|
||||
assert.NoError(t, err)
|
||||
defer h.Close()
|
||||
|
||||
err = h.Kill(0)
|
||||
assert.ErrorIs(t, err, unix.ESRCH)
|
||||
}
|
||||
|
||||
func TestPIDHandleKillPidfdNotSupported(t *testing.T) {
|
||||
// Mock the pidfdOpen
|
||||
original := pidfdOpen
|
||||
defer func() { pidfdOpen = original }()
|
||||
pidfdOpen = func(pid int, flags int) (int, error) {
|
||||
return -1, unix.ENOSYS
|
||||
}
|
||||
|
||||
h, err := NewPIDHandle(os.Getpid())
|
||||
assert.NoError(t, err)
|
||||
defer h.Close()
|
||||
|
||||
pidData, err := h.String()
|
||||
assert.NoError(t, err)
|
||||
h, err = NewPIDHandleFromString(os.Getpid(), pidData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = h.Kill(0)
|
||||
assert.NoError(t, err)
|
||||
isAlive, err := h.IsAlive()
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, isAlive)
|
||||
}
|
||||
|
||||
func TestPIDHandleKillPidfdNotSupportedStartTimeNotMatch(t *testing.T) {
|
||||
// Mock the pidfdOpen
|
||||
original := pidfdOpen
|
||||
defer func() { pidfdOpen = original }()
|
||||
pidfdOpen = func(pid int, flags int) (int, error) {
|
||||
return -1, unix.ENOSYS
|
||||
}
|
||||
|
||||
h, err := NewPIDHandle(os.Getpid())
|
||||
assert.NoError(t, err)
|
||||
defer h.Close()
|
||||
|
||||
h, err = NewPIDHandleFromString(os.Getpid(), "start-time:1234567890")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = h.Kill(0)
|
||||
assert.ErrorIs(t, err, unix.ESRCH)
|
||||
|
||||
isAlive, err := h.IsAlive()
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, isAlive)
|
||||
}
|
24
pkg/pidhandle/pidhandle_unix.go
Normal file
24
pkg/pidhandle/pidhandle_unix.go
Normal file
@ -0,0 +1,24 @@
|
||||
//go:build !linux && !windows
|
||||
|
||||
// Package for handling processes and PIDs.
|
||||
package pidhandle
|
||||
|
||||
// Creates new PIDHandle for a given process pid.
|
||||
func NewPIDHandle(pid int) (PIDHandle, error) {
|
||||
h := pidHandle{pid: pid}
|
||||
pidData, err := h.String()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h.pidData = pidData
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
// Creates new PIDHandle for a given process pid using the pidData
|
||||
// originally obtained from PIDHandle.String().
|
||||
func NewPIDHandleFromString(pid int, pidData string) (PIDHandle, error) {
|
||||
return &pidHandle{
|
||||
pid: pid,
|
||||
pidData: pidData,
|
||||
}, nil
|
||||
}
|
@ -66,7 +66,8 @@ function Local-Unit {
|
||||
Build-Ginkgo
|
||||
$skippackages="hack,internal\domain\infra\abi,internal\domain\infra\tunnel,libpod\lock\shm,pkg\api\handlers\libpod,pkg\api\handlers\utils,pkg\bindings,"
|
||||
$skippackages+="pkg\domain\infra\abi,pkg\emulation,pkg\machine\apple,pkg\machine\applehv,pkg\machine\e2e,pkg\machine\libkrun,"
|
||||
$skippackages+="pkg\machine\provider,pkg\machine\proxyenv,pkg\machine\qemu,pkg\specgen\generate,pkg\systemd,test\e2e,test\utils,cmd\rootlessport"
|
||||
$skippackages+="pkg\machine\provider,pkg\machine\proxyenv,pkg\machine\qemu,pkg\specgen\generate,pkg\systemd,test\e2e,test\utils,cmd\rootlessport,"
|
||||
$skippackages+="pkg\pidhandle"
|
||||
Run-Command "./bin/ginkgo.exe -vv -r --tags `"$remotetags`" --timeout=15m --trace --no-color --skip-package `"$skippackages`""
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user