diff options
author | Nicholas Marriott <nicm@cvs.openbsd.org> | 2019-12-12 11:39:57 +0000 |
---|---|---|
committer | Nicholas Marriott <nicm@cvs.openbsd.org> | 2019-12-12 11:39:57 +0000 |
commit | 26afc37a415118f304bd257969301773a0bc74fb (patch) | |
tree | b646def5c74316209bf3eeb049cf42a9488bb607 /usr.bin | |
parent | 7ff24f14fc5aa2876f20cae2c52edf2cbb12951a (diff) |
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.
Diffstat (limited to 'usr.bin')
-rw-r--r-- | usr.bin/tmux/Makefile | 3 | ||||
-rw-r--r-- | usr.bin/tmux/client.c | 332 | ||||
-rw-r--r-- | usr.bin/tmux/cmd-capture-pane.c | 17 | ||||
-rw-r--r-- | usr.bin/tmux/cmd-load-buffer.c | 162 | ||||
-rw-r--r-- | usr.bin/tmux/cmd-queue.c | 36 | ||||
-rw-r--r-- | usr.bin/tmux/cmd-save-buffer.c | 100 | ||||
-rw-r--r-- | usr.bin/tmux/control-notify.c | 4 | ||||
-rw-r--r-- | usr.bin/tmux/control.c | 38 | ||||
-rw-r--r-- | usr.bin/tmux/file.c | 390 | ||||
-rw-r--r-- | usr.bin/tmux/server-client.c | 215 | ||||
-rw-r--r-- | usr.bin/tmux/server-fn.c | 32 | ||||
-rw-r--r-- | usr.bin/tmux/tmux.h | 110 | ||||
-rw-r--r-- | usr.bin/tmux/window.c | 29 |
13 files changed, 961 insertions, 507 deletions
diff --git a/usr.bin/tmux/Makefile b/usr.bin/tmux/Makefile index d6466e0b736..6849af9377c 100644 --- a/usr.bin/tmux/Makefile +++ b/usr.bin/tmux/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.100 2019/06/13 19:46:00 nicm Exp $ +# $OpenBSD: Makefile,v 1.101 2019/12/12 11:39:56 nicm Exp $ PROG= tmux SRCS= alerts.c \ @@ -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/usr.bin/tmux/client.c b/usr.bin/tmux/client.c index 2a9ae93ef00..9cfe9517e88 100644 --- a/usr.bin/tmux/client.c +++ b/usr.bin/tmux/client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: client.c,v 1.131 2019/07/26 20:08:40 nicm Exp $ */ +/* $OpenBSD: client.c,v 1.132 2019/12/12 11:39:56 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> @@ -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,39 +423,234 @@ 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); + + bufferevent_free(cf->event); + cf->event = NULL; + + close(cf->fd); + cf->fd = -1; +} + +/* File write callback. */ +static void +client_write_callback(__unused struct bufferevent *bev, void *arg) +{ + struct client_file *cf = arg; + + 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; + } - proc_send(client_peer, MSG_STDIN, -1, &data, sizeof data); - if (data.size <= 0) - event_del(&client_stdin); + 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); + } } -/* Force write to file descriptor. */ +/* File read callback. */ static void -client_write(int fd, const char *data, size_t size) +client_read_callback(__unused struct bufferevent *bev, void *arg) { - ssize_t used; - - 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; + 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 */ } - data += used; - size -= used; } + 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. */ @@ -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/usr.bin/tmux/cmd-capture-pane.c b/usr.bin/tmux/cmd-capture-pane.c index e7fe6862168..61562216e91 100644 --- a/usr.bin/tmux/cmd-capture-pane.c +++ b/usr.bin/tmux/cmd-capture-pane.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-capture-pane.c,v 1.48 2019/08/01 08:42:34 nicm Exp $ */ +/* $OpenBSD: cmd-capture-pane.c,v 1.49 2019/12/12 11:39:56 nicm Exp $ */ /* * Copyright (c) 2009 Jonathan Alvarado <radobobo@users.sourceforge.net> @@ -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/usr.bin/tmux/cmd-load-buffer.c b/usr.bin/tmux/cmd-load-buffer.c index 0f4622058ac..a2450e0bfc0 100644 --- a/usr.bin/tmux/cmd-load-buffer.c +++ b/usr.bin/tmux/cmd-load-buffer.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-load-buffer.c,v 1.56 2019/06/18 11:08:42 nicm Exp $ */ +/* $OpenBSD: cmd-load-buffer.c,v 1.57 2019/12/12 11:39:56 nicm Exp $ */ /* * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org> @@ -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/usr.bin/tmux/cmd-queue.c b/usr.bin/tmux/cmd-queue.c index 30f3b8e60b3..a66894a7811 100644 --- a/usr.bin/tmux/cmd-queue.c +++ b/usr.bin/tmux/cmd-queue.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-queue.c,v 1.75 2019/09/10 07:50:33 nicm Exp $ */ +/* $OpenBSD: cmd-queue.c,v 1.76 2019/12/12 11:39:56 nicm Exp $ */ /* * Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com> @@ -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/usr.bin/tmux/cmd-save-buffer.c b/usr.bin/tmux/cmd-save-buffer.c index 6c3b6b3a42e..453558ca9db 100644 --- a/usr.bin/tmux/cmd-save-buffer.c +++ b/usr.bin/tmux/cmd-save-buffer.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-save-buffer.c,v 1.46 2019/06/13 21:44:13 nicm Exp $ */ +/* $OpenBSD: cmd-save-buffer.c,v 1.47 2019/12/12 11:39:56 nicm Exp $ */ /* * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org> @@ -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/usr.bin/tmux/control-notify.c b/usr.bin/tmux/control-notify.c index 8fb17783212..2e8b50f9c32 100644 --- a/usr.bin/tmux/control-notify.c +++ b/usr.bin/tmux/control-notify.c @@ -1,4 +1,4 @@ -/* $OpenBSD: control-notify.c,v 1.24 2019/07/10 11:20:10 nicm Exp $ */ +/* $OpenBSD: control-notify.c,v 1.25 2019/12/12 11:39:56 nicm Exp $ */ /* * Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com> @@ -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/usr.bin/tmux/control.c b/usr.bin/tmux/control.c index d2bb592541a..2a002d1b941 100644 --- a/usr.bin/tmux/control.c +++ b/usr.bin/tmux/control.c @@ -1,4 +1,4 @@ -/* $OpenBSD: control.c,v 1.24 2019/07/09 13:19:36 nicm Exp $ */ +/* $OpenBSD: control.c,v 1.25 2019/12/12 11:39:56 nicm Exp $ */ /* * Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com> @@ -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/usr.bin/tmux/file.c b/usr.bin/tmux/file.c new file mode 100644 index 00000000000..1067c7af9b0 --- /dev/null +++ b/usr.bin/tmux/file.c @@ -0,0 +1,390 @@ +/* $OpenBSD: file.c,v 1.1 2019/12/12 11:39:56 nicm Exp $ */ + +/* + * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com> + * + * 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 <sys/types.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#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/usr.bin/tmux/server-client.c b/usr.bin/tmux/server-client.c index d6e6e225ebd..1b131cebe91 100644 --- a/usr.bin/tmux/server-client.c +++ b/usr.bin/tmux/server-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server-client.c,v 1.299 2019/12/03 10:47:22 nicm Exp $ */ +/* $OpenBSD: server-client.c,v 1.300 2019/12/12 11:39:56 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com> @@ -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,91 +1997,69 @@ 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; - - if (~c->flags & CLIENT_DEAD) - server_client_push_stdout(c); - server_client_unref(c); + struct msg_write_ready *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + struct client_file find, *cf; + + 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); } -/* Push stdout to client if possible. */ -void -server_client_push_stdout(struct client *c) +/* Handle read data message. */ +static void +server_client_dispatch_read_data(struct client *c, struct imsg *imsg) { - 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); + 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 (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->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); + 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); } } -/* Event callback to push more stderr data if any left. */ +/* Handle read done message. */ static void -server_client_stderr_cb(__unused int fd, __unused short events, void *arg) +server_client_dispatch_read_done(struct client *c, struct imsg *imsg) { - 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); + 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; - } - - 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; - - if (proc_send(c->peer, MSG_STDERR, -1, &data, sizeof data) != 0) - break; - evbuffer_drain(c->stderr_data, sent); - 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 done", c->name, cf->stream); + cf->error = msg->error; + file_fire_done(cf); } /* Add to client message log. */ diff --git a/usr.bin/tmux/server-fn.c b/usr.bin/tmux/server-fn.c index 8a4a1505a50..268ed7a3a9a 100644 --- a/usr.bin/tmux/server-fn.c +++ b/usr.bin/tmux/server-fn.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server-fn.c,v 1.122 2019/06/20 11:59:59 nicm Exp $ */ +/* $OpenBSD: server-fn.c,v 1.123 2019/12/12 11:39:56 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> @@ -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/usr.bin/tmux/tmux.h b/usr.bin/tmux/tmux.h index 1bababd9519..1a7f91eaab9 100644 --- a/usr.bin/tmux/tmux.h +++ b/usr.bin/tmux/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.937 2019/12/10 14:22:15 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.938 2019/12/12 11:39:56 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> @@ -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,25 +500,49 @@ 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; - char data[BUFSIZ]; +struct msg_read_open { + int stream; + int fd; + char path[PATH_MAX]; }; -struct msg_stdout_data { - ssize_t size; +struct msg_read_data { + int stream; + size_t size; char data[BUFSIZ]; }; -struct msg_stderr_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_write_ready { + int stream; + int error; +}; + +struct msg_write_close { + int stream; +}; + /* Mode keys. */ #define MODEKEY_EMACS 0 #define MODEKEY_VI 1 @@ -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/usr.bin/tmux/window.c b/usr.bin/tmux/window.c index 19f67664382..2d8755af148 100644 --- a/usr.bin/tmux/window.c +++ b/usr.bin/tmux/window.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window.c,v 1.245 2019/11/28 09:45:16 nicm Exp $ */ +/* $OpenBSD: window.c,v 1.246 2019/12/12 11:39:56 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> @@ -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); } |