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