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