File: Utilities\StandardHandleInheritance.cs
Web Access
Project: Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Runtime.InteropServices;

namespace Microsoft.CodeAnalysis.Shared.Utilities;

/// <summary>
/// Controls whether this process's standard input, output, and error handles are inheritable by child processes.
/// <para>
/// On Windows a <see cref="System.Diagnostics.Process"/> started with any redirected stream is created with
/// <c>CreateProcess(bInheritHandles: true)</c>, which leaks <em>all</em> of this process's inheritable handles - in
/// particular its own standard handles - to the child. Marking the standard handles non-inheritable prevents the child
/// from receiving them, while the freshly created redirection pipes (which the runtime sets up separately) are
/// unaffected. A no-op off Windows, where redirected children don't leak the parent's standard handles.
/// </para>
/// TODO - Switch to ProcessStartInfo.InheritedHandles when we upgrade to .NET 11 which allows us to configure handle inheritance directly.
/// </summary>
internal static class StandardHandleInheritance
{
    private const int STD_INPUT_HANDLE = -10;
    private const int STD_OUTPUT_HANDLE = -11;
    private const int STD_ERROR_HANDLE = -12;
    private const uint HANDLE_FLAG_INHERIT = 0x00000001;

    private static readonly IntPtr s_invalidHandleValue = new(-1);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr GetStdHandle(int nStdHandle);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetHandleInformation(IntPtr hObject, uint dwMask, uint dwFlags);

    /// <summary>
    /// On Windows, sets whether this process's standard input, output, and error handles are inheritable by child
    /// processes. A no-op off Windows.
    /// </summary>
    public static void SetStandardHandlesInheritable(bool inheritable)
    {
        if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            return;

        var flags = inheritable ? HANDLE_FLAG_INHERIT : 0u;
        SetInheritable(STD_INPUT_HANDLE, flags);
        SetInheritable(STD_OUTPUT_HANDLE, flags);
        SetInheritable(STD_ERROR_HANDLE, flags);

        static void SetInheritable(int stdHandle, uint flags)
        {
            var handle = GetStdHandle(stdHandle);
            if (handle != IntPtr.Zero && handle != s_invalidHandleValue)
                _ = SetHandleInformation(handle, HANDLE_FLAG_INHERIT, flags);
        }
    }
}