Skip to content

Commit 5a26031

Browse files
authored
Merge pull request swiftlang#2542 from weissi/jw-dont-inherit-fds
2 parents 914a8ae + 1483b6f commit 5a26031

File tree

3 files changed

+82
-1
lines changed

3 files changed

+82
-1
lines changed

Foundation/Process.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99

1010
import CoreFoundation
1111

12+
#if canImport(Darwin)
13+
import Darwin
14+
#endif
15+
1216
extension Process {
1317
public enum TerminationReason : Int {
1418
case exit
@@ -872,6 +876,21 @@ open class Process: NSObject {
872876
posix(_CFPosixSpawnFileActionsAddClose(fileActions, fd))
873877
}
874878

879+
#if canImport(Darwin)
880+
var spawnAttrs: posix_spawnattr_t? = nil
881+
posix_spawnattr_init(&spawnAttrs)
882+
posix_spawnattr_setflags(&spawnAttrs, .init(POSIX_SPAWN_CLOEXEC_DEFAULT))
883+
#else
884+
for fd in 3 ..< getdtablesize() {
885+
guard adddup2[fd] == nil &&
886+
!addclose.contains(fd) &&
887+
fd != taskSocketPair[1] else {
888+
continue // Do not double-close descriptors, or close those pertaining to Pipes or FileHandles we want inherited.
889+
}
890+
posix(_CFPosixSpawnFileActionsAddClose(fileActions, fd))
891+
}
892+
#endif
893+
875894
let fileManager = FileManager()
876895
let previousDirectoryPath = fileManager.currentDirectoryPath
877896
if let dir = currentDirectoryURL?.path, !fileManager.changeCurrentDirectoryPath(dir) {
@@ -885,9 +904,16 @@ open class Process: NSObject {
885904

886905
// Launch
887906
var pid = pid_t()
907+
#if os(macOS)
908+
guard _CFPosixSpawn(&pid, launchPath, fileActions, &spawnAttrs, argv, envp) == 0 else {
909+
throw _NSErrorWithErrno(errno, reading: true, path: launchPath)
910+
}
911+
#else
888912
guard _CFPosixSpawn(&pid, launchPath, fileActions, nil, argv, envp) == 0 else {
889913
throw _NSErrorWithErrno(errno, reading: true, path: launchPath)
890914
}
915+
#endif
916+
891917

892918
// Close the write end of the input and output pipes.
893919
if let pipe = standardInput as? Pipe {

TestFoundation/TestProcess.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,45 @@ class TestProcess : XCTestCase {
682682
}
683683
}
684684

685+
#if !os(Windows)
686+
func test_fileDescriptorsAreNotInherited() throws {
687+
let task = Process()
688+
let someExtraFDs = [dup(1), dup(1), dup(1), dup(1), dup(1), dup(1), dup(1)]
689+
task.executableURL = xdgTestHelperURL()
690+
task.arguments = ["--print-open-file-descriptors"]
691+
task.standardInput = FileHandle.nullDevice
692+
let stdoutPipe = Pipe()
693+
task.standardOutput = stdoutPipe.fileHandleForWriting
694+
task.standardError = FileHandle.nullDevice
695+
XCTAssertNoThrow(try task.run())
696+
697+
try stdoutPipe.fileHandleForWriting.close()
698+
let stdoutData = try stdoutPipe.fileHandleForReading.readToEnd()
699+
task.waitUntilExit()
700+
let stdoutString = String(decoding: stdoutData ?? Data(), as: Unicode.UTF8.self)
701+
#if os(macOS)
702+
XCTAssertEqual("0\n1\n2\n", stdoutString)
703+
#else
704+
// on Linux we may also have a /dev/urandom open as well as some socket that Process uses for something.
705+
706+
// we should definitely have stdin (0), stdout (1), and stderr (2) open
707+
XCTAssert(stdoutString.utf8.starts(with: "0\n1\n2\n".utf8))
708+
709+
// in total we should have 6 or fewer lines:
710+
// 1. stdin
711+
// 2. stdout
712+
// 3. stderr
713+
// 4. /dev/urandom (optional)
714+
// 5. communication socket (optional)
715+
// 6. trailing new line
716+
XCTAssertLessThanOrEqual(stdoutString.components(separatedBy: "\n").count, 6, "\(stdoutString)")
717+
#endif
718+
for fd in someExtraFDs {
719+
close(fd)
720+
}
721+
}
722+
#endif
723+
685724
func test_pipeCloseBeforeLaunch() {
686725
let process = Process()
687726
let stdInput = Pipe()
@@ -777,6 +816,7 @@ class TestProcess : XCTestCase {
777816
("test_redirect_all_using_nil", test_redirect_all_using_nil),
778817
("test_plutil", test_plutil),
779818
("test_currentDirectory", test_currentDirectory),
819+
("test_fileDescriptorsAreNotInherited", test_fileDescriptorsAreNotInherited),
780820
("test_pipeCloseBeforeLaunch", test_pipeCloseBeforeLaunch),
781821
("test_multiProcesses", test_multiProcesses),
782822
]
@@ -786,6 +826,7 @@ class TestProcess : XCTestCase {
786826
tests += [
787827
("test_interrupt", test_interrupt),
788828
("test_suspend_resume", test_suspend_resume),
829+
("test_fileDescriptorsAreNotInherited", test_fileDescriptorsAreNotInherited),
789830
]
790831
#endif
791832
return tests

TestFoundation/xdgTestHelper/main.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,17 @@ func cat(_ args: ArraySlice<String>.Iterator) {
196196
exit(exitCode)
197197
}
198198

199+
#if !os(Windows)
200+
func printOpenFileDescriptors() {
201+
for fd in 0..<getdtablesize() {
202+
if fcntl(fd, F_GETFD) != -1 {
203+
print(fd)
204+
}
205+
}
206+
exit(0)
207+
}
208+
#endif
209+
199210
// -----
200211

201212
var arguments = ProcessInfo.processInfo.arguments.dropFirst().makeIterator()
@@ -254,8 +265,11 @@ case "--nspathfor":
254265
#if !os(Windows)
255266
case "--signal-test":
256267
signalTest()
268+
269+
case "--print-open-file-descriptors":
270+
printOpenFileDescriptors()
257271
#endif
258-
272+
259273
default:
260274
fatalError("These arguments are not recognized. Only run this from a unit test.")
261275
}

0 commit comments

Comments
 (0)