Add Environment.ProcessPath (#42768)
authorJan Kotas <jkotas@microsoft.com>
Thu, 1 Oct 2020 14:21:30 +0000 (07:21 -0700)
committerGitHub <noreply@github.com>
Thu, 1 Oct 2020 14:21:30 +0000 (07:21 -0700)
Fixes #40862

Co-authored-by: Stephen Toub <stoub@microsoft.com>
Co-authored-by: Ryan Lucia <ryan@luciaonline.net>
21 files changed:
src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetProcessPath.cs [moved from src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetLine.cs with 57% similarity]
src/libraries/Common/src/Interop/Unix/System.Native/Interop.POpen.cs [deleted file]
src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetModuleFileName.cs [new file with mode: 0644]
src/libraries/Native/Unix/CMakeLists.txt
src/libraries/Native/Unix/System.Native/pal_io.c
src/libraries/Native/Unix/System.Native/pal_io.h
src/libraries/Native/Unix/System.Native/pal_process.c
src/libraries/Native/Unix/System.Native/pal_process.h
src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.FreeBSD.cs
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.OSX.cs
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.UnknownUnix.cs
src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs
src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs
src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs
src/libraries/System.Private.CoreLib/src/System/Environment.cs
src/libraries/System.Runtime.Extensions/tests/System/EnvironmentTests.cs
src/libraries/System.Runtime/ref/System.Runtime.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);
+        /// <summary>
+        /// Returns the full path to the executable for the current process, resolving symbolic links.
+        /// </summary>
+        [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 (file)
index efa67b8..0000000
+++ /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 (file)
index 0000000..9c8f010
--- /dev/null
@@ -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);
+    }
+}
index e08ba51..c9af0c4 100644 (file)
@@ -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
 #
index 79f9c83..ea8375c 100644 (file)
 // 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);
index b955a56..dbc2be8 100644 (file)
@@ -599,13 +599,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.
  *
  * Returns the number of bytes read on success; otherwise, -1 is returned an errno is set.
index 44cb59d..fce3c5f 100644 (file)
 #include <sched.h>
 #endif
 
+#ifdef __APPLE__
+#include <mach-o/dyld.h>
+#endif
+
+#ifdef __FreeBSD__
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#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
+}
index 49d5b85..c8ae2d2 100644 (file)
@@ -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);
index a2132a7..46f663f 100644 (file)
              Link="Common\Interop\Unix\Interop.ForkAndExecProcess.cs" />
     <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetGroupList.cs"
              Link="Common\Interop\Unix\Interop.GetGroupList.cs" />
-    <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetLine.cs"
-             Link="Common\Interop\Unix\Interop.GetLine.cs" />
     <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetPwUid.cs"
              Link="Common\Interop\Unix\Interop.GetPwUid.cs" />
     <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetSetPriority.cs"
              Link="Common\Interop\Unix\Interop.ResourceLimits.cs" />
     <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.PathConf.cs"
              Link="Common\Interop\Unix\Interop.PathConf.cs" />
-    <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.POpen.cs"
-             Link="Common\Interop\Unix\Interop.POpen.cs" />
     <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.WaitId.cs"
              Link="Common\Interop\Unix\Interop.WaitId.cs" />
     <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.WaitPid.cs"
index 398d267..709fbba 100644 (file)
@@ -101,12 +101,6 @@ namespace System.Diagnostics
             }
         }
 
-        /// <summary>Gets the path to the current executable, or null if it could not be retrieved.</summary>
-        private static string? GetExePath()
-        {
-            return Interop.Process.GetProcPath(Environment.ProcessId);
-        }
-
         // ----------------------------------
         // ---- Unix PAL layer ends here ----
         // ----------------------------------
index 1028d21..c06f622 100644 (file)
@@ -91,21 +91,6 @@ namespace System.Diagnostics
             }
         }
 
-        /// <summary>Gets the path to the current executable, or null if it could not be retrieved.</summary>
-        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 ----
         // ----------------------------------
index 1fb5535..d9ed6c4 100644 (file)
@@ -680,7 +680,7 @@ namespace System.Diagnostics
             }
 
             // Then check the executable's directory
-            string? path = GetExePath();
+            string? path = Environment.ProcessPath;
             if (path != null)
             {
                 try
index 072fb48..67b4af3 100644 (file)
@@ -76,11 +76,6 @@ namespace System.Diagnostics
             throw new PlatformNotSupportedException();
         }
 
-        private static string GetExePath()
-        {
-            throw new PlatformNotSupportedException();
-        }
-
         /// <summary>Gets execution path</summary>
         private string GetPathToOpenFile()
         {
index 5fa900d..c67df20 100644 (file)
@@ -494,15 +494,10 @@ namespace System.Diagnostics.Tests
         {
             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<string, string> normalize = PlatformDetection.IsInAppContainer ?
-                (Func<string, string>)((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))]
index 4cf5f00..1b32a55 100644 (file)
     <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetLongPathNameW.cs">
       <Link>Common\Interop\Windows\Kernel32\Interop.GetLongPathNameW.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetModuleFileName.cs">
+      <Link>Common\Interop\Windows\Kernel32\Interop.GetModuleFileName.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetProcessMemoryInfo.cs">
       <Link>Common\Interop\Windows\Kernel32\Interop.GetProcessMemoryInfo.cs</Link>
     </Compile>
     <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetHostName.cs">
       <Link>Common\Interop\Unix\System.Native\Interop.GetHostName.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetProcessPath.cs">
+      <Link>Common\Interop\Unix\System.Native\Interop.GetProcessPath.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetRandomBytes.cs">
       <Link>Common\Interop\Unix\System.Native\Interop.GetRandomBytes.cs</Link>
     </Compile>
index 07ad3b8..7ba016e 100644 (file)
@@ -25,6 +25,12 @@ namespace System
             return new OperatingSystem(PlatformID.Other, new Version(1, 0, 0, 0));
         }
 
-        private static int GetCurrentProcessId() => 42;
+        private static int GetProcessId() => 42;
+
+        /// <summary>
+        /// Returns the path of the executable that started the currently executing process. Returns null when the path is not available.
+        /// </summary>
+        /// <returns>Path of the executable that started the currently executing process</returns>
+        private static string? GetProcessPath() => null;
     }
 }
index 9b5ff6a..6ed2b34 100644 (file)
@@ -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 @@ namespace System
             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();
     }
 }
index 751747b..b769f59 100644 (file)
@@ -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 @@ namespace System
             }
 
             if (length == 0)
-                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
+                throw Win32Marshal.GetExceptionForLastWin32Error();
 
             // length includes the null terminator
             builder.Length = (int)length - 1;
@@ -86,7 +87,25 @@ namespace System
             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()
         {
index c971a01..15e1946 100644 (file)
@@ -117,21 +117,48 @@ namespace System
             return GetFolderPathCore(folder, option);
         }
 
-        private static int s_processId;
-        private static volatile bool s_haveProcessId;
+        private static volatile int s_processId;
 
         /// <summary>Gets the unique identifier for the current process.</summary>
         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;
+        /// <summary>
+        /// Returns the path of the executable that started the currently executing process. Returns null when the path is not available.
+        /// </summary>
+        /// <returns>Path of the executable that started the currently executing process</returns>
+        /// <remarks>
+        /// If the executable is renamed or deleted before this property is first accessed, the return value is undefined and depends on the operating system.
+        /// </remarks>
+        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 @@ namespace System
 
         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;
             }
         }
 
index ec2a052..8007fa8 100644 (file)
@@ -60,6 +60,21 @@ namespace System.Tests
             Assert.Equal(Environment.CurrentManagedThreadId, Environment.CurrentManagedThreadId);
         }
 
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+        public void CurrentManagedThreadId_DifferentForActiveThreads()
+        {
+            var ids = new HashSet<int>();
+            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 @@ namespace System.Tests
             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<int>();
-            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]
index 01ce637..e5143cc 100644 (file)
@@ -1884,6 +1884,7 @@ namespace System
         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; } }