]> Shamusworld >> Repos - init-ng/blob - openrc-init.c
Added version which conforms to openrc coding guidelines.
[init-ng] / openrc-init.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 #include <unistd.h>
14 #include <errno.h>
15 #include <signal.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <sys/reboot.h>
20 #include <sys/syscall.h>
21 #include <sys/stat.h>
22 #include <sys/types.h>
23 #include <sys/un.h>
24 #include <sys/wait.h>
25
26 static const char fifoPath[] = "/dev/initctl";
27 static int shuttingDown = 0;
28 static int verbose = 0;         /* N.B.: There is no mechanism to set this ATM */
29 static struct sigaction action;
30 static int freeProcessSlot = 0;
31 static pid_t processPID[1024];
32 static char * processCmd[1024];
33 static char * processName[1024];
34 static char * args[256];        /* 256 arguments ought to be enough for anybody ;-) */
35
36
37 /*
38  * Find a process in the monitored process list (MPL) by PID. Returns the index
39  * of the entry in the MPL.
40  */
41 static int find_monitored_pid(pid_t pid)
42 {
43         int i;
44
45         for(i=0; i<freeProcessSlot; i++)
46         {
47                 if (processPID[i] == pid)
48                         return i;
49         }
50
51         return -1;
52 }
53
54
55 /*
56  * Remove the given process from the MPL by index
57  */
58 static void remove_entry(int entry)
59 {
60         free(processCmd[entry]);
61         free(processName[entry]);
62         processPID[entry] = -1;
63
64         if (entry == (freeProcessSlot - 1))
65                 freeProcessSlot--;
66 }
67
68
69 /*
70  * Add a process to the MPL
71  */
72 static void add_entry(pid_t pid, const char * name, const char * command)
73 {
74         int i;
75
76         for(i=0; i<freeProcessSlot; i++)
77         {
78                 if (processPID[i] == -1)
79                         break;
80         }
81
82         processPID[i] = pid;
83         processCmd[i] = strdup(command);
84         processName[i] = strdup(name);
85
86         if (i == freeProcessSlot)
87                 freeProcessSlot++;
88 }
89
90
91 /*
92  * Take a given space delimited string and turn it into an array of strings
93  * suitable for execv().
94  */
95 static void make_argument_list(const char * s)
96 {
97         char buf[4096];
98         char * tokSave;
99         int i;
100         char * token;
101
102         strncpy(buf, s, 4095);
103         buf[4095] = 0;
104         i = 0;
105         token = strtok_r(buf, " ", &tokSave);
106
107         while (token != NULL)
108         {
109                 args[i++] = token;
110                 token = strtok_r(NULL, " ", &tokSave);
111         }
112
113         /* Argument list *must* end with a NULL entry: */
114         args[i] = NULL;
115 }
116
117
118 /*
119  * Launch a command from PID 1
120  *
121  * name:    The human readable name for the process to launch
122  * command: The command to launch + any parameters it needs
123  * monitor: Tells Launch whether or not this process should be monitored
124  *          and respawned if it goes away (0 = no, 1 = yes)
125  */
126 static pid_t launch(const char * name, const char * command, int monitor)
127 {
128         pid_t pid;
129
130         if (verbose)
131                 printf("Starting %s (%smonitored)...\n", name, (monitor ? "" : "un"));
132
133         make_argument_list(command);
134         pid = fork();
135
136         if (pid == 0)
137         {
138                 /* Child process gets here (if fork was successful)... */
139                 setsid();
140                 execv(args[0], args);
141
142                 /* We only get here if an error happened with execv()... Need to figure
143                  * out how to handle this gracefully...
144                  */
145                 printf("execv() failed!\n");
146                 return 0;
147         }
148         else if (pid < 0)
149         {
150                 printf("Fork failed; can't start %s!\n", name);
151                 return pid;
152         }
153
154         /* Parent process gets here... So add to monitor list if requested */
155         if (monitor)
156                 add_entry(pid, name, command);
157
158         return pid;
159 }
160
161
162 /*
163  * Here we do some process monitoring
164  * At some point, we'll read inittab instead of recording just our own internal
165  * processes...
166  */
167 static void handle_sigchld(int signum)
168 {
169         pid_t pid;
170         int entry;
171
172         while (1)
173         {
174                 /* Get PID of exiting child process(es), if any */
175                 pid = waitpid(-1, NULL, WNOHANG);
176
177                 /* Check for exit conditions... */
178                 if (pid == 0)
179                         break;
180                 else if (pid == -1)
181                 {
182                         if (errno == ECHILD)
183                                 break;
184
185                         perror("waitpid");
186                         continue;
187                 }
188
189                 /* If we're shutting down, we don't do any more process monitoring */
190                 if (shuttingDown)
191                         continue;
192
193                 entry = find_monitored_pid(pid);
194
195                 if (entry != -1)
196                 {
197                         /* We remove the old entry last, because it frees the name & cmd */
198                         launch(processName[entry], processCmd[entry], 1);
199                         remove_entry(entry);
200                 }
201         }
202 }
203
204
205 /*
206  * Start up the system, using the awesome power of OpenRC
207  */
208 static void init(void)
209 {
210         pid_t pid;
211
212         printf("*** init-ng v1.0.1 starting...\n");
213
214         pid = launch("openrc", "/sbin/rc sysinit", 0);
215         waitpid(pid, NULL, 0);
216
217         pid = launch("openrc", "/sbin/rc boot", 0);
218         waitpid(pid, NULL, 0);
219
220         pid = launch("openrc", "/sbin/rc default", 0);
221         waitpid(pid, NULL, 0);
222 }
223
224
225 static void shut_down(int cmd)
226 {
227         pid_t pid;
228
229         shuttingDown = 1;
230         pid = launch("openrc", "/sbin/rc reboot", 0);
231
232         /* We do this because our custom signal handler will repeatedly abort this
233          * call. We probably should do a bit more checking than this, but it seems
234          * to work without any problem as is.
235          */
236         while (waitpid(pid, NULL, 0) != pid);
237
238         sync();
239         reboot(cmd);
240 }
241
242
243 /*
244  * Connect to PID 1's FIFO to send a command
245  */
246 static void do_cmd(char * cmd)
247 {
248         FILE * file;
249         size_t ignored;
250
251         file = fopen(fifoPath, "w");
252
253         if (file == NULL)
254         {
255                 perror("do_cmd: fopen");
256                 return;
257         }
258
259         ignored = fwrite(cmd, 1, strlen(cmd), file);
260         fclose(file);
261 }
262
263
264 int main(int argc, char * argv[])
265 {
266         char buf[2048], buf2[2048];
267         pid_t pid;
268         int i;
269         FILE * file;
270         size_t read;
271
272         pid = getpid();
273
274         /* If we're not PID 1, then we're (possibly) being asked to run a command */
275         if (pid != 1)
276         {
277                 if (argc > 1)
278                 {
279                         if ((strcmp(argv[1], "poweroff") == 0)
280                                 || (strcmp(argv[1], "restart") == 0)
281                                 || (strcmp(argv[1], "halt") == 0)
282                                 || (strcmp(argv[1], "status") == 0))
283                         {
284                                 do_cmd(argv[1]);
285                         }
286                         else if (strcmp(argv[1], "test") == 0)
287                         {
288                                 shut_down(RB_POWER_OFF);
289                         }
290                         else
291                                 printf("Invalid command\n");
292                 }
293                 else
294                         printf("Usage: poweroff, restart, halt, status, test\n");
295
296                 return 1;
297         }
298
299         /* We are PID EINS, so start the system using OpenRC... */
300         init();
301
302         /* Launch TTYs...
303          * N.B.: To be a drop in replacement for SysV init, we really should read
304          *       and parse the entries in /etc/inittab; at least the c entries...
305          */
306         for(i=1; i<=6; i++)
307         {
308                 sprintf(buf, "agetty%i", i);
309                 sprintf(buf2, "/sbin/agetty tty%i --noclear", i);
310                 launch(buf, buf2, 1);
311         }
312
313         /* Install SIGCHLD signal handler to do process monitoring */
314         memset(&action, 0, sizeof(action));
315         action.sa_handler = handle_sigchld;
316         action.sa_flags = SA_SIGINFO;
317         sigaction(SIGCHLD, &action, NULL);
318
319         /* Create a named pipe to listen on for commands */
320         if (mkfifo(fifoPath, 0600) == -1)
321         {
322                 /* This is bad... If this happens, we have no comms */
323                 perror("mkfifo");
324         }
325
326         /* Main loop for PID 1. All commands are received and acted upon from here. */
327         while (1)
328         {
329                 /* This will block until a command is sent down the pipe... */
330                 file = fopen(fifoPath, "r");
331
332                 if (file == NULL)
333                 {
334                         /* Check to see if the signal handler blew things up on us */
335                         if (errno == EINTR)
336                                 continue;
337
338                         perror("fopen");
339                         continue;
340                 }
341
342                 /* Grab whatever was shoved into the FIFO */
343                 read = fread(buf, 1, 2047, file);
344                 buf[read] = 0;
345                 fclose(file);
346
347                 if (verbose)
348                         printf("PID1: Received \"%s\" from FIFO...\n", buf);
349
350                 /* Now do something with it... */
351                 if (strcmp(buf, "poweroff") == 0)
352                 {
353                         shut_down(RB_POWER_OFF);
354                 }
355                 else if (strcmp(buf, "restart") == 0)
356                 {
357                         shut_down(RB_AUTOBOOT);
358                 }
359                 else if (strcmp(buf, "halt") == 0)
360                 {
361                         shut_down(RB_HALT_SYSTEM);
362                 }
363                 else if (strcmp(buf, "status") == 0)
364                 {
365                         printf("[Status placeholder]\n");
366                 }
367         }
368
369         /* We should never get here... */
370         return 0;
371 }
372