1/* $OpenBSD$ */
2
3/*
4 * Copyright (c) 2019 Nicholas Marriott <[email protected]>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/types.h>
20#include <sys/queue.h>
21
22#include <errno.h>
23#include <fcntl.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <unistd.h>
28
29#include "tmux.h"
30
31static int file_next_stream = 3;
32
33RB_GENERATE(client_files, client_file, entry, file_cmp);
34
35static char *
36file_get_path(struct client *c, const char *file)
37{
38 char *path;
39
40 if (*file == '/')
41 path = xstrdup(file);
42 else
43 xasprintf(&path, "%s/%s", server_client_get_cwd(c, NULL), file);
44 return (path);
45}
46
47int
48file_cmp(struct client_file *cf1, struct client_file *cf2)
49{
50 if (cf1->stream < cf2->stream)
51 return (-1);
52 if (cf1->stream > cf2->stream)
53 return (1);
54 return (0);
55}
56
57struct client_file *
58file_create(struct client *c, int stream, client_file_cb cb, void *cbdata)
59{
60 struct client_file *cf;
61
62 cf = xcalloc(1, sizeof *cf);
63 cf->c = c;
64 cf->references = 1;
65 cf->stream = stream;
66
67 cf->buffer = evbuffer_new();
68 if (cf->buffer == NULL)
69 fatalx("out of memory");
70
71 cf->cb = cb;
72 cf->data = cbdata;
73
74 if (cf->c != NULL) {
75 RB_INSERT(client_files, &cf->c->files, cf);
76 cf->c->references++;
77 }
78
79 return (cf);
80}
81
82void
83file_free(struct client_file *cf)
84{
85 if (--cf->references != 0)
86 return;
87
88 evbuffer_free(cf->buffer);
89 free(cf->path);
90
91 if (cf->c != NULL) {
92 RB_REMOVE(client_files, &cf->c->files, cf);
93 server_client_unref(cf->c);
94 }
95 free(cf);
96}
97
98static void
99file_fire_done_cb(__unused int fd, __unused short events, void *arg)
100{
101 struct client_file *cf = arg;
102 struct client *c = cf->c;
103
104 if (cf->cb != NULL && (c == NULL || (~c->flags & CLIENT_DEAD)))
105 cf->cb(c, cf->path, cf->error, 1, cf->buffer, cf->data);
106 file_free(cf);
107}
108
109void
110file_fire_done(struct client_file *cf)
111{
112 event_once(-1, EV_TIMEOUT, file_fire_done_cb, cf, NULL);
113}
114
115void
116file_fire_read(struct client_file *cf)
117{
118 struct client *c = cf->c;
119
120 if (cf->cb != NULL)
121 cf->cb(c, cf->path, cf->error, 0, cf->buffer, cf->data);
122}
123
124int
125file_can_print(struct client *c)
126{
127 if (c == NULL)
128 return (0);
129 if (c->session != NULL && (~c->flags & CLIENT_CONTROL))
130 return (0);
131 return (1);
132}
133
134void
135file_print(struct client *c, const char *fmt, ...)
136{
137 va_list ap;
138
139 va_start(ap, fmt);
140 file_vprint(c, fmt, ap);
141 va_end(ap);
142}
143
144void
145file_vprint(struct client *c, const char *fmt, va_list ap)
146{
147 struct client_file find, *cf;
148 struct msg_write_open msg;
149
150 if (!file_can_print(c))
151 return;
152
153 find.stream = 1;
154 if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) {
155 cf = file_create(c, 1, NULL, NULL);
156 cf->path = xstrdup("-");
157
158 evbuffer_add_vprintf(cf->buffer, fmt, ap);
159
160 msg.stream = 1;
161 msg.fd = STDOUT_FILENO;
162 msg.flags = 0;
163 proc_send(c->peer, MSG_WRITE_OPEN, -1, &msg, sizeof msg);
164 } else {
165 evbuffer_add_vprintf(cf->buffer, fmt, ap);
166 file_push(cf);
167 }
168}
169
170void
171file_print_buffer(struct client *c, void *data, size_t size)
172{
173 struct client_file find, *cf;
174 struct msg_write_open msg;
175
176 if (!file_can_print(c))
177 return;
178
179 find.stream = 1;
180 if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) {
181 cf = file_create(c, 1, NULL, NULL);
182 cf->path = xstrdup("-");
183
184 evbuffer_add(cf->buffer, data, size);
185
186 msg.stream = 1;
187 msg.fd = STDOUT_FILENO;
188 msg.flags = 0;
189 proc_send(c->peer, MSG_WRITE_OPEN, -1, &msg, sizeof msg);
190 } else {
191 evbuffer_add(cf->buffer, data, size);
192 file_push(cf);
193 }
194}
195
196void
197file_error(struct client *c, const char *fmt, ...)
198{
199 struct client_file find, *cf;
200 struct msg_write_open msg;
201 va_list ap;
202
203 if (!file_can_print(c))
204 return;
205
206 va_start(ap, fmt);
207
208 find.stream = 2;
209 if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) {
210 cf = file_create(c, 2, NULL, NULL);
211 cf->path = xstrdup("-");
212
213 evbuffer_add_vprintf(cf->buffer, fmt, ap);
214
215 msg.stream = 2;
216 msg.fd = STDERR_FILENO;
217 msg.flags = 0;
218 proc_send(c->peer, MSG_WRITE_OPEN, -1, &msg, sizeof msg);
219 } else {
220 evbuffer_add_vprintf(cf->buffer, fmt, ap);
221 file_push(cf);
222 }
223
224 va_end(ap);
225}
226
227void
228file_write(struct client *c, const char *path, int flags, const void *bdata,
229 size_t bsize, client_file_cb cb, void *cbdata)
230{
231 struct client_file *cf;
232 FILE *f;
233 struct msg_write_open *msg;
234 size_t msglen;
235 int fd = -1;
236 const char *mode;
237
238 if (strcmp(path, "-") == 0) {
239 cf = file_create(c, file_next_stream++, cb, cbdata);
240 cf->path = xstrdup("-");
241
242 fd = STDOUT_FILENO;
243 if (c == NULL || c->flags & CLIENT_ATTACHED) {
244 cf->error = EBADF;
245 goto done;
246 }
247 goto skip;
248 }
249
250 cf = file_create(c, file_next_stream++, cb, cbdata);
251 cf->path = file_get_path(c, path);
252
253 if (c == NULL || c->flags & CLIENT_ATTACHED) {
254 if (flags & O_APPEND)
255 mode = "ab";
256 else
257 mode = "wb";
258 f = fopen(cf->path, mode);
259 if (f == NULL) {
260 cf->error = errno;
261 goto done;
262 }
263 if (fwrite(bdata, 1, bsize, f) != bsize) {
264 fclose(f);
265 cf->error = EIO;
266 goto done;
267 }
268 fclose(f);
269 goto done;
270 }
271
272skip:
273 evbuffer_add(cf->buffer, bdata, bsize);
274
275 msglen = strlen(cf->path) + 1 + sizeof *msg;
276 if (msglen > MAX_IMSGSIZE - IMSG_HEADER_SIZE) {
277 cf->error = E2BIG;
278 goto done;
279 }
280 msg = xmalloc(msglen);
281 msg->stream = cf->stream;
282 msg->fd = fd;
283 msg->flags = flags;
284 memcpy(msg + 1, cf->path, msglen - sizeof *msg);
285 if (proc_send(c->peer, MSG_WRITE_OPEN, -1, msg, msglen) != 0) {
286 free(msg);
287 cf->error = EINVAL;
288 goto done;
289 }
290 free(msg);
291 return;
292
293done:
294 file_fire_done(cf);
295}
296
297void
298file_read(struct client *c, const char *path, client_file_cb cb, void *cbdata)
299{
300 struct client_file *cf;
301 FILE *f;
302 struct msg_read_open *msg;
303 size_t msglen, size;
304 int fd = -1;
305 char buffer[BUFSIZ];
306
307 if (strcmp(path, "-") == 0) {
308 cf = file_create(c, file_next_stream++, cb, cbdata);
309 cf->path = xstrdup("-");
310
311 fd = STDIN_FILENO;
312 if (c == NULL || c->flags & CLIENT_ATTACHED) {
313 cf->error = EBADF;
314 goto done;
315 }
316 goto skip;
317 }
318
319 cf = file_create(c, file_next_stream++, cb, cbdata);
320 cf->path = file_get_path(c, path);
321
322 if (c == NULL || c->flags & CLIENT_ATTACHED) {
323 f = fopen(cf->path, "rb");
324 if (f == NULL) {
325 cf->error = errno;
326 goto done;
327 }
328 for (;;) {
329 size = fread(buffer, 1, sizeof buffer, f);
330 if (evbuffer_add(cf->buffer, buffer, size) != 0) {
331 cf->error = ENOMEM;
332 goto done;
333 }
334 if (size != sizeof buffer)
335 break;
336 }
337 if (ferror(f)) {
338 cf->error = EIO;
339 goto done;
340 }
341 fclose(f);
342 goto done;
343 }
344
345skip:
346 msglen = strlen(cf->path) + 1 + sizeof *msg;
347 if (msglen > MAX_IMSGSIZE - IMSG_HEADER_SIZE) {
348 cf->error = E2BIG;
349 goto done;
350 }
351 msg = xmalloc(msglen);
352 msg->stream = cf->stream;
353 msg->fd = fd;
354 memcpy(msg + 1, cf->path, msglen - sizeof *msg);
355 if (proc_send(c->peer, MSG_READ_OPEN, -1, msg, msglen) != 0) {
356 free(msg);
357 cf->error = EINVAL;
358 goto done;
359 }
360 free(msg);
361 return;
362
363done:
364 file_fire_done(cf);
365}
366
367static void
368file_push_cb(__unused int fd, __unused short events, void *arg)
369{
370 struct client_file *cf = arg;
371 struct client *c = cf->c;
372
373 if (~c->flags & CLIENT_DEAD)
374 file_push(cf);
375 file_free(cf);
376}
377
378void
379file_push(struct client_file *cf)
380{
381 struct client *c = cf->c;
382 struct msg_write_data *msg;
383 size_t msglen, sent, left;
384 struct msg_write_close close;
385
386 msg = xmalloc(sizeof *msg);
387 left = EVBUFFER_LENGTH(cf->buffer);
388 while (left != 0) {
389 sent = left;
390 if (sent > MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg)
391 sent = MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg;
392
393 msglen = (sizeof *msg) + sent;
394 msg = xrealloc(msg, msglen);
395 msg->stream = cf->stream;
396 memcpy(msg + 1, EVBUFFER_DATA(cf->buffer), sent);
397 if (proc_send(c->peer, MSG_WRITE, -1, msg, msglen) != 0)
398 break;
399 evbuffer_drain(cf->buffer, sent);
400
401 left = EVBUFFER_LENGTH(cf->buffer);
402 log_debug("%s: file %d sent %zu, left %zu", c->name, cf->stream,
403 sent, left);
404 }
405 if (left != 0) {
406 cf->references++;
407 event_once(-1, EV_TIMEOUT, file_push_cb, cf, NULL);
408 } else if (cf->stream > 2) {
409 close.stream = cf->stream;
410 proc_send(c->peer, MSG_WRITE_CLOSE, -1, &close, sizeof close);
411 file_fire_done(cf);
412 }
413 free(msg);
414}
415