#include "cgroup.h"
#include "elogind.h"
#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
#include "mount-setup.h"
+#include "parse-util.h"
+#include "process-util.h"
+#include "signal-util.h"
#include "socket-util.h"
+#include "stdio-util.h"
#include "string-util.h"
#include "strv.h"
#include "umask-util.h"
#define CGROUPS_AGENT_RCVBUF_SIZE (8*1024*1024)
+#ifndef ELOGIND_PID_FILE
+# define ELOGIND_PID_FILE "/run/elogind.pid"
+#endif // ELOGIND_PID_FILE
-static int signal_agent_released(sd_bus_message *message, void *userdata, sd_bus_error *error) {
- _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+static int elogind_signal_handler(sd_event_source *s,
+ const struct signalfd_siginfo *si,
+ void *userdata) {
Manager *m = userdata;
- const char *cgroup;
- uid_t sender_uid;
int r;
- assert(message);
- assert(m);
+ log_warning("Received signal %u [%s]", si->ssi_signo,
+ signal_to_string(si->ssi_signo));
+
+ r = sd_event_get_state(m->event);
+
+ if (r != SD_EVENT_FINISHED)
+ sd_event_exit(m->event, si->ssi_signo);
+
+ return 0;
+}
+
+
+static void remove_pid_file(void) {
+ if (access(ELOGIND_PID_FILE, F_OK) == 0)
+ unlink_noerrno(ELOGIND_PID_FILE);
+}
+
+
+static void write_pid_file(void) {
+ char c[DECIMAL_STR_MAX(pid_t) + 2];
+ pid_t pid;
+ int r;
+
+ pid = getpid();
- /* only accept org.freedesktop.login1.Agent from UID=0 */
- r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
+ xsprintf(c, PID_FMT "\n", pid);
+
+ r = write_string_file(ELOGIND_PID_FILE, c,
+ WRITE_STRING_FILE_CREATE |
+ WRITE_STRING_FILE_VERIFY_ON_FAILURE);
if (r < 0)
- return r;
+ log_error_errno(-r, "Failed to write PID file %s: %m",
+ ELOGIND_PID_FILE);
- r = sd_bus_creds_get_euid(creds, &sender_uid);
- if (r < 0 || sender_uid != 0)
- return 0;
+ /* Make sure the PID file gets cleaned up on exit! */
+ atexit(remove_pid_file);
+}
- /* parse 'cgroup-empty' notification */
- r = sd_bus_message_read(message, "s", &cgroup);
- if (r < 0) {
- bus_log_parse_error(r);
- return 0;
+
+/** daemonize elogind by double forking.
+ * The grand child returns 0.
+ * The parent and child return their forks PID.
+ * On error, a value < 0 is returned.
+**/
+static int elogind_daemonize(void) {
+ pid_t child;
+ pid_t grandchild;
+ pid_t SID;
+ int r;
+
+#ifdef ENABLE_DEBUG_ELOGIND
+ log_notice("Double forking elogind");
+ log_notice("Parent PID : %5d", getpid());
+ log_notice("Parent SID : %5d", getsid(getpid()));
+#endif // ENABLE_DEBUG_ELOGIND
+
+ child = fork();
+
+ if (child < 0)
+ return log_error_errno(errno, "Failed to fork: %m");
+
+ if (child) {
+ /* Wait for the child to terminate, so the decoupling
+ * is guaranteed to succeed.
+ */
+ r = wait_for_terminate_and_warn("elogind control child", child, true);
+ if (r < 0)
+ return r;
+ return child;
}
- manager_notify_cgroup_empty(m, cgroup);
+#ifdef ENABLE_DEBUG_ELOGIND
+ log_notice("Child PID : %5d", getpid());
+ log_notice("Child SID : %5d", getsid(getpid()));
+#endif // ENABLE_DEBUG_ELOGIND
+
+ /* The first child has to become a new session leader. */
+ close_all_fds(NULL, 0);
+ SID = setsid();
+ if ((pid_t)-1 == SID)
+ return log_error_errno(errno, "Failed to create new SID: %m");
+
+#ifdef ENABLE_DEBUG_ELOGIND
+ log_notice("Child new SID : %5d", getsid(getpid()));
+#endif // ENABLE_DEBUG_ELOGIND
+
+ umask(0022);
+
+ /* Now the grandchild, the true daemon, can be created. */
+ grandchild = fork();
+
+ if (grandchild < 0)
+ return log_error_errno(errno, "Failed to double fork: %m");
+
+ if (grandchild)
+ /* Exit immediately! */
+ return grandchild;
+
+ close_all_fds(NULL, 0);
+ umask(0022);
+
+#ifdef ENABLE_DEBUG_ELOGIND
+ log_notice("Grand child PID: %5d", getpid());
+ log_notice("Grand child SID: %5d", getsid(getpid()));
+#endif // ENABLE_DEBUG_ELOGIND
+
+ /* Take care of our PID-file now */
+ write_pid_file();
return 0;
}
-/// Add-On for manager_connect_bus()
-/// Original: src/core/dbus.c:bus_setup_system()
-void elogind_bus_setup_system(Manager* m) {
+
+/// Simple tool to see, if elogind is already running
+static pid_t elogind_is_already_running(bool need_pid_file) {
+ _cleanup_free_ char *s = NULL;
+ pid_t pid;
int r;
- assert(m);
- assert(m->bus);
-
- /* if we are a user instance we get the Released message via the system bus */
- if (MANAGER_IS_USER(m)) {
- r = sd_bus_add_match(
- m->bus,
- NULL,
- "type='signal',"
- "interface='org.freedesktop.login1.Agent',"
- "member='Released',"
- "path='/org/freedesktop.login1/agent'",
- signal_agent_released, m);
- if (r < 0)
- log_warning_errno(r, "Failed to register Released match on system bus: %m");
- }
+ r = read_one_line_file(ELOGIND_PID_FILE, &s);
+
+ if (r < 0)
+ goto we_are_alone;
+
+ r = safe_atoi32(s, &pid);
+
+ if (r < 0)
+ goto we_are_alone;
- log_debug("Successfully connected to system bus.");
+ if ( (pid != getpid()) && pid_is_alive(pid))
+ return pid;
+
+we_are_alone:
+
+ /* Take care of our PID-file now.
+ If the user is going to fork elogind, the PID file
+ will be overwritten. */
+ if (need_pid_file)
+ write_pid_file();
+
+ return 0;
}
+
static int manager_dispatch_cgroups_agent_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
Manager *m = userdata;
char buf[PATH_MAX+1];
return 0;
}
+
/// Add-On for manager_connect_bus()
/// Original: src/core/manager.c:manager_setup_cgroups_agent()
int elogind_setup_cgroups_agent(Manager *m) {
if (!MANAGER_IS_SYSTEM(m))
return 0;
- if (cg_unified(SYSTEMD_CGROUP_CONTROLLER) > 0) /* We don't need this anymore on the unified hierarchy */
+ r = cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine whether unified cgroups hierarchy is used: %m");
+ if (r > 0) /* We don't need this anymore on the unified hierarchy */
return 0;
if (m->cgroups_agent_fd < 0) {
return 0;
}
+
+/** Extra functionality at startup, exclusive to elogind
+ * return < 0 on error, exit with failure.
+ * return = 0 on success, continue normal operation.
+ * return > 0 if elogind is already running or forked, exit with success.
+**/
+int elogind_startup(int argc, char *argv[]) {
+ bool daemonize = false;
+ pid_t pid;
+ int r = 0;
+ bool show_help = false;
+ bool wrong_arg = false;
+
+ /* add a -h/--help and a -d/--daemon argument. */
+ if ( (argc == 2) && argv[1] && strlen(argv[1]) ) {
+ if ( streq(argv[1], "-D") || streq(argv[1], "--daemon") )
+ daemonize = true;
+ else if ( streq(argv[1], "-h") || streq(argv[1], "--help") ) {
+ show_help = true;
+ r = 1;
+ } else
+ wrong_arg = true;
+ } else if (argc > 2)
+ wrong_arg = true;
+
+ /* Note: At this point, the logging is not initialized, so we can not
+ use log_debug_elogind(). */
+#ifdef ENABLE_DEBUG_ELOGIND
+ log_notice("elogind startup: Daemonize: %s, Show Help: %s, Wrong arg: %s",
+ daemonize ? "True" : "False",
+ show_help ? "True" : "False",
+ wrong_arg ? "True" : "False");
+#endif // ENABLE_DEBUG_ELOGIND
+
+ /* try to get some meaningful output in case of an error */
+ if (wrong_arg) {
+ log_error("Unknown arguments");
+ show_help = true;
+ r = -EINVAL;
+ }
+ if (show_help) {
+ log_info("%s [<-D|--daemon>|<-h|--help>]", basename(argv[0]));
+ return r;
+ }
+
+ /* Do not continue if elogind is already running */
+ pid = elogind_is_already_running(!daemonize);
+ if (pid) {
+ log_error("elogind is already running as PID " PID_FMT, pid);
+ return pid;
+ }
+
+ /* elogind allows to be daemonized using one argument "-D" / "--daemon" */
+ if (daemonize)
+ r = elogind_daemonize();
+
+ return r;
+}
+
+
/// Add-On for manager_free()
void elogind_manager_free(Manager* m) {
strv_free(m->hybrid_sleep_state);
}
+
/// Add-On for manager_new()
int elogind_manager_new(Manager* m) {
int r = 0;
m->hybrid_sleep_state = NULL;
/* If elogind should be its own controller, mount its cgroup */
- if (streq(SYSTEMD_CGROUP_CONTROLLER, "name=elogind")) {
+ if (streq(SYSTEMD_CGROUP_CONTROLLER, "_elogind")) {
m->is_system = true;
r = mount_setup(true);
} else
return r;
}
+
/// Add-On for manager_reset_config()
void elogind_manager_reset_config(Manager* m) {
dbg_cnt, m->hybrid_sleep_state[dbg_cnt]);
#endif // ENABLE_DEBUG_ELOGIND
}
+
+
+/// Add-On for manager_startup()
+int elogind_manager_startup(Manager *m) {
+ int r;
+
+ assert(m);
+
+ assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, -1) >= 0);
+ r = sd_event_add_signal(m->event, NULL, SIGINT, elogind_signal_handler, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to register SIGINT handler: %m");
+
+ assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGQUIT, -1) >= 0);
+ r = sd_event_add_signal(m->event, NULL, SIGQUIT, elogind_signal_handler, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to register SIGQUIT handler: %m");
+
+ assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGTERM, -1) >= 0);
+ r = sd_event_add_signal(m->event, NULL, SIGTERM, elogind_signal_handler, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to register SIGTERM handler: %m");
+
+ return 0;
+}