#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>
+#ifdef __linux__
+#include <sys/prctl.h>
+#include <sys/klog.h>
+#include <sys/reboot.h>
+#endif
+#include "dinit.h"
#include "dasynq.h"
#include "service.h"
#include "control.h"
#include "dinit-log.h"
#include "dinit-socket.h"
-#ifdef __linux__
-#include <sys/klog.h>
-#include <sys/reboot.h>
-#endif
-
/*
* When running as the system init process, Dinit processes the following signals:
*
* services even if the halt/reboot commands are unavailable for some reason.
*/
+using eventloop_t = dasynq::event_loop<dasynq::null_mutex>;
-using namespace dasynq;
-using eventloop_t = event_loop<null_mutex>;
-
-eventloop_t eventLoop = eventloop_t();
+eventloop_t event_loop;
static void sigint_reboot_cb(eventloop_t &eloop) noexcept;
static void sigquit_cb(eventloop_t &eloop) noexcept;
static void control_socket_cb(eventloop_t *loop, int fd);
-void open_control_socket(bool report_ro_failure = true) noexcept;
-void setup_external_log() noexcept;
-
// Variables
namespace {
class callback_signal_handler : public eventloop_t::signal_watcher_impl<callback_signal_handler>
{
+ using rearm = dasynq::rearm;
+
public:
typedef void (*cb_func_t)(eventloop_t &);
class control_socket_watcher : public eventloop_t::fd_watcher_impl<control_socket_watcher>
{
+ using rearm = dasynq::rearm;
+
public:
rearm fd_event(eventloop_t &loop, int fd, int flags) noexcept
{
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;
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.
sigint_watcher.setCbFunc(sigterm_cb);
}
- sigint_watcher.add_watch(eventLoop, SIGINT);
- sigterm_watcher.add_watch(eventLoop, SIGTERM);
+ sigint_watcher.add_watch(event_loop, SIGINT);
+ sigterm_watcher.add_watch(event_loop, SIGTERM);
if (am_system_init) {
// PID 1: SIGQUIT exec's shutdown
- sigquit_watcher.add_watch(eventLoop, SIGQUIT);
+ sigquit_watcher.add_watch(event_loop, SIGQUIT);
// As a user process, we instead just let SIGQUIT perform the default action.
}
// 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 */
// exit if user process).
}
catch (service_not_found &snf) {
- log(LogLevel::ERROR, snf.serviceName, ": Could not find service description.");
+ log(loglevel_t::ERROR, snf.serviceName, ": Could not find service description.");
}
catch (service_load_exc &sle) {
- log(LogLevel::ERROR, sle.serviceName, ": ", sle.excDescription);
+ log(loglevel_t::ERROR, sle.serviceName, ": ", sle.excDescription);
}
catch (std::bad_alloc &badalloce) {
- log(LogLevel::ERROR, "Out of memory when trying to start service: ", svc, ".");
+ log(loglevel_t::ERROR, "Out of memory when trying to start service: ", svc, ".");
break;
}
}
// Process events until all services have terminated.
while (services->count_active_services() != 0) {
- eventLoop.run();
+ event_loop.run();
}
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 == shutdown_type_t::REBOOT) {
- logMsgEnd(" Will reboot.");
+ log_msg_end(" Will reboot.");
}
else if (shutdown_type == shutdown_type_t::HALT) {
- logMsgEnd(" Will halt.");
+ log_msg_end(" Will halt.");
}
else if (shutdown_type == shutdown_type_t::POWEROFF) {
- logMsgEnd(" Will power down.");
+ log_msg_end(" Will power down.");
}
else {
- logMsgEnd(" Re-initiating boot sequence.");
+ log_msg_end(" Re-initiating boot sequence.");
}
}
while (! is_log_flushed()) {
- eventLoop.run();
+ event_loop.run();
}
close_control_socket();
}
catch (...) {
// Now what do we do? try to reboot, but wait for user ack to avoid boot loop.
- log(LogLevel::ERROR, "Could not start 'boot' service. Will attempt reboot.");
+ log(loglevel_t::ERROR, "Could not start 'boot' service. Will attempt reboot.");
wait_for_user_input();
shutdown_type = shutdown_type_t::REBOOT;
}
// 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();
+ event_loop.run();
}
}
else if (shutdown_type == shutdown_type_t::REBOOT) {
// Callback for control socket
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 = dinit_accept4(sockfd, nullptr, nullptr, SOCK_NONBLOCK | SOCK_CLOEXEC);
if (newfd != -1) {
try {
- new control_conn_t(loop, services, newfd); // will delete itself when it's finished
+ new control_conn_t(*loop, services, newfd); // will delete itself when it's finished
}
catch (std::exception &exc) {
- log(LogLevel::ERROR, "Accepting control connection: ", exc.what());
+ log(loglevel_t::ERROR, "Accepting control connection: ", exc.what());
close(newfd);
}
}
struct sockaddr_un * name = static_cast<sockaddr_un *>(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;
}
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) {
if (errno != EROFS || report_ro_failure) {
- log(LogLevel::ERROR, "Error binding control socket: ", strerror(errno));
+ log(loglevel_t::ERROR, "Error binding control socket: ", strerror(errno));
}
close(sockfd);
free(name);
// 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;
}
try {
- control_socket_io.add_watch(eventLoop, sockfd, IN_EVENTS);
+ control_socket_io.add_watch(event_loop, sockfd, dasynq::IN_EVENTS);
control_socket_open = true;
}
catch (std::exception &e)
{
- log(LogLevel::ERROR, "Could not setup I/O on control socket: ", e.what());
+ log(loglevel_t::ERROR, "Could not setup I/O on control socket: ", e.what());
close(sockfd);
}
}
{
if (control_socket_open) {
int fd = control_socket_io.get_watched_fd();
- control_socket_io.deregister(eventLoop);
+ control_socket_io.deregister(event_loop);
close(fd);
// Unlink the socket:
struct sockaddr_un * name = static_cast<sockaddr_un *>(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;
}
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);
}
}
// This performs an immediate shutdown, without service rollback.
close_control_socket();
execl("/sbin/shutdown", "/sbin/shutdown", "--system", (char *) 0);
- log(LogLevel::ERROR, "Error executing /sbin/shutdown: ", strerror(errno));
+ log(loglevel_t::ERROR, "Error executing /sbin/shutdown: ", strerror(errno));
sync(); // since a hard poweroff might be required at this point...
}