diff --git a/client.c b/client.c index 113083fd..4ebcf2ad 100644 --- a/client.c +++ b/client.c @@ -35,6 +35,7 @@ struct imsgbuf client_ibuf; struct event client_event; +struct event client_stdin; enum { CLIENT_EXIT_NONE, CLIENT_EXIT_DETACHED, @@ -56,6 +57,7 @@ void client_send_environ(void); void client_write_server(enum msgtype, void *, size_t); void client_update_event(void); void client_signal(int, short, void *); +void client_stdin_callback(int, short, void *); void client_callback(int, short, void *); int client_dispatch_attached(void); int client_dispatch_wait(void *); @@ -227,6 +229,11 @@ client_main(int argc, char **argv, int flags) imsg_init(&client_ibuf, fd); event_set(&client_event, fd, EV_READ, client_callback, shell_cmd); + /* Create stdin handler. */ + setblocking(STDIN_FILENO, 0); + event_set(&client_stdin, STDIN_FILENO, EV_READ|EV_PERSIST, + client_stdin_callback, NULL); + /* Establish signal handlers. */ set_signals(client_signal); @@ -255,6 +262,7 @@ client_main(int argc, char **argv, int flags) /* Set the event and dispatch. */ client_update_event(); + event_add (&client_stdin, NULL); event_dispatch(); /* Print the exit message, if any, and exit. */ @@ -266,6 +274,7 @@ client_main(int argc, char **argv, int flags) if (client_exittype == MSG_DETACHKILL && ppid > 1) kill(ppid, SIGHUP); } + setblocking(STDIN_FILENO, 1); return (client_exitval); } @@ -287,20 +296,11 @@ client_send_identify(int flags) strlcpy(data.term, term, sizeof data.term) >= sizeof data.term) *data.term = '\0'; - if ((fd = dup(STDOUT_FILENO)) == -1) - fatal("dup failed"); - imsg_compose(&client_ibuf, - MSG_STDOUT, PROTOCOL_VERSION, -1, fd, NULL, 0); - - if ((fd = dup(STDERR_FILENO)) == -1) - fatal("dup failed"); - imsg_compose(&client_ibuf, - MSG_STDERR, PROTOCOL_VERSION, -1, fd, NULL, 0); - if ((fd = dup(STDIN_FILENO)) == -1) fatal("dup failed"); imsg_compose(&client_ibuf, MSG_IDENTIFY, PROTOCOL_VERSION, -1, fd, &data, sizeof data); + client_update_event(); } /* Forward entire environment to server. */ @@ -322,6 +322,7 @@ void client_write_server(enum msgtype type, void *buf, size_t len) { imsg_compose(&client_ibuf, type, PROTOCOL_VERSION, -1, -1, buf, len); + client_update_event(); } /* Update client event based on whether it needs to read or read and write. */ @@ -421,6 +422,23 @@ lost_server: event_loopexit(NULL); } +/* Callback for client stdin read events. */ +/* ARGSUSED */ +void +client_stdin_callback(unused int fd, unused short events, unused void *data1) +{ + struct msg_stdin_data data; + + data.size = read(STDIN_FILENO, data.data, sizeof data.data); + if (data.size < 0 && (errno == EINTR || errno == EAGAIN)) + return; + + client_write_server(MSG_STDIN, &data, sizeof data); + if (data.size <= 0) + event_del(&client_stdin); + client_update_event(); +} + /* Dispatch imsgs when in wait state (before MSG_READY). */ int client_dispatch_wait(void *data) @@ -429,11 +447,10 @@ client_dispatch_wait(void *data) ssize_t n, datalen; struct msg_shell_data shelldata; struct msg_exit_data exitdata; + struct msg_stdout_data stdoutdata; + struct msg_stderr_data stderrdata; const char *shellcmd = data; - if ((n = imsg_read(&client_ibuf)) == -1 || n == 0) - fatalx("imsg_read failed"); - for (;;) { if ((n = imsg_get(&client_ibuf, &imsg)) == -1) fatalx("imsg_get failed"); @@ -441,6 +458,7 @@ client_dispatch_wait(void *data) return (0); datalen = imsg.hdr.len - IMSG_HEADER_SIZE; + log_debug("got %d from server", imsg.hdr.type); switch (imsg.hdr.type) { case MSG_EXIT: case MSG_SHUTDOWN: @@ -457,14 +475,30 @@ client_dispatch_wait(void *data) if (datalen != 0) fatalx("bad MSG_READY size"); + event_del(&client_stdin); client_attached = 1; break; + case MSG_STDOUT: + if (datalen != sizeof stdoutdata) + fatalx("bad MSG_STDOUT"); + memcpy(&stdoutdata, imsg.data, sizeof stdoutdata); + + fwrite(stdoutdata.data, stdoutdata.size, 1, stdout); + break; + case MSG_STDERR: + if (datalen != sizeof stderrdata) + fatalx("bad MSG_STDERR"); + memcpy(&stderrdata, imsg.data, sizeof stderrdata); + + fwrite(stderrdata.data, stderrdata.size, 1, stderr); + break; case MSG_VERSION: if (datalen != 0) fatalx("bad MSG_VERSION size"); - log_warnx("protocol version mismatch (client %u, " - "server %u)", PROTOCOL_VERSION, imsg.hdr.peerid); + fprintf(stderr, "protocol version mismatch " + "(client %u, server %u)\n", PROTOCOL_VERSION, + imsg.hdr.peerid); client_exitval = 1; imsg_free(&imsg); diff --git a/cmd-load-buffer.c b/cmd-load-buffer.c index 09fddbf7..1e6b6024 100644 --- a/cmd-load-buffer.c +++ b/cmd-load-buffer.c @@ -31,7 +31,7 @@ */ int cmd_load_buffer_exec(struct cmd *, struct cmd_ctx *); -void cmd_load_buffer_callback(struct client *, void *); +void cmd_load_buffer_callback(struct client *, int, void *); const struct cmd_entry cmd_load_buffer_entry = { "load-buffer", "loadb", @@ -54,8 +54,7 @@ cmd_load_buffer_exec(struct cmd *self, struct cmd_ctx *ctx) char *pdata, *new_pdata, *cause; size_t psize; u_int limit; - int ch, buffer; - int *buffer_ptr; + int ch, error, buffer, *buffer_ptr; if (!args_has(args, 'b')) buffer = -1; @@ -70,27 +69,16 @@ cmd_load_buffer_exec(struct cmd *self, struct cmd_ctx *ctx) path = args->argv[0]; if (strcmp(path, "-") == 0) { - if (c == NULL) { - ctx->error(ctx, "%s: can't read from stdin", path); - return (-1); - } - if (c->flags & CLIENT_TERMINAL) { - ctx->error(ctx, "%s: stdin is a tty", path); - return (-1); - } - if (c->stdin_fd == -1) { - ctx->error(ctx, "%s: can't read from stdin", path); - return (-1); - } - buffer_ptr = xmalloc(sizeof *buffer_ptr); *buffer_ptr = buffer; - c->stdin_data = buffer_ptr; - c->stdin_callback = cmd_load_buffer_callback; - - c->references++; - bufferevent_enable(c->stdin_event, EV_READ); + error = server_set_stdin_callback (c, cmd_load_buffer_callback, + buffer_ptr, &cause); + if (error != 0) { + ctx->error(ctx, "%s: %s", path, cause); + xfree(cause); + return (-1); + } return (1); } @@ -154,35 +142,36 @@ error: } void -cmd_load_buffer_callback(struct client *c, void *data) +cmd_load_buffer_callback(struct client *c, int closed, void *data) { int *buffer = data; char *pdata; size_t psize; u_int limit; - /* - * Event callback has already checked client is not dead and reduced - * its reference count. But tell it to exit. - */ + if (!closed) + return; + c->stdin_callback = NULL; + + c->references--; c->flags |= CLIENT_EXIT; - psize = EVBUFFER_LENGTH(c->stdin_event->input); + psize = EVBUFFER_LENGTH(c->stdin_data); if (psize == 0 || (pdata = malloc(psize + 1)) == NULL) { xfree(data); return; } - bufferevent_read(c->stdin_event, pdata, psize); + memcpy(pdata, EVBUFFER_DATA(c->stdin_data), psize); pdata[psize] = '\0'; + evbuffer_drain(c->stdin_data, psize); limit = options_get_number(&global_options, "buffer-limit"); if (*buffer == -1) paste_add(&global_buffers, pdata, psize, limit); else if (paste_replace(&global_buffers, *buffer, pdata, psize) != 0) { /* No context so can't use server_client_msg_error. */ - evbuffer_add_printf( - c->stderr_event->output, "no buffer %d\n", *buffer); - bufferevent_enable(c->stderr_event, EV_WRITE); + evbuffer_add_printf(c->stderr_data, "no buffer %d\n", *buffer); + server_push_stderr(c); } xfree(data); diff --git a/cmd-save-buffer.c b/cmd-save-buffer.c index 93ec4b0b..80f9d572 100644 --- a/cmd-save-buffer.c +++ b/cmd-save-buffer.c @@ -79,7 +79,8 @@ cmd_save_buffer_exec(struct cmd *self, struct cmd_ctx *ctx) ctx->error(ctx, "%s: can't write to stdout", path); return (-1); } - bufferevent_write(c->stdout_event, pb->data, pb->size); + evbuffer_add(c->stdout_data, pb->data, pb->size); + server_push_stdout(c); } else { if (c != NULL) wd = c->cwd; diff --git a/server-client.c b/server-client.c index 2cbb5bf3..25f17f06 100644 --- a/server-client.c +++ b/server-client.c @@ -35,9 +35,6 @@ void server_client_check_exit(struct client *); void server_client_check_redraw(struct client *); void server_client_set_title(struct client *); void server_client_reset_state(struct client *); -void server_client_in_callback(struct bufferevent *, short, void *); -void server_client_out_callback(struct bufferevent *, short, void *); -void server_client_err_callback(struct bufferevent *, short, void *); int server_client_msg_dispatch(struct client *); void server_client_msg_command(struct client *, struct msg_command_data *); @@ -67,9 +64,9 @@ server_client_create(int fd) fatal("gettimeofday failed"); memcpy(&c->activity_time, &c->creation_time, sizeof c->activity_time); - c->stdin_event = NULL; - c->stdout_event = NULL; - c->stderr_event = NULL; + c->stdin_data = evbuffer_new (); + c->stdout_data = evbuffer_new (); + c->stderr_data = evbuffer_new (); c->tty.fd = -1; c->title = NULL; @@ -144,24 +141,9 @@ server_client_lost(struct client *c) if (c->flags & CLIENT_TERMINAL) tty_free(&c->tty); - if (c->stdin_event != NULL) - bufferevent_free(c->stdin_event); - if (c->stdin_fd != -1) { - setblocking(c->stdin_fd, 1); - close(c->stdin_fd); - } - if (c->stdout_event != NULL) - bufferevent_free(c->stdout_event); - if (c->stdout_fd != -1) { - setblocking(c->stdout_fd, 1); - close(c->stdout_fd); - } - if (c->stderr_event != NULL) - bufferevent_free(c->stderr_event); - if (c->stderr_fd != -1) { - setblocking(c->stderr_fd, 1); - close(c->stderr_fd); - } + evbuffer_free (c->stdin_data); + evbuffer_free (c->stdout_data); + evbuffer_free (c->stderr_data); status_free_jobs(&c->status_new); status_free_jobs(&c->status_old); @@ -240,6 +222,9 @@ server_client_callback(int fd, short events, void *data) goto client_lost; } + server_push_stdout(c); + server_push_stderr(c); + server_update_event(c); return; @@ -603,11 +588,11 @@ server_client_check_exit(struct client *c) if (!(c->flags & CLIENT_EXIT)) return; - if (c->stdout_fd != -1 && c->stdout_event != NULL && - EVBUFFER_LENGTH(c->stdout_event->output) != 0) + if (EVBUFFER_LENGTH(c->stdin_data) != 0) return; - if (c->stderr_fd != -1 && c->stderr_event != NULL && - EVBUFFER_LENGTH(c->stderr_event->output) != 0) + if (EVBUFFER_LENGTH(c->stdout_data) != 0) + return; + if (EVBUFFER_LENGTH(c->stderr_data) != 0) return; exitdata.retcode = c->retcode; @@ -686,55 +671,6 @@ server_client_set_title(struct client *c) xfree(title); } -/* - * Error callback for client stdin. Caller must increase reference count when - * enabling event! - */ -void -server_client_in_callback( - unused struct bufferevent *bufev, unused short what, void *data) -{ - struct client *c = data; - - c->references--; - if (c->flags & CLIENT_DEAD) - return; - - bufferevent_disable(c->stdin_event, EV_READ|EV_WRITE); - setblocking(c->stdin_fd, 1); - close(c->stdin_fd); - c->stdin_fd = -1; - - if (c->stdin_callback != NULL) - c->stdin_callback(c, c->stdin_data); -} - -/* Error callback for client stdout. */ -void -server_client_out_callback( - unused struct bufferevent *bufev, unused short what, unused void *data) -{ - struct client *c = data; - - bufferevent_disable(c->stdout_event, EV_READ|EV_WRITE); - setblocking(c->stdout_fd, 1); - close(c->stdout_fd); - c->stdout_fd = -1; -} - -/* Error callback for client stderr. */ -void -server_client_err_callback( - unused struct bufferevent *bufev, unused short what, unused void *data) -{ - struct client *c = data; - - bufferevent_disable(c->stderr_event, EV_READ|EV_WRITE); - setblocking(c->stderr_fd, 1); - close(c->stderr_fd); - c->stderr_fd = -1; -} - /* Dispatch message from client. */ int server_client_msg_dispatch(struct client *c) @@ -743,6 +679,7 @@ server_client_msg_dispatch(struct client *c) struct msg_command_data commanddata; struct msg_identify_data identifydata; struct msg_environ_data environdata; + struct msg_stdin_data stdindata; ssize_t n, datalen; if ((n = imsg_read(&c->ibuf)) == -1 || n == 0) @@ -778,42 +715,23 @@ server_client_msg_dispatch(struct client *c) fatalx("MSG_IDENTIFY missing fd"); memcpy(&identifydata, imsg.data, sizeof identifydata); - c->stdin_fd = imsg.fd; - c->stdin_event = bufferevent_new(c->stdin_fd, - NULL, NULL, server_client_in_callback, c); - if (c->stdin_event == NULL) - fatalx("failed to create stdin event"); - setblocking(c->stdin_fd, 0); - server_client_msg_identify(c, &identifydata, imsg.fd); break; - case MSG_STDOUT: - if (datalen != 0) - fatalx("bad MSG_STDOUT size"); - if (imsg.fd == -1) - fatalx("MSG_STDOUT missing fd"); - - c->stdout_fd = imsg.fd; - c->stdout_event = bufferevent_new(c->stdout_fd, - NULL, NULL, server_client_out_callback, c); - if (c->stdout_event == NULL) - fatalx("failed to create stdout event"); - setblocking(c->stdout_fd, 0); - - break; - case MSG_STDERR: - if (datalen != 0) - fatalx("bad MSG_STDERR size"); - if (imsg.fd == -1) - fatalx("MSG_STDERR missing fd"); - - c->stderr_fd = imsg.fd; - c->stderr_event = bufferevent_new(c->stderr_fd, - NULL, NULL, server_client_err_callback, c); - if (c->stderr_event == NULL) - fatalx("failed to create stderr event"); - setblocking(c->stderr_fd, 0); + case MSG_STDIN: + if (datalen != sizeof stdindata) + fatalx("bad MSG_STDIN size"); + memcpy(&stdindata, imsg.data, sizeof stdindata); + if (c->stdin_callback == NULL) + break; + if (stdindata.size <= 0) + c->stdin_closed = 1; + else { + evbuffer_add(c->stdin_data, stdindata.data, + stdindata.size); + } + c->stdin_callback(c, c->stdin_closed, + c->stdin_callback_data); break; case MSG_RESIZE: if (datalen != 0) @@ -880,10 +798,11 @@ server_client_msg_error(struct cmd_ctx *ctx, const char *fmt, ...) va_list ap; va_start(ap, fmt); - evbuffer_add_vprintf(ctx->cmdclient->stderr_event->output, fmt, ap); + evbuffer_add_vprintf(ctx->cmdclient->stderr_data, fmt, ap); va_end(ap); - bufferevent_write(ctx->cmdclient->stderr_event, "\n", 1); + evbuffer_add(ctx->cmdclient->stderr_data, "\n", 1); + server_push_stderr(ctx->cmdclient); ctx->cmdclient->retcode = 1; } @@ -894,10 +813,11 @@ server_client_msg_print(struct cmd_ctx *ctx, const char *fmt, ...) va_list ap; va_start(ap, fmt); - evbuffer_add_vprintf(ctx->cmdclient->stdout_event->output, fmt, ap); + evbuffer_add_vprintf(ctx->cmdclient->stdout_data, fmt, ap); va_end(ap); - bufferevent_write(ctx->cmdclient->stdout_event, "\n", 1); + evbuffer_add(ctx->cmdclient->stdout_data, "\n", 1); + server_push_stdout(ctx->cmdclient); } /* Callback to send print message to client, if not quiet. */ @@ -910,10 +830,11 @@ server_client_msg_info(struct cmd_ctx *ctx, const char *fmt, ...) return; va_start(ap, fmt); - evbuffer_add_vprintf(ctx->cmdclient->stdout_event->output, fmt, ap); + evbuffer_add_vprintf(ctx->cmdclient->stdout_data, fmt, ap); va_end(ap); - bufferevent_write(ctx->cmdclient->stdout_event, "\n", 1); + evbuffer_add(ctx->cmdclient->stdout_data, "\n", 1); + server_push_stdout(ctx->cmdclient); } /* Handle command message. */ @@ -970,8 +891,6 @@ void server_client_msg_identify( struct client *c, struct msg_identify_data *data, int fd) { - int tty_fd; - c->cwd = NULL; data->cwd[(sizeof data->cwd) - 1] = '\0'; if (*data->cwd != '\0') @@ -979,10 +898,8 @@ server_client_msg_identify( if (!isatty(fd)) return; - if ((tty_fd = dup(fd)) == -1) - fatal("dup failed"); data->term[(sizeof data->term) - 1] = '\0'; - tty_init(&c->tty, tty_fd, data->term); + tty_init(&c->tty, fd, data->term); if (data->flags & IDENTIFY_UTF8) c->tty.flags |= TTY_UTF8; if (data->flags & IDENTIFY_256COLOURS) diff --git a/server-fn.c b/server-fn.c index 600955af..791aab1c 100644 --- a/server-fn.c +++ b/server-fn.c @@ -46,17 +46,21 @@ server_fill_environ(struct session *s, struct environ *env) environ_set(env, "TMUX", var); } -void +int server_write_client( struct client *c, enum msgtype type, const void *buf, size_t len) { struct imsgbuf *ibuf = &c->ibuf; + int error; if (c->flags & CLIENT_BAD) - return; + return (-1); log_debug("writing %d to client %d", type, c->ibuf.fd); - imsg_compose(ibuf, type, PROTOCOL_VERSION, -1, -1, (void *) buf, len); - server_update_event(c); + error = imsg_compose(ibuf, type, PROTOCOL_VERSION, -1, -1, + (void *) buf, len); + if (error == 1) + server_update_event(c); + return (error == 1 ? 0 : -1); } void @@ -502,3 +506,71 @@ server_update_event(struct client *c) event_set(&c->event, c->ibuf.fd, events, server_client_callback, c); event_add(&c->event, NULL); } + +/* Push stdout to client if possible. */ +void +server_push_stdout(struct client *c) +{ + struct msg_stdout_data data; + size_t size; + + size = EVBUFFER_LENGTH(c->stdout_data); + if (size == 0) + return; + if (size > sizeof data.data) + size = sizeof data.data; + + memcpy(data.data, EVBUFFER_DATA(c->stdout_data), size); + data.size = size; + + if (server_write_client(c, MSG_STDOUT, &data, sizeof data) == 0) + evbuffer_drain(c->stdout_data, size); +} + +/* Push stderr to client if possible. */ +void +server_push_stderr(struct client *c) +{ + struct msg_stderr_data data; + size_t size; + + size = EVBUFFER_LENGTH(c->stderr_data); + if (size == 0) + return; + if (size > sizeof data.data) + size = sizeof data.data; + + memcpy(data.data, EVBUFFER_DATA(c->stderr_data), size); + data.size = size; + + if (server_write_client(c, MSG_STDERR, &data, sizeof data) == 0) + evbuffer_drain(c->stderr_data, size); +} + +/* Set stdin callback. */ +int +server_set_stdin_callback(struct client *c, void (*cb)(struct client *, int, + void *), void *cb_data, char **cause) +{ + if (c == NULL) { + *cause = xstrdup("no client with stdin"); + return (-1); + } + if (c->flags & CLIENT_TERMINAL) { + *cause = xstrdup("stdin is a tty"); + return (-1); + } + if (c->stdin_callback != NULL) { + *cause = xstrdup("stdin in use"); + return (-1); + } + + c->stdin_callback_data = cb_data; + c->stdin_callback = cb; + + c->references++; + + if (c->stdin_closed) + c->stdin_callback (c, 1, c->stdin_callback_data); + return (0); +} diff --git a/tmux.h b/tmux.h index 8c66bca5..96efb661 100644 --- a/tmux.h +++ b/tmux.h @@ -19,7 +19,7 @@ #ifndef TMUX_H #define TMUX_H -#define PROTOCOL_VERSION 6 +#define PROTOCOL_VERSION 7 #include #include @@ -369,7 +369,7 @@ enum msgtype { MSG_EXITED, MSG_EXITING, MSG_IDENTIFY, - MSG_PRINT, + MSG_STDIN, MSG_READY, MSG_RESIZE, MSG_SHUTDOWN, @@ -425,6 +425,21 @@ struct msg_exit_data { int retcode; }; +struct msg_stdin_data { + ssize_t size; + char data[BUFSIZ]; +}; + +struct msg_stdout_data { + ssize_t size; + char data[BUFSIZ]; +}; + +struct msg_stderr_data { + ssize_t size; + char data[BUFSIZ]; +}; + /* Mode key commands. */ enum mode_key_cmd { MODEKEY_NONE, @@ -1161,16 +1176,12 @@ struct client { struct tty tty; - int stdin_fd; - void *stdin_data; - void (*stdin_callback)(struct client *, void *); - struct bufferevent *stdin_event; - - int stdout_fd; - struct bufferevent *stdout_event; - - int stderr_fd; - struct bufferevent *stderr_event; + void (*stdin_callback)(struct client *, int, void *); + void *stdin_callback_data; + struct evbuffer *stdin_data; + int stdin_closed; + struct evbuffer *stdout_data; + struct evbuffer *stderr_data; struct event repeat_timer; @@ -1734,7 +1745,7 @@ void server_window_loop(void); /* server-fn.c */ void server_fill_environ(struct session *, struct environ *); -void server_write_client( +int server_write_client( struct client *, enum msgtype, const void *, size_t); void server_write_session( struct session *, enum msgtype, const void *, size_t); @@ -1762,6 +1773,10 @@ void server_check_unattached (void); void server_set_identify(struct client *); void server_clear_identify(struct client *); void server_update_event(struct client *); +void server_push_stdout(struct client *); +void server_push_stderr(struct client *); +int server_set_stdin_callback(struct client *, void (*)(struct client *, + int, void *), void *, char **); /* status.c */ int status_out_cmp(struct status_out *, struct status_out *);