Coverage Report

Created: 2026-04-29 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}