chiark / gitweb /
logind: kill a session as soon as its pipe fd gets EOF
[elogind.git] / src / logind.c
index 5d5181a1bb38ff004a9aea4aba941bf9303710e7..8507c2e152a50f05b64b6f395fd2fd26764e3374 100644 (file)
 #include "dbus-common.h"
 #include "dbus-loop.h"
 
-enum {
-        FD_UDEV,
-        FD_CONSOLE,
-        FD_BUS
-};
-
 Manager *manager_new(void) {
         Manager *m;
 
@@ -56,6 +50,8 @@ Manager *manager_new(void) {
         m->seats = hashmap_new(string_hash_func, string_compare_func);
         m->sessions = hashmap_new(string_hash_func, string_compare_func);
         m->users = hashmap_new(trivial_hash_func, trivial_compare_func);
+        m->cgroups = hashmap_new(string_hash_func, string_compare_func);
+        m->pipe_fds = hashmap_new(trivial_hash_func, trivial_compare_func);
 
         if (!m->devices || !m->seats || !m->sessions || !m->users) {
                 manager_free(m);
@@ -100,6 +96,8 @@ void manager_free(Manager *m) {
         hashmap_free(m->users);
         hashmap_free(m->devices);
         hashmap_free(m->seats);
+        hashmap_free(m->cgroups);
+        hashmap_free(m->pipe_fds);
 
         if (m->console_active_fd >= 0)
                 close_nointr_nofail(m->console_active_fd);
@@ -255,9 +253,15 @@ int manager_process_device(Manager *m, struct udev_device *d) {
 
         assert(m);
 
+        /* FIXME: drop this check as soon as libudev's enum support
+         * honours tags and subsystem matches at the same time */
+        if (!streq_ptr(udev_device_get_subsystem(d), "graphics"))
+                return 0;
+
         if (streq_ptr(udev_device_get_action(d), "remove")) {
 
-                device = hashmap_get(m->devices, udev_device_get_syspath(d));
+                /* FIXME: use syspath instead of sysname here, as soon as fb driver is fixed */
+                device = hashmap_get(m->devices, udev_device_get_sysname(d));
                 if (!device)
                         return 0;
 
@@ -268,14 +272,16 @@ int manager_process_device(Manager *m, struct udev_device *d) {
                 const char *sn;
                 Seat *seat;
 
-                sn = udev_device_get_property_value(d, "SEAT");
+                sn = udev_device_get_property_value(d, "ID_SEAT");
                 if (!sn)
                         sn = "seat0";
 
-                if (!startswith(sn, "seat"))
-                        return -EINVAL;
+                if (!seat_name_is_valid(sn)) {
+                        log_warning("Device with invalid seat name %s found, ignoring.", sn);
+                        return 0;
+                }
 
-                r = manager_add_device(m, udev_device_get_syspath(d), &device);
+                r = manager_add_device(m, udev_device_get_sysname(d), &device);
                 if (r < 0)
                         return r;
 
@@ -288,6 +294,7 @@ int manager_process_device(Manager *m, struct udev_device *d) {
                 }
 
                 device_attach(device, seat);
+                seat_start(seat);
         }
 
         return 0;
@@ -373,9 +380,6 @@ int manager_enumerate_seats(Manager *m) {
                 if (!dirent_is_file(de))
                         continue;
 
-                if (!startswith(de->d_name, "seat"))
-                        continue;
-
                 s = hashmap_get(m->seats, de->d_name);
                 if (!s) {
                         unlinkat(dirfd(d), de->d_name, 0);
@@ -648,7 +652,13 @@ static int vt_is_busy(int vtnr) {
 
         assert(vtnr >= 1);
 
-        fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC);
+        /* We explicitly open /dev/tty1 here instead of /dev/tty0. If
+         * we'd open the latter we'd open the foreground tty which
+         * hence would be unconditionally busy. By opening /dev/tty1
+         * we avoid this. Since tty1 is special and needs to be an
+         * explicitly loaded getty or DM this is safe. */
+
+        fd = open_terminal("/dev/tty1", O_RDWR|O_NOCTTY|O_CLOEXEC);
         if (fd < 0)
                 return -errno;
 
@@ -664,39 +674,107 @@ static int vt_is_busy(int vtnr) {
 
 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);
 
+        dbus_error_init(&error);
+
         r = vt_is_busy(vtnr);
         if (r != 0)
                 return r;
 
-        /* ... */
+        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;
+        }
+
+        if (asprintf(&name, "autovt-getty@tty%i.service", vtnr) < 0) {
+                log_error("Could not allocate service name.");
+                r = -ENOMEM;
+                goto finish;
+        }
 
-        return 0;
+        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;
+
+finish:
+        free(name);
+
+        if (message)
+                dbus_message_unref(message);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
 }
 
-static DBusHandlerResult login_message_handler(
-                DBusConnection *connection,
-                DBusMessage *message,
-                void *userdata) {
+void manager_cgroup_notify_empty(Manager *m, const char *cgroup) {
+        Session *s;
+        char *p;
+
+        assert(m);
+        assert(cgroup);
+
+        p = strdup(cgroup);
+        if (!p) {
+                log_error("Out of memory.");
+                return;
+        }
 
-        return DBUS_HANDLER_RESULT_HANDLED;
+        for (;;) {
+                char *e;
+
+                if (isempty(p) || streq(p, "/"))
+                        break;
+
+                s = hashmap_get(m->cgroups, p);
+                if (s)
+                        session_add_to_gc_queue(s);
+
+                assert_se(e = strrchr(p, '/'));
+                *e = 0;
+        }
+
+        free(p);
 }
 
-static DBusHandlerResult login_message_filter(
-                DBusConnection *connection,
-                DBusMessage *message,
-                void *userdata) {
+static void manager_pipe_notify_eof(Manager *m, int fd) {
+        Session *s;
+
+        assert_se(m);
+        assert_se(fd >= 0);
+
+        assert_se(s = hashmap_get(m->pipe_fds, INT_TO_PTR(fd + 1)));
+        assert(s->pipe_fd == fd);
+        session_unset_pipe_fd(s);
 
-        return DBUS_HANDLER_RESULT_HANDLED;
+        session_stop(s);
 }
 
 static int manager_connect_bus(Manager *m) {
-        const DBusObjectPathVTable login_vtable = {
-                .message_function = login_message_handler
-        };
-
         DBusError error;
         int r;
         struct epoll_event ev;
@@ -709,18 +787,16 @@ static int manager_connect_bus(Manager *m) {
 
         m->bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
         if (!m->bus) {
-                log_error("Failed to get system D-Bus connection: %s", error.message);
+                log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
                 r = -ECONNREFUSED;
                 goto fail;
         }
 
-        if (!dbus_connection_register_object_path(m->bus, "/org/freedesktop/login1", &login_vtable, NULL)) {
-                log_error("Not enough memory");
-                r = -ENOMEM;
-                goto fail;
-        }
-
-        if (!dbus_connection_add_filter(m->bus, login_message_filter, m, NULL)) {
+        if (!dbus_connection_register_object_path(m->bus, "/org/freedesktop/login1", &bus_manager_vtable, m) ||
+            !dbus_connection_register_fallback(m->bus, "/org/freedesktop/login1/seat", &bus_seat_vtable, m) ||
+            !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;
                 goto fail;
@@ -734,13 +810,20 @@ static int manager_connect_bus(Manager *m) {
                            &error);
 
         if (dbus_error_is_set(&error)) {
-                log_error("Failed to register match: %s", error.message);
+                log_error("Failed to register match: %s", bus_error_message(&error));
                 r = -EIO;
                 goto fail;
         }
 
-        if (dbus_bus_request_name(m->bus, "org.freedesktop.login1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error) < 0) {
-                log_error("Failed to register name on bus: %s", error.message);
+        r = dbus_bus_request_name(m->bus, "org.freedesktop.login1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
+        if (dbus_error_is_set(&error)) {
+                log_error("Failed to register name on bus: %s", bus_error_message(&error));
+                r = -EIO;
+                goto fail;
+        }
+
+        if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)  {
+                log_error("Failed to acquire name.");
                 r = -EEXIST;
                 goto fail;
         }
@@ -861,6 +944,43 @@ void manager_gc(Manager *m) {
         }
 }
 
+int manager_get_idle_hint(Manager *m, dual_timestamp *t) {
+        Session *s;
+        bool idle_hint = true;
+        dual_timestamp ts = { 0, 0 };
+        Iterator i;
+
+        assert(m);
+
+        HASHMAP_FOREACH(s, m->sessions, i) {
+                dual_timestamp k;
+                int ih;
+
+                ih = session_get_idle_hint(s, &k);
+                if (ih < 0)
+                        return ih;
+
+                if (!ih) {
+                        if (!idle_hint) {
+                                if (k.monotonic < ts.monotonic)
+                                        ts = k;
+                        } else {
+                                idle_hint = false;
+                                ts = k;
+                        }
+                } else if (idle_hint) {
+
+                        if (k.monotonic > ts.monotonic)
+                                ts = k;
+                }
+        }
+
+        if (t)
+                *t = ts;
+
+        return idle_hint;
+}
+
 int manager_startup(Manager *m) {
         int r;
         Seat *seat;
@@ -929,8 +1049,13 @@ int manager_run(Manager *m) {
                 if (dbus_connection_dispatch(m->bus) != DBUS_DISPATCH_COMPLETE)
                         continue;
 
+                manager_gc(m);
+
                 n = epoll_wait(m->epoll_fd, &event, 1, -1);
                 if (n < 0) {
+                        if (errno == EINTR || errno == EAGAIN)
+                                continue;
+
                         log_error("epoll() failed: %m");
                         return -errno;
                 }
@@ -948,6 +1073,10 @@ int manager_run(Manager *m) {
                 case FD_BUS:
                         bus_loop_dispatch(m->bus_fd);
                         break;
+
+                default:
+                        if (event.data.u32 >= FD_PIPE_BASE)
+                                manager_pipe_notify_eof(m, event.data.u32 - FD_PIPE_BASE);
                 }
         }