Skip to content

Commit 43898c0

Browse files
authored
Merge pull request #2608 from weissi/jw-SR-11354-51
2 parents 6107f49 + a1e0920 commit 43898c0

File tree

3 files changed

+123
-1
lines changed

3 files changed

+123
-1
lines changed

Foundation/Process.swift

Lines changed: 63 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
@@ -44,6 +48,43 @@ private var managerThreadRunLoopIsRunningCondition = NSCondition()
4448
internal let kCFSocketDataCallBack = CFSocketCallBackType.dataCallBack.rawValue
4549
#endif
4650

51+
#if !canImport(Darwin) && !os(Windows)
52+
private func findMaximumOpenFromProcSelfFD() -> CInt? {
53+
guard let dirPtr = opendir("/proc/self/fd") else {
54+
return nil
55+
}
56+
defer {
57+
closedir(dirPtr)
58+
}
59+
var highestFDSoFar = CInt(0)
60+
61+
while let dirEntPtr = readdir(dirPtr) {
62+
var entryName = dirEntPtr.pointee.d_name
63+
let thisFD = withUnsafeBytes(of: &entryName) { entryNamePtr -> CInt? in
64+
CInt(String(decoding: entryNamePtr.prefix(while: { $0 != 0 }), as: Unicode.UTF8.self))
65+
}
66+
highestFDSoFar = max(thisFD ?? -1, highestFDSoFar)
67+
}
68+
69+
return highestFDSoFar
70+
}
71+
72+
func findMaximumOpenFD() -> CInt {
73+
if let maxFD = findMaximumOpenFromProcSelfFD() {
74+
// the precise method worked, let's return this fd.
75+
return maxFD
76+
}
77+
78+
// We don't have /proc, let's go with the best estimate.
79+
#if os(Linux)
80+
return getdtablesize()
81+
#else
82+
return 4096
83+
#endif
84+
}
85+
#endif
86+
87+
4788
private func emptyRunLoopCallback(_ context : UnsafeMutableRawPointer?) -> Void {}
4889

4990

@@ -825,6 +866,21 @@ open class Process: NSObject {
825866
posix(_CFPosixSpawnFileActionsAddClose(fileActions, fd))
826867
}
827868

869+
#if canImport(Darwin)
870+
var spawnAttrs: posix_spawnattr_t? = nil
871+
posix_spawnattr_init(&spawnAttrs)
872+
posix_spawnattr_setflags(&spawnAttrs, .init(POSIX_SPAWN_CLOEXEC_DEFAULT))
873+
#else
874+
for fd in 3 ... findMaximumOpenFD() {
875+
guard adddup2[fd] == nil &&
876+
!addclose.contains(fd) &&
877+
fd != taskSocketPair[1] else {
878+
continue // Do not double-close descriptors, or close those pertaining to Pipes or FileHandles we want inherited.
879+
}
880+
posix(_CFPosixSpawnFileActionsAddClose(fileActions, fd))
881+
}
882+
#endif
883+
828884
let fileManager = FileManager()
829885
let previousDirectoryPath = fileManager.currentDirectoryPath
830886
if !fileManager.changeCurrentDirectoryPath(currentDirectoryURL.path) {
@@ -838,9 +894,16 @@ open class Process: NSObject {
838894

839895
// Launch
840896
var pid = pid_t()
897+
#if os(macOS)
898+
guard _CFPosixSpawn(&pid, launchPath, fileActions, &spawnAttrs, argv, envp) == 0 else {
899+
throw _NSErrorWithErrno(errno, reading: true, path: launchPath)
900+
}
901+
#else
841902
guard _CFPosixSpawn(&pid, launchPath, fileActions, nil, argv, envp) == 0 else {
842903
throw _NSErrorWithErrno(errno, reading: true, path: launchPath)
843904
}
905+
#endif
906+
844907

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

TestFoundation/TestProcess.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,44 @@ class TestProcess : XCTestCase {
565565
XCTAssertEqual(String(data: stdoutData, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines), "No files specified.")
566566
}
567567

568+
#if !os(Windows)
569+
func test_fileDescriptorsAreNotInherited() throws {
570+
let task = Process()
571+
let someExtraFDs = [dup(1), dup(1), dup(1), dup(1), dup(1), dup(1), dup(1)]
572+
task.executableURL = xdgTestHelperURL()
573+
task.arguments = ["--print-open-file-descriptors"]
574+
task.standardInput = FileHandle.nullDevice
575+
let stdoutPipe = Pipe()
576+
task.standardOutput = stdoutPipe.fileHandleForWriting
577+
task.standardError = FileHandle.nullDevice
578+
XCTAssertNoThrow(try task.run())
579+
580+
try stdoutPipe.fileHandleForWriting.close()
581+
let stdoutData = try stdoutPipe.fileHandleForReading.readToEnd()
582+
task.waitUntilExit()
583+
let stdoutString = String(decoding: stdoutData ?? Data(), as: Unicode.UTF8.self)
584+
#if os(macOS)
585+
XCTAssertEqual("0\n1\n2\n", stdoutString)
586+
#else
587+
// on Linux we may also have a /dev/urandom open as well as some socket that Process uses for something.
588+
589+
// we should definitely have stdin (0), stdout (1), and stderr (2) open
590+
XCTAssert(stdoutString.utf8.starts(with: "0\n1\n2\n".utf8))
591+
592+
// in total we should have 6 or fewer lines:
593+
// 1. stdin
594+
// 2. stdout
595+
// 3. stderr
596+
// 4. /dev/urandom (optional)
597+
// 5. communication socket (optional)
598+
// 6. trailing new line
599+
XCTAssertLessThanOrEqual(stdoutString.components(separatedBy: "\n").count, 6, "\(stdoutString)")
600+
#endif
601+
for fd in someExtraFDs {
602+
close(fd)
603+
}
604+
}
605+
#endif
568606

569607
static var allTests: [(String, (TestProcess) -> () throws -> Void)] {
570608
var tests = [
@@ -599,6 +637,7 @@ class TestProcess : XCTestCase {
599637
tests += [
600638
("test_interrupt", test_interrupt),
601639
("test_suspend_resume", test_suspend_resume),
640+
("test_fileDescriptorsAreNotInherited", test_fileDescriptorsAreNotInherited),
602641
]
603642
#endif
604643
return tests

TestFoundation/xdgTestHelper/main.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,23 @@ func cat(_ args: ArraySlice<String>.Iterator) {
206206
exit(exitCode)
207207
}
208208

209+
#if !os(Windows)
210+
func printOpenFileDescriptors() {
211+
let reasonableMaxFD: CInt
212+
#if os(Linux) || os(macOS)
213+
reasonableMaxFD = getdtablesize()
214+
#else
215+
reasonableMaxFD = 4096
216+
#endif
217+
for fd in 0..<reasonableMaxFD {
218+
if fcntl(fd, F_GETFD) != -1 {
219+
print(fd)
220+
}
221+
}
222+
exit(0)
223+
}
224+
#endif
225+
209226
// -----
210227

211228
var arguments = ProcessInfo.processInfo.arguments.dropFirst().makeIterator()
@@ -264,8 +281,11 @@ case "--nspathfor":
264281
#if !os(Windows)
265282
case "--signal-test":
266283
signalTest()
284+
285+
case "--print-open-file-descriptors":
286+
printOpenFileDescriptors()
267287
#endif
268-
288+
269289
default:
270290
fatalError("These arguments are not recognized. Only run this from a unit test.")
271291
}

0 commit comments

Comments
 (0)