From 4e18affa9b8c0624f9646a50622e7842b14b098c Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 27 Nov 2025 13:09:10 +0100 Subject: [PATCH] Allow skipping bootstrap wait with Ctrl-C When setting up a new system with Finit it is very common to make small logical mistakes that cause "hangs" at boot. This is when Finit waits for 180 sec. for run/tasks to complete before moving to the configured runlevel. This patch adds console input monitoring during bootstrap wait that allows users to press Ctrl-C to skip waiting and proceed to the configured runlevel. When Ctrl-C is detected, all incomplete run/task/services are logged to syslog for later troubleshooting after login. Signed-off-by: Joachim Wiberg --- src/service.c | 36 +++++++++++++++++++++ src/service.h | 1 + src/sm.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 124 insertions(+), 2 deletions(-) diff --git a/src/service.c b/src/service.c index 509c0d42..0384d11f 100644 --- a/src/service.c +++ b/src/service.c @@ -3092,6 +3092,42 @@ int service_completed(svc_t **svcp) return 1; } +/* + * service_log_incomplete - Log all incomplete run/task in current runlevel + * + * Iterates through all run/task services and logs those that have not + * yet completed. Useful for diagnosing bootstrap delays or reporting + * what was skipped when user interrupts bootstrap wait. + */ +void service_log_incomplete(void) +{ + svc_t *svc, *iter = NULL; + int count = 0; + + for (svc = svc_iterator(&iter, 1); svc; svc = svc_iterator(&iter, 0)) { + if (!svc_is_runtask(svc)) + continue; + + if (!svc_enabled(svc)) + continue; + + if (svc_conflicts(svc)) + continue; + + if (strstr(svc->cond, plugin_hook_str(HOOK_SVC_UP)) || + strstr(svc->cond, plugin_hook_str(HOOK_SYSTEM_UP))) + continue; + + if (!svc->once) { + logit(LOG_WARNING, "Incomplete: %s", svc_ident(svc, NULL, 0)); + count++; + } + } + + if (count == 0) + logit(LOG_WARNING, "No incomplete run/task services found"); +} + /* * Called in SM_RELOAD_WAIT_STATE to update unmodified non-native * services' READY condition to the current generation. Similar diff --git a/src/service.h b/src/service.h index a0acd81a..5efc2354 100644 --- a/src/service.h +++ b/src/service.h @@ -48,6 +48,7 @@ void service_step_all (int types); void service_worker (void *unused); int service_completed (svc_t **svc); +void service_log_incomplete (void); void service_notify_reconf (void); void service_init (uev_ctx_t *ctx); diff --git a/src/sm.c b/src/sm.c index b7cbff6b..079a8673 100644 --- a/src/sm.c +++ b/src/sm.c @@ -23,8 +23,10 @@ #include "config.h" /* Generated by configure script */ +#include #include #include +#include #include "finit.h" #include "cond.h" @@ -56,8 +58,80 @@ static struct { int newlevel; /* Set to new runlevel on change, -1 otherwise */ int reload; /* Set on reload event, else 0 */ int in_reload; /* Set when waiting for all processes to be halted */ + int skip_bootstrap; /* Set to skip bootstrap waiting */ } sm; +static uev_t console_watcher; +static int console_fd = -1; + +/* + * Console input callback - handles Ctrl-C during bootstrap wait + */ +static void console_input_cb(uev_t *w, void *arg, int events) +{ + ssize_t len; + char ch; + + (void)arg; + + if (UEV_ERROR == events) { + warnx("Error in console watcher"); + return; + } + + /* Read and check for Ctrl-C (ASCII 3) */ + len = read(w->fd, &ch, 1); + if (len == 1 && ch == 3) { /* Ctrl-C */ + sm.skip_bootstrap = 1; + print(2, "Bootstrap wait interrupted by Ctrl-C, resuming ..."); + } +} + +/* + * Stop and cleanup the console watcher + */ +static void console_watcher_stop(void) +{ + if (console_fd >= 0) { + uev_io_stop(&console_watcher); + close(console_fd); + console_fd = -1; + } +} + +/* + * Setup console watcher for Ctrl-C during bootstrap + */ +static void console_watcher_start(uev_ctx_t *ctx) +{ + struct termios tio; + + /* Open console in non-blocking mode */ + console_fd = open(console(), O_RDONLY | O_NONBLOCK); + if (console_fd < 0) { + dbg("Failed opening console for input monitoring: %s", strerror(errno)); + return; + } + + /* Get current terminal settings */ + if (tcgetattr(console_fd, &tio) == 0) { + /* Put terminal in raw mode to catch Ctrl-C */ + tio.c_lflag &= ~(ICANON | ECHO | ISIG); + tio.c_cc[VMIN] = 0; + tio.c_cc[VTIME] = 0; + tcsetattr(console_fd, TCSANOW, &tio); + } + + /* Register I/O watcher */ + if (uev_io_init(ctx, &console_watcher, console_input_cb, NULL, console_fd, UEV_READ)) { + dbg("Failed setting up console watcher"); + close(console_fd); + console_fd = -1; + } else { + dbg("Console watcher active, press Ctrl-C to skip bootstrap wait"); + } +} + /* * Wait for system bootstrap to complete, all SVC_TYPE_RUNTASK must be @@ -76,13 +150,19 @@ static void sm_check_bootstrap(void *work) service_step_all(SVC_TYPE_ANY); bootstrap_done = service_completed(&svc); - if (timeout-- > 0 && !bootstrap_done) { + if (timeout-- > 0 && !bootstrap_done && !sm.skip_bootstrap) { dbg("Not all bootstrap run/tasks have completed yet ... %d", timeout); schedule_work(work); return; } - if (timeout > 0) { + /* Stop console watcher before proceeding */ + console_watcher_stop(); + + if (sm.skip_bootstrap) { + logit(LOG_WARNING, "Ctrl-C received, skipped waiting for the following:"); + service_log_incomplete(); + } else if (timeout > 0) { dbg("All run/task have completed, resuming bootstrap."); } else { dbg("Timeout, resuming bootstrap."); @@ -184,6 +264,7 @@ void sm_init(void) sm.newlevel = -1; sm.reload = 0; sm.in_reload = 0; + sm.skip_bootstrap = 0; dbg("Starting bootstrap finalize timer ..."); schedule_work(&work); @@ -206,6 +287,7 @@ restart: service_step_all(SVC_TYPE_RUNTASK | SVC_TYPE_SERVICE | SVC_TYPE_SYSV); sm.state = SM_BOOTSTRAP_WAIT_STATE; + console_watcher_start(ctx); break; /* @@ -229,6 +311,9 @@ restart: if (sm.newlevel == -1) break; + /* Ensure console watcher is stopped before proceeding */ + console_watcher_stop(); + /* Hooks that should run at the very end */ dbg("Calling all system up hooks ..."); plugin_run_hooks(HOOK_SYSTEM_UP);