File: pidhandle_linux.go

package info (click to toggle)
podman 5.7.0%2Bds2-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 23,824 kB
  • sloc: sh: 4,700; python: 2,798; perl: 1,885; ansic: 1,484; makefile: 977; ruby: 42; csh: 8
file content (201 lines) | stat: -rw-r--r-- 5,271 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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()
}