diff --git a/conf.c b/conf.c index 8d98e970..358aad02 100644 --- a/conf.c +++ b/conf.c @@ -61,20 +61,22 @@ void parse_finit_conf(char *file) if ((fp = fopen(file, "r")) != NULL) { char *x; + const char *err = NULL; _d("Parse %s ...", file); while (!feof(fp)) { if (!fgets(line, sizeof(line), fp)) continue; - chomp(line); _d("conf: %s", line); + chomp(line); /* Skip comments. */ - if (MATCH_CMD(line, "#", x)) { + if (MATCH_CMD(line, "#", x)) continue; - } - /* Do this before mounting / read-write */ + + /* Do this before mounting / read-write + * XXX: Move to plugin which checks /etc/fstab instead */ if (MATCH_CMD(line, "check ", x)) { char *dev = strip_line(x); @@ -126,7 +128,7 @@ void parse_finit_conf(char *file) continue; } if (MATCH_CMD(line, "startx ", x)) { - svc_register(strip_line(x), username); + svc_register(SVC_CMD_SERVICE, strip_line(x), username); continue; } if (MATCH_CMD(line, "shutdown ", x)) { @@ -135,18 +137,39 @@ void parse_finit_conf(char *file) continue; } + /* The desired runlevel to start when leaving + * bootstrap (S). Finit supports 1-9, but most + * systems only use 1-6, where 6 is reserved for + * reboot */ if (MATCH_CMD(line, "runlevel ", x)) { char *token = strip_line(x); - runlevel = strtonum(token, 1, 5, NULL); - if (!runlevel) - runlevel = RUNLEVEL; - if (runlevel < 1 || runlevel > 5) - runlevel = 3; /* Fallback */ + cfglevel = strtonum(token, 1, 9, &err); + if (err) + cfglevel = RUNLEVEL; + if (cfglevel < 1 || cfglevel > 9 || cfglevel == 6) + cfglevel = 2; /* Fallback */ continue; } + + /* Monitored daemon, will be respawned on exit, as + * long as the (optional) service callback returns + * non-zero */ if (MATCH_CMD(line, "service ", x)) { - svc_register(x, NULL); + svc_register(SVC_CMD_SERVICE, x, NULL); + continue; + } + + /* One-shot task, will not be respawned. Only runs if + * the (optional) service callback returns true */ + if (MATCH_CMD(line, "task ", x)) { + svc_register(SVC_CMD_TASK, x, NULL); + continue; + } + + /* Like task but waits for completion, useful w/ [S] */ + if (MATCH_CMD(line, "run ", x)) { + svc_register(SVC_CMD_RUN, x, NULL); continue; } diff --git a/finit.c b/finit.c index ddb8516a..952ec632 100644 --- a/finit.c +++ b/finit.c @@ -38,8 +38,9 @@ int debug = 0; int verbose = 1; -int runlevel = 3; -int prevlevel = 1; +int runlevel = RUNLEVEL_BOOT; /* Bootstrap */ +int cfglevel = RUNLEVEL; /* Fallback if no configured runlevel */ +int prevlevel = 0; /* HALT */ char *sdown = NULL; char *network = NULL; char *username = NULL; @@ -131,6 +132,7 @@ static int client(int argc, char *argv[]) static int run_loop(void) { + _d("Entering main loop ..."); while (1) { svc_monitor(); plugin_monitor(); @@ -204,8 +206,6 @@ int main(int argc, char* argv[]) /* * Mount filesystems */ - _d("Mount filesystems in /etc/fstab ..."); - #ifdef REMOUNT_ROOTFS_RW run("/bin/mount -n -o remount,rw /"); #endif @@ -224,14 +224,17 @@ int main(int argc, char* argv[]) /* Cleanup stale files, if any still linger on. */ run_interactive("rm -rf /tmp/* /var/run/* /var/lock/*", "Cleanup temporary directories"); - /* - * Base FS up, enable standard SysV init signals - */ + /* Base FS up, enable standard SysV init signals */ sig_setup(); _d("Base FS up, calling hooks ..."); plugin_run_hooks(HOOK_BASEFS_UP); + /* + * Start all bootstrap tasks, no network available! + */ + svc_bootstrap(); + /* * Network stuff */ @@ -247,16 +250,16 @@ int main(int argc, char* argv[]) run_interactive(network, "Starting networking: %s", network); umask(022); - /* - * Hooks that rely on loopback, or basic networking being up. - */ + /* Hooks that rely on loopback, or basic networking being up. */ plugin_run_hooks(HOOK_NETWORK_UP); /* - * Start service monitor framework + * Start all tasks/services in the configured runlevel */ - _d("Starting all static services from %s", FINIT_CONF); - svc_start_all(); + svc_runlevel(cfglevel); + + _d("Running svc up hooks ..."); + plugin_run_hooks(HOOK_SVC_UP); /* * Run startup scripts in /etc/finit.d/, if any. @@ -266,9 +269,7 @@ int main(int argc, char* argv[]) run_parts(rcsd, NULL); } - /* - * Hooks that should run at the very end - */ + /* Hooks that should run at the very end */ plugin_run_hooks(HOOK_SYSTEM_UP); /* Start GETTY on console(s) */ @@ -277,7 +278,6 @@ int main(int argc, char* argv[]) /* * Enter main loop to monior /dev/initctl and services */ - _d("Entering main loop ..."); return run_loop(); } diff --git a/finit.h b/finit.h index 277563cf..b768fcc5 100644 --- a/finit.h +++ b/finit.h @@ -73,6 +73,7 @@ struct init_request { }; extern int runlevel; +extern int cfglevel; extern int prevlevel; extern char *sdown; extern char *network; diff --git a/helpers.c b/helpers.c index 6cd34b12..f661ebc8 100644 --- a/helpers.c +++ b/helpers.c @@ -309,7 +309,7 @@ int procname_kill (char *name, int signo) } -static int print_uptime(void) +static int print_timestamp(void) { #if defined(CONFIG_PRINTK_TIME) FILE * uptimefile; @@ -332,12 +332,10 @@ static int print_uptime(void) void print_desc(char *action, char *desc) { - const char home[] = "\r\e[K"; const char dots[] = " ....................................................................."; - write(STDERR_FILENO, home, strlen(home)); - - print_uptime(); + delline(); + print_timestamp(); write(STDERR_FILENO, action, strlen(action)); write(STDERR_FILENO, desc, strlen(desc)); @@ -354,6 +352,25 @@ int print_result(int fail) return fail; } +/* Wait for process completion, returns status of waitpid(2) syscall */ +int complete(char *cmd, int pid) +{ + int status = 0; + + if (waitpid(pid, &status, 0) == -1) { + if (errno == EINTR) + _e("Caught unblocked signal waiting for %s, aborting.", cmd); + else if (errno == ECHILD) + _e("Caught SIGCHLD waiting for %s, aborting.", cmd); + else + _e("Failed starting %s, error %d: %s", cmd, errno, strerror (errno)); + + return -1; + } + + return status; +} + int run(char *cmd) { int status, result, i = 0; @@ -423,14 +440,8 @@ int run(char *cmd) return -1; } - if (waitpid(pid, &status, 0) == -1) { - if (errno == EINTR) - _e("Caught unblocked signal waiting for %s, aborting.", args[0]); - else if (errno == ECHILD) - _e("Caught SIGCHLD waiting for %s, aborting.", args[0]); - else - _e("Failed starting %s, error %d: %s", args[0], errno, strerror (errno)); - + status = complete(args[0], pid); + if (-1 == status) { if (fp) fclose(fp); free(backup); diff --git a/helpers.h b/helpers.h index 80c0110a..492fef50 100644 --- a/helpers.h +++ b/helpers.h @@ -97,6 +97,7 @@ int getuser (char *username); int getgroup (char *group); void set_hostname (char *hostname); +int complete (char *cmd, int pid); int run (char *cmd); int run_interactive (char *cmd, char *fmt, ...); pid_t run_getty (char *cmd, int console); diff --git a/plugins/initctl.c b/plugins/initctl.c index 96dec18b..37a059f9 100644 --- a/plugins/initctl.c +++ b/plugins/initctl.c @@ -91,10 +91,13 @@ static void parse(void *UNUSED(arg), int fd, int UNUSED(events)) case 's': case 'S': + _d("Cannot enter bootstrap after boot ..."); rq.runlevel = '1'; /* Fall through to regular processing */ + case '1'...'5': - _d("Setting new runlevel %c ...", rq.runlevel); + case '7'...'9': + _d("Setting new runlevel %c", rq.runlevel); svc_runlevel(rq.runlevel - '0'); break; diff --git a/private.h b/private.h index 49558eb0..ec8048a6 100644 --- a/private.h +++ b/private.h @@ -1,7 +1,7 @@ /* Private header file for main finit daemon, not for plugins * * Copyright (c) 2008-2010 Claudio Matsuoka - * Copyright (c) 2008-2012 Joachim Nilsson + * Copyright (c) 2008-2013 Joachim Nilsson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,7 +28,7 @@ #include "svc.h" #include "plugin.h" -void svc_start_all (void); +void svc_bootstrap (void); void svc_monitor (void); void plugin_run_hooks (hook_point_t no); diff --git a/svc.c b/svc.c index fda7de22..b0e0c018 100644 --- a/svc.c +++ b/svc.c @@ -1,7 +1,7 @@ -/* Finit service monitor and heneric API for managing svc_t structures +/* Finit service monitor, task starter and generic API for managing svc_t * * Copyright (c) 2008-2010 Claudio Matsuoka - * Copyright (c) 2008-2012 Joachim Nilsson + * Copyright (c) 2008-2013 Joachim Nilsson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -125,6 +125,25 @@ svc_t *svc_iterator(int restart) return &services[i++]; } +/** + * svc_bootstrap - Start bootstrap services and tasks + * + * System startup, runlevel S, where only services, tasks and + * run commands absolutely essential to bootstrap are located. + */ +void svc_bootstrap(void) +{ + svc_t *svc; + svc_cmd_t cmd; + + _d("Bootstrapping all services in runlevel S from %s", FINIT_CONF); + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + cmd = svc_enabled(svc, 0, NULL); + if (SVC_START == cmd || (SVC_RELOAD == cmd)) + svc_start(svc); + } +} + /** * svc_runlevel - Change to a new runlevel * @newlevel: New runlevel to activate @@ -164,22 +183,36 @@ void svc_runlevel (int newlevel) } /** - * svc_register - Register a non-backgrounding service to be monitored + * svc_register - Register service, task or run commands + * @type: %SVC_CMD_SERVICE(0), %SVC_CMD_TASK(1), %SVC_CMD_RUN(2) * @line: A complete command line with -- separated description text * @username: Optional username to run service as, or %NULL to run as root * - * Actually, the @line can optionally start with a username, denoted by - * an @ character. Like this: + * This function is used to register commands to be run on different + * system runlevels with optional username. The @type argument details + * if it's service to bo monitored/respawned (daemon), a one-shot task + * or a command that must run in sequence and not in parallell, like + * service and task commands do. * - * "service @username [!0-6] /path/to/daemon -- Description text" + * The @line can optionally start with a username, denoted by an @ + * character. Like this: * - * If the username is left out the daemon is started as root. The [] - * brackets are there to denote the allowed runlevels. + * "service @username [!0-6,S] /path/to/daemon arg -- Description text" + * "task @username [!0-6,S] /path/to/task arg -- Description text" + * "run @username [!0-6,S] /path/to/cmd arg -- Description text" + * + * If the username is left out the command is started as root. + + * The [] brackets are there to denote the allowed runlevels. Allowed + * runlevels mimic that of SysV init with the addition of the 'S' + * runlevel, which is only run once at startup. It can be seen as the + * system bootstrap. If a task or run command is listed in more than the + * [S] runlevel they will be called when changing runlevel. * * Returns: * POSIX OK(0) on success, or non-zero errno exit status on failure. */ -int svc_register(char *line, char *username) +int svc_register(int type, char *line, char *username) { int i = 0, not = 0; char *cmd, *desc, *runlevels = NULL; @@ -221,13 +254,17 @@ int svc_register(char *line, char *username) return errno = ENOMEM; } + svc->type = type; + if (desc) strlcpy(svc->desc, desc + 3, sizeof(svc->desc)); + if (username) strlcpy(svc->username, username, sizeof(svc->username)); - strlcpy(svc->cmd, cmd, sizeof(svc->cmd)); - strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); + strlcpy(svc->cmd, cmd, sizeof(svc->cmd)); + + strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); while ((cmd = strtok(NULL, " "))) strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); svc->args[i][0] = 0; @@ -243,14 +280,15 @@ int svc_register(char *line, char *username) break; if ('!' == lvl) { not = 1; - svc->runlevels = 0x7E; + svc->runlevels = 0x3FE; continue; } + if ('s' == lvl || 'S' == lvl) - lvl = '1'; + lvl = ':'; /* RUNLEVEL_BOOT */ level = lvl - '0'; - if (level > 6 || level < 0) + if (level > RUNLEVEL_BOOT || level < 0) continue; if (not) @@ -258,6 +296,7 @@ int svc_register(char *line, char *username) else SETBIT(svc->runlevels, level); } + _d("Service %s runlevel 0x%2x", svc->cmd, svc->runlevels); return 0; @@ -320,12 +359,15 @@ void svc_monitor(void) return; for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - char *name = basename(svc->cmd); - if (lost != svc->pid) continue; - _d("Ouch, lost pid %d - %s(%d)", lost, name, svc->pid); + if (SVC_CMD_SERVICE != svc->type) { + svc->pid = 0; + continue; + } + + _d("Ouch, lost pid %d - %s(%d)", lost, basename(svc->cmd), svc->pid); /* No longer running, update books. */ svc->pid = 0; @@ -336,8 +378,8 @@ void svc_monitor(void) } /* Cleanup any lingering or semi-restarting tasks before respawning */ - _d("Sending SIGTERM to service group %s", name); - procname_kill(name, SIGTERM); + _d("Sending SIGTERM to service group %s", basename(svc->cmd)); + procname_kill(basename(svc->cmd), SIGTERM); /* Restarting lost service. */ if (svc_enabled(svc, 0, NULL)) { @@ -375,7 +417,9 @@ int svc_start(svc_t *svc) if (is_norespawn()) return 0; - if (!respawn) + if (SVC_CMD_SERVICE != svc->type) + print_desc("", svc->desc); + else if (!respawn) print_desc("Starting ", svc->desc); /* Block sigchild while forking. */ @@ -435,7 +479,9 @@ int svc_start(svc_t *svc) } svc->pid = pid; - if (!respawn) + if (SVC_CMD_RUN == svc->type) + print_result(WEXITSTATUS(complete(svc->cmd, pid))); + else if (!respawn) print_result(svc->pid > 1 ? 0 : 1); return 0; @@ -467,6 +513,9 @@ int svc_stop(svc_t *svc) return 1; } + if (SVC_CMD_SERVICE != svc->type) + return 0; + 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)); @@ -500,24 +549,6 @@ int svc_reload(svc_t *svc) return kill(svc->pid, SIGHUP); } -void svc_start_all(void) -{ - svc_t *svc; - svc_cmd_t cmd; - - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - cmd = svc_enabled(svc, 0, NULL); - if (SVC_START == cmd || (SVC_RELOAD == cmd && svc->pid == 0)) - svc_start(svc); - else if (SVC_RELOAD == cmd) - svc_reload(svc); - } - - _d("Running svc up hooks ..."); - plugin_run_hooks(HOOK_SVC_UP); -} - - /** * Local Variables: * compile-command: "gcc -g -DUNITTEST -o unittest svc.c" diff --git a/svc.h b/svc.h index b2ccef56..26d0e632 100644 --- a/svc.h +++ b/svc.h @@ -1,4 +1,4 @@ -/* Finit service monitor and heneric API for managing svc_t structures +/* Finit service monitor and generic API for managing svc_t structures * * Copyright (c) 2008-2010 Claudio Matsuoka * Copyright (c) 2008-2012 Joachim Nilsson @@ -36,6 +36,13 @@ typedef enum { SVC_RELOAD /* Enabled, needs restart */ } svc_cmd_t; +typedef enum { + SVC_CMD_SERVICE = 0, /* Monitored, will be respawned */ + SVC_CMD_TASK, /* One-shot, runs in parallell */ + SVC_CMD_RUN /* Like task, but wait for completion */ +} svc_type_t; + +#define RUNLEVEL_BOOT 10 #define FINIT_SHM_ID 0x494E4954 /* "INIT", see ascii(7) */ #define MAX_ARG_LEN 64 #define MAX_STR_LEN 64 @@ -49,6 +56,7 @@ typedef enum { * initctl service */ typedef struct svc { pid_t pid; + svc_type_t type; int reload; int runlevels; unsigned int stat_restart_counter; /* Incremented for each restart by service monitor. */ @@ -90,7 +98,7 @@ 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_register (int type, char *line, char *username); int svc_id_by_name (char *name); svc_cmd_t svc_enabled (svc_t *svc, int event, void *arg); int svc_start (svc_t *svc);