diff --git a/Makefile.am b/Makefile.am index adbcc7ad..0e8dabc5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,7 @@ ACLOCAL_AMFLAGS = -I m4 sbin_PROGRAMS = finit initctl reboot finit_SOURCES = finit.c api.c client.c conf.c exec.c helpers.c pid.c \ - sig.c svc.c service.c plugin.c tty.c cond.c cond-w.c + sig.c svc.c service.c plugin.c tty.c cond.c cond-w.c sm.c pkginclude_HEADERS = cond.h finit.h helpers.h inetd.h plugin.h queue.h svc.h if INETD finit_SOURCES += inetd.c diff --git a/finit.c b/finit.c index 51c30bff..7393a84b 100644 --- a/finit.c +++ b/finit.c @@ -39,6 +39,7 @@ #include "tty.h" #include "lite/lite.h" #include "inetd.h" +#include "sm.h" int debug = 0; int quiet = QUIET_MODE; /* Delayed disable of silent mode. */ @@ -53,6 +54,7 @@ char *hostname = NULL; char *rcsd = FINIT_RCSD; char *runparts = NULL; char *console = NULL; +sm_t sm; uev_ctx_t *ctx = NULL; /* Main loop context */ @@ -176,9 +178,10 @@ int main(int argc, char* argv[]) plugin_run_hooks(HOOK_BASEFS_UP); /* - * Start all bootstrap tasks, no network available! + * Initalize finit state machine and start all bootstrap tasks, no network available! */ - service_bootstrap(); + sm_init(&sm); + sm_step(&sm); /* * Network stuff diff --git a/finit.h b/finit.h index 21df1b65..426cba00 100644 --- a/finit.h +++ b/finit.h @@ -33,8 +33,9 @@ #include #include #include +#include -#include "lite/lite.h" +#include "sm.h" #define CMD_SIZE 256 #define LINE_SIZE 1024 @@ -79,6 +80,7 @@ extern char *username; extern char *runparts; extern char *console; extern char *__progname; +extern sm_t sm; #endif /* FINIT_H_ */ diff --git a/plugins/pidfile.c b/plugins/pidfile.c index 9e9e82ec..013f6275 100644 --- a/plugins/pidfile.c +++ b/plugins/pidfile.c @@ -71,7 +71,7 @@ static void pidfile_callback(void *UNUSED(arg), int fd, int UNUSED(events)) _d("%s: match %s", basename, svc->cmd); snprintf(cond, sizeof(cond), "svc%s", svc->cmd); - if (ev->mask & (IN_CREATE | IN_ATTRIB)) { + if (ev->mask & (IN_CREATE | IN_ATTRIB | IN_MODIFY)) { svc_started(svc); cond_set(cond); } else if (ev->mask & IN_DELETE) @@ -105,7 +105,7 @@ static void pidfile_init(void *arg) struct context *ctx = arg; ctx->wd = inotify_add_watch(ctx->fd, _PATH_VARRUN, - IN_CREATE | IN_ATTRIB | IN_DELETE); + IN_CREATE | IN_ATTRIB | IN_DELETE | IN_MODIFY); if (ctx->wd < 0) { _pe("inotify_add_watch()"); close(ctx->fd); diff --git a/private.h b/private.h index 336f65dd..a8e98733 100644 --- a/private.h +++ b/private.h @@ -33,7 +33,6 @@ uev_ctx_t *ctx; /* Main loop context */ int api_init (uev_ctx_t *ctx); int client (int argc, char *argv[]); -void service_bootstrap(void); void service_monitor (pid_t lost); void plugin_run_hook (hook_point_t no, void *arg); diff --git a/service.c b/service.c index 3c1ca1a4..7771a113 100755 --- a/service.c +++ b/service.c @@ -38,27 +38,14 @@ #include "tty.h" #include "service.h" #include "inetd.h" +#include "sm.h" #define RESPAWN_MAX 10 /* Prevent endless respawn of faulty services. */ -static int in_teardown = 0, in_dyn_teardown = 0; - #ifndef INETD_DISABLED static svc_t *find_inetd_svc (char *path, char *service, char *proto); #endif -/** - * service_bootstrap - Start bootstrap services and tasks - * - * System startup, runlevel S, where only services, tasks and - * run commands absolutely essential to bootstrap are located. - */ -void service_bootstrap(void) -{ - _d("Bootstrapping all services in runlevel S from %s", FINIT_CONF); - service_step_all(SVC_TYPE_RUN | SVC_TYPE_TASK | SVC_TYPE_SERVICE); -} - /** * service_enabled - Should the service run? * @svc: Pointer to &svc_t object @@ -85,7 +72,7 @@ int service_enabled(svc_t *svc) * Returns: * 1, if all stopped services have been collected. 0 otherwise. */ -static int service_stop_is_done(void) +int service_stop_is_done(void) { svc_t *svc; @@ -372,30 +359,6 @@ static int service_restart(svc_t *svc) return err; } -/** - * service_reload_dynamic_finish - Finish dynamic service reload - * - * Second stage of dynamic reload. Called either directly from first - * stage if no services had to be stopped, or later from - * service_monitor() once all stopped services have been collected. - */ -static void service_reload_dynamic_finish(void) -{ - in_dyn_teardown = 0; - - /* Cleanup stale services */ - svc_clean_dynamic(service_unregister); - - _d("Starting services after reconf ..."); - service_step_all(SVC_TYPE_SERVICE | SVC_TYPE_INETD); - - _d("Calling reconf hooks ..."); - plugin_run_hooks(HOOK_SVC_RECONF); - - service_step_all(SVC_TYPE_SERVICE | SVC_TYPE_INETD); - _d("Reconfiguration done"); -} - /** * service_reload_dynamic - Called on SIGHUP, 'init q' or 'initctl reload' * @@ -405,62 +368,8 @@ static void service_reload_dynamic_finish(void) */ void service_reload_dynamic(void) { - /* First reload all *.conf in /etc/finit.d/ */ - conf_reload_dynamic(); - - /* Then, mark all affected service conditions as in-flux and - * let all affected services move to WAITING/HALTED */ - _d("Stopping services services not allowed after reconf ..."); - in_dyn_teardown = 1; - cond_reload(); - service_step_all(SVC_TYPE_SERVICE | SVC_TYPE_INETD); - - /* Need to wait for any services to stop? If so, exit early - * and perform second stage from service_monitor() later. */ - if (!service_stop_is_done()) - return; - - /* Otherwise, kick all svcs again right away */ - service_reload_dynamic_finish(); -} - -/** - * service_runlevel_finish - Finish runlevel change - * - * Second stage of runlevel change. Called either directly from first - * stage if no services had to be stopped, or later from - * service_monitor() once all stopped services have been collected. - */ -static void service_runlevel_finish(void) -{ - /* Prev runlevel services stopped, call hooks before starting new runlevel ... */ - _d("All services have been stoppped, calling runlevel change hooks ..."); - plugin_run_hooks(HOOK_RUNLEVEL_CHANGE); /* Reconfigure HW/VLANs/etc here */ - - _d("Starting services services new to this runlevel ..."); - in_teardown = 0; - service_step_all(SVC_TYPE_ANY); - - /* Cleanup stale services */ - svc_clean_dynamic(service_unregister); - - if (0 == runlevel) { - do_shutdown(SIGUSR2); - return; - } - if (6 == runlevel) { - do_shutdown(SIGUSR1); - return; - } - - if (runlevel == 1) - touch("/etc/nologin"); /* Disable login in single-user mode */ - else - erase("/etc/nologin"); - - /* No TTYs run at bootstrap, they have a delayed start. */ - if (prevlevel > 0) - tty_runlevel(runlevel); + sm_set_reload(&sm); + sm_step(&sm); } /** @@ -472,31 +381,8 @@ static void service_runlevel_finish(void) */ void service_runlevel(int newlevel) { - if (runlevel == newlevel) - return; - - if (newlevel < 0 || newlevel > 9) - return; - - prevlevel = runlevel; - runlevel = newlevel; - - _d("Setting new runlevel --> %d <-- previous %d", runlevel, prevlevel); - runlevel_set(prevlevel, newlevel); - - /* Make sure to (re)load all *.conf in /etc/finit.d/ */ - conf_reload_dynamic(); - - _d("Stopping services services not allowed in new runlevel ..."); - in_teardown = 1; - service_step_all(SVC_TYPE_ANY); - - /* Need to wait for any services to stop? If so, exit early - * and perform second stage from service_monitor() later. */ - if (!service_stop_is_done()) - return; - - service_runlevel_finish(); + sm_set_runlevel(&sm, newlevel); + sm_step(&sm); } /** @@ -744,32 +630,10 @@ void service_unregister(svc_t *svc) svc_del(svc); } -/** - * service_teardown_finish - Complete runlevel change or dynamic reload - * - * If any runlevel change or dynamic service reload is in progress and - * all services that had to be stopped have been collected, run the - * corresponding second stage. - */ -static void service_teardown_finish(void) -{ - if (!(in_teardown || in_dyn_teardown)) - return; - - if (!service_stop_is_done()) - return; - - if (in_teardown) - service_runlevel_finish(); - - if (in_dyn_teardown) - service_reload_dynamic_finish(); -} - - void service_monitor(pid_t lost) { svc_t *svc; + char pidfile[MAX_ARG_LEN]; if (fexist(SYNC_SHUTDOWN) || lost <= 1) return; @@ -791,12 +655,15 @@ void service_monitor(pid_t lost) _d("collected %s(%d)", svc->cmd, lost); + /* Remove pid file (in case service is careless) */ + snprintf(pidfile, sizeof(pidfile), "%s%s.pid", _PATH_VARRUN, basename(svc->cmd)); + remove(pidfile); + /* No longer running, update books. */ svc->pid = 0; service_step(svc); - /* Check if we're still collecting stopped dynamic services */ - service_teardown_finish(); + sm_step(&sm); } static void svc_set_state(svc_t *svc, svc_state_t new) @@ -877,6 +744,10 @@ restart: break; } + /* wait until all processes has been stopped before continuing... */ + if (sm_is_in_teardown(&sm)) + break; + err = service_start(svc); if (err) { (*restart_counter)++; @@ -933,6 +804,9 @@ restart: case COND_ON: if (svc_is_changed(svc)) { if (svc->sighup) { + /* wait until all processes has been stopped before continuing... */ + if (sm_is_in_teardown(&sm)) + break; service_restart(svc); } else { service_stop(svc); diff --git a/service.h b/service.h index 062e858d..7a3da8fa 100644 --- a/service.h +++ b/service.h @@ -37,6 +37,8 @@ void service_reload_dynamic (void); void service_step(svc_t *svc); void service_step_all(int types); +int service_stop_is_done(void); + #endif /* FINIT_SERVICE_H_ */ /** diff --git a/sm.c b/sm.c new file mode 100644 index 00000000..288473cf --- /dev/null +++ b/sm.c @@ -0,0 +1,219 @@ +/* Finit state machine + * + * Copyright (c) 2016 xxx + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" /* Generated by configure script */ + +#include + +#include "finit.h" +#include "cond.h" +#include "conf.h" +#include "helpers.h" +#include "private.h" +#include "service.h" +#include "sig.h" +#include "tty.h" +#include "sm.h" + +void sm_init(sm_t *sm) +{ + sm->state = SM_BOOTSTRAP_STATE; + sm->newlevel = -1; + sm->reload = 0; + sm->in_teardown = 0; +} + +static char *sm_status(sm_t *sm) +{ + switch (sm->state) { + case SM_BOOTSTRAP_STATE: + return "bootstrap"; + case SM_RUNNING_STATE: + return "running"; + case SM_RUNLEVEL_CHANGE_STATE: + return "runlevel/change"; + case SM_RUNLEVEL_WAIT_STATE: + return "runlevel/wait"; + case SM_RELOAD_CHANGE_STATE: + return "reload/change"; + case SM_RELOAD_WAIT_STATE: + return "reload/wait"; + default: + return "unknown"; + } +} + +void sm_set_runlevel(sm_t *sm, int newlevel) +{ + sm->newlevel = newlevel; +} + +void sm_set_reload(sm_t *sm) +{ + sm->reload = 1; +} + +int sm_is_in_teardown(sm_t *sm) +{ + return sm->in_teardown; +} + +void sm_step(sm_t *sm) +{ + sm_state_t old_state; + +restart: + old_state = sm->state; + + _d("state: %s", sm_status(sm)); + + switch (sm->state) { + case SM_BOOTSTRAP_STATE: + _d("Bootstrapping all services in runlevel S from %s", FINIT_CONF); + service_step_all(SVC_TYPE_RUN | SVC_TYPE_TASK | SVC_TYPE_SERVICE); + sm->state = SM_RUNNING_STATE; + break; + + case SM_RUNNING_STATE: + /* runlevel changed? */ + if (sm->newlevel >= 0 && sm->newlevel <= 9) { + if (runlevel == sm->newlevel) { + sm->newlevel = -1; + break; + } + sm->state = SM_RUNLEVEL_CHANGE_STATE; + break; + } + /* reload ? */ + if (sm->reload) { + sm->reload = 0; + sm->state = SM_RELOAD_CHANGE_STATE; + } + break; + + case SM_RUNLEVEL_CHANGE_STATE: + prevlevel = runlevel; + runlevel = sm->newlevel; + sm->newlevel = -1; + + _d("Setting new runlevel --> %d <-- previous %d", runlevel, prevlevel); + runlevel_set(prevlevel, runlevel); + + /* Make sure to (re)load all *.conf in /etc/finit.d/ */ + conf_reload_dynamic(); + + _d("Stopping services services not allowed in new runlevel ..."); + sm->in_teardown = 1; + service_step_all(SVC_TYPE_ANY); + + sm->state = SM_RUNLEVEL_WAIT_STATE; + break; + + case SM_RUNLEVEL_WAIT_STATE: + /* Need to wait for any services to stop? If so, exit early + * and perform second stage from service_monitor later. */ + if (!service_stop_is_done()) + break; + + /* Prev runlevel services stopped, call hooks before starting new runlevel ... */ + _d("All services have been stoppped, calling runlevel change hooks ..."); + plugin_run_hooks(HOOK_RUNLEVEL_CHANGE); /* Reconfigure HW/VLANs/etc here */ + + _d("Starting services services new to this runlevel ..."); + sm->in_teardown = 0; + service_step_all(SVC_TYPE_ANY); + + /* Cleanup stale services */ + svc_clean_dynamic(service_unregister); + + if (0 == runlevel) { + do_shutdown(SIGUSR2); + sm->state = SM_RUNNING_STATE; + break; + } + if (6 == runlevel) { + do_shutdown(SIGUSR1); + sm->state = SM_RUNNING_STATE; + break; + } + + if (runlevel == 1) + touch("/etc/nologin"); /* Disable login in single-user mode */ + else + erase("/etc/nologin"); + + /* No TTYs run at bootstrap, they have a delayed start. */ + if (prevlevel > 0) + tty_runlevel(runlevel); + + sm->state = SM_RUNNING_STATE; + break; + + case SM_RELOAD_CHANGE_STATE: + /* First reload all *.conf in /etc/finit.d/ */ + conf_reload_dynamic(); + + /* Then, mark all affected service conditions as in-flux and + * let all affected services move to WAITING/HALTED */ + _d("Stopping services services not allowed after reconf ..."); + sm->in_teardown = 1; + cond_reload(); + service_step_all(SVC_TYPE_SERVICE | SVC_TYPE_INETD); + + sm->state = SM_RELOAD_WAIT_STATE; + break; + + case SM_RELOAD_WAIT_STATE: + /* Need to wait for any services to stop? If so, exit early + * and perform second stage from service_monitor later. */ + if (!service_stop_is_done()) + break; + + sm->in_teardown = 0; + /* Cleanup stale services */ + svc_clean_dynamic(service_unregister); + + _d("Starting services after reconf ..."); + service_step_all(SVC_TYPE_SERVICE | SVC_TYPE_INETD); + + _d("Calling reconf hooks ..."); + plugin_run_hooks(HOOK_SVC_RECONF); + + service_step_all(SVC_TYPE_SERVICE | SVC_TYPE_INETD); + _d("Reconfiguration done"); + + sm->state = SM_RUNNING_STATE; + break; + } + + if (sm->state != old_state) { + goto restart; + } +} + +/** + * Local Variables: + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/sm.h b/sm.h new file mode 100644 index 00000000..9bb8d584 --- /dev/null +++ b/sm.h @@ -0,0 +1,56 @@ +/* Finit state machine + * + * Copyright (c) 2016 xxx + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FINIT_SM_H_ +#define FINIT_SM_H_ + +typedef enum { + SM_BOOTSTRAP_STATE = 0, /* Init state, bootstrap services */ + SM_RUNNING_STATE, /* Normal state, services running */ + SM_RUNLEVEL_CHANGE_STATE, /* A runlevel change has occured */ + SM_RUNLEVEL_WAIT_STATE, /* Waiting for all stopped runlevel processes to be halted */ + SM_RELOAD_CHANGE_STATE, /* A reload event has occured */ + SM_RELOAD_WAIT_STATE, /* Waiting for all stopped reload processes to be halted */ +} sm_state_t; + +typedef struct sm { + sm_state_t state; /* Running, Changed, Waiting, ... */ + int newlevel; /* Set on runlevel change to new runlevel, -1 if not change */ + int reload; /* Set on reload event, else 0 */ + int in_teardown; /* Set when waiting for all processes to be halted */ +} sm_t; + +void sm_init(sm_t *sm); +void sm_step(sm_t *sm); +void sm_set_runlevel(sm_t *sm, int newlevel); +void sm_set_reload(sm_t *sm); +int sm_is_in_teardown(sm_t *sm); + +#endif /* FINIT_SM_H_ */ + +/** + * Local Variables: + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */