#include <iostream>
+#include <fstream>
#include <list>
#include <cstring>
#include <csignal>
#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>
+#ifdef __linux__
+#include <sys/prctl.h>
+#include <sys/klog.h>
+#include <sys/reboot.h>
+#endif
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+#include <sys/procctl.h>
+#endif
+#include "dinit.h"
#include "dasynq.h"
#include "service.h"
#include "control.h"
#include "dinit-log.h"
#include "dinit-socket.h"
+#include "static-string.h"
-#ifdef __linux__
-#include <sys/klog.h>
-#include <sys/reboot.h>
-#endif
+#include "mconfig.h"
/*
* 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 namespace cts;
-using namespace dasynq;
-using EventLoop_t = event_loop<null_mutex>;
+using eventloop_t = dasynq::event_loop<dasynq::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 sigterm_cb(EventLoop_t &eloop) 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 close_control_socket() noexcept;
static void wait_for_user_input() noexcept;
+static void read_env_file(const char *);
-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;
-
-
-class ControlSocketWatcher : public EventLoop_t::fd_watcher_impl<ControlSocketWatcher>
-{
- public:
- rearm fd_event(EventLoop_t &loop, int fd, int flags) noexcept
- {
- control_socket_cb(&loop, fd);
- return rearm::REARM;
- }
-};
-
-ControlSocketWatcher control_socket_io;
+static void control_socket_cb(eventloop_t *loop, int fd);
// Variables
-static ServiceSet *service_set;
+static dirload_service_set *services;
+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 control_socket_open = false;
// Control socket path. We maintain a string (control_socket_str) in case we need
// to allocate storage, but control_socket_path is the authoritative value.
-static const char *control_socket_path = "/dev/dinitctl";
+static const char *control_socket_path = SYSCONTROLSOCKET;
static std::string control_socket_str;
-static const char *log_socket_path = "/dev/log";
+static const char *env_file_path = "/etc/dinit/environment";
+
+static const char *log_path = "/dev/log";
+static bool log_is_syslog = true; // if false, log is a file
static const char *user_home_path = nullptr;
namespace {
- class CallbackSignalHandler : public EventLoop_t::signal_watcher_impl<CallbackSignalHandler>
+ // Event-loop handler for a signal, which just delegates to a function (pointer).
+ 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 &);
+ 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)
+ void set_cb_func(cb_func_t cb_func)
{
this->cb_func = cb_func;
}
- rearm received(EventLoop_t &eloop, int signo, siginfo_p siginfo)
+ rearm received(eventloop_t &eloop, int signo, siginfo_p siginfo)
{
cb_func(eloop);
return rearm::REARM;
}
};
- class ControlSocketWatcher : public EventLoop_t::fd_watcher_impl<ControlSocketWatcher>
+ // Event-loop handler for when a connection is made to the control socket.
+ class control_socket_watcher : public eventloop_t::fd_watcher_impl<control_socket_watcher>
{
- rearm fd_event(EventLoop_t &loop, int fd, int flags)
+ using rearm = dasynq::rearm;
+
+ public:
+ rearm fd_event(eventloop_t &loop, int fd, int flags) noexcept
{
control_socket_cb(&loop, fd);
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>
+ {
+ using rearm = dasynq::rearm;
+
+ bool expired = false;
+
+ public:
+ rearm timer_expiry(eventloop_t &, int expiry_count)
+ {
+ expired = true;
+ return rearm::DISARM;
+ }
+
+ bool has_expired()
+ {
+ return expired;
+ }
+ };
+
+ control_socket_watcher control_socket_io;
+ log_flush_timer_t log_flush_timer;
}
-static int dinit_main(int argc, char **argv)
+int dinit_main(int argc, char **argv)
{
using namespace std;
- am_system_init = (getpid() == 1);
+ am_pid_one = (getpid() == 1);
+ am_system_init = (getuid() == 0);
const char * service_dir = nullptr;
- string service_dir_str; // to hold storage for above if necessary
+ bool service_dir_dynamic = false; // service_dir dynamically allocated?
+ const char * env_file = nullptr;
bool control_socket_path_set = false;
+ bool env_file_set = false;
// list of services to start
list<const char *> services_to_start;
// Arguments, if given, specify a list of services to start.
- // If we are running as init (PID=1), the kernel gives us any command line
- // arguments it was given but didn't recognize, including "single" (usually
- // for "boot to single user mode" aka just start the shell). We can treat
- // them as service names. In the worst case we can't find any of the named
+ // If we are running as init (PID=1), the Linux kernel gives us any command line arguments it was given
+ // but didn't recognize, including "single" (usually for "boot to single user mode" aka just start the
+ // shell). We can treat them as service names. In the worst case we can't find any of the named
// services, and so we'll start the "boot" service by default.
if (argc > 1) {
for (int i = 1; i < argc; i++) {
if (argv[i][0] == '-') {
// An option...
- if (strcmp(argv[i], "--services-dir") == 0 ||
- strcmp(argv[i], "-d") == 0) {
+ if (strcmp(argv[i], "--env-file") == 0 || strcmp(argv[i], "-e") == 0) {
+ if (++i < argc) {
+ env_file_set = true;
+ env_file = argv[i];
+ }
+ else {
+ cerr << "dinit: '--env-file' (-e) requires an argument" << endl;
+ }
+ }
+ else if (strcmp(argv[i], "--services-dir") == 0 || strcmp(argv[i], "-d") == 0) {
if (++i < argc) {
service_dir = argv[i];
}
return 1;
}
}
- else if (strcmp(argv[i], "--system") == 0 ||
- strcmp(argv[i], "-s") == 0) {
+ else if (strcmp(argv[i], "--system") == 0 || strcmp(argv[i], "-s") == 0) {
am_system_init = true;
}
- else if (strcmp(argv[i], "--socket-path") == 0 ||
- strcmp(argv[i], "-p") == 0) {
+ else if (strcmp(argv[i], "--socket-path") == 0 || strcmp(argv[i], "-p") == 0) {
if (++i < argc) {
control_socket_path = argv[i];
control_socket_path_set = true;
return 1;
}
}
+ else if (strcmp(argv[i], "--log-file") == 0 || strcmp(argv[i], "-l") == 0) {
+ if (++i < argc) {
+ log_path = argv[i];
+ log_is_syslog = false;
+ }
+ else {
+ cerr << "dinit: '--log-file' (-l) requires an argument" << endl;
+ return 1;
+ }
+ }
else if (strcmp(argv[i], "--help") == 0) {
- cout << "dinit, an init with dependency management" << endl;
- cout << " --help display help" << endl;
- cout << " --services-dir <dir>, -d <dir>" << endl;
- cout << " set base directory for service description" << endl;
- cout << " files (-d <dir>)" << endl;
- cout << " --system, -s run as the system init process" << endl;
- cout << " --socket-path <path>, -p <path>" << endl;
- cout << " path to control socket" << endl;
- cout << " <service-name> start service with name <service-name>" << endl;
+ cout << "dinit, an init with dependency management\n"
+ " --help display help\n"
+ " --env-file <file>, -e <file>\n"
+ " environment variable initialisation file\n"
+ " --services-dir <dir>, -d <dir>\n"
+ " set base directory for service description\n"
+ " files (-d <dir>)\n"
+ " --system, -s run as the system init process\n"
+ " --socket-path <path>, -p <path>\n"
+ " path to control socket\n"
+ " <service-name> start service with name <service-name>\n";
return 0;
}
else {
if (! am_system_init || strcmp(argv[i], "auto") != 0) {
services_to_start.push_back(argv[i]);
}
+#else
+ services_to_start.push_back(argv[i]);
#endif
}
}
if (onefd > 2) close(onefd);
if (twofd > 2) close(twofd);
+
+ if (! env_file_set) {
+ env_file = env_file_path;
+ }
}
-
+
/* Set up signal handlers etc */
/* SIG_CHILD is ignored by default: good */
sigset_t sigwait_set;
sigaddset(&sigwait_set, SIGCHLD);
sigaddset(&sigwait_set, SIGINT);
sigaddset(&sigwait_set, SIGTERM);
- if (am_system_init) sigaddset(&sigwait_set, SIGQUIT);
+ if (am_pid_one) 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.
if (service_dir == nullptr && ! am_system_init) {
const char * userhome = get_user_home();
if (userhome != nullptr) {
- service_dir_str = get_user_home();
- service_dir_str += "/dinit.d";
- service_dir = service_dir_str.c_str();
+ const char * user_home = get_user_home();
+ size_t user_home_len = strlen(user_home);
+ size_t dinit_d_len = strlen("/dinit.d");
+ size_t full_len = user_home_len + dinit_d_len + 1;
+ char *service_dir_w = new char[full_len];
+ std::memcpy(service_dir_w, user_home, user_home_len);
+ std::memcpy(service_dir_w + user_home_len, "/dinit.d", dinit_d_len);
+ service_dir_w[full_len - 1] = 0;
+
+ service_dir = service_dir_w;
+ service_dir_dynamic = true;
}
}
- if (service_dir == nullptr) {
- service_dir = "/etc/dinit.d";
- }
-
if (services_to_start.empty()) {
services_to_start.push_back("boot");
}
// Set up signal handlers
- CallbackSignalHandler sigterm_watcher {sigterm_cb};
- CallbackSignalHandler sigint_watcher;
- CallbackSignalHandler sigquit_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);
- sigquit_watcher.setCbFunc(sigquit_cb);
+ if (am_pid_one) {
+ sigint_watcher.set_cb_func(sigint_reboot_cb);
+ sigquit_watcher.set_cb_func(sigquit_cb);
}
else {
- sigint_watcher.setCbFunc(sigterm_cb);
+ sigint_watcher.set_cb_func(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) {
+ if (am_pid_one) {
// 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);
+#elif defined(__FreeBSD__) || defined(__DragonFly__)
+ // Documentation (man page) for this kind of sucks. PROC_REAP_ACQUIRE "acquires the reaper status for
+ // the current process" but does that mean the first two arguments still need valid values to be
+ // supplied? We'll play it safe and explicitly target our own process:
+ procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL);
#endif
+ log_flush_timer.add_timer(event_loop, dasynq::clock_type::MONOTONIC);
+
+ bool add_all_service_dirs = false;
+ if (service_dir == nullptr) {
+ service_dir = "/etc/dinit.d";
+ add_all_service_dirs = true;
+ }
+
/* start requested services */
- service_set = new ServiceSet(service_dir);
+ services = new dirload_service_set(service_dir, service_dir_dynamic);
+ if (add_all_service_dirs) {
+ services->add_service_dir("/usr/local/lib/dinit.d", false);
+ services->add_service_dir("/lib/dinit.d", false);
+ }
- init_log(service_set);
+ init_log(services, log_is_syslog);
+ if (am_system_init) {
+ log(loglevel_t::INFO, false, "starting system");
+ }
+ // Only try to set up the external log now if we aren't the system init. (If we are the
+ // system init, wait until the log service starts).
+ if (! am_system_init) setup_external_log();
+
+ if (env_file != nullptr) {
+ read_env_file(env_file);
+ }
+
for (auto svc : services_to_start) {
try {
- service_set->startService(svc);
+ 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: ", svc, ".");
+ 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) {
- eventLoop.run();
+ while (services->count_active_services() != 0) {
+ event_loop.run();
}
- ShutdownType shutdown_type = service_set->getShutdownType();
+ shutdown_type_t shutdown_type = services->get_shutdown_type();
- if (am_system_init) {
- logMsgBegin(LogLevel::INFO, "No more active services.");
+ if (am_pid_one) {
+ 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.");
}
}
- while (! is_log_flushed()) {
- eventLoop.run();
+ log_flush_timer.arm_timer_rel(event_loop, timespec{5,0}); // 5 seconds
+ while (! is_log_flushed() && ! log_flush_timer.has_expired()) {
+ event_loop.run();
}
close_control_socket();
- if (am_system_init) {
- if (shutdown_type == ShutdownType::CONTINUE) {
+ 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 {
- service_set->startService("boot");
+ 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::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 = ShutdownType::REBOOT;
+ 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 {
}
// Fork and execute dinit-reboot.
- execl("/sbin/shutdown", "/sbin/shutdown", "--system", cmd_arg, nullptr);
- log(LogLevel::ERROR, "Could not execute /sbin/shutdown: ", strerror(errno));
+ constexpr auto shutdown_exec = literal(SBINDIR) + "/shutdown";
+ execl(shutdown_exec.c_str(), shutdown_exec.c_str(), "--system", cmd_arg, nullptr);
+ log(loglevel_t::ERROR, (literal("Could not execute ") + SBINDIR + "/shutdown: ").c_str(),
+ 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 == ShutdownType::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);
return 0;
}
-int main(int argc, char **argv)
+// Log a parse error when reading the environment file.
+static void log_bad_env(int linenum)
{
- try {
- return dinit_main(argc, argv);
- }
- catch (std::bad_alloc &badalloc) {
- std::cout << "dinit: Out-of-memory during initialisation" << std::endl;
- return 1;
- }
- catch (std::system_error &syserr) {
- std::cout << "dinit: unexpected system error during initialisation: " << syserr.what() << std::endl;
- return 1;
- }
- catch (...) {
- std::cout << "dinit: unexpected error during initialisation" << std::endl;
- return 1;
+ log(loglevel_t::ERROR, "invalid environment variable setting in environment file (line ", linenum, ")");
+}
+
+// Read and set environment variables from a file.
+static void read_env_file(const char *env_file_path)
+{
+ // Note that we can't use the log in this function; it hasn't been initialised yet.
+
+ std::ifstream env_file(env_file_path);
+ if (! env_file) return;
+
+ env_file.exceptions(std::ios::badbit);
+
+ auto &clocale = std::locale::classic();
+ std::string line;
+ int linenum = 0;
+
+ while (std::getline(env_file, line)) {
+ linenum++;
+ auto lpos = line.begin();
+ auto lend = line.end();
+ while (lpos != lend && std::isspace(*lpos, clocale)) {
+ ++lpos;
+ }
+
+ if (lpos != lend) {
+ if (*lpos != '#') {
+ if (*lpos == '=') {
+ log_bad_env(linenum);
+ continue;
+ }
+ auto name_begin = lpos++;
+ // skip until '=' or whitespace:
+ while (lpos != lend && *lpos != '=' && ! std::isspace(*lpos, clocale)) ++lpos;
+ auto name_end = lpos;
+ // skip whitespace:
+ while (lpos != lend && std::isspace(*lpos, clocale)) ++lpos;
+ if (lpos == lend) {
+ log_bad_env(linenum);
+ continue;
+ }
+
+ ++lpos;
+ auto val_begin = lpos;
+ while (lpos != lend && *lpos != '\n') ++lpos;
+ auto val_end = lpos;
+
+ std::string name = line.substr(name_begin - line.begin(), name_end - name_begin);
+ std::string value = line.substr(val_begin - line.begin(), val_end - val_begin);
+ if (setenv(name.c_str(), value.c_str(), true) == -1) {
+ throw std::system_error(errno, std::system_category());
+ }
+ }
+ }
}
}
}
// 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 = 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::exception &exc) {
- log(LogLevel::ERROR, "Accepting control connection: ", exc.what());
+ log(loglevel_t::ERROR, "Accepting control connection: ", exc.what());
close(newfd);
}
}
}
+// 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
{
if (! control_socket_open) {
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:
unlink(control_socket_path);
+
+ control_socket_open = false;
}
}
void setup_external_log() noexcept
{
if (! external_log_open) {
-
- const char * saddrname = log_socket_path;
- size_t saddrname_len = strlen(saddrname);
- uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + saddrname_len + 1;
-
- struct sockaddr_un * name = static_cast<sockaddr_un *>(malloc(sockaddr_size));
- if (name == nullptr) {
- log(LogLevel::ERROR, "Connecting to log socket: out of memory");
- return;
- }
-
- name->sun_family = AF_UNIX;
- memcpy(name->sun_path, saddrname, saddrname_len + 1);
-
- 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));
- 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.
- try {
- setup_main_log(sockfd);
+ if (log_is_syslog) {
+ const char * saddrname = log_path;
+ size_t saddrname_len = strlen(saddrname);
+ uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + saddrname_len + 1;
+
+ struct sockaddr_un * name = static_cast<sockaddr_un *>(malloc(sockaddr_size));
+ if (name == nullptr) {
+ log(loglevel_t::ERROR, "Connecting to log socket: out of memory");
+ return;
+ }
+
+ name->sun_family = AF_UNIX;
+ memcpy(name->sun_path, saddrname, saddrname_len + 1);
+
+ int sockfd = dinit_socket(AF_UNIX, SOCK_DGRAM, 0, SOCK_NONBLOCK | SOCK_CLOEXEC);
+ if (sockfd == -1) {
+ log(loglevel_t::ERROR, "Error creating log socket: ", strerror(errno));
+ free(name);
+ return;
}
- catch (std::exception &e) {
- log(LogLevel::ERROR, "Setting up log failed: ", e.what());
+
+ if (connect(sockfd, (struct sockaddr *) name, sockaddr_size) == 0 || errno == EINPROGRESS) {
+ // 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);
+ external_log_open = true;
+ }
+ catch (std::exception &e) {
+ 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.
close(sockfd);
}
+
+ free(name);
}
else {
- // Note if connect fails, we haven't warned at all, because the syslog server might not
- // have started yet.
- close(sockfd);
+ // log to file:
+ int log_fd = open(log_path, O_WRONLY | O_CREAT | O_APPEND | O_NONBLOCK | O_CLOEXEC, 0644);
+ if (log_fd >= 0) {
+ try {
+ setup_main_log(log_fd);
+ external_log_open = true;
+ }
+ catch (std::exception &e) {
+ log(loglevel_t::ERROR, "Setting up log failed: ", e.what());
+ close(log_fd);
+ }
+ }
+ else {
+ // log failure to log? It makes more sense than first appears, because we also log to console:
+ log(loglevel_t::ERROR, "Setting up log failed: ", strerror(errno));
+ }
}
-
- free(name);
}
}
/* handle SIGINT signal (generated by Linux kernel when ctrl+alt+del pressed) */
-static void sigint_reboot_cb(EventLoop_t &eloop) noexcept
+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 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));
+ constexpr auto shutdown_exec = literal(SBINDIR) + "/shutdown";
+ execl(shutdown_exec.c_str(), shutdown_exec.c_str(), "--system", (char *) 0);
+ log(loglevel_t::ERROR, literal("Error executing ") + SBINDIR + "/sbin/shutdown: ", strerror(errno));
sync(); // since a hard poweroff might be required at this point...
}
/* handle SIGTERM/SIGQUIT(non-system-daemon) - stop all services and shut down */
-static void sigterm_cb(EventLoop_t &eloop) noexcept
+static void sigterm_cb(eventloop_t &eloop) noexcept
{
- service_set->stop_all_services();
+ services->stop_all_services();
}