Skip to content

Commit 2965d6e

Browse files
committed
dup3 wrapper proposal
Improve a sentence.
1 parent 4956c2c commit 2965d6e

File tree

1 file changed

+175
-0
lines changed

1 file changed

+175
-0
lines changed
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# Add support for `dup3` and `pipe2` POSIX API to `FileDescriptor`
2+
3+
* Proposal: SYS-0007
4+
- Author(s): Jake Petroules <jake.petroules@apple.com>, Guillaume Lessard <guillaume.lessard@apple.com>
5+
6+
* Other Reviews: [Swift Forums Pitch](https://forums.swift.org/t/82486)
7+
8+
##### Revision history
9+
10+
* **v1** Initial version
11+
12+
## Introduction
13+
14+
Swift System today provides `FileDescriptor.duplicate()`, `FileDescriptor.duplicate(as:)` and `FileDescriptor.pipe()` cover APIs for the POSIX `dup`, `dup2`, and `pipe` functions, respectively.
15+
16+
This proposal adds additional `FileDescriptor` overloads to cover new POSIX 2024 functions in this family of APIs. These overloads will be available on Linux, FreeBSD and Android.
17+
18+
## Motivation
19+
20+
It is considered best practice to set the `O_CLOEXEC` (close-on-exec) bit on newly created file descriptors to prevent them from being inherited by subprocesses.
21+
22+
Some POSIX functions such as `open` provide a "flags" parameter allowing this the close-on-exec bit to be set atomically on the newly created file descriptor. However, others provide no such flags parameter, and require the caller to use `fcntl` to set the close-on-exec bit after the fact. This can lead to race conditions and security bugs where file descriptors can be inherited between calling the function creating the file descriptor, and calling `fcntl`.
23+
24+
POSIX 2024 corrects this deficiency for `dup2` and `pipe` by introducing `dup3` and `pipe2` variants that allow the close-on-exec bit to be set atomically.
25+
26+
While it's *also* considered best practice for subprocess spawning code to close all open file descriptors in the newly created subprocess (swiftlang/swift-subprocess does this for example), there is no guarantee that a user of Swift System is using a mechanism which does so throughout their entire process, and might be using process spawning code they don't control. Adding the proposed overloads will allow developers to write code which adopts a posture of defence in depth with respect to opened file descriptors.
27+
28+
## Proposed solution
29+
30+
This proposal adds new overloads to `FileDescriptor.duplicate(as:)` and `FileDescriptor.pipe()` that will wrap the functionality of the `dup3` and `pipe2` functions.
31+
32+
## Example usage
33+
34+
```swift
35+
import System
36+
37+
let fd0 = try FileDescriptor.open("/tmp/test.txt", .readOnly)
38+
let fd1 = FileDescriptor(rawValue: 731)
39+
let fd2 = fd0.duplicate(as: fd1, options: [.closeOnFork, .closeOnExec])
40+
```
41+
42+
## Detailed design
43+
44+
`FileDescriptor` will add the following overloads and types:
45+
46+
```swift
47+
struct FileDescriptor {
48+
/// Creates a unidirectional data channel, which can be used for
49+
/// interprocess communication.
50+
///
51+
/// - Parameters:
52+
/// - options: The behavior for creating the pipe.
53+
///
54+
/// - Returns: The pair of file descriptors.
55+
///
56+
/// The corresponding C function is `pipe2`.
57+
public static func pipe(
58+
options: PipeOptions
59+
) throws(Errno) -> (readEnd: FileDescriptor, writeEnd: FileDescriptor)
60+
61+
/// Duplicates this file descriptor and return the newly created copy.
62+
///
63+
/// - Parameters:
64+
/// - `target`: The desired target file descriptor.
65+
/// - `options`: The behavior for creating the target file descriptor.
66+
/// - retryOnInterrupt: Whether to retry the write operation
67+
/// if it throws ``Errno/interrupted``. The default is `true`.
68+
/// Pass `false` to try only once and throw an error upon interruption.
69+
/// - Returns: The new file descriptor.
70+
///
71+
/// If the `target` descriptor is already in use, then it is first
72+
/// deallocated as if a close(2) call had been done first.
73+
///
74+
/// File descriptors are merely references to some underlying system resource.
75+
/// The system does not distinguish between the original and the new file
76+
/// descriptor in any way. For example, read, write and seek operations on
77+
/// one of them also affect the logical file position in the other, and
78+
/// append mode, non-blocking I/O and asynchronous I/O options are shared
79+
/// between the references. If a separate pointer into the file is desired,
80+
/// a different object reference to the file must be obtained by issuing an
81+
/// additional call to `open`.
82+
///
83+
/// However, each file descriptor maintains its own close-on-exec flag.
84+
///
85+
/// The corresponding C function is `dup3`.
86+
@discardableResult
87+
public func duplicate(
88+
as target: FileDescriptor,
89+
options: DuplicateOptions,
90+
retryOnInterrupt: Bool = true
91+
) throws(Errno) -> FileDescriptor
92+
93+
/// Options that specify behavior for a newly-created pipe.
94+
public struct PipeOptions: OptionSet, Sendable, Hashable, Codable {
95+
/// The raw C options.
96+
public var rawValue: CInt
97+
98+
/// Create a strongly-typed options value from raw C options.
99+
public init(rawValue: CInt)
100+
101+
/// Indicates that all subsequent input and output operations
102+
/// on the pipe's file descriptors will be nonblocking.
103+
///
104+
/// The corresponding C constant is `O_NONBLOCK`.
105+
public static var nonBlocking: OpenOptions
106+
107+
/// Indicates that executing a program closes the file.
108+
///
109+
/// Normally, file descriptors remain open across calls to the `exec(2)`
110+
/// family of functions. If you specify this option, the file descriptor
111+
/// is closed when replacing this process with another process.
112+
///
113+
/// The state of the file descriptor flags can be inspected using `F_GETFD`,
114+
/// as described in the `fcntl(2)` man page.
115+
///
116+
/// The corresponding C constant is `O_CLOEXEC`.
117+
public static var closeOnExec: PipeOptions
118+
119+
/// Indicates that forking a program closes the file.
120+
///
121+
/// Normally, file descriptors remain open across calls to the `fork(2)`
122+
/// function. If you specify this option, the file descriptor is closed
123+
/// when forking this process into another process.
124+
///
125+
/// The state of the file descriptor flags can be inspected using `F_GETFD`,
126+
/// as described in the `fcntl(2)` man page.
127+
///
128+
/// The corresponding C constant is `O_CLOFORK`.
129+
public static var closeOnFork: PipeOptions
130+
}
131+
132+
/// Options that specify behavior for a duplicated file descriptor.
133+
public struct DuplicateOptions: OptionSet, Sendable, Hashable, Codable {
134+
/// The raw C options.
135+
public var rawValue: CInt
136+
137+
/// Create a strongly-typed options value from raw C options.
138+
public init(rawValue: CInt)
139+
140+
/// Normally, file descriptors remain open across calls to the `exec(2)`
141+
/// family of functions. If you specify this option, the file descriptor
142+
/// is closed when replacing this process with another process.
143+
///
144+
/// The state of the file descriptor flags can be inspected using `F_GETFD`,
145+
/// as described in the `fcntl(2)` man page.
146+
///
147+
/// The corresponding C constant is `O_CLOEXEC`.
148+
public static var closeOnExec: DuplicateOptions
149+
150+
/// Indicates that forking a program closes the file.
151+
///
152+
/// Normally, file descriptors remain open across calls to the `fork(2)`
153+
/// function. If you specify this option, the file descriptor is closed
154+
/// when forking this process into another process.
155+
///
156+
/// The state of the file descriptor flags can be inspected using `F_GETFD`,
157+
/// as described in the `fcntl(2)` man page.
158+
///
159+
/// The corresponding C constant is `O_CLOFORK`.
160+
public static var closeOnFork: DuplicateOptions
161+
}
162+
}
163+
```
164+
165+
These API additions are unavailable on Windows and Darwin, as the underlying `dup3` and `pipe2` APIs do not exist.
166+
167+
`closeOnFork` is unavailable on Linux and Android, as the underlying `O_CLOFORK` constant does not exist.
168+
169+
## Impact on existing code
170+
171+
This change will have no impact on existing code, as it is purely additive.
172+
173+
## Alternatives considered
174+
175+
None. The underlying POSIX API are additions that help C developers follow best practices, and these overloads provide the same value to Swift developers.

0 commit comments

Comments
 (0)