--- /dev/null
+//
+// 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;
+}
+