Skip to content

Commit 13afe50

Browse files
committed
Simplify logic, check glibc runtime version
1 parent a0fd8ae commit 13afe50

File tree

5 files changed

+88
-121
lines changed

5 files changed

+88
-121
lines changed

Sources/Testing/ExitTests/ExitTest.swift

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -801,30 +801,6 @@ extension ExitTest {
801801
childEnvironment["SWT_EXPERIMENTAL_CAPTURED_VALUES"] = capturedValuesEnvironmentVariable
802802
}
803803

804-
#if !SWT_TARGET_OS_APPLE
805-
// Set inherited those file handles that the child process needs. On
806-
// Darwin, this isn't needed because we use POSIX_SPAWN_CLOEXEC_DEFAULT.
807-
var setFileHandlesInherited = false
808-
#if os(Windows)
809-
// Always set the file handles to be inherited on Windows. Our calls to
810-
// CreateProcessW() specify which file handles are actually inherited on
811-
// a per-child basis.
812-
setFileHandlesInherited = true
813-
#elseif canImport(Glibc)
814-
// On Glibc ≥ 2.29, posix_spawn_file_actions_adddup2() automatically
815-
// clears FD_CLOEXEC for us in the child process.
816-
var glibcVersion: (major: CInt, minor: CInt) = (0, 0)
817-
swt_getGlibcVersion(&glibcVersion.major, &glibcVersion.minor)
818-
setFileHandlesInherited = glibcVersion.major < 2 || (glibcVersion.major == 2 && glibcVersion.minor < 29)
819-
#endif
820-
if setFileHandlesInherited {
821-
try stdoutWriteEnd?.setInherited(true)
822-
try stderrWriteEnd?.setInherited(true)
823-
try backChannelWriteEnd.setInherited(true)
824-
try capturedValuesReadEnd.setInherited(true)
825-
}
826-
#endif
827-
828804
// Spawn the child process.
829805
let processID = try withUnsafePointer(to: backChannelWriteEnd) { backChannelWriteEnd in
830806
try withUnsafePointer(to: capturedValuesReadEnd) { capturedValuesReadEnd in

Sources/Testing/ExitTests/SpawnProcess.swift

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ func spawnExecutable(
118118

119119
// Forward standard I/O streams and any explicitly added file handles.
120120
var highestFD = max(STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO)
121+
#if canImport(Glibc)
122+
lazy var glibcVersion = glibcVersion
123+
#endif
121124
func inherit(_ fileHandle: borrowing FileHandle, as standardFD: CInt? = nil) throws {
122125
try fileHandle.withUnsafePOSIXFileDescriptor { fd in
123126
guard let fd else {
@@ -130,6 +133,13 @@ func spawnExecutable(
130133
_ = posix_spawn_file_actions_addinherit_np(fileActions, fd)
131134
#else
132135
_ = posix_spawn_file_actions_adddup2(fileActions, fd, fd)
136+
#if canImport(Glibc)
137+
if glibcVersion.major < 2 || (glibcVersion.major == 2 && glibcVersion.minor < 29) {
138+
// This system is using an older version of glibc that does not
139+
// implement FD_CLOEXEC clearing in posix_spawn_file_actions_adddup2().
140+
try setFD_CLOEXEC(false, onFileDescriptor: fd)
141+
}
142+
#endif
133143
#endif
134144
highestFD = max(highestFD, fd)
135145
}
@@ -218,36 +228,42 @@ func spawnExecutable(
218228
}
219229
#elseif os(Windows)
220230
return try _withStartupInfoEx(attributeCount: 1) { startupInfo in
221-
func inherit(_ fileHandle: borrowing FileHandle, as outWindowsHANDLE: inout HANDLE?) throws {
231+
func inherit(_ fileHandle: borrowing FileHandle) throws -> HANDLE {
222232
try fileHandle.withUnsafeWindowsHANDLE { windowsHANDLE in
223233
guard let windowsHANDLE else {
224234
throw SystemError(description: "A child process cannot inherit a file handle without an associated Windows handle. Please file a bug report at https://github.com/swiftlang/swift-testing/issues/new")
225235
}
226-
outWindowsHANDLE = windowsHANDLE
236+
237+
// Ensure the file handle can be inherited by the child process.
238+
guard SetHandleInformation(windowsHANDLE, DWORD(HANDLE_FLAG_INHERIT), DWORD(HANDLE_FLAG_INHERIT)) else {
239+
throw Win32Error(rawValue: GetLastError())
240+
}
241+
242+
return windowsHANDLE
227243
}
228244
}
229-
func inherit(_ fileHandle: borrowing FileHandle?, as outWindowsHANDLE: inout HANDLE?) throws {
245+
func inherit(_ fileHandle: borrowing FileHandle?) throws -> HANDLE? {
230246
if fileHandle != nil {
231-
try inherit(fileHandle!, as: &outWindowsHANDLE)
247+
return try inherit(fileHandle!)
232248
} else {
233-
outWindowsHANDLE = nil
249+
return nil
234250
}
235251
}
236252

237253
// Forward standard I/O streams.
238-
try inherit(standardInput, as: &startupInfo.pointee.StartupInfo.hStdInput)
239-
try inherit(standardOutput, as: &startupInfo.pointee.StartupInfo.hStdOutput)
240-
try inherit(standardError, as: &startupInfo.pointee.StartupInfo.hStdError)
254+
startupInfo.pointee.StartupInfo.hStdInput = try inherit(standardInput)
255+
startupInfo.pointee.StartupInfo.hStdOutput = try inherit(standardOutput)
256+
startupInfo.pointee.StartupInfo.hStdError = try inherit(standardError)
241257
startupInfo.pointee.StartupInfo.dwFlags |= STARTF_USESTDHANDLES
242258

243259
// Ensure standard I/O streams and any explicitly added file handles are
244260
// inherited by the child process.
245261
var inheritedHandles = [HANDLE?](repeating: nil, count: additionalFileHandles.count + 3)
246-
try inherit(standardInput, as: &inheritedHandles[0])
247-
try inherit(standardOutput, as: &inheritedHandles[1])
248-
try inherit(standardError, as: &inheritedHandles[2])
262+
inheritedHandles[0] = startupInfo.pointee.StartupInfo.hStdInput
263+
inheritedHandles[1] = startupInfo.pointee.StartupInfo.hStdOutput
264+
inheritedHandles[2] = startupInfo.pointee.StartupInfo.hStdError
249265
for i in 0 ..< additionalFileHandles.count {
250-
try inherit(additionalFileHandles[i].pointee, as: &inheritedHandles[i + 3])
266+
inheritedHandles[i + 3] = try inherit(additionalFileHandles[i].pointee)
251267
}
252268
inheritedHandles = inheritedHandles.compactMap(\.self)
253269

Sources/Testing/Support/FileHandle.swift

Lines changed: 36 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,7 @@ struct FileHandle: ~Copyable, Sendable {
108108
///
109109
/// By default, the resulting file handle is not inherited by any child
110110
/// processes (that is, `FD_CLOEXEC` is set on POSIX-like systems and
111-
/// `HANDLE_FLAG_INHERIT` is cleared on Windows.) To make it inheritable, call
112-
/// ``setInherited()``.
111+
/// `HANDLE_FLAG_INHERIT` is cleared on Windows.).
113112
init(forReadingAtPath path: String) throws {
114113
try self.init(atPath: path, mode: "reb")
115114
}
@@ -123,8 +122,7 @@ struct FileHandle: ~Copyable, Sendable {
123122
///
124123
/// By default, the resulting file handle is not inherited by any child
125124
/// processes (that is, `FD_CLOEXEC` is set on POSIX-like systems and
126-
/// `HANDLE_FLAG_INHERIT` is cleared on Windows.) To make it inheritable, call
127-
/// ``setInherited()``.
125+
/// `HANDLE_FLAG_INHERIT` is cleared on Windows.).
128126
init(forWritingAtPath path: String) throws {
129127
try self.init(atPath: path, mode: "web")
130128
}
@@ -492,8 +490,7 @@ extension FileHandle {
492490
///
493491
/// By default, the resulting file handles are not inherited by any child
494492
/// processes (that is, `FD_CLOEXEC` is set on POSIX-like systems and
495-
/// `HANDLE_FLAG_INHERIT` is cleared on Windows.) To make them inheritable,
496-
/// call ``setInherited()``.
493+
/// `HANDLE_FLAG_INHERIT` is cleared on Windows.).
497494
static func makePipe(readEnd: inout FileHandle?, writeEnd: inout FileHandle?) throws {
498495
#if !os(Windows)
499496
var pipe2Called = false
@@ -533,8 +530,8 @@ extension FileHandle {
533530
if !pipe2Called {
534531
// pipe2() is not available. Use pipe() instead and simulate O_CLOEXEC
535532
// to the best of our ability.
536-
try _setFileDescriptorInherited(fdReadEnd, false)
537-
try _setFileDescriptorInherited(fdWriteEnd, false)
533+
try setFD_CLOEXEC(true, onFileDescriptor: fdReadEnd)
534+
try setFD_CLOEXEC(true, onFileDescriptor: fdWriteEnd)
538535
}
539536
#endif
540537

@@ -612,72 +609,6 @@ extension FileHandle {
612609
#endif
613610
}
614611
#endif
615-
616-
#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android)
617-
/// Set whether or not the given file descriptor is inherited by child processes.
618-
///
619-
/// - Parameters:
620-
/// - fd: The file descriptor.
621-
/// - inherited: Whether or not `fd` is inherited by child processes
622-
/// (ignoring overriding functionality such as Apple's
623-
/// `POSIX_SPAWN_CLOEXEC_DEFAULT` flag.)
624-
///
625-
/// - Throws: Any error that occurred while setting the flag.
626-
private static func _setFileDescriptorInherited(_ fd: CInt, _ inherited: Bool) throws {
627-
switch swt_getfdflags(fd) {
628-
case -1:
629-
// An error occurred reading the flags for this file descriptor.
630-
throw CError(rawValue: swt_errno())
631-
case let oldValue:
632-
let newValue = if inherited {
633-
oldValue & ~FD_CLOEXEC
634-
} else {
635-
oldValue | FD_CLOEXEC
636-
}
637-
if oldValue == newValue {
638-
// No need to make a second syscall as nothing has changed.
639-
return
640-
}
641-
if -1 == swt_setfdflags(fd, newValue) {
642-
// An error occurred setting the flags for this file descriptor.
643-
throw CError(rawValue: swt_errno())
644-
}
645-
}
646-
}
647-
#endif
648-
649-
/// Set whether or not this file handle is inherited by child processes.
650-
///
651-
/// - Parameters:
652-
/// - inherited: Whether or not this file handle is inherited by child
653-
/// processes (ignoring overriding functionality such as Apple's
654-
/// `POSIX_SPAWN_CLOEXEC_DEFAULT` flag.)
655-
///
656-
/// - Throws: Any error that occurred while setting the flag.
657-
func setInherited(_ inherited: Bool) throws {
658-
#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android)
659-
try withUnsafePOSIXFileDescriptor { fd in
660-
guard let fd else {
661-
throw SystemError(description: "Cannot set whether a file handle is inherited unless it is backed by a file descriptor. Please file a bug report at https://github.com/swiftlang/swift-testing/issues/new")
662-
}
663-
try withLock {
664-
try Self._setFileDescriptorInherited(fd, inherited)
665-
}
666-
}
667-
#elseif os(Windows)
668-
return try withUnsafeWindowsHANDLE { handle in
669-
guard let handle else {
670-
throw SystemError(description: "Cannot set whether a file handle is inherited unless it is backed by a Windows file handle. Please file a bug report at https://github.com/swiftlang/swift-testing/issues/new")
671-
}
672-
let newValue = inherited ? DWORD(HANDLE_FLAG_INHERIT) : 0
673-
guard SetHandleInformation(handle, DWORD(HANDLE_FLAG_INHERIT), newValue) else {
674-
throw Win32Error(rawValue: GetLastError())
675-
}
676-
}
677-
#else
678-
#warning("Platform-specific implementation missing: cannot set whether a file handle is inherited")
679-
#endif
680-
}
681612
}
682613

683614
// MARK: - General path utilities
@@ -757,4 +688,35 @@ func canonicalizePath(_ path: String) -> String? {
757688
return nil
758689
#endif
759690
}
691+
692+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android)
693+
/// Set the given file descriptor's `FD_CLOEXEC` flag.
694+
///
695+
/// - Parameters:
696+
/// - flag: The new value of `fd`'s `FD_CLOEXEC` flag.
697+
/// - fd: The file descriptor.
698+
///
699+
/// - Throws: Any error that occurred while setting the flag.
700+
func setFD_CLOEXEC(_ flag: Bool, onFileDescriptor fd: CInt) throws {
701+
switch swt_getfdflags(fd) {
702+
case -1:
703+
// An error occurred reading the flags for this file descriptor.
704+
throw CError(rawValue: swt_errno())
705+
case let oldValue:
706+
let newValue = if flag {
707+
oldValue & ~FD_CLOEXEC
708+
} else {
709+
oldValue | FD_CLOEXEC
710+
}
711+
if oldValue == newValue {
712+
// No need to make a second syscall as nothing has changed.
713+
return
714+
}
715+
if -1 == swt_setfdflags(fd, newValue) {
716+
// An error occurred setting the flags for this file descriptor.
717+
throw CError(rawValue: swt_errno())
718+
}
719+
}
720+
}
721+
#endif
760722
#endif

Sources/Testing/Support/Versions.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,30 @@ let swiftStandardLibraryVersion: String = {
153153
return "unknown"
154154
}()
155155

156+
#if canImport(Glibc)
157+
/// Get the (runtime, not compile-time) version of glibc in use on this system.
158+
///
159+
/// - Returns: The version of glibc currently in use on this system.
160+
var glibcVersion: (major: Int, minor: Int) {
161+
// Default to the statically available version number if the function call
162+
// fails for some reason.
163+
var major = Int(clamping: __GLIBC__)
164+
var minor = Int(clamping: __GLIBC_MINOR__)
165+
166+
if let strVersion = gnu_get_libc_version() {
167+
withUnsafeMutablePointer(to: &major) { major in
168+
withUnsafeMutablePointer(to: &minor) { minor in
169+
withVaList([major, minor]) { args in
170+
_ = vsscanf(strVersion, "%zd.%zd", args)
171+
}
172+
}
173+
}
174+
}
175+
176+
return (major, minor)
177+
}
178+
#endif
179+
156180
// MARK: - sysctlbyname() Wrapper
157181

158182
#if !SWT_NO_SYSCTL && SWT_TARGET_OS_APPLE

Sources/_TestingInternals/include/Versions.h

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,6 @@ static const char *_Nullable swt_getWASIVersion(void) {
4848
}
4949
#endif
5050

51-
#if defined(__GLIBC__)
52-
/// Get the version of glibc used when compiling the testing library.
53-
///
54-
/// This function is provided because `__GLIBC__` and `__GLIBC_MINOR__` are not
55-
/// available in Swift.
56-
static void swt_getGlibcVersion(int *outMajor, int *outMinor) {
57-
*outMajor = __GLIBC__;
58-
*outMinor = __GLIBC_MINOR__;
59-
}
60-
#endif
61-
6251
SWT_ASSUME_NONNULL_END
6352

6453
#endif

0 commit comments

Comments
 (0)