Add basic support for runlevels and bugfix FIFO I/O plugins

Like most other init implementations do, when entering runlevel 1, finit
creates the file /etc/nologin to prevent users from logging in during
single-user mode, this file is then removed upon leaving runlevel 1.

Also, bugfix how FIFO I/O plugins are handled in finit.  A FIFO will
need to be reopened by the server side when the client closes the pipe.

Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
This commit is contained in:
Joachim Nilsson
2013-05-19 19:01:44 +02:00
parent ec95243dad
commit c3b76fdccf
7 changed files with 237 additions and 57 deletions
+26
View File
@@ -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
+45 -9
View File
@@ -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.
*/
+13
View File
@@ -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;
+9 -4
View File
@@ -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;
+51 -33
View File
@@ -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);
}
+91 -11
View File
@@ -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;
+2
View File
@@ -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);