fiss-minit

A standalone service supervisor based on minit
Log | Files | Refs | README | LICENSE

minit.c (14838B)


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