From 2461d3da1e86781cd6abff771ce1d9c3b0ea0197 Mon Sep 17 00:00:00 2001 From: "Franklin \"Snaipe\" Mathieu" Date: Fri, 28 Jun 2024 16:40:43 +0200 Subject: [PATCH] tty: cleanly handle EOF The old code would loop forever trying to read 0 bytes and thinking that the input fd was read-ready. This is normally not an issue, but because we're emulating regular files as eventfds (since epoll won't support them), things like bst --tty cat +#include #include #include #include @@ -101,3 +102,22 @@ void rebind_fds_and_close_rest(int start_fd, ...) err(1, "close_range"); } } + +/* close_null closes fd by rebinding it to /dev/null. + This is done to avoid leaving the old fd number unoccupied, + which can cause issues for the standard file descriptor numbers. */ +void close_null(int fd) +{ + int nfd = open("/dev/null", O_RDWR | O_CLOEXEC); + if (nfd == -1) { + err(1, "close_null: open /dev/null"); + } + + if (dup3(nfd, fd, O_CLOEXEC) == -1) { + err(1, "close_null: dup2 fd %d -> /dev/null", fd); + } + + if (close(nfd) == -1) { + err(1, "close_null: close"); + } +} diff --git a/fd.h b/fd.h index 8944b81..c168d60 100644 --- a/fd.h +++ b/fd.h @@ -10,5 +10,6 @@ int recv_fd(int socket); void send_fd(int socket, int fd); void rebind_fds_and_close_rest(int start_fd, ...); +void close_null(int fd); #endif /* !FD_H */ diff --git a/tty.c b/tty.c index a8c662b..0a7b842 100644 --- a/tty.c +++ b/tty.c @@ -97,6 +97,9 @@ static ssize_t io_copy(int out_fd, int in_fd, struct buffer *buf) } return -1; } + if (rd == 0) { + return copied; + } buf->size = (size_t) rd; buf->index = 0; } @@ -134,26 +137,37 @@ static int send_veof(int ptm) return write(ptm, &tios.c_cc[VEOF], 1); } -void tty_parent_cleanup(void) +static void tty_parent_resetterm(void) { - if (info.termfd >= 0) { - /* Drain any remaining data in the terminal buffer */ - set_nonblock(STDOUT_FILENO, 0); - set_nonblock(info.termfd, 0); - struct buffer drain = { - .size = 0, - }; + tcsetattr(STDIN_FILENO, TCSADRAIN, &info.orig); + info.stdinIsatty = false; +} - if (io_copy(STDOUT_FILENO, info.termfd, &drain) == -1 && errno != EIO) { - warn("copy tty -> stdout"); - } +static void tty_parent_drain(void) +{ + /* Drain any remaining data in the terminal buffer */ + set_nonblock(STDOUT_FILENO, 0); + set_nonblock(info.termfd, 0); + struct buffer drain = { + .size = 0, + }; + + if (io_copy(STDOUT_FILENO, info.termfd, &drain) == -1 && errno != EIO) { + warn("copy tty -> stdout"); + } + + close_null(STDOUT_FILENO); + close(info.termfd); + info.termfd = -1; +} - close(info.termfd); - info.termfd = -1; +void tty_parent_cleanup(void) +{ + if (info.termfd >= 0) { + tty_parent_drain(); } if (info.stdinIsatty) { - tcsetattr(STDIN_FILENO, TCSADRAIN, &info.orig); - info.stdinIsatty = false; + tty_parent_resetterm(); } } @@ -235,6 +249,11 @@ static int tty_handle_io(int epollfd, const struct epoll_event *ev, int fd, pid_ inbound_handler.ready &= ~(READ_READY|WRITE_READY); + if (copied == 0) { + tty_parent_drain(); + return EPOLL_HANDLER_CONTINUE; + } + struct epoll_event newev = { .events = EPOLLIN | EPOLLONESHOT, .data.ptr = &inbound_handler, @@ -276,12 +295,18 @@ static int tty_handle_io(int epollfd, const struct epoll_event *ev, int fd, pid_ /* outbound_handler.fd might contain our eventfd workaround */ int write_fd = STDOUT_FILENO; - if (io_copy(write_fd, read_fd, &outbound_buffer) == -1) { + ssize_t copied = io_copy(write_fd, read_fd, &outbound_buffer); + if (copied == -1) { err(1, "copy tty -> stdout"); } outbound_handler.ready = 0; + if (copied == 0) { + close_null(write_fd); + return EPOLL_HANDLER_CONTINUE; + } + struct epoll_event newev = { .events = EPOLLOUT | EPOLLONESHOT, .data.ptr = &outbound_handler, @@ -338,8 +363,9 @@ void tty_parent_setup(struct tty_opts *opts, int epollfd, int socket) /* We changed the terminal to raw mode. Line-endings now need carriage returns in order to be palatable. */ err_line_ending = "\r\n"; + + atexit(tty_parent_resetterm); } - atexit(tty_parent_cleanup); // Wait for the child to create the pty pair and pass the master back. info.termfd = recv_fd(socket);