diff --git a/src/uvtd_ctx.c b/src/uvtd_ctx.c index 1388929..20a9220 100644 --- a/src/uvtd_ctx.c +++ b/src/uvtd_ctx.c @@ -28,6 +28,37 @@ * 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: + * 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/tty: + * 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/tty and create a symlink: + * /dev/tty -> /dev/ttyFD/tty + * 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/, choose a node, look at the + * minor and then open /dev/tty respectively. + * This provides full backwards compatibility for applications that require + * /dev/tty 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 @@ -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 void ctx_legacy_cdev_event(struct uvt_cdev *cdev, - struct uvt_cdev_event *ev, void *data) +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_CDEV_HUP: - break; - case UVT_CDEV_OPEN: + 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 int ctx_legacy_cdev_init(struct uvtd_ctx *ctx, unsigned int id) +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_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) +{ + 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; - char *name; 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); + } +} diff --git a/src/uvtd_ctx.h b/src/uvtd_ctx.h index 7a17891..78e3f0b 100644 --- a/src/uvtd_ctx.h +++ b/src/uvtd_ctx.h @@ -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 */