unix/minit

minit.c in master
Repositories | Summary | Log | Files | README | COPYING

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}