@@ -73,6 +73,13 @@ struct FileHandle: ~Copyable, Sendable {
73
73
return
74
74
}
75
75
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
+
76
83
// Windows deprecates fopen() as insecure, so call _wfopen_s() instead.
77
84
let fileHandle = try path. withCString ( encodedAs: UTF16 . self) { path in
78
85
try mode. withCString ( encodedAs: UTF16 . self) { mode in
@@ -98,8 +105,13 @@ struct FileHandle: ~Copyable, Sendable {
98
105
/// - path: The path to read from.
99
106
///
100
107
/// - 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()``.
101
113
init ( forReadingAtPath path: String ) throws {
102
- try self . init ( atPath: path, mode: " rb " )
114
+ try self . init ( atPath: path, mode: " reb " )
103
115
}
104
116
105
117
/// Initialize an instance of this type to write to the given path.
@@ -108,8 +120,13 @@ struct FileHandle: ~Copyable, Sendable {
108
120
/// - path: The path to write to.
109
121
///
110
122
/// - 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()``.
111
128
init ( forWritingAtPath path: String ) throws {
112
- try self . init ( atPath: path, mode: " wb " )
129
+ try self . init ( atPath: path, mode: " web " )
113
130
}
114
131
115
132
/// Initialize an instance of this type with an existing C file handle.
@@ -445,6 +462,17 @@ extension FileHandle {
445
462
#if !SWT_NO_PIPES
446
463
// MARK: - Pipes
447
464
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
+
448
476
extension FileHandle {
449
477
/// Make a pipe connecting two new file handles.
450
478
///
@@ -461,15 +489,37 @@ extension FileHandle {
461
489
/// - Bug: This function should return a tuple containing the file handles
462
490
/// instead of returning them via `inout` arguments. Swift does not support
463
491
/// 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()``.
464
497
static func makePipe( readEnd: inout FileHandle ? , writeEnd: inout FileHandle ? ) throws {
498
+ #if !os(Windows)
499
+ var pipe2Called = false
500
+ #endif
501
+
465
502
var ( fdReadEnd, fdWriteEnd) = try withUnsafeTemporaryAllocation ( of: CInt . self, capacity: 2 ) { fds in
466
503
#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 {
468
505
throw CError ( rawValue: swt_errno ( ) )
469
506
}
470
507
#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
+ }
473
523
}
474
524
#endif
475
525
return ( fds [ 0 ] , fds [ 1 ] )
@@ -479,6 +529,15 @@ extension FileHandle {
479
529
Self . _close ( fdWriteEnd)
480
530
}
481
531
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
+
482
541
do {
483
542
defer {
484
543
fdReadEnd = - 1
@@ -553,6 +612,72 @@ extension FileHandle {
553
612
#endif
554
613
}
555
614
#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
+ }
556
681
}
557
682
558
683
// MARK: - General path utilities
0 commit comments