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