/* See LICENSE file for copyright and license details. */ #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"); } static int verbosity = 0; pid_t spawn(sigset_t set, char *const argv[]) { pid_t pid; pid = fork(); if (pid < 0) { PRINT_FATAL("Fork failed: '%s'", strerror(errno)); _exit(1); } else if (pid == 0) { sigprocmask(SIG_UNBLOCK, &set, NULL); execvp(argv[0], argv); PRINT_FATAL("Executing child process failed: '%s'", strerror(errno)); _exit(1); } else { return pid; } } void print_usage_and_exit(const char *name, FILE *file, const int status) { fprintf(file, "Usage: %s [-h | program arg1 arg2]\n", name); exit(status); } int main(int argc, char *argv[]) { char* name = argv[0]; siginfo_t sig; pid_t child_pid; pid_t current_pid; int current_status; int exit_code = -1; struct timespec ts; ts.tv_sec = 1; ts.tv_nsec = 0; sigset_t set; /* Start with argument processing */ int c; while ((c = getopt (argc, argv, "hv")) != -1) { switch (c) { case 'h': print_usage_and_exit(name, stdout, 0); break; case 'v': verbosity++; break; case '?': print_usage_and_exit(name, stderr, 1); break; default: /* Should never happen */ abort(); } } int i; char* child_args[argc-optind+1]; for (i = 0; i < argc - optind; i++) { child_args[i] = argv[optind+i]; } child_args[i] = NULL; if (i == 0) { /* User forgot to provide args! */ print_usage_and_exit(name, stdout, 1); } /* Prepare signals */ if (sigfillset(&set)) { PRINT_FATAL("sigfillset failed: '%s'", strerror(errno)); return 1; } if (sigprocmask(SIG_BLOCK, &set, NULL)) { PRINT_FATAL("Blocking signals failed: '%s'", strerror(errno)); return 1; } /* Spawn the main command */ child_pid = spawn(set, child_args); PRINT_INFO("Spawned child process"); /* Loop forever: * - Reap zombies * - Forward signals */ while (1) { if (sigtimedwait(&set, &sig, &ts) == -1) { switch (errno) { case EAGAIN: break; case EINTR: break; case EINVAL: PRINT_INFO("EINVAL on sigtimedwait!"); return 2; } } 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 */ kill(child_pid, sig.si_signo); break; } } /* Now, reap zombies */ while (1) { current_pid = waitpid(-1, ¤t_status, WNOHANG); switch (current_pid) { case -1: if (errno == ECHILD) { // No childs to wait. Let's break out of the loop. break; } /* An unknown error occured. Print it and exit. */ PRINT_FATAL("Error while waiting for pids: '%s'", strerror(errno)); return 1; case 0: /* No child to reap. We'll break out of the loop here. */ 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)); exit_code = 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))); exit_code = 128 + WTERMSIG(current_status); } else { PRINT_FATAL("Main child exited for unknown reason!"); return 1; } } continue; } /* If exit_code is not equal to -1, then we're exiting because our main child has exited */ if (exit_code != -1 ) { return exit_code; } /* If we make it here, that's because we did not continue in the switch case. */ break; } } /* not reachable */ return 0; }