@@ -73,6 +73,13 @@ struct FileHandle: ~Copyable, Sendable {
7373 return
7474 }
7575
76+ // On Windows, "N" is used rather than "e" to signify that a file handle is
77+ // not inherited.
78+ var mode = mode
79+ if let eIndex = mode. firstIndex ( of: " e " ) {
80+ mode. replaceSubrange ( eIndex ... eIndex, with: " N " )
81+ }
82+
7683 // Windows deprecates fopen() as insecure, so call _wfopen_s() instead.
7784 let fileHandle = try path. withCString ( encodedAs: UTF16 . self) { path in
7885 try mode. withCString ( encodedAs: UTF16 . self) { mode in
@@ -98,8 +105,13 @@ struct FileHandle: ~Copyable, Sendable {
98105 /// - path: The path to read from.
99106 ///
100107 /// - Throws: Any error preventing the stream from being opened.
108+ ///
109+ /// By default, the resulting file handle is not inherited by any child
110+ /// 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()``.
101113 init ( forReadingAtPath path: String ) throws {
102- try self . init ( atPath: path, mode: " rb " )
114+ try self . init ( atPath: path, mode: " reb " )
103115 }
104116
105117 /// Initialize an instance of this type to write to the given path.
@@ -108,8 +120,13 @@ struct FileHandle: ~Copyable, Sendable {
108120 /// - path: The path to write to.
109121 ///
110122 /// - Throws: Any error preventing the stream from being opened.
123+ ///
124+ /// By default, the resulting file handle is not inherited by any child
125+ /// 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()``.
111128 init ( forWritingAtPath path: String ) throws {
112- try self . init ( atPath: path, mode: " wb " )
129+ try self . init ( atPath: path, mode: " web " )
113130 }
114131
115132 /// Initialize an instance of this type with an existing C file handle.
@@ -445,6 +462,17 @@ extension FileHandle {
445462#if !SWT_NO_PIPES
446463// MARK: - Pipes
447464
465+ #if !SWT_TARGET_OS_APPLE && !os(Windows) && !SWT_NO_DYNAMIC_LINKING
466+ /// Create a pipe with flags.
467+ ///
468+ /// This function declaration is provided because `pipe2()` is only declared if
469+ /// `_GNU_SOURCE` is set, but setting it causes build errors due to conflicts
470+ /// with Swift's Glibc module.
471+ private let _pipe2 = symbol ( named: " pipe2 " ) . map {
472+ castCFunction ( at: $0, to: ( @convention( c) ( UnsafeMutablePointer < CInt > , CInt) - > CInt) . self)
473+ }
474+ #endif
475+
448476extension FileHandle {
449477 /// Make a pipe connecting two new file handles.
450478 ///
@@ -461,15 +489,37 @@ extension FileHandle {
461489 /// - Bug: This function should return a tuple containing the file handles
462490 /// instead of returning them via `inout` arguments. Swift does not support
463491 /// tuples with move-only elements. ([104669935](rdar://104669935))
492+ ///
493+ /// By default, the resulting file handles are not inherited by any child
494+ /// 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()``.
464497 static func makePipe( readEnd: inout FileHandle ? , writeEnd: inout FileHandle ? ) throws {
498+ #if !os(Windows)
499+ var pipe2Called = false
500+ #endif
501+
465502 var ( fdReadEnd, fdWriteEnd) = try withUnsafeTemporaryAllocation ( of: CInt . self, capacity: 2 ) { fds in
466503#if os(Windows)
467- guard 0 == _pipe ( fds. baseAddress, 0 , _O_BINARY) else {
504+ guard 0 == _pipe ( fds. baseAddress, 0 , _O_BINARY | _O_NOINHERIT ) else {
468505 throw CError ( rawValue: swt_errno ( ) )
469506 }
470507#else
471- guard 0 == pipe ( fds. baseAddress!) else {
472- throw CError ( rawValue: swt_errno ( ) )
508+ #if !SWT_TARGET_OS_APPLE && !os(Windows) && !SWT_NO_DYNAMIC_LINKING
509+ if let _pipe2 {
510+ guard 0 == _pipe2 ( fds. baseAddress!, O_CLOEXEC) else {
511+ throw CError ( rawValue: swt_errno ( ) )
512+ }
513+ pipe2Called = true
514+ }
515+ #endif
516+
517+ if !pipe2Called {
518+ // pipe2() is not available. Use pipe() instead and simulate O_CLOEXEC
519+ // to the best of our ability.
520+ guard 0 == pipe ( fds. baseAddress!) else {
521+ throw CError ( rawValue: swt_errno ( ) )
522+ }
473523 }
474524#endif
475525 return ( fds [ 0 ] , fds [ 1 ] )
@@ -479,6 +529,15 @@ extension FileHandle {
479529 Self . _close ( fdWriteEnd)
480530 }
481531
532+ #if !os(Windows)
533+ if !pipe2Called {
534+ // pipe2() is not available. Use pipe() instead and simulate O_CLOEXEC
535+ // to the best of our ability.
536+ try _setFileDescriptorInherited ( fdReadEnd, false )
537+ try _setFileDescriptorInherited ( fdWriteEnd, false )
538+ }
539+ #endif
540+
482541 do {
483542 defer {
484543 fdReadEnd = - 1
@@ -553,6 +612,72 @@ extension FileHandle {
553612#endif
554613 }
555614#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+ }
556681}
557682
558683// MARK: - General path utilities
0 commit comments