// Run the command specified by the argv array and kill it after timeout // seconds. static void SpawnCommand(char *const *argv, double timeout_secs) { CHECK_CALL(global_child_pid = fork()); if (global_child_pid == 0) { // In child. CHECK_CALL(setsid()); ClearSignalMask(); // Force umask to include read and execute for everyone, to make // output permissions predictable. umask(022); // Does not return unless something went wrong. execvp(argv[0], argv); err(EXIT_FAILURE, "execvp(\"%s\", ...)", argv[0]); } else { // In parent. // Set up a signal handler which kills all subprocesses when the given // signal is triggered. HandleSignal(SIGALRM, OnSignal); HandleSignal(SIGTERM, OnSignal); HandleSignal(SIGINT, OnSignal); SetTimeout(timeout_secs); int status = WaitChild(global_child_pid, argv[0]); // The child is done for, but may have grandchildren that we still have to // kill. kill(-global_child_pid, SIGKILL); if (global_signal > 0) { // Don't trust the exit code if we got a timeout or signal. UnHandle(global_signal); raise(global_signal); } else if (WIFEXITED(status)) { exit(WEXITSTATUS(status)); } else { int sig = WTERMSIG(status); UnHandle(sig); raise(sig); } } }
// Usage: process-wrapper // <timeout_sec> <kill_delay_sec> <stdout file> <stderr file> // [cmdline] int main(int argc, char *argv[]) { if (argc <= 5) { DIE("Not enough cmd line arguments to process-wrapper"); } // Parse the cmdline args to get the timeout and redirect files. argv++; double timeout; if (sscanf(*argv++, "%lf", &timeout) != 1) { DIE("timeout_sec is not a real number."); } if (sscanf(*argv++, "%lf", &global_kill_delay) != 1) { DIE("kill_delay_sec is not a real number."); } char *stdout_path = *argv++; char *stderr_path = *argv++; if (strcmp(stdout_path, "-")) { // Redirect stdout and stderr. int fd_out = open(stdout_path, O_WRONLY|O_CREAT|O_TRUNC, 0666); if (fd_out == -1) { DIE("Could not open %s for stdout", stdout_path); } if (dup2(fd_out, STDOUT_FILENO) == -1) { DIE("dup2 failed for stdout"); } CHECK_CALL(close(fd_out)); } if (strcmp(stderr_path, "-")) { int fd_err = open(stderr_path, O_WRONLY|O_CREAT|O_TRUNC, 0666); if (fd_err == -1) { DIE("Could not open %s for stderr", stderr_path); } if (dup2(fd_err, STDERR_FILENO) == -1) { DIE("dup2 failed for stderr"); } CHECK_CALL(close(fd_err)); } global_pid = fork(); if (global_pid < 0) { DIE("Fork failed"); } else if (global_pid == 0) { // In child. if (setsid() == -1) { DIE("Could not setsid from child"); } ClearSignalMask(); // Force umask to include read and execute for everyone, to make // output permissions predictable. umask(022); execvp(argv[0], argv); // Does not return. DIE("execvpe %s failed", argv[0]); } else { // In parent. InstallSignalHandler(SIGALRM); InstallSignalHandler(SIGTERM); InstallSignalHandler(SIGINT); EnableAlarm(timeout); int status = WaitChild(global_pid, argv[0]); // The child is done, but may have grandchildren. kill(-global_pid, SIGKILL); if (global_signal > 0) { // Don't trust the exit code if we got a timeout or signal. UnHandle(global_signal); raise(global_signal); } else if (WIFEXITED(status)) { exit(WEXITSTATUS(status)); } else { int sig = WTERMSIG(status); UnHandle(sig); raise(sig); } } }