/* * The dumb-init signal handler. * * The main job of this signal handler is to forward signals along to our child * process(es). In setsid mode, this means signaling the entire process group * rooted at our child. In non-setsid mode, this is just signaling the primary * child. * * In most cases, simply proxying the received signal is sufficient. If we * receive a job control signal, however, we should not only forward it, but * also sleep dumb-init itself. * * This allows users to run foreground processes using dumb-init and to * control them using normal shell job control features (e.g. Ctrl-Z to * generate a SIGTSTP and suspend the process). * * The libc manual is useful: * https://www.gnu.org/software/libc/manual/html_node/Job-Control-Signals.html * * When running in setsid mode, however, it is not sufficient to forward * SIGTSTP/SIGTTIN/SIGTTOU in most cases. If the process has not added a custom * signal handler for these signals, then the kernel will not apply default * signal handling behavior (which would be suspending the process) since it is * a member of an orphaned process group. * * Sadly this doesn't appear to be well documented except in the kernel itself: * https://github.com/torvalds/linux/blob/v4.2/kernel/signal.c#L2296-L2299 * * Forwarding SIGSTOP instead is effective, though not ideal; unlike SIGTSTP, * SIGSTOP cannot be caught, and so it doesn't allow processes a change to * clean up before suspending. In non-setsid mode, we proxy the original signal * instead of SIGSTOP for this reason. */ void handle_signal(int signum) { DEBUG("Received signal %d.\n", signum); if ( signum == SIGTSTP || // tty: background yourself signum == SIGTTIN || // tty: stop reading signum == SIGTTOU // tty: stop writing ) { if (use_setsid) { DEBUG("Running in setsid mode, so forwarding SIGSTOP instead.\n"); forward_signal(SIGSTOP); } else { DEBUG("Not running in setsid mode, so forwarding the original signal (%d).\n", signum); forward_signal(signum); } DEBUG("Suspending self due to TTY signal.\n"); kill(getpid(), SIGSTOP); } else { forward_signal(signum); } signal_forwarded = 1; }
int main(int argc, char *argv[]) { int signum, opt; struct option long_options[] = { {"help", no_argument, NULL, 'h'}, {"single-child", no_argument, NULL, 'c'}, {"verbose", no_argument, NULL, 'v'}, {"version", no_argument, NULL, 'V'}, }; while ((opt = getopt_long(argc, argv, "+hvVc", long_options, NULL)) != -1) { switch (opt) { case 'h': print_help(argv); return 0; case 'v': debug = 1; break; case 'V': fprintf(stderr, "dumb-init v%s", VERSION); return 0; case 'c': use_setsid = 0; break; default: return 1; } } if (optind >= argc) { fprintf( stderr, "Usage: %s [option] program [args]\n" "Try %s --help for full usage.\n", argv[0], argv[0] ); return 1; } char **cmd = &argv[optind]; char *debug_env = getenv("DUMB_INIT_DEBUG"); if (debug_env && strcmp(debug_env, "1") == 0) { debug = 1; DEBUG("Running in debug mode.\n"); } char *setsid_env = getenv("DUMB_INIT_SETSID"); if (setsid_env && strcmp(setsid_env, "0") == 0) { use_setsid = 0; DEBUG("Not running in setsid mode.\n"); } /* register signal handlers */ for (signum = 1; signum < 32; signum++) { if (signum == SIGKILL || signum == SIGSTOP || signum == SIGCHLD) continue; if (signal(signum, handle_signal) == SIG_ERR) { PRINTERR("Couldn't register signal handler for signal `%d`. Exiting.\n", signum); return 1; } } /* launch our process */ child_pid = fork(); if (child_pid < 0) { PRINTERR("Unable to fork. Exiting.\n"); return 1; } if (child_pid == 0) { if (use_setsid) { pid_t result = setsid(); if (result == -1) { PRINTERR( "Unable to setsid (errno=%d %s). Exiting.\n", errno, strerror(errno) ); exit(1); } DEBUG("setsid complete.\n"); } execvp(cmd[0], &cmd[0]); // if this point is reached, exec failed, so we should exit nonzero PRINTERR("%s: %s\n", argv[1], strerror(errno)); exit(2); } else { pid_t killed_pid; int child_exit_status = 0, exit_status, status; DEBUG("Child spawned with PID %d.\n", child_pid); while ((killed_pid = waitpid(-1, &status, 0))) { exit_status = WEXITSTATUS(status); DEBUG("A child with PID %d exited with exit status %d.\n", killed_pid, exit_status); if (killed_pid == -1) { if (errno == 10) { DEBUG("No more child processes to wait for. Exiting.\n"); exit_status = child_exit_status; } else { PRINTERR( "waitpid (errno=%d %s). Exiting.\n", errno, strerror(errno) ); } exit(exit_status); } else if (killed_pid == child_pid) { DEBUG("Child exited with status %d.\n", exit_status); if (use_setsid) { // send SIGTERM to any remaining children if not using // single child mode and not done already if (!signal_forwarded) { forward_signal(SIGTERM); } } else { // in single child mode leave after direct child exited if // we are not init if (getpid() != 1) { DEBUG("Goodbye.\n"); exit(exit_status); } } child_exit_status = exit_status; } } } return 0; }
int main(int argc, char *argv[]) { FILE *input; char *filename; char line[MAXLINE]; char *command, *fromstr, *tostr, *signal_type, *source, *destination, *tokptr; system_state state; entity from, to; signal signal; int i; if (argc == 1) { filename = "/dev/stdin"; } else { filename = argv[1]; if (strcmp(filename, "-")) filename = "/dev/stdin"; } input = fopen(filename, "r"); if (!input) { perror("Could not open file for reading"); return 2; } initialize_system_state(&state); while(fgets(line, MAXLINE, input)) { command = strtok_r(line, " \t\r\n", &tokptr); if (!command) continue; if (!strcasecmp(command, "SIGNAL")) { fromstr = strtok_r(NULL, " \t\r\n", &tokptr); strtok_r(NULL, " \t\r\n", &tokptr); // ignore "to" tostr = strtok_r(NULL, ": \t\r\n", &tokptr); signal_type = strtok_r(NULL, " \t\r\n", &tokptr); source = strtok_r(NULL, " \t\r\n", &tokptr); strtok_r(NULL, " \t\r\n", &tokptr); // ignore potential "to" destination = strtok_r(NULL, " \t\r\n", &tokptr); from = str_to_entity(fromstr); to = str_to_entity(tostr); signal.type = SIGNAL_INVALID; signal.source = str_to_connection(source); signal.destination = str_to_connection(destination); for (i = 0; i < NUM_POSSIBLE_SIGNALS; i++) if (!strcasecmp(signal_type, signal_name[i])) signal.type = i; forward_signal(&state, from, to, signal); printf("\n"); } else if (!strcasecmp(command, "QUIT")) { break; } else { printf("Invalid command: %s\n", command); } } print_state(&state); destroy_system_state(&state); if (ferror(input)) { perror("Could not read from input file"); fclose(input); return 3; } fclose(input); return EXIT_SUCCESS; }