Skip to content

Commit 37e23f2

Browse files
authored
Merge pull request #28 from lorentey/whoop-de-dup
Add support for dup/dup2
2 parents a5b2501 + 2d24a58 commit 37e23f2

File tree

3 files changed

+87
-3
lines changed

3 files changed

+87
-3
lines changed

Sources/System/FileOperations.swift

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,4 +309,66 @@ extension FileDescriptor {
309309
buffer,
310310
retryOnInterrupt: retryOnInterrupt)
311311
}
312+
313+
/// Duplicate this file descriptor and return the newly created copy.
314+
///
315+
/// - Parameters:
316+
/// - `target`: The desired target file descriptor, or `nil`, in which case
317+
/// the copy is assigned to the file descriptor with the lowest raw value
318+
/// that is not currently in use by the process.
319+
/// - retryOnInterrupt: Whether to retry the write operation
320+
/// if it throws ``Errno/interrupted``. The default is `true`.
321+
/// Pass `false` to try only once and throw an error upon interruption.
322+
/// - Returns: The new file descriptor.
323+
///
324+
/// If the `target` descriptor is already in use, then it is first
325+
/// deallocated as if a close(2) call had been done first.
326+
///
327+
/// File descriptors are merely references to some underlying system resource.
328+
/// The system does not distinguish between the original and the new file
329+
/// descriptor in any way. For example, read, write and seek operations on
330+
/// one of them also affect the logical file position in the other, and
331+
/// append mode, non-blocking I/O and asynchronous I/O options are shared
332+
/// between the references. If a separate pointer into the file is desired,
333+
/// a different object reference to the file must be obtained by issuing an
334+
/// additional call to `open`.
335+
///
336+
/// However, each file descriptor maintains its own close-on-exec flag.
337+
///
338+
///
339+
/// The corresponding C functions are `dup` and `dup2`.
340+
@_alwaysEmitIntoClient
341+
// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
342+
public func duplicate(
343+
as target: FileDescriptor? = nil,
344+
retryOnInterrupt: Bool = true
345+
) throws -> FileDescriptor {
346+
try _duplicate(as: target, retryOnInterrupt: retryOnInterrupt).get()
347+
}
348+
349+
// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
350+
@usableFromInline
351+
internal func _duplicate(
352+
as target: FileDescriptor?,
353+
retryOnInterrupt: Bool
354+
) throws -> Result<FileDescriptor, Errno> {
355+
valueOrErrno(retryOnInterrupt: retryOnInterrupt) {
356+
if let target = target {
357+
return system_dup2(self.rawValue, target.rawValue)
358+
}
359+
return system_dup(self.rawValue)
360+
}.map(FileDescriptor.init(rawValue:))
361+
}
362+
363+
@_alwaysEmitIntoClient
364+
@available(*, unavailable, renamed: "duplicate")
365+
public func dup() throws -> FileDescriptor {
366+
fatalError("Not implemented")
367+
}
368+
369+
@_alwaysEmitIntoClient
370+
@available(*, unavailable, renamed: "duplicate")
371+
public func dup2() throws -> FileDescriptor {
372+
fatalError("Not implemented")
373+
}
312374
}

Sources/SystemInternals/Syscalls.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ import ucrt
1919

2020
#if ENABLE_MOCKING
2121
// Strip the mock_system prefix and the arg list suffix
22-
private func originalSyscallName(_ s: String) -> String {
23-
precondition(s.starts(with: "system_"))
24-
return String(s.dropFirst("system_".count).prefix { $0.isLetter })
22+
private func originalSyscallName(_ function: String) -> String {
23+
// `function` must be of format `system_<name>(<parameters>)`
24+
precondition(function.starts(with: "system_"))
25+
return String(function.dropFirst("system_".count).prefix { $0 != "(" })
2526
}
2627

2728
private func mockImpl(
@@ -152,3 +153,16 @@ public func system_pwrite(
152153
return pwrite(fd, buf, nbyte, offset)
153154
}
154155

156+
public func system_dup(_ fd: Int32) -> Int32 {
157+
#if ENABLE_MOCKING
158+
if mockingEnabled { return mock(fd) }
159+
#endif
160+
return dup(fd)
161+
}
162+
163+
public func system_dup2(_ fd: Int32, _ fd2: Int32) -> Int32 {
164+
#if ENABLE_MOCKING
165+
if mockingEnabled { return mock(fd, fd2) }
166+
#endif
167+
return dup2(fd, fd2)
168+
}

Tests/SystemTests/FileOperationsTest.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ final class FileOperationsTest: XCTestCase {
6868
_ = try fd.close()
6969
},
7070

71+
MockTestCase(name: "dup", rawFD, interruptable: true) { retryOnInterrupt in
72+
_ = try fd.duplicate(retryOnInterrupt: retryOnInterrupt)
73+
},
74+
75+
MockTestCase(name: "dup2", rawFD, 42, interruptable: true) { retryOnInterrupt in
76+
_ = try fd.duplicate(as: FileDescriptor(rawValue: 42),
77+
retryOnInterrupt: retryOnInterrupt)
78+
},
7179
]
7280

7381
for test in syscallTestCases { test.runAllTests() }

0 commit comments

Comments
 (0)