diff --git a/src/Makefile.am b/src/Makefile.am index e57c3f18..844964d4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,7 +8,7 @@ AM_LDFLAGS = -export-dynamic endif sbin_PROGRAMS = finit initctl -pkglibexec_PROGRAMS = getty logit +pkglibexec_PROGRAMS = getty logit runparts if SULOGIN pkglibexec_PROGRAMS += sulogin endif @@ -32,6 +32,11 @@ keventd_CFLAGS = -W -Wall -Wextra -std=gnu99 keventd_CFLAGS += $(lite_CFLAGS) keventd_LDADD = $(lite_LIBS) +runparts_SOURCES = runparts.c +runparts_CFLAGS = -W -Wall -Wextra -std=gnu99 +runparts_CFLAGS += $(lite_CFLAGS) +runparts_LDADD = $(lite_LIBS) + sulogin_SOURCES = sulogin.c sulogin_CFLAGS = -W -Wall -Wextra -std=gnu99 sulogin_LDADD = -lcrypt @@ -54,7 +59,7 @@ finit_SOURCES = api.c cgroup.c cgroup.h \ mdadm.c mount.c \ pid.c pid.h \ plugin.c plugin.h private.h \ - schedule.c schedule.h \ + runparts.c schedule.c schedule.h \ service.c service.h \ sig.c sig.h \ sm.c sm.h \ diff --git a/src/conf.c b/src/conf.c index 9c7a7045..e2769843 100644 --- a/src/conf.c +++ b/src/conf.c @@ -862,7 +862,7 @@ static int parse_static(char *line, int is_rcsd) service_interval = val * 1000; /* to milliseconds */ if (disabled) - service_init(); + service_init(NULL); } return 0; } @@ -1448,6 +1448,18 @@ int conf_init(uev_ctx_t *ctx) return 1; } + /* + * Background startup scripts in the runparts directory, if any. + */ + if (runparts && fisdir(runparts) && !rescue) { + char conf[sizeof(_PATH_RUNPARTS) + strlen(runparts) + 100]; + + snprintf(conf, sizeof(conf), "[S] %s %s" + " -- Calling runparts %s in the background", + _PATH_RUNPARTS, runparts, runparts); + service_register(SVC_TYPE_TASK, conf, global_rlimit, NULL); + } + return 0; } diff --git a/src/devmon.c b/src/devmon.c index 8ac057b5..87762693 100644 --- a/src/devmon.c +++ b/src/devmon.c @@ -243,7 +243,7 @@ static void devmon_cb(uev_t *w, void *arg, int events) } } -void devmon_init(void) +void devmon_init(uev_ctx_t *ctx) { char dir[MAX_ARG_LEN]; diff --git a/src/devmon.h b/src/devmon.h index 8588fd36..558c64f4 100644 --- a/src/devmon.h +++ b/src/devmon.h @@ -27,6 +27,6 @@ void devmon_add_cond(const char *cond); void devmon_del_cond(const char *cond); -void devmon_init(void); +void devmon_init(uev_ctx_t *ctx); #endif /* FINIT_DEVMON_H_ */ diff --git a/src/exec.c b/src/exec.c index 20d070d7..6d44429d 100644 --- a/src/exec.c +++ b/src/exec.c @@ -24,8 +24,6 @@ #include "config.h" /* Generated by configure script */ -#include /* isdigit() */ -#include #include #include #include @@ -443,96 +441,20 @@ pid_t run_sh(char *tty, int noclear, int nowait, struct rlimit rlimit[]) return rc; } -static void run_env(const char *env[]) +pid_t run_bg(char *cmd, char *args[]) { - size_t i = 0; + pid_t pid; - if (!env) - return; + if (access(cmd, X_OK)) + return 0; - while (env[i]) { - size_t j = i + 1; + print(-1, "Calling %s in the background", cmd); + pid = fork(); + if (!pid) + _exit(exec_runtask(cmd, args)); + print_result(pid == -1); - if (!env[j]) - break; - - setenv(env[i], env[j], 1); - i += 2; - } -} - -int run_parts(char *dir, char *cmd, const char *env[], int progress) -{ - struct dirent **e; - int i, num; - - num = scandir(dir, &e, NULL, alphasort); - if (num < 0) { - dbg("No files found in %s, skipping ...", dir); - return -1; - } - - for (i = 0; i < num; i++) { - const char *name = e[i]->d_name; - char path[strlen(dir) + strlen(name) + 2]; - struct stat st; - char *argv[4] = { - "sh", - "-c", - path, - NULL - }; - pid_t pid = 0; - int status; - - paste(path, sizeof(path), dir, name); - if (stat(path, &st)) { - dbg("Failed stat(%s): %s", path, strerror(errno)); - continue; - } - - if (!S_ISEXEC(st.st_mode) || S_ISDIR(st.st_mode)) { - if (strcmp(name, ".") && strcmp(name, "..")) - dbg("Skipping %s ...", path); - continue; - } - - /* If the callee didn't supply a run_parts() argument */ - if (!cmd) { - /* Check if Sservice or Kservice notation is used */ -// dbg("Checking if %s is a sysvinit startstop script ...", name); - if (name[0] == 'S' && isdigit(name[1])) - strlcat(path, " start", sizeof(path)); - else if (name[0] == 'K' && isdigit(name[1])) - strlcat(path, " stop", sizeof(path)); - } else { - strlcat(path, cmd, sizeof(path)); - } - - if (progress) - print_desc("Calling ", path); - pid = fork(); - if (!pid) { - sig_unblock(); - run_env(env); - - return execvp(_PATH_BSHELL, argv); - } - - status = complete(path, pid); - if (WIFEXITED(status)) - warnx("%s exited with status %d", path, WEXITSTATUS(status)); - else if (WIFSIGNALED(status)) - warnx("%s terminated by signad %d", path, WTERMSIG(status)); - if (progress) - print_result(WEXITSTATUS(status)); - } - - while (num--) - free(e[num]); - free(e); - - return 0; + return pid; } /** diff --git a/src/finit.c b/src/finit.c index 067f6b51..00e17cb4 100644 --- a/src/finit.c +++ b/src/finit.c @@ -58,7 +58,6 @@ #include "tty.h" #include "util.h" #include "utmp-api.h" -#include "schedule.h" int runlevel = INIT_LEVEL; /* Bootstrap 'S' */ int cfglevel = RUNLEVEL; /* Fallback if no configured runlevel */ @@ -521,113 +520,6 @@ static void fs_init(void) } -/* - * Handle bootstrap transition to configured runlevel, start TTYs - * - * This is the final stage of bootstrap. It changes to the default - * (configured) runlevel, calls all external start scripts and final - * bootstrap hooks before bringing up TTYs. - * - * We must ensure that all declared `task [S]` and `run [S]` jobs in - * finit.conf, or *.conf in finit.d/, run to completion before we - * finalize the bootstrap process by calling this function. - */ -static void finalize(void *unused) -{ - /* Clean up bootstrap-only tasks/services that never started */ - dbg("Clean up all bootstrap-only tasks/services ..."); - svc_prune_bootstrap(); - - /* All services/tasks/etc. in configure runlevel have started */ - dbg("Running svc up hooks ..."); - plugin_run_hooks(HOOK_SVC_UP); - service_step_all(SVC_TYPE_ANY); - - /* Convenient SysV compat for when you just don't care ... */ - if (!access(FINIT_RC_LOCAL, X_OK) && !rescue) - run_interactive(FINIT_RC_LOCAL, "Calling %s", FINIT_RC_LOCAL); - - /* Hooks that should run at the very end */ - dbg("Calling all system up hooks ..."); - plugin_run_hooks(HOOK_SYSTEM_UP); - service_step_all(SVC_TYPE_ANY); - - /* Disable progress output at normal runtime */ - enable_progress(0); - - /* System bootrapped, launch TTYs et al */ - bootstrap = 0; - service_step_all(SVC_TYPE_RESPAWN); -} - -/* - * Start cranking the big state machine - */ -static void crank_worker(void *unused) -{ - /* - * Initialize state machine and start all bootstrap tasks - * NOTE: no network available! - */ - sm_init(&sm); - sm_step(&sm); -} - -/* - * Wait for system bootstrap to complete, all SVC_TYPE_RUNTASK must be - * allowed to complete their work in [S], or timeout, before we switch - * to the configured runlevel and call finalize(), should not take more - * than 120 sec. - */ -static void bootstrap_worker(void *work) -{ - static struct wq final = { - .cb = finalize, - .delay = 10 - }; - static int cnt = 120 * 10; /* We run with 100ms period */ - int level = cfglevel; - - dbg("Step all services ..."); - service_step_all(SVC_TYPE_ANY); - - if (cnt-- > 0 && !service_completed()) { - dbg("Not all bootstrap run/tasks have completed yet ... %d", cnt); - schedule_work(work); - return; - } - - if (cnt > 0) - dbg("All run/task have completed, resuming bootstrap."); - else - dbg("Timeout, resuming bootstrap."); - - dbg("Starting runlevel change finalize ..."); - schedule_work(&final); - - /* - * Run startup scripts in the runparts directory, if any. - */ - if (runparts && fisdir(runparts) && !rescue) - run_parts(runparts, NULL, NULL, 1); - - - dbg("Flushing pending .conf file events ..."); - conf_flush_events(); - - /* - * Start all tasks/services in the configured runlevel, or jump - * into the runlevel selected from the command line. - */ - if (cmdlevel) { - dbg("Runlevel %d requested from command line, starting all services ...", cmdlevel); - level = cmdlevel; - } else - dbg("Change to default runlevel(%d), starting all services ...", cfglevel); - - service_runlevel(level); -} - static int version(int rc) { puts(PACKAGE_STRING); @@ -708,14 +600,6 @@ static int telinit(int argc, char *argv[]) int main(int argc, char *argv[]) { - struct wq crank_work = { - .cb = crank_worker, - .delay = 10 - }; - struct wq bootstrap_work = { - .cb = bootstrap_worker, - .delay = 100 - }; uev_ctx_t loop; /* user calling telinit or init */ @@ -819,7 +703,7 @@ int main(int argc, char *argv[]) * Initialize .conf system and load static /etc/finit.conf then * tell the world what we used. */ - devmon_init(); + devmon_init(&loop); conf_init(&loop); conf_saverc(); @@ -832,16 +716,15 @@ int main(int argc, char *argv[]) dbg("Starting initctl API responder ..."); api_init(&loop); - dbg("Starting the big state machine ..."); - schedule_work(&crank_work); - - dbg("Starting bootstrap finalize timer ..."); - schedule_work(&bootstrap_work); + dbg("Starting service interval monitor ..."); + service_init(&loop); /* - * Background service tasks + * Initialize state machine and start all bootstrap tasks + * NOTE: no network available! */ - service_init(); + sm_init(&sm); + sm_step(&sm); /* * Enter main loop to monitor /dev/initctl and services diff --git a/src/finit.h b/src/finit.h index a084693d..b03c01a6 100644 --- a/src/finit.h +++ b/src/finit.h @@ -56,6 +56,7 @@ /* To use these, include config.h, or define FINIT_EXECPATH_ */ #define _PATH_LOGIT FINIT_EXECPATH_ "/logit" +#define _PATH_RUNPARTS FINIT_EXECPATH_ "/runparts" #define _PATH_SULOGIN FINIT_EXECPATH_ "/sulogin" #define _PATH_GETTY FINIT_EXECPATH_ "/getty" diff --git a/src/helpers.h b/src/helpers.h index 397490e8..e930dc65 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -88,6 +88,7 @@ int run_interactive (char *cmd, char *fmt, ...) __attribute__ ((format (prin int exec_runtask (char *cmd, char *args[]); pid_t run_getty (char *tty, char *cmd, char *args[], int noclear, int nowait, struct rlimit rlimit[]); pid_t run_sh (char *tty, int noclear, int nowait, struct rlimit rlimit[]); +pid_t run_bg (char *cmd, char *args[]); int run_parts (char *dir, char *cmd, const char *env[], int progress); /* diff --git a/src/runparts.c b/src/runparts.c new file mode 100644 index 00000000..53e329ea --- /dev/null +++ b/src/runparts.c @@ -0,0 +1,215 @@ +#include /* isdigit() */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _LIBITE_LITE +# include +# include +#else +# include +# include +#endif + +#include "log.h" +#include "sig.h" +#include "util.h" + +#ifdef __FINIT__ +#include "helpers.h" +#else +#include + +int debug; + +static void print_desc(const char *prefix, const char *msg) +{ + printf("\e[1m[\e[0m\e[1;33m ⋯ \e[0m\e[1m]\e[0m %s %s", prefix, msg); +} +static void print_result(int rc) +{ + if (rc) + puts("\r\e[1m[\e[0m\e[1;31mFAIL\e[0m\e[1m]\e[0m"); + else + puts("\r\e[1m[\e[0m\e[1;32m OK \e[0m\e[1m]\e[0m"); +} + +void sig_unblock(void) +{ + struct sigaction sa; + sigset_t nmask; + int i; + + sigemptyset(&nmask); + sigaddset(&nmask, SIGHUP); + sigaddset(&nmask, SIGCHLD); + sigaddset(&nmask, SIGINT); + sigaddset(&nmask, SIGPWR); + sigaddset(&nmask, SIGSTOP); + sigaddset(&nmask, SIGTSTP); + sigaddset(&nmask, SIGCONT); + sigaddset(&nmask, SIGTERM); + sigaddset(&nmask, SIGUSR1); + sigaddset(&nmask, SIGUSR2); + sigprocmask(SIG_UNBLOCK, &nmask, NULL); + + /* Reset signal handlers that were set by the parent process */ + for (i = 1; i < NSIG; i++) + DFLSIG(sa, i, 0); +} +#endif /* __FINIT__ */ + +static void run_env(const char *env[]) +{ + size_t i = 0; + + if (!env) + return; + + while (env[i]) { + size_t j = i + 1; + + if (!env[j]) + break; + + setenv(env[i], env[j], 1); + i += 2; + } +} + +int run_parts(char *dir, char *cmd, const char *env[], int progress) +{ + struct dirent **e; + int i, num; + int rc = 0; + + num = scandir(dir, &e, NULL, alphasort); + if (num < 0) { + dbg("No files found in %s, skipping ...", dir); + return -1; + } + + for (i = 0; i < num; i++) { + const char *name = e[i]->d_name; + char path[strlen(dir) + strlen(name) + 2]; + struct stat st; + char *argv[4] = { + "sh", + "-c", + path, + NULL + }; + int result = 0; + pid_t pid = 0; + int status; + + paste(path, sizeof(path), dir, name); + if (stat(path, &st)) { + dbg("Failed stat(%s): %s", path, strerror(errno)); + continue; + } + + if (!S_ISEXEC(st.st_mode) || S_ISDIR(st.st_mode)) { + if (strcmp(name, ".") && strcmp(name, "..")) + dbg("Skipping %s ...", path); + continue; + } + + /* If the callee didn't supply a run_parts() argument */ + if (!cmd) { + /* Check if Sservice or Kservice notation is used */ +// dbg("Checking if %s is a sysvinit startstop script ...", name); + if (name[0] == 'S' && isdigit(name[1])) + strlcat(path, " start", sizeof(path)); + else if (name[0] == 'K' && isdigit(name[1])) + strlcat(path, " stop", sizeof(path)); + } else { + strlcat(path, cmd, sizeof(path)); + } + + if (progress) + print_desc("Calling ", path); + pid = fork(); + if (!pid) { + sig_unblock(); + run_env(env); + + _exit(execvp(_PATH_BSHELL, argv)); + } + + if (waitpid(pid, &status, 0) == -1) { + warnx("failed starting %s, error %d: %s", path, errno, strerror(errno)); + result = 1; + } else { + if (WIFEXITED(status)) { + result = WEXITSTATUS(status); + warnx("%s exited with status %d", path, result); + } else if (WIFSIGNALED(status)) { + warnx("%s terminated by signal %d", path, WTERMSIG(status)); + result = 1; + } else + result = 1; + } + + if (progress) + print_result(result); + rc += result; + } + + while (num--) + free(e[num]); + free(e); + + return rc; +} + +#ifndef __FINIT__ +static int usage(int rc) +{ + warnx("usage: runparts [-dh?] DIRECTORY"); + return rc; +} + +int main(int argc, char *argv[]) +{ + int rc, c, progress = 0; + char *dir; + + while ((c = getopt(argc, argv, "dh?p")) != EOF) { + switch(c) { + case 'd': + debug = 1; + break; + case 'h': + case '?': + return usage(0); + case 'p': + progress = 1; + break; + default: + return usage(1); + } + } + + if (optind >= argc) + return usage(1); + + dir = argv[optind]; + if (!fisdir(dir)) + return usage(1); + + prctl(PR_SET_CHILD_SUBREAPER, 1); + + rc = run_parts(dir, NULL, NULL, progress); + if (rc == -1) + err(1, "failed run-parts %s", dir); + + return rc; +} +#endif /* __FINIT__ */ diff --git a/src/service.c b/src/service.c index d1da53c4..6171d428 100644 --- a/src/service.c +++ b/src/service.c @@ -2714,23 +2714,24 @@ static void service_interval_cb(uev_t *w, void *arg, int events) } } - service_init(); + service_init(NULL); } /* * The service_interval may change (conf) between invocations, so we * periodically reset the one-shot timer instead of using a periodic. */ -void service_init(void) +void service_init(uev_ctx_t *ctx) { static int initialized = 0; static uev_t watcher; - if (!initialized) { + if (!initialized) uev_timer_init(ctx, &watcher, service_interval_cb, NULL, service_interval, 0); - initialized = 1; - } else + else uev_timer_set(&watcher, service_interval, 0); + + initialized = 1; } /** diff --git a/src/service.h b/src/service.h index 85fd8689..bea5bc27 100644 --- a/src/service.h +++ b/src/service.h @@ -52,7 +52,7 @@ void service_worker (void *unused); int service_completed (void); void service_notify_reconf (void); -void service_init (void); +void service_init (uev_ctx_t *ctx); #endif /* FINIT_SERVICE_H_ */ diff --git a/src/sm.c b/src/sm.c index 472602f5..acc8f4d0 100644 --- a/src/sm.c +++ b/src/sm.c @@ -31,6 +31,7 @@ #include "conf.h" #include "helpers.h" #include "private.h" +#include "schedule.h" #include "service.h" #include "sig.h" #include "tty.h" @@ -43,12 +44,52 @@ sm_t sm; #define FINIT_NOLOGIN_PATH _PATH_NOLOGIN /* Stop user logging in. */ #endif -void sm_init(sm_t *sm) +/* + * Wait for system bootstrap to complete, all SVC_TYPE_RUNTASK must be + * allowed to complete their work in [S], or timeout, before we switch + * to the configured runlevel and call finalize(), should not take more + * than 120 sec. + */ +static void sm_check_bootstrap(void *work) { - sm->state = SM_BOOTSTRAP_STATE; - sm->newlevel = -1; - sm->reload = 0; - sm->in_teardown = 0; + static int cnt = 120 * 10; /* We run with 100ms period */ + int level = cfglevel; + + dbg("Step all services ..."); + service_step_all(SVC_TYPE_ANY); + + if (cnt-- > 0 && !service_completed()) { + dbg("Not all bootstrap run/tasks have completed yet ... %d", cnt); + return; + } + + if (cnt > 0) + dbg("All run/task have completed, resuming bootstrap."); + else + dbg("Timeout, resuming bootstrap."); + + dbg("Flushing pending .conf file events ..."); + conf_flush_events(); + + /* + * Start all tasks/services in the configured runlevel, or jump + * into the runlevel selected from the command line. + */ + if (cmdlevel) { + dbg("Runlevel %d requested from command line, starting all services ...", cmdlevel); + level = cmdlevel; + } else + dbg("Change to default runlevel(%d), starting all services ...", cfglevel); + + service_runlevel(level); + + /* Clean up bootstrap-only tasks/services that never started */ + dbg("Clean up all bootstrap-only tasks/services ..."); + svc_prune_bootstrap(); + + /* All services/tasks/etc. in configure runlevel have started */ + dbg("Running svc up hooks ..."); + plugin_run_hooks(HOOK_SVC_UP); } static char *sm_status(sm_state_t state) @@ -100,6 +141,22 @@ static void nologin(void) erase(FINIT_NOLOGIN_PATH); } +void sm_init(sm_t *sm) +{ + static struct wq work = { + .cb = sm_check_bootstrap, + .delay = 1000 + }; + + sm->state = SM_BOOTSTRAP_STATE; + sm->newlevel = -1; + sm->reload = 0; + sm->in_teardown = 0; + + dbg("Starting bootstrap finalize timer ..."); + schedule_work(&work); +} + void sm_set_runlevel(sm_t *sm, int newlevel) { sm->newlevel = newlevel; @@ -117,8 +174,8 @@ int sm_is_in_teardown(sm_t *sm) void sm_step(sm_t *sm) { - svc_t *svc; sm_state_t old_state; + svc_t *svc; restart: old_state = sm->state; @@ -130,10 +187,49 @@ restart: case SM_BOOTSTRAP_STATE: dbg("Bootstrapping all services in runlevel S from %s", finit_conf); service_step_all(SVC_TYPE_RUNTASK | SVC_TYPE_SERVICE); + + sm->state = SM_BOOTSTRAP_WAIT_STATE; + break; + + /* + * Handle bootstrap transition to configured runlevel, start TTYs + * + * This is the final stage of bootstrap. It changes to the default + * (configured) runlevel, calls all external start scripts and final + * bootstrap hooks before bringing up TTYs. + * + * We must ensure that all declared `task [S]` and `run [S]` jobs in + * finit.conf, or *.conf in finit.d/, run to completion before we + * finalize the bootstrap process by calling this function. + */ + case SM_BOOTSTRAP_WAIT_STATE: + /* We come here from bootstrap, runlevel change and conf reload */ + service_step_all(SVC_TYPE_ANY); + + /* Allow runparts to start */ + cond_set("int/bootstrap"); + + if (sm->newlevel == -1) + break; + + /* Hooks that should run at the very end */ + dbg("Calling all system up hooks ..."); + plugin_run_hooks(HOOK_SYSTEM_UP); + service_step_all(SVC_TYPE_ANY); + + /* Disable progress output at normal runtime */ + enable_progress(0); + + /* System bootrapped, launch TTYs et al */ + bootstrap = 0; + service_step_all(SVC_TYPE_RESPAWN); sm->state = SM_RUNNING_STATE; break; case SM_RUNNING_STATE: + /* We come here from bootstrap, runlevel change and conf reload */ + service_step_all(SVC_TYPE_ANY); + /* runlevel changed? */ if (sm->newlevel >= 0 && sm->newlevel <= 9) { if (runlevel == sm->newlevel) { @@ -143,6 +239,7 @@ restart: sm->state = SM_RUNLEVEL_CHANGE_STATE; break; } + /* reload ? */ if (sm->reload) { sm->reload = 0; @@ -210,17 +307,18 @@ restart: /* Cleanup stale services */ svc_clean_dynamic(service_unregister); + /* Compat SysV init */ + if (prevlevel == INIT_LEVEL && !rescue) + run_bg(FINIT_RC_LOCAL, NULL); + /* * "I've seen things you people wouldn't believe. Attack ships on fire off * the shoulder of Orion. I watched C-beams glitter in the dark near the * Tannhäuser Gate. All those .. moments .. will be lost in time, like * tears ... in ... rain." */ - if (runlevel == 0 || runlevel == 6) { + if (runlevel == 0 || runlevel == 6) do_shutdown(halt); - sm->state = SM_RUNNING_STATE; - break; - } sm->state = SM_RUNNING_STATE; break; @@ -266,9 +364,7 @@ restart: dbg("Update configuration generation of unmodified non-native services ..."); service_notify_reconf(); - service_step_all(SVC_TYPE_ANY); dbg("Reconfiguration done"); - sm->state = SM_RUNNING_STATE; break; } diff --git a/src/sm.h b/src/sm.h index 2e7a52e6..86c5c643 100644 --- a/src/sm.h +++ b/src/sm.h @@ -26,6 +26,7 @@ typedef enum { SM_BOOTSTRAP_STATE = 0, /* Init state, bootstrap services */ + SM_BOOTSTRAP_WAIT_STATE, /* Waiting for bootstrap to complete */ SM_RUNNING_STATE, /* Normal state, services running */ SM_RUNLEVEL_CHANGE_STATE, /* A runlevel change has occurred */ SM_RUNLEVEL_WAIT_STATE, /* Waiting for all stopped runlevel processes to be halted */