init: massive rewrite to support SYS5 inittab
authorAlan Cox <alan@linux.intel.com>
Mon, 25 May 2015 22:45:57 +0000 (23:45 +0100)
committerAlan Cox <alan@linux.intel.com>
Mon, 25 May 2015 22:45:57 +0000 (23:45 +0100)
Need to add telinit support to it yet.

May also make sense to extend the built in getty to understand some
basic modem setting and perhaps integrate cron as an internal.

You will now need an inittab, somethng like

id:3:initdefault:
si::sysinit:/bin/sh
is:s:respawn:/bin/sh
rc::bootwait:/etc/rc
01:3:respawn:getty /dev/tty1
02:3:respawn:getty /dev/tty2
03:3:off:getty /dev/tty3

Other things to look at that fall from this are init arguments on the boot
line

Applications/util/init.c

index 8c51567..677f7de 100644 (file)
 #include <unistd.h>
 #include <fcntl.h>
 #include <pwd.h>
+#include <utmp.h>
+#include <errno.h>
+#include <paths.h>
+#include <sys/wait.h>
+
+#define        INIT_OFF        0
+#define INIT_SYS       1
+#define INIT_BOOT      2
+#define INIT_ONCE      3
+#define INIT_RESPAWN   4
+#define INIT_DEFAULT   5
+#define INIT_OPMASK    0x0F
+#define INIT_WAIT      0x80
+#define MASK_BOOT      0x80
+
+#define MAX_ARGS       16
 
-char *argp[] = { "sh", NULL };
 
 #define crlf   write(1, "\n", 1)
 
-int  login(char *);
-void spawn(struct passwd *);
-int  showfile(char *);
-void putstr(char *);
-void sigalarm(int);
+static void spawn_login(struct passwd *, const char *, const char *);
+static pid_t getty(const char *, const char *);
+
+static struct utmp ut;
+
+/* Next line to parse */
+static uint8_t *snext;
+/* Current parse position */
+static uint8_t *sdata;
+/* End of input */
+static uint8_t *sdata_end;
+/* Current output ptr */
+static uint8_t *idata;
+/* Pointer to record start */
+static uint8_t *ibackup;
+/* PID table */
+static uint16_t *initpid;
+/* Pointers into idata */
+static uint8_t **initptr;
+/* Base of processed data */
+static uint8_t *inittab;
+/* Number of entries */
+static int initcount;
+
+int default_rl;
+int runlevel;
 
-int main(int argc, char *argv[])
+void sigalarm(int sig)
 {
-    int fdtty1, sh_pid, pid;
+       return;
+}
 
-    signal(SIGINT, SIG_IGN);
+int showfile(char *fname)
+{
+       int fd, len;
+       char buf[80];
+
+       fd = open(fname, O_RDONLY);
+       if (fd > 0) {
+               do {
+                       len = read(fd, buf, 80);
+                       write(1, buf, len);
+               } while (len > 0);
+               close(fd);
+               return 1;
+       }
+       return 0;
+}
 
-    /* remove any stale /etc/mtab file */
+void putstr(char *str)
+{
+       write(1, str, strlen(str));
+}
 
-    unlink("/etc/mtab");
+static uint8_t *stackmem(uint8_t * a, unsigned int size)
+{
+       uint8_t *tp = sbrk(size);
+       if (tp == (uint8_t *) - 1) {
+               write(2, "init: out of memory.\n", 21);
+               _exit(1);
+       }
+       memcpy(tp, a, size);
+       return tp;
+}
 
-    /* loop until we can open the first terminal */
+static pid_t spawn_process(uint8_t * p, uint8_t wait)
+{
+       static const char *args[MAX_ARGS + 3];
+       uint8_t *dp = p + 5;
+       uint8_t *ep = p + *p - 1; /* -1 as there is a final \0 */
+       pid_t pid;
+       int an = 3;
+
+       args[0] = "/bin/sh";
+       args[1] = "-c";
+       args[2] = dp;
+       /* Set pointers to each string */
+       while (dp < ep) {
+               if (*dp++ == 0 && an < MAX_ARGS)
+                       args[an++] = dp;
+       }
+       args[an] = NULL;
+
+       /* Check for internal processes */
+       if (strcmp(args[2], "getty") == 0)
+               pid = getty(args[3], p + 1);
+       else {
+               /* External */
+               pid = fork();
+               if (pid == -1) {
+                       perror("fork");
+                       return 0;
+               }
+               if (pid == 0) {
+                       /* Child */
+                       ut.ut_type = INIT_PROCESS;
+                       ut.ut_pid = getpid();
+                       ut.ut_id[0] = p[1];
+                       ut.ut_id[1] = p[2];
+                       pututline(&ut);
+                       /* Don't leak utmp into the child */
+                       endutent();
+                       /* Run the child */
+                       execv(args[2], args + 2);
+                       /* If it didn't look binary run it via the shell */
+                       if (errno == ENOEXEC)
+                               execv("/bin/sh", args);
+                       /* Oh bugger */
+                       perror(args[2]);
+                       exit(1);
+               }
+       }
+       /* Let it complete if that is the instruction */
+       if (wait) {
+               while (waitpid(pid, NULL, 0) != pid);
+               return 0;
+       }
+       else
+               return pid;
+}
 
-    do {
-        fdtty1 = open("/dev/tty1", O_RDWR);
-    } while (fdtty1 < 0);
+/*
+ *     Clear any dead processes
+ */
+static void clear_zombies(int flags)
+{
+       int i;
+       pid_t pid = waitpid(-1, NULL, flags);
+       /* Interrupted ? */
+       if (pid < 0)
+               return;
+       /* See if we care what died. If we do then also check that
+        * do not need to respawn it
+        */
+       for (i = 0; i < initcount; i++) {
+               if (initpid[i] == pid) {
+                       uint8_t *p = initptr[i];
+                       /* Clear the utmp entry */
+                       ut.ut_pid = 1;
+                       ut.ut_type = INIT_PROCESS;
+                       *ut.ut_line = 0;
+                       ut.ut_id[0] = p[1];
+                       ut.ut_id[1] = p[2];
+                       *ut.ut_user = 0;
+                       pututline(&ut);
+                       /* Mark as done */
+                       initpid[i] = 0;
+                       /* Respawn the task if appropriate */
+                       if ((p[3] & (1 << runlevel)) && p[4] == INIT_RESPAWN)
+                               initpid[i] = spawn_process(p, 0);
+                       break;
+               }
+       }
+}
 
-    /* make stdin, stdout and stderr point to /dev/tty1 */
+/*
+ *     The init line we are processing turns out to be broken. Move on a line
+ *     and rewind the idata pointer to undo the parse of the record
+ */
+static void bad_line(void)
+{
+       putstr("inittab error: ");
+       snext = strchr(sdata, '\n');
+       if (snext)
+               *snext++ = 0;
+       putstr(sdata);
+       sdata = snext;
+       idata = ibackup;
+}
 
-    if (fdtty1 != 0) close(0);
-    dup(fdtty1);
-    close(1);
-    dup(fdtty1);
-    close(2);
-    dup(fdtty1);
+/*
+ *     Turn the character codes 0123456sS into run level numbers
+ */
+static uint8_t to_runlevel(uint8_t c)
+{
+       if (c == 's' || c == 'S')
+               return 7;               /* 1 << 7 is used for boot/single */
+       if (c >=  '0 ' && c <= '6')
+               return c - '0';
+       return -1;
+}
 
-    putstr("init version 0.8.1ac\n");
-    
-    /* then call the login procedure on it */
+/*
+ *     Squash an init line in situ from
+ *
+ *     id:runlevels:type:args
+ *     into
+ *
+ *     uint8 length
+ *     char id[2]
+ *     uint8 runlevel bit mask
+ *     uint8 optype
+ *     arguments with \0 separation
+ */
+static void parse_initline(void)
+{
+       uint8_t bit = 0;
+       uint8_t *linelen;
+
+       if (*sdata == '#') {
+               sdata = strchr(sdata, '\n');
+               if (sdata)
+                       sdata++;
+               return;
+       }
+       /* We start with a line length then the id: bits. Don't write
+        * the length yet - we may still be using that byte for iput */
+       linelen = idata++;
+       *idata++ = *sdata++;
+       *idata++ = *sdata++;    /* Copy the init string */
+       if (*sdata++ != ':') {
+               bad_line();
+               return;
+       }
+       while (*sdata != ':') {
+               if (*sdata == '\n' || sdata > sdata_end) {
+                       bad_line();
+                       return;
+               }
+               bit = to_runlevel(*sdata++);
+               if (bit == -1) {
+                       bad_line();
+                       return;
+               }
+               /* Add the run level to the bitmask */
+               *idata |= 1 << bit;
+       }
+
+       idata++;
+       sdata++;
+       if (memcmp(sdata, "respawn:", 8) == 0) {
+               *idata++ = INIT_RESPAWN;
+               sdata += 8;
+       } else if (memcmp(sdata, "wait:", 5) == 0) {
+               *idata++ = INIT_ONCE | INIT_WAIT;
+               sdata += 5;
+       } else if (memcmp(sdata, "once:", 5) == 0) {
+               *idata++ = INIT_ONCE;
+               sdata += 5;
+       } else if (memcmp(sdata, "boot:", 5) == 0) {
+               idata[-1] = MASK_BOOT;
+               *idata++ = INIT_BOOT;
+               sdata += 5;
+       } else if (memcmp(sdata, "bootwait:", 9) == 0) {
+               idata[-1] = MASK_BOOT;
+               *idata++ = INIT_BOOT | INIT_WAIT;
+               sdata += 9;
+       } else if (memcmp(sdata, "off:", 4) == 0) {
+               *idata++ = INIT_OFF;
+               sdata += 4;
+       } else if (memcmp(sdata, "initdefault:", 12) == 0) {
+               *idata++ = INIT_DEFAULT;
+               default_rl = bit;
+               sdata += 12;
+       } else if (memcmp(sdata, "sysinit:", 8) == 0) {
+               idata[-1] = MASK_BOOT;
+               *idata++ = INIT_SYS | INIT_WAIT;
+               sdata += 8;
+       } else {
+               /* We don't yet spport power* methods */
+               bad_line();
+               return;
+       }
+       while (*sdata && *sdata != '\n' && sdata < sdata_end) {
+               if (*sdata != ' ')
+                       *idata++ = *sdata;
+               else
+                       *idata++ = 0;
+               sdata++;
+       }
+       /* Terminate the final argument */
+       *idata++ = 0;
+       *linelen = idata - linelen;
+       sdata++;
+       initcount++;
+}
 
-    for (;;) {
+/*
+ *     Parse the init table, then set up the pointers after the processed
+ *     data, and adjust the brk() value to allow for the tables
+ */
+static void parse_inittab(void)
+{
+       idata = inittab = sdata;
+       while (sdata < sdata_end)
+               parse_initline();
+       /* Allocate space for the control arrays */
+       initpid = (uint16_t *) idata;
+       idata += 2 * initcount;
+       initptr = (uint8_t **) idata;
+       idata += sizeof(void *) * initcount;
+       if (brk(idata) == -1)
+               putstr("unable to return space\n");
+       memset(initpid, 0, 2 * initcount);
+       memset(initptr, 0, sizeof(uint8_t *) * initcount);
+}
 
-        sh_pid = login("/dev/tty1");
+/*
+ *     Load the inittab into brk space. If it doesn't work then throw a
+ *     wobbly and run /bin/sh
+ */
 
-        /* wait until the user exits the shell */
+static void load_inittab(void)
+{
+       int fd = open("/etc/inittab", O_RDONLY);
+       static struct stat st;
+       if (fd == -1 || fstat(fd, &st) == -1 || !S_ISREG(st.st_mode) || !st.st_size) {
+               write(2, "init: no inittab\n", 17);
+               goto fail;
+       }
+       sdata = sbrk(st.st_size + 1);
+       if (sdata == (uint8_t *) - 1) {
+               write(2, "init: out of memory\n", 20);
+               goto fail;
+       }
+       if (read(fd, sdata, st.st_size) != st.st_size) {
+               write(2, "init: read error\n", 17);
+               goto fail;
+       }
+       close(fd);
+       sdata_end = sdata + st.st_size;
+       return;
+      fail:
+       close(fd);
+       execl("/bin/sh", "-sh", NULL);
+       execl("/bin/ssh", "-ssh", NULL);
+       perror("sh");
+       _exit(1);
+}
 
-        do {
-            pid = wait(NULL);
-        } while (sh_pid != pid);
+/*
+ *     We are exiting a runlevel. Prune anybody who is running and should
+ *     no longer be present. We don't do the cleanup here. We do that
+ *     in clear_zombies() or after we return to the main loop.
+ */
+static int cleanup_runlevel(uint8_t oldmask, uint8_t newmask, int sig)
+{
+       uint8_t *p = inittab;
+       int n = 0;
+       int nrun = 0;
+
+       while (n < initcount) {
+               /* Dying ? */
+               if ((p[3] & oldmask) && !(p[3] && newmask)) {
+                       /* Count number still to die */
+                       if (p[4] == INIT_RESPAWN && initpid[n]) {
+                               /* Group kill */
+                               if (kill(-initpid[n], sig) == 0)
+                                       nrun++;
+                       }
+               }
+               /* Next entry */
+               p += *p;
+               n++;
+       }
+
+       return nrun;
+}
 
-        /* then loop to call login again */
-        
-        crlf;
-    }
+/*
+ *     Clear up the tasks that should not be running. Start with a HUP then
+ *     probe them allowing up to 45 seconds, after which we send SIGKILL
+ */
+static void exit_runlevel(uint8_t oldmask, uint8_t newmask)
+{
+       uint8_t n = 0;
+       if (cleanup_runlevel(oldmask, newmask, SIGHUP)) {
+               while (n++ < 9) {
+                       sleep(5);
+                       clear_zombies(WNOHANG);
+                       if (cleanup_runlevel(oldmask, newmask, 0) == 0)
+                               return;
+               }
+               cleanup_runlevel(oldmask, newmask, SIGKILL);
+       }
 }
 
-static char *env[10];
-static int envn;
+/*
+ *     Start everything that should be runnign at this run level. Take
+ *     care not to re-start stuff that survives the transition
+ */
+static void do_for_runlevel(uint8_t newmask, int op)
+{
+       uint8_t *p = inittab;
+       int n = 0;
+       while (n < initcount) {
+               initptr[n] = p;
+               if (!(p[3] & newmask))
+                       goto next;
+               if ((p[4] & INIT_OPMASK) == op) {
+                       /* Already running ? */
+                       if (op == INIT_RESPAWN && initpid[n])
+                               goto next;
+                       /* Spawn and maybe wait for a process */
+                       initpid[n] = spawn_process(p, (p[3] & INIT_WAIT));
+               }
+next:          p += *p;
+               n++;
+       }
+}
 
-static void envset(char *a, char *b)
+/*
+ *     Launch the one off processes for this run level, then begin
+ *     the normal respawn processing.
+ */
+static void enter_runlevel(uint8_t newmask)
+{
+       do_for_runlevel(newmask, INIT_ONCE);
+       do_for_runlevel(newmask, INIT_RESPAWN);
+}
+
+/*
+ *     Run through the boot processing from inittab
+ */
+static void boot_runlevel(void)
 {
-    int al = strlen(a);
-    static char hptr[5];
-    char *tp = sbrk(al + strlen(b) + 2);
-    if (tp == (char *)-1) {
-        putstr("out of memory.\n");
-        return;
-    }
-    strcpy(tp, a);
-    tp[al]='=';
-    strcpy(tp + al + 1, b);
-    env[envn++] = tp;
+       do_for_runlevel(MASK_BOOT, INIT_SYS);
+       do_for_runlevel(MASK_BOOT, INIT_BOOT);
+       runlevel = default_rl;
+       enter_runlevel(1 << default_rl);
 }
 
-int login(char *ttyname)
+
+int main(int argc, char *argv[])
 {
-    int  fdtty, pid;
-    struct passwd *pwd;
-    char *p, buf[50], salt[3];
+       int fdtty1;
 
-    for (;;) {
-        pid = fork();
-        if (pid == -1) {
-            putstr("init: can't fork\n");
-        } else {
-            if (pid != 0)
-                /* parent's context: return pid of the child process */
-                return pid;
+       signal(SIGINT, SIG_IGN);
+//     signal(SIGHUP, telinit);
 
-            close(0);
-            close(1);
-            close(2);
-            setpgrp();
+       /* remove any stale /etc/mtab file */
 
-            fdtty = open(ttyname, O_RDWR);
-            if (fdtty < 0)
-                return -1;
+       unlink("/etc/mtab");
 
-            /* here we are inside child's context of execution */
-            envset("PATH", "/bin:/usr/bin");     
-            envset("CTTY", ttyname);
+       /* loop until we can open the first terminal */
 
-            /* make stdin, stdout and stderr point to fdtty */
+       do {
+               fdtty1 = open("/dev/tty1", O_RDWR);
+       } while (fdtty1 < 0);
 
-            dup(fdtty);
-            dup(fdtty);
+       /* make stdin, stdout and stderr point to /dev/tty1 */
 
-            /* display the /etc/issue file, if exists */
-            showfile("/etc/issue");
+       if (fdtty1 != 0)
+               close(0);
+       dup(fdtty1);
+       close(1);
+       dup(fdtty1);
+       close(2);
+       dup(fdtty1);
 
-            /* loop until a valid user name is entered
-             * and a shell is spawned */
+       putstr("init version 0.9.0ac#1\n");
 
-            for (;;) {
-                putstr("login: ");
-                while (read(0, buf, 20) < 0);    /* EINTR might happens because of the alarm() call below */
+       close(open("/var/run/utmp", O_WRONLY | O_CREAT | O_TRUNC));
 
-                if ((p = strchr(buf, '\n')) != NULL)
-                    *p = '\0';                    /* strip newline */
+       load_inittab();
+       parse_inittab();
 
-                pwd = getpwnam(buf);
-                
-                if (pwd) {
-                    if (pwd->pw_passwd[0] != '\0') {
-                        p = getpass("Password: ");
-                        salt[0] = pwd->pw_passwd[0];
-                        salt[1] = pwd->pw_passwd[1];
-                        salt[2] = '\0';
-                        p = crypt(p, salt);
-                    } else {
-                        p = "";
-                    }
-                    if (strcmp(p, pwd->pw_passwd) == 0) spawn(pwd);
-                }
+       boot_runlevel();
+
+       for (;;) {
+               clear_zombies(0);
+               /* FIXME: telinit handling, HUP handling */
+       }
+}
+
+/*
+ *     Child process helper logic. Use sbrk space to build the environment
+ */
+static char *env[10];
+static int envn;
 
-                putstr("Login incorrect\n\n");
-                signal(SIGALRM, sigalarm);
-                alarm(2);
-                pause();
-            }
-        }
-    }
+static void envset(char *a, char *b)
+{
+       int al = strlen(a);
+       static char hptr[5];
+       char *tp = sbrk(al + strlen(b) + 2);
+       if (tp == (char *) -1) {
+               putstr("out of memory.\n");
+               return;
+       }
+       strcpy(tp, a);
+       tp[al] = '=';
+       strcpy(tp + al + 1, b);
+       env[envn++] = tp;
 }
 
-void spawn(struct passwd *pwd)
+/*
+ *     Internal implementation of "getty" and "login"
+ */
+static pid_t getty(char *ttyname, char *id)
 {
-    char *p, buf[50];
+       int fdtty, pid;
+       struct passwd *pwd;
+       const char *pr;
+       char *p, buf[50], salt[3];
+       char hn[64];
+       gethostname(hn, sizeof(hn));
+
+       for (;;) {
+               pid = fork();
+               if (pid == -1) {
+                       putstr("init: can't fork\n");
+               } else {
+                       if (pid != 0)
+                               /* parent's context: return pid of the child process */
+                               return pid;
+
+                       close(0);
+                       close(1);
+                       close(2);
+                       setpgrp();
+
+                       fdtty = open(ttyname, O_RDWR);
+                       if (fdtty < 0)
+                               return -1;
+
+                       /* here we are inside child's context of execution */
+                       envset("PATH", "/bin:/usr/bin");
+                       envset("CTTY", ttyname);
+
+                       /* make stdin, stdout and stderr point to fdtty */
+
+                       dup(fdtty);
+                       dup(fdtty);
+
+                       ut.ut_type = INIT_PROCESS;
+                       ut.ut_pid = getpid();
+                       ut.ut_id[0] = id[0];
+                       ut.ut_id[1] = id[1];
+                       pututline(&ut);
+
+                       /* display the /etc/issue file, if exists */
+                       showfile("/etc/issue");
+                       if (*hn) {
+                               putstr(hn);
+                               putstr(" ");
+                       }
+                       /* loop until a valid user name is entered
+                        * and a shell is spawned */
+
+                       for (;;) {
+                               putstr("login: ");
+                               while (read(0, buf, 20) < 0);   /* EINTR might happens because of the alarm() call below */
+
+                               if ((p = strchr(buf, '\n')) != NULL)
+                                       *p = '\0';      /* strip newline */
+
+                               pwd = getpwnam(buf);
+
+                               if (pwd) {
+                                       if (pwd->pw_passwd[0] != '\0') {
+                                               p = getpass("Password: ");
+                                               salt[0] = pwd->pw_passwd[0];
+                                               salt[1] = pwd->pw_passwd[1];
+                                               salt[2] = '\0';
+                                               pr = crypt(p, salt);
+                                       } else {
+                                               pr = "";
+                                       }
+                                       if (strcmp(pr, pwd->pw_passwd) == 0)
+                                               spawn_login(pwd, ttyname, id);
+                               }
+
+                               putstr("\nLogin incorrect\n\n");
+                               signal(SIGALRM, sigalarm);
+                               alarm(2);
+                               pause();
+                       }
+               }
+       }
+}
 
-    setgid(pwd->pw_gid);
-    setuid(pwd->pw_uid);
-    signal(SIGINT, SIG_DFL);
 
-    /* setup user environment variables */
+static char *argp[] = { "sh", NULL };
 
-    envset("LOGNAME", pwd->pw_name);
-    envset("HOME", pwd->pw_dir);
-    envset("SHELL", pwd->pw_shell);
+static void spawn_login(struct passwd *pwd, const char *tty, const char *id)
+{
+       char *p, buf[50];
 
-    /*chdir(pwd->pw_dir);*/
+       /* utmp */
+       ut.ut_type = USER_PROCESS;
+       ut.ut_pid = getpid();
+       strncpy(ut.ut_line, tty, UT_LINESIZE);
+       strncpy(ut.ut_id, id, 2);
+       time(&ut.ut_time);
+       strncpy(ut.ut_user, pwd->pw_name, UT_NAMESIZE);
+       pututline(&ut);
+       /* Don't leak utmp into the child */
+       endutent();
 
-    /* show the motd file */
+       setgid(pwd->pw_gid);
+       setuid(pwd->pw_uid);
+       signal(SIGINT, SIG_DFL);
 
-    if (!showfile("/etc/motd")) crlf;
+       /* setup user environment variables */
 
-    /* and spawn the shell */
+       envset("LOGNAME", pwd->pw_name);
+       envset("HOME", pwd->pw_dir);
+       envset("SHELL", pwd->pw_shell);
 
-    strcpy(buf, "-");
-    if ((p = strrchr(pwd->pw_shell, '/')) != NULL)
-        strcat(buf, ++p);
-    else
-        strcat(buf, pwd->pw_shell);
+       /* home directory */
 
-    argp[0] = buf;
-    argp[1] = NULL;
+       if (chdir(pwd->pw_dir))
+               putstr("login: unable to change to home directory, using /\n");
 
-    execve(pwd->pw_shell, (void *)argp, (void *)env);
-    putstr("login: can't execute shell\n");
-    exit(1);
-}
+       /* show the motd file */
 
-void sigalarm(int sig)
-{
-    return;
-}
+       if (!showfile("/etc/motd"))
+               crlf;
 
-int showfile(char *fname)
-{
-    int  fd, len;
-    char buf[80];
-    
-    fd = open(fname, O_RDONLY);
-    if (fd > 0) {
-        do {
-            len = read(fd, buf, 80);
-            write(1, buf, len);
-        } while (len > 0);
-        close(fd);
-        return 1;
-    }
-    return 0;
-}
+       /* and spawn the shell */
 
-void putstr(char *str)
-{
-    write(1, str, strlen(str));
+       strcpy(buf, "-");
+       if ((p = strrchr(pwd->pw_shell, '/')) != NULL)
+               strcat(buf, ++p);
+       else
+               strcat(buf, pwd->pw_shell);
+
+       argp[0] = buf;
+       argp[1] = NULL;
+
+       execve(pwd->pw_shell, (void *) argp, (void *) env);
+       putstr("login: can't execute shell\n");
+       exit(1);
 }