diff options
author | Glenn L McGrath | 2003-05-11 14:52:39 +0000 |
---|---|---|
committer | Glenn L McGrath | 2003-05-11 14:52:39 +0000 |
commit | 1e11c34be4decfef8fbda8a8e01cd60def8232e5 (patch) | |
tree | 6e93956fef2bcd4c1db18031dc081dcaf689f2d1 /init/minit.c | |
parent | 8c6887c855460ee9e688e2a51e29f99faa2a2d8c (diff) | |
download | busybox-1e11c34be4decfef8fbda8a8e01cd60def8232e5.zip busybox-1e11c34be4decfef8fbda8a8e01cd60def8232e5.tar.gz |
minit, a Minimal init system.
Diffstat (limited to 'init/minit.c')
-rw-r--r-- | init/minit.c | 612 |
1 files changed, 612 insertions, 0 deletions
diff --git a/init/minit.c b/init/minit.c new file mode 100644 index 0000000..6645dc6 --- /dev/null +++ b/init/minit.c @@ -0,0 +1,612 @@ +/* + * minit version 0.9.1 by Felix von Leitner + * ported to busybox by Glenn McGrath <bug1@optushome.com.au> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <time.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> +#include <errno.h> +#include <sys/fcntl.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <sys/reboot.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <linux/kd.h> + +#include "busybox.h" + +#define MINITROOT "/etc/minit" + +static int i_am_init; +static int infd, outfd; + +extern char **environ; + +static struct process { + char *name; + pid_t pid; + char respawn; + char circular; + time_t startedat; + int __stdin, __stdout; + int logservice; +} *root; + +static int maxprocess = -1; + +static int processalloc = 0; + +static unsigned int fmt_ulong(char *dest, unsigned long i) +{ + register unsigned long len, tmp, len2; + + /* first count the number of bytes needed */ + for (len = 1, tmp = i; tmp > 9; ++len) + tmp /= 10; + if (dest) + for (tmp = i, dest += len, len2 = len + 1; --len2; tmp /= 10) + *--dest = (tmp % 10) + '0'; + return len; +} + +/* split buf into n strings that are separated by c. return n as *len. + * Allocate plus more slots and leave the first ofs of them alone. */ +static char **split(char *buf, int c, int *len, int plus, int ofs) +{ + int n = 1; + char **v = 0; + char **w; + + /* step 1: count tokens */ + char *s; + + for (s = buf; *s; s++) + if (*s == c) + n++; + /* step 2: allocate space for pointers */ + v = (char **) malloc((n + plus) * sizeof(char *)); + if (!v) + return 0; + w = v + ofs; + *w++ = buf; + for (s = buf;; s++) { + while (*s && *s != c) + s++; + if (*s == 0) + break; + if (*s == c) { + *s = 0; + *w++ = s + 1; + } + } + *len = w - v; + return v; +} + +static int openreadclose(char *fn, char **buf, unsigned long *len) +{ + int fd = open(fn, O_RDONLY); + + if (fd < 0) + return -1; + if (!*buf) { + *len = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + *buf = (char *) malloc(*len + 1); + if (!*buf) { + close(fd); + return -1; + } + } + *len = read(fd, *buf, *len); + if (*len != (unsigned long) -1) + (*buf)[*len] = 0; + return close(fd); +} + +/* return index of service in process data structure or -1 if not found */ +static int findservice(char *service) +{ + int i; + + for (i = 0; i <= maxprocess; i++) { + if (!strcmp(root[i].name, service)) + return i; + } + return -1; +} + +/* look up process index in data structure by PID */ +static int findbypid(pid_t pid) +{ + int i; + + for (i = 0; i <= maxprocess; i++) { + if (root[i].pid == pid) + return i; + } + return -1; +} + +/* clear circular dependency detection flags */ +static void circsweep(void) +{ + int i; + + for (i = 0; i <= maxprocess; i++) + root[i].circular = 0; +} + +/* add process to data structure, return index or -1 */ +static int addprocess(struct process *p) +{ + if (maxprocess + 1 >= processalloc) { + struct process *fump; + + processalloc += 8; + if ((fump = + (struct process *) xrealloc(root, + processalloc * + sizeof(struct process))) == 0) + return -1; + root = fump; + } + memmove(&root[++maxprocess], p, sizeof(struct process)); + return maxprocess; +} + +/* load a service into the process data structure and return index or -1 + * if failed */ +static int loadservice(char *service) +{ + struct process tmp; + int fd; + + if (*service == 0) + return -1; + fd = findservice(service); + if (fd >= 0) + return fd; + if (chdir(MINITROOT) || chdir(service)) + return -1; + if (!(tmp.name = strdup(service))) + return -1; + tmp.pid = 0; + fd = open("respawn", O_RDONLY); + if (fd >= 0) { + tmp.respawn = 1; + close(fd); + } else + tmp.respawn = 0; + tmp.startedat = 0; + tmp.circular = 0; + tmp.__stdin = 0; + tmp.__stdout = 1; + { + char *logservice = alloca(strlen(service) + 5); + + strcpy(logservice, service); + strcat(logservice, "/log"); + tmp.logservice = loadservice(logservice); + if (tmp.logservice >= 0) { + int pipefd[2]; + + if (pipe(pipefd)) + return -1; + root[tmp.logservice].__stdin = pipefd[0]; + tmp.__stdout = pipefd[1]; + } + } + return (addprocess(&tmp)); +} + +/* usage: isup(findservice("sshd")). + * returns nonzero if process is up */ +static int isup(int service) +{ + if (service < 0) + return 0; + return (root[service].pid != 0); +} + +static void opendevconsole(void) +{ + int fd; + + if ((fd = open("/dev/console", O_RDWR | O_NOCTTY)) >= 0) { + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + if (fd > 2) + close(fd); + } +} + +/* called from inside the service directory, return the PID or 0 on error */ +static pid_t forkandexec(int pause_flag, int service) +{ + char **argv = 0; + int count = 0; + pid_t p; + int fd; + unsigned long len; + char *s = 0; + int argc; + char *argv0 = 0; + + again: + switch (p = fork()) { + case (pid_t) - 1: + if (count > 3) + return 0; + sleep(++count * 2); + goto again; + case 0: + /* child */ + + if (i_am_init) { + ioctl(0, TIOCNOTTY, 0); + setsid(); + opendevconsole(); + tcsetpgrp(0, getpgrp()); + } + close(infd); + close(outfd); + if (pause_flag) { + struct timespec req; + + req.tv_sec = 0; + req.tv_nsec = 500000000; + nanosleep(&req, 0); + } + if (!openreadclose("params", &s, &len)) { + argv = split(s, '\n', &argc, 2, 1); + if (argv[argc - 1]) + argv[argc - 1] = 0; + else + argv[argc] = 0; + } else { + argv = (char **) xmalloc(2 * sizeof(char *)); + argv[1] = 0; + } + argv0 = (char *) xmalloc(PATH_MAX + 1); + if (!argv || !argv0) + goto abort; + if (readlink("run", argv0, PATH_MAX) < 0) { + if (errno != EINVAL) + goto abort; /* not a symbolic link */ + argv0 = strdup("./run"); + } + argv[0] = strrchr(argv0, '/'); + if (argv[0]) + argv[0]++; + else + argv[0] = argv0; + if (root[service].__stdin != 0) + dup2(root[service].__stdin, 0); + if (root[service].__stdout != 1) { + dup2(root[service].__stdout, 1); + dup2(root[service].__stdout, 2); + } + { + int i; + + for (i = 3; i < 1024; ++i) + close(i); + } + execve(argv0, argv, environ); + _exit(0); + abort: + free(argv0); + free(argv); + _exit(0); + default: + fd = open("sync", O_RDONLY); + if (fd >= 0) { + pid_t p2; + + close(fd); + p2 = waitpid(p, 0, 0); + return 1; + } + return p; + } +} + +/* start a service, return nonzero on error */ +static int startnodep(int service, int pause_flag) +{ + /* step 1: see if the process is already up */ + if (isup(service)) + return 0; + + /* step 2: fork and exec service, put PID in data structure */ + if (chdir(MINITROOT) || chdir(root[service].name)) + return -1; + root[service].startedat = time(0); + root[service].pid = forkandexec(pause_flag, service); + return root[service].pid; +} + +static int startservice(int service, int pause_flag) +{ + int dir = -1; + unsigned long len; + char *s = 0; + pid_t pid; + + if (service < 0) + return 0; + if (root[service].circular) + return 0; + root[service].circular = 1; + if (root[service].logservice >= 0) + startservice(root[service].logservice, pause_flag); + if (chdir(MINITROOT) || chdir(root[service].name)) + return -1; + if ((dir = open(".", O_RDONLY)) >= 0) { + if (!openreadclose("depends", &s, &len)) { + char **deps; + int depc, i; + + deps = split(s, '\n', &depc, 0, 0); + for (i = 0; i < depc; i++) { + int service_index; + + if (deps[i][0] == '#') + continue; + service_index = loadservice(deps[i]); + if (service_index >= 0 && root[service_index].pid != 1) + startservice(service_index, 0); + } + fchdir(dir); + } + pid = startnodep(service, pause_flag); + close(dir); + dir = -1; + return pid; + } + return 0; +} + +static void sulogin(void) +{ + /* exiting on an initialization failure is not a good idea for init */ + char *argv[] = { "sulogin", 0 }; + execve("/sbin/sulogin", argv, environ); + exit(1); +} + +static void handlekilled(pid_t killed) +{ + int i; + + if (killed == (pid_t) - 1) { + write(2, "all services exited.\n", 21); + exit(0); + } + if (killed == 0) + return; + i = findbypid(killed); + if (i >= 0) { + root[i].pid = 0; + if (root[i].respawn) { + circsweep(); + startservice(i, time(0) - root[i].startedat < 1); + } else { + root[i].startedat = time(0); + root[i].pid = 1; + } + } +} + +static void childhandler(void) +{ + int status; + pid_t killed; + + do { + killed = waitpid(-1, &status, WNOHANG); + handlekilled(killed); + } while (killed && killed != (pid_t) - 1); +} + +static volatile int dowinch = 0; +static volatile int doint = 0; + +static void sigchild(int whatever) +{ +} +static void sigwinch(int sig) +{ + dowinch = 1; +} +static void sigint(int sig) +{ + doint = 1; +} + +extern int minit_main(int argc, char *argv[]) +{ + /* Schritt 1: argv[1] als Service nehmen und starten */ + struct pollfd pfd; + time_t last = time(0); + int nfds = 1; + int count = 0; + int i; + + infd = open("/etc/minit/in", O_RDWR); + outfd = open("/etc/minit/out", O_RDWR | O_NONBLOCK); + if (getpid() == 1) { + int fd; + + i_am_init = 1; + reboot(0); + if ((fd = open("/dev/console", O_RDWR | O_NOCTTY))) { + ioctl(fd, KDSIGACCEPT, SIGWINCH); + close(fd); + } else + ioctl(0, KDSIGACCEPT, SIGWINCH); + } +/* signal(SIGPWR,sighandler); don't know what to do about it */ +/* signal(SIGHUP,sighandler); ??? */ + { + struct sigaction sa; + + sigemptyset(&sa.sa_mask); + sa.sa_sigaction = 0; + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + sa.sa_handler = sigchild; + sigaction(SIGCHLD, &sa, 0); + sa.sa_handler = sigint; + sigaction(SIGINT, &sa, 0); /* ctrl-alt-del */ + sa.sa_handler = sigwinch; + sigaction(SIGWINCH, &sa, 0); /* keyboard request */ + } + if (infd < 0 || outfd < 0) { + puts("minit: could not open /etc/minit/in or /etc/minit/out\n"); + sulogin(); + nfds = 0; + } else + pfd.fd = infd; + pfd.events = POLLIN; + + for (i = 1; i < argc; i++) { + circsweep(); + if (startservice(loadservice(argv[i]), 0)) + count++; + } + circsweep(); + if (!count) + startservice(loadservice("default"), 0); + for (;;) { + char buf[1501]; + time_t now; + + if (doint) { + doint = 0; + startservice(loadservice("ctrlaltdel"), 0); + } + if (dowinch) { + dowinch = 0; + startservice(loadservice("kbreq"), 0); + } + childhandler(); + now = time(0); + if (now < last || now - last > 30) { + /* The system clock was reset. Compensate. */ + long diff = last - now; + int j; + + for (j = 0; j <= maxprocess; ++j) { + root[j].startedat -= diff; + } + } + last = now; + switch (poll(&pfd, nfds, 5000)) { + case -1: + if (errno == EINTR) { + childhandler(); + break; + } + opendevconsole(); + puts("poll failed!\n"); + sulogin(); + /* what should we do if poll fails?! */ + break; + case 1: + i = read(infd, buf, 1500); + if (i > 1) { + pid_t pid; + int idx = 0; + int tmp; + + buf[i] = 0; + + if (buf[0] != 's' && ((idx = findservice(buf + 1)) < 0)) + error: + write(outfd, "0", 1); + else { + switch (buf[0]) { + case 'p': + write(outfd, buf, fmt_ulong(buf, root[idx].pid)); + break; + case 'r': + root[idx].respawn = 0; + goto ok; + case 'R': + root[idx].respawn = 1; + goto ok; + case 'C': + if (kill(root[idx].pid, 0)) { /* check if still active */ + handlekilled(root[idx].pid); /* no!?! remove form active list */ + goto error; + } + goto ok; + break; + case 'P': + { + unsigned char *x = buf + strlen(buf) + 1; + unsigned char c; + + tmp = 0; + while ((c = *x++ - '0') < 10) + tmp = tmp * 10 + c; + } + if (tmp > 0) { + if (kill(tmp, 0)) + goto error; + pid = tmp; + } + root[idx].pid = tmp; + goto ok; + case 's': + idx = loadservice(buf + 1); + if (idx < 0) + goto error; + if (root[idx].pid < 2) { + root[idx].pid = 0; + circsweep(); + idx = startservice(idx, 0); + if (idx == 0) { + write(outfd, "0", 1); + break; + } + } + ok: + write(outfd, "1", 1); + break; + case 'u': + write(outfd, buf, + fmt_ulong(buf, time(0) - root[idx].startedat)); + } + } + } + break; + default: + break; + } + } +} |