+static int session_open_vt(Session *s) {
+ char path[sizeof("/dev/tty") + DECIMAL_STR_MAX(s->vtnr)];
+
+ if (s->vtnr < 1)
+ return -ENODEV;
+
+ if (s->vtfd >= 0)
+ return s->vtfd;
+
+ sprintf(path, "/dev/tty%u", 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 -errno;
+ }
+
+ return s->vtfd;
+}
+
+int session_prepare_vt(Session *s) {
+ int vt, r;
+ struct vt_mode mode = { 0 };
+
+ if (s->vtnr < 1)
+ return 0;
+
+ vt = session_open_vt(s);
+ if (vt < 0)
+ return vt;
+
+ r = fchown(vt, s->user->uid, -1);
+ if (r < 0) {
+ r = -errno;
+ log_error("Cannot change owner of /dev/tty%u: %m", s->vtnr);
+ goto error;
+ }
+
+ r = ioctl(vt, KDSKBMODE, K_OFF);
+ if (r < 0) {
+ r = -errno;
+ log_error("Cannot set K_OFF on /dev/tty%u: %m", s->vtnr);
+ goto error;
+ }
+
+ r = ioctl(vt, KDSETMODE, KD_GRAPHICS);
+ if (r < 0) {
+ r = -errno;
+ log_error("Cannot set KD_GRAPHICS on /dev/tty%u: %m", s->vtnr);
+ 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 = SIGRTMIN;
+ mode.acqsig = SIGRTMIN + 1;
+ r = ioctl(vt, VT_SETMODE, &mode);
+ if (r < 0) {
+ r = -errno;
+ log_error("Cannot set VT_PROCESS on /dev/tty%u: %m", s->vtnr);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ session_restore_vt(s);
+ return r;
+}
+
+void session_restore_vt(Session *s) {
+ _cleanup_free_ char *utf8 = NULL;
+ int vt, kb = K_XLATE;
+ struct vt_mode mode = { 0 };
+
+ vt = session_open_vt(s);
+ if (vt < 0)
+ return;
+
+ 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);
+
+ fchown(vt, 0, -1);
+
+ s->vtfd = safe_close(s->vtfd);
+}
+
+bool session_is_controller(Session *s, const char *sender) {
+ assert(s);
+
+ return streq_ptr(s->controller, sender);
+}
+
+static void session_release_controller(Session *s, bool notify) {
+ _cleanup_free_ char *name = NULL;
+ SessionDevice *sd;
+
+ if (!s->controller)
+ return;
+
+ name = s->controller;
+
+ /* By resetting the controller before releasing the devices, we won't
+ * send notification signals. This avoids sending useless notifications
+ * if the controller is released on disconnects. */
+ if (!notify)
+ s->controller = NULL;
+
+ while ((sd = hashmap_first(s->devices)))
+ session_device_free(sd);
+
+ s->controller = NULL;
+ manager_drop_busname(s->manager, name);
+}
+
+int session_set_controller(Session *s, const char *sender, bool force) {
+ _cleanup_free_ char *name = NULL;
+ int r;
+
+ assert(s);
+ assert(sender);
+
+ if (session_is_controller(s, sender))
+ return 0;
+ if (s->controller && !force)
+ return -EBUSY;
+
+ name = strdup(sender);
+ if (!name)
+ return -ENOMEM;
+
+ r = manager_watch_busname(s->manager, name);
+ if (r)
+ return r;
+
+ /* 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. */
+ r = session_prepare_vt(s);
+ if (r < 0) {
+ manager_drop_busname(s->manager, name);
+ return r;
+ }
+
+ session_release_controller(s, true);
+ s->controller = name;
+ name = NULL;
+ session_save(s);
+
+ return 0;
+}
+
+void session_drop_controller(Session *s) {
+ assert(s);
+
+ if (!s->controller)
+ return;
+
+ session_release_controller(s, false);
+ session_save(s);
+ session_restore_vt(s);
+}
+