|
1 | 1 | // This source file is part of the Swift.org open source project
|
2 | 2 | //
|
3 |
| -// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors |
| 3 | +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors |
4 | 4 | // Licensed under Apache License v2.0 with Runtime Library Exception
|
5 | 5 | //
|
6 | 6 | // See http://swift.org/LICENSE.txt for license information
|
7 | 7 | // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
8 | 8 | //
|
9 | 9 |
|
10 | 10 | @_implementationOnly import _CoreFoundation
|
11 |
| -#if os(Windows) |
12 |
| -import WinSDK |
13 |
| -#endif |
14 |
| - |
15 |
| -public struct OperatingSystemVersion { |
16 |
| - public var majorVersion: Int |
17 |
| - public var minorVersion: Int |
18 |
| - public var patchVersion: Int |
19 |
| - public init() { |
20 |
| - self.init(majorVersion: 0, minorVersion: 0, patchVersion: 0) |
21 |
| - } |
22 |
| - |
23 |
| - public init(majorVersion: Int, minorVersion: Int, patchVersion: Int) { |
24 |
| - self.majorVersion = majorVersion |
25 |
| - self.minorVersion = minorVersion |
26 |
| - self.patchVersion = patchVersion |
27 |
| - } |
28 |
| -} |
29 |
| - |
30 |
| - |
31 |
| - |
32 |
| -open class ProcessInfo: NSObject { |
33 |
| - |
34 |
| - public static let processInfo = ProcessInfo() |
35 |
| - |
36 |
| - internal override init() { |
37 |
| - |
38 |
| - } |
39 |
| - |
40 |
| - open var environment: [String : String] { |
41 |
| - let equalSign = Character("=") |
42 |
| - let strEncoding = String.defaultCStringEncoding |
43 |
| - let envp = _CFEnviron() |
44 |
| - var env: [String : String] = [:] |
45 |
| - var idx = 0 |
46 |
| - |
47 |
| - while let entry = envp.advanced(by: idx).pointee { |
48 |
| - if let entry = String(cString: entry, encoding: strEncoding), |
49 |
| - let i = entry.firstIndex(of: equalSign) { |
50 |
| - let key = String(entry.prefix(upTo: i)) |
51 |
| - let value = String(entry.suffix(from: i).dropFirst()) |
52 |
| - env[key] = value |
53 |
| - } |
54 |
| - idx += 1 |
55 |
| - } |
56 |
| - return env |
57 |
| - } |
58 |
| - |
59 |
| - open var arguments: [String] { |
60 |
| - return CommandLine.arguments // seems reasonable to flip the script here... |
61 |
| - } |
62 |
| - |
63 |
| - open var hostName: String { |
64 |
| - if let name = Host.current().name { |
65 |
| - return name |
66 |
| - } else { |
67 |
| - return "localhost" |
68 |
| - } |
69 |
| - } |
70 |
| - |
71 |
| - open var processName: String = _CFProcessNameString()._swiftObject |
72 |
| - |
73 |
| - open var processIdentifier: Int32 { |
74 |
| -#if os(Windows) |
75 |
| - return Int32(GetProcessId(GetCurrentProcess())) |
76 |
| -#else |
77 |
| - return Int32(getpid()) |
78 |
| -#endif |
79 |
| - } |
80 |
| - |
81 |
| - open var globallyUniqueString: String { |
82 |
| - let uuid = CFUUIDCreate(kCFAllocatorSystemDefault) |
83 |
| - return CFUUIDCreateString(kCFAllocatorSystemDefault, uuid)._swiftObject |
84 |
| - } |
85 |
| - |
86 |
| -#if os(Windows) |
87 |
| - internal var _rawOperatingSystemVersionInfo: RTL_OSVERSIONINFOEXW? { |
88 |
| - guard let ntdll = ("ntdll.dll".withCString(encodedAs: UTF16.self) { |
89 |
| - LoadLibraryExW($0, nil, DWORD(LOAD_LIBRARY_SEARCH_SYSTEM32)) |
90 |
| - }) else { |
91 |
| - return nil |
92 |
| - } |
93 |
| - defer { FreeLibrary(ntdll) } |
94 |
| - typealias RTLGetVersionTy = @convention(c) (UnsafeMutablePointer<RTL_OSVERSIONINFOEXW>) -> NTSTATUS |
95 |
| - guard let pfnRTLGetVersion = unsafeBitCast(GetProcAddress(ntdll, "RtlGetVersion"), to: Optional<RTLGetVersionTy>.self) else { |
96 |
| - return nil |
97 |
| - } |
98 |
| - var osVersionInfo = RTL_OSVERSIONINFOEXW() |
99 |
| - osVersionInfo.dwOSVersionInfoSize = DWORD(MemoryLayout<RTL_OSVERSIONINFOEXW>.size) |
100 |
| - guard pfnRTLGetVersion(&osVersionInfo) == 0 else { |
101 |
| - return nil |
102 |
| - } |
103 |
| - return osVersionInfo |
104 |
| - } |
105 |
| -#endif |
106 |
| - |
107 |
| - internal lazy var _operatingSystemVersionString: String = { |
108 |
| -#if canImport(Darwin) |
109 |
| - // Just use CoreFoundation on Darwin |
110 |
| - return CFCopySystemVersionString()?._swiftObject ?? "Darwin" |
111 |
| -#elseif os(Linux) |
112 |
| - // Try to parse a `PRETTY_NAME` out of `/etc/os-release`. |
113 |
| - if let osReleaseContents = try? String(contentsOf: URL(fileURLWithPath: "/etc/os-release", isDirectory: false)), |
114 |
| - let name = osReleaseContents.split(separator: "\n").first(where: { $0.hasPrefix("PRETTY_NAME=") }) |
115 |
| - { |
116 |
| - // This is extremely simplistic but manages to work for all known cases. |
117 |
| - return String(name.dropFirst("PRETTY_NAME=".count).trimmingCharacters(in: .init(charactersIn: "\""))) |
118 |
| - } |
119 |
| - |
120 |
| - // Okay, we can't get a distro name, so try for generic info. |
121 |
| - var versionString = "Linux" |
122 |
| - |
123 |
| - // Try to get a release version number from `uname -r`. |
124 |
| - var utsNameBuffer = utsname() |
125 |
| - if uname(&utsNameBuffer) == 0 { |
126 |
| - let release = withUnsafePointer(to: &utsNameBuffer.release.0) { String(cString: $0) } |
127 |
| - if !release.isEmpty { |
128 |
| - versionString += " \(release)" |
129 |
| - } |
130 |
| - } |
131 |
| - |
132 |
| - return versionString |
133 |
| -#elseif os(Windows) |
134 |
| - var versionString = "Windows" |
135 |
| - |
136 |
| - guard let osVersionInfo = self._rawOperatingSystemVersionInfo else { |
137 |
| - return versionString |
138 |
| - } |
139 |
| - |
140 |
| - // Windows has no canonical way to turn the fairly complex `RTL_OSVERSIONINFOW` version info into a string. We |
141 |
| - // do our best here to construct something consistent. Unfortunately, to provide a useful result, this requires |
142 |
| - // hardcoding several of the somewhat ambiguous values in the table provided here: |
143 |
| - // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfoexw#remarks |
144 |
| - switch (osVersionInfo.dwMajorVersion, osVersionInfo.dwMinorVersion) { |
145 |
| - case (5, 0): versionString += " 2000" |
146 |
| - case (5, 1): versionString += " XP" |
147 |
| - case (5, 2) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += " XP Professional x64" |
148 |
| - case (5, 2) where osVersionInfo.wSuiteMask == VER_SUITE_WH_SERVER: versionString += " Home Server" |
149 |
| - case (5, 2): versionString += " Server 2003" |
150 |
| - case (6, 0) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += " Vista" |
151 |
| - case (6, 0): versionString += " Server 2008" |
152 |
| - case (6, 1) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += " 7" |
153 |
| - case (6, 1): versionString += " Server 2008 R2" |
154 |
| - case (6, 2) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += " 8" |
155 |
| - case (6, 2): versionString += " Server 2012" |
156 |
| - case (6, 3) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += " 8.1" |
157 |
| - case (6, 3): versionString += " Server 2012 R2" // We assume the "10,0" numbers in the table for this are a typo |
158 |
| - case (10, 0) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += " 10" |
159 |
| - case (10, 0): versionString += " Server 2019" // The table gives identical values for 2016 and 2019, so we just assume 2019 here |
160 |
| - case let (maj, min): versionString += " \(maj).\(min)" // If all else fails, just give the raw version number |
161 |
| - } |
162 |
| - versionString += " (build \(osVersionInfo.dwBuildNumber))" |
163 |
| - // For now we ignore the `szCSDVersion`, `wServicePackMajor`, and `wServicePackMinor` values. |
164 |
| - return versionString |
165 |
| -#elseif os(FreeBSD) |
166 |
| - // Try to get a release version from `uname -r`. |
167 |
| - var versionString = "FreeBSD" |
168 |
| - var utsNameBuffer = utsname() |
169 |
| - if uname(&utsNameBuffer) == 0 { |
170 |
| - let release = withUnsafePointer(to: &utsNameBuffer.release.0) { String(cString: $0) } |
171 |
| - if !release.isEmpty { |
172 |
| - versionString += " \(release)" |
173 |
| - } |
174 |
| - } |
175 |
| - return versionString |
176 |
| -#elseif os(OpenBSD) |
177 |
| - // TODO: `uname -r` probably works here too. |
178 |
| - return "OpenBSD" |
179 |
| -#elseif os(Android) |
180 |
| - /// In theory, we need to do something like this: |
181 |
| - /// |
182 |
| - /// var versionString = "Android" |
183 |
| - /// let property = String(unsafeUninitializedCapacity: PROP_VALUE_MAX) { buf in |
184 |
| - /// __system_property_get("ro.build.description", buf.baseAddress!) |
185 |
| - /// } |
186 |
| - /// if !property.isEmpty { |
187 |
| - /// versionString += " \(property)" |
188 |
| - /// } |
189 |
| - /// return versionString |
190 |
| - return "Android" |
191 |
| -#elseif os(PS4) |
192 |
| - return "PS4" |
193 |
| -#elseif os(Cygwin) |
194 |
| - // TODO: `uname -r` probably works here too. |
195 |
| - return "Cygwin" |
196 |
| -#elseif os(Haiku) |
197 |
| - return "Haiku" |
198 |
| -#elseif os(WASI) |
199 |
| - return "WASI" |
200 |
| -#else |
201 |
| - // On other systems at least return something. |
202 |
| - return "Unknown" |
203 |
| -#endif |
204 |
| - }() |
205 |
| - open var operatingSystemVersionString: String { return _operatingSystemVersionString } |
206 |
| - |
207 |
| - open var operatingSystemVersion: OperatingSystemVersion { |
208 |
| - // The following fallback values match Darwin Foundation |
209 |
| - let fallbackMajor = -1 |
210 |
| - let fallbackMinor = 0 |
211 |
| - let fallbackPatch = 0 |
212 |
| - let versionString: String |
213 |
| - |
214 |
| -#if canImport(Darwin) |
215 |
| - guard let systemVersionDictionary = _CFCopySystemVersionDictionary() else { |
216 |
| - return OperatingSystemVersion(majorVersion: fallbackMajor, minorVersion: fallbackMinor, patchVersion: fallbackPatch) |
217 |
| - } |
218 |
| - |
219 |
| - let productVersionKey = unsafeBitCast(_kCFSystemVersionProductVersionKey, to: UnsafeRawPointer.self) |
220 |
| - guard let productVersion = unsafeBitCast(CFDictionaryGetValue(systemVersionDictionary, productVersionKey), to: NSString?.self) else { |
221 |
| - return OperatingSystemVersion(majorVersion: fallbackMajor, minorVersion: fallbackMinor, patchVersion: fallbackPatch) |
222 |
| - } |
223 |
| - versionString = productVersion._swiftObject |
224 |
| -#elseif os(Windows) |
225 |
| - guard let osVersionInfo = self._rawOperatingSystemVersionInfo else { |
226 |
| - return OperatingSystemVersion(majorVersion: fallbackMajor, minorVersion: fallbackMinor, patchVersion: fallbackPatch) |
227 |
| - } |
228 |
| - |
229 |
| - return OperatingSystemVersion( |
230 |
| - majorVersion: Int(osVersionInfo.dwMajorVersion), |
231 |
| - minorVersion: Int(osVersionInfo.dwMinorVersion), |
232 |
| - patchVersion: Int(osVersionInfo.dwBuildNumber) |
233 |
| - ) |
234 |
| -#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) |
235 |
| - var utsNameBuffer = utsname() |
236 |
| - guard uname(&utsNameBuffer) == 0 else { |
237 |
| - return OperatingSystemVersion(majorVersion: fallbackMajor, minorVersion: fallbackMinor, patchVersion: fallbackPatch) |
238 |
| - } |
239 |
| - let release = withUnsafePointer(to: &utsNameBuffer.release.0) { |
240 |
| - return String(cString: $0) |
241 |
| - } |
242 |
| - let idx = release.firstIndex(of: "-") ?? release.endIndex |
243 |
| - versionString = String(release[..<idx]) |
244 |
| -#else |
245 |
| - return OperatingSystemVersion(majorVersion: fallbackMajor, minorVersion: fallbackMinor, patchVersion: fallbackPatch) |
246 |
| -#endif |
247 |
| - let versionComponents = versionString.split(separator: ".").map(String.init).compactMap({ Int($0) }) |
248 |
| - let majorVersion = versionComponents.dropFirst(0).first ?? fallbackMajor |
249 |
| - let minorVersion = versionComponents.dropFirst(1).first ?? fallbackMinor |
250 |
| - let patchVersion = versionComponents.dropFirst(2).first ?? fallbackPatch |
251 |
| - return OperatingSystemVersion(majorVersion: majorVersion, minorVersion: minorVersion, patchVersion: patchVersion) |
252 |
| - } |
253 |
| - |
254 |
| - internal let _processorCount: Int = Int(__CFProcessorCount()) |
255 |
| - open var processorCount: Int { _processorCount } |
256 |
| - |
257 |
| -#if os(Linux) |
258 |
| - // coreCount takes into account cgroup information eg if running under Docker |
259 |
| - // __CFActiveProcessorCount uses sched_getaffinity() and sysconf(_SC_NPROCESSORS_ONLN) |
260 |
| - internal let _activeProcessorCount: Int = ProcessInfo.coreCount() ?? Int(__CFActiveProcessorCount()) |
261 |
| -#else |
262 |
| - internal let _activeProcessorCount: Int = Int(__CFActiveProcessorCount()) |
263 |
| -#endif |
264 |
| - |
265 |
| - open var activeProcessorCount: Int { _activeProcessorCount } |
266 |
| - |
267 |
| - internal let _physicalMemory = __CFMemorySize() |
268 |
| - open var physicalMemory: UInt64 { |
269 |
| - return _physicalMemory |
270 |
| - } |
271 |
| - |
272 |
| - open func isOperatingSystemAtLeast(_ version: OperatingSystemVersion) -> Bool { |
273 |
| - let ourVersion = operatingSystemVersion |
274 |
| - if ourVersion.majorVersion < version.majorVersion { |
275 |
| - return false |
276 |
| - } |
277 |
| - if ourVersion.majorVersion > version.majorVersion { |
278 |
| - return true |
279 |
| - } |
280 |
| - if ourVersion.minorVersion < version.minorVersion { |
281 |
| - return false |
282 |
| - } |
283 |
| - if ourVersion.minorVersion > version.minorVersion { |
284 |
| - return true |
285 |
| - } |
286 |
| - if ourVersion.patchVersion < version.patchVersion { |
287 |
| - return false |
288 |
| - } |
289 |
| - if ourVersion.patchVersion > version.patchVersion { |
290 |
| - return true |
291 |
| - } |
292 |
| - return true |
293 |
| - } |
294 |
| - |
295 |
| - open var systemUptime: TimeInterval { |
296 |
| - return CFGetSystemUptime() |
297 |
| - } |
298 |
| - |
299 |
| - open var userName: String { |
300 |
| - return NSUserName() |
301 |
| - } |
302 |
| - |
303 |
| - open var fullUserName: String { |
304 |
| - return NSFullUserName() |
305 |
| - } |
306 |
| - |
307 |
| - |
308 |
| -#if os(Linux) |
309 |
| - // Support for CFS quotas for cpu count as used by Docker. |
310 |
| - // Based on swift-nio code, https://github.com/apple/swift-nio/pull/1518 |
311 |
| - private static let cfsQuotaPath = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us" |
312 |
| - private static let cfsPeriodPath = "/sys/fs/cgroup/cpu/cpu.cfs_period_us" |
313 |
| - private static let cpuSetPath = "/sys/fs/cgroup/cpuset/cpuset.cpus" |
314 |
| - |
315 |
| - private static func firstLineOfFile(path: String) throws -> Substring { |
316 |
| - // TODO: Replace with URL version once that is available in FoundationEssentials |
317 |
| - let data = try Data(contentsOf: path) |
318 |
| - if let string = String(data: data, encoding: .utf8), let line = string.split(separator: "\n").first { |
319 |
| - return line |
320 |
| - } else { |
321 |
| - return "" |
322 |
| - } |
323 |
| - } |
324 |
| - |
325 |
| - // These are internal access for testing |
326 |
| - static func countCoreIds(cores: Substring) -> Int? { |
327 |
| - let ids = cores.split(separator: "-", maxSplits: 1) |
328 |
| - guard let first = ids.first.flatMap({ Int($0, radix: 10) }), |
329 |
| - let last = ids.last.flatMap({ Int($0, radix: 10) }), |
330 |
| - last >= first |
331 |
| - else { |
332 |
| - return nil |
333 |
| - } |
334 |
| - return 1 + last - first |
335 |
| - } |
336 |
| - |
337 |
| - static func coreCount(cpuset cpusetPath: String) -> Int? { |
338 |
| - guard let cpuset = try? firstLineOfFile(path: cpusetPath).split(separator: ","), |
339 |
| - !cpuset.isEmpty |
340 |
| - else { return nil } |
341 |
| - if let first = cpuset.first, let count = countCoreIds(cores: first) { |
342 |
| - return count |
343 |
| - } else { |
344 |
| - return nil |
345 |
| - } |
346 |
| - } |
347 |
| - |
348 |
| - static func coreCount(quota quotaPath: String, period periodPath: String) -> Int? { |
349 |
| - guard let quota = try? Int(firstLineOfFile(path: quotaPath)), |
350 |
| - quota > 0 |
351 |
| - else { return nil } |
352 |
| - guard let period = try? Int(firstLineOfFile(path: periodPath)), |
353 |
| - period > 0 |
354 |
| - else { return nil } |
355 |
| - |
356 |
| - return (quota - 1 + period) / period // always round up if fractional CPU quota requested |
357 |
| - } |
358 |
| - |
359 |
| - private static func coreCount() -> Int? { |
360 |
| - if let quota = coreCount(quota: cfsQuotaPath, period: cfsPeriodPath) { |
361 |
| - return quota |
362 |
| - } else if let cpusetCount = coreCount(cpuset: cpuSetPath) { |
363 |
| - return cpusetCount |
364 |
| - } else { |
365 |
| - return nil |
366 |
| - } |
367 |
| - } |
368 |
| -#endif |
369 |
| -} |
370 | 11 |
|
371 | 12 | // SPI for TestFoundation
|
372 | 13 | internal extension ProcessInfo {
|
|
0 commit comments