diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetLine.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetProcessPath.cs similarity index 57% rename from src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetLine.cs rename to src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetProcessPath.cs index 8033720f2a907..c64d21e5054b8 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetLine.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetProcessPath.cs @@ -1,14 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Runtime.InteropServices; internal static partial class Interop { internal static partial class Sys { - [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetLine", SetLastError = true)] - internal static extern string GetLine(IntPtr stream); + /// + /// Returns the full path to the executable for the current process, resolving symbolic links. + /// + [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetProcessPath", SetLastError = true)] + internal static extern string? GetProcessPath(); } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.POpen.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.POpen.cs deleted file mode 100644 index efa67b813cf8e..0000000000000 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.POpen.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Runtime.InteropServices; - -internal static partial class Interop -{ - internal static partial class Sys - { - [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_POpen", SetLastError = true)] - internal static extern IntPtr POpen(string command, string type); - - [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_PClose", SetLastError = true)] - internal static extern int PClose(IntPtr stream); - } -} diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetModuleFileName.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetModuleFileName.cs new file mode 100644 index 0000000000000..9c8f0109177b3 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetModuleFileName.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. + +using System; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32; + +internal static partial class Interop +{ + internal static unsafe partial class Kernel32 + { + [DllImport(Libraries.Kernel32, EntryPoint = "GetModuleFileNameW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern uint GetModuleFileName(IntPtr hModule, ref char lpFilename, uint nSize); + } +} diff --git a/src/libraries/Native/Unix/CMakeLists.txt b/src/libraries/Native/Unix/CMakeLists.txt index e08ba5111c053..c9af0c4c7086d 100644 --- a/src/libraries/Native/Unix/CMakeLists.txt +++ b/src/libraries/Native/Unix/CMakeLists.txt @@ -156,10 +156,6 @@ if (CLR_CMAKE_TARGET_LINUX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_GNU_SOURCE") endif () -if(CLR_CMAKE_TARGET_FREEBSD) - add_definitions(-D_BSD_SOURCE) # required for getline -endif(CLR_CMAKE_TARGET_FREEBSD) - # CLR_ADDITIONAL_LINKER_FLAGS - used for passing additional arguments to linker # CLR_ADDITIONAL_COMPILER_OPTIONS - used for passing additional arguments to compiler # diff --git a/src/libraries/Native/Unix/System.Native/pal_io.c b/src/libraries/Native/Unix/System.Native/pal_io.c index 79f9c831afed6..ea8375c1b8df1 100644 --- a/src/libraries/Native/Unix/System.Native/pal_io.c +++ b/src/libraries/Native/Unix/System.Native/pal_io.c @@ -40,11 +40,6 @@ // Somehow, AIX mangles the definition for this behind a C++ def // Redeclare it here extern int getpeereid(int, uid_t *__restrict__, gid_t *__restrict__); -// This function declaration is hidden behind `_XOPEN_SOURCE=700`, but we need -// `_ALL_SOURCE` to build the runtime, and that resets that definition to 600. -// Instead of trying to wrangle ifdefs in system headers with more definitions, -// just declare it here. -extern ssize_t getline(char **, size_t *, FILE *); #endif #if HAVE_STAT64 @@ -961,17 +956,6 @@ int32_t SystemNative_PosixFAdvise(intptr_t fd, int64_t offset, int64_t length, i #endif } -char* SystemNative_GetLine(FILE* stream) -{ - assert(stream != NULL); - - char* lineptr = NULL; - size_t n = 0; - ssize_t length = getline(&lineptr, &n, stream); - - return length >= 0 ? lineptr : NULL; -} - int32_t SystemNative_Read(intptr_t fd, void* buffer, int32_t bufferSize) { return Common_Read(fd, buffer, bufferSize); diff --git a/src/libraries/Native/Unix/System.Native/pal_io.h b/src/libraries/Native/Unix/System.Native/pal_io.h index b955a569f37db..dbc2be8721a94 100644 --- a/src/libraries/Native/Unix/System.Native/pal_io.h +++ b/src/libraries/Native/Unix/System.Native/pal_io.h @@ -598,13 +598,6 @@ PALEXPORT int32_t SystemNative_Poll(PollEvent* pollEvents, uint32_t eventCount, */ PALEXPORT int32_t SystemNative_PosixFAdvise(intptr_t fd, int64_t offset, int64_t length, int32_t advice); -/** -* Reads a line from the provided stream. -* -* Returns the read line, or null if no line could be read. The caller is responsible for freeing the malloc'd line. -*/ -PALEXPORT char* SystemNative_GetLine(FILE* stream); - /** * Reads the number of bytes specified into the provided buffer from the specified, opened file descriptor. * diff --git a/src/libraries/Native/Unix/System.Native/pal_process.c b/src/libraries/Native/Unix/System.Native/pal_process.c index 44cb59d4c1a2a..fce3c5fcd07ac 100644 --- a/src/libraries/Native/Unix/System.Native/pal_process.c +++ b/src/libraries/Native/Unix/System.Native/pal_process.c @@ -28,6 +28,15 @@ #include #endif +#ifdef __APPLE__ +#include +#endif + +#ifdef __FreeBSD__ +#include +#include +#include +#endif // Validate that our SysLogPriority values are correct for the platform c_static_assert(PAL_LOG_EMERG == LOG_EMERG); @@ -516,19 +525,6 @@ done:; #endif } -FILE* SystemNative_POpen(const char* command, const char* type) -{ - assert(command != NULL); - assert(type != NULL); - return popen(command, type); -} - -int32_t SystemNative_PClose(FILE* stream) -{ - assert(stream != NULL); - return pclose(stream); -} - // Each platform type has it's own RLIMIT values but the same name, so we need // to convert our standard types into the platform specific ones. static int32_t ConvertRLimitResourcesPalToPlatform(RLimitResources value) @@ -871,3 +867,57 @@ int32_t SystemNative_SchedGetAffinity(int32_t pid, intptr_t* mask) return result; } #endif + +// Returns the full path to the executable for the current process, resolving symbolic links. +// The caller is responsible for releasing the buffer. Returns null on error. +char* SystemNative_GetProcessPath() +{ +#if defined(__APPLE__) + uint32_t path_length = 0; + if (_NSGetExecutablePath(NULL, &path_length) != -1) + { + errno = EINVAL; + return NULL; + } + + char path_buf[path_length]; + if (_NSGetExecutablePath(path_buf, &path_length) != 0) + { + errno = EINVAL; + return NULL; + } + + return realpath(path_buf, NULL); +#elif defined(__FreeBSD__) + static const int name[] = + { + CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 + }; + + char path[PATH_MAX]; + size_t len; + + len = sizeof(path); + if (sysctl(name, 4, path, &len, NULL, 0) != 0) + { + return NULL; + } + + return strdup(path); +#elif defined(__sun) + const char* path = getexecname(); + if (path == NULL) + return NULL; + return realpath(path, NULL); +#else + +#ifdef __linux__ + const char* symlinkEntrypointExecutable = "/proc/self/exe"; +#else + const char* symlinkEntrypointExecutable = "/proc/curproc/exe"; +#endif + + // Resolve the symlink to the executable from /proc + return realpath(symlinkEntrypointExecutable, NULL); +#endif +} diff --git a/src/libraries/Native/Unix/System.Native/pal_process.h b/src/libraries/Native/Unix/System.Native/pal_process.h index 49d5b85553c5a..c8ae2d2dff4f1 100644 --- a/src/libraries/Native/Unix/System.Native/pal_process.h +++ b/src/libraries/Native/Unix/System.Native/pal_process.h @@ -36,16 +36,6 @@ PALEXPORT int32_t SystemNative_ForkAndExecProcess( int32_t* stdoutFd, // [out] if redirectStdout, the parent's fd for the child's stdout int32_t* stderrFd); // [out] if redirectStderr, the parent's fd for the child's stderr -/** - * Shim for the popen function. - */ -PALEXPORT FILE* SystemNative_POpen(const char* command, const char* type); - -/** - * Shim for the pclose function. - */ -PALEXPORT int32_t SystemNative_PClose(FILE* stream); - /************ * The values below in the header are fixed and correct for managed callers to use forever. * We must never change them. The implementation must either static_assert that they are equal @@ -252,3 +242,9 @@ PALEXPORT int32_t SystemNative_SchedSetAffinity(int32_t pid, intptr_t* mask); */ PALEXPORT int32_t SystemNative_SchedGetAffinity(int32_t pid, intptr_t* mask); #endif + +/** + * Returns the path of the executable that started the currently executing process, + * resolving symbolic links. The caller is responsible for releasing the buffer. + */ +PALEXPORT char* SystemNative_GetProcessPath(void); diff --git a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj index 947d0f2a1bb0c..f0eeba653ea37 100644 --- a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj +++ b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj @@ -240,8 +240,6 @@ Link="Common\Interop\Unix\Interop.ForkAndExecProcess.cs" /> - - Gets the path to the current executable, or null if it could not be retrieved. - private static string? GetExePath() - { - return Interop.Process.GetProcPath(Environment.ProcessId); - } - // ---------------------------------- // ---- Unix PAL layer ends here ---- // ---------------------------------- diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.OSX.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.OSX.cs index 1028d215e03f6..c06f622880ddd 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.OSX.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.OSX.cs @@ -91,21 +91,6 @@ private int ParentProcessId } } - /// Gets the path to the current executable, or null if it could not be retrieved. - private static string? GetExePath() - { - try - { - return Interop.libproc.proc_pidpath(Environment.ProcessId); - } - catch (Win32Exception) - { - // It will throw System.ComponentModel.Win32Exception (2): No such file or Directory when - // the executable file is deleted. - return null; - } - } - // ---------------------------------- // ---- Unix PAL layer ends here ---- // ---------------------------------- diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs index 5096a2adea86c..35fb9912b25ed 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs @@ -680,7 +680,7 @@ private static string[] CreateEnvp(ProcessStartInfo psi) } // Then check the executable's directory - string? path = GetExePath(); + string? path = Environment.ProcessPath; if (path != null) { try diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.UnknownUnix.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.UnknownUnix.cs index 072fb489f56c9..67b4af31225b6 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.UnknownUnix.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.UnknownUnix.cs @@ -76,11 +76,6 @@ private void SetWorkingSetLimitsCore(IntPtr? newMin, IntPtr? newMax, out IntPtr throw new PlatformNotSupportedException(); } - private static string GetExePath() - { - throw new PlatformNotSupportedException(); - } - /// Gets execution path private string GetPathToOpenFile() { diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs index 5fa900d1883de..c67df209280de 100644 --- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs +++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs @@ -494,15 +494,10 @@ public void TestMainModule() { Process p = Process.GetCurrentProcess(); - // On UAP casing may not match - we use Path.GetFileName(exePath) instead of kernel32!GetModuleFileNameEx which is not available on UAP - Func normalize = PlatformDetection.IsInAppContainer ? - (Func)((s) => s.ToLowerInvariant()) : - (s) => s; - Assert.InRange(p.Modules.Count, 1, int.MaxValue); - Assert.Equal(normalize(RemoteExecutor.HostRunnerName), normalize(p.MainModule.ModuleName)); - Assert.EndsWith(normalize(RemoteExecutor.HostRunnerName), normalize(p.MainModule.FileName)); - Assert.Equal(normalize(string.Format("System.Diagnostics.ProcessModule ({0})", RemoteExecutor.HostRunnerName)), normalize(p.MainModule.ToString())); + Assert.Equal(RemoteExecutor.HostRunnerName, p.MainModule.ModuleName); + Assert.EndsWith(RemoteExecutor.HostRunnerName, p.MainModule.FileName); + Assert.Equal(string.Format("System.Diagnostics.ProcessModule ({0})", RemoteExecutor.HostRunnerName), p.MainModule.ToString()); } [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 4cf5f00f44b7f..1b32a55a27fba 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1380,6 +1380,9 @@ Common\Interop\Windows\Kernel32\Interop.GetLongPathNameW.cs + + Common\Interop\Windows\Kernel32\Interop.GetModuleFileName.cs + Common\Interop\Windows\Kernel32\Interop.GetProcessMemoryInfo.cs @@ -1709,6 +1712,9 @@ Common\Interop\Unix\System.Native\Interop.GetHostName.cs + + Common\Interop\Unix\System.Native\Interop.GetProcessPath.cs + Common\Interop\Unix\System.Native\Interop.GetRandomBytes.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs index 07ad3b8b31c75..7ba016ee24ba2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs @@ -25,6 +25,12 @@ private static OperatingSystem GetOSVersion() return new OperatingSystem(PlatformID.Other, new Version(1, 0, 0, 0)); } - private static int GetCurrentProcessId() => 42; + private static int GetProcessId() => 42; + + /// + /// Returns the path of the executable that started the currently executing process. Returns null when the path is not available. + /// + /// Path of the executable that started the currently executing process + private static string? GetProcessPath() => null; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs index 9b5ff6ac417cd..6ed2b34a291f6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.IO; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Threading; @@ -111,6 +112,10 @@ private static unsafe bool TryGetUserNameFromPasswd(byte* buf, int bufLen, out s throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno); } - private static int GetCurrentProcessId() => Interop.Sys.GetPid(); + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Avoid inlining PInvoke frame into the hot path + private static int GetProcessId() => Interop.Sys.GetPid(); + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Avoid inlining PInvoke frame into the hot path + private static string? GetProcessPath() => Interop.Sys.GetProcessPath(); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs index 751747b96593c..b769f59cc2df2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.IO; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -72,7 +73,7 @@ private static string ExpandEnvironmentVariablesCore(string name) } if (length == 0) - Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + throw Win32Marshal.GetExceptionForLastWin32Error(); // length includes the null terminator builder.Length = (int)length - 1; @@ -86,7 +87,25 @@ private static string ExpandEnvironmentVariablesCore(string name) Interop.Kernel32.GetComputerName() ?? throw new InvalidOperationException(SR.InvalidOperation_ComputerName); - private static int GetCurrentProcessId() => unchecked((int)Interop.Kernel32.GetCurrentProcessId()); + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Avoid inlining PInvoke frame into the hot path + private static int GetProcessId() => unchecked((int)Interop.Kernel32.GetCurrentProcessId()); + + private static string? GetProcessPath() + { + var builder = new ValueStringBuilder(stackalloc char[Interop.Kernel32.MAX_PATH]); + + uint length; + while ((length = Interop.Kernel32.GetModuleFileName(IntPtr.Zero, ref builder.GetPinnableReference(), (uint)builder.Capacity)) >= builder.Capacity) + { + builder.EnsureCapacity((int)length); + } + + if (length == 0) + throw Win32Marshal.GetExceptionForLastWin32Error(); + + builder.Length = (int)length; + return builder.ToString(); + } private static unsafe OperatingSystem GetOSVersion() { diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.cs index c971a01516498..15e19465cae31 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.cs @@ -117,21 +117,48 @@ public static string GetFolderPath(SpecialFolder folder, SpecialFolderOption opt return GetFolderPathCore(folder, option); } - private static int s_processId; - private static volatile bool s_haveProcessId; + private static volatile int s_processId; /// Gets the unique identifier for the current process. public static int ProcessId { get { - if (!s_haveProcessId) + int processId = s_processId; + if (processId == 0) { - s_processId = GetCurrentProcessId(); - s_haveProcessId = true; + Interlocked.CompareExchange(ref s_processId, GetProcessId(), 0); + processId = s_processId; + // Assume that process Id zero is invalid for user processes. It holds for all mainstream operating systems. + Debug.Assert(processId != 0); } + return processId; + } + } + + private static volatile string? s_processPath; - return s_processId; + /// + /// Returns the path of the executable that started the currently executing process. Returns null when the path is not available. + /// + /// Path of the executable that started the currently executing process + /// + /// If the executable is renamed or deleted before this property is first accessed, the return value is undefined and depends on the operating system. + /// + public static string? ProcessPath + { + get + { + string? processPath = s_processPath; + if (processPath == null) + { + // The value is cached both as a performance optimization and to ensure that the API always returns + // the same path in a given process. + Interlocked.CompareExchange(ref s_processPath, GetProcessPath() ?? "", null); + processPath = s_processPath; + Debug.Assert(processPath != null); + } + return (processPath.Length != 0) ? processPath : null; } } @@ -141,17 +168,20 @@ public static int ProcessId public static string NewLine => NewLineConst; - private static OperatingSystem? s_osVersion; + private static volatile OperatingSystem? s_osVersion; public static OperatingSystem OSVersion { get { - if (s_osVersion == null) + OperatingSystem? osVersion = s_osVersion; + if (osVersion == null) { Interlocked.CompareExchange(ref s_osVersion, GetOSVersion(), null); + osVersion = s_osVersion; + Debug.Assert(osVersion != null); } - return s_osVersion; + return osVersion; } } diff --git a/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs b/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs index ec2a052d827d8..8007fa8f2ec9f 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs @@ -60,6 +60,21 @@ public void CurrentManagedThreadId_Idempotent() Assert.Equal(Environment.CurrentManagedThreadId, Environment.CurrentManagedThreadId); } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + 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 ProcessId_Idempotent() { @@ -74,19 +89,17 @@ public void ProcessId_MatchesExpectedValue() Assert.Equal(handle.Process.Id, int.Parse(handle.Process.StandardOutput.ReadToEnd())); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - public void CurrentManagedThreadId_DifferentForActiveThreads() + [Fact] + public void ProcessPath_Idempotent() { - 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); + Assert.Same(Environment.ProcessPath, Environment.ProcessPath); + } + + [Fact] + public void ProcessPath_MatchesExpectedValue() + { + string expectedProcessPath = PlatformDetection.IsBrowser ? null : Process.GetCurrentProcess().MainModule.FileName; + Assert.Equal(expectedProcessPath, Environment.ProcessPath); } [Fact] diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 01ce63724ef5a..e5143cc08060c 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -1884,6 +1884,7 @@ public static partial class Environment public static string MachineName { get { throw null; } } public static string NewLine { get { throw null; } } public static System.OperatingSystem OSVersion { get { throw null; } } + public static string? ProcessPath { get { throw null; } } public static int ProcessId { get { throw null; } } public static int ProcessorCount { get { throw null; } } public static string StackTrace { get { throw null; } }