Skip to content

Commit

Permalink
Support macOS (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
vandycknick authored Mar 29, 2020
1 parent f5dd7db commit 1604153
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 8 deletions.
12 changes: 9 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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'
107 changes: 106 additions & 1 deletion src/Tmds.ExecFunction/ExecFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>();

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 | <int> 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' });
Expand All @@ -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();

Expand Down
6 changes: 3 additions & 3 deletions test/Tmds.ExecFunction.Tests/Tmds.ExecFunction.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>

<IsPackable>false</IsPackable>
</PropertyGroup>
Expand All @@ -12,8 +12,8 @@
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Tmds.ExecFunction\Tmds.ExecFunction.csproj" />
<ItemGroup>
<ProjectReference Include="..\..\src\Tmds.ExecFunction\Tmds.ExecFunction.csproj" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion test/Tmds.ExecFunction.Tests/UnitTest1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down

0 comments on commit 1604153

Please sign in to comment.