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