@@ -80,15 +80,126 @@ open class ProcessInfo: NSObject {
80
80
return CFUUIDCreateString ( kCFAllocatorSystemDefault, uuid) . _swiftObject
81
81
}
82
82
83
- open var operatingSystemVersionString : String {
84
- let fallback = " Unknown "
85
- #if os(Linux)
86
- let version = try ? String ( contentsOf: URL ( fileURLWithPath: " /proc/version_signature " , isDirectory: false ) , encoding: . utf8)
87
- return version ?? fallback
83
+ #if os(Windows)
84
+ internal var _rawOperatingSystemVersionInfo : RTL_OSVERSIONINFOEXW ? {
85
+ guard let ntdll = ( " ntdll.dll " . withCString ( encodedAs: UTF16 . self) {
86
+ LoadLibraryExW ( $0, nil , DWORD ( LOAD_LIBRARY_SEARCH_SYSTEM32) )
87
+ } ) else {
88
+ return nil
89
+ }
90
+ defer { FreeLibrary ( ntdll) }
91
+ typealias RTLGetVersionTy = @convention ( c) ( UnsafeMutablePointer < RTL_OSVERSIONINFOEXW > ) -> NTSTATUS
92
+ guard let pfnRTLGetVersion = unsafeBitCast ( GetProcAddress ( ntdll, " RtlGetVersion " ) , to: Optional< RTLGetVersionTy> . self ) else {
93
+ return nil
94
+ }
95
+ var osVersionInfo = RTL_OSVERSIONINFOEXW ( )
96
+ osVersionInfo. dwOSVersionInfoSize = DWORD ( MemoryLayout< RTL_OSVERSIONINFOEXW> . size)
97
+ guard pfnRTLGetVersion ( & osVersionInfo) == 0 else {
98
+ return nil
99
+ }
100
+ return osVersionInfo
101
+ }
102
+ #endif
103
+
104
+ internal lazy var _operatingSystemVersionString : String = {
105
+ #if canImport(Darwin)
106
+ // Just use CoreFoundation on Darwin
107
+ return CFCopySystemVersionString ( ) ? . _swiftObject ?? " Darwin "
108
+ #elseif os(Linux)
109
+ // Try to parse a `PRETTY_NAME` out of `/etc/os-release`.
110
+ if let osReleaseContents = try ? String ( contentsOf: URL ( fileURLWithPath: " /etc/os-release " , isDirectory: false ) ) ,
111
+ let name = osReleaseContents. split ( separator: " \n " ) . first ( where: { $0. hasPrefix ( " PRETTY_NAME= " ) } )
112
+ {
113
+ // This is extremely simplistic but manages to work for all known cases.
114
+ return String ( name. dropFirst ( " PRETTY_NAME= " . count) . trimmingCharacters ( in: . init( charactersIn: " \" " ) ) )
115
+ }
116
+
117
+ // Okay, we can't get a distro name, so try for generic info.
118
+ var versionString = " Linux "
119
+
120
+ // Try to get a release version number from `uname -r`.
121
+ var utsNameBuffer = utsname ( )
122
+ if uname ( & utsNameBuffer) == 0 {
123
+ let release = withUnsafePointer ( to: & utsNameBuffer. release. 0 ) { String ( cString: $0) }
124
+ if !release. isEmpty {
125
+ versionString += " \( release) "
126
+ }
127
+ }
128
+
129
+ return versionString
130
+ #elseif os(Windows)
131
+ var versionString = " Windows "
132
+
133
+ guard let osVersionInfo = self . _rawOperatingSystemVersionInfo else {
134
+ return versionString
135
+ }
136
+
137
+ // Windows has no canonical way to turn the fairly complex `RTL_OSVERSIONINFOW` version info into a string. We
138
+ // do our best here to construct something consistent. Unfortunately, to provide a useful result, this requires
139
+ // hardcoding several of the somewhat ambiguous values in the table provided here:
140
+ // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfoexw#remarks
141
+ switch ( osVersionInfo. dwMajorVersion, osVersionInfo. dwMinorVersion) {
142
+ case ( 5 , 0 ) : versionString += " 2000 "
143
+ case ( 5 , 1 ) : versionString += " XP "
144
+ case ( 5 , 2 ) where osVersionInfo. wProductType == VER_NT_WORKSTATION: versionString += " XP Professional x64 "
145
+ case ( 5 , 2 ) where osVersionInfo. wSuiteMask == VER_SUITE_WH_SERVER: versionString += " Home Server "
146
+ case ( 5 , 2 ) : versionString += " Server 2003 "
147
+ case ( 6 , 0 ) where osVersionInfo. wProductType == VER_NT_WORKSTATION: versionString += " Vista "
148
+ case ( 6 , 0 ) : versionString += " Server 2008 "
149
+ case ( 6 , 1 ) where osVersionInfo. wProductType == VER_NT_WORKSTATION: versionString += " 7 "
150
+ case ( 6 , 1 ) : versionString += " Server 2008 R2 "
151
+ case ( 6 , 2 ) where osVersionInfo. wProductType == VER_NT_WORKSTATION: versionString += " 8 "
152
+ case ( 6 , 2 ) : versionString += " Server 2012 "
153
+ case ( 6 , 3 ) where osVersionInfo. wProductType == VER_NT_WORKSTATION: versionString += " 8.1 "
154
+ case ( 6 , 3 ) : versionString += " Server 2012 R2 " // We assume the "10,0" numbers in the table for this are a typo
155
+ case ( 10 , 0 ) where osVersionInfo. wProductType == VER_NT_WORKSTATION: versionString += " 10 "
156
+ case ( 10 , 0 ) : versionString += " Server 2019 " // The table gives identical values for 2016 and 2019, so we just assume 2019 here
157
+ case let ( maj, min) : versionString += " \( maj) . \( min) " // If all else fails, just give the raw version number
158
+ }
159
+ versionString += " (build \( osVersionInfo. dwBuildNumber) ) "
160
+ // For now we ignore the `szCSDVersion`, `wServicePackMajor`, and `wServicePackMinor` values.
161
+ return versionString
162
+ #elseif os(FreeBSD)
163
+ // Try to get a release version from `uname -r`.
164
+ var versionString = " FreeBSD "
165
+ var utsNameBuffer = utsname ( )
166
+ if uname ( & utsNameBuffer) == 0 {
167
+ let release = withUnsafePointer ( to: & utsNameBuffer. release. 0 ) { String ( cString: $0) }
168
+ if !release. isEmpty {
169
+ versionString += " \( release) "
170
+ }
171
+ }
172
+ return versionString
173
+ #elseif os(OpenBSD)
174
+ // TODO: `uname -r` probably works here too.
175
+ return " OpenBSD "
176
+ #elseif os(Android)
177
+ /// In theory, we need to do something like this:
178
+ ///
179
+ /// var versionString = "Android"
180
+ /// let property = String(unsafeUninitializedCapacity: PROP_VALUE_MAX) { buf in
181
+ /// __system_property_get("ro.build.description", buf.baseAddress!)
182
+ /// }
183
+ /// if !property.isEmpty {
184
+ /// versionString += " \(property)"
185
+ /// }
186
+ /// return versionString
187
+ return " Android "
188
+ #elseif os(PS4)
189
+ return " PS4 "
190
+ #elseif os(Cygwin)
191
+ // TODO: `uname -r` probably works here too.
192
+ return " Cygwin "
193
+ #elseif os(Haiku)
194
+ return " Haiku "
195
+ #elseif os(WASI)
196
+ return " WASI "
88
197
#else
89
- return CFCopySystemVersionString ( ) ? . _swiftObject ?? fallback
198
+ // On other systems at least return something.
199
+ return " Unknown "
90
200
#endif
91
- }
201
+ } ( )
202
+ open var operatingSystemVersionString : String { return _operatingSystemVersionString }
92
203
93
204
open var operatingSystemVersion : OperatingSystemVersion {
94
205
// The following fallback values match Darwin Foundation
@@ -108,27 +219,16 @@ open class ProcessInfo: NSObject {
108
219
}
109
220
versionString = productVersion. _swiftObject
110
221
#elseif os(Windows)
111
- guard let ntdll = ( " ntdll.dll " . withCString ( encodedAs: UTF16 . self) {
112
- LoadLibraryExW ( $0, nil , DWORD ( LOAD_LIBRARY_SEARCH_SYSTEM32) )
113
- } ) else {
114
- return OperatingSystemVersion ( majorVersion: fallbackMajor, minorVersion: fallbackMinor, patchVersion: fallbackPatch)
115
- }
116
- defer { FreeLibrary ( ntdll) }
117
- typealias RTLGetVersionTy = @convention ( c) ( UnsafeMutablePointer < RTL_OSVERSIONINFOW > ) -> NTSTATUS
118
- guard let pfnRTLGetVersion = unsafeBitCast ( GetProcAddress ( ntdll, " RtlGetVersion " ) , to: Optional< RTLGetVersionTy> . self ) else {
119
- return OperatingSystemVersion ( majorVersion: fallbackMajor, minorVersion: fallbackMinor, patchVersion: fallbackPatch)
120
- }
121
- var osVersionInfo = RTL_OSVERSIONINFOW ( )
122
- osVersionInfo. dwOSVersionInfoSize = DWORD ( MemoryLayout< RTL_OSVERSIONINFOW> . size)
123
- guard pfnRTLGetVersion ( & osVersionInfo) == 0 else {
222
+ guard let osVersionInfo = self . _rawOperatingSystemVersionInfo else {
124
223
return OperatingSystemVersion ( majorVersion: fallbackMajor, minorVersion: fallbackMinor, patchVersion: fallbackPatch)
125
224
}
225
+
126
226
return OperatingSystemVersion (
127
227
majorVersion: Int ( osVersionInfo. dwMajorVersion) ,
128
228
minorVersion: Int ( osVersionInfo. dwMinorVersion) ,
129
229
patchVersion: Int ( osVersionInfo. dwBuildNumber)
130
230
)
131
- #else
231
+ #elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android)
132
232
var utsNameBuffer = utsname ( )
133
233
guard uname ( & utsNameBuffer) == 0 else {
134
234
return OperatingSystemVersion ( majorVersion: fallbackMajor, minorVersion: fallbackMinor, patchVersion: fallbackPatch)
@@ -138,6 +238,8 @@ open class ProcessInfo: NSObject {
138
238
}
139
239
let idx = release. firstIndex ( of: " - " ) ?? release. endIndex
140
240
versionString = String ( release [ ..< idx] )
241
+ #else
242
+ return OperatingSystemVersion ( majorVersion: fallbackMajor, minorVersion: fallbackMinor, patchVersion: fallbackPatch)
141
243
#endif
142
244
let versionComponents = versionString. split ( separator: " . " ) . map ( String . init) . compactMap ( { Int ( $0) } )
143
245
let majorVersion = versionComponents. dropFirst ( 0 ) . first ?? fallbackMajor
0 commit comments