-/* $OpenBSD: client.c,v 1.151 2021/01/17 16:17:41 nicm Exp $ */
+/* $OpenBSD: client.c,v 1.152 2021/02/11 08:28:45 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
proc_send(client_peer, MSG_IDENTIFY_DONE, -1, NULL, 0);
}
-/* File write error callback. */
-static void
-client_write_error_callback(__unused struct bufferevent *bev,
- __unused short what, void *arg)
-{
- struct client_file *cf = arg;
-
- log_debug("write error file %d", cf->stream);
-
- bufferevent_free(cf->event);
- cf->event = NULL;
-
- close(cf->fd);
- cf->fd = -1;
-
- if (client_exitflag)
- client_exit();
-}
-
-/* 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);
- }
-
- if (client_exitflag)
- client_exit();
-}
-
-/* Open write file. */
-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)
- fatalx("bad MSG_WRITE_OPEN size");
- 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) {
- 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(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;
- size_t size = 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", size, cf->stream);
-
- if (cf->event != NULL)
- bufferevent_write(cf->event, msg + 1, 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;
- 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 > 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;
- 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. */
-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;
- const char *path;
- struct msg_read_done reply;
- struct client_file find, *cf;
- const int flags = O_NONBLOCK|O_RDONLY;
- int error;
-
- if (datalen < sizeof *msg)
- fatalx("bad MSG_READ_OPEN size");
- 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) {
- 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(path, flags);
- else {
- if (msg->fd != STDIN_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, 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)
}
}
+/* Callback for file write error or close. */
+static void
+client_file_check_cb(__unused struct client *c, __unused const char *path,
+ __unused int error, __unused int closed, __unused struct evbuffer *buffer,
+ __unused void *data)
+{
+ if (client_exitflag)
+ client_exit();
+}
+
/* Callback for client read events. */
static void
client_dispatch(struct imsg *imsg, __unused void *arg)
proc_exit(client_proc);
break;
case MSG_READ_OPEN:
- client_read_open(data, datalen);
+ file_read_open(&client_files, client_peer, imsg, 1,
+ !(client_flags & CLIENT_CONTROL), client_file_check_cb,
+ NULL);
break;
case MSG_WRITE_OPEN:
- client_write_open(data, datalen);
+ file_write_open(&client_files, client_peer, imsg, 1,
+ !(client_flags & CLIENT_CONTROL), client_file_check_cb,
+ NULL);
break;
case MSG_WRITE:
- client_write_data(data, datalen);
+ file_write_data(&client_files, imsg);
break;
case MSG_WRITE_CLOSE:
- client_write_close(data, datalen);
+ file_write_close(&client_files, imsg);
break;
case MSG_OLDSTDERR:
case MSG_OLDSTDIN:
-/* $OpenBSD: file.c,v 1.7 2020/05/26 08:41:47 nicm Exp $ */
+/* $OpenBSD: file.c,v 1.8 2021/02/11 08:28:45 nicm Exp $ */
/*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
#include "tmux.h"
+/*
+ * IPC file handling. Both client and server use the same data structures
+ * (client_file and client_files) to store list of active files. Most functions
+ * are for use either in client or server but not both.
+ */
+
static int file_next_stream = 3;
RB_GENERATE(client_files, client_file, entry, file_cmp);
+/* Get path for file, either as given or from working directory. */
static char *
file_get_path(struct client *c, const char *file)
{
return (path);
}
+/* Tree comparison function. */
int
file_cmp(struct client_file *cf1, struct client_file *cf2)
{
return (0);
}
+/*
+ * Create a file object in the client process - the peer is the server to send
+ * messages to. Check callback is fired when the file is finished with so the
+ * process can decide if it needs to exit (if it is waiting for files to
+ * flush).
+ */
struct client_file *
-file_create(struct client *c, int stream, client_file_cb cb, void *cbdata)
+file_create_with_peer(struct tmuxpeer *peer, struct client_files *files,
+ int stream, client_file_cb cb, void *cbdata)
{
struct client_file *cf;
+ cf = xcalloc(1, sizeof *cf);
+ cf->c = NULL;
+ cf->references = 1;
+ cf->stream = stream;
+
+ cf->buffer = evbuffer_new();
+ if (cf->buffer == NULL)
+ fatalx("out of memory");
+
+ cf->cb = cb;
+ cf->data = cbdata;
+
+ cf->peer = peer;
+ cf->tree = files;
+ RB_INSERT(client_files, files, cf);
+
+ return (cf);
+}
+
+/* Create a file object in the server, communicating with the given client. */
+struct client_file *
+file_create_with_client(struct client *c, int stream, client_file_cb cb,
+ void *cbdata)
+{
+ struct client_file *cf;
+
+ if (c != NULL && (c->flags & CLIENT_ATTACHED))
+ c = NULL;
+
cf = xcalloc(1, sizeof *cf);
cf->c = c;
cf->references = 1;
cf->data = cbdata;
if (cf->c != NULL) {
+ cf->peer = cf->c->peer;
+ cf->tree = &cf->c->files;
RB_INSERT(client_files, &cf->c->files, cf);
cf->c->references++;
}
return (cf);
}
+/* Free a file. */
void
file_free(struct client_file *cf)
{
evbuffer_free(cf->buffer);
free(cf->path);
- if (cf->c != NULL) {
- RB_REMOVE(client_files, &cf->c->files, cf);
+ if (cf->tree != NULL)
+ RB_REMOVE(client_files, cf->tree, cf);
+ if (cf->c != NULL)
server_client_unref(cf->c);
- }
+
free(cf);
}
+/* Event to fire the done callback. */
static void
file_fire_done_cb(__unused int fd, __unused short events, void *arg)
{
file_free(cf);
}
+/* Add an event to fire the done callback (used by the server). */
void
file_fire_done(struct client_file *cf)
{
event_once(-1, EV_TIMEOUT, file_fire_done_cb, cf, NULL);
}
+/* Fire the read callback. */
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);
+ cf->cb(cf->c, cf->path, cf->error, 0, cf->buffer, cf->data);
}
+/* Can this file be printed to? */
int
file_can_print(struct client *c)
{
return (1);
}
+/* Print a message to a file. */
void
file_print(struct client *c, const char *fmt, ...)
{
va_end(ap);
}
+/* Print a message to a file. */
void
file_vprint(struct client *c, const char *fmt, va_list ap)
{
find.stream = 1;
if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) {
- cf = file_create(c, 1, NULL, NULL);
+ cf = file_create_with_client(c, 1, NULL, NULL);
cf->path = xstrdup("-");
evbuffer_add_vprintf(cf->buffer, fmt, ap);
}
}
+/* Print a buffer to a file. */
void
file_print_buffer(struct client *c, void *data, size_t size)
{
find.stream = 1;
if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) {
- cf = file_create(c, 1, NULL, NULL);
+ cf = file_create_with_client(c, 1, NULL, NULL);
cf->path = xstrdup("-");
evbuffer_add(cf->buffer, data, size);
}
}
+/* Report an error to a file. */
void
file_error(struct client *c, const char *fmt, ...)
{
find.stream = 2;
if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) {
- cf = file_create(c, 2, NULL, NULL);
+ cf = file_create_with_client(c, 2, NULL, NULL);
cf->path = xstrdup("-");
evbuffer_add_vprintf(cf->buffer, fmt, ap);
va_end(ap);
}
+/* Write data to a file. */
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;
size_t msglen;
int fd = -1;
+ u_int stream = file_next_stream++;
+ FILE *f;
const char *mode;
if (strcmp(path, "-") == 0) {
- cf = file_create(c, file_next_stream++, cb, cbdata);
+ cf = file_create_with_client(c, stream, cb, cbdata);
cf->path = xstrdup("-");
fd = STDOUT_FILENO;
goto skip;
}
- cf = file_create(c, file_next_stream++, cb, cbdata);
+ cf = file_create_with_client(c, stream, cb, cbdata);
cf->path = file_get_path(c, path);
if (c == NULL || c->flags & CLIENT_ATTACHED) {
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) {
+ if (proc_send(cf->peer, MSG_WRITE_OPEN, -1, msg, msglen) != 0) {
free(msg);
cf->error = EINVAL;
goto done;
file_fire_done(cf);
}
+/* Read a file. */
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;
- size_t msglen, size;
+ size_t msglen;
int fd = -1;
+ u_int stream = file_next_stream++;
+ FILE *f;
+ size_t size;
char buffer[BUFSIZ];
if (strcmp(path, "-") == 0) {
- cf = file_create(c, file_next_stream++, cb, cbdata);
+ cf = file_create_with_client(c, stream, cb, cbdata);
cf->path = xstrdup("-");
fd = STDIN_FILENO;
goto skip;
}
- cf = file_create(c, file_next_stream++, cb, cbdata);
+ cf = file_create_with_client(c, stream, cb, cbdata);
cf->path = file_get_path(c, path);
if (c == NULL || c->flags & CLIENT_ATTACHED) {
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) {
+ if (proc_send(cf->peer, MSG_READ_OPEN, -1, msg, msglen) != 0) {
free(msg);
cf->error = EINVAL;
goto done;
file_fire_done(cf);
}
+/* Push event, fired if there is more writing to be done. */
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)
+ if (cf->c == NULL || ~cf->c->flags & CLIENT_DEAD)
file_push(cf);
file_free(cf);
}
+/* Push uwritten data to the client for a file, if it will accept it. */
void
file_push(struct client_file *cf)
{
- struct client *c = cf->c;
struct msg_write_data *msg;
size_t msglen, sent, left;
struct msg_write_close close;
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)
+ if (proc_send(cf->peer, MSG_WRITE, -1, msg, msglen) != 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);
+ log_debug("file %d sent %zu, left %zu", 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);
+ proc_send(cf->peer, MSG_WRITE_CLOSE, -1, &close, sizeof close);
file_fire_done(cf);
}
free(msg);
}
+
+/* Client file write error callback. */
+static void
+file_write_error_callback(__unused struct bufferevent *bev, __unused short what,
+ void *arg)
+{
+ struct client_file *cf = arg;
+
+ log_debug("write error file %d", cf->stream);
+
+ if (cf->cb != NULL)
+ cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
+
+ bufferevent_free(cf->event);
+ cf->event = NULL;
+
+ close(cf->fd);
+ cf->fd = -1;
+}
+
+/* Client file write callback. */
+static void
+file_write_callback(__unused struct bufferevent *bev, void *arg)
+{
+ struct client_file *cf = arg;
+
+ log_debug("write check file %d", cf->stream);
+
+ if (cf->cb != NULL)
+ cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
+
+ if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0) {
+ bufferevent_free(cf->event);
+ close(cf->fd);
+ RB_REMOVE(client_files, cf->tree, cf);
+ file_free(cf);
+ }
+}
+
+/* Handle a file write open message (client). */
+void
+file_write_open(struct client_files *files, struct tmuxpeer *peer,
+ struct imsg *imsg, int allow_streams, int close_received,
+ client_file_cb cb, void *cbdata)
+{
+ struct msg_write_open *msg = imsg->data;
+ size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ 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 (msglen < sizeof *msg)
+ fatalx("bad MSG_WRITE_OPEN size");
+ if (msglen == 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, files, &find)) != NULL) {
+ error = EBADF;
+ goto reply;
+ }
+ cf = file_create_with_peer(peer, files, msg->stream, cb, cbdata);
+ if (cf->closed) {
+ error = EBADF;
+ goto reply;
+ }
+
+ cf->fd = -1;
+ if (msg->fd == -1)
+ cf->fd = open(path, msg->flags|flags, 0644);
+ else if (allow_streams) {
+ if (msg->fd != STDOUT_FILENO && msg->fd != STDERR_FILENO)
+ errno = EBADF;
+ else {
+ cf->fd = dup(msg->fd);
+ if (close_received)
+ close(msg->fd); /* can only be used once */
+ }
+ } else
+ errno = EBADF;
+ if (cf->fd == -1) {
+ error = errno;
+ goto reply;
+ }
+
+ cf->event = bufferevent_new(cf->fd, NULL, file_write_callback,
+ file_write_error_callback, cf);
+ bufferevent_enable(cf->event, EV_WRITE);
+ goto reply;
+
+reply:
+ reply.stream = msg->stream;
+ reply.error = error;
+ proc_send(peer, MSG_WRITE_READY, -1, &reply, sizeof reply);
+}
+
+/* Handle a file write data message (client). */
+void
+file_write_data(struct client_files *files, struct imsg *imsg)
+{
+ struct msg_write_data *msg = imsg->data;
+ size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ struct client_file find, *cf;
+ size_t size = msglen - sizeof *msg;
+
+ if (msglen < sizeof *msg)
+ fatalx("bad MSG_WRITE size");
+ find.stream = msg->stream;
+ if ((cf = RB_FIND(client_files, files, &find)) == NULL)
+ fatalx("unknown stream number");
+ log_debug("write %zu to file %d", size, cf->stream);
+
+ if (cf->event != NULL)
+ bufferevent_write(cf->event, msg + 1, size);
+}
+
+/* Handle a file write close message (client). */
+void
+file_write_close(struct client_files *files, struct imsg *imsg)
+{
+ struct msg_write_close *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_CLOSE size");
+ find.stream = msg->stream;
+ if ((cf = RB_FIND(client_files, 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, files, cf);
+ file_free(cf);
+ }
+}
+
+/* Client file read error callback. */
+static void
+file_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(cf->peer, MSG_READ_DONE, -1, &msg, sizeof msg);
+
+ bufferevent_free(cf->event);
+ close(cf->fd);
+ RB_REMOVE(client_files, cf->tree, cf);
+ file_free(cf);
+}
+
+/* Client file read callback. */
+static void
+file_read_callback(__unused struct bufferevent *bev, void *arg)
+{
+ struct client_file *cf = arg;
+ void *bdata;
+ size_t bsize;
+ 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 > 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;
+ msg = xrealloc(msg, msglen);
+ msg->stream = cf->stream;
+ memcpy(msg + 1, bdata, bsize);
+ proc_send(cf->peer, MSG_READ, -1, msg, msglen);
+
+ evbuffer_drain(cf->event->input, bsize);
+ }
+ free(msg);
+}
+
+/* Handle a file read open message (client). */
+void
+file_read_open(struct client_files *files, struct tmuxpeer *peer,
+ struct imsg *imsg, int allow_streams, int close_received, client_file_cb cb,
+ void *cbdata)
+{
+ struct msg_read_open *msg = imsg->data;
+ size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ const char *path;
+ struct msg_read_done reply;
+ struct client_file find, *cf;
+ const int flags = O_NONBLOCK|O_RDONLY;
+ int error;
+
+ if (msglen < sizeof *msg)
+ fatalx("bad MSG_READ_OPEN size");
+ if (msglen == 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, files, &find)) != NULL) {
+ error = EBADF;
+ goto reply;
+ }
+ cf = file_create_with_peer(peer, files, msg->stream, cb, cbdata);
+ if (cf->closed) {
+ error = EBADF;
+ goto reply;
+ }
+
+ cf->fd = -1;
+ if (msg->fd == -1)
+ cf->fd = open(path, flags);
+ else if (allow_streams) {
+ if (msg->fd != STDIN_FILENO)
+ errno = EBADF;
+ else {
+ cf->fd = dup(msg->fd);
+ if (close_received)
+ close(msg->fd); /* can only be used once */
+ }
+ } else
+ errno = EBADF;
+ if (cf->fd == -1) {
+ error = errno;
+ goto reply;
+ }
+
+ cf->event = bufferevent_new(cf->fd, file_read_callback, NULL,
+ file_read_error_callback, cf);
+ bufferevent_enable(cf->event, EV_READ);
+ return;
+
+reply:
+ reply.stream = msg->stream;
+ reply.error = error;
+ proc_send(peer, MSG_READ_DONE, -1, &reply, sizeof reply);
+}
+
+/* Handle a write ready message (server). */
+void
+file_write_ready(struct client_files *files, struct imsg *imsg)
+{
+ 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, files, &find)) == NULL)
+ return;
+ if (msg->error != 0) {
+ cf->error = msg->error;
+ file_fire_done(cf);
+ } else
+ file_push(cf);
+}
+
+/* Handle read data message (server). */
+void
+file_read_data(struct client_files *files, 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 + 1;
+ size_t bsize = msglen - sizeof *msg;
+
+ if (msglen < sizeof *msg)
+ fatalx("bad MSG_READ_DATA size");
+ find.stream = msg->stream;
+ if ((cf = RB_FIND(client_files, files, &find)) == NULL)
+ return;
+
+ log_debug("file %d read %zu bytes", 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 a read done message (server). */
+void
+file_read_done(struct client_files *files, 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, files, &find)) == NULL)
+ return;
+
+ log_debug("file %d read done", cf->stream);
+ cf->error = msg->error;
+ file_fire_done(cf);
+}