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