Skip to content
Merged
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
11 changes: 6 additions & 5 deletions Sources/System/FileOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ extension FileDescriptor {
_ mode: FileDescriptor.AccessMode,
options: FileDescriptor.OpenOptions,
permissions: FilePermissions?,
retryOnInterrupt: Bool = true
retryOnInterrupt: Bool
) -> Result<FileDescriptor, Errno> {
let oFlag = mode.rawValue | options.rawValue
let descOrError: Result<CInt, Errno> = valueOrErrno(retryOnInterrupt: retryOnInterrupt) {
Expand All @@ -96,7 +96,7 @@ extension FileDescriptor {

@usableFromInline
internal func _close() -> Result<(), Errno> {
nothingOrErrno(system_close(self.rawValue))
nothingOrErrno(retryOnInterrupt: false) { system_close(self.rawValue) }
}

/// Reposition the offset for the given file descriptor.
Expand All @@ -120,8 +120,9 @@ extension FileDescriptor {
internal func _seek(
offset: Int64, from whence: FileDescriptor.SeekOrigin
) -> Result<Int64, Errno> {
let newOffset = system_lseek(self.rawValue, _COffT(offset), whence.rawValue)
return valueOrErrno(Int64(newOffset))
valueOrErrno(retryOnInterrupt: false) {
Int64(system_lseek(self.rawValue, _COffT(offset), whence.rawValue))
}
}


Expand Down Expand Up @@ -163,7 +164,7 @@ extension FileDescriptor {
@usableFromInline
internal func _read(
into buffer: UnsafeMutableRawBufferPointer,
retryOnInterrupt: Bool = true
retryOnInterrupt: Bool
) throws -> Result<Int, Errno> {
valueOrErrno(retryOnInterrupt: retryOnInterrupt) {
system_read(self.rawValue, buffer.baseAddress, buffer.count)
Expand Down
11 changes: 9 additions & 2 deletions Sources/System/Util.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@

// Results in errno if i == -1
// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *)
internal func valueOrErrno<I: FixedWidthInteger>(
private func valueOrErrno<I: FixedWidthInteger>(
_ i: I
) -> Result<I, Errno> {
i == -1 ? .failure(Errno.current) : .success(i)
}

// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *)
internal func nothingOrErrno<I: FixedWidthInteger>(
private func nothingOrErrno<I: FixedWidthInteger>(
_ i: I
) -> Result<(), Errno> {
valueOrErrno(i).map { _ in () }
Expand All @@ -36,6 +36,13 @@ internal func valueOrErrno<I: FixedWidthInteger>(
} while true
}

// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *)
internal func nothingOrErrno<I: FixedWidthInteger>(
retryOnInterrupt: Bool, _ f: () -> I
) -> Result<(), Errno> {
valueOrErrno(retryOnInterrupt: retryOnInterrupt, f).map { _ in () }
}

// Run a precondition for debug client builds
internal func _debugPrecondition(
_ condition: @autoclosure () -> Bool,
Expand Down
22 changes: 11 additions & 11 deletions Tests/SystemTests/FileOperationsTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,55 +29,55 @@ final class FileOperationsTest: XCTestCase {
let writeBufAddr = writeBuf.baseAddress

let syscallTestCases: Array<MockTestCase> = [
MockTestCase(name: "open", "a path", O_RDWR | O_APPEND, interruptable: true) {
MockTestCase(name: "open", .interruptable, "a path", O_RDWR | O_APPEND) {
retryOnInterrupt in
_ = try FileDescriptor.open(
"a path", .readWrite, options: [.append], retryOnInterrupt: retryOnInterrupt)
},

MockTestCase(name: "open", "a path", O_WRONLY | O_CREAT | O_APPEND, 0o777, interruptable: true) {
MockTestCase(name: "open", .interruptable, "a path", O_WRONLY | O_CREAT | O_APPEND, 0o777) {
retryOnInterrupt in
_ = try FileDescriptor.open(
"a path", .writeOnly, options: [.create, .append],
permissions: [.groupReadWriteExecute, .ownerReadWriteExecute, .otherReadWriteExecute],
retryOnInterrupt: retryOnInterrupt)
},

MockTestCase(name: "read", rawFD, bufAddr, bufCount, interruptable: true) {
MockTestCase(name: "read", .interruptable, rawFD, bufAddr, bufCount) {
retryOnInterrupt in
_ = try fd.read(into: rawBuf, retryOnInterrupt: retryOnInterrupt)
},

MockTestCase(name: "pread", rawFD, bufAddr, bufCount, 5, interruptable: true) {
MockTestCase(name: "pread", .interruptable, rawFD, bufAddr, bufCount, 5) {
retryOnInterrupt in
_ = try fd.read(fromAbsoluteOffset: 5, into: rawBuf, retryOnInterrupt: retryOnInterrupt)
},

MockTestCase(name: "lseek", rawFD, -2, SEEK_END, interruptable: false) {
MockTestCase(name: "lseek", .noInterrupt, rawFD, -2, SEEK_END) {
_ in
_ = try fd.seek(offset: -2, from: .end)
},

MockTestCase(name: "write", rawFD, writeBufAddr, bufCount, interruptable: true) {
MockTestCase(name: "write", .interruptable, rawFD, writeBufAddr, bufCount) {
retryOnInterrupt in
_ = try fd.write(writeBuf, retryOnInterrupt: retryOnInterrupt)
},

MockTestCase(name: "pwrite", rawFD, writeBufAddr, bufCount, 7, interruptable: true) {
MockTestCase(name: "pwrite", .interruptable, rawFD, writeBufAddr, bufCount, 7) {
retryOnInterrupt in
_ = try fd.write(toAbsoluteOffset: 7, writeBuf, retryOnInterrupt: retryOnInterrupt)
},

MockTestCase(name: "close", rawFD, interruptable: false) {
MockTestCase(name: "close", .noInterrupt, rawFD) {
_ in
_ = try fd.close()
},

MockTestCase(name: "dup", rawFD, interruptable: true) { retryOnInterrupt in
MockTestCase(name: "dup", .interruptable, rawFD) { retryOnInterrupt in
_ = try fd.duplicate(retryOnInterrupt: retryOnInterrupt)
},

MockTestCase(name: "dup2", rawFD, 42, interruptable: true) { retryOnInterrupt in
MockTestCase(name: "dup2", .interruptable, rawFD, 42) { retryOnInterrupt in
_ = try fd.duplicate(as: FileDescriptor(rawValue: 42),
retryOnInterrupt: retryOnInterrupt)
},
Expand Down Expand Up @@ -128,7 +128,7 @@ final class FileOperationsTest: XCTestCase {
func testGithubIssues() {
// https://github.com/apple/swift-system/issues/26
let issue26 = MockTestCase(
name: "open", "a path", O_WRONLY | O_CREAT, 0o020, interruptable: true
name: "open", .interruptable, "a path", O_WRONLY | O_CREAT, 0o020
) {
retryOnInterrupt in
_ = try FileDescriptor.open(
Expand Down
34 changes: 30 additions & 4 deletions Tests/SystemTests/TestingInfrastructure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,22 +107,35 @@ internal struct MockTestCase: TestCase {
var line: UInt

var expected: Trace.Entry
var interruptable: Bool
var interruptBehavior: InterruptBehavior

var interruptable: Bool { return interruptBehavior == .interruptable }

internal enum InterruptBehavior {
// Retry the syscall on EINTR
case interruptable

// Cannot return EINTR
case noInterrupt

// Cannot error at all
case noError
}

var body: (_ retryOnInterrupt: Bool) throws -> ()

init(
_ file: StaticString = #file,
_ line: UInt = #line,
name: String,
_ interruptable: InterruptBehavior,
_ args: AnyHashable...,
interruptable: Bool,
_ body: @escaping (_ retryOnInterrupt: Bool) throws -> ()
body: @escaping (_ retryOnInterrupt: Bool) throws -> ()
) {
self.file = file
self.line = line
self.expected = Trace.Entry(name: name, args)
self.interruptable = interruptable
self.interruptBehavior = interruptable
self.body = body
}

Expand All @@ -141,6 +154,19 @@ internal struct MockTestCase: TestCase {
self.fail()
}

// Non-error-ing syscalls shouldn't ever throw
guard interruptBehavior != .noError else {
do {
try body(interruptable)
self.expectEqual(self.expected, mocking.trace.dequeue())
try body(!interruptable)
self.expectEqual(self.expected, mocking.trace.dequeue())
} catch {
self.fail()
}
return
}

// Test interupt behavior. Interruptable calls will be told not to
// retry to catch the EINTR. Non-interruptable calls will be told to
// retry, to make sure they don't spin (e.g. if API changes to include
Expand Down