1 /*- 2 * Copyright (c) 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * Copyright (c) 1993, 1994, 1995, 1996 5 * Keith Bostic. All rights reserved. 6 * 7 * See the LICENSE file for redistribution information. 8 */ 9 10 #include "config.h" 11 12 #include <sys/types.h> 13 #include <sys/queue.h> 14 #include <sys/stat.h> 15 16 /* 17 * We include <sys/file.h>, because the open #defines were found there 18 * on historical systems. We also include <fcntl.h> because the open(2) 19 * #defines are found there on newer systems. 20 */ 21 #include <sys/file.h> 22 23 #include <bitstring.h> 24 #include <dirent.h> 25 #include <errno.h> 26 #include <fcntl.h> 27 #include <limits.h> 28 #include <pwd.h> 29 #include <netinet/in.h> /* Required by resolv.h. */ 30 #include <resolv.h> 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #include <time.h> 35 #include <unistd.h> 36 37 #include "../ex/version.h" 38 #include "common.h" 39 #include "pathnames.h" 40 41 /* 42 * Recovery code. 43 * 44 * The basic scheme is as follows. In the EXF structure, we maintain full 45 * paths of a b+tree file and a mail recovery file. The former is the file 46 * used as backing store by the DB package. The latter is the file that 47 * contains an email message to be sent to the user if we crash. The two 48 * simple states of recovery are: 49 * 50 * + first starting the edit session: 51 * the b+tree file exists and is mode 700, the mail recovery 52 * file doesn't exist. 53 * + after the file has been modified: 54 * the b+tree file exists and is mode 600, the mail recovery 55 * file exists, and is exclusively locked. 56 * 57 * In the EXF structure we maintain a file descriptor that is the locked 58 * file descriptor for the mail recovery file. 59 * 60 * To find out if a recovery file/backing file pair are in use, try to get 61 * a lock on the recovery file. 62 * 63 * To find out if a backing file can be deleted at boot time, check for an 64 * owner execute bit. (Yes, I know it's ugly, but it's either that or put 65 * special stuff into the backing file itself, or correlate the files at 66 * boot time, neither of which looks like fun.) Note also that there's a 67 * window between when the file is created and the X bit is set. It's small, 68 * but it's there. To fix the window, check for 0 length files as well. 69 * 70 * To find out if a file can be recovered, check the F_RCV_ON bit. Note, 71 * this DOES NOT mean that any initialization has been done, only that we 72 * haven't yet failed at setting up or doing recovery. 73 * 74 * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit. 75 * If that bit is not set when ending a file session: 76 * If the EXF structure paths (rcv_path and rcv_mpath) are not NULL, 77 * they are unlink(2)'d, and free(3)'d. 78 * If the EXF file descriptor (rcv_fd) is not -1, it is closed. 79 * 80 * The backing b+tree file is set up when a file is first edited, so that 81 * the DB package can use it for on-disk caching and/or to snapshot the 82 * file. When the file is first modified, the mail recovery file is created, 83 * the backing file permissions are updated, the file is sync(2)'d to disk, 84 * and the timer is started. Then, at RCV_PERIOD second intervals, the 85 * b+tree file is synced to disk. RCV_PERIOD is measured using SIGALRM, which 86 * means that the data structures (SCR, EXF, the underlying tree structures) 87 * must be consistent when the signal arrives. 88 * 89 * The recovery mail file contains normal mail headers, with two additional 90 * 91 * X-vi-data: <file|path>;<base64 encoded path> 92 * 93 * MIME headers; the folding character is limited to ' '. 94 * 95 * Btree files are named "vi.XXXXXX" and recovery files are named 96 * "recover.XXXXXX". 97 */ 98 99 #define VI_DHEADER "X-vi-data:" 100 101 static int rcv_copy(SCR *, int, char *); 102 static void rcv_email(SCR *, char *); 103 static int rcv_mailfile(SCR *, int, char *); 104 static int rcv_mktemp(SCR *, char *, char *); 105 static int rcv_dlnwrite(SCR *, const char *, const char *, FILE *); 106 static int rcv_dlnread(SCR *, char **, char **, FILE *); 107 108 /* 109 * rcv_tmp -- 110 * Build a file name that will be used as the recovery file. 111 * 112 * PUBLIC: int rcv_tmp(SCR *, EXF *, char *); 113 */ 114 int 115 rcv_tmp(SCR *sp, EXF *ep, char *name) 116 { 117 struct stat sb; 118 int fd; 119 char *dp, *path; 120 121 /* 122 * !!! 123 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. 124 * 125 * 126 * If the recovery directory doesn't exist, try and create it. As 127 * the recovery files are themselves protected from reading/writing 128 * by other than the owner, the worst that can happen is that a user 129 * would have permission to remove other user's recovery files. If 130 * the sticky bit has the BSD semantics, that too will be impossible. 131 */ 132 if (opts_empty(sp, O_RECDIR, 0)) 133 goto err; 134 dp = O_STR(sp, O_RECDIR); 135 if (stat(dp, &sb)) { 136 if (errno != ENOENT || mkdir(dp, 0)) { 137 msgq(sp, M_SYSERR, "%s", dp); 138 goto err; 139 } 140 (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX); 141 } 142 143 if ((path = join(dp, "vi.XXXXXX")) == NULL) 144 goto err; 145 if ((fd = rcv_mktemp(sp, path, dp)) == -1) { 146 free(path); 147 goto err; 148 } 149 (void)fchmod(fd, S_IRWXU); 150 (void)close(fd); 151 152 ep->rcv_path = path; 153 if (0) { 154 err: msgq(sp, M_ERR, 155 "056|Modifications not recoverable if the session fails"); 156 return (1); 157 } 158 159 /* We believe the file is recoverable. */ 160 F_SET(ep, F_RCV_ON); 161 return (0); 162 } 163 164 /* 165 * rcv_init -- 166 * Force the file to be snapshotted for recovery. 167 * 168 * PUBLIC: int rcv_init(SCR *); 169 */ 170 int 171 rcv_init(SCR *sp) 172 { 173 EXF *ep; 174 recno_t lno; 175 176 ep = sp->ep; 177 178 /* Only do this once. */ 179 F_CLR(ep, F_FIRSTMODIFY); 180 181 /* If we already know the file isn't recoverable, we're done. */ 182 if (!F_ISSET(ep, F_RCV_ON)) 183 return (0); 184 185 /* Turn off recoverability until we figure out if this will work. */ 186 F_CLR(ep, F_RCV_ON); 187 188 /* Test if we're recovering a file, not editing one. */ 189 if (ep->rcv_mpath == NULL) { 190 /* Build a file to mail to the user. */ 191 if (rcv_mailfile(sp, 0, NULL)) 192 goto err; 193 194 /* Force a read of the entire file. */ 195 if (db_last(sp, &lno)) 196 goto err; 197 198 /* Turn on a busy message, and sync it to backing store. */ 199 sp->gp->scr_busy(sp, 200 "057|Copying file for recovery...", BUSY_ON); 201 if (ep->db->sync(ep->db, R_RECNOSYNC)) { 202 msgq_str(sp, M_SYSERR, ep->rcv_path, 203 "058|Preservation failed: %s"); 204 sp->gp->scr_busy(sp, NULL, BUSY_OFF); 205 goto err; 206 } 207 sp->gp->scr_busy(sp, NULL, BUSY_OFF); 208 } 209 210 /* Turn off the owner execute bit. */ 211 (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR); 212 213 /* We believe the file is recoverable. */ 214 F_SET(ep, F_RCV_ON); 215 return (0); 216 217 err: msgq(sp, M_ERR, 218 "059|Modifications not recoverable if the session fails"); 219 return (1); 220 } 221 222 /* 223 * rcv_sync -- 224 * Sync the file, optionally: 225 * flagging the backup file to be preserved 226 * snapshotting the backup file and send email to the user 227 * sending email to the user if the file was modified 228 * ending the file session 229 * 230 * PUBLIC: int rcv_sync(SCR *, u_int); 231 */ 232 int 233 rcv_sync(SCR *sp, u_int flags) 234 { 235 EXF *ep; 236 int fd, rval; 237 char *dp, *buf; 238 239 /* Make sure that there's something to recover/sync. */ 240 ep = sp->ep; 241 if (ep == NULL || !F_ISSET(ep, F_RCV_ON)) 242 return (0); 243 244 /* Sync the file if it's been modified. */ 245 if (F_ISSET(ep, F_MODIFIED)) { 246 if (ep->db->sync(ep->db, R_RECNOSYNC)) { 247 F_CLR(ep, F_RCV_ON | F_RCV_NORM); 248 msgq_str(sp, M_SYSERR, 249 ep->rcv_path, "060|File backup failed: %s"); 250 return (1); 251 } 252 253 /* REQUEST: don't remove backing file on exit. */ 254 if (LF_ISSET(RCV_PRESERVE)) 255 F_SET(ep, F_RCV_NORM); 256 257 /* REQUEST: send email. */ 258 if (LF_ISSET(RCV_EMAIL)) 259 rcv_email(sp, ep->rcv_mpath); 260 } 261 262 /* 263 * !!! 264 * Each time the user exec's :preserve, we have to snapshot all of 265 * the recovery information, i.e. it's like the user re-edited the 266 * file. We copy the DB(3) backing file, and then create a new mail 267 * recovery file, it's simpler than exiting and reopening all of the 268 * underlying files. 269 * 270 * REQUEST: snapshot the file. 271 */ 272 rval = 0; 273 if (LF_ISSET(RCV_SNAPSHOT)) { 274 if (opts_empty(sp, O_RECDIR, 0)) 275 goto err; 276 dp = O_STR(sp, O_RECDIR); 277 if ((buf = join(dp, "vi.XXXXXX")) == NULL) { 278 msgq(sp, M_SYSERR, NULL); 279 goto err; 280 } 281 if ((fd = rcv_mktemp(sp, buf, dp)) == -1) { 282 free(buf); 283 goto err; 284 } 285 sp->gp->scr_busy(sp, 286 "061|Copying file for recovery...", BUSY_ON); 287 if (rcv_copy(sp, fd, ep->rcv_path) || 288 close(fd) || rcv_mailfile(sp, 1, buf)) { 289 (void)unlink(buf); 290 (void)close(fd); 291 rval = 1; 292 } 293 free(buf); 294 sp->gp->scr_busy(sp, NULL, BUSY_OFF); 295 } 296 if (0) { 297 err: rval = 1; 298 } 299 300 /* REQUEST: end the file session. */ 301 if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1)) 302 rval = 1; 303 304 return (rval); 305 } 306 307 /* 308 * rcv_mailfile -- 309 * Build the file to mail to the user. 310 */ 311 static int 312 rcv_mailfile(SCR *sp, int issync, char *cp_path) 313 { 314 EXF *ep; 315 GS *gp; 316 struct passwd *pw; 317 int len; 318 time_t now; 319 uid_t uid; 320 int fd; 321 FILE *fp; 322 char *dp, *p, *t, *qt, *buf, *mpath; 323 char *t1, *t2, *t3; 324 int st; 325 326 /* 327 * XXX 328 * MAXHOSTNAMELEN/HOST_NAME_MAX are deprecated. We try sysconf(3) 329 * first, then fallback to _POSIX_HOST_NAME_MAX. 330 */ 331 char *host; 332 long hostmax = sysconf(_SC_HOST_NAME_MAX); 333 if (hostmax < 0) 334 hostmax = _POSIX_HOST_NAME_MAX; 335 336 gp = sp->gp; 337 if ((pw = getpwuid(uid = getuid())) == NULL) { 338 msgq(sp, M_ERR, 339 "062|Information on user id %u not found", uid); 340 return (1); 341 } 342 343 if (opts_empty(sp, O_RECDIR, 0)) 344 return (1); 345 dp = O_STR(sp, O_RECDIR); 346 if ((mpath = join(dp, "recover.XXXXXX")) == NULL) { 347 msgq(sp, M_SYSERR, NULL); 348 return (1); 349 } 350 if ((fd = rcv_mktemp(sp, mpath, dp)) == -1) { 351 free(mpath); 352 return (1); 353 } 354 if ((fp = fdopen(fd, "w")) == NULL) { 355 free(mpath); 356 close(fd); 357 return (1); 358 } 359 360 /* 361 * XXX 362 * We keep an open lock on the file so that the recover option can 363 * distinguish between files that are live and those that need to 364 * be recovered. There's an obvious window between the mkstemp call 365 * and the lock, but it's pretty small. 366 */ 367 ep = sp->ep; 368 if (file_lock(sp, NULL, fd, 1) != LOCK_SUCCESS) 369 msgq(sp, M_SYSERR, "063|Unable to lock recovery file"); 370 if (!issync) { 371 /* Save the recover file descriptor, and mail path. */ 372 ep->rcv_fd = dup(fd); 373 ep->rcv_mpath = mpath; 374 cp_path = ep->rcv_path; 375 } 376 377 t = sp->frp->name; 378 if ((p = strrchr(t, '/')) == NULL) 379 p = t; 380 else 381 ++p; 382 (void)time(&now); 383 384 if ((st = rcv_dlnwrite(sp, "file", t, fp))) { 385 if (st == 1) 386 goto werr; 387 goto err; 388 } 389 if ((st = rcv_dlnwrite(sp, "path", cp_path, fp))) { 390 if (st == 1) 391 goto werr; 392 goto err; 393 } 394 395 MALLOC(sp, host, hostmax + 1); 396 if (host == NULL) 397 goto err; 398 (void)gethostname(host, hostmax + 1); 399 400 len = fprintf(fp, "%s%s%s\n%s%s%s%s\n%s%.40s\n%s\n\n", 401 "From: root@", host, " (Nvi recovery program)", 402 "To: ", pw->pw_name, "@", host, 403 "Subject: Nvi saved the file ", p, 404 "Precedence: bulk"); /* For vacation(1). */ 405 if (len < 0) { 406 free(host); 407 goto werr; 408 } 409 410 if ((qt = quote(t)) == NULL) { 411 free(host); 412 msgq(sp, M_SYSERR, NULL); 413 goto err; 414 } 415 len = asprintf(&buf, "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n", 416 "On ", ctime(&now), ", the user ", pw->pw_name, 417 " was editing a file named ", t, " on the machine ", 418 host, ", when it was saved for recovery. ", 419 "You can recover most, if not all, of the changes ", 420 "to this file using the -r option to ", getprogname(), ":\n\n\t", 421 getprogname(), " -r ", qt); 422 free(qt); 423 free(host); 424 if (len == -1) { 425 msgq(sp, M_SYSERR, NULL); 426 goto err; 427 } 428 429 /* 430 * Format the message. (Yes, I know it's silly.) 431 * Requires that the message end in a <newline>. 432 */ 433 #define FMTCOLS 60 434 for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) { 435 /* Check for a short length. */ 436 if (len <= FMTCOLS) { 437 t2 = t1 + (len - 1); 438 goto wout; 439 } 440 441 /* Check for a required <newline>. */ 442 t2 = strchr(t1, '\n'); 443 if (t2 - t1 <= FMTCOLS) 444 goto wout; 445 446 /* Find the closest space, if any. */ 447 for (t3 = t2; t2 > t1; --t2) 448 if (*t2 == ' ') { 449 if (t2 - t1 <= FMTCOLS) 450 goto wout; 451 t3 = t2; 452 } 453 t2 = t3; 454 455 /* t2 points to the last character to display. */ 456 wout: *t2++ = '\n'; 457 458 /* t2 points one after the last character to display. */ 459 if (fwrite(t1, 1, t2 - t1, fp) != t2 - t1) { 460 free(buf); 461 goto werr; 462 } 463 } 464 465 if (issync) { 466 fflush(fp); 467 rcv_email(sp, mpath); 468 free(mpath); 469 } 470 if (fclose(fp)) { 471 free(buf); 472 werr: msgq(sp, M_SYSERR, "065|Recovery file"); 473 goto err; 474 } 475 free(buf); 476 return (0); 477 478 err: if (!issync) 479 ep->rcv_fd = -1; 480 if (fp != NULL) 481 (void)fclose(fp); 482 return (1); 483 } 484 485 /* 486 * people making love 487 * never exactly the same 488 * just like a snowflake 489 * 490 * rcv_list -- 491 * List the files that can be recovered by this user. 492 * 493 * PUBLIC: int rcv_list(SCR *); 494 */ 495 int 496 rcv_list(SCR *sp) 497 { 498 struct dirent *dp; 499 struct stat sb; 500 DIR *dirp; 501 FILE *fp; 502 int found; 503 char *p, *file, *path; 504 char *dtype, *data; 505 int st; 506 507 /* Open the recovery directory for reading. */ 508 if (opts_empty(sp, O_RECDIR, 0)) 509 return (1); 510 p = O_STR(sp, O_RECDIR); 511 if (chdir(p) || (dirp = opendir(".")) == NULL) { 512 msgq_str(sp, M_SYSERR, p, "recdir: %s"); 513 return (1); 514 } 515 516 /* Read the directory. */ 517 for (found = 0; (dp = readdir(dirp)) != NULL;) { 518 if (strncmp(dp->d_name, "recover.", 8)) 519 continue; 520 521 /* If it's readable, it's recoverable. */ 522 if ((fp = fopen(dp->d_name, "r")) == NULL) 523 continue; 524 525 switch (file_lock(sp, NULL, fileno(fp), 1)) { 526 case LOCK_FAILED: 527 /* 528 * XXX 529 * Assume that a lock can't be acquired, but that we 530 * should permit recovery anyway. If this is wrong, 531 * and someone else is using the file, we're going to 532 * die horribly. 533 */ 534 break; 535 case LOCK_SUCCESS: 536 break; 537 case LOCK_UNAVAIL: 538 /* If it's locked, it's live. */ 539 (void)fclose(fp); 540 continue; 541 } 542 543 /* Check the headers. */ 544 for (file = NULL, path = NULL; 545 file == NULL || path == NULL;) { 546 if ((st = rcv_dlnread(sp, &dtype, &data, fp))) { 547 if (st == 1) 548 msgq_str(sp, M_ERR, dp->d_name, 549 "066|%s: malformed recovery file"); 550 goto next; 551 } 552 if (dtype == NULL) 553 continue; 554 if (!strcmp(dtype, "file")) 555 file = data; 556 else if (!strcmp(dtype, "path")) 557 path = data; 558 else 559 free(data); 560 } 561 562 /* 563 * If the file doesn't exist, it's an orphaned recovery file, 564 * toss it. 565 * 566 * XXX 567 * This can occur if the backup file was deleted and we crashed 568 * before deleting the email file. 569 */ 570 errno = 0; 571 if (stat(path, &sb) && 572 errno == ENOENT) { 573 (void)unlink(dp->d_name); 574 goto next; 575 } 576 577 /* Get the last modification time and display. */ 578 (void)fstat(fileno(fp), &sb); 579 (void)printf("%.24s: %s\n", 580 ctime(&sb.st_mtime), file); 581 found = 1; 582 583 /* Close, discarding lock. */ 584 next: (void)fclose(fp); 585 free(file); 586 free(path); 587 } 588 if (found == 0) 589 (void)printf("%s: No files to recover\n", getprogname()); 590 (void)closedir(dirp); 591 return (0); 592 } 593 594 /* 595 * rcv_read -- 596 * Start a recovered file as the file to edit. 597 * 598 * PUBLIC: int rcv_read(SCR *, FREF *); 599 */ 600 int 601 rcv_read(SCR *sp, FREF *frp) 602 { 603 struct dirent *dp; 604 struct stat sb; 605 DIR *dirp; 606 FILE *fp; 607 EXF *ep; 608 struct timespec rec_mtim = { 0, 0 }; 609 int found, locked = 0, requested, sv_fd; 610 char *name, *p, *t, *rp, *recp, *pathp; 611 char *file, *path, *recpath; 612 char *dtype, *data; 613 int st; 614 615 if (opts_empty(sp, O_RECDIR, 0)) 616 return (1); 617 rp = O_STR(sp, O_RECDIR); 618 if ((dirp = opendir(rp)) == NULL) { 619 msgq_str(sp, M_SYSERR, rp, "%s"); 620 return (1); 621 } 622 623 name = frp->name; 624 sv_fd = -1; 625 recp = pathp = NULL; 626 for (found = requested = 0; (dp = readdir(dirp)) != NULL;) { 627 if (strncmp(dp->d_name, "recover.", 8)) 628 continue; 629 if ((recpath = join(rp, dp->d_name)) == NULL) { 630 msgq(sp, M_SYSERR, NULL); 631 continue; 632 } 633 634 /* If it's readable, it's recoverable. */ 635 if ((fp = fopen(recpath, "r")) == NULL) { 636 free(recpath); 637 continue; 638 } 639 640 switch (file_lock(sp, NULL, fileno(fp), 1)) { 641 case LOCK_FAILED: 642 /* 643 * XXX 644 * Assume that a lock can't be acquired, but that we 645 * should permit recovery anyway. If this is wrong, 646 * and someone else is using the file, we're going to 647 * die horribly. 648 */ 649 locked = 0; 650 break; 651 case LOCK_SUCCESS: 652 locked = 1; 653 break; 654 case LOCK_UNAVAIL: 655 /* If it's locked, it's live. */ 656 (void)fclose(fp); 657 continue; 658 } 659 660 /* Check the headers. */ 661 for (file = NULL, path = NULL; 662 file == NULL || path == NULL;) { 663 if ((st = rcv_dlnread(sp, &dtype, &data, fp))) { 664 if (st == 1) 665 msgq_str(sp, M_ERR, dp->d_name, 666 "067|%s: malformed recovery file"); 667 goto next; 668 } 669 if (dtype == NULL) 670 continue; 671 if (!strcmp(dtype, "file")) 672 file = data; 673 else if (!strcmp(dtype, "path")) 674 path = data; 675 else 676 free(data); 677 } 678 ++found; 679 680 /* 681 * If the file doesn't exist, it's an orphaned recovery file, 682 * toss it. 683 * 684 * XXX 685 * This can occur if the backup file was deleted and we crashed 686 * before deleting the email file. 687 */ 688 errno = 0; 689 if (stat(path, &sb) && 690 errno == ENOENT) { 691 (void)unlink(dp->d_name); 692 goto next; 693 } 694 695 /* Check the file name. */ 696 if (strcmp(file, name)) 697 goto next; 698 699 ++requested; 700 701 /* If we've found more than one, take the most recent. */ 702 (void)fstat(fileno(fp), &sb); 703 if (recp == NULL || 704 timespeccmp(&rec_mtim, &sb.st_mtim, <)) { 705 p = recp; 706 t = pathp; 707 recp = recpath; 708 pathp = path; 709 if (p != NULL) { 710 free(p); 711 free(t); 712 } 713 rec_mtim = sb.st_mtim; 714 if (sv_fd != -1) 715 (void)close(sv_fd); 716 sv_fd = dup(fileno(fp)); 717 } else { 718 next: free(recpath); 719 free(path); 720 } 721 (void)fclose(fp); 722 free(file); 723 } 724 (void)closedir(dirp); 725 726 if (recp == NULL) { 727 msgq_str(sp, M_INFO, name, 728 "068|No files named %s, readable by you, to recover"); 729 return (1); 730 } 731 if (found) { 732 if (requested > 1) 733 msgq(sp, M_INFO, 734 "069|There are older versions of this file for you to recover"); 735 if (found > requested) 736 msgq(sp, M_INFO, 737 "070|There are other files for you to recover"); 738 } 739 740 /* 741 * Create the FREF structure, start the btree file. 742 * 743 * XXX 744 * file_init() is going to set ep->rcv_path. 745 */ 746 if (file_init(sp, frp, pathp, 0)) { 747 free(recp); 748 free(pathp); 749 (void)close(sv_fd); 750 return (1); 751 } 752 free(pathp); 753 754 /* 755 * We keep an open lock on the file so that the recover option can 756 * distinguish between files that are live and those that need to 757 * be recovered. The lock is already acquired, just copy it. 758 */ 759 ep = sp->ep; 760 ep->rcv_mpath = recp; 761 ep->rcv_fd = sv_fd; 762 if (!locked) 763 F_SET(frp, FR_UNLOCKED); 764 765 /* We believe the file is recoverable. */ 766 F_SET(ep, F_RCV_ON); 767 return (0); 768 } 769 770 /* 771 * rcv_copy -- 772 * Copy a recovery file. 773 */ 774 static int 775 rcv_copy(SCR *sp, int wfd, char *fname) 776 { 777 int nr, nw, off, rfd; 778 char buf[8 * 1024]; 779 780 if ((rfd = open(fname, O_RDONLY, 0)) == -1) 781 goto err; 782 while ((nr = read(rfd, buf, sizeof(buf))) > 0) 783 for (off = 0; nr; nr -= nw, off += nw) 784 if ((nw = write(wfd, buf + off, nr)) < 0) 785 goto err; 786 if (nr == 0) 787 return (0); 788 789 err: msgq_str(sp, M_SYSERR, fname, "%s"); 790 return (1); 791 } 792 793 /* 794 * rcv_mktemp -- 795 * Paranoid make temporary file routine. 796 */ 797 static int 798 rcv_mktemp(SCR *sp, char *path, char *dname) 799 { 800 int fd; 801 802 if ((fd = mkstemp(path)) == -1) 803 msgq_str(sp, M_SYSERR, dname, "%s"); 804 return (fd); 805 } 806 807 /* 808 * rcv_email -- 809 * Send email. 810 */ 811 static void 812 rcv_email(SCR *sp, char *fname) 813 { 814 char *buf; 815 816 if (asprintf(&buf, _PATH_SENDMAIL " -odb -t < %s", fname) == -1) { 817 msgq_str(sp, M_ERR, strerror(errno), 818 "071|not sending email: %s"); 819 return; 820 } 821 (void)system(buf); 822 free(buf); 823 } 824 825 /* 826 * rcv_dlnwrite -- 827 * Encode a string into an X-vi-data line and write it. 828 */ 829 static int 830 rcv_dlnwrite(SCR *sp, const char *dtype, const char *src, FILE *fp) 831 { 832 char *bp = NULL, *p; 833 size_t blen = 0; 834 size_t dlen, len; 835 int plen, xlen; 836 837 len = strlen(src); 838 dlen = strlen(dtype); 839 GET_SPACE_GOTOC(sp, bp, blen, (len + 2) / 3 * 4 + dlen + 2); 840 (void)memcpy(bp, dtype, dlen); 841 bp[dlen] = ';'; 842 if ((xlen = b64_ntop((u_char *)src, 843 len, bp + dlen + 1, blen)) == -1) 844 goto err; 845 xlen += dlen + 1; 846 847 /* Output as an MIME folding header. */ 848 if ((plen = fprintf(fp, VI_DHEADER " %.*s\n", 849 FMTCOLS - (int)sizeof(VI_DHEADER), bp)) < 0) 850 goto err; 851 plen -= (int)sizeof(VI_DHEADER) + 1; 852 for (p = bp, xlen -= plen; xlen > 0; xlen -= plen) { 853 p += plen; 854 if ((plen = fprintf(fp, " %.*s\n", FMTCOLS - 1, p)) < 0) 855 goto err; 856 plen -= 2; 857 } 858 FREE_SPACE(sp, bp, blen); 859 return (0); 860 861 err: FREE_SPACE(sp, bp, blen); 862 return (1); 863 alloc_err: 864 msgq(sp, M_SYSERR, NULL); 865 return (-1); 866 } 867 868 /* 869 * rcv_dlnread -- 870 * Read an X-vi-data line and decode it. 871 */ 872 static int 873 rcv_dlnread(SCR *sp, char **dtypep, 874 char **datap, /* free *datap if != NULL after use. */ 875 FILE *fp) 876 { 877 int ch; 878 char buf[1024]; 879 char *bp = NULL, *p, *src; 880 size_t blen = 0; 881 size_t len, off, dlen; 882 char *dtype, *data; 883 int xlen; 884 885 if (fgets(buf, sizeof(buf), fp) == NULL) 886 return (1); 887 if (strncmp(buf, VI_DHEADER, sizeof(VI_DHEADER) - 1)) { 888 *dtypep = NULL; 889 *datap = NULL; 890 return (0); 891 } 892 893 /* Fetch an MIME folding header. */ 894 len = strlen(buf) - sizeof(VI_DHEADER) + 1; 895 GET_SPACE_GOTOC(sp, bp, blen, len); 896 (void)memcpy(bp, buf + sizeof(VI_DHEADER) - 1, len); 897 p = bp + len; 898 while ((ch = fgetc(fp)) == ' ') { 899 if (fgets(buf, sizeof(buf), fp) == NULL) 900 goto err; 901 off = strlen(buf); 902 len += off; 903 ADD_SPACE_GOTOC(sp, bp, blen, len); 904 p = bp + len - off; 905 (void)memcpy(p, buf, off); 906 } 907 bp[len] = '\0'; 908 (void)ungetc(ch, fp); 909 910 for (p = bp; *p == ' ' || *p == '\n'; p++); 911 if ((src = strchr(p, ';')) == NULL) 912 goto err; 913 dlen = src - p; 914 src += 1; 915 len -= src - bp; 916 917 /* Memory looks like: "<data>\0<dtype>\0". */ 918 MALLOC(sp, data, dlen + len / 4 * 3 + 2); 919 if (data == NULL) 920 goto err; 921 if ((xlen = (b64_pton(p + dlen + 1, 922 (u_char *)data, len / 4 * 3 + 1))) == -1) { 923 free(data); 924 goto err; 925 } 926 data[xlen] = '\0'; 927 dtype = data + xlen + 1; 928 (void)memcpy(dtype, p, dlen); 929 dtype[dlen] = '\0'; 930 FREE_SPACE(sp, bp, blen); 931 *dtypep = dtype; 932 *datap = data; 933 return (0); 934 935 err: FREE_SPACE(sp, bp, blen); 936 return (1); 937 alloc_err: 938 msgq(sp, M_SYSERR, NULL); 939 return (-1); 940 } 941