aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am1
-rw-r--r--cfg.c46
-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--cmd-source-file.c147
-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.h112
-rw-r--r--window.c27
15 files changed, 1108 insertions, 533 deletions
diff --git a/Makefile.am b/Makefile.am
index dc281383..6902477e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -132,6 +132,7 @@ dist_tmux_SOURCES = \
control-notify.c \
control.c \
environ.c \
+ file.c \
format.c \
format-draw.c \
grid-view.c \
diff --git a/cfg.c b/cfg.c
index 8dbb6233..ddc112a2 100644
--- a/cfg.c
+++ b/cfg.c
@@ -194,6 +194,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/client.c b/client.c
index 204a431b..5bbd4a42 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",
@@ -428,39 +425,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. */
@@ -534,6 +726,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)
@@ -555,12 +770,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
@@ -569,10 +782,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;
@@ -586,38 +801,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");
@@ -641,6 +833,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 3395612f..84e50242 100644
--- a/cmd-save-buffer.c
+++ b/cmd-save-buffer.c
@@ -55,6 +55,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)
{
@@ -64,18 +78,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);
@@ -88,74 +101,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/cmd-source-file.c b/cmd-source-file.c
index 2e01ae67..0f1f79de 100644
--- a/cmd-source-file.c
+++ b/cmd-source-file.c
@@ -31,8 +31,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",
@@ -44,31 +42,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
@@ -86,30 +168,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/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..f0d622bc
--- /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 == 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 f3731ae9..743ced40 100644
--- a/server-client.c
+++ b/server-client.c
@@ -48,6 +48,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
@@ -195,16 +201,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;
@@ -223,6 +219,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);
@@ -264,6 +262,7 @@ void
server_client_lost(struct client *c)
{
struct message_entry *msg, *msg1;
+ struct client_file *cf;
c->flags |= CLIENT_DEAD;
@@ -271,8 +270,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);
@@ -286,11 +287,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);
@@ -1558,17 +1554,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);
@@ -1707,11 +1703,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;
@@ -1738,21 +1733,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");
@@ -1804,6 +1784,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;
}
}
@@ -1822,7 +1811,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;
@@ -1966,19 +1955,11 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg)
#endif
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);
@@ -2018,91 +1999,69 @@ server_client_dispatch_shell(struct client *c)
proc_kill_peer(c->peer);
}
-/* Event callback to push more stdout data if any left. */
+/* Handle write ready message. */
static void
-server_client_stdout_cb(__unused int fd, __unused short events, void *arg)
+server_client_dispatch_write_ready(struct client *c, struct imsg *imsg)
{
- struct client *c = arg;
-
- if (~c->flags & CLIENT_DEAD)
- server_client_push_stdout(c);
- server_client_unref(c);
+ struct msg_write_ready *msg = imsg->data;
+ size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ struct client_file find, *cf;
+
+ if (msglen != sizeof *msg)
+ fatalx("bad MSG_WRITE_READY size");
+ find.stream = msg->stream;
+ if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL)
+ return;
+ if (msg->error != 0) {
+ cf->error = msg->error;
+ file_fire_done(cf);
+ } else
+ file_push(cf);
}
-/* Push stdout to client if possible. */
-void
-server_client_push_stdout(struct client *c)
+/* Handle read data message. */
+static void
+server_client_dispatch_read_data(struct client *c, struct imsg *imsg)
{
- struct msg_stdout_data data;
- size_t sent, left;
-
- left = EVBUFFER_LENGTH(c->stdout_data);
- while (left != 0) {
- sent = left;
- if (sent > sizeof data.data)
- sent = sizeof data.data;
- memcpy(data.data, EVBUFFER_DATA(c->stdout_data), sent);
- data.size = sent;
-
- if (proc_send(c->peer, MSG_STDOUT, -1, &data, sizeof data) != 0)
- break;
- evbuffer_drain(c->stdout_data, sent);
+ struct msg_read_data *msg = imsg->data;
+ size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ struct client_file find, *cf;
+ void *bdata = msg->data;
+ size_t bsize = msg->size;
+
+ if (msglen != sizeof *msg)
+ fatalx("bad MSG_READ_DATA size");
+ find.stream = msg->stream;
+ if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL)
+ return;
- left = EVBUFFER_LENGTH(c->stdout_data);
- log_debug("%s: client %p, sent %zu, left %zu", __func__, c,
- sent, left);
- }
- if (left != 0) {
- c->references++;
- event_once(-1, EV_TIMEOUT, server_client_stdout_cb, c, NULL);
- log_debug("%s: client %p, queued", __func__, c);
+ log_debug("%s: file %d read %zu bytes", c->name, cf->stream, bsize);
+ if (cf->error == 0) {
+ if (evbuffer_add(cf->buffer, bdata, bsize) != 0) {
+ cf->error = ENOMEM;
+ file_fire_done(cf);
+ } else
+ file_fire_read(cf);
}
}
-/* Event callback to push more stderr data if any left. */
+/* Handle read done message. */
static void
-server_client_stderr_cb(__unused int fd, __unused short events, void *arg)
+server_client_dispatch_read_done(struct client *c, struct imsg *imsg)
{
- struct client *c = arg;
-
- if (~c->flags & CLIENT_DEAD)
- server_client_push_stderr(c);
- server_client_unref(c);
-}
-
-/* Push stderr to client if possible. */
-void
-server_client_push_stderr(struct client *c)
-{
- struct msg_stderr_data data;
- size_t sent, left;
-
- if (c->stderr_data == c->stdout_data) {
- server_client_push_stdout(c);
+ struct msg_read_done *msg = imsg->data;
+ size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ struct client_file find, *cf;
+
+ if (msglen != sizeof *msg)
+ fatalx("bad MSG_READ_DONE size");
+ find.stream = msg->stream;
+ if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL)
return;
- }
-
- left = EVBUFFER_LENGTH(c->stderr_data);
- while (left != 0) {
- sent = left;
- if (sent > sizeof data.data)
- sent = sizeof data.data;
- memcpy(data.data, EVBUFFER_DATA(c->stderr_data), sent);
- data.size = sent;
-
- if (proc_send(c->peer, MSG_STDERR, -1, &data, sizeof data) != 0)
- break;
- evbuffer_drain(c->stderr_data, sent);
- left = EVBUFFER_LENGTH(c->stderr_data);
- log_debug("%s: client %p, sent %zu, left %zu", __func__, c,
- sent, left);
- }
- if (left != 0) {
- c->references++;
- event_once(-1, EV_TIMEOUT, server_client_stderr_cb, c, NULL);
- log_debug("%s: client %p, queued", __func__, c);
- }
+ log_debug("%s: file %d read done", c->name, cf->stream);
+ cf->error = msg->error;
+ file_fire_done(cf);
}
/* Add to client message log. */
diff --git a/server-fn.c b/server-fn.c
index cfafef21..2247f1c5 100644
--- a/server-fn.c
+++ b/server-fn.c
@@ -440,36 +440,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 f1228191..bdebf248 100644
--- a/tmux.h
+++ b/tmux.h
@@ -480,13 +480,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
};
/*
@@ -494,25 +502,49 @@ enum msgtype {
*
* Don't forget to bump PROTOCOL_VERSION if any of these change!
*/
-struct msg_command_data {
+struct msg_command {
int argc;
}; /* followed by packed argv */
-struct msg_stdin_data {
- ssize_t size;
- char data[BUFSIZ];
+struct msg_read_open {
+ int stream;
+ int fd;
+ char path[PATH_MAX];
};
-struct msg_stdout_data {
- ssize_t size;
+struct msg_read_data {
+ int stream;
+ size_t size;
char data[BUFSIZ];
};
-struct msg_stderr_data {
- ssize_t size;
+struct msg_read_done {
+ int stream;
+ int error;
+};
+
+struct msg_write_open {
+ int stream;
+ int fd;
+ char path[PATH_MAX];
+ int flags;
+};
+
+struct msg_write_data {
+ int stream;
+ size_t size;
char data[BUFSIZ];
};
+struct msg_write_ready {
+ int stream;
+ int error;
+};
+
+struct msg_write_close {
+ int stream;
+};
+
/* Mode keys. */
#define MODEKEY_EMACS 0
#define MODEKEY_VI 1
@@ -1476,6 +1508,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 *);
@@ -1509,13 +1564,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;
@@ -1599,6 +1647,8 @@ struct client {
void *overlay_data;
struct event overlay_timer;
+ struct client_files files;
+
TAILQ_ENTRY(client) entry;
};
TAILQ_HEAD(clients, client);
@@ -1755,6 +1805,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 *);
@@ -2072,6 +2124,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 *);
@@ -2131,6 +2185,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;
@@ -2189,8 +2260,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 */
@@ -2575,9 +2644,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 242ad48d..96632058 100644
--- a/window.c
+++ b/window.c
@@ -1608,31 +1608,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
@@ -1651,6 +1648,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);
}