Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f47b2fd
Refactor StartCore to accept SafeFileHandle parameters; move pipe cre…
Copilot Mar 18, 2026
fea2a80
Fix build errors: XML doc comments and unsafe partial mismatch; add I…
Copilot Mar 18, 2026
07d9369
Address PR feedback: remove partial methods, use SafeFileHandle.Creat…
Copilot Mar 19, 2026
b65842b
address my own feedback
adamsitnik Mar 19, 2026
43b6301
more polishing after reading the code again
adamsitnik Mar 19, 2026
dc85b03
fix Unix build
adamsitnik Mar 19, 2026
9ee1881
trigger the CI as it seems to got stuck
adamsitnik Mar 19, 2026
17bc483
fix the tests:
adamsitnik Mar 19, 2026
c9c55f0
Merge branch 'main' into copilot/refactor-startcore-method-arguments
adamsitnik Mar 19, 2026
7cf7f0c
handle INVALID_HANDLE_VALUE on Windows
adamsitnik Mar 19, 2026
2c8c444
address code review feedback: don't use Console.OpenStandard*Handle o…
adamsitnik Mar 19, 2026
bba0144
Make Console.OpenStandard*Handle APIs work on Android by returning fd…
Copilot Mar 19, 2026
7a345e2
Revert "Make Console.OpenStandard*Handle APIs work on Android by retu…
adamsitnik Mar 20, 2026
b362a72
address code review feedback: don't dup 0/1/2 on Unix
adamsitnik Mar 20, 2026
419d7ab
Add StandardInput/Output/Error SafeFileHandle properties to ProcessSt…
Copilot Mar 20, 2026
18995de
Remove unrelated files from commit
Copilot Mar 20, 2026
d40cf18
Address code review: fix pipe handle cleanup in tests
Copilot Mar 20, 2026
60844f3
Address review feedback: reuse CantRedirectStreams, add LeaveHandlesO…
Copilot Mar 20, 2026
c8b8723
Address feedback: handles don't need to be inheritable, mention OpenN…
Copilot Mar 20, 2026
12edafb
Merge branch 'main' into copilot/refactor-startcore-method-arguments
adamsitnik Mar 21, 2026
6718589
Only create pipe handles for redirected streams, update callers to ha…
Copilot Mar 21, 2026
6ddf417
Fix usesTerminal logic: null handle means child inherits parent's str…
Copilot Mar 21, 2026
dfec029
Merge branch 'main' into copilot/refactor-startcore-method-arguments
adamsitnik Mar 22, 2026
4199424
Merge branch 'main' into copilot/refactor-startcore-method-arguments
adamsitnik Mar 23, 2026
0aa6fcf
Merge branch 'copilot/refactor-startcore-method-arguments' into copil…
adamsitnik Mar 23, 2026
017f286
Address review feedback: simplify test try/catch, remove manual Close…
Copilot Mar 23, 2026
95247af
address my own feedback:
adamsitnik Mar 23, 2026
7920b52
Rename StandardInput/Output/Error to StandardInputHandle/OutputHandle…
Copilot Mar 23, 2026
477224c
don't duplicate the handle if it's inheritable already
adamsitnik Mar 24, 2026
a914e57
remove LeaveHandlesOpen
adamsitnik Mar 24, 2026
7c1fa73
improve wording
adamsitnik Mar 24, 2026
c0ecbbc
address code review feedback
adamsitnik Mar 24, 2026
e30abcc
Apply suggestions from code review
adamsitnik Mar 24, 2026
e81296d
Fix ValidateHandle order and use fully qualified XML doc cref names
Copilot Mar 24, 2026
0f57884
Apply suggestions from code review
adamsitnik Mar 25, 2026
354126b
Apply suggestions from code review
adamsitnik Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,73 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;

internal static partial class Interop
{
internal static partial class Sys
{
internal static unsafe int ForkAndExecProcess(
string filename, string[] argv, string[] envp, string? cwd,
bool redirectStdin, bool redirectStdout, bool redirectStderr,
bool setUser, uint userId, uint groupId, uint[]? groups,
out int lpChildPid, out int stdinFd, out int stdoutFd, out int stderrFd, bool shouldThrow = true)
out int lpChildPid, SafeFileHandle? stdinFd, SafeFileHandle? stdoutFd, SafeFileHandle? stderrFd, bool shouldThrow = true)
{
byte** argvPtr = null, envpPtr = null;
int result = -1;

bool stdinRefAdded = false, stdoutRefAdded = false, stderrRefAdded = false;
try
{
int stdinRawFd = -1, stdoutRawFd = -1, stderrRawFd = -1;

if (stdinFd is not null)
{
stdinFd.DangerousAddRef(ref stdinRefAdded);
stdinRawFd = stdinFd.DangerousGetHandle().ToInt32();
}

if (stdoutFd is not null)
{
stdoutFd.DangerousAddRef(ref stdoutRefAdded);
stdoutRawFd = stdoutFd.DangerousGetHandle().ToInt32();
}

if (stderrFd is not null)
{
stderrFd.DangerousAddRef(ref stderrRefAdded);
stderrRawFd = stderrFd.DangerousGetHandle().ToInt32();
}

AllocNullTerminatedArray(argv, ref argvPtr);
AllocNullTerminatedArray(envp, ref envpPtr);
fixed (uint* pGroups = groups)
{
result = ForkAndExecProcess(
filename, argvPtr, envpPtr, cwd,
redirectStdin ? 1 : 0, redirectStdout ? 1 : 0, redirectStderr ? 1 : 0,
setUser ? 1 : 0, userId, groupId, pGroups, groups?.Length ?? 0,
out lpChildPid, out stdinFd, out stdoutFd, out stderrFd);
out lpChildPid, stdinRawFd, stdoutRawFd, stderrRawFd);
}
return result == 0 ? 0 : Marshal.GetLastPInvokeError();
}
finally
{
FreeArray(envpPtr, envp.Length);
FreeArray(argvPtr, argv.Length);

if (stdinRefAdded)
stdinFd!.DangerousRelease();
if (stdoutRefAdded)
stdoutFd!.DangerousRelease();
if (stderrRefAdded)
stderrFd!.DangerousRelease();
}
}

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

private static unsafe void AllocNullTerminatedArray(string[] arr, ref byte** arrPtr)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

internal static partial class Interop
{
Expand All @@ -11,5 +12,9 @@ internal static partial class Sys
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_IsATty")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool IsATty(IntPtr fd);

[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_IsATty")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool IsATty(SafeFileHandle fd);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Sys
{
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_IsAtomicNonInheritablePipeCreationSupported", SetLastError = false)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool IsAtomicNonInheritablePipeCreationSupportedImpl();

private static NullableBool s_atomicNonInheritablePipeCreationSupported;

internal static bool IsAtomicNonInheritablePipeCreationSupported
{
get
{
NullableBool isSupported = s_atomicNonInheritablePipeCreationSupported;
if (isSupported == NullableBool.Undefined)
{
s_atomicNonInheritablePipeCreationSupported = isSupported = IsAtomicNonInheritablePipeCreationSupportedImpl() ? NullableBool.True : NullableBool.False;
}
return isSupported == NullableBool.True;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ internal enum HandleFlags : uint
HANDLE_FLAG_PROTECT_FROM_CLOSE = 2
}

[LibraryImport(Libraries.Kernel32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool GetHandleInformation(SafeHandle hObject, out HandleFlags lpdwFlags);

[LibraryImport(Libraries.Kernel32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool SetHandleInformation(SafeHandle hObject, HandleFlags dwMask, HandleFlags dwFlags);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,9 @@ public ProcessStartInfo(string fileName, System.Collections.Generic.IEnumerable<
public bool RedirectStandardInput { get { throw null; } set { } }
public bool RedirectStandardOutput { get { throw null; } set { } }
public System.Text.Encoding? StandardErrorEncoding { get { throw null; } set { } }
public Microsoft.Win32.SafeHandles.SafeFileHandle? StandardErrorHandle { get { throw null; } set { } }
public Microsoft.Win32.SafeHandles.SafeFileHandle? StandardInputHandle { get { throw null; } set { } }
public Microsoft.Win32.SafeHandles.SafeFileHandle? StandardOutputHandle { get { throw null; } set { } }
public System.Text.Encoding? StandardInputEncoding { get { throw null; } set { } }
public System.Text.Encoding? StandardOutputEncoding { get { throw null; } set { } }
[System.Diagnostics.CodeAnalysis.AllowNullAttribute]
Comment thread
adamsitnik marked this conversation as resolved.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,12 @@
<data name="CantRedirectStreams" xml:space="preserve">
<value>The Process object must have the UseShellExecute property set to false in order to redirect IO streams.</value>
</data>
<data name="Arg_InvalidHandle" xml:space="preserve">
<value>Invalid handle.</value>
</data>
<data name="CantSetHandleAndRedirect" xml:space="preserve">
<value>The StandardInputHandle, StandardOutputHandle, and StandardErrorHandle properties cannot be used together with the corresponding RedirectStandardInput, RedirectStandardOutput, and RedirectStandardError properties.</value>
</data>
<data name="DirectoryNotValidAsInput" xml:space="preserve">
<value>The FileName property should not be a directory unless UseShellExecute is set.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@
Link="Common\Interop\Windows\Kernel32\Interop.GetModuleBaseName.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetModuleFileNameEx.cs"
Link="Common\Interop\Windows\Kernel32\Interop.GetModuleFileNameEx.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.HandleInformation.cs"
Link="Common\Interop\Windows\Kernel32\Interop.HandleInformation.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SetProcessWorkingSetSizeEx.cs"
Link="Common\Interop\Windows\Kernel32\Interop.SetProcessWorkingSetSizeEx.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetProcessWorkingSetSizeEx.cs"
Expand Down Expand Up @@ -191,7 +193,7 @@
<Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.ProcessOptions.cs"
Link="Common\Interop\Windows\Advapi32\Interop.ProcessOptions.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.HandleOptions.cs"
Link="Common\Interop\Windows\kernel32\Interop.ProcessOptions.cs" />
Link="Common\Interop\Windows\Kernel32\Interop.HandleOptions.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.MultiByteToWideChar.cs"
Link="Common\Interop\Windows\Kernel32\Interop.MultiByteToWideChar.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.UNICODE_STRING.cs"
Expand Down Expand Up @@ -285,6 +287,12 @@
Link="Common\Interop\Unix\Interop.GetEUid.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.IsMemberOfGroup.cs"
Link="Common\Interop\Unix\Interop.IsMemberOfGroup.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.IsAtomicNonInheritablePipeCreationSupported.cs"
Link="Common\Interop\Unix\Interop.IsAtomicNonInheritablePipeCreationSupported.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.IsATty.cs"
Link="Common\Interop\Unix\Interop.IsATty.cs" />
<Compile Include="$(CommonPath)System\NullableBool.cs"
Link="Common\System\NullableBool.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetPlatformIdentifier)' != '' and '$(TargetPlatformIdentifier)' != 'windows' and '$(IsiOSLike)' != 'true'">
Expand All @@ -301,8 +309,6 @@
<Compile Include="System\Diagnostics\Process.Linux.cs" />
<Compile Include="System\Diagnostics\ProcessManager.Linux.cs" />
<Compile Include="System\Diagnostics\ProcessThread.Linux.cs" />
<Compile Include="$(CommonPath)System\NullableBool.cs"
Link="Common\System\NullableBool.cs" />
<Compile Include="$(CommonPath)Interop\Linux\cgroups\Interop.cgroups.cs"
Link="Common\Interop\Linux\Interop.cgroups.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.MountPoints.FormatInfo.cs"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ internal static void ConfigureTerminalForChildProcesses(int increment, bool conf
int childrenUsingTerminalRemaining = Interlocked.Add(ref s_childrenUsingTerminalCount, increment);
if (increment > 0)
{
Debug.Assert(s_processStartLock.IsReadLockHeld);
Debug.Assert(ProcessUtils.s_processStartLock.IsReadLockHeld);
Debug.Assert(configureConsole);

// At least one child is using the terminal.
Interop.Sys.ConfigureTerminalForChildProcess(childUsesTerminal: true);
}
else
{
Debug.Assert(s_processStartLock.IsWriteLockHeld);
Debug.Assert(ProcessUtils.s_processStartLock.IsWriteLockHeld);

if (childrenUsingTerminalRemaining == 0 && configureConsole)
{
Expand All @@ -44,7 +44,7 @@ private static unsafe void SetDelayedSigChildConsoleConfigurationHandler()
private static void DelayedSigChildConsoleConfiguration()
{
// Lock to avoid races with Process.Start
s_processStartLock.EnterWriteLock();
ProcessUtils.s_processStartLock.EnterWriteLock();
try
{
if (s_childrenUsingTerminalCount == 0)
Expand All @@ -55,7 +55,7 @@ private static void DelayedSigChildConsoleConfiguration()
}
finally
{
s_processStartLock.ExitWriteLock();
ProcessUtils.s_processStartLock.ExitWriteLock();
}
}

Expand Down
Loading
Loading