Skip to content

Commit a36bdd0

Browse files
committed
Update ProcessInfo.operatingSystemVersionString to return more meaningful values on Linux and Windows.
1 parent 1c1e52a commit a36bdd0

File tree

1 file changed

+61
-15
lines changed

1 file changed

+61
-15
lines changed

Diff for: Sources/Foundation/ProcessInfo.swift

+61-15
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,68 @@ open class ProcessInfo: NSObject {
8080
return CFUUIDCreateString(kCFAllocatorSystemDefault, uuid)._swiftObject
8181
}
8282

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+
83104
open var operatingSystemVersionString: String {
84105
let fallback = "Unknown"
85106
#if os(Linux)
86-
let version = try? String(contentsOf: URL(fileURLWithPath: "/proc/version_signature", isDirectory: false), encoding: .utf8)
87-
return version ?? fallback
107+
var utsNameBuffer = utsname()
108+
guard uname(&utsNameBuffer) == 0 else {
109+
return fallback
110+
}
111+
let release = withUnsafePointer(to: &utsNameBuffer.release.0) { String(cString: $0) }
112+
113+
return release
114+
#elseif os(Windows)
115+
guard let osVersionInfo = self._rawOperatingSystemVersionInfo else {
116+
return fallback
117+
}
118+
119+
// Windows has no canonical way to turn the fairly complex `RTL_OSVERSIONINFOW` version info into a string. We
120+
// do our best here to construct something consistent. Unfortunately, to provide a useful result, this requires
121+
// hardcoding several of the somewhat ambiguous values in the table provided here:
122+
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfoexw#remarks
123+
var versionString = ""
124+
switch (osVersionInfo.dwMajorVersion, osVersionInfo.dwMinorVersion) {
125+
case (5, 0): versionString += "Windows 2000"
126+
case (5, 1): versionString += "Windows XP"
127+
case (5, 2) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += "Windows XP Professional x64"
128+
case (5, 2) where osVersionInfo.wSuiteMask == VER_SUITE_WH_SERVER: versionString += "Windows Home Server"
129+
case (5, 2): versionString += "Windows Server 2003"
130+
case (6, 0) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += "Windows Vista"
131+
case (6, 0): versionString += "Windows Server 2008"
132+
case (6, 1) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += "Windows 7"
133+
case (6, 1): versionString += "Windows Server 2008 R2"
134+
case (6, 2) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += "Windows 8"
135+
case (6, 2): versionString += "Windows Server 2012"
136+
case (6, 3) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += "Windows 8.1"
137+
case (6, 3): versionString += "Windows Server 2012 R2" // We assume the "10,0" numbers in the table for this are a typo
138+
case (10, 0) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += "Windows 10"
139+
case (10, 0): versionString += "Windows Server 2019" // The table gives identical values for 2016 and 2019, so we just assume 2019 here
140+
default: return fallback
141+
}
142+
versionString += " (build \(osVersionInfo.dwBuildNumber))"
143+
// For now we ignore the `szCSDVersion`, `wServicePackMajor`, and `wServicePackMinor` values.
144+
return versionString
88145
#else
89146
return CFCopySystemVersionString()?._swiftObject ?? fallback
90147
#endif
@@ -108,21 +165,10 @@ open class ProcessInfo: NSObject {
108165
}
109166
versionString = productVersion._swiftObject
110167
#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 {
168+
guard let osVersionInfo = self._rawOperatingSystemVersionInfo else {
124169
return OperatingSystemVersion(majorVersion: fallbackMajor, minorVersion: fallbackMinor, patchVersion: fallbackPatch)
125170
}
171+
126172
return OperatingSystemVersion(
127173
majorVersion: Int(osVersionInfo.dwMajorVersion),
128174
minorVersion: Int(osVersionInfo.dwMinorVersion),

0 commit comments

Comments
 (0)