minit.c (17432B) download
1#define _GNU_SOURCE
2
3#include <sys/types.h>
4#include <time.h>
5#include <string.h>
6#include <fcntl.h>
7#include <unistd.h>
8#include <limits.h>
9#include <errno.h>
10#include <sys/socket.h>
11#include <sys/un.h>
12#include <sys/poll.h>
13#include <signal.h>
14#include <sys/wait.h>
15#include <stdio.h>
16#include <linux/kd.h>
17#include <sys/ioctl.h>
18#include <stdlib.h>
19#include <alloca.h>
20#include <sys/reboot.h>
21#include <write12.h>
22
23#include <libowfat/fmt.h>
24#include <libowfat/str.h>
25#include <libowfat/compiletimeassert.h>
26
27#include "minit.h"
28
29compiletimeassert(sizeof(MINITROOT) + 64 < PATH_MAX);
30
31#define MALLOC_TEST
32#if !defined(__dietlibc__) && !defined(__GLIBC__)
33#undef MALLOC_TEST
34#endif
35
36#ifdef MALLOC_TEST
37extern void* __libc_malloc(size_t size);
38extern void* __libc_realloc(void* x,size_t size);
39extern void __libc_free(void* x);
40static char malloc_buf[1024];
41static unsigned long n;
42static struct process procbuf[50];
43void *malloc(size_t size) {
44 if (n+size<sizeof(malloc_buf)) {
45 char* tmp=malloc_buf+n;
46 n+=size;
47 n=(n+3)&~3;
48 return tmp;
49 }
50 return __libc_malloc(size);
51}
52void free(void* x) {
53 if ((char*)x>=malloc_buf && (char*)x<malloc_buf+sizeof(malloc_buf)) return;
54 __libc_free(x);
55}
56void* realloc(void* x,size_t size) {
57 if (x==0 || x==procbuf) {
58 void* y;
59 if (size<=sizeof(procbuf))
60 return procbuf;
61 y=__libc_malloc(size);
62 if (!y) return 0;
63 memcpy(y,x,size);
64 return y;
65 }
66 return __libc_realloc(x,size);
67}
68#endif
69
70char** Argv;
71
72#undef printf
73extern int printf(const char *format,...);
74
75extern void opendevconsole();
76
77#define UPDATE
78#ifdef UPDATE
79static int doupdate;
80#endif
81
82static int i_am_init;
83
84extern int openreadclose(char *fn, char **buf, size_t *len);
85extern char **split(char *buf,int c,size_t* len,size_t plus,size_t ofs);
86
87extern char **environ;
88
89#define HISTORY 10
90#ifdef HISTORY
91int history[HISTORY];
92#endif
93
94/* return index of service in process data structure or -1 if not found */
95int findservice(char *service) {
96 int i;
97 for (i=0; i<=maxprocess; ++i) {
98 if (!strcmp(root[i].name,service))
99 return i;
100 }
101 return -1;
102}
103
104/* look up process index in data structure by PID */
105int findbypid(pid_t pid) {
106 int i;
107 for (i=0; i<=maxprocess; ++i) {
108 if (root[i].pid == pid)
109 return i;
110 }
111 return -1;
112}
113
114/* clear circular dependency detection flags */
115void circsweep() {
116 int i;
117 for (i=0; i<=maxprocess; ++i)
118 root[i].circular=0;
119}
120
121/* add process to data structure, return index or -1 */
122int addprocess(struct process *p) {
123 if (maxprocess+1>=processalloc) {
124 struct process *fump;
125 processalloc+=8;
126 if ((fump=(struct process *)realloc(root,processalloc*sizeof(struct process)))==0) return -1;
127 root=fump;
128 }
129 memmove(&root[++maxprocess],p,sizeof(struct process));
130 return maxprocess;
131}
132
133/* load a service into the process data structure and return index or -1
134 * if failed */
135int loadservice(char *service) {
136 struct process tmp;
137 int fd;
138 if (*service==0) return -1;
139 fd=findservice(service);
140 if (fd>=0) return fd;
141 if (chdir(MINITROOT) || chdir(service)) return -1;
142 if (!(tmp.name=strdup(service))) return -1;
143 tmp.pid=0;
144 fd=open("respawn",O_RDONLY);
145 if (fd>=0) {
146 tmp.respawn=1;
147 close(fd);
148 } else
149 tmp.respawn=0;
150 tmp.startedat=0;
151 tmp.circular=0;
152 tmp.__stdin=0; tmp.__stdout=1;
153 {
154 char *logservice=alloca(str_len(service)+5);
155 strcpy(logservice,service);
156 strcat(logservice,"/log");
157 tmp.logservice=loadservice(logservice);
158 if (tmp.logservice>=0) {
159 int pipefd[2];
160 if (pipe(pipefd)) return -1;
161 fcntl(pipefd[0],F_SETFD,FD_CLOEXEC);
162 fcntl(pipefd[1],F_SETFD,FD_CLOEXEC);
163 root[tmp.logservice].__stdin=pipefd[0];
164 tmp.__stdout=pipefd[1];
165 }
166 }
167 return addprocess(&tmp);
168}
169
170/* usage: isup(findservice("sshd")).
171 * returns nonzero if process is up */
172int isup(int service) {
173 if (service<0) return 0;
174 return (root[service].pid!=0);
175}
176
177int startservice(int service,int pause,int father);
178
179void sulogin() { /* exiting on an initialization failure is not a good idea for init */
180 char *argv[]={"sulogin",0};
181 if (i_am_init)
182 execve("/sbin/sulogin",argv,environ);
183 _exit(1);
184}
185
186#undef debug
187void handlekilled(pid_t killed) {
188 int i;
189#ifdef debug
190 {
191 char buf[50];
192 snprintf(buf,50," %d\n",killed);
193 write(2,buf,str_len(buf));
194 }
195#endif
196 if (killed == (pid_t)-1) {
197 static int saidso;
198 if (!saidso) { write(2,"all services exited.\n",21); saidso=1; }
199 if (i_am_init) sulogin(); else exit(0);
200 }
201 if (killed==0) return;
202 i=findbypid(killed);
203#if 0
204 printf("%d exited, idx %d -> service %s\n",killed,i,i>=0?root[i].name:"[unknown]");
205#endif
206 if (i>=0) {
207 root[i].pid=0;
208 if (root[i].respawn) {
209#if 0
210 printf("restarting %s\n",root[i].name);
211#endif
212 circsweep();
213 startservice(i,time(0)-root[i].startedat<1,root[i].father);
214 } else {
215 root[i].startedat=time(0);
216 root[i].pid=1;
217 }
218 }
219}
220
221/* called from inside the service directory, return the PID or 0 on error */
222pid_t forkandexec(int pause,int service) {
223 char **argv=0;
224 int count=0;
225 pid_t p;
226 int fd;
227 size_t len;
228 int islink;
229 char *s=0;
230 size_t argc;
231 char *argv0=0;
232again:
233 switch (p=fork()) {
234 case (pid_t)-1:
235 if (count>3) return 0;
236 sleep(++count*2);
237 goto again;
238 case 0:
239 /* child */
240
241 if (i_am_init) {
242 ioctl(0, TIOCNOTTY, 0);
243 if (setsid() == -1) {
244 __write2("setsid failed unexpectedly.\n");
245 // This should never fail. init is run as root.
246 // If it does fail, don't exit for fear of bricking the system
247 }
248 opendevconsole();
249/* ioctl(0, TIOCSCTTY, 1); */
250 int r = tcsetpgrp(0, getpgrp());
251 if (r == -1 && errno!=ENOTTY) { // will get this error for log services
252 __write2("tcsetpgrp failed unexpectedly: ");
253 switch (errno) {
254 case EBADF: __write2("EBADF\n"); break;
255 case EINVAL: __write2("EINVAL\n"); break;
256// case ENOTTY: __write2("ENOTTY\n"); break;
257 case EPERM: __write2("EPERM\n"); break;
258 default: __write2("unhandled\n");
259 }
260 // This should never fail. init is run as root.
261 // If it does fail, don't exit for fear of bricking the system
262 }
263 }
264
265 if (pause) {
266 struct timespec req;
267 req.tv_sec=0;
268 req.tv_nsec=500000000;
269 nanosleep(&req,0);
270 }
271 if ((fd=open("in",O_RDONLY))!=-1) {
272 if (dup2(fd,0) != 0) {
273 __write2("dup2 failed unexpectedly.\n");
274 // This should never fail. init is run as root.
275 // If it does fail, don't exit for fear of bricking the system
276 }
277 fcntl(0,F_SETFD,0);
278 }
279 if ((fd=open("out",O_WRONLY))!=-1) {
280 if (dup2(fd,1) != 1 || dup2(fd,2) != 2) {
281 __write2("dup2 failed unexpectedly.\n");
282 // This should never fail. init is run as root.
283 // If it does fail, don't exit for fear of bricking the system
284 }
285 fcntl(1,F_SETFD,0);
286 fcntl(2,F_SETFD,0);
287 }
288 // openreadclose and split allocate memory.
289 // We leak it here, because we are in the child process that is
290 // about to execve somebody else, at which point the OS frees all.
291 if (!openreadclose("nice",&s,&len)) {
292 int n=atoi(s);
293 nice(n);
294 s=0;
295 }
296 if (!openreadclose("params",&s,&len)) {
297 argv=split(s,'\n',&argc,2,1);
298 if (argc && argv[argc-1][0]==0) --argc; // if params ended on newline, don't pass empty last arg
299 argv[argc]=0;
300 } else {
301 argv=(char**)alloca(2*sizeof(char*));
302 argv[1]=0;
303 }
304 argv0=(char*)alloca(PATH_MAX+1);
305 if (!argv) _exit(1);
306 if (readlink("run",argv0,PATH_MAX)<0) {
307 if (errno!=EINVAL) _exit(1); /* not a symbolic link */
308 strcpy(argv0,"run");
309 islink=0;
310 } else
311 islink=1;
312 argv[0]=strrchr(argv0,'/');
313 if (argv[0])
314 argv[0]++;
315 else
316 argv[0]=argv0;
317 argv[0]=strdupa(argv[0]);
318 if (islink && argv0[0]!='/') {
319 char* c=alloca(strlen(argv0)+sizeof(MINITROOT)+strlen(root[service].name+2));
320 char* d;
321 int fd=open(".",O_RDONLY|O_DIRECTORY);
322 stpcpy(stpcpy(stpcpy(stpcpy(c,MINITROOT "/"),root[service].name),"/"),argv0);
323 if (fd!=-1) {
324 /* c is now something like /etc/minit/default/../../../var/whatever/doit
325 * attempt to clean it up a little */
326 d=strrchr(c,'/');
327 *d=0;
328 if (chdir(c)==0) {
329 *d='/';
330 if (getcwd(argv0,PATH_MAX))
331 strncat(argv0,d,PATH_MAX);
332 fchdir(fd);
333 close(fd);
334 } else {
335 *d='/';
336 argv0=c;
337 }
338 } else
339 argv0=c;
340 }
341 if (root[service].__stdin != 0) {
342 if (dup2(root[service].__stdin,0) != 0) {
343 __write2("dup2 failed unexpectedly.\n");
344 // This should never fail. init is run as root.
345 // If it does fail, don't exit for fear of bricking the system
346 }
347 fcntl(0,F_SETFD,0);
348 }
349 if (root[service].__stdout != 1) {
350 if (dup2(root[service].__stdout,1) != 1 || dup2(root[service].__stdout,2) != 2) {
351 __write2("dup2 failed unexpectedly.\n");
352 // This should never fail. init is run as root.
353 // If it does fail, don't exit for fear of bricking the system
354 }
355 fcntl(1,F_SETFD,0);
356 fcntl(2,F_SETFD,0);
357 }
358 {
359 int i;
360 for (i=3; i<1024; ++i) close(i);
361 }
362 chdir("root");
363 execve(argv0,argv,environ);
364 _exit(1);
365 default:
366 fd=open("sync",O_RDONLY);
367 if (fd>=0) {
368 close(fd);
369 waitpid(p,0,0);
370 return 1;
371 }
372 return p;
373 }
374}
375
376/* start a service, return nonzero on error */
377int startnodep(int service,int pause) {
378 /* step 1: see if the process is already up */
379 if (isup(service)) return 0;
380 /* step 2: fork and exec service, put PID in data structure */
381 if (chdir(MINITROOT) || chdir(root[service].name)) return -1;
382 root[service].startedat=time(0);
383 root[service].pid=forkandexec(pause,service);
384 return root[service].pid;
385}
386
387int startservice(int service,int pause,int father) {
388 int dir=-1;
389 unsigned long len;
390 char *s=0;
391 pid_t pid=0;
392 if (service<0) return 0;
393 if (root[service].circular)
394 return 0;
395 root[service].circular=1;
396#if 0
397 printf("setting father of %d (%s) to %d (%s)\n",
398 service,root[service].name,father,father>=0?root[father].name:"[msvc]");
399#endif
400 root[service].father=father;
401#ifdef HISTORY
402 {
403 memmove(history+1,history,sizeof(int)*((HISTORY)-1));
404 history[0]=service;
405 }
406#endif
407 if (root[service].logservice>=0)
408 startservice(root[service].logservice,pause,service);
409 if (chdir(MINITROOT) || chdir(root[service].name)) return -1;
410 if ((dir=open(".",O_RDONLY))>=0) {
411 // openreadclose allocates memory and reads the file contents into it.
412 // Need to free(s) independent of openreadclose return value
413 if (!openreadclose("depends",&s,&len)) {
414 char **deps=0;
415 size_t depc,i;
416 deps=split(s,'\n',&depc,0,0);
417 for (i=0; i<depc; i++) {
418 int Service,blacklisted,j;
419 if (deps[i][0]=='#') continue;
420 Service=loadservice(deps[i]);
421
422#if 1
423 for (j=blacklisted=0; Argv[j]; ++j)
424 if (Argv[j][0]=='-' && !strcmp(Argv[j]+1,deps[i])) {
425 blacklisted=1;
426 ++Argv[j];
427 break;
428 }
429#endif
430
431 if (Service>=0 && root[Service].pid!=1 && !blacklisted)
432 startservice(Service,0,service);
433 }
434 fchdir(dir);
435 free(deps);
436 }
437 free(s);
438 pid=startnodep(service,pause);
439
440#if 0
441 write(1,"started service ",17);
442 write(1,root[service].name,str_len(root[service].name));
443 write(1," -> ",4);
444 {
445 char buf[10];
446 snprintf(buf,10,"%d\n",pid);
447 write(1,buf,str_len(buf));
448 }
449#endif
450 close(dir);
451 dir=-1;
452 }
453 chdir(MINITROOT);
454 return pid;
455}
456
457static void _puts(const char* s) {
458 write(1,s,str_len(s));
459}
460
461void childhandler() {
462 int status;
463 pid_t killed;
464#ifdef debug
465 write(2,"wait...",7);
466#endif
467#if 0
468 if (getpid()!=1) {
469 char buf[100];
470 _puts("childhandler() called from pid ");
471 buf[fmt_ulong(buf,getpid())]=0;
472 _puts(buf);
473 _puts("\n");
474 return;
475 }
476#endif
477#ifdef UPDATE
478if (doupdate) return;
479#endif
480
481 do {
482 killed=waitpid(-1,&status,WNOHANG);
483 handlekilled(killed);
484 } while (killed && killed!=(pid_t)-1);
485}
486
487static volatile int dowinch=0;
488static volatile int doint=0;
489
490void sigchild(int sig) { (void)sig; }
491void sigwinch(int sig) { (void)sig; dowinch=1; }
492void sigint(int sig) { (void)sig; doint=1; }
493
494int main(int argc, char *argv[]) {
495 /* Schritt 1: argv[1] als Service nehmen und starten */
496 int count=0;
497 int i;
498 struct pollfd pfd;
499 time_t last=time(0);
500 int nfds=1;
501
502#ifdef HISTORY
503 for (i=0; i<HISTORY; ++i)
504 history[i]=-1;
505#endif
506
507 Argv=argv;
508
509 infd=open(MINITROOT "/in",O_RDWR);
510 outfd=open(MINITROOT "/out",O_RDWR|O_NONBLOCK);
511
512 if (getpid()==1) {
513 int fd;
514 i_am_init=1;
515 reboot(RB_DISABLE_CAD);
516 if ((fd=open("/dev/console",O_RDWR|O_NOCTTY))!=-1) {
517 ioctl(fd, KDSIGACCEPT, SIGWINCH);
518 close(fd);
519 } else
520 ioctl(0, KDSIGACCEPT, SIGWINCH);
521 }
522/* signal(SIGPWR,sighandler); don't know what to do about it */
523/* signal(SIGHUP,sighandler); ??? */
524 {
525 static struct sigaction sa;
526 sigemptyset(&sa.sa_mask);
527 errno=0;
528 sa.sa_sigaction=0;
529 sa.sa_flags=SA_RESTART | SA_NOCLDSTOP;
530 sa.sa_handler=sigchild; sigaction(SIGCHLD,&sa,0);
531 sa.sa_flags=SA_RESTART;
532 if (i_am_init) {
533 sa.sa_handler=sigint; sigaction(SIGINT,&sa,0); /* ctrl-alt-del */
534 sa.sa_handler=sigwinch; sigaction(SIGWINCH,&sa,0); /* keyboard request */
535 }
536 if (errno) _puts("sigaction failed!\n");
537 }
538
539 if (infd<0 || outfd<0) {
540 _puts("minit: could not open " MINITROOT "/in or " MINITROOT "/out\n");
541 sulogin();
542 nfds=0;
543 } else
544 pfd.fd=infd;
545 pfd.events=POLLIN;
546
547 fcntl(infd,F_SETFD,FD_CLOEXEC);
548 fcntl(outfd,F_SETFD,FD_CLOEXEC);
549
550#ifdef UPDATE
551 {
552 struct flock fl;
553 fl.l_whence=SEEK_CUR;
554 fl.l_start=0;
555 fl.l_len=0;
556 fl.l_pid=0;
557 if ( (0 == fcntl(infd,F_GETLK,&fl)) &&
558 (fl.l_type != F_UNLCK )) doupdate=1;
559 }
560
561 if(!doupdate) {
562#endif
563 for (i=1; i<argc; i++) {
564 circsweep();
565 if (startservice(loadservice(argv[i]),0,-1)) count++;
566 }
567 circsweep();
568 if (!count) startservice(loadservice("default"),0,-1);
569#ifdef UPDATE
570 }
571#endif
572 for (;;) {
573 int i;
574 char buf[1501];
575 time_t now;
576 if (doint) {
577 doint=0;
578 startservice(loadservice("ctrlaltdel"),0,-1);
579 }
580 if (dowinch) {
581 dowinch=0;
582 startservice(loadservice("kbreq"),0,-1);
583 }
584 childhandler();
585 now=time(0);
586 if (now<last || now-last>30) {
587 /* The system clock was reset. Compensate. */
588 long diff=last-now;
589 int j;
590
591 for (j=0; j<=maxprocess; ++j)
592 root[j].startedat-=diff;
593 }
594 last=now;
595 switch (poll(&pfd,nfds,5000)) {
596 case -1:
597 if (errno==EINTR) {
598 childhandler();
599 break;
600 }
601 opendevconsole();
602 _puts("poll failed!\n");
603 sulogin();
604 /* what should we do if poll fails?! */
605 break;
606 case 1:
607 i=read(infd,buf,1500);
608 if (i>1) {
609 int idx=0,tmp;
610 buf[i]=0;
611
612/* write(1,buf,str_len(buf)); write(1,"\n",1); */
613#ifdef UPDATE
614 if(!strcmp(buf,"update")) {
615 execve("/sbin/minit",argv, environ);
616 }
617
618 if (((buf[0]!='U') && buf[0]!='s') && ((idx=findservice(buf+1))<0)
619 && strcmp(buf,"d-"))
620#else
621 if (buf[0]!='s' && ((idx=findservice(buf+1))<0) && strcmp(buf,"d-") )
622#endif
623error:
624 write(outfd,"0",1);
625 else {
626 switch (buf[0]) {
627 case 'p':
628 write(outfd,buf,fmt_ulong(buf,root[idx].pid));
629 break;
630#ifdef UPDATE
631 case 'D':
632 doupdate=1;
633 write(outfd, &root[idx], sizeof(struct process));
634 break;
635 case 'U':
636 doupdate=1;
637 write(outfd,"1",1);
638 if (1==poll(&pfd,nfds,5000)) {
639 struct process tmp;
640 if (read(infd,&tmp,sizeof tmp) != sizeof(tmp)) {
641 __write2("update failed, struct size mismatch.\n");
642 goto ok;
643 }
644 tmp.name=strdup(buf+1);
645 addprocess(&tmp);
646 }
647 goto ok;
648#endif
649 case 'r':
650 root[idx].respawn=0;
651 goto ok;
652 case 'R':
653 root[idx].respawn=1;
654 goto ok;
655 case 'C':
656 if (kill(root[idx].pid,0)) { /* check if still active */
657 handlekilled(root[idx].pid); /* no!?! remove form active list */
658 goto error;
659 }
660 goto ok;
661 case 'P':
662 {
663 unsigned char *x=(unsigned char*)buf+str_len(buf)+1;
664 unsigned char c;
665 tmp=0;
666 while ((c=*x++-'0')<10) tmp=tmp*10+c;
667 }
668 if (tmp>0) {
669 if (kill(tmp,0)) goto error;
670 }
671 root[idx].pid=tmp;
672 goto ok;
673 case 's':
674 idx=loadservice(buf+1);
675 if (idx<0) goto error;
676 if (root[idx].pid<2) {
677 root[idx].pid=0;
678 circsweep();
679 idx=startservice(idx,0,-1);
680 if (idx==0) {
681 write(outfd,"0",1);
682 break;
683 }
684 }
685ok:
686 write(outfd,"1",1);
687 break;
688 case 'u':
689 write(outfd,buf,fmt_ulong(buf,time(0)-root[idx].startedat));
690 break;
691 case 'd':
692 write(outfd,"1:",2);
693 {
694 int i;
695#if 0
696 printf("looking for father==%d\n",idx);
697#endif
698 for (i=0; i<=maxprocess; ++i) {
699#if 0
700 printf("pid of %d(%s) is %lu, father is %d\n",
701 i,root[i].name?root[i].name:"[none]",root[i].pid,root[i].father);
702#endif
703 if (root[i].father==idx)
704 write(outfd,root[i].name,str_len(root[i].name)+1);
705 }
706 write(outfd,"\0",2);
707 }
708 break;
709 }
710 }
711 } else {
712 if (buf[0]=='h') {
713#ifdef HISTORY
714 write(outfd,"1:",2);
715 {
716 int i;
717 for (i=0; i<HISTORY; ++i)
718 if (history[i]!=-1)
719 write(outfd,root[history[i]].name,str_len(root[history[i]].name)+1);
720 write(outfd,"\0",2);
721 }
722#else
723 write(outfd,"0",1);
724#endif
725 }
726 }
727 break;
728 default:
729#ifdef UPDATE
730 doupdate=0;
731#endif
732 break;
733 }
734 }
735}