]> Shamusworld >> Repos - init-ng/commitdiff
Rewrite of Ruby script into C. To my eye, it's much cleaner. :-)
authorShamus Hammons <jlhamm@acm.org>
Mon, 3 Apr 2017 14:05:29 +0000 (09:05 -0500)
committerShamus Hammons <jlhamm@acm.org>
Mon, 3 Apr 2017 14:05:29 +0000 (09:05 -0500)
There are still a few things that need to be added, like status of
monitored processes, and reading/parsing of inittab to make it a true
drop in replacement for SysV init, but this is pretty close as it is.

Makefile [new file with mode: 0644]
README
init-ng [new file with mode: 0755]
init-ng.c [new file with mode: 0644]
init-ng.rb [moved from init with 100% similarity]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..561f3c3
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,5 @@
+# Doesn't need to be more complex than this
+
+init-ng: init-ng.c
+       gcc -std=c99 -g init-ng.c -o init-ng
+
diff --git a/README b/README
index be745eb17e6f9a4c8a9036613928b549bd54986f..7f5e22434c7ca90c710c3adc90dce3b5cfa4969c 100644 (file)
--- a/README
+++ b/README
@@ -32,6 +32,11 @@ HOW TO INSTALL/USE IT
 Once you have rebooted successfully and logged in as root, typing 'init' on the
 command line will display the commands that init-ng understands.
 
+It is also possible to leave the existing init in place without renaming it; in
+this case you would rename the new init to something else, such as init-ng.
+Then, on your kernel boot command line, you would add "init=/sbin/init-ng"
+(without the quotes) to have the kernel use it instead of your current init.
+
 
 CREDITS
 -------
diff --git a/init-ng b/init-ng
new file mode 100755 (executable)
index 0000000..dcd7ff3
Binary files /dev/null and b/init-ng differ
diff --git a/init-ng.c b/init-ng.c
new file mode 100644 (file)
index 0000000..3b969be
--- /dev/null
+++ b/init-ng.c
@@ -0,0 +1,387 @@
+//
+// Init-NG
+//
+// A minimal, drop-in replacement for SysV init.
+// This leverages OpenRC to do most of the heavy lifting, and does the minimum
+// amount that it should--unlike other so-called inits, that have world +
+// kitchen sink thrown in, seemingly to enlarge the attack surface. ;-)
+//
+// by James Hammons
+// (C) 2017 Underground Software
+//
+
+#define _GNU_SOURCE            // Without this, functions in unistd.h go missing
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/reboot.h>
+#include <sys/syscall.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+
+static const char socketPath[] = "/run/initctl";
+static int shuttingDown = 0;
+static int verbose = 0;                // N.B.: There is no mechanism to set this ATM
+static struct sigaction action;
+static int freeProcessSlot = 0;
+static pid_t processPID[1024];
+static char * processCmd[1024];
+static char * processName[1024];
+static char * args[256];       // 256 arguments ought to be enough for anybody ;-)
+
+
+//
+// Find a process in the monitored process list (MPL) by PID. Returns the index
+// of the entry in the MPL.
+//
+int FindMonitoredPID(pid_t pid)
+{
+       for(int i=0; i<freeProcessSlot; i++)
+       {
+               if (processPID[i] == pid)
+                       return i;
+       }
+
+       return -1;
+}
+
+
+//
+// Remove the given process from the MPL by index
+//
+void RemoveEntry(int entry)
+{
+       free(processCmd[entry]);
+       free(processName[entry]);
+       processPID[entry] = -1;
+
+       if (entry == (freeProcessSlot - 1))
+               freeProcessSlot--;
+}
+
+
+//
+// Add a process to the MPL
+//
+void AddEntry(pid_t pid, char * name, char * command)
+{
+       int i;
+
+       for(i=0; i<freeProcessSlot; i++)
+       {
+               if (processPID[i] == -1)
+                       break;
+       }
+
+       processPID[i] = pid;
+       processCmd[i] = strdup(command);
+       processName[i] = strdup(name);
+
+       if (i == freeProcessSlot)
+               freeProcessSlot++;
+}
+
+
+//
+// Take a given space delimited string and turn it into an array of strings
+// suitable for execv().
+//
+void MakeArgumentList(char * s)
+{
+       char buf[4096];
+       char * tokSave;
+
+       strncpy(buf, s, 4095);
+       buf[4095] = 0;
+       int i = 0;
+       char * token = strtok_r(buf, " ", &tokSave);
+
+       while (token != NULL)
+       {
+               args[i++] = token;
+               token = strtok_r(NULL, " ", &tokSave);
+       }
+
+       // Argument list *must* end with a NULL entry:
+       args[i] = NULL;
+}
+
+
+//
+// Launch a command from PID 1
+//
+// name:    The human readable name for the process to launch
+// command: The command to launch + any parameters it needs
+// monitor: Tells Launch whether or not this process should be monitored
+//          and respawned if it goes away (0 = no, 1 = yes)
+//
+pid_t Launch(char * name, char * command, int monitor)
+{
+       if (verbose)
+               printf("Starting %s (%smonitored)...\n", name, (monitor ? "" : "un"));
+
+       MakeArgumentList(command);
+       pid_t pid = fork();
+
+       if (pid == 0)
+       {
+               // Child process gets here (if fork was successful)...
+               setsid();
+               execv(args[0], args);
+
+               // We only get here if an error happened with execv()... Need to figure
+               // out how to handle this gracefully...
+               printf("execv() failed!\n");
+               return 0;
+       }
+       else if (pid < 0)
+       {
+               printf("Fork failed; can't start %s!\n", name);
+               return pid;
+       }
+
+       // Parent process gets here... So add to monitor list if requested
+       if (monitor)
+               AddEntry(pid, name, command);
+
+       return pid;
+}
+
+
+//
+// Here we do some process monitoring
+// At some point, we'll read inittab instead of recording just our own internal
+// processes...
+//
+void HandleSIGCHLD(int signum, siginfo_t * info, void * ptr)
+{
+       while (1)
+       {
+               // Get PID of exiting child process(es), if any
+               pid_t pid = waitpid(-1, NULL, WNOHANG);
+
+               // Check for exit conditions...
+               if (pid == 0)
+                       break;
+               else if (pid == -1)
+               {
+                       if (errno == ECHILD)
+                               break;
+
+                       perror("waitpid");
+                       continue;
+               }
+
+               // If we're shutting down, we don't do any more process monitoring
+               if (shuttingDown)
+                       continue;
+
+               int entry = FindMonitoredPID(pid);
+
+               if (entry != -1)
+               {
+                       // We remove the old entry last, because it frees the name & cmd
+                       Launch(processName[entry], processCmd[entry], 1);
+                       RemoveEntry(entry);
+               }
+       }
+}
+
+
+//
+// Start up the system, using the awesome power of OpenRC
+//
+void Init(void)
+{
+       printf("*** init-ng v1.0.0 starting...\n");
+
+       pid_t pid = Launch("openrc", "/sbin/rc sysinit", 0);
+       waitpid(pid, NULL, 0);
+
+       pid = Launch("openrc", "/sbin/rc boot", 0);
+       waitpid(pid, NULL, 0);
+
+       pid = Launch("openrc", "/sbin/rc default", 0);
+       waitpid(pid, NULL, 0);
+}
+
+
+void ShutDown(int cmd)
+{
+       shuttingDown = 1;
+       pid_t pid = Launch("openrc", "/sbin/rc reboot", 0);
+
+       // We do this because our custom signal handler will repeatedly abort this
+       // call. We probably should do a bit more checking than this, but it seems
+       // to work without any problem as is.
+       while (waitpid(pid, NULL, 0) != pid);
+
+       // Could just call sync() as well...
+       syscall(SYS_sync);
+       syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2C, cmd);
+}
+
+
+//
+// Connect to PID 1's socket to send a command
+//
+void DoCmd(char * cmd)
+{
+       struct sockaddr_un remote;
+       int s = socket(AF_UNIX, SOCK_STREAM, 0);
+
+       if (s == -1)
+       {
+               // Handle error
+               printf("Could not get socket in DoCmd()...\n");
+               return;
+       }
+
+       remote.sun_family = AF_UNIX;
+       strcpy(remote.sun_path, socketPath);
+       int len = strlen(remote.sun_path) + sizeof(remote.sun_family);
+       int s2 = connect(s, (struct sockaddr *)&remote, len);
+
+       if (s2 == -1)
+       {
+               // Handle error
+               printf("Could not connect in DoCmd()...\n");
+               return;
+       }
+
+       send(s, cmd, strlen(cmd), 0);
+       close(s);
+}
+
+
+int main(int argc, char * argv[])
+{
+       char buf[2048], buf2[2048];
+       pid_t pid = getpid();
+
+       // If we're not PID 1, then we're (possibly) being asked to run a command
+       if (pid != 1)
+       {
+               if (argc > 1)
+               {
+                       if ((strcmp(argv[1], "poweroff") == 0)
+                               || (strcmp(argv[1], "restart") == 0)
+                               || (strcmp(argv[1], "halt") == 0)
+                               || (strcmp(argv[1], "status") == 0))
+                       {
+                               DoCmd(argv[1]);
+                       }
+                       else if (strcmp(argv[1], "test") == 0)
+                       {
+                               ShutDown(LINUX_REBOOT_CMD_POWER_OFF);
+                       }
+                       else
+                               printf("Invalid command\n");
+               }
+               else
+                       printf("Usage: poweroff, restart, halt, status, test\n");
+
+               return 1;
+       }
+
+       // We are PID EINS, so start the system using OpenRC...
+       Init();
+
+       // Launch TTYs...
+       // N.B.: To be a drop in replacement for SysV init, we really should read
+       //       and parse the entries in /etc/inittab; at least the c entries...
+       for(int i=1; i<=6; i++)
+       {
+               sprintf(buf, "agetty%i", i);
+               sprintf(buf2, "/sbin/agetty tty%i --noclear", i);
+               Launch(buf, buf2, 1);
+       }
+
+       // Install SIGCHLD signal handler to do process monitoring
+       memset(&action, 0, sizeof(action));
+       action.sa_sigaction = HandleSIGCHLD;
+       action.sa_flags = SA_SIGINFO;
+       sigaction(SIGCHLD, &action, NULL);
+
+       // Create socket to listen on for commands...
+       struct sockaddr_un local, remote;
+       unsigned int s = socket(AF_UNIX, SOCK_STREAM, 0);
+
+       if (s == -1)
+       {
+               perror("socket");
+               printf("init-ng: socket() failed!\n");
+       }
+
+       local.sun_family = AF_UNIX;
+       strcpy(local.sun_path, socketPath);
+       unlink(local.sun_path);
+       int len = strlen(local.sun_path) + sizeof(local.sun_family);
+
+       if (bind(s, (struct sockaddr *)&local, len) == -1)
+       {
+               perror("bind");
+               printf("init-ng: bind() failed!\n");
+       }
+
+       // Queue size of 20 ought to be enough for anybody ;-)
+       if (listen(s, 20) == -1)
+       {
+               perror("listen");
+               printf("init-ng: listen() failed!\n");
+       }
+
+       // Main loop for PID 1. All commands are received and acted upon from here.
+       while (1)
+       {
+               int t = sizeof(remote);
+               unsigned int s2 = accept(s, (struct sockaddr *)&remote, &t);
+
+               if (s2 == -1)
+               {
+                       // Check to see if the signal handler blew things up on us
+                       if (errno == EINTR)
+                               continue;
+
+                       // An unanticipated error occurred, handle it
+                       perror("accept");
+                       printf("init-ng: accept() failed!\n");
+               }
+
+               // Get the message from the socket
+               int n = recv(s2, buf, 2048, 0);
+               buf[n] = 0;
+
+               if (verbose)
+                       printf("PID1: Received \"%s\" from socket...\n", buf);
+
+               // Now do something with it...
+               if (strcmp(buf, "poweroff") == 0)
+               {
+                       ShutDown(LINUX_REBOOT_CMD_POWER_OFF);
+               }
+               else if (strcmp(buf, "restart") == 0)
+               {
+                       ShutDown(LINUX_REBOOT_CMD_RESTART);
+               }
+               else if (strcmp(buf, "halt") == 0)
+               {
+                       ShutDown(LINUX_REBOOT_CMD_HALT);
+               }
+               else if (strcmp(buf, "status") == 0)
+               {
+                       printf("[Status placeholder]\n");
+               }
+
+               close(s2);
+       }
+
+       // We should never get here...
+       return 0;
+}
+
diff --git a/init b/init-ng.rb
similarity index 100%
rename from init
rename to init-ng.rb