#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/poll.h>
#ifdef __CYGWIN__
#define NOGDI
#include <windows.h>
#endif
#include "distcc.h"
#include "trace.h"
#include "util.h"
#include "exitcode.h"
#include "exec.h"
#include "lock.h"
#include "hosts.h"
#include "dopt.h"
const int timeout_null_fd = -1;
int dcc_job_lifetime = 0;
static void dcc_inside_child(char **argv,
const char *stdin_file,
const char *stdout_file,
const char *stderr_file) NORETURN;
static void dcc_execvp(char **argv) NORETURN;
void dcc_note_execution(struct dcc_hostdef *host, char **argv)
{
char *astr;
astr = dcc_argv_tostr(argv);
rs_log(RS_LOG_INFO|RS_LOG_NONAME, "exec on %s: %s",
host->hostdef_string, astr);
free(astr);
}
int dcc_redirect_fds(const char *stdin_file,
const char *stdout_file,
const char *stderr_file)
{
int ret;
if (stdin_file)
if ((ret = dcc_redirect_fd(STDIN_FILENO, stdin_file, O_RDONLY)))
return ret;
if (stdout_file) {
if ((ret = dcc_redirect_fd(STDOUT_FILENO, stdout_file,
O_WRONLY | O_CREAT | O_TRUNC)))
return ret;
}
if (stderr_file) {
if ((ret = dcc_redirect_fd(STDERR_FILENO, stderr_file,
O_WRONLY | O_CREAT | O_APPEND)))
return ret;
}
return 0;
}
#ifdef __CYGWIN__
static DWORD dcc_execvp_cyg(char **argv, const char *input_file,
const char *output_file, const char *error_file)
{
STARTUPINFO m_siStartInfo;
PROCESS_INFORMATION m_piProcInfo;
char cmdline[MAX_PATH+1]={0};
HANDLE stdin_hndl=INVALID_HANDLE_VALUE;
HANDLE stdout_hndl=INVALID_HANDLE_VALUE;
HANDLE stderr_hndl=INVALID_HANDLE_VALUE;
char **ptr;
DWORD exit_code;
BOOL bRet=0;
ZeroMemory(&m_siStartInfo, sizeof(STARTUPINFO));
ZeroMemory( &m_piProcInfo, sizeof(PROCESS_INFORMATION) );
if (input_file && strcmp(input_file,"/dev/null")!=0)
{
if ((stdin_hndl = CreateFile(input_file,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_ALWAYS,
FILE_ATTRIBUTE_TEMPORARY,NULL)) == INVALID_HANDLE_VALUE) {
exit_code = GetLastError();
goto cleanup;
}
} else
stdin_hndl = GetStdHandle(STD_INPUT_HANDLE);
if (output_file && strcmp(output_file,"/dev/null")!=0)
{
if ((stdout_hndl = CreateFile(output_file,GENERIC_WRITE,FILE_SHARE_READ,NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY,NULL)) == INVALID_HANDLE_VALUE) {
exit_code = GetLastError();
goto cleanup;
}
} else
stdout_hndl = GetStdHandle(STD_OUTPUT_HANDLE);
if (error_file && strcmp(error_file,"/dev/null")!=0)
{
if ((stderr_hndl = CreateFile(error_file, GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_TEMPORARY,NULL)) == INVALID_HANDLE_VALUE) {
exit_code = GetLastError();
goto cleanup;
}
SetFilePointer(stderr_hndl,0,NULL,FILE_END);
} else
stderr_hndl = GetStdHandle(STD_ERROR_HANDLE);
SetHandleInformation(stdin_hndl,HANDLE_FLAG_INHERIT,HANDLE_FLAG_INHERIT);
SetHandleInformation(stdout_hndl,HANDLE_FLAG_INHERIT,HANDLE_FLAG_INHERIT);
SetHandleInformation(stderr_hndl,HANDLE_FLAG_INHERIT,HANDLE_FLAG_INHERIT);
m_siStartInfo.cb = sizeof(STARTUPINFO);
m_siStartInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
m_siStartInfo.wShowWindow = SW_HIDE;
m_siStartInfo.hStdInput = stdin_hndl;
m_siStartInfo.hStdOutput = stdout_hndl;
m_siStartInfo.hStdError = stderr_hndl;
for (ptr=argv;*ptr!=NULL;ptr++)
{
strcat(cmdline, *ptr);
strcat(cmdline, " ");
}
bRet = CreateProcess(NULL,
cmdline,
NULL,
NULL,
TRUE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&m_siStartInfo,
&m_piProcInfo);
if (!bRet) {
exit_code = GetLastError();
goto cleanup;
}
WaitForSingleObject(m_piProcInfo.hProcess, (DWORD)(-1L));
GetExitCodeProcess(m_piProcInfo.hProcess, &exit_code);
CloseHandle(m_piProcInfo.hProcess);
cleanup:
if (stdin_hndl != INVALID_HANDLE_VALUE) CloseHandle(stdin_hndl);
if (stdout_hndl != INVALID_HANDLE_VALUE) CloseHandle(stdout_hndl);
if (stderr_hndl != INVALID_HANDLE_VALUE) CloseHandle(stderr_hndl);
if (bRet)
ExitProcess(exit_code);
else
return exit_code;
}
#endif
static void dcc_execvp(char **argv)
{
char *slash;
execvp(argv[0], argv);
slash = strrchr(argv[0], '/');
if (slash)
execvp(slash + 1, argv);
rs_log_error("failed to exec %s: %s", argv[0], strerror(errno));
dcc_exit(EXIT_COMPILER_MISSING);
}
static void dcc_inside_child(char **argv,
const char *stdin_file,
const char *stdout_file,
const char *stderr_file)
{
int ret;
if ((ret = dcc_ignore_sigpipe(0)))
goto fail;
dcc_increment_safeguard();
#ifdef __CYGWIN__
if (argv[0] && ((argv[0][0] != '\0' && argv[0][1] == ':') ||
(argv[0][0] == '\\' && argv[0][1] == '\\'))) {
DWORD status;
status = dcc_execvp_cyg(argv, stdin_file, stdout_file, stderr_file);
if (status != 3) {
ret = EXIT_DISTCC_FAILED;
goto fail;
}
}
#endif
if ((ret = dcc_redirect_fds(stdin_file, stdout_file, stderr_file)))
goto fail;
dcc_execvp(argv);
ret = EXIT_DISTCC_FAILED;
fail:
dcc_exit(ret);
}
int dcc_new_pgrp(void)
{
if (getpgrp() == getpid()) {
rs_trace("already a process group leader");
return 0;
}
if (setpgid(0, 0) == 0) {
rs_trace("entered process group");
return 0;
} else {
rs_trace("setpgid(0, 0) failed: %s", strerror(errno));
return EXIT_DISTCC_FAILED;
}
}
int dcc_spawn_child(char **argv, pid_t *pidptr,
const char *stdin_file,
const char *stdout_file,
const char *stderr_file)
{
pid_t pid;
dcc_trace_argv("forking to execute", argv);
pid = fork();
if (pid == -1) {
rs_log_error("failed to fork: %s", strerror(errno));
return EXIT_OUT_OF_MEMORY;
} else if (pid == 0) {
if (stdout_file != NULL) {
if (dcc_new_pgrp() != 0)
rs_trace("Unable to start a new group\n");
}
dcc_inside_child(argv, stdin_file, stdout_file, stderr_file);
} else {
*pidptr = pid;
rs_trace("child started as pid%d", (int) pid);
return 0;
}
}
void dcc_reset_signal(int whichsig)
{
struct sigaction act_dfl;
memset(&act_dfl, 0, sizeof act_dfl);
act_dfl.sa_handler = SIG_DFL;
sigaction(whichsig, &act_dfl, NULL);
}
static int sys_wait4(pid_t pid, int *status, int options, struct rusage *rusage)
{
#ifdef HAVE_WAITPID
memset(rusage, 0, sizeof *rusage);
return waitpid(pid, status, options);
#elif HAVE_WAIT4
return wait4(pid, status, options, rusage);
#else
#error Please port this
#endif
}
int dcc_collect_child(const char *what, pid_t pid,
int *wait_status, int in_fd)
{
struct rusage ru;
pid_t ret_pid;
int ret;
int wait_timeout_sec;
fd_set fds,readfds;
wait_timeout_sec = dcc_job_lifetime;
FD_ZERO(&readfds);
if (in_fd != timeout_null_fd){
FD_SET(in_fd,&readfds);
}
while (!dcc_job_lifetime || wait_timeout_sec-- >= 0) {
int flags = (in_fd == timeout_null_fd) ? 0 : WNOHANG;
ret_pid = sys_wait4(pid, wait_status, flags, &ru);
if (ret_pid == -1) {
if (errno == EINTR) {
rs_trace("wait4 was interrupted; retrying");
} else {
rs_log_error("sys_wait4(pid=%d) borked: %s", (int) pid, strerror(errno));
return EXIT_DISTCC_FAILED;
}
} else if (ret_pid != 0) {
rs_trace("%s child %ld terminated with status %#x",
what, (long) ret_pid, *wait_status);
rs_log_info("%s times: user %ld.%06lds, system %ld.%06lds, "
"%ld minflt, %ld majflt",
what,
ru.ru_utime.tv_sec, (long) ru.ru_utime.tv_usec,
ru.ru_stime.tv_sec, (long) ru.ru_stime.tv_usec,
ru.ru_minflt, ru.ru_majflt);
return 0;
}
if (in_fd != timeout_null_fd){
struct timeval timeout;
fds = readfds;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
ret = select(in_fd+1,&fds,NULL,NULL,&timeout);
if (ret == 1) {
char buf;
int nread = read(in_fd, &buf, 1);
if ((nread == -1) && (errno == EWOULDBLOCK)) {
;
} else if (nread == 0) {
rs_log_error("Client fd disconnected, killing job");
if (killpg(pid,SIGTERM)!=0)
kill(pid, SIGTERM);
return EXIT_IO_ERROR;
} else if (nread == 1) {
rs_log_error("Bug! Read from fd succeeded when checking whether client disconnected!");
} else
rs_log_error("Bug! nread %d, errno %d checking whether client disconnected!", nread, errno);
}
} else
poll(NULL, 0, 1000);
}
if (killpg(pid,SIGTERM) !=0 )
kill(pid, SIGTERM);
rs_log_error("Compilation takes too long, timeout.");
return EXIT_TIMEOUT;
}
int dcc_critique_status(int status,
const char *command,
const char *input_fname,
struct dcc_hostdef *host,
int verbose)
{
int logmode;
if (verbose)
logmode = RS_LOG_ERR | RS_LOG_NONAME;
else
logmode = RS_LOG_INFO | RS_LOG_NONAME;
if (input_fname == NULL)
input_fname = "(null)";
if (WIFSIGNALED(status)) {
#ifdef HAVE_STRSIGNAL
rs_log(logmode,
"%s %s on %s:%s %s",
command, input_fname, host->hostdef_string,
strsignal(WTERMSIG(status)),
WCOREDUMP(status) ? " (core dumped)" : "");
#else
rs_log(logmode,
"%s %s on %s terminated by signal %d%s",
command, input_fname, host->hostdef_string,
WTERMSIG(status),
WCOREDUMP(status) ? " (core dumped)" : "");
#endif
return 128 + WTERMSIG(status);
} else if (WEXITSTATUS(status) == 1) {
rs_log(logmode, "%s %s on %s failed", command, input_fname, host->hostdef_string);
return WEXITSTATUS(status);
} else if (WEXITSTATUS(status)) {
rs_log(logmode,
"%s %s on %s failed with exit code %d",
command, input_fname, host->hostdef_string, WEXITSTATUS(status));
return WEXITSTATUS(status);
} else {
rs_log(RS_LOG_INFO|RS_LOG_NONAME,
"%s %s on %s completed ok", command, input_fname, host->hostdef_string);
return 0;
}
}