diff --git a/README.rst b/README.rst index e8cac5ec..1e539681 100644 --- a/README.rst +++ b/README.rst @@ -60,6 +60,31 @@ runs a directory of scripts. This replaces the earlier ``/usr/sbin/services.sh`` support of the original finit_. +Runlevels +--------- + +Very simple support for runlevels_ exist. Currently the system will +always start in runlevel 3 and all services belonging to that runlevel +will be started at boot. + +To specify an allowed set of runlevels for a service, add [NNN] to its +service stanza in finit.conf, like this: + + service [345] /sbin/klogd -n -x -- Kernel log daemon + +Meaning that klogd is allowed to run in runlevels 3, 4 and 5, only. + + service [!06] /sbin/klogd -n -x -- Kernel log daemon + +Meaning all runlevels *but* 0 and 6. + +Existing finit.conf files that lack runlevel setting will get a default +runlevel assigned, [345]. + +Switching between runlevels can be done by calling init with a single +argument, e.g., 'init 5' switches to runlevel 5. + + Rebooting and Halting --------------------- @@ -191,5 +216,6 @@ Regards .. _fastinit: http://wiki.eeeuser.com/boot_process:the_boot_process .. _`SysV init`: http://savannah.nongnu.org/projects/sysvinit .. _upstart: http://upstart.ubuntu.com/ +.. _runlevels: http://en.wikipedia.org/wiki/Runlevel .. _openrc: http://www.gentoo.org/proj/en/base/openrc/ .. _`finit-1.3.tar.xz`: http://github.com/downloads/troglobit/finit/finit-1.3.tar.xz diff --git a/finit.c b/finit.c index e519ac63..13750690 100644 --- a/finit.c +++ b/finit.c @@ -35,14 +35,16 @@ #include "sig.h" #include "lite.h" -int debug = 0; -int verbose = 1; -char *sdown = NULL; -char *network = NULL; -char *username = NULL; -char *hostname = NULL; -char *rcsd = NULL; -char *console = NULL; +int debug = 0; +int verbose = 1; +int runlevel = 3; +int prevlevel = 1; +char *sdown = NULL; +char *network = NULL; +char *username = NULL; +char *hostname = NULL; +char *rcsd = NULL; +char *console = NULL; static void parse_kernel_cmdline(void) { @@ -63,6 +65,37 @@ static void parse_kernel_cmdline(void) } } +static int client(int argc, char *argv[]) +{ + int fd; + struct init_request rq = { + .magic = INIT_MAGIC, + .cmd = INIT_CMD_RUNLVL, + }; + + if (!fexist(FINIT_FIFO)) { + fprintf(stderr, "/sbin/init does not support %s!\n", FINIT_FIFO); + return 1; + } + + if (argc < 2) { + fprintf(stderr, "Missing argument.\n"); + return 1; + } + + fd = open(FINIT_FIFO, O_WRONLY); + if (-1 == fd) { + perror("Failed opening " FINIT_FIFO); + return 1; + } + + rq.runlevel = (int)argv[1][0]; + write(fd, &rq, sizeof(rq)); + close(fd); + + return 0; +} + static int run_loop(void) { while (1) { @@ -82,8 +115,11 @@ static void banner(void) #endif } -int main(int UNUSED(args), char* UNUSED(argv[])) +int main(int argc, char* argv[]) { + if (getpid() != 1) + return client(argc, argv); + /* * Initial setup of signals, ignore all until we're up. */ diff --git a/finit.h b/finit.h index 525562e6..4bd8349c 100644 --- a/finit.h +++ b/finit.h @@ -62,6 +62,19 @@ #define USERNAME_SIZE 16 #define HOSTNAME_SIZE 32 +#define INIT_MAGIC 0x03091969 +#define INIT_CMD_RUNLVL 1 + +struct init_request { + int magic; /* Magic number */ + int cmd; /* What kind of request */ + int runlevel; /* Runlevel to change to */ + int sleeptime; /* Time between TERM and KILL */ + char data[368]; +}; + +extern int runlevel; +extern int prevlevel; extern char *sdown; extern char *network; extern char *hostname; diff --git a/plugin.c b/plugin.c index d9971e01..13ea7d73 100644 --- a/plugin.c +++ b/plugin.c @@ -189,15 +189,20 @@ void plugin_run_hooks(hook_point_t no) } /* Generic libev I/O callback, looks up correct plugin and calls its callback */ -static void generic_io_cb(int fd, int events) +static void generic_io_cb(struct pollfd *io) { plugin_t *p, *tmp; /* Find matching plugin, pick first matching fd */ PLUGIN_ITERATOR(p, tmp) { - if (is_io_plugin(p) && p->io.fd == fd) { + if (is_io_plugin(p) && p->io.fd == io->fd) { _d("Calling I/O %s from runloop...", basename(p->name)); - p->io.cb(p->io.arg, fd, events); + p->io.cb(p->io.arg, io->fd, io->events); + + /* Update fd, may be changed by plugin callback, e.g., if FIFO */ + io->fd = p->io.fd; + io->events = p->io.flags; + io->revents = 0; break; } } @@ -221,7 +226,7 @@ void plugin_monitor(void) /* Traverse all I/O fds and run callbacks */ for (i = 0; i < num_fds; i++) { if (fds[i].revents) - generic_io_cb(fds[i].fd, fds[i].revents); + generic_io_cb(&fds[i]); } break; diff --git a/plugins/initctl.c b/plugins/initctl.c index f5e97dbb..c0e7bd6d 100644 --- a/plugins/initctl.c +++ b/plugins/initctl.c @@ -32,78 +32,96 @@ #include "helpers.h" #include "plugin.h" #include "sig.h" +#include "finit.h" -#define INIT_MAGIC 0x03091969 -#define INIT_CMD_RUNLVL 1 +static void parse(void *arg, int fd, int events); -struct init_request { - int magic; /* Magic number */ - int cmd; /* What kind of request */ - int runlevel; /* Runlevel to change to */ - int sleeptime; /* Time between TERM and KILL */ - char data[368]; +static plugin_t plugin = { + .io = { + .cb = parse, + .flags = PLUGIN_IO_READ, + }, }; +static void fifo_open(void) +{ + plugin.io.fd = open(FINIT_FIFO, O_RDONLY | O_NONBLOCK); + if (-1 == plugin.io.fd) { + _e("Failed opening %s FIFO, error %d: %s", FINIT_FIFO, errno, strerror(errno)); + return; + } +} + /* Standard reboot/shutdown utilities talk to init using /dev/initctl. * We should check if the fifo was recreated and reopen it. */ -static void read_initctl(void *UNUSED(arg), int fd, int UNUSED(events)) +static void parse(void *UNUSED(arg), int fd, int UNUSED(events)) { - struct init_request request; + struct init_request rq; _d("Receiving request on %s...", FINIT_FIFO); while (1) { - ssize_t len = read(fd, &request, sizeof(request)); + ssize_t len = read(fd, &rq, sizeof(rq)); - if (-1 == len) { - _d("Error %d", errno); - break; /* Probably EAGAIN */ + if (len <= 0) { + if (-1 == len) { + if (EINTR == errno) + continue; + + if (EAGAIN == errno) + break; + + _e("Failed reading initctl request, error %d: %s", errno, strerror(errno)); + } + break; } - if (request.magic != INIT_MAGIC) - continue; + if (rq.magic != INIT_MAGIC || len != sizeof(rq)) { + _e("Invalid initctl request."); + break; + } _d("Magic OK..."); - if (request.cmd == INIT_CMD_RUNLVL) { - switch (request.runlevel) { + if (rq.cmd == INIT_CMD_RUNLVL) { + switch (rq.runlevel) { case '0': _d("Halting system (SIGUSR2)"); do_shutdown(SIGUSR2); break; + case 's': + case 'S': + rq.runlevel = '1'; + /* Fall through to regular processing */ + case '1'...'5': + _d("Setting new runlevel %c ...", rq.runlevel); + svc_runlevel(rq.runlevel - '0'); + break; + case '6': _d("Rebooting system (SIGUSR1)"); do_shutdown(SIGUSR1); break; default: - _d("Unsupported runlevel: %d", request.runlevel); + _d("Unsupported runlevel: %d", rq.runlevel); break; } } else { - _d("Unsupported cmd: %d", request.cmd); + _d("Unsupported cmd: %d", rq.cmd); } } -} -static plugin_t plugin = { - .io = { - .cb = read_initctl, - .flags = PLUGIN_IO_READ, - }, -}; + close(fd); + fifo_open(); +} PLUGIN_INIT(plugin_init) { _d("Setting up %s", FINIT_FIFO); mkfifo(FINIT_FIFO, 0600); - plugin.io.fd = open(FINIT_FIFO, O_RDONLY | O_NONBLOCK); - if (-1 == plugin.io.fd) { - _e("Failed opening %s FIFO, error %d: %s", FINIT_FIFO, errno, strerror(errno)); - return; - } - + fifo_open(); plugin_register(&plugin); } diff --git a/svc.c b/svc.c index e22816e7..fda7de22 100644 --- a/svc.c +++ b/svc.c @@ -32,6 +32,10 @@ #include "lite.h" #include "svc.h" +#define ISSET(a,i) ((a & (1 << (i)) ? 1 : 0)) +#define SETBIT(a,i) (a |= (1 << (i))) +#define CLRBIT(a,i) (a &= ~(1 << (i))) + /* The registered services are kept in shared memory for easy read-only access * by 3rd party APIs. Mostly because of the ability to, from the outside, query * liveness state of all daemons that should run. */ @@ -121,6 +125,44 @@ svc_t *svc_iterator(int restart) return &services[i++]; } +/** + * svc_runlevel - Change to a new runlevel + * @newlevel: New runlevel to activate + * + * Stops all services not in @newlevel and starts, or lets continue to run, + * those in @newlevel. Also updates @prevlevel and active @runlevel. + */ +void svc_runlevel (int newlevel) +{ + svc_t *svc; + + if (runlevel == newlevel) + return; + + if (newlevel > 5 || newlevel < 1) + return; + + prevlevel = runlevel; + runlevel = newlevel; + + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + int run = svc_enabled(svc, 0, NULL); + + if (svc->pid) { + if (run == SVC_STOP) + svc_stop(svc); + } else { + if (run == SVC_START) + svc_start(svc); + } + } + + if (runlevel == 1) + touch("/etc/nologin"); /* Disable login in single-user mode */ + else + remove("/etc/nologin"); +} + /** * svc_register - Register a non-backgrounding service to be monitored * @line: A complete command line with -- separated description text @@ -129,18 +171,18 @@ svc_t *svc_iterator(int restart) * Actually, the @line can optionally start with a username, denoted by * an @ character. Like this: * - * "service [@username] /path/to/daemon -- Description text" + * "service @username [!0-6] /path/to/daemon -- Description text" * - * The [] brackets means the argument is optional. If the username is - * left out the daemon is started as root. + * If the username is left out the daemon is started as root. The [] + * brackets are there to denote the allowed runlevels. * * Returns: * POSIX OK(0) on success, or non-zero errno exit status on failure. */ int svc_register(char *line, char *username) { - int i = 0; - char *cmd, *desc; + int i = 0, not = 0; + char *cmd, *desc, *runlevels = NULL; svc_t *svc; if (!line) { @@ -159,8 +201,13 @@ int svc_register(char *line, char *username) return errno = ENOENT; } - if (cmd[0] == '@') { /* @username */ - username = &cmd[1]; + while (cmd) { + if (cmd[0] == '@') /* @username */ + username = &cmd[1]; + else if (cmd[0] == '[') /* [runlevels] */ + runlevels = &cmd[0]; + else + break; /* Check if valid command follows... */ cmd = strtok(NULL, " "); @@ -170,7 +217,7 @@ int svc_register(char *line, char *username) svc = svc_new(); if (!svc) { - _e("Out of memorym, cannot register service %s", cmd); + _e("Out of memory, cannot register service %s", cmd); return errno = ENOMEM; } @@ -185,6 +232,34 @@ int svc_register(char *line, char *username) strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); svc->args[i][0] = 0; + if (!runlevels) + runlevels = "[234]"; + i = 1; + while (i) { + int level; + char lvl = runlevels[i++]; + + if (']' == lvl || 0 == lvl) + break; + if ('!' == lvl) { + not = 1; + svc->runlevels = 0x7E; + continue; + } + if ('s' == lvl || 'S' == lvl) + lvl = '1'; + + level = lvl - '0'; + if (level > 6 || level < 0) + continue; + + if (not) + CLRBIT(svc->runlevels, level); + else + SETBIT(svc->runlevels, level); + } + _d("Service %s runlevel 0x%2x", svc->cmd, svc->runlevels); + return 0; } @@ -210,6 +285,9 @@ svc_cmd_t svc_enabled(svc_t *svc, int event, void *arg) return SVC_STOP; } + if (!ISSET(svc->runlevels, runlevel)) + return SVC_STOP; + /* Is there a service plugin registered? */ if (svc->cb) return svc->cb(svc, event, arg); @@ -333,7 +411,7 @@ int svc_start(svc_t *svc) int fd; char buf[CMD_SIZE] = ""; - fd = open (CONSOLE, O_WRONLY | O_APPEND); + fd = open(CONSOLE, O_WRONLY | O_APPEND); if (-1 != fd) { dup2(STDOUT_FILENO, fd); dup2(STDERR_FILENO, fd); @@ -389,10 +467,12 @@ int svc_stop(svc_t *svc) return 1; } - print_desc("Stopping ", svc->desc); + if (runlevel != 1) + print_desc("Stopping ", svc->desc); _d("Sending SIGTERM to pid:%d name:'%s'", svc->pid, pid_get_name(svc->pid, NULL, 0)); res = kill(svc->pid, SIGTERM); - print_result(res); + if (runlevel != 1) + print_result(res); svc->pid = 0; svc->stat_restart_counter = 0; diff --git a/svc.h b/svc.h index 63a16b41..b2ccef56 100644 --- a/svc.h +++ b/svc.h @@ -50,6 +50,7 @@ typedef enum { typedef struct svc { pid_t pid; int reload; + int runlevels; unsigned int stat_restart_counter; /* Incremented for each restart by service monitor. */ char cmd[MAX_ARG_LEN]; char args[MAX_NUM_SVC_ARGS][MAX_ARG_LEN]; @@ -87,6 +88,7 @@ static inline svc_t *finit_svc_connect(void) svc_t *svc_new (void); svc_t *svc_find (char *name); svc_t *svc_iterator (int restart); +void svc_runlevel (int newlevel); int svc_register (char *line, char *username); int svc_id_by_name (char *name);