File: Processes\DetachedProcessLauncher.Unix.cs
Web Access
Project: src\src\Aspire.Cli\Aspire.Cli.csproj (aspire)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
 
namespace Aspire.Cli.Processes;
 
internal static partial class DetachedProcessLauncher
{
    /// <summary>
    /// Unix implementation using Process.Start with stdio redirection.
    /// On Linux/macOS, the redirect pipes' original fds are created with O_CLOEXEC,
    /// but dup2 onto fd 0/1/2 clears that flag — so grandchildren DO inherit the pipe
    /// as their stdio. However, since we close the parent's read-end immediately, the
    /// pipe has no reader and writes produce EPIPE (harmless). The key difference from
    /// Windows is that on Unix, only fds 0/1/2 survive exec — no extra handle leakage.
    /// </summary>
    private static Process StartUnix(string fileName, IReadOnlyList<string> arguments, string workingDirectory)
    {
        var startInfo = new ProcessStartInfo
        {
            FileName = fileName,
            UseShellExecute = false,
            CreateNoWindow = true,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            RedirectStandardInput = false,
            WorkingDirectory = workingDirectory
        };
 
        foreach (var arg in arguments)
        {
            startInfo.ArgumentList.Add(arg);
        }
 
        var process = Process.Start(startInfo)
            ?? throw new InvalidOperationException("Failed to start detached process");
 
        // Close the parent's read-end of the pipes. This means the pipe has no reader,
        // so if the grandchild (AppHost) writes to inherited stdout/stderr, it gets EPIPE
        // which is harmless. The important thing is no caller is blocked waiting on the
        // pipe — unlike Windows where the handle stays open and blocks execSync callers.
        process.StandardOutput.Close();
        process.StandardError.Close();
 
        return process;
    }
}