Created
February 19, 2019 17:23
-
-
Save simplexidev/763cc4af64a2dad295c7758d6a9df8e4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*************************************************************************************************** | |
* FileName: PlatformHelper.cs | |
* Date: 20180913 | |
* Copyright: Copyright © 2017-2019 Thomas Corwin, et al. All Rights Reserved. | |
* License: MIT License | |
**************************************************************************************************/ | |
using System; | |
using System.IO; | |
using System.Runtime.InteropServices; | |
namespace TCD | |
{ | |
// Based on: https://github.com/dotnet/core-setup/tree/master/src/Microsoft.DotNet.PlatformAbstractions | |
/// <summary> | |
/// Provides methods to determine platform-specific information, such as the name and version of the running OS. | |
/// </summary> | |
public sealed class PlatformHelper | |
{ | |
static PlatformHelper() | |
{ | |
switch (Platform) | |
{ | |
case Platform.Windows: | |
Version = GetWindowsVersion() ?? string.Empty; | |
OS = "Windows"; | |
break; | |
case Platform.Linux: | |
Version = GetLinuxInfo().Version ?? string.Empty; | |
OS = GetLinuxInfo().ID ?? "Linux"; | |
break; | |
case Platform.MacOS: | |
Version = GetDarwinVersion() ?? string.Empty; | |
OS = "macOS"; | |
break; | |
case Platform.FreeBSD: | |
Version = GetFreeBSDVersion() ?? string.Empty; | |
OS = "FreeBSD"; | |
break; | |
default: | |
Version = string.Empty; | |
OS = string.Empty; | |
break; | |
} | |
} | |
public static string RID => Environment.GetEnvironmentVariable("DOTNET_RUNTIME_ID") ?? $"{GetRIDPlatform()}{GetRIDOSVersion()}-{GetRIDArchitecture()}"; | |
public static Platform Platform = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Platform.Windows | |
: RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? Platform.Linux | |
: RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? Platform.MacOS | |
: RuntimeInformation.IsOSPlatform(OSPlatform.Create("FREEBSD")) ? Platform.FreeBSD | |
: Platform.Unknown; | |
public static PlatformArch Arch => (PlatformArch)RuntimeInformation.ProcessArchitecture; | |
//TODO: Replace with an enum of all supported OS's based on the RID graph. | |
public static string OS { get; } | |
public static string Version { get; } | |
private static string GetWindowsVersion() | |
{ | |
Native.Ntdll.RTL_OSVERSIONINFOEX osvi = new Native.Ntdll.RTL_OSVERSIONINFOEX(); | |
osvi.dwOSVersionInfoSize = (uint)Marshal.SizeOf(osvi); | |
return Native.Ntdll.RtlGetVersion(out osvi) == 0 ? $"{osvi.dwMajorVersion}.{osvi.dwMinorVersion}.{osvi.dwBuildNumber}" : string.Empty; | |
} | |
private static unsafe string GetDarwinVersion() | |
{ | |
int CTL_KERN = 1; | |
int KERN_OSRELEASE = 2; | |
const uint BUFFER_LENGTH = 32; | |
int* name = stackalloc int[2]; | |
name[0] = CTL_KERN; | |
name[1] = KERN_OSRELEASE; | |
byte* buf = stackalloc byte[(int)BUFFER_LENGTH]; | |
uint* len = stackalloc uint[1]; | |
*len = BUFFER_LENGTH; | |
string kernelRelease = null; | |
try | |
{ | |
if (Native.Libc.sysctl(name, 2, buf, len, IntPtr.Zero, 0) == 0 && *len < BUFFER_LENGTH) | |
kernelRelease = Marshal.PtrToStringAnsi((IntPtr)buf, (int)*len); | |
} | |
catch (Exception ex) | |
{ | |
throw new PlatformNotSupportedException("Unable to retrieve macOS version.", ex); | |
} | |
if (kernelRelease == null) | |
throw new PlatformNotSupportedException("Unknown error trying to read macOS version."); | |
return !Version.TryParse(kernelRelease, out Version version) || version.Major < 5 ? "10.0" : $"10.{version.Major - 4}"; | |
} | |
private static string GetFreeBSDVersion() | |
{ | |
// This is same as sysctl kern.version | |
// FreeBSD 11.0-RELEASE-p1 FreeBSD 11.0-RELEASE-p1 #0 r306420: Thu Sep 29 01:43:23 UTC 2016 [email protected]:/usr/obj/usr/src/sys/GENERIC | |
// What we want is major release as minor releases should be compatible. | |
try | |
{ | |
return RuntimeInformation.OSDescription.Split()[1].Split('.')[0]; // second token up to first dot | |
} | |
catch { } | |
return string.Empty; | |
} | |
private static LinuxInfo GetLinuxInfo() | |
{ | |
LinuxInfo result = null; | |
// Sample os-release file: | |
// NAME="Ubuntu" | |
// VERSION = "14.04.3 LTS, Trusty Tahr" | |
// ID = ubuntu | |
// ID_LIKE = debian | |
// PRETTY_NAME = "Ubuntu 14.04.3 LTS" | |
// VERSION_ID = "14.04" | |
// HOME_URL = "http://www.ubuntu.com/" | |
// SUPPORT_URL = "http://help.ubuntu.com/" | |
// BUG_REPORT_URL = "http://bugs.launchpad.net/ubuntu/" | |
// We use ID and VERSION_ID | |
if (File.Exists("/etc/os-release")) | |
{ | |
string[] lines = File.ReadAllLines("/etc/os-release"); | |
result = new LinuxInfo(); | |
foreach (string line in lines) | |
{ | |
if (line.StartsWith("ID=", StringComparison.Ordinal)) | |
result.ID = line.Substring(3).Trim('"', '\''); | |
else if (line.StartsWith("VERSION_ID=", StringComparison.Ordinal)) | |
result.Version = line.Substring(11).Trim('"', '\''); | |
} | |
} | |
else if (File.Exists("/etc/redhat-release")) | |
{ | |
string[] lines = File.ReadAllLines("/etc/redhat-release"); | |
if (lines.Length >= 1) | |
{ | |
string line = lines[0]; | |
if (line.StartsWith("Red Hat Enterprise Linux Server release 6.") || line.StartsWith("CentOS release 6.")) | |
result = new LinuxInfo("rhel", "6"); | |
} | |
} | |
if (result != null) | |
{ | |
// For some distros, we don't want to use the full version from VERSION_ID. One example is | |
// Red Hat Enterprise Linux, which includes a minor version in their VERSION_ID but minor | |
// versions are backwards compatible. | |
// | |
// In this case, we'll normalized RIDs like 'rhel.7.2' and 'rhel.7.3' to a generic | |
// 'rhel.7'. This brings RHEL in line with other distros like CentOS or Debian which | |
// don't put minor version numbers in their VERSION_ID fields because all minor versions | |
// are backwards compatible. | |
// Handle if VersionId is null by just setting the index to -1. | |
int lastVersionNumberSeparatorIndex = result.Version?.IndexOf('.') ?? -1; | |
// For Alpine, the version reported has three components, so we need to find the second version separator | |
if (lastVersionNumberSeparatorIndex != -1 && result.ID == "alpine") | |
lastVersionNumberSeparatorIndex = result.Version.IndexOf('.', lastVersionNumberSeparatorIndex + 1); | |
if (lastVersionNumberSeparatorIndex != -1 && (result.ID == "rhel" || result.ID == "alpine")) | |
result.Version = result.Version.Substring(0, lastVersionNumberSeparatorIndex); | |
} | |
return result; | |
} | |
private sealed class LinuxInfo | |
{ | |
public LinuxInfo(string id = null, string version = null) | |
{ | |
ID = id; | |
Version = version; | |
} | |
public string ID { get; set; } | |
public string Version { get; set; } | |
} | |
private static string GetRIDArch() => OSArchitecture.ToString().ToLowerInvariant(); | |
private static string GetRIDPlatform() | |
{ | |
switch (CurrentPlatform) | |
{ | |
case Platform.Windows: | |
return "win"; | |
case Platform.Linux: | |
return OSName.ToLowerInvariant(); | |
case Platform.MacOS: | |
return "osx"; | |
case Platform.FreeBSD: | |
return "freebsd"; | |
default: | |
return "unknown"; | |
} | |
} | |
private static string GetRIDVersion() | |
{ | |
// Windows RIDs do not separate OS name and version by "." due to legacy | |
// Others do, that's why we have the "." prefix on them below | |
switch (CurrentPlatform) | |
{ | |
case Platform.Windows: | |
Version ver = Version.Parse(OSVersion); | |
if (ver.Major == 6) | |
{ | |
if (ver.Minor == 1) | |
return "7"; | |
else if (ver.Minor == 2) | |
return "8"; | |
else if (ver.Minor == 3) | |
return "81"; | |
} | |
// Return the major version for use in RID computation without applying any cap. | |
else if (ver.Major >= 10) | |
return ver.Major.ToString(); | |
return string.Empty; // Unknown version | |
case Platform.Linux: | |
if (string.IsNullOrEmpty(OSVersion)) | |
return string.Empty; | |
return $".{OSVersion}"; | |
case Platform.MacOS: | |
return $".{OSVersion}"; | |
case Platform.FreeBSD: | |
return $".{OSVersion}"; | |
default: | |
return string.Empty; // Unknown Platform? Unknown Version! | |
} | |
} | |
public enum Platform | |
{ | |
Unknown = 0, | |
Windows = 1, | |
Linux = 2, | |
MacOS = 3, | |
FreeBSD = 4 | |
} | |
public enum PlatformArch | |
{ | |
X86 = 1, | |
X64 = 2, | |
ARM = 3, | |
ARM64 = 4 | |
} | |
} | |
namespace Native | |
{ | |
internal static class Libc | |
{ | |
private const string AssemblyRef = "libc"; | |
[DllImport(AssemblyRef)] | |
#pragma warning disable IDE1006 | |
internal static extern unsafe int sysctl(int* name, uint namelen, byte* oldp, uint* oldlenp, IntPtr newp, uint newlen); | |
#pragma warning restore IDE1006 | |
} | |
internal static class Ntdll | |
{ | |
private const string AssemblyRef = "ntdll"; | |
[StructLayout(LayoutKind.Sequential)] | |
internal struct RTL_OSVERSIONINFOEX | |
{ | |
internal uint dwOSVersionInfoSize; | |
internal uint dwMajorVersion; | |
internal uint dwMinorVersion; | |
internal uint dwBuildNumber; | |
internal uint dwPlatformId; | |
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] | |
internal string szCSDVersion; | |
} | |
[DllImport(AssemblyRef)] | |
internal static extern int RtlGetVersion(out RTL_OSVERSIONINFOEX lpVersionInformation); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment