/* 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,
max_boot_state
};
static char *boot_state_name[] = {
"sysinit",
"boot",
"single",
"normal"
};
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] = xstrdup (var);
else
for (j = i + 1; j < NR_ENVHINT; j++, i++)
sysvinit_environ_hint[i] =
sysvinit_environ_hint[j];
break;
}
}
}
static void create_fifo (void);
static int
sysvinit_stop_filter (struct prog *prog, void *data)
{
switch (prog->v.p.status)
{
case status_enabled:
case status_listener:
prog_stop (prog, SIGTERM);
prog->v.p.status = status_disabled; /* See FIXME, progman.c:2364 */
break;
default:
break;
}
return 0;
}
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;
}
void
sysvinit_begin ()
{
int sigv[] = {
SIGINT,
SIGPWR,
SIGWINCH,
};
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:
/* boot -> normal */
newlevel = dfl_level ? dfl_level : getinitdefault ();
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;
}