Ejemplo n.º 1
0
        // 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);
            }
        }