From 0cc84b8cae70099b80eafd44afb9d624103a453a Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 10 Jul 2026 15:45:11 +0000 Subject: [PATCH] Do not make pty fds blocking again until all the data has been consumed or control mode clients can get stuck, GitHub issue 5356 from Ben Maurer. --- client.c | 27 ++++++++++----------------- control.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ tmux.h | 1 + 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/client.c b/client.c index fcc49c395..b315f9083 100644 --- a/client.c +++ b/client.c @@ -240,9 +240,8 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags, pid_t ppid; enum msgtype msg; struct termios tio, saved_tio; - size_t size, linesize = 0; - ssize_t linelen; - char *line = NULL, **caps = NULL, *cause; + size_t size; + char **caps = NULL, *cause; u_int ncaps = 0; struct args_value *values; @@ -400,11 +399,6 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags, client_exec(client_execshell, client_execcmd); } - /* Restore streams to blocking. */ - setblocking(STDIN_FILENO, 1); - setblocking(STDOUT_FILENO, 1); - setblocking(STDERR_FILENO, 1); - /* Print the exit message, if any, and exit. */ if (client_attached) { if (client_exitreason != CLIENT_EXIT_NONE) @@ -419,15 +413,8 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags, else printf("%%exit\n"); fflush(stdout); - if (client_flags & CLIENT_CONTROL_WAITEXIT) { - setvbuf(stdin, NULL, _IOLBF, 0); - for (;;) { - linelen = getline(&line, &linesize, stdin); - if (linelen <= 1) - break; - } - free(line); - } + if (client_flags & CLIENT_CONTROL_WAITEXIT) + control_wait_exit(STDIN_FILENO); if (client_flags & CLIENT_CONTROLCONTROL) { printf("\033\\"); fflush(stdout); @@ -435,6 +422,12 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags, } } else if (client_exitreason != CLIENT_EXIT_NONE) fprintf(stderr, "%s\n", client_exit_message()); + + /* Restore the streams to blocking. */ + setblocking(STDIN_FILENO, 1); + setblocking(STDOUT_FILENO, 1); + setblocking(STDERR_FILENO, 1); + return (client_exitval); } diff --git a/control.c b/control.c index c1cc79e25..a177c8b88 100644 --- a/control.c +++ b/control.c @@ -19,7 +19,9 @@ #include +#include #include +#include #include #include #include @@ -486,6 +488,53 @@ control_all_done(struct client *c) return (EVBUFFER_LENGTH(cs->write_event->output) == 0); } +/* + * Wait for the terminal to send an empty line or close, used by a control + * client after printing %exit so a wrapping terminal (such as iTerm2) can + * finish reading. + */ +void +control_wait_exit(int fd) +{ + struct pollfd pfd; + struct evbuffer *evb; + char *line; + int n; + + evb = evbuffer_new(); + if (evb == NULL) + fatalx("out of memory"); + + for (;;) { + line = evbuffer_readln(evb, NULL, EVBUFFER_EOL_LF); + if (line != NULL) { + if (*line == '\0') { /* empty line, stop */ + free(line); + break; + } + free(line); + continue; /* drain buffered lines first */ + } + + memset(&pfd, 0, sizeof pfd); + pfd.fd = fd; + pfd.events = POLLIN; + if (poll(&pfd, 1, INFTIM) == -1) { + if (errno == EINTR) + continue; + break; + } + + n = evbuffer_read(evb, fd, -1); + if (n == 0) + break; + if (n == -1 && errno != EAGAIN && errno != EINTR) + break; + } + + evbuffer_free(evb); +} + /* Flush all blocks until output. */ static void control_flush_all_blocks(struct client *c) diff --git a/tmux.h b/tmux.h index e698adf1b..b62e4ae23 100644 --- a/tmux.h +++ b/tmux.h @@ -3917,6 +3917,7 @@ void control_discard(struct client *); void control_start(struct client *); void control_ready(struct client *); void control_stop(struct client *); +void control_wait_exit(int); void control_set_pane_on(struct client *, struct window_pane *); void control_set_pane_off(struct client *, struct window_pane *); void control_continue_pane(struct client *, struct window_pane *);