diff --git a/.travis.yml b/.travis.yml index b729c78..07ccc8d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,13 @@ language: csharp mono: none -dist: xenial -dotnet: 2.2 + +jobs: + include: + - os: linux + dotnet: 3.1 + dist: xenial + - os: osx + dotnet: 3.1.200 env: global: @@ -16,5 +22,5 @@ script: after_success: - 'if [ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then - curl -H "X-NuGet-ApiKey: $NUGET_APIKEY" -T src/Tmds.ExecFunction/Tmds.ExecFunction.*.nupkg https://www.myget.org/F/tmds/api/v2/package ; + curl -H "X-NuGet-ApiKey: $NUGET_APIKEY" -T Tmds.ExecFunction.*.nupkg https://www.myget.org/F/tmds/api/v2/package ; fi' diff --git a/src/Tmds.ExecFunction/ExecFunction.cs b/src/Tmds.ExecFunction/ExecFunction.cs index e431c5c..bd3193d 100644 --- a/src/Tmds.ExecFunction/ExecFunction.cs +++ b/src/Tmds.ExecFunction/ExecFunction.cs @@ -268,10 +268,93 @@ private static string GetApplicationArgument(string[] arguments, string name) return null; } + private static string[] GetOSXCommandLineArguments() + { + // The following logic is based on https://gist.github.com/nonowarn/770696 + // Set up the mib array and the query for process maximum args size + var mib = new int[3]; + int mibLength = 2; + mib[0] = MACOS_CTL_KERN; + mib[1] = MACOS_KERN_ARGMAX; + + int size = IntPtr.Size / 2; + int argmax = 0; + var argv = new List(); + + var mibHandle = GCHandle.Alloc(mib, GCHandleType.Pinned); + try + { + var mibPtr = mibHandle.AddrOfPinnedObject(); + + // Get the process args size + SysCtl(mibPtr, mibLength, ref argmax, ref size, IntPtr.Zero, 0); + + // Get the PID so we can query this process' args + var pid = Process.GetCurrentProcess().Id; + + // Now read the process args into the allocated space + IntPtr procargs = Marshal.AllocHGlobal(argmax); + try + { + mib[0] = MACOS_CTL_KERN; + mib[1] = MACOS_KERN_PROCARGS2; + mib[2] = pid; + mibLength = 3; + + SysCtl(mibPtr, mibLength, procargs, ref argmax, IntPtr.Zero, 0); + + // The memory block we're reading is a series of null-terminated strings + // that looks something like this: + // + // | argc | is always 4 bytes long even on 64bit architectures + // | exec_path | ... \0\0\0\0 * ? + // | argv[0] | ... \0 + // | argv[1] | ... \0 + // | argv[2] | ... \0 + // ... + // | env[0] | ... \0 (VALUE = SOMETHING\0) + + // Read argc + var argc = Marshal.ReadInt32(procargs); + + // Skip over argc + var argvPtr = IntPtr.Add(procargs, sizeof(int)); + + // Skip over exec_path + var offset = 0; + while (Marshal.ReadByte(argvPtr, offset) != 0) { offset++; } + while (Marshal.ReadByte(argvPtr, offset) == 0) { offset++; } + argvPtr = IntPtr.Add(argvPtr, offset); + + // Start reading argv + for (var i = 0; i < argc; i++) + { + offset = 0; + // Keep reading bytes until we find a null-terminated string + while (Marshal.ReadByte(argvPtr, offset) != 0) { offset++; } + var arg = Marshal.PtrToStringAnsi(argvPtr, offset); + argv.Add(arg); + + // Move pointer to the start of the next arg (= currentArg + \0) + argvPtr = IntPtr.Add(argvPtr, offset + sizeof(byte)); + } + } + finally + { + Marshal.FreeHGlobal(procargs); + } + } + finally + { + mibHandle.Free(); + } + + return argv.ToArray(); + } + private static string[] GetApplicationArguments() { // Environment.GetCommandLineArgs doesn't include arguments passed to the runtime. - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { return File.ReadAllText($"/proc/{Process.GetCurrentProcess().Id}/cmdline").Split(new[] { '\0' }); @@ -282,12 +365,34 @@ private static string[] GetApplicationArguments() string commandLine = Marshal.PtrToStringAuto(ptr); return CommandLineToArgs(commandLine); } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return GetOSXCommandLineArguments(); + } else { throw new PlatformNotSupportedException($"{nameof(GetApplicationArguments)} is unsupported on this platform"); } } + private const int MACOS_CTL_KERN = 1; + private const int MACOS_KERN_ARGMAX = 8; + private const int MACOS_KERN_PROCARGS2 = 49; + + [DllImport("libc", + EntryPoint = "sysctl", + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + SetLastError = true)] + private static extern int SysCtl(IntPtr mib, int mibLength, ref int oldp, ref int oldlenp, IntPtr newp, int newlenp); + + [DllImport("libc", + EntryPoint = "sysctl", + CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Ansi, + SetLastError = true)] + private static extern int SysCtl(IntPtr mib, int mibLength, IntPtr oldp, ref int oldlenp, IntPtr newp, int newlenp); + [DllImport("kernel32.dll", CharSet = CharSet.Auto)] private static extern System.IntPtr GetCommandLine(); diff --git a/test/Tmds.ExecFunction.Tests/Tmds.ExecFunction.Tests.csproj b/test/Tmds.ExecFunction.Tests/Tmds.ExecFunction.Tests.csproj index f32eb56..928f4e8 100644 --- a/test/Tmds.ExecFunction.Tests/Tmds.ExecFunction.Tests.csproj +++ b/test/Tmds.ExecFunction.Tests/Tmds.ExecFunction.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp2.2 + netcoreapp3.1 false @@ -12,8 +12,8 @@ - - + + diff --git a/test/Tmds.ExecFunction.Tests/UnitTest1.cs b/test/Tmds.ExecFunction.Tests/UnitTest1.cs index 990db9f..bbfbf56 100644 --- a/test/Tmds.ExecFunction.Tests/UnitTest1.cs +++ b/test/Tmds.ExecFunction.Tests/UnitTest1.cs @@ -22,7 +22,7 @@ public void TestArgVoidReturnInt() public void TestArgStringArrayReturnVoid() { FunctionExecutor.Run( - (string[] args) => + (string[] args) => { Assert.Equal("arg1", args[0]); Assert.Equal("arg2", args[1]);