summaryrefslogtreecommitdiffstats
path: root/file.c
diff options
context:
space:
mode:
authorThomas Adam <thomas@xteddy.org>2021-02-17 09:58:12 +0000
committerThomas Adam <thomas@xteddy.org>2021-02-17 09:58:12 +0000
commitce5de765929ea38b68e3fd6c26e554ab0c1e09b1 (patch)
treec4f54994379a0f590ecf0183aeeb373af7be2ef9 /file.c
parent0526d074d0170ad248b06187b64f4e44a0c05dcc (diff)
parentd768fc2553c2bdec6bb7b026ffffdaee0dd102f4 (diff)
Merge branch 'obsd-master' into master
Diffstat (limited to 'file.c')
-rw-r--r--file.c454
1 files changed, 428 insertions, 26 deletions
diff --git a/file.c b/file.c
index f7f99111..baa2bd58 100644
--- a/file.c
+++ b/file.c
@@ -27,10 +27,17 @@
#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)
{
@@ -43,6 +50,7 @@ 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)
{
@@ -53,11 +61,47 @@ 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_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(struct client *c, int stream, client_file_cb cb, void *cbdata)
+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;
@@ -71,6 +115,8 @@ file_create(struct client *c, int stream, client_file_cb cb, void *cbdata)
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++;
}
@@ -78,6 +124,7 @@ file_create(struct client *c, int stream, client_file_cb cb, void *cbdata)
return (cf);
}
+/* Free a file. */
void
file_free(struct client_file *cf)
{
@@ -87,13 +134,15 @@ 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)
{
@@ -105,21 +154,22 @@ 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)
{
@@ -130,6 +180,7 @@ file_can_print(struct client *c)
return (1);
}
+/* Print a message to a file. */
void
file_print(struct client *c, const char *fmt, ...)
{
@@ -140,6 +191,7 @@ 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)
{
@@ -151,7 +203,7 @@ 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);
@@ -166,6 +218,7 @@ file_vprint(struct client *c, const char *fmt, va_list ap)
}
}
+/* Print a buffer to a file. */
void
file_print_buffer(struct client *c, void *data, size_t size)
{
@@ -177,7 +230,7 @@ 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);
@@ -192,6 +245,7 @@ file_print_buffer(struct client *c, void *data, size_t size)
}
}
+/* Report an error to a file. */
void
file_error(struct client *c, const char *fmt, ...)
{
@@ -206,7 +260,7 @@ 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);
@@ -223,19 +277,21 @@ file_error(struct client *c, const char *fmt, ...)
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;
@@ -248,7 +304,7 @@ file_write(struct client *c, const char *path, int flags, const void *bdata,
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) {
@@ -283,7 +339,7 @@ skip:
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;
@@ -295,18 +351,21 @@ 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;
@@ -319,7 +378,7 @@ file_read(struct client *c, const char *path, client_file_cb cb, void *cbdata)
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) {
@@ -355,7 +414,7 @@ skip:
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;
@@ -367,21 +426,21 @@ 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;
@@ -397,21 +456,364 @@ file_push(struct client_file *cf)
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);
}
+
+/* Check if any files have data left to write. */
+int
+file_write_left(struct client_files *files)
+{
+ struct client_file *cf;
+ size_t left;
+ int waiting = 0;
+
+ RB_FOREACH(cf, client_files, 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);
+ }
+ }
+ return (waiting != 0);
+}
+
+/* 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);
+}