diff options
Diffstat (limited to 'src/shared/ptyfwd.c')
-rw-r--r-- | src/shared/ptyfwd.c | 222 |
1 files changed, 170 insertions, 52 deletions
diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c index 1e2852b1af..7225b933ff 100644 --- a/src/shared/ptyfwd.c +++ b/src/shared/ptyfwd.c @@ -28,12 +28,49 @@ #include "util.h" #include "ptyfwd.h" -int process_pty(int master, sigset_t *mask, pid_t kill_pid, int signo) { +#define ESCAPE_USEC USEC_PER_SEC + +static bool look_for_escape(usec_t *timestamp, unsigned *counter, const char *buffer, size_t n) { + const char *p; + + assert(timestamp); + assert(counter); + assert(buffer); + assert(n > 0); + + for (p = buffer; p < buffer + n; p++) { + + /* Check for ^] */ + if (*p == 0x1D) { + usec_t nw = now(CLOCK_MONOTONIC); + + if (*counter == 0 || nw > *timestamp + USEC_PER_SEC) { + *timestamp = nw; + *counter = 1; + } else { + (*counter)++; + + if (*counter >= 3) + return true; + } + } else { + *timestamp = 0; + *counter = 0; + } + } + + return false; +} + +static int process_pty_loop(int master, sigset_t *mask, pid_t kill_pid, int signo) { char in_buffer[LINE_MAX], out_buffer[LINE_MAX]; size_t in_buffer_full = 0, out_buffer_full = 0; struct epoll_event stdin_ev, stdout_ev, master_ev, signal_ev; bool stdin_readable = false, stdout_writable = false, master_readable = false, master_writable = false; - bool tried_orderly_shutdown = false; + bool stdin_hangup = false, stdout_hangup = false, master_hangup = false; + bool tried_orderly_shutdown = false, process_signalfd = false, quit = false; + usec_t escape_timestamp = 0; + unsigned escape_counter = 0; _cleanup_close_ int ep = -1, signal_fd = -1; assert(master >= 0); @@ -103,7 +140,7 @@ int process_pty(int master, sigset_t *mask, pid_t kill_pid, int signo) { ssize_t k; int i, nfds; - nfds = epoll_wait(ep, ev, ELEMENTSOF(ev), -1); + nfds = epoll_wait(ep, ev, ELEMENTSOF(ev), quit ? 0 : -1); if (nfds < 0) { if (errno == EINTR || errno == EAGAIN) @@ -113,7 +150,8 @@ int process_pty(int master, sigset_t *mask, pid_t kill_pid, int signo) { return -errno; } - assert(nfds >= 1); + if (nfds == 0) + return 0; for (i = 0; i < nfds; i++) { if (ev[i].data.fd == STDIN_FILENO) { @@ -134,45 +172,8 @@ int process_pty(int master, sigset_t *mask, pid_t kill_pid, int signo) { if (ev[i].events & (EPOLLOUT|EPOLLHUP)) master_writable = true; - } else if (ev[i].data.fd == signal_fd) { - struct signalfd_siginfo sfsi; - ssize_t n; - - n = read(signal_fd, &sfsi, sizeof(sfsi)); - if (n != sizeof(sfsi)) { - - if (n >= 0) { - log_error("Failed to read from signalfd: invalid block size"); - return -EIO; - } - - if (errno != EINTR && errno != EAGAIN) { - log_error("Failed to read from signalfd: %m"); - return -errno; - } - } else { - - if (sfsi.ssi_signo == SIGWINCH) { - struct winsize ws; - - /* The window size changed, let's forward that. */ - if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) >= 0) - ioctl(master, TIOCSWINSZ, &ws); - - } else if (sfsi.ssi_signo == SIGTERM && kill_pid > 0 && signo > 0 && !tried_orderly_shutdown) { - - if (kill(kill_pid, signo) < 0) - return 0; - - log_info("Trying to halt container. Send SIGTERM again to trigger immediate termination."); - - /* This only works for systemd... */ - tried_orderly_shutdown = true; - - } else - return 0; - } - } + } else if (ev[i].data.fd == signal_fd) + process_signalfd = true; } while ((stdin_readable && in_buffer_full <= 0) || @@ -185,14 +186,26 @@ int process_pty(int master, sigset_t *mask, pid_t kill_pid, int signo) { k = read(STDIN_FILENO, in_buffer + in_buffer_full, LINE_MAX - in_buffer_full); if (k < 0) { - if (errno == EAGAIN || errno == EPIPE || errno == ECONNRESET || errno == EIO) + if (errno == EAGAIN) stdin_readable = false; - else { + else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) { + stdin_readable = false; + stdin_hangup = true; + epoll_ctl(ep, EPOLL_CTL_DEL, STDIN_FILENO, NULL); + } else { log_error("read(): %m"); return -errno; } - } else + } else { + /* Check if ^] has been + * pressed three times within + * one second. If we get this + * we quite immediately. */ + if (look_for_escape(&escape_timestamp, &escape_counter, in_buffer + in_buffer_full, k)) + return !quit; + in_buffer_full += (size_t) k; + } } if (master_writable && in_buffer_full > 0) { @@ -200,9 +213,13 @@ int process_pty(int master, sigset_t *mask, pid_t kill_pid, int signo) { k = write(master, in_buffer, in_buffer_full); if (k < 0) { - if (errno == EAGAIN || errno == EPIPE || errno == ECONNRESET || errno == EIO) + if (errno == EAGAIN || errno == EIO) master_writable = false; - else { + else if (errno == EPIPE || errno == ECONNRESET) { + master_writable = master_readable = false; + master_hangup = true; + epoll_ctl(ep, EPOLL_CTL_DEL, master, NULL); + } else { log_error("write(): %m"); return -errno; } @@ -219,9 +236,21 @@ int process_pty(int master, sigset_t *mask, pid_t kill_pid, int signo) { k = read(master, out_buffer + out_buffer_full, LINE_MAX - out_buffer_full); if (k < 0) { - if (errno == EAGAIN || errno == EPIPE || errno == ECONNRESET || errno == EIO) + /* Note that EIO on the master + * device might be cause by + * vhangup() or temporary + * closing of everything on + * the other side, we treat it + * like EAGAIN here and try + * again. */ + + if (errno == EAGAIN || errno == EIO) master_readable = false; - else { + else if (errno == EPIPE || errno == ECONNRESET) { + master_readable = master_writable = false; + master_hangup = true; + epoll_ctl(ep, EPOLL_CTL_DEL, master, NULL); + } else { log_error("read(): %m"); return -errno; } @@ -234,9 +263,13 @@ int process_pty(int master, sigset_t *mask, pid_t kill_pid, int signo) { k = write(STDOUT_FILENO, out_buffer, out_buffer_full); if (k < 0) { - if (errno == EAGAIN || errno == EPIPE || errno == ECONNRESET || errno == EIO) + if (errno == EAGAIN) stdout_writable = false; - else { + else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) { + stdout_writable = false; + stdout_hangup = true; + epoll_ctl(ep, EPOLL_CTL_DEL, STDOUT_FILENO, NULL); + } else { log_error("write(): %m"); return -errno; } @@ -247,6 +280,91 @@ int process_pty(int master, sigset_t *mask, pid_t kill_pid, int signo) { out_buffer_full -= k; } } + + } + + if (process_signalfd) { + struct signalfd_siginfo sfsi; + ssize_t n; + + n = read(signal_fd, &sfsi, sizeof(sfsi)); + if (n != sizeof(sfsi)) { + + if (n >= 0) { + log_error("Failed to read from signalfd: invalid block size"); + return -EIO; + } + + if (errno != EINTR && errno != EAGAIN) { + log_error("Failed to read from signalfd: %m"); + return -errno; + } + } else { + + if (sfsi.ssi_signo == SIGWINCH) { + struct winsize ws; + + /* The window size changed, let's forward that. */ + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) >= 0) + ioctl(master, TIOCSWINSZ, &ws); + + } else if (sfsi.ssi_signo == SIGTERM && kill_pid > 0 && signo > 0 && !tried_orderly_shutdown) { + + if (kill(kill_pid, signo) < 0) + quit = true; + else { + log_info("Trying to halt container. Send SIGTERM again to trigger immediate termination."); + + /* This only works for systemd... */ + tried_orderly_shutdown = true; + } + + } else + /* Signals that where + * delivered via signalfd that + * we didn't know are a reason + * for us to quit */ + quit = true; + } + } + + if (stdin_hangup || stdout_hangup || master_hangup) { + /* Exit the loop if any side hung up and if + * there's nothing more to write or nothing we + * could write. */ + + if ((out_buffer_full <= 0 || stdout_hangup) && + (in_buffer_full <= 0 || master_hangup)) + return !quit; } } } + +int process_pty(int master, sigset_t *mask, pid_t kill_pid, int signo) { + struct termios saved_attr; + bool saved = false; + struct winsize ws; + int r; + + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) >= 0) + ioctl(master, TIOCSWINSZ, &ws); + + if (tcgetattr(STDIN_FILENO, &saved_attr) >= 0) { + struct termios raw_attr; + saved = true; + + raw_attr = saved_attr; + cfmakeraw(&raw_attr); + raw_attr.c_lflag &= ~ECHO; + + tcsetattr(STDIN_FILENO, TCSANOW, &raw_attr); + } + + r = process_pty_loop(master, mask, kill_pid, signo); + + if (saved) + tcsetattr(STDIN_FILENO, TCSANOW, &saved_attr); + + return r; + +} |