From fe5f4fbebc9e85c8e512a273567719dca15ac764 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 5 Jul 2016 12:44:40 -0400 Subject: [PATCH 1/5] Move Environment and friends to corefx Currently System.Runtime.Extensions type forwards to the Environment in the runtime, which means we need to implement functionality in both coreclr and corert. It's also missing functionality, which means to add that functionality back we'd need to do so in both runtimes, and we'd miss out on sharing code with elsewhere in corefx. This commit: 1) Adds Environment, EnvironmentVariableTarget, OperatingSystem, and PlatformID to corefx so that System.Runtime.Extensions contains the type rather than forwarding to it. 2) Adds implementations of all of the missing members: CommandLine, CurrentDirectory, ExitCode, Is64BitProcess, Is64BitOperatingSystem, OSVersion, SystemPageSize, UserInteractive, UserName, UserDomainName, Version, WorkingSet, GetLogicalDrives, GetFolderPath, and the overloads of Get/SetEnvironmentVariable(s) that take an EnvironmentVariableTarget. 3) Adds the additional members to the contract. 4) Adds some basic tests to ensure basic functionality works. Work that still needs to be done after this: 1) Add an EnvironmentAugments type to System.Private.Corelib in both corefx and corert, use that to replace the reflection-based hack in this commit, then remove the current Environment from Corelib's model.xml so that the rewriter will make it internal. As part of this, we need to determine how to handle "IsAppXmodel/DesignMode" from corefx, whether we need two different builds for Win32/WinRT, etc. 2) Fix Get/SetEnvironmentVariable(s) to not ignore the EnvironmentVariableTarget. This should be doable once (1) is done. 3) Add more support to the Unix implementation of GetFolderPath for more special folders 4) Add a GetTickCount method to the System.Native native lib, ala the implementation in libcoreclr; the current implementation is functional but slower than it could be. 5) Implement more Windows functionality if/when missing functions become available: SHGetFolderPath and GetVersionExW. 6) Use GetLogicalProcessInformationEx in ProcessorCount (or delegate to the implementation in coreclr). I will open issues for each of these prior to merging this commit. --- .../System.Native/Interop.GetUnixRelease.cs | 14 + .../Unix/System.Native/Interop.SysConf.cs | 1 + .../src/Interop/Windows/Interop.Libraries.cs | 1 + .../Interop.ExpandEnvironmentStringsW.cs | 15 + .../mincore/Interop.GetSystemDirectoryW.cs | 15 + .../Windows/mincore/Interop.GetTickCount64.cs | 14 + .../Windows/mincore/Interop.GetUserNameExW.cs | 17 + .../Windows/mincore/Interop.GetUserNameW.cs | 17 + .../Windows/mincore/Interop.GetVersionExW.cs | 31 ++ .../mincore/Interop.IsWow64Process_IntPtr.cs | 15 + ...terop.IsWow64Process_SafeProcessHandle.cs} | 0 .../mincore/Interop.LookupAccountNameW.cs | 18 ++ src/Native/System.Native/pal_io.cpp | 2 + src/Native/System.Native/pal_io.h | 3 +- .../System.Native/pal_runtimeinformation.cpp | 9 + .../System.Native/pal_runtimeinformation.h | 2 + .../src/System.Diagnostics.Process.csproj | 4 +- .../ref/System.Runtime.Extensions.cs | 103 ++++++- .../src/Resources/Strings.resx | 9 + .../src/System.Runtime.Extensions.csproj | 87 +++++- .../src/System/Environment.SpecialFolder.cs | 115 +++++++ .../System/Environment.SpecialFolderOption.cs | 26 ++ .../src/System/Environment.Unix.cs | 205 +++++++++++++ .../src/System/Environment.Windows.cs | 290 ++++++++++++++++++ .../src/System/Environment.cs | 217 +++++++++++++ .../src/System/EnvironmentVariableTarget.cs | 13 + .../src/System/OperatingSystem.cs | 78 +++++ .../src/System/PlatformID.cs | 17 + .../System.Runtime.Extensions.Tests.csproj | 3 +- .../tests/System/Environment.Exit.cs | 27 +- .../System/Environment.GetCommandLineArgs.cs | 13 +- .../Environment.GetEnvironmentVariable.cs | 2 +- .../tests/System/Environment.NewLine.cs | 4 +- .../System/Environment.ProcessorCount.cs | 4 +- .../tests/System/EnvironmentTests.cs | 238 ++++++++++++++ .../tests/System/IO/Path.Combine.cs | 4 +- .../tests/System/IO/PathTests.cs | 52 ++-- 37 files changed, 1634 insertions(+), 51 deletions(-) create mode 100644 src/Common/src/Interop/Unix/System.Native/Interop.GetUnixRelease.cs create mode 100644 src/Common/src/Interop/Windows/mincore/Interop.ExpandEnvironmentStringsW.cs create mode 100644 src/Common/src/Interop/Windows/mincore/Interop.GetSystemDirectoryW.cs create mode 100644 src/Common/src/Interop/Windows/mincore/Interop.GetTickCount64.cs create mode 100644 src/Common/src/Interop/Windows/mincore/Interop.GetUserNameExW.cs create mode 100644 src/Common/src/Interop/Windows/mincore/Interop.GetUserNameW.cs create mode 100644 src/Common/src/Interop/Windows/mincore/Interop.GetVersionExW.cs create mode 100644 src/Common/src/Interop/Windows/mincore/Interop.IsWow64Process_IntPtr.cs rename src/Common/src/Interop/Windows/mincore/{Interop.IsWow64Process.cs => Interop.IsWow64Process_SafeProcessHandle.cs} (100%) create mode 100644 src/Common/src/Interop/Windows/mincore/Interop.LookupAccountNameW.cs create mode 100644 src/System.Runtime.Extensions/src/System/Environment.SpecialFolder.cs create mode 100644 src/System.Runtime.Extensions/src/System/Environment.SpecialFolderOption.cs create mode 100644 src/System.Runtime.Extensions/src/System/Environment.Unix.cs create mode 100644 src/System.Runtime.Extensions/src/System/Environment.Windows.cs create mode 100644 src/System.Runtime.Extensions/src/System/Environment.cs create mode 100644 src/System.Runtime.Extensions/src/System/EnvironmentVariableTarget.cs create mode 100644 src/System.Runtime.Extensions/src/System/OperatingSystem.cs create mode 100644 src/System.Runtime.Extensions/src/System/PlatformID.cs create mode 100644 src/System.Runtime.Extensions/tests/System/EnvironmentTests.cs diff --git a/src/Common/src/Interop/Unix/System.Native/Interop.GetUnixRelease.cs b/src/Common/src/Interop/Unix/System.Native/Interop.GetUnixRelease.cs new file mode 100644 index 000000000000..5e41ae98046e --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Native/Interop.GetUnixRelease.cs @@ -0,0 +1,14 @@ +// 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; + +internal static partial class Interop +{ + internal static partial class Sys + { + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetUnixRelease", CharSet = CharSet.Ansi, SetLastError = true)] + public static extern string GetUnixRelease(); + } +} diff --git a/src/Common/src/Interop/Unix/System.Native/Interop.SysConf.cs b/src/Common/src/Interop/Unix/System.Native/Interop.SysConf.cs index 6c32df48d61b..58566ae65a6b 100644 --- a/src/Common/src/Interop/Unix/System.Native/Interop.SysConf.cs +++ b/src/Common/src/Interop/Unix/System.Native/Interop.SysConf.cs @@ -12,6 +12,7 @@ internal enum SysConfName { _SC_CLK_TCK = 1, _SC_PAGESIZE = 2, + _SC_NPROCESSORS_ONLN = 3, } [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_SysConf", SetLastError = true)] diff --git a/src/Common/src/Interop/Windows/Interop.Libraries.cs b/src/Common/src/Interop/Windows/Interop.Libraries.cs index 66ed890f7ac9..c4c8d83dd75f 100644 --- a/src/Common/src/Interop/Windows/Interop.Libraries.cs +++ b/src/Common/src/Interop/Windows/Interop.Libraries.cs @@ -66,6 +66,7 @@ internal static class Libraries internal const string Synch = "api-ms-win-core-synch-l1-1-0.dll"; internal const string SystemInfo_L1_1 = "api-ms-win-core-sysinfo-l1-1-0.dll"; internal const string SystemInfo_L1_2 = "api-ms-win-core-sysinfo-l1-2-0.dll"; + internal const string SystemInfo_L2_1 = "api-ms-win-core-sysinfo-l2-1-0.dll"; internal const string ThreadPool = "api-ms-win-core-threadpool-l1-2-0.dll"; internal const string User32 = "user32.dll"; internal const string Util = "api-ms-win-core-util-l1-1-0.dll"; diff --git a/src/Common/src/Interop/Windows/mincore/Interop.ExpandEnvironmentStringsW.cs b/src/Common/src/Interop/Windows/mincore/Interop.ExpandEnvironmentStringsW.cs new file mode 100644 index 000000000000..ebc116bb295a --- /dev/null +++ b/src/Common/src/Interop/Windows/mincore/Interop.ExpandEnvironmentStringsW.cs @@ -0,0 +1,15 @@ +// 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 System.Text; + +internal partial class Interop +{ + internal partial class mincore + { + [DllImport(Libraries.ProcessEnvironment, CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int ExpandEnvironmentStringsW(string lpSrc, [Out] StringBuilder lpDst, int nSize); + } +} diff --git a/src/Common/src/Interop/Windows/mincore/Interop.GetSystemDirectoryW.cs b/src/Common/src/Interop/Windows/mincore/Interop.GetSystemDirectoryW.cs new file mode 100644 index 000000000000..ddf9dd6d8f06 --- /dev/null +++ b/src/Common/src/Interop/Windows/mincore/Interop.GetSystemDirectoryW.cs @@ -0,0 +1,15 @@ +// 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 System.Text; + +internal static partial class Interop +{ + internal static partial class mincore + { + [DllImport(Libraries.SystemInfo_L1_1, CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int GetSystemDirectoryW([Out] StringBuilder lpBuffer, int jSize); + } +} diff --git a/src/Common/src/Interop/Windows/mincore/Interop.GetTickCount64.cs b/src/Common/src/Interop/Windows/mincore/Interop.GetTickCount64.cs new file mode 100644 index 000000000000..0ab307a7a55c --- /dev/null +++ b/src/Common/src/Interop/Windows/mincore/Interop.GetTickCount64.cs @@ -0,0 +1,14 @@ +// 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; + +internal partial class Interop +{ + internal partial class mincore + { + [DllImport(Libraries.SystemInfo_L1_1)] + internal static extern long GetTickCount64(); + } +} diff --git a/src/Common/src/Interop/Windows/mincore/Interop.GetUserNameExW.cs b/src/Common/src/Interop/Windows/mincore/Interop.GetUserNameExW.cs new file mode 100644 index 000000000000..9391ab73f35b --- /dev/null +++ b/src/Common/src/Interop/Windows/mincore/Interop.GetUserNameExW.cs @@ -0,0 +1,17 @@ +// 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 System.Text; + +internal partial class Interop +{ + internal partial class mincore + { + [DllImport(Libraries.Sspi, CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern byte GetUserNameExW(int NameFormat, [Out] StringBuilder lpNameBuffer, ref uint lpnSize); + + internal const int NameSamCompatible = 2; + } +} diff --git a/src/Common/src/Interop/Windows/mincore/Interop.GetUserNameW.cs b/src/Common/src/Interop/Windows/mincore/Interop.GetUserNameW.cs new file mode 100644 index 000000000000..2792671045a8 --- /dev/null +++ b/src/Common/src/Interop/Windows/mincore/Interop.GetUserNameW.cs @@ -0,0 +1,17 @@ +// 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 System.Text; + +internal partial class Interop +{ + internal partial class mincore + { +#pragma warning disable BCL0015 // not available on Windows 7 + [DllImport(Libraries.SystemInfo_L2_1, CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern bool GetUserNameW([Out] StringBuilder lpBuffer, ref int lpnSize); +#pragma warning restore BCL0015 + } +} diff --git a/src/Common/src/Interop/Windows/mincore/Interop.GetVersionExW.cs b/src/Common/src/Interop/Windows/mincore/Interop.GetVersionExW.cs new file mode 100644 index 000000000000..c4512f7b9a9b --- /dev/null +++ b/src/Common/src/Interop/Windows/mincore/Interop.GetVersionExW.cs @@ -0,0 +1,31 @@ +// 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; + +internal partial class Interop +{ + internal partial class mincore + { + [DllImport(Libraries.SystemInfo_L1_1, CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern bool GetVersionExW(ref OSVERSIONINFOEX osvi); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct OSVERSIONINFOEX + { + public int dwOSVersionInfoSize; + public int dwMajorVersion; + public int dwMinorVersion; + public int dwBuildNumber; + public int dwPlatformId; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string szCSDVersion; + public ushort wServicePackMajor; + public ushort wServicePackMinor; + public ushort wSuiteMask; + public byte wProductType; + public byte wReserved; + } + } +} diff --git a/src/Common/src/Interop/Windows/mincore/Interop.IsWow64Process_IntPtr.cs b/src/Common/src/Interop/Windows/mincore/Interop.IsWow64Process_IntPtr.cs new file mode 100644 index 000000000000..e3c95d40332a --- /dev/null +++ b/src/Common/src/Interop/Windows/mincore/Interop.IsWow64Process_IntPtr.cs @@ -0,0 +1,15 @@ +// 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; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class mincore + { + [DllImport(Libraries.Wow64, SetLastError = true)] + internal static extern bool IsWow64Process(IntPtr hProcess, out bool Wow64Process); + } +} diff --git a/src/Common/src/Interop/Windows/mincore/Interop.IsWow64Process.cs b/src/Common/src/Interop/Windows/mincore/Interop.IsWow64Process_SafeProcessHandle.cs similarity index 100% rename from src/Common/src/Interop/Windows/mincore/Interop.IsWow64Process.cs rename to src/Common/src/Interop/Windows/mincore/Interop.IsWow64Process_SafeProcessHandle.cs diff --git a/src/Common/src/Interop/Windows/mincore/Interop.LookupAccountNameW.cs b/src/Common/src/Interop/Windows/mincore/Interop.LookupAccountNameW.cs new file mode 100644 index 000000000000..fab189d5e6be --- /dev/null +++ b/src/Common/src/Interop/Windows/mincore/Interop.LookupAccountNameW.cs @@ -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. + +using System.Runtime.InteropServices; +using System.Text; + +internal partial class Interop +{ + internal partial class mincore + { + [DllImport(Libraries.SecurityLsa, CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern bool LookupAccountNameW( + string machineName, string accountName, byte[] sid, + ref int sidLen, + [Out] StringBuilder domainName, ref uint domainNameLen, out int peUse); + } +} diff --git a/src/Native/System.Native/pal_io.cpp b/src/Native/System.Native/pal_io.cpp index 7da1bffdadc8..d7446fc409b5 100644 --- a/src/Native/System.Native/pal_io.cpp +++ b/src/Native/System.Native/pal_io.cpp @@ -753,6 +753,8 @@ extern "C" int64_t SystemNative_SysConf(SysConfName name) return sysconf(_SC_CLK_TCK); case PAL_SC_PAGESIZE: return sysconf(_SC_PAGESIZE); + case PAL_SC_NPROCESSORS_ONLN: + return sysconf(_SC_NPROCESSORS_ONLN); } assert(false && "Unknown SysConfName"); diff --git a/src/Native/System.Native/pal_io.h b/src/Native/System.Native/pal_io.h index b618f579f3d0..d48a4a0d59f2 100644 --- a/src/Native/System.Native/pal_io.h +++ b/src/Native/System.Native/pal_io.h @@ -210,7 +210,8 @@ enum MemoryAdvice : int32_t enum SysConfName : int32_t { PAL_SC_CLK_TCK = 1, // Number of clock ticks per second - PAL_SC_PAGESIZE = 2, // Size of a page in bytes + PAL_SC_PAGESIZE = 2, // Size of a page in bytes, + PAL_SC_NPROCESSORS_ONLN = 3, // Number of active processors }; /** diff --git a/src/Native/System.Native/pal_runtimeinformation.cpp b/src/Native/System.Native/pal_runtimeinformation.cpp index e2eb8e437d23..6994312e4183 100644 --- a/src/Native/System.Native/pal_runtimeinformation.cpp +++ b/src/Native/System.Native/pal_runtimeinformation.cpp @@ -6,6 +6,7 @@ #include "pal_runtimeinformation.h" #include "pal_types.h" #include +#include #include extern "C" const char* SystemNative_GetUnixName() @@ -13,6 +14,14 @@ extern "C" const char* SystemNative_GetUnixName() return PAL_UNIX_NAME; } +extern "C" char* SystemNative_GetUnixRelease() +{ + struct utsname _utsname; + return uname(&_utsname) != -1 ? + strdup(_utsname.release) : + nullptr; +} + extern "C" int32_t SystemNative_GetUnixVersion(char* version, int* capacity) { struct utsname _utsname; diff --git a/src/Native/System.Native/pal_runtimeinformation.h b/src/Native/System.Native/pal_runtimeinformation.h index 44248a84c022..dc57500d3ade 100644 --- a/src/Native/System.Native/pal_runtimeinformation.h +++ b/src/Native/System.Native/pal_runtimeinformation.h @@ -8,6 +8,8 @@ extern "C" const char* SystemNative_GetUnixName(); +extern "C" char* SystemNative_GetUnixRelease(); + extern "C" int32_t SystemNative_GetUnixVersion(char* version, int* capacity); extern "C" int32_t SystemNative_GetOSArchitecture(); diff --git a/src/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj b/src/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj index 5426901c33ec..8d8a02d6898c 100644 --- a/src/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj +++ b/src/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj @@ -61,8 +61,8 @@ Common\Interop\Windows\Interop.PERF_INFO.cs - - Common\Interop\Windows\Interop.IsWow64Process.cs + + Common\Interop\Windows\Interop.IsWow64Process_SafeProcessHandle.cs Common\Interop\Windows\Interop.GetExitCodeProcess.cs diff --git a/src/System.Runtime.Extensions/ref/System.Runtime.Extensions.cs b/src/System.Runtime.Extensions/ref/System.Runtime.Extensions.cs index 8b87b3445500..74263790e23a 100644 --- a/src/System.Runtime.Extensions/ref/System.Runtime.Extensions.cs +++ b/src/System.Runtime.Extensions/ref/System.Runtime.Extensions.cs @@ -431,23 +431,103 @@ public static partial class Convert } public static partial class Environment { + public static string CommandLine { get { return default(string); } } + public static string CurrentDirectory { get { return default(string); } set { } } public static int CurrentManagedThreadId { get { return default(int); } } + public static int ExitCode { get { return default(int); } set { } } public static bool HasShutdownStarted { get { return default(bool); } } + public static bool Is64BitProcess { get { return default(bool); } } + public static bool Is64BitOperatingSystem { get { return default(bool); } } public static string MachineName { get { return default(string); } } public static string NewLine { get { return default(string); } } + public static System.OperatingSystem OSVersion { get { return default(System.OperatingSystem); } } public static int ProcessorCount { get { return default(int); } } public static string StackTrace { get { return default(string); } } + public static int SystemPageSize { get { return default(int); } } public static int TickCount { get { return default(int); } } + public static bool UserInteractive { get { return default(bool); } } + public static string UserName { get { return default(string); } } + public static string UserDomainName { get { return default(string); } } + public static System.Version Version { get { return default(System.Version); } } + public static long WorkingSet { get { return default(long); } } public static string ExpandEnvironmentVariables(string name) { return default(string); } public static void Exit(int exitCode) {} [System.Security.SecurityCriticalAttribute] public static void FailFast(string message) { } [System.Security.SecurityCriticalAttribute] public static void FailFast(string message, System.Exception exception) { } + public static string[] GetCommandLineArgs() { return default(string[]); } public static string GetEnvironmentVariable(string variable) { return default(string); } + public static string GetEnvironmentVariable(string variable, System.EnvironmentVariableTarget target) { return default(string); } public static System.Collections.IDictionary GetEnvironmentVariables() { return default(System.Collections.IDictionary); } + public static System.Collections.IDictionary GetEnvironmentVariables(System.EnvironmentVariableTarget target) { return default(System.Collections.IDictionary); } + public static string GetFolderPath(System.Environment.SpecialFolder folder) { return default(string); } + public static string GetFolderPath(System.Environment.SpecialFolder folder, System.Environment.SpecialFolderOption option) { return default(string); } + public static string[] GetLogicalDrives() { return default(string[]); } public static void SetEnvironmentVariable(string variable, string value) { } - public static string[] GetCommandLineArgs() { return default(string[]); } + public static void SetEnvironmentVariable(string variable, string value, System.EnvironmentVariableTarget target) { } + public enum SpecialFolder + { + ApplicationData = 0x001a, + CommonApplicationData = 0x0023, + LocalApplicationData = 0x001c, + Cookies = 0x0021, + Desktop = 0x0000, + Favorites = 0x0006, + History = 0x0022, + InternetCache = 0x0020, + Programs = 0x0002, + MyComputer = 0x0011, + MyMusic = 0x000d, + MyPictures = 0x0027, + MyVideos = 0x000e, + Recent = 0x0008, + SendTo = 0x0009, + StartMenu = 0x000b, + Startup = 0x0007, + System = 0x0025, + Templates = 0x0015, + DesktopDirectory = 0x0010, + Personal = 0x0005, + MyDocuments = 0x0005, + ProgramFiles = 0x0026, + CommonProgramFiles = 0x002b, + AdminTools = 0x0030, + CDBurning = 0x003b, + CommonAdminTools = 0x002f, + CommonDocuments = 0x002e, + CommonMusic = 0x0035, + CommonOemLinks = 0x003a, + CommonPictures = 0x0036, + CommonStartMenu = 0x0016, + CommonPrograms = 0X0017, + CommonStartup = 0x0018, + CommonDesktopDirectory = 0x0019, + CommonTemplates = 0x002d, + CommonVideos = 0x0037, + Fonts = 0x0014, + NetworkShortcuts = 0x0013, + PrinterShortcuts = 0x001b, + UserProfile = 0x0028, + CommonProgramFilesX86 = 0x002c, + ProgramFilesX86 = 0x002a, + Resources = 0x0038, + LocalizedResources = 0x0039, + SystemX86 = 0x0029, + Windows = 0x0024, + } + public enum SpecialFolderOption + { + None = 0, + Create = 0x8000, + DoNotVerify = 0x4000, + } + } + public enum EnvironmentVariableTarget + { + Process = 0, + User = 1, + Machine = 2, } public static partial class Math { @@ -534,6 +614,27 @@ public enum MidpointRounding AwayFromZero = 1, ToEven = 0, } + public sealed class OperatingSystem + { + private OperatingSystem() { } + public OperatingSystem(System.PlatformID platform, System.Version version) { } + public System.PlatformID Platform { get { return default(System.PlatformID); } } + public string ServicePack { get { return default(string); } } + public System.Version Version { get { return default(System.Version); } } + public object Clone() { return default(object); } + public override string ToString() { return default(string); } + public string VersionString { get { return default(string); } } + } + public enum PlatformID + { + Win32S = 0, + Win32Windows = 1, + Win32NT = 2, + WinCE = 3, + Unix = 4, + Xbox = 5, + MacOSX = 6 + } public partial class Progress : System.IProgress { public Progress() { } diff --git a/src/System.Runtime.Extensions/src/Resources/Strings.resx b/src/System.Runtime.Extensions/src/Resources/Strings.resx index 2e5ebea11836..3e75e9d6b559 100644 --- a/src/System.Runtime.Extensions/src/Resources/Strings.resx +++ b/src/System.Runtime.Extensions/src/Resources/Strings.resx @@ -228,4 +228,13 @@ Unknown error '{0}'. + + Illegal enum value: {0} + + + Computer name could not be obtained. + + + OSVersion's call to GetVersionEx failed + \ No newline at end of file diff --git a/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj b/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj index 3a40d2eb2de9..cbc4819b4ec2 100644 --- a/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj +++ b/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj @@ -37,6 +37,12 @@ + + + + + + @@ -64,9 +70,13 @@ + + + Common\System\IO\DriveInfoInternal.Win32.cs + Common\System\IO\Win32Marshal.cs @@ -76,20 +86,65 @@ Common\Interop\Windows\mincore\Interop.Errors.cs + + Common\Interop\Windows\mincore\Interop.ExpandEnvironmentStringsW.cs + Common\Interop\Windows\mincore\Interop.FormatMessage.cs + + Common\Interop\Windows\mincore\Interop.GetComputerNameW.cs + + + Common\Interop\Windows\mincore\Interop.GetCurrentDirectory.cs + + + Common\Interop\Windows\mincore\Interop.GetCurrentProcess_IntPtr.cs + Common\Interop\Windows\mincore\Interop.GetFullPathNameW.cs + + Common\Interop\Windows\Interop.GetLogicalDrive.cs + + + Common\Interop\Windows\mincore\Interop.GetLongPathName.cs + + + Common\Interop\Windows\mincore\Interop.GetLongPathNameW.cs + + + Common\Interop\Windows\mincore\Interop.GetSystemDirectoryW.cs + + + Common\Interop\Windows\mincore\Interop.GetSystemInfo.cs + Common\Interop\Windows\mincore\Interop.GetTempFileNameW.cs Common\Interop\Windows\mincore\Interop.GetTempPathW.cs - - Common\Interop\Windows\mincore\Interop.GetLongPathNameW.cs + + Common\Interop\Windows\mincore\Interop.GetTickCount64.cs + + + Common\Interop\Windows\mincore\Interop.GetUserNameW.cs + + + Common\Interop\Windows\mincore\Interop.GetUserNameExW.cs + + + Common\Interop\Windows\mincore\Interop.GetVersionExW.cs + + + Common\Interop\Windows\mincore\Interop.IsWow64Process_IntPtr.cs + + + Common\Interop\Windows\mincore\Interop.LookupAccountNameW.cs + + + Common\Interop\Windows\mincore\Interop.MaxLengths.cs Common\Interop\Windows\mincore\Interop.QueryPerformanceCounter.cs @@ -97,6 +152,12 @@ Common\Interop\Windows\mincore\Interop.QueryPerformanceFrequency.cs + + Common\Interop\Windows\mincore\Interop.SetCurrentDirectory.cs + + + Common\Interop\Windows\mincore\Interop.SYSTEM_INFO.cs + Common\System\IO\PathInternal.Windows.cs @@ -116,6 +177,7 @@ + @@ -127,21 +189,42 @@ Common\Interop\Unix\Interop.IOErrors.cs + + Common\Interop\Unix\System.Native\Interop.ChDir.cs + Common\Interop\Unix\System.Native\Interop.Close.cs Common\Interop\Unix\System.Native\Interop.GetCwd.cs + + Common\Interop\Unix\System.Native\Interop.GetEUid.cs + + + Common\Interop\Unix\System.Native\Interop.GetHostName.cs + Common\Interop\Unix\System.Native\Interop.GetTimestamp.cs + + Common\Interop\Unix\System.Native\Interop.GetPwUid.cs + + + Common\Interop\Unix\System.Native\Interop.GetUnixRelease.cs + Common\Interop\Unix\System.Native\Interop.MksTemps.cs + + Common\Interop\Unix\System.Native\Interop.MountPoints.cs + Common\Interop\Unix\System.Native\Interop.PathConf.cs + + Common\Interop\Unix\System.Native\Interop.SysConf.cs + Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Initialization.cs diff --git a/src/System.Runtime.Extensions/src/System/Environment.SpecialFolder.cs b/src/System.Runtime.Extensions/src/System/Environment.SpecialFolder.cs new file mode 100644 index 000000000000..07af8e34e4c2 --- /dev/null +++ b/src/System.Runtime.Extensions/src/System/Environment.SpecialFolder.cs @@ -0,0 +1,115 @@ +// 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 + { + public enum SpecialFolder + { + ApplicationData = SpecialFolderValues.CSIDL_APPDATA, + CommonApplicationData = SpecialFolderValues.CSIDL_COMMON_APPDATA, + LocalApplicationData = SpecialFolderValues.CSIDL_LOCAL_APPDATA, + Cookies = SpecialFolderValues.CSIDL_COOKIES, + Desktop = SpecialFolderValues.CSIDL_DESKTOP, + Favorites = SpecialFolderValues.CSIDL_FAVORITES, + History = SpecialFolderValues.CSIDL_HISTORY, + InternetCache = SpecialFolderValues.CSIDL_INTERNET_CACHE, + Programs = SpecialFolderValues.CSIDL_PROGRAMS, + MyComputer = SpecialFolderValues.CSIDL_DRIVES, + MyMusic = SpecialFolderValues.CSIDL_MYMUSIC, + MyPictures = SpecialFolderValues.CSIDL_MYPICTURES, + MyVideos = SpecialFolderValues.CSIDL_MYVIDEO, + Recent = SpecialFolderValues.CSIDL_RECENT, + SendTo = SpecialFolderValues.CSIDL_SENDTO, + StartMenu = SpecialFolderValues.CSIDL_STARTMENU, + Startup = SpecialFolderValues.CSIDL_STARTUP, + System = SpecialFolderValues.CSIDL_SYSTEM, + Templates = SpecialFolderValues.CSIDL_TEMPLATES, + DesktopDirectory = SpecialFolderValues.CSIDL_DESKTOPDIRECTORY, + Personal = SpecialFolderValues.CSIDL_PERSONAL, + MyDocuments = SpecialFolderValues.CSIDL_PERSONAL, + ProgramFiles = SpecialFolderValues.CSIDL_PROGRAM_FILES, + CommonProgramFiles = SpecialFolderValues.CSIDL_PROGRAM_FILES_COMMON, + AdminTools = SpecialFolderValues.CSIDL_ADMINTOOLS, + CDBurning = SpecialFolderValues.CSIDL_CDBURN_AREA, + CommonAdminTools = SpecialFolderValues.CSIDL_COMMON_ADMINTOOLS, + CommonDocuments = SpecialFolderValues.CSIDL_COMMON_DOCUMENTS, + CommonMusic = SpecialFolderValues.CSIDL_COMMON_MUSIC, + CommonOemLinks = SpecialFolderValues.CSIDL_COMMON_OEM_LINKS, + CommonPictures = SpecialFolderValues.CSIDL_COMMON_PICTURES, + CommonStartMenu = SpecialFolderValues.CSIDL_COMMON_STARTMENU, + CommonPrograms = SpecialFolderValues.CSIDL_COMMON_PROGRAMS, + CommonStartup = SpecialFolderValues.CSIDL_COMMON_STARTUP, + CommonDesktopDirectory = SpecialFolderValues.CSIDL_COMMON_DESKTOPDIRECTORY, + CommonTemplates = SpecialFolderValues.CSIDL_COMMON_TEMPLATES, + CommonVideos = SpecialFolderValues.CSIDL_COMMON_VIDEO, + Fonts = SpecialFolderValues.CSIDL_FONTS, + NetworkShortcuts = SpecialFolderValues.CSIDL_NETHOOD, + PrinterShortcuts = SpecialFolderValues.CSIDL_PRINTHOOD, + UserProfile = SpecialFolderValues.CSIDL_PROFILE, + CommonProgramFilesX86 = SpecialFolderValues.CSIDL_PROGRAM_FILES_COMMONX86, + ProgramFilesX86 = SpecialFolderValues.CSIDL_PROGRAM_FILESX86, + Resources = SpecialFolderValues.CSIDL_RESOURCES, + LocalizedResources = SpecialFolderValues.CSIDL_RESOURCES_LOCALIZED, + SystemX86 = SpecialFolderValues.CSIDL_SYSTEMX86, + Windows = SpecialFolderValues.CSIDL_WINDOWS, + } + + // These values are specific to Windows and are known to SHGetFolderPath, however they are + // also the values used in the SpecialFolder enum. As such, we keep them as constants + // with their Win32 names, but keep them here rather than in Interop.mincore as they're + // used on all platforms. + private static class SpecialFolderValues + { + internal const int CSIDL_APPDATA = 0x001a; + internal const int CSIDL_COMMON_APPDATA = 0x0023; + internal const int CSIDL_LOCAL_APPDATA = 0x001c; + internal const int CSIDL_COOKIES = 0x0021; + internal const int CSIDL_FAVORITES = 0x0006; + internal const int CSIDL_HISTORY = 0x0022; + internal const int CSIDL_INTERNET_CACHE = 0x0020; + internal const int CSIDL_PROGRAMS = 0x0002; + internal const int CSIDL_RECENT = 0x0008; + internal const int CSIDL_SENDTO = 0x0009; + internal const int CSIDL_STARTMENU = 0x000b; + internal const int CSIDL_STARTUP = 0x0007; + internal const int CSIDL_SYSTEM = 0x0025; + internal const int CSIDL_TEMPLATES = 0x0015; + internal const int CSIDL_DESKTOPDIRECTORY = 0x0010; + internal const int CSIDL_PERSONAL = 0x0005; + internal const int CSIDL_PROGRAM_FILES = 0x0026; + internal const int CSIDL_PROGRAM_FILES_COMMON = 0x002b; + internal const int CSIDL_DESKTOP = 0x0000; + internal const int CSIDL_DRIVES = 0x0011; + internal const int CSIDL_MYMUSIC = 0x000d; + internal const int CSIDL_MYPICTURES = 0x0027; + + internal const int CSIDL_ADMINTOOLS = 0x0030; // \Start Menu\Programs\Administrative Tools + internal const int CSIDL_CDBURN_AREA = 0x003b; // USERPROFILE\Local Settings\Application Data\Microsoft\CD Burning + internal const int CSIDL_COMMON_ADMINTOOLS = 0x002f; // All Users\Start Menu\Programs\Administrative Tools + internal const int CSIDL_COMMON_DOCUMENTS = 0x002e; // All Users\Documents + internal const int CSIDL_COMMON_MUSIC = 0x0035; // All Users\My Music + internal const int CSIDL_COMMON_OEM_LINKS = 0x003a; // Links to All Users OEM specific apps + internal const int CSIDL_COMMON_PICTURES = 0x0036; // All Users\My Pictures + internal const int CSIDL_COMMON_STARTMENU = 0x0016; // All Users\Start Menu + internal const int CSIDL_COMMON_PROGRAMS = 0X0017; // All Users\Start Menu\Programs + internal const int CSIDL_COMMON_STARTUP = 0x0018; // All Users\Startup + internal const int CSIDL_COMMON_DESKTOPDIRECTORY = 0x0019; // All Users\Desktop + internal const int CSIDL_COMMON_TEMPLATES = 0x002d; // All Users\Templates + internal const int CSIDL_COMMON_VIDEO = 0x0037; // All Users\My Video + internal const int CSIDL_FONTS = 0x0014; // windows\fonts + internal const int CSIDL_MYVIDEO = 0x000e; // "My Videos" folder + internal const int CSIDL_NETHOOD = 0x0013; // %APPDATA%\Microsoft\Windows\Network Shortcuts + internal const int CSIDL_PRINTHOOD = 0x001b; // %APPDATA%\Microsoft\Windows\Printer Shortcuts + internal const int CSIDL_PROFILE = 0x0028; // %USERPROFILE% (%SystemDrive%\Users\%USERNAME%) + internal const int CSIDL_PROGRAM_FILES_COMMONX86 = 0x002c; // x86 Program Files\Common on RISC + internal const int CSIDL_PROGRAM_FILESX86 = 0x002a; // x86 C:\Program Files on RISC + internal const int CSIDL_RESOURCES = 0x0038; // %windir%\Resources + internal const int CSIDL_RESOURCES_LOCALIZED = 0x0039; // %windir%\resources\0409 (code page) + internal const int CSIDL_SYSTEMX86 = 0x0029; // %windir%\system32 + internal const int CSIDL_WINDOWS = 0x0024; // GetWindowsDirectory() + } + } +} diff --git a/src/System.Runtime.Extensions/src/System/Environment.SpecialFolderOption.cs b/src/System.Runtime.Extensions/src/System/Environment.SpecialFolderOption.cs new file mode 100644 index 000000000000..312ac5c7105e --- /dev/null +++ b/src/System.Runtime.Extensions/src/System/Environment.SpecialFolderOption.cs @@ -0,0 +1,26 @@ +// 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 + { + public enum SpecialFolderOption + { + None = 0, + Create = SpecialFolderOptionValues.CSIDL_FLAG_CREATE, + DoNotVerify = SpecialFolderOptionValues.CSIDL_FLAG_DONT_VERIFY, + } + + // These values are specific to Windows and are known to SHGetFolderPath, however they are + // also the values used in the SpecialFolderOption enum. As such, we keep them as constants + // with their Win32 names, but keep them here rather than in Interop.mincore as they're + // used on all platforms. + private static class SpecialFolderOptionValues + { + internal const int CSIDL_FLAG_CREATE = 0x8000; // force folder creation in SHGetFolderPath + internal const int CSIDL_FLAG_DONT_VERIFY = 0x4000; // return an unverified folder path + } + } +} diff --git a/src/System.Runtime.Extensions/src/System/Environment.Unix.cs b/src/System.Runtime.Extensions/src/System/Environment.Unix.cs new file mode 100644 index 000000000000..45e500f86eb3 --- /dev/null +++ b/src/System.Runtime.Extensions/src/System/Environment.Unix.cs @@ -0,0 +1,205 @@ +// 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.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace System +{ + public static partial class Environment + { + private static string CurrentDirectoryCore + { + get { return Interop.Sys.GetCwd(); } + set { Interop.CheckIo(Interop.Sys.ChDir(value), value, isDirectory: true); } + } + + private static string ExpandEnvironmentVariablesCore(string name) + { + StringBuilder result = StringBuilderCache.Acquire(); + + int lastPos = 0, pos; + while (lastPos < name.Length && (pos = name.IndexOf('%', lastPos + 1)) >= 0) + { + if (name[lastPos] == '%') + { + string key = name.Substring(lastPos + 1, pos - lastPos - 1); + string value = GetEnvironmentVariable(key); + if (value != null) + { + result.Append(value); + lastPos = pos + 1; + continue; + } + } + result.Append(name.Substring(lastPos, pos - lastPos)); + lastPos = pos; + } + result.Append(name.Substring(lastPos)); + + return StringBuilderCache.GetStringAndRelease(result); + } + + private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option) + { + string home = GetEnvironmentVariable("HOME"); + + switch (folder) + { + case SpecialFolder.Personal: // same as SpecialFolder.MyDocuments + if (!string.IsNullOrEmpty(home)) + { + return home; + } + break; + + // TODO: Add more special folder handling + } + + throw new PlatformNotSupportedException(); + } + + public static string[] GetLogicalDrives() => Interop.Sys.GetAllMountPoints().ToArray(); + + private static bool Is64BitOperatingSystemWhen32BitProcess => false; + + public static string MachineName => Interop.Sys.GetHostName(); + + public static string NewLine => "\n"; + + private static Lazy s_osVersion = new Lazy(() => + { + int major = 0, minor = 0, build = 0, revision = 0; + + // Get the uname's utsname.release. Then parse it for the first four numbers found. + // This isn't perfect, but Version already doesn't map exactly to all possible release + // formats, e.g. + string release = Interop.Sys.GetUnixRelease(); + 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') + { + num = (num * 10) + (c - '0'); + } + else break; + } + return num; + } + + public static int ProcessorCount => (int)Interop.Sys.SysConf(Interop.Sys.SysConfName._SC_NPROCESSORS_ONLN); + + public static string SystemDirectory => GetFolderPathCore(SpecialFolder.System, SpecialFolderOption.None); + + public static int SystemPageSize => (int)Interop.Sys.SysConf(Interop.Sys.SysConfName._SC_PAGESIZE); + + public static int TickCount + { + get + { + // TODO: While this is functional, it would be better performing to put an implementation into System.Native + // similar to what's used in the libcoreclr's GetTickCount() implementation. + return (int)(Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency * 1000); + } + } + + public static unsafe string UserName + { + get + { + // First try with a buffer that should suffice for 99% of cases. + string username; + const int BufLen = 1024; + byte* stackBuf = stackalloc byte[BufLen]; + if (TryGetUserNameFromPasswd(stackBuf, BufLen, out username)) + { + return username; + } + + // Fallback to heap allocations if necessary, growing the buffer until + // we succeed. TryGetHomeDirectory will throw if there's an unexpected error. + int lastBufLen = BufLen; + while (true) + { + lastBufLen *= 2; + byte[] heapBuf = new byte[lastBufLen]; + fixed (byte* buf = heapBuf) + { + if (TryGetUserNameFromPasswd(buf, heapBuf.Length, out username)) + { + return username; + } + } + } + + } + } + + private static unsafe bool TryGetUserNameFromPasswd(byte* buf, int bufLen, out string path) + { + // Call getpwuid_r to get the passwd struct + Interop.Sys.Passwd passwd; + int error = Interop.Sys.GetPwUidR(Interop.Sys.GetEUid(), out passwd, buf, bufLen); + + // If the call succeeds, give back the user name retrieved + if (error == 0) + { + Debug.Assert(passwd.Name != null); + path = Marshal.PtrToStringAnsi((IntPtr)passwd.Name); + return true; + } + + // If the current user's entry could not be found, give back null, + // but still return true as false indicates the buffer was too small. + if (error == -1) + { + path = null; + return true; + } + + var errorInfo = new Interop.ErrorInfo(error); + + // If the call failed because the buffer was too small, return false to + // indicate the caller should try again with a larger buffer. + if (errorInfo.Error == Interop.Error.ERANGE) + { + path = null; + return false; + } + + // Otherwise, fail. + throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno); + } + + public static string UserDomainName => MachineName; + } +} diff --git a/src/System.Runtime.Extensions/src/System/Environment.Windows.cs b/src/System.Runtime.Extensions/src/System/Environment.Windows.cs new file mode 100644 index 000000000000..63ebacd995c8 --- /dev/null +++ b/src/System.Runtime.Extensions/src/System/Environment.Windows.cs @@ -0,0 +1,290 @@ +// 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.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; + +namespace System +{ + public static partial class Environment + { + private static readonly Lazy s_isAppX = new Lazy(() => + { + // TODO: Determine the right way to get at this information from outside of System.Private.Corelib + try + { + Type appDomain = typeof(object).Assembly.GetType("System.AppDomain", throwOnError: true); + bool isAppXModel = (bool)appDomain.GetMethod("IsAppXModel", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null); + bool isAppXDesignMode = (bool)appDomain.GetMethod("IsAppXDesignMode", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null); + return isAppXModel && !isAppXDesignMode; + } + catch { return true; } + }); + + private static string CurrentDirectoryCore + { + get + { + // TODO: Combine into Common with System.IO.FileSystem's Directory's implementation (or have it delegate to this one). + + StringBuilder sb = StringBuilderCache.Acquire(Interop.mincore.MAX_PATH + 1); + if (Interop.mincore.GetCurrentDirectory(sb.Capacity, sb) == 0) + { + StringBuilderCache.Release(sb); + throw Win32Marshal.GetExceptionForLastWin32Error(); + } + string currentDirectory = sb.ToString(); + + // Note that if we have somehow put our command prompt into short + // file name mode (i.e. by running edlin or a DOS grep, etc), then + // this will return a short file name. + if (currentDirectory.IndexOf('~') >= 0) + { + int r = Interop.mincore.GetLongPathName(currentDirectory, sb, sb.Capacity); + if (r == 0 || r >= Interop.mincore.MAX_PATH) + { + int errorCode = r >= Interop.mincore.MAX_PATH ? + Interop.mincore.Errors.ERROR_FILENAME_EXCED_RANGE : + Marshal.GetLastWin32Error(); + + if (errorCode != Interop.mincore.Errors.ERROR_FILE_NOT_FOUND && + errorCode != Interop.mincore.Errors.ERROR_PATH_NOT_FOUND && + errorCode != Interop.mincore.Errors.ERROR_INVALID_FUNCTION && + errorCode != Interop.mincore.Errors.ERROR_ACCESS_DENIED) + { + StringBuilderCache.Release(sb); + throw Win32Marshal.GetExceptionForWin32Error(errorCode); + } + } + + currentDirectory = sb.ToString(); + } + + StringBuilderCache.Release(sb); + return currentDirectory; + } + set + { + if (!Interop.mincore.SetCurrentDirectory(value)) + { + int errorCode = Marshal.GetLastWin32Error(); + throw Win32Marshal.GetExceptionForWin32Error( + errorCode == Interop.mincore.Errors.ERROR_FILE_NOT_FOUND ? Interop.mincore.Errors.ERROR_PATH_NOT_FOUND : errorCode, + value); + } + } + } + + private static string ExpandEnvironmentVariablesCore(string name) + { + if (s_isAppX.Value) + { + // If environment variables are not available, behave as if not defined. + return name; + } + + int currentSize = 100; + StringBuilder result = StringBuilderCache.Acquire(currentSize); // A somewhat reasonable default size + + result.Length = 0; + int size = Interop.mincore.ExpandEnvironmentStringsW(name, result, currentSize); + if (size == 0) + { + StringBuilderCache.Release(result); + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + } + + while (size > currentSize) + { + currentSize = size; + result.Capacity = currentSize; + result.Length = 0; + + size = Interop.mincore.ExpandEnvironmentStringsW(name, result, currentSize); + if (size == 0) + { + StringBuilderCache.Release(result); + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + } + } + + return StringBuilderCache.GetStringAndRelease(result); + } + + private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option) + { + // TODO: SHGetFolderPath is not available in the approved API list + throw new PlatformNotSupportedException(); + } + + public static string[] GetLogicalDrives() => DriveInfoInternal.GetLogicalDrives(); + + private static bool Is64BitOperatingSystemWhen32BitProcess + { + get + { + bool isWow64; + return + !s_isAppX.Value && + Interop.mincore.IsWow64Process(Interop.mincore.GetCurrentProcess(), out isWow64) && + isWow64; + } + } + + public static string MachineName + { + get + { + if (s_isAppX.Value) + { + throw new PlatformNotSupportedException(); + } + + string name = Interop.mincore.GetComputerName(); + if (name == null) + { + throw new InvalidOperationException(SR.InvalidOperation_ComputerName); + } + return name; + } + } + + public static string NewLine => "\r\n"; + + private static Lazy s_osVersion = new Lazy(() => + { + if (s_isAppX.Value) + { + // GetVersionExW isn't available. We could throw a PlatformNotSupportedException, but we can + // at least hand back Win32NT to highlight that we're on Windows rather than Unix. + return new OperatingSystem(PlatformID.Win32NT, new Version(0, 0)); + } + + var version = new Interop.mincore.OSVERSIONINFOEX { dwOSVersionInfoSize = Marshal.SizeOf() }; + if (!Interop.mincore.GetVersionExW(ref version)) + { + throw new InvalidOperationException(SR.InvalidOperation_GetVersion); + } + + return new OperatingSystem( + PlatformID.Win32NT, + new Version(version.dwMajorVersion, version.dwMinorVersion, version.dwBuildNumber, (version.wServicePackMajor << 16) | version.wServicePackMinor), + version.szCSDVersion); + }); + + + public static int ProcessorCount + { + get + { + if (!s_isAppX.Value) + { + // TODO: Need to use GetLogicalProcessorInformationEx to get the number of + // cores for when there's more than one CPU group, e.g. > 64 cores + } + + var info = default(Interop.mincore.SYSTEM_INFO); + Interop.mincore.GetSystemInfo(out info); + return info.dwNumberOfProcessors; + } + } + + public static string SystemDirectory + { + get + { + if (s_isAppX.Value) + { + throw new PlatformNotSupportedException(); + } + + StringBuilder sb = StringBuilderCache.Acquire(Path.MaxPath); + if (Interop.mincore.GetSystemDirectoryW(sb, Path.MaxPath) == 0) + { + StringBuilderCache.Release(sb); + throw Win32Marshal.GetExceptionForLastWin32Error(); + } + return StringBuilderCache.GetStringAndRelease(sb); + } + } + + public static int SystemPageSize + { + get + { + var info = default(Interop.mincore.SYSTEM_INFO); + Interop.mincore.GetSystemInfo(out info); + return info.dwPageSize; + } + } + + public static int TickCount => (int)Interop.mincore.GetTickCount64(); + + public static string UserName + { + get + { + if (s_isAppX.Value) + { + throw new PlatformNotSupportedException(); + } + + const int UNLEN = 254; + StringBuilder sb = StringBuilderCache.Acquire(UNLEN + 1); + int size = sb.Capacity; + + try + { + if (Interop.mincore.GetUserNameW(sb, ref size)) + { + return StringBuilderCache.GetStringAndRelease(sb); + } + } + catch (EntryPointNotFoundException) + { + // not available on Windows 7 + } + + StringBuilderCache.Release(sb); + return string.Empty; + } + } + + public static string UserDomainName + { + get + { + if (s_isAppX.Value) + { + throw new PlatformNotSupportedException(); + } + + var domainName = new StringBuilder(1024); + uint domainNameLen = (uint)domainName.Capacity; + if (Interop.mincore.GetUserNameExW(Interop.mincore.NameSamCompatible, domainName, ref domainNameLen) == 1) + { + string samName = domainName.ToString(); + int index = samName.IndexOf('\\'); + if (index != -1) + { + return samName.Substring(0, index); + } + } + domainNameLen = (uint)domainName.Capacity; + + byte[] sid = new byte[1024]; + int sidLen = sid.Length; + int peUse; + if (!Interop.mincore.LookupAccountNameW(null, UserName, sid, ref sidLen, domainName, ref domainNameLen, out peUse)) + { + throw new InvalidOperationException(Win32Marshal.GetExceptionForLastWin32Error().Message); + } + + return domainName.ToString(); + } + } + } +} diff --git a/src/System.Runtime.Extensions/src/System/Environment.cs b/src/System.Runtime.Extensions/src/System/Environment.cs new file mode 100644 index 000000000000..2720f0a25098 --- /dev/null +++ b/src/System.Runtime.Extensions/src/System/Environment.cs @@ -0,0 +1,217 @@ +// 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 Internal.Runtime.Augments; +using System; +using System.Collections; +using System.IO; +using System.Text; + +namespace System +{ + public static partial class Environment + { + public static string CommandLine + { + get + { + StringBuilder sb = StringBuilderCache.Acquire(); + + foreach (string arg in GetCommandLineArgs()) + { + bool containsQuotes = false, containsWhitespace = false; + foreach (char c in arg) + { + if (char.IsWhiteSpace(c)) + { + containsWhitespace = true; + } + else if (c == '"') + { + containsQuotes = true; + } + } + + string quote = containsWhitespace ? "\"" : ""; + string formattedArg = containsQuotes && containsWhitespace ? arg.Replace("\"", "\\\"") : arg; + + sb.Append(quote).Append(formattedArg).Append(quote).Append(' '); + } + + if (sb.Length > 0) + { + sb.Length--; + } + + return StringBuilderCache.GetStringAndRelease(sb); + } + } + + public static string CurrentDirectory + { + get { return CurrentDirectoryCore; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (value.Length == 0) + { + throw new ArgumentException(SR.Argument_PathEmpty, nameof(value)); + } + + CurrentDirectoryCore = value; + } + } + + public static int CurrentManagedThreadId => EnvironmentAugments.CurrentManagedThreadId; + + public static void Exit(int exitCode) => EnvironmentAugments.Exit(exitCode); + + public static int ExitCode { get { return EnvironmentAugments.ExitCode; } set { EnvironmentAugments.ExitCode = value; } } + + public static void FailFast(string message) => FailFast(message, exception: null); + + public static void FailFast(string message, Exception exception) => EnvironmentAugments.FailFast(message, exception); + + public static string ExpandEnvironmentVariables(string name) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (name.Length == 0) + { + return name; + } + + return ExpandEnvironmentVariablesCore(name); + } + + public static string[] GetCommandLineArgs() => EnvironmentAugments.GetCommandLineArgs(); + + public static string GetEnvironmentVariable(string variable) => GetEnvironmentVariable(variable, EnvironmentVariableTarget.Process); + + public static string GetEnvironmentVariable(string variable, EnvironmentVariableTarget target) => EnvironmentAugments.GetEnvironmentVariable(variable, target); + + public static IDictionary GetEnvironmentVariables() => GetEnvironmentVariables(EnvironmentVariableTarget.Process); + + public static IDictionary GetEnvironmentVariables(EnvironmentVariableTarget target) => EnvironmentAugments.GetEnvironmentVariables(target); + + public static string GetFolderPath(SpecialFolder folder) => GetFolderPath(folder, SpecialFolderOption.None); + + public static string GetFolderPath(SpecialFolder folder, SpecialFolderOption option) + { + if (!Enum.IsDefined(typeof(SpecialFolder), folder)) + { + throw new ArgumentOutOfRangeException(nameof(folder), folder, SR.Format(SR.Arg_EnumIllegalVal, folder)); + } + + if (option != SpecialFolderOption.None && !Enum.IsDefined(typeof(SpecialFolderOption), option)) + { + throw new ArgumentOutOfRangeException(nameof(option), option, SR.Format(SR.Arg_EnumIllegalVal, option)); + } + + return GetFolderPathCore(folder, option); + } + + public static bool HasShutdownStarted => EnvironmentAugments.HasShutdownStarted; + + public static bool Is64BitProcess => IntPtr.Size == 8; + + public static bool Is64BitOperatingSystem => Is64BitProcess || Is64BitOperatingSystemWhen32BitProcess; + + public static void SetEnvironmentVariable(string variable, string value) => SetEnvironmentVariable(variable, value, EnvironmentVariableTarget.Process); + + public static void SetEnvironmentVariable(string variable, string value, EnvironmentVariableTarget target) => EnvironmentAugments.SetEnvironmentVariable(variable, value, target); + + public static OperatingSystem OSVersion => s_osVersion.Value; + + public static string StackTrace => EnvironmentAugments.StackTrace; + + public static bool UserInteractive => true; + + public static Version Version + { + // Previously this represented the File version of mscorlib.dll. Many other libraries in the framework and outside took dependencies on the first three parts of this version + // remaining constant throughout 4.x. From 4.0 to 4.5.2 this was fine since the file version only incremented the last part. Starting with 4.6 we switched to a file versioning + // scheme that matched the product version. In order to preserve compatibility with existing libraries, this needs to be hard-coded. + get { return new Version(4, 0, 30319, 42000); } + } + + public static long WorkingSet + { + get + { + // Use reflection to access the implementation in System.Diagnostics.Process.dll. While far from ideal, + // we do this to avoid duplicating the Windows, Linux, macOS, and potentially other platform-specific implementations + // present in Process. If it proves important, we could look at separating that functionality out of Process into + // Common files which could also be included here. + Type processType = Type.GetType("System.Diagnostics.Process, System.Diagnostics.Process, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", throwOnError: false); + IDisposable currentProcess = processType?.GetMethod("GetCurrentProcess")?.Invoke(null, null) as IDisposable; + if (currentProcess != null) + { + try + { + object result = processType.GetProperty("WorkingSet64")?.GetMethod?.Invoke(currentProcess, null); + if (result is long) return (long)result; + } + finally { currentProcess.Dispose(); } + } + + // Could not get the current working set. + return 0; + } + } + } +} + +namespace Internal.Runtime.Augments +{ + // TODO: Temporary mechanism for getting at System.Private.Corelib's runtime-based Environment functionality. + // This should be moved to a different "internal" class in System.Private.Corelib, exposed to corefx but not in a contract, + // so that it may be accessed from System.Runtime.Extensions without needing to use reflection. (Our build environment + // doesn't currently appear to support extern aliases, or else we could simply call the relevant functionality on the Environment + // in Corelib directly.) In the meantime, we create delegates to the various pieces of functionality. + internal static class EnvironmentAugments + { + private static readonly Type s_environment = typeof(object).Assembly.GetType("System.Environment", throwOnError: true); + + private static readonly Lazy> s_currentManagedThreadId = CreateGetter>("CurrentManagedThreadId"); + private static readonly Lazy> s_exitCodeGet = CreateGetter>("ExitCode"); + private static readonly Lazy> s_exitCodeSet = CreateSetter>("ExitCode"); + private static readonly Lazy> s_hasShutdownStarted = CreateGetter>("HasShutdownStarted"); + private static readonly Lazy> s_stackTrace = CreateGetter>("StackTrace"); + + private static readonly Lazy> s_exit = CreateMethod>("Exit", new[] { typeof(int) }); + private static readonly Lazy> s_failFast = CreateMethod>("FailFast", new[] { typeof(string), typeof(Exception) }); + private static readonly Lazy> s_getCommandLineArgs = CreateMethod>("GetCommandLineArgs", Array.Empty()); + private static readonly Lazy> s_getEnvironmentVariable = CreateMethod>("GetEnvironmentVariable", new[] { typeof(string) }); + private static readonly Lazy> s_getEnvironmentVariables = CreateMethod>("GetEnvironmentVariables", Array.Empty()); + private static readonly Lazy> s_setEnvironmentVariable = CreateMethod>("SetEnvironmentVariable", new[] { typeof(string), typeof(string) }); + + private static Lazy CreateMethod(string name, Type[] argTypes) => + new Lazy(() => (TDelegate)(object)Delegate.CreateDelegate(typeof(TDelegate), s_environment.GetMethod(name, argTypes), throwOnBindFailure: true)); + private static Lazy CreateGetter(string name) => + new Lazy(() => (TDelegate)(object)Delegate.CreateDelegate(typeof(TDelegate), s_environment.GetProperty(name).GetGetMethod(), throwOnBindFailure: true)); + private static Lazy CreateSetter(string name) => + new Lazy(() => (TDelegate)(object)Delegate.CreateDelegate(typeof(TDelegate), s_environment.GetProperty(name).GetSetMethod(), throwOnBindFailure: true)); + + public static int CurrentManagedThreadId => s_currentManagedThreadId.Value(); + public static int ExitCode { get { return s_exitCodeGet.Value(); } set { s_exitCodeSet.Value(value); } } + public static bool HasShutdownStarted => s_hasShutdownStarted.Value(); + public static string StackTrace => s_stackTrace.Value(); + + public static void Exit(int exitCode) => s_exit.Value(ExitCode); + public static void FailFast(string message, Exception error) => s_failFast.Value(message, error); + public static string[] GetCommandLineArgs() => s_getCommandLineArgs.Value(); + + public static string GetEnvironmentVariable(string name, EnvironmentVariableTarget target) => s_getEnvironmentVariable.Value(name); // TODO: Use target when it's available + public static IDictionary GetEnvironmentVariables(EnvironmentVariableTarget target) => s_getEnvironmentVariables.Value(); // TODO: use target when it's available + public static void SetEnvironmentVariable(string name, string value, EnvironmentVariableTarget target) => s_setEnvironmentVariable.Value(name, value); // TODO: use target when it's available + } +} diff --git a/src/System.Runtime.Extensions/src/System/EnvironmentVariableTarget.cs b/src/System.Runtime.Extensions/src/System/EnvironmentVariableTarget.cs new file mode 100644 index 000000000000..42b93215697b --- /dev/null +++ b/src/System.Runtime.Extensions/src/System/EnvironmentVariableTarget.cs @@ -0,0 +1,13 @@ +// 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 enum EnvironmentVariableTarget + { + Process = 0, + User = 1, + Machine = 2, + } +} diff --git a/src/System.Runtime.Extensions/src/System/OperatingSystem.cs b/src/System.Runtime.Extensions/src/System/OperatingSystem.cs new file mode 100644 index 000000000000..30bcff253a7c --- /dev/null +++ b/src/System.Runtime.Extensions/src/System/OperatingSystem.cs @@ -0,0 +1,78 @@ +// 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.Diagnostics; + +namespace System +{ + public sealed class OperatingSystem + { + private readonly Version _version; + private readonly PlatformID _platform; + private readonly string _servicePack; + private string _versionString; + + private OperatingSystem() { } + + public OperatingSystem(PlatformID platform, Version version) : + this(platform, version, null) { } + + internal OperatingSystem(PlatformID platform, Version version, string servicePack) + { + if (platform < PlatformID.Win32S || platform > PlatformID.MacOSX) + { + throw new ArgumentOutOfRangeException(nameof(platform), platform, SR.Arg_EnumIllegalVal); + } + + if (version == null) + { + throw new ArgumentNullException(nameof(version)); + } + + _platform = platform; + _version = version; + _servicePack = servicePack; + } + + public PlatformID Platform => _platform; + + public string ServicePack => _servicePack ?? string.Empty; + + public Version Version => _version; + + public object Clone() => new OperatingSystem(_platform, _version, _servicePack); + + public override string ToString() => VersionString; + + public string VersionString + { + get + { + if (_versionString == null) + { + string os; + switch (_platform) + { + case PlatformID.Win32S: os = "Microsoft Win32S "; break; + case PlatformID.Win32Windows: os = (_version.Major > 4 || (_version.Major == 4 && _version.Minor > 0)) ? "Microsoft Windows 98 " : "Microsoft Windows 95 "; break; + case PlatformID.Win32NT: os = "Microsoft Windows NT "; break; + case PlatformID.WinCE: os = "Microsoft Windows CE "; break; + case PlatformID.Unix: os = "Unix "; break; + case PlatformID.Xbox: os = "Xbox "; break; + case PlatformID.MacOSX: os = "Mac OS X "; break; + default: + Debug.Fail($"Unknown platform {_platform}"); + os = " "; break; + } + + _versionString = string.IsNullOrEmpty(_servicePack) ? + os + _version.ToString() : + os + _version.ToString(3) + " " + _servicePack; + } + + return _versionString; + } + } + } +} diff --git a/src/System.Runtime.Extensions/src/System/PlatformID.cs b/src/System.Runtime.Extensions/src/System/PlatformID.cs new file mode 100644 index 000000000000..9ad8d2e887fe --- /dev/null +++ b/src/System.Runtime.Extensions/src/System/PlatformID.cs @@ -0,0 +1,17 @@ +// 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 enum PlatformID + { + Win32S = 0, + Win32Windows = 1, + Win32NT = 2, + WinCE = 3, + Unix = 4, + Xbox = 5, + MacOSX = 6 + } +} diff --git a/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.csproj b/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.csproj index 0268190bc916..5c0c414074e8 100644 --- a/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.csproj +++ b/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.csproj @@ -1,4 +1,4 @@ - + Windows_Debug @@ -21,6 +21,7 @@ + diff --git a/src/System.Runtime.Extensions/tests/System/Environment.Exit.cs b/src/System.Runtime.Extensions/tests/System/Environment.Exit.cs index 71e0186890b1..96232c883017 100644 --- a/src/System.Runtime.Extensions/tests/System/Environment.Exit.cs +++ b/src/System.Runtime.Extensions/tests/System/Environment.Exit.cs @@ -10,13 +10,18 @@ namespace System.Tests { public class Environment_Exit : RemoteExecutorTestBase { + public static object[][] ExitCodeValues = new object[][] + { + new object[] { 0 }, + new object[] { 1 }, + new object[] { 42 }, + new object[] { -1 }, + new object[] { -45 }, + new object[] { 255 }, + }; + [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(42)] - [InlineData(-1)] - [InlineData(-45)] - [InlineData(255)] + [MemberData(nameof(ExitCodeValues))] public static void CheckExitCode(int expectedExitCode) { using (Process p = RemoteInvoke(s => int.Parse(s), expectedExitCode.ToString()).Process) @@ -32,5 +37,15 @@ public static void CheckExitCode(int expectedExitCode) } } } + + [Theory] + [MemberData(nameof(ExitCodeValues))] + public static void ExitCode_Roundtrips(int exitCode) + { + Environment.ExitCode = exitCode; + Assert.Equal(exitCode, Environment.ExitCode); + + Environment.ExitCode = 0; // in case the test host has a void returning Main + } } } diff --git a/src/System.Runtime.Extensions/tests/System/Environment.GetCommandLineArgs.cs b/src/System.Runtime.Extensions/tests/System/Environment.GetCommandLineArgs.cs index 2cc52fb64ad9..c8e26cf2ff6a 100644 --- a/src/System.Runtime.Extensions/tests/System/Environment.GetCommandLineArgs.cs +++ b/src/System.Runtime.Extensions/tests/System/Environment.GetCommandLineArgs.cs @@ -57,15 +57,19 @@ public static void RemoteInvoke(string[] args) switch (args.Length) { case 1: - RemoteInvoke((arg) => { return CheckCommandLineArgs(new string[] { arg }); }, args[0]); + RemoteInvoke((arg) => CheckCommandLineArgs(new string[] { arg }), args[0]); break; case 2: - RemoteInvoke((arg1, arg2) => { return CheckCommandLineArgs(new string[] { arg1, arg2 }); }, args[0], args[1]); + RemoteInvoke((arg1, arg2) => CheckCommandLineArgs(new string[] { arg1, arg2 }), args[0], args[1]); break; case 3: - RemoteInvoke((arg1, arg2, arg3) => { return CheckCommandLineArgs(new string[] { arg1, arg2, arg3 }); }, args[0], args[1], args[2]); + RemoteInvoke((arg1, arg2, arg3) => CheckCommandLineArgs(new string[] { arg1, arg2, arg3 }), args[0], args[1], args[2]); + break; + + default: + Assert.True(false, "Unexpected number of args passed to test"); break; } @@ -73,13 +77,12 @@ public static void RemoteInvoke(string[] args) public static int CheckCommandLineArgs(string[] args) { - string[] cmdLineArgs = Environment.GetCommandLineArgs(); + string cmdLine = Environment.CommandLine; Assert.InRange(cmdLineArgs.Length, 4, int.MaxValue); /*AppName, AssemblyName, TypeName, MethodName*/ Assert.True(cmdLineArgs[0].Contains(TestConsoleApp)); /*The host returns the fullName*/ - Type t = typeof(GetCommandLineArgs); MethodInfo mi = t.GetMethod("CheckCommandLineArgs"); Assembly a = t.GetTypeInfo().Assembly; diff --git a/src/System.Runtime.Extensions/tests/System/Environment.GetEnvironmentVariable.cs b/src/System.Runtime.Extensions/tests/System/Environment.GetEnvironmentVariable.cs index 776ccc23f392..7d3a7f960e65 100644 --- a/src/System.Runtime.Extensions/tests/System/Environment.GetEnvironmentVariable.cs +++ b/src/System.Runtime.Extensions/tests/System/Environment.GetEnvironmentVariable.cs @@ -25,7 +25,7 @@ public void EmptyVariableReturnsNull() } [Fact] - [PlatformSpecific(PlatformID.Windows)] // GetEnvironmentVariable by design doesn't respect changes via setenv + [PlatformSpecific(Xunit.PlatformID.Windows)] // GetEnvironmentVariable by design doesn't respect changes via setenv public void RandomLongVariableNameCanRoundTrip() { // NOTE: The limit of 32766 characters enforced by desktop diff --git a/src/System.Runtime.Extensions/tests/System/Environment.NewLine.cs b/src/System.Runtime.Extensions/tests/System/Environment.NewLine.cs index 18cea6a6ad27..3cff98ca1e01 100644 --- a/src/System.Runtime.Extensions/tests/System/Environment.NewLine.cs +++ b/src/System.Runtime.Extensions/tests/System/Environment.NewLine.cs @@ -8,14 +8,14 @@ namespace System.Tests { public class EnvironmentNewLine { - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Fact] public void Windows_NewLineTest() { Assert.Equal("\r\n", Environment.NewLine); } - [PlatformSpecific(PlatformID.AnyUnix)] + [PlatformSpecific(Xunit.PlatformID.AnyUnix)] [Fact] public void Unix_NewLineTest() { diff --git a/src/System.Runtime.Extensions/tests/System/Environment.ProcessorCount.cs b/src/System.Runtime.Extensions/tests/System/Environment.ProcessorCount.cs index 4807d88c3fdb..4be494b75a1b 100644 --- a/src/System.Runtime.Extensions/tests/System/Environment.ProcessorCount.cs +++ b/src/System.Runtime.Extensions/tests/System/Environment.ProcessorCount.cs @@ -9,7 +9,7 @@ namespace System.Tests { public class EnvironmentProcessorCount { - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Fact] public void Windows_ProcessorCountTest() { @@ -26,7 +26,7 @@ public void Windows_ProcessorCountTest() Assert.Equal(expected, actual); } - [PlatformSpecific(PlatformID.AnyUnix)] + [PlatformSpecific(Xunit.PlatformID.AnyUnix)] [Fact] public void Unix_ProcessorCountTest() { diff --git a/src/System.Runtime.Extensions/tests/System/EnvironmentTests.cs b/src/System.Runtime.Extensions/tests/System/EnvironmentTests.cs new file mode 100644 index 000000000000..f4354246cd2a --- /dev/null +++ b/src/System.Runtime.Extensions/tests/System/EnvironmentTests.cs @@ -0,0 +1,238 @@ +// 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.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace System.Tests +{ + public class EnvironmentTests : RemoteExecutorTestBase + { + [Fact] + public void CurrentDirectory_Null_Path_Throws_ArgumentNullException() + { + Assert.Throws("value", () => Environment.CurrentDirectory = null); + } + + [Fact] + public void CurrentDirectory_Empty_Path_Throws_ArgumentException() + { + Assert.Throws("value", () => Environment.CurrentDirectory = string.Empty); + } + + [Fact] + public void CurrentDirectory_SetToNonExistentDirectory_ThrowsDirectoryNotFoundException() + { + Assert.Throws(() => Environment.CurrentDirectory = GetTestFilePath()); + } + + [Fact] + public void CurrentDirectory_SetToValidOtherDirectory() + { + RemoteInvoke(() => + { + Environment.CurrentDirectory = TestDirectory; + Assert.Equal(Directory.GetCurrentDirectory(), Environment.CurrentDirectory); + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + // On OSX, the temp directory /tmp/ is a symlink to /private/tmp, so setting the current + // directory to a symlinked path will result in GetCurrentDirectory returning the absolute + // path that followed the symlink. + Assert.Equal(TestDirectory, Directory.GetCurrentDirectory()); + } + + return SuccessExitCode; + }).Dispose(); + } + + [Fact] + public void CurrentManagedThreadId_Idempotent() + { + Assert.Equal(Environment.CurrentManagedThreadId, Environment.CurrentManagedThreadId); + } + + [Fact] + public void CurrentManagedThreadId_DifferentForActiveThreads() + { + var ids = new HashSet(); + Barrier b = new Barrier(10); + Task.WaitAll((from i in Enumerable.Range(0, b.ParticipantCount) + select Task.Factory.StartNew(() => + { + b.SignalAndWait(); + lock (ids) ids.Add(Environment.CurrentManagedThreadId); + b.SignalAndWait(); + }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default)).ToArray()); + Assert.Equal(b.ParticipantCount, ids.Count); + } + + [Fact] + public void HasShutdownStarted_FalseWhileExecuting() + { + Assert.False(Environment.HasShutdownStarted); + } + + [Fact] + public void Is64BitProcess_MatchesIntPtrSize() + { + Assert.Equal(IntPtr.Size == 8, Environment.Is64BitProcess); + } + + [Fact] + public void Is64BitOperatingSystem_TrueIf64BitProcess() + { + if (Environment.Is64BitProcess) + { + Assert.True(Environment.Is64BitOperatingSystem); + } + } + + [Fact] + [PlatformSpecific(Xunit.PlatformID.AnyUnix)] + public void Is64BitOperatingSystem_Unix_TrueIff64BitProcess() + { + Assert.Equal(Environment.Is64BitProcess, Environment.Is64BitOperatingSystem); + } + + [Fact] + public void OSVersion_Idempotent() + { + Assert.Same(Environment.OSVersion, Environment.OSVersion); + } + + [Fact] + public void OSVersion_MatchesPlatform() + { + PlatformID id = Environment.OSVersion.Platform; + Assert.Equal( + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? PlatformID.Win32NT : PlatformID.Unix, + id); + } + + [Fact] + public void OSVersion_ValidVersion() + { + Version version = Environment.OSVersion.Version; + string versionString = Environment.OSVersion.VersionString; + + Assert.False(string.IsNullOrWhiteSpace(versionString), "Expected non-empty version string"); + Assert.True(version.Major > 0); + + Assert.Contains(version.ToString(2), versionString); + Assert.Contains(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Windows" : "Unix", versionString); + } + + [Fact] + public void SystemPageSize_Valid() + { + int pageSize = Environment.SystemPageSize; + Assert.Equal(pageSize, Environment.SystemPageSize); + + Assert.True(pageSize > 0, "Expected positive page size"); + Assert.True((pageSize & (pageSize - 1)) == 0, "Expected power-of-2 page size"); + } + + [Fact] + public void UserInteractive_True() + { + Assert.True(Environment.UserInteractive); + } + + [Fact] + public void UserName_Valid() + { + Assert.False(string.IsNullOrWhiteSpace(Environment.UserName)); + } + + [Fact] + public void UserDomainName_Valid() + { + Assert.False(string.IsNullOrWhiteSpace(Environment.UserDomainName)); + } + + [Fact] + [PlatformSpecific(Xunit.PlatformID.AnyUnix)] + public void UserDomainName_Unix_MatchesMachineName() + { + Assert.Equal(Environment.MachineName, Environment.UserDomainName); + } + + [Fact] + public void Version_MatchesFixedVersion() + { + Assert.Equal(new Version(4, 0, 30319, 42000), Environment.Version); + } + + [Fact] + public void WorkingSet_Valid() + { + Assert.True(Environment.WorkingSet > 0, "Expected positive WorkingSet value"); + } + + [Fact] + public void FailFast_ExpectFailureExitCode() + { + using (Process p = RemoteInvoke(() => { Environment.FailFast("message"); return SuccessExitCode; }).Process) + { + p.WaitForExit(); + Assert.NotEqual(SuccessExitCode, p.ExitCode); + } + + using (Process p = RemoteInvoke(() => { Environment.FailFast("message", new Exception("uh oh")); return SuccessExitCode; }).Process) + { + p.WaitForExit(); + Assert.NotEqual(SuccessExitCode, p.ExitCode); + } + } + + [Fact] + [PlatformSpecific(Xunit.PlatformID.AnyUnix)] + public void GetFolderPath_Unix_PersonalIsHome() + { + Assert.Equal(Environment.GetEnvironmentVariable("HOME"), Environment.GetFolderPath(Environment.SpecialFolder.Personal)); + Assert.Equal(Environment.GetEnvironmentVariable("HOME"), Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)); + } + + [Fact] + [PlatformSpecific(Xunit.PlatformID.AnyUnix)] + public void GetLogicalDrives_Unix_AtLeastOneIsRoot() + { + string[] drives = Environment.GetLogicalDrives(); + Assert.NotNull(drives); + Assert.True(drives.Length > 0, "Expected at least one drive"); + Assert.All(drives, d => Assert.NotNull(d)); + Assert.Contains(drives, d => d == "/"); + } + + [Fact] + [PlatformSpecific(Xunit.PlatformID.Windows)] + public void GetLogicalDrives_Windows_MatchesExpectedLetters() + { + string[] drives = Environment.GetLogicalDrives(); + + uint mask = (uint)GetLogicalDrives(); + var bits = new BitArray(new[] { (int)mask }); + + Assert.Equal(bits.Cast().Count(b => b), drives.Length); + for (int i = 0; i < drives.Length; i++) + { + if (bits[i]) + { + Assert.Contains((char)('A' + i), drives[i]); + } + } + } + + [DllImport("api-ms-win-core-file-l1-1-0.dll", SetLastError = true)] + internal static extern int GetLogicalDrives(); + } +} diff --git a/src/System.Runtime.Extensions/tests/System/IO/Path.Combine.cs b/src/System.Runtime.Extensions/tests/System/IO/Path.Combine.cs index f35cb13cc282..827b4b092e37 100644 --- a/src/System.Runtime.Extensions/tests/System/IO/Path.Combine.cs +++ b/src/System.Runtime.Extensions/tests/System/IO/Path.Combine.cs @@ -131,7 +131,7 @@ public static void ContainsInvalidCharWithoutRootedAfterArgumentNull() } [Fact] - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] public static void ContainsInvalidCharWithoutRootedAfterArgumentNull_Windows() { //any path contains invalid character without rooted after (AE) @@ -153,7 +153,7 @@ public static void ContainsInvalidCharWithRootedAfterArgumentNull() } [Fact] - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] public static void ContainsInvalidCharWithRootedAfterArgumentNull_Windows() { //any path contains invalid character with rooted after (AE) diff --git a/src/System.Runtime.Extensions/tests/System/IO/PathTests.cs b/src/System.Runtime.Extensions/tests/System/IO/PathTests.cs index e85841a96645..aa33a6afda7f 100644 --- a/src/System.Runtime.Extensions/tests/System/IO/PathTests.cs +++ b/src/System.Runtime.Extensions/tests/System/IO/PathTests.cs @@ -55,7 +55,7 @@ public static void GetDirectoryName(string path, string expected) [InlineData(@"..\..\files.txt", @"..\..")] [InlineData(@"C:\", null)] [InlineData(@"C:", null)] - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] public static void GetDirectoryName_Windows(string path, string expected) { Assert.Equal(expected, Path.GetDirectoryName(path)); @@ -68,7 +68,7 @@ public static void GetDirectoryName_Windows(string path, string expected) [InlineData(@"dir/baz/bar", @"dir/baz")] [InlineData(@"../../files.txt", @"../..")] [InlineData(@"/", null)] - [PlatformSpecific(PlatformID.AnyUnix)] + [PlatformSpecific(Xunit.PlatformID.AnyUnix)] public static void GetDirectoryName_Unix(string path, string expected) { Assert.Equal(expected, Path.GetDirectoryName(path)); @@ -82,7 +82,7 @@ public static void GetDirectoryName_CurrentDirectory() Assert.Equal(null, Path.GetDirectoryName(Path.GetPathRoot(curDir))); } - [PlatformSpecific(PlatformID.AnyUnix)] + [PlatformSpecific(Xunit.PlatformID.AnyUnix)] [Fact] public static void GetDirectoryName_ControlCharacters_Unix() { @@ -110,7 +110,7 @@ public static void GetExtension(string path, string expected) Assert.Equal(!string.IsNullOrEmpty(expected), Path.HasExtension(path)); } - [PlatformSpecific(PlatformID.AnyUnix)] + [PlatformSpecific(Xunit.PlatformID.AnyUnix)] [Theory] [InlineData("file.e xe", ".e xe")] [InlineData("file. ", ". ")] @@ -143,7 +143,7 @@ public static void GetFileName(string path, string expected) Assert.Equal(expected, Path.GetFileName(path)); } - [PlatformSpecific(PlatformID.AnyUnix)] + [PlatformSpecific(Xunit.PlatformID.AnyUnix)] [Fact] public static void GetFileName_Unix() { @@ -185,7 +185,7 @@ public static void GetPathRoot() Assert.False(Path.IsPathRooted("file.exe")); } - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Theory] [InlineData(@"\\test\unc\path\to\something", @"\\test\unc")] [InlineData(@"\\a\b\c\d\e", @"\\a\b")] @@ -203,7 +203,7 @@ public static void GetPathRoot_Windows_UncAndExtended(string value, string expec Assert.Equal(expected, Path.GetPathRoot(value)); } - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Theory] [InlineData(@"C:", @"C:")] [InlineData(@"C:\", @"C:\")] @@ -218,7 +218,7 @@ public static void GetPathRoot_Windows(string value, string expected) Assert.Equal(expected, Path.GetPathRoot(value)); } - [PlatformSpecific(PlatformID.AnyUnix)] + [PlatformSpecific(Xunit.PlatformID.AnyUnix)] [Fact] public static void GetPathRoot_Unix() { @@ -304,7 +304,7 @@ public static void GetTempPath_Default() Assert.True(Directory.Exists(tmpPath)); } - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Theory] [InlineData(@"C:\Users\someuser\AppData\Local\Temp\", @"C:\Users\someuser\AppData\Local\Temp")] [InlineData(@"C:\Users\someuser\AppData\Local\Temp\", @"C:\Users\someuser\AppData\Local\Temp\")] @@ -316,7 +316,7 @@ public static void GetTempPath_SetEnvVar_Windows(string expected, string newTemp GetTempPath_SetEnvVar("TMP", expected, newTempPath); } - [PlatformSpecific(PlatformID.AnyUnix)] + [PlatformSpecific(Xunit.PlatformID.AnyUnix)] [Theory] [InlineData("/tmp/", "/tmp")] [InlineData("/tmp/", "/tmp/")] @@ -413,7 +413,7 @@ public static void GetFullPath_BasicExpansions(string path, string expected) Assert.Equal(expected, Path.GetFullPath(path)); } - [PlatformSpecific(PlatformID.AnyUnix)] + [PlatformSpecific(Xunit.PlatformID.AnyUnix)] [Fact] public static void GetFullPath_Unix_Whitespace() { @@ -423,7 +423,7 @@ public static void GetFullPath_Unix_Whitespace() Assert.Equal(Path.Combine(curDir, "\r\n"), Path.GetFullPath("\r\n")); } - [PlatformSpecific(PlatformID.AnyUnix)] + [PlatformSpecific(Xunit.PlatformID.AnyUnix)] [Theory] [InlineData("http://www.microsoft.com")] [InlineData("file://somefile")] @@ -435,7 +435,7 @@ public static void GetFullPath_Unix_URIsAsFileNames(string uriAsFileName) Path.GetFullPath(uriAsFileName)); } - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Fact] public static void GetFullPath_Windows_NormalizedLongPathTooLong() { @@ -452,7 +452,7 @@ public static void GetFullPath_Windows_NormalizedLongPathTooLong() Assert.NotNull(Path.GetFullPath(longPath.ToString())); } - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Fact] public static void GetFullPath_Windows_AlternateDataStreamsNotSupported() { @@ -461,7 +461,7 @@ public static void GetFullPath_Windows_AlternateDataStreamsNotSupported() Assert.Throws(() => Path.GetFullPath(@"C:\some\bad:path")); } - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Fact] public static void GetFullPath_Windows_URIFormatNotSupported() { @@ -470,7 +470,7 @@ public static void GetFullPath_Windows_URIFormatNotSupported() Assert.Throws(() => Path.GetFullPath("file://www.microsoft.com")); } - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Theory] [InlineData(@"bad::$DATA")] [InlineData(@"C :")] @@ -481,7 +481,7 @@ public static void GetFullPath_Windows_NotSupportedExceptionPaths(string path) Assert.Throws(() => Path.GetFullPath(path)); } - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Theory] [InlineData(@"C:...")] [InlineData(@"C:...\somedir")] @@ -495,7 +495,7 @@ public static void GetFullPath_Windows_LegacyArgumentExceptionPaths(string path) Path.GetFullPath(path); } - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Fact] public static void GetFullPath_Windows_MaxPathNotTooLong() { @@ -503,14 +503,14 @@ public static void GetFullPath_Windows_MaxPathNotTooLong() Path.GetFullPath(@"C:\" + new string('a', 255) + @"\"); } - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Fact] public static void GetFullPath_Windows_PathTooLong() { Assert.Throws(() => Path.GetFullPath(@"C:\" + new string('a', short.MaxValue) + @"\")); } - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Theory] [InlineData(@"C:\", @"C:\")] [InlineData(@"C:\.", @"C:\")] @@ -523,7 +523,7 @@ public static void GetFullPath_Windows_RelativeRoot(string path, string expected Assert.Equal(Path.GetFullPath(path), expected); } - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Fact] public static void GetFullPath_Windows_StrangeButLegalPaths() { @@ -542,7 +542,7 @@ public static void GetFullPath_Windows_StrangeButLegalPaths() Path.GetFullPath(curDir + Path.DirectorySeparatorChar + ".. " + Path.DirectorySeparatorChar)); } - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Theory] [InlineData(@"\\?\C:\ ")] [InlineData(@"\\?\C:\ \ ")] @@ -599,7 +599,7 @@ public static void GetFullPath_Windows_ValidExtendedPaths(string path) } } - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Theory] [InlineData(@"\\server\share", @"\\server\share")] [InlineData(@"\\server\share", @" \\server\share")] @@ -619,7 +619,7 @@ public static void GetFullPath_Windows_UNC_Valid(string expected, string input) Assert.Equal(expected, Path.GetFullPath(input)); } - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Theory] [InlineData(@"\\")] [InlineData(@"\\server")] @@ -631,7 +631,7 @@ public static void GetFullPath_Windows_UNC_Invalid(string invalidPath) Assert.Throws(() => Path.GetFullPath(invalidPath)); } - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [ConditionalFact(nameof(PlatformDetection) + "." + nameof(PlatformDetection.IsNotWindowsNanoServer))] public static void GetFullPath_Windows_83Paths() { @@ -676,7 +676,7 @@ public static void GetFullPath_Windows_83Paths() } } - [PlatformSpecific(PlatformID.Windows)] + [PlatformSpecific(Xunit.PlatformID.Windows)] [Theory] [InlineData('*')] [InlineData('?')] From 282ad11461c480a282a529c38d8fc353bc03663b Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 5 Jul 2016 23:32:12 -0400 Subject: [PATCH 2/5] Address PR feedback PR feedback: - Remove AppX checks and split Environment.Windows into Environment.Windows, Environment.CoreCLR, and Environment.NETNative - Change marshaling of service pack version field in OSVERSIONINFOEX - Forward TickCount to EnvironmentAugments rather than implementing it via a P/Invoke - Move environment variables support from coreclr to corefx Plus: - Use GetLogicalProcessorInformationEx to get the processor count, and cache it - Use GetUserNameExW instead of GetUserNameW, as it's available on more platforms - Fix compilation for .NET Native - Fixed a test --- .../Unix/System.Native/Interop.GetEnviron.cs | 16 + .../Unix/System.Native/Interop.MountPoints.cs | 15 +- .../mincore/Interop.EnvironmentVariables.cs | 27 ++ .../Interop.ExpandEnvironmentStringsW.cs | 15 - ...nterop.GetLogicalProcessorInformationEx.cs | 46 ++ .../Windows/mincore/Interop.GetVersionExW.cs | 5 +- .../Generic/LowLevelDictionary.IDictionary.cs | 149 +++++++ .../Collections/Generic/LowLevelDictionary.cs | 44 +- .../src/System/IO/DriveInfoInternal.Unix.cs | 5 +- src/Native/System.Native/pal_process.cpp | 5 + src/Native/System.Native/pal_process.h | 2 + .../src/System/IO/DriveInfo.Unix.cs | 4 +- .../src/Resources/Strings.resx | 12 + .../src/System.Runtime.Extensions.csproj | 60 +-- .../src/System/Environment.CoreCLR.cs | 406 ++++++++++++++++++ .../src/System/Environment.NETNative.cs | 62 +++ .../src/System/Environment.Unix.cs | 112 ++++- .../src/System/Environment.Windows.cs | 197 +-------- .../src/System/Environment.cs | 122 +++++- .../tests/System/EnvironmentTests.cs | 6 +- 20 files changed, 1000 insertions(+), 310 deletions(-) create mode 100644 src/Common/src/Interop/Unix/System.Native/Interop.GetEnviron.cs create mode 100644 src/Common/src/Interop/Windows/mincore/Interop.EnvironmentVariables.cs delete mode 100644 src/Common/src/Interop/Windows/mincore/Interop.ExpandEnvironmentStringsW.cs create mode 100644 src/Common/src/Interop/Windows/mincore/Interop.GetLogicalProcessorInformationEx.cs create mode 100644 src/Common/src/System/Collections/Generic/LowLevelDictionary.IDictionary.cs create mode 100644 src/System.Runtime.Extensions/src/System/Environment.CoreCLR.cs create mode 100644 src/System.Runtime.Extensions/src/System/Environment.NETNative.cs diff --git a/src/Common/src/Interop/Unix/System.Native/Interop.GetEnviron.cs b/src/Common/src/Interop/Unix/System.Native/Interop.GetEnviron.cs new file mode 100644 index 000000000000..25faaa38b865 --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Native/Interop.GetEnviron.cs @@ -0,0 +1,16 @@ +// 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; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class Sys + { + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetEnviron", SetLastError = true)] + internal static unsafe extern byte** GetEnviron(); + } +} diff --git a/src/Common/src/Interop/Unix/System.Native/Interop.MountPoints.cs b/src/Common/src/Interop/Unix/System.Native/Interop.MountPoints.cs index 4e6552c5dfff..7e3119714988 100644 --- a/src/Common/src/Interop/Unix/System.Native/Interop.MountPoints.cs +++ b/src/Common/src/Interop/Unix/System.Native/Interop.MountPoints.cs @@ -16,18 +16,25 @@ internal static partial class Sys [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetAllMountPoints", SetLastError = true)] private static extern int GetAllMountPoints(MountPointFound mpf); - internal static List GetAllMountPoints() + internal static string[] GetAllMountPoints() { - List lst = new List(); + int count = 0; + var found = new string[4]; + unsafe { int result = GetAllMountPoints((byte* name) => { - lst.Add(Marshal.PtrToStringAnsi((IntPtr)name)); + if (count == found.Length) + { + Array.Resize(ref found, count * 2); + } + found[count++] = Marshal.PtrToStringAnsi((IntPtr)name); }); } - return lst; + Array.Resize(ref found, count); + return found; } } } diff --git a/src/Common/src/Interop/Windows/mincore/Interop.EnvironmentVariables.cs b/src/Common/src/Interop/Windows/mincore/Interop.EnvironmentVariables.cs new file mode 100644 index 000000000000..ad3b323524d1 --- /dev/null +++ b/src/Common/src/Interop/Windows/mincore/Interop.EnvironmentVariables.cs @@ -0,0 +1,27 @@ +// 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 System.Text; + +internal partial class Interop +{ + internal partial class mincore + { + [DllImport(Libraries.ProcessEnvironment, CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int ExpandEnvironmentStringsW(string lpSrc, [Out] StringBuilder lpDst, int nSize); + + [DllImport(Libraries.ProcessEnvironment, CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int GetEnvironmentVariableW(string lpName, [Out] StringBuilder lpValue, int size); + + [DllImport(Libraries.ProcessEnvironment, CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern bool SetEnvironmentVariableW(string lpName, string lpValue); + + [DllImport(Libraries.ProcessEnvironment, CharSet = CharSet.Unicode)] + internal static extern unsafe char* GetEnvironmentStringsW(); + + [DllImport(Libraries.ProcessEnvironment, CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern unsafe bool FreeEnvironmentStringsW(char* pStrings); + } +} diff --git a/src/Common/src/Interop/Windows/mincore/Interop.ExpandEnvironmentStringsW.cs b/src/Common/src/Interop/Windows/mincore/Interop.ExpandEnvironmentStringsW.cs deleted file mode 100644 index ebc116bb295a..000000000000 --- a/src/Common/src/Interop/Windows/mincore/Interop.ExpandEnvironmentStringsW.cs +++ /dev/null @@ -1,15 +0,0 @@ -// 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 System.Text; - -internal partial class Interop -{ - internal partial class mincore - { - [DllImport(Libraries.ProcessEnvironment, CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern int ExpandEnvironmentStringsW(string lpSrc, [Out] StringBuilder lpDst, int nSize); - } -} diff --git a/src/Common/src/Interop/Windows/mincore/Interop.GetLogicalProcessorInformationEx.cs b/src/Common/src/Interop/Windows/mincore/Interop.GetLogicalProcessorInformationEx.cs new file mode 100644 index 000000000000..bd5f7dd9fb6a --- /dev/null +++ b/src/Common/src/Interop/Windows/mincore/Interop.GetLogicalProcessorInformationEx.cs @@ -0,0 +1,46 @@ +// 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; +using System.Runtime.InteropServices; + +#pragma warning disable 0649 // fields never explicitly assigned to +#pragma warning disable 0169 // fields never used + +internal partial class Interop +{ + internal partial class mincore + { + [DllImport(Libraries.SystemInfo_L1_1, SetLastError = true)] + internal static extern bool GetLogicalProcessorInformationEx(LOGICAL_PROCESSOR_RELATIONSHIP RelationshipType, IntPtr Buffer, ref uint ReturnedLength); + + internal enum LOGICAL_PROCESSOR_RELATIONSHIP + { + RelationGroup = 4 + } + + internal struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX + { + public LOGICAL_PROCESSOR_RELATIONSHIP Relationship; + public uint Size; + public GROUP_RELATIONSHIP Group; // part of a union, but we only need the Group + } + + internal unsafe struct GROUP_RELATIONSHIP + { + private byte MaximumGroupCount; + public ushort ActiveGroupCount; + private fixed byte Reserved[20]; + public PROCESSOR_GROUP_INFO GroupInfo; // actually a GroupInfo[ANYSIZE_ARRAY], so used for its address + } + + internal unsafe struct PROCESSOR_GROUP_INFO + { + public byte MaximumProcessorCount; + public byte ActiveProcessorCount; + public fixed byte Reserved[38]; + public IntPtr ActiveProcessorMask; + } + } +} diff --git a/src/Common/src/Interop/Windows/mincore/Interop.GetVersionExW.cs b/src/Common/src/Interop/Windows/mincore/Interop.GetVersionExW.cs index c4512f7b9a9b..f39fd398c1a2 100644 --- a/src/Common/src/Interop/Windows/mincore/Interop.GetVersionExW.cs +++ b/src/Common/src/Interop/Windows/mincore/Interop.GetVersionExW.cs @@ -12,15 +12,14 @@ internal partial class mincore internal static extern bool GetVersionExW(ref OSVERSIONINFOEX osvi); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct OSVERSIONINFOEX + internal unsafe struct OSVERSIONINFOEX { public int dwOSVersionInfoSize; public int dwMajorVersion; public int dwMinorVersion; public int dwBuildNumber; public int dwPlatformId; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] - public string szCSDVersion; + public fixed char szCSDVersion[128]; public ushort wServicePackMajor; public ushort wServicePackMinor; public ushort wSuiteMask; diff --git a/src/Common/src/System/Collections/Generic/LowLevelDictionary.IDictionary.cs b/src/Common/src/System/Collections/Generic/LowLevelDictionary.IDictionary.cs new file mode 100644 index 000000000000..f8363bcc487d --- /dev/null +++ b/src/Common/src/System/Collections/Generic/LowLevelDictionary.IDictionary.cs @@ -0,0 +1,149 @@ +// 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.Collections.Generic +{ + internal partial class LowLevelDictionary : IDictionary + { + bool IDictionary.IsFixedSize => false; + bool IDictionary.IsReadOnly => false; + + object IDictionary.this[object key] + { + get { return this[(TKey)key]; } + set { this[(TKey)key] = (TValue)value; } + } + + ICollection IDictionary.Keys + { + get + { + if (_numEntries == 0) + { + return Array.Empty(); + } + + var keys = new TKey[_numEntries]; + int dst = 0; + for (int bucket = 0; bucket < _buckets.Length; bucket++) + { + for (Entry entry = _buckets[bucket]; entry != null; entry = entry._next) + { + keys[dst++] = entry._key; + } + } + return keys; + } + } + + ICollection IDictionary.Values + { + get + { + if (_numEntries == 0) + { + return Array.Empty(); + } + + var values = new TValue[_numEntries]; + int dst = 0; + for (int bucket = 0; bucket < _buckets.Length; bucket++) + { + for (Entry entry = _buckets[bucket]; entry != null; entry = entry._next) + { + values[dst++] = entry._value; + } + } + return values; + } + } + + public LowLevelDictionary Clone() + { + var result = new LowLevelDictionary(_numEntries); + for (int bucket = 0; bucket < _buckets.Length; bucket++) + { + for (Entry entry = _buckets[bucket]; entry != null; entry = entry._next) + { + result.Add(entry._key, entry._value); + } + } + return result; + } + + bool ICollection.IsSynchronized => false; + + object ICollection.SyncRoot => this; + + void IDictionary.Add(object key, object value) => Add((TKey)key, (TValue)value); + + bool IDictionary.Contains(object key) => Find((TKey)key) != null; + + IDictionaryEnumerator IDictionary.GetEnumerator() => new DictionaryEnumerator(this); + IEnumerator IEnumerable.GetEnumerator() => new DictionaryEnumerator(this); + + void IDictionary.Remove(object key) { Remove((TKey)key); } + + void IDictionary.Clear() => Clear(); + + void ICollection.CopyTo(Array array, int index) + { + int dst = 0; + for (int bucket = 0; bucket < _buckets.Length; bucket++) + { + for (Entry entry = _buckets[bucket]; entry != null; entry = entry._next) + { + array.SetValue(new DictionaryEntry(entry._key, entry._value), dst++); + } + } + } + + private sealed class DictionaryEnumerator : IDictionaryEnumerator + { + private readonly DictionaryEntry[] _entries; + private int _pos = -1; + + internal DictionaryEnumerator(LowLevelDictionary dict) + { + var entries = new DictionaryEntry[dict._numEntries]; + int dst = 0; + for (int bucket = 0; bucket < dict._buckets.Length; bucket++) + { + for (Entry entry = dict._buckets[bucket]; entry != null; entry = entry._next) + { + entries[dst++] = new DictionaryEntry(entry._key, entry._value); + } + } + _entries = entries; + } + + public object Current => Entry; + public object Key => Entry.Key; + public object Value => Entry.Value; + + public DictionaryEntry Entry + { + get + { + if (_pos < 0 || _pos >= _entries.Length) + { + throw new InvalidOperationException(); + } + return _entries[_pos]; + } + } + + public bool MoveNext() + { + if (_pos < _entries.Length) + { + _pos++; + } + return _pos < _entries.Length; + } + + public void Reset() { throw new NotSupportedException(); } + } + } +} diff --git a/src/Common/src/System/Collections/Generic/LowLevelDictionary.cs b/src/Common/src/System/Collections/Generic/LowLevelDictionary.cs index dca5ac0ff149..aa8e5c7e7881 100644 --- a/src/Common/src/System/Collections/Generic/LowLevelDictionary.cs +++ b/src/Common/src/System/Collections/Generic/LowLevelDictionary.cs @@ -21,7 +21,7 @@ namespace System.Collections.Generic ** behavior.) ** ===========================================================*/ - internal class LowLevelDictionary + internal partial class LowLevelDictionary { private const int DefaultSize = 17; @@ -63,7 +63,7 @@ public TValue this[TKey key] Entry entry = Find(key); if (entry == null) throw new KeyNotFoundException(); - return entry.m_value; + return entry._value; } set { @@ -73,7 +73,7 @@ public TValue this[TKey key] _version++; Entry entry = Find(key); if (entry != null) - entry.m_value = value; + entry._value = value; else UncheckedAdd(key, value); } @@ -87,7 +87,7 @@ public bool TryGetValue(TKey key, out TValue value) Entry entry = Find(key); if (entry != null) { - value = entry.m_value; + value = entry._value; return true; } return false; @@ -120,15 +120,15 @@ public bool Remove(TKey key) Entry entry = _buckets[bucket]; while (entry != null) { - if (_comparer.Equals(key, entry.m_key)) + if (_comparer.Equals(key, entry._key)) { if (prev == null) { - _buckets[bucket] = entry.m_next; + _buckets[bucket] = entry._next; } else { - prev.m_next = entry.m_next; + prev._next = entry._next; } _version++; _numEntries--; @@ -136,7 +136,7 @@ public bool Remove(TKey key) } prev = entry; - entry = entry.m_next; + entry = entry._next; } return false; } @@ -145,7 +145,7 @@ internal TValue LookupOrAdd(TKey key, TValue value) { Entry entry = Find(key); if (entry != null) - return entry.m_value; + return entry._value; UncheckedAdd(key, value); return value; } @@ -156,10 +156,10 @@ private Entry Find(TKey key) Entry entry = _buckets[bucket]; while (entry != null) { - if (_comparer.Equals(key, entry.m_key)) + if (_comparer.Equals(key, entry._key)) return entry; - entry = entry.m_next; + entry = entry._next; } return null; } @@ -167,11 +167,11 @@ private Entry Find(TKey key) private Entry UncheckedAdd(TKey key, TValue value) { Entry entry = new Entry(); - entry.m_key = key; - entry.m_value = value; + entry._key = key; + entry._value = value; int bucket = GetBucket(key); - entry.m_next = _buckets[bucket]; + entry._next = _buckets[bucket]; _buckets[bucket] = entry; _numEntries++; @@ -193,10 +193,10 @@ private void ExpandBuckets() Entry entry = _buckets[i]; while (entry != null) { - Entry nextEntry = entry.m_next; + Entry nextEntry = entry._next; - int bucket = GetBucket(entry.m_key, newNumBuckets); - entry.m_next = newBuckets[bucket]; + int bucket = GetBucket(entry._key, newNumBuckets); + entry._next = newBuckets[bucket]; newBuckets[bucket] = entry; entry = nextEntry; @@ -219,9 +219,9 @@ private int GetBucket(TKey key, int numBuckets = 0) private sealed class Entry { - public TKey m_key; - public TValue m_value; - public Entry m_next; + public TKey _key; + public TValue _value; + public Entry _next; } private Entry[] _buckets; @@ -262,7 +262,7 @@ public LowLevelDictEnumerator(LowLevelDictionary dict) while (entry != null) { entries[dst++] = entry; - entry = entry.m_next; + entry = entry._next; } } _entries = entries; @@ -278,7 +278,7 @@ public KeyValuePair Current if (_curPosition == -1 || _curPosition == _entries.Length) throw new InvalidOperationException("InvalidOperation_EnumOpCantHappen"); Entry entry = _entries[_curPosition]; - return new KeyValuePair(entry.m_key, entry.m_value); + return new KeyValuePair(entry._key, entry._value); } } diff --git a/src/Common/src/System/IO/DriveInfoInternal.Unix.cs b/src/Common/src/System/IO/DriveInfoInternal.Unix.cs index cdeb762787df..78ef95704fe9 100644 --- a/src/Common/src/System/IO/DriveInfoInternal.Unix.cs +++ b/src/Common/src/System/IO/DriveInfoInternal.Unix.cs @@ -10,9 +10,6 @@ namespace System.IO /// Contains internal volume helpers that are shared between many projects. internal static partial class DriveInfoInternal { - internal static string[] GetLogicalDrives() - { - return Interop.Sys.GetAllMountPoints().ToArray(); - } + internal static string[] GetLogicalDrives() => Interop.Sys.GetAllMountPoints(); } } diff --git a/src/Native/System.Native/pal_process.cpp b/src/Native/System.Native/pal_process.cpp index 78fe87584176..46be9252238a 100644 --- a/src/Native/System.Native/pal_process.cpp +++ b/src/Native/System.Native/pal_process.cpp @@ -535,3 +535,8 @@ extern "C" int32_t SystemNative_SchedGetAffinity(int32_t pid, intptr_t* mask) return result; } #endif + +extern "C" char** SystemNative_GetEnviron() +{ + return environ; +} diff --git a/src/Native/System.Native/pal_process.h b/src/Native/System.Native/pal_process.h index 8696d91c653c..a7a8752ed4da 100644 --- a/src/Native/System.Native/pal_process.h +++ b/src/Native/System.Native/pal_process.h @@ -286,3 +286,5 @@ extern "C" int32_t SystemNative_SchedSetAffinity(int32_t pid, intptr_t* mask); */ extern "C" int32_t SystemNative_SchedGetAffinity(int32_t pid, intptr_t* mask); #endif + +extern "C" char** SystemNative_GetEnviron(); diff --git a/src/System.IO.FileSystem.DriveInfo/src/System/IO/DriveInfo.Unix.cs b/src/System.IO.FileSystem.DriveInfo/src/System/IO/DriveInfo.Unix.cs index e3350b059e9a..6d70695de628 100644 --- a/src/System.IO.FileSystem.DriveInfo/src/System/IO/DriveInfo.Unix.cs +++ b/src/System.IO.FileSystem.DriveInfo/src/System/IO/DriveInfo.Unix.cs @@ -11,8 +11,8 @@ public sealed partial class DriveInfo { public static DriveInfo[] GetDrives() { - List mountPoints = Interop.Sys.GetAllMountPoints(); - DriveInfo[] info = new DriveInfo[mountPoints.Count]; + string[] mountPoints = Interop.Sys.GetAllMountPoints(); + DriveInfo[] info = new DriveInfo[mountPoints.Length]; for (int i = 0; i < info.Length; i++) { info[i] = new DriveInfo(mountPoints[i]); diff --git a/src/System.Runtime.Extensions/src/Resources/Strings.resx b/src/System.Runtime.Extensions/src/Resources/Strings.resx index 3e75e9d6b559..a6ff74c14b23 100644 --- a/src/System.Runtime.Extensions/src/Resources/Strings.resx +++ b/src/System.Runtime.Extensions/src/Resources/Strings.resx @@ -237,4 +237,16 @@ OSVersion's call to GetVersionEx failed + + Environment variable name cannot contain equal character. + + + Environment variable name or value is too long. + + + The first char in the string is the null character. + + + String cannot be of zero length. + \ No newline at end of file diff --git a/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj b/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj index cbc4819b4ec2..fe11de1c4473 100644 --- a/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj +++ b/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj @@ -49,6 +49,9 @@ Common\System\Collections\Generic\LowLevelDictionary.cs + + Common\System\Collections\Generic\LowLevelDictionary.IDictionary.cs + Common\System\IO\StringBuilderCache.cs @@ -86,21 +89,12 @@ Common\Interop\Windows\mincore\Interop.Errors.cs - - Common\Interop\Windows\mincore\Interop.ExpandEnvironmentStringsW.cs - Common\Interop\Windows\mincore\Interop.FormatMessage.cs - - Common\Interop\Windows\mincore\Interop.GetComputerNameW.cs - Common\Interop\Windows\mincore\Interop.GetCurrentDirectory.cs - - Common\Interop\Windows\mincore\Interop.GetCurrentProcess_IntPtr.cs - Common\Interop\Windows\mincore\Interop.GetFullPathNameW.cs @@ -113,9 +107,6 @@ Common\Interop\Windows\mincore\Interop.GetLongPathNameW.cs - - Common\Interop\Windows\mincore\Interop.GetSystemDirectoryW.cs - Common\Interop\Windows\mincore\Interop.GetSystemInfo.cs @@ -125,24 +116,9 @@ Common\Interop\Windows\mincore\Interop.GetTempPathW.cs - - Common\Interop\Windows\mincore\Interop.GetTickCount64.cs - - - Common\Interop\Windows\mincore\Interop.GetUserNameW.cs - - - Common\Interop\Windows\mincore\Interop.GetUserNameExW.cs - Common\Interop\Windows\mincore\Interop.GetVersionExW.cs - - Common\Interop\Windows\mincore\Interop.IsWow64Process_IntPtr.cs - - - Common\Interop\Windows\mincore\Interop.LookupAccountNameW.cs - Common\Interop\Windows\mincore\Interop.MaxLengths.cs @@ -167,6 +143,7 @@ + Common\Interop\Windows\BCrypt\Interop.BCryptGenRandom.cs @@ -174,6 +151,30 @@ Common\Interop\Windows\BCrypt\Interop.NTSTATUS.cs + + Common\Interop\Windows\mincore\Interop.EnvironmentVariables.cs + + + Common\Interop\Windows\mincore\Interop.GetComputerNameW.cs + + + Common\Interop\Windows\mincore\Interop.GetCurrentProcess_IntPtr.cs + + + Common\Interop\Windows\mincore\Interop.GetLogicalProcessorInformationEx.cs + + + Common\Interop\Windows\mincore\Interop.GetSystemDirectoryW.cs + + + Common\Interop\Windows\mincore\Interop.GetUserNameExW.cs + + + Common\Interop\Windows\mincore\Interop.IsWow64Process_IntPtr.cs + + + Common\Interop\Windows\mincore\Interop.LookupAccountNameW.cs + @@ -198,6 +199,9 @@ Common\Interop\Unix\System.Native\Interop.GetCwd.cs + + Common\Interop\Unix\System.Native\Interop.GetEnviron.cs + Common\Interop\Unix\System.Native\Interop.GetEUid.cs @@ -237,6 +241,7 @@ + @@ -257,6 +262,7 @@ + diff --git a/src/System.Runtime.Extensions/src/System/Environment.CoreCLR.cs b/src/System.Runtime.Extensions/src/System/Environment.CoreCLR.cs new file mode 100644 index 000000000000..2ae6ec729221 --- /dev/null +++ b/src/System.Runtime.Extensions/src/System/Environment.CoreCLR.cs @@ -0,0 +1,406 @@ +// 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 Internal.Runtime.Augments; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace System +{ + public static partial class Environment + { + public static int ExitCode { get { return EnvironmentAugments.ExitCode; } set { EnvironmentAugments.ExitCode = value; } } + + private static string ExpandEnvironmentVariablesCore(string name) + { + int currentSize = 100; + StringBuilder result = StringBuilderCache.Acquire(currentSize); // A somewhat reasonable default size + + result.Length = 0; + int size = Interop.mincore.ExpandEnvironmentStringsW(name, result, currentSize); + if (size == 0) + { + StringBuilderCache.Release(result); + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + } + + while (size > currentSize) + { + currentSize = size; + result.Capacity = currentSize; + result.Length = 0; + + size = Interop.mincore.ExpandEnvironmentStringsW(name, result, currentSize); + if (size == 0) + { + StringBuilderCache.Release(result); + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + } + } + + return StringBuilderCache.GetStringAndRelease(result); + } + + private static string GetEnvironmentVariableCore(string variable, EnvironmentVariableTarget target) + { + if (target == EnvironmentVariableTarget.Process) + { + StringBuilder sb = StringBuilderCache.Acquire(128); // a somewhat reasonable default size + int requiredSize = Interop.mincore.GetEnvironmentVariableW(variable, sb, sb.Capacity); + if (requiredSize == 0 && Marshal.GetLastWin32Error() == Interop.mincore.Errors.ERROR_ENVVAR_NOT_FOUND) + { + StringBuilderCache.Release(sb); + return null; + } + + while (requiredSize > sb.Capacity) + { + sb.Capacity = requiredSize; + sb.Length = 0; + requiredSize = Interop.mincore.GetEnvironmentVariableW(variable, sb, sb.Capacity); + } + + return StringBuilderCache.GetStringAndRelease(sb); + } + else if (target == EnvironmentVariableTarget.Machine) + { + // TODO #8533: Uncomment/fix when registry APIs available + //using (RegistryKey environmentKey = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\Session Manager\Environment", false)) + //{ + // return environmentKey?.GetValue(variable) as string ?? null; + //} + return null; + } + else + { + Debug.Assert(target == EnvironmentVariableTarget.User); + + // TODO #8533: Uncomment/fix when registry APIs available + //using (RegistryKey environmentKey = Registry.CurrentUser.OpenSubKey("Environment", false)) + //{ + // return environmentKey?.GetValue(variable) as string ?? null; + //} + return null; + } + } + + private static IDictionary GetEnvironmentVariablesCore(EnvironmentVariableTarget target) + { + var results = new Dictionary(); + + if (target == EnvironmentVariableTarget.Process) + { + // Format for GetEnvironmentStrings is: + // (=HiddenVar=value\0 | Variable=value\0)* \0 + // See the description of Environment Blocks in MSDN's + // CreateProcess page (null-terminated array of null-terminated strings). + // Note the =HiddenVar's aren't always at the beginning. + + // Copy strings out, parsing into pairs and inserting into the table. + // The first few environment variable entries start with an '='. + // The current working directory of every drive (except for those drives + // you haven't cd'ed into in your DOS window) are stored in the + // environment block (as =C:=pwd) and the program's exit code is + // as well (=ExitCode=00000000). + + char[] block = GetEnvironmentCharArray(); + for (int i = 0; i < block.Length; i++) + { + int startKey = i; + + // Skip to key. On some old OS, the environment block can be corrupted. + // Some will not have '=', so we need to check for '\0'. + while (block[i] != '=' && block[i] != '\0') i++; + if (block[i] == '\0') continue; + + // Skip over environment variables starting with '=' + if (i - startKey == 0) + { + while (block[i] != 0) i++; + continue; + } + + string key = new string(block, startKey, i - startKey); + i++; // skip over '=' + + int startValue = i; + while (block[i] != 0) i++; // Read to end of this entry + string value = new string(block, startValue, i - startValue); // skip over 0 handled by for loop's i++ + + results[key] = value; + } + } + else if (target == EnvironmentVariableTarget.Machine) + { + // TODO #8533: Uncomment/fix when registry APIs available + //using (RegistryKey environmentKey = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\Session Manager\Environment", false)) + //{ + // return GetRegistryKeyNameValuePairs(environmentKey); + //} + } + else + { + Debug.Assert(target == EnvironmentVariableTarget.User); + // TODO #8533: Uncomment/fix when registry APIs available + //using (RegistryKey environmentKey = Registry.CurrentUser.OpenSubKey("Environment", false)) + //{ + // return GetRegistryKeyNameValuePairs(environmentKey); + //} + } + + return results; + } + + // TODO #8533: Uncomment/fix when registry APIs available + //private static IDictionary GetRegistryKeyNameValuePairs(RegistryKey registryKey) + //{ + // Hashtable table = new Hashtable(20); + // if (registryKey != null) + // { + // string[] names = registryKey.GetValueNames(); + // foreach (string name in names) + // { + // string value = registryKey.GetValue(name, "").ToString(); + // table.Add(name, value); + // } + // } + // return table; + //} + + private unsafe static char[] GetEnvironmentCharArray() + { + // Format for GetEnvironmentStrings is: + // [=HiddenVar=value\0]* [Variable=value\0]* \0 + // See the description of Environment Blocks in MSDN's + // CreateProcess page (null-terminated array of null-terminated strings). + char* pStrings = Interop.mincore.GetEnvironmentStringsW(); + if (pStrings == null) + { + throw new OutOfMemoryException(); + } + try + { + // Search for terminating \0\0 (two unicode \0's). + char* p = pStrings; + while (!(*p == '\0' && *(p + 1) == '\0')) p++; + + var block = new char[(int)(p - pStrings + 1)]; + Marshal.Copy((IntPtr)pStrings, block, 0, block.Length); + return block; + } + finally + { + Interop.mincore.FreeEnvironmentStringsW(pStrings); // ignore any cleanup error + } + } + + private static bool Is64BitOperatingSystemWhen32BitProcess + { + get + { + bool isWow64; + return Interop.mincore.IsWow64Process(Interop.mincore.GetCurrentProcess(), out isWow64) && isWow64; + } + } + + public static string MachineName + { + get + { + string name = Interop.mincore.GetComputerName(); + if (name == null) + { + throw new InvalidOperationException(SR.InvalidOperation_ComputerName); + } + return name; + } + } + + private static unsafe Lazy s_osVersion = new Lazy(() => + { + var version = new Interop.mincore.OSVERSIONINFOEX { dwOSVersionInfoSize = sizeof(Interop.mincore.OSVERSIONINFOEX) }; + if (!Interop.mincore.GetVersionExW(ref version)) + { + throw new InvalidOperationException(SR.InvalidOperation_GetVersion); + } + + return new OperatingSystem( + PlatformID.Win32NT, + new Version(version.dwMajorVersion, version.dwMinorVersion, version.dwBuildNumber, (version.wServicePackMajor << 16) | version.wServicePackMinor), + Marshal.PtrToStringUni((IntPtr)version.szCSDVersion)); + }); + + private static unsafe int ProcessorCountCore + { + get + { + // Determine how much size we need for a call to GetLogicalProcessorInformationEx + uint len = 0; + if (!Interop.mincore.GetLogicalProcessorInformationEx(Interop.mincore.LOGICAL_PROCESSOR_RELATIONSHIP.RelationGroup, IntPtr.Zero, ref len) && + Marshal.GetLastWin32Error() == Interop.mincore.Errors.ERROR_INSUFFICIENT_BUFFER) + { + // Allocate that much space + Debug.Assert(len > 0); + var buffer = new byte[len]; + fixed (byte* bufferPtr = buffer) + { + // Call GetLogicalProcessorInformationEx with the allocated buffer + if (Interop.mincore.GetLogicalProcessorInformationEx(Interop.mincore.LOGICAL_PROCESSOR_RELATIONSHIP.RelationGroup, (IntPtr)bufferPtr, ref len)) + { + // Walk each SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX in the buffer, where the Size of each dictates how + // much space it's consuming. For each group relation, count the number of active processors in each of its group infos. + int processorCount = 0; + byte* ptr = bufferPtr, endPtr = bufferPtr + len; + while (ptr < endPtr) + { + var current = (Interop.mincore.SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)ptr; + if (current->Relationship == Interop.mincore.LOGICAL_PROCESSOR_RELATIONSHIP.RelationGroup) + { + Interop.mincore.PROCESSOR_GROUP_INFO* groupInfo = ¤t->Group.GroupInfo; + int groupCount = current->Group.ActiveGroupCount; + for (int i = 0; i < groupCount; i++) + { + processorCount += (groupInfo + i)->ActiveProcessorCount; + } + } + ptr += current->Size; + } + return processorCount; + } + } + } + + // GetLogicalProcessorInformationEx didn't work for some reason. Fall back to using GetSystemInfo. + return ProcessorCountFromSystemInfo; + } + } + + private static void SetEnvironmentVariableCore(string variable, string value, EnvironmentVariableTarget target) + { + if (target == EnvironmentVariableTarget.Process) + { + if (!Interop.mincore.SetEnvironmentVariableW(variable, value)) + { + int errorCode = Marshal.GetLastWin32Error(); + switch (errorCode) + { + case Interop.mincore.Errors.ERROR_ENVVAR_NOT_FOUND: // Allow user to try to clear a environment variable + return; + case Interop.mincore.Errors.ERROR_FILENAME_EXCED_RANGE: // Fix inaccurate error code from Win32 + throw new ArgumentException(SR.Argument_LongEnvVarValue, nameof(value)); + default: + throw new ArgumentException(Interop.mincore.GetMessage(errorCode)); + } + } + } + else if (target == EnvironmentVariableTarget.Machine) + { + // TODO #8533: Uncomment/fix when registry APIs available + //using (RegistryKey environmentKey = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\Session Manager\Environment", true)) + //{ + // if (environmentKey != null) + // { + // if (value == null) environmentKey.DeleteValue(variable, false); + // else environmentKey.SetValue(variable, value); + // } + //} + } + else + { + Debug.Assert(target == EnvironmentVariableTarget.User); + + // User-wide environment variables stored in the registry are limited to 255 chars for the environment variable name. + const int MaxUserEnvVariableLength = 255; + if (variable.Length >= MaxUserEnvVariableLength) + { + throw new ArgumentException(SR.Argument_LongEnvVarValue, nameof(variable)); + } + + // TODO #8533: Uncomment/fix when registry APIs available + //using (RegistryKey environmentKey = Registry.CurrentUser.OpenSubKey("Environment", true)) + //{ + // if (environmentKey != null) + // { + // if (value == null) environmentKey.DeleteValue(variable, false); + // else environmentKey.SetValue(variable, value); + // } + //} + } + + //// Desktop sends a WM_SETTINGCHANGE message to all windows. Not available on all platforms. + //Interop.mincore.SendMessageTimeout( + // new IntPtr(Interop.mincore.HWND_BROADCAST), Interop.mincore.WM_SETTINGCHANGE, + // IntPtr.Zero, "Environment", 0, 1000, IntPtr.Zero); + } + + public static string SystemDirectory + { + get + { + StringBuilder sb = StringBuilderCache.Acquire(Path.MaxPath); + if (Interop.mincore.GetSystemDirectoryW(sb, Path.MaxPath) == 0) + { + StringBuilderCache.Release(sb); + throw Win32Marshal.GetExceptionForLastWin32Error(); + } + return StringBuilderCache.GetStringAndRelease(sb); + } + } + + public static string UserName + { + get + { + // Use GetUserNameExW, as GetUserNameW isn't available on all platforms, e.g. Win7 + var domainName = new StringBuilder(1024); + uint domainNameLen = (uint)domainName.Capacity; + if (Interop.mincore.GetUserNameExW(Interop.mincore.NameSamCompatible, domainName, ref domainNameLen) == 1) + { + string samName = domainName.ToString(); + int index = samName.IndexOf('\\'); + if (index != -1) + { + return samName.Substring(index + 1); + } + } + + return string.Empty; + } + } + + public static string UserDomainName + { + get + { + var domainName = new StringBuilder(1024); + uint domainNameLen = (uint)domainName.Capacity; + if (Interop.mincore.GetUserNameExW(Interop.mincore.NameSamCompatible, domainName, ref domainNameLen) == 1) + { + string samName = domainName.ToString(); + int index = samName.IndexOf('\\'); + if (index != -1) + { + return samName.Substring(0, index); + } + } + domainNameLen = (uint)domainName.Capacity; + + byte[] sid = new byte[1024]; + int sidLen = sid.Length; + int peUse; + if (!Interop.mincore.LookupAccountNameW(null, UserName, sid, ref sidLen, domainName, ref domainNameLen, out peUse)) + { + throw new InvalidOperationException(Win32Marshal.GetExceptionForLastWin32Error().Message); + } + + return domainName.ToString(); + } + } + + } +} diff --git a/src/System.Runtime.Extensions/src/System/Environment.NETNative.cs b/src/System.Runtime.Extensions/src/System/Environment.NETNative.cs new file mode 100644 index 000000000000..9374a281b987 --- /dev/null +++ b/src/System.Runtime.Extensions/src/System/Environment.NETNative.cs @@ -0,0 +1,62 @@ +// 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.Collections.Generic; + +namespace System +{ + public static partial class Environment + { + public static int ExitCode + { + get { return 0; } + set { throw new PlatformNotSupportedException(); } + } + + private static string ExpandEnvironmentVariablesCore(string name) => name; + + private static string GetEnvironmentVariableCore(string variable, EnvironmentVariableTarget target) + { + return string.Empty; + } + + private static IDictionary GetEnvironmentVariablesCore(EnvironmentVariableTarget target) + { + return new LowLevelDictionary(); + } + + private static bool Is64BitOperatingSystemWhen32BitProcess => false; + + public static string MachineName { get { throw new PlatformNotSupportedException(); } } + + private static Lazy s_osVersion = new Lazy(() => + { + // GetVersionExW isn't available. We could throw a PlatformNotSupportedException, but we can + // at least hand back Win32NT to highlight that we're on Windows rather than Unix. + return new OperatingSystem(PlatformID.Win32NT, new Version(0, 0)); + }); + + private static int ProcessorCountCore => ProcessorCountFromSystemInfo; + + private static void SetEnvironmentVariableCore(string variable, string value, EnvironmentVariableTarget target) + { + throw new PlatformNotSupportedException(); + } + + public static string SystemDirectory + { + get { throw new PlatformNotSupportedException(); } + } + + public static string UserName + { + get { throw new PlatformNotSupportedException(); } + } + + public static string UserDomainName + { + get { throw new PlatformNotSupportedException(); } + } + } +} diff --git a/src/System.Runtime.Extensions/src/System/Environment.Unix.cs b/src/System.Runtime.Extensions/src/System/Environment.Unix.cs index 45e500f86eb3..fcf928e6722e 100644 --- a/src/System.Runtime.Extensions/src/System/Environment.Unix.cs +++ b/src/System.Runtime.Extensions/src/System/Environment.Unix.cs @@ -2,6 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Internal.Runtime.Augments; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; @@ -11,12 +14,37 @@ namespace System { public static partial class Environment { + private readonly unsafe static Lazy> s_environ = new Lazy>(() => + { + var results = new LowLevelDictionary(); + byte** environ = Interop.Sys.GetEnviron(); + if (environ != null) + { + for (byte** ptr = Interop.Sys.GetEnviron(); *ptr != null; ptr++) + { + string entry = Marshal.PtrToStringAnsi((IntPtr)(*ptr)); + int equalsPos = entry.IndexOf('='); + if (equalsPos != -1) + { + results.Add(entry.Substring(0, equalsPos), entry.Substring(equalsPos + 1)); + } + else + { + results.Add(entry, string.Empty); + } + } + } + return results; + }); + private static string CurrentDirectoryCore { get { return Interop.Sys.GetCwd(); } set { Interop.CheckIo(Interop.Sys.ChDir(value), value, isDirectory: true); } } + public static int ExitCode { get { return EnvironmentAugments.ExitCode; } set { EnvironmentAugments.ExitCode = value; } } + private static string ExpandEnvironmentVariablesCore(string name) { StringBuilder result = StringBuilderCache.Acquire(); @@ -43,6 +71,33 @@ private static string ExpandEnvironmentVariablesCore(string name) return StringBuilderCache.GetStringAndRelease(result); } + private static string GetEnvironmentVariableCore(string variable, EnvironmentVariableTarget target) + { + if (target == EnvironmentVariableTarget.Machine || target == EnvironmentVariableTarget.User) + { + return null; + } + + lock (s_environ) + { + string value; + return s_environ.Value.TryGetValue(variable, out value) ? value : null; + } + } + + private static IDictionary GetEnvironmentVariablesCore(EnvironmentVariableTarget target) + { + if (target == EnvironmentVariableTarget.Machine || target == EnvironmentVariableTarget.User) + { + return new LowLevelDictionary(); + } + + lock (s_environ) + { + return s_environ.Value.Clone(); + } + } + private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option) { string home = GetEnvironmentVariable("HOME"); @@ -62,7 +117,7 @@ private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOptio throw new PlatformNotSupportedException(); } - public static string[] GetLogicalDrives() => Interop.Sys.GetAllMountPoints().ToArray(); + public static string[] GetLogicalDrives() => Interop.Sys.GetAllMountPoints(); private static bool Is64BitOperatingSystemWhen32BitProcess => false; @@ -116,29 +171,64 @@ private static int FindAndParseNextNumber(string text, ref int pos) return num; } - public static int ProcessorCount => (int)Interop.Sys.SysConf(Interop.Sys.SysConfName._SC_NPROCESSORS_ONLN); + private static int ProcessorCountCore => (int)Interop.Sys.SysConf(Interop.Sys.SysConfName._SC_NPROCESSORS_ONLN); - public static string SystemDirectory => GetFolderPathCore(SpecialFolder.System, SpecialFolderOption.None); + private static void SetEnvironmentVariableCore(string variable, string value, EnvironmentVariableTarget target) + { + if (target == EnvironmentVariableTarget.Machine || target == EnvironmentVariableTarget.User) + { + return; + } - public static int SystemPageSize => (int)Interop.Sys.SysConf(Interop.Sys.SysConfName._SC_PAGESIZE); + int nullEnd; - public static int TickCount - { - get + // Ensure variable doesn't include a null char + nullEnd = variable.IndexOf('\0'); + if (nullEnd != -1) { - // TODO: While this is functional, it would be better performing to put an implementation into System.Native - // similar to what's used in the libcoreclr's GetTickCount() implementation. - return (int)(Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency * 1000); + variable = variable.Substring(0, nullEnd); + } + + // Ensure value doesn't include a null char + if (value != null) + { + nullEnd = value.IndexOf('\0'); + if (nullEnd != -1) + { + value = value.Substring(0, nullEnd); + } + } + + lock (s_environ) + { + // Remove the entry if the value is null, otherwise add/overwrite it + if (value == null) + { + s_environ.Value.Remove(variable); + } + else + { + s_environ.Value[variable] = value; + } } } + public static string SystemDirectory => GetFolderPathCore(SpecialFolder.System, SpecialFolderOption.None); + + public static int SystemPageSize => (int)Interop.Sys.SysConf(Interop.Sys.SysConfName._SC_PAGESIZE); + public static unsafe string UserName { get { // First try with a buffer that should suffice for 99% of cases. string username; - const int BufLen = 1024; + const int BufLen = +#if DEBUG // test fallback behavior by starting with smaller buffer size + 1; +#else + 1024; +#endif byte* stackBuf = stackalloc byte[BufLen]; if (TryGetUserNameFromPasswd(stackBuf, BufLen, out username)) { diff --git a/src/System.Runtime.Extensions/src/System/Environment.Windows.cs b/src/System.Runtime.Extensions/src/System/Environment.Windows.cs index 63ebacd995c8..76853fb1edc1 100644 --- a/src/System.Runtime.Extensions/src/System/Environment.Windows.cs +++ b/src/System.Runtime.Extensions/src/System/Environment.Windows.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.IO; -using System.Reflection; using System.Runtime.InteropServices; using System.Text; @@ -11,25 +10,10 @@ namespace System { public static partial class Environment { - private static readonly Lazy s_isAppX = new Lazy(() => - { - // TODO: Determine the right way to get at this information from outside of System.Private.Corelib - try - { - Type appDomain = typeof(object).Assembly.GetType("System.AppDomain", throwOnError: true); - bool isAppXModel = (bool)appDomain.GetMethod("IsAppXModel", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null); - bool isAppXDesignMode = (bool)appDomain.GetMethod("IsAppXDesignMode", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null); - return isAppXModel && !isAppXDesignMode; - } - catch { return true; } - }); - private static string CurrentDirectoryCore { get { - // TODO: Combine into Common with System.IO.FileSystem's Directory's implementation (or have it delegate to this one). - StringBuilder sb = StringBuilderCache.Acquire(Interop.mincore.MAX_PATH + 1); if (Interop.mincore.GetCurrentDirectory(sb.Capacity, sb) == 0) { @@ -78,42 +62,6 @@ private static string CurrentDirectoryCore } } - private static string ExpandEnvironmentVariablesCore(string name) - { - if (s_isAppX.Value) - { - // If environment variables are not available, behave as if not defined. - return name; - } - - int currentSize = 100; - StringBuilder result = StringBuilderCache.Acquire(currentSize); // A somewhat reasonable default size - - result.Length = 0; - int size = Interop.mincore.ExpandEnvironmentStringsW(name, result, currentSize); - if (size == 0) - { - StringBuilderCache.Release(result); - Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); - } - - while (size > currentSize) - { - currentSize = size; - result.Capacity = currentSize; - result.Length = 0; - - size = Interop.mincore.ExpandEnvironmentStringsW(name, result, currentSize); - if (size == 0) - { - StringBuilderCache.Release(result); - Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); - } - } - - return StringBuilderCache.GetStringAndRelease(result); - } - private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option) { // TODO: SHGetFolderPath is not available in the approved API list @@ -122,95 +70,18 @@ private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOptio public static string[] GetLogicalDrives() => DriveInfoInternal.GetLogicalDrives(); - private static bool Is64BitOperatingSystemWhen32BitProcess - { - get - { - bool isWow64; - return - !s_isAppX.Value && - Interop.mincore.IsWow64Process(Interop.mincore.GetCurrentProcess(), out isWow64) && - isWow64; - } - } - - public static string MachineName - { - get - { - if (s_isAppX.Value) - { - throw new PlatformNotSupportedException(); - } - - string name = Interop.mincore.GetComputerName(); - if (name == null) - { - throw new InvalidOperationException(SR.InvalidOperation_ComputerName); - } - return name; - } - } - public static string NewLine => "\r\n"; - private static Lazy s_osVersion = new Lazy(() => - { - if (s_isAppX.Value) - { - // GetVersionExW isn't available. We could throw a PlatformNotSupportedException, but we can - // at least hand back Win32NT to highlight that we're on Windows rather than Unix. - return new OperatingSystem(PlatformID.Win32NT, new Version(0, 0)); - } - - var version = new Interop.mincore.OSVERSIONINFOEX { dwOSVersionInfoSize = Marshal.SizeOf() }; - if (!Interop.mincore.GetVersionExW(ref version)) - { - throw new InvalidOperationException(SR.InvalidOperation_GetVersion); - } - - return new OperatingSystem( - PlatformID.Win32NT, - new Version(version.dwMajorVersion, version.dwMinorVersion, version.dwBuildNumber, (version.wServicePackMajor << 16) | version.wServicePackMinor), - version.szCSDVersion); - }); - - - public static int ProcessorCount + private static int ProcessorCountFromSystemInfo { get { - if (!s_isAppX.Value) - { - // TODO: Need to use GetLogicalProcessorInformationEx to get the number of - // cores for when there's more than one CPU group, e.g. > 64 cores - } - var info = default(Interop.mincore.SYSTEM_INFO); Interop.mincore.GetSystemInfo(out info); return info.dwNumberOfProcessors; } } - public static string SystemDirectory - { - get - { - if (s_isAppX.Value) - { - throw new PlatformNotSupportedException(); - } - - StringBuilder sb = StringBuilderCache.Acquire(Path.MaxPath); - if (Interop.mincore.GetSystemDirectoryW(sb, Path.MaxPath) == 0) - { - StringBuilderCache.Release(sb); - throw Win32Marshal.GetExceptionForLastWin32Error(); - } - return StringBuilderCache.GetStringAndRelease(sb); - } - } - public static int SystemPageSize { get @@ -220,71 +91,5 @@ public static int SystemPageSize return info.dwPageSize; } } - - public static int TickCount => (int)Interop.mincore.GetTickCount64(); - - public static string UserName - { - get - { - if (s_isAppX.Value) - { - throw new PlatformNotSupportedException(); - } - - const int UNLEN = 254; - StringBuilder sb = StringBuilderCache.Acquire(UNLEN + 1); - int size = sb.Capacity; - - try - { - if (Interop.mincore.GetUserNameW(sb, ref size)) - { - return StringBuilderCache.GetStringAndRelease(sb); - } - } - catch (EntryPointNotFoundException) - { - // not available on Windows 7 - } - - StringBuilderCache.Release(sb); - return string.Empty; - } - } - - public static string UserDomainName - { - get - { - if (s_isAppX.Value) - { - throw new PlatformNotSupportedException(); - } - - var domainName = new StringBuilder(1024); - uint domainNameLen = (uint)domainName.Capacity; - if (Interop.mincore.GetUserNameExW(Interop.mincore.NameSamCompatible, domainName, ref domainNameLen) == 1) - { - string samName = domainName.ToString(); - int index = samName.IndexOf('\\'); - if (index != -1) - { - return samName.Substring(0, index); - } - } - domainNameLen = (uint)domainName.Capacity; - - byte[] sid = new byte[1024]; - int sidLen = sid.Length; - int peUse; - if (!Interop.mincore.LookupAccountNameW(null, UserName, sid, ref sidLen, domainName, ref domainNameLen, out peUse)) - { - throw new InvalidOperationException(Win32Marshal.GetExceptionForLastWin32Error().Message); - } - - return domainName.ToString(); - } - } } } diff --git a/src/System.Runtime.Extensions/src/System/Environment.cs b/src/System.Runtime.Extensions/src/System/Environment.cs index 2720f0a25098..5e1dad69d9a1 100644 --- a/src/System.Runtime.Extensions/src/System/Environment.cs +++ b/src/System.Runtime.Extensions/src/System/Environment.cs @@ -6,12 +6,15 @@ using System; using System.Collections; using System.IO; +using System.Reflection; using System.Text; namespace System { public static partial class Environment { + private static readonly Lazy s_processorCount = new Lazy(() => ProcessorCountCore); + public static string CommandLine { get @@ -70,9 +73,7 @@ public static string CurrentDirectory public static int CurrentManagedThreadId => EnvironmentAugments.CurrentManagedThreadId; public static void Exit(int exitCode) => EnvironmentAugments.Exit(exitCode); - - public static int ExitCode { get { return EnvironmentAugments.ExitCode; } set { EnvironmentAugments.ExitCode = value; } } - + public static void FailFast(string message) => FailFast(message, exception: null); public static void FailFast(string message, Exception exception) => EnvironmentAugments.FailFast(message, exception); @@ -96,11 +97,26 @@ public static string ExpandEnvironmentVariables(string name) public static string GetEnvironmentVariable(string variable) => GetEnvironmentVariable(variable, EnvironmentVariableTarget.Process); - public static string GetEnvironmentVariable(string variable, EnvironmentVariableTarget target) => EnvironmentAugments.GetEnvironmentVariable(variable, target); + public static string GetEnvironmentVariable(string variable, EnvironmentVariableTarget target) + { + if (variable == null) + { + throw new ArgumentNullException(nameof(variable)); + } + + ValidateTarget(target); + + return GetEnvironmentVariableCore(variable, target); + } public static IDictionary GetEnvironmentVariables() => GetEnvironmentVariables(EnvironmentVariableTarget.Process); - public static IDictionary GetEnvironmentVariables(EnvironmentVariableTarget target) => EnvironmentAugments.GetEnvironmentVariables(target); + public static IDictionary GetEnvironmentVariables(EnvironmentVariableTarget target) + { + ValidateTarget(target); + + return GetEnvironmentVariablesCore(target); + } public static string GetFolderPath(SpecialFolder folder) => GetFolderPath(folder, SpecialFolderOption.None); @@ -125,14 +141,56 @@ public static string GetFolderPath(SpecialFolder folder, SpecialFolderOption opt public static bool Is64BitOperatingSystem => Is64BitProcess || Is64BitOperatingSystemWhen32BitProcess; + public static int ProcessorCount => s_processorCount.Value; + public static void SetEnvironmentVariable(string variable, string value) => SetEnvironmentVariable(variable, value, EnvironmentVariableTarget.Process); - public static void SetEnvironmentVariable(string variable, string value, EnvironmentVariableTarget target) => EnvironmentAugments.SetEnvironmentVariable(variable, value, target); + public static void SetEnvironmentVariable(string variable, string value, EnvironmentVariableTarget target) + { + const int MaxEnvVariableValueLength = 32767; + + if (variable == null) + { + throw new ArgumentNullException(nameof(variable)); + } + if (variable.Length == 0) + { + throw new ArgumentException(SR.Argument_StringZeroLength, nameof(variable)); + } + if (variable[0] == '\0') + { + throw new ArgumentException(SR.Argument_StringFirstCharIsZero, nameof(variable)); + } + if (variable.Length >= MaxEnvVariableValueLength) + { + throw new ArgumentException(SR.Argument_LongEnvVarValue, nameof(variable)); + } + if (variable.IndexOf('=') != -1) + { + throw new ArgumentException(SR.Argument_IllegalEnvVarName, nameof(variable)); + } + + if (string.IsNullOrEmpty(value) || value[0] == '\0') + { + // Explicitly null out value if it's empty + value = null; + } + else if (value.Length >= MaxEnvVariableValueLength) + { + throw new ArgumentException(SR.Argument_LongEnvVarValue, nameof(value)); + } + + ValidateTarget(target); + + SetEnvironmentVariableCore(variable, value, target); + } public static OperatingSystem OSVersion => s_osVersion.Value; public static string StackTrace => EnvironmentAugments.StackTrace; + public static int TickCount => EnvironmentAugments.TickCount; + public static bool UserInteractive => true; public static Version Version @@ -152,12 +210,12 @@ public static long WorkingSet // present in Process. If it proves important, we could look at separating that functionality out of Process into // Common files which could also be included here. Type processType = Type.GetType("System.Diagnostics.Process, System.Diagnostics.Process, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", throwOnError: false); - IDisposable currentProcess = processType?.GetMethod("GetCurrentProcess")?.Invoke(null, null) as IDisposable; + IDisposable currentProcess = processType?.GetTypeInfo().GetDeclaredMethod("GetCurrentProcess")?.Invoke(null, null) as IDisposable; if (currentProcess != null) { try { - object result = processType.GetProperty("WorkingSet64")?.GetMethod?.Invoke(currentProcess, null); + object result = processType.GetTypeInfo().GetDeclaredProperty("WorkingSet64")?.GetMethod?.Invoke(currentProcess, null); if (result is long) return (long)result; } finally { currentProcess.Dispose(); } @@ -167,6 +225,16 @@ public static long WorkingSet return 0; } } + + private static void ValidateTarget(EnvironmentVariableTarget target) + { + if (target != EnvironmentVariableTarget.Process && + target != EnvironmentVariableTarget.Machine && + target != EnvironmentVariableTarget.User) + { + throw new ArgumentOutOfRangeException(nameof(target), target, SR.Format(SR.Arg_EnumIllegalVal, target)); + } + } } } @@ -179,39 +247,47 @@ namespace Internal.Runtime.Augments // in Corelib directly.) In the meantime, we create delegates to the various pieces of functionality. internal static class EnvironmentAugments { - private static readonly Type s_environment = typeof(object).Assembly.GetType("System.Environment", throwOnError: true); + private static readonly Type s_environment = typeof(object).GetTypeInfo().Assembly.GetType("System.Environment", throwOnError: true, ignoreCase: false); private static readonly Lazy> s_currentManagedThreadId = CreateGetter>("CurrentManagedThreadId"); private static readonly Lazy> s_exitCodeGet = CreateGetter>("ExitCode"); private static readonly Lazy> s_exitCodeSet = CreateSetter>("ExitCode"); private static readonly Lazy> s_hasShutdownStarted = CreateGetter>("HasShutdownStarted"); private static readonly Lazy> s_stackTrace = CreateGetter>("StackTrace"); + private static readonly Lazy> s_tickCount = CreateGetter>("TickCount"); - private static readonly Lazy> s_exit = CreateMethod>("Exit", new[] { typeof(int) }); - private static readonly Lazy> s_failFast = CreateMethod>("FailFast", new[] { typeof(string), typeof(Exception) }); - private static readonly Lazy> s_getCommandLineArgs = CreateMethod>("GetCommandLineArgs", Array.Empty()); - private static readonly Lazy> s_getEnvironmentVariable = CreateMethod>("GetEnvironmentVariable", new[] { typeof(string) }); - private static readonly Lazy> s_getEnvironmentVariables = CreateMethod>("GetEnvironmentVariables", Array.Empty()); - private static readonly Lazy> s_setEnvironmentVariable = CreateMethod>("SetEnvironmentVariable", new[] { typeof(string), typeof(string) }); + private static readonly Lazy> s_exit = CreateMethod>("Exit", 1); + private static readonly Lazy> s_failFast = CreateMethod>("FailFast", 2); + private static readonly Lazy> s_getCommandLineArgs = CreateMethod>("GetCommandLineArgs", 0); + + private static Lazy CreateMethod(string name, int argCount) => + new Lazy(() => + { + foreach (var method in s_environment.GetTypeInfo().GetDeclaredMethods(name)) + { + ParameterInfo[] parameters = method.GetParameters(); + if (parameters.Length == argCount) + { + return (TDelegate)(object)method.CreateDelegate(typeof(TDelegate)); + } + } + return default(TDelegate); + }); - private static Lazy CreateMethod(string name, Type[] argTypes) => - new Lazy(() => (TDelegate)(object)Delegate.CreateDelegate(typeof(TDelegate), s_environment.GetMethod(name, argTypes), throwOnBindFailure: true)); private static Lazy CreateGetter(string name) => - new Lazy(() => (TDelegate)(object)Delegate.CreateDelegate(typeof(TDelegate), s_environment.GetProperty(name).GetGetMethod(), throwOnBindFailure: true)); + new Lazy(() => (TDelegate)(object)s_environment.GetTypeInfo().GetDeclaredProperty(name).GetMethod.CreateDelegate(typeof(TDelegate))); + private static Lazy CreateSetter(string name) => - new Lazy(() => (TDelegate)(object)Delegate.CreateDelegate(typeof(TDelegate), s_environment.GetProperty(name).GetSetMethod(), throwOnBindFailure: true)); + new Lazy(() => (TDelegate)(object)s_environment.GetTypeInfo().GetDeclaredProperty(name).SetMethod.CreateDelegate(typeof(TDelegate))); public static int CurrentManagedThreadId => s_currentManagedThreadId.Value(); public static int ExitCode { get { return s_exitCodeGet.Value(); } set { s_exitCodeSet.Value(value); } } public static bool HasShutdownStarted => s_hasShutdownStarted.Value(); public static string StackTrace => s_stackTrace.Value(); + public static int TickCount => s_tickCount.Value(); public static void Exit(int exitCode) => s_exit.Value(ExitCode); public static void FailFast(string message, Exception error) => s_failFast.Value(message, error); public static string[] GetCommandLineArgs() => s_getCommandLineArgs.Value(); - - public static string GetEnvironmentVariable(string name, EnvironmentVariableTarget target) => s_getEnvironmentVariable.Value(name); // TODO: Use target when it's available - public static IDictionary GetEnvironmentVariables(EnvironmentVariableTarget target) => s_getEnvironmentVariables.Value(); // TODO: use target when it's available - public static void SetEnvironmentVariable(string name, string value, EnvironmentVariableTarget target) => s_setEnvironmentVariable.Value(name, value); // TODO: use target when it's available } } diff --git a/src/System.Runtime.Extensions/tests/System/EnvironmentTests.cs b/src/System.Runtime.Extensions/tests/System/EnvironmentTests.cs index f4354246cd2a..21dbc0ee7457 100644 --- a/src/System.Runtime.Extensions/tests/System/EnvironmentTests.cs +++ b/src/System.Runtime.Extensions/tests/System/EnvironmentTests.cs @@ -223,11 +223,11 @@ public void GetLogicalDrives_Windows_MatchesExpectedLetters() var bits = new BitArray(new[] { (int)mask }); Assert.Equal(bits.Cast().Count(b => b), drives.Length); - for (int i = 0; i < drives.Length; i++) + for (int bit = 0, d = 0; bit < bits.Length; bit++) { - if (bits[i]) + if (bits[bit]) { - Assert.Contains((char)('A' + i), drives[i]); + Assert.Contains((char)('A' + bit), drives[d++]); } } } From 8ea801cdd2bc2d661b78e300064b43c8733b0471 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 6 Jul 2016 11:21:15 -0400 Subject: [PATCH 3/5] Address more PR feedback and additional fixes Main changes: - Rename Environment Windows files to .Win32.cs and .WinRT.cs - Refactor environment variable functions to help with tree shaking - Fix environ access on macOS - Remove direct Environment dependency from System.Diagnostics.Debug causing ambiguity --- src/Native/Common/pal_config.h.in | 2 + src/Native/System.Native/pal_process.cpp | 8 + src/Native/System.Native/pal_uid.cpp | 2 +- src/Native/configure.cmake | 11 ++ .../tests/DebugTests.cs | 31 ++-- .../TestsManifestNegative.cs | 4 +- .../src/System.Runtime.Extensions.csproj | 4 +- .../src/System/Environment.Unix.cs | 56 +++++-- ...onment.CoreCLR.cs => Environment.Win32.cs} | 154 ++++++++++-------- ...ment.NETNative.cs => Environment.WinRT.cs} | 32 ++-- .../src/System/Environment.cs | 37 ++++- .../Environment.SetEnvironmentVariable.cs | 1 + 12 files changed, 214 insertions(+), 128 deletions(-) rename src/System.Runtime.Extensions/src/System/{Environment.CoreCLR.cs => Environment.Win32.cs} (78%) rename src/System.Runtime.Extensions/src/System/{Environment.NETNative.cs => Environment.WinRT.cs} (66%) diff --git a/src/Native/Common/pal_config.h.in b/src/Native/Common/pal_config.h.in index 228eecc174c6..25a94af52ae2 100644 --- a/src/Native/Common/pal_config.h.in +++ b/src/Native/Common/pal_config.h.in @@ -59,6 +59,8 @@ #cmakedefine01 HAVE_GSS_SPNEGO_MECHANISM #cmakedefine01 HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X #cmakedefine01 HAVE_HEIMDAL_HEADERS +#cmakedefine01 HAVE_NSGETENVIRON +#cmakedefine01 HAVE_CRT_EXTERNS_H // Mac OS X has stat64, but it is deprecated since plain stat now // provides the same 64-bit aware struct when targeting OS X > 10.5 diff --git a/src/Native/System.Native/pal_process.cpp b/src/Native/System.Native/pal_process.cpp index 46be9252238a..ec78a19a5b21 100644 --- a/src/Native/System.Native/pal_process.cpp +++ b/src/Native/System.Native/pal_process.cpp @@ -17,6 +17,9 @@ #include #include #include +#if HAVE_CRT_EXTERNS_H +#include +#endif #if HAVE_PIPE2 #include #endif @@ -538,5 +541,10 @@ extern "C" int32_t SystemNative_SchedGetAffinity(int32_t pid, intptr_t* mask) extern "C" char** SystemNative_GetEnviron() { +#if HAVE_NSGETENVIRON + return *(_NSGetEnviron()); +#else + extern char **environ; return environ; +#endif } diff --git a/src/Native/System.Native/pal_uid.cpp b/src/Native/System.Native/pal_uid.cpp index 3e50af309cf6..d3417cdcddfc 100644 --- a/src/Native/System.Native/pal_uid.cpp +++ b/src/Native/System.Native/pal_uid.cpp @@ -25,7 +25,7 @@ extern "C" int32_t SystemNative_GetPwUidR(uint32_t uid, Passwd* pwd, char* buf, struct passwd nativePwd; struct passwd* result; int error; - while ((error = getpwuid_r(uid, &nativePwd, buf, UnsignedCast(buflen), &result) == EINTR)); + while ((error = getpwuid_r(uid, &nativePwd, buf, UnsignedCast(buflen), &result)) == EINTR); // positive error number returned -> failure other than entry-not-found if (error != 0) diff --git a/src/Native/configure.cmake b/src/Native/configure.cmake index 017d75f74a6f..d7d4baf619d2 100644 --- a/src/Native/configure.cmake +++ b/src/Native/configure.cmake @@ -473,6 +473,17 @@ else () HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X) endif () +check_include_files(crt_externs.h HAVE_CRT_EXTERNS_H) + +if (HAVE_CRT_EXTERNS_H) + check_cxx_source_compiles( + " + #include + int main() { char** e = *(_NSGetEnviron()); } + " + HAVE_NSGETENVIRON) +endif() + set (CMAKE_REQUIRED_LIBRARIES) configure_file( diff --git a/src/System.Diagnostics.Debug/tests/DebugTests.cs b/src/System.Diagnostics.Debug/tests/DebugTests.cs index d8475c66caa8..d522135da907 100644 --- a/src/System.Diagnostics.Debug/tests/DebugTests.cs +++ b/src/System.Diagnostics.Debug/tests/DebugTests.cs @@ -2,12 +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.IO; +using System.Runtime.InteropServices; using Xunit; namespace System.Diagnostics.Tests { public class DebugTests { + private readonly string s_newline = // avoid Environment direct dependency, due to it being visible from both System.Private.Corelib and System.Runtime.Extensions + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "\r\n" : "\n"; + [Fact] public void Asserts() { @@ -48,15 +53,15 @@ public void Write() [Fact] public void WriteLine() { - VerifyLogged(() => { Debug.WriteLine(5); }, "5" + Environment.NewLine); - VerifyLogged(() => { Debug.WriteLine((string)null); }, Environment.NewLine); - VerifyLogged(() => { Debug.WriteLine((object)null); }, Environment.NewLine); - VerifyLogged(() => { Debug.WriteLine(5, "category"); }, "category:5" + Environment.NewLine); - VerifyLogged(() => { Debug.WriteLine((object)null, "category"); }, "category:" + Environment.NewLine); - VerifyLogged(() => { Debug.WriteLine("logged"); }, "logged" + Environment.NewLine); - VerifyLogged(() => { Debug.WriteLine("logged", "category"); }, "category:logged" + Environment.NewLine); - VerifyLogged(() => { Debug.WriteLine("logged", (string)null); }, "logged" + Environment.NewLine); - VerifyLogged(() => { Debug.WriteLine("{0} {1}", 'a', 'b'); }, "a b" + Environment.NewLine); + VerifyLogged(() => { Debug.WriteLine(5); }, "5" + s_newline); + VerifyLogged(() => { Debug.WriteLine((string)null); }, s_newline); + VerifyLogged(() => { Debug.WriteLine((object)null); }, s_newline); + VerifyLogged(() => { Debug.WriteLine(5, "category"); }, "category:5" + s_newline); + VerifyLogged(() => { Debug.WriteLine((object)null, "category"); }, "category:" + s_newline); + VerifyLogged(() => { Debug.WriteLine("logged"); }, "logged" + s_newline); + VerifyLogged(() => { Debug.WriteLine("logged", "category"); }, "category:logged" + s_newline); + VerifyLogged(() => { Debug.WriteLine("logged", (string)null); }, "logged" + s_newline); + VerifyLogged(() => { Debug.WriteLine("{0} {1}", 'a', 'b'); }, "a b" + s_newline); } [Fact] @@ -78,16 +83,16 @@ public void WriteIf() [Fact] public void WriteLineIf() { - VerifyLogged(() => { Debug.WriteLineIf(true, 5); }, "5" + Environment.NewLine); + VerifyLogged(() => { Debug.WriteLineIf(true, 5); }, "5" + s_newline); VerifyLogged(() => { Debug.WriteLineIf(false, 5); }, ""); - VerifyLogged(() => { Debug.WriteLineIf(true, 5, "category"); }, "category:5" + Environment.NewLine); + VerifyLogged(() => { Debug.WriteLineIf(true, 5, "category"); }, "category:5" + s_newline); VerifyLogged(() => { Debug.WriteLineIf(false, 5, "category"); }, ""); - VerifyLogged(() => { Debug.WriteLineIf(true, "logged"); }, "logged" + Environment.NewLine); + VerifyLogged(() => { Debug.WriteLineIf(true, "logged"); }, "logged" + s_newline); VerifyLogged(() => { Debug.WriteLineIf(false, "logged"); }, ""); - VerifyLogged(() => { Debug.WriteLineIf(true, "logged", "category"); }, "category:logged" + Environment.NewLine); + VerifyLogged(() => { Debug.WriteLineIf(true, "logged", "category"); }, "category:logged" + s_newline); VerifyLogged(() => { Debug.WriteLineIf(false, "logged", "category"); }, ""); } diff --git a/src/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestsManifestNegative.cs b/src/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestsManifestNegative.cs index 81ebaedd9635..d44cc7f88524 100644 --- a/src/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestsManifestNegative.cs +++ b/src/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestsManifestNegative.cs @@ -31,7 +31,9 @@ public static string GetResourceString(string key, params object[] args) private static string GetResourceStringFromReflection(string key) { BindingFlags flags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; - MethodInfo getResource = typeof(Environment).GetMethods(flags).Where(x => x.Name == "GetResourceString" && x.GetParameters().Count() == 1).First(); + MethodInfo getResource = + typeof(Environment).GetMethods(flags).Where(x => x.Name == "GetResourceString" && x.GetParameters().Count() == 1).FirstOrDefault() ?? + typeof(object).GetTypeInfo().Assembly.GetType("System.Environment").GetMethods(flags).Where(x => x.Name == "GetResourceString" && x.GetParameters().Count() == 1).First(); object resource = getResource.Invoke(null, new object[] { key }); return (string)resource; diff --git a/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj b/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj index fe11de1c4473..8e57c8dd1193 100644 --- a/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj +++ b/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj @@ -143,7 +143,7 @@ - + Common\Interop\Windows\BCrypt\Interop.BCryptGenRandom.cs @@ -241,7 +241,7 @@ - + diff --git a/src/System.Runtime.Extensions/src/System/Environment.Unix.cs b/src/System.Runtime.Extensions/src/System/Environment.Unix.cs index fcf928e6722e..808537550b1e 100644 --- a/src/System.Runtime.Extensions/src/System/Environment.Unix.cs +++ b/src/System.Runtime.Extensions/src/System/Environment.Unix.cs @@ -20,7 +20,7 @@ public static partial class Environment byte** environ = Interop.Sys.GetEnviron(); if (environ != null) { - for (byte** ptr = Interop.Sys.GetEnviron(); *ptr != null; ptr++) + for (byte** ptr = environ; *ptr != null; ptr++) { string entry = Marshal.PtrToStringAnsi((IntPtr)(*ptr)); int equalsPos = entry.IndexOf('='); @@ -71,13 +71,16 @@ private static string ExpandEnvironmentVariablesCore(string name) return StringBuilderCache.GetStringAndRelease(result); } - private static string GetEnvironmentVariableCore(string variable, EnvironmentVariableTarget target) + private static string GetEnvironmentVariableCore(string variable) { - if (target == EnvironmentVariableTarget.Machine || target == EnvironmentVariableTarget.User) + // Ensure variable doesn't include a null char + int nullEnd = variable.IndexOf('\0'); + if (nullEnd != -1) { - return null; + variable = variable.Substring(0, nullEnd); } + // Get the value of the variable lock (s_environ) { string value; @@ -85,19 +88,28 @@ private static string GetEnvironmentVariableCore(string variable, EnvironmentVar } } - private static IDictionary GetEnvironmentVariablesCore(EnvironmentVariableTarget target) + private static string GetEnvironmentVariableCore(string variable, EnvironmentVariableTarget target) { - if (target == EnvironmentVariableTarget.Machine || target == EnvironmentVariableTarget.User) - { - return new LowLevelDictionary(); - } + return target == EnvironmentVariableTarget.Process ? + GetEnvironmentVariableCore(variable) : + null; + } + private static IDictionary GetEnvironmentVariablesCore() + { lock (s_environ) { return s_environ.Value.Clone(); } } + private static IDictionary GetEnvironmentVariablesCore(EnvironmentVariableTarget target) + { + return target == EnvironmentVariableTarget.Process ? + GetEnvironmentVariablesCore() : + new LowLevelDictionary(); + } + private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option) { string home = GetEnvironmentVariable("HOME"); @@ -121,7 +133,15 @@ private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOptio private static bool Is64BitOperatingSystemWhen32BitProcess => false; - public static string MachineName => Interop.Sys.GetHostName(); + public static string MachineName + { + get + { + string hostName = Interop.Sys.GetHostName(); + int dotPos = hostName.IndexOf('.'); + return dotPos == -1 ? hostName : hostName.Substring(0, dotPos); + } + } public static string NewLine => "\n"; @@ -173,13 +193,8 @@ private static int FindAndParseNextNumber(string text, ref int pos) private static int ProcessorCountCore => (int)Interop.Sys.SysConf(Interop.Sys.SysConfName._SC_NPROCESSORS_ONLN); - private static void SetEnvironmentVariableCore(string variable, string value, EnvironmentVariableTarget target) + private static void SetEnvironmentVariableCore(string variable, string value) { - if (target == EnvironmentVariableTarget.Machine || target == EnvironmentVariableTarget.User) - { - return; - } - int nullEnd; // Ensure variable doesn't include a null char @@ -213,6 +228,15 @@ private static void SetEnvironmentVariableCore(string variable, string value, En } } + private static void SetEnvironmentVariableCore(string variable, string value, EnvironmentVariableTarget target) + { + if (target == EnvironmentVariableTarget.Process) + { + SetEnvironmentVariableCore(variable, value); + } + // other targets ignored + } + public static string SystemDirectory => GetFolderPathCore(SpecialFolder.System, SpecialFolderOption.None); public static int SystemPageSize => (int)Interop.Sys.SysConf(Interop.Sys.SysConfName._SC_PAGESIZE); diff --git a/src/System.Runtime.Extensions/src/System/Environment.CoreCLR.cs b/src/System.Runtime.Extensions/src/System/Environment.Win32.cs similarity index 78% rename from src/System.Runtime.Extensions/src/System/Environment.CoreCLR.cs rename to src/System.Runtime.Extensions/src/System/Environment.Win32.cs index 2ae6ec729221..3bc8b154d285 100644 --- a/src/System.Runtime.Extensions/src/System/Environment.CoreCLR.cs +++ b/src/System.Runtime.Extensions/src/System/Environment.Win32.cs @@ -46,26 +46,31 @@ private static string ExpandEnvironmentVariablesCore(string name) return StringBuilderCache.GetStringAndRelease(result); } - private static string GetEnvironmentVariableCore(string variable, EnvironmentVariableTarget target) + private static string GetEnvironmentVariableCore(string variable) { - if (target == EnvironmentVariableTarget.Process) + StringBuilder sb = StringBuilderCache.Acquire(128); // a somewhat reasonable default size + int requiredSize = Interop.mincore.GetEnvironmentVariableW(variable, sb, sb.Capacity); + if (requiredSize == 0 && Marshal.GetLastWin32Error() == Interop.mincore.Errors.ERROR_ENVVAR_NOT_FOUND) { - StringBuilder sb = StringBuilderCache.Acquire(128); // a somewhat reasonable default size - int requiredSize = Interop.mincore.GetEnvironmentVariableW(variable, sb, sb.Capacity); - if (requiredSize == 0 && Marshal.GetLastWin32Error() == Interop.mincore.Errors.ERROR_ENVVAR_NOT_FOUND) - { - StringBuilderCache.Release(sb); - return null; - } + StringBuilderCache.Release(sb); + return null; + } - while (requiredSize > sb.Capacity) - { - sb.Capacity = requiredSize; - sb.Length = 0; - requiredSize = Interop.mincore.GetEnvironmentVariableW(variable, sb, sb.Capacity); - } + while (requiredSize > sb.Capacity) + { + sb.Capacity = requiredSize; + sb.Length = 0; + requiredSize = Interop.mincore.GetEnvironmentVariableW(variable, sb, sb.Capacity); + } - return StringBuilderCache.GetStringAndRelease(sb); + return StringBuilderCache.GetStringAndRelease(sb); + } + + private static string GetEnvironmentVariableCore(string variable, EnvironmentVariableTarget target) + { + if (target == EnvironmentVariableTarget.Process) + { + return GetEnvironmentVariableCore(variable); } else if (target == EnvironmentVariableTarget.Machine) { @@ -89,53 +94,60 @@ private static string GetEnvironmentVariableCore(string variable, EnvironmentVar } } - private static IDictionary GetEnvironmentVariablesCore(EnvironmentVariableTarget target) + private static IDictionary GetEnvironmentVariablesCore() { - var results = new Dictionary(); - - if (target == EnvironmentVariableTarget.Process) + // Format for GetEnvironmentStrings is: + // (=HiddenVar=value\0 | Variable=value\0)* \0 + // See the description of Environment Blocks in MSDN's + // CreateProcess page (null-terminated array of null-terminated strings). + // Note the =HiddenVar's aren't always at the beginning. + + // Copy strings out, parsing into pairs and inserting into the table. + // The first few environment variable entries start with an '='. + // The current working directory of every drive (except for those drives + // you haven't cd'ed into in your DOS window) are stored in the + // environment block (as =C:=pwd) and the program's exit code is + // as well (=ExitCode=00000000). + + var results = new LowLevelDictionary(); + char[] block = GetEnvironmentCharArray(); + for (int i = 0; i < block.Length; i++) { - // Format for GetEnvironmentStrings is: - // (=HiddenVar=value\0 | Variable=value\0)* \0 - // See the description of Environment Blocks in MSDN's - // CreateProcess page (null-terminated array of null-terminated strings). - // Note the =HiddenVar's aren't always at the beginning. - - // Copy strings out, parsing into pairs and inserting into the table. - // The first few environment variable entries start with an '='. - // The current working directory of every drive (except for those drives - // you haven't cd'ed into in your DOS window) are stored in the - // environment block (as =C:=pwd) and the program's exit code is - // as well (=ExitCode=00000000). - - char[] block = GetEnvironmentCharArray(); - for (int i = 0; i < block.Length; i++) - { - int startKey = i; + int startKey = i; - // Skip to key. On some old OS, the environment block can be corrupted. - // Some will not have '=', so we need to check for '\0'. - while (block[i] != '=' && block[i] != '\0') i++; - if (block[i] == '\0') continue; + // Skip to key. On some old OS, the environment block can be corrupted. + // Some will not have '=', so we need to check for '\0'. + while (block[i] != '=' && block[i] != '\0') i++; + if (block[i] == '\0') continue; - // Skip over environment variables starting with '=' - if (i - startKey == 0) - { - while (block[i] != 0) i++; - continue; - } + // Skip over environment variables starting with '=' + if (i - startKey == 0) + { + while (block[i] != 0) i++; + continue; + } - string key = new string(block, startKey, i - startKey); - i++; // skip over '=' + string key = new string(block, startKey, i - startKey); + i++; // skip over '=' - int startValue = i; - while (block[i] != 0) i++; // Read to end of this entry - string value = new string(block, startValue, i - startValue); // skip over 0 handled by for loop's i++ + int startValue = i; + while (block[i] != 0) i++; // Read to end of this entry + string value = new string(block, startValue, i - startValue); // skip over 0 handled by for loop's i++ - results[key] = value; - } + results[key] = value; } - else if (target == EnvironmentVariableTarget.Machine) + return results; + } + + private static IDictionary GetEnvironmentVariablesCore(EnvironmentVariableTarget target) + { + if (target == EnvironmentVariableTarget.Process) + { + return GetEnvironmentVariablesCore(); + } + + var results = new LowLevelDictionary(); + if (target == EnvironmentVariableTarget.Machine) { // TODO #8533: Uncomment/fix when registry APIs available //using (RegistryKey environmentKey = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\Session Manager\Environment", false)) @@ -152,7 +164,6 @@ private static IDictionary GetEnvironmentVariablesCore(EnvironmentVariableTarget // return GetRegistryKeyNameValuePairs(environmentKey); //} } - return results; } @@ -280,24 +291,29 @@ private static unsafe int ProcessorCountCore } } - private static void SetEnvironmentVariableCore(string variable, string value, EnvironmentVariableTarget target) + private static void SetEnvironmentVariableCore(string variable, string value) { - if (target == EnvironmentVariableTarget.Process) + if (!Interop.mincore.SetEnvironmentVariableW(variable, value)) { - if (!Interop.mincore.SetEnvironmentVariableW(variable, value)) + int errorCode = Marshal.GetLastWin32Error(); + switch (errorCode) { - int errorCode = Marshal.GetLastWin32Error(); - switch (errorCode) - { - case Interop.mincore.Errors.ERROR_ENVVAR_NOT_FOUND: // Allow user to try to clear a environment variable - return; - case Interop.mincore.Errors.ERROR_FILENAME_EXCED_RANGE: // Fix inaccurate error code from Win32 - throw new ArgumentException(SR.Argument_LongEnvVarValue, nameof(value)); - default: - throw new ArgumentException(Interop.mincore.GetMessage(errorCode)); - } + case Interop.mincore.Errors.ERROR_ENVVAR_NOT_FOUND: // Allow user to try to clear a environment variable + return; + case Interop.mincore.Errors.ERROR_FILENAME_EXCED_RANGE: // Fix inaccurate error code from Win32 + throw new ArgumentException(SR.Argument_LongEnvVarValue, nameof(value)); + default: + throw new ArgumentException(Interop.mincore.GetMessage(errorCode)); } } + } + + private static void SetEnvironmentVariableCore(string variable, string value, EnvironmentVariableTarget target) + { + if (target == EnvironmentVariableTarget.Process) + { + SetEnvironmentVariableCore(variable, value); + } else if (target == EnvironmentVariableTarget.Machine) { // TODO #8533: Uncomment/fix when registry APIs available diff --git a/src/System.Runtime.Extensions/src/System/Environment.NETNative.cs b/src/System.Runtime.Extensions/src/System/Environment.WinRT.cs similarity index 66% rename from src/System.Runtime.Extensions/src/System/Environment.NETNative.cs rename to src/System.Runtime.Extensions/src/System/Environment.WinRT.cs index 9374a281b987..58f66922d39d 100644 --- a/src/System.Runtime.Extensions/src/System/Environment.NETNative.cs +++ b/src/System.Runtime.Extensions/src/System/Environment.WinRT.cs @@ -16,15 +16,13 @@ public static int ExitCode private static string ExpandEnvironmentVariablesCore(string name) => name; - private static string GetEnvironmentVariableCore(string variable, EnvironmentVariableTarget target) - { - return string.Empty; - } + private static string GetEnvironmentVariableCore(string variable) => string.Empty; - private static IDictionary GetEnvironmentVariablesCore(EnvironmentVariableTarget target) - { - return new LowLevelDictionary(); - } + private static string GetEnvironmentVariableCore(string variable, EnvironmentVariableTarget target) => string.Empty; + + private static IDictionary GetEnvironmentVariablesCore() => new LowLevelDictionary(); + + private static IDictionary GetEnvironmentVariablesCore(EnvironmentVariableTarget target) => new LowLevelDictionary(); private static bool Is64BitOperatingSystemWhen32BitProcess => false; @@ -39,24 +37,20 @@ private static IDictionary GetEnvironmentVariablesCore(EnvironmentVariableTarget private static int ProcessorCountCore => ProcessorCountFromSystemInfo; - private static void SetEnvironmentVariableCore(string variable, string value, EnvironmentVariableTarget target) + private static void SetEnvironmentVariableCore(string variable, string value) { throw new PlatformNotSupportedException(); } - public static string SystemDirectory + private static void SetEnvironmentVariableCore(string variable, string value, EnvironmentVariableTarget target) { - get { throw new PlatformNotSupportedException(); } + throw new PlatformNotSupportedException(); } - public static string UserName - { - get { throw new PlatformNotSupportedException(); } - } + public static string SystemDirectory { get { throw new PlatformNotSupportedException(); } } - public static string UserDomainName - { - get { throw new PlatformNotSupportedException(); } - } + public static string UserName { get { throw new PlatformNotSupportedException(); } } + + public static string UserDomainName { get { throw new PlatformNotSupportedException(); } } } } diff --git a/src/System.Runtime.Extensions/src/System/Environment.cs b/src/System.Runtime.Extensions/src/System/Environment.cs index 5e1dad69d9a1..4dcfdf38f1b1 100644 --- a/src/System.Runtime.Extensions/src/System/Environment.cs +++ b/src/System.Runtime.Extensions/src/System/Environment.cs @@ -95,7 +95,16 @@ public static string ExpandEnvironmentVariables(string name) public static string[] GetCommandLineArgs() => EnvironmentAugments.GetCommandLineArgs(); - public static string GetEnvironmentVariable(string variable) => GetEnvironmentVariable(variable, EnvironmentVariableTarget.Process); + public static string GetEnvironmentVariable(string variable) + { + if (variable == null) + { + throw new ArgumentNullException(nameof(variable)); + } + + // separated from the EnvironmentVariableTarget overload to help with tree shaking in common case + return GetEnvironmentVariableCore(variable); + } public static string GetEnvironmentVariable(string variable, EnvironmentVariableTarget target) { @@ -109,7 +118,11 @@ public static string GetEnvironmentVariable(string variable, EnvironmentVariable return GetEnvironmentVariableCore(variable, target); } - public static IDictionary GetEnvironmentVariables() => GetEnvironmentVariables(EnvironmentVariableTarget.Process); + public static IDictionary GetEnvironmentVariables() + { + // separated from the EnvironmentVariableTarget overload to help with tree shaking in common case + return GetEnvironmentVariablesCore(); + } public static IDictionary GetEnvironmentVariables(EnvironmentVariableTarget target) { @@ -143,9 +156,23 @@ public static string GetFolderPath(SpecialFolder folder, SpecialFolderOption opt public static int ProcessorCount => s_processorCount.Value; - public static void SetEnvironmentVariable(string variable, string value) => SetEnvironmentVariable(variable, value, EnvironmentVariableTarget.Process); + public static void SetEnvironmentVariable(string variable, string value) + { + ValidateVariableAndValue(variable, ref value); + + // separated from the EnvironmentVariableTarget overload to help with tree shaking in common case + SetEnvironmentVariableCore(variable, value); + } public static void SetEnvironmentVariable(string variable, string value, EnvironmentVariableTarget target) + { + ValidateVariableAndValue(variable, ref value); + ValidateTarget(target); + + SetEnvironmentVariableCore(variable, value, target); + } + + private static void ValidateVariableAndValue(string variable, ref string value) { const int MaxEnvVariableValueLength = 32767; @@ -179,10 +206,6 @@ public static void SetEnvironmentVariable(string variable, string value, Environ { throw new ArgumentException(SR.Argument_LongEnvVarValue, nameof(value)); } - - ValidateTarget(target); - - SetEnvironmentVariableCore(variable, value, target); } public static OperatingSystem OSVersion => s_osVersion.Value; diff --git a/src/System.Runtime.Extensions/tests/System/Environment.SetEnvironmentVariable.cs b/src/System.Runtime.Extensions/tests/System/Environment.SetEnvironmentVariable.cs index 984433667012..9d86944b436c 100644 --- a/src/System.Runtime.Extensions/tests/System/Environment.SetEnvironmentVariable.cs +++ b/src/System.Runtime.Extensions/tests/System/Environment.SetEnvironmentVariable.cs @@ -138,6 +138,7 @@ public void NonInitialNullCharacterInVariableName() try { Environment.SetEnvironmentVariable(varName, value); + Assert.Equal(value, Environment.GetEnvironmentVariable(varName)); Assert.Equal(value, Environment.GetEnvironmentVariable(varNamePrefix)); } finally From 51920556182596c42efa36a43869229434507e3b Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 6 Jul 2016 19:53:34 -0400 Subject: [PATCH 4/5] More tweaks to Environment --- .../Generic/LowLevelDictionary.IDictionary.cs | 11 +++++++---- .../src/System/Environment.Win32.cs | 12 ++++++++++++ .../src/System/Environment.WinRT.cs | 5 +++++ .../src/System/Environment.Windows.cs | 6 ------ 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/Common/src/System/Collections/Generic/LowLevelDictionary.IDictionary.cs b/src/Common/src/System/Collections/Generic/LowLevelDictionary.IDictionary.cs index f8363bcc487d..a2790187492f 100644 --- a/src/Common/src/System/Collections/Generic/LowLevelDictionary.IDictionary.cs +++ b/src/Common/src/System/Collections/Generic/LowLevelDictionary.IDictionary.cs @@ -11,7 +11,11 @@ internal partial class LowLevelDictionary : IDictionary object IDictionary.this[object key] { - get { return this[(TKey)key]; } + get + { + Entry e = Find((TKey)key); + return e != null ? (object)e._value : null; + } set { this[(TKey)key] = (TValue)value; } } @@ -89,12 +93,11 @@ public LowLevelDictionary Clone() void ICollection.CopyTo(Array array, int index) { - int dst = 0; for (int bucket = 0; bucket < _buckets.Length; bucket++) { for (Entry entry = _buckets[bucket]; entry != null; entry = entry._next) { - array.SetValue(new DictionaryEntry(entry._key, entry._value), dst++); + array.SetValue(new DictionaryEntry(entry._key, entry._value), index++); } } } @@ -143,7 +146,7 @@ public bool MoveNext() return _pos < _entries.Length; } - public void Reset() { throw new NotSupportedException(); } + public void Reset() { _pos = -1; } } } } diff --git a/src/System.Runtime.Extensions/src/System/Environment.Win32.cs b/src/System.Runtime.Extensions/src/System/Environment.Win32.cs index 3bc8b154d285..b67436d2df37 100644 --- a/src/System.Runtime.Extensions/src/System/Environment.Win32.cs +++ b/src/System.Runtime.Extensions/src/System/Environment.Win32.cs @@ -210,6 +210,18 @@ private unsafe static char[] GetEnvironmentCharArray() } } + private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option) + { + switch (folder) + { + case SpecialFolder.System: + return SystemDirectory; + default: + // TODO: SHGetFolderPath is not available in the approved API list + throw new PlatformNotSupportedException(); + } + } + private static bool Is64BitOperatingSystemWhen32BitProcess { get diff --git a/src/System.Runtime.Extensions/src/System/Environment.WinRT.cs b/src/System.Runtime.Extensions/src/System/Environment.WinRT.cs index 58f66922d39d..aa951f8ef825 100644 --- a/src/System.Runtime.Extensions/src/System/Environment.WinRT.cs +++ b/src/System.Runtime.Extensions/src/System/Environment.WinRT.cs @@ -24,6 +24,11 @@ public static int ExitCode private static IDictionary GetEnvironmentVariablesCore(EnvironmentVariableTarget target) => new LowLevelDictionary(); + private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option) + { + throw new PlatformNotSupportedException(); + } + private static bool Is64BitOperatingSystemWhen32BitProcess => false; public static string MachineName { get { throw new PlatformNotSupportedException(); } } diff --git a/src/System.Runtime.Extensions/src/System/Environment.Windows.cs b/src/System.Runtime.Extensions/src/System/Environment.Windows.cs index 76853fb1edc1..162ec740b948 100644 --- a/src/System.Runtime.Extensions/src/System/Environment.Windows.cs +++ b/src/System.Runtime.Extensions/src/System/Environment.Windows.cs @@ -62,12 +62,6 @@ private static string CurrentDirectoryCore } } - private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option) - { - // TODO: SHGetFolderPath is not available in the approved API list - throw new PlatformNotSupportedException(); - } - public static string[] GetLogicalDrives() => DriveInfoInternal.GetLogicalDrives(); public static string NewLine => "\r\n"; From 565926e26af1a6172fe6faeaad5197ad0364782e Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 6 Jul 2016 21:29:41 -0400 Subject: [PATCH 5/5] Remove DEBUG ifdef UserName test is failing on debug (with the initial buffer size set to 1) due to what appears to be a CentOS bug: https://bugs.centos.org/view.php?id=7324 --- .../src/System/Environment.Unix.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/System.Runtime.Extensions/src/System/Environment.Unix.cs b/src/System.Runtime.Extensions/src/System/Environment.Unix.cs index 808537550b1e..adf5b116d7a4 100644 --- a/src/System.Runtime.Extensions/src/System/Environment.Unix.cs +++ b/src/System.Runtime.Extensions/src/System/Environment.Unix.cs @@ -247,12 +247,7 @@ public static unsafe string UserName { // First try with a buffer that should suffice for 99% of cases. string username; - const int BufLen = -#if DEBUG // test fallback behavior by starting with smaller buffer size - 1; -#else - 1024; -#endif + const int BufLen = 1024; byte* stackBuf = stackalloc byte[BufLen]; if (TryGetUserNameFromPasswd(stackBuf, BufLen, out username)) {