Skip to content

Commit a2de33f

Browse files
committed
Prob whether waitid supports P_PIDFD instead of checking for Linux kernel version
1 parent 89e5d2f commit a2de33f

File tree

4 files changed

+47
-66
lines changed

4 files changed

+47
-66
lines changed

Sources/Subprocess/Platforms/Subprocess+Linux.swift

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ internal func monitorProcessTermination(
304304
// pidfd is only supported on Linux kernel 5.4 and above
305305
// On older releases, use signalfd so we do not need
306306
// to register anything with epoll
307-
if _isLinuxKernelAtLeast(major: 5, minor: 4) {
307+
if processIdentifier.processFileDescriptor > 0 {
308308
// Register processFileDescriptor with epoll
309309
var event = epoll_event(
310310
events: EPOLLIN.rawValue,
@@ -418,8 +418,10 @@ private extension siginfo_t {
418418
}
419419
}
420420

421-
// Okay to be unlocked global mutable because this thread is only created once
421+
// Okay to be unlocked global mutable because this value is only set once like dispatch_once
422422
private nonisolated(unsafe) var _signalPipe: (readEnd: CInt, writeEnd: CInt) = (readEnd: -1, writeEnd: -1)
423+
// Okay to be unlocked global mutable because this value is only set once like dispatch_once
424+
private nonisolated(unsafe) var _waitProcessFileDescriptorSupported = false
423425
private let _processMonitorState: Mutex<ProcessMonitorState> = .init(.notStarted)
424426

425427
private func shutdown() {
@@ -518,7 +520,7 @@ private func monitorThreadFunc(args: UnsafeMutableRawPointer?) -> UnsafeMutableR
518520
}
519521

520522
// P_PIDFD requires Linux Kernel 5.4 and above
521-
if _isLinuxKernelAtLeast(major: 5, minor: 4) {
523+
if _waitProcessFileDescriptorSupported {
522524
_blockAndWaitForProcessFileDescriptor(targetFileDescriptor, context: context)
523525
} else {
524526
_reapAllKnownChildProcesses(targetFileDescriptor, context: context)
@@ -569,9 +571,9 @@ private let setup: () = {
569571
return
570572
}
571573

572-
// On Linux kernel lower than 5.4 we have to rely on signal handler
574+
// If the current kernel does not support pidfd, fallback to signal handler
573575
// Create the self-pipe that signal handler writes to
574-
if !_isLinuxKernelAtLeast(major: 5, minor: 4) {
576+
if !_isWaitProcessFileDescriptorSupported() {
575577
var pipeCreationError: SubprocessError? = nil
576578
do {
577579
let (readEnd, writeEnd) = try FileDescriptor.pipe()
@@ -608,6 +610,9 @@ private let setup: () = {
608610
_reportFailureWithErrno(errno)
609611
return
610612
}
613+
} else {
614+
// Mark waitid(P_PIDFD) as supported
615+
_waitProcessFileDescriptorSupported = true
611616
}
612617
let monitorThreadContext = MonitorThreadContext(
613618
epollFileDescriptor: epollFileDescriptor,
@@ -755,36 +760,25 @@ private func _reapAllKnownChildProcesses(_ signalFd: CInt, context: MonitorThrea
755760
}
756761
}
757762

758-
private func _isLinuxKernelAtLeast(major: Int, minor: Int) -> Bool {
759-
var buffer = utsname()
760-
guard uname(&buffer) == 0 else {
761-
return false
762-
}
763-
let releaseString = withUnsafeBytes(of: &buffer.release) { ptr in
764-
let buffer = ptr.bindMemory(to: CChar.self)
765-
return String(cString: buffer.baseAddress!)
766-
}
767-
768-
// utsname release follows the format
769-
// major.minor.patch-extra
770-
guard let mainVersion = releaseString.split(separator: "-").first else {
771-
return false
772-
}
773-
774-
let versionComponents = mainVersion.split(separator: ".")
775-
776-
guard let currentMajor = Int(versionComponents[0]),
777-
let currentMinor = Int(versionComponents[1]) else {
778-
return false
779-
}
780-
781-
if currentMajor > major {
782-
return true
783-
} else if currentMajor == major {
784-
return currentMinor >= minor
785-
} else {
763+
internal func _isWaitProcessFileDescriptorSupported() -> Bool {
764+
// waitid(P_PIDFD) is only supported on Linux kernel 5.4 and above
765+
// Prob whether the current system supports it by calling it with self pidfd
766+
// and checking for EINVAL (waitid sets errno to EINVAL if it does not
767+
// recognize the id type).
768+
var siginfo = siginfo_t()
769+
let selfPidfd = _pidfd_open(getpid())
770+
if selfPidfd < 0 {
771+
// If we can not retrieve pidfd, the system does not support waitid(P_PIDFD)
786772
return false
787773
}
774+
/// The following call will fail either with
775+
/// - ECHILD: in this case we know P_PIDFD is supported and waitid correctly
776+
/// reported that we don't have a child with the same selfPidfd;
777+
/// - EINVAL: in this case we know P_PIDFD is not supported because it does not
778+
/// recognize the `P_PIDFD` type
779+
waitid(idtype_t(UInt32(P_PIDFD)), id_t(selfPidfd), &siginfo, WEXITED | WNOWAIT)
780+
return errno == ECHILD
788781
}
789782

783+
790784
#endif // canImport(Glibc) || canImport(Android) || canImport(Musl)

Sources/_SubprocessCShims/include/process_shims.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,11 @@ int _shims_snprintf(
8282
char * _Nonnull str2
8383
);
8484

85+
int _pidfd_open(pid_t pid);
8586
int _pidfd_send_signal(int pidfd, int signal);
8687

8788
// P_PIDFD is only defined on Linux Kernel 5.4 and above
88-
// Define our dummy value if it's not available
89+
// Define our value if it's not available
8990
#ifndef P_PIDFD
9091
#define P_PIDFD 3
9192
#endif

Sources/_SubprocessCShims/process_shims.c

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ int _subprocess_spawn(
311311
#define __GLIBC_PREREQ(maj, min) 0
312312
#endif
313313

314-
static int _pidfd_open(pid_t pid) {
314+
int _pidfd_open(pid_t pid) {
315315
return syscall(SYS_pidfd_open, pid, 0);
316316
}
317317

@@ -336,25 +336,6 @@ static int _clone3(int *pidfd) {
336336
return syscall(SYS_clone3, &args, sizeof(args));
337337
}
338338

339-
static int _linux_kernel_version_at_least(
340-
int required_major,
341-
int required_minor
342-
) {
343-
struct utsname buf;
344-
if (uname(&buf) != 0) {
345-
return 0; // Cannot determine kernel version
346-
}
347-
348-
int major = 0, minor = 0;
349-
if (sscanf(buf.release, "%d.%d", &major, &minor) < 2) {
350-
return 0;
351-
}
352-
353-
if (major > required_major) return 1;
354-
if (major == required_major && minor >= required_minor) return 1;
355-
return 0;
356-
}
357-
358339
int _subprocess_fork_exec(
359340
pid_t * _Nonnull pid,
360341
int * _Nonnull pidfd,
@@ -423,17 +404,21 @@ int _subprocess_fork_exec(
423404

424405
// Finally, fork / clone
425406
int _pidfd = -1;
426-
pid_t childPid = -1;
427-
if (_linux_kernel_version_at_least(5, 3)) {
428-
// One Linux 5.3 and above, use the new clone3 syscall
429-
// So we can atomically retrieve pidfd
430-
childPid = _clone3(&_pidfd);
431-
} else {
432-
// On older linux versions, fall back to fork()
407+
// First attempt to use clone3, only fall back to fork if clone3 is not available
408+
pid_t childPid = _clone3(&_pidfd);
409+
if (childPid < 0) {
410+
if (errno == ENOSYS) {
411+
// clone3 is not implemented. Use fork instead
433412
#pragma GCC diagnostic push
434413
#pragma GCC diagnostic ignored "-Wdeprecated"
435-
childPid = fork();
414+
childPid = fork();
436415
#pragma GCC diagnostic pop
416+
} else {
417+
// Report all other errors
418+
close(pipefd[0]);
419+
close(pipefd[1]);
420+
return errno;
421+
}
437422
}
438423

439424
if (childPid < 0) {
@@ -557,7 +542,7 @@ int _subprocess_fork_exec(
557542
} else {
558543
// On Linux 5.3 and lower, we have to fetch pidfd separately
559544
// Newer Linux supports clone3 which returns pidfd directly
560-
if (_linux_kernel_version_at_least(5, 3) == 0) {
545+
if (_pidfd < 0) {
561546
_pidfd = _pidfd_open(childPid);
562547
if (_pidfd < 0) {
563548
return errno;

Tests/SubprocessTests/SubprocessTests+Windows.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -450,9 +450,10 @@ extension SubprocessWindowsTests {
450450

451451
// MARK: - PlatformOption Tests
452452
extension SubprocessWindowsTests {
453-
// Disabled until we investigate CI specific failures
454-
// https://github.com/swiftlang/swift-subprocess/issues/128
455-
@Test(.enabled(if: false))
453+
@Test(
454+
.disabled("Disabled until we investigate CI specific failures"),
455+
.bug("https://github.com/swiftlang/swift-subprocess/issues/128")
456+
)
456457
func testPlatformOptionsRunAsUser() async throws {
457458
try await self.withTemporaryUser { username, password, succeed in
458459
// Use public directory as working directory so the newly created user

0 commit comments

Comments
 (0)