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