summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/basic/terminal-util.c50
-rw-r--r--src/basic/terminal-util.h2
-rw-r--r--src/login/logind-session.c69
-rw-r--r--src/login/logind-session.h1
-rw-r--r--src/login/logind.c25
5 files changed, 121 insertions, 26 deletions
diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c
index 2c7b4508ce..7fce84bf82 100644
--- a/src/basic/terminal-util.c
+++ b/src/basic/terminal-util.c
@@ -1271,3 +1271,53 @@ int vt_reset_keyboard(int fd) {
return 0;
}
+
+int vt_restore(int fd) {
+ static const struct vt_mode mode = {
+ .mode = VT_AUTO,
+ };
+ int r, q = 0;
+
+ r = ioctl(fd, KDSETMODE, KD_TEXT);
+ if (r < 0)
+ q = log_debug_errno(errno, "Failed to set VT in text mode, ignoring: %m");
+
+ r = vt_reset_keyboard(fd);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to reset keyboard mode, ignoring: %m");
+ if (q >= 0)
+ q = r;
+ }
+
+ r = ioctl(fd, VT_SETMODE, &mode);
+ if (r < 0) {
+ log_debug_errno(errno, "Failed to set VT_AUTO mode, ignoring: %m");
+ if (q >= 0)
+ q = -errno;
+ }
+
+ r = fchown(fd, 0, (gid_t) -1);
+ if (r < 0) {
+ log_debug_errno(errno, "Failed to chown VT, ignoring: %m");
+ if (q >= 0)
+ q = -errno;
+ }
+
+ return q;
+}
+
+int vt_release(int fd, bool restore) {
+ assert(fd >= 0);
+
+ /* This function releases the VT by acknowledging the VT-switch signal
+ * sent by the kernel and optionally reset the VT in text and auto
+ * VT-switching modes. */
+
+ if (ioctl(fd, VT_RELDISP, 1) < 0)
+ return -errno;
+
+ if (restore)
+ return vt_restore(fd);
+
+ return 0;
+}
diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h
index 2d64afaee6..86e730028e 100644
--- a/src/basic/terminal-util.h
+++ b/src/basic/terminal-util.h
@@ -154,3 +154,5 @@ int open_terminal_in_namespace(pid_t pid, const char *name, int mode);
int vt_default_utf8(void);
int vt_reset_keyboard(int fd);
+int vt_restore(int fd);
+int vt_release(int fd, bool restore_vt);
diff --git a/src/login/logind-session.c b/src/login/logind-session.c
index 90af6bf070..e8be1ffbed 100644
--- a/src/login/logind-session.c
+++ b/src/login/logind-session.c
@@ -36,6 +36,7 @@
#define RELEASE_USEC (20*USEC_PER_SEC)
static void session_remove_fifo(Session *s);
+static void session_restore_vt(Session *s);
int session_new(Session **ret, Manager *m, const char *id) {
_cleanup_(session_freep) Session *s = NULL;
@@ -1223,35 +1224,55 @@ error:
return r;
}
-void session_restore_vt(Session *s) {
-
- static const struct vt_mode mode = {
- .mode = VT_AUTO,
- };
-
- int vt, old_fd;
-
- /* We need to get a fresh handle to the virtual terminal,
- * since the old file-descriptor is potentially in a hung-up
- * state after the controlling process exited; we do a
- * little dance to avoid having the terminal be available
- * for reuse before we've cleaned it up.
- */
- old_fd = TAKE_FD(s->vtfd);
+static void session_restore_vt(Session *s) {
+ pid_t pid;
+ int r;
- vt = session_open_vt(s);
- safe_close(old_fd);
+ if (s->vtnr < 1)
+ return;
- if (vt < 0)
+ if (s->vtfd < 0)
return;
- (void) ioctl(vt, KDSETMODE, KD_TEXT);
+ /* The virtual terminal can potentially be entering in hung-up state at any time
+ * depending on when the controlling process exits.
+ *
+ * If the controlling process exits while we're restoring the virtual terminal,
+ * the VT will enter in hung-up state and we'll fail at restoring it. To prevent
+ * this case, we kick off the current controlling process (if any) in a child
+ * process so logind doesn't play around with tty ownership.
+ *
+ * If the controlling process already exited, getting a fresh handle to the
+ * virtual terminal reset the hung-up state. */
+ r = safe_fork("(logind)", FORK_REOPEN_LOG|FORK_CLOSE_ALL_FDS|FORK_RESET_SIGNALS|FORK_WAIT|FORK_LOG, &pid);
+ if (r == 0) {
+ char path[sizeof("/dev/tty") + DECIMAL_STR_MAX(s->vtnr)];
+ int vt;
+
+ /* We must be a session leader in order to become the controlling process. */
+ pid = setsid();
+ if (pid < 0) {
+ log_error_errno(errno, "Failed to become session leader: %m");
+ _exit(EXIT_FAILURE);
+ }
- (void) vt_reset_keyboard(vt);
+ sprintf(path, "/dev/tty%u", s->vtnr);
+ vt = acquire_terminal(path, ACQUIRE_TERMINAL_FORCE, USEC_INFINITY);
+ if (vt < 0) {
+ log_error_errno(vt, "Cannot acquire VT %s of session %s: %m", path, s->id);
+ _exit(EXIT_FAILURE);
+ }
- (void) ioctl(vt, VT_SETMODE, &mode);
- (void) fchown(vt, 0, (gid_t) -1);
+ r = vt_restore(vt);
+ if (r < 0)
+ log_warning_errno(r, "Failed to restore VT, ignoring: %m");
+
+ /* Give up and release the controlling terminal. */
+ safe_close(vt);
+ _exit(EXIT_SUCCESS);
+ }
+ /* Close the fd in any cases. */
s->vtfd = safe_close(s->vtfd);
}
@@ -1275,9 +1296,9 @@ void session_leave_vt(Session *s) {
return;
session_device_pause_all(s);
- r = ioctl(s->vtfd, VT_RELDISP, 1);
+ r = vt_release(s->vtfd, false);
if (r < 0)
- log_debug_errno(errno, "Cannot release VT of session %s: %m", s->id);
+ log_debug_errno(r, "Cannot release VT of session %s: %m", s->id);
}
bool session_is_controller(Session *s, const char *sender) {
diff --git a/src/login/logind-session.h b/src/login/logind-session.h
index 61d188c5c5..f3c17a8d91 100644
--- a/src/login/logind-session.h
+++ b/src/login/logind-session.h
@@ -173,7 +173,6 @@ const char* tty_validity_to_string(TTYValidity t) _const_;
TTYValidity tty_validity_from_string(const char *s) _pure_;
int session_prepare_vt(Session *s);
-void session_restore_vt(Session *s);
void session_leave_vt(Session *s);
bool session_is_controller(Session *s, const char *sender);
diff --git a/src/login/logind.c b/src/login/logind.c
index f419186506..b8530520ef 100644
--- a/src/login/logind.c
+++ b/src/login/logind.c
@@ -25,6 +25,7 @@
#include "selinux-util.h"
#include "signal-util.h"
#include "strv.h"
+#include "terminal-util.h"
static Manager* manager_unref(Manager *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref);
@@ -747,7 +748,29 @@ static int manager_vt_switch(sd_event_source *src, const struct signalfd_siginfo
active = m->seat0->active;
if (!active || active->vtnr < 1) {
- log_warning("Received VT_PROCESS signal without a registered session on that VT.");
+ _cleanup_close_ int fd = -1;
+ int r;
+
+ /* We are requested to acknowledge the VT-switch signal by the kernel but
+ * there's no registered sessions for the current VT. Normally this
+ * shouldn't happen but something wrong might have happened when we tried
+ * to release the VT. Better be safe than sorry, and try to release the VT
+ * one more time otherwise the user will be locked with the current VT. */
+
+ log_warning("Received VT_PROCESS signal without a registered session, restoring VT.");
+
+ /* At this point we only have the kernel mapping for referring to the
+ * current VT. */
+ fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
+ if (fd < 0) {
+ log_warning_errno(fd, "Failed to open, ignoring: %m");
+ return 0;
+ }
+
+ r = vt_release(fd, true);
+ if (r < 0)
+ log_warning_errno(r, "Failed to release VT, ignoring: %m");
+
return 0;
}