chiark / gitweb /
login: share VT-signal handler between sessions
authorDavid Herrmann <dh.herrmann@gmail.com>
Mon, 11 Aug 2014 16:17:54 +0000 (18:17 +0200)
committerDavid Herrmann <dh.herrmann@gmail.com>
Wed, 13 Aug 2014 07:05:20 +0000 (09:05 +0200)
sd-event does not allow multiple handlers for a single signal. However,
logind sets up signal handlers for each session with VT_PROCESS set (that
is, it has an active controller). Therefore, registering multiple such
controllers will fail.

Lets make the VT-handler global, as it's mostly trivial, anyway. This way,
the sessions don't have to take care of that and we can simply acknowledge
all VT-switch requests as we always did.

src/login/logind-session.c
src/login/logind-session.h
src/login/logind.c

index 5a1cb81ccfd7c13f982816e705c843101d577a36..feedc30fa6142fa8fefc7b208447ba32ef2aba77 100644 (file)
@@ -153,8 +153,6 @@ void session_free(Session *s) {
 
         hashmap_remove(s->manager->sessions, s->id);
 
-        s->vt_source = sd_event_source_unref(s->vt_source);
-
         free(s->state_file);
         free(s);
 }
@@ -994,19 +992,9 @@ static int session_open_vt(Session *s) {
         return s->vtfd;
 }
 
-static int session_vt_fn(sd_event_source *source, const struct signalfd_siginfo *si, void *data) {
-        Session *s = data;
-
-        if (s->vtfd >= 0)
-                ioctl(s->vtfd, VT_RELDISP, 1);
-
-        return 0;
-}
-
 int session_prepare_vt(Session *s) {
         int vt, r;
         struct vt_mode mode = { 0 };
-        sigset_t mask;
 
         if (s->vtnr < 1)
                 return 0;
@@ -1036,20 +1024,12 @@ int session_prepare_vt(Session *s) {
                 goto error;
         }
 
-        sigemptyset(&mask);
-        sigaddset(&mask, SIGUSR1);
-        sigprocmask(SIG_BLOCK, &mask, NULL);
-
-        r = sd_event_add_signal(s->manager->event, &s->vt_source, SIGUSR1, session_vt_fn, s);
-        if (r < 0)
-                goto error;
-
         /* Oh, thanks to the VT layer, VT_AUTO does not work with KD_GRAPHICS.
          * So we need a dummy handler here which just acknowledges *all* VT
          * switch requests. */
         mode.mode = VT_PROCESS;
-        mode.relsig = SIGUSR1;
-        mode.acqsig = SIGUSR1;
+        mode.relsig = SIGRTMIN;
+        mode.acqsig = SIGRTMIN + 1;
         r = ioctl(vt, VT_SETMODE, &mode);
         if (r < 0) {
                 r = -errno;
@@ -1073,8 +1053,6 @@ void session_restore_vt(Session *s) {
         if (vt < 0)
                 return;
 
-        s->vt_source = sd_event_source_unref(s->vt_source);
-
         ioctl(vt, KDSETMODE, KD_TEXT);
 
         if (read_one_line_file("/sys/module/vt/parameters/default_utf8", &utf8) >= 0 && *utf8 == '1')
index 2ab31828cedb9a62a54777d5e4b3627faf7a1a89..cb8514e580b9d3e3dd2bc173a69f6b2382d82f06 100644 (file)
@@ -98,7 +98,6 @@ struct Session {
         Seat *seat;
         unsigned int vtnr;
         int vtfd;
-        sd_event_source *vt_source;
 
         pid_t leader;
         uint32_t audit_id;
index ec5529db1a3868430d989c440bcab0cd0e68c560..8ec810535732a7dab589a1a9de9c43d31cde363c 100644 (file)
@@ -720,6 +720,47 @@ static int manager_connect_bus(Manager *m) {
         return 0;
 }
 
+static int manager_vt_switch(sd_event_source *src, const struct signalfd_siginfo *si, void *data) {
+        Manager *m = data;
+        Session *active, *iter;
+
+        /*
+         * We got a VT-switch signal and we have to acknowledge it immediately.
+         * Preferably, we'd just use m->seat0->active->vtfd, but unfortunately,
+         * old user-space might run multiple sessions on a single VT, *sigh*.
+         * Therefore, we have to iterate all sessions and find one with a vtfd
+         * on the requested VT.
+         * As only VTs with active controllers have VT_PROCESS set, our current
+         * notion of the active VT might be wrong (for instance if the switch
+         * happens while we setup VT_PROCESS). Therefore, read the current VT
+         * first and then use s->active->vtnr as reference. Note that this is
+         * not racy, as no further VT-switch can happen as long as we're in
+         * synchronous VT_PROCESS mode.
+         */
+
+        assert(m->seat0);
+        seat_read_active_vt(m->seat0);
+
+        active = m->seat0->active;
+        if (!active || active->vtnr < 1) {
+                log_warning("Received VT_PROCESS signal without a registered session on that VT.");
+                return 0;
+        }
+
+        if (active->vtfd >= 0) {
+                ioctl(active->vtfd, VT_RELDISP, 1);
+        } else {
+                LIST_FOREACH(sessions_by_seat, iter, m->seat0->sessions) {
+                        if (iter->vtnr == active->vtnr && iter->vtfd >= 0) {
+                                ioctl(iter->vtfd, VT_RELDISP, 1);
+                                break;
+                        }
+                }
+        }
+
+        return 0;
+}
+
 static int manager_connect_console(Manager *m) {
         int r;
 
@@ -750,6 +791,34 @@ static int manager_connect_console(Manager *m) {
                 return r;
         }
 
+        /*
+         * SIGRTMIN is used as global VT-release signal, SIGRTMIN + 1 is used
+         * as VT-acquire signal. We ignore any acquire-events (yes, we still
+         * have to provide a valid signal-number for it!) and acknowledge all
+         * release events immediately.
+         */
+
+        if (SIGRTMIN + 1 > SIGRTMAX) {
+                log_error("Not enough real-time signals available: %u-%u", SIGRTMIN, SIGRTMAX);
+                return -EINVAL;
+        }
+
+        r = ignore_signals(SIGRTMIN + 1, -1);
+        if (r < 0) {
+                log_error("Cannot ignore SIGRTMIN + 1: %s", strerror(-r));
+                return r;
+        }
+
+        r = sigprocmask_many(SIG_BLOCK, SIGRTMIN, -1);
+        if (r < 0) {
+                log_error("Cannot block SIGRTMIN: %s", strerror(-r));
+                return r;
+        }
+
+        r = sd_event_add_signal(m->event, NULL, SIGRTMIN, manager_vt_switch, m);
+        if (r < 0)
+                return r;
+
         return 0;
 }