[systemd-commits] 5 commits - src/libsystemd-bus src/login

David Herrmann dvdhrm at kemper.freedesktop.org
Thu Nov 28 06:26:30 PST 2013


 src/libsystemd-bus/bus-util.c     |   26 ++++++
 src/libsystemd-bus/bus-util.h     |    2 
 src/libsystemd-bus/sd-event.c     |    4 
 src/login/logind-session-device.c |    6 -
 src/login/logind-session.c        |  162 ++++++++++++++++++++++++++++++++++----
 src/login/logind-session.h        |    7 +
 6 files changed, 187 insertions(+), 20 deletions(-)

New commits:
commit 2a16a986ce5f1bdb7e96abfe14fcb9f34c9364b6
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Wed Nov 27 10:36:35 2013 +0100

    event: allow EPOLLET as event flag
    
    EPOLLET enables edge-triggered mode (see epoll(7) for more). For most
    use-cases, level-triggered is just fine, but for master-TTYs we need
    edge-triggered to catch EPOLLHUP. master-TTYs signal EPOLLHUP if no client
    is connected, but a client may connect some time later (same happens
    during vhangup(2)).
    
    However, epoll doesn't allow masking EPOLLHUP so it's signaled constantly.
    To avoid this, edge-triggered mode is needed.

diff --git a/src/libsystemd-bus/sd-event.c b/src/libsystemd-bus/sd-event.c
index 6a6581b..b5ddf71 100644
--- a/src/libsystemd-bus/sd-event.c
+++ b/src/libsystemd-bus/sd-event.c
@@ -584,7 +584,7 @@ _public_ int sd_event_add_io(
 
         assert_return(e, -EINVAL);
         assert_return(fd >= 0, -EINVAL);
-        assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP)), -EINVAL);
+        assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL);
         assert_return(callback, -EINVAL);
         assert_return(ret, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
@@ -1022,7 +1022,7 @@ _public_ int sd_event_source_set_io_events(sd_event_source *s, uint32_t events)
 
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_IO, -EDOM);
-        assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP)), -EINVAL);
+        assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL);
         assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_pid_changed(s->event), -ECHILD);
 

commit 90a18413f8be577a649900eca977e060273f2b5b
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Thu Nov 28 15:10:24 2013 +0100

    logind: mute/restore VT on behalf of session controllers
    
    If a session process calls TakeControl(), we now put the VT into
    KD_GRAPHICS+K_OFF mode. This way, the new session controller can solely
    rely on the logind-dbus API to manage the session.
    
    Once the controller exits or calls ReleaseControl(), we restore the VT. We
    also restore it, if we lost a controller during crash/restart (but only if
    there really *was* a controller previously).
    
    Note that we also must put the VT into VT_PROCESS mode. We want VT_AUTO
    semantics, but VT_AUTO+KD_GRAPHICS actually disables *all* VT switches
    (who came up with that great idea?). Hence, we set VT_PROCESS for logind
    but acknowledge *all* requests immediately.
    
    If a compositor wants custom VT setups, they can still get this by *first*
    calling TakeControl() and afterwards setting up the VT. logind doesn't
    touch the VT during controller runtime, only during setup/teardown. This
    is actually what weston already does.

diff --git a/src/login/logind-session.c b/src/login/logind-session.c
index 0e1c40b..c12683c 100644
--- a/src/login/logind-session.c
+++ b/src/login/logind-session.c
@@ -20,9 +20,13 @@
 ***/
 
 #include <errno.h>
+#include <fcntl.h>
+#include <linux/vt.h>
+#include <linux/kd.h>
+#include <signal.h>
 #include <string.h>
+#include <sys/ioctl.h>
 #include <unistd.h>
-#include <fcntl.h>
 
 #include "sd-id128.h"
 #include "sd-messages.h"
@@ -86,6 +90,7 @@ Session* session_new(Manager *m, const char *id) {
 
         s->manager = m;
         s->fifo_fd = -1;
+        s->vtfd = -1;
 
         return s;
 }
@@ -395,6 +400,8 @@ int session_load(Session *s) {
         if (controller) {
                 if (bus_name_has_owner(s->manager->bus, controller, NULL) > 0)
                         session_set_controller(s, controller, false);
+                else
+                        session_restore_vt(s);
         }
 
         return r;
@@ -971,6 +978,101 @@ int session_kill(Session *s, KillWho who, int signo) {
         return manager_kill_unit(s->manager, s->scope, who, signo, NULL);
 }
 
+static int session_open_vt(Session *s) {
+        char path[128];
+
+        if (s->vtnr <= 0)
+                return -1;
+
+        if (s->vtfd >= 0)
+                return s->vtfd;
+
+        sprintf(path, "/dev/tty%d", s->vtnr);
+        s->vtfd = open(path, O_RDWR | O_CLOEXEC | O_NONBLOCK | O_NOCTTY);
+        if (s->vtfd < 0) {
+                log_error("cannot open VT %s of session %s: %m", path, s->id);
+                return -1;
+        }
+
+        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;
+}
+
+void session_mute_vt(Session *s) {
+        int vt, r;
+        struct vt_mode mode = { 0 };
+        sigset_t mask;
+
+        vt = session_open_vt(s);
+        if (vt < 0)
+                return;
+
+        r = ioctl(vt, KDSKBMODE, K_OFF);
+        if (r < 0)
+                goto error;
+
+        r = ioctl(vt, KDSETMODE, KD_GRAPHICS);
+        if (r < 0)
+                goto error;
+
+        sigemptyset(&mask);
+        sigaddset(&mask, SIGUSR1);
+        sigprocmask(SIG_BLOCK, &mask, NULL);
+
+        r = sd_event_add_signal(s->manager->event, SIGUSR1, session_vt_fn, s, &s->vt_source);
+        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;
+        r = ioctl(vt, VT_SETMODE, &mode);
+        if (r < 0)
+                goto error;
+
+        return;
+
+error:
+        log_error("cannot mute VT %d for session %s (%d/%d)", s->vtnr, s->id, r, errno);
+        session_restore_vt(s);
+}
+
+void session_restore_vt(Session *s) {
+        _cleanup_free_ char *utf8;
+        int vt, kb = K_XLATE;
+        struct vt_mode mode = { 0 };
+
+        vt = session_open_vt(s);
+        if (vt < 0)
+                return;
+
+        sd_event_source_unref(s->vt_source);
+        s->vt_source = NULL;
+
+        ioctl(vt, KDSETMODE, KD_TEXT);
+
+        if (read_one_line_file("/sys/module/vt/parameters/default_utf8", &utf8) >= 0 && *utf8 == '1')
+                kb = K_UNICODE;
+        ioctl(vt, KDSKBMODE, kb);
+
+        mode.mode = VT_AUTO;
+        ioctl(vt, VT_SETMODE, &mode);
+
+        close_nointr_nofail(vt);
+        s->vtfd = -1;
+}
+
 bool session_is_controller(Session *s, const char *sender) {
         assert(s);
 
@@ -990,6 +1092,9 @@ static void session_swap_controller(Session *s, char *name) {
                  * dbus signals. */
                 while ((sd = hashmap_first(s->devices)))
                         session_device_free(sd);
+
+                if (!name)
+                        session_restore_vt(s);
         }
 
         s->controller = name;
@@ -1020,6 +1125,16 @@ int session_set_controller(Session *s, const char *sender, bool force) {
 
         session_swap_controller(s, t);
 
+        /* When setting a session controller, we forcibly mute the VT and set
+         * it into graphics-mode. Applications can override that by changing
+         * VT state after calling TakeControl(). However, this serves as a good
+         * default and well-behaving controllers can now ignore VTs entirely.
+         * Note that we reset the VT on ReleaseControl() and if the controller
+         * exits.
+         * If logind crashes/restarts, we restore the controller during restart
+         * or reset the VT in case it crashed/exited, too. */
+        session_mute_vt(s);
+
         return 0;
 }
 
diff --git a/src/login/logind-session.h b/src/login/logind-session.h
index f7a9dbc..aab39b7 100644
--- a/src/login/logind-session.h
+++ b/src/login/logind-session.h
@@ -89,8 +89,10 @@ struct Session {
         char *scope;
         char *scope_job;
 
-        int vtnr;
         Seat *seat;
+        int vtnr;
+        int vtfd;
+        sd_event_source *vt_source;
 
         pid_t leader;
         uint32_t audit_id;
@@ -162,6 +164,9 @@ SessionClass session_class_from_string(const char *s) _pure_;
 const char *kill_who_to_string(KillWho k) _const_;
 KillWho kill_who_from_string(const char *s) _pure_;
 
+void session_mute_vt(Session *s);
+void session_restore_vt(Session *s);
+
 bool session_is_controller(Session *s, const char *sender);
 int session_set_controller(Session *s, const char *sender, bool force);
 void session_drop_controller(Session *s);

commit 6d33772f9ae6769c557e2267d16b7d31f67db914
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Thu Nov 28 14:58:57 2013 +0100

    logind: restore session-controller after crash
    
    We now save the unique bus-name of a session-controller as CONTROLLER=%s
    in the session files. This allows us to restore the controller after a
    crash or restart.
    
    Note that we test whether the name is still valid (dbus guarantees that
    the name is unique as long as the machine is up and running). If it is,
    we know that the controller still exists and can safely restore it. Our
    dbus-name-tracking guarantees that we're notified once it exits.
    
    Also note that session-devices are *not* restored. We have no way to know
    which devices where used before the crash. We could store all these on
    disk, too, or mark them via udev. However, this seems to be rather
    cumbersome. Instead, we expect controllers to listen for NewSession
    signals for their own session. This is sent on session_load() and they can
    then re-request all devices.
    
    The only race I could find is if logind crashes, then the session
    controller tries calling ReleaseControl() (which will fail as logind is
    down) but keeps the bus-connection valid for other independent requests.
    If logind is restarted, it will restore the old controller and thus block
    the session.
    However, this seems unlikely for several reasons:
     - The ReleaseControl() call must occur exactly in the timespan where
       logind is dead.
     - A process which calls ReleaseControl() usually closes the
       bus-connection afterwards. Especially if ReleaseControl() fails, the
       process should notice that something is wrong and close the bus.
     - A process calling ReleaseControl() usually exits afterwards. There may
       be any cleanup pending, but other than that, usual compositors exit.
     - If a session-controller calls ReleaseControl(), a session is usually
       considered closing. There is no known use-case where we hand-over
       session-control in a single session. So we don't care whether the
       controller is locked afterwards.
    
    So this seems negligible.

diff --git a/src/login/logind-session.c b/src/login/logind-session.c
index d343373..0e1c40b 100644
--- a/src/login/logind-session.c
+++ b/src/login/logind-session.c
@@ -239,6 +239,9 @@ int session_save(Session *s) {
                         (unsigned long long) s->timestamp.realtime,
                         (unsigned long long) s->timestamp.monotonic);
 
+        if (s->controller)
+                fprintf(f, "CONTROLLER=%s\n", s->controller);
+
         fflush(f);
 
         if (ferror(f) || rename(temp_path, s->state_file) < 0) {
@@ -263,7 +266,8 @@ int session_load(Session *s) {
                 *class = NULL,
                 *uid = NULL,
                 *realtime = NULL,
-                *monotonic = NULL;
+                *monotonic = NULL,
+                *controller = NULL;
 
         int k, r;
 
@@ -287,6 +291,7 @@ int session_load(Session *s) {
                            "UID",            &uid,
                            "REALTIME",       &realtime,
                            "MONOTONIC",      &monotonic,
+                           "CONTROLLER",     &controller,
                            NULL);
 
         if (r < 0) {
@@ -387,6 +392,11 @@ int session_load(Session *s) {
                         s->timestamp.monotonic = l;
         }
 
+        if (controller) {
+                if (bus_name_has_owner(s->manager->bus, controller, NULL) > 0)
+                        session_set_controller(s, controller, false);
+        }
+
         return r;
 }
 
@@ -967,6 +977,25 @@ bool session_is_controller(Session *s, const char *sender) {
         return streq_ptr(s->controller, sender);
 }
 
+static void session_swap_controller(Session *s, char *name) {
+        SessionDevice *sd;
+
+        if (s->controller) {
+                manager_drop_busname(s->manager, s->controller);
+                free(s->controller);
+                s->controller = NULL;
+
+                /* Drop all devices as they're now unused. Do that after the
+                 * controller is released to avoid sending out useles
+                 * dbus signals. */
+                while ((sd = hashmap_first(s->devices)))
+                        session_device_free(sd);
+        }
+
+        s->controller = name;
+        session_save(s);
+}
+
 int session_set_controller(Session *s, const char *sender, bool force) {
         char *t;
         int r;
@@ -989,28 +1018,18 @@ int session_set_controller(Session *s, const char *sender, bool force) {
                 return r;
         }
 
-        session_drop_controller(s);
+        session_swap_controller(s, t);
 
-        s->controller = t;
         return 0;
 }
 
 void session_drop_controller(Session *s) {
-        SessionDevice *sd;
-
         assert(s);
 
         if (!s->controller)
                 return;
 
-        manager_drop_busname(s->manager, s->controller);
-        free(s->controller);
-        s->controller = NULL;
-
-        /* Drop all devices as they're now unused. Do that after the controller
-         * is released to avoid sending out useles dbus signals. */
-        while ((sd = hashmap_first(s->devices)))
-                session_device_free(sd);
+        session_swap_controller(s, NULL);
 }
 
 static const char* const session_state_table[_SESSION_STATE_MAX] = {

commit d1107170f9e0fa2cb6e8d18586a003f0d96abfc3
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Thu Nov 28 14:51:40 2013 +0100

    logind: ignore failing close() on session-devices
    
    Unfortunately, close() on a revoked/removed character-device fails with
    ENODEV. I tried tracking this down in the kernel, but couldn't figure out
    were exactly it comes from. However, can be easily reproduced with:
      fd = open("/dev/input/event0", O_RDWR);
      ioctl(fd, EVIOCREVOKE, 0);
      r = close(fd);
    A second close on @fd would return EBADF so the close is actually valid.
    
    We simply ignore close() errors for all session-devices as their access
    may be revoked asynchronously, or the device might get unplugged.
    We use close_nointr() in case anyone ever looks at the return value (or
    anyone runs "grep 'close(' -r src/" to find broken close() calls).
    
    Fixes:
      systemd-logind[31992]: Assertion 'close_nointr(fd) == 0' failed at src/shared/util.c:185, function close_nointr_nofail(). Aborting.

diff --git a/src/login/logind-session-device.c b/src/login/logind-session-device.c
index b2ef8cc..592bcf2 100644
--- a/src/login/logind-session-device.c
+++ b/src/login/logind-session-device.c
@@ -162,7 +162,7 @@ static int session_device_open(SessionDevice *sd, bool active) {
                          * state. */
                         r = sd_drmsetmaster(fd);
                         if (r < 0) {
-                                close(fd);
+                                close_nointr(fd);
                                 return r;
                         }
                 } else {
@@ -209,7 +209,7 @@ static int session_device_start(SessionDevice *sd) {
                 r = session_device_open(sd, true);
                 if (r < 0)
                         return r;
-                close_nointr_nofail(sd->fd);
+                close_nointr(sd->fd);
                 sd->fd = r;
                 break;
         case DEVICE_TYPE_UNKNOWN:
@@ -407,7 +407,7 @@ void session_device_free(SessionDevice *sd) {
 
         session_device_stop(sd);
         session_device_notify(sd, SESSION_DEVICE_RELEASE);
-        close_nointr_nofail(sd->fd);
+        close_nointr(sd->fd);
 
         LIST_REMOVE(sd_by_device, sd->device->session_devices, sd);
 

commit 5fd38859b30b95008e483109578c7fef2b5072f3
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Thu Nov 28 14:50:19 2013 +0100

    bus: add bus_name_has_owner() helper
    
    Small helper to run a synchronous "NameHasOwner" request on the
    dbus-daemon.

diff --git a/src/libsystemd-bus/bus-util.c b/src/libsystemd-bus/bus-util.c
index 2daf8c1..7a21975 100644
--- a/src/libsystemd-bus/bus-util.c
+++ b/src/libsystemd-bus/bus-util.c
@@ -103,6 +103,32 @@ int bus_event_loop_with_idle(sd_event *e, sd_bus *bus, const char *name, usec_t
         return 0;
 }
 
+int bus_name_has_owner(sd_bus *c, const char *name, sd_bus_error *error) {
+        _cleanup_bus_message_unref_ sd_bus_message *rep = NULL;
+        int r, has_owner = 0;
+
+        assert(c);
+        assert(name);
+
+        r = sd_bus_call_method(c,
+                               "org.freedesktop.DBus",
+                               "/org/freedesktop/dbus",
+                               "org.freedesktop.DBus",
+                               "NameHasOwner",
+                               error,
+                               &rep,
+                               "s",
+                               name);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_read_basic(rep, 'b', &has_owner);
+        if (r < 0)
+                return sd_bus_error_set_errno(error, r);
+
+        return has_owner;
+}
+
 int bus_verify_polkit(
                 sd_bus *bus,
                 sd_bus_message *m,
diff --git a/src/libsystemd-bus/bus-util.h b/src/libsystemd-bus/bus-util.h
index 20739a9..38d468e 100644
--- a/src/libsystemd-bus/bus-util.h
+++ b/src/libsystemd-bus/bus-util.h
@@ -56,6 +56,8 @@ int bus_async_unregister_and_quit(sd_event *e, sd_bus *bus, const char *name);
 
 int bus_event_loop_with_idle(sd_event *e, sd_bus *bus, const char *name, usec_t timeout);
 
+int bus_name_has_owner(sd_bus *c, const char *name, sd_bus_error *error);
+
 int bus_check_peercred(sd_bus *c);
 
 int bus_verify_polkit(sd_bus *bus, sd_bus_message *m, const char *action, bool interactive, bool *_challenge, sd_bus_error *e);



More information about the systemd-commits mailing list