Coverage Report

Created: 2026-10-28 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/swift-nio/Sources/NIOCore/Utilities.swift
Line
Count
Source
1
//===----------------------------------------------------------------------===//
2
//
3
// This source file is part of the SwiftNIO open source project
4
//
5
// Copyright (c) 2021-2023 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
#if os(Linux) || os(FreeBSD) || os(Android)
15
import CNIOLinux
16
#if canImport(Glibc)
17
@preconcurrency import Glibc
18
#elseif canImport(Musl)
19
@preconcurrency import Musl
20
#elseif canImport(Android)
21
@preconcurrency import Android
22
#endif
23
#elseif os(OpenBSD)
24
import CNIOOpenBSD
25
@preconcurrency import Glibc
26
#elseif os(Windows)
27
import ucrt
28
import let WinSDK.RelationProcessorCore
29
30
import let WinSDK.AF_UNSPEC
31
import let WinSDK.ERROR_BUFFER_OVERFLOW
32
import let WinSDK.ERROR_SUCCESS
33
34
import func WinSDK.GetAdaptersAddresses
35
import func WinSDK.GetLastError
36
import func WinSDK.GetLogicalProcessorInformation
37
38
import struct WinSDK.IP_ADAPTER_ADDRESSES
39
import struct WinSDK.IP_ADAPTER_UNICAST_ADDRESS
40
import struct WinSDK.SYSTEM_LOGICAL_PROCESSOR_INFORMATION
41
import struct WinSDK.ULONG
42
43
import typealias WinSDK.DWORD
44
#elseif canImport(Darwin)
45
import Darwin
46
#elseif canImport(WASILibc)
47
@preconcurrency import WASILibc
48
#else
49
#error("The Core utilities module was unable to identify your C library.")
50
#endif
51
52
/// A utility function that runs the body code only in debug builds, without
53
/// emitting compiler warnings.
54
///
55
/// This is currently the only way to do this in Swift: see
56
/// https://forums.swift.org/t/support-debug-only-code/11037 for a discussion.
57
@inlinable
58
6.51G
internal func debugOnly(_ body: () -> Void) {
59
6.51G
    // FIXME: duplicated with NIO.
60
6.51G
    assert(
61
6.51G
        {
62
2.08M
            body()
63
2.08M
            return true
64
2.08M
        }()
$s7NIOCore9debugOnlyyyyyXEFSbyXEfu_
Line
Count
Source
61
2.08M
        {
62
2.08M
            body()
63
2.08M
            return true
64
2.08M
        }()
$s7NIOCore9debugOnlyyyyyXEFSbyXEfu_SbyXEfU_
Line
Count
Source
61
2.08M
        {
62
2.08M
            body()
63
2.08M
            return true
64
2.08M
        }()
65
6.51G
    )
66
6.51G
}
67
68
/// Allows to "box" another value.
69
final class Box<T> {
70
    // FIXME: Duplicated with NIO.
71
    let value: T
72
0
    init(_ value: T) { self.value = value }
73
}
74
75
extension Box: Sendable where T: Sendable {}
76
77
public enum System: Sendable {
78
    /// A utility function that returns an estimate of the number of *logical* cores
79
    /// on the system available for use.
80
    ///
81
    /// This value can be used to help provide an estimate of how many threads to use with
82
    /// the `MultiThreadedEventLoopGroup`. The exact ratio between this number and the number
83
    /// of threads to use is a matter for the programmer, and can be determined based on the
84
    /// specific execution behaviour of the program.
85
    ///
86
    /// On Linux the value returned will take account of cgroup or cpuset restrictions.
87
    /// The result will be rounded up to the nearest whole number where fractional CPUs have been assigned.
88
    ///
89
    /// - Returns: The logical core count on the system.
90
0
    public static var coreCount: Int {
91
        #if os(Windows)
92
        var dwLength: DWORD = 0
93
        _ = GetLogicalProcessorInformation(nil, &dwLength)
94
95
        let alignment: Int =
96
            MemoryLayout<SYSTEM_LOGICAL_PROCESSOR_INFORMATION>.alignment
97
        let pBuffer: UnsafeMutableRawPointer =
98
            UnsafeMutableRawPointer.allocate(
99
                byteCount: Int(dwLength),
100
                alignment: alignment
101
            )
102
        defer {
103
            pBuffer.deallocate()
104
        }
105
106
        let dwSLPICount: Int =
107
            Int(dwLength) / MemoryLayout<SYSTEM_LOGICAL_PROCESSOR_INFORMATION>.stride
108
        let pSLPI: UnsafeMutablePointer<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> =
109
            pBuffer.bindMemory(
110
                to: SYSTEM_LOGICAL_PROCESSOR_INFORMATION.self,
111
                capacity: dwSLPICount
112
            )
113
114
        let bResult: Bool = GetLogicalProcessorInformation(pSLPI, &dwLength)
115
        precondition(bResult, "GetLogicalProcessorInformation: \(GetLastError())")
116
117
        return UnsafeBufferPointer<SYSTEM_LOGICAL_PROCESSOR_INFORMATION>(
118
            start: pSLPI,
119
            count: dwSLPICount
120
        )
121
        .filter { $0.Relationship == RelationProcessorCore }
122
        .map { $0.ProcessorMask.nonzeroBitCount }
123
        .reduce(0, +)
124
        #elseif os(Linux) || os(Android)
125
0
        var cpuSetPath: String?
126
0
127
0
        switch Linux.cgroupVersion {
128
0
        case .v1:
129
0
            if let quota = Linux.coreCountCgroup1Restriction() {
130
0
                return quota
131
0
            }
132
0
            cpuSetPath = Linux.cpuSetPathV1
133
0
        case .v2:
134
0
            if let quota = Linux.coreCountCgroup2Restriction() {
135
0
                return quota
136
0
            }
137
0
            cpuSetPath = Linux.cpuSetPathV2
138
0
        case .none:
139
0
            break
140
0
        }
141
0
142
0
        if let cpuSetPath,
143
0
            let cpusetCount = Linux.coreCount(cpuset: cpuSetPath)
144
0
        {
145
0
            return cpusetCount
146
0
        } else {
147
0
            return sysconf(CInt(_SC_NPROCESSORS_ONLN))
148
0
        }
149
        #else
150
        return sysconf(CInt(_SC_NPROCESSORS_ONLN))
151
        #endif
152
0
    }
153
154
    #if !os(Windows) && !os(WASI)
155
    /// A utility function that enumerates the available network interfaces on this machine.
156
    ///
157
    /// This function returns values that are true for a brief snapshot in time. These results can
158
    /// change, and the returned values will not change to reflect them. This function must be called
159
    /// again to get new results.
160
    ///
161
    /// - Returns: An array of network interfaces available on this machine.
162
    /// - Throws: If an error is encountered while enumerating interfaces.
163
    @available(*, deprecated, renamed: "enumerateDevices")
164
    #if compiler(>=6.3)
165
    @available(Android 24, *)
166
    #endif
167
0
    public static func enumerateInterfaces() throws -> [NIONetworkInterface] {
168
0
        var interfaces: [NIONetworkInterface] = []
169
0
        interfaces.reserveCapacity(12)  // Arbitrary choice.
170
0
171
0
        var interface: UnsafeMutablePointer<ifaddrs>? = nil
172
0
        try SystemCalls.getifaddrs(&interface)
173
0
        let originalInterface = interface
174
0
        defer {
175
0
            freeifaddrs(originalInterface)
176
0
        }
177
0
178
0
        while let concreteInterface = interface {
179
0
            if let nioInterface = NIONetworkInterface(concreteInterface.pointee) {
180
0
                interfaces.append(nioInterface)
181
0
            }
182
0
            interface = concreteInterface.pointee.ifa_next
183
0
        }
184
0
185
0
        return interfaces
186
0
    }
187
    #endif
188
189
    /// A utility function that enumerates the available network devices on this machine.
190
    ///
191
    /// This function returns values that are true for a brief snapshot in time. These results can
192
    /// change, and the returned values will not change to reflect them. This function must be called
193
    /// again to get new results.
194
    ///
195
    /// - Returns: An array of network devices available on this machine.
196
    /// - Throws: If an error is encountered while enumerating interfaces.
197
    #if compiler(>=6.3)
198
    @available(Android 24, *)
199
    #endif
200
0
    public static func enumerateDevices() throws -> [NIONetworkDevice] {
201
0
        var devices: [NIONetworkDevice] = []
202
0
        devices.reserveCapacity(12)  // Arbitrary choice.
203
0
204
        #if os(Windows)
205
        // `GetAdaptersAddresses` writes a linked list of variably sized records into the
206
        // caller's buffer, so the buffer must be sized in *bytes*, not in whole
207
        // `IP_ADAPTER_ADDRESSES` records. 15KB is the initial allocation recommended by the
208
        // documentation; if that is not enough (or if the adapter information grows between
209
        // two calls) the call reports `ERROR_BUFFER_OVERFLOW` and updates `ulSize` with the
210
        // size now required, so the enumeration is retried a small number of times.
211
        var ulSize: ULONG = 15 * 1024
212
        let ulBufferOverflow: ULONG = ULONG(ERROR_BUFFER_OVERFLOW)
213
        var ulResult: ULONG = ulBufferOverflow
214
        var attemptsRemaining: Int = 3
215
216
        while ulResult == ulBufferOverflow && attemptsRemaining > 0 {
217
            attemptsRemaining -= 1
218
219
            let pBuffer: UnsafeMutableRawPointer =
220
                UnsafeMutableRawPointer.allocate(
221
                    byteCount: Int(ulSize),
222
                    alignment: MemoryLayout<IP_ADAPTER_ADDRESSES>.alignment
223
                )
224
            defer {
225
                pBuffer.deallocate()
226
            }
227
228
            let pAdapters: UnsafeMutablePointer<IP_ADAPTER_ADDRESSES> =
229
                pBuffer.bindMemory(to: IP_ADAPTER_ADDRESSES.self, capacity: 1)
230
231
            ulResult = GetAdaptersAddresses(ULONG(AF_UNSPEC), 0, nil, pAdapters, &ulSize)
232
            guard ulResult == ERROR_SUCCESS else {
233
                continue
234
            }
235
236
            var pAdapter: UnsafeMutablePointer<IP_ADAPTER_ADDRESSES>? = pAdapters
237
            while let adapter = pAdapter {
238
                var pUnicastAddress: UnsafeMutablePointer<IP_ADAPTER_UNICAST_ADDRESS>? =
239
                    adapter.pointee.FirstUnicastAddress
240
                while let unicastAddress = pUnicastAddress {
241
                    if let device = NIONetworkDevice(adapter, unicastAddress) {
242
                        devices.append(device)
243
                    }
244
                    pUnicastAddress = unicastAddress.pointee.Next
245
                }
246
                pAdapter = adapter.pointee.Next
247
            }
248
        }
249
250
        guard ulResult == ERROR_SUCCESS else {
251
            throw IOError(windows: ulResult, reason: "GetAdaptersAddresses")
252
        }
253
        #elseif !os(WASI)
254
0
        var interface: UnsafeMutablePointer<ifaddrs>? = nil
255
0
        try SystemCalls.getifaddrs(&interface)
256
0
        let originalInterface = interface
257
0
        defer {
258
0
            freeifaddrs(originalInterface)
259
0
        }
260
0
261
0
        while let concreteInterface = interface {
262
0
            if let nioInterface = NIONetworkDevice(concreteInterface.pointee) {
263
0
                devices.append(nioInterface)
264
0
            }
265
0
            interface = concreteInterface.pointee.ifa_next
266
0
        }
267
0
268
        #endif
269
0
        return devices
270
0
    }
271
}
272
273
extension System {
274
    #if os(Linux)
275
    /// Returns true if the platform supports `UDP_SEGMENT` (GSO).
276
    ///
277
    /// The option can be enabled by setting the ``ChannelOptions/Types/DatagramSegmentSize`` channel option.
278
    public static let supportsUDPSegmentationOffload: Bool = CNIOLinux_supports_udp_segment()
279
    #else
280
    /// Returns true if the platform supports `UDP_SEGMENT` (GSO).
281
    ///
282
    /// The option can be enabled by setting the ``ChannelOptions/Types/DatagramSegmentSize`` channel option.
283
    public static let supportsUDPSegmentationOffload: Bool = false
284
    #endif
285
286
    #if os(Linux)
287
    /// Returns true if the platform supports `UDP_GRO`.
288
    ///
289
    /// The option can be enabled by setting the ``ChannelOptions/Types/DatagramReceiveOffload`` channel option.
290
    public static let supportsUDPReceiveOffload: Bool = CNIOLinux_supports_udp_gro()
291
    #else
292
    /// Returns true if the platform supports `UDP_GRO`.
293
    ///
294
    /// The option can be enabled by setting the ``ChannelOptions/Types/DatagramReceiveOffload`` channel option.
295
    public static let supportsUDPReceiveOffload: Bool = false
296
    #endif
297
298
    /// Returns `nil`.
299
    @available(*, deprecated, message: "UDP_MAX_SEGMENTS isn't exposed by the Linux kernel")
300
0
    public static var udpMaxSegments: Int? {
301
0
        nil
302
0
    }
303
}
304
305
#if os(Windows)
306
@usableFromInline
307
package enum Windows {
308
    @usableFromInline
309
    package static func strerror(_ errnoCode: CInt) -> String? {
310
        withUnsafeTemporaryAllocation(of: CChar.self, capacity: 256) { ptr in
311
            if strerror_s(ptr.baseAddress, ptr.count, errnoCode) == 0 {
312
                return String(cString: UnsafePointer(ptr.baseAddress!))
313
            }
314
            return nil
315
        }
316
    }
317
318
    package static func getenv(_ env: String) -> String? {
319
        var count = 0
320
        var ptr: UnsafeMutablePointer<CChar>? = nil
321
        withUnsafeMutablePointer(to: &ptr) { buffer in
322
            // according to docs only EINVAL and ENOMEM are possible here.
323
            _ = _dupenv_s(buffer, &count, env)
324
        }
325
        defer { if let ptr { free(ptr) } }
326
        if count > 0, let ptr {
327
            let buffer = UnsafeBufferPointer(start: ptr, count: count)
328
            return buffer.withMemoryRebound(to: UInt8.self) {
329
                String(decoding: $0, as: Unicode.UTF8.self)
330
            }
331
        } else {
332
            return nil
333
        }
334
    }
335
}
336
#endif