1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.ComponentModel; 7 using System.Diagnostics; 8 using System.Runtime.InteropServices; 9 using System.Text; 10 11 internal static partial class Interop 12 { 13 internal static partial class Sys 14 { ForkAndExecProcess( string filename, string[] argv, string[] envp, string cwd, bool redirectStdin, bool redirectStdout, bool redirectStderr, out int lpChildPid, out int stdinFd, out int stdoutFd, out int stderrFd, bool shouldThrow = true)15 internal static unsafe void ForkAndExecProcess( 16 string filename, string[] argv, string[] envp, string cwd, 17 bool redirectStdin, bool redirectStdout, bool redirectStderr, 18 out int lpChildPid, out int stdinFd, out int stdoutFd, out int stderrFd, bool shouldThrow = true) 19 { 20 byte** argvPtr = null, envpPtr = null; 21 int result = -1; 22 try 23 { 24 AllocNullTerminatedArray(argv, ref argvPtr); 25 AllocNullTerminatedArray(envp, ref envpPtr); 26 result = ForkAndExecProcess( 27 filename, argvPtr, envpPtr, cwd, 28 redirectStdin ? 1 : 0, redirectStdout ? 1 : 0, redirectStderr ? 1 :0, 29 out lpChildPid, out stdinFd, out stdoutFd, out stderrFd); 30 if (result != 0) 31 { 32 // Normally we'd simply make this method return the result of the native 33 // call and allow the caller to use GetLastWin32Error. However, we need 34 // to free the native arrays after calling the function, and doing so 35 // stomps on the runtime's captured last error. So we need to access the 36 // error here, and without SetLastWin32Error available, we can't propagate 37 // the error to the caller via the normal GetLastWin32Error mechanism. We could 38 // return 0 on success or the GetLastWin32Error value on failure, but that's 39 // technically ambiguous, in the case of a failure with a 0 errno. Simplest 40 // solution then is just to throw here the same exception the Process caller 41 // would have. This can be revisited if we ever have another call site. 42 throw new Win32Exception(); 43 } 44 } 45 finally 46 { 47 FreeArray(envpPtr, envp.Length); 48 FreeArray(argvPtr, argv.Length); 49 } 50 } 51 52 [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ForkAndExecProcess", SetLastError = true)] ForkAndExecProcess( string filename, byte** argv, byte** envp, string cwd, int redirectStdin, int redirectStdout, int redirectStderr, out int lpChildPid, out int stdinFd, out int stdoutFd, out int stderrFd)53 private static extern unsafe int ForkAndExecProcess( 54 string filename, byte** argv, byte** envp, string cwd, 55 int redirectStdin, int redirectStdout, int redirectStderr, 56 out int lpChildPid, out int stdinFd, out int stdoutFd, out int stderrFd); 57 AllocNullTerminatedArray(string[] arr, ref byte** arrPtr)58 private static unsafe void AllocNullTerminatedArray(string[] arr, ref byte** arrPtr) 59 { 60 int arrLength = arr.Length + 1; // +1 is for null termination 61 62 // Allocate the unmanaged array to hold each string pointer. 63 // It needs to have an extra element to null terminate the array. 64 arrPtr = (byte**)Marshal.AllocHGlobal(sizeof(IntPtr) * arrLength); 65 Debug.Assert(arrPtr != null); 66 67 // Zero the memory so that if any of the individual string allocations fails, 68 // we can loop through the array to free any that succeeded. 69 // The last element will remain null. 70 for (int i = 0; i < arrLength; i++) 71 { 72 arrPtr[i] = null; 73 } 74 75 // Now copy each string to unmanaged memory referenced from the array. 76 // We need the data to be an unmanaged, null-terminated array of UTF8-encoded bytes. 77 for (int i = 0; i < arr.Length; i++) 78 { 79 byte[] byteArr = Encoding.UTF8.GetBytes(arr[i]); 80 81 arrPtr[i] = (byte*)Marshal.AllocHGlobal(byteArr.Length + 1); //+1 for null termination 82 Debug.Assert(arrPtr[i] != null); 83 84 Marshal.Copy(byteArr, 0, (IntPtr)arrPtr[i], byteArr.Length); // copy over the data from the managed byte array 85 arrPtr[i][byteArr.Length] = (byte)'\0'; // null terminate 86 } 87 } 88 FreeArray(byte** arr, int length)89 private static unsafe void FreeArray(byte** arr, int length) 90 { 91 if (arr != null) 92 { 93 // Free each element of the array 94 for (int i = 0; i < length; i++) 95 { 96 if (arr[i] != null) 97 { 98 Marshal.FreeHGlobal((IntPtr)arr[i]); 99 arr[i] = null; 100 } 101 } 102 103 // And then the array itself 104 Marshal.FreeHGlobal((IntPtr)arr); 105 } 106 } 107 } 108 } 109