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