Merge pull request #25906 from jankaluza/25104-pidfs

Verify the ExecSession pid before killing it.
This commit is contained in:
openshift-merge-bot[bot]
2025-05-08 10:03:50 +00:00
committed by GitHub
7 changed files with 664 additions and 22 deletions

View File

@ -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()

View File

@ -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
View 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
}

View 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()
}

View 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)
}

View 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
}

View File

@ -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`""
}