/src/swift-nio/Sources/NIOCore/IO.swift
Line | Count | Source |
1 | | //===----------------------------------------------------------------------===// |
2 | | // |
3 | | // This source file is part of the SwiftNIO open source project |
4 | | // |
5 | | // Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors |
6 | | // Licensed under Apache License v2.0 |
7 | | // |
8 | | // See LICENSE.txt for license information |
9 | | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors |
10 | | // |
11 | | // SPDX-License-Identifier: Apache-2.0 |
12 | | // |
13 | | //===----------------------------------------------------------------------===// |
14 | | |
15 | | #if os(Windows) |
16 | | import ucrt |
17 | | import func WinSDK.FormatMessageW |
18 | | import func WinSDK.LocalFree |
19 | | import let WinSDK.FORMAT_MESSAGE_ALLOCATE_BUFFER |
20 | | import let WinSDK.FORMAT_MESSAGE_FROM_SYSTEM |
21 | | import let WinSDK.FORMAT_MESSAGE_IGNORE_INSERTS |
22 | | import let WinSDK.LANG_NEUTRAL |
23 | | import let WinSDK.SUBLANG_DEFAULT |
24 | | import typealias WinSDK.DWORD |
25 | | import typealias WinSDK.WCHAR |
26 | | import typealias WinSDK.WORD |
27 | | |
28 | | internal func MAKELANGID(_ p: WORD, _ s: WORD) -> DWORD { |
29 | | DWORD((s << 10) | p) |
30 | | } |
31 | | #elseif canImport(Glibc) |
32 | | @preconcurrency import Glibc |
33 | | #elseif canImport(Musl) |
34 | | @preconcurrency import Musl |
35 | | #elseif canImport(Bionic) |
36 | | @preconcurrency import Bionic |
37 | | #elseif canImport(WASILibc) |
38 | | @preconcurrency import WASILibc |
39 | | #elseif canImport(Darwin) |
40 | | import Darwin |
41 | | #else |
42 | | #error("The IO module was unable to identify your C library.") |
43 | | #endif |
44 | | |
45 | | /// An `Error` for an IO operation. |
46 | | public struct IOError: Swift.Error { |
47 | | @available(*, deprecated, message: "NIO no longer uses FailureDescription.") |
48 | | public enum FailureDescription: Sendable { |
49 | | case function(StaticString) |
50 | | case reason(String) |
51 | | } |
52 | | |
53 | | /// The actual reason (in an human-readable form) for this `IOError`. |
54 | | private var failureDescription: String |
55 | | |
56 | | @available( |
57 | | *, |
58 | | deprecated, |
59 | | message: "NIO no longer uses FailureDescription, use IOError.description for a human-readable error description" |
60 | | ) |
61 | 0 | public var reason: FailureDescription { |
62 | 0 | .reason(self.failureDescription) |
63 | 0 | } |
64 | | |
65 | | private enum Error { |
66 | | #if os(Windows) |
67 | | case windows(DWORD) |
68 | | case winsock(CInt) |
69 | | #endif |
70 | | case errno(CInt) |
71 | | } |
72 | | |
73 | | private let error: Error |
74 | | |
75 | | /// The `errno` that was set for the operation. |
76 | 0 | public var errnoCode: CInt { |
77 | 0 | switch self.error { |
78 | 0 | case .errno(let code): |
79 | 0 | return code |
80 | | #if os(Windows) |
81 | | default: |
82 | | fatalError("IOError domain is not `errno`") |
83 | | #endif |
84 | 0 | } |
85 | 0 | } |
86 | | |
87 | | #if os(Windows) |
88 | | public init(windows code: DWORD, reason: String) { |
89 | | self.error = .windows(code) |
90 | | self.failureDescription = reason |
91 | | } |
92 | | |
93 | | public init(winsock code: CInt, reason: String) { |
94 | | self.error = .winsock(code) |
95 | | self.failureDescription = reason |
96 | | } |
97 | | #endif |
98 | | |
99 | | /// Creates a new `IOError`` |
100 | | /// |
101 | | /// - Parameters: |
102 | | /// - errnoCode: the `errno` that was set for the operation. |
103 | | /// - reason: the actual reason (in an human-readable form). |
104 | 0 | public init(errnoCode: CInt, reason: String) { |
105 | 0 | self.error = .errno(errnoCode) |
106 | 0 | self.failureDescription = reason |
107 | 0 | } |
108 | | |
109 | | /// Creates a new `IOError`` |
110 | | /// |
111 | | /// - Parameters: |
112 | | /// - errnoCode: the `errno` that was set for the operation. |
113 | | /// - function: The function the error happened in, the human readable description will be generated automatically when needed. |
114 | | @available(*, deprecated, renamed: "init(errnoCode:reason:)") |
115 | 0 | public init(errnoCode: CInt, function: StaticString) { |
116 | 0 | self.error = .errno(errnoCode) |
117 | 0 | self.failureDescription = "\(function)" |
118 | 0 | } |
119 | | } |
120 | | |
121 | | /// Returns a reason to use when constructing a `IOError`. |
122 | | /// |
123 | | /// - Parameters: |
124 | | /// - errnoCode: the `errno` that was set for the operation. |
125 | | /// - reason: what failed |
126 | | /// - Returns: the constructed reason. |
127 | 0 | private func reasonForError(errnoCode: CInt, reason: String) -> String { |
128 | | #if os(Windows) |
129 | | let errorDesc = Windows.strerror(errnoCode) |
130 | | #else |
131 | 0 | let errorDesc = strerror(errnoCode).flatMap { String(cString: $0) } |
132 | | #endif |
133 | 0 | if let errorDesc { |
134 | 0 | return "\(reason): \(errorDesc)) (errno: \(errnoCode))" |
135 | 0 | } else { |
136 | 0 | return "\(reason): Broken strerror, unknown error: \(errnoCode)" |
137 | 0 | } |
138 | 0 | } |
139 | | |
140 | | #if os(Windows) |
141 | | private func reasonForWinError(_ code: DWORD) -> String { |
142 | | let dwFlags: DWORD = |
143 | | DWORD(FORMAT_MESSAGE_ALLOCATE_BUFFER) |
144 | | | DWORD(FORMAT_MESSAGE_FROM_SYSTEM) |
145 | | | DWORD(FORMAT_MESSAGE_IGNORE_INSERTS) |
146 | | |
147 | | var buffer: UnsafeMutablePointer<WCHAR>? |
148 | | // We use `FORMAT_MESSAGE_ALLOCATE_BUFFER` in flags which means that the |
149 | | // buffer will be allocated by the call to `FormatMessageW`. The function |
150 | | // expects a `LPWSTR` and expects the user to type-pun in this case. |
151 | | let dwResult: DWORD = withUnsafeMutablePointer(to: &buffer) { |
152 | | $0.withMemoryRebound(to: WCHAR.self, capacity: 2) { |
153 | | FormatMessageW( |
154 | | dwFlags, |
155 | | nil, |
156 | | code, |
157 | | MAKELANGID(WORD(LANG_NEUTRAL), WORD(SUBLANG_DEFAULT)), |
158 | | $0, |
159 | | 0, |
160 | | nil |
161 | | ) |
162 | | } |
163 | | } |
164 | | guard dwResult > 0, let message = buffer else { |
165 | | return "unknown error \(code)" |
166 | | } |
167 | | defer { LocalFree(buffer) } |
168 | | return String(decodingCString: message, as: UTF16.self) |
169 | | } |
170 | | #endif |
171 | | |
172 | | extension IOError: CustomStringConvertible { |
173 | 0 | public var description: String { |
174 | 0 | self.localizedDescription |
175 | 0 | } |
176 | | |
177 | 0 | public var localizedDescription: String { |
178 | | #if os(Windows) |
179 | | switch self.error { |
180 | | case .errno(let errno): |
181 | | return reasonForError(errnoCode: errno, reason: self.failureDescription) |
182 | | case .windows(let code): |
183 | | return reasonForWinError(code) |
184 | | case .winsock(let code): |
185 | | return reasonForWinError(DWORD(code)) |
186 | | } |
187 | | #else |
188 | 0 | return reasonForError(errnoCode: self.errnoCode, reason: self.failureDescription) |
189 | | #endif |
190 | 0 | } |
191 | | } |
192 | | |
193 | | // FIXME: Duplicated with NIO. |
194 | | /// An result for an IO operation that was done on a non-blocking resource. |
195 | | enum CoreIOResult<T: Equatable>: Equatable { |
196 | | |
197 | | /// Signals that the IO operation could not be completed as otherwise we would need to block. |
198 | | case wouldBlock(T) |
199 | | |
200 | | /// Signals that the IO operation was completed. |
201 | | case processed(T) |
202 | | } |
203 | | |
204 | | extension CoreIOResult where T: FixedWidthInteger { |
205 | 0 | var result: T { |
206 | 0 | switch self { |
207 | 0 | case .processed(let value): |
208 | 0 | return value |
209 | 0 | case .wouldBlock(_): |
210 | 0 | fatalError("cannot unwrap CoreIOResult") |
211 | 0 | } |
212 | 0 | } |
213 | | } |