Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Sources/CSystem/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ target_include_directories(CSystem INTERFACE

install(FILES
include/CSystemLinux.h
include/CSystemShared.h
include/CSystemWindows.h
include/module.modulemap
DESTINATION include/CSystem)
Expand Down
2 changes: 2 additions & 0 deletions Sources/CSystem/include/CSystemShared.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
extern int csystem_posix_pipe2(int fildes[2], int flag);
extern int csystem_posix_dup3(int fildes, int fildes2, int flag);
1 change: 1 addition & 0 deletions Sources/CSystem/include/module.modulemap
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module CSystem {
header "CSystemShared.h"
header "CSystemLinux.h"
header "CSystemWASI.h"
header "CSystemWindows.h"
Expand Down
32 changes: 30 additions & 2 deletions Sources/CSystem/shims.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,40 @@
See https://swift.org/LICENSE.txt for license information
*/

#ifdef __linux__
#if defined(__FreeBSD__) || defined(__OpenBSD__)
#define __BSD_VISIBLE
#include <unistd.h>
#endif

#ifdef __linux__
#define _GNU_SOURCE
#include <CSystemLinux.h>

#endif

#if defined(_WIN32)
#include <CSystemWindows.h>
#endif

#include <errno.h>

#if !defined(_WIN32) && !defined(__wasi__) && !defined(__APPLE__)
#define HAVE_PIPE2_DUP3
#endif

// Wrappers are required because _GNU_SOURCE causes a conflict with other imports when defined in CSystemLinux.h
extern int csystem_posix_pipe2(int fildes[2], int flag) {
#ifdef HAVE_PIPE2_DUP3
return pipe2(fildes, flag);
#else
errno = ENOSYS;
return -1;
#endif
}
extern int csystem_posix_dup3(int fildes, int fildes2, int flag) {
#ifdef HAVE_PIPE2_DUP3
return dup3(fildes, fildes2, flag);
#else
errno = ENOSYS;
return -1;
#endif
}
135 changes: 135 additions & 0 deletions Sources/System/FileDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,141 @@ extension FileDescriptor {
#endif
}

/// Options that specify behavior for a newly-created pipe.
@frozen
@available(Windows, unavailable)
@available(macOS, unavailable)
@available(iOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
@available(visionOS, unavailable)
public struct PipeOptions: OptionSet, Sendable, Hashable, Codable {
/// The raw C options.
@_alwaysEmitIntoClient
public var rawValue: CInt

/// Create a strongly-typed options value from raw C options.
@_alwaysEmitIntoClient
public init(rawValue: CInt) { self.rawValue = rawValue }

#if !os(Windows)
/// Indicates that all
/// subsequent input and output operations on the pipe's file descriptors will be nonblocking.
///
/// The corresponding C constant is `O_NONBLOCK`.
@_alwaysEmitIntoClient
public static var nonBlocking: OpenOptions { .init(rawValue: _O_NONBLOCK) }

@_alwaysEmitIntoClient
@available(*, unavailable, renamed: "nonBlocking")
public static var O_NONBLOCK: OpenOptions { nonBlocking }

/// Indicates that executing a program closes the file.
///
/// Normally, file descriptors remain open
/// across calls to the `exec(2)` family of functions.
/// If you specify this option,
/// the file descriptor is closed when replacing this process
/// with another process.
///
/// The state of the file
/// descriptor flags can be inspected using `F_GETFD`,
/// as described in the `fcntl(2)` man page.
///
/// The corresponding C constant is `O_CLOEXEC`.
@_alwaysEmitIntoClient
public static var closeOnExec: OpenOptions { .init(rawValue: _O_CLOEXEC) }

@_alwaysEmitIntoClient
@available(*, unavailable, renamed: "closeOnExec")
public static var O_CLOEXEC: OpenOptions { closeOnExec }

#if !os(WASI) && !os(Linux) && !os(Android)
/// Indicates that forking a program closes the file.
///
/// Normally, file descriptors remain open
/// across calls to the `fork(2)` function.
/// If you specify this option,
/// the file descriptor is closed when forking this process
/// into another process.
///
/// The state of the file
/// descriptor flags can be inspected using `F_GETFD`,
/// as described in the `fcntl(2)` man page.
///
/// The corresponding C constant is `O_CLOFORK`.
@_alwaysEmitIntoClient
public static var closeOnFork: OpenOptions { .init(rawValue: _O_CLOFORK) }

@_alwaysEmitIntoClient
@available(*, unavailable, renamed: "closeOnFork")
public static var O_CLOFORK: OpenOptions { closeOnFork }
#endif
#endif
}

/// Options that specify behavior for a duplicated file descriptor.
@frozen
@available(Windows, unavailable)
@available(macOS, unavailable)
@available(iOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
@available(visionOS, unavailable)
public struct DuplicateOptions: OptionSet, Sendable, Hashable, Codable {
/// The raw C options.
@_alwaysEmitIntoClient
public var rawValue: CInt

/// Create a strongly-typed options value from raw C options.
@_alwaysEmitIntoClient
public init(rawValue: CInt) { self.rawValue = rawValue }

#if !os(Windows)
/// Indicates that executing a program closes the file.
///
/// Normally, file descriptors remain open
/// across calls to the `exec(2)` family of functions.
/// If you specify this option,
/// the file descriptor is closed when replacing this process
/// with another process.
///
/// The state of the file
/// descriptor flags can be inspected using `F_GETFD`,
/// as described in the `fcntl(2)` man page.
///
/// The corresponding C constant is `O_CLOEXEC`.
@_alwaysEmitIntoClient
public static var closeOnExec: OpenOptions { .init(rawValue: _O_CLOEXEC) }

@_alwaysEmitIntoClient
@available(*, unavailable, renamed: "closeOnExec")
public static var O_CLOEXEC: OpenOptions { closeOnExec }

#if !os(WASI) && !os(Linux) && !os(Android)
/// Indicates that forking a program closes the file.
///
/// Normally, file descriptors remain open
/// across calls to the `fork(2)` function.
/// If you specify this option,
/// the file descriptor is closed when forking this process
/// into another process.
///
/// The state of the file
/// descriptor flags can be inspected using `F_GETFD`,
/// as described in the `fcntl(2)` man page.
///
/// The corresponding C constant is `O_CLOFORK`.
@_alwaysEmitIntoClient
public static var closeOnFork: OpenOptions { .init(rawValue: _O_CLOFORK) }

@_alwaysEmitIntoClient
@available(*, unavailable, renamed: "closeOnFork")
public static var O_CLOFORK: OpenOptions { closeOnFork }
#endif
#endif
}

/// Options for specifying what a file descriptor's offset is relative to.
@frozen
@available(System 0.0.1, *)
Expand Down
89 changes: 83 additions & 6 deletions Sources/System/FileOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -403,17 +403,61 @@ extension FileDescriptor {
as target: FileDescriptor? = nil,
retryOnInterrupt: Bool = true
) throws -> FileDescriptor {
try _duplicate(as: target, retryOnInterrupt: retryOnInterrupt).get()
try _duplicate(as: target, options: 0, retryOnInterrupt: retryOnInterrupt).get()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The package version that will include this drops support for Swift toolchains older than 6.0. We could therefore make these @_aEIC entry points use typed throws, i.e. throws(ErrNo).

}

/// Duplicates this file descriptor and return the newly created copy.
///
/// - Parameters:
/// - `target`: The desired target file descriptor.
/// - `options`: The behavior for creating the target file descriptor.
/// - retryOnInterrupt: Whether to retry the write operation
/// if it throws ``Errno/interrupted``. The default is `true`.
/// Pass `false` to try only once and throw an error upon interruption.
/// - Returns: The new file descriptor.
///
/// If the `target` descriptor is already in use, then it is first
/// deallocated as if a close(2) call had been done first.
///
/// File descriptors are merely references to some underlying system resource.
/// The system does not distinguish between the original and the new file
/// descriptor in any way. For example, read, write and seek operations on
/// one of them also affect the logical file position in the other, and
/// append mode, non-blocking I/O and asynchronous I/O options are shared
/// between the references. If a separate pointer into the file is desired,
/// a different object reference to the file must be obtained by issuing an
/// additional call to `open`.
///
/// However, each file descriptor maintains its own close-on-exec flag.
///
///
/// The corresponding C function is `dup3`.
@_alwaysEmitIntoClient
@available(Windows, unavailable)
@available(macOS, unavailable)
@available(iOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
@available(visionOS, unavailable)
public func duplicate(
as target: FileDescriptor,
options: DuplicateOptions,
retryOnInterrupt: Bool = true
) throws -> FileDescriptor {
try _duplicate(as: target, options: options.rawValue, retryOnInterrupt: retryOnInterrupt).get()
}

@available(System 0.0.2, *)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A @usableFromInline symbol requires availability.

@usableFromInline
internal func _duplicate(
as target: FileDescriptor?,
options: Int32,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't remove the old @usableFromInline symbol; it could be re-implemented by forwarding to the new implementation.

retryOnInterrupt: Bool
) throws -> Result<FileDescriptor, Errno> {
valueOrErrno(retryOnInterrupt: retryOnInterrupt) {
if let target = target {
if options != 0 {
return system_dup3(self.rawValue, target.rawValue, options)
}
return system_dup2(self.rawValue, target.rawValue)
}
return system_dup(self.rawValue)
Expand All @@ -431,6 +475,12 @@ extension FileDescriptor {
public func dup2() throws -> FileDescriptor {
fatalError("Not implemented")
}

@_alwaysEmitIntoClient
@available(*, unavailable, renamed: "duplicate")
public func dup3() throws -> FileDescriptor {
fatalError("Not implemented")
}
}
#endif

Expand All @@ -445,21 +495,48 @@ extension FileDescriptor {
@_alwaysEmitIntoClient
@available(System 1.1.0, *)
public static func pipe() throws -> (readEnd: FileDescriptor, writeEnd: FileDescriptor) {
try _pipe().get()
try _pipe(options: 0).get()
}

/// Creates a unidirectional data channel, which can be used for interprocess communication.
///
/// - Parameters:
/// - options: The behavior for creating the pipe.
///
/// - Returns: The pair of file descriptors.
///
/// The corresponding C function is `pipe2`.
@_alwaysEmitIntoClient
@available(Windows, unavailable)
@available(macOS, unavailable)
@available(iOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
@available(visionOS, unavailable)
public static func pipe(options: PipeOptions) throws -> (readEnd: FileDescriptor, writeEnd: FileDescriptor) {
try _pipe(options: options.rawValue).get()
}

@available(System 1.1.0, *)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, @usableFromInline symbols require availability.

@usableFromInline
internal static func _pipe() -> Result<(readEnd: FileDescriptor, writeEnd: FileDescriptor), Errno> {
internal static func _pipe(options: Int32) -> Result<(readEnd: FileDescriptor, writeEnd: FileDescriptor), Errno> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above.

var fds: (Int32, Int32) = (-1, -1)
return withUnsafeMutablePointer(to: &fds) { pointer in
pointer.withMemoryRebound(to: Int32.self, capacity: 2) { fds in
valueOrErrno(retryOnInterrupt: false) {
system_pipe(fds)
if options != 0 {
return system_pipe2(fds, options)
}
return system_pipe(fds)
}.map { _ in (.init(rawValue: fds[0]), .init(rawValue: fds[1])) }
}
}
}

@_alwaysEmitIntoClient
@available(*, unavailable, renamed: "pipe")
public func pipe2() throws -> FileDescriptor {
fatalError("Not implemented")
}
}
#endif

Expand Down
9 changes: 9 additions & 0 deletions Sources/System/Internals/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,15 @@ internal var _O_SYMLINK: CInt { O_SYMLINK }
#if !os(Windows)
@_alwaysEmitIntoClient
internal var _O_CLOEXEC: CInt { O_CLOEXEC }

@_alwaysEmitIntoClient
internal var _O_CLOFORK: CInt {
#if !os(WASI) && !os(Linux) && !os(Android) && !canImport(Darwin)
O_CLOFORK
#else
0
#endif
}
#endif

@_alwaysEmitIntoClient
Expand Down
17 changes: 16 additions & 1 deletion Sources/System/Internals/Syscalls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import Glibc
#elseif canImport(Musl)
import Musl
#elseif canImport(WASILibc)
import CSystem
import WASILibc
#elseif os(Windows)
import ucrt
Expand All @@ -24,6 +23,8 @@ import Android
#error("Unsupported Platform")
#endif

import CSystem

// Interacting with the mocking system, tracing, etc., is a potentially significant
// amount of code size, so we hand outline that code for every syscall

Expand Down Expand Up @@ -139,6 +140,13 @@ internal func system_dup2(_ fd: Int32, _ fd2: Int32) -> Int32 {
#endif
return dup2(fd, fd2)
}

internal func system_dup3(_ fd: Int32, _ fd2: Int32, _ oflag: Int32) -> Int32 {
#if ENABLE_MOCKING
if mockingEnabled { return _mock(fd, fd2, oflag) }
#endif
return csystem_posix_dup3(fd, fd2, oflag)
}
#endif

#if !os(WASI)
Expand All @@ -148,6 +156,13 @@ internal func system_pipe(_ fds: UnsafeMutablePointer<Int32>) -> CInt {
#endif
return pipe(fds)
}

internal func system_pipe2(_ fds: UnsafeMutablePointer<Int32>, _ oflag: Int32) -> CInt {
#if ENABLE_MOCKING
if mockingEnabled { return _mock(fds, oflag) }
#endif
return csystem_posix_pipe2(fds, oflag)
}
#endif

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