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; } }