]> Shamusworld >> Repos - init-ng/blob - init-ng.c
Switch from _GNU_SOURCE to _DEFAULT_SOURCE.
[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 _DEFAULT_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 <sys/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         sync();
225         reboot(cmd);
226 }
227
228
229 //
230 // Connect to PID 1's socket to send a command
231 //
232 void DoCmd(char * cmd)
233 {
234         struct sockaddr_un remote;
235         int s = socket(AF_UNIX, SOCK_STREAM, 0);
236
237         if (s == -1)
238         {
239                 // Handle error
240                 printf("Could not get socket in DoCmd()...\n");
241                 return;
242         }
243
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);
248
249         if (s2 == -1)
250         {
251                 // Handle error
252                 printf("Could not connect in DoCmd()...\n");
253                 return;
254         }
255
256         send(s, cmd, strlen(cmd), 0);
257         close(s);
258 }
259
260
261 int main(int argc, char * argv[])
262 {
263         char buf[2048], buf2[2048];
264         pid_t pid = getpid();
265
266         // If we're not PID 1, then we're (possibly) being asked to run a command
267         if (pid != 1)
268         {
269                 if (argc > 1)
270                 {
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))
275                         {
276                                 DoCmd(argv[1]);
277                         }
278                         else if (strcmp(argv[1], "test") == 0)
279                         {
280                                 ShutDown(RB_POWER_OFF);
281                         }
282                         else
283                                 printf("Invalid command\n");
284                 }
285                 else
286                         printf("Usage: poweroff, restart, halt, status, test\n");
287
288                 return 1;
289         }
290
291         // We are PID EINS, so start the system using OpenRC...
292         Init();
293
294         // Launch TTYs...
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++)
298         {
299                 sprintf(buf, "agetty%i", i);
300                 sprintf(buf2, "/sbin/agetty tty%i --noclear", i);
301                 Launch(buf, buf2, 1);
302         }
303
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);
309
310         // Create socket to listen on for commands...
311         struct sockaddr_un local, remote;
312         unsigned int s = socket(AF_UNIX, SOCK_STREAM, 0);
313
314         if (s == -1)
315         {
316                 perror("socket");
317                 printf("init-ng: socket() failed!\n");
318         }
319
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);
324
325         if (bind(s, (struct sockaddr *)&local, len) == -1)
326         {
327                 perror("bind");
328                 printf("init-ng: bind() failed!\n");
329         }
330
331         // Queue size of 20 ought to be enough for anybody ;-)
332         if (listen(s, 20) == -1)
333         {
334                 perror("listen");
335                 printf("init-ng: listen() failed!\n");
336         }
337
338         // Main loop for PID 1. All commands are received and acted upon from here.
339         while (1)
340         {
341                 int t = sizeof(remote);
342                 unsigned int s2 = accept(s, (struct sockaddr *)&remote, &t);
343
344                 if (s2 == -1)
345                 {
346                         // Check to see if the signal handler blew things up on us
347                         if (errno == EINTR)
348                                 continue;
349
350                         // An unanticipated error occurred, handle it
351                         perror("accept");
352                         printf("init-ng: accept() failed!\n");
353                 }
354
355                 // Get the message from the socket
356                 int n = recv(s2, buf, 2048, 0);
357                 buf[n] = 0;
358
359                 if (verbose)
360                         printf("PID1: Received \"%s\" from socket...\n", buf);
361
362                 // Now do something with it...
363                 if (strcmp(buf, "poweroff") == 0)
364                 {
365                         ShutDown(RB_POWER_OFF);
366                 }
367                 else if (strcmp(buf, "restart") == 0)
368                 {
369                         ShutDown(RB_AUTOBOOT);
370                 }
371                 else if (strcmp(buf, "halt") == 0)
372                 {
373                         ShutDown(RB_HALT_SYSTEM);
374                 }
375                 else if (strcmp(buf, "status") == 0)
376                 {
377                         printf("[Status placeholder]\n");
378                 }
379
380                 close(s2);
381         }
382
383         // We should never get here...
384         return 0;
385 }
386