X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=src%2Fdinit.cc;h=86e67dbdac2bfe9dac071904ecfc848c3bf7cd36;hb=b2577653d9f7259721e3ee7514f3da25968cd447;hp=a82f502c69d3f583e4cc75e9cdb91321d31a9384;hpb=a2c7e27fbe7e95b41bc485aad425c2e5158f1709;p=oweals%2Fdinit.git diff --git a/src/dinit.cc b/src/dinit.cc index a82f502..86e67db 100644 --- a/src/dinit.cc +++ b/src/dinit.cc @@ -12,92 +12,51 @@ #include #include #include - -#include "dasync.h" -#include "service.h" -#include "control.h" -#include "dinit-log.h" - #ifdef __linux__ +#include #include #include #endif +#include "dasynq.h" +#include "service.h" +#include "control.h" +#include "dinit-log.h" +#include "dinit-socket.h" + /* - * "simpleinit" from util-linux-ng package handles signals as follows: - * SIGTSTP - spawn no more gettys (in preparation for shutdown etc). - * In dinit terms this should probably mean "no more auto restarts" - * (for any service). (Actually the signal acts as a toggle, if - * respawn is disabled it will be re-enabled and init will - * act as if SIGHUP had also been sent) - * SIGTERM - kill spawned gettys (which are still alive) - * Interestingly, simpleinit just sends a SIGTERM to the gettys, - * which will not normall kill shells (eg bash ignores SIGTERM). - * "/sbin/initctl -r" - rollback services (ran by "shutdown"/halt etc); - * shouldn't return until all services have been stopped. - * shutdown calls this after sending SIGTERM to processes running - * with uid >= 100 ("mortals"). - * SIGQUIT - init will exec() shutdown. shutdown will detect that it is - * running as pid 1 and will just loop and reap child processes. - * This is used by shutdown so that init will not hang on to its - * inode, allowing the filesystem to be re-mounted readonly - * (this is only an issue if the init binary has been unlinked, - * since it's then holding an inode which can't be maintained - * when the filesystem is unmounted). + * When running as the system init process, Dinit processes the following signals: * - * Not sent by shutdown: - * SIGHUP - re-read inittab and spawn any new getty entries - * SIGINT - (ctrl+alt+del handler) - fork & exec "reboot" - * - * On the contrary dinit currently uses: * SIGTERM - roll back services and then fork/exec /sbin/halt * SIGINT - roll back services and then fork/exec /sbin/reboot - * SIGQUIT - exec() /sbin/shutdown as per above. + * SIGQUIT - exec() /sbin/shutdown without rolling back services * - * It's an open question about whether dinit should roll back services *before* + * It's an open question about whether Dinit should roll back services *before* * running halt/reboot, since those commands should prompt rollback of services - * anyway. But it seems safe to do so. + * anyway. But it seems safe to do so, and it means the user can at least stop + * services even if the halt/reboot commands are unavailable for some reason. */ +using namespace dasynq; +using eventloop_t = event_loop; -using namespace dasync; -using EventLoop_t = EventLoop; +eventloop_t eventLoop; -EventLoop_t eventLoop = EventLoop_t(); +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 close_control_socket() noexcept; +static void wait_for_user_input() noexcept; -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(EventLoop_t *loop) noexcept; -static void close_control_socket(EventLoop_t *loop) noexcept; +static void control_socket_cb(eventloop_t *loop, int fd); -static void control_socket_cb(EventLoop_t *loop, int fd); - -class ControlSocketWatcher : public PosixFdWatcher -{ - Rearm gotEvent(EventLoop_t * loop, int fd, int flags) - { - control_socket_cb(loop, fd); - return Rearm::REARM; - } - - public: - // TODO the fd is already stored, must we really store it again... - int fd; - - void registerWith(EventLoop_t * loop, int fd, int flags) - { - this->fd = fd; - PosixFdWatcher::registerWith(loop, fd, flags); - } -}; - -ControlSocketWatcher control_socket_io; +void open_control_socket(bool report_ro_failure = true) noexcept; +void setup_external_log() noexcept; // Variables -static ServiceSet *service_set; +static dirload_service_set *services; static bool am_system_init = false; // true if we are the system init process @@ -133,41 +92,44 @@ const char * get_user_home() namespace { - class CallbackSignalHandler : public PosixSignalWatcher + class callback_signal_handler : public eventloop_t::signal_watcher_impl { public: - typedef void (*cb_func_t)(EventLoop_t *); + typedef void (*cb_func_t)(eventloop_t &); private: cb_func_t cb_func; public: - CallbackSignalHandler() : cb_func(nullptr) { } - CallbackSignalHandler(cb_func_t pcb_func) : cb_func(pcb_func) { } + callback_signal_handler() : cb_func(nullptr) { } + callback_signal_handler(cb_func_t pcb_func) : cb_func(pcb_func) { } void setCbFunc(cb_func_t cb_func) { this->cb_func = cb_func; } - Rearm gotSignal(EventLoop_t * eloop, int signo, SigInfo_p siginfo) override + rearm received(eventloop_t &eloop, int signo, siginfo_p siginfo) { - service_set->stop_all_services(ShutdownType::REBOOT); - return Rearm::REARM; + cb_func(eloop); + return rearm::REARM; } }; - class ControlSocketWatcher : public PosixFdWatcher + class control_socket_watcher : public eventloop_t::fd_watcher_impl { - Rearm gotEvent(EventLoop_t * loop, int fd, int flags) + public: + rearm fd_event(eventloop_t &loop, int fd, int flags) noexcept { - control_socket_cb(loop, fd); - return Rearm::REARM; + control_socket_cb(&loop, fd); + return rearm::REARM; } }; + + control_socket_watcher control_socket_io; } -int main(int argc, char **argv) +int dinit_main(int argc, char **argv) { using namespace std; @@ -235,10 +197,12 @@ int main(int argc, char **argv) } } else { +#ifdef __linux__ // LILO puts "auto" on the kernel command line for unattended boots; we'll filter it. if (! am_system_init || strcmp(argv[i], "auto") != 0) { services_to_start.push_back(argv[i]); } +#endif } } } @@ -254,7 +218,7 @@ int main(int argc, char **argv) if (onefd > 2) close(onefd); if (twofd > 2) close(twofd); } - + /* Set up signal handlers etc */ /* SIG_CHILD is ignored by default: good */ sigset_t sigwait_set; @@ -262,8 +226,9 @@ int main(int argc, char **argv) sigaddset(&sigwait_set, SIGCHLD); sigaddset(&sigwait_set, SIGINT); sigaddset(&sigwait_set, SIGTERM); + if (am_system_init) sigaddset(&sigwait_set, SIGQUIT); sigprocmask(SIG_BLOCK, &sigwait_set, NULL); - + // Terminal access control signals - we block these so that dinit can't be // suspended if it writes to the terminal after some other process has claimed // ownership of it. @@ -271,6 +236,8 @@ int main(int argc, char **argv) signal(SIGTTIN, SIG_IGN); signal(SIGTTOU, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + if (! am_system_init && ! control_socket_path_set) { const char * userhome = get_user_home(); if (userhome != nullptr) { @@ -299,32 +266,29 @@ int main(int argc, char **argv) } // Set up signal handlers - CallbackSignalHandler sigint_watcher; + callback_signal_handler sigterm_watcher {sigterm_cb}; + callback_signal_handler sigint_watcher; + callback_signal_handler sigquit_watcher; + if (am_system_init) { - sigint_watcher.setCbFunc(sigint_reboot_cb); + sigint_watcher.setCbFunc(sigint_reboot_cb); + sigquit_watcher.setCbFunc(sigquit_cb); } else { - sigint_watcher.setCbFunc(sigterm_cb); + sigint_watcher.setCbFunc(sigterm_cb); } + + sigint_watcher.add_watch(eventLoop, SIGINT); + sigterm_watcher.add_watch(eventLoop, SIGTERM); - CallbackSignalHandler sigquit_watcher; if (am_system_init) { // PID 1: SIGQUIT exec's shutdown - sigquit_watcher.setCbFunc(sigquit_cb); + sigquit_watcher.add_watch(eventLoop, SIGQUIT); + // As a user process, we instead just let SIGQUIT perform the default action. } - else { - // Otherwise: SIGQUIT terminates dinit - sigquit_watcher.setCbFunc(sigterm_cb); - } - - auto sigterm_watcher = CallbackSignalHandler(sigterm_cb); - - sigint_watcher.registerWatch(&eventLoop, SIGINT); - sigquit_watcher.registerWatch(&eventLoop, SIGQUIT); - sigterm_watcher.registerWatch(&eventLoop, SIGTERM); // Try to open control socket (may fail due to readonly filesystem) - open_control_socket(&eventLoop); + open_control_socket(false); #ifdef __linux__ if (am_system_init) { @@ -333,53 +297,60 @@ int main(int argc, char **argv) // Make ctrl+alt+del combination send SIGINT to PID 1 (this process) reboot(RB_DISABLE_CAD); } + + // Mark ourselves as a subreaper. This means that if a process we start double-forks, the + // orphaned child will re-parent to us rather than to PID 1 (although that could be us too). + prctl(PR_SET_CHILD_SUBREAPER, 1); #endif /* start requested services */ - service_set = new ServiceSet(service_dir); + services = new dirload_service_set(service_dir); - init_log(service_set); + init_log(services); - for (list::iterator i = services_to_start.begin(); - i != services_to_start.end(); - ++i) { + for (auto svc : services_to_start) { try { - service_set->startService(*i); + services->start_service(svc); + // Note in general if we fail to start a service we don't need any special error handling, + // since we either leave other services running or, if it was the only service, then no + // services will be running and we will process normally (reboot if system process, + // exit if user process). } - catch (ServiceNotFound &snf) { - log(LogLevel::ERROR, snf.serviceName, ": Could not find service description."); + catch (service_not_found &snf) { + log(loglevel_t::ERROR, snf.serviceName, ": Could not find service description."); } - catch (ServiceLoadExc &sle) { - log(LogLevel::ERROR, sle.serviceName, ": ", sle.excDescription); + catch (service_load_exc &sle) { + log(loglevel_t::ERROR, sle.serviceName, ": ", sle.excDescription); } catch (std::bad_alloc &badalloce) { - log(LogLevel::ERROR, "Out of memory when trying to start service: ", *i, "."); + log(loglevel_t::ERROR, "Out of memory when trying to start service: ", svc, "."); + break; } } event_loop: // Process events until all services have terminated. - while (service_set->count_active_services() != 0) { + while (services->count_active_services() != 0) { eventLoop.run(); } - ShutdownType shutdown_type = service_set->getShutdownType(); + shutdown_type_t shutdown_type = services->getShutdownType(); if (am_system_init) { - logMsgBegin(LogLevel::INFO, "No more active services."); + log_msg_begin(loglevel_t::INFO, "No more active services."); - if (shutdown_type == ShutdownType::REBOOT) { - logMsgEnd(" Will reboot."); + if (shutdown_type == shutdown_type_t::REBOOT) { + log_msg_end(" Will reboot."); } - else if (shutdown_type == ShutdownType::HALT) { - logMsgEnd(" Will halt."); + else if (shutdown_type == shutdown_type_t::HALT) { + log_msg_end(" Will halt."); } - else if (shutdown_type == ShutdownType::POWEROFF) { - logMsgEnd(" Will power down."); + else if (shutdown_type == shutdown_type_t::POWEROFF) { + log_msg_end(" Will power down."); } else { - logMsgEnd(" Re-initiating boot sequence."); + log_msg_end(" Re-initiating boot sequence."); } } @@ -387,29 +358,30 @@ int main(int argc, char **argv) eventLoop.run(); } - close_control_socket(&eventLoop); + close_control_socket(); if (am_system_init) { - if (shutdown_type == ShutdownType::CONTINUE) { + 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 { - service_set->startService("boot"); + services->start_service("boot"); goto event_loop; // yes, the "evil" goto } catch (...) { - // Now WTF do we do? try to reboot - log(LogLevel::ERROR, "Could not start 'boot' service; rebooting."); - shutdown_type = ShutdownType::REBOOT; + // 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; } } const char * cmd_arg; - if (shutdown_type == ShutdownType::HALT) { + if (shutdown_type == shutdown_type_t::HALT) { cmd_arg = "-h"; } - else if (shutdown_type == ShutdownType::REBOOT) { + else if (shutdown_type == shutdown_type_t::REBOOT) { cmd_arg = "-r"; } else { @@ -419,47 +391,66 @@ int main(int argc, char **argv) // Fork and execute dinit-reboot. execl("/sbin/shutdown", "/sbin/shutdown", "--system", cmd_arg, nullptr); - log(LogLevel::ERROR, "Could not execute /sbin/shutdown: ", strerror(errno)); + log(loglevel_t::ERROR, "Could not execute /sbin/shutdown: ", strerror(errno)); // PID 1 must not actually exit, although we should never reach this point: while (true) { eventLoop.run(); } } + 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); + raise(SIGINT); + sigprocmask(SIG_UNBLOCK, &sigwait_set, 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 +{ + std::cout << "Press Enter to continue." << std::endl; + char buf[1]; + read(STDIN_FILENO, buf, 1); +} + // Callback for control socket -static void control_socket_cb(EventLoop_t *loop, int sockfd) +static void control_socket_cb(eventloop_t *loop, int sockfd) { - // TODO limit the number of active connections. Keep a tally, and disable the - // control connection listening socket watcher if it gets high, and re-enable - // it once it falls below the maximum. + // Considered keeping a limit the number of active connections, however, there doesn't + // seem much to be gained from that. Only root can create connections and not being + // able to establish a control connection is as much a denial-of-service as is not being + // able to start a service due to lack of fd's. // Accept a connection - int newfd = accept4(sockfd, nullptr, nullptr, SOCK_NONBLOCK | SOCK_CLOEXEC); + int newfd = dinit_accept4(sockfd, nullptr, nullptr, SOCK_NONBLOCK | SOCK_CLOEXEC); if (newfd != -1) { try { - new ControlConn(loop, service_set, newfd); // will delete itself when it's finished + new control_conn_t(loop, services, newfd); // will delete itself when it's finished } - catch (std::bad_alloc &bad_alloc_exc) { - log(LogLevel::ERROR, "Accepting control connection: Out of memory"); + catch (std::exception &exc) { + log(loglevel_t::ERROR, "Accepting control connection: ", exc.what()); close(newfd); } } } -static void open_control_socket(EventLoop_t *loop) noexcept +void open_control_socket(bool report_ro_failure) noexcept { if (! control_socket_open) { const char * saddrname = control_socket_path; - uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen(saddrname) + 1; + size_t saddrname_len = strlen(saddrname); + uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + saddrname_len + 1; struct sockaddr_un * name = static_cast(malloc(sockaddr_size)); if (name == nullptr) { - log(LogLevel::ERROR, "Opening control socket: out of memory"); + log(loglevel_t::ERROR, "Opening control socket: out of memory"); return; } @@ -470,19 +461,19 @@ static void open_control_socket(EventLoop_t *loop) noexcept } name->sun_family = AF_UNIX; - strcpy(name->sun_path, saddrname); + memcpy(name->sun_path, saddrname, saddrname_len + 1); - // OpenBSD and Linux both allow combining NONBLOCK/CLOEXEC flags with socket type, however - // it's not actually POSIX. (TODO). - int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + int sockfd = dinit_socket(AF_UNIX, SOCK_STREAM, 0, SOCK_NONBLOCK | SOCK_CLOEXEC); if (sockfd == -1) { - log(LogLevel::ERROR, "Error creating control socket: ", strerror(errno)); + log(loglevel_t::ERROR, "Error creating control socket: ", strerror(errno)); free(name); return; } if (bind(sockfd, (struct sockaddr *) name, sockaddr_size) == -1) { - log(LogLevel::ERROR, "Error binding control socket: ", strerror(errno)); + if (errno != EROFS || report_ro_failure) { + log(loglevel_t::ERROR, "Error binding control socket: ", strerror(errno)); + } close(sockfd); free(name); return; @@ -493,27 +484,34 @@ static void open_control_socket(EventLoop_t *loop) noexcept // No connections can be made until we listen, so it is fine to change the permissions now // (and anyway there is no way to atomically create the socket and set permissions): if (chmod(saddrname, S_IRUSR | S_IWUSR) == -1) { - log(LogLevel::ERROR, "Error setting control socket permissions: ", strerror(errno)); + log(loglevel_t::ERROR, "Error setting control socket permissions: ", strerror(errno)); close(sockfd); return; } if (listen(sockfd, 10) == -1) { - log(LogLevel::ERROR, "Error listening on control socket: ", strerror(errno)); + log(loglevel_t::ERROR, "Error listening on control socket: ", strerror(errno)); close(sockfd); return; } - control_socket_open = true; - control_socket_io.registerWith(&eventLoop, sockfd, in_events); + try { + control_socket_io.add_watch(eventLoop, sockfd, IN_EVENTS); + control_socket_open = true; + } + catch (std::exception &e) + { + log(loglevel_t::ERROR, "Could not setup I/O on control socket: ", e.what()); + close(sockfd); + } } } -static void close_control_socket(EventLoop_t *loop) noexcept +static void close_control_socket() noexcept { if (control_socket_open) { - int fd = control_socket_io.fd; - control_socket_io.deregisterWatch(&eventLoop); + int fd = control_socket_io.get_watched_fd(); + control_socket_io.deregister(eventLoop); close(fd); // Unlink the socket: @@ -521,44 +519,45 @@ static void close_control_socket(EventLoop_t *loop) noexcept } } -static void setup_external_log() noexcept +void setup_external_log() noexcept { if (! external_log_open) { const char * saddrname = log_socket_path; - uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen(saddrname) + 1; + size_t saddrname_len = strlen(saddrname); + uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + saddrname_len + 1; struct sockaddr_un * name = static_cast(malloc(sockaddr_size)); if (name == nullptr) { - log(LogLevel::ERROR, "Connecting to log socket: out of memory"); + log(loglevel_t::ERROR, "Connecting to log socket: out of memory"); return; } name->sun_family = AF_UNIX; - strcpy(name->sun_path, saddrname); + memcpy(name->sun_path, saddrname, saddrname_len + 1); - int sockfd = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + int sockfd = dinit_socket(AF_UNIX, SOCK_DGRAM, 0, SOCK_NONBLOCK | SOCK_CLOEXEC); if (sockfd == -1) { - log(LogLevel::ERROR, "Error creating log socket: ", strerror(errno)); + log(loglevel_t::ERROR, "Error creating log socket: ", strerror(errno)); free(name); return; } if (connect(sockfd, (struct sockaddr *) name, sockaddr_size) == 0 || errno == EINPROGRESS) { - // TODO for EINPROGRESS, set up a watcher so we can properly wait until - // connection is established (or fails) before we pass it to the logging subsystem. + // For EINPROGRESS, connection is still being established; however, we can select on + // the file descriptor so we will be notified when it's ready. In other words we can + // basically use it anyway. try { setup_main_log(sockfd); } catch (std::exception &e) { - log(LogLevel::ERROR, "Setting up log failed: ", e.what()); + log(loglevel_t::ERROR, "Setting up log failed: ", e.what()); close(sockfd); } } else { // Note if connect fails, we haven't warned at all, because the syslog server might not - // have started yet. TODO, have a special startup flag to indicate when syslog should - // be available. + // have started yet. close(sockfd); } @@ -566,33 +565,24 @@ static void setup_external_log() noexcept } } -// Called from service when the system is "rw ready" (filesystem mounted r/w etc). -// May be called more than once. -void system_rw_ready() noexcept -{ - open_control_socket(&eventLoop); - setup_external_log(); -} - -/* handle SIGINT signal (generated by kernel when ctrl+alt+del pressed) */ -static void sigint_reboot_cb(EventLoop_t *eloop) noexcept +/* handle SIGINT signal (generated by Linux kernel when ctrl+alt+del pressed) */ +static void sigint_reboot_cb(eventloop_t &eloop) noexcept { - service_set->stop_all_services(ShutdownType::REBOOT); + services->stop_all_services(shutdown_type_t::REBOOT); } /* handle SIGQUIT (if we are system init) */ -static void sigquit_cb(EventLoop_t *eloop) noexcept +static void sigquit_cb(eventloop_t &eloop) noexcept { - // This allows remounting the filesystem read-only if the dinit binary has been - // unlinked. In that case the kernel holds the binary open, so that it can't be - // properly removed. - close_control_socket(eloop); - execl("/sbin/shutdown", "/sbin/shutdown", (char *) 0); - log(LogLevel::ERROR, "Error executing /sbin/shutdown: ", strerror(errno)); + // This performs an immediate shutdown, without service rollback. + close_control_socket(); + execl("/sbin/shutdown", "/sbin/shutdown", "--system", (char *) 0); + log(loglevel_t::ERROR, "Error executing /sbin/shutdown: ", strerror(errno)); + sync(); // since a hard poweroff might be required at this point... } -/* handle SIGTERM/SIGQUIT - stop all services (not used for system daemon) */ -static void sigterm_cb(EventLoop_t *eloop) noexcept +/* handle SIGTERM/SIGQUIT(non-system-daemon) - stop all services and shut down */ +static void sigterm_cb(eventloop_t &eloop) noexcept { - service_set->stop_all_services(); + services->stop_all_services(); }