/* This file is part of GNU Pies.
Copyright (C) 2013-2016 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
#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;
char *emergency_shell = EMERGENCY_SHELL;
int emergency;
static int inittrans (void);
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 (void)
{
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 (void)
{
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 (void)
{
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;
};
static int
enablecomp (struct prog *prog, void *data)
{
struct enstate *s = data;
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_ondemand:
/* Active flag persists: */
return prog->active;
case pies_comp_powerfail:
case pies_comp_powerwait:
case pies_comp_powerokwait:
case pies_comp_ctrlaltdel:
case pies_comp_powerfailnow:
case pies_comp_kbrequest:
return s && (s->mask & PIES_COMP_MASK (comp->mode));
default:
break;
}
}
if (!comp->runlevels)
return -1;
if (!strchr (comp->runlevels, runlevel))
{
if (prog->v.p.status == status_finished && comp->mode != pies_comp_once)
prog->v.p.status = status_stopped;
return 0;
}
if (prog->v.p.status == status_finished)
return -1;
return 1;
}
static int
runlevel_setup_prog (struct prog *prog, void *data)
{
if (IS_COMPONENT (prog)
&& !(prog->v.p.comp->flags & CF_DISABLED)
&& is_sysvinit (prog->v.p.comp))
{
int rc = enablecomp (prog, data);
if (rc < 0 || prog->active == rc)
return 0;
prog->active = rc;
if (rc)
prog->wait = is_comp_wait (prog->v.p.comp);
else if (prog->v.p.status != status_stopped
&& prog->v.p.status != status_finished)
prog->stop = 1;
debug (2, ("%s: %s%s", prog_tag (prog),
prog->active ?
"enabled" : "disabled",
prog->active && prog->wait ? " (wait)" : ""));
}
return 0;
}
void
sysvinit_runlevel_setup (int mask)
{
struct enstate s;
s.mask = mask;
progman_foreach (runlevel_setup_prog, &s);
pies_schedule_children (PIES_CHLD_GC);
}
static int
demand_prog (struct prog *prog, void *data)
{
int *rl = data;
struct component *comp = prog->v.p.comp;
if (comp->mode == pies_comp_ondemand
&& comp->runlevels
&& strchr (comp->runlevels, *rl))
{
prog->active = 1;
debug (1, ("%s: %s", prog_tag (prog), "enabled"));
}
return 0;
}
static void
sysvinit_demand (int rl)
{
progman_foreach (demand_prog, &rl);
}
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+1] = {
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 (void)
{
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);
}
}
}
int
sysvinit_envlocate (char const *name, char **value)
{
int i, j;
char *var;
for (i = 0; (var = sysvinit_environ_hint[i]); i++)
{
for (j = 0; var[j] && name[j] == var[j]; j++)
;
if (!name[j] && var[j] == '=')
{
if (value)
*value = var + j + 1;
return i;
}
}
return -1;
}
int
sysvinit_envupdate (char const *var)
{
int i, j;
size_t len;
len = strcspn (var, "=");
for (i = 0; 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;
if (i < ENVI_AVAIL)
return 1;
free (sysvinit_environ_hint[i]);
}
if (var[len] == '=')
sysvinit_environ_hint[i] = grecs_strdup (var);
else
while ((sysvinit_environ_hint[i] = sysvinit_environ_hint[i+1]))
++i;
return 0;
}
return -1;
}
static void
sysvinit_setenv (char const *data, int size)
{
while (size)
{
char const *var = data;
size_t len = strlen (var) + 1;
size -= len;
if (size < 0)
break;
data += len;
sysvinit_envupdate (var);
}
}
int
sysvinit_set_runlevel (int newlevel)
{
switch (newlevel)
{
case 'A':
case 'B':
case 'C':
diagmsg (DIAG_ALL, LOG_INFO,
"Activating on-demand level '%c'", newlevel);
sysvinit_demand (newlevel);
break;
default:
if (newlevel == runlevel)
break;
else if (runlevel_index (newlevel) == -1)
return -1;
else
{
diagmsg (DIAG_ALL, LOG_INFO, "Switching to runlevel: %c", newlevel);
dfl_level = newlevel;
pies_schedule_children (PIES_CHLD_WAKEUP);
}
}
return 0;
}
char *init_fifo = INIT_FIFO;
static void create_fifo (void);
static void powerfailcmd (int power_stat);
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;
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));
if (buf.req.sleeptime > 0)
shutdown_timeout = buf.req.sleeptime;
switch (buf.req.cmd)
{
case INIT_CMD_RUNLVL:
buf.req.runlevel = toupper (buf.req.runlevel);
switch (buf.req.runlevel)
{
case 'Q':
pies_schedule_action (ACTION_RELOAD);
break;
default:
sysvinit_set_runlevel (buf.req.runlevel);
}
break;
case INIT_CMD_SETENV:
sysvinit_setenv (buf.req.data, sizeof (buf.req.data));
break;
case INIT_CMD_POWERFAIL:
powerfailcmd (POWER_STAT_FAIL);
break;
case INIT_CMD_POWERFAILNOW:
powerfailcmd (POWER_STAT_LOW);
break;
case INIT_CMD_POWEROK:
powerfailcmd (POWER_STAT_OK);
break;
/* FIXME: react on other commands */
}
}
size = 0;
}
return 0;
}
static void
create_fifo (void)
{
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);
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 (void)
{
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:
/* Ignore these signals. */
*pact = ACTION_CONT;
break;
case SIGPWR:
*pact = ACTION_POWER;
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)
;
}
static void
start_shell (const char *shell)
{
pid_t pid, rc;
int st;
struct sigaction act, old;
act.sa_flags = SA_RESTART;
act.sa_handler = SIG_DFL;
sigemptyset (&act.sa_mask);
sigaction (SIGCHLD, &act, &old);
pid = fork ();
if (pid == -1)
{
logmsg (LOG_CRIT, "cannot run `%s': fork failed: %s",
shell, strerror (errno));
exit (1);
}
if (pid == 0)
{
int fd;
signal_setup (SIG_DFL);
setsid ();
fd = console_open (O_RDWR|O_NOCTTY);
if (fd < 0)
{
logmsg (LOG_CRIT, "open(%s): %s",
console_device, strerror (errno));
exit (1);
}
ioctl (fd, TIOCSCTTY, 1);
dup (fd);
dup (fd);
execl (shell, shell, NULL);
_exit (127);
}
while ((rc = wait (&st)) != pid)
if (rc == (pid_t) -1 && errno == ECHILD)
break;
logmsg (LOG_CRIT, "shell finished");
sigaction (SIGCHLD, &old, NULL);
}
/* 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;
}
static void
save_argv (void)
{
int i;
char **av;
instance = grecs_strdup (instance);
av = grecs_calloc (pies_master_argc + 1, sizeof (av[0]));
for (i = 0; i < pies_master_argc; i++)
av[i] = grecs_strdup (pies_master_argv[i]);
av[i] = NULL;
pies_master_argv = av;
}
void
sysvinit_begin (void)
{
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);
add_extra_sigv (sigv, ARRAY_SIZE (sigv));
sysvinit_sysdep_begin ();
save_argv ();
pies_set_hook (inittrans);
if (emergency)
start_shell (emergency_shell);
}
#define IS_RUNNING_DISABLED_PROG(prog) \
(IS_COMPONENT (prog) \
&& prog->v.p.status == status_running \
&& !prog->active)
int
inittrans (void)
{
int n;
int newlevel = 0;
enum boot_state newstate;
int trans = 0;
int userchg = 0;
if (progman_waiting_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;
}
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 ();
ctl_open ();
diag_setup (DIAG_TO_SYSLOG | DIAG_REOPEN_LOG);
}
else
userchg = 1;
}
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;
}
if (trans)
{
envsetup ();
if (userchg)
pies_schedule_action (ACTION_RELOAD);
else
{
sysvinit_runlevel_setup (PIES_COMP_DEFAULT);
pies_schedule_children (PIES_CHLD_WAKEUP);
}
}
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_sysinit:
case pies_comp_bootwait:
case pies_comp_wait:
case pies_comp_powerwait:
case pies_comp_powerfailnow:
case pies_comp_powerokwait:
case pies_comp_ctrlaltdel:
return 1;
default:
break;
}
return 0;
}
#include "telinit.h"
int
telinit (int argc, char **argv)
{
int fd;
struct sysvinit_request req;
memset (&req, 0, sizeof (req));
req.magic = INIT_MAGIC;
telinit_parser (&req, argc, argv);
signal (SIGALRM, SIG_DFL);
alarm (5);
fd = open (init_fifo, O_WRONLY);
if (fd < 0)
{
logmsg (LOG_ERR, _("cannot open %s: %s"), init_fifo, strerror (errno));
return EX_UNAVAILABLE;
}
if (write (fd, &req, sizeof (req)) != sizeof (req))
{
logmsg (LOG_ERR, _("error writing to %s: %s"),
init_fifo, strerror (errno));
return EX_UNAVAILABLE;
}
alarm (0);
close (fd);
return 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;
}
static char *
strupr (char *s)
{
char *p;
for (p = s; *p; p++)
*p = toupper (*p);
return s;
}
struct inittab_ctx
{
char *buf;
size_t size;
char const *file;
unsigned line_no;
};
enum inittab_status
{
inittab_ok,
inittab_err,
inittab_stop
};
static enum inittab_status
inittab_parse_line (struct inittab_ctx *ctx)
{
char *id, *runlevels, *action, *process, *p;
struct action_parser *ap;
struct component *comp;
struct wordsplit ws;
ctx->line_no++;
for (p = ctx->buf; *p && c_isblank (*p); p++)
;
if (!p || *p == '\n')
return inittab_ok;
if (*p == '#')
{
if (wordsplit (p+1, &ws, WRDSF_DEFFLAGS))
{
logmsg (LOG_ERR, "%s:%u: wordsplit: %s", ctx->file, ctx->line_no,
strerror (errno));
return inittab_err;
}
/* 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)
{
struct config_syntax *synt =
str_to_config_syntax (ws.ws_wordv[3]);
if (!synt)
logmsg (LOG_ERR, "%s:%u: %s",
ctx->file, ctx->line_no, _("unknown syntax type"));
else
config_file_add (synt, ws.ws_wordv[4]);
}
else if (ws.ws_wordc == 4)
config_file_add_type (CONF_PIES, ws.ws_wordv[3]);
}
else if (strcmp (ws.ws_wordv[2], "stop") == 0)
{
wordsplit_free (&ws);
return inittab_stop;
}
}
wordsplit_free (&ws);
return inittab_ok;
}
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",
ctx->file, ctx->line_no, _("not enough fields"));
return inittab_err;
}
if (strcmp (action, "initdefault") == 0)
{
if (!runlevels[0] || !is_valid_runlevel (runlevels[0]))
{
logmsg (LOG_ERR, "%s:%u: %s",
ctx->file, ctx->line_no, _("invalid runlevel"));
return inittab_err;
}
else
initdefault = toupper (runlevels[0]);
return inittab_ok;
}
if (strcmp (action, "off") == 0)
/* Ignore the entry */
return inittab_ok;
ap = find_action_parser (action);
if (!ap)
{
logmsg (LOG_ERR, "%s:%u: %s", ctx->file, ctx->line_no,
_("unknown action"));
return inittab_err;
}
comp = component_create (id);
comp->mode = ap->mode;
comp->runlevels = grecs_strdup (strupr (runlevels));
if (wordsplit (process, &ws, WRDSF_DEFFLAGS))
{
component_free (comp);
logmsg (LOG_ERR, "%s:%u: wordsplit: %s", ctx->file, ctx->line_no,
strerror (errno));
return inittab_err;
}
wordsplit_getwords (&ws, &comp->argc, &comp->argv);
comp->program = grecs_strdup (comp->argv[0]);
wordsplit_free (&ws);
comp->flags |= CF_SIGGROUP;
if (ap->parser && ap->parser (comp, ctx->file, ctx->line_no))
{
component_free (comp);
return inittab_err;
}
return inittab_ok;
}
/* Return true if we hit a single-user component */
static int
is_single_user (struct component *comp, void *data)
{
return is_sysvinit (comp) && comp->runlevels && strchr (comp->runlevels, 'S');
}
int
inittab_parse (const char *file)
{
FILE *fp;
struct inittab_ctx ctx;
int err;
ctx.size = 0;
ctx.buf = NULL;
ctx.file = file;
ctx.line_no = 0;
fp = fopen (file, "r");
if (fp)
{
while (getline (&ctx.buf, &ctx.size, fp) >= 0)
{
enum inittab_status st = inittab_parse_line (&ctx);
if (st == inittab_err)
err = 1;
else if (st == inittab_stop)
break;
}
fclose (fp);
}
else
{
logmsg (LOG_ERR,
_("cannot open configuration file %s: %s"),
file, strerror (errno));
err = 1;
}
if (!component_foreach (is_single_user, 0))
{
/* Provide single-user entry */
ctx.file = __FILE__;
ctx.line_no = __LINE__;
grecs_asprintf (&ctx.buf, &ctx.size, "~~:S:wait:%s\n", emergency_shell);
inittab_parse_line (&ctx);
}
free (ctx.buf);
return err;
}
char *power_stat_file = POWER_STAT_FILE;
void
sysvinit_power (void)
{
int power_stat = POWER_STAT_FAIL;
int fd;
fd = open (power_stat_file, O_RDONLY);
if (fd >= 0)
{
char c;
switch (read (fd, &c, 1))
{
case 1:
power_stat = c;
break;
case 0:
logmsg (LOG_NOTICE, _("unexpected EOF on %s"), power_stat_file);
break;
case -1:
logmsg (LOG_ERR, _("error reading from %s: %s"),
power_stat_file, strerror (errno));
}
close (fd);
if (unlink (power_stat_file))
logfuncall ("unlink", power_stat_file, errno);
}
else
debug (1, (_("cannot open %s: %s"), power_stat_file, strerror (errno)));
powerfailcmd (power_stat);
}
static void
powerfailcmd (int power_stat)
{
int mask;
switch (power_stat)
{
case POWER_STAT_OK: /* The power is OK */
mask = PIES_COMP_MASK (pies_comp_powerokwait);
break;
case POWER_STAT_LOW: /* Low battery: shut down now */
mask = PIES_COMP_MASK (pies_comp_powerfailnow);
break;
default: /* Power failure */
mask = PIES_COMP_MASK (pies_comp_powerfail)
| PIES_COMP_MASK (pies_comp_powerwait);
}
sysvinit_runlevel_setup (mask);
}
void
sysvinit_report (struct json_value *obj)
{
json_object_set_string (obj, "runlevel", "%c", runlevel);
json_object_set_string (obj, "prevlevel", "%c", prevlevel);
json_object_set_string (obj, "bootstate", "%s",
boot_state_name[boot_state]);
if (initdefault)
json_object_set_string (obj, "initdefault", "%c", initdefault);
}
void
sysvinit_parse_argv (int argc, char **argv)
{
while (--argc)
{
int c;
char *arg = *++argv;
if (!strcmp (arg, "single") || !strcmp (arg, "-s"))
dfl_level = 'S';
else if (!strcmp (arg, "-b") || !strcmp (arg, "emergency"))
emergency = 1;
else if (!arg[1] && strchr (valid_runlevels, (c = toupper (arg[0]))))
dfl_level = c;
}
}