chiark / gitweb /
logind: implement D-Bus properties
[elogind.git] / src / logind-seat.c
index 315490043df53d52c61e79c69686f617df598cb5..2ba3060be33940fb4a691778696898c43486e80d 100644 (file)
 #include <fcntl.h>
 #include <sys/ioctl.h>
 #include <linux/vt.h>
+#include <string.h>
 
 #include "logind-seat.h"
+#include "logind-acl.h"
 #include "util.h"
 
 Seat *seat_new(Manager *m, const char *id) {
@@ -46,21 +48,23 @@ Seat *seat_new(Manager *m, const char *id) {
         }
 
         s->id = file_name_from_path(s->state_file);
+        s->manager = m;
 
         if (hashmap_put(m->seats, s->id, s) < 0) {
-                free(s->id);
+                free(s->state_file);
                 free(s);
                 return NULL;
         }
 
-        s->manager = m;
-
         return s;
 }
 
 void seat_free(Seat *s) {
         assert(s);
 
+        if (s->in_gc_queue)
+                LIST_REMOVE(Seat, gc_queue, s->manager->seat_gc_queue, s);
+
         while (s->sessions)
                 session_free(s->sessions);
 
@@ -76,20 +80,24 @@ void seat_free(Seat *s) {
 }
 
 int seat_save(Seat *s) {
-        FILE *f;
         int r;
+        FILE *f;
+        char *temp_path;
 
         assert(s);
 
         r = safe_mkdir("/run/systemd/seat", 0755, 0, 0);
         if (r < 0)
-                return r;
+                goto finish;
+
+        r = fopen_temporary(s->state_file, &f, &temp_path);
+        if (r < 0)
+                goto finish;
 
-        f = fopen(s->state_file, "we");
-        if (!f)
-                return -errno;
+        fchmod(fileno(f), 0644);
 
         fprintf(f,
+                "# This is private data. Do not parse.\n"
                 "IS_VTCONSOLE=%i\n",
                 s->manager->vtconsole == s);
 
@@ -105,28 +113,45 @@ int seat_save(Seat *s) {
 
         if (s->sessions) {
                 Session *i;
-                fputs("OTHER_UIDS=", f);
 
+                fputs("OTHER=", f);
                 LIST_FOREACH(sessions_by_seat, i, s->sessions) {
-                        assert(i->user);
+                        if (i == s->active)
+                                continue;
 
+                        fprintf(f,
+                                "%s%c",
+                                i->id,
+                                i->sessions_by_seat_next ? ' ' : '\n');
+                }
+
+                fputs("OTHER_UIDS=", f);
+                LIST_FOREACH(sessions_by_seat, i, s->sessions) {
                         if (i == s->active)
                                 continue;
 
                         fprintf(f,
-                                "%s%lu",
-                                i == s->sessions ? "" : " ",
-                                (unsigned long) i->user->uid);
+                                "%lu%c",
+                                (unsigned long) i->user->uid,
+                                i->sessions_by_seat_next ? ' ' : '\n');
                 }
         }
 
         fflush(f);
-        if (ferror(f)) {
+
+        if (ferror(f) || rename(temp_path, s->state_file) < 0) {
                 r = -errno;
                 unlink(s->state_file);
+                unlink(temp_path);
         }
 
         fclose(f);
+        free(temp_path);
+
+finish:
+        if (r < 0)
+                log_error("Failed to save seat data for %s: %s", s->id, strerror(-r));
+
         return r;
 }
 
@@ -156,8 +181,9 @@ static int vt_allocate(int vtnr) {
         return r;
 }
 
-int seat_preallocate_vts(Seat *s) {
-        int i, r = 0;
+static int seat_preallocate_vts(Seat *s) {
+        int r = 0;
+        unsigned i;
 
         assert(s);
         assert(s->manager);
@@ -172,74 +198,190 @@ int seat_preallocate_vts(Seat *s) {
                 int q;
 
                 q = vt_allocate(i);
-                if (r >= 0 && q < 0)
+                if (q < 0) {
+                        log_error("Failed to preallocate VT %i: %s", i, strerror(-q));
                         r = q;
+                }
         }
 
         return r;
 }
 
-int seat_apply_acls(Seat *s) {
+int seat_apply_acls(Seat *s, Session *old_active) {
+        int r;
+
         assert(s);
 
+        r = devnode_acl_all(s->manager->udev,
+                            s->id,
+                            false,
+                            !!old_active, old_active ? old_active->user->uid : 0,
+                            !!s->active, s->active ? s->active->user->uid : 0);
 
-        return 0;
+        if (r < 0)
+                log_error("Failed to apply ACLs: %s", strerror(-r));
+
+        return r;
 }
 
-static int vt_is_busy(int vtnr) {
-        struct vt_stat vt_stat;
-        int r = 0, fd;
+int seat_active_vt_changed(Seat *s, int vtnr) {
+        Session *i, *new_active = NULL, *old_active;
 
+        assert(s);
         assert(vtnr >= 1);
 
-        fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC);
-        if (fd < 0)
-                return -errno;
+        if (s->manager->vtconsole != s)
+                return -EINVAL;
 
-        if (ioctl(fd, VT_GETSTATE, &vt_stat) < 0)
-                r = -errno;
-        else
-                r = !!(vt_stat.v_state & (1 << vtnr));
+        log_debug("VT changed to %i", vtnr);
+
+        LIST_FOREACH(sessions_by_seat, i, s->sessions)
+                if (i->vtnr == vtnr) {
+                        new_active = i;
+                        break;
+                }
 
-        close_nointr_nofail(fd);
+        if (new_active == s->active)
+                return 0;
 
-        return r;
+        old_active = s->active;
+        s->active = new_active;
+
+        seat_apply_acls(s, old_active);
+        manager_spawn_autovt(s->manager, vtnr);
+
+        return 0;
 }
 
-void seat_active_vt_changed(Seat *s, int vtnr) {
-        Session *i;
+int seat_read_active_vt(Seat *s) {
+        char t[64];
+        ssize_t k;
+        int r, vtnr;
 
         assert(s);
-        assert(vtnr >= 1);
-        assert(s->manager->vtconsole == s);
 
-        s->active = NULL;
+        if (s->manager->vtconsole != s)
+                return 0;
 
-        LIST_FOREACH(sessions_by_seat, i, s->sessions)
-                if (i->vtnr == vtnr) {
-                        s->active = i;
-                        break;
-                }
+        lseek(s->manager->console_active_fd, SEEK_SET, 0);
+
+        k = read(s->manager->console_active_fd, t, sizeof(t)-1);
+        if (k <= 0) {
+                log_error("Failed to read current console: %s", k < 0 ? strerror(-errno) : "EOF");
+                return k < 0 ? -errno : -EIO;
+        }
+
+        t[k] = 0;
+        truncate_nl(t);
+
+        if (!startswith(t, "tty")) {
+                log_error("Hm, /sys/class/tty/tty0/active is badly formatted.");
+                return -EIO;
+        }
+
+        r = safe_atoi(t+3, &vtnr);
+        if (r < 0) {
+                log_error("Failed to parse VT number %s", t+3);
+                return r;
+        }
+
+        if (vtnr <= 0) {
+                log_error("VT number invalid: %s", t+3);
+                return -EIO;
+        }
+
+        return seat_active_vt_changed(s, vtnr);
+}
+
+int seat_start(Seat *s) {
+        assert(s);
+
+        if (s->started)
+                return 0;
+
+        log_info("New seat %s.", s->id);
 
-        seat_apply_acls(s);
+        /* Initialize VT magic stuff */
+        seat_preallocate_vts(s);
 
-        if (vt_is_busy(vtnr) == 0)
-                manager_spawn_autovt(s->manager, vtnr);
+        /* Read current VT */
+        seat_read_active_vt(s);
+
+        /* Save seat data */
+        seat_save(s);
+
+        s->started = true;
+
+        return 0;
 }
 
 int seat_stop(Seat *s) {
         Session *session;
-        int r = 0;
+        int r = 0, k;
 
         assert(s);
 
-        LIST_FOREACH(sessions_by_seat, session, s->sessions) {
-                int k;
+        if (!s->started)
+                return 0;
+
+        log_info("Removed seat %s.", s->id);
 
+        LIST_FOREACH(sessions_by_seat, session, s->sessions) {
                 k = session_stop(session);
                 if (k < 0)
                         r = k;
         }
 
+        unlink(s->state_file);
+        seat_add_to_gc_queue(s);
+
+        s->started = false;
+
         return r;
 }
+
+int seat_check_gc(Seat *s) {
+        assert(s);
+
+        if (s->manager->vtconsole == s)
+                return 1;
+
+        return !!s->devices;
+}
+
+void seat_add_to_gc_queue(Seat *s) {
+        assert(s);
+
+        if (s->in_gc_queue)
+                return;
+
+        LIST_PREPEND(Seat, gc_queue, s->manager->seat_gc_queue, s);
+        s->in_gc_queue = true;
+}
+
+static bool seat_name_valid_char(char c) {
+        return
+                (c >= 'a' && c <= 'z') ||
+                (c >= 'A' && c <= 'Z') ||
+                (c >= '0' && c <= '9') ||
+                c == '-' ||
+                c == '_';
+}
+
+bool seat_name_is_valid(const char *name) {
+        const char *p;
+
+        assert(name);
+
+        if (!startswith(name, "seat"))
+                return false;
+
+        if (!name[4])
+                return false;
+
+        for (p = name; *p; p++)
+                if (!seat_name_valid_char(*p))
+                        return false;
+
+        return true;
+}