podman/pkg/pidhandle/pidhandle_linux.go

202 lines
5.1 KiB
Go

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