#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <linux/vt.h>
+#include <sys/timerfd.h>
#include <systemd/sd-daemon.h>
#include "dbus-loop.h"
#include "strv.h"
#include "conf-parser.h"
+#include "mkdir.h"
Manager *manager_new(void) {
Manager *m;
m->udev_vcsa_fd = -1;
m->udev_button_fd = -1;
m->epoll_fd = -1;
+ m->reserve_vt_fd = -1;
m->n_autovts = 6;
+ m->reserve_vt = 6;
m->inhibit_delay_max = 5 * USEC_PER_SEC;
- m->handle_power_key = HANDLE_NO_SESSION;
- m->handle_sleep_key = HANDLE_TTY_SESSION;
- m->handle_lid_switch = HANDLE_OFF;
+ m->handle_power_key = HANDLE_POWEROFF;
+ m->handle_suspend_key = HANDLE_SUSPEND;
+ m->handle_hibernate_key = HANDLE_HIBERNATE;
+ m->handle_lid_switch = HANDLE_SUSPEND;
+ m->lid_switch_ignore_inhibited = true;
+
+ m->idle_action_fd = -1;
+ m->idle_action_usec = 30 * USEC_PER_MINUTE;
+ m->idle_action = HANDLE_IGNORE;
+ m->idle_action_not_before_usec = now(CLOCK_MONOTONIC);
m->devices = hashmap_new(string_hash_func, string_compare_func);
m->seats = hashmap_new(string_hash_func, string_compare_func);
m->inhibitors = hashmap_new(string_hash_func, string_compare_func);
m->buttons = hashmap_new(string_hash_func, string_compare_func);
- m->cgroups = hashmap_new(string_hash_func, string_compare_func);
+ m->user_cgroups = hashmap_new(string_hash_func, string_compare_func);
+ m->session_cgroups = hashmap_new(string_hash_func, string_compare_func);
+
m->session_fds = hashmap_new(trivial_hash_func, trivial_compare_func);
m->inhibitor_fds = hashmap_new(trivial_hash_func, trivial_compare_func);
m->button_fds = hashmap_new(trivial_hash_func, trivial_compare_func);
if (!m->devices || !m->seats || !m->sessions || !m->users || !m->inhibitors || !m->buttons ||
- !m->cgroups || !m->session_fds || !m->inhibitor_fds || !m->button_fds) {
+ !m->user_cgroups || !m->session_cgroups ||
+ !m->session_fds || !m->inhibitor_fds || !m->button_fds) {
manager_free(m);
return NULL;
}
hashmap_free(m->inhibitors);
hashmap_free(m->buttons);
- hashmap_free(m->cgroups);
+ hashmap_free(m->user_cgroups);
+ hashmap_free(m->session_cgroups);
+
hashmap_free(m->session_fds);
hashmap_free(m->inhibitor_fds);
hashmap_free(m->button_fds);
if (m->epoll_fd >= 0)
close_nointr_nofail(m->epoll_fd);
+ if (m->reserve_vt_fd >= 0)
+ close_nointr_nofail(m->reserve_vt_fd);
+
+ if (m->idle_action_fd >= 0)
+ close_nointr_nofail(m->idle_action_fd);
+
strv_free(m->controllers);
strv_free(m->reset_controllers);
strv_free(m->kill_only_users);
strv_free(m->kill_exclude_users);
+ free(m->action_job);
+
free(m->cgroup_path);
free(m);
}
assert(m);
assert(name);
- r = get_user_creds(&name, &uid, &gid, NULL);
+ r = get_user_creds(&name, &uid, &gid, NULL, NULL);
if (r < 0)
return r;
goto finish;
}
- r = udev_enumerate_add_match_subsystem(e, "graphics");
- if (r < 0)
- goto finish;
-
- r = udev_enumerate_add_match_tag(e, "seat");
+ r = udev_enumerate_add_match_tag(e, "master-of-seat");
if (r < 0)
goto finish;
/* Loads buttons from udev */
- if (m->handle_power_key == HANDLE_OFF &&
- m->handle_sleep_key == HANDLE_OFF &&
- m->handle_lid_switch == HANDLE_OFF)
+ if (m->handle_power_key == HANDLE_IGNORE &&
+ m->handle_suspend_key == HANDLE_IGNORE &&
+ m->handle_hibernate_key == HANDLE_IGNORE &&
+ m->handle_lid_switch == HANDLE_IGNORE)
return 0;
e = udev_enumerate_new(m->udev);
}
static int manager_enumerate_users_from_cgroup(Manager *m) {
+ _cleanup_closedir_ DIR *d = NULL;
int r = 0, k;
char *name;
- DIR *d;
r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_path, &d);
if (r < 0) {
while ((k = cg_read_subgroup(d, &name)) > 0) {
User *user;
+ char *e;
- k = manager_add_user_by_name(m, name, &user);
- if (k < 0) {
- free(name);
- r = k;
- continue;
- }
-
- user_add_to_gc_queue(user);
+ e = endswith(name, ".user");
+ if (e) {
+ *e = 0;
- if (!user->cgroup_path)
- if (asprintf(&user->cgroup_path, "%s/%s", m->cgroup_path, name) < 0) {
- r = -ENOMEM;
+ k = manager_add_user_by_name(m, name, &user);
+ if (k < 0) {
free(name);
- break;
+ r = k;
+ continue;
+ }
+
+ user_add_to_gc_queue(user);
+
+ if (!user->cgroup_path) {
+ user->cgroup_path = strjoin(m->cgroup_path, "/", name, NULL);
+ if (!user->cgroup_path) {
+ k = log_oom();
+ free(name);
+ break;
+ }
}
+ }
free(name);
}
- if (r >= 0 && k < 0)
+ if (k < 0)
r = k;
- closedir(d);
-
return r;
}
int r = 0;
HASHMAP_FOREACH(u, m->users, i) {
- DIR *d;
+ _cleanup_closedir_ DIR *d = NULL;
char *name;
int k;
while ((k = cg_read_subgroup(d, &name)) > 0) {
Session *session;
+ char *e;
- if (streq(name, "shared"))
- continue;
+ e = endswith(name, ".session");
+ if (e) {
+ *e = 0;
- k = manager_add_session(m, u, name, &session);
- if (k < 0) {
- free(name);
- break;
- }
+ k = manager_add_session(m, u, name, &session);
+ if (k < 0) {
+ free(name);
+ r = k;
+ continue;
+ }
- session_add_to_gc_queue(session);
+ session_add_to_gc_queue(session);
- if (!session->cgroup_path)
- if (asprintf(&session->cgroup_path, "%s/%s", u->cgroup_path, name) < 0) {
- k = -ENOMEM;
- free(name);
- break;
+ if (!session->cgroup_path) {
+ session->cgroup_path = strjoin(m->cgroup_path, "/", name, NULL);
+ if (!session->cgroup_path) {
+ k = log_oom();
+ free(name);
+ break;
+ }
}
+ }
free(name);
}
- closedir(d);
-
if (k < 0)
r = k;
}
int manager_spawn_autovt(Manager *m, int vtnr) {
int r;
- DBusMessage *message = NULL, *reply = NULL;
char *name = NULL;
const char *mode = "fail";
- DBusError error;
assert(m);
assert(vtnr >= 1);
- dbus_error_init(&error);
-
- if ((unsigned) vtnr > m->n_autovts)
+ if ((unsigned) vtnr > m->n_autovts &&
+ (unsigned) vtnr != m->reserve_vt)
return 0;
- r = vt_is_busy(vtnr);
- if (r < 0)
- return r;
- else if (r > 0)
- return -EBUSY;
+ if ((unsigned) vtnr != m->reserve_vt) {
+ /* If this is the reserved TTY, we'll start the getty
+ * on it in any case, but otherwise only if it is not
+ * busy. */
- message = dbus_message_new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartUnit");
- if (!message) {
- log_error("Could not allocate message.");
- r = -ENOMEM;
- goto finish;
+ r = vt_is_busy(vtnr);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ return -EBUSY;
}
if (asprintf(&name, "autovt@tty%i.service", vtnr) < 0) {
goto finish;
}
- if (!dbus_message_append_args(message,
- DBUS_TYPE_STRING, &name,
- DBUS_TYPE_STRING, &mode,
- DBUS_TYPE_INVALID)) {
- log_error("Could not attach target and flag information to message.");
- r = -ENOMEM;
- goto finish;
- }
-
- reply = dbus_connection_send_with_reply_and_block(m->bus, message, -1, &error);
- if (!reply) {
- log_error("Failed to start unit: %s", bus_error_message(&error));
- goto finish;
- }
-
- r = 0;
+ r = bus_method_call_with_reply (
+ m->bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "StartUnit",
+ NULL,
+ NULL,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &mode,
+ DBUS_TYPE_INVALID);
finish:
free(name);
- if (message)
- dbus_message_unref(message);
+ return r;
+}
- if (reply)
- dbus_message_unref(reply);
+static int manager_reserve_vt(Manager *m) {
+ _cleanup_free_ char *p = NULL;
- dbus_error_free(&error);
+ assert(m);
- return r;
+ if (m->reserve_vt <= 0)
+ return 0;
+
+ if (asprintf(&p, "/dev/tty%u", m->reserve_vt) < 0)
+ return log_oom();
+
+ m->reserve_vt_fd = open(p, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
+ if (m->reserve_vt_fd < 0) {
+
+ /* Don't complain on VT-less systems */
+ if (errno != ENOENT)
+ log_warning("Failed to pin reserved VT: %m");
+ return -errno;
+ }
+
+ return 0;
}
int manager_get_session_by_cgroup(Manager *m, const char *cgroup, Session **session) {
assert(cgroup);
assert(session);
- s = hashmap_get(m->cgroups, cgroup);
+ s = hashmap_get(m->session_cgroups, cgroup);
if (s) {
*session = s;
return 1;
}
- p = strdup(cgroup);
- if (!p) {
- log_error("Out of memory.");
- return -ENOMEM;
- }
+ p = strdupa(cgroup);
for (;;) {
char *e;
e = strrchr(p, '/');
if (!e || e == p) {
- free(p);
*session = NULL;
return 0;
}
*e = 0;
- s = hashmap_get(m->cgroups, p);
+ s = hashmap_get(m->session_cgroups, p);
if (s) {
- free(p);
*session = s;
return 1;
}
}
}
-int manager_get_session_by_pid(Manager *m, pid_t pid, Session **session) {
+int manager_get_user_by_cgroup(Manager *m, const char *cgroup, User **user) {
+ User *u;
char *p;
+
+ assert(m);
+ assert(cgroup);
+ assert(user);
+
+ u = hashmap_get(m->user_cgroups, cgroup);
+ if (u) {
+ *user = u;
+ return 1;
+ }
+
+ p = strdupa(cgroup);
+ if (!p)
+ return log_oom();
+
+ for (;;) {
+ char *e;
+
+ e = strrchr(p, '/');
+ if (!e || e == p) {
+ *user = NULL;
+ return 0;
+ }
+
+ *e = 0;
+
+ u = hashmap_get(m->user_cgroups, p);
+ if (u) {
+ *user = u;
+ return 1;
+ }
+ }
+}
+
+int manager_get_session_by_pid(Manager *m, pid_t pid, Session **session) {
+ _cleanup_free_ char *p = NULL;
int r;
assert(m);
assert(pid >= 1);
assert(session);
- r = cg_get_by_pid(SYSTEMD_CGROUP_CONTROLLER, pid, &p);
+ r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &p);
if (r < 0)
return r;
- r = manager_get_session_by_cgroup(m, p, session);
- free(p);
-
- return r;
+ return manager_get_session_by_cgroup(m, p, session);
}
void manager_cgroup_notify_empty(Manager *m, const char *cgroup) {
Session *s;
+ User *u;
int r;
r = manager_get_session_by_cgroup(m, cgroup, &s);
- if (r <= 0)
- return;
+ if (r > 0)
+ session_add_to_gc_queue(s);
- session_add_to_gc_queue(s);
+ r = manager_get_user_by_cgroup(m, cgroup, &u);
+ if (r > 0)
+ user_add_to_gc_queue(u);
}
static void manager_dispatch_other(Manager *m, int fd) {
static int manager_connect_bus(Manager *m) {
DBusError error;
int r;
- struct epoll_event ev;
+ struct epoll_event ev = {
+ .events = EPOLLIN,
+ .data.u32 = FD_BUS,
+ };
assert(m);
assert(!m->bus);
!dbus_connection_register_fallback(m->bus, "/org/freedesktop/login1/session", &bus_session_vtable, m) ||
!dbus_connection_register_fallback(m->bus, "/org/freedesktop/login1/user", &bus_user_vtable, m) ||
!dbus_connection_add_filter(m->bus, bus_message_filter, m, NULL)) {
- log_error("Not enough memory");
- r = -ENOMEM;
+ r = log_oom();
goto fail;
}
goto fail;
}
- zero(ev);
- ev.events = EPOLLIN;
- ev.data.u32 = FD_BUS;
-
if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->bus_fd, &ev) < 0)
goto fail;
}
static int manager_connect_console(Manager *m) {
- struct epoll_event ev;
+ struct epoll_event ev = {
+ .events = 0,
+ .data.u32 = FD_CONSOLE,
+ };
assert(m);
assert(m->console_active_fd < 0);
m->console_active_fd = open("/sys/class/tty/tty0/active", O_RDONLY|O_NOCTTY|O_CLOEXEC);
if (m->console_active_fd < 0) {
+
+ /* On some systems the device node /dev/tty0 may exist
+ * even though /sys/class/tty/tty0 does not. */
+ if (errno == ENOENT)
+ return 0;
+
log_error("Failed to open /sys/class/tty/tty0/active: %m");
return -errno;
}
- zero(ev);
- ev.events = 0;
- ev.data.u32 = FD_CONSOLE;
-
if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->console_active_fd, &ev) < 0)
return -errno;
}
static int manager_connect_udev(Manager *m) {
- struct epoll_event ev;
int r;
+ struct epoll_event ev = {
+ .events = EPOLLIN,
+ .data.u32 = FD_SEAT_UDEV,
+ };
assert(m);
assert(!m->udev_seat_monitor);
if (!m->udev_seat_monitor)
return -ENOMEM;
- r = udev_monitor_filter_add_match_tag(m->udev_seat_monitor, "seat");
- if (r < 0)
- return r;
-
- r = udev_monitor_filter_add_match_subsystem_devtype(m->udev_seat_monitor, "graphics", NULL);
+ r = udev_monitor_filter_add_match_tag(m->udev_seat_monitor, "master-of-seat");
if (r < 0)
return r;
m->udev_seat_fd = udev_monitor_get_fd(m->udev_seat_monitor);
- zero(ev);
- ev.events = EPOLLIN;
- ev.data.u32 = FD_SEAT_UDEV;
if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->udev_seat_fd, &ev) < 0)
return -errno;
/* Don't watch keys if nobody cares */
- if (m->handle_power_key != HANDLE_OFF ||
- m->handle_sleep_key != HANDLE_OFF ||
- m->handle_lid_switch != HANDLE_OFF) {
+ if (m->handle_power_key != HANDLE_IGNORE ||
+ m->handle_suspend_key != HANDLE_IGNORE ||
+ m->handle_hibernate_key != HANDLE_IGNORE ||
+ m->handle_lid_switch != HANDLE_IGNORE) {
m->udev_button_monitor = udev_monitor_new_from_netlink(m->udev, "udev");
if (!m->udev_button_monitor)
assert(m);
- idle_hint = !manager_is_inhibited(m, INHIBIT_IDLE, INHIBIT_BLOCK, t);
+ idle_hint = !manager_is_inhibited(m, INHIBIT_IDLE, INHIBIT_BLOCK, t, false, false, 0);
HASHMAP_FOREACH(s, m->sessions, i) {
dual_timestamp k;
return idle_hint;
}
+int manager_dispatch_idle_action(Manager *m) {
+ struct dual_timestamp since;
+ struct itimerspec its = {};
+ int r;
+ usec_t n;
+
+ assert(m);
+
+ if (m->idle_action == HANDLE_IGNORE ||
+ m->idle_action_usec <= 0) {
+ r = 0;
+ goto finish;
+ }
+
+ n = now(CLOCK_MONOTONIC);
+
+ r = manager_get_idle_hint(m, &since);
+ if (r <= 0)
+ /* Not idle. Let's check if after a timeout it might be idle then. */
+ timespec_store(&its.it_value, n + m->idle_action_usec);
+ else {
+ /* Idle! Let's see if it's time to do something, or if
+ * we shall sleep for longer. */
+
+ if (n >= since.monotonic + m->idle_action_usec &&
+ (m->idle_action_not_before_usec <= 0 || n >= m->idle_action_not_before_usec + m->idle_action_usec)) {
+ log_info("System idle. Taking action.");
+
+ manager_handle_action(m, 0, m->idle_action, false, false);
+ m->idle_action_not_before_usec = n;
+ }
+
+ timespec_store(&its.it_value, MAX(since.monotonic, m->idle_action_not_before_usec) + m->idle_action_usec);
+ }
+
+ if (m->idle_action_fd < 0) {
+ struct epoll_event ev = {
+ .events = EPOLLIN,
+ .data.u32 = FD_IDLE_ACTION,
+ };
+
+ m->idle_action_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC);
+ if (m->idle_action_fd < 0) {
+ log_error("Failed to create idle action timer: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->idle_action_fd, &ev) < 0) {
+ log_error("Failed to add idle action timer to epoll: %m");
+ r = -errno;
+ goto finish;
+ }
+ }
+
+ if (timerfd_settime(m->idle_action_fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) {
+ log_error("Failed to reset timerfd: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ return 0;
+
+finish:
+ if (m->idle_action_fd >= 0) {
+ close_nointr_nofail(m->idle_action_fd);
+ m->idle_action_fd = -1;
+ }
+
+ return r;
+}
int manager_startup(Manager *m) {
int r;
Seat *seat;
/* Remove stale objects before we start them */
manager_gc(m, false);
+ /* Reserve the special reserved VT */
+ manager_reserve_vt(m);
+
/* And start everything */
HASHMAP_FOREACH(seat, m->seats, i)
seat_start(seat);
HASHMAP_FOREACH(inhibitor, m->inhibitors, i)
inhibitor_start(inhibitor);
+ manager_dispatch_idle_action(m);
+
return 0;
}
+static int manager_recheck_buttons(Manager *m) {
+ Iterator i;
+ Button *b;
+ int r = 0;
+
+ assert(m);
+
+ HASHMAP_FOREACH(b, m->buttons, i) {
+ int q;
+
+ q = button_recheck(b);
+ if (q > 0)
+ return 1;
+ if (q < 0)
+ r = q;
+ }
+
+ return r;
+}
+
int manager_run(Manager *m) {
assert(m);
if (manager_dispatch_delayed(m) > 0)
continue;
+ if (manager_recheck_buttons(m) > 0)
+ continue;
+
if (dbus_connection_dispatch(m->bus) != DBUS_DISPATCH_COMPLETE)
continue;
manager_gc(m, true);
- if (m->delayed_unit) {
+ if (m->action_what != 0 && !m->action_job) {
usec_t x, y;
x = now(CLOCK_MONOTONIC);
- y = m->delayed_timestamp + m->inhibit_delay_max;
+ y = m->action_timestamp + m->inhibit_delay_max;
msec = x >= y ? 0 : (int) ((y - x) / USEC_PER_MSEC);
}
manager_dispatch_console(m);
break;
+ case FD_IDLE_ACTION:
+ manager_dispatch_idle_action(m);
+ break;
+
case FD_BUS:
bus_loop_dispatch(m->bus_fd);
break;
return -errno;
}
- r = config_parse(fn, f, "Login\0", config_item_perf_lookup, (void*) logind_gperf_lookup, false, m);
+ r = config_parse(NULL, fn, f, "Login\0", config_item_perf_lookup, (void*) logind_gperf_lookup, false, m);
if (r < 0)
log_warning("Failed to parse configuration file: %s", strerror(-r));
goto finish;
}
+ /* Always create the directories people can create inotify
+ * watches in. Note that some applications might check for the
+ * existence of /run/systemd/seats/ to determine whether
+ * logind is available, so please always make sure this check
+ * stays in. */
+ mkdir_label("/run/systemd/seats", 0755);
+ mkdir_label("/run/systemd/users", 0755);
+ mkdir_label("/run/systemd/sessions", 0755);
+
m = manager_new();
if (!m) {
- log_error("Out of memory");
- r = -ENOMEM;
+ r = log_oom();
goto finish;
}