summaryrefslogtreecommitdiff
path: root/init/minit.c
diff options
context:
space:
mode:
authorGlenn L McGrath2003-05-11 14:52:39 +0000
committerGlenn L McGrath2003-05-11 14:52:39 +0000
commit1e11c34be4decfef8fbda8a8e01cd60def8232e5 (patch)
tree6e93956fef2bcd4c1db18031dc081dcaf689f2d1 /init/minit.c
parent8c6887c855460ee9e688e2a51e29f99faa2a2d8c (diff)
downloadbusybox-1e11c34be4decfef8fbda8a8e01cd60def8232e5.zip
busybox-1e11c34be4decfef8fbda8a8e01cd60def8232e5.tar.gz
minit, a Minimal init system.
Diffstat (limited to 'init/minit.c')
-rw-r--r--init/minit.c612
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;
+ }
+ }
+}