summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile1
-rw-r--r--client.c330
-rw-r--r--cmd-capture-pane.c15
-rw-r--r--cmd-load-buffer.c160
-rw-r--r--cmd-queue.c34
-rw-r--r--cmd-save-buffer.c98
-rw-r--r--control-notify.c2
-rw-r--r--control.c36
-rw-r--r--file.c390
-rw-r--r--server-client.c213
-rw-r--r--server-fn.c30
-rw-r--r--tmux.h108
-rw-r--r--window.c27
13 files changed, 949 insertions, 495 deletions
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,39 +423,234 @@ client_send_identify(const char *ttynam, const char *cwd)
proc_send(client_peer, MSG_IDENTIFY_DONE, -1, NULL, 0);
}
-/* Callback for client stdin read events. */
+/* File write error callback. */
static void
-client_stdin_callback(__unused int fd, __unused short events,
- __unused void *arg)
+client_write_error_callback(__unused struct bufferevent *bev,
+ __unused short what, void *arg)
{
- struct msg_stdin_data data;
+ struct client_file *cf = arg;
- data.size = read(STDIN_FILENO, data.data, sizeof data.data);
- if (data.size == -1 && (errno == EINTR || errno == EAGAIN))
- return;
+ log_debug("write error file %d", cf->stream);
+
+ bufferevent_free(cf->event);
+ cf->event = NULL;
+
+ close(cf->fd);
+ cf->fd = -1;
+}
+
+/* File write callback. */
+static void
+client_write_callback(__unused struct bufferevent *bev, void *arg)
+{
+ struct client_file *cf = arg;
+
+ if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0) {
+ bufferevent_free(cf->event);
+ close(cf->fd);
+ RB_REMOVE(client_files, &client_files, cf);
+ file_free(cf);
+ }
+}
+
+/* Open write file. */
+static void
+client_write_open(void *data, size_t datalen)
+{
+ struct msg_write_open *msg = data;
+ struct msg_write_ready reply;
+ struct client_file find, *cf;
+ const int flags = O_NONBLOCK|O_WRONLY|O_CREAT;
+ int error = 0;
+
+ if (datalen != sizeof *msg)
+ fatalx("bad MSG_WRITE_OPEN size");
+ log_debug("open write file %d %s", msg->stream, msg->path);
+
+ find.stream = msg->stream;
+ if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL) {
+ cf = file_create(NULL, msg->stream, NULL, NULL);
+ RB_INSERT(client_files, &client_files, cf);
+ } else {
+ error = EBADF;
+ goto reply;
+ }
+ if (cf->closed) {
+ error = EBADF;
+ goto reply;
+ }
+
+ cf->fd = -1;
+ if (msg->fd == -1)
+ cf->fd = open(msg->path, msg->flags|flags, 0644);
+ else {
+ if (msg->fd != STDOUT_FILENO && msg->fd != STDERR_FILENO)
+ errno = EBADF;
+ else {
+ cf->fd = dup(msg->fd);
+ if (client_flags & CLIENT_CONTROL)
+ close(msg->fd); /* can only be used once */
+ }
+ }
+ if (cf->fd == -1) {
+ error = errno;
+ goto reply;
+ }
- proc_send(client_peer, MSG_STDIN, -1, &data, sizeof data);
- if (data.size <= 0)
- event_del(&client_stdin);
+ cf->event = bufferevent_new(cf->fd, NULL, client_write_callback,
+ client_write_error_callback, cf);
+ bufferevent_enable(cf->event, EV_WRITE);
+ goto reply;
+
+reply:
+ reply.stream = msg->stream;
+ reply.error = error;
+ proc_send(client_peer, MSG_WRITE_READY, -1, &reply, sizeof reply);
+}
+
+/* Write to client file. */
+static void
+client_write_data(void *data, size_t datalen)
+{
+ struct msg_write_data *msg = data;
+ struct client_file find, *cf;
+
+ if (datalen != sizeof *msg)
+ fatalx("bad MSG_WRITE size");
+ find.stream = msg->stream;
+ if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL)
+ fatalx("unknown stream number");
+ log_debug("write %zu to file %d", msg->size, cf->stream);
+
+ if (cf->event != NULL)
+ bufferevent_write(cf->event, msg->data, msg->size);
+}
+
+/* Close client file. */
+static void
+client_write_close(void *data, size_t datalen)
+{
+ struct msg_write_close *msg = data;
+ struct client_file find, *cf;
+
+ if (datalen != sizeof *msg)
+ fatalx("bad MSG_WRITE_CLOSE size");
+ find.stream = msg->stream;
+ if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL)
+ fatalx("unknown stream number");
+ log_debug("close file %d", cf->stream);
+
+ if (cf->event == NULL || EVBUFFER_LENGTH(cf->event->output) == 0) {
+ if (cf->event != NULL)
+ bufferevent_free(cf->event);
+ if (cf->fd != -1)
+ close(cf->fd);
+ RB_REMOVE(client_files, &client_files, cf);
+ file_free(cf);
+ }
}
-/* Force write to file descriptor. */
+/* File read callback. */
static void
-client_write(int fd, const char *data, size_t size)
+client_read_callback(__unused struct bufferevent *bev, void *arg)
{
- ssize_t used;
-
- log_debug("%s: %.*s", __func__, (int)size, data);
- while (size != 0) {
- used = write(fd, data, size);
- if (used == -1) {
- if (errno == EINTR || errno == EAGAIN)
- continue;
+ struct client_file *cf = arg;
+ void *bdata;
+ size_t bsize;
+ struct msg_read_data msg;
+
+ for (;;) {
+ bdata = EVBUFFER_DATA(cf->event->input);
+ bsize = EVBUFFER_LENGTH(cf->event->input);
+
+ if (bsize == 0)
break;
+ if (bsize > sizeof msg.data)
+ bsize = sizeof msg.data;
+ log_debug("read %zu from file %d", bsize, cf->stream);
+
+ memcpy(msg.data, bdata, bsize);
+ msg.size = bsize;
+
+ msg.stream = cf->stream;
+ proc_send(client_peer, MSG_READ, -1, &msg, sizeof msg);
+
+ evbuffer_drain(cf->event->input, bsize);
+ }
+}
+
+/* File read error callback. */
+static void
+client_read_error_callback(__unused struct bufferevent *bev,
+ __unused short what, void *arg)
+{
+ struct client_file *cf = arg;
+ struct msg_read_done msg;
+
+ log_debug("read error file %d", cf->stream);
+
+ msg.stream = cf->stream;
+ msg.error = 0;
+ proc_send(client_peer, MSG_READ_DONE, -1, &msg, sizeof msg);
+
+ bufferevent_free(cf->event);
+ close(cf->fd);
+ RB_REMOVE(client_files, &client_files, cf);
+ file_free(cf);
+}
+
+/* Open read file. */
+static void
+client_read_open(void *data, size_t datalen)
+{
+ struct msg_read_open *msg = data;
+ struct msg_read_done reply;
+ struct client_file find, *cf;
+ const int flags = O_NONBLOCK|O_RDONLY;
+ int error = 0;
+
+ if (datalen != sizeof *msg)
+ fatalx("bad MSG_READ_OPEN size");
+ log_debug("open read file %d %s", msg->stream, msg->path);
+
+ find.stream = msg->stream;
+ if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL) {
+ cf = file_create(NULL, msg->stream, NULL, NULL);
+ RB_INSERT(client_files, &client_files, cf);
+ } else {
+ error = EBADF;
+ goto reply;
+ }
+ if (cf->closed) {
+ error = EBADF;
+ goto reply;
+ }
+
+ cf->fd = -1;
+ if (msg->fd == -1)
+ cf->fd = open(msg->path, flags);
+ else {
+ if (msg->fd != STDIN_FILENO)
+ errno = EBADF;
+ else {
+ cf->fd = dup(msg->fd);
+ close(msg->fd); /* can only be used once */
}
- data += used;
- size -= used;
}
+ if (cf->fd == -1) {
+ error = errno;
+ goto reply;
+ }
+
+ cf->event = bufferevent_new(cf->fd, client_read_callback, NULL,
+ client_read_error_callback, cf);
+ bufferevent_enable(cf->event, EV_READ);
+ return;
+
+reply:
+ reply.stream = msg->stream;
+ reply.error = error;
+ proc_send(client_peer, MSG_READ_DONE, -1, &reply, sizeof reply);
}
/* Run command in shell; used for -c. */
@@ -532,6 +724,29 @@ client_signal(int sig)
}
}
+/* Exit if all streams flushed. */
+static void
+client_exit(__unused int fd, __unused short events, __unused void *arg)
+{
+ struct client_file *cf;
+ size_t left;
+ int waiting = 0;
+
+ RB_FOREACH (cf, client_files, &client_files) {
+ if (cf->event == NULL)
+ continue;
+ left = EVBUFFER_LENGTH(cf->event->output);
+ if (left != 0) {
+ waiting++;
+ log_debug("file %u %zu bytes left", cf->stream, left);
+ }
+ }
+ if (waiting == 0)
+ proc_exit(client_proc);
+ else
+ event_once(-1, EV_TIMEOUT, client_exit, NULL, NULL);
+}
+
/* Callback for client read events. */
static void
client_dispatch(struct imsg *imsg, __unused void *arg)
@@ -553,12 +768,10 @@ client_dispatch(struct imsg *imsg, __unused void *arg)
static void
client_dispatch_wait(struct imsg *imsg)
{
- char *data;
- ssize_t datalen;
- struct msg_stdout_data stdoutdata;
- struct msg_stderr_data stderrdata;
- int retval;
- static int pledge_applied;
+ char *data;
+ ssize_t datalen;
+ int retval;
+ static int pledge_applied;
/*
* "sendfd" is no longer required once all of the identify messages
@@ -567,10 +780,12 @@ client_dispatch_wait(struct imsg *imsg)
* get the first message from the server.
*/
if (!pledge_applied) {
- if (pledge("stdio unix proc exec tty", NULL) != 0)
+ if (pledge(
+ "stdio rpath wpath cpath unix proc exec tty",
+ NULL) != 0)
fatal("pledge failed");
pledge_applied = 1;
- };
+ }
data = imsg->data;
datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
@@ -584,38 +799,15 @@ client_dispatch_wait(struct imsg *imsg)
memcpy(&retval, data, sizeof retval);
client_exitval = retval;
}
- proc_exit(client_proc);
+ event_once(-1, EV_TIMEOUT, client_exit, NULL, NULL);
break;
case MSG_READY:
if (datalen != 0)
fatalx("bad MSG_READY size");
- event_del(&client_stdin);
client_attached = 1;
proc_send(client_peer, MSG_RESIZE, -1, NULL, 0);
break;
- case MSG_STDIN:
- if (datalen != 0)
- fatalx("bad MSG_STDIN size");
-
- event_add(&client_stdin, NULL);
- break;
- case MSG_STDOUT:
- if (datalen != sizeof stdoutdata)
- fatalx("bad MSG_STDOUT size");
- memcpy(&stdoutdata, data, sizeof stdoutdata);
-
- client_write(STDOUT_FILENO, stdoutdata.data,
- stdoutdata.size);
- break;
- case MSG_STDERR:
- if (datalen != sizeof stderrdata)
- fatalx("bad MSG_STDERR size");
- memcpy(&stderrdata, data, sizeof stderrdata);
-
- client_write(STDERR_FILENO, stderrdata.data,
- stderrdata.size);
- break;
case MSG_VERSION:
if (datalen != 0)
fatalx("bad MSG_VERSION size");
@@ -639,6 +831,18 @@ client_dispatch_wait(struct imsg *imsg)
case MSG_EXITED:
proc_exit(client_proc);
break;
+ case MSG_READ_OPEN:
+ client_read_open(data, datalen);
+ break;
+ case MSG_WRITE_OPEN:
+ client_write_open(data, datalen);
+ break;
+ case MSG_WRITE:
+ client_write_data(data, datalen);
+ break;
+ case MSG_WRITE_CLOSE:
+ client_write_close(data, datalen);
+ break;
}
}
diff --git a/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 <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+static int file_next_stream = 3;
+
+RB_GENERATE(client_files, client_file, entry, file_cmp);
+
+int
+file_cmp(struct client_file *cf1, struct client_file *cf2)
+{
+ if (cf1->stream < cf2->stream)
+ return (-1);
+ if (cf1->stream > cf2->stream)
+ return (1);
+ return (0);
+}
+
+struct client_file *
+file_create(struct client *c, int stream, client_file_cb cb, void *cbdata)
+{
+ struct client_file *cf;
+
+ cf = xcalloc(1, sizeof *cf);
+ cf->c = c;
+ cf->references = 1;
+ cf->stream = stream;
+
+ cf->buffer = evbuffer_new();
+ if (cf->buffer == NULL)
+ fatalx("out of memory");
+
+ cf->cb = cb;
+ cf->data = cbdata;
+
+ if (cf->c != NULL) {
+ RB_INSERT(client_files, &cf->c->files, cf);
+ cf->c->references++;
+ }
+
+ return (cf);
+}
+
+void
+file_free(struct client_file *cf)
+{
+ if (--cf->references != 0)
+ return;
+
+ evbuffer_free(cf->buffer);
+ free(cf->path);
+
+ if (cf->c != NULL) {
+ RB_REMOVE(client_files, &cf->c->files, cf);
+ server_client_unref(cf->c);
+ }
+ free(cf);
+}
+
+static void
+file_fire_done_cb(__unused int fd, __unused short events, void *arg)
+{
+ struct client_file *cf = arg;
+ struct client *c = cf->c;
+
+ if (cf->cb != NULL && (~c->flags & CLIENT_DEAD))
+ cf->cb(c, cf->path, cf->error, 1, cf->buffer, cf->data);
+ file_free(cf);
+}
+
+void
+file_fire_done(struct client_file *cf)
+{
+ event_once(-1, EV_TIMEOUT, file_fire_done_cb, cf, NULL);
+}
+
+void
+file_fire_read(struct client_file *cf)
+{
+ struct client *c = cf->c;
+
+ if (cf->cb != NULL)
+ cf->cb(c, cf->path, cf->error, 0, cf->buffer, cf->data);
+}
+
+int
+file_can_print(struct client *c)
+{
+ if (c == NULL)
+ return (0);
+ if (c->session != NULL && (~c->flags & CLIENT_CONTROL))
+ return (0);
+ return (1);
+}
+
+void
+file_print(struct client *c, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ file_vprint(c, fmt, ap);
+ va_end(ap);
+}
+
+void
+file_vprint(struct client *c, const char *fmt, va_list ap)
+{
+ struct client_file find, *cf;
+ struct msg_write_open msg;
+
+ if (!file_can_print(c))
+ return;
+
+ find.stream = 1;
+ if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) {
+ cf = file_create(c, 1, NULL, NULL);