uvtd: ctx: link control/legacy nodes with VTs
authorDavid Herrmann <dh.herrmann@gmail.com>
Tue, 5 Mar 2013 17:05:09 +0000 (18:05 +0100)
committerDavid Herrmann <dh.herrmann@gmail.com>
Tue, 5 Mar 2013 17:05:09 +0000 (18:05 +0100)
We now create a VT for each legacy node and assign all clients to it. For
control nodes, we create a different VT for each open-file/client.

Note that we do not recreate VTs or CDEVs directly on HUP instead of
during the next open() or reconfiguration. This avoids trapping into the
same error again and gives control to the user to recreate the nodes at
the right time.

Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
src/uvtd_ctx.c
src/uvtd_ctx.h

index 1388929..20a9220 100644 (file)
  * A context manages a single UVT seat. It creates the seat object, allocates
  * the VTs and provides all the bookkeeping for the sessions. It's the main
  * entry point after the seat selectors in uvtd-main.
+ *
+ * For each seat we create two different kinds of character-devices:
+ *   /dev/ttyFC<seat>:
+ *     This is the control-node. It's the preferred way to open new VTs. It
+ *     provides a fully-backwards compatible VT API so legacy apps should be
+ *     able to make use of it. Each open-file is associated to a different VT so
+ *     you cannot share these easily, anymore. You need to pass the FD instead.
+ *     This avoids problems with multiple users on the same VT that we had in
+ *     the past.
+ *   /dev/ttyFD<seat>/tty<num>:
+ *     These are legacy VTs. They are put into a subdirectory and provide full
+ *     backwards compatibility to real VTs. They are preallocated and there is
+ *     only a limited number of them. You can control how many of these are
+ *     allocated via the configuration options.
+ *     These VTs can be shared between processes easily as all open-files on a
+ *     single node share the same VT.
+ * All character devices share the MAJOR number that is also used by real VTs.
+ * However, the minor numbers use a relatively high offset (default: 2^14) so
+ * they don't clash with real VTs.
+ * If you need backwards-compatible symlinks, you can use the minor number of a
+ * VT node in /dev/ttyFD<seat>/tty<num> and create a symlink:
+ *   /dev/tty<minor> -> /dev/ttyFD<seat>/tty<num>
+ * As the minors are globally unique, they won't clash with other tty nodes in
+ * /dev. However, you loose the ability to see which seat a node is associated
+ * to. So you normally look into /dev/ttyFD<seat>/, choose a node, look at the
+ * minor and then open /dev/tty<minor> respectively.
+ * This provides full backwards compatibility for applications that require
+ * /dev/tty<num> paths (like old xservers).
+ *
+ * The VT logic is found in uvtd-vt subsystem. This file only provides the
+ * character-device control nodes and links them to the right VTs.
  */
 
 #include <errno.h>
@@ -42,6 +73,7 @@
 #include "uvt.h"
 #include "uvtd_ctx.h"
 #include "uvtd_seat.h"
+#include "uvtd_vt.h"
 
 #define LOG_SUBSYSTEM "ctx"
 
@@ -51,6 +83,7 @@ struct ctx_legacy {
        unsigned int minor;
        unsigned int id;
        struct uvt_cdev *cdev;
+       struct uvtd_vt *vt;
 };
 
 struct uvtd_ctx {
@@ -62,31 +95,132 @@ struct uvtd_ctx {
        unsigned int main_cdev_minor;
        struct uvt_cdev *main_cdev;
 
-       struct shl_dlist legacy_cdevs;
+       struct shl_dlist legacy_list;
        unsigned int legacy_num;
 };
 
+static int ctx_legacy_init_vt(struct ctx_legacy *legacy);
+
+static void ctx_legacy_vt_event(void *vt, struct uvt_vt_event *ev, void *data)
+{
+       struct ctx_legacy *legacy = data;
+
+       switch (ev->type) {
+       case UVT_VT_TTY:
+               if (ev->tty.type != UVT_TTY_HUP)
+                       break;
+
+               /* fallthrough */
+       case UVT_VT_HUP:
+               log_debug("HUP on legacy VT %p", legacy);
+
+               uvtd_vt_unregister_cb(legacy->vt, ctx_legacy_vt_event, legacy);
+               uvtd_vt_unref(legacy->vt);
+               legacy->vt = NULL;
+               break;
+       }
+}
+
 static void ctx_legacy_cdev_event(struct uvt_cdev *cdev,
                                  struct uvt_cdev_event *ev, void *data)
 {
+       struct ctx_legacy *legacy = data;
+       int ret;
+
        switch (ev->type) {
        case UVT_CDEV_HUP:
+               log_warning("HUP on legacy cdev %p", cdev);
+               uvt_cdev_unregister_cb(legacy->cdev, ctx_legacy_cdev_event,
+                                      legacy);
+               uvt_cdev_unref(legacy->cdev);
+               legacy->cdev = NULL;
                break;
        case UVT_CDEV_OPEN:
+               /* A legacy VT might get closed by the seat/session-scheduler at
+                * any time. We want to avoid respawning it right away to avoid
+                * error-throttling, so instead we respawn it when the next
+                * client opens the underlying cdev. */
+               if (!legacy->vt) {
+                       log_debug("reinitializing VT on legacy cdev %p",
+                                 legacy);
+                       ret = ctx_legacy_init_vt(legacy);
+                       if (ret) {
+                               log_warning("cannot reinitialize VT on legacy cdev %p",
+                                           legacy);
+                               uvt_client_kill(ev->client);
+                               break;
+                       }
+               }
+
+               ret = uvt_client_set_vt(ev->client, &uvtd_vt_ops, legacy->vt);
+               if (ret) {
+                       log_warning("cannot assign VT to new client: %d",
+                                   ret);
+                       uvt_client_kill(ev->client);
+               }
                break;
        }
 }
 
-static int ctx_legacy_cdev_init(struct uvtd_ctx *ctx, unsigned int id)
+static int ctx_legacy_init_vt(struct ctx_legacy *legacy)
+{
+       int ret;
+
+       ret = uvtd_vt_new(&legacy->vt, legacy->ctx->uctx, legacy->id,
+                         legacy->ctx->seat, true);
+       if (ret)
+               return ret;
+
+       ret = uvtd_vt_register_cb(legacy->vt, ctx_legacy_vt_event, legacy);
+       if (ret) {
+               uvtd_vt_unref(legacy->vt);
+               legacy->vt = NULL;
+               return ret;
+       }
+
+       return 0;
+}
+
+static int ctx_legacy_init_cdev(struct ctx_legacy *legacy)
 {
-       struct ctx_legacy *legacy;
        char *name;
        int ret;
 
+       ret = asprintf(&name, "ttyFD%s!tty%u", legacy->ctx->seatname,
+                      legacy->minor);
+       if (ret <= 0)
+               return -ENOMEM;
+
+       ret = uvt_cdev_new(&legacy->cdev, legacy->ctx->uctx, name,
+                          uvt_ctx_get_major(legacy->ctx->uctx),
+                          legacy->minor);
+       free(name);
+
+       if (ret)
+               return ret;
+
+       ret = uvt_cdev_register_cb(legacy->cdev, ctx_legacy_cdev_event,
+                                  legacy);
+       if (ret) {
+               uvt_cdev_unref(legacy->cdev);
+               legacy->cdev = NULL;
+               return ret;
+       }
+
+       return 0;
+}
+
+static int ctx_legacy_init(struct uvtd_ctx *ctx, unsigned int id)
+{
+       struct ctx_legacy *legacy;
+       int ret;
+
        legacy = malloc(sizeof(*legacy));
        if (!legacy)
                return -ENOMEM;
 
+       log_debug("new legacy cdev %p on ctx %p", legacy, ctx);
+
        memset(legacy, 0, sizeof(*legacy));
        legacy->id = id;
        legacy->ctx = ctx;
@@ -95,28 +229,19 @@ static int ctx_legacy_cdev_init(struct uvtd_ctx *ctx, unsigned int id)
        if (ret)
                goto err_free;
 
-       ret = asprintf(&name, "ttysF%s!tty%u", ctx->seatname, legacy->minor);
-       if (ret <= 0) {
-               ret = -ENOMEM;
-               goto err_minor;
-       }
-
-       ret = uvt_cdev_new(&legacy->cdev, ctx->uctx, name,
-                          uvt_ctx_get_major(ctx->uctx), legacy->minor);
-       free(name);
-
+       ret = ctx_legacy_init_cdev(legacy);
        if (ret)
                goto err_minor;
 
-       ret = uvt_cdev_register_cb(legacy->cdev, ctx_legacy_cdev_event,
-                                  legacy);
+       ret = ctx_legacy_init_vt(legacy);
        if (ret)
                goto err_cdev;
 
-       shl_dlist_link(&ctx->legacy_cdevs, &legacy->list);
+       shl_dlist_link(&ctx->legacy_list, &legacy->list);
        return 0;
 
 err_cdev:
+       uvt_cdev_unregister_cb(legacy->cdev, ctx_legacy_cdev_event, legacy);
        uvt_cdev_unref(legacy->cdev);
 err_minor:
        uvt_ctx_free_minor(ctx->uctx, legacy->minor);
@@ -125,24 +250,53 @@ err_free:
        return ret;
 }
 
-static void ctx_legacy_cdev_destroy(struct ctx_legacy *legacy)
+static void ctx_legacy_destroy(struct ctx_legacy *legacy)
 {
+       log_debug("free legacy cdev %p", legacy);
+
        shl_dlist_unlink(&legacy->list);
+       uvtd_vt_unregister_cb(legacy->vt, ctx_legacy_vt_event, legacy);
+       uvtd_vt_unref(legacy->vt);
        uvt_cdev_unregister_cb(legacy->cdev, ctx_legacy_cdev_event, legacy);
        uvt_cdev_unref(legacy->cdev);
        uvt_ctx_free_minor(legacy->ctx->uctx, legacy->minor);
        free(legacy);
 }
 
-static void ctx_legacy_cdev_conf(struct uvtd_ctx *ctx, unsigned int num)
+static void ctx_legacy_reconf(struct uvtd_ctx *ctx, unsigned int num)
 {
        struct ctx_legacy *l;
+       struct shl_dlist *iter;
        unsigned int i;
        int ret;
 
+       /* If a legacy cdev received a HUP or some other error and got closed,
+        * we try to reinitialize it whenever the context is reconfigured. This
+        * avoids implementing any error-throttling while at the same time users
+        * can trigger a reinitialization with a reconfiguration.
+        * This doesn't touch running cdevs, but only HUP'ed cdevs. */
+       shl_dlist_for_each(iter, &ctx->legacy_list) {
+               l = shl_dlist_entry(iter, struct ctx_legacy, list);
+               if (l->cdev)
+                       continue;
+
+               log_debug("reinitialize legacy cdev %p", l);
+
+               ret = ctx_legacy_init_cdev(l);
+               if (ret)
+                       log_warning("cannot reinitialize legacy cdev %p: %d",
+                                   l, ret);
+       }
+
+       if (num == ctx->legacy_num)
+               return;
+
+       log_debug("changing #num of legacy cdevs on ctx %p from %u to %u",
+                 ctx, ctx->legacy_num, num);
+
        if (num > ctx->legacy_num) {
                for (i = ctx->legacy_num; i < num; ++i) {
-                       ret = ctx_legacy_cdev_init(ctx, i);
+                       ret = ctx_legacy_init(ctx, i);
                        if (ret)
                                break;
                }
@@ -150,9 +304,9 @@ static void ctx_legacy_cdev_conf(struct uvtd_ctx *ctx, unsigned int num)
                ctx->legacy_num = i;
        } else {
                for (i = num; i < ctx->legacy_num; ++i) {
-                       l = shl_dlist_last(&ctx->legacy_cdevs,
+                       l = shl_dlist_last(&ctx->legacy_list,
                                           struct ctx_legacy, list);
-                       ctx_legacy_cdev_destroy(l);
+                       ctx_legacy_destroy(l);
                }
 
                ctx->legacy_num = num;
@@ -164,20 +318,52 @@ static void ctx_main_cdev_event(struct uvt_cdev *cdev,
                                void *data)
 {
        struct uvtd_ctx *ctx = data;
+       struct uvtd_vt *vt;
+       int ret;
 
        switch (ev->type) {
        case UVT_CDEV_HUP:
-               log_error("HUP on main cdev on seat %s", ctx->seatname);
+               log_warning("HUP on main cdev on ctx %p", ctx);
+               uvt_cdev_unregister_cb(ctx->main_cdev, ctx_main_cdev_event,
+                                      ctx);
+               uvt_cdev_unref(ctx->main_cdev);
+               ctx->main_cdev = NULL;
                break;
        case UVT_CDEV_OPEN:
-               log_debug("new client on main cdev on seat %s",
-                         ctx->seatname);
+               ret = uvtd_vt_new(&vt, ctx->uctx, 0, ctx->seat, false);
+               if (ret)
+                       break;
+
+               uvt_client_set_vt(ev->client, &uvtd_vt_ops, vt);
+               uvtd_vt_unref(vt);
                break;
        }
 }
 
-static void ctx_seat_event(struct uvtd_seat *seat, unsigned int ev, void *data)
+static int ctx_init_cdev(struct uvtd_ctx *ctx)
 {
+       int ret;
+       char *name;
+
+       ret = asprintf(&name, "ttyFC%s", ctx->seatname);
+       if (ret <= 0)
+               return -ENOMEM;
+
+       ret = uvt_cdev_new(&ctx->main_cdev, ctx->uctx, name,
+                          uvt_ctx_get_major(ctx->uctx), ctx->main_cdev_minor);
+       free(name);
+
+       if (ret)
+               return ret;
+
+       ret = uvt_cdev_register_cb(ctx->main_cdev, ctx_main_cdev_event, ctx);
+       if (ret) {
+               uvt_cdev_unref(ctx->main_cdev);
+               ctx->main_cdev = NULL;
+               return ret;
+       }
+
+       return 0;
 }
 
 static bool has_real_vts(const char *seatname)
@@ -191,7 +377,6 @@ int uvtd_ctx_new(struct uvtd_ctx **out, const char *seatname,
 {
        struct uvtd_ctx *ctx;
        int ret;
-       char *name;
 
        if (!out || !seatname || !eloop || !uctx)
                return -EINVAL;
@@ -208,7 +393,7 @@ int uvtd_ctx_new(struct uvtd_ctx **out, const char *seatname,
        memset(ctx, 0, sizeof(*ctx));
        ctx->eloop = eloop;
        ctx->uctx = uctx;
-       shl_dlist_init(&ctx->legacy_cdevs);
+       shl_dlist_init(&ctx->legacy_list);
 
        ctx->seatname = strdup(seatname);
        if (!ctx->seatname) {
@@ -216,8 +401,7 @@ int uvtd_ctx_new(struct uvtd_ctx **out, const char *seatname,
                goto err_free;
        }
 
-       ret = uvtd_seat_new(&ctx->seat, seatname, ctx->eloop, ctx_seat_event,
-                           ctx);
+       ret = uvtd_seat_new(&ctx->seat, seatname, ctx->eloop, NULL, NULL);
        if (ret)
                goto err_name;
 
@@ -225,32 +409,18 @@ int uvtd_ctx_new(struct uvtd_ctx **out, const char *seatname,
        if (ret)
                goto err_seat;
 
-       ret = asprintf(&name, "ttyF%s", seatname);
-       if (ret <= 0) {
-               ret = -ENOMEM;
-               goto err_minor;
-       }
-
-       ret = uvt_cdev_new(&ctx->main_cdev, ctx->uctx, name,
-                          uvt_ctx_get_major(ctx->uctx), ctx->main_cdev_minor);
-       free(name);
-
+       ret = ctx_init_cdev(ctx);
        if (ret)
                goto err_minor;
 
-       ret = uvt_cdev_register_cb(ctx->main_cdev, ctx_main_cdev_event, ctx);
-       if (ret)
-               goto err_cdev;
-
-       ctx_legacy_cdev_conf(ctx, 8);
-
        ev_eloop_ref(ctx->eloop);
        uvt_ctx_ref(ctx->uctx);
        *out = ctx;
+
+       ctx_legacy_reconf(ctx, 8);
+
        return 0;
 
-err_cdev:
-       uvt_cdev_unref(ctx->main_cdev);
 err_minor:
        uvt_ctx_free_minor(ctx->uctx, ctx->main_cdev_minor);
 err_seat:
@@ -267,13 +437,38 @@ void uvtd_ctx_free(struct uvtd_ctx *ctx)
        if (!ctx)
                return;
 
-       ctx_legacy_cdev_conf(ctx, 0);
+       log_debug("free ctx %p", ctx);
+
+       ctx_legacy_reconf(ctx, 0);
        uvt_cdev_unregister_cb(ctx->main_cdev, ctx_main_cdev_event, ctx);
        uvt_cdev_unref(ctx->main_cdev);
        uvt_ctx_free_minor(ctx->uctx, ctx->main_cdev_minor);
        uvtd_seat_free(ctx->seat);
+       free(ctx->seatname);
        uvt_ctx_unref(ctx->uctx);
        ev_eloop_unref(ctx->eloop);
-       free(ctx->seatname);
        free(ctx);
 }
+
+void uvtd_ctx_reconf(struct uvtd_ctx *ctx, unsigned int legacy_num)
+{
+       int ret;
+
+       if (!ctx)
+               return;
+
+       ctx_legacy_reconf(ctx, legacy_num);
+
+       /* Lets recreate the control node if it got busted during runtime. We do
+        * not recreate it right away after receiving a HUP signal to avoid
+        * trapping into the same error that caused the HUP.
+        * Instead we recreate the node on reconfiguration so users can control
+        * when to recreate them. */
+       if (!ctx->main_cdev) {
+               log_debug("recreating main cdev on ctx %p", ctx);
+               ret = ctx_init_cdev(ctx);
+               if (ret)
+                       log_warning("cannot recreate main cdev on ctx %p",
+                                   ctx);
+       }
+}
index 7a17891..78e3f0b 100644 (file)
@@ -43,4 +43,6 @@ int uvtd_ctx_new(struct uvtd_ctx **out, const char *seatname,
                 struct ev_eloop *eloop, struct uvt_ctx *uctx);
 void uvtd_ctx_free(struct uvtd_ctx *ctx);
 
+void uvtd_ctx_reconf(struct uvtd_ctx *ctx, unsigned int legacy_num);
+
 #endif /* UVTD_CTX_H */