/* This file is part of GNU Pies. Copyright (C) 2013 Sergey Poznyakoff GNU Pies 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 3, or (at your option) any later version. GNU Pies 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 GNU Pies. If not, see . */ #include "pies.h" #include "prog.h" #include enum boot_state { sysinit, boot, single, normal, }; static char *boot_state_name[] = { [sysinit] = "sysinit", [boot] = "boot", [single] = "single", [normal] = "normal" }; #define max_boot_state sizeof(boot_state_name) / sizeof (boot_state_name[0]) static char boot_state_str[] = "#*s "; static const char valid_runlevels[] = "0123456789S"; static int boot_trans_tab[max_boot_state][sizeof(valid_runlevels)-1] = { /* 0, 1, 2, 3, 4, */ /* 5, 6, 7, 8, 9, S */ /* sysinit */ { boot, boot, boot, boot, boot, boot, boot, boot, boot, boot, single }, /* boot */ { normal, normal, normal, normal, normal, normal, normal, normal, normal, normal, normal }, /* single */ { boot, boot, boot, boot, boot, boot, boot, boot, boot, boot, single }, /* normal */ { normal, normal, normal, normal, normal, normal, normal, normal, normal, normal, normal }, }; enum boot_state boot_state; int runlevel = 0; int prevlevel = 'N'; int initdefault; /* Default runlevel */ int dfl_level; int console_open (int mode) { int i, fd; for (i = 0; i < 5; i++) { fd = open (console_device, mode | O_NONBLOCK); if (fd >= 0) { fcntl (fd, F_SETFL, mode); return fd; } } return -1; } void console_stty () { struct termios tty; int fd; if ((fd = console_open (O_RDWR|O_NOCTTY)) < 0) { logmsg (LOG_CRIT, "can't open %s", console_device); return; } tcgetattr (fd, &tty); tty.c_cflag &= CBAUD|CBAUDEX|CSIZE|CSTOPB|PARENB|PARODD; tty.c_cflag |= HUPCL|CLOCAL|CREAD; tty.c_cc[VINTR] = 3; /* ctrl('c') */ tty.c_cc[VQUIT] = 28; /* ctrl('\\') */ tty.c_cc[VERASE] = 127; tty.c_cc[VKILL] = 24; /* ctrl('x') */ tty.c_cc[VEOF] = 4; /* ctrl('d') */ tty.c_cc[VTIME] = 0; tty.c_cc[VMIN] = 1; tty.c_cc[VSTART] = 17; /* ctrl('q') */ tty.c_cc[VSTOP] = 19; /* ctrl('s') */ tty.c_cc[VSUSP] = 26; /* ctrl('z') */ /* * Set pre and post processing */ tty.c_iflag = IGNPAR|ICRNL|IXON|IXANY; tty.c_oflag = OPOST|ONLCR; tty.c_lflag = ISIG|ICANON|ECHO|ECHOCTL|ECHOPRT|ECHOKE; /* * Now set the terminal line. * We don't care about non-transmitted output data * and non-read input data. */ tcsetattr (fd, TCSANOW, &tty); tcflush(fd, TCIOFLUSH); close (fd); } int askrunlevel () { int fd; char c, lvl = -1; enum { srl_init, srl_char, srl_r, srl_n, srl_skip } srl = srl_init; static const char prompt[] = "\nEnter runlevel: "; console_stty (); fd = console_open (O_RDWR|O_NOCTTY); if (fd == -1) return 'S'; while (1) { if (srl == srl_init) { write (fd, prompt, sizeof (prompt) - 1); if (read (fd, &lvl, 1) != 1) return 'S'; srl = srl_char; } else if (srl == srl_n) { if (is_valid_runlevel (lvl)) break; srl = srl_init; } else { if (read (fd, &c, 1) != 1) { if (!is_valid_runlevel (lvl)) lvl = 'S'; break; } switch (srl) { case srl_char: if (c == '\r') srl = srl_r; else if (c == '\n') srl = srl_n; else srl = srl_skip; break; case srl_r: if (c == '\n') srl = srl_n; else srl = srl_skip; break; case srl_skip: if (c == '\n') srl = srl_init; default: break; } } } close (fd); return toupper (lvl); } static int getinitdefault () { return initdefault ? initdefault : askrunlevel (); } static int runlevel_index (int n) { char *p; if (n == 0) return -1; p = strchr (valid_runlevels, n); if (!p) return -1; return p - valid_runlevels; } struct enstate { int mask; int wait; }; static int enablecomp (struct prog *prog, void *data) { struct enstate *s = data; int rc; struct component *comp = prog->v.p.comp; switch (boot_state) { case sysinit: return comp->mode == pies_comp_sysinit; case boot: return comp->mode == pies_comp_boot || comp->mode == pies_comp_bootwait; case single: case normal: switch (comp->mode) { case pies_comp_sysinit: case pies_comp_boot: case pies_comp_bootwait: return 0; case pies_comp_powerfail: case pies_comp_powerwait: case pies_comp_powerokwait: case pies_comp_ctrlaltdel: case pies_comp_ondemand: case pies_comp_powerfailnow: case pies_comp_kbrequest: return s && (s->mask & PIES_COMP_MASK (comp->mode)); default: break; } } rc = !!strchr (comp->runlevels, runlevel); if (!rc) return rc; if (prog->v.p.status == status_finished) return -1; if (s && s->mask & PIES_COMP_WAIT) { if (comp->mode == pies_comp_wait) { s->wait = 1; return 1; } return 0; } return rc; } static int runlevel_setup_prog (struct prog *prog, void *data) { if (IS_COMPONENT (prog) && is_sysvinit (prog->v.p.comp)) { int rc = enablecomp (prog, data); if (rc < 0) return 0; if (rc) prog->v.p.status = status_enabled; else prog->v.p.status = status_disabled; debug (1, ("%s: %s", prog->tag, prog->v.p.status == status_enabled ? "enabled" : "disabled")); } return 0; } void sysvinit_runlevel_setup (int mask, int *wait) { struct enstate s; s.mask = mask; if (wait) { s.mask |= PIES_COMP_WAIT; s.wait = *wait; } progman_foreach (runlevel_setup_prog, &s); if (wait) *wait = s.wait; } static const char valid_runlevel_arg[] = "0123456789SsQqAaBbCcUu"; int is_valid_runlevel (int c) { return !!strchr (valid_runlevel_arg, c); } #define ENVAR_CONSOLE "CONSOLE=" #define ENVTMPL_CONSOLE "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" static char env_prevlevel[] = "PREVLEVEL=x"; static char env_runlevel[] = "RUNLEVEL=x"; static char env_console[] = ENVAR_CONSOLE ENVTMPL_CONSOLE; #define NR_ENVHINT 32 char *sysvinit_environ_hint[NR_ENVHINT] = { env_prevlevel, env_runlevel, env_console, "INIT_VERSION=" PACKAGE_STRING, "PATH=/bin:/usr/bin:/sbin:/usr/sbin", NULL }; #define ENVI_PREVLEVEL 0 #define ENVI_RUNLEVEL 1 #define ENVI_CONSOLE 2 #define ENVI_VERSION 3 #define ENVI_PATH 4 #define ENVI_AVAIL 5 static void envsetup () { int i; for (i = 0; i < ENVI_AVAIL; i++) { char *str = sysvinit_environ_hint[i]; switch (i) { case ENVI_PREVLEVEL: str[strlen (str) - 1] = prevlevel; break; case ENVI_RUNLEVEL: str[strlen (str) - 1] = boot_state_str[boot_state] == ' ' ? (runlevel ? runlevel : '#') : boot_state_str[boot_state]; break; case ENVI_CONSOLE: if (strlen (console_device) >= sizeof (ENVTMPL_CONSOLE)) logmsg (LOG_ERR, "console device name too long"); else strcpy (str + sizeof (ENVAR_CONSOLE) - 1, console_device); } } } static void sysvinit_setenv (char const *data, int size) { int i, j; while (size) { char const *var = data; size_t len = strlen (var) + 1; size -= len; if (size < 0) break; data += len; if (strncmp (var, "INIT_", 5) != 0) continue; len = strcspn (var, "="); for (i = ENVI_AVAIL; i < NR_ENVHINT; i++) { char *s = sysvinit_environ_hint[i]; if (s) { for (j = 0; *s && j < len; j++, s++) if (var[j] != *s) break; if (*s != '=' || j != len) continue; free (sysvinit_environ_hint[i]); } if (var[len] == '=') sysvinit_environ_hint[i] = grecs_strdup (var); else for (j = i + 1; j < NR_ENVHINT; j++, i++) sysvinit_environ_hint[i] = sysvinit_environ_hint[j]; break; } } } char *init_fifo = INIT_FIFO; static void create_fifo (void); static int sysvinit_fifo_handler (int fd, void *data) { static size_t size; union { struct sysvinit_request req; char data[sizeof (struct sysvinit_request)]; } buf; int rc; rc = read (fd, buf.data + size, sizeof (struct sysvinit_request) - size); if (rc == -1) { logmsg (LOG_ERR, _("error reading from %s: %s"), init_fifo, strerror (errno)); size = 0; return 0; } if (rc == 0) { logmsg (LOG_ERR, _("end of file on %s: reopening"), init_fifo); size = 0; close (fd); deregister_socket (fd); create_fifo (); return 0; } size += rc; if (size == sizeof (struct sysvinit_request)) { if (buf.req.magic != INIT_MAGIC) logmsg (LOG_ERR, _("got invalid initreq")); else { debug (1, ("INITREQ: cmd=%d, runlevel=%d, sleeptime=%d", buf.req.cmd, buf.req.runlevel, buf.req.sleeptime)); switch (buf.req.cmd) { case INIT_CMD_RUNLVL: buf.req.runlevel = toupper (buf.req.runlevel); if (buf.req.runlevel != runlevel) { progman_stop (); dfl_level = buf.req.runlevel; inittrans (); } break; case INIT_CMD_SETENV: sysvinit_setenv (buf.req.data, sizeof (buf.req.data)); break; /* FIXME: react on other commands */ } } size = 0; } return 0; } static void create_fifo () { static int fd = -1; struct stat st, fst; if (stat (init_fifo, &st) < 0) { if (errno != ENOENT) { logmsg (LOG_ERR, "cannot stat fifo %s: %s", init_fifo, strerror (errno)); return; } else if (mkfifo (init_fifo, 0600)) { logmsg (LOG_ERR, "cannot create fifo %s: %s", init_fifo, strerror (errno)); return; } } else { if (!S_ISFIFO (st.st_mode)) { logmsg (LOG_ERR, "not a fifo: %s", init_fifo); return; } chmod (init_fifo, 0600); } if (fd != -1) { fstat (fd, &fst); if (fst.st_dev != st.st_dev || fst.st_ino != st.st_ino) { deregister_socket (fd); close (fd); } debug (1, ("reopening %s", init_fifo)); } /* Opening the socket in read-write mode ensures we won't get EOF on it when the caller party closes connection (at least on Linux). Nevertheless, the svinit_fifo_handler is prepared for that eventuality, too. */ fd = open (init_fifo, O_RDWR|O_NONBLOCK); if (fd == -1) { logmsg (LOG_ERR, "cannot open %s: %s", init_fifo, strerror (errno)); return; } register_socket (fd, sysvinit_fifo_handler, NULL, NULL, NULL); } static char *try_console[] = { NULL, "/dev/console", "/dev/tty0" }; char *console_device; static void set_console_dev () { int i; for (i = 0; i < ARRAY_SIZE (try_console); i++) { if (try_console[i]) { int fd = open (try_console[i], O_RDONLY|O_NONBLOCK); if (fd >= 0) { close (fd); console_device = try_console[i]; return; } } } /* provide default */ console_device = "/dev/null"; } int sysvinit_sigtrans (int sig, int *pact) { switch (sig) { case SIGINT: *pact = ACTION_CTRLALTDEL; break; case SIGWINCH: *pact = ACTION_KBREQUEST; break; case SIGTERM: case SIGQUIT: case SIGHUP: /* Ignore these signals. */ *pact = ACTION_CONT; break; default: return 0; } return 1; } static void unintr_sleep (unsigned n) { struct timeval tv; tv.tv_sec = n; tv.tv_usec = 0; while (select (0, NULL, NULL, NULL, &tv) < 0 && errno == EINTR) ; } /* Memory allocation functions for INIT. They may not fail, therefore they just retry allocation until it eventually succeeds. */ static void * sysvinit_malloc (size_t size) { void *p; while (!(p = malloc (size))) { logmsg (LOG_ERR, _("out of memory")); unintr_sleep (5); } return p; } static void * sysvinit_realloc (void *ptr, size_t size) { void *p; while (!(p = realloc (ptr, size))) { logmsg (LOG_ERR, _("out of memory")); unintr_sleep (5); } return p; } void sysvinit_begin () { int sigv[] = { SIGINT, SIGPWR, SIGWINCH, }; grecs_malloc_fun = sysvinit_malloc; grecs_realloc_fun = sysvinit_realloc; close (0); close (1); close (2); set_console_dev (); console_stty (); setsid (); envsetup (); sysvinit_runlevel_setup (PIES_COMP_DEFAULT, NULL); add_extra_sigv (sigv, ARRAY_SIZE (sigv)); sysvinit_sysdep_begin (); } int inittrans () { int n; int newlevel = 0; enum boot_state newstate; int trans = 0; static int wait = 0; if (progman_running_p ()) /* Noting to do if there are processes left in the previous runlevel */ return 0; if (runlevel == 0) n = runlevel_index (dfl_level ? dfl_level : getinitdefault ()); else n = runlevel_index (runlevel); if (n == -1) n = runlevel_index ('S'); newstate = boot_trans_tab[boot_state][n]; if (newstate != boot_state) { debug (1, ("STATE TRANS: %s -> %s", boot_state_name[boot_state], boot_state_name[newstate])); boot_state = newstate; trans = 1; wait = 0; } switch (boot_state) { case sysinit: break; case boot: sysvinit_acct (SYSV_ACCT_BOOT, "reboot", "~~", 0, "~"); break; case single: newlevel = 'S'; break; case normal: newlevel = dfl_level ? dfl_level : getinitdefault (); if (trans) /* boot -> normal */ create_fifo (); } if (newlevel && newlevel != runlevel) { prevlevel = runlevel ? runlevel : 'N'; debug (1, ("RL TRANS: %c -> %c", prevlevel, newlevel)); sysvinit_acct (SYSV_ACCT_RUNLEVEL, "runlevel", "~~", newlevel + 256 * runlevel, "~"); mf_proctitle_format ("init [%c]", newlevel); runlevel = newlevel; trans = 1; wait = 0; } if (wait) trans = 1; if (trans) { envsetup (); if (wait == 0) { sysvinit_runlevel_setup (PIES_COMP_DEFAULT, &wait); if (wait) return 1; } sysvinit_runlevel_setup (PIES_COMP_DEFAULT, NULL); wait = 0; } return trans; } /* Return true if the progman should wait for the component to terminate. */ int is_comp_wait (struct component *comp) { switch (comp->mode) { case pies_comp_boot: case pies_comp_powerfail: case pies_comp_ctrlaltdel: case pies_comp_powerfailnow: case pies_comp_kbrequest: return 0; default: break; } return 1; } int telinit (const char *arg) { int fd; struct sysvinit_request req; if (arg[1] || !is_valid_runlevel (*arg)) { logmsg (LOG_CRIT, "invalid argument"); exit (EX_USAGE); } memset (&req, 0, sizeof (req)); req.magic = INIT_MAGIC; req.cmd = INIT_CMD_RUNLVL; req.runlevel = *arg; #if 0 req.sleeptime = sltime; #endif signal (SIGALRM, SIG_DFL); alarm (5); fd = open (init_fifo, O_WRONLY); if (fd < 0) { logmsg (LOG_ERR, _("can't open %s: %s"), init_fifo, strerror (errno)); exit (EX_UNAVAILABLE); } if (write (fd, &req, sizeof (req)) != sizeof (req)) { logmsg (LOG_ERR, _("error writing to %s: %s"), init_fifo, strerror (errno)); exit (EX_UNAVAILABLE); } alarm (0); close (fd); exit (0); } static char * getfld (char *str, char **endp) { char *p; if (str) { p = strchr (str, ':'); if (p) *p++ = 0; *endp = p; } else *endp = NULL; return str; } struct action_parser { const char *action; enum pies_comp_mode mode; int (*parser) (struct component *comp, const char *file, unsigned line); }; static struct action_parser action_tab[] = { { "wait", pies_comp_wait }, { "once", pies_comp_once }, { "boot", pies_comp_boot }, { "bootwait", pies_comp_bootwait }, { "powerfail", pies_comp_powerfail }, { "powerwait", pies_comp_powerwait }, { "powerokwait", pies_comp_powerokwait }, { "ctrlaltdel", pies_comp_ctrlaltdel }, { "ondemand", pies_comp_ondemand }, { "sysinit", pies_comp_sysinit }, { "powerfailnow", pies_comp_powerfailnow }, { "kbrequest", pies_comp_kbrequest }, { "respawn", pies_comp_respawn }, { NULL } }; static struct action_parser * find_action_parser (const char *action) { struct action_parser *ap; for (ap = action_tab; ap->action; ap++) if (strcmp (ap->action, action) == 0) return ap; return NULL; } int inittab_parse (const char *file) { FILE *fp; size_t size = 0; char *buf = NULL; unsigned line_no = 0; int err = 0; fp = fopen (file, "r"); if (!fp) { logmsg (LOG_ERR, _("cannot open configuration file %s: %s"), file, strerror (errno)); return 1; } while (getline (&buf, &size, fp) >= 0) { char *id, *runlevels, *action, *process, *p; struct action_parser *ap; struct component *comp; struct wordsplit ws; line_no++; for (p = buf; *p && c_isblank (*p); p++) ; if (!p || *p == '\n') continue; if (*p == '#') { if (wordsplit (p+1, &ws, WRDSF_DEFFLAGS)) { logmsg (LOG_ERR, "%s:%u: wordsplit: %s", file, line_no, strerror (errno)); err = 1; } /* pies pragma debug N */ /* pies pragma next FORMAT FILE */ /* pies pragma stop */ if (ws.ws_wordc > 2 && strcmp (ws.ws_wordv[0], "pies") == 0 && strcmp (ws.ws_wordv[1], "pragma") == 0) { if (strcmp (ws.ws_wordv[2], "debug") == 0) { if (ws.ws_wordc == 4) debug_level = atoi (ws.ws_wordv[3]); } else if (strcmp (ws.ws_wordv[2], "next") == 0) { if (ws.ws_wordc >= 5) { enum config_syntax synt; if (str_to_config_syntax (ws.ws_wordv[3], &synt)) logmsg (LOG_ERR, "%s:%u: %s", file, line_no, _("unknown syntax type")); else add_config (synt, ws.ws_wordv[4]); } else if (ws.ws_wordc == 4) add_config (CONF_PIES, ws.ws_wordv[3]); } else if (strcmp (ws.ws_wordv[2], "stop") == 0) { wordsplit_free (&ws); break; } } wordsplit_free (&ws); continue; } id = getfld (p, &p); runlevels = getfld (p, &p); action = getfld (p, &p); process = p; if (!id || !runlevels || !action || !process) { logmsg (LOG_ERR, "%s:%u: %s", file, line_no, _("not enough fields")); err = 1; continue; } if (strcmp (action, "initdefault") == 0) { if (!runlevels[0] || !is_valid_runlevel (runlevels[0])) { logmsg (LOG_ERR, "%s:%u: %s", file, line_no, _("invalid runlevel")); err = 1; } else initdefault = toupper (runlevels[0]); continue; } ap = find_action_parser (action); if (!ap) { logmsg (LOG_ERR, "%s:%u: %s", file, line_no, _("unknown action")); err = 1; continue; } comp = calloc (1, sizeof (*comp)); if (!comp) { logmsg (LOG_ERR, "%s:%u: %s", file, line_no, _("not enough memory")); err = 1; continue; } comp->mode = ap->mode; comp->tag = strdup (id); comp->runlevels = strdup (runlevels); if (!comp->tag || !comp->runlevels) { component_free (comp); logmsg (LOG_ERR, "%s:%u: %s", file, line_no, _("not enough memory")); err = 1; continue; } if (wordsplit (process, &ws, WRDSF_DEFFLAGS)) { component_free (comp); logmsg (LOG_ERR, "%s:%u: wordsplit: %s", file, line_no, strerror (errno)); err = 1; continue; } comp->argc = ws.ws_wordc; comp->argv = ws.ws_wordv; comp->program = strdup (ws.ws_wordv[0]); ws.ws_wordc = 0; ws.ws_wordv = NULL; wordsplit_free (&ws); comp->flags |= CF_SIGGROUP; if (ap->parser && ap->parser (comp, file, line_no)) { component_free (comp); err = 1; continue; } register_prog (comp); } free (buf); fclose (fp); return err; }