#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>
+#include <termios.h>
#ifdef __linux__
#include <sys/prctl.h>
#include <sys/klog.h>
#include "dinit-log.h"
#include "dinit-socket.h"
#include "static-string.h"
+#include "dinit-utmp.h"
#include "mconfig.h"
static void sigint_reboot_cb(eventloop_t &eloop) noexcept;
static void sigquit_cb(eventloop_t &eloop) noexcept;
static void sigterm_cb(eventloop_t &eloop) noexcept;
+static void open_control_socket(bool report_ro_failure = true) noexcept;
static void close_control_socket() noexcept;
-static void wait_for_user_input() noexcept;
+static void confirm_restart_boot() noexcept;
static void read_env_file(const char *);
static void control_socket_cb(eventloop_t *loop, int fd);
static bool am_pid_one = false; // true if we are PID 1
static bool am_system_init = false; // true if we are the system init process
+static bool did_log_boot = false;
static bool control_socket_open = false;
static bool external_log_open = false;
int active_control_conns = 0;
static const char *user_home_path = nullptr;
+// Set to true (when console_input_watcher is active) if console input becomes available
+static bool console_input_ready = false;
// Get user home (and set user_home_path). (The return may become invalid after
// changing the evironment (HOME variable) or using the getpwuid() function).
-const char * get_user_home()
+static const char * get_user_home()
{
if (user_home_path == nullptr) {
user_home_path = getenv("HOME");
}
};
+ class console_input_watcher : public eventloop_t::fd_watcher_impl<console_input_watcher>
+ {
+ using rearm = dasynq::rearm;
+
+ public:
+ rearm fd_event(eventloop_t &loop, int fd, int flags) noexcept
+ {
+ console_input_ready = true;
+ return rearm::REARM;
+ }
+ };
+
// Simple timer used to limit the amount of time waiting for the log flush to complete (at shutdown)
class log_flush_timer_t : public eventloop_t::timer_impl<log_flush_timer_t>
{
};
control_socket_watcher control_socket_io;
+ console_input_watcher console_input_io;
log_flush_timer_t log_flush_timer;
}
sigint_watcher.add_watch(event_loop, SIGINT);
sigterm_watcher.add_watch(event_loop, SIGTERM);
+ console_input_io.add_watch(event_loop, STDIN_FILENO, dasynq::IN_EVENTS, false);
if (am_pid_one) {
// PID 1: SIGQUIT exec's shutdown
}
}
- event_loop:
+ run_event_loop:
// Process events until all services have terminated.
while (services->count_active_services() != 0) {
}
shutdown_type_t shutdown_type = services->get_shutdown_type();
+ if (shutdown_type == shutdown_type_t::REMAIN) {
+ goto run_event_loop;
+ }
if (am_pid_one) {
log_msg_begin(loglevel_t::INFO, "No more active services.");
else if (shutdown_type == shutdown_type_t::POWEROFF) {
log_msg_end(" Will power down.");
}
- else {
- log_msg_end(" Re-initiating boot sequence.");
- }
}
log_flush_timer.arm_timer_rel(event_loop, timespec{5,0}); // 5 seconds
close_control_socket();
if (am_pid_one) {
- if (shutdown_type == shutdown_type_t::CONTINUE) {
- // It could be that we started in single user mode, and the
- // user has now exited the shell. We'll try and re-start the
- // boot process...
- try {
- services->start_service("boot");
- goto event_loop; // yes, the "evil" goto
- }
- catch (...) {
- // Now what do we do? try to reboot, but wait for user ack to avoid boot loop.
- log(loglevel_t::ERROR, "Could not start 'boot' service. Will attempt reboot.");
- wait_for_user_input();
- shutdown_type = shutdown_type_t::REBOOT;
+ if (shutdown_type == shutdown_type_t::NONE) {
+ // Services all stopped but there was no shutdown issued. Inform user, wait for ack, and
+ // re-start boot sequence.
+ std::cout << "No shutdown was requested; boot failure? Will re-run boot sequence." << std::endl;
+ sync(); // Sync to minimise data loss if user elects to power off / hard reset
+ confirm_restart_boot();
+ shutdown_type = services->get_shutdown_type();
+ if (shutdown_type == shutdown_type_t::NONE) {
+ try {
+ services->start_service("boot");
+ goto run_event_loop; // yes, the "evil" goto
+ }
+ catch (...) {
+ // Now what do we do? try to reboot, but wait for user ack to avoid boot loop.
+ log(loglevel_t::ERROR, "Could not start 'boot' service. Will attempt reboot.");
+ shutdown_type = shutdown_type_t::REBOOT;
+ }
}
}
}
else if (shutdown_type == shutdown_type_t::REBOOT) {
// Non-system-process. If we got SIGINT, let's die due to it:
- sigset_t sigwait_set;
- sigemptyset(&sigwait_set);
- sigaddset(&sigwait_set, SIGINT);
+ sigset_t sigwait_set_int;
+ sigemptyset(&sigwait_set_int);
+ sigaddset(&sigwait_set_int, SIGINT);
raise(SIGINT);
- sigprocmask(SIG_UNBLOCK, &sigwait_set, NULL);
+ sigprocmask(SIG_UNBLOCK, &sigwait_set_int, NULL);
}
return 0;
}
}
-// In exception situations we want user confirmation before proceeding (eg on critical boot failure
-// we wait before rebooting to avoid a reboot loop).
-static void wait_for_user_input() noexcept
+// Get user confirmation before proceeding with restarting boot sequence.
+static void confirm_restart_boot() noexcept
{
+ // Bypass log; we want to make certain the message is seen:
std::cout << "Press Enter to continue." << std::endl;
- char buf[1];
- read(STDIN_FILENO, buf, 1);
+
+ // Drain input, set non-blocking mode
+ tcflush(STDIN_FILENO, TCIFLUSH);
+ int origFlags = fcntl(STDIN_FILENO, F_GETFL);
+
+ console_input_io.set_enabled(event_loop, true);
+ do {
+ event_loop.run();
+ } while (! console_input_ready && services->get_shutdown_type() == shutdown_type_t::NONE);
+ console_input_io.set_enabled(event_loop, false);
+
+ // We either have input, or shutdown type has been set, or both.
+ if (console_input_ready) {
+ char buf[1];
+ read(STDIN_FILENO, buf, 1); // read a single character, to make sure we wait for input
+ tcflush(STDIN_FILENO, TCIFLUSH); // discard the rest of input
+ console_input_ready = false;
+ }
+
+ fcntl(STDIN_FILENO, F_SETFL, origFlags);
}
// Callback for control socket
}
}
+// Callback when the root filesystem is read/write:
+void rootfs_is_rw() noexcept
+{
+ open_control_socket(true);
+ if (! did_log_boot) {
+ did_log_boot = log_boot();
+ }
+}
+
// Open/create the control socket, normally /dev/dinitctl, used to allow client programs to connect
// and issue service orders and shutdown commands etc. This can safely be called multiple times;
// once the socket has been successfully opened, further calls have no effect.
-void open_control_socket(bool report_ro_failure) noexcept
+static void open_control_socket(bool report_ro_failure) noexcept
{
+ if (control_socket_open) {
+ struct stat stat_buf;
+ if (stat(control_socket_path, &stat_buf) != 0 && errno == ENOENT) {
+ // Looks like our control socket has disappeared from the filesystem. Close our control
+ // socket and re-create it:
+ control_socket_io.deregister(event_loop);
+ close(control_socket_io.get_watched_fd());
+ control_socket_open = false; // now re-open below
+ }
+ }
+
if (! control_socket_open) {
const char * saddrname = control_socket_path;
size_t saddrname_len = strlen(saddrname);