xref: /openbsd-src/usr.bin/tmux/file.c (revision 57431a22fe3be827192a0a68630e2b601a4e935d)
1 /* $OpenBSD: file.c,v 1.4 2019/12/16 16:09:28 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
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 <imsg.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 
30 #include "tmux.h"
31 
32 static int	file_next_stream = 3;
33 
34 RB_GENERATE(client_files, client_file, entry, file_cmp);
35 
36 int
37 file_cmp(struct client_file *cf1, struct client_file *cf2)
38 {
39 	if (cf1->stream < cf2->stream)
40 		return (-1);
41 	if (cf1->stream > cf2->stream)
42 		return (1);
43 	return (0);
44 }
45 
46 struct client_file *
47 file_create(struct client *c, int stream, client_file_cb cb, void *cbdata)
48 {
49 	struct client_file	*cf;
50 
51 	cf = xcalloc(1, sizeof *cf);
52 	cf->c = c;
53 	cf->references = 1;
54 	cf->stream = stream;
55 
56 	cf->buffer = evbuffer_new();
57 	if (cf->buffer == NULL)
58 		fatalx("out of memory");
59 
60 	cf->cb = cb;
61 	cf->data = cbdata;
62 
63 	if (cf->c != NULL) {
64 		RB_INSERT(client_files, &cf->c->files, cf);
65 		cf->c->references++;
66 	}
67 
68 	return (cf);
69 }
70 
71 void
72 file_free(struct client_file *cf)
73 {
74 	if (--cf->references != 0)
75 		return;
76 
77 	evbuffer_free(cf->buffer);
78 	free(cf->path);
79 
80 	if (cf->c != NULL) {
81 		RB_REMOVE(client_files, &cf->c->files, cf);
82 		server_client_unref(cf->c);
83 	}
84 	free(cf);
85 }
86 
87 static void
88 file_fire_done_cb(__unused int fd, __unused short events, void *arg)
89 {
90 	struct client_file	*cf = arg;
91 	struct client		*c = cf->c;
92 
93 	if (cf->cb != NULL && (c == NULL || (~c->flags & CLIENT_DEAD)))
94 		cf->cb(c, cf->path, cf->error, 1, cf->buffer, cf->data);
95 	file_free(cf);
96 }
97 
98 void
99 file_fire_done(struct client_file *cf)
100 {
101 	event_once(-1, EV_TIMEOUT, file_fire_done_cb, cf, NULL);
102 }
103 
104 void
105 file_fire_read(struct client_file *cf)
106 {
107 	struct client	*c = cf->c;
108 
109 	if (cf->cb != NULL)
110 		cf->cb(c, cf->path, cf->error, 0, cf->buffer, cf->data);
111 }
112 
113 int
114 file_can_print(struct client *c)
115 {
116 	if (c == NULL)
117 		return (0);
118 	if (c->session != NULL && (~c->flags & CLIENT_CONTROL))
119 		return (0);
120 	return (1);
121 }
122 
123 void
124 file_print(struct client *c, const char *fmt, ...)
125 {
126 	va_list	ap;
127 
128 	va_start(ap, fmt);
129 	file_vprint(c, fmt, ap);
130 	va_end(ap);
131 }
132 
133 void
134 file_vprint(struct client *c, const char *fmt, va_list ap)
135 {
136 	struct client_file	 find, *cf;
137 	struct msg_write_open	 msg;
138 
139 	if (!file_can_print(c))
140 		return;
141 
142 	find.stream = 1;
143 	if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) {
144 		cf = file_create(c, 1, NULL, NULL);
145 		cf->path = xstrdup("-");
146 
147 		evbuffer_add_vprintf(cf->buffer, fmt, ap);
148 
149 		msg.stream = 1;
150 		msg.fd = STDOUT_FILENO;
151 		msg.flags = 0;
152 		proc_send(c->peer, MSG_WRITE_OPEN, -1, &msg, sizeof msg);
153 	} else {
154 		evbuffer_add_vprintf(cf->buffer, fmt, ap);
155 		file_push(cf);
156 	}
157 }
158 
159 void
160 file_print_buffer(struct client *c, void *data, size_t size)
161 {
162 	struct client_file	 find, *cf;
163 	struct msg_write_open	 msg;
164 
165 	if (!file_can_print(c))
166 		return;
167 
168 	find.stream = 1;
169 	if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) {
170 		cf = file_create(c, 1, NULL, NULL);
171 		cf->path = xstrdup("-");
172 
173 		evbuffer_add(cf->buffer, data, size);
174 
175 		msg.stream = 1;
176 		msg.fd = STDOUT_FILENO;
177 		msg.flags = 0;
178 		proc_send(c->peer, MSG_WRITE_OPEN, -1, &msg, sizeof msg);
179 	} else {
180 		evbuffer_add(cf->buffer, data, size);
181 		file_push(cf);
182 	}
183 }
184 
185 void
186 file_error(struct client *c, const char *fmt, ...)
187 {
188 	struct client_file	 find, *cf;
189 	struct msg_write_open	 msg;
190 	va_list			 ap;
191 
192 	if (!file_can_print(c))
193 		return;
194 
195 	va_start(ap, fmt);
196 
197 	find.stream = 2;
198 	if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) {
199 		cf = file_create(c, 2, NULL, NULL);
200 		cf->path = xstrdup("-");
201 
202 		evbuffer_add_vprintf(cf->buffer, fmt, ap);
203 
204 		msg.stream = 2;
205 		msg.fd = STDERR_FILENO;
206 		msg.flags = 0;
207 		proc_send(c->peer, MSG_WRITE_OPEN, -1, &msg, sizeof msg);
208 	} else {
209 		evbuffer_add_vprintf(cf->buffer, fmt, ap);
210 		file_push(cf);
211 	}
212 
213 	va_end(ap);
214 }
215 
216 void
217 file_write(struct client *c, const char *path, int flags, const void *bdata,
218     size_t bsize, client_file_cb cb, void *cbdata)
219 {
220 	struct client_file	*cf;
221 	FILE			*f;
222 	struct msg_write_open	*msg;
223 	size_t			 msglen;
224 	int			 fd = -1;
225 	const char		*mode;
226 
227 	if (strcmp(path, "-") == 0) {
228 		cf = file_create(c, file_next_stream++, cb, cbdata);
229 		cf->path = xstrdup("-");
230 
231 		fd = STDOUT_FILENO;
232 		if (c == NULL || c->flags & CLIENT_ATTACHED) {
233 			cf->error = EBADF;
234 			goto done;
235 		}
236 		goto skip;
237 	}
238 
239 	cf = file_create(c, file_next_stream++, cb, cbdata);
240 	cf->path = server_client_get_path(c, path);
241 
242 	if (c == NULL || c->flags & CLIENT_ATTACHED) {
243 		if (flags & O_APPEND)
244 			mode = "ab";
245 		else
246 			mode = "wb";
247 		f = fopen(cf->path, mode);
248 		if (f == NULL) {
249 			cf->error = errno;
250 			goto done;
251 		}
252 		if (fwrite(bdata, 1, bsize, f) != bsize) {
253 			fclose(f);
254 			cf->error = EIO;
255 			goto done;
256 		}
257 		fclose(f);
258 		goto done;
259 	}
260 
261 skip:
262 	evbuffer_add(cf->buffer, bdata, bsize);
263 
264 	msglen = strlen(cf->path) + 1 + sizeof *msg;
265 	if (msglen > MAX_IMSGSIZE - IMSG_HEADER_SIZE) {
266 		cf->error = E2BIG;
267 		goto done;
268 	}
269 	msg = xmalloc(msglen);
270 	msg->stream = cf->stream;
271 	msg->fd = fd;
272 	msg->flags = flags;
273 	memcpy(msg + 1, cf->path, msglen - sizeof *msg);
274 	if (proc_send(c->peer, MSG_WRITE_OPEN, -1, msg, msglen) != 0) {
275 		free(msg);
276 		cf->error = EINVAL;
277 		goto done;
278 	}
279 	free(msg);
280 	return;
281 
282 done:
283 	file_fire_done(cf);
284 }
285 
286 void
287 file_read(struct client *c, const char *path, client_file_cb cb, void *cbdata)
288 {
289 	struct client_file	*cf;
290 	FILE			*f;
291 	struct msg_read_open	*msg;
292 	size_t			 msglen, size;
293 	int			 fd = -1;
294 	char			 buffer[BUFSIZ];
295 
296 	if (strcmp(path, "-") == 0) {
297 		cf = file_create(c, file_next_stream++, cb, cbdata);
298 		cf->path = xstrdup("-");
299 
300 		fd = STDIN_FILENO;
301 		if (c == NULL || c->flags & CLIENT_ATTACHED) {
302 			cf->error = EBADF;
303 			goto done;
304 		}
305 		goto skip;
306 	}
307 
308 	cf = file_create(c, file_next_stream++, cb, cbdata);
309 	cf->path = server_client_get_path(c, path);
310 
311 	if (c == NULL || c->flags & CLIENT_ATTACHED) {
312 		f = fopen(cf->path, "rb");
313 		if (f == NULL) {
314 			cf->error = errno;
315 			goto done;
316 		}
317 		for (;;) {
318 			size = fread(buffer, 1, sizeof buffer, f);
319 			if (evbuffer_add(cf->buffer, buffer, size) != 0) {
320 				cf->error = ENOMEM;
321 				goto done;
322 			}
323 			if (size != sizeof buffer)
324 				break;
325 		}
326 		if (ferror(f)) {
327 			cf->error = EIO;
328 			goto done;
329 		}
330 		fclose(f);
331 		goto done;
332 	}
333 
334 skip:
335 	msglen = strlen(cf->path) + 1 + sizeof *msg;
336 	if (msglen > MAX_IMSGSIZE - IMSG_HEADER_SIZE) {
337 		cf->error = E2BIG;
338 		goto done;
339 	}
340 	msg = xmalloc(msglen);
341 	msg->stream = cf->stream;
342 	msg->fd = fd;
343 	memcpy(msg + 1, cf->path, msglen - sizeof *msg);
344 	if (proc_send(c->peer, MSG_READ_OPEN, -1, msg, msglen) != 0) {
345 		free(msg);
346 		cf->error = EINVAL;
347 		goto done;
348 	}
349 	free(msg);
350 	return;
351 
352 done:
353 	file_fire_done(cf);
354 }
355 
356 static void
357 file_push_cb(__unused int fd, __unused short events, void *arg)
358 {
359 	struct client_file	*cf = arg;
360 	struct client		*c = cf->c;
361 
362 	if (~c->flags & CLIENT_DEAD)
363 		file_push(cf);
364 	file_free(cf);
365 }
366 
367 void
368 file_push(struct client_file *cf)
369 {
370 	struct client		*c = cf->c;
371 	struct msg_write_data	*msg;
372 	size_t			 msglen, sent, left;
373 	struct msg_write_close	 close;
374 
375 	msg = xmalloc(sizeof *msg);
376 	left = EVBUFFER_LENGTH(cf->buffer);
377 	while (left != 0) {
378 		sent = left;
379 		if (sent > MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg)
380 			sent = MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg;
381 
382 		msglen = (sizeof *msg) + sent;
383 		msg = xrealloc(msg, msglen);
384 		msg->stream = cf->stream;
385 		memcpy(msg + 1, EVBUFFER_DATA(cf->buffer), sent);
386 		if (proc_send(c->peer, MSG_WRITE, -1, msg, msglen) != 0)
387 			break;
388 		evbuffer_drain(cf->buffer, sent);
389 
390 		left = EVBUFFER_LENGTH(cf->buffer);
391 		log_debug("%s: file %d sent %zu, left %zu", c->name, cf->stream,
392 		    sent, left);
393 	}
394 	if (left != 0) {
395 		cf->references++;
396 		event_once(-1, EV_TIMEOUT, file_push_cb, cf, NULL);
397 	} else if (cf->stream > 2) {
398 		close.stream = cf->stream;
399 		proc_send(c->peer, MSG_WRITE_CLOSE, -1, &close, sizeof close);
400 		file_fire_done(cf);
401 	}
402 	free(msg);
403 }
404