Skip to content

Commit 66bc4e3

Browse files
committed
Add overloads to cover dup3 and pipe2 POSIX APIs
This follows the existing pattern of wrapping dup/dup2 and pipe with FileDescriptor.duplicate and FileDescriptor.pipe, with additional overloads for passing the O_CLOEXEC and similar flags.
1 parent f477389 commit 66bc4e3

File tree

5 files changed

+225
-5
lines changed

5 files changed

+225
-5
lines changed

Sources/CSystem/include/CSystemLinux.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99

1010
#ifdef __linux__
11-
11+
#define _GNU_SOURCE
1212
#include <sys/epoll.h>
1313
#include <sys/eventfd.h>
1414
#include <sys/stat.h>

Sources/System/FileDescriptor.swift

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,129 @@ extension FileDescriptor {
324324
#endif
325325
}
326326

327+
/// Options that specify behavior for a newly-created pipe.
328+
@frozen
329+
public struct PipeOptions: OptionSet, Sendable, Hashable, Codable {
330+
/// The raw C options.
331+
@_alwaysEmitIntoClient
332+
public var rawValue: CInt
333+
334+
/// Create a strongly-typed options value from raw C options.
335+
@_alwaysEmitIntoClient
336+
public init(rawValue: CInt) { self.rawValue = rawValue }
337+
338+
#if !os(Windows) && !canImport(Darwin)
339+
/// Indicates that all
340+
/// subsequent input and output operations on the pipe's file descriptors will be nonblocking.
341+
///
342+
/// The corresponding C constant is `O_NONBLOCK`.
343+
@_alwaysEmitIntoClient
344+
public static var nonBlocking: OpenOptions { .init(rawValue: _O_NONBLOCK) }
345+
346+
@_alwaysEmitIntoClient
347+
@available(*, unavailable, renamed: "nonBlocking")
348+
public static var O_NONBLOCK: OpenOptions { nonBlocking }
349+
350+
/// Indicates that executing a program closes the file.
351+
///
352+
/// Normally, file descriptors remain open
353+
/// across calls to the `exec(2)` family of functions.
354+
/// If you specify this option,
355+
/// the file descriptor is closed when replacing this process
356+
/// with another process.
357+
///
358+
/// The state of the file
359+
/// descriptor flags can be inspected using `F_GETFD`,
360+
/// as described in the `fcntl(2)` man page.
361+
///
362+
/// The corresponding C constant is `O_CLOEXEC`.
363+
@_alwaysEmitIntoClient
364+
public static var closeOnExec: OpenOptions { .init(rawValue: _O_CLOEXEC) }
365+
366+
@_alwaysEmitIntoClient
367+
@available(*, unavailable, renamed: "closeOnExec")
368+
public static var O_CLOEXEC: OpenOptions { closeOnExec }
369+
370+
#if !os(WASI) && !os(Linux) && !os(Android)
371+
/// Indicates that forking a program closes the file.
372+
///
373+
/// Normally, file descriptors remain open
374+
/// across calls to the `fork(2)` function.
375+
/// If you specify this option,
376+
/// the file descriptor is closed when forking this process
377+
/// into another process.
378+
///
379+
/// The state of the file
380+
/// descriptor flags can be inspected using `F_GETFD`,
381+
/// as described in the `fcntl(2)` man page.
382+
///
383+
/// The corresponding C constant is `O_CLOFORK`.
384+
@_alwaysEmitIntoClient
385+
public static var closeOnFork: OpenOptions { .init(rawValue: _O_CLOFORK) }
386+
387+
@_alwaysEmitIntoClient
388+
@available(*, unavailable, renamed: "closeOnFork")
389+
public static var O_CLOFORK: OpenOptions { closeOnFork }
390+
#endif
391+
#endif
392+
}
393+
394+
/// Options that specify behavior for a duplicated file descriptor.
395+
@frozen
396+
public struct DuplicateOptions: OptionSet, Sendable, Hashable, Codable {
397+
/// The raw C options.
398+
@_alwaysEmitIntoClient
399+
public var rawValue: CInt
400+
401+
/// Create a strongly-typed options value from raw C options.
402+
@_alwaysEmitIntoClient
403+
public init(rawValue: CInt) { self.rawValue = rawValue }
404+
405+
#if !os(Windows) && !canImport(Darwin)
406+
/// Indicates that executing a program closes the file.
407+
///
408+
/// Normally, file descriptors remain open
409+
/// across calls to the `exec(2)` family of functions.
410+
/// If you specify this option,
411+
/// the file descriptor is closed when replacing this process
412+
/// with another process.
413+
///
414+
/// The state of the file
415+
/// descriptor flags can be inspected using `F_GETFD`,
416+
/// as described in the `fcntl(2)` man page.
417+
///
418+
/// The corresponding C constant is `O_CLOEXEC`.
419+
@_alwaysEmitIntoClient
420+
public static var closeOnExec: OpenOptions { .init(rawValue: _O_CLOEXEC) }
421+
422+
@_alwaysEmitIntoClient
423+
@available(*, unavailable, renamed: "closeOnExec")
424+
public static var O_CLOEXEC: OpenOptions { closeOnExec }
425+
426+
#if !os(WASI) && !os(Linux) && !os(Android)
427+
/// Indicates that forking a program closes the file.
428+
///
429+
/// Normally, file descriptors remain open
430+
/// across calls to the `fork(2)` function.
431+
/// If you specify this option,
432+
/// the file descriptor is closed when forking this process
433+
/// into another process.
434+
///
435+
/// The state of the file
436+
/// descriptor flags can be inspected using `F_GETFD`,
437+
/// as described in the `fcntl(2)` man page.
438+
///
439+
/// The corresponding C constant is `O_CLOFORK`.
440+
@_alwaysEmitIntoClient
441+
public static var closeOnFork: OpenOptions { .init(rawValue: _O_CLOFORK) }
442+
443+
@_alwaysEmitIntoClient
444+
@available(*, unavailable, renamed: "closeOnFork")
445+
public static var O_CLOFORK: OpenOptions { closeOnFork }
446+
#endif
447+
#endif
448+
}
449+
327450
/// Options for specifying what a file descriptor's offset is relative to.
328451
@frozen
329452
@available(System 0.0.1, *)

Sources/System/FileOperations.swift

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -403,17 +403,59 @@ extension FileDescriptor {
403403
as target: FileDescriptor? = nil,
404404
retryOnInterrupt: Bool = true
405405
) throws -> FileDescriptor {
406-
try _duplicate(as: target, retryOnInterrupt: retryOnInterrupt).get()
406+
try _duplicate(as: target, options: [], retryOnInterrupt: retryOnInterrupt).get()
407+
}
408+
409+
/// Duplicates this file descriptor and return the newly created copy.
410+
///
411+
/// - Parameters:
412+
/// - `target`: The desired target file descriptor.
413+
/// - `options`: The behavior for creating the target file descriptor.
414+
/// - retryOnInterrupt: Whether to retry the write operation
415+
/// if it throws ``Errno/interrupted``. The default is `true`.
416+
/// Pass `false` to try only once and throw an error upon interruption.
417+
/// - Returns: The new file descriptor.
418+
///
419+
/// If the `target` descriptor is already in use, then it is first
420+
/// deallocated as if a close(2) call had been done first.
421+
///
422+
/// File descriptors are merely references to some underlying system resource.
423+
/// The system does not distinguish between the original and the new file
424+
/// descriptor in any way. For example, read, write and seek operations on
425+
/// one of them also affect the logical file position in the other, and
426+
/// append mode, non-blocking I/O and asynchronous I/O options are shared
427+
/// between the references. If a separate pointer into the file is desired,
428+
/// a different object reference to the file must be obtained by issuing an
429+
/// additional call to `open`.
430+
///
431+
/// However, each file descriptor maintains its own close-on-exec flag.
432+
///
433+
///
434+
/// The corresponding C function is `dup3`.
435+
@_alwaysEmitIntoClient
436+
@available(System 0.0.2, *)
437+
public func duplicate(
438+
as target: FileDescriptor,
439+
options: DuplicateOptions,
440+
retryOnInterrupt: Bool = true
441+
) throws -> FileDescriptor {
442+
try _duplicate(as: target, options: options, retryOnInterrupt: retryOnInterrupt).get()
407443
}
408444

409445
@available(System 0.0.2, *)
410446
@usableFromInline
411447
internal func _duplicate(
412448
as target: FileDescriptor?,
449+
options: DuplicateOptions,
413450
retryOnInterrupt: Bool
414451
) throws -> Result<FileDescriptor, Errno> {
415452
valueOrErrno(retryOnInterrupt: retryOnInterrupt) {
416453
if let target = target {
454+
#if !os(Windows) && !canImport(Darwin)
455+
if !options.isEmpty {
456+
return system_dup3(self.rawValue, target.rawValue, options.rawValue)
457+
}
458+
#endif
417459
return system_dup2(self.rawValue, target.rawValue)
418460
}
419461
return system_dup(self.rawValue)
@@ -431,6 +473,12 @@ extension FileDescriptor {
431473
public func dup2() throws -> FileDescriptor {
432474
fatalError("Not implemented")
433475
}
476+
477+
@_alwaysEmitIntoClient
478+
@available(*, unavailable, renamed: "duplicate")
479+
public func dup3() throws -> FileDescriptor {
480+
fatalError("Not implemented")
481+
}
434482
}
435483
#endif
436484

@@ -445,21 +493,46 @@ extension FileDescriptor {
445493
@_alwaysEmitIntoClient
446494
@available(System 1.1.0, *)
447495
public static func pipe() throws -> (readEnd: FileDescriptor, writeEnd: FileDescriptor) {
448-
try _pipe().get()
496+
try _pipe(options: []).get()
497+
}
498+
499+
/// Creates a unidirectional data channel, which can be used for interprocess communication.
500+
///
501+
/// - Parameters:
502+
/// - options: The behavior for creating the pipe.
503+
///
504+
/// - Returns: The pair of file descriptors.
505+
///
506+
/// The corresponding C function is `pipe2`.
507+
@_alwaysEmitIntoClient
508+
@available(/*System 1.1.0: macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4*/iOS 8, *)
509+
public static func pipe(options: PipeOptions) throws -> (readEnd: FileDescriptor, writeEnd: FileDescriptor) {
510+
try _pipe(options: options).get()
449511
}
450512

451513
@available(System 1.1.0, *)
452514
@usableFromInline
453-
internal static func _pipe() -> Result<(readEnd: FileDescriptor, writeEnd: FileDescriptor), Errno> {
515+
internal static func _pipe(options: PipeOptions) -> Result<(readEnd: FileDescriptor, writeEnd: FileDescriptor), Errno> {
454516
var fds: (Int32, Int32) = (-1, -1)
455517
return withUnsafeMutablePointer(to: &fds) { pointer in
456518
pointer.withMemoryRebound(to: Int32.self, capacity: 2) { fds in
457519
valueOrErrno(retryOnInterrupt: false) {
458-
system_pipe(fds)
520+
#if !os(Windows) && !canImport(Darwin)
521+
if !options.isEmpty {
522+
return system_pipe2(fds, options.rawValue)
523+
}
524+
#endif
525+
return system_pipe(fds)
459526
}.map { _ in (.init(rawValue: fds[0]), .init(rawValue: fds[1])) }
460527
}
461528
}
462529
}
530+
531+
@_alwaysEmitIntoClient
532+
@available(*, unavailable, renamed: "pipe")
533+
public func pipe2() throws -> FileDescriptor {
534+
fatalError("Not implemented")
535+
}
463536
}
464537
#endif
465538

Sources/System/Internals/Constants.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,11 @@ internal var _O_SYMLINK: CInt { O_SYMLINK }
621621
#if !os(Windows)
622622
@_alwaysEmitIntoClient
623623
internal var _O_CLOEXEC: CInt { O_CLOEXEC }
624+
625+
#if !os(WASI) && !os(Linux) && !os(Android) && !canImport(Darwin)
626+
@_alwaysEmitIntoClient
627+
internal var _O_CLOFORK: CInt { O_CLOFORK }
628+
#endif
624629
#endif
625630

626631
@_alwaysEmitIntoClient

Sources/System/Internals/Syscalls.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#if SYSTEM_PACKAGE_DARWIN
1111
import Darwin
1212
#elseif canImport(Glibc)
13+
import CSystem
1314
import Glibc
1415
#elseif canImport(Musl)
1516
import Musl
@@ -139,6 +140,15 @@ internal func system_dup2(_ fd: Int32, _ fd2: Int32) -> Int32 {
139140
#endif
140141
return dup2(fd, fd2)
141142
}
143+
144+
#if !os(Windows) && !canImport(Darwin)
145+
internal func system_dup3(_ fd: Int32, _ fd2: Int32, _ oflag: Int32) -> Int32 {
146+
#if ENABLE_MOCKING
147+
if mockingEnabled { return _mock(fd, fd2, oflag) }
148+
#endif
149+
return dup3(fd, fd2, oflag)
150+
}
151+
#endif
142152
#endif
143153

144154
#if !os(WASI)
@@ -148,6 +158,15 @@ internal func system_pipe(_ fds: UnsafeMutablePointer<Int32>) -> CInt {
148158
#endif
149159
return pipe(fds)
150160
}
161+
162+
#if !os(Windows) && !canImport(Darwin)
163+
internal func system_pipe2(_ fds: UnsafeMutablePointer<Int32>, _ oflag: Int32) -> CInt {
164+
#if ENABLE_MOCKING
165+
if mockingEnabled { return _mock(fds, oflag) }
166+
#endif
167+
return pipe2(fds, oflag)
168+
}
169+
#endif
151170
#endif
152171

153172
internal func system_ftruncate(_ fd: Int32, _ length: off_t) -> Int32 {

0 commit comments

Comments
 (0)