Skip to content

Commit

Permalink
Return the correct OSVersion on OSX like systems. (dotnet#36029)
Browse files Browse the repository at this point in the history
* Return the correct OSVersion on OSX like systems.

Fix dotnet#34977

* Rename files to not have Linux when they aren't Linux specific.

* Assert OSVersion Build number is valid.

* PR feedback

Move interop code to Common\src\Interop.
Specify the full path to libobjc.dylib.
Fix objc_msgSend_stret for ARM64.

* Specify full path to libproc.dylib.

Fix dotnet#24095
  • Loading branch information
eerhardt authored May 11, 2020
1 parent 993db61 commit 2d4acbd
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 67 deletions.
4 changes: 2 additions & 2 deletions src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ 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 libproc = "libproc";
internal const string libobjc = "/usr/lib/libobjc.dylib";
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";
internal const string SystemConfigurationLibrary = "/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration";
internal const string AppleCryptoNative = "System.Security.Cryptography.Native.Apple";
internal const string MsQuic = "msquic";

}
}
59 changes: 59 additions & 0 deletions src/libraries/Common/src/Interop/OSX/Interop.libobjc.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</PropertyGroup>
<PropertyGroup>
<Nullable>enable</Nullable>
<IsOSXLike Condition="'$(TargetsOSX)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true'">true</IsOSXLike>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Internal\IO\File.cs" />
Expand Down Expand Up @@ -1750,14 +1751,16 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\RuntimeEventSourceHelper.Unix.cs" Condition="'$(FeaturePerfTracing)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.NoRegistry.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.Unix.GetFolderPathCore.cs" Condition="'$(TargetsiOS)' != 'true' and '$(TargetstvOS)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.OSVersion.OSX.cs" Condition="'$(IsOSXLike)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.OSVersion.Unix.cs" Condition="'$(IsOSXLike)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.GetFolderPathCore.Unix.cs" Condition="'$(TargetsiOS)' != 'true' and '$(TargetstvOS)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureData.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureInfo.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\HijriCalendar.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Guid.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\DriveInfoInternal.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.OSX.cs" Condition="'$(TargetsOSX)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.Linux.cs" Condition="'$(TargetsOSX)' != 'true' and '$(TargetsiOS)' != 'true' and '$(TargetstvOS)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.Lock.OSX.cs" Condition="'$(IsOSXLike)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.Lock.Unix.cs" Condition="'$(IsOSXLike)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStream.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\Path.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\PathInternal.Unix.cs" />
Expand All @@ -1773,6 +1776,14 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\TimerQueue.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\TimeZoneInfo.Unix.cs" />
</ItemGroup>
<ItemGroup Condition="'$(IsOSXLike)' == 'true'">
<Compile Include="$(CommonPath)Interop\OSX\Interop.libobjc.cs">
<Link>Common\Interop\OSX\Interop.libobjc.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\OSX\Interop.Libraries.cs">
<Link>Common\Interop\OSX\Interop.Libraries.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup Condition="'$(FeatureHardwareIntrinsics)' == 'true' and ('$(Platform)' == 'x64' or ('$(Platform)' == 'x86' and '$(TargetsUnix)' != 'true'))">
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Intrinsics\X86\Aes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\Intrinsics\X86\Avx.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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()
{
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, version);
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -154,6 +154,19 @@ 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.True(version.Build >= 0, "OSVersion Build should be non-negative");
Assert.Equal(-1, version.Revision); // Revision is never set on OSX
}

[Fact]
public void SystemPageSize_Valid()
{
Expand Down

0 comments on commit 2d4acbd

Please sign in to comment.