1/* oneit.c - tiny init replacement to launch a single child process. 2 * 3 * Copyright 2005, 2007 by Rob Landley <rob@landley.net>. 4 5USE_ONEIT(NEWTOY(oneit, "^<1nc:p3[!pn]", TOYFLAG_SBIN)) 6 7config ONEIT 8 bool "oneit" 9 default y 10 help 11 usage: oneit [-p] [-c /dev/tty0] command [...] 12 13 Simple init program that runs a single supplied command line with a 14 controlling tty (so CTRL-C can kill it). 15 16 -c Which console device to use (/dev/console doesn't do CTRL-C, etc). 17 -p Power off instead of rebooting when command exits. 18 -r Restart child when it exits. 19 -3 Write 32 bit PID of each exiting reparented process to fd 3 of child. 20 (Blocking writes, child must read to avoid eventual deadlock.) 21 22 Spawns a single child process (because PID 1 has signals blocked) 23 in its own session, reaps zombies until the child exits, then 24 reboots the system (or powers off with -p, or restarts the child with -r). 25*/ 26 27#define FOR_oneit 28#include "toys.h" 29#include <sys/reboot.h> 30 31GLOBALS( 32 char *console; 33) 34 35// The minimum amount of work necessary to get ctrl-c and such to work is: 36// 37// - Fork a child (PID 1 is special: can't exit, has various signals blocked). 38// - Do a setsid() (so we have our own session). 39// - In the child, attach stdio to /dev/tty0 (/dev/console is special) 40// - Exec the rest of the command line. 41// 42// PID 1 then reaps zombies until the child process it spawned exits, at which 43// point it calls sync() and reboot(). I could stick a kill -1 in there. 44 45// Perform actions in response to signals. (Only root can send us signals.) 46static void oneit_signaled(int signal) 47{ 48 int action = RB_AUTOBOOT; 49 50 toys.signal = signal; 51 if (signal == SIGUSR1) action = RB_HALT_SYSTEM; 52 if (signal == SIGUSR2) action = RB_POWER_OFF; 53 54 // PID 1 can't call reboot() because it kills the task that calls it, 55 // which causes the kernel to panic before the actual reboot happens. 56 sync(); 57 if (!vfork()) reboot(action); 58} 59 60void oneit_main(void) 61{ 62 int i, pid, pipes[] = {SIGUSR1, SIGUSR2, SIGTERM, SIGINT}; 63 64 if (FLAG_3) { 65 // Ensure next available filehandle is #3 66 while (open("/", 0) < 3); 67 close(3); 68 close(4); 69 if (pipe(pipes)) perror_exit("pipe"); 70 fcntl(4, F_SETFD, FD_CLOEXEC); 71 } 72 73 // Setup signal handlers for signals of interest 74 for (i = 0; i<ARRAY_LEN(pipes); i++) xsignal(pipes[i], oneit_signaled); 75 76 while (!toys.signal) { 77 78 // Create a new child process. 79 pid = vfork(); 80 if (pid) { 81 82 // pid 1 reaps zombies until it gets its child, then halts system. 83 // We ignore the return value of write (what would we do with it?) 84 // but save it in a variable we never read to make fortify shut up. 85 // (Real problem is if pid2 never reads, write() fills pipe and blocks.) 86 while (pid != wait(&i)) if (FLAG_3) i = write(4, &pid, 4); 87 if (toys.optflags & FLAG_n) continue; 88 89 oneit_signaled((toys.optflags & FLAG_p) ? SIGUSR2 : SIGTERM); 90 } else { 91 // Redirect stdio to /dev/tty0, with new session ID, so ctrl-c works. 92 setsid(); 93 for (i=0; i<3; i++) { 94 close(i); 95 // Remember, O_CLOEXEC is backwards for xopen() 96 xopen(TT.console ? TT.console : "/dev/tty0", O_RDWR|O_CLOEXEC); 97 } 98 99 // Can't xexec() here, we vforked so we don't want to error_exit(). 100 toy_exec(toys.optargs); 101 execvp(*toys.optargs, toys.optargs); 102 perror_msg("%s not in PATH=%s", *toys.optargs, getenv("PATH")); 103 104 break; 105 } 106 } 107 108 // Give reboot() time to kick in, or avoid rapid spinning if exec failed 109 sleep(5); 110 _exit(127); 111} 112