Merge pull request #9961 from fbuihuu/logind-fix-vt-reinit-race
Logind fix vt reinit race
This commit is contained in:
commit
9d52a6e5a9
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 void session_restore_vt(Session *s) {
|
||||
pid_t pid;
|
||||
int r;
|
||||
|
||||
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);
|
||||
|
||||
vt = session_open_vt(s);
|
||||
safe_close(old_fd);
|
||||
|
||||
if (vt < 0)
|
||||
if (s->vtnr < 1)
|
||||
return;
|
||||
|
||||
(void) ioctl(vt, KDSETMODE, KD_TEXT);
|
||||
if (s->vtfd < 0)
|
||||
return;
|
||||
|
||||
(void) vt_reset_keyboard(vt);
|
||||
/* 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;
|
||||
|
||||
(void) ioctl(vt, VT_SETMODE, &mode);
|
||||
(void) fchown(vt, 0, (gid_t) -1);
|
||||
/* 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue