/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include #include #include #define PRINT_FATAL(...) fprintf(stderr, "[FATAL] "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); #define PRINT_WARNING(...) if (verbosity > 0) { fprintf(stderr, "[WARN ] "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } #define PRINT_INFO(...) if (verbosity > 1) { fprintf(stderr, "[INFO ] "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } #define PRINT_DEBUG(...) if (verbosity > 2) { fprintf(stderr, "[DEBUG] "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } #define PRINT_TRACE(...) if (verbosity > 3) { fprintf(stderr, "[TRACE] "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } #define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0])) static int verbosity = 0; static struct timespec ts = { .tv_sec = 1, .tv_nsec = 0 }; int spawn(const sigset_t* const child_sigset_ptr, char (*argv[]), int* const child_pid_ptr) { pid_t pid; pid = fork(); if (pid < 0) { PRINT_FATAL("Fork failed: '%s'", strerror(errno)); return 1; } else if (pid == 0) { // Child if (sigprocmask(SIG_SETMASK, child_sigset_ptr, NULL)) { PRINT_FATAL("Setting child signal mask failed: '%s'", strerror(errno)); return 1; } execvp(argv[0], argv); PRINT_FATAL("Executing child process '%s' failed: '%s'", argv[0], strerror(errno)); return 1; } else { // Parent PRINT_INFO("Spawned child process '%s' with pid '%i'", argv[0], pid); *child_pid_ptr = pid; return 0; } } void print_usage(char* const name, FILE* const file) { fprintf(file, "Usage: %s [-h | program arg1 arg2]\n", name); } int parse_args(const int argc, char* const argv[], char* (**child_args_ptr_ptr)[], int* const parse_fail_exitcode_ptr) { char* name = argv[0]; int c; while ((c = getopt (argc, argv, "hv")) != -1) { switch (c) { case 'h': /* TODO - Shouldn't cause exit with -1 ..*/ print_usage(name, stdout); *parse_fail_exitcode_ptr = 0; return 1; case 'v': verbosity++; break; case '?': print_usage(name, stderr); return 1; default: /* Should never happen */ return 1; } } *child_args_ptr_ptr = calloc(argc-optind+1, sizeof(char*)); if (*child_args_ptr_ptr == NULL) { PRINT_FATAL("Failed to allocate memory for child args: '%s'", strerror(errno)); return 1; } int i; for (i = 0; i < argc - optind; i++) { (**child_args_ptr_ptr)[i] = argv[optind+i]; } (**child_args_ptr_ptr)[i] = NULL; if (i == 0) { /* User forgot to provide args! */ print_usage(name, stderr); return 1; } return 0; } int prepare_sigmask(sigset_t* const parent_sigset_ptr, sigset_t* const child_sigset_ptr) { /* Prepare signals to block; make sure we don't block program error signals. */ if (sigfillset(parent_sigset_ptr)) { PRINT_FATAL("sigfillset failed: '%s'", strerror(errno)); return 1; } uint i; int ignore_signals[] = {SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGABRT, SIGTRAP, SIGSYS} ; for (i = 0; i < ARRAY_LEN(ignore_signals); i++) { if (sigdelset(parent_sigset_ptr, ignore_signals[i])) { PRINT_FATAL("sigdelset failed: '%i'", ignore_signals[i]); return 1; } } if (sigprocmask(SIG_SETMASK, parent_sigset_ptr, child_sigset_ptr)) { PRINT_FATAL("sigprocmask failed: '%s'", strerror(errno)); return 1; } return 0; } int wait_and_forward_signal(sigset_t const* const parent_sigset_ptr, pid_t const child_pid) { siginfo_t sig; if (sigtimedwait(parent_sigset_ptr, &sig, &ts) == -1) { switch (errno) { case EAGAIN: break; case EINTR: break; default: PRINT_FATAL("Unexpected error in sigtimedwait: '%s'", strerror(errno)); return 1; } } else { /* There is a signal to handle here */ switch (sig.si_signo) { case SIGCHLD: /* Special-cased, as we don't forward SIGCHLD. Instead, we'll * fallthrough to reaping processes. */ PRINT_DEBUG("Received SIGCHLD"); break; default: PRINT_DEBUG("Passing signal: '%s'", strsignal(sig.si_signo)); /* Forward anything else */ if (kill(child_pid, sig.si_signo)) { if (errno == ESRCH) { PRINT_WARNING("Child was dead when forwarding signal"); } else { PRINT_FATAL("Unexpected error when forwarding signal: '%s'", strerror(errno)); return 1; } } break; } } return 0; } int reap_zombies(const pid_t child_pid, int* const child_exitcode_ptr) { pid_t current_pid; int current_status; while (1) { current_pid = waitpid(-1, ¤t_status, WNOHANG); switch (current_pid) { case -1: if (errno == ECHILD) { PRINT_TRACE("No child to wait."); break; } PRINT_FATAL("Error while waiting for pids: '%s'", strerror(errno)); return 1; case 0: PRINT_TRACE("No child to reap."); break; default: /* A child was reaped. Check whether it's the main one. If it is, then * set the exit_code, which will cause us to exit once we've reaped everyone else. */ PRINT_DEBUG("Reaped child with pid: '%i'", current_pid); if (current_pid == child_pid) { if (WIFEXITED(current_status)) { /* Our process exited normally. */ PRINT_INFO("Main child exited normally (with status '%i')", WEXITSTATUS(current_status)); *child_exitcode_ptr = WEXITSTATUS(current_status); } else if (WIFSIGNALED(current_status)) { /* Our process was terminated. Emulate what sh / bash * would do, which is to return 128 + signal number. */ PRINT_INFO("Main child exited with signal (with signal '%s')", strsignal(WTERMSIG(current_status))); *child_exitcode_ptr = 128 + WTERMSIG(current_status); } else { PRINT_FATAL("Main child exited for unknown reason!"); return 1; } } // Check if other childs have been reaped. continue; } /* If we make it here, that's because we did not continue in the switch case. */ break; } return 0; } int main(int argc, char *argv[]) { pid_t child_pid; // Those are passed to functions to get an exitcode back. int child_exitcode = -1; // This isn't a valid exitcode, and lets us tell whether the child has exited. int parse_exitcode = 1; // By default, we exit with 1 if parsing fails. /* Prepare sigmask */ sigset_t parent_sigset; sigset_t child_sigset; if (prepare_sigmask(&parent_sigset, &child_sigset)) { return 1; } /* Parse command line arguments */ char* (*child_args_ptr)[]; int parse_args_ret = parse_args(argc, argv, &child_args_ptr, &parse_exitcode); if (parse_args_ret) { return parse_exitcode; } if (spawn(&child_sigset, *child_args_ptr, &child_pid)) { return 1; } free(child_args_ptr); while (1) { /* Wait for one signal, and forward it */ if (wait_and_forward_signal(&parent_sigset, child_pid)) { return 1; } /* Now, reap zombies */ if (reap_zombies(child_pid, &child_exitcode)) { return 1; } if (child_exitcode != -1) { PRINT_TRACE("Child has exited. Exiting"); return child_exitcode; } } }