From c284ebe0ade7cc85ad6c3fe5ce7ed5108119222d Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 12 Dec 2019 11:39:56 +0000 Subject: [PATCH] Rewrite the code for reading and writing files. Now, if the client is not attached, the server process asks it to open the file, similar to how works for stdin, stdout, stderr. This makes special files like /dev/fd/X work (used by some shells). stdin, stdout and stderr and control mode are now just special cases of the same mechanism. This will also make it easier to use for other commands that read files such as source-file. --- Makefile | 1 + client.c | 332 ++++++++++++++++++++++++++++++-------- cmd-capture-pane.c | 15 +- cmd-load-buffer.c | 160 +++++-------------- cmd-queue.c | 34 ++-- cmd-save-buffer.c | 98 +++--------- control-notify.c | 2 +- control.c | 36 ++--- file.c | 390 +++++++++++++++++++++++++++++++++++++++++++++ server-client.c | 223 +++++++++++--------------- server-fn.c | 30 ---- tmux.h | 108 ++++++++++--- window.c | 27 ++-- 13 files changed, 955 insertions(+), 501 deletions(-) create mode 100644 file.c diff --git a/Makefile b/Makefile index 4efeb3f7..6e29c4fe 100644 --- a/Makefile +++ b/Makefile @@ -73,6 +73,7 @@ SRCS= alerts.c \ control-notify.c \ control.c \ environ.c \ + file.c \ format.c \ format-draw.c \ grid-view.c \ diff --git a/client.c b/client.c index a593d9ac..db3ad07c 100644 --- a/client.c +++ b/client.c @@ -35,7 +35,6 @@ static struct tmuxproc *client_proc; static struct tmuxpeer *client_peer; static int client_flags; -static struct event client_stdin; static enum { CLIENT_EXIT_NONE, CLIENT_EXIT_DETACHED, @@ -52,13 +51,12 @@ static const char *client_exitsession; static const char *client_execshell; static const char *client_execcmd; static int client_attached; +static struct client_files client_files = RB_INITIALIZER(&client_files); static __dead void client_exec(const char *,const char *); static int client_get_lock(char *); static int client_connect(struct event_base *, const char *, int); static void client_send_identify(const char *, const char *); -static void client_stdin_callback(int, short, void *); -static void client_write(int, const char *, size_t); static void client_signal(int); static void client_dispatch(struct imsg *, void *); static void client_dispatch_attached(struct imsg *); @@ -217,7 +215,7 @@ client_main(struct event_base *base, int argc, char **argv, int flags) { struct cmd_parse_result *pr; struct cmd *cmd; - struct msg_command_data *data; + struct msg_command *data; int cmdflags, fd, i; const char *ttynam, *cwd; pid_t ppid; @@ -291,7 +289,9 @@ client_main(struct event_base *base, int argc, char **argv, int flags) * * "sendfd" is dropped later in client_dispatch_wait(). */ - if (pledge("stdio unix sendfd proc exec tty", NULL) != 0) + if (pledge( + "stdio rpath wpath cpath unix sendfd proc exec tty", + NULL) != 0) fatal("pledge failed"); /* Free stuff that is not used in the client. */ @@ -302,10 +302,7 @@ client_main(struct event_base *base, int argc, char **argv, int flags) options_free(global_w_options); environ_free(global_environ); - /* Create stdin handler. */ - setblocking(STDIN_FILENO, 0); - event_set(&client_stdin, STDIN_FILENO, EV_READ|EV_PERSIST, - client_stdin_callback, NULL); + /* Set up control mode. */ if (client_flags & CLIENT_CONTROLCONTROL) { if (tcgetattr(STDIN_FILENO, &saved_tio) != 0) { fprintf(stderr, "tcgetattr failed: %s\n", @@ -426,41 +423,236 @@ client_send_identify(const char *ttynam, const char *cwd) proc_send(client_peer, MSG_IDENTIFY_DONE, -1, NULL, 0); } -/* Callback for client stdin read events. */ +/* File write error callback. */ static void -client_stdin_callback(__unused int fd, __unused short events, - __unused void *arg) +client_write_error_callback(__unused struct bufferevent *bev, + __unused short what, void *arg) { - struct msg_stdin_data data; + struct client_file *cf = arg; - data.size = read(STDIN_FILENO, data.data, sizeof data.data); - if (data.size == -1 && (errno == EINTR || errno == EAGAIN)) - return; + log_debug("write error file %d", cf->stream); - proc_send(client_peer, MSG_STDIN, -1, &data, sizeof data); - if (data.size <= 0) - event_del(&client_stdin); + bufferevent_free(cf->event); + cf->event = NULL; + + close(cf->fd); + cf->fd = -1; } -/* Force write to file descriptor. */ +/* File write callback. */ static void -client_write(int fd, const char *data, size_t size) +client_write_callback(__unused struct bufferevent *bev, void *arg) { - ssize_t used; + struct client_file *cf = arg; - log_debug("%s: %.*s", __func__, (int)size, data); - while (size != 0) { - used = write(fd, data, size); - if (used == -1) { - if (errno == EINTR || errno == EAGAIN) - continue; - break; - } - data += used; - size -= used; + if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0) { + bufferevent_free(cf->event); + close(cf->fd); + RB_REMOVE(client_files, &client_files, cf); + file_free(cf); } } +/* Open write file. */ +static void +client_write_open(void *data, size_t datalen) +{ + struct msg_write_open *msg = data; + struct msg_write_ready reply; + struct client_file find, *cf; + const int flags = O_NONBLOCK|O_WRONLY|O_CREAT; + int error = 0; + + if (datalen != sizeof *msg) + fatalx("bad MSG_WRITE_OPEN size"); + log_debug("open write file %d %s", msg->stream, msg->path); + + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL) { + cf = file_create(NULL, msg->stream, NULL, NULL); + RB_INSERT(client_files, &client_files, cf); + } else { + error = EBADF; + goto reply; + } + if (cf->closed) { + error = EBADF; + goto reply; + } + + cf->fd = -1; + if (msg->fd == -1) + cf->fd = open(msg->path, msg->flags|flags, 0644); + else { + if (msg->fd != STDOUT_FILENO && msg->fd != STDERR_FILENO) + errno = EBADF; + else { + cf->fd = dup(msg->fd); + if (client_flags & CLIENT_CONTROL) + close(msg->fd); /* can only be used once */ + } + } + if (cf->fd == -1) { + error = errno; + goto reply; + } + + cf->event = bufferevent_new(cf->fd, NULL, client_write_callback, + client_write_error_callback, cf); + bufferevent_enable(cf->event, EV_WRITE); + goto reply; + +reply: + reply.stream = msg->stream; + reply.error = error; + proc_send(client_peer, MSG_WRITE_READY, -1, &reply, sizeof reply); +} + +/* Write to client file. */ +static void +client_write_data(void *data, size_t datalen) +{ + struct msg_write_data *msg = data; + struct client_file find, *cf; + + if (datalen != sizeof *msg) + fatalx("bad MSG_WRITE size"); + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL) + fatalx("unknown stream number"); + log_debug("write %zu to file %d", msg->size, cf->stream); + + if (cf->event != NULL) + bufferevent_write(cf->event, msg->data, msg->size); +} + +/* Close client file. */ +static void +client_write_close(void *data, size_t datalen) +{ + struct msg_write_close *msg = data; + struct client_file find, *cf; + + if (datalen != sizeof *msg) + fatalx("bad MSG_WRITE_CLOSE size"); + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL) + fatalx("unknown stream number"); + log_debug("close file %d", cf->stream); + + if (cf->event == NULL || EVBUFFER_LENGTH(cf->event->output) == 0) { + if (cf->event != NULL) + bufferevent_free(cf->event); + if (cf->fd != -1) + close(cf->fd); + RB_REMOVE(client_files, &client_files, cf); + file_free(cf); + } +} + +/* File read callback. */ +static void +client_read_callback(__unused struct bufferevent *bev, void *arg) +{ + struct client_file *cf = arg; + void *bdata; + size_t bsize; + struct msg_read_data msg; + + for (;;) { + bdata = EVBUFFER_DATA(cf->event->input); + bsize = EVBUFFER_LENGTH(cf->event->input); + + if (bsize == 0) + break; + if (bsize > sizeof msg.data) + bsize = sizeof msg.data; + log_debug("read %zu from file %d", bsize, cf->stream); + + memcpy(msg.data, bdata, bsize); + msg.size = bsize; + + msg.stream = cf->stream; + proc_send(client_peer, MSG_READ, -1, &msg, sizeof msg); + + evbuffer_drain(cf->event->input, bsize); + } +} + +/* File read error callback. */ +static void +client_read_error_callback(__unused struct bufferevent *bev, + __unused short what, void *arg) +{ + struct client_file *cf = arg; + struct msg_read_done msg; + + log_debug("read error file %d", cf->stream); + + msg.stream = cf->stream; + msg.error = 0; + proc_send(client_peer, MSG_READ_DONE, -1, &msg, sizeof msg); + + bufferevent_free(cf->event); + close(cf->fd); + RB_REMOVE(client_files, &client_files, cf); + file_free(cf); +} + +/* Open read file. */ +static void +client_read_open(void *data, size_t datalen) +{ + struct msg_read_open *msg = data; + struct msg_read_done reply; + struct client_file find, *cf; + const int flags = O_NONBLOCK|O_RDONLY; + int error = 0; + + if (datalen != sizeof *msg) + fatalx("bad MSG_READ_OPEN size"); + log_debug("open read file %d %s", msg->stream, msg->path); + + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL) { + cf = file_create(NULL, msg->stream, NULL, NULL); + RB_INSERT(client_files, &client_files, cf); + } else { + error = EBADF; + goto reply; + } + if (cf->closed) { + error = EBADF; + goto reply; + } + + cf->fd = -1; + if (msg->fd == -1) + cf->fd = open(msg->path, flags); + else { + if (msg->fd != STDIN_FILENO) + errno = EBADF; + else { + cf->fd = dup(msg->fd); + close(msg->fd); /* can only be used once */ + } + } + if (cf->fd == -1) { + error = errno; + goto reply; + } + + cf->event = bufferevent_new(cf->fd, client_read_callback, NULL, + client_read_error_callback, cf); + bufferevent_enable(cf->event, EV_READ); + return; + +reply: + reply.stream = msg->stream; + reply.error = error; + proc_send(client_peer, MSG_READ_DONE, -1, &reply, sizeof reply); +} + /* Run command in shell; used for -c. */ static __dead void client_exec(const char *shell, const char *shellcmd) @@ -532,6 +724,29 @@ client_signal(int sig) } } +/* Exit if all streams flushed. */ +static void +client_exit(__unused int fd, __unused short events, __unused void *arg) +{ + struct client_file *cf; + size_t left; + int waiting = 0; + + RB_FOREACH (cf, client_files, &client_files) { + if (cf->event == NULL) + continue; + left = EVBUFFER_LENGTH(cf->event->output); + if (left != 0) { + waiting++; + log_debug("file %u %zu bytes left", cf->stream, left); + } + } + if (waiting == 0) + proc_exit(client_proc); + else + event_once(-1, EV_TIMEOUT, client_exit, NULL, NULL); +} + /* Callback for client read events. */ static void client_dispatch(struct imsg *imsg, __unused void *arg) @@ -553,12 +768,10 @@ client_dispatch(struct imsg *imsg, __unused void *arg) static void client_dispatch_wait(struct imsg *imsg) { - char *data; - ssize_t datalen; - struct msg_stdout_data stdoutdata; - struct msg_stderr_data stderrdata; - int retval; - static int pledge_applied; + char *data; + ssize_t datalen; + int retval; + static int pledge_applied; /* * "sendfd" is no longer required once all of the identify messages @@ -567,10 +780,12 @@ client_dispatch_wait(struct imsg *imsg) * get the first message from the server. */ if (!pledge_applied) { - if (pledge("stdio unix proc exec tty", NULL) != 0) + if (pledge( + "stdio rpath wpath cpath unix proc exec tty", + NULL) != 0) fatal("pledge failed"); pledge_applied = 1; - }; + } data = imsg->data; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; @@ -584,38 +799,15 @@ client_dispatch_wait(struct imsg *imsg) memcpy(&retval, data, sizeof retval); client_exitval = retval; } - proc_exit(client_proc); + event_once(-1, EV_TIMEOUT, client_exit, NULL, NULL); break; case MSG_READY: if (datalen != 0) fatalx("bad MSG_READY size"); - event_del(&client_stdin); client_attached = 1; proc_send(client_peer, MSG_RESIZE, -1, NULL, 0); break; - case MSG_STDIN: - if (datalen != 0) - fatalx("bad MSG_STDIN size"); - - event_add(&client_stdin, NULL); - break; - case MSG_STDOUT: - if (datalen != sizeof stdoutdata) - fatalx("bad MSG_STDOUT size"); - memcpy(&stdoutdata, data, sizeof stdoutdata); - - client_write(STDOUT_FILENO, stdoutdata.data, - stdoutdata.size); - break; - case MSG_STDERR: - if (datalen != sizeof stderrdata) - fatalx("bad MSG_STDERR size"); - memcpy(&stderrdata, data, sizeof stderrdata); - - client_write(STDERR_FILENO, stderrdata.data, - stderrdata.size); - break; case MSG_VERSION: if (datalen != 0) fatalx("bad MSG_VERSION size"); @@ -639,6 +831,18 @@ client_dispatch_wait(struct imsg *imsg) case MSG_EXITED: proc_exit(client_proc); break; + case MSG_READ_OPEN: + client_read_open(data, datalen); + break; + case MSG_WRITE_OPEN: + client_write_open(data, datalen); + break; + case MSG_WRITE: + client_write_data(data, datalen); + break; + case MSG_WRITE_CLOSE: + client_write_close(data, datalen); + break; } } diff --git a/cmd-capture-pane.c b/cmd-capture-pane.c index fc6c26e4..18be3f77 100644 --- a/cmd-capture-pane.c +++ b/cmd-capture-pane.c @@ -193,7 +193,7 @@ static enum cmd_retval cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = self->args; - struct client *c; + struct client *c = item->client; struct window_pane *wp = item->target.wp; char *buf, *cause; const char *bufname; @@ -214,18 +214,15 @@ cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); if (args_has(args, 'p')) { - c = item->client; - if (c == NULL || - (c->session != NULL && !(c->flags & CLIENT_CONTROL))) { - cmdq_error(item, "can't write to stdout"); + if (!file_can_print(c)) { + cmdq_error(item, "can't write output to client"); free(buf); return (CMD_RETURN_ERROR); } - evbuffer_add(c->stdout_data, buf, len); - free(buf); + file_print_buffer(c, buf, len); if (args_has(args, 'P') && len > 0) - evbuffer_add(c->stdout_data, "\n", 1); - server_client_push_stdout(c); + file_print(c, "\n"); + free(buf); } else { bufname = NULL; if (args_has(args, 'b')) diff --git a/cmd-load-buffer.c b/cmd-load-buffer.c index cdf44bf7..5e930126 100644 --- a/cmd-load-buffer.c +++ b/cmd-load-buffer.c @@ -33,8 +33,6 @@ static enum cmd_retval cmd_load_buffer_exec(struct cmd *, struct cmdq_item *); -static void cmd_load_buffer_callback(struct client *, int, void *); - const struct cmd_entry cmd_load_buffer_entry = { .name = "load-buffer", .alias = "loadb", @@ -48,9 +46,40 @@ const struct cmd_entry cmd_load_buffer_entry = { struct cmd_load_buffer_data { struct cmdq_item *item; - char *bufname; + char *name; }; +static void +cmd_load_buffer_done(__unused struct client *c, const char *path, int error, + int closed, struct evbuffer *buffer, void *data) +{ + struct cmd_load_buffer_data *cdata = data; + struct cmdq_item *item = cdata->item; + void *bdata = EVBUFFER_DATA(buffer); + size_t bsize = EVBUFFER_LENGTH(buffer); + void *copy; + char *cause; + + if (!closed) + return; + + if (error != 0) + cmdq_error(item, "%s: %s", path, strerror(error)); + else if (bsize != 0) { + copy = xmalloc(bsize); + memcpy(copy, bdata, bsize); + if (paste_set(copy, bsize, cdata->name, &cause) != 0) { + cmdq_error(item, "%s", cause); + free(cause); + free(copy); + } + } + cmdq_continue(item); + + free(cdata->name); + free(cdata); +} + static enum cmd_retval cmd_load_buffer_exec(struct cmd *self, struct cmdq_item *item) { @@ -60,124 +89,19 @@ cmd_load_buffer_exec(struct cmd *self, struct cmdq_item *item) struct session *s = item->target.s; struct winlink *wl = item->target.wl; struct window_pane *wp = item->target.wp; - FILE *f; - const char *bufname; - char *pdata = NULL, *new_pdata, *cause; - char *path, *file; - size_t psize; - int ch, error; + const char *bufname = args_get(args, 'b'); + char *path; - bufname = NULL; - if (args_has(args, 'b')) - bufname = args_get(args, 'b'); + cdata = xmalloc(sizeof *cdata); + cdata->item = item; + if (bufname != NULL) + cdata->name = xstrdup(bufname); + else + cdata->name = NULL; path = format_single(item, args->argv[0], c, s, wl, wp); - if (strcmp(path, "-") == 0) { - free(path); - c = item->client; - - cdata = xcalloc(1, sizeof *cdata); - cdata->item = item; - - if (bufname != NULL) - cdata->bufname = xstrdup(bufname); - - error = server_set_stdin_callback(c, cmd_load_buffer_callback, - cdata, &cause); - if (error != 0) { - cmdq_error(item, "-: %s", cause); - free(cause); - free(cdata); - return (CMD_RETURN_ERROR); - } - return (CMD_RETURN_WAIT); - } - - file = server_client_get_path(item->client, path); + file_read(item->client, path, cmd_load_buffer_done, cdata); free(path); - f = fopen(file, "rb"); - if (f == NULL) { - cmdq_error(item, "%s: %s", file, strerror(errno)); - goto error; - } - - pdata = NULL; - psize = 0; - while ((ch = getc(f)) != EOF) { - /* Do not let the server die due to memory exhaustion. */ - if ((new_pdata = realloc(pdata, psize + 2)) == NULL) { - cmdq_error(item, "realloc error: %s", strerror(errno)); - goto error; - } - pdata = new_pdata; - pdata[psize++] = ch; - } - if (ferror(f)) { - cmdq_error(item, "%s: read error", file); - goto error; - } - if (pdata != NULL) - pdata[psize] = '\0'; - - fclose(f); - free(file); - - if (paste_set(pdata, psize, bufname, &cause) != 0) { - cmdq_error(item, "%s", cause); - free(pdata); - free(cause); - return (CMD_RETURN_ERROR); - } - - return (CMD_RETURN_NORMAL); - -error: - free(pdata); - if (f != NULL) - fclose(f); - free(file); - return (CMD_RETURN_ERROR); -} - -static void -cmd_load_buffer_callback(struct client *c, int closed, void *data) -{ - struct cmd_load_buffer_data *cdata = data; - char *pdata, *cause, *saved; - size_t psize; - - if (!closed) - return; - c->stdin_callback = NULL; - - server_client_unref(c); - if (c->flags & CLIENT_DEAD) - goto out; - - psize = EVBUFFER_LENGTH(c->stdin_data); - if (psize == 0 || (pdata = malloc(psize + 1)) == NULL) - goto out; - - memcpy(pdata, EVBUFFER_DATA(c->stdin_data), psize); - pdata[psize] = '\0'; - evbuffer_drain(c->stdin_data, psize); - - if (paste_set(pdata, psize, cdata->bufname, &cause) != 0) { - /* No context so can't use server_client_msg_error. */ - if (~c->flags & CLIENT_UTF8) { - saved = cause; - cause = utf8_sanitize(saved); - free(saved); - } - evbuffer_add_printf(c->stderr_data, "%s", cause); - server_client_push_stderr(c); - free(pdata); - free(cause); - } -out: - cmdq_continue(cdata->item); - - free(cdata->bufname); - free(cdata); + return (CMD_RETURN_WAIT); } diff --git a/cmd-queue.c b/cmd-queue.c index fa6999e8..6019ab51 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -475,13 +475,11 @@ void cmdq_guard(struct cmdq_item *item, const char *guard, int flags) { struct client *c = item->client; + long t = item->time; + u_int number = item->number; - if (c == NULL || !(c->flags & CLIENT_CONTROL)) - return; - - evbuffer_add_printf(c->stdout_data, "%%%s %ld %u %d\n", guard, - (long)item->time, item->number, flags); - server_client_push_stdout(c); + if (c != NULL && (c->flags & CLIENT_CONTROL)) + file_print(c, "%%%s %ld %u %d\n", guard, t, number, flags); } /* Show message from command. */ @@ -495,20 +493,20 @@ cmdq_print(struct cmdq_item *item, const char *fmt, ...) char *tmp, *msg; va_start(ap, fmt); + xvasprintf(&msg, fmt, ap); + va_end(ap); + + log_debug("%s: %s", __func__, msg); if (c == NULL) /* nothing */; else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { if (~c->flags & CLIENT_UTF8) { - xvasprintf(&tmp, fmt, ap); + tmp = msg; msg = utf8_sanitize(tmp); free(tmp); - evbuffer_add(c->stdout_data, msg, strlen(msg)); - free(msg); - } else - evbuffer_add_vprintf(c->stdout_data, fmt, ap); - evbuffer_add(c->stdout_data, "\n", 1); - server_client_push_stdout(c); + } + file_print(c, "%s\n", msg); } else { wp = c->session->curw->window->active; wme = TAILQ_FIRST(&wp->modes); @@ -517,7 +515,7 @@ cmdq_print(struct cmdq_item *item, const char *fmt, ...) window_copy_vadd(wp, fmt, ap); } - va_end(ap); + free(msg); } /* Show error from command. */ @@ -528,11 +526,10 @@ cmdq_error(struct cmdq_item *item, const char *fmt, ...) struct cmd *cmd = item->cmd; va_list ap; char *msg; - size_t msglen; char *tmp; va_start(ap, fmt); - msglen = xvasprintf(&msg, fmt, ap); + xvasprintf(&msg, fmt, ap); va_end(ap); log_debug("%s: %s", __func__, msg); @@ -544,11 +541,8 @@ cmdq_error(struct cmdq_item *item, const char *fmt, ...) tmp = msg; msg = utf8_sanitize(tmp); free(tmp); - msglen = strlen(msg); } - evbuffer_add(c->stderr_data, msg, msglen); - evbuffer_add(c->stderr_data, "\n", 1); - server_client_push_stderr(c); + file_error(c, "%s\n", msg); c->retval = 1; } else { *msg = toupper((u_char) *msg); diff --git a/cmd-save-buffer.c b/cmd-save-buffer.c index a8d43f87..6830e5fc 100644 --- a/cmd-save-buffer.c +++ b/cmd-save-buffer.c @@ -56,6 +56,20 @@ const struct cmd_entry cmd_show_buffer_entry = { .exec = cmd_save_buffer_exec }; +static void +cmd_save_buffer_done(__unused struct client *c, const char *path, int error, + __unused int closed, __unused struct evbuffer *buffer, void *data) +{ + struct cmdq_item *item = data; + + if (!closed) + return; + + if (error != 0) + cmdq_error(item, "%s: %s", path, strerror(error)); + cmdq_continue(item); +} + static enum cmd_retval cmd_save_buffer_exec(struct cmd *self, struct cmdq_item *item) { @@ -65,18 +79,17 @@ cmd_save_buffer_exec(struct cmd *self, struct cmdq_item *item) struct winlink *wl = item->target.wl; struct window_pane *wp = item->target.wp; struct paste_buffer *pb; - const char *bufname, *bufdata, *start, *end, *flags; - char *msg, *path, *file; - size_t size, used, msglen, bufsize; - FILE *f; + int flags; + const char *bufname = args_get(args, 'b'), *bufdata; + size_t bufsize; + char *path; - if (!args_has(args, 'b')) { + if (bufname == NULL) { if ((pb = paste_get_top(NULL)) == NULL) { cmdq_error(item, "no buffers"); return (CMD_RETURN_ERROR); } } else { - bufname = args_get(args, 'b'); pb = paste_get_name(bufname); if (pb == NULL) { cmdq_error(item, "no buffer %s", bufname); @@ -89,74 +102,13 @@ cmd_save_buffer_exec(struct cmd *self, struct cmdq_item *item) path = xstrdup("-"); else path = format_single(item, args->argv[0], c, s, wl, wp); - if (strcmp(path, "-") == 0) { - free(path); - c = item->client; - if (c == NULL) { - cmdq_error(item, "can't write to stdout"); - return (CMD_RETURN_ERROR); - } - if (c->session == NULL || (c->flags & CLIENT_CONTROL)) - goto do_stdout; - goto do_print; - } - - flags = "wb"; if (args_has(self->args, 'a')) - flags = "ab"; - - file = server_client_get_path(item->client, path); + flags = O_APPEND; + else + flags = 0; + file_write(item->client, path, flags, bufdata, bufsize, + cmd_save_buffer_done, item); free(path); - f = fopen(file, flags); - if (f == NULL) { - cmdq_error(item, "%s: %s", file, strerror(errno)); - free(file); - return (CMD_RETURN_ERROR); - } - - if (fwrite(bufdata, 1, bufsize, f) != bufsize) { - cmdq_error(item, "%s: write error", file); - fclose(f); - free(file); - return (CMD_RETURN_ERROR); - } - - fclose(f); - free(file); - - return (CMD_RETURN_NORMAL); - -do_stdout: - evbuffer_add(c->stdout_data, bufdata, bufsize); - server_client_push_stdout(c); - return (CMD_RETURN_NORMAL); - -do_print: - if (bufsize > (INT_MAX / 4) - 1) { - cmdq_error(item, "buffer too big"); - return (CMD_RETURN_ERROR); - } - msg = NULL; - - used = 0; - while (used != bufsize) { - start = bufdata + used; - end = memchr(start, '\n', bufsize - used); - if (end != NULL) - size = end - start; - else - size = bufsize - used; - - msglen = size * 4 + 1; - msg = xrealloc(msg, msglen); - - strvisx(msg, start, size, VIS_OCTAL|VIS_TAB); - cmdq_print(item, "%s", msg); - - used += size + (end != NULL); - } - - free(msg); - return (CMD_RETURN_NORMAL); + return (CMD_RETURN_WAIT); } diff --git a/control-notify.c b/control-notify.c index a1e2b7bf..babfcf2d 100644 --- a/control-notify.c +++ b/control-notify.c @@ -54,7 +54,7 @@ control_notify_input(struct client *c, struct window_pane *wp, else evbuffer_add_printf(message, "%c", buf[i]); } - control_write_buffer(c, message); + control_write(c, "%s", EVBUFFER_DATA(message)); evbuffer_free(message); } } diff --git a/control.c b/control.c index c4cf5338..b8a16e73 100644 --- a/control.c +++ b/control.c @@ -30,23 +30,12 @@ void control_write(struct client *c, const char *fmt, ...) { - va_list ap; + va_list ap; va_start(ap, fmt); - evbuffer_add_vprintf(c->stdout_data, fmt, ap); + file_vprint(c, fmt, ap); + file_print(c, "\n"); va_end(ap); - - evbuffer_add(c->stdout_data, "\n", 1); - server_client_push_stdout(c); -} - -/* Write a buffer, adding a terminal newline. Empties buffer. */ -void -control_write_buffer(struct client *c, struct evbuffer *buffer) -{ - evbuffer_add_buffer(c->stdout_data, buffer); - evbuffer_add(c->stdout_data, "\n", 1); - server_client_push_stdout(c); } /* Control error callback. */ @@ -65,20 +54,22 @@ control_error(struct cmdq_item *item, void *data) } /* Control input callback. Read lines and fire commands. */ -void -control_callback(struct client *c, int closed, __unused void *data) +static void +control_callback(__unused struct client *c, __unused const char *path, + int error, int closed, struct evbuffer *buffer, __unused void *data) { char *line; struct cmdq_item *item; struct cmd_parse_result *pr; - if (closed) + if (closed || error != 0) c->flags |= CLIENT_EXIT; for (;;) { - line = evbuffer_readln(c->stdin_data, NULL, EVBUFFER_EOL_LF); + line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF); if (line == NULL) break; + log_debug("%s: %s", __func__, line); if (*line == '\0') { /* empty line exit */ free(line); c->flags |= CLIENT_EXIT; @@ -104,3 +95,12 @@ control_callback(struct client *c, int closed, __unused void *data) free(line); } } + +void +control_start(struct client *c) +{ + file_read(c, "-", control_callback, c); + + if (c->flags & CLIENT_CONTROLCONTROL) + file_print(c, "\033P1000p"); +} diff --git a/file.c b/file.c new file mode 100644 index 00000000..ccd62a94 --- /dev/null +++ b/file.c @@ -0,0 +1,390 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2019 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "tmux.h" + +static int file_next_stream = 3; + +RB_GENERATE(client_files, client_file, entry, file_cmp); + +int +file_cmp(struct client_file *cf1, struct client_file *cf2) +{ + if (cf1->stream < cf2->stream) + return (-1); + if (cf1->stream > cf2->stream) + return (1); + return (0); +} + +struct client_file * +file_create(struct client *c, int stream, client_file_cb cb, void *cbdata) +{ + struct client_file *cf; + + cf = xcalloc(1, sizeof *cf); + cf->c = c; + cf->references = 1; + cf->stream = stream; + + cf->buffer = evbuffer_new(); + if (cf->buffer == NULL) + fatalx("out of memory"); + + cf->cb = cb; + cf->data = cbdata; + + if (cf->c != NULL) { + RB_INSERT(client_files, &cf->c->files, cf); + cf->c->references++; + } + + return (cf); +} + +void +file_free(struct client_file *cf) +{ + if (--cf->references != 0) + return; + + evbuffer_free(cf->buffer); + free(cf->path); + + if (cf->c != NULL) { + RB_REMOVE(client_files, &cf->c->files, cf); + server_client_unref(cf->c); + } + free(cf); +} + +static void +file_fire_done_cb(__unused int fd, __unused short events, void *arg) +{ + struct client_file *cf = arg; + struct client *c = cf->c; + + if (cf->cb != NULL && (~c->flags & CLIENT_DEAD)) + cf->cb(c, cf->path, cf->error, 1, cf->buffer, cf->data); + file_free(cf); +} + +void +file_fire_done(struct client_file *cf) +{ + event_once(-1, EV_TIMEOUT, file_fire_done_cb, cf, NULL); +} + +void +file_fire_read(struct client_file *cf) +{ + struct client *c = cf->c; + + if (cf->cb != NULL) + cf->cb(c, cf->path, cf->error, 0, cf->buffer, cf->data); +} + +int +file_can_print(struct client *c) +{ + if (c == NULL) + return (0); + if (c->session != NULL && (~c->flags & CLIENT_CONTROL)) + return (0); + return (1); +} + +void +file_print(struct client *c, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + file_vprint(c, fmt, ap); + va_end(ap); +} + +void +file_vprint(struct client *c, const char *fmt, va_list ap) +{ + struct client_file find, *cf; + struct msg_write_open msg; + + if (!file_can_print(c)) + return; + + find.stream = 1; + if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) { + cf = file_create(c, 1, NULL, NULL); + cf->path = xstrdup("-"); + + evbuffer_add_vprintf(cf->buffer, fmt, ap); + + msg.stream = 1; + msg.fd = STDOUT_FILENO; + msg.flags = 0; + strlcpy(msg.path, "-", sizeof msg.path); + proc_send(c->peer, MSG_WRITE_OPEN, -1, &msg, sizeof msg); + } else { + evbuffer_add_vprintf(cf->buffer, fmt, ap); + file_push(cf); + } +} + +void +file_print_buffer(struct client *c, void *data, size_t size) +{ + struct client_file find, *cf; + struct msg_write_open msg; + + if (!file_can_print(c)) + return; + + find.stream = 1; + if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) { + cf = file_create(c, 1, NULL, NULL); + cf->path = xstrdup("-"); + + evbuffer_add(cf->buffer, data, size); + + msg.stream = 1; + msg.fd = STDOUT_FILENO; + msg.flags = 0; + strlcpy(msg.path, "-", sizeof msg.path); + proc_send(c->peer, MSG_WRITE_OPEN, -1, &msg, sizeof msg); + } else { + evbuffer_add(cf->buffer, data, size); + file_push(cf); + } +} + +void +file_error(struct client *c, const char *fmt, ...) +{ + struct client_file find, *cf; + struct msg_write_open msg; + va_list ap; + + if (!file_can_print(c)) + return; + + va_start(ap, fmt); + + find.stream = 2; + if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) { + cf = file_create(c, 2, NULL, NULL); + cf->path = xstrdup("-"); + + evbuffer_add_vprintf(cf->buffer, fmt, ap); + + msg.stream = 2; + msg.fd = STDERR_FILENO; + msg.flags = 0; + strlcpy(msg.path, "-", sizeof msg.path); + proc_send(c->peer, MSG_WRITE_OPEN, -1, &msg, sizeof msg); + } else { + evbuffer_add_vprintf(cf->buffer, fmt, ap); + file_push(cf); + } + + va_end(ap); +} + +void +file_write(struct client *c, const char *path, int flags, const void *bdata, + size_t bsize, client_file_cb cb, void *cbdata) +{ + struct client_file *cf; + FILE *f; + struct msg_write_open msg; + int fd = -1; + const char *mode; + + if (strcmp(path, "-") == 0) { + cf = file_create(c, file_next_stream++, cb, cbdata); + cf->path = xstrdup("-"); + + fd = STDOUT_FILENO; + if (c == NULL || c->flags & CLIENT_ATTACHED) { + cf->error = EBADF; + goto done; + } + goto skip; + } + + cf = file_create(c, file_next_stream++, cb, cbdata); + cf->path = server_client_get_path(c, path); + + if (c == NULL || c->flags & CLIENT_ATTACHED) { + if (flags & O_APPEND) + mode = "ab"; + else + mode = "wb"; + f = fopen(cf->path, mode); + if (f == NULL) { + cf->error = errno; + goto done; + } + if (fwrite(bdata, 1, bsize, f) != bsize) { + fclose(f); + cf->error = EIO; + goto done; + } + fclose(f); + goto done; + } + +skip: + evbuffer_add(cf->buffer, bdata, bsize); + + msg.stream = cf->stream; + msg.fd = fd; + msg.flags = flags; + if (strlcpy(msg.path, cf->path, sizeof msg.path) >= sizeof msg.path) { + cf->error = E2BIG; + goto done; + } + if (proc_send(c->peer, MSG_WRITE_OPEN, -1, &msg, sizeof msg) != 0) { + cf->error = EINVAL; + goto done; + } + return; + +done: + file_fire_done(cf); +} + +void +file_read(struct client *c, const char *path, client_file_cb cb, void *cbdata) +{ + struct client_file *cf; + FILE *f; + struct msg_read_open msg; + int fd = -1; + char buffer[BUFSIZ]; + size_t size; + + if (strcmp(path, "-") == 0) { + cf = file_create(c, file_next_stream++, cb, cbdata); + cf->path = xstrdup("-"); + + fd = STDIN_FILENO; + if (c == NULL || c->flags & CLIENT_ATTACHED) { + cf->error = EBADF; + goto done; + } + goto skip; + } + + cf = file_create(c, file_next_stream++, cb, cbdata); + cf->path = server_client_get_path(c, path); + + if (c == NULL || c->flags & CLIENT_ATTACHED) { + f = fopen(cf->path, "rb"); + if (f == NULL) { + cf->error = errno; + goto done; + } + for (;;) { + size = fread(buffer, 1, sizeof buffer, f); + if (evbuffer_add(cf->buffer, buffer, size) != 0) { + cf->error = ENOMEM; + goto done; + } + if (size != sizeof buffer) + break; + } + if (ferror(f)) { + cf->error = EIO; + goto done; + } + fclose(f); + goto done; + } + +skip: + msg.stream = cf->stream; + msg.fd = fd; + if (strlcpy(msg.path, cf->path, sizeof msg.path) >= sizeof msg.path) { + cf->error = E2BIG; + goto done; + } + if (proc_send(c->peer, MSG_READ_OPEN, -1, &msg, sizeof msg) != 0) { + cf->error = EINVAL; + goto done; + } + return; + +done: + file_fire_done(cf); +} + +static void +file_push_cb(__unused int fd, __unused short events, void *arg) +{ + struct client_file *cf = arg; + struct client *c = cf->c; + + if (~c->flags & CLIENT_DEAD) + file_push(cf); + file_free(cf); +} + +void +file_push(struct client_file *cf) +{ + struct client *c = cf->c; + struct msg_write_data msg; + struct msg_write_close close; + size_t sent, left; + + left = EVBUFFER_LENGTH(cf->buffer); + while (left != 0) { + sent = left; + if (sent > sizeof msg.data) + sent = sizeof msg.data; + memcpy(msg.data, EVBUFFER_DATA(cf->buffer), sent); + msg.size = sent; + + msg.stream = cf->stream; + if (proc_send(c->peer, MSG_WRITE, -1, &msg, sizeof msg) != 0) + break; + evbuffer_drain(cf->buffer, sent); + + left = EVBUFFER_LENGTH(cf->buffer); + log_debug("%s: file %d sent %zu, left %zu", c->name, cf->stream, + sent, left); + } + if (left != 0) { + cf->references++; + event_once(-1, EV_TIMEOUT, file_push_cb, cf, NULL); + } else if (cf->stream > 2) { + close.stream = cf->stream; + proc_send(c->peer, MSG_WRITE_CLOSE, -1, &close, sizeof close); + file_fire_done(cf); + } +} diff --git a/server-client.c b/server-client.c index bd256cb8..e6e4d8a9 100644 --- a/server-client.c +++ b/server-client.c @@ -50,6 +50,12 @@ static void server_client_dispatch(struct imsg *, void *); static void server_client_dispatch_command(struct client *, struct imsg *); static void server_client_dispatch_identify(struct client *, struct imsg *); static void server_client_dispatch_shell(struct client *); +static void server_client_dispatch_write_ready(struct client *, + struct imsg *); +static void server_client_dispatch_read_data(struct client *, + struct imsg *); +static void server_client_dispatch_read_done(struct client *, + struct imsg *); /* Number of attached clients. */ u_int @@ -197,16 +203,6 @@ server_client_create(int fd) TAILQ_INIT(&c->queue); - c->stdin_data = evbuffer_new(); - if (c->stdin_data == NULL) - fatalx("out of memory"); - c->stdout_data = evbuffer_new(); - if (c->stdout_data == NULL) - fatalx("out of memory"); - c->stderr_data = evbuffer_new(); - if (c->stderr_data == NULL) - fatalx("out of memory"); - c->tty.fd = -1; c->title = NULL; @@ -225,6 +221,8 @@ server_client_create(int fd) c->prompt_buffer = NULL; c->prompt_index = 0; + RB_INIT(&c->files); + c->flags |= CLIENT_FOCUSED; c->keytable = key_bindings_get_table("root", 1); @@ -266,6 +264,7 @@ void server_client_lost(struct client *c) { struct message_entry *msg, *msg1; + struct client_file *cf; c->flags |= CLIENT_DEAD; @@ -273,8 +272,10 @@ server_client_lost(struct client *c) status_prompt_clear(c); status_message_clear(c); - if (c->stdin_callback != NULL) - c->stdin_callback(c, 1, c->stdin_callback_data); + RB_FOREACH(cf, client_files, &c->files) { + cf->error = EINTR; + file_fire_done(cf); + } TAILQ_REMOVE(&clients, c, entry); log_debug("lost client %p", c); @@ -288,11 +289,6 @@ server_client_lost(struct client *c) free(c->ttyname); free(c->term); - evbuffer_free(c->stdin_data); - evbuffer_free(c->stdout_data); - if (c->stderr_data != c->stdout_data) - evbuffer_free(c->stderr_data); - status_free(c); free(c->title); @@ -1560,17 +1556,17 @@ server_client_click_timer(__unused int fd, __unused short events, void *data) static void server_client_check_exit(struct client *c) { + struct client_file *cf; + if (~c->flags & CLIENT_EXIT) return; if (c->flags & CLIENT_EXITED) return; - if (EVBUFFER_LENGTH(c->stdin_data) != 0) - return; - if (EVBUFFER_LENGTH(c->stdout_data) != 0) - return; - if (EVBUFFER_LENGTH(c->stderr_data) != 0) - return; + RB_FOREACH(cf, client_files, &c->files) { + if (EVBUFFER_LENGTH(cf->buffer) != 0) + return; + } if (c->flags & CLIENT_ATTACHED) notify_client("client-detached", c); @@ -1709,11 +1705,10 @@ server_client_set_title(struct client *c) static void server_client_dispatch(struct imsg *imsg, void *arg) { - struct client *c = arg; - struct msg_stdin_data stdindata; - const char *data; - ssize_t datalen; - struct session *s; + struct client *c = arg; + const char *data; + ssize_t datalen; + struct session *s; if (c->flags & CLIENT_DEAD) return; @@ -1740,21 +1735,6 @@ server_client_dispatch(struct imsg *imsg, void *arg) case MSG_COMMAND: server_client_dispatch_command(c, imsg); break; - case MSG_STDIN: - if (datalen != sizeof stdindata) - fatalx("bad MSG_STDIN size"); - memcpy(&stdindata, 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) fatalx("bad MSG_RESIZE size"); @@ -1806,6 +1786,15 @@ server_client_dispatch(struct imsg *imsg, void *arg) server_client_dispatch_shell(c); break; + case MSG_WRITE_READY: + server_client_dispatch_write_ready(c, imsg); + break; + case MSG_READ: + server_client_dispatch_read_data(c, imsg); + break; + case MSG_READ_DONE: + server_client_dispatch_read_done(c, imsg); + break; } } @@ -1824,7 +1813,7 @@ server_client_command_done(struct cmdq_item *item, __unused void *data) static void server_client_dispatch_command(struct client *c, struct imsg *imsg) { - struct msg_command_data data; + struct msg_command data; char *buf; size_t len; int argc; @@ -1964,19 +1953,11 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg) log_debug("client %p name is %s", c, c->name); if (c->flags & CLIENT_CONTROL) { - c->stdin_callback = control_callback; - - evbuffer_free(c->stderr_data); - c->stderr_data = c->stdout_data; - - if (c->flags & CLIENT_CONTROLCONTROL) - evbuffer_add_printf(c->stdout_data, "\033P1000p"); - proc_send(c->peer, MSG_STDIN, -1, NULL, 0); - - c->tty.fd = -1; - close(c->fd); c->fd = -1; + + control_start(c); + c->tty.fd = -1; } else if (c->fd != -1) { if (tty_init(&c->tty, c, c->fd, c->term) != 0) { close(c->fd); @@ -2016,93 +1997,71 @@ server_client_dispatch_shell(struct client *c) proc_kill_peer(c->peer); } -/* Event callback to push more stdout data if any left. */ +/* Handle write ready message. */ static void -server_client_stdout_cb(__unused int fd, __unused short events, void *arg) +server_client_dispatch_write_ready(struct client *c, struct imsg *imsg) { - struct client *c = arg; + struct msg_write_ready *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + struct client_file find, *cf; - if (~c->flags & CLIENT_DEAD) - server_client_push_stdout(c); - server_client_unref(c); -} - -/* Push stdout to client if possible. */ -void -server_client_push_stdout(struct client *c) -{ - struct msg_stdout_data data; - size_t sent, left; - - left = EVBUFFER_LENGTH(c->stdout_data); - while (left != 0) { - sent = left; - if (sent > sizeof data.data) - sent = sizeof data.data; - memcpy(data.data, EVBUFFER_DATA(c->stdout_data), sent); - data.size = sent; - - if (proc_send(c->peer, MSG_STDOUT, -1, &data, sizeof data) != 0) - break; - evbuffer_drain(c->stdout_data, sent); - - left = EVBUFFER_LENGTH(c->stdout_data); - log_debug("%s: client %p, sent %zu, left %zu", __func__, c, - sent, left); - } - if (left != 0) { - c->references++; - event_once(-1, EV_TIMEOUT, server_client_stdout_cb, c, NULL); - log_debug("%s: client %p, queued", __func__, c); - } -} - -/* Event callback to push more stderr data if any left. */ -static void -server_client_stderr_cb(__unused int fd, __unused short events, void *arg) -{ - struct client *c = arg; - - if (~c->flags & CLIENT_DEAD) - server_client_push_stderr(c); - server_client_unref(c); -} - -/* Push stderr to client if possible. */ -void -server_client_push_stderr(struct client *c) -{ - struct msg_stderr_data data; - size_t sent, left; - - if (c->stderr_data == c->stdout_data) { - server_client_push_stdout(c); + if (msglen != sizeof *msg) + fatalx("bad MSG_WRITE_READY size"); + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) return; - } + if (msg->error != 0) { + cf->error = msg->error; + file_fire_done(cf); + } else + file_push(cf); +} - left = EVBUFFER_LENGTH(c->stderr_data); - while (left != 0) { - sent = left; - if (sent > sizeof data.data) - sent = sizeof data.data; - memcpy(data.data, EVBUFFER_DATA(c->stderr_data), sent); - data.size = sent; +/* Handle read data message. */ +static void +server_client_dispatch_read_data(struct client *c, struct imsg *imsg) +{ + struct msg_read_data *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + struct client_file find, *cf; + void *bdata = msg->data; + size_t bsize = msg->size; - if (proc_send(c->peer, MSG_STDERR, -1, &data, sizeof data) != 0) - break; - evbuffer_drain(c->stderr_data, sent); + if (msglen != sizeof *msg) + fatalx("bad MSG_READ_DATA size"); + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) + return; - left = EVBUFFER_LENGTH(c->stderr_data); - log_debug("%s: client %p, sent %zu, left %zu", __func__, c, - sent, left); - } - if (left != 0) { - c->references++; - event_once(-1, EV_TIMEOUT, server_client_stderr_cb, c, NULL); - log_debug("%s: client %p, queued", __func__, c); + log_debug("%s: file %d read %zu bytes", c->name, cf->stream, bsize); + if (cf->error == 0) { + if (evbuffer_add(cf->buffer, bdata, bsize) != 0) { + cf->error = ENOMEM; + file_fire_done(cf); + } else + file_fire_read(cf); } } +/* Handle read done message. */ +static void +server_client_dispatch_read_done(struct client *c, struct imsg *imsg) +{ + struct msg_read_done *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + struct client_file find, *cf; + + if (msglen != sizeof *msg) + fatalx("bad MSG_READ_DONE size"); + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) + return; + + log_debug("%s: file %d read done", c->name, cf->stream); + cf->error = msg->error; + file_fire_done(cf); +} + /* Add to client message log. */ void server_client_add_message(struct client *c, const char *fmt, ...) diff --git a/server-fn.c b/server-fn.c index 25d3aeb1..aa4a482b 100644 --- a/server-fn.c +++ b/server-fn.c @@ -439,36 +439,6 @@ server_check_unattached(void) } } -int -server_set_stdin_callback(struct client *c, void (*cb)(struct client *, int, - void *), void *cb_data, char **cause) -{ - if (c == NULL || c->session != 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 is 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); - - proc_send(c->peer, MSG_STDIN, -1, NULL, 0); - - return (0); -} - void server_unzoom_window(struct window *w) { diff --git a/tmux.h b/tmux.h index 833e74bf..d8308e1a 100644 --- a/tmux.h +++ b/tmux.h @@ -478,13 +478,21 @@ enum msgtype { MSG_RESIZE, MSG_SHELL, MSG_SHUTDOWN, - MSG_STDERR, - MSG_STDIN, - MSG_STDOUT, + MSG_OLDSTDERR, /* unused */ + MSG_OLDSTDIN, /* unused */ + MSG_OLDSTDOUT, /* unused */ MSG_SUSPEND, MSG_UNLOCK, MSG_WAKEUP, MSG_EXEC, + + MSG_READ_OPEN = 300, + MSG_READ, + MSG_READ_DONE, + MSG_WRITE_OPEN, + MSG_WRITE, + MSG_WRITE_READY, + MSG_WRITE_CLOSE }; /* @@ -492,23 +500,47 @@ enum msgtype { * * Don't forget to bump PROTOCOL_VERSION if any of these change! */ -struct msg_command_data { +struct msg_command { int argc; }; /* followed by packed argv */ -struct msg_stdin_data { - ssize_t size; +struct msg_read_open { + int stream; + int fd; + char path[PATH_MAX]; +}; + +struct msg_read_data { + int stream; + size_t size; char data[BUFSIZ]; }; -struct msg_stdout_data { - ssize_t size; +struct msg_read_done { + int stream; + int error; +}; + +struct msg_write_open { + int stream; + int fd; + char path[PATH_MAX]; + int flags; +}; + +struct msg_write_data { + int stream; + size_t size; char data[BUFSIZ]; }; -struct msg_stderr_data { - ssize_t size; - char data[BUFSIZ]; +struct msg_write_ready { + int stream; + int error; +}; + +struct msg_write_close { + int stream; }; /* Mode keys. */ @@ -1474,6 +1506,29 @@ struct status_line { struct status_line_entry entries[STATUS_LINES_LIMIT]; }; +/* File in client. */ +typedef void (*client_file_cb) (struct client *, const char *, int, int, + struct evbuffer *, void *); +struct client_file { + struct client *c; + int references; + int stream; + + char *path; + struct evbuffer *buffer; + struct bufferevent *event; + + int fd; + int error; + int closed; + + client_file_cb cb; + void *data; + + RB_ENTRY (client_file) entry; +}; +RB_HEAD(client_files, client_file); + /* Client connection. */ typedef int (*prompt_input_cb)(struct client *, void *, const char *, int); typedef void (*prompt_free_cb)(void *); @@ -1507,13 +1562,6 @@ struct client { size_t discarded; size_t redraw; - 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; struct event click_timer; @@ -1597,6 +1645,8 @@ struct client { void *overlay_data; struct event overlay_timer; + struct client_files files; + TAILQ_ENTRY(client) entry; }; TAILQ_HEAD(clients, client); @@ -2129,6 +2179,23 @@ void alerts_reset_all(void); void alerts_queue(struct window *, int); void alerts_check_session(struct session *); +/* file.c */ +int file_cmp(struct client_file *, struct client_file *); +RB_PROTOTYPE(client_files, client_file, entry, file_cmp); +struct client_file *file_create(struct client *, int, client_file_cb, void *); +void file_free(struct client_file *); +void file_fire_done(struct client_file *); +void file_fire_read(struct client_file *); +int file_can_print(struct client *); +void printflike(2, 3) file_print(struct client *, const char *, ...); +void file_vprint(struct client *, const char *, va_list); +void file_print_buffer(struct client *, void *, size_t); +void printflike(2, 3) file_error(struct client *, const char *, ...); +void file_write(struct client *, const char *, int, const void *, size_t, + client_file_cb, void *); +void file_read(struct client *, const char *, client_file_cb, void *); +void file_push(struct client_file *); + /* server.c */ extern struct tmuxproc *server_proc; extern struct clients clients; @@ -2187,8 +2254,6 @@ void server_unlink_window(struct session *, struct winlink *); void server_destroy_pane(struct window_pane *, int); void server_destroy_session(struct session *); void server_check_unattached(void); -int server_set_stdin_callback(struct client *, void (*)(struct client *, - int, void *), void *, char **); void server_unzoom_window(struct window *); /* status.c */ @@ -2573,9 +2638,8 @@ char *default_window_name(struct window *); char *parse_window_name(const char *); /* control.c */ -void control_callback(struct client *, int, void *); +void control_start(struct client *); void printflike(2, 3) control_write(struct client *, const char *, ...); -void control_write_buffer(struct client *, struct evbuffer *); /* control-notify.c */ void control_notify_input(struct client *, struct window_pane *, diff --git a/window.c b/window.c index bde9693b..64c95855 100644 --- a/window.c +++ b/window.c @@ -1598,31 +1598,28 @@ winlink_shuffle_up(struct session *s, struct winlink *wl) } static void -window_pane_input_callback(struct client *c, int closed, void *data) +window_pane_input_callback(struct client *c, __unused const char *path, + int error, int closed, struct evbuffer *buffer, void *data) { struct window_pane_input_data *cdata = data; struct window_pane *wp; - struct evbuffer *evb = c->stdin_data; - u_char *buf = EVBUFFER_DATA(evb); - size_t len = EVBUFFER_LENGTH(evb); + u_char *buf = EVBUFFER_DATA(buffer); + size_t len = EVBUFFER_LENGTH(buffer); wp = window_pane_find_by_id(cdata->wp); - if (wp == NULL || closed || c->flags & CLIENT_DEAD) { + if (wp == NULL || closed || error != 0 || c->flags & CLIENT_DEAD) { if (wp == NULL) c->flags |= CLIENT_EXIT; - evbuffer_drain(evb, len); - - c->stdin_callback = NULL; - server_client_unref(c); + evbuffer_drain(buffer, len); cmdq_continue(cdata->item); - free(cdata); + server_client_unref(c); + free(cdata); return; } - input_parse_buffer(wp, buf, len); - evbuffer_drain(evb, len); + evbuffer_drain(buffer, len); } int @@ -1641,6 +1638,8 @@ window_pane_start_input(struct window_pane *wp, struct cmdq_item *item, cdata->item = item; cdata->wp = wp->id; - return (server_set_stdin_callback(c, window_pane_input_callback, cdata, - cause)); + c->references++; + file_read(c, "-", window_pane_input_callback, cdata); + + return (0); }