Skip to content

Commit a0fd8ae

Browse files
committed
Use posix_spawn_file_actions_adddup2() to clear FD_CLOEXEC.
On FreeBSD, OpenBSD, Android, and Glibc ≥ 2.29, `posix_spawn_file_actions_adddup2()` automatically clears `FD_CLOEXEC` if the file descriptors passed to it are equal. Relying on this behaviour eliminates a race condition when spawning child processes. Some older Linuxes (Amazon Linux 2 in particular) don't have this functionality, so we do a runtime check of the Glibc version.
1 parent b0a1efd commit a0fd8ae

File tree

3 files changed

+33
-5
lines changed

3 files changed

+33
-5
lines changed

Sources/Testing/ExitTests/ExitTest.swift

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -803,11 +803,26 @@ extension ExitTest {
803803

804804
#if !SWT_TARGET_OS_APPLE
805805
// Set inherited those file handles that the child process needs. On
806-
// Darwin, this is a no-op because we use POSIX_SPAWN_CLOEXEC_DEFAULT.
807-
try stdoutWriteEnd?.setInherited(true)
808-
try stderrWriteEnd?.setInherited(true)
809-
try backChannelWriteEnd.setInherited(true)
810-
try capturedValuesReadEnd.setInherited(true)
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+
}
811826
#endif
812827

813828
// Spawn the child process.

Sources/Testing/ExitTests/SpawnProcess.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ func spawnExecutable(
128128
} else {
129129
#if SWT_TARGET_OS_APPLE
130130
_ = posix_spawn_file_actions_addinherit_np(fileActions, fd)
131+
#else
132+
_ = posix_spawn_file_actions_adddup2(fileActions, fd, fd)
131133
#endif
132134
highestFD = max(highestFD, fd)
133135
}

Sources/_TestingInternals/include/Versions.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ 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+
5162
SWT_ASSUME_NONNULL_END
5263

5364
#endif

0 commit comments

Comments
 (0)