4 // A minimal, drop-in replacement for SysV init.
5 // This leverages OpenRC to do most of the heavy lifting, and does the minimum
6 // amount that it should--unlike other so-called inits, that have world +
7 // kitchen sink thrown in, seemingly to enlarge the attack surface. ;-)
10 // (C) 2017 Underground Software
13 #define _DEFAULT_SOURCE // Without this, functions in unistd.h go missing
20 #include <sys/reboot.h>
21 #include <sys/syscall.h>
22 #include <sys/socket.h>
23 #include <sys/types.h>
27 static const char socketPath[] = "/run/initctl";
28 static int shuttingDown = 0;
29 static int verbose = 0; // N.B.: There is no mechanism to set this ATM
30 static struct sigaction action;
31 static int freeProcessSlot = 0;
32 static pid_t processPID[1024];
33 static char * processCmd[1024];
34 static char * processName[1024];
35 static char * args[256]; // 256 arguments ought to be enough for anybody ;-)
39 // Find a process in the monitored process list (MPL) by PID. Returns the index
40 // of the entry in the MPL.
42 int FindMonitoredPID(pid_t pid)
44 for(int i=0; i<freeProcessSlot; i++)
46 if (processPID[i] == pid)
55 // Remove the given process from the MPL by index
57 void RemoveEntry(int entry)
59 free(processCmd[entry]);
60 free(processName[entry]);
61 processPID[entry] = -1;
63 if (entry == (freeProcessSlot - 1))
69 // Add a process to the MPL
71 void AddEntry(pid_t pid, char * name, char * command)
75 for(i=0; i<freeProcessSlot; i++)
77 if (processPID[i] == -1)
82 processCmd[i] = strdup(command);
83 processName[i] = strdup(name);
85 if (i == freeProcessSlot)
91 // Take a given space delimited string and turn it into an array of strings
92 // suitable for execv().
94 void MakeArgumentList(char * s)
99 strncpy(buf, s, 4095);
102 char * token = strtok_r(buf, " ", &tokSave);
104 while (token != NULL)
107 token = strtok_r(NULL, " ", &tokSave);
110 // Argument list *must* end with a NULL entry:
116 // Launch a command from PID 1
118 // name: The human readable name for the process to launch
119 // command: The command to launch + any parameters it needs
120 // monitor: Tells Launch whether or not this process should be monitored
121 // and respawned if it goes away (0 = no, 1 = yes)
123 pid_t Launch(char * name, char * command, int monitor)
126 printf("Starting %s (%smonitored)...\n", name, (monitor ? "" : "un"));
128 MakeArgumentList(command);
133 // Child process gets here (if fork was successful)...
135 execv(args[0], args);
137 // We only get here if an error happened with execv()... Need to figure
138 // out how to handle this gracefully...
139 printf("execv() failed!\n");
144 printf("Fork failed; can't start %s!\n", name);
148 // Parent process gets here... So add to monitor list if requested
150 AddEntry(pid, name, command);
157 // Here we do some process monitoring
158 // At some point, we'll read inittab instead of recording just our own internal
161 void HandleSIGCHLD(int signum, siginfo_t * info, void * ptr)
165 // Get PID of exiting child process(es), if any
166 pid_t pid = waitpid(-1, NULL, WNOHANG);
168 // Check for exit conditions...
180 // If we're shutting down, we don't do any more process monitoring
184 int entry = FindMonitoredPID(pid);
188 // We remove the old entry last, because it frees the name & cmd
189 Launch(processName[entry], processCmd[entry], 1);
197 // Start up the system, using the awesome power of OpenRC
201 printf("*** init-ng v1.0.0 starting...\n");
203 pid_t pid = Launch("openrc", "/sbin/rc sysinit", 0);
204 waitpid(pid, NULL, 0);
206 pid = Launch("openrc", "/sbin/rc boot", 0);
207 waitpid(pid, NULL, 0);
209 pid = Launch("openrc", "/sbin/rc default", 0);
210 waitpid(pid, NULL, 0);
214 void ShutDown(int cmd)
217 pid_t pid = Launch("openrc", "/sbin/rc reboot", 0);
219 // We do this because our custom signal handler will repeatedly abort this
220 // call. We probably should do a bit more checking than this, but it seems
221 // to work without any problem as is.
222 while (waitpid(pid, NULL, 0) != pid);
230 // Connect to PID 1's socket to send a command
232 void DoCmd(char * cmd)
234 struct sockaddr_un remote;
235 int s = socket(AF_UNIX, SOCK_STREAM, 0);
240 printf("Could not get socket in DoCmd()...\n");
244 remote.sun_family = AF_UNIX;
245 strcpy(remote.sun_path, socketPath);
246 int len = strlen(remote.sun_path) + sizeof(remote.sun_family);
247 int s2 = connect(s, (struct sockaddr *)&remote, len);
252 printf("Could not connect in DoCmd()...\n");
256 send(s, cmd, strlen(cmd), 0);
261 int main(int argc, char * argv[])
263 char buf[2048], buf2[2048];
264 pid_t pid = getpid();
266 // If we're not PID 1, then we're (possibly) being asked to run a command
271 if ((strcmp(argv[1], "poweroff") == 0)
272 || (strcmp(argv[1], "restart") == 0)
273 || (strcmp(argv[1], "halt") == 0)
274 || (strcmp(argv[1], "status") == 0))
278 else if (strcmp(argv[1], "test") == 0)
280 ShutDown(RB_POWER_OFF);
283 printf("Invalid command\n");
286 printf("Usage: poweroff, restart, halt, status, test\n");
291 // We are PID EINS, so start the system using OpenRC...
295 // N.B.: To be a drop in replacement for SysV init, we really should read
296 // and parse the entries in /etc/inittab; at least the c entries...
297 for(int i=1; i<=6; i++)
299 sprintf(buf, "agetty%i", i);
300 sprintf(buf2, "/sbin/agetty tty%i --noclear", i);
301 Launch(buf, buf2, 1);
304 // Install SIGCHLD signal handler to do process monitoring
305 memset(&action, 0, sizeof(action));
306 action.sa_sigaction = HandleSIGCHLD;
307 action.sa_flags = SA_SIGINFO;
308 sigaction(SIGCHLD, &action, NULL);
310 // Create socket to listen on for commands...
311 struct sockaddr_un local, remote;
312 unsigned int s = socket(AF_UNIX, SOCK_STREAM, 0);
317 printf("init-ng: socket() failed!\n");
320 local.sun_family = AF_UNIX;
321 strcpy(local.sun_path, socketPath);
322 unlink(local.sun_path);
323 int len = strlen(local.sun_path) + sizeof(local.sun_family);
325 if (bind(s, (struct sockaddr *)&local, len) == -1)
328 printf("init-ng: bind() failed!\n");
331 // Queue size of 20 ought to be enough for anybody ;-)
332 if (listen(s, 20) == -1)
335 printf("init-ng: listen() failed!\n");
338 // Main loop for PID 1. All commands are received and acted upon from here.
341 int t = sizeof(remote);
342 unsigned int s2 = accept(s, (struct sockaddr *)&remote, &t);
346 // Check to see if the signal handler blew things up on us
350 // An unanticipated error occurred, handle it
352 printf("init-ng: accept() failed!\n");
355 // Get the message from the socket
356 int n = recv(s2, buf, 2048, 0);
360 printf("PID1: Received \"%s\" from socket...\n", buf);
362 // Now do something with it...
363 if (strcmp(buf, "poweroff") == 0)
365 ShutDown(RB_POWER_OFF);
367 else if (strcmp(buf, "restart") == 0)
369 ShutDown(RB_AUTOBOOT);
371 else if (strcmp(buf, "halt") == 0)
373 ShutDown(RB_HALT_SYSTEM);
375 else if (strcmp(buf, "status") == 0)
377 printf("[Status placeholder]\n");
383 // We should never get here...