/// <summary> /// Starts a child process as specified in <paramref name="startInfo"/>. /// </summary> /// <param name="startInfo"><see cref="ChildProcessStartInfo"/>.</param> /// <returns>The started process.</returns> /// <exception cref="ArgumentException"><paramref name="startInfo"/> has an invalid value.</exception> /// <exception cref="ArgumentNullException"><paramref name="startInfo"/> is null.</exception> /// <exception cref="FileNotFoundException">The executable not found.</exception> /// <exception cref="IOException">Failed to open a specified file.</exception> /// <exception cref="AsmichiChildProcessLibraryCrashedException">The operation failed due to critical disturbance.</exception> /// <exception cref="Win32Exception">Another kind of native errors.</exception> public static IChildProcess Start(ChildProcessStartInfo startInfo) { _ = startInfo ?? throw new ArgumentNullException(nameof(startInfo)); var startInfoInternal = new ChildProcessStartInfoInternal(startInfo); _ = startInfoInternal.FileName ?? throw new ArgumentException("ChildProcessStartInfo.FileName must not be null.", nameof(startInfo)); _ = startInfoInternal.Arguments ?? throw new ArgumentException("ChildProcessStartInfo.Arguments must not be null.", nameof(startInfo)); var flags = startInfoInternal.Flags; if (flags.HasUseCustomCodePage() && flags.HasAttachToCurrentConsole()) { throw new ArgumentException( $"{nameof(ChildProcessFlags.UseCustomCodePage)} cannot be combined with {nameof(ChildProcessFlags.AttachToCurrentConsole)}.", nameof(startInfo)); } var resolvedPath = ResolveExecutablePath(startInfoInternal.FileName, startInfoInternal.Flags); using var stdHandles = new PipelineStdHandleCreator(ref startInfoInternal); IChildProcessStateHolder processState; try { processState = ChildProcessHelper.Shared.SpawnProcess( startInfo: ref startInfoInternal, resolvedPath: resolvedPath, stdIn: stdHandles.PipelineStdIn, stdOut: stdHandles.PipelineStdOut, stdErr: stdHandles.PipelineStdErr); } catch (Win32Exception ex) { if (EnvironmentPal.IsFileNotFoundError(ex.NativeErrorCode)) { ThrowHelper.ThrowExecutableNotFoundException(resolvedPath, startInfoInternal.Flags, ex); } // Win32Exception does not provide detailed information by its type. // The NativeErrorCode and Message property should be enough because normally there is // nothing we can do to programmatically recover from this error. throw; } var process = new ChildProcessImpl(processState, stdHandles.InputStream, stdHandles.OutputStream, stdHandles.ErrorStream); stdHandles.DetachStreams(); return(process); }
public IChildProcessStateHolder SpawnProcess( ref ChildProcessStartInfoInternal startInfo, string resolvedPath, SafeHandle stdIn, SafeHandle stdOut, SafeHandle stdErr) { var arguments = startInfo.Arguments; var environmentVariables = startInfo.EnvironmentVariables; var workingDirectory = startInfo.WorkingDirectory; Span <int> fds = stackalloc int[3]; int handleCount = 0; uint flags = 0; if (stdIn != null) { fds[handleCount++] = stdIn.DangerousGetHandle().ToInt32(); flags |= RequestFlagsRedirectStdin; } if (stdOut != null) { fds[handleCount++] = stdOut.DangerousGetHandle().ToInt32(); flags |= RequestFlagsRedirectStdout; } if (stdErr != null) { fds[handleCount++] = stdErr.DangerousGetHandle().ToInt32(); flags |= RequestFlagsRedirectStderr; } if (startInfo.CreateNewConsole) { flags |= RequestFlagsCreateNewProcessGroup; } else { Debug.Assert(!startInfo.AllowSignal); } // If AttachToCurrentConsole (== !startInfo.AllowSignal), leave the process running after we (the parent) exit. // After being orphaned (and possibly reparented to the shell), it may continue running or may be terminated by SIGTTIN/SIGTTOU. if (startInfo.AllowSignal) { flags |= RequestFlagsEnableAutoTermination; } using var bw = new MyBinaryWriter(InitialBufferCapacity); var stateHolder = UnixChildProcessState.Create(this, startInfo.AllowSignal); try { bw.Write(stateHolder.State.Token); bw.Write(flags); bw.Write(workingDirectory); bw.Write(resolvedPath); bw.Write((uint)(arguments.Count + 1)); bw.Write(resolvedPath); foreach (var x in arguments) { bw.Write(x); } if (!startInfo.UseCustomEnvironmentVariables) { // Send the environment variables of this process to the helper process. // // NOTE: We cannot cache or detect updates to the environment block; only the runtime can. // Concurrently invoking getenv and setenv is a racy operation; therefore the runtime // employs a process-global lock. // // Fortunately, the caller can take a snapshot of environment variables theirselves. var processEnvVars = Environment.GetEnvironmentVariables(); var envVarCount = processEnvVars.Count; bw.Write((uint)envVarCount); var sortedEnvVars = ArrayPool <KeyValuePair <string, string> > .Shared.Rent(envVarCount); try { EnvironmentVariableListUtil.ToSortedKeyValuePairs(processEnvVars, sortedEnvVars); foreach (var(name, value) in sortedEnvVars.AsSpan <KeyValuePair <string, string> >().Slice(0, envVarCount)) { bw.WriteEnvironmentVariable(name, value); } } finally { ArrayPool <KeyValuePair <string, string> > .Shared.Return(sortedEnvVars); } } else { bw.Write((uint)environmentVariables.Length); foreach (var(name, value) in environmentVariables.Span) { bw.WriteEnvironmentVariable(name, value); } } // Work around https://github.com/microsoft/WSL/issues/6490 // On WSL 1, if you call recvmsg multiple times to fully receive data sent with sendmsg, // the fds will be duplicated for each recvmsg call. // Send only fixed length of of data with the fds and receive that much data with one recvmsg call. // That will be safer anyway. Span <byte> header = stackalloc byte[sizeof(uint) * 2]; if (!BitConverter.TryWriteBytes(header, (uint)UnixHelperProcessCommand.SpawnProcess) || !BitConverter.TryWriteBytes(header.Slice(sizeof(uint)), bw.Length)) { Debug.Fail("Should never fail."); } var subchannel = _helperProcess.RentSubchannelAsync(default).AsTask().GetAwaiter().GetResult();
public PipelineStdHandleCreator(ref ChildProcessStartInfoInternal startInfo) { var stdInputRedirection = startInfo.StdInputRedirection; var stdOutputRedirection = startInfo.StdOutputRedirection; var stdErrorRedirection = startInfo.StdErrorRedirection; var stdInputFile = startInfo.StdInputFile; var stdOutputFile = startInfo.StdOutputFile; var stdErrorFile = startInfo.StdErrorFile; var stdInputHandle = startInfo.StdInputHandle; var stdOutputHandle = startInfo.StdOutputHandle; var stdErrorHandle = startInfo.StdErrorHandle; if (stdInputRedirection == InputRedirection.Handle && stdInputHandle == null) { throw new ArgumentException($"{nameof(ChildProcessStartInfo.StdInputHandle)} must not be null.", nameof(startInfo)); } if (stdInputRedirection == InputRedirection.File && stdInputFile == null) { throw new ArgumentException($"{nameof(ChildProcessStartInfo.StdInputFile)} must not be null.", nameof(startInfo)); } if (stdOutputRedirection == OutputRedirection.Handle && stdOutputHandle == null) { throw new ArgumentException($"{nameof(ChildProcessStartInfo.StdOutputHandle)} must not be null.", nameof(startInfo)); } if (IsFileRedirection(stdOutputRedirection) && stdOutputFile == null) { throw new ArgumentException($"{nameof(ChildProcessStartInfo.StdOutputFile)} must not be null.", nameof(startInfo)); } if (stdErrorRedirection == OutputRedirection.Handle && stdErrorHandle == null) { throw new ArgumentException($"{nameof(ChildProcessStartInfo.StdErrorHandle)} must not be null.", nameof(startInfo)); } if (IsFileRedirection(stdErrorRedirection) && stdErrorFile == null) { throw new ArgumentException($"{nameof(ChildProcessStartInfo.StdErrorFile)} must not be null.", nameof(startInfo)); } bool redirectingToSameFile = IsFileRedirection(stdOutputRedirection) && IsFileRedirection(stdErrorRedirection) && stdOutputFile == stdErrorFile; if (redirectingToSameFile && stdErrorRedirection != stdOutputRedirection) { throw new ArgumentException( "StdOutputRedirection and StdErrorRedirection must be the same value when both stdout and stderr redirect to the same file.", nameof(startInfo)); } try { if (stdInputRedirection == InputRedirection.InputPipe) { (InputStream, _inputReadPipe) = FilePal.CreatePipePairWithAsyncServerSide(System.IO.Pipes.PipeDirection.Out); } if (stdOutputRedirection == OutputRedirection.OutputPipe || stdErrorRedirection == OutputRedirection.OutputPipe) { (OutputStream, _outputWritePipe) = FilePal.CreatePipePairWithAsyncServerSide(System.IO.Pipes.PipeDirection.In); } if (stdOutputRedirection == OutputRedirection.ErrorPipe || stdErrorRedirection == OutputRedirection.ErrorPipe) { (ErrorStream, _errorWritePipe) = FilePal.CreatePipePairWithAsyncServerSide(System.IO.Pipes.PipeDirection.In); } PipelineStdIn = ChooseInput( stdInputRedirection, stdInputFile, stdInputHandle, _inputReadPipe, startInfo.CreateNewConsole); PipelineStdOut = ChooseOutput( stdOutputRedirection, stdOutputFile, stdOutputHandle, _outputWritePipe, _errorWritePipe, startInfo.CreateNewConsole); if (redirectingToSameFile) { PipelineStdErr = PipelineStdOut; } else { PipelineStdErr = ChooseOutput( stdErrorRedirection, stdErrorFile, stdErrorHandle, _outputWritePipe, _errorWritePipe, startInfo.CreateNewConsole); } } catch { Dispose(); throw; } }
public unsafe IChildProcessStateHolder SpawnProcess( ref ChildProcessStartInfoInternal startInfo, string resolvedPath, SafeHandle stdIn, SafeHandle stdOut, SafeHandle stdErr) { var arguments = startInfo.Arguments; var environmentVariables = startInfo.EnvironmentVariables; var workingDirectory = startInfo.WorkingDirectory; var flags = startInfo.Flags; Debug.Assert(startInfo.CreateNewConsole || ConsolePal.HasConsoleWindow()); var commandLine = WindowsCommandLineUtil.MakeCommandLine(resolvedPath, arguments ?? Array.Empty <string>(), !flags.HasDisableArgumentQuoting()); var environmentBlock = startInfo.UseCustomEnvironmentVariables ? WindowsEnvironmentBlockUtil.MakeEnvironmentBlock(environmentVariables.Span) : null; // Objects that need cleanup InputWriterOnlyPseudoConsole?pseudoConsole = null; SafeJobObjectHandle? jobObjectHandle = null; SafeProcessHandle? processHandle = null; SafeThreadHandle? threadHandle = null; try { pseudoConsole = startInfo.CreateNewConsole ? InputWriterOnlyPseudoConsole.Create() : null; if (pseudoConsole is not null && flags.HasUseCustomCodePage()) { ChangeCodePage(pseudoConsole, startInfo.CodePage, workingDirectory); } bool killOnClose = startInfo.AllowSignal && WindowsVersion.NeedsWorkaroundForWindows1809; jobObjectHandle = CreateJobObject(killOnClose, startInfo.DisableWindowsErrorReportingDialog); using var inheritableHandleStore = new InheritableHandleStore(3); var childStdIn = stdIn != null?inheritableHandleStore.Add(stdIn) : null; var childStdOut = stdOut != null?inheritableHandleStore.Add(stdOut) : null; var childStdErr = stdErr != null?inheritableHandleStore.Add(stdErr) : null; IntPtr jobObjectHandles = jobObjectHandle.DangerousGetHandle(); Span <IntPtr> inheritableHandles = stackalloc IntPtr[inheritableHandleStore.Count]; inheritableHandleStore.DangerousGetHandles(inheritableHandles); fixed(IntPtr *pInheritableHandles = inheritableHandles) { using var attr = new ProcThreadAttributeList(3); if (pseudoConsole is not null) { attr.UpdatePseudoConsole(pseudoConsole.Handle.DangerousGetHandle()); } attr.UpdateHandleList(pInheritableHandles, inheritableHandles.Length); attr.UpdateJobList(&jobObjectHandles, 1); const int CreationFlags = Kernel32.CREATE_UNICODE_ENVIRONMENT | Kernel32.EXTENDED_STARTUPINFO_PRESENT; int processId; (processId, processHandle, threadHandle) = InvokeCreateProcess( commandLine, CreationFlags, environmentBlock, workingDirectory, childStdIn, childStdOut, childStdErr, attr); return(new WindowsChildProcessState(processId, processHandle, jobObjectHandle, pseudoConsole, startInfo.AllowSignal)); } } catch { if (processHandle is not null) { Kernel32.TerminateProcess(processHandle, -1); processHandle.Dispose(); } pseudoConsole?.Dispose(); jobObjectHandle?.Dispose(); throw; } finally { threadHandle?.Dispose(); } }