Skip to content

Commit a317e0f

Browse files
CopilotadamsitnikCopilotjkotas
authored
Add StandardInputHandle/OutputHandle/ErrorHandle SafeFileHandle properties to ProcessStartInfo (#125848)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jan Kotas <jkotas@microsoft.com>
1 parent ad026d4 commit a317e0f

22 files changed

Lines changed: 892 additions & 385 deletions

src/libraries/Common/src/Interop/Unix/System.Native/Interop.ForkAndExecProcess.cs

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,73 @@
66
using System.Diagnostics;
77
using System.Runtime.InteropServices;
88
using System.Text;
9+
using Microsoft.Win32.SafeHandles;
910

1011
internal static partial class Interop
1112
{
1213
internal static partial class Sys
1314
{
1415
internal static unsafe int ForkAndExecProcess(
1516
string filename, string[] argv, string[] envp, string? cwd,
16-
bool redirectStdin, bool redirectStdout, bool redirectStderr,
1717
bool setUser, uint userId, uint groupId, uint[]? groups,
18-
out int lpChildPid, out int stdinFd, out int stdoutFd, out int stderrFd, bool shouldThrow = true)
18+
out int lpChildPid, SafeFileHandle? stdinFd, SafeFileHandle? stdoutFd, SafeFileHandle? stderrFd, bool shouldThrow = true)
1919
{
2020
byte** argvPtr = null, envpPtr = null;
2121
int result = -1;
22+
23+
bool stdinRefAdded = false, stdoutRefAdded = false, stderrRefAdded = false;
2224
try
2325
{
26+
int stdinRawFd = -1, stdoutRawFd = -1, stderrRawFd = -1;
27+
28+
if (stdinFd is not null)
29+
{
30+
stdinFd.DangerousAddRef(ref stdinRefAdded);
31+
stdinRawFd = stdinFd.DangerousGetHandle().ToInt32();
32+
}
33+
34+
if (stdoutFd is not null)
35+
{
36+
stdoutFd.DangerousAddRef(ref stdoutRefAdded);
37+
stdoutRawFd = stdoutFd.DangerousGetHandle().ToInt32();
38+
}
39+
40+
if (stderrFd is not null)
41+
{
42+
stderrFd.DangerousAddRef(ref stderrRefAdded);
43+
stderrRawFd = stderrFd.DangerousGetHandle().ToInt32();
44+
}
45+
2446
AllocNullTerminatedArray(argv, ref argvPtr);
2547
AllocNullTerminatedArray(envp, ref envpPtr);
2648
fixed (uint* pGroups = groups)
2749
{
2850
result = ForkAndExecProcess(
2951
filename, argvPtr, envpPtr, cwd,
30-
redirectStdin ? 1 : 0, redirectStdout ? 1 : 0, redirectStderr ? 1 : 0,
3152
setUser ? 1 : 0, userId, groupId, pGroups, groups?.Length ?? 0,
32-
out lpChildPid, out stdinFd, out stdoutFd, out stderrFd);
53+
out lpChildPid, stdinRawFd, stdoutRawFd, stderrRawFd);
3354
}
3455
return result == 0 ? 0 : Marshal.GetLastPInvokeError();
3556
}
3657
finally
3758
{
3859
FreeArray(envpPtr, envp.Length);
3960
FreeArray(argvPtr, argv.Length);
61+
62+
if (stdinRefAdded)
63+
stdinFd!.DangerousRelease();
64+
if (stdoutRefAdded)
65+
stdoutFd!.DangerousRelease();
66+
if (stderrRefAdded)
67+
stderrFd!.DangerousRelease();
4068
}
4169
}
4270

4371
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_ForkAndExecProcess", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)]
4472
private static unsafe partial int ForkAndExecProcess(
4573
string filename, byte** argv, byte** envp, string? cwd,
46-
int redirectStdin, int redirectStdout, int redirectStderr,
4774
int setUser, uint userId, uint groupId, uint* groups, int groupsLength,
48-
out int lpChildPid, out int stdinFd, out int stdoutFd, out int stderrFd);
75+
out int lpChildPid, int stdinFd, int stdoutFd, int stderrFd);
4976

5077
private static unsafe void AllocNullTerminatedArray(string[] arr, ref byte** arrPtr)
5178
{

src/libraries/Common/src/Interop/Unix/System.Native/Interop.IsATty.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Runtime.InteropServices;
6+
using Microsoft.Win32.SafeHandles;
67

78
internal static partial class Interop
89
{
@@ -11,5 +12,9 @@ internal static partial class Sys
1112
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_IsATty")]
1213
[return: MarshalAs(UnmanagedType.Bool)]
1314
internal static partial bool IsATty(IntPtr fd);
15+
16+
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_IsATty")]
17+
[return: MarshalAs(UnmanagedType.Bool)]
18+
internal static partial bool IsATty(SafeFileHandle fd);
1419
}
1520
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
internal static partial class Interop
8+
{
9+
internal static partial class Sys
10+
{
11+
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_IsAtomicNonInheritablePipeCreationSupported", SetLastError = false)]
12+
[return: MarshalAs(UnmanagedType.Bool)]
13+
private static partial bool IsAtomicNonInheritablePipeCreationSupportedImpl();
14+
15+
private static NullableBool s_atomicNonInheritablePipeCreationSupported;
16+
17+
internal static bool IsAtomicNonInheritablePipeCreationSupported
18+
{
19+
get
20+
{
21+
NullableBool isSupported = s_atomicNonInheritablePipeCreationSupported;
22+
if (isSupported == NullableBool.Undefined)
23+
{
24+
s_atomicNonInheritablePipeCreationSupported = isSupported = IsAtomicNonInheritablePipeCreationSupportedImpl() ? NullableBool.True : NullableBool.False;
25+
}
26+
return isSupported == NullableBool.True;
27+
}
28+
}
29+
}
30+
}

src/libraries/Common/src/Interop/Windows/Kernel32/Interop.HandleInformation.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ internal enum HandleFlags : uint
1717
HANDLE_FLAG_PROTECT_FROM_CLOSE = 2
1818
}
1919

20+
[LibraryImport(Libraries.Kernel32, SetLastError = true)]
21+
[return: MarshalAs(UnmanagedType.Bool)]
22+
internal static partial bool GetHandleInformation(SafeHandle hObject, out HandleFlags lpdwFlags);
23+
2024
[LibraryImport(Libraries.Kernel32, SetLastError = true)]
2125
[return: MarshalAs(UnmanagedType.Bool)]
2226
internal static partial bool SetHandleInformation(SafeHandle hObject, HandleFlags dwMask, HandleFlags dwFlags);

src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,9 @@ public ProcessStartInfo(string fileName, System.Collections.Generic.IEnumerable<
253253
public bool RedirectStandardInput { get { throw null; } set { } }
254254
public bool RedirectStandardOutput { get { throw null; } set { } }
255255
public System.Text.Encoding? StandardErrorEncoding { get { throw null; } set { } }
256+
public Microsoft.Win32.SafeHandles.SafeFileHandle? StandardErrorHandle { get { throw null; } set { } }
257+
public Microsoft.Win32.SafeHandles.SafeFileHandle? StandardInputHandle { get { throw null; } set { } }
258+
public Microsoft.Win32.SafeHandles.SafeFileHandle? StandardOutputHandle { get { throw null; } set { } }
256259
public System.Text.Encoding? StandardInputEncoding { get { throw null; } set { } }
257260
public System.Text.Encoding? StandardOutputEncoding { get { throw null; } set { } }
258261
[System.Diagnostics.CodeAnalysis.AllowNullAttribute]

src/libraries/System.Diagnostics.Process/src/Resources/Strings.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,12 @@
210210
<data name="CantRedirectStreams" xml:space="preserve">
211211
<value>The Process object must have the UseShellExecute property set to false in order to redirect IO streams.</value>
212212
</data>
213+
<data name="Arg_InvalidHandle" xml:space="preserve">
214+
<value>Invalid handle.</value>
215+
</data>
216+
<data name="CantSetHandleAndRedirect" xml:space="preserve">
217+
<value>The StandardInputHandle, StandardOutputHandle, and StandardErrorHandle properties cannot be used together with the corresponding RedirectStandardInput, RedirectStandardOutput, and RedirectStandardError properties.</value>
218+
</data>
213219
<data name="DirectoryNotValidAsInput" xml:space="preserve">
214220
<value>The FileName property should not be a directory unless UseShellExecute is set.</value>
215221
</data>

src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@
120120
Link="Common\Interop\Windows\Kernel32\Interop.GetModuleBaseName.cs" />
121121
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetModuleFileNameEx.cs"
122122
Link="Common\Interop\Windows\Kernel32\Interop.GetModuleFileNameEx.cs" />
123+
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.HandleInformation.cs"
124+
Link="Common\Interop\Windows\Kernel32\Interop.HandleInformation.cs" />
123125
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SetProcessWorkingSetSizeEx.cs"
124126
Link="Common\Interop\Windows\Kernel32\Interop.SetProcessWorkingSetSizeEx.cs" />
125127
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetProcessWorkingSetSizeEx.cs"
@@ -191,7 +193,7 @@
191193
<Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.ProcessOptions.cs"
192194
Link="Common\Interop\Windows\Advapi32\Interop.ProcessOptions.cs" />
193195
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.HandleOptions.cs"
194-
Link="Common\Interop\Windows\kernel32\Interop.ProcessOptions.cs" />
196+
Link="Common\Interop\Windows\Kernel32\Interop.HandleOptions.cs" />
195197
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.MultiByteToWideChar.cs"
196198
Link="Common\Interop\Windows\Kernel32\Interop.MultiByteToWideChar.cs" />
197199
<Compile Include="$(CommonPath)Interop\Windows\Interop.UNICODE_STRING.cs"
@@ -285,6 +287,12 @@
285287
Link="Common\Interop\Unix\Interop.GetEUid.cs" />
286288
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.IsMemberOfGroup.cs"
287289
Link="Common\Interop\Unix\Interop.IsMemberOfGroup.cs" />
290+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.IsAtomicNonInheritablePipeCreationSupported.cs"
291+
Link="Common\Interop\Unix\Interop.IsAtomicNonInheritablePipeCreationSupported.cs" />
292+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.IsATty.cs"
293+
Link="Common\Interop\Unix\Interop.IsATty.cs" />
294+
<Compile Include="$(CommonPath)System\NullableBool.cs"
295+
Link="Common\System\NullableBool.cs" />
288296
</ItemGroup>
289297

290298
<ItemGroup Condition="'$(TargetPlatformIdentifier)' != '' and '$(TargetPlatformIdentifier)' != 'windows' and '$(IsiOSLike)' != 'true'">
@@ -301,8 +309,6 @@
301309
<Compile Include="System\Diagnostics\Process.Linux.cs" />
302310
<Compile Include="System\Diagnostics\ProcessManager.Linux.cs" />
303311
<Compile Include="System\Diagnostics\ProcessThread.Linux.cs" />
304-
<Compile Include="$(CommonPath)System\NullableBool.cs"
305-
Link="Common\System\NullableBool.cs" />
306312
<Compile Include="$(CommonPath)Interop\Linux\cgroups\Interop.cgroups.cs"
307313
Link="Common\Interop\Linux\Interop.cgroups.cs" />
308314
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.MountPoints.FormatInfo.cs"

src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.ConfigureTerminalForChildProcesses.Unix.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ internal static void ConfigureTerminalForChildProcesses(int increment, bool conf
1717
int childrenUsingTerminalRemaining = Interlocked.Add(ref s_childrenUsingTerminalCount, increment);
1818
if (increment > 0)
1919
{
20-
Debug.Assert(s_processStartLock.IsReadLockHeld);
20+
Debug.Assert(ProcessUtils.s_processStartLock.IsReadLockHeld);
2121
Debug.Assert(configureConsole);
2222

2323
// At least one child is using the terminal.
2424
Interop.Sys.ConfigureTerminalForChildProcess(childUsesTerminal: true);
2525
}
2626
else
2727
{
28-
Debug.Assert(s_processStartLock.IsWriteLockHeld);
28+
Debug.Assert(ProcessUtils.s_processStartLock.IsWriteLockHeld);
2929

3030
if (childrenUsingTerminalRemaining == 0 && configureConsole)
3131
{
@@ -44,7 +44,7 @@ private static unsafe void SetDelayedSigChildConsoleConfigurationHandler()
4444
private static void DelayedSigChildConsoleConfiguration()
4545
{
4646
// Lock to avoid races with Process.Start
47-
s_processStartLock.EnterWriteLock();
47+
ProcessUtils.s_processStartLock.EnterWriteLock();
4848
try
4949
{
5050
if (s_childrenUsingTerminalCount == 0)
@@ -55,7 +55,7 @@ private static void DelayedSigChildConsoleConfiguration()
5555
}
5656
finally
5757
{
58-
s_processStartLock.ExitWriteLock();
58+
ProcessUtils.s_processStartLock.ExitWriteLock();
5959
}
6060
}
6161

0 commit comments

Comments
 (0)