From 21a8e5cb7a2333cecb74bd4103dcf005fb31cc4a Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Thu, 7 May 2020 15:31:13 -0500 Subject: [PATCH 1/5] Return the correct OSVersion on OSX like systems. Fix #34977 --- .../System.Private.CoreLib.Shared.projitems | 2 + .../src/System/Environment.Linux.cs | 67 +++++++++++++++++++ .../src/System/Environment.OSX.cs | 49 ++++++++++++++ .../src/System/Environment.Unix.cs | 60 ----------------- .../tests/System/EnvironmentTests.cs | 16 ++++- 5 files changed, 132 insertions(+), 62 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 951552c11ba490..9597cbdd4bc9d4 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1750,6 +1750,8 @@ + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs new file mode 100644 index 00000000000000..0ccea27611aeda --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System +{ + public static partial class Environment + { + private static OperatingSystem GetOSVersion() => GetOperatingSystem(Interop.Sys.GetUnixRelease()); + + // Tests exercise this method for corner cases via private reflection + private static OperatingSystem GetOperatingSystem(string release) + { + int major = 0, minor = 0, build = 0, revision = 0; + + // Parse the uname's utsname.release for the first four numbers found. + // This isn't perfect, but Version already doesn't map exactly to all possible release + // formats, e.g. 2.6.19-1.2895.fc6 + if (release != null) + { + int i = 0; + major = FindAndParseNextNumber(release, ref i); + minor = FindAndParseNextNumber(release, ref i); + build = FindAndParseNextNumber(release, ref i); + revision = FindAndParseNextNumber(release, ref i); + } + + return new OperatingSystem(PlatformID.Unix, new Version(major, minor, build, revision)); + } + + private static int FindAndParseNextNumber(string text, ref int pos) + { + // Move to the beginning of the number + for (; pos < text.Length; pos++) + { + char c = text[pos]; + if ('0' <= c && c <= '9') + { + break; + } + } + + // Parse the number; + int num = 0; + for (; pos < text.Length; pos++) + { + char c = text[pos]; + if ('0' > c || c > '9') + break; + + try + { + num = checked((num * 10) + (c - '0')); + } + // Integer overflow can occur for example with: + // Linux nelknet 4.15.0-24201807041620-generic + // To form a valid Version, num must be positive. + catch (OverflowException) + { + return int.MaxValue; + } + } + + return num; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs new file mode 100644 index 00000000000000..4f227bc90aba07 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; +using nint = System.IntPtr; + +namespace System +{ + public static partial class Environment + { + private static OperatingSystem GetOSVersion() + { + int major = 0; + int minor = 0; + int patch = 0; + + IntPtr processInfo = objc_msgSend(objc_getClass("NSProcessInfo"), sel_getUid("processInfo")); + if (processInfo != IntPtr.Zero) + { + NSOperatingSystemVersion osVersion = get_operatingSystemVersion(processInfo, sel_getUid("operatingSystemVersion")); + major = osVersion.majorVersion.ToInt32(); + minor = osVersion.minorVersion.ToInt32(); + patch = osVersion.patchVersion.ToInt32(); + } + + // For compatibility reasons with Mono, PlatformID.Unix is returned on MacOSX. PlatformID.MacOSX + // is hidden from the editor and shouldn't be used. + return new OperatingSystem(PlatformID.Unix, new Version(major, minor, patch)); + } + + [StructLayout(LayoutKind.Sequential)] + private struct NSOperatingSystemVersion + { + public nint majorVersion; + public nint minorVersion; + public nint patchVersion; + } + + [DllImport("libobjc.dylib")] + private static extern IntPtr objc_getClass(string className); + [DllImport("libobjc.dylib")] + private static extern IntPtr sel_getUid(string selector); + [DllImport("libobjc.dylib")] + private static extern IntPtr objc_msgSend(IntPtr basePtr, IntPtr selector); + [DllImport("libobjc.dylib", EntryPoint = "objc_msgSend_stret")] + private static extern NSOperatingSystemVersion get_operatingSystemVersion(IntPtr basePtr, IntPtr selector); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs index c29a56c14b53f0..81b516dd45f5df 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs @@ -63,66 +63,6 @@ public static string MachineName internal const string NewLineConst = "\n"; - private static OperatingSystem GetOSVersion() => GetOperatingSystem(Interop.Sys.GetUnixRelease()); - - // Tests exercise this method for corner cases via private reflection - private static OperatingSystem GetOperatingSystem(string release) - { - int major = 0, minor = 0, build = 0, revision = 0; - - // Parse the uname's utsname.release for the first four numbers found. - // This isn't perfect, but Version already doesn't map exactly to all possible release - // formats, e.g. 2.6.19-1.2895.fc6 - if (release != null) - { - int i = 0; - major = FindAndParseNextNumber(release, ref i); - minor = FindAndParseNextNumber(release, ref i); - build = FindAndParseNextNumber(release, ref i); - revision = FindAndParseNextNumber(release, ref i); - } - - // For compatibility reasons with Mono, PlatformID.Unix is returned on MacOSX. PlatformID.MacOSX - // is hidden from the editor and shouldn't be used. - return new OperatingSystem(PlatformID.Unix, new Version(major, minor, build, revision)); - } - - private static int FindAndParseNextNumber(string text, ref int pos) - { - // Move to the beginning of the number - for (; pos < text.Length; pos++) - { - char c = text[pos]; - if ('0' <= c && c <= '9') - { - break; - } - } - - // Parse the number; - int num = 0; - for (; pos < text.Length; pos++) - { - char c = text[pos]; - if ('0' > c || c > '9') - break; - - try - { - num = checked((num * 10) + (c - '0')); - } - // Integer overflow can occur for example with: - // Linux nelknet 4.15.0-24201807041620-generic - // To form a valid Version, num must be positive. - catch (OverflowException) - { - return int.MaxValue; - } - } - - return num; - } - public static string SystemDirectory => GetFolderPathCore(SpecialFolder.System, SpecialFolderOption.None); public static int SystemPageSize => CheckedSysConf(Interop.Sys.SysConfName._SC_PAGESIZE); diff --git a/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs b/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs index 3ea51bf15b4cfb..e43bd97b669086 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs @@ -132,9 +132,9 @@ public void OSVersion_ValidVersion() Assert.Contains(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Windows" : "Unix", versionString); } - // On Unix, we must parse the version from uname -r + // On non-OSX Unix, we must parse the version from uname -r [Theory] - [PlatformSpecific(TestPlatforms.AnyUnix)] + [PlatformSpecific(TestPlatforms.AnyUnix & ~TestPlatforms.OSX)] [InlineData("2.6.19-1.2895.fc6", 2, 6, 19, 1)] [InlineData("xxx1yyy2zzz3aaa4bbb", 1, 2, 3, 4)] [InlineData("2147483647.2147483647.2147483647.2147483647", int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue)] @@ -154,6 +154,18 @@ public void OSVersion_ParseVersion(string input, int major, int minor, int build Assert.Equal(expected, actual); } + [Fact] + [PlatformSpecific(TestPlatforms.OSX)] + public void OSVersion_ValidVersion_OSX() + { + Version version = Environment.OSVersion.Version; + + // verify that the Environment.OSVersion.Version matches the current RID + Assert.Contains(version.ToString(2), RuntimeInformation.RuntimeIdentifier); + + Assert.Equal(-1, version.Revision); // Revision is never set on OSX + } + [Fact] public void SystemPageSize_Valid() { From 6799fe3130ec98a11f558f7291faff70f4fbdf21 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Fri, 8 May 2020 13:05:03 -0500 Subject: [PATCH 2/5] Rename files to not have Linux when they aren't Linux specific. --- .../src/System.Private.CoreLib.Shared.projitems | 10 +++++----- ...thCore.cs => Environment.GetFolderPathCore.Unix.cs} | 0 ...Environment.OSX.cs => Environment.OSVersion.OSX.cs} | 0 ...ironment.Linux.cs => Environment.OSVersion.Unix.cs} | 0 .../IO/{FileStream.OSX.cs => FileStream.Lock.OSX.cs} | 0 .../{FileStream.Linux.cs => FileStream.Lock.Unix.cs} | 0 6 files changed, 5 insertions(+), 5 deletions(-) rename src/libraries/System.Private.CoreLib/src/System/{Environment.Unix.GetFolderPathCore.cs => Environment.GetFolderPathCore.Unix.cs} (100%) rename src/libraries/System.Private.CoreLib/src/System/{Environment.OSX.cs => Environment.OSVersion.OSX.cs} (100%) rename src/libraries/System.Private.CoreLib/src/System/{Environment.Linux.cs => Environment.OSVersion.Unix.cs} (100%) rename src/libraries/System.Private.CoreLib/src/System/IO/{FileStream.OSX.cs => FileStream.Lock.OSX.cs} (100%) rename src/libraries/System.Private.CoreLib/src/System/IO/{FileStream.Linux.cs => FileStream.Lock.Unix.cs} (100%) diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 9597cbdd4bc9d4..9eaf1b4677fb60 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1750,16 +1750,16 @@ - - - + + + - - + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.GetFolderPathCore.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.GetFolderPathCore.Unix.cs similarity index 100% rename from src/libraries/System.Private.CoreLib/src/System/Environment.Unix.GetFolderPathCore.cs rename to src/libraries/System.Private.CoreLib/src/System/Environment.GetFolderPathCore.Unix.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.OSX.cs similarity index 100% rename from src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs rename to src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.OSX.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.Unix.cs similarity index 100% rename from src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs rename to src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.Unix.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.OSX.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.Lock.OSX.cs similarity index 100% rename from src/libraries/System.Private.CoreLib/src/System/IO/FileStream.OSX.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/FileStream.Lock.OSX.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.Linux.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.Lock.Unix.cs similarity index 100% rename from src/libraries/System.Private.CoreLib/src/System/IO/FileStream.Linux.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/FileStream.Lock.Unix.cs From 282e52b97a29ae789e5f89199e2d872db8531262 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Fri, 8 May 2020 13:15:49 -0500 Subject: [PATCH 3/5] Assert OSVersion Build number is valid. --- .../System.Runtime.Extensions/tests/System/EnvironmentTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs b/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs index e43bd97b669086..5129caf6dc8e86 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs @@ -163,6 +163,7 @@ public void OSVersion_ValidVersion_OSX() // verify that the Environment.OSVersion.Version matches the current RID Assert.Contains(version.ToString(2), RuntimeInformation.RuntimeIdentifier); + Assert.True(version.Build >= 0, "OSVersion Build should be non-negative"); Assert.Equal(-1, version.Revision); // Revision is never set on OSX } From ee009d07d443283c266345e416c9f2332dae4c4f Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Fri, 8 May 2020 14:22:39 -0500 Subject: [PATCH 4/5] PR feedback Move interop code to Common\src\Interop. Specify the full path to libobjc.dylib. Fix objc_msgSend_stret for ARM64. --- .../src/Interop/OSX/Interop.Libraries.cs | 2 +- .../Common/src/Interop/OSX/Interop.libobjc.cs | 59 +++++++++++++++++++ .../System.Private.CoreLib.Shared.projitems | 17 ++++-- .../src/System/Environment.OSVersion.OSX.cs | 35 +---------- 4 files changed, 75 insertions(+), 38 deletions(-) create mode 100644 src/libraries/Common/src/Interop/OSX/Interop.libobjc.cs diff --git a/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs b/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs index 84bf6d6398da89..ecd5acf4ce2675 100644 --- a/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs +++ b/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs @@ -9,6 +9,7 @@ internal static partial class Libraries internal const string CoreFoundationLibrary = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"; internal const string CoreServicesLibrary = "/System/Library/Frameworks/CoreServices.framework/CoreServices"; internal const string CFNetworkLibrary = "/System/Library/Frameworks/CFNetwork.framework/CFNetwork"; + internal const string libobjc = "/usr/lib/libobjc.dylib"; internal const string libproc = "libproc"; internal const string LibSystemCommonCrypto = "/usr/lib/system/libcommonCrypto"; internal const string LibSystemKernel = "/usr/lib/system/libsystem_kernel"; @@ -16,6 +17,5 @@ internal static partial class Libraries internal const string SystemConfigurationLibrary = "/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration"; internal const string AppleCryptoNative = "System.Security.Cryptography.Native.Apple"; internal const string MsQuic = "msquic"; - } } diff --git a/src/libraries/Common/src/Interop/OSX/Interop.libobjc.cs b/src/libraries/Common/src/Interop/OSX/Interop.libobjc.cs new file mode 100644 index 00000000000000..6658d2b4c5d405 --- /dev/null +++ b/src/libraries/Common/src/Interop/OSX/Interop.libobjc.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Runtime.InteropServices; +using nint = System.IntPtr; + +internal static partial class Interop +{ + internal static partial class libobjc + { +#if TARGET_ARM64 + private const string MessageSendStructReturnEntryPoint = "objc_msgSend"; +#else + private const string MessageSendStructReturnEntryPoint = "objc_msgSend_stret"; +#endif + + [StructLayout(LayoutKind.Sequential)] + private struct NSOperatingSystemVersion + { + public nint majorVersion; + public nint minorVersion; + public nint patchVersion; + } + + [DllImport(Libraries.libobjc)] + private static extern IntPtr objc_getClass(string className); + [DllImport(Libraries.libobjc)] + private static extern IntPtr sel_getUid(string selector); + [DllImport(Libraries.libobjc)] + private static extern IntPtr objc_msgSend(IntPtr basePtr, IntPtr selector); + + internal static Version GetOperatingSystemVersion() + { + int major = 0; + int minor = 0; + int patch = 0; + + IntPtr processInfo = objc_msgSend(objc_getClass("NSProcessInfo"), sel_getUid("processInfo")); + + if (processInfo != IntPtr.Zero) + { + NSOperatingSystemVersion osVersion = get_operatingSystemVersion(processInfo, sel_getUid("operatingSystemVersion")); + + major = osVersion.majorVersion.ToInt32(); + minor = osVersion.minorVersion.ToInt32(); + patch = osVersion.patchVersion.ToInt32(); + } + + return new Version(major, minor, patch); + } + + [DllImport(Libraries.libobjc, EntryPoint = MessageSendStructReturnEntryPoint)] + private static extern NSOperatingSystemVersion get_operatingSystemVersion(IntPtr basePtr, IntPtr selector); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 9eaf1b4677fb60..23fd990bfb42c2 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -8,6 +8,7 @@ enable + true @@ -1750,16 +1751,16 @@ - - + + - - + + @@ -1775,6 +1776,14 @@ + + + Common\Interop\OSX\Interop.libobjc.cs + + + Common\Interop\OSX\Interop.Libraries.cs + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.OSX.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.OSX.cs index 4f227bc90aba07..0c8771938f0414 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.OSX.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.OSVersion.OSX.cs @@ -2,48 +2,17 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Runtime.InteropServices; -using nint = System.IntPtr; - namespace System { public static partial class Environment { private static OperatingSystem GetOSVersion() { - int major = 0; - int minor = 0; - int patch = 0; - - IntPtr processInfo = objc_msgSend(objc_getClass("NSProcessInfo"), sel_getUid("processInfo")); - if (processInfo != IntPtr.Zero) - { - NSOperatingSystemVersion osVersion = get_operatingSystemVersion(processInfo, sel_getUid("operatingSystemVersion")); - major = osVersion.majorVersion.ToInt32(); - minor = osVersion.minorVersion.ToInt32(); - patch = osVersion.patchVersion.ToInt32(); - } + Version version = Interop.libobjc.GetOperatingSystemVersion(); // For compatibility reasons with Mono, PlatformID.Unix is returned on MacOSX. PlatformID.MacOSX // is hidden from the editor and shouldn't be used. - return new OperatingSystem(PlatformID.Unix, new Version(major, minor, patch)); - } - - [StructLayout(LayoutKind.Sequential)] - private struct NSOperatingSystemVersion - { - public nint majorVersion; - public nint minorVersion; - public nint patchVersion; + return new OperatingSystem(PlatformID.Unix, version); } - - [DllImport("libobjc.dylib")] - private static extern IntPtr objc_getClass(string className); - [DllImport("libobjc.dylib")] - private static extern IntPtr sel_getUid(string selector); - [DllImport("libobjc.dylib")] - private static extern IntPtr objc_msgSend(IntPtr basePtr, IntPtr selector); - [DllImport("libobjc.dylib", EntryPoint = "objc_msgSend_stret")] - private static extern NSOperatingSystemVersion get_operatingSystemVersion(IntPtr basePtr, IntPtr selector); } } From f0fd6b272a7502eec98c9076353d4c15d1e4a89a Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Fri, 8 May 2020 14:37:31 -0500 Subject: [PATCH 5/5] Specify full path to libproc.dylib. Fix #24095 --- src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs b/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs index ecd5acf4ce2675..6c2a0ab78c8d74 100644 --- a/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs +++ b/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs @@ -10,7 +10,7 @@ internal static partial class Libraries internal const string CoreServicesLibrary = "/System/Library/Frameworks/CoreServices.framework/CoreServices"; internal const string CFNetworkLibrary = "/System/Library/Frameworks/CFNetwork.framework/CFNetwork"; internal const string libobjc = "/usr/lib/libobjc.dylib"; - internal const string libproc = "libproc"; + internal const string libproc = "/usr/lib/libproc.dylib"; internal const string LibSystemCommonCrypto = "/usr/lib/system/libcommonCrypto"; internal const string LibSystemKernel = "/usr/lib/system/libsystem_kernel"; internal const string Odbc32 = "libodbc.2.dylib";