diff --git a/src/powershell/Program.cs b/src/powershell/Program.cs
index 70346a1d1ce..f4f66a1d1fa 100644
--- a/src/powershell/Program.cs
+++ b/src/powershell/Program.cs
@@ -4,6 +4,7 @@
#nullable enable
using System;
+using System.Diagnostics;
using System.IO;
using System.Management.Automation;
using System.Reflection;
@@ -81,7 +82,7 @@ public static int Main(string[] args)
/// In the event of success, we use an exec() call, so this method never returns.
///
/// The startup arguments to pwsh.
- private static void AttemptExecPwshLogin(string[] args)
+ private static unsafe void AttemptExecPwshLogin(string[] args)
{
// If the login environment variable is set, we have already done the login logic and have been exec'd
if (Environment.GetEnvironmentVariable(LOGIN_ENV_VAR_NAME) != null)
@@ -90,8 +91,6 @@ private static void AttemptExecPwshLogin(string[] args)
return;
}
- bool isLinux = Platform.IsLinux;
-
// The first byte (ASCII char) of the name of this process, used to detect '-' for login
byte procNameFirstByte;
@@ -99,7 +98,7 @@ private static void AttemptExecPwshLogin(string[] args)
string? pwshPath;
// On Linux, we can simply use the /proc filesystem
- if (isLinux)
+ if (Platform.IsLinux)
{
// Read the process name byte
using (FileStream fs = File.OpenRead("/proc/self/cmdline"))
@@ -114,36 +113,25 @@ private static void AttemptExecPwshLogin(string[] args)
}
// Read the symlink to the startup executable
- IntPtr linkPathPtr = Marshal.AllocHGlobal(LINUX_PATH_MAX);
- IntPtr bufSize = ReadLink("/proc/self/exe", linkPathPtr, (UIntPtr)LINUX_PATH_MAX);
- pwshPath = Marshal.PtrToStringAnsi(linkPathPtr, (int)bufSize);
- Marshal.FreeHGlobal(linkPathPtr);
-
- ArgumentNullException.ThrowIfNull(pwshPath);
+ byte* linkPathPtr = (byte*)NativeMemory.Alloc(LINUX_PATH_MAX);
+ nint len = ReadLink("/proc/self/exe", linkPathPtr, LINUX_PATH_MAX);
+ pwshPath = new string((sbyte*)linkPathPtr, 0, checked((int)len));
+ NativeMemory.Free(linkPathPtr);
// exec pwsh
ThrowOnFailure("exec", ExecPwshLogin(args, pwshPath, isMacOS: false));
return;
}
- // At this point, we are on macOS
+ Debug.Assert(Platform.IsMacOS);
// Set up the mib array and the query for process maximum args size
- Span mib = stackalloc int[3];
- int mibLength = 2;
- mib[0] = MACOS_CTL_KERN;
- mib[1] = MACOS_KERN_ARGMAX;
- int size = IntPtr.Size / 2;
- int argmax = 0;
+ Span mib = [MACOS_CTL_KERN, MACOS_KERN_ARGMAX];
+ nuint argmax = 0;
+ nuint size = (uint)sizeof(nuint);
// Get the process args size
- unsafe
- {
- fixed (int *mibptr = mib)
- {
- ThrowOnFailure(nameof(argmax), SysCtl(mibptr, mibLength, &argmax, &size, IntPtr.Zero, 0));
- }
- }
+ ThrowOnFailure(nameof(argmax), SysCtl(mib, &argmax, &size, null, 0));
// Get the PID so we can query this process' args
int pid = GetPid();
@@ -151,50 +139,41 @@ private static void AttemptExecPwshLogin(string[] args)
// The following logic is based on https://gist.github.com/nonowarn/770696
// Now read the process args into the allocated space
- IntPtr procargs = Marshal.AllocHGlobal(argmax);
- IntPtr executablePathPtr = IntPtr.Zero;
+ byte* procargs = (byte*)NativeMemory.Alloc(argmax);
+ byte* executablePathPtr = null;
try
{
- mib[0] = MACOS_CTL_KERN;
- mib[1] = MACOS_KERN_PROCARGS2;
- mib[2] = pid;
- mibLength = 3;
-
- unsafe
- {
- fixed (int *mibptr = mib)
- {
- ThrowOnFailure(nameof(procargs), SysCtl(mibptr, mibLength, procargs.ToPointer(), &argmax, IntPtr.Zero, 0));
- }
-
- // The memory block we're reading is a series of null-terminated strings
- // that looks something like this:
- //
- // | argc |
- // | exec_path | ... \0
- // | argv[0] | ... \0
- // | argv[1] | ... \0
- // ...
- //
- // We care about argv[0], since that's the name the process was started with.
- // If argv[0][0] == '-', we have been invoked as login.
- // Doing this, the buffer we populated also recorded `exec_path`,
- // which is the path to our executable `pwsh`.
- // We can reuse this value later to prevent needing to call a .NET API
- // to generate our exec invocation.
-
- // We don't care about argc's value, since argv[0] must always exist.
- // Skip over argc, but remember where exec_path is for later
- executablePathPtr = IntPtr.Add(procargs, sizeof(int));
-
- // Skip over exec_path
- byte *argvPtr = (byte *)executablePathPtr;
- while (*argvPtr != 0) { argvPtr++; }
- while (*argvPtr == 0) { argvPtr++; }
-
- // First char in argv[0]
- procNameFirstByte = *argvPtr;
- }
+ mib = [MACOS_CTL_KERN, MACOS_KERN_PROCARGS2, pid];
+
+ ThrowOnFailure(nameof(procargs), SysCtl(mib, procargs, &argmax, null, 0));
+
+ // The memory block we're reading is a series of null-terminated strings
+ // that looks something like this:
+ //
+ // | argc |
+ // | exec_path | ... \0
+ // | argv[0] | ... \0
+ // | argv[1] | ... \0
+ // ...
+ //
+ // We care about argv[0], since that's the name the process was started with.
+ // If argv[0][0] == '-', we have been invoked as login.
+ // Doing this, the buffer we populated also recorded `exec_path`,
+ // which is the path to our executable `pwsh`.
+ // We can reuse this value later to prevent needing to call a .NET API
+ // to generate our exec invocation.
+
+ // We don't care about argc's value, since argv[0] must always exist.
+ // Skip over argc, but remember where exec_path is for later
+ executablePathPtr = procargs + sizeof(int);
+
+ // Skip over exec_path
+ byte* argvPtr = executablePathPtr;
+ while (*argvPtr != 0) { argvPtr++; }
+ while (*argvPtr == 0) { argvPtr++; }
+
+ // First char in argv[0]
+ procNameFirstByte = *argvPtr;
if (!IsLogin(procNameFirstByte, args))
{
@@ -202,16 +181,14 @@ private static void AttemptExecPwshLogin(string[] args)
}
// Get the pwshPath from exec_path
- pwshPath = Marshal.PtrToStringAnsi(executablePathPtr);
-
- ArgumentNullException.ThrowIfNull(pwshPath);
+ pwshPath = new string((sbyte*)executablePathPtr);
// exec pwsh
ThrowOnFailure("exec", ExecPwshLogin(args, pwshPath, isMacOS: true));
}
finally
{
- Marshal.FreeHGlobal(procargs);
+ NativeMemory.Free(procargs);
}
}
@@ -451,14 +428,14 @@ private static void ThrowOnFailure(string call, int code)
///
/// The path to the symlink to read.
/// Pointer to a buffer to fill with the result.
- /// The size of the buffer we have supplied.
+ /// The size of the buffer we have supplied.
/// The number of bytes placed in the buffer.
[DllImport("libc",
EntryPoint = "readlink",
CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi,
SetLastError = true)]
- private static extern IntPtr ReadLink(string pathname, IntPtr buf, UIntPtr size);
+ private static extern unsafe nint ReadLink(string pathname, byte* buf, nuint bufsiz);
///
/// The `getpid` POSIX syscall we use to quickly get the current process PID on macOS.
@@ -488,19 +465,27 @@ private static void ThrowOnFailure(string call, int code)
///
/// The `sysctl` BSD sycall used to get system information on macOS.
///
- /// The Management Information Base name, used to query information.
- /// The length of the MIB name.
+ /// The Management Information Base name, used to query information.
+ /// The length of the MIB name.
/// The object passed out of sysctl (may be null)
/// The size of the object passed out of sysctl.
/// The object passed in to sysctl.
- /// The length of the object passed in to sysctl.
+ /// The length of the object passed in to sysctl.
///
[DllImport("libc",
EntryPoint = "sysctl",
CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi,
SetLastError = true)]
- private static extern unsafe int SysCtl(int *mib, int mibLength, void *oldp, int *oldlenp, IntPtr newp, int newlenp);
+ private static extern unsafe int SysCtl(int* name, uint namelen, void* oldp, nuint* oldlenp, void* newp, nuint newlen);
+
+ private static unsafe int SysCtl(Span mib, void* oldp, nuint* oldlenp, void* newp, nuint newlen)
+ {
+ fixed (int* name = &MemoryMarshal.GetReference(mib))
+ {
+ return SysCtl(name, (uint)mib.Length, oldp, oldlenp, newp, newlen);
+ }
+ }
#endif
}
}