mirror of
https://github.com/tmux/tmux.git
synced 2026-07-22 16:52:42 +00:00
additional payload (to reduce problems with lifetime of objects) and are delivered to one or more event sinks. This is more powerful and reduces the complex dependencies between control mode and hooks. Events are now used for hooks, control mode notifications and for monitors (set-hook -B). wait-for can now wait for an event to fire (-E flag, with -F to for filter), with -v to print the payload, as well as listing (-l) waiting clients on an event and forcing one to wake (-w). A few additional hooks are also now available (pane-created, pane-resized, etc) and some of the existing ones have additional format variables available.
880 lines
20 KiB
C
880 lines
20 KiB
C
/* $OpenBSD: file.c,v 1.20 2026/05/17 10:54:01 nicm Exp $ */
|
|
|
|
/*
|
|
* 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 <sys/queue.h>
|
|
#include <sys/uio.h>
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <imsg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#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)
|
|
{
|
|
const char *home;
|
|
char *path, *full_path;
|
|
|
|
if (strncmp(file, "~/", 2) != 0)
|
|
path = xstrdup(file);
|
|
else {
|
|
home = find_home();
|
|
if (home == NULL)
|
|
home = "";
|
|
xasprintf(&path, "%s%s", home, file + 1);
|
|
}
|
|
if (*path == '/')
|
|
return (path);
|
|
xasprintf(&full_path, "%s/%s", server_client_get_cwd(c, NULL), path);
|
|
free(path);
|
|
return (full_path);
|
|
}
|
|
|
|
/* Tree comparison function. */
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* 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_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->stream = stream;
|
|
|
|
cf->buffer = evbuffer_new();
|
|
if (cf->buffer == NULL)
|
|
fatalx("out of memory");
|
|
|
|
cf->cb = cb;
|
|
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)
|
|
{
|
|
if (--cf->references != 0)
|
|
return;
|
|
|
|
evbuffer_free(cf->buffer);
|
|
free(cf->path);
|
|
|
|
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)
|
|
{
|
|
struct client_file *cf = arg;
|
|
struct client *c = cf->c;
|
|
|
|
if (cf->cb != NULL &&
|
|
(cf->closed || c == NULL || (~c->flags & CLIENT_DEAD)))
|
|
cf->cb(c, cf->path, cf->error, 1, cf->buffer, cf->data);
|
|
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)
|
|
{
|
|
if (cf->cb != NULL)
|
|
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)
|
|
{
|
|
if (c == NULL ||
|
|
(c->flags & CLIENT_ATTACHED) ||
|
|
(c->flags & CLIENT_CONTROL))
|
|
return (0);
|
|
return (1);
|
|
}
|
|
|
|
/* Print a message to a file. */
|
|
void
|
|
file_print(struct client *c, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
file_vprint(c, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
/* Print a message to a file. */
|
|
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_with_client(c, 1, NULL, NULL);
|
|
cf->path = xstrdup("-");
|
|
|
|
evbuffer_add_vprintf(cf->buffer, fmt, ap);
|
|
|
|
msg.stream = 1;
|
|
msg.fd = STDOUT_FILENO;
|
|
msg.flags = 0;
|
|
proc_send(c->peer, MSG_WRITE_OPEN, -1, &msg, sizeof msg);
|
|
} else {
|
|
evbuffer_add_vprintf(cf->buffer, fmt, ap);
|
|
file_push(cf);
|
|
}
|
|
}
|
|
|
|
/* Print a buffer to a file. */
|
|
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_with_client(c, 1, NULL, NULL);
|
|
cf->path = xstrdup("-");
|
|
|
|
evbuffer_add(cf->buffer, data, size);
|
|
|
|
msg.stream = 1;
|
|
msg.fd = STDOUT_FILENO;
|
|
msg.flags = 0;
|
|
proc_send(c->peer, MSG_WRITE_OPEN, -1, &msg, sizeof msg);
|
|
} else {
|
|
evbuffer_add(cf->buffer, data, size);
|
|
file_push(cf);
|
|
}
|
|
}
|
|
|
|
/* Report an error to a file. */
|
|
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_with_client(c, 2, NULL, NULL);
|
|
cf->path = xstrdup("-");
|
|
|
|
evbuffer_add_vprintf(cf->buffer, fmt, ap);
|
|
|
|
msg.stream = 2;
|
|
msg.fd = STDERR_FILENO;
|
|
msg.flags = 0;
|
|
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);
|
|
}
|
|
|
|
/* 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;
|
|
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_with_client(c, stream, cb, cbdata);
|
|
cf->path = xstrdup("-");
|
|
|
|
fd = STDOUT_FILENO;
|
|
if (c == NULL ||
|
|
(c->flags & CLIENT_ATTACHED) ||
|
|
(c->flags & CLIENT_CONTROL)) {
|
|
cf->error = EBADF;
|
|
goto done;
|
|
}
|
|
goto skip;
|
|
}
|
|
|
|
cf = file_create_with_client(c, stream, cb, cbdata);
|
|
cf->path = file_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);
|
|
|
|
msglen = strlen(cf->path) + 1 + sizeof *msg;
|
|
if (msglen > MAX_IMSGSIZE - IMSG_HEADER_SIZE) {
|
|
cf->error = E2BIG;
|
|
goto done;
|
|
}
|
|
msg = xmalloc(msglen);
|
|
msg->stream = cf->stream;
|
|
msg->fd = fd;
|
|
msg->flags = flags;
|
|
memcpy(msg + 1, cf->path, msglen - sizeof *msg);
|
|
if (proc_send(cf->peer, MSG_WRITE_OPEN, -1, msg, msglen) != 0) {
|
|
free(msg);
|
|
cf->error = EINVAL;
|
|
goto done;
|
|
}
|
|
free(msg);
|
|
return;
|
|
|
|
done:
|
|
file_fire_done(cf);
|
|
}
|
|
|
|
/* Read a file. */
|
|
struct client_file *
|
|
file_read(struct client *c, const char *path, client_file_cb cb, void *cbdata)
|
|
{
|
|
struct client_file *cf;
|
|
struct msg_read_open *msg;
|
|
size_t msglen;
|
|
int fd = -1;
|
|
u_int stream = file_next_stream++;
|
|
FILE *f = NULL;
|
|
size_t size;
|
|
char buffer[BUFSIZ];
|
|
|
|
if (strcmp(path, "-") == 0) {
|
|
cf = file_create_with_client(c, stream, cb, cbdata);
|
|
cf->path = xstrdup("-");
|
|
|
|
fd = STDIN_FILENO;
|
|
if (c == NULL ||
|
|
(c->flags & CLIENT_ATTACHED) ||
|
|
(c->flags & CLIENT_CONTROL)) {
|
|
cf->error = EBADF;
|
|
goto done;
|
|
}
|
|
goto skip;
|
|
}
|
|
|
|
cf = file_create_with_client(c, stream, cb, cbdata);
|
|
cf->path = file_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 (ferror(f)) {
|
|
cf->error = errno;
|
|
goto done;
|
|
}
|
|
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;
|
|
}
|
|
goto done;
|
|
}
|
|
|
|
skip:
|
|
msglen = strlen(cf->path) + 1 + sizeof *msg;
|
|
if (msglen > MAX_IMSGSIZE - IMSG_HEADER_SIZE) {
|
|
cf->error = E2BIG;
|
|
goto done;
|
|
}
|
|
msg = xmalloc(msglen);
|
|
msg->stream = cf->stream;
|
|
msg->fd = fd;
|
|
memcpy(msg + 1, cf->path, msglen - sizeof *msg);
|
|
if (proc_send(cf->peer, MSG_READ_OPEN, -1, msg, msglen) != 0) {
|
|
free(msg);
|
|
cf->error = EINVAL;
|
|
goto done;
|
|
}
|
|
free(msg);
|
|
return cf;
|
|
|
|
done:
|
|
if (f != NULL)
|
|
fclose(f);
|
|
file_fire_done(cf);
|
|
return NULL;
|
|
}
|
|
|
|
/* Cancel a file read. */
|
|
void
|
|
file_cancel(struct client_file *cf)
|
|
{
|
|
struct msg_read_cancel msg;
|
|
|
|
log_debug("read cancel file %d", cf->stream);
|
|
|
|
if (cf->closed)
|
|
return;
|
|
cf->closed = 1;
|
|
|
|
msg.stream = cf->stream;
|
|
proc_send(cf->peer, MSG_READ_CANCEL, -1, &msg, sizeof msg);
|
|
}
|
|
|
|
/* 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;
|
|
|
|
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 msg_write_data *msg;
|
|
size_t msglen, sent, left;
|
|
struct msg_write_close close;
|
|
|
|
msg = xmalloc(sizeof *msg);
|
|
left = EVBUFFER_LENGTH(cf->buffer);
|
|
while (left != 0) {
|
|
sent = left;
|
|
if (sent > MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg)
|
|
sent = MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg;
|
|
|
|
msglen = (sizeof *msg) + sent;
|
|
msg = xrealloc(msg, msglen);
|
|
msg->stream = cf->stream;
|
|
memcpy(msg + 1, EVBUFFER_DATA(cf->buffer), sent);
|
|
if (proc_send(cf->peer, MSG_WRITE, -1, msg, msglen) != 0)
|
|
break;
|
|
evbuffer_drain(cf->buffer, sent);
|
|
|
|
left = EVBUFFER_LENGTH(cf->buffer);
|
|
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(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);
|
|
|
|
bufferevent_free(cf->event);
|
|
cf->event = NULL;
|
|
|
|
close(cf->fd);
|
|
cf->fd = -1;
|
|
|
|
if (cf->cb != NULL)
|
|
cf->cb(NULL, NULL, 0, -1, NULL, cf->data);
|
|
}
|
|
|
|
/* 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 (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);
|
|
if (cf->event == NULL)
|
|
fatalx("out of memory");
|
|
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, 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 = (what & EVBUFFER_ERROR) ? EIO : 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 (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);
|
|
if (cf->event == NULL)
|
|
fatalx("out of memory");
|
|
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 read cancel message (client). */
|
|
void
|
|
file_read_cancel(struct client_files *files, struct imsg *imsg)
|
|
{
|
|
struct msg_read_cancel *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_CANCEL size");
|
|
find.stream = msg->stream;
|
|
if ((cf = RB_FIND(client_files, files, &find)) == NULL)
|
|
fatalx("unknown stream number");
|
|
log_debug("cancel file %d", cf->stream);
|
|
|
|
file_read_error_callback(NULL, 0, cf);
|
|
}
|
|
|
|
/* Handle a write ready message (server). */
|
|
int
|
|
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)
|
|
return (-1);
|
|
find.stream = msg->stream;
|
|
if ((cf = RB_FIND(client_files, files, &find)) == NULL)
|
|
return (0);
|
|
if (msg->error != 0) {
|
|
cf->error = msg->error;
|
|
file_fire_done(cf);
|
|
} else
|
|
file_push(cf);
|
|
return (0);
|
|
}
|
|
|
|
/* Handle read data message (server). */
|
|
int
|
|
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)
|
|
return (-1);
|
|
find.stream = msg->stream;
|
|
if ((cf = RB_FIND(client_files, files, &find)) == NULL)
|
|
return (0);
|
|
|
|
log_debug("file %d read %zu bytes", cf->stream, bsize);
|
|
if (cf->error == 0 && !cf->closed) {
|
|
if (evbuffer_add(cf->buffer, bdata, bsize) != 0) {
|
|
cf->error = ENOMEM;
|
|
file_fire_done(cf);
|
|
} else
|
|
file_fire_read(cf);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/* Handle a read done message (server). */
|
|
int
|
|
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)
|
|
return (-1);
|
|
find.stream = msg->stream;
|
|
if ((cf = RB_FIND(client_files, files, &find)) == NULL)
|
|
return (0);
|
|
|
|
log_debug("file %d read done", cf->stream);
|
|
cf->error = msg->error;
|
|
file_fire_done(cf);
|
|
return (0);
|
|
}
|