chiark / gitweb /
unit: introduce ConditionFileIsExecutable= and use it where we check for a binary...
[elogind.git] / src / logind-seat.c
index 315490043df53d52c61e79c69686f617df598cb5..3cf3958c8d47330d13cac925573238713a54ef55 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) {
@@ -39,28 +41,30 @@ Seat *seat_new(Manager *m, const char *id) {
         if (!s)
                 return NULL;
 
-        s->state_file = strappend("/run/systemd/seat/", id);
+        s->state_file = strappend("/run/systemd/seats/", id);
         if (!s->state_file) {
                 free(s);
                 return NULL;
         }
 
         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,22 +80,29 @@ 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 (!s->started)
+                return 0;
+
+        r = safe_mkdir("/run/systemd/seats", 0755, 0, 0);
         if (r < 0)
-                return r;
+                goto finish;
 
-        f = fopen(s->state_file, "we");
-        if (!f)
-                return -errno;
+        r = fopen_temporary(s->state_file, &f, &temp_path);
+        if (r < 0)
+                goto finish;
+
+        fchmod(fileno(f), 0644);
 
         fprintf(f,
+                "# This is private data. Do not parse.\n"
                 "IS_VTCONSOLE=%i\n",
-                s->manager->vtconsole == s);
+                seat_is_vtconsole(s));
 
         if (s->active) {
                 assert(s->active->user);
@@ -105,34 +116,46 @@ int seat_save(Seat *s) {
 
         if (s->sessions) {
                 Session *i;
-                fputs("OTHER_UIDS=", f);
 
+                fputs("SESSIONS=", f);
                 LIST_FOREACH(sessions_by_seat, i, s->sessions) {
-                        assert(i->user);
-
-                        if (i == s->active)
-                                continue;
-
                         fprintf(f,
-                                "%s%lu",
-                                i == s->sessions ? "" : " ",
-                                (unsigned long) i->user->uid);
+                                "%s%c",
+                                i->id,
+                                i->sessions_by_seat_next ? ' ' : '\n');
                 }
+
+                fputs("UIDS=", f);
+                LIST_FOREACH(sessions_by_seat, i, s->sessions)
+                        fprintf(f,
+                                "%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;
 }
 
 int seat_load(Seat *s) {
         assert(s);
 
+        /* There isn't actually anything to read here ... */
+
         return 0;
 }
 
@@ -157,85 +180,201 @@ static int vt_allocate(int vtnr) {
 }
 
 int seat_preallocate_vts(Seat *s) {
-        int i, r = 0;
+        int r = 0;
+        unsigned i;
 
         assert(s);
         assert(s->manager);
 
+        log_debug("Preallocating VTs...");
+
         if (s->manager->n_autovts <= 0)
                 return 0;
 
-        if (s->manager->vtconsole != s)
+        if (!seat_is_vtconsole(s))
                 return 0;
 
-        for (i = 1; i < s->manager->n_autovts; i++) {
+        for (i = 1; i <= s->manager->n_autovts; i++) {
                 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_set_active(Seat *s, Session *session) {
+        Session *old_active;
 
-        assert(vtnr >= 1);
+        assert(s);
+        assert(!session || session->seat == s);
 
-        fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC);
-        if (fd < 0)
-                return -errno;
+        if (session == s->active)
+                return 0;
 
-        if (ioctl(fd, VT_GETSTATE, &vt_stat) < 0)
-                r = -errno;
-        else
-                r = !!(vt_stat.v_state & (1 << vtnr));
+        old_active = s->active;
+        s->active = session;
 
-        close_nointr_nofail(fd);
+        seat_apply_acls(s, old_active);
 
-        return r;
+        if (session && session->started)
+                session_send_changed(session, "Active\0");
+
+        if (!session || session->started)
+                seat_send_changed(s, "ActiveSession\0");
+
+        seat_save(s);
+
+        if (session) {
+                session_save(session);
+                user_save(session->user);
+        }
+
+        if (old_active) {
+                session_save(old_active);
+                user_save(old_active->user);
+        }
+
+        return 0;
 }
 
-void seat_active_vt_changed(Seat *s, int vtnr) {
-        Session *i;
+int seat_active_vt_changed(Seat *s, int vtnr) {
+        Session *i, *new_active = NULL;
+        int r;
 
         assert(s);
         assert(vtnr >= 1);
-        assert(s->manager->vtconsole == s);
 
-        s->active = NULL;
+        if (!seat_is_vtconsole(s))
+                return -EINVAL;
+
+        log_debug("VT changed to %i", vtnr);
 
         LIST_FOREACH(sessions_by_seat, i, s->sessions)
                 if (i->vtnr == vtnr) {
-                        s->active = i;
+                        new_active = i;
                         break;
                 }
 
-        seat_apply_acls(s);
+        r = seat_set_active(s, new_active);
+        manager_spawn_autovt(s->manager, vtnr);
+
+        return r;
+}
+
+int seat_read_active_vt(Seat *s) {
+        char t[64];
+        ssize_t k;
+        int r, vtnr;
+
+        assert(s);
+
+        if (!seat_is_vtconsole(s))
+                return 0;
+
+        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 (vt_is_busy(vtnr) == 0)
-                manager_spawn_autovt(s->manager, vtnr);
+        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);
+
+        /* Initialize VT magic stuff */
+        seat_preallocate_vts(s);
+
+        /* Read current VT */
+        seat_read_active_vt(s);
+
+        s->started = true;
+
+        /* Save seat data */
+        seat_save(s);
+
+        seat_send_signal(s, true);
+
+        return 0;
 }
 
 int seat_stop(Seat *s) {
-        Session *session;
         int r = 0;
 
         assert(s);
 
-        LIST_FOREACH(sessions_by_seat, session, s->sessions) {
-                int k;
+        if (s->started)
+                log_info("Removed seat %s.", s->id);
+
+        seat_stop_sessions(s);
+
+        unlink(s->state_file);
+        seat_add_to_gc_queue(s);
+
+        if (s->started)
+                seat_send_signal(s, false);
+
+        s->started = false;
+
+        return r;
+}
+
+int seat_stop_sessions(Seat *s) {
+        Session *session;
+        int r = 0, k;
+
+        assert(s);
 
+        LIST_FOREACH(sessions_by_seat, session, s->sessions) {
                 k = session_stop(session);
                 if (k < 0)
                         r = k;
@@ -243,3 +382,118 @@ int seat_stop(Seat *s) {
 
         return r;
 }
+
+int seat_attach_session(Seat *s, Session *session) {
+        assert(s);
+        assert(session);
+        assert(!session->seat);
+
+        if (!seat_is_vtconsole(s) && s->sessions)
+                return -EEXIST;
+
+        session->seat = s;
+        LIST_PREPEND(Session, sessions_by_seat, s->sessions, session);
+
+        seat_send_changed(s, "Sessions\0");
+
+        if (!seat_is_vtconsole(s)) {
+                assert(!s->active);
+                seat_set_active(s, session);
+        }
+
+        return 0;
+}
+
+bool seat_is_vtconsole(Seat *s) {
+        assert(s);
+
+        return s->manager->vtconsole == s;
+}
+
+int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
+        Session *session;
+        bool idle_hint = true;
+        dual_timestamp ts = { 0, 0 };
+
+        assert(s);
+
+        LIST_FOREACH(sessions_by_seat, session, s->sessions) {
+                dual_timestamp k;
+                int ih;
+
+                ih = session_get_idle_hint(session, &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 seat_check_gc(Seat *s, bool drop_not_started) {
+        assert(s);
+
+        if (drop_not_started && !s->started)
+                return 0;
+
+        if (seat_is_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;
+
+        if (strlen(name) > 255)
+                return false;
+
+        return true;
+}