Based on 92683ad2e28c79891e4123d9a421b018dc58870c Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Mon, 11 Aug 2014 18:17:54 +0200 Subject: [PATCH] login: share VT-signal handler between sessions 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/libsystemd/sd-event/sd-event.c | 5 +- src/login/logind-session.c | 26 +------------ src/login/logind-session.h | 1 src/login/logind.c | 70 +++++++++++++++++++++++++++++++++++++ src/shared/util.c | 18 +++++++++ src/shared/util.h | 1 6 files changed, 94 insertions(+), 27 deletions(-) --- src/libsystemd/sd-event/sd-event.c +++ src/libsystemd/sd-event/sd-event.c 2014-08-26 11:02:54.500683967 +0000 @@ -839,7 +839,6 @@ _public_ int sd_event_add_signal( assert_return(sig > 0, -EINVAL); assert_return(sig < _NSIG, -EINVAL); assert_return(callback, -EINVAL); - assert_return(ret, -EINVAL); assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); assert_return(!event_pid_changed(e), -ECHILD); @@ -877,7 +876,9 @@ _public_ int sd_event_add_signal( } } - *ret = s; + if (ret) + *ret = s; + return 0; } --- src/login/logind-session.c +++ src/login/logind-session.c 2014-08-25 17:22:46.000000000 +0000 @@ -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); } @@ -966,19 +964,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_mute_vt(Session *s) { int vt, r; struct vt_mode mode = { 0 }; - sigset_t mask; if (s->vtnr < 1) return 0; @@ -1008,20 +996,12 @@ int session_mute_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; @@ -1045,8 +1025,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') --- src/login/logind-session.h +++ src/login/logind-session.h 2014-08-25 00:00:00.000000000 +0000 @@ -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; --- src/login/logind.c +++ src/login/logind.c 2014-08-26 11:18:41.422235366 +0000 @@ -25,6 +25,7 @@ #include #include #include +#include #include #include "sd-daemon.h" @@ -714,6 +715,47 @@ static int manager_connect_bus(Manager * 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; @@ -744,6 +786,34 @@ static int manager_connect_console(Manag 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; } --- src/shared/util.c +++ src/shared/util.c 2014-08-26 10:20:14.000000000 +0000 @@ -2434,6 +2434,24 @@ void sigset_add_many(sigset_t *ss, ...) va_end(ap); } +int sigprocmask_many(int how, ...) { + va_list ap; + sigset_t ss; + int sig; + + assert_se(sigemptyset(&ss) == 0); + + va_start(ap, how); + while ((sig = va_arg(ap, int)) > 0) + assert_se(sigaddset(&ss, sig) == 0); + va_end(ap); + + if (sigprocmask(how, &ss, NULL) < 0) + return -errno; + + return 0; +} + char* gethostname_malloc(void) { struct utsname u; --- src/shared/util.h +++ src/shared/util.h 2014-08-26 10:21:08.000000000 +0000 @@ -390,6 +390,7 @@ char* dirname_malloc(const char *path); void rename_process(const char name[8]); void sigset_add_many(sigset_t *ss, ...); +int sigprocmask_many(int how, ...); bool hostname_is_set(void);