diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/basic/terminal-util.c | 50 | ||||
-rw-r--r-- | src/basic/terminal-util.h | 2 | ||||
-rw-r--r-- | src/login/logind-session.c | 69 | ||||
-rw-r--r-- | src/login/logind-session.h | 1 | ||||
-rw-r--r-- | src/login/logind.c | 25 |
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; } |