swift-nio/Sources/NIOCore/IO.swift

191 lines
6.0 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#if os(Windows)
import ucrt
import func WinSDK.FormatMessageW
import func WinSDK.LocalFree
import let WinSDK.FORMAT_MESSAGE_ALLOCATE_BUFFER
import let WinSDK.FORMAT_MESSAGE_FROM_SYSTEM
import let WinSDK.FORMAT_MESSAGE_IGNORE_INSERTS
import let WinSDK.LANG_NEUTRAL
import let WinSDK.SUBLANG_DEFAULT
import typealias WinSDK.DWORD
import typealias WinSDK.WCHAR
import typealias WinSDK.WORD
internal func MAKELANGID(_ p: WORD, _ s: WORD) -> DWORD {
return DWORD((s << 10) | p)
}
#elseif os(Linux) || os(Android)
import Glibc
#elseif os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import Darwin
#endif
/// An `Error` for an IO operation.
public struct IOError: Swift.Error {
@available(*, deprecated, message: "NIO no longer uses FailureDescription.")
public enum FailureDescription {
case function(StaticString)
case reason(String)
}
/// The actual reason (in an human-readable form) for this `IOError`.
private var failureDescription: String
@available(*, deprecated, message: "NIO no longer uses FailureDescription, use IOError.description for a human-readable error description")
public var reason: FailureDescription {
return .reason(self.failureDescription)
}
private enum Error {
#if os(Windows)
case windows(DWORD)
case winsock(CInt)
#endif
case errno(CInt)
}
private let error: Error
/// The `errno` that was set for the operation.
public var errnoCode: CInt {
switch self.error {
case .errno(let code):
return code
#if os(Windows)
default:
fatalError("IOError domain is not `errno`")
#endif
}
}
#if os(Windows)
public init(windows code: DWORD, reason: String) {
self.error = .windows(code)
self.failureDescription = reason
}
public init(winsock code: CInt, reason: String) {
self.error = .winsock(code)
self.failureDescription = reason
}
#endif
/// Creates a new `IOError``
///
/// - parameters:
/// - errorCode: the `errno` that was set for the operation.
/// - reason: the actual reason (in an human-readable form).
public init(errnoCode code: CInt, reason: String) {
self.error = .errno(code)
self.failureDescription = reason
}
/// Creates a new `IOError``
///
/// - parameters:
/// - errorCode: the `errno` that was set for the operation.
/// - function: The function the error happened in, the human readable description will be generated automatically when needed.
@available(*, deprecated, renamed: "init(errnoCode:reason:)")
public init(errnoCode code: CInt, function: StaticString) {
self.error = .errno(code)
self.failureDescription = "\(function)"
}
}
/// Returns a reason to use when constructing a `IOError`.
///
/// - parameters:
/// - errorCode: the `errno` that was set for the operation.
/// - reason: what failed
/// - returns: the constructed reason.
private func reasonForError(errnoCode: CInt, reason: String) -> String {
if let errorDescC = strerror(errnoCode) {
return "\(reason): \(String(cString: errorDescC)) (errno: \(errnoCode))"
} else {
return "\(reason): Broken strerror, unknown error: \(errnoCode)"
}
}
#if os(Windows)
private func reasonForWinError(_ code: DWORD) -> String {
let dwFlags: DWORD = DWORD(FORMAT_MESSAGE_ALLOCATE_BUFFER)
| DWORD(FORMAT_MESSAGE_FROM_SYSTEM)
| DWORD(FORMAT_MESSAGE_IGNORE_INSERTS)
var buffer: UnsafeMutablePointer<WCHAR>?
// We use `FORMAT_MESSAGE_ALLOCATE_BUFFER` in flags which means that the
// buffer will be allocated by the call to `FormatMessageW`. The function
// expects a `LPWSTR` and expects the user to type-pun in this case.
let dwResult: DWORD = withUnsafeMutablePointer(to: &buffer) {
$0.withMemoryRebound(to: WCHAR.self, capacity: 2) {
FormatMessageW(dwFlags, nil, code,
MAKELANGID(WORD(LANG_NEUTRAL), WORD(SUBLANG_DEFAULT)),
$0, 0, nil)
}
}
guard dwResult > 0, let message = buffer else {
return "unknown error \(code)"
}
defer { LocalFree(buffer) }
return String(decodingCString: message, as: UTF16.self)
}
#endif
extension IOError: CustomStringConvertible {
public var description: String {
return self.localizedDescription
}
public var localizedDescription: String {
#if os(Windows)
switch self.error {
case .errno(let errno):
return reasonForError(errnoCode: errno, reason: self.failureDescription)
case .windows(let code):
return reasonForWinError(code)
case .winsock(let code):
return reasonForWinError(DWORD(code))
}
#else
return reasonForError(errnoCode: self.errnoCode, reason: self.failureDescription)
#endif
}
}
// FIXME: Duplicated with NIO.
/// An result for an IO operation that was done on a non-blocking resource.
enum CoreIOResult<T: Equatable>: Equatable {
/// Signals that the IO operation could not be completed as otherwise we would need to block.
case wouldBlock(T)
/// Signals that the IO operation was completed.
case processed(T)
}
internal extension CoreIOResult where T: FixedWidthInteger {
var result: T {
switch self {
case .processed(let value):
return value
case .wouldBlock(_):
fatalError("cannot unwrap CoreIOResult")
}
}
}