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 committed 2013-05-19 19:01:44 +02:00
1 parent ec95243dad
commit c3b76fdccf
7 files changed
+237 -57

No files matched your search

+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_. ``/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 Rebooting and Halting
--------------------- ---------------------
@@ -191,5 +216,6 @@ Regards
.. _fastinit: http://wiki.eeeuser.com/boot_process:the_boot_process .. _fastinit: http://wiki.eeeuser.com/boot_process:the_boot_process
.. _`SysV init`: http://savannah.nongnu.org/projects/sysvinit .. _`SysV init`: http://savannah.nongnu.org/projects/sysvinit
.. _upstart: http://upstart.ubuntu.com/ .. _upstart: http://upstart.ubuntu.com/
.. _runlevels: http://en.wikipedia.org/wiki/Runlevel
.. _openrc: http://www.gentoo.org/proj/en/base/openrc/ .. _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 .. _`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 "sig.h"
#include "lite.h" #include "lite.h"
int debug = 0; int debug = 0;
int verbose = 1; int verbose = 1;
char *sdown = NULL; int runlevel = 3;
char *network = NULL; int prevlevel = 1;
char *username = NULL; char *sdown = NULL;
char *hostname = NULL; char *network = NULL;
char *rcsd = NULL; char *username = NULL;
char *console = NULL; char *hostname = NULL;
char *rcsd = NULL;
char *console = NULL;
static void parse_kernel_cmdline(void) 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) static int run_loop(void)
{ {
while (1) { while (1) {
@@ -82,8 +115,11 @@ static void banner(void)
#endif #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. * Initial setup of signals, ignore all until we're up.
*/ */
+13
View File
@@ -62,6 +62,19 @@
#define USERNAME_SIZE 16 #define USERNAME_SIZE 16
#define HOSTNAME_SIZE 32 #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 *sdown;
extern char *network; extern char *network;
extern char *hostname; 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 */ /* 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; plugin_t *p, *tmp;
/* Find matching plugin, pick first matching fd */ /* Find matching plugin, pick first matching fd */
PLUGIN_ITERATOR(p, tmp) { 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)); _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; break;
} }
} }
@@ -221,7 +226,7 @@ void plugin_monitor(void)
/* Traverse all I/O fds and run callbacks */ /* Traverse all I/O fds and run callbacks */
for (i = 0; i < num_fds; i++) { for (i = 0; i < num_fds; i++) {
if (fds[i].revents) if (fds[i].revents)
generic_io_cb(fds[i].fd, fds[i].revents); generic_io_cb(&fds[i]);
} }
break; break;
+51 -33
View File
@@ -32,78 +32,96 @@
#include "helpers.h" #include "helpers.h"
#include "plugin.h" #include "plugin.h"
#include "sig.h" #include "sig.h"
#include "finit.h"
#define INIT_MAGIC 0x03091969 static void parse(void *arg, int fd, int events);
#define INIT_CMD_RUNLVL 1
struct init_request { static plugin_t plugin = {
int magic; /* Magic number */ .io = {
int cmd; /* What kind of request */ .cb = parse,
int runlevel; /* Runlevel to change to */ .flags = PLUGIN_IO_READ,
int sleeptime; /* Time between TERM and KILL */ },
char data[368];
}; };
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. /* Standard reboot/shutdown utilities talk to init using /dev/initctl.
* We should check if the fifo was recreated and reopen it. * 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); _d("Receiving request on %s...", FINIT_FIFO);
while (1) { while (1) {
ssize_t len = read(fd, &request, sizeof(request)); ssize_t len = read(fd, &rq, sizeof(rq));
if (-1 == len) { if (len <= 0) {
_d("Error %d", errno); if (-1 == len) {
break; /* Probably EAGAIN */ 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) if (rq.magic != INIT_MAGIC || len != sizeof(rq)) {
continue; _e("Invalid initctl request.");
break;
}
_d("Magic OK..."); _d("Magic OK...");
if (request.cmd == INIT_CMD_RUNLVL) { if (rq.cmd == INIT_CMD_RUNLVL) {
switch (request.runlevel) { switch (rq.runlevel) {
case '0': case '0':
_d("Halting system (SIGUSR2)"); _d("Halting system (SIGUSR2)");
do_shutdown(SIGUSR2); do_shutdown(SIGUSR2);
break; 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': case '6':
_d("Rebooting system (SIGUSR1)"); _d("Rebooting system (SIGUSR1)");
do_shutdown(SIGUSR1); do_shutdown(SIGUSR1);
break; break;
default: default:
_d("Unsupported runlevel: %d", request.runlevel); _d("Unsupported runlevel: %d", rq.runlevel);
break; break;
} }
} else { } else {
_d("Unsupported cmd: %d", request.cmd); _d("Unsupported cmd: %d", rq.cmd);
} }
} }
}
static plugin_t plugin = { close(fd);
.io = { fifo_open();
.cb = read_initctl, }
.flags = PLUGIN_IO_READ,
},
};
PLUGIN_INIT(plugin_init) PLUGIN_INIT(plugin_init)
{ {
_d("Setting up %s", FINIT_FIFO); _d("Setting up %s", FINIT_FIFO);
mkfifo(FINIT_FIFO, 0600); mkfifo(FINIT_FIFO, 0600);
plugin.io.fd = open(FINIT_FIFO, O_RDONLY | O_NONBLOCK); fifo_open();
if (-1 == plugin.io.fd) {
_e("Failed opening %s FIFO, error %d: %s", FINIT_FIFO, errno, strerror(errno));
return;
}
plugin_register(&plugin); plugin_register(&plugin);
} }
+91 -11
View File
@@ -32,6 +32,10 @@
#include "lite.h" #include "lite.h"
#include "svc.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 /* 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 * by 3rd party APIs. Mostly because of the ability to, from the outside, query
* liveness state of all daemons that should run. */ * liveness state of all daemons that should run. */
@@ -121,6 +125,44 @@ svc_t *svc_iterator(int restart)
return &services[i++]; 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 * svc_register - Register a non-backgrounding service to be monitored
* @line: A complete command line with -- separated description text * @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 * Actually, the @line can optionally start with a username, denoted by
* an @ character. Like this: * 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 * If the username is left out the daemon is started as root. The []
* left out the daemon is started as root. * brackets are there to denote the allowed runlevels.
* *
* Returns: * Returns:
* POSIX OK(0) on success, or non-zero errno exit status on failure. * POSIX OK(0) on success, or non-zero errno exit status on failure.
*/ */
int svc_register(char *line, char *username) int svc_register(char *line, char *username)
{ {
int i = 0; int i = 0, not = 0;
char *cmd, *desc; char *cmd, *desc, *runlevels = NULL;
svc_t *svc; svc_t *svc;
if (!line) { if (!line) {
@@ -159,8 +201,13 @@ int svc_register(char *line, char *username)
return errno = ENOENT; return errno = ENOENT;
} }
if (cmd[0] == '@') { /* @username */ while (cmd) {
username = &cmd[1]; if (cmd[0] == '@') /* @username */
username = &cmd[1];
else if (cmd[0] == '[') /* [runlevels] */
runlevels = &cmd[0];
else
break;
/* Check if valid command follows... */ /* Check if valid command follows... */
cmd = strtok(NULL, " "); cmd = strtok(NULL, " ");
@@ -170,7 +217,7 @@ int svc_register(char *line, char *username)
svc = svc_new(); svc = svc_new();
if (!svc) { if (!svc) {
_e("Out of memorym, cannot register service %s", cmd); _e("Out of memory, cannot register service %s", cmd);
return errno = ENOMEM; return errno = ENOMEM;
} }
@@ -185,6 +232,34 @@ int svc_register(char *line, char *username)
strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); strlcpy(svc->args[i++], cmd, sizeof(svc->args[0]));
svc->args[i][0] = 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; return 0;
} }
@@ -210,6 +285,9 @@ svc_cmd_t svc_enabled(svc_t *svc, int event, void *arg)
return SVC_STOP; return SVC_STOP;
} }
if (!ISSET(svc->runlevels, runlevel))
return SVC_STOP;
/* Is there a service plugin registered? */ /* Is there a service plugin registered? */
if (svc->cb) if (svc->cb)
return svc->cb(svc, event, arg); return svc->cb(svc, event, arg);
@@ -333,7 +411,7 @@ int svc_start(svc_t *svc)
int fd; int fd;
char buf[CMD_SIZE] = ""; char buf[CMD_SIZE] = "";
fd = open (CONSOLE, O_WRONLY | O_APPEND); fd = open(CONSOLE, O_WRONLY | O_APPEND);
if (-1 != fd) { if (-1 != fd) {
dup2(STDOUT_FILENO, fd); dup2(STDOUT_FILENO, fd);
dup2(STDERR_FILENO, fd); dup2(STDERR_FILENO, fd);
@@ -389,10 +467,12 @@ int svc_stop(svc_t *svc)
return 1; 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)); _d("Sending SIGTERM to pid:%d name:'%s'", svc->pid, pid_get_name(svc->pid, NULL, 0));
res = kill(svc->pid, SIGTERM); res = kill(svc->pid, SIGTERM);
print_result(res); if (runlevel != 1)
print_result(res);
svc->pid = 0; svc->pid = 0;
svc->stat_restart_counter = 0; svc->stat_restart_counter = 0;
+2
View File
@@ -50,6 +50,7 @@ typedef enum {
typedef struct svc { typedef struct svc {
pid_t pid; pid_t pid;
int reload; int reload;
int runlevels;
unsigned int stat_restart_counter; /* Incremented for each restart by service monitor. */ unsigned int stat_restart_counter; /* Incremented for each restart by service monitor. */
char cmd[MAX_ARG_LEN]; char cmd[MAX_ARG_LEN];
char args[MAX_NUM_SVC_ARGS][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_new (void);
svc_t *svc_find (char *name); svc_t *svc_find (char *name);
svc_t *svc_iterator (int restart); svc_t *svc_iterator (int restart);
void svc_runlevel (int newlevel);
int svc_register (char *line, char *username); int svc_register (char *line, char *username);
int svc_id_by_name (char *name); int svc_id_by_name (char *name);