1176 lines
31 KiB
C
1176 lines
31 KiB
C
/*
|
|
rc.c
|
|
rc - manager for init scripts which control the startup, shutdown
|
|
and the running of daemons on a Gentoo system.
|
|
|
|
Also a multicall binary for various commands that can be used in shell
|
|
scripts to query service state, mark service state and provide the
|
|
Gentoo einfo family of informational functions.
|
|
|
|
Copyright 2007 Gentoo Foundation
|
|
Released under the GPLv2
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/utsname.h>
|
|
#include <sys/wait.h>
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
#include <libgen.h>
|
|
#include <limits.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
|
|
#include "einfo.h"
|
|
#include "rc.h"
|
|
#include "rc-misc.h"
|
|
#include "rc-plugin.h"
|
|
#include "strlist.h"
|
|
|
|
#define INITSH RC_LIBDIR "sh/init.sh"
|
|
#define HALTSH RC_INITDIR "halt.sh"
|
|
|
|
#define RC_SVCDIR_STARTING RC_SVCDIR "starting/"
|
|
#define RC_SVCDIR_INACTIVE RC_SVCDIR "inactive/"
|
|
#define RC_SVCDIR_STARTED RC_SVCDIR "started/"
|
|
#define RC_SVCDIR_COLDPLUGGED RC_SVCDIR "coldplugged/"
|
|
|
|
#define INTERACTIVE RC_SVCDIR "interactive"
|
|
|
|
#define DEVBOOT "/dev/.rcboot"
|
|
|
|
/* Cleanup anything in main */
|
|
#define CHAR_FREE(_item) \
|
|
if (_item) \
|
|
{ \
|
|
free (_item); \
|
|
_item = NULL; \
|
|
}
|
|
|
|
extern char **environ;
|
|
|
|
static char **env = NULL;
|
|
static char **newenv = NULL;
|
|
static char **coldplugged_services;
|
|
static char **stop_services = NULL;
|
|
static char **start_services = NULL;
|
|
static rc_depinfo_t *deptree = NULL;
|
|
static char **types = NULL;
|
|
static char *mycmd = NULL;
|
|
static char *myarg = NULL;
|
|
static char *tmp = NULL;
|
|
static char *applet = NULL;
|
|
|
|
struct termios *termios_orig;
|
|
|
|
static void cleanup (void)
|
|
{
|
|
rc_plugin_unload ();
|
|
|
|
if (termios_orig)
|
|
{
|
|
tcsetattr (STDIN_FILENO, TCSANOW, termios_orig);
|
|
free (termios_orig);
|
|
}
|
|
|
|
if (env)
|
|
rc_strlist_free (env);
|
|
if (newenv)
|
|
rc_strlist_free (newenv);
|
|
if (coldplugged_services)
|
|
rc_strlist_free (coldplugged_services);
|
|
if (stop_services)
|
|
rc_strlist_free (stop_services);
|
|
if (start_services)
|
|
rc_strlist_free (start_services);
|
|
if (deptree)
|
|
rc_free_deptree (deptree);
|
|
if (types)
|
|
rc_strlist_free (types);
|
|
if (mycmd)
|
|
free (mycmd);
|
|
if (myarg)
|
|
free (myarg);
|
|
|
|
/* Clean runlevel start, stop markers */
|
|
if (rc_is_dir (RC_SVCDIR "softscripts.new"))
|
|
rc_rm_dir (RC_SVCDIR "softscripts.new", true);
|
|
if (rc_is_dir (RC_SVCDIR "softscripts.old"))
|
|
rc_rm_dir (RC_SVCDIR "softscripts.old", true);
|
|
}
|
|
|
|
static int do_e (int argc, char **argv)
|
|
{
|
|
int retval = EXIT_SUCCESS;
|
|
int i;
|
|
int l = 0;
|
|
char *message = NULL;
|
|
char *p;
|
|
char *fmt = NULL;
|
|
|
|
if (strcmp (applet, "eend") == 0 ||
|
|
strcmp (applet, "ewend") == 0 ||
|
|
strcmp (applet, "veend") == 0 ||
|
|
strcmp (applet, "vweend") == 0)
|
|
{
|
|
if (argc > 0)
|
|
{
|
|
errno = 0;
|
|
retval = strtol (argv[0], NULL, 0);
|
|
if (errno != 0)
|
|
retval = EXIT_FAILURE;
|
|
else
|
|
{
|
|
argc--;
|
|
argv++;
|
|
}
|
|
}
|
|
else
|
|
retval = EXIT_FAILURE;
|
|
}
|
|
|
|
if (argc > 0)
|
|
{
|
|
for (i = 0; i < argc; i++)
|
|
l += strlen (argv[i]) + 1;
|
|
|
|
message = rc_xmalloc (l);
|
|
p = message;
|
|
|
|
for (i = 0; i < argc; i++)
|
|
{
|
|
if (i > 0)
|
|
*p++ = ' ';
|
|
memcpy (p, argv[i], strlen (argv[i]));
|
|
p += strlen (argv[i]);
|
|
}
|
|
*p = 0;
|
|
}
|
|
|
|
if (message)
|
|
fmt = strdup ("%s");
|
|
|
|
if (strcmp (applet, "einfo") == 0)
|
|
einfo (fmt, message);
|
|
else if (strcmp (applet, "einfon") == 0)
|
|
einfon (fmt, message);
|
|
else if (strcmp (applet, "ewarn") == 0)
|
|
ewarn (fmt, message);
|
|
else if (strcmp (applet, "ewarnn") == 0)
|
|
ewarnn (fmt, message);
|
|
else if (strcmp (applet, "eerror") == 0)
|
|
{
|
|
eerror (fmt, message);
|
|
retval = 1;
|
|
}
|
|
else if (strcmp (applet, "eerrorn") == 0)
|
|
{
|
|
eerrorn (fmt, message);
|
|
retval = 1;
|
|
}
|
|
else if (strcmp (applet, "ebegin") == 0)
|
|
ebegin (fmt, message);
|
|
else if (strcmp (applet, "eend") == 0)
|
|
eend (retval, fmt, message);
|
|
else if (strcmp (applet, "ewend") == 0)
|
|
ewend (retval, fmt, message);
|
|
else if (strcmp (applet, "veinfo") == 0)
|
|
veinfo (fmt, message);
|
|
else if (strcmp (applet, "veinfon") == 0)
|
|
veinfon (fmt, message);
|
|
else if (strcmp (applet, "vewarn") == 0)
|
|
vewarn (fmt, message);
|
|
else if (strcmp (applet, "vewarnn") == 0)
|
|
vewarnn (fmt, message);
|
|
else if (strcmp (applet, "vebegin") == 0)
|
|
vebegin (fmt, message);
|
|
else if (strcmp (applet, "veend") == 0)
|
|
veend (retval, fmt, message);
|
|
else if (strcmp (applet, "vewend") == 0)
|
|
vewend (retval, fmt, message);
|
|
else if (strcmp (applet, "eindent") == 0)
|
|
eindent ();
|
|
else if (strcmp (applet, "eoutdent") == 0)
|
|
eoutdent ();
|
|
else if (strcmp (applet, "veindent") == 0)
|
|
veindent ();
|
|
else if (strcmp (applet, "veoutdent") == 0)
|
|
veoutdent ();
|
|
else if (strcmp (applet, "eflush") == 0)
|
|
eflush ();
|
|
else
|
|
{
|
|
eerror ("%s: unknown applet", applet);
|
|
retval = EXIT_FAILURE;
|
|
}
|
|
|
|
if (fmt)
|
|
free (fmt);
|
|
if (message)
|
|
free (message);
|
|
return (retval);
|
|
}
|
|
|
|
static int do_service (int argc, char **argv)
|
|
{
|
|
bool ok = false;
|
|
|
|
if (argc < 1 || ! argv[0] || strlen (argv[0]) == 0)
|
|
eerrorx ("%s: no service specified", applet);
|
|
|
|
if (strcmp (applet, "service_started") == 0)
|
|
ok = rc_service_state (argv[0], rc_service_started);
|
|
else if (strcmp (applet, "service_stopped") == 0)
|
|
ok = rc_service_state (argv[0], rc_service_stopped);
|
|
else if (strcmp (applet, "service_inactive") == 0)
|
|
ok = rc_service_state (argv[0], rc_service_inactive);
|
|
else if (strcmp (applet, "service_starting") == 0)
|
|
ok = rc_service_state (argv[0], rc_service_starting);
|
|
else if (strcmp (applet, "service_stopping") == 0)
|
|
ok = rc_service_state (argv[0], rc_service_stopping);
|
|
else if (strcmp (applet, "service_coldplugged") == 0)
|
|
ok = rc_service_state (argv[0], rc_service_coldplugged);
|
|
else if (strcmp (applet, "service_wasinactive") == 0)
|
|
ok = rc_service_state (argv[0], rc_service_wasinactive);
|
|
else if (strcmp (applet, "service_started_daemon") == 0)
|
|
{
|
|
int idx = 0;
|
|
if (argc > 2)
|
|
sscanf (argv[2], "%d", &idx);
|
|
exit (rc_service_started_daemon (argv[0], argv[1], idx)
|
|
? 0 : 1);
|
|
}
|
|
else
|
|
eerrorx ("%s: unknown applet", applet);
|
|
|
|
return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
|
|
}
|
|
|
|
static int do_mark_service (int argc, char **argv)
|
|
{
|
|
bool ok = false;
|
|
char *svcname = getenv ("SVCNAME");
|
|
|
|
if (argc < 1 || ! argv[0] || strlen (argv[0]) == 0)
|
|
eerrorx ("%s: no service specified", applet);
|
|
|
|
if (strcmp (applet, "mark_service_started") == 0)
|
|
ok = rc_mark_service (argv[0], rc_service_started);
|
|
else if (strcmp (applet, "mark_service_stopped") == 0)
|
|
ok = rc_mark_service (argv[0], rc_service_stopped);
|
|
else if (strcmp (applet, "mark_service_inactive") == 0)
|
|
ok = rc_mark_service (argv[0], rc_service_inactive);
|
|
else if (strcmp (applet, "mark_service_starting") == 0)
|
|
ok = rc_mark_service (argv[0], rc_service_starting);
|
|
else if (strcmp (applet, "mark_service_stopping") == 0)
|
|
ok = rc_mark_service (argv[0], rc_service_stopping);
|
|
else if (strcmp (applet, "mark_service_coldplugged") == 0)
|
|
ok = rc_mark_service (argv[0], rc_service_coldplugged);
|
|
else
|
|
eerrorx ("%s: unknown applet", applet);
|
|
|
|
/* If we're marking ourselves then we need to inform our parent runscript
|
|
process so they do not mark us based on our exit code */
|
|
if (ok && svcname && strcmp (svcname, argv[0]) == 0)
|
|
{
|
|
char *runscript_pid = getenv ("RC_RUNSCRIPT_PID");
|
|
char *mtime;
|
|
pid_t pid = 0;
|
|
int l;
|
|
|
|
if (runscript_pid && sscanf (runscript_pid, "%d", &pid) == 1)
|
|
if (kill (pid, SIGHUP) != 0)
|
|
eerror ("%s: failed to signal parent %d: %s",
|
|
applet, pid, strerror (errno));
|
|
|
|
/* Remove the exclsive time test. This ensures that it's not
|
|
in control as well */
|
|
l = strlen (RC_SVCDIR "exclusive") +
|
|
strlen (svcname) +
|
|
strlen (runscript_pid) +
|
|
4;
|
|
mtime = rc_xmalloc (l);
|
|
snprintf (mtime, l, RC_SVCDIR "exclusive/%s.%s",
|
|
svcname, runscript_pid);
|
|
if (rc_exists (mtime) && unlink (mtime) != 0)
|
|
eerror ("%s: unlink: %s", applet, strerror (errno));
|
|
free (mtime);
|
|
}
|
|
|
|
return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
|
|
}
|
|
|
|
static int do_options (int argc, char **argv)
|
|
{
|
|
bool ok = false;
|
|
char *service = getenv ("SVCNAME");
|
|
|
|
if (! service)
|
|
eerrorx ("%s: no service specified", applet);
|
|
|
|
if (argc < 1 || ! argv[0] || strlen (argv[0]) == 0)
|
|
eerrorx ("%s: no option specified", applet);
|
|
|
|
if (strcmp (applet, "get_options") == 0)
|
|
{
|
|
char buffer[1024];
|
|
memset (buffer, 0, 1024);
|
|
ok = rc_get_service_option (service, argv[0], buffer);
|
|
if (ok)
|
|
printf ("%s", buffer);
|
|
}
|
|
else if (strcmp (applet, "save_options") == 0)
|
|
ok = rc_set_service_option (service, argv[0], argv[1]);
|
|
else
|
|
eerrorx ("%s: unknown applet", applet);
|
|
|
|
return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
|
|
}
|
|
|
|
static char read_key (bool block)
|
|
{
|
|
struct termios termios;
|
|
char c = 0;
|
|
|
|
if (! isatty (STDIN_FILENO))
|
|
return (false);
|
|
|
|
/* Now save our terminal settings. We need to restore them at exit as we
|
|
will be changing it for non-blocking reads for Interactive */
|
|
if (! termios_orig)
|
|
{
|
|
termios_orig = rc_xmalloc (sizeof (struct termios));
|
|
tcgetattr (STDIN_FILENO, termios_orig);
|
|
}
|
|
|
|
tcgetattr (STDIN_FILENO, &termios);
|
|
termios.c_lflag &= ~(ICANON | ECHO);
|
|
if (block)
|
|
termios.c_cc[VMIN] = 1;
|
|
else
|
|
{
|
|
termios.c_cc[VMIN] = 0;
|
|
termios.c_cc[VTIME] = 0;
|
|
}
|
|
tcsetattr (STDIN_FILENO, TCSANOW, &termios);
|
|
|
|
read (STDIN_FILENO, &c, 1);
|
|
|
|
tcsetattr (STDIN_FILENO, TCSANOW, termios_orig);
|
|
|
|
return (c);
|
|
}
|
|
|
|
static bool want_interactive (void)
|
|
{
|
|
char c = read_key (false);
|
|
return ((c == 'I' || c == 'i') ? true : false);
|
|
}
|
|
|
|
static void mark_interactive (void)
|
|
{
|
|
FILE *fp = fopen (INTERACTIVE, "w");
|
|
if (fp)
|
|
fclose (fp);
|
|
}
|
|
|
|
static void sulogin (bool cont)
|
|
{
|
|
#ifdef __linux__
|
|
if (cont)
|
|
{
|
|
int status = 0;
|
|
pid_t pid = fork();
|
|
|
|
if (pid == -1)
|
|
eerrorx ("%s: fork: %s", applet, strerror (errno));
|
|
if (pid == 0)
|
|
{
|
|
newenv = rc_filter_env ();
|
|
mycmd = rc_xstrdup ("/sbin/sulogin");
|
|
myarg = rc_xstrdup (getenv ("CONSOLE"));
|
|
execle (mycmd, mycmd, myarg, (char *) NULL, newenv);
|
|
eerrorx ("%s: unable to exec `/sbin/sulogin': %s", applet, strerror (errno));
|
|
}
|
|
waitpid (pid, &status, 0);
|
|
}
|
|
else
|
|
{
|
|
|
|
newenv = rc_filter_env ();
|
|
mycmd = rc_xstrdup ("/sbin/sulogin");
|
|
myarg = rc_xstrdup (getenv ("CONSOLE"));
|
|
execle (mycmd, mycmd, myarg, (char *) NULL, newenv);
|
|
eerrorx ("%s: unable to exec `/sbin/sulogin': %s", applet, strerror (errno));
|
|
}
|
|
#else
|
|
/* Appease gcc */
|
|
cont = cont;
|
|
exit (EXIT_SUCCESS);
|
|
#endif
|
|
}
|
|
|
|
static void set_ksoftlevel (const char *runlevel)
|
|
{
|
|
FILE *fp;
|
|
|
|
if (! runlevel ||
|
|
strcmp (runlevel, RC_LEVEL_BOOT) == 0 ||
|
|
strcmp (runlevel, RC_LEVEL_SINGLE) == 0 ||
|
|
strcmp (runlevel, RC_LEVEL_SYSINIT) == 0)
|
|
{
|
|
if (rc_exists (RC_SVCDIR "ksoftlevel") &&
|
|
unlink (RC_SVCDIR "ksoftlevel") != 0)
|
|
eerror ("unlink `%s': %s", RC_SVCDIR "ksoftlevel", strerror (errno));
|
|
return;
|
|
}
|
|
|
|
if (! (fp = fopen (RC_SVCDIR "ksoftlevel", "w")))
|
|
{
|
|
eerror ("fopen `%s': %s", RC_SVCDIR "ksoftlevel", strerror (errno));
|
|
return;
|
|
}
|
|
|
|
fprintf (fp, "%s", runlevel);
|
|
fclose (fp);
|
|
}
|
|
|
|
static void wait_for_services ()
|
|
{
|
|
int status = 0;
|
|
struct timeval tv;
|
|
while (wait (&status) != -1);
|
|
|
|
/* Wait for a little bit to flush our ebuffer */
|
|
tv.tv_usec = 50000;
|
|
tv.tv_sec = 0;
|
|
select (0, NULL, NULL, NULL, &tv);
|
|
}
|
|
|
|
int main (int argc, char **argv)
|
|
{
|
|
char *RUNLEVEL = NULL;
|
|
char *PREVLEVEL = NULL;
|
|
char *runlevel = NULL;
|
|
char *newlevel = NULL;
|
|
char *service = NULL;
|
|
char **deporder = NULL;
|
|
int i = 0;
|
|
int j = 0;
|
|
bool going_down = false;
|
|
bool interactive = false;
|
|
int depoptions = RC_DEP_STRICT | RC_DEP_TRACE;
|
|
char ksoftbuffer [PATH_MAX];
|
|
|
|
if (argv[0])
|
|
applet = basename (argv[0]);
|
|
|
|
if (! applet)
|
|
eerrorx ("arguments required");
|
|
|
|
argc--;
|
|
argv++;
|
|
|
|
/* Handle multicall stuff */
|
|
if (applet[0] == 'e' || (applet[0] == 'v' && applet[1] == 'e'))
|
|
exit (do_e (argc, argv));
|
|
|
|
if (strncmp (applet, "service_", strlen ("service_")) == 0)
|
|
exit (do_service (argc, argv));
|
|
|
|
if (strcmp (applet, "get_options") == 0 ||
|
|
strcmp (applet, "save_options") == 0)
|
|
exit (do_options (argc, argv));
|
|
|
|
if (strncmp (applet, "mark_service_", strlen ("mark_service_")) == 0)
|
|
exit (do_mark_service (argc, argv));
|
|
|
|
if (strcmp (applet, "is_runlevel_start") == 0)
|
|
exit (rc_runlevel_starting () ? 0 : 1);
|
|
else if (strcmp (applet, "is_runlevel_stop") == 0)
|
|
exit (rc_runlevel_stopping () ? 0 : 1);
|
|
else if (strcmp (applet, "color_terminal") == 0)
|
|
exit (colour_terminal () ? 0 : 1);
|
|
|
|
if (strcmp (applet, "rc" ) != 0)
|
|
eerrorx ("%s: unknown applet", applet);
|
|
|
|
/* OK, so we really are the main RC process
|
|
Only root should be able to run us */
|
|
if (geteuid () != 0)
|
|
eerrorx ("%s: root access required", applet);
|
|
|
|
atexit (cleanup);
|
|
newlevel = argv[0];
|
|
|
|
/* Ensure our environment is pure
|
|
Also, add our configuration to it */
|
|
env = rc_filter_env ();
|
|
env = rc_config_env (env);
|
|
|
|
if (env)
|
|
{
|
|
char *p;
|
|
|
|
#ifdef __linux__
|
|
/* clearenv isn't portable, but there's no harm in using it
|
|
if we have it */
|
|
clearenv ();
|
|
#else
|
|
char *var;
|
|
/* No clearenv present here then.
|
|
We could manipulate environ directly ourselves, but it seems that
|
|
some kernels bitch about this according to the environ man pages
|
|
so we walk though environ and call unsetenv for each value. */
|
|
while (environ[0])
|
|
{
|
|
tmp = rc_xstrdup (environ[0]);
|
|
p = tmp;
|
|
var = strsep (&p, "=");
|
|
unsetenv (var);
|
|
free (tmp);
|
|
}
|
|
tmp = NULL;
|
|
#endif
|
|
|
|
STRLIST_FOREACH (env, p, i)
|
|
if (strcmp (p, "RC_SOFTLEVEL") != 0 && strcmp (p, "SOFTLEVEL") != 0)
|
|
putenv (p);
|
|
|
|
/* We don't free our list as that would be null in environ */
|
|
}
|
|
|
|
/* Enable logging */
|
|
setenv ("RC_ELOG", "rc", 1);
|
|
|
|
interactive = rc_exists (INTERACTIVE);
|
|
rc_plugin_load ();
|
|
|
|
/* RUNLEVEL is set by sysvinit as is a magic number
|
|
RC_SOFTLEVEL is set by us and is the name for this magic number
|
|
even though all our userland documentation refers to runlevel */
|
|
RUNLEVEL = getenv ("RUNLEVEL");
|
|
PREVLEVEL = getenv ("PREVLEVEL");
|
|
|
|
if (RUNLEVEL && newlevel)
|
|
{
|
|
if (strcmp (RUNLEVEL, "S") == 0 || strcmp (RUNLEVEL, "1") == 0)
|
|
{
|
|
/* OK, we're either in runlevel 1 or single user mode */
|
|
if (strcmp (newlevel, RC_LEVEL_SYSINIT) == 0)
|
|
{
|
|
struct utsname uts;
|
|
pid_t pid;
|
|
pid_t wpid;
|
|
int status = 0;
|
|
#ifdef __linux__
|
|
FILE *fp;
|
|
#endif
|
|
|
|
uname (&uts);
|
|
|
|
printf ("\n");
|
|
PEINFO_GOOD;
|
|
printf (" Gentoo/%s; ", uts.sysname);
|
|
PEINFO_BRACKET;
|
|
printf ("http://www.gentoo.org/");
|
|
PEINFO_NORMAL;
|
|
printf ("\n Copyright 1999-2007 Gentoo Foundation; "
|
|
"Distributed under the GPLv2\n\n");
|
|
|
|
printf ("Press ");
|
|
PEINFO_GOOD;
|
|
printf ("I");
|
|
PEINFO_NORMAL;
|
|
printf (" to enter interactive boot mode\n\n");
|
|
|
|
setenv ("RC_SOFTLEVEL", newlevel, 1);
|
|
rc_plugin_run (rc_hook_runlevel_start_in, newlevel);
|
|
|
|
if ((pid = fork ()) == -1)
|
|
eerrorx ("%s: fork: %s", applet, strerror (errno));
|
|
|
|
if (pid == 0)
|
|
{
|
|
mycmd = rc_xstrdup (INITSH);
|
|
execl (mycmd, mycmd, (char *) NULL);
|
|
eerrorx ("%s: unable to exec `" INITSH "': %s",
|
|
applet, strerror (errno));
|
|
}
|
|
|
|
do
|
|
{
|
|
wpid = waitpid (pid, &status, 0);
|
|
if (wpid < 1)
|
|
eerror ("waitpid: %s", strerror (errno));
|
|
} while (! WIFEXITED (status) && ! WIFSIGNALED (status));
|
|
|
|
if (! WIFEXITED (status) || ! WEXITSTATUS (status) == 0)
|
|
exit (EXIT_FAILURE);
|
|
|
|
/* If we requested a softlevel, save it now */
|
|
#ifdef __linux__
|
|
set_ksoftlevel (NULL);
|
|
|
|
if ((fp = fopen ("/proc/cmdline", "r")))
|
|
{
|
|
char buffer[RC_LINEBUFFER];
|
|
char *soft;
|
|
|
|
memset (buffer, 0, sizeof (buffer));
|
|
if (fgets (buffer, RC_LINEBUFFER, fp) &&
|
|
(soft = strstr (buffer, "softlevel=")))
|
|
{
|
|
i = soft - buffer;
|
|
if (i == 0 || buffer[i - 1] == ' ')
|
|
{
|
|
char *level;
|
|
|
|
/* Trim the trailing carriage return if present */
|
|
i = strlen (buffer) - 1;
|
|
if (buffer[i] == '\n')
|
|
buffer[i] = 0;
|
|
|
|
soft += strlen ("softlevel=");
|
|
level = strsep (&soft, " ");
|
|
set_ksoftlevel (level);
|
|
}
|
|
}
|
|
fclose (fp);
|
|
}
|
|
#endif
|
|
rc_plugin_run (rc_hook_runlevel_start_out, newlevel);
|
|
|
|
if (want_interactive ())
|
|
mark_interactive ();
|
|
|
|
exit (EXIT_SUCCESS);
|
|
}
|
|
|
|
#ifdef __linux__
|
|
/* Parse the inittab file so we can work out the level to telinit */
|
|
if (strcmp (newlevel, RC_LEVEL_BOOT) != 0 &&
|
|
strcmp (newlevel, RC_LEVEL_SINGLE) != 0)
|
|
{
|
|
char **inittab = rc_get_list (NULL, "/etc/inittab");
|
|
char *line;
|
|
char *p;
|
|
char *token;
|
|
char lvl[2] = {0, 0};
|
|
|
|
STRLIST_FOREACH (inittab, line, i)
|
|
{
|
|
p = line;
|
|
token = strsep (&p, ":");
|
|
if (! token || token[0] != 'l')
|
|
continue;
|
|
|
|
if ((token = strsep (&p, ":")) == NULL)
|
|
continue;
|
|
|
|
/* Snag the level */
|
|
lvl[0] = token[0];
|
|
|
|
/* The name is spaced after this */
|
|
if ((token = strsep (&p, " ")) == NULL)
|
|
continue;
|
|
|
|
if ((token = strsep (&p, " ")) == NULL)
|
|
continue;
|
|
|
|
if (strcmp (token, newlevel) == 0)
|
|
break;
|
|
}
|
|
rc_strlist_free (inittab);
|
|
|
|
/* We have a level, so telinit into it */
|
|
if (lvl[0] == 0)
|
|
{
|
|
eerrorx ("%s: couldn't find a runlevel called `%s'",
|
|
applet, newlevel);
|
|
}
|
|
else
|
|
{
|
|
mycmd = rc_xstrdup ("/sbin/telinit");
|
|
myarg = rc_xstrdup (lvl);
|
|
execl (mycmd, mycmd, myarg, (char *) NULL);
|
|
eerrorx ("%s: unable to exec `/sbin/telinit': %s",
|
|
applet, strerror (errno));
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* Check we're in the runlevel requested, ie from
|
|
rc single
|
|
rc shutdown
|
|
rc reboot
|
|
*/
|
|
if (newlevel)
|
|
{
|
|
if (myarg)
|
|
{
|
|
free (myarg);
|
|
myarg = NULL;
|
|
}
|
|
|
|
if (strcmp (newlevel, RC_LEVEL_SINGLE) == 0)
|
|
{
|
|
if (! RUNLEVEL ||
|
|
(strcmp (RUNLEVEL, "S") != 0 &&
|
|
strcmp (RUNLEVEL, "1") != 0))
|
|
{
|
|
/* Remember the current runlevel for when we come back */
|
|
set_ksoftlevel (runlevel);
|
|
#ifdef __linux__
|
|
mycmd = rc_xstrdup ("/sbin/telinit");
|
|
myarg = rc_xstrdup ("S");
|
|
execl (mycmd, mycmd, myarg, (char *) NULL);
|
|
eerrorx ("%s: unable to exec `/%s': %s",
|
|
mycmd, applet, strerror (errno));
|
|
#else
|
|
if (kill (1, SIGTERM) != 0)
|
|
eerrorx ("%s: unable to send SIGTERM to init (pid 1): %s",
|
|
applet, strerror (errno));
|
|
exit (EXIT_SUCCESS);
|
|
#endif
|
|
}
|
|
}
|
|
else if (strcmp (newlevel, RC_LEVEL_REBOOT) == 0)
|
|
{
|
|
if (! RUNLEVEL ||
|
|
strcmp (RUNLEVEL, "6") != 0)
|
|
{
|
|
mycmd = rc_xstrdup ("/sbin/shutdown");
|
|
myarg = rc_xstrdup ("-r");
|
|
tmp = rc_xstrdup ("now");
|
|
execl (mycmd, mycmd, myarg, tmp, (char *) NULL);
|
|
eerrorx ("%s: unable to exec `%s': %s",
|
|
mycmd, applet, strerror (errno));
|
|
}
|
|
}
|
|
else if (strcmp (newlevel, RC_LEVEL_SHUTDOWN) == 0)
|
|
{
|
|
if (! RUNLEVEL ||
|
|
strcmp (RUNLEVEL, "0") != 0)
|
|
{
|
|
mycmd = rc_xstrdup ("/sbin/shutdown");
|
|
#ifdef __linux__
|
|
myarg = rc_xstrdup ("-h");
|
|
#else
|
|
myarg = rc_xstrdup ("-p");
|
|
#endif
|
|
tmp = rc_xstrdup ("now");
|
|
execl (mycmd, mycmd, myarg, tmp, (char *) NULL);
|
|
eerrorx ("%s: unable to exec `%s': %s",
|
|
mycmd, applet, strerror (errno));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Export our current softlevel */
|
|
runlevel = rc_get_runlevel ();
|
|
|
|
/* If we're in the default runlevel and ksoftlevel exists, we should use
|
|
that instead */
|
|
if (newlevel &&
|
|
rc_exists (RC_SVCDIR "ksoftlevel") &&
|
|
strcmp (newlevel, RC_LEVEL_DEFAULT) == 0)
|
|
{
|
|
/* We should only use ksoftlevel if we were in single user mode
|
|
If not, we need to erase ksoftlevel now. */
|
|
if (PREVLEVEL &&
|
|
(strcmp (PREVLEVEL, "1") == 0 ||
|
|
strcmp (PREVLEVEL, "S") == 0 ||
|
|
strcmp (PREVLEVEL, "N") == 0))
|
|
{
|
|
FILE *fp;
|
|
|
|
if (! (fp = fopen (RC_SVCDIR "ksoftlevel", "r")))
|
|
eerror ("fopen `%s': %s", RC_SVCDIR "ksoftlevel",
|
|
strerror (errno));
|
|
else
|
|
{
|
|
if (fgets (ksoftbuffer, sizeof (ksoftbuffer), fp))
|
|
{
|
|
i = strlen (ksoftbuffer) - 1;
|
|
if (ksoftbuffer[i] == '\n')
|
|
ksoftbuffer[i] = 0;
|
|
newlevel = ksoftbuffer;
|
|
}
|
|
fclose (fp);
|
|
}
|
|
}
|
|
else
|
|
set_ksoftlevel (NULL);
|
|
}
|
|
|
|
if (newlevel &&
|
|
(strcmp (newlevel, RC_LEVEL_REBOOT) == 0 ||
|
|
strcmp (newlevel, RC_LEVEL_SHUTDOWN) == 0 ||
|
|
strcmp (newlevel, RC_LEVEL_SINGLE) == 0))
|
|
{
|
|
going_down = true;
|
|
rc_set_runlevel (newlevel);
|
|
setenv ("RC_SOFTLEVEL", newlevel, 1);
|
|
rc_plugin_run (rc_hook_runlevel_stop_in, newlevel);
|
|
}
|
|
else
|
|
{
|
|
rc_plugin_run (rc_hook_runlevel_stop_in, runlevel);
|
|
}
|
|
|
|
/* Check if runlevel is valid if we're changing */
|
|
if (newlevel && strcmp (runlevel, newlevel) != 0 && ! going_down)
|
|
{
|
|
tmp = rc_strcatpaths (RC_RUNLEVELDIR, newlevel, (char *) NULL);
|
|
if (! rc_is_dir (tmp))
|
|
eerrorx ("%s: is not a valid runlevel", newlevel);
|
|
CHAR_FREE (tmp);
|
|
}
|
|
|
|
/* Load our deptree now */
|
|
if ((deptree = rc_load_deptree ()) == NULL)
|
|
eerrorx ("failed to load deptree");
|
|
|
|
/* Clean the failed services state dir now */
|
|
if (rc_is_dir (RC_SVCDIR "failed"))
|
|
rc_rm_dir (RC_SVCDIR "failed", false);
|
|
|
|
mkdir (RC_SVCDIR "/softscripts.new", 0755);
|
|
|
|
#ifdef __linux__
|
|
/* udev likes to start services before we're ready when it does
|
|
its coldplugging thing. runscript knows when we're not ready so it
|
|
stores a list of coldplugged services in DEVBOOT for us to pick up
|
|
here when we are ready for them */
|
|
if (rc_is_dir (DEVBOOT))
|
|
{
|
|
start_services = rc_ls_dir (NULL, DEVBOOT, RC_LS_INITD);
|
|
rc_rm_dir (DEVBOOT, true);
|
|
|
|
STRLIST_FOREACH (start_services, service, i)
|
|
if (rc_allow_plug (service))
|
|
rc_mark_service (service, rc_service_coldplugged);
|
|
/* We need to dump this list now.
|
|
This may seem redunant, but only Linux needs this and saves on
|
|
code bloat. */
|
|
rc_strlist_free (start_services);
|
|
start_services = NULL;
|
|
}
|
|
#else
|
|
/* BSD's on the other hand populate /dev automagically and use devd.
|
|
The only downside of this approach and ours is that we have to hard code
|
|
the device node to the init script to simulate the coldplug into
|
|
runlevel for our dependency tree to work. */
|
|
if (newlevel && strcmp (newlevel, RC_LEVEL_BOOT) == 0 &&
|
|
(strcmp (runlevel, RC_LEVEL_SINGLE) == 0 ||
|
|
strcmp (runlevel, RC_LEVEL_SYSINIT) == 0) &&
|
|
rc_is_env ("RC_COLDPLUG", "yes"))
|
|
{
|
|
/* The net interfaces are easy - they're all in net /dev/net :) */
|
|
start_services = rc_ls_dir (NULL, "/dev/net", 0);
|
|
STRLIST_FOREACH (start_services, service, i)
|
|
{
|
|
j = (strlen ("net.") + strlen (service) + 1);
|
|
tmp = rc_xmalloc (sizeof (char *) * j);
|
|
snprintf (tmp, j, "net.%s", service);
|
|
if (rc_service_exists (tmp) && rc_allow_plug (tmp))
|
|
rc_mark_service (tmp, rc_service_coldplugged);
|
|
CHAR_FREE (tmp);
|
|
}
|
|
rc_strlist_free (start_services);
|
|
|
|
/* The mice are a little more tricky.
|
|
If we coldplug anything else, we'll probably do it here. */
|
|
start_services = rc_ls_dir (NULL, "/dev", 0);
|
|
STRLIST_FOREACH (start_services, service, i)
|
|
{
|
|
if (strncmp (service, "psm", 3) == 0 ||
|
|
strncmp (service, "ums", 3) == 0)
|
|
{
|
|
char *p = service + 3;
|
|
if (p && isdigit (*p))
|
|
{
|
|
j = (strlen ("moused.") + strlen (service) + 1);
|
|
tmp = rc_xmalloc (sizeof (char *) * j);
|
|
snprintf (tmp, j, "moused.%s", service);
|
|
if (rc_service_exists (tmp) && rc_allow_plug (tmp))
|
|
rc_mark_service (tmp, rc_service_coldplugged);
|
|
CHAR_FREE (tmp);
|
|
}
|
|
}
|
|
}
|
|
rc_strlist_free (start_services);
|
|
start_services = NULL;
|
|
}
|
|
#endif
|
|
|
|
/* Build a list of all services to stop and then work out the
|
|
correct order for stopping them */
|
|
stop_services = rc_ls_dir (stop_services, RC_SVCDIR_STARTING, RC_LS_INITD);
|
|
stop_services = rc_ls_dir (stop_services, RC_SVCDIR_INACTIVE, RC_LS_INITD);
|
|
stop_services = rc_ls_dir (stop_services, RC_SVCDIR_STARTED, RC_LS_INITD);
|
|
|
|
types = rc_strlist_add (NULL, "ineed");
|
|
types = rc_strlist_add (types, "iuse");
|
|
types = rc_strlist_add (types, "iafter");
|
|
deporder = rc_get_depends (deptree, types, stop_services,
|
|
runlevel, depoptions);
|
|
rc_strlist_free (stop_services);
|
|
rc_strlist_free (types);
|
|
stop_services = deporder;
|
|
deporder = NULL;
|
|
types = NULL;
|
|
rc_strlist_reverse (stop_services);
|
|
|
|
/* Load our list of coldplugged services */
|
|
coldplugged_services = rc_ls_dir (coldplugged_services,
|
|
RC_SVCDIR_COLDPLUGGED, RC_LS_INITD);
|
|
|
|
/* Load our start services now.
|
|
We have different rules dependent on runlevel. */
|
|
if (newlevel && strcmp (newlevel, RC_LEVEL_BOOT) == 0)
|
|
{
|
|
if (coldplugged_services)
|
|
{
|
|
einfon ("Device initiated services:");
|
|
STRLIST_FOREACH (coldplugged_services, service, i)
|
|
{
|
|
printf (" %s", service);
|
|
start_services = rc_strlist_add (start_services, service);
|
|
}
|
|
printf ("\n");
|
|
}
|
|
tmp = rc_strcatpaths (RC_RUNLEVELDIR, newlevel ? newlevel : runlevel,
|
|
(char *) NULL);
|
|
start_services = rc_ls_dir (start_services, tmp, RC_LS_INITD);
|
|
CHAR_FREE (tmp);
|
|
}
|
|
else
|
|
{
|
|
/* Store our list of coldplugged services */
|
|
coldplugged_services = rc_ls_dir (coldplugged_services, RC_SVCDIR_COLDPLUGGED,
|
|
RC_LS_INITD);
|
|
if (strcmp (newlevel ? newlevel : runlevel, RC_LEVEL_SINGLE) != 0 &&
|
|
strcmp (newlevel ? newlevel : runlevel, RC_LEVEL_SHUTDOWN) != 0 &&
|
|
strcmp (newlevel ? newlevel : runlevel, RC_LEVEL_REBOOT) != 0)
|
|
{
|
|
/* We need to include the boot runlevel services if we're not in it */
|
|
start_services = rc_ls_dir (start_services, RC_RUNLEVELDIR RC_LEVEL_BOOT,
|
|
RC_LS_INITD);
|
|
STRLIST_FOREACH (coldplugged_services, service, i)
|
|
start_services = rc_strlist_add (start_services, service);
|
|
|
|
tmp = rc_strcatpaths (RC_RUNLEVELDIR,
|
|
newlevel ? newlevel : runlevel, (char *) NULL);
|
|
start_services = rc_ls_dir (start_services, tmp, RC_LS_INITD);
|
|
CHAR_FREE (tmp);
|
|
}
|
|
}
|
|
|
|
/* Save out softlevel now */
|
|
if (going_down)
|
|
rc_set_runlevel (newlevel);
|
|
|
|
types = rc_strlist_add (NULL, "needsme");
|
|
types = rc_strlist_add (types, "usesme");
|
|
/* Now stop the services that shouldn't be running */
|
|
STRLIST_FOREACH (stop_services, service, i)
|
|
{
|
|
bool found = false;
|
|
char *conf = NULL;
|
|
char **stopdeps = NULL;
|
|
char *svc1 = NULL;
|
|
char *svc2 = NULL;
|
|
int k;
|
|
|
|
if (rc_service_state (service, rc_service_stopped))
|
|
continue;
|
|
|
|
/* We always stop the service when in these runlevels */
|
|
if (going_down)
|
|
{
|
|
rc_stop_service (service);
|
|
continue;
|
|
}
|
|
|
|
/* If we're in the start list then don't bother stopping us */
|
|
STRLIST_FOREACH (start_services, svc1, j)
|
|
if (strcmp (svc1, service) == 0)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
/* Unless we would use a different config file */
|
|
if (found)
|
|
{
|
|
if (! newlevel)
|
|
continue;
|
|
|
|
tmp = rc_xmalloc (strlen (service) + strlen (runlevel) + 2);
|
|
sprintf (tmp, "%s.%s", service, runlevel);
|
|
conf = rc_strcatpaths (RC_CONFDIR, tmp, (char *) NULL);
|
|
found = rc_exists (conf);
|
|
CHAR_FREE (conf);
|
|
CHAR_FREE (tmp);
|
|
if (! found)
|
|
{
|
|
tmp = rc_xmalloc (strlen (service) + strlen (newlevel) + 2);
|
|
sprintf (tmp, "%s.%s", service, newlevel);
|
|
conf = rc_strcatpaths (RC_CONFDIR, tmp, (char *) NULL);
|
|
found = rc_exists (conf);
|
|
CHAR_FREE (conf);
|
|
CHAR_FREE (tmp);
|
|
if (!found)
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
/* Allow coldplugged services not to be in the runlevels list */
|
|
{
|
|
if (rc_service_state (service, rc_service_coldplugged))
|
|
continue;
|
|
}
|
|
|
|
/* We got this far! Or last check is to see if any any service that
|
|
going to be started depends on us */
|
|
stopdeps = rc_strlist_add (stopdeps, service);
|
|
deporder = rc_get_depends (deptree, types, stopdeps,
|
|
runlevel, RC_DEP_STRICT);
|
|
rc_strlist_free (stopdeps);
|
|
stopdeps = NULL;
|
|
found = false;
|
|
STRLIST_FOREACH (deporder, svc1, j)
|
|
{
|
|
STRLIST_FOREACH (start_services, svc2, k)
|
|
if (strcmp (svc1, svc2) == 0)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
if (found)
|
|
break;
|
|
}
|
|
rc_strlist_free (deporder);
|
|
deporder = NULL;
|
|
|
|
/* After all that we can finally stop the blighter! */
|
|
if (! found)
|
|
rc_stop_service (service);
|
|
}
|
|
rc_strlist_free (types);
|
|
types = NULL;
|
|
|
|
/* Wait for our services to finish */
|
|
if (rc_is_env ("RC_PARALLEL_STARTUP", "yes"))
|
|
wait_for_services ();
|
|
|
|
/* Notify the plugins we have finished */
|
|
rc_plugin_run (rc_hook_runlevel_stop_out, runlevel);
|
|
|
|
rmdir (RC_SVCDIR "/softscripts.new");
|
|
|
|
/* Store the new runlevel */
|
|
if (newlevel)
|
|
{
|
|
rc_set_runlevel (newlevel);
|
|
runlevel = newlevel;
|
|
setenv ("RC_SOFTLEVEL", runlevel, 1);
|
|
}
|
|
|
|
/* Run the halt script if needed */
|
|
if (strcmp (runlevel, RC_LEVEL_SHUTDOWN) == 0 ||
|
|
strcmp (runlevel, RC_LEVEL_REBOOT) == 0)
|
|
{
|
|
mycmd = rc_xstrdup (HALTSH);
|
|
myarg = rc_xstrdup (runlevel);
|
|
execl (mycmd, mycmd, myarg, (char *) NULL);
|
|
eerrorx ("%s: unable to exec `%s': %s",
|
|
applet, HALTSH, strerror (errno));
|
|
}
|
|
|
|
/* Single user is done now */
|
|
if (strcmp (runlevel, RC_LEVEL_SINGLE) == 0)
|
|
{
|
|
if (rc_exists (INTERACTIVE))
|
|
unlink (INTERACTIVE);
|
|
sulogin (false);
|
|
}
|
|
|
|
mkdir (RC_SVCDIR "/softscripts.old", 0755);
|
|
rc_plugin_run (rc_hook_runlevel_start_in, runlevel);
|
|
|
|
/* Re-add our coldplugged services if they stopped */
|
|
STRLIST_FOREACH (coldplugged_services, service, i)
|
|
rc_mark_service (service, rc_service_coldplugged);
|
|
|
|
/* Order the services to start */
|
|
types = rc_strlist_add (NULL, "ineed");
|
|
types = rc_strlist_add (types, "iuse");
|
|
types = rc_strlist_add (types, "iafter");
|
|
deporder = rc_get_depends (deptree, types, start_services,
|
|
runlevel, depoptions);
|
|
rc_strlist_free (types);
|
|
types = NULL;
|
|
rc_strlist_free (start_services);
|
|
start_services = deporder;
|
|
deporder = NULL;
|
|
|
|
STRLIST_FOREACH (start_services, service, i)
|
|
{
|
|
if (rc_service_state (service, rc_service_stopped))
|
|
{
|
|
if (! interactive)
|
|
interactive = want_interactive ();
|
|
|
|
if (interactive)
|
|
{
|
|
interactive_retry:
|
|
printf ("\n");
|
|
einfo ("About to start the service %s", service);
|
|
eindent ();
|
|
einfo ("1) Start the service\t\t2) Skip the service");
|
|
einfo ("3) Continue boot process\t\t4) Exit to shell");
|
|
eoutdent ();
|
|
interactive_option:
|
|
switch (read_key (true))
|
|
{
|
|
case '1': break;
|
|
case '2': continue;
|
|
case '3': interactive = false; break;
|
|
case '4': sulogin (true); goto interactive_retry;
|
|
default: goto interactive_option;
|
|
}
|
|
}
|
|
rc_start_service (service);
|
|
}
|
|
}
|
|
|
|
/* Wait for our services to finish */
|
|
if (rc_is_env ("RC_PARALLEL_STARTUP", "yes"))
|
|
wait_for_services ();
|
|
|
|
rc_plugin_run (rc_hook_runlevel_start_out, runlevel);
|
|
|
|
/* Store our interactive status for boot */
|
|
if (interactive && strcmp (runlevel, RC_LEVEL_BOOT) == 0)
|
|
mark_interactive ();
|
|
else
|
|
{
|
|
if (rc_exists (INTERACTIVE))
|
|
unlink (INTERACTIVE);
|
|
}
|
|
|
|
return (EXIT_SUCCESS);
|
|
}
|
|
|