Skip to content

Commit 7366a54

Browse files
committed
argv0: fix implementation of getting native argv[0]
Our previous implementation of getting the native argv[0] was flawed on Linux and Mac. The Mac `_NSArgv` and Linux `/proc/self/cmdline` function/proc-file return the raw argv[0] which may not be an absolute or rooted path, but we expected this to be the same. Additionally, the logic of appending the CWD to the argv[0] value when the latter wasn't rooted was also just wrong! Update to now use `_NSExecutablePath` on macOS, which always returns the full absolute path, and add correct handling of unrooted argv[0] on Linux. Furthermore, on Linux, if we are unable to handle the value from /proc/self/cmdline, we fall back to resolving the /proc/self/exe symlink instead.
1 parent 583eb3b commit 7366a54

File tree

4 files changed

+77
-20
lines changed

4 files changed

+77
-20
lines changed

src/shared/Core/ApplicationBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public Task<int> RunAsync(string[] args)
8484

8585
public static string GetEntryApplicationPath()
8686
{
87-
return PlatformUtils.GetNativeArgv0() ??
87+
return PlatformUtils.GetNativeEntryPath() ??
8888
Process.GetCurrentProcess().MainModule?.FileName ??
8989
Environment.GetCommandLineArgs()[0];
9090
}

src/shared/Core/Interop/MacOS/Native/LibC.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@ public static class LibC
88
private const string LibCLib = "libc";
99

1010
[DllImport(LibCLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
11-
public static extern IntPtr _NSGetArgv();
12-
13-
[DllImport(LibCLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
14-
public static extern IntPtr _NSGetProgname();
11+
public static extern int _NSGetExecutablePath(IntPtr buf, out int bufsize);
1512
}
1613
}

src/shared/Core/Interop/Posix/Native/Unistd.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Runtime.InteropServices;
2-
using System.Text;
32

43
namespace GitCredentialManager.Interop.Posix.Native
54
{

src/shared/Core/PlatformUtils.cs

Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -176,52 +176,113 @@ public static bool IsElevatedUser()
176176
return false;
177177
}
178178

179-
#region Platform argv[0] Utils
179+
#region Platform Entry Path Utils
180180

181-
public static string GetNativeArgv0()
181+
/// <summary>
182+
/// Get the native entry executable absolute path.
183+
/// </summary>
184+
/// <returns>Entry absolute path or null if there was an error.</returns>
185+
public static string GetNativeEntryPath()
182186
{
183187
try
184188
{
185189
if (IsWindows())
186190
{
187-
return GetWindowsArgv0();
191+
return GetWindowsEntryPath();
188192
}
189193

190194
if (IsMacOS())
191195
{
192-
return GetMacOSArgv0();
196+
return GetMacOSEntryPath();
193197
}
194198

195199
if (IsLinux())
196200
{
197-
return GetLinuxArgv0();
201+
return GetLinuxEntryPath();
198202
}
199203
}
200204
catch
201205
{
202-
// If there are any issues getting the native argv[0]
206+
// If there are any issues getting the native entry path
203207
// we should not throw, and certainly not crash!
204208
// Just return null instead.
205209
}
206210

207211
return null;
208212
}
209213

210-
private static string GetLinuxArgv0()
214+
private static string GetLinuxEntryPath()
211215
{
216+
// Try to extract our native argv[0] from the original cmdline
212217
string cmdline = File.ReadAllText("/proc/self/cmdline");
213-
return cmdline.Split('\0')[0];
218+
string argv0 = cmdline.Split('\0')[0];
219+
220+
// argv[0] is an absolute file path
221+
if (Path.IsPathRooted(argv0))
222+
{
223+
return argv0;
224+
}
225+
226+
string path = Path.GetFullPath(
227+
Path.Combine(Environment.CurrentDirectory, argv0)
228+
);
229+
230+
// argv[0] is relative to current directory (./app) or a relative
231+
// name resolved from the current directory (subdir/app).
232+
// Note that we do NOT want to consider the case when it is just
233+
// a simple filename (argv[0] == "app") because that would actually
234+
// have been resolved from the $PATH instead (handled below)!
235+
if ((argv0.StartsWith("./") || argv0.IndexOf('/') > 0) && File.Exists(path))
236+
{
237+
return path;
238+
}
239+
240+
// argv[0] is a name that was resolved from the $PATH
241+
string pathVar = Environment.GetEnvironmentVariable("PATH");
242+
if (pathVar != null)
243+
{
244+
string[] paths = pathVar.Split(':');
245+
foreach (string pathBase in paths)
246+
{
247+
path = Path.Combine(pathBase, argv0);
248+
if (File.Exists(path))
249+
{
250+
return path;
251+
}
252+
}
253+
}
254+
255+
#if NETFRAMEWORK
256+
return null;
257+
#else
258+
//
259+
// We cannot determine the absolute file path from argv[0]
260+
// (how we were launched), so let's now try to extract the
261+
// fully resolved executable path from /proc/self/exe.
262+
// Note that this means we may miss if we've been invoked
263+
// via a symlink, but it's better than nothing at this point!
264+
//
265+
FileSystemInfo fsi = File.ResolveLinkTarget("/proc/self/exe", returnFinalTarget: false);
266+
return fsi?.FullName;
267+
#endif
214268
}
215269

216-
private static string GetMacOSArgv0()
270+
private static string GetMacOSEntryPath()
217271
{
218-
IntPtr ptr = Interop.MacOS.Native.LibC._NSGetArgv();
219-
IntPtr argvPtr = Marshal.ReadIntPtr(ptr);
220-
IntPtr argv0Ptr = Marshal.ReadIntPtr(argvPtr);
221-
return Marshal.PtrToStringAnsi(argv0Ptr);
272+
// Determine buffer size by passing NULL initially
273+
Interop.MacOS.Native.LibC._NSGetExecutablePath(IntPtr.Zero, out int size);
274+
275+
IntPtr bufPtr = Marshal.AllocHGlobal(size);
276+
int result = Interop.MacOS.Native.LibC._NSGetExecutablePath(bufPtr, out size);
277+
278+
// buf is null-byte terminated
279+
string name = result == 0 ? Marshal.PtrToStringAuto(bufPtr) : null;
280+
Marshal.FreeHGlobal(bufPtr);
281+
282+
return name;
222283
}
223284

224-
private static string GetWindowsArgv0()
285+
private static string GetWindowsEntryPath()
225286
{
226287
IntPtr argvPtr = Interop.Windows.Native.Shell32.CommandLineToArgvW(
227288
Interop.Windows.Native.Kernel32.GetCommandLine(), out _);

0 commit comments

Comments
 (0)