]> Shamusworld >> Repos - init-ng/blob - init-ng.c
3b969be0e62235aa03ee59df65cc1f0e173d1d9d
[init-ng] / init-ng.c
1 //
2 // Init-NG
3 //
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. ;-)
8 //
9 // by James Hammons
10 // (C) 2017 Underground Software
11 //
12
13 #define _GNU_SOURCE             // Without this, functions in unistd.h go missing
14 #include <unistd.h>
15 #include <errno.h>
16 #include <signal.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <linux/reboot.h>
21 #include <sys/syscall.h>
22 #include <sys/socket.h>
23 #include <sys/types.h>
24 #include <sys/un.h>
25 #include <sys/wait.h>
26
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 ;-)
36
37
38 //
39 // Find a process in the monitored process list (MPL) by PID. Returns the index
40 // of the entry in the MPL.
41 //
42 int FindMonitoredPID(pid_t pid)
43 {
44         for(int i=0; i<freeProcessSlot; i++)
45         {
46                 if (processPID[i] == pid)
47                         return i;
48         }
49
50         return -1;
51 }
52
53
54 //
55 // Remove the given process from the MPL by index
56 //
57 void RemoveEntry(int entry)
58 {
59         free(processCmd[entry]);
60         free(processName[entry]);
61         processPID[entry] = -1;
62
63         if (entry == (freeProcessSlot - 1))
64                 freeProcessSlot--;
65 }
66
67
68 //
69 // Add a process to the MPL
70 //
71 void AddEntry(pid_t pid, char * name, char * command)
72 {
73         int i;
74
75         for(i=0; i<freeProcessSlot; i++)
76         {
77                 if (processPID[i] == -1)
78                         break;
79         }
80
81         processPID[i] = pid;
82         processCmd[i] = strdup(command);
83         processName[i] = strdup(name);
84
85         if (i == freeProcessSlot)
86                 freeProcessSlot++;
87 }
88
89
90 //
91 // Take a given space delimited string and turn it into an array of strings
92 // suitable for execv().
93 //
94 void MakeArgumentList(char * s)
95 {
96         char buf[4096];
97         char * tokSave;
98
99         strncpy(buf, s, 4095);
100         buf[4095] = 0;
101         int i = 0;
102         char * token = strtok_r(buf, " ", &tokSave);
103
104         while (token != NULL)
105         {
106                 args[i++] = token;
107                 token = strtok_r(NULL, " ", &tokSave);
108         }
109
110         // Argument list *must* end with a NULL entry:
111         args[i] = NULL;
112 }
113
114
115 //
116 // Launch a command from PID 1
117 //
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)
122 //
123 pid_t Launch(char * name, char * command, int monitor)
124 {
125         if (verbose)
126                 printf("Starting %s (%smonitored)...\n", name, (monitor ? "" : "un"));
127
128         MakeArgumentList(command);
129         pid_t pid = fork();
130
131         if (pid == 0)
132         {
133                 // Child process gets here (if fork was successful)...
134                 setsid();
135                 execv(args[0], args);
136
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");
140                 return 0;
141         }
142         else if (pid < 0)
143         {
144                 printf("Fork failed; can't start %s!\n", name);
145                 return pid;
146         }
147
148         // Parent process gets here... So add to monitor list if requested
149         if (monitor)
150                 AddEntry(pid, name, command);
151
152         return pid;
153 }
154
155
156 //
157 // Here we do some process monitoring
158 // At some point, we'll read inittab instead of recording just our own internal
159 // processes...
160 //
161 void HandleSIGCHLD(int signum, siginfo_t * info, void * ptr)
162 {
163         while (1)
164         {
165                 // Get PID of exiting child process(es), if any
166                 pid_t pid = waitpid(-1, NULL, WNOHANG);
167
168                 // Check for exit conditions...
169                 if (pid == 0)
170                         break;
171                 else if (pid == -1)
172                 {
173                         if (errno == ECHILD)
174                                 break;
175
176                         perror("waitpid");
177                         continue;
178                 }
179
180                 // If we're shutting down, we don't do any more process monitoring
181                 if (shuttingDown)
182                         continue;
183
184                 int entry = FindMonitoredPID(pid);
185
186                 if (entry != -1)
187                 {
188                         // We remove the old entry last, because it frees the name & cmd
189                         Launch(processName[entry], processCmd[entry], 1);
190                         RemoveEntry(entry);
191                 }
192         }
193 }
194
195
196 //
197 // Start up the system, using the awesome power of OpenRC
198 //
199 void Init(void)
200 {
201         printf("*** init-ng v1.0.0 starting...\n");
202
203         pid_t pid = Launch("openrc", "/sbin/rc sysinit", 0);
204         waitpid(pid, NULL, 0);
205
206         pid = Launch("openrc", "/sbin/rc boot", 0);
207         waitpid(pid, NULL, 0);
208
209         pid = Launch("openrc", "/sbin/rc default", 0);
210         waitpid(pid, NULL, 0);
211 }
212
213
214 void ShutDown(int cmd)
215 {
216         shuttingDown = 1;
217         pid_t pid = Launch("openrc", "/sbin/rc reboot", 0);
218
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);
223
224         // Could just call sync() as well...
225         syscall(SYS_sync);
226         syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2C, cmd);
227 }
228
229
230 //
231 // Connect to PID 1's socket to send a command
232 //
233 void DoCmd(char * cmd)
234 {
235         struct sockaddr_un remote;
236         int s = socket(AF_UNIX, SOCK_STREAM, 0);
237
238         if (s == -1)
239         {
240                 // Handle error
241                 printf("Could not get socket in DoCmd()...\n");
242                 return;
243         }
244
245         remote.sun_family = AF_UNIX;
246         strcpy(remote.sun_path, socketPath);
247         int len = strlen(remote.sun_path) + sizeof(remote.sun_family);
248         int s2 = connect(s, (struct sockaddr *)&remote, len);
249
250         if (s2 == -1)
251         {
252                 // Handle error
253                 printf("Could not connect in DoCmd()...\n");
254                 return;
255         }
256
257         send(s, cmd, strlen(cmd), 0);
258         close(s);
259 }
260
261
262 int main(int argc, char * argv[])
263 {
264         char buf[2048], buf2[2048];
265         pid_t pid = getpid();
266
267         // If we're not PID 1, then we're (possibly) being asked to run a command
268         if (pid != 1)
269         {
270                 if (argc > 1)
271                 {
272                         if ((strcmp(argv[1], "poweroff") == 0)
273                                 || (strcmp(argv[1], "restart") == 0)
274                                 || (strcmp(argv[1], "halt") == 0)
275                                 || (strcmp(argv[1], "status") == 0))
276                         {
277                                 DoCmd(argv[1]);
278                         }
279                         else if (strcmp(argv[1], "test") == 0)
280                         {
281                                 ShutDown(LINUX_REBOOT_CMD_POWER_OFF);
282                         }
283                         else
284                                 printf("Invalid command\n");
285                 }
286                 else
287                         printf("Usage: poweroff, restart, halt, status, test\n");
288
289                 return 1;
290         }
291
292         // We are PID EINS, so start the system using OpenRC...
293         Init();
294
295         // Launch TTYs...
296         // N.B.: To be a drop in replacement for SysV init, we really should read
297         //       and parse the entries in /etc/inittab; at least the c entries...
298         for(int i=1; i<=6; i++)
299         {
300                 sprintf(buf, "agetty%i", i);
301                 sprintf(buf2, "/sbin/agetty tty%i --noclear", i);
302                 Launch(buf, buf2, 1);
303         }
304
305         // Install SIGCHLD signal handler to do process monitoring
306         memset(&action, 0, sizeof(action));
307         action.sa_sigaction = HandleSIGCHLD;
308         action.sa_flags = SA_SIGINFO;
309         sigaction(SIGCHLD, &action, NULL);
310
311         // Create socket to listen on for commands...
312         struct sockaddr_un local, remote;
313         unsigned int s = socket(AF_UNIX, SOCK_STREAM, 0);
314
315         if (s == -1)
316         {
317                 perror("socket");
318                 printf("init-ng: socket() failed!\n");
319         }
320
321         local.sun_family = AF_UNIX;
322         strcpy(local.sun_path, socketPath);
323         unlink(local.sun_path);
324         int len = strlen(local.sun_path) + sizeof(local.sun_family);
325
326         if (bind(s, (struct sockaddr *)&local, len) == -1)
327         {
328                 perror("bind");
329                 printf("init-ng: bind() failed!\n");
330         }
331
332         // Queue size of 20 ought to be enough for anybody ;-)
333         if (listen(s, 20) == -1)
334         {
335                 perror("listen");
336                 printf("init-ng: listen() failed!\n");
337         }
338
339         // Main loop for PID 1. All commands are received and acted upon from here.
340         while (1)
341         {
342                 int t = sizeof(remote);
343                 unsigned int s2 = accept(s, (struct sockaddr *)&remote, &t);
344
345                 if (s2 == -1)
346                 {
347                         // Check to see if the signal handler blew things up on us
348                         if (errno == EINTR)
349                                 continue;
350
351                         // An unanticipated error occurred, handle it
352                         perror("accept");
353                         printf("init-ng: accept() failed!\n");
354                 }
355
356                 // Get the message from the socket
357                 int n = recv(s2, buf, 2048, 0);
358                 buf[n] = 0;
359
360                 if (verbose)
361                         printf("PID1: Received \"%s\" from socket...\n", buf);
362
363                 // Now do something with it...
364                 if (strcmp(buf, "poweroff") == 0)
365                 {
366                         ShutDown(LINUX_REBOOT_CMD_POWER_OFF);
367                 }
368                 else if (strcmp(buf, "restart") == 0)
369                 {
370                         ShutDown(LINUX_REBOOT_CMD_RESTART);
371                 }
372                 else if (strcmp(buf, "halt") == 0)
373                 {
374                         ShutDown(LINUX_REBOOT_CMD_HALT);
375                 }
376                 else if (strcmp(buf, "status") == 0)
377                 {
378                         printf("[Status placeholder]\n");
379                 }
380
381                 close(s2);
382         }
383
384         // We should never get here...
385         return 0;
386 }
387