diff --git a/src/ansi-c/c_preprocess.cpp b/src/ansi-c/c_preprocess.cpp index f9fc552b7e4..b60228bb68f 100644 --- a/src/ansi-c/c_preprocess.cpp +++ b/src/ansi-c/c_preprocess.cpp @@ -305,8 +305,6 @@ bool c_preprocess_visual_studio( command_file << shell_quote(file) << "\n"; } - // _popen isn't very reliable on WIN32 - // that's why we use run() int result = run("cl", {"cl", "@" + command_file_name()}, "", outstream, stderr_file()); diff --git a/src/util/run.cpp b/src/util/run.cpp index 52a386540e9..1a50b4bb59f 100644 --- a/src/util/run.cpp +++ b/src/util/run.cpp @@ -541,40 +541,119 @@ int run( return result; #else - std::string command; + int pipefd[2]; + if(pipe(pipefd) == -1) + return -1; + + int stdin_fd = stdio_redirection(STDIN_FILENO, std_input); + int stdout_fd = pipefd[1]; + int stderr_fd = stdio_redirection(STDERR_FILENO, std_error); - bool first = true; + if(stdin_fd == -1 || stdout_fd == -1 || stderr_fd == -1) + return 1; + + // temporarily suspend all signals + sigset_t new_mask, old_mask; + sigemptyset(&new_mask); + sigprocmask(SIG_SETMASK, &new_mask, &old_mask); - // note we use 'what' instead of 'argv[0]' as the name of the executable - for(const auto &arg : argv) + /* now create new process */ + pid_t childpid = fork(); + + if(childpid >= 0) /* fork succeeded */ { - if(first) // this is argv[0] + if(childpid == 0) /* fork() returns 0 to the child process */ { - command += shell_quote(what); - first = false; + // resume signals + remove_signal_catcher(); + sigprocmask(SIG_SETMASK, &old_mask, nullptr); + + close(pipefd[0]); // unused in child + + std::vector _argv(argv.size() + 1); + for(std::size_t i = 0; i < argv.size(); i++) + _argv[i] = strdup(argv[i].c_str()); + + _argv[argv.size()] = nullptr; + + if(stdin_fd != STDIN_FILENO) + dup2(stdin_fd, STDIN_FILENO); + if(stdout_fd != STDOUT_FILENO) + dup2(stdout_fd, STDOUT_FILENO); + if(stderr_fd != STDERR_FILENO) + dup2(stderr_fd, STDERR_FILENO); + + errno = 0; + execvp(what.c_str(), _argv.data()); + + /* usually no return */ + perror(std::string("execvp " + what + " failed").c_str()); + exit(1); } - else - command += " " + shell_quote(arg); - } + else /* fork() returns new pid to the parent process */ + { + // must do before resuming signals to avoid race + register_child(childpid); - if(!std_input.empty()) - command += " < " + shell_quote(std_input); + // resume signals + sigprocmask(SIG_SETMASK, &old_mask, nullptr); - if(!std_error.empty()) - command += " 2> " + shell_quote(std_error); + close(pipefd[1]); // unused in the parent + + const int buffer_size = 1024; + std::vector buffer(buffer_size); + ssize_t bytes_read; - FILE *stream=popen(command.c_str(), "r"); + while((bytes_read = read(pipefd[0], buffer.data(), buffer_size)) > 0) + std_output.write(buffer.data(), bytes_read); - if(stream!=nullptr) + int status; /* parent process: child's exit status */ + + /* wait for child to exit, and store its status */ + while(waitpid(childpid, &status, 0) == -1) + { + if(errno == EINTR) + continue; // try again + else + { + unregister_child(); + + perror("Waiting for child process failed"); + if(stdin_fd != STDIN_FILENO) + close(stdin_fd); + if(stdout_fd != STDOUT_FILENO) + close(stdout_fd); + if(stderr_fd != STDERR_FILENO) + close(stderr_fd); + return 1; + } + } + + unregister_child(); + + if(stdin_fd != STDIN_FILENO) + close(stdin_fd); + if(stdout_fd != STDOUT_FILENO) + close(stdout_fd); + if(stderr_fd != STDERR_FILENO) + close(stderr_fd); + + return WEXITSTATUS(status); + } + } + else /* fork returns -1 on failure */ { - int ch; - while((ch=fgetc(stream))!=EOF) - std_output << (unsigned char)ch; + // resume signals + sigprocmask(SIG_SETMASK, &old_mask, nullptr); - int result = pclose(stream); - return WEXITSTATUS(result); + if(stdin_fd != STDIN_FILENO) + close(stdin_fd); + if(stdout_fd != STDOUT_FILENO) + close(stdout_fd); + if(stderr_fd != STDERR_FILENO) + close(stderr_fd); + + return 1; } - else - return -1; - #endif +#endif }