changeset 1725:b2b2d214727a

Upgrade oneit with -r (restart), -3 (send exiting PID values to child), and signal handling.
author Rob Landley <rob@landley.net>
date Mon, 09 Mar 2015 14:52:32 -0500
parents 81d8a7b20ce9
children 26170eb7685d
files lib/lib.h lib/xwrap.c toys/other/oneit.c
diffstat 3 files changed, 78 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/lib/lib.h	Fri Mar 06 16:50:54 2015 -0600
+++ b/lib/lib.h	Mon Mar 09 14:52:32 2015 -0500
@@ -129,6 +129,7 @@
 void xpidfile(char *name);
 void xregcomp(regex_t *preg, char *rexec, int cflags);
 char *xtzset(char *new);
+void xsignal(int signal, void *handler);
 
 // lib.c
 void verror_msg(char *msg, int err, va_list va);
--- a/lib/xwrap.c	Fri Mar 06 16:50:54 2015 -0600
+++ b/lib/xwrap.c	Mon Mar 09 14:52:32 2015 -0500
@@ -638,3 +638,14 @@
 
   return tz;
 }
+
+// Set a signal handler
+void xsignal(int signal, void *handler)
+{
+  struct sigaction *sa = (void *)libbuf;
+
+  memset(sa, 0, sizeof(struct sigaction));
+  sa->sa_handler = handler;
+
+  if (sigaction(signal, sa, 0)) perror_exit("xsignal %d", signal);
+}
--- a/toys/other/oneit.c	Fri Mar 06 16:50:54 2015 -0600
+++ b/toys/other/oneit.c	Mon Mar 09 14:52:32 2015 -0500
@@ -2,7 +2,7 @@
  *
  * Copyright 2005, 2007 by Rob Landley <rob@landley.net>.
 
-USE_ONEIT(NEWTOY(oneit, "^<1c:p", TOYFLAG_SBIN))
+USE_ONEIT(NEWTOY(oneit, "^<1nc:p3[!pn]", TOYFLAG_SBIN))
 
 config ONEIT
   bool "oneit"
@@ -10,16 +10,18 @@
   help
     usage: oneit [-p] [-c /dev/tty0] command [...]
 
-    A simple init program that runs a single supplied command line with a
+    Simple init program that runs a single supplied command line with a
     controlling tty (so CTRL-C can kill it).
 
+    -c	Which console device to use (/dev/console doesn't do CTRL-C, etc).
     -p	Power off instead of rebooting when command exits.
-    -c	Which console device to use.
+    -r	Restart child when it exits.
+    -3	Write 32 bit PID of each exiting reparented process to fd 3 of child.
+    	(Blocking writes, child must read to avoid eventual deadlock.)
 
-    The oneit command runs the supplied command line as a child process
-    (because PID 1 has signals blocked), attached to /dev/tty0, in its
-    own session. Then oneit reaps zombies until the child exits, at
-    which point it reboots (or with -p, powers off) the system.
+    Spawns a single child process (because PID 1 has signals blocked)
+    in its own session, reaps zombies until the child exits, then
+    reboots the system (or powers off with -p, or restarts the child with -r).
 */
 
 #define FOR_oneit
@@ -40,37 +42,70 @@
 // PID 1 then reaps zombies until the child process it spawned exits, at which
 // point it calls sync() and reboot().  I could stick a kill -1 in there.
 
+// Perform actions in response to signals. (Only root can send us signals.)
+static void oneit_signaled(int signal)
+{
+  int action = RB_AUTOBOOT;
+
+  toys.signal = signal;
+  if (signal == SIGUSR1) action = RB_HALT_SYSTEM;
+  if (signal == SIGUSR2) action = RB_POWER_OFF;
+
+  // PID 1 can't call reboot() because it kills the task that calls it,
+  // which causes the kernel to panic before the actual reboot happens.
+  sync();
+  if (!vfork()) reboot(action);
+}
 
 void oneit_main(void)
 {
-  int i;
-  pid_t pid;
+  int i, pid, pipes[] = {SIGUSR1, SIGUSR2, SIGTERM, SIGINT};
 
-  // Create a new child process.
-  pid = vfork();
-  if (pid) {
-
-    // pid 1 just reaps zombies until it gets its child, then halts the system.
-    while (pid != wait(&i));
-    sync();
-
-    // PID 1 can't call reboot() because it kills the task that calls it,
-    // which causes the kernel to panic before the actual reboot happens.
-    if (!vfork()) reboot((toys.optflags & FLAG_p) ? RB_POWER_OFF : RB_AUTOBOOT);
-    sleep(5);
-    _exit(1);
+  if (FLAG_3) {
+    // Ensure next available filehandle is #3
+    while (open("/", 0) < 3);
+    close(3);
+    close(4);
+    if (pipe(pipes)) perror_exit("pipe");
+    fcntl(4, F_SETFD, FD_CLOEXEC);
   }
 
-  // Redirect stdio to /dev/tty0, with new session ID, so ctrl-c works.
-  setsid();
-  for (i=0; i<3; i++) {
-    close(i);
-    // Remember, O_CLOEXEC is backwards for xopen()
-    xopen(TT.console ? TT.console : "/dev/tty0", O_RDWR|O_CLOEXEC);
+  // Setup signal handlers for signals of interest
+  for (i = 0; i<ARRAY_LEN(pipes); i++) xsignal(pipes[i], oneit_signaled);
+
+  while (!toys.signal) {
+
+    // Create a new child process.
+    pid = vfork();
+    if (pid) {
+
+      // pid 1 reaps zombies until it gets its child, then halts system.
+      // We ignore the return value of write (what would we do with it?)
+      // but save it in a variable we never read to make fortify shut up.
+      // (Real problem is if pid2 never reads, write() fills pipe and blocks.)
+      while (pid != wait(&i)) if (FLAG_3) i = write(4, &pid, 4);
+      if (toys.optflags & FLAG_n) continue;
+
+      oneit_signaled((toys.optflags & FLAG_p) ? SIGUSR2 : SIGTERM);
+    } else {
+      // Redirect stdio to /dev/tty0, with new session ID, so ctrl-c works.
+      setsid();
+      for (i=0; i<3; i++) {
+        close(i);
+        // Remember, O_CLOEXEC is backwards for xopen()
+        xopen(TT.console ? TT.console : "/dev/tty0", O_RDWR|O_CLOEXEC);
+      }
+
+      // Can't xexec() here, we vforked so we don't want to error_exit().
+      toy_exec(toys.optargs);
+      execvp(*toys.optargs, toys.optargs);
+      perror_msg("%s not in PATH=%s", *toys.optargs, getenv("PATH"));
+
+      break;
+    }
   }
 
-  // Can't xexec() here, because we vforked so we don't want to error_exit().
-  toy_exec(toys.optargs);
-  execvp(*toys.optargs, toys.optargs);
+  // Give reboot() time to kick in, or avoid rapid spinning if exec failed
+  sleep(5);
   _exit(127);
 }