// 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();
}
}
+++ /dev/null
-// 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);
- }
-}
--- /dev/null
+// 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);
+ }
+}
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
#
// 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
#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);
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.
#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);
#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)
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
+}
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
*/
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);
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"
}
}
- /// <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 ----
// ----------------------------------
}
}
- /// <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 ----
// ----------------------------------
}
// Then check the executable's directory
- string? path = GetExePath();
+ string? path = Environment.ProcessPath;
if (path != null)
{
try
throw new PlatformNotSupportedException();
}
- private static string GetExePath()
- {
- throw new PlatformNotSupportedException();
- }
-
/// <summary>Gets execution path</summary>
private string GetPathToOpenFile()
{
{
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))]
<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>
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;
}
}
using System.Diagnostics;
using System.IO;
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
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();
}
}
// 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;
}
if (length == 0)
- Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
+ throw Win32Marshal.GetExceptionForLastWin32Error();
// length includes the null terminator
builder.Length = (int)length - 1;
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()
{
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;
}
}
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;
}
}
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()
{
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]
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; } }