Patch from vodz:
[oweals/busybox.git] / init / minit.c
1 /*
2  *  minit version 0.9.1 by Felix von Leitner
3  *  ported to busybox by Glenn McGrath <bug1@optushome.com.au>
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #include <time.h>
21 #include <signal.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <limits.h>
27 #include <errno.h>
28 #include <sys/fcntl.h>
29 #include <sys/ioctl.h>
30 #include <sys/poll.h>
31 #include <sys/reboot.h>
32 #include <sys/socket.h>
33 #include <sys/types.h>
34 #include <sys/un.h>
35 #include <sys/wait.h>
36 #include <linux/kd.h>
37
38 #include "busybox.h"
39
40 #define MINITROOT "/etc/minit"
41
42 static int i_am_init;
43 static int infd, outfd;
44
45 extern char **environ;
46
47 static struct process {
48         char *name;
49         pid_t pid;
50         char respawn;
51         char circular;
52         time_t startedat;
53         int __stdin, __stdout;
54         int logservice;
55 } *root;
56
57 static int maxprocess = -1;
58
59 static int processalloc = 0;
60
61 static unsigned int fmt_ulong(char *dest, unsigned long i)
62 {
63         register unsigned long len, tmp, len2;
64
65         /* first count the number of bytes needed */
66         for (len = 1, tmp = i; tmp > 9; ++len)
67                 tmp /= 10;
68         if (dest)
69                 for (tmp = i, dest += len, len2 = len + 1; --len2; tmp /= 10)
70                         *--dest = (tmp % 10) + '0';
71         return len;
72 }
73
74 /* split buf into n strings that are separated by c.  return n as *len.
75  * Allocate plus more slots and leave the first ofs of them alone. */
76 static char **split(char *buf, int c, int *len, int plus, int ofs)
77 {
78         int n = 1;
79         char **v = 0;
80         char **w;
81
82         /* step 1: count tokens */
83         char *s;
84
85         for (s = buf; *s; s++)
86                 if (*s == c)
87                         n++;
88         /* step 2: allocate space for pointers */
89         v = (char **) malloc((n + plus) * sizeof(char *));
90         if (!v)
91                 return 0;
92         w = v + ofs;
93         *w++ = buf;
94         for (s = buf;; s++) {
95                 while (*s && *s != c)
96                         s++;
97                 if (*s == 0)
98                         break;
99                 if (*s == c) {
100                         *s = 0;
101                         *w++ = s + 1;
102                 }
103         }
104         *len = w - v;
105         return v;
106 }
107
108 static int openreadclose(char *fn, char **buf, unsigned long *len)
109 {
110         int fd = open(fn, O_RDONLY);
111
112         if (fd < 0)
113                 return -1;
114         if (!*buf) {
115                 *len = lseek(fd, 0, SEEK_END);
116                 lseek(fd, 0, SEEK_SET);
117                 *buf = (char *) malloc(*len + 1);
118                 if (!*buf) {
119                         close(fd);
120                         return -1;
121                 }
122         }
123         *len = read(fd, *buf, *len);
124         if (*len != (unsigned long) -1)
125                 (*buf)[*len] = 0;
126         return close(fd);
127 }
128
129 /* return index of service in process data structure or -1 if not found */
130 static int findservice(char *service)
131 {
132         int i;
133
134         for (i = 0; i <= maxprocess; i++) {
135                 if (!strcmp(root[i].name, service))
136                         return i;
137         }
138         return -1;
139 }
140
141 /* look up process index in data structure by PID */
142 static int findbypid(pid_t pid)
143 {
144         int i;
145
146         for (i = 0; i <= maxprocess; i++) {
147                 if (root[i].pid == pid)
148                         return i;
149         }
150         return -1;
151 }
152
153 /* clear circular dependency detection flags */
154 static void circsweep(void)
155 {
156         int i;
157
158         for (i = 0; i <= maxprocess; i++)
159                 root[i].circular = 0;
160 }
161
162 /* add process to data structure, return index or -1 */
163 static int addprocess(struct process *p)
164 {
165         if (maxprocess + 1 >= processalloc) {
166                 struct process *fump;
167
168                 processalloc += 8;
169                 if ((fump =
170                          (struct process *) xrealloc(root,
171                                                                                  processalloc *
172                                                                                  sizeof(struct process))) == 0)
173                         return -1;
174                 root = fump;
175         }
176         memmove(&root[++maxprocess], p, sizeof(struct process));
177         return maxprocess;
178 }
179
180 /* load a service into the process data structure and return index or -1
181  * if failed */
182 static int loadservice(char *service)
183 {
184         struct process tmp;
185         int fd;
186
187         if (*service == 0)
188                 return -1;
189         fd = findservice(service);
190         if (fd >= 0)
191                 return fd;
192         if (chdir(MINITROOT) || chdir(service))
193                 return -1;
194         if (!(tmp.name = strdup(service)))
195                 return -1;
196         tmp.pid = 0;
197         fd = open("respawn", O_RDONLY);
198         if (fd >= 0) {
199                 tmp.respawn = 1;
200                 close(fd);
201         } else
202                 tmp.respawn = 0;
203         tmp.startedat = 0;
204         tmp.circular = 0;
205         tmp.__stdin = 0;
206         tmp.__stdout = 1;
207         {
208                 char *logservice = alloca(strlen(service) + 5);
209
210                 strcpy(logservice, service);
211                 strcat(logservice, "/log");
212                 tmp.logservice = loadservice(logservice);
213                 if (tmp.logservice >= 0) {
214                         int pipefd[2];
215
216                         if (pipe(pipefd))
217                                 return -1;
218                         root[tmp.logservice].__stdin = pipefd[0];
219                         tmp.__stdout = pipefd[1];
220                 }
221         }
222         return (addprocess(&tmp));
223 }
224
225 /* usage: isup(findservice("sshd")).
226  * returns nonzero if process is up */
227 static int isup(int service)
228 {
229         if (service < 0)
230                 return 0;
231         return (root[service].pid != 0);
232 }
233
234 static void opendevconsole(void)
235 {
236         int fd;
237
238         if ((fd = open("/dev/console", O_RDWR | O_NOCTTY)) >= 0) {
239                 dup2(fd, 0);
240                 dup2(fd, 1);
241                 dup2(fd, 2);
242                 if (fd > 2)
243                         close(fd);
244         }
245 }
246
247 /* called from inside the service directory, return the PID or 0 on error */
248 static pid_t forkandexec(int pause_flag, int service)
249 {
250         char **argv = 0;
251         int count = 0;
252         pid_t p;
253         int fd;
254         unsigned long len;
255         char *s = 0;
256         int argc;
257         char *argv0 = 0;
258
259   again:
260         switch (p = fork()) {
261         case (pid_t) - 1:
262                 if (count > 3)
263                         return 0;
264                 sleep(++count * 2);
265                 goto again;
266         case 0:
267                 /* child */
268
269                 if (i_am_init) {
270                         ioctl(0, TIOCNOTTY, 0);
271                         setsid();
272                         opendevconsole();
273                         tcsetpgrp(0, getpgrp());
274                 }
275                 close(infd);
276                 close(outfd);
277                 if (pause_flag) {
278                         struct timespec req;
279
280                         req.tv_sec = 0;
281                         req.tv_nsec = 500000000;
282                         nanosleep(&req, 0);
283                 }
284                 if (!openreadclose("params", &s, &len)) {
285                         argv = split(s, '\n', &argc, 2, 1);
286                         if (argv[argc - 1])
287                                 argv[argc - 1] = 0;
288                         else
289                                 argv[argc] = 0;
290                 } else {
291                         argv = (char **) xmalloc(2 * sizeof(char *));
292                         argv[1] = 0;
293                 }
294                 argv0 = (char *) xmalloc(PATH_MAX + 1);
295                 if (!argv || !argv0)
296                         goto abort;
297                 if (readlink("run", argv0, PATH_MAX) < 0) {
298                         if (errno != EINVAL)
299                                 goto abort;     /* not a symbolic link */
300                         argv0 = strdup("./run");
301                 }
302                 argv[0] = strrchr(argv0, '/');
303                 if (argv[0])
304                         argv[0]++;
305                 else
306                         argv[0] = argv0;
307                 if (root[service].__stdin != 0)
308                         dup2(root[service].__stdin, 0);
309                 if (root[service].__stdout != 1) {
310                         dup2(root[service].__stdout, 1);
311                         dup2(root[service].__stdout, 2);
312                 }
313                 {
314                         int i;
315
316                         for (i = 3; i < 1024; ++i)
317                                 close(i);
318                 }
319                 execve(argv0, argv, environ);
320                 _exit(0);
321           abort:
322                 free(argv0);
323                 free(argv);
324                 _exit(0);
325         default:
326                 fd = open("sync", O_RDONLY);
327                 if (fd >= 0) {
328                         pid_t p2;
329
330                         close(fd);
331                         p2 = waitpid(p, 0, 0);
332                         return 1;
333                 }
334                 return p;
335         }
336 }
337
338 /* start a service, return nonzero on error */
339 static int startnodep(int service, int pause_flag)
340 {
341         /* step 1: see if the process is already up */
342         if (isup(service))
343                 return 0;
344
345         /* step 2: fork and exec service, put PID in data structure */
346         if (chdir(MINITROOT) || chdir(root[service].name))
347                 return -1;
348         root[service].startedat = time(0);
349         root[service].pid = forkandexec(pause_flag, service);
350         return root[service].pid;
351 }
352
353 static int startservice(int service, int pause_flag)
354 {
355         int dir = -1;
356         unsigned long len;
357         char *s = 0;
358         pid_t pid;
359
360         if (service < 0)
361                 return 0;
362         if (root[service].circular)
363                 return 0;
364         root[service].circular = 1;
365         if (root[service].logservice >= 0)
366                 startservice(root[service].logservice, pause_flag);
367         if (chdir(MINITROOT) || chdir(root[service].name))
368                 return -1;
369         if ((dir = open(".", O_RDONLY)) >= 0) {
370                 if (!openreadclose("depends", &s, &len)) {
371                         char **deps;
372                         int depc, i;
373
374                         deps = split(s, '\n', &depc, 0, 0);
375                         for (i = 0; i < depc; i++) {
376                                 int service_index;
377
378                                 if (deps[i][0] == '#')
379                                         continue;
380                                 service_index = loadservice(deps[i]);
381                                 if (service_index >= 0 && root[service_index].pid != 1)
382                                         startservice(service_index, 0);
383                         }
384                         fchdir(dir);
385                 }
386                 pid = startnodep(service, pause_flag);
387                 close(dir);
388                 dir = -1;
389                 return pid;
390         }
391         return 0;
392 }
393
394 static void sulogin(void)
395 {
396         /* exiting on an initialization failure is not a good idea for init */
397         char *argv[] = { "sulogin", 0 };
398         execve("/sbin/sulogin", argv, environ);
399         exit(1);
400 }
401
402 static void handlekilled(pid_t killed)
403 {
404         int i;
405
406         if (killed == (pid_t) - 1) {
407                 write(2, "all services exited.\n", 21);
408                 exit(0);
409         }
410         if (killed == 0)
411                 return;
412         i = findbypid(killed);
413         if (i >= 0) {
414                 root[i].pid = 0;
415                 if (root[i].respawn) {
416                         circsweep();
417                         startservice(i, time(0) - root[i].startedat < 1);
418                 } else {
419                         root[i].startedat = time(0);
420                         root[i].pid = 1;
421                 }
422         }
423 }
424
425 static void childhandler(void)
426 {
427         int status;
428         pid_t killed;
429
430         do {
431                 killed = waitpid(-1, &status, WNOHANG);
432                 handlekilled(killed);
433         } while (killed && killed != (pid_t) - 1);
434 }
435
436 static volatile int dowinch = 0;
437 static volatile int doint = 0;
438
439 static void sigchild(int whatever)
440 {
441 }
442 static void sigwinch(int sig)
443 {
444         dowinch = 1;
445 }
446 static void sigint(int sig)
447 {
448         doint = 1;
449 }
450
451 extern int minit_main(int argc, char *argv[])
452 {
453         /* Schritt 1: argv[1] als Service nehmen und starten */
454         struct pollfd pfd;
455         time_t last = time(0);
456         int nfds = 1;
457         int count = 0;
458         int i;
459
460         infd = open("/etc/minit/in", O_RDWR);
461         outfd = open("/etc/minit/out", O_RDWR | O_NONBLOCK);
462         if (getpid() == 1) {
463                 int fd;
464
465                 i_am_init = 1;
466                 reboot(0);
467                 if ((fd = open("/dev/console", O_RDWR | O_NOCTTY))) {
468                         ioctl(fd, KDSIGACCEPT, SIGWINCH);
469                         close(fd);
470                 } else
471                         ioctl(0, KDSIGACCEPT, SIGWINCH);
472         }
473 /*  signal(SIGPWR,sighandler); don't know what to do about it */
474 /*  signal(SIGHUP,sighandler); ??? */
475         {
476                 struct sigaction sa;
477
478                 sigemptyset(&sa.sa_mask);
479                 sa.sa_sigaction = 0;
480                 sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
481                 sa.sa_handler = sigchild;
482                 sigaction(SIGCHLD, &sa, 0);
483                 sa.sa_handler = sigint;
484                 sigaction(SIGINT, &sa, 0);      /* ctrl-alt-del */
485                 sa.sa_handler = sigwinch;
486                 sigaction(SIGWINCH, &sa, 0);    /* keyboard request */
487         }
488         if (infd < 0 || outfd < 0) {
489                 puts("minit: could not open /etc/minit/in or /etc/minit/out\n");
490                 sulogin();
491                 nfds = 0;
492         } else
493                 pfd.fd = infd;
494         pfd.events = POLLIN;
495
496         for (i = 1; i < argc; i++) {
497                 circsweep();
498                 if (startservice(loadservice(argv[i]), 0))
499                         count++;
500         }
501         circsweep();
502         if (!count)
503                 startservice(loadservice("default"), 0);
504         for (;;) {
505                 char buf[1501];
506                 time_t now;
507
508                 if (doint) {
509                         doint = 0;
510                         startservice(loadservice("ctrlaltdel"), 0);
511                 }
512                 if (dowinch) {
513                         dowinch = 0;
514                         startservice(loadservice("kbreq"), 0);
515                 }
516                 childhandler();
517                 now = time(0);
518                 if (now < last || now - last > 30) {
519                         /* The system clock was reset.  Compensate. */
520                         long diff = last - now;
521                         int j;
522
523                         for (j = 0; j <= maxprocess; ++j) {
524                                 root[j].startedat -= diff;
525                         }
526                 }
527                 last = now;
528                 switch (poll(&pfd, nfds, 5000)) {
529                 case -1:
530                         if (errno == EINTR) {
531                                 childhandler();
532                                 break;
533                         }
534                         opendevconsole();
535                         puts("poll failed!\n");
536                         sulogin();
537                         /* what should we do if poll fails?! */
538                         break;
539                 case 1:
540                         i = read(infd, buf, 1500);
541                         if (i > 1) {
542                                 pid_t pid;
543                                 int idx = 0;
544                                 int tmp;
545
546                                 buf[i] = 0;
547
548                                 if (buf[0] != 's' && ((idx = findservice(buf + 1)) < 0))
549                                   error:
550                                         write(outfd, "0", 1);
551                                 else {
552                                         switch (buf[0]) {
553                                         case 'p':
554                                                 write(outfd, buf, fmt_ulong(buf, root[idx].pid));
555                                                 break;
556                                         case 'r':
557                                                 root[idx].respawn = 0;
558                                                 goto ok;
559                                         case 'R':
560                                                 root[idx].respawn = 1;
561                                                 goto ok;
562                                         case 'C':
563                                                 if (kill(root[idx].pid, 0)) {   /* check if still active */
564                                                         handlekilled(root[idx].pid);    /* no!?! remove form active list */
565                                                         goto error;
566                                                 }
567                                                 goto ok;
568                                                 break;
569                                         case 'P':
570                                         {
571                                                 unsigned char *x = buf + strlen(buf) + 1;
572                                                 unsigned char c;
573
574                                                 tmp = 0;
575                                                 while ((c = *x++ - '0') < 10)
576                                                         tmp = tmp * 10 + c;
577                                         }
578                                                 if (tmp > 0) {
579                                                         if (kill(tmp, 0))
580                                                                 goto error;
581                                                         pid = tmp;
582                                                 }
583                                                 root[idx].pid = tmp;
584                                                 goto ok;
585                                         case 's':
586                                                 idx = loadservice(buf + 1);
587                                                 if (idx < 0)
588                                                         goto error;
589                                                 if (root[idx].pid < 2) {
590                                                         root[idx].pid = 0;
591                                                         circsweep();
592                                                         idx = startservice(idx, 0);
593                                                         if (idx == 0) {
594                                                                 write(outfd, "0", 1);
595                                                                 break;
596                                                         }
597                                                 }
598                                           ok:
599                                                 write(outfd, "1", 1);
600                                                 break;
601                                         case 'u':
602                                                 write(outfd, buf,
603                                                           fmt_ulong(buf, time(0) - root[idx].startedat));
604                                         }
605                                 }
606                         }
607                         break;
608                 default:
609                         break;
610                 }
611         }
612 }