From 55eb3e4773b8e73d7073c53aa5a61a017631b519 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 10 Dec 2019 14:22:15 +0000 Subject: [PATCH 01/23] Make TMUX_CONF a list of files and expand leading $FOO or ~. --- cfg.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++----------- tmux.h | 4 ++-- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/cfg.c b/cfg.c index a47621a4..fd7ee897 100644 --- a/cfg.c +++ b/cfg.c @@ -67,12 +67,45 @@ set_cfg_file(const char *path) cfg_file = xstrdup(path); } +static char * +expand_cfg_file(const char *path, const char *home) +{ + char *expanded, *name; + const char *end; + struct environ_entry *value; + + if (strncmp(path, "~/", 2) == 0) { + if (home == NULL) + return (NULL); + xasprintf(&expanded, "%s%s", home, path + 1); + return (expanded); + } + + if (*path == '$') { + end = strchr(path, '/'); + if (end == NULL) + name = xstrdup(path + 1); + else + name = xstrndup(path + 1, end - path - 1); + value = environ_find(global_environ, name); + free(name); + if (value == NULL) + return (NULL); + if (end == NULL) + end = ""; + xasprintf(&expanded, "%s%s", value->value, end); + return (expanded); + } + + return (xstrdup(path)); +} + void start_cfg(void) { - const char *home; - int flags = 0; + const char *home = find_home(); struct client *c; + char *path, *copy, *next, *expanded; /* * Configuration files are loaded without a client, so commands are run @@ -90,15 +123,21 @@ start_cfg(void) cmdq_append(c, cfg_item); } - if (cfg_file == NULL) - load_cfg(TMUX_CONF, c, NULL, CMD_PARSE_QUIET, NULL); - - if (cfg_file == NULL && (home = find_home()) != NULL) { - xasprintf(&cfg_file, "%s/.tmux.conf", home); - flags = CMD_PARSE_QUIET; - } - if (cfg_file != NULL) - load_cfg(cfg_file, c, NULL, flags, NULL); + if (cfg_file == NULL) { + path = copy = xstrdup(TMUX_CONF); + while ((next = strsep(&path, ":")) != NULL) { + expanded = expand_cfg_file(next, home); + if (expanded == NULL) { + log_debug("couldn't expand %s", next); + continue; + } + log_debug("expanded %s to %s", next, expanded); + load_cfg(expanded, c, NULL, CMD_PARSE_QUIET, NULL); + free(expanded); + } + free(copy); + } else + load_cfg(cfg_file, c, NULL, 0, NULL); cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL)); } diff --git a/tmux.h b/tmux.h index bcdc99a4..833e74bf 100644 --- a/tmux.h +++ b/tmux.h @@ -60,9 +60,9 @@ struct winlink; /* Client-server protocol version. */ #define PROTOCOL_VERSION 8 -/* Default global configuration file. */ +/* Default configuration files. */ #ifndef TMUX_CONF -#define TMUX_CONF "/etc/tmux.conf" +#define TMUX_CONF "/etc/tmux.conf:~/.tmux.conf" #endif /* Minimum layout cell size, NOT including border lines. */ From 15d7e564ddab575dd3ac803989cc99ac13b57198 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 10 Dec 2019 16:31:01 +0000 Subject: [PATCH 02/23] Add ~/.config/tmux/tmux.conf to the default search path for configuration files (in Makefile.am, so portable tmux only). --- Makefile.am | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index ab302637..dc281383 100644 --- a/Makefile.am +++ b/Makefile.am @@ -11,7 +11,8 @@ EXTRA_DIST = \ dist_EXTRA_tmux_SOURCES = compat/*.[ch] # Preprocessor flags. -AM_CPPFLAGS += @XOPEN_DEFINES@ -DTMUX_CONF="\"$(sysconfdir)/tmux.conf\"" +AM_CPPFLAGS += @XOPEN_DEFINES@ +AM_CPPFLAGS += -DTMUX_CONF="\"$(sysconfdir)/tmux.conf:~/.tmux.conf:~/.config/tmux/tmux.conf\"" # Additional object files. LDADD = $(LIBOBJS) From 6aeb6790660d39117abaeef2d97c8af5447ae1b8 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 10 Dec 2019 19:02:27 +0000 Subject: [PATCH 03/23] Add to CHANGES. --- CHANGES | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/CHANGES b/CHANGES index 051c8e05..a08ed526 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,48 @@ CHANGES FROM 3.0 to X.X +* Add ~/.config/tmux/tmux.conf to the default search path for configuration + files. + +* Bump the escape sequence timeout to five seconds to allow for longer + legitimate sequences. + +* Make a best effort to set xpixel and ypixel for each pane and add formats for + them. + +* Add push-default to status-left and status-right in status-format[0]. + +* Add -F flag to send-keys to expand formats in search-backward and forward + copy mode commands and copy_cursor_word and copy_cursor_line formats for word + and line at cursor in copy mode. Use for default # and * binding with vi(1) + keys. + +* Do not clear search marks on cursor movement with vi(1) keys. + +* Add p format modifier for padding to width and allow multiple substitutions + in a single format. + +* Add -f for full size to join-pane (like split-window). + +* Handle OSC 7 (a VTE extension) and put the result in a new format (pane_path). + +* Change new-session -A without a session name (that is, no -s option also) to + attach to the best existing session like attach-session rather than creating + a new one. + +* Add an option to set the key sent by backspace for those whose system uses ^H + rather than ^?. + +* Add formats for cursor and selection position in copy mode. + +* Add support for percentage sizes for resize-pane ("-x 10%"). Also change + split-window and join-pane -l to accept similar percentages and no longer + document -p. + +* Turn automatic-rename back on if the rename escape sequence is used with an + empty name. + +* Make select-pane -P set window-active-style also to match previous behaviour. + * Do not use bright when emulating 256 colours on an 8 colour terminal because it is also bold on some terminals. From f733d3f3ebd144d0d0e1c11a544eb093637590e6 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 11 Dec 2019 12:13:37 +0000 Subject: [PATCH 04/23] Do not set cursor colour to default unless it has been changed, GitHub issue 2013. --- tty.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tty.c b/tty.c index 594f02fa..2d7acb17 100644 --- a/tty.c +++ b/tty.c @@ -335,7 +335,8 @@ tty_start_tty(struct tty *tty) tty->flags |= TTY_STARTED; tty_invalidate(tty); - tty_force_cursor_colour(tty, ""); + if (*tty->ccolour != '\0') + tty_force_cursor_colour(tty, ""); tty->mouse_drag_flag = 0; tty->mouse_drag_update = NULL; @@ -381,7 +382,8 @@ tty_stop_tty(struct tty *tty) } if (tty->mode & MODE_BRACKETPASTE) tty_raw(tty, "\033[?2004l"); - tty_raw(tty, tty_term_string(tty->term, TTYC_CR)); + if (*tty->ccolour != '\0') + tty_raw(tty, tty_term_string(tty->term, TTYC_CR)); tty_raw(tty, tty_term_string(tty->term, TTYC_CNORM)); if (tty_term_has(tty->term, TTYC_KMOUS)) From ab630f72ed7a076a3e49b6649a49f9d8764a861c Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 11 Dec 2019 18:23:34 +0000 Subject: [PATCH 05/23] Allow search across wrapped lines and fix some inconsistencies in how th position is represented, GitHub issue 2014 from Anindya Mukherjee. --- window-copy.c | 46 +++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/window-copy.c b/window-copy.c index 16708d04..32e254b5 100644 --- a/window-copy.c +++ b/window-copy.c @@ -2082,16 +2082,24 @@ static int window_copy_search_lr(struct grid *gd, struct grid *sgd, u_int *ppx, u_int py, u_int first, u_int last, int cis) { - u_int ax, bx, px; + u_int ax, bx, px, pywrap, endline; int matched; + endline = gd->hsize + gd->sy - 1; for (ax = first; ax < last; ax++) { - if (ax + sgd->sx > gd->sx) - break; for (bx = 0; bx < sgd->sx; bx++) { px = ax + bx; - matched = window_copy_search_compare(gd, px, py, sgd, - bx, cis); + pywrap = py; + /* Wrap line. */ + while (px >= gd->sx && pywrap < endline) { + px -= gd->sx; + pywrap++; + } + /* We have run off the end of the grid. */ + if (px >= gd->sx) + break; + matched = window_copy_search_compare(gd, px, pywrap, + sgd, bx, cis); if (!matched) break; } @@ -2107,16 +2115,24 @@ static int window_copy_search_rl(struct grid *gd, struct grid *sgd, u_int *ppx, u_int py, u_int first, u_int last, int cis) { - u_int ax, bx, px; + u_int ax, bx, px, pywrap, endline; int matched; - for (ax = last + 1; ax > first; ax--) { - if (gd->sx - (ax - 1) < sgd->sx) - continue; + endline = gd->hsize + gd->sy - 1; + for (ax = last; ax > first; ax--) { for (bx = 0; bx < sgd->sx; bx++) { px = ax - 1 + bx; - matched = window_copy_search_compare(gd, px, py, sgd, - bx, cis); + pywrap = py; + /* Wrap line. */ + while (px >= gd->sx && pywrap < endline) { + px -= gd->sx; + pywrap++; + } + /* We have run off the end of the grid. */ + if (px >= gd->sx) + break; + matched = window_copy_search_compare(gd, px, pywrap, + sgd, bx, cis); if (!matched) break; } @@ -2135,7 +2151,7 @@ window_copy_move_left(struct screen *s, u_int *fx, u_int *fy, int wrapflag) if (*fy == 0) { /* top */ if (wrapflag) { *fx = screen_size_x(s) - 1; - *fy = screen_hsize(s) + screen_size_y(s); + *fy = screen_hsize(s) + screen_size_y(s) - 1; } return; } @@ -2149,7 +2165,7 @@ static void window_copy_move_right(struct screen *s, u_int *fx, u_int *fy, int wrapflag) { if (*fx == screen_size_x(s) - 1) { /* right */ - if (*fy == screen_hsize(s) + screen_size_y(s)) { /* bottom */ + if (*fy == screen_hsize(s) + screen_size_y(s) - 1) { /* bottom */ if (wrapflag) { *fx = 0; *fy = 0; @@ -2199,12 +2215,12 @@ window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd, } else { for (i = fy + 1; endline < i; i--) { found = window_copy_search_rl(gd, sgd, &px, i - 1, 0, - fx, cis); + fx + 1, cis); if (found) { i--; break; } - fx = gd->sx; + fx = gd->sx - 1; } } From 64fb7e472a9e627ee486707ab9d30b607d76478a Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 11 Dec 2019 18:30:29 +0000 Subject: [PATCH 06/23] Tweak previous to check the wrapped flag and stop if not set. --- window-copy.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/window-copy.c b/window-copy.c index 32e254b5..4787fb27 100644 --- a/window-copy.c +++ b/window-copy.c @@ -2082,8 +2082,9 @@ static int window_copy_search_lr(struct grid *gd, struct grid *sgd, u_int *ppx, u_int py, u_int first, u_int last, int cis) { - u_int ax, bx, px, pywrap, endline; - int matched; + u_int ax, bx, px, pywrap, endline; + int matched; + struct grid_line *gl; endline = gd->hsize + gd->sy - 1; for (ax = first; ax < last; ax++) { @@ -2092,6 +2093,9 @@ window_copy_search_lr(struct grid *gd, pywrap = py; /* Wrap line. */ while (px >= gd->sx && pywrap < endline) { + gl = grid_get_line(gd, pywrap); + if (~gl->flags & GRID_LINE_WRAPPED) + break; px -= gd->sx; pywrap++; } @@ -2115,8 +2119,9 @@ static int window_copy_search_rl(struct grid *gd, struct grid *sgd, u_int *ppx, u_int py, u_int first, u_int last, int cis) { - u_int ax, bx, px, pywrap, endline; - int matched; + u_int ax, bx, px, pywrap, endline; + int matched; + struct grid_line *gl; endline = gd->hsize + gd->sy - 1; for (ax = last; ax > first; ax--) { @@ -2125,6 +2130,9 @@ window_copy_search_rl(struct grid *gd, pywrap = py; /* Wrap line. */ while (px >= gd->sx && pywrap < endline) { + gl = grid_get_line(gd, pywrap); + if (~gl->flags & GRID_LINE_WRAPPED) + break; px -= gd->sx; pywrap++; } From c284ebe0ade7cc85ad6c3fe5ce7ed5108119222d Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 12 Dec 2019 11:39:56 +0000 Subject: [PATCH 07/23] 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); } From 268f2b047a6e9b932a0ff51aa526e1df61d54165 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 12 Dec 2019 11:51:32 +0000 Subject: [PATCH 08/23] Do not check if client is dead if it is NULL. --- file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/file.c b/file.c index ccd62a94..f0d622bc 100644 --- a/file.c +++ b/file.c @@ -88,7 +88,7 @@ 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)) + if (cf->cb != NULL && (c == NULL || (~c->flags & CLIENT_DEAD))) cf->cb(c, cf->path, cf->error, 1, cf->buffer, cf->data); file_free(cf); } From 5134666702ce972390f39e34bed9b60fe566263a Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 12 Dec 2019 12:49:36 +0000 Subject: [PATCH 09/23] Change source-file to use new file code which allows it to read from stdin. --- cfg.c | 46 +++++++++++++++ cmd-source-file.c | 147 ++++++++++++++++++++++++++++++++++------------ tmux.h | 4 ++ 3 files changed, 159 insertions(+), 38 deletions(-) diff --git a/cfg.c b/cfg.c index fd7ee897..8509ad1b 100644 --- a/cfg.c +++ b/cfg.c @@ -195,6 +195,52 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item, int flags, return (0); } +int +load_cfg_from_buffer(const void *buf, size_t len, const char *path, + struct client *c, struct cmdq_item *item, int flags, + struct cmdq_item **new_item) +{ + struct cmd_parse_input pi; + struct cmd_parse_result *pr; + struct cmdq_item *new_item0; + + if (new_item != NULL) + *new_item = NULL; + + log_debug("loading %s", path); + + memset(&pi, 0, sizeof pi); + pi.flags = flags; + pi.file = path; + pi.line = 1; + pi.item = item; + pi.c = c; + + pr = cmd_parse_from_buffer(buf, len, &pi); + if (pr->status == CMD_PARSE_EMPTY) + return (0); + if (pr->status == CMD_PARSE_ERROR) { + cfg_add_cause("%s", pr->error); + free(pr->error); + return (-1); + } + if (flags & CMD_PARSE_PARSEONLY) { + cmd_list_free(pr->cmdlist); + return (0); + } + + new_item0 = cmdq_get_command(pr->cmdlist, NULL, NULL, 0); + if (item != NULL) + cmdq_insert_after(item, new_item0); + else + cmdq_append(NULL, new_item0); + cmd_list_free(pr->cmdlist); + + if (new_item != NULL) + *new_item = new_item0; + return (0); +} + void cfg_add_cause(const char *fmt, ...) { diff --git a/cmd-source-file.c b/cmd-source-file.c index b867db67..a07c27c7 100644 --- a/cmd-source-file.c +++ b/cmd-source-file.c @@ -32,8 +32,6 @@ static enum cmd_retval cmd_source_file_exec(struct cmd *, struct cmdq_item *); -static enum cmd_retval cmd_source_file_done(struct cmdq_item *, void *); - const struct cmd_entry cmd_source_file_entry = { .name = "source-file", .alias = "source", @@ -45,31 +43,115 @@ const struct cmd_entry cmd_source_file_entry = { .exec = cmd_source_file_exec }; +struct cmd_source_file_data { + struct cmdq_item *item; + int flags; + + struct cmdq_item *after; + enum cmd_retval retval; + + u_int current; + char **files; + u_int nfiles; +}; + +static enum cmd_retval +cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data) +{ + cfg_print_causes(item); + return (CMD_RETURN_NORMAL); +} + +static void +cmd_source_file_complete(struct client *c, struct cmd_source_file_data *cdata) +{ + struct cmdq_item *new_item; + + if (cfg_finished) { + if (cdata->retval == CMD_RETURN_ERROR && c->session == NULL) + c->retval = 1; + new_item = cmdq_get_callback(cmd_source_file_complete_cb, NULL); + cmdq_insert_after(cdata->after, new_item); + } + + free(cdata->files); + free(cdata); +} + +static void +cmd_source_file_done(struct client *c, const char *path, int error, + int closed, struct evbuffer *buffer, void *data) +{ + struct cmd_source_file_data *cdata = data; + struct cmdq_item *item = cdata->item; + void *bdata = EVBUFFER_DATA(buffer); + size_t bsize = EVBUFFER_LENGTH(buffer); + u_int n; + struct cmdq_item *new_item; + + if (!closed) + return; + + if (error != 0) + cmdq_error(item, "%s: %s", path, strerror(error)); + else if (bsize != 0) { + if (load_cfg_from_buffer(bdata, bsize, path, c, cdata->after, + cdata->flags, &new_item) < 0) + cdata->retval = CMD_RETURN_ERROR; + else if (new_item != NULL) + cdata->after = new_item; + } + + n = ++cdata->current; + if (n < cdata->nfiles) + file_read(c, cdata->files[n], cmd_source_file_done, cdata); + else { + cmd_source_file_complete(c, cdata); + cmdq_continue(item); + } +} + +static void +cmd_source_file_add(struct cmd_source_file_data *cdata, const char *path) +{ + cdata->files = xreallocarray(cdata->files, cdata->nfiles + 1, + sizeof *cdata->files); + cdata->files[cdata->nfiles++] = xstrdup(path); +} + static enum cmd_retval cmd_source_file_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - int flags = 0; - struct client *c = item->client; - struct cmdq_item *new_item, *after; - enum cmd_retval retval; - char *pattern, *cwd; - const char *path, *error; - glob_t g; - int i; - u_int j; + struct args *args = self->args; + struct cmd_source_file_data *cdata; + int flags = 0; + struct client *c = item->client; + enum cmd_retval retval = CMD_RETURN_NORMAL; + char *pattern, *cwd; + const char *path, *error; + glob_t g; + int i; + u_int j; + + cdata = xcalloc(1, sizeof *cdata); + cdata->item = item; if (args_has(args, 'q')) - flags |= CMD_PARSE_QUIET; + cdata->flags |= CMD_PARSE_QUIET; if (args_has(args, 'n')) - flags |= CMD_PARSE_PARSEONLY; + cdata->flags |= CMD_PARSE_PARSEONLY; if (args_has(args, 'v')) - flags |= CMD_PARSE_VERBOSE; + cdata->flags |= CMD_PARSE_VERBOSE; + utf8_stravis(&cwd, server_client_get_cwd(c, NULL), VIS_GLOB); - retval = CMD_RETURN_NORMAL; for (i = 0; i < args->argc; i++) { path = args->argv[i]; + if (strcmp(path, "-") == 0) { + cmd_source_file_add(cdata, "-"); + continue; + } + if (*path == '/') pattern = xstrdup(path); else @@ -87,30 +169,19 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item) } free(pattern); - after = item; - for (j = 0; j < g.gl_pathc; j++) { - path = g.gl_pathv[j]; - if (load_cfg(path, c, after, flags, &new_item) < 0) - retval = CMD_RETURN_ERROR; - else if (new_item != NULL) - after = new_item; - } - globfree(&g); - } - if (cfg_finished) { - if (retval == CMD_RETURN_ERROR && c->session == NULL) - c->retval = 1; - new_item = cmdq_get_callback(cmd_source_file_done, NULL); - cmdq_insert_after(item, new_item); + for (j = 0; j < g.gl_pathc; j++) + cmd_source_file_add(cdata, g.gl_pathv[j]); } + cdata->after = item; + cdata->retval = retval; + + if (cdata->nfiles != 0) { + file_read(c, cdata->files[0], cmd_source_file_done, cdata); + retval = CMD_RETURN_WAIT; + } else + cmd_source_file_complete(c, cdata); + free(cwd); return (retval); } - -static enum cmd_retval -cmd_source_file_done(struct cmdq_item *item, __unused void *data) -{ - cfg_print_causes(item); - return (CMD_RETURN_NORMAL); -} diff --git a/tmux.h b/tmux.h index d8308e1a..60e55475 100644 --- a/tmux.h +++ b/tmux.h @@ -1803,6 +1803,8 @@ extern struct client *cfg_client; void start_cfg(void); int load_cfg(const char *, struct client *, struct cmdq_item *, int, struct cmdq_item **); +int load_cfg_from_buffer(const void *, size_t, const char *, + struct client *, struct cmdq_item *, int, struct cmdq_item **); void set_cfg_file(const char *); void printflike(1, 2) cfg_add_cause(const char *, ...); void cfg_print_causes(struct cmdq_item *); @@ -2120,6 +2122,8 @@ void cmd_parse_empty(struct cmd_parse_input *); struct cmd_parse_result *cmd_parse_from_file(FILE *, struct cmd_parse_input *); struct cmd_parse_result *cmd_parse_from_string(const char *, struct cmd_parse_input *); +struct cmd_parse_result *cmd_parse_from_buffer(const void *, size_t, + struct cmd_parse_input *); struct cmd_parse_result *cmd_parse_from_arguments(int, char **, struct cmd_parse_input *); From 2b2b19379110fd90f4d84fb973628900f2d3fd4d Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 12 Dec 2019 15:01:54 +0000 Subject: [PATCH 10/23] Add function to the right file. --- cmd-parse.y | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cmd-parse.y b/cmd-parse.y index 2c924010..97b50f57 100644 --- a/cmd-parse.y +++ b/cmd-parse.y @@ -745,6 +745,12 @@ cmd_parse_from_file(FILE *f, struct cmd_parse_input *pi) struct cmd_parse_result * cmd_parse_from_string(const char *s, struct cmd_parse_input *pi) +{ + return (cmd_parse_from_buffer(s, strlen(s), pi)); +} + +struct cmd_parse_result * +cmd_parse_from_buffer(const void *buf, size_t len, struct cmd_parse_input *pi) { static struct cmd_parse_result pr; struct cmd_parse_input input; @@ -757,14 +763,14 @@ cmd_parse_from_string(const char *s, struct cmd_parse_input *pi) } memset(&pr, 0, sizeof pr); - if (*s == '\0') { + if (len == 0) { pr.status = CMD_PARSE_EMPTY; pr.cmdlist = NULL; pr.error = NULL; return (&pr); } - cmds = cmd_parse_do_buffer(s, strlen(s), pi, &cause); + cmds = cmd_parse_do_buffer(buf, len, pi, &cause); if (cmds == NULL) { pr.status = CMD_PARSE_ERROR; pr.error = cause; From dcf41ec9275a2a1a87a5943d9ba189d83de67a89 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 12 Dec 2019 15:03:13 +0000 Subject: [PATCH 11/23] Do not crash in tree modes if the pane is only 1 in width, reported by KOIE Hidetaka in GitHub issue 2015. --- mode-tree.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mode-tree.c b/mode-tree.c index 054989fb..b9fa5f65 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -598,6 +598,8 @@ mode_tree_draw(struct mode_tree_data *mtd) xasprintf(&text, "%-*s%s%s%s: ", keylen, key, start, mti->name, tag); width = utf8_cstrwidth(text); + if (width > w) + width = w; free(start); if (mti->tagged) { @@ -607,11 +609,11 @@ mode_tree_draw(struct mode_tree_data *mtd) if (i != mtd->current) { screen_write_clearendofline(&ctx, 8); - screen_write_puts(&ctx, &gc0, "%s", text); + screen_write_nputs(&ctx, w, &gc0, "%s", text); format_draw(&ctx, &gc0, w - width, mti->text, NULL); } else { screen_write_clearendofline(&ctx, gc.bg); - screen_write_puts(&ctx, &gc, "%s", text); + screen_write_nputs(&ctx, w, &gc, "%s", text); format_draw(&ctx, &gc, w - width, mti->text, NULL); } free(text); From 828001ecc51d94f8e7a7f0da1f42204984ede2e9 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 13 Dec 2019 06:55:12 +0000 Subject: [PATCH 12/23] Do not spin waiting for exit, instead check in the write callback. --- client.c | 51 +++++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/client.c b/client.c index db3ad07c..11b13dc2 100644 --- a/client.c +++ b/client.c @@ -45,6 +45,7 @@ static enum { CLIENT_EXIT_EXITED, CLIENT_EXIT_SERVER_EXITED, } client_exitreason = CLIENT_EXIT_NONE; +static int client_exitflag; static int client_exitval; static enum msgtype client_exittype; static const char *client_exitsession; @@ -209,6 +210,27 @@ client_exit_message(void) return ("unknown reason"); } +/* Exit if all streams flushed. */ +static void +client_exit(void) +{ + 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); +} + /* Client main loop. */ int client_main(struct event_base *base, int argc, char **argv, int flags) @@ -451,6 +473,9 @@ client_write_callback(__unused struct bufferevent *bev, void *arg) RB_REMOVE(client_files, &client_files, cf); file_free(cf); } + + if (client_exitflag) + client_exit(); } /* Open write file. */ @@ -724,29 +749,6 @@ 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) @@ -799,7 +801,8 @@ client_dispatch_wait(struct imsg *imsg) memcpy(&retval, data, sizeof retval); client_exitval = retval; } - event_once(-1, EV_TIMEOUT, client_exit, NULL, NULL); + client_exitflag = 1; + client_exit(); break; case MSG_READY: if (datalen != 0) From 6ce943f4d979423290662f691e3d8a3cded79a42 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 13 Dec 2019 07:00:22 +0000 Subject: [PATCH 13/23] Need to check in the error callback also. --- client.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client.c b/client.c index 11b13dc2..4bebb977 100644 --- a/client.c +++ b/client.c @@ -459,6 +459,9 @@ client_write_error_callback(__unused struct bufferevent *bev, close(cf->fd); cf->fd = -1; + + if (client_exitflag) + client_exit(); } /* File write callback. */ From 21f9b39f060006fe769034ac2bb9b71d0a910f80 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 13 Dec 2019 09:15:13 +0000 Subject: [PATCH 14/23] Show UTF-8 in choose-buffer mode. From KOIE Hidetaka. --- window-buffer.c | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/window-buffer.c b/window-buffer.c index 4091bc07..e03dfa28 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -208,9 +208,9 @@ window_buffer_draw(__unused void *modedata, void *itemdata, { struct window_buffer_itemdata *item = itemdata; struct paste_buffer *pb; - char line[1024]; - const char *pdata, *end, *cp; - size_t psize, at; + const char *pdata, *start, *end; + char *buf = NULL; + size_t psize, len; u_int i, cx = ctx->s->cx, cy = ctx->s->cy; pb = paste_get_name(item->name); @@ -219,27 +219,22 @@ window_buffer_draw(__unused void *modedata, void *itemdata, pdata = end = paste_buffer_data(pb, &psize); for (i = 0; i < sy; i++) { - at = 0; - while (end != pdata + psize && *end != '\n') { - if ((sizeof line) - at > 5) { - cp = vis(line + at, *end, VIS_OCTAL|VIS_TAB, 0); - at = cp - line; - } + start = end; + while (end != pdata + psize && *end != '\n') end++; - } - if (at > sx) - at = sx; - line[at] = '\0'; - - if (*line != '\0') { + buf = xreallocarray(buf, 4, end - start + 1); + len = utf8_strvis(buf, start, end - start, VIS_OCTAL|VIS_TAB); + if (*buf != '\0') { screen_write_cursormove(ctx, cx, cy + i, 0); - screen_write_puts(ctx, &grid_default_cell, "%s", line); + screen_write_nputs(ctx, sx, &grid_default_cell, "%s", + buf); } if (end == pdata + psize) break; end++; } + free(buf); } static int From e6b02dec199a0f058aee4e9575f57d92b39501f5 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Fri, 13 Dec 2019 11:31:53 +0000 Subject: [PATCH 15/23] Add to CHANGES. --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index a08ed526..cd3a1b8a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,9 @@ CHANGES FROM 3.0 to X.X +* Change file reading and writing to go through the client if necessary. This + fixes commands like "tmux loadb /dev/fd/X". Also modify source-file to + support "-" for standard input, like load-buffer and save-buffer. + * Add ~/.config/tmux/tmux.conf to the default search path for configuration files. From eaa58d28dc7da9b2ef0d77f4c8e85aab55b71935 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 16 Dec 2019 15:48:50 +0000 Subject: [PATCH 16/23] Instead of using large buffers in imsgs, add the data or path onto the end. --- client.c | 48 ++++++++++++++++++++++++++-------------- file.c | 59 ++++++++++++++++++++++++++++++------------------- server-client.c | 6 ++--- tmux.h | 12 +++------- 4 files changed, 73 insertions(+), 52 deletions(-) diff --git a/client.c b/client.c index 4bebb977..38fe52cd 100644 --- a/client.c +++ b/client.c @@ -486,14 +486,19 @@ static void client_write_open(void *data, size_t datalen) { struct msg_write_open *msg = data; + const char *path; 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) + if (datalen < sizeof *msg) fatalx("bad MSG_WRITE_OPEN size"); - log_debug("open write file %d %s", msg->stream, msg->path); + if (datalen == sizeof *msg) + path = "-"; + else + path = (const char *)(msg + 1); + log_debug("open write file %d %s", msg->stream, path); find.stream = msg->stream; if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL) { @@ -510,7 +515,7 @@ client_write_open(void *data, size_t datalen) cf->fd = -1; if (msg->fd == -1) - cf->fd = open(msg->path, msg->flags|flags, 0644); + cf->fd = open(path, msg->flags|flags, 0644); else { if (msg->fd != STDOUT_FILENO && msg->fd != STDERR_FILENO) errno = EBADF; @@ -542,16 +547,17 @@ client_write_data(void *data, size_t datalen) { struct msg_write_data *msg = data; struct client_file find, *cf; + size_t size = datalen - sizeof *msg; - if (datalen != sizeof *msg) + 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); + log_debug("write %zu to file %d", size, cf->stream); if (cf->event != NULL) - bufferevent_write(cf->event, msg->data, msg->size); + bufferevent_write(cf->event, msg + 1, size); } /* Close client file. */ @@ -585,26 +591,29 @@ client_read_callback(__unused struct bufferevent *bev, void *arg) struct client_file *cf = arg; void *bdata; size_t bsize; - struct msg_read_data msg; + struct msg_read_data *msg; + size_t msglen; + msg = xmalloc(sizeof *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; + if (bsize > MAX_IMSGSIZE - IMSG_HEADER_SIZE) + bsize = MAX_IMSGSIZE - IMSG_HEADER_SIZE; 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); + msglen = (sizeof *msg) + bsize; + msg = xrealloc(msg, msglen); + msg->stream = cf->stream; + memcpy(msg + 1, bdata, bsize); + proc_send(client_peer, MSG_READ, -1, msg, msglen); evbuffer_drain(cf->event->input, bsize); } + free(msg); } /* File read error callback. */ @@ -632,14 +641,19 @@ static void client_read_open(void *data, size_t datalen) { struct msg_read_open *msg = data; + const char *path; struct msg_read_done reply; struct client_file find, *cf; const int flags = O_NONBLOCK|O_RDONLY; int error = 0; - if (datalen != sizeof *msg) + if (datalen < sizeof *msg) fatalx("bad MSG_READ_OPEN size"); - log_debug("open read file %d %s", msg->stream, msg->path); + if (datalen == sizeof *msg) + path = "-"; + else + path = (const char *)(msg + 1); + log_debug("open read file %d %s", msg->stream, path); find.stream = msg->stream; if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL) { @@ -656,7 +670,7 @@ client_read_open(void *data, size_t datalen) cf->fd = -1; if (msg->fd == -1) - cf->fd = open(msg->path, flags); + cf->fd = open(path, flags); else { if (msg->fd != STDIN_FILENO) errno = EBADF; diff --git a/file.c b/file.c index f0d622bc..7e1f1879 100644 --- a/file.c +++ b/file.c @@ -17,9 +17,11 @@ */ #include +#include #include #include +#include #include #include #include @@ -147,7 +149,6 @@ file_vprint(struct client *c, const char *fmt, va_list 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); @@ -174,7 +175,6 @@ file_print_buffer(struct client *c, void *data, size_t 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); @@ -204,7 +204,6 @@ file_error(struct client *c, const char *fmt, ...) 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); @@ -220,7 +219,8 @@ file_write(struct client *c, const char *path, int flags, const void *bdata, { struct client_file *cf; FILE *f; - struct msg_write_open msg; + struct msg_write_open *msg; + size_t msglen; int fd = -1; const char *mode; @@ -261,17 +261,22 @@ file_write(struct client *c, const char *path, int flags, const void *bdata, 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) { + msglen = strlen(cf->path) + 1 + sizeof *msg; + if (msglen > MAX_IMSGSIZE - IMSG_HEADER_SIZE) { cf->error = E2BIG; goto done; } - if (proc_send(c->peer, MSG_WRITE_OPEN, -1, &msg, sizeof msg) != 0) { + msg = xmalloc(msglen); + msg->stream = cf->stream; + msg->fd = fd; + msg->flags = flags; + memcpy(msg + 1, cf->path, msglen - sizeof *msg); + if (proc_send(c->peer, MSG_WRITE_OPEN, -1, msg, msglen) != 0) { + free(msg); cf->error = EINVAL; goto done; } + free(msg); return; done: @@ -283,10 +288,10 @@ 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; + struct msg_read_open *msg; + size_t msglen, size; int fd = -1; char buffer[BUFSIZ]; - size_t size; if (strcmp(path, "-") == 0) { cf = file_create(c, file_next_stream++, cb, cbdata); @@ -327,16 +332,21 @@ file_read(struct client *c, const char *path, client_file_cb cb, void *cbdata) } skip: - msg.stream = cf->stream; - msg.fd = fd; - if (strlcpy(msg.path, cf->path, sizeof msg.path) >= sizeof msg.path) { + msglen = strlen(cf->path) + 1 + sizeof *msg; + if (msglen > MAX_IMSGSIZE - IMSG_HEADER_SIZE) { cf->error = E2BIG; goto done; } - if (proc_send(c->peer, MSG_READ_OPEN, -1, &msg, sizeof msg) != 0) { + msg = xmalloc(msglen); + msg->stream = cf->stream; + msg->fd = fd; + memcpy(msg + 1, cf->path, msglen - sizeof *msg); + if (proc_send(c->peer, MSG_READ_OPEN, -1, msg, msglen) != 0) { + free(msg); cf->error = EINVAL; goto done; } + free(msg); return; done: @@ -358,20 +368,22 @@ void file_push(struct client_file *cf) { struct client *c = cf->c; - struct msg_write_data msg; + struct msg_write_data *msg; + size_t msglen, sent, left; struct msg_write_close close; - size_t sent, left; + msg = xmalloc(sizeof *msg); 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; + if (sent > MAX_IMSGSIZE - IMSG_HEADER_SIZE) + sent = MAX_IMSGSIZE - IMSG_HEADER_SIZE; - msg.stream = cf->stream; - if (proc_send(c->peer, MSG_WRITE, -1, &msg, sizeof msg) != 0) + msglen = (sizeof *msg) + sent; + msg = xrealloc(msg, msglen); + msg->stream = cf->stream; + memcpy(msg + 1, EVBUFFER_DATA(cf->buffer), sent); + if (proc_send(c->peer, MSG_WRITE, -1, msg, msglen) != 0) break; evbuffer_drain(cf->buffer, sent); @@ -387,4 +399,5 @@ file_push(struct client_file *cf) proc_send(c->peer, MSG_WRITE_CLOSE, -1, &close, sizeof close); file_fire_done(cf); } + free(msg); } diff --git a/server-client.c b/server-client.c index e6e4d8a9..fdedc35f 100644 --- a/server-client.c +++ b/server-client.c @@ -2024,10 +2024,10 @@ 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; + void *bdata = msg + 1; + size_t bsize = msglen - sizeof *msg; - if (msglen != sizeof *msg) + if (msglen < sizeof *msg) fatalx("bad MSG_READ_DATA size"); find.stream = msg->stream; if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) diff --git a/tmux.h b/tmux.h index 60e55475..84b2056c 100644 --- a/tmux.h +++ b/tmux.h @@ -507,13 +507,10 @@ struct msg_command { struct msg_read_open { int stream; int fd; - char path[PATH_MAX]; -}; +}; /* followed by path */ struct msg_read_data { int stream; - size_t size; - char data[BUFSIZ]; }; struct msg_read_done { @@ -524,15 +521,12 @@ struct msg_read_done { struct msg_write_open { int stream; int fd; - char path[PATH_MAX]; int flags; -}; +}; /* followed by path */ struct msg_write_data { int stream; - size_t size; - char data[BUFSIZ]; -}; +}; /* followed by data */ struct msg_write_ready { int stream; From b4520aaf2cb56cd14519e2df9d99ea6efc8ddd03 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 16 Dec 2019 16:09:28 +0000 Subject: [PATCH 17/23] Need to include message size in the maximum buffer calculation. --- client.c | 4 ++-- file.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client.c b/client.c index 38fe52cd..a36c6471 100644 --- a/client.c +++ b/client.c @@ -601,8 +601,8 @@ client_read_callback(__unused struct bufferevent *bev, void *arg) if (bsize == 0) break; - if (bsize > MAX_IMSGSIZE - IMSG_HEADER_SIZE) - bsize = MAX_IMSGSIZE - IMSG_HEADER_SIZE; + if (bsize > MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg) + bsize = MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg; log_debug("read %zu from file %d", bsize, cf->stream); msglen = (sizeof *msg) + bsize; diff --git a/file.c b/file.c index 7e1f1879..9a3e79de 100644 --- a/file.c +++ b/file.c @@ -376,8 +376,8 @@ file_push(struct client_file *cf) left = EVBUFFER_LENGTH(cf->buffer); while (left != 0) { sent = left; - if (sent > MAX_IMSGSIZE - IMSG_HEADER_SIZE) - sent = MAX_IMSGSIZE - IMSG_HEADER_SIZE; + if (sent > MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg) + sent = MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg; msglen = (sizeof *msg) + sent; msg = xrealloc(msg, msglen); From 1bdd4828bd0a13d6fb81c903078ad99ff2429b5d Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 16 Dec 2019 16:39:03 +0000 Subject: [PATCH 18/23] If /dev/fd/X is a symlink and realpath() expands symlinks, /dev/fd/X ends up pointing to the wrong place before it is passed to the client. The path is only used internally so there is no real need for realpath(), remove it and move the get_path function to file.c where all the callers are. --- file.c | 16 ++++++++++++++-- server-client.c | 16 ---------------- tmux.h | 1 - 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/file.c b/file.c index 9a3e79de..439d3464 100644 --- a/file.c +++ b/file.c @@ -33,6 +33,18 @@ static int file_next_stream = 3; RB_GENERATE(client_files, client_file, entry, file_cmp); +static char * +file_get_path(struct client *c, const char *file) +{ + char *path; + + if (*file == '/') + path = xstrdup(file); + else + xasprintf(&path, "%s/%s", server_client_get_cwd(c, NULL), file); + return (path); +} + int file_cmp(struct client_file *cf1, struct client_file *cf2) { @@ -237,7 +249,7 @@ file_write(struct client *c, const char *path, int flags, const void *bdata, } cf = file_create(c, file_next_stream++, cb, cbdata); - cf->path = server_client_get_path(c, path); + cf->path = file_get_path(c, path); if (c == NULL || c->flags & CLIENT_ATTACHED) { if (flags & O_APPEND) @@ -306,7 +318,7 @@ file_read(struct client *c, const char *path, client_file_cb cb, void *cbdata) } cf = file_create(c, file_next_stream++, cb, cbdata); - cf->path = server_client_get_path(c, path); + cf->path = file_get_path(c, path); if (c == NULL || c->flags & CLIENT_ATTACHED) { f = fopen(cf->path, "rb"); diff --git a/server-client.c b/server-client.c index fdedc35f..ee7b4c70 100644 --- a/server-client.c +++ b/server-client.c @@ -2111,19 +2111,3 @@ server_client_get_cwd(struct client *c, struct session *s) return (home); return ("/"); } - -/* Resolve an absolute path or relative to client working directory. */ -char * -server_client_get_path(struct client *c, const char *file) -{ - char *path, resolved[PATH_MAX]; - - if (*file == '/') - path = xstrdup(file); - else - xasprintf(&path, "%s/%s", server_client_get_cwd(c, NULL), file); - if (realpath(path, resolved) == NULL) - return (path); - free(path); - return (xstrdup(resolved)); -} diff --git a/tmux.h b/tmux.h index 84b2056c..8b23bfae 100644 --- a/tmux.h +++ b/tmux.h @@ -2228,7 +2228,6 @@ void server_client_push_stdout(struct client *); void server_client_push_stderr(struct client *); void printflike(2, 3) server_client_add_message(struct client *, const char *, ...); -char *server_client_get_path(struct client *, const char *); const char *server_client_get_cwd(struct client *, struct session *); /* server-fn.c */ From 479d411ddacf61da4c19ed706cb880f6caa7a663 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 16 Dec 2019 20:01:26 +0000 Subject: [PATCH 19/23] Remove imsg.h. --- file.c | 1 - 1 file changed, 1 deletion(-) diff --git a/file.c b/file.c index 439d3464..2c06765c 100644 --- a/file.c +++ b/file.c @@ -21,7 +21,6 @@ #include #include -#include #include #include #include From 3879509426d8d269acb7b2aec3eb04f5b5a141d3 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 16 Dec 2019 21:34:36 +0000 Subject: [PATCH 20/23] Define FNM_CASEFOLD to 0 for AIX, from Eric N Vander Weele. --- compat.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compat.h b/compat.h index df1ac03c..794ac67d 100644 --- a/compat.h +++ b/compat.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -169,6 +170,10 @@ void warnx(const char *, ...); #define O_DIRECTORY 0 #endif +#ifndef FNM_CASEFOLD +#define FNM_CASEFOLD 0 +#endif + #ifndef INFTIM #define INFTIM -1 #endif From f8cb759bdbf767348dba1ae841a86f2bdafe3f25 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 17 Dec 2019 11:43:23 +0000 Subject: [PATCH 21/23] Use the message that has already been built rather than the va_list. --- cmd-queue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd-queue.c b/cmd-queue.c index 6019ab51..69e4f6b2 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -512,7 +512,7 @@ cmdq_print(struct cmdq_item *item, const char *fmt, ...) wme = TAILQ_FIRST(&wp->modes); if (wme == NULL || wme->mode != &window_view_mode) window_pane_set_mode(wp, &window_view_mode, NULL, NULL); - window_copy_vadd(wp, fmt, ap); + window_copy_add(wp, "%s", msg); } free(msg); From ef54a08080ef7d721d05361bf10e27217c87590e Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 18 Dec 2019 07:48:56 +0000 Subject: [PATCH 22/23] Do not rely on errno after glob(3) fails. --- cmd-source-file.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cmd-source-file.c b/cmd-source-file.c index a07c27c7..6af1a6d0 100644 --- a/cmd-source-file.c +++ b/cmd-source-file.c @@ -130,7 +130,7 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item) char *pattern, *cwd; const char *path, *error; glob_t g; - int i; + int i, result; u_int j; cdata = xcalloc(1, sizeof *cdata); @@ -158,9 +158,15 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item) xasprintf(&pattern, "%s/%s", cwd, path); log_debug("%s: %s", __func__, pattern); - if (glob(pattern, 0, NULL, &g) != 0) { - error = strerror(errno); - if (errno != ENOENT || (~flags & CMD_PARSE_QUIET)) { + if ((result = glob(pattern, 0, NULL, &g)) != 0) { + if (result != GLOB_NOMATCH || + (~flags & CMD_PARSE_QUIET)) { + if (result == GLOB_NOMATCH) + error = strerror(ENOENT); + else if (result == GLOB_NOSPACE) + error = strerror(ENOMEM); + else + error = strerror(EINVAL); cmdq_error(item, "%s: %s", path, error); retval = CMD_RETURN_ERROR; } From 54efe337993b5571728a09b247c7f39d493659a8 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 18 Dec 2019 15:58:06 +0000 Subject: [PATCH 23/23] Add back utempter code, reported by Peter Schellenbach. --- spawn.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spawn.c b/spawn.c index 54021817..51e4ae78 100644 --- a/spawn.c +++ b/spawn.c @@ -433,6 +433,15 @@ spawn_pane(struct spawn_context *sc, char **cause) _exit(1); complete: +#ifdef HAVE_UTEMPTER + if (~new_wp->flags & PANE_EMPTY) { + xasprintf(&cp, "tmux(%lu).%%%u", (long)getpid(), new_wp->id); + utempter_add_record(new_wp->fd, cp); + kill(getpid(), SIGCHLD); + free(cp); + } +#endif + new_wp->pipe_off = 0; new_wp->flags &= ~PANE_EXITED;