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.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.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/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.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.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.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..f39fd398c1a2 --- /dev/null +++ b/src/Common/src/Interop/Windows/mincore/Interop.GetVersionExW.cs @@ -0,0 +1,30 @@ +// 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 unsafe struct OSVERSIONINFOEX + { + public int dwOSVersionInfoSize; + public int dwMajorVersion; + public int dwMinorVersion; + public int dwBuildNumber; + public int dwPlatformId; + public fixed char szCSDVersion[128]; + 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/Common/src/System/Collections/Generic/LowLevelDictionary.IDictionary.cs b/src/Common/src/System/Collections/Generic/LowLevelDictionary.IDictionary.cs new file mode 100644 index 000000000000..a2790187492f --- /dev/null +++ b/src/Common/src/System/Collections/Generic/LowLevelDictionary.IDictionary.cs @@ -0,0 +1,152 @@ +// 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 + { + Entry e = Find((TKey)key); + return e != null ? (object)e._value : null; + } + 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) + { + 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), index++); + } + } + } + + 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() { _pos = -1; } + } + } +} 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/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_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_process.cpp b/src/Native/System.Native/pal_process.cpp index 78fe87584176..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 @@ -535,3 +538,13 @@ extern "C" int32_t SystemNative_SchedGetAffinity(int32_t pid, intptr_t* mask) return result; } #endif + +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_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/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/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.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.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.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/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..a6ff74c14b23 100644 --- a/src/System.Runtime.Extensions/src/Resources/Strings.resx +++ b/src/System.Runtime.Extensions/src/Resources/Strings.resx @@ -228,4 +228,25 @@ Unknown error '{0}'. + + Illegal enum value: {0} + + + Computer name could not be obtained. + + + 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 3a40d2eb2de9..8e57c8dd1193 100644 --- a/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj +++ b/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj @@ -37,12 +37,21 @@ + + + + + + Common\System\Collections\Generic\LowLevelDictionary.cs + + Common\System\Collections\Generic\LowLevelDictionary.IDictionary.cs + Common\System\IO\StringBuilderCache.cs @@ -64,9 +73,13 @@ + + + Common\System\IO\DriveInfoInternal.Win32.cs + Common\System\IO\Win32Marshal.cs @@ -79,17 +92,35 @@ Common\Interop\Windows\mincore\Interop.FormatMessage.cs + + Common\Interop\Windows\mincore\Interop.GetCurrentDirectory.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.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.GetVersionExW.cs + + + Common\Interop\Windows\mincore\Interop.MaxLengths.cs Common\Interop\Windows\mincore\Interop.QueryPerformanceCounter.cs @@ -97,6 +128,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 @@ -106,6 +143,7 @@ + Common\Interop\Windows\BCrypt\Interop.BCryptGenRandom.cs @@ -113,9 +151,34 @@ 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 + + @@ -127,21 +190,45 @@ 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.GetEnviron.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 @@ -154,6 +241,7 @@ + @@ -174,6 +262,7 @@ + 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..adf5b116d7a4 --- /dev/null +++ b/src/System.Runtime.Extensions/src/System/Environment.Unix.cs @@ -0,0 +1,314 @@ +// 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 + { + private readonly unsafe static Lazy> s_environ = new Lazy>(() => + { + var results = new LowLevelDictionary(); + byte** environ = Interop.Sys.GetEnviron(); + if (environ != null) + { + for (byte** ptr = environ; *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(); + + 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 GetEnvironmentVariableCore(string variable) + { + // Ensure variable doesn't include a null char + int nullEnd = variable.IndexOf('\0'); + if (nullEnd != -1) + { + variable = variable.Substring(0, nullEnd); + } + + // Get the value of the variable + lock (s_environ) + { + string value; + return s_environ.Value.TryGetValue(variable, out value) ? value : null; + } + } + + private static string GetEnvironmentVariableCore(string variable, EnvironmentVariableTarget target) + { + 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"); + + 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(); + + private static bool Is64BitOperatingSystemWhen32BitProcess => false; + + 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"; + + 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; + } + + private static int ProcessorCountCore => (int)Interop.Sys.SysConf(Interop.Sys.SysConfName._SC_NPROCESSORS_ONLN); + + private static void SetEnvironmentVariableCore(string variable, string value) + { + int nullEnd; + + // Ensure variable doesn't include a null char + nullEnd = variable.IndexOf('\0'); + if (nullEnd != -1) + { + 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; + } + } + } + + 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); + + 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.Win32.cs b/src/System.Runtime.Extensions/src/System/Environment.Win32.cs new file mode 100644 index 000000000000..b67436d2df37 --- /dev/null +++ b/src/System.Runtime.Extensions/src/System/Environment.Win32.cs @@ -0,0 +1,434 @@ +// 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) + { + 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); + } + + private static string GetEnvironmentVariableCore(string variable, EnvironmentVariableTarget target) + { + if (target == EnvironmentVariableTarget.Process) + { + return GetEnvironmentVariableCore(variable); + } + 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() + { + // 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++) + { + 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; + } + 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)) + //{ + // 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 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 + { + 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) + { + 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)); + } + } + } + + 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 + //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.WinRT.cs b/src/System.Runtime.Extensions/src/System/Environment.WinRT.cs new file mode 100644 index 000000000000..aa951f8ef825 --- /dev/null +++ b/src/System.Runtime.Extensions/src/System/Environment.WinRT.cs @@ -0,0 +1,61 @@ +// 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) => string.Empty; + + 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 string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option) + { + throw new PlatformNotSupportedException(); + } + + 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) + { + throw new PlatformNotSupportedException(); + } + + 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.Windows.cs b/src/System.Runtime.Extensions/src/System/Environment.Windows.cs new file mode 100644 index 000000000000..162ec740b948 --- /dev/null +++ b/src/System.Runtime.Extensions/src/System/Environment.Windows.cs @@ -0,0 +1,89 @@ +// 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.Runtime.InteropServices; +using System.Text; + +namespace System +{ + public static partial class Environment + { + private static string CurrentDirectoryCore + { + get + { + 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); + } + } + } + + public static string[] GetLogicalDrives() => DriveInfoInternal.GetLogicalDrives(); + + public static string NewLine => "\r\n"; + + private static int ProcessorCountFromSystemInfo + { + get + { + var info = default(Interop.mincore.SYSTEM_INFO); + Interop.mincore.GetSystemInfo(out info); + return info.dwNumberOfProcessors; + } + } + + public static int SystemPageSize + { + get + { + var info = default(Interop.mincore.SYSTEM_INFO); + Interop.mincore.GetSystemInfo(out info); + return info.dwPageSize; + } + } + } +} 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..4dcfdf38f1b1 --- /dev/null +++ b/src/System.Runtime.Extensions/src/System/Environment.cs @@ -0,0 +1,316 @@ +// 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.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 + { + 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 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) + { + 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) + { + if (variable == null) + { + throw new ArgumentNullException(nameof(variable)); + } + + ValidateTarget(target); + + return GetEnvironmentVariableCore(variable, target); + } + + 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) + { + ValidateTarget(target); + + return GetEnvironmentVariablesCore(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 int ProcessorCount => s_processorCount.Value; + + 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; + + 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)); + } + } + + 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 + { + // 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?.GetTypeInfo().GetDeclaredMethod("GetCurrentProcess")?.Invoke(null, null) as IDisposable; + if (currentProcess != null) + { + try + { + object result = processType.GetTypeInfo().GetDeclaredProperty("WorkingSet64")?.GetMethod?.Invoke(currentProcess, null); + if (result is long) return (long)result; + } + finally { currentProcess.Dispose(); } + } + + // Could not get the current working set. + 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)); + } + } + } +} + +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).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", 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 CreateGetter(string name) => + new Lazy(() => (TDelegate)(object)s_environment.GetTypeInfo().GetDeclaredProperty(name).GetMethod.CreateDelegate(typeof(TDelegate))); + + private static Lazy CreateSetter(string name) => + 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(); + } +} 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/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 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..21dbc0ee7457 --- /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 bit = 0, d = 0; bit < bits.Length; bit++) + { + if (bits[bit]) + { + Assert.Contains((char)('A' + bit), drives[d++]); + } + } + } + + [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('?')]