// TODO: restructure this function // For example posix_spawn from chromium seems to have a // maintainable flow: https://chromium.googlesource.com/native_client/nacl-newlib/+/bf66148d14c7fca26b9198dd5dc81e743893bb66/newlib/libc/posix/posix_spawn.c private static unsafe int ForkAndExec( string filename, string[] argv, string[] envp, string cwd, bool useTty, bool redirectStdin, bool redirectStdout, bool redirectStderr, out int stdin, out int stdout, out int stderr ) { byte **argvPtr = null; byte **envpPtr = null; bool success = true; ArrayHelpers.AllocNullTerminatedArray(argv, ref argvPtr); ArrayHelpers.AllocNullTerminatedArray(envp, ref envpPtr); int[] stdInFds = new[] { -1, -1 }; int[] stdOutFds = new[] { -1, -1 }; int[] stdErrFds = new[] { -1, -1 }; int masterFd = -1; int slaveFd = -1; try { int inFd, outFd, errFd; if (filename == null || argv == null || envp == null) { success = false; throw new ArgumentException("Provide the correct arguments"); } if (Libc.access(filename, Libc.X_OK) != 0) { success = false; throw new Exception("The given file is not accessible"); } if (useTty) { var size = new winsize { ws_col = DEFAULT_WIDTH, ws_row = DEFAULT_HEIGHT }; if (Libc.openpty(out masterFd, out slaveFd, IntPtr.Zero, IntPtr.Zero, ref size) == -1) { success = false; throw new Exception("Could not open a new pty"); } } else { try { if (redirectStdin) { CreateCloseOnExecPipe(stdInFds); } if (redirectStdout) { CreateCloseOnExecPipe(stdOutFds); } if (redirectStderr) { CreateCloseOnExecPipe(stdErrFds); } } catch (Exception ex) { success = false; throw ex; } } var pid = Libc.fork(); if (pid < 0) { success = false; Error.ThrowExceptionForLastError(); } if (pid == 0) { if (useTty) { Libc.close(masterFd); Libc.setsid(); if (Libc.ioctl(slaveFd, Libc.TIOCSCTTY) == -1) { success = false; Error.ThrowExceptionForLastError(); } inFd = outFd = errFd = slaveFd; } else { inFd = stdInFds[READ_END_OF_PIPE]; outFd = stdOutFds[WRITE_END_OF_PIPE]; errFd = stdErrFds[WRITE_END_OF_PIPE]; } // TODO: this code is just horrible and the likely hood introducing bugs here is quite high. // I should refactor this asap. But first I would love to get some more tests in place that // could catch any regressions.. if (redirectStdin) { while (Error.ShouldRetrySyscall(Libc.dup2(inFd, Libc.STDIN_FILENO)) && Libc.errno == Libc.EBUSY) { ; } } if (redirectStdout) { while (Error.ShouldRetrySyscall(Libc.dup2(outFd, Libc.STDOUT_FILENO)) && Libc.errno == Libc.EBUSY) { ; } } if (redirectStderr) { while (Error.ShouldRetrySyscall(Libc.dup2(errFd, Libc.STDERR_FILENO)) && Libc.errno == Libc.EBUSY) { ; } } if (!string.IsNullOrEmpty(cwd)) { var ret = Libc.chdir(cwd); if (ret == -1) { success = false; Error.ThrowExceptionForLastError(); } } Libc.execve(filename, argvPtr, envpPtr); // Exec syscall should never return, and thus we should never get here, if we do then exit immediately Libc._exit(Libc.errno != 0 ? Libc.errno : -1); } Libc.close(slaveFd); if (useTty) { stdin = masterFd; stdout = masterFd; stderr = masterFd; } else { stdin = stdInFds[WRITE_END_OF_PIPE]; stdout = stdOutFds[READ_END_OF_PIPE]; stderr = stdErrFds[READ_END_OF_PIPE]; } return(pid); } finally { // Regardless of success or failure, close the parent's copy of the child's end of // any opened pipes. The parent doesn't need them anymore. CloseIfOpen(stdInFds[READ_END_OF_PIPE]); CloseIfOpen(stdOutFds[WRITE_END_OF_PIPE]); CloseIfOpen(stdErrFds[WRITE_END_OF_PIPE]); if (!success) { // Cleanup all open fd CloseIfOpen(masterFd); CloseIfOpen(slaveFd); CloseIfOpen(stdInFds[WRITE_END_OF_PIPE]); CloseIfOpen(stdOutFds[READ_END_OF_PIPE]); CloseIfOpen(stdErrFds[READ_END_OF_PIPE]); } ArrayHelpers.FreeArray(argvPtr, argv.Length); ArrayHelpers.FreeArray(envpPtr, envp.Length); } }