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.2 2012/10/09 08:06:58 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 __P((SCR *, int, char *)); 106 static void rcv_email __P((SCR *, char *)); 107 static int rcv_mailfile __P((SCR *, int, char *)); 108 static int rcv_mktemp __P((SCR *, char *, char *)); 109 static int rcv_dlnwrite __P((SCR *, const char *, const char *, FILE *)); 110 static int rcv_dlnread __P((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 __P((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 __P((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 __P((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 SIGBLOCK; 256 if (ep->db->sync(ep->db, R_RECNOSYNC)) { 257 F_CLR(ep, F_RCV_ON | F_RCV_NORM); 258 msgq_str(sp, M_SYSERR, 259 ep->rcv_path, "060|File backup failed: %s"); 260 SIGUNBLOCK; 261 return (1); 262 } 263 SIGUNBLOCK; 264 265 /* REQUEST: don't remove backing file on exit. */ 266 if (LF_ISSET(RCV_PRESERVE)) 267 F_SET(ep, F_RCV_NORM); 268 269 /* REQUEST: send email. */ 270 if (LF_ISSET(RCV_EMAIL)) 271 rcv_email(sp, ep->rcv_mpath); 272 } 273 274 /* 275 * !!! 276 * Each time the user exec's :preserve, we have to snapshot all of 277 * the recovery information, i.e. it's like the user re-edited the 278 * file. We copy the DB(3) backing file, and then create a new mail 279 * recovery file, it's simpler than exiting and reopening all of the 280 * underlying files. 281 * 282 * REQUEST: snapshot the file. 283 */ 284 rval = 0; 285 if (LF_ISSET(RCV_SNAPSHOT)) { 286 if (opts_empty(sp, O_RECDIR, 0)) 287 goto err; 288 dp = O_STR(sp, O_RECDIR); 289 if ((buf = join(dp, "vi.XXXXXX")) == NULL) { 290 msgq(sp, M_SYSERR, NULL); 291 goto err; 292 } 293 if ((fd = rcv_mktemp(sp, buf, dp)) == -1) { 294 free(buf); 295 goto err; 296 } 297 sp->gp->scr_busy(sp, 298 "061|Copying file for recovery...", BUSY_ON); 299 if (rcv_copy(sp, fd, ep->rcv_path) || 300 close(fd) || rcv_mailfile(sp, 1, buf)) { 301 (void)unlink(buf); 302 (void)close(fd); 303 rval = 1; 304 } 305 free(buf); 306 sp->gp->scr_busy(sp, NULL, BUSY_OFF); 307 } 308 if (0) { 309 err: rval = 1; 310 } 311 312 /* REQUEST: end the file session. */ 313 if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1)) 314 rval = 1; 315 316 return (rval); 317 } 318 319 /* 320 * rcv_mailfile -- 321 * Build the file to mail to the user. 322 */ 323 static int 324 rcv_mailfile( 325 SCR *sp, 326 int issync, 327 char *cp_path) 328 { 329 EXF *ep; 330 GS *gp; 331 struct passwd *pw; 332 int len; 333 time_t now; 334 uid_t uid; 335 int fd; 336 FILE *fp; 337 char *dp, *p, *t, *qt, *buf, *mpath; 338 char *t1, *t2, *t3; 339 int st; 340 341 /* 342 * XXX 343 * MAXHOSTNAMELEN/HOST_NAME_MAX are deprecated. We try sysconf(3) 344 * first, then fallback to _POSIX_HOST_NAME_MAX. 345 */ 346 char *host; 347 long hostmax = sysconf(_SC_HOST_NAME_MAX); 348 if (hostmax < 0) 349 hostmax = _POSIX_HOST_NAME_MAX; 350 351 gp = sp->gp; 352 if ((pw = getpwuid(uid = getuid())) == NULL) { 353 msgq(sp, M_ERR, 354 "062|Information on user id %u not found", uid); 355 return (1); 356 } 357 358 if (opts_empty(sp, O_RECDIR, 0)) 359 return (1); 360 dp = O_STR(sp, O_RECDIR); 361 if ((mpath = join(dp, "recover.XXXXXX")) == NULL) { 362 msgq(sp, M_SYSERR, NULL); 363 return (1); 364 } 365 if ((fd = rcv_mktemp(sp, mpath, dp)) == -1) { 366 free(mpath); 367 return (1); 368 } 369 if ((fp = fdopen(fd, "w")) == NULL) { 370 free(mpath); 371 close(fd); 372 return (1); 373 } 374 375 /* 376 * XXX 377 * We keep an open lock on the file so that the recover option can 378 * distinguish between files that are live and those that need to 379 * be recovered. There's an obvious window between the mkstemp call 380 * and the lock, but it's pretty small. 381 */ 382 ep = sp->ep; 383 if (file_lock(sp, NULL, fd, 1) != LOCK_SUCCESS) 384 msgq(sp, M_SYSERR, "063|Unable to lock recovery file"); 385 if (!issync) { 386 /* Save the recover file descriptor, and mail path. */ 387 ep->rcv_fd = dup(fd); 388 ep->rcv_mpath = mpath; 389 cp_path = ep->rcv_path; 390 } 391 392 t = sp->frp->name; 393 if ((p = strrchr(t, '/')) == NULL) 394 p = t; 395 else 396 ++p; 397 (void)time(&now); 398 399 if ((st = rcv_dlnwrite(sp, "file", t, fp))) { 400 if (st == 1) 401 goto werr; 402 goto err; 403 } 404 if ((st = rcv_dlnwrite(sp, "path", cp_path, fp))) { 405 if (st == 1) 406 goto werr; 407 goto err; 408 } 409 410 MALLOC(sp, host, char *, hostmax + 1); 411 if (host == NULL) 412 goto err; 413 (void)gethostname(host, hostmax + 1); 414 415 len = fprintf(fp, "%s%s%s\n%s%s%s%s\n%s%.40s\n%s\n\n", 416 "From: root@", host, " (Nvi recovery program)", 417 "To: ", pw->pw_name, "@", host, 418 "Subject: Nvi saved the file ", p, 419 "Precedence: bulk"); /* For vacation(1). */ 420 if (len < 0) { 421 free(host); 422 goto werr; 423 } 424 425 if ((qt = quote(t)) == NULL) { 426 free(host); 427 msgq(sp, M_SYSERR, NULL); 428 goto err; 429 } 430 len = asprintf(&buf, "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n", 431 "On ", ctime(&now), ", the user ", pw->pw_name, 432 " was editing a file named ", t, " on the machine ", 433 host, ", when it was saved for recovery. ", 434 "You can recover most, if not all, of the changes ", 435 "to this file using the -r option to ", gp->progname, ":\n\n\t", 436 gp->progname, " -r ", qt); 437 free(qt); 438 free(host); 439 if (buf == NULL) { 440 msgq(sp, M_SYSERR, NULL); 441 goto err; 442 } 443 444 /* 445 * Format the message. (Yes, I know it's silly.) 446 * Requires that the message end in a <newline>. 447 */ 448 #define FMTCOLS 60 449 for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) { 450 /* Check for a short length. */ 451 if (len <= FMTCOLS) { 452 t2 = t1 + (len - 1); 453 goto wout; 454 } 455 456 /* Check for a required <newline>. */ 457 t2 = strchr(t1, '\n'); 458 if (t2 - t1 <= FMTCOLS) 459 goto wout; 460 461 /* Find the closest space, if any. */ 462 for (t3 = t2; t2 > t1; --t2) 463 if (*t2 == ' ') { 464 if (t2 - t1 <= FMTCOLS) 465 goto wout; 466 t3 = t2; 467 } 468 t2 = t3; 469 470 /* t2 points to the last character to display. */ 471 wout: *t2++ = '\n'; 472 473 /* t2 points one after the last character to display. */ 474 if (fwrite(t1, 1, t2 - t1, fp) != t2 - t1) { 475 free(buf); 476 goto werr; 477 } 478 } 479 480 if (issync) { 481 fflush(fp); 482 rcv_email(sp, mpath); 483 free(mpath); 484 } 485 if (fclose(fp)) { 486 free(buf); 487 werr: msgq(sp, M_SYSERR, "065|Recovery file"); 488 goto err; 489 } 490 free(buf); 491 return (0); 492 493 err: if (!issync) 494 ep->rcv_fd = -1; 495 if (fp != NULL) 496 (void)fclose(fp); 497 return (1); 498 } 499 500 /* 501 * people making love 502 * never exactly the same 503 * just like a snowflake 504 * 505 * rcv_list -- 506 * List the files that can be recovered by this user. 507 * 508 * PUBLIC: int rcv_list __P((SCR *)); 509 */ 510 int 511 rcv_list(SCR *sp) 512 { 513 struct dirent *dp; 514 struct stat sb; 515 DIR *dirp; 516 FILE *fp; 517 int found; 518 char *p, *file, *path; 519 char *dtype, *data; 520 int st; 521 522 /* Open the recovery directory for reading. */ 523 if (opts_empty(sp, O_RECDIR, 0)) 524 return (1); 525 p = O_STR(sp, O_RECDIR); 526 if (chdir(p) || (dirp = opendir(".")) == NULL) { 527 msgq_str(sp, M_SYSERR, p, "recdir: %s"); 528 return (1); 529 } 530 531 /* Read the directory. */ 532 for (found = 0; (dp = readdir(dirp)) != NULL;) { 533 if (strncmp(dp->d_name, "recover.", 8)) 534 continue; 535 536 /* If it's readable, it's recoverable. */ 537 if ((fp = fopen(dp->d_name, "r")) == NULL) 538 continue; 539 540 switch (file_lock(sp, NULL, fileno(fp), 1)) { 541 case LOCK_FAILED: 542 /* 543 * XXX 544 * Assume that a lock can't be acquired, but that we 545 * should permit recovery anyway. If this is wrong, 546 * and someone else is using the file, we're going to 547 * die horribly. 548 */ 549 break; 550 case LOCK_SUCCESS: 551 break; 552 case LOCK_UNAVAIL: 553 /* If it's locked, it's live. */ 554 (void)fclose(fp); 555 continue; 556 } 557 558 /* Check the headers. */ 559 for (file = NULL, path = NULL; 560 file == NULL || path == NULL;) { 561 if ((st = rcv_dlnread(sp, &dtype, &data, fp))) { 562 if (st == 1) 563 msgq_str(sp, M_ERR, dp->d_name, 564 "066|%s: malformed recovery file"); 565 goto next; 566 } 567 if (dtype == NULL) 568 continue; 569 if (!strcmp(dtype, "file")) 570 file = data; 571 else if (!strcmp(dtype, "path")) 572 path = data; 573 else 574 free(data); 575 } 576 577 /* 578 * If the file doesn't exist, it's an orphaned recovery file, 579 * toss it. 580 * 581 * XXX 582 * This can occur if the backup file was deleted and we crashed 583 * before deleting the email file. 584 */ 585 errno = 0; 586 if (stat(path, &sb) && 587 errno == ENOENT) { 588 (void)unlink(dp->d_name); 589 goto next; 590 } 591 592 /* Get the last modification time and display. */ 593 (void)fstat(fileno(fp), &sb); 594 (void)printf("%.24s: %s\n", 595 ctime(&sb.st_mtime), file); 596 found = 1; 597 598 /* Close, discarding lock. */ 599 next: (void)fclose(fp); 600 if (file != NULL) 601 free(file); 602 if (path != NULL) 603 free(path); 604 } 605 if (found == 0) 606 (void)printf("%s: No files to recover\n", sp->gp->progname); 607 (void)closedir(dirp); 608 return (0); 609 } 610 611 /* 612 * rcv_read -- 613 * Start a recovered file as the file to edit. 614 * 615 * PUBLIC: int rcv_read __P((SCR *, FREF *)); 616 */ 617 int 618 rcv_read( 619 SCR *sp, 620 FREF *frp) 621 { 622 struct dirent *dp; 623 struct stat sb; 624 DIR *dirp; 625 FILE *fp; 626 EXF *ep; 627 struct timespec rec_mtim = { 0, 0 }; 628 int found, locked = 0, requested, sv_fd; 629 char *name, *p, *t, *rp, *recp, *pathp; 630 char *file, *path, *recpath; 631 char *dtype, *data; 632 int st; 633 634 if (opts_empty(sp, O_RECDIR, 0)) 635 return (1); 636 rp = O_STR(sp, O_RECDIR); 637 if ((dirp = opendir(rp)) == NULL) { 638 msgq_str(sp, M_ERR, rp, "%s"); 639 return (1); 640 } 641 642 name = frp->name; 643 sv_fd = -1; 644 recp = pathp = NULL; 645 for (found = requested = 0; (dp = readdir(dirp)) != NULL;) { 646 if (strncmp(dp->d_name, "recover.", 8)) 647 continue; 648 if ((recpath = join(rp, dp->d_name)) == NULL) { 649 msgq(sp, M_SYSERR, NULL); 650 continue; 651 } 652 653 /* If it's readable, it's recoverable. */ 654 if ((fp = fopen(recpath, "r")) == NULL) { 655 free(recpath); 656 continue; 657 } 658 659 switch (file_lock(sp, NULL, fileno(fp), 1)) { 660 case LOCK_FAILED: 661 /* 662 * XXX 663 * Assume that a lock can't be acquired, but that we 664 * should permit recovery anyway. If this is wrong, 665 * and someone else is using the file, we're going to 666 * die horribly. 667 */ 668 locked = 0; 669 break; 670 case LOCK_SUCCESS: 671 locked = 1; 672 break; 673 case LOCK_UNAVAIL: 674 /* If it's locked, it's live. */ 675 (void)fclose(fp); 676 continue; 677 } 678 679 /* Check the headers. */ 680 for (file = NULL, path = NULL; 681 file == NULL || path == NULL;) { 682 if ((st = rcv_dlnread(sp, &dtype, &data, fp))) { 683 if (st == 1) 684 msgq_str(sp, M_ERR, dp->d_name, 685 "067|%s: malformed recovery file"); 686 goto next; 687 } 688 if (dtype == NULL) 689 continue; 690 if (!strcmp(dtype, "file")) 691 file = data; 692 else if (!strcmp(dtype, "path")) 693 path = data; 694 else 695 free(data); 696 } 697 ++found; 698 699 /* 700 * If the file doesn't exist, it's an orphaned recovery file, 701 * toss it. 702 * 703 * XXX 704 * This can occur if the backup file was deleted and we crashed 705 * before deleting the email file. 706 */ 707 errno = 0; 708 if (stat(path, &sb) && 709 errno == ENOENT) { 710 (void)unlink(dp->d_name); 711 goto next; 712 } 713 714 /* Check the file name. */ 715 if (strcmp(file, name)) 716 goto next; 717 718 ++requested; 719 720 /* If we've found more than one, take the most recent. */ 721 (void)fstat(fileno(fp), &sb); 722 if (recp == NULL || 723 timespeccmp(&rec_mtim, &sb.st_mtimespec, <)) { 724 p = recp; 725 t = pathp; 726 recp = recpath; 727 pathp = path; 728 if (p != NULL) { 729 free(p); 730 free(t); 731 } 732 rec_mtim = sb.st_mtimespec; 733 if (sv_fd != -1) 734 (void)close(sv_fd); 735 sv_fd = dup(fileno(fp)); 736 } else { 737 next: free(recpath); 738 if (path != NULL) 739 free(path); 740 } 741 (void)fclose(fp); 742 if (file != NULL) 743 free(file); 744 } 745 (void)closedir(dirp); 746 747 if (recp == NULL) { 748 msgq_str(sp, M_INFO, name, 749 "068|No files named %s, readable by you, to recover"); 750 return (1); 751 } 752 if (found) { 753 if (requested > 1) 754 msgq(sp, M_INFO, 755 "069|There are older versions of this file for you to recover"); 756 if (found > requested) 757 msgq(sp, M_INFO, 758 "070|There are other files for you to recover"); 759 } 760 761 /* 762 * Create the FREF structure, start the btree file. 763 * 764 * XXX 765 * file_init() is going to set ep->rcv_path. 766 */ 767 if (file_init(sp, frp, pathp, 0)) { 768 free(recp); 769 free(pathp); 770 (void)close(sv_fd); 771 return (1); 772 } 773 free(pathp); 774 775 /* 776 * We keep an open lock on the file so that the recover option can 777 * distinguish between files that are live and those that need to 778 * be recovered. The lock is already acquired, just copy it. 779 */ 780 ep = sp->ep; 781 ep->rcv_mpath = recp; 782 ep->rcv_fd = sv_fd; 783 if (!locked) 784 F_SET(frp, FR_UNLOCKED); 785 786 /* We believe the file is recoverable. */ 787 F_SET(ep, F_RCV_ON); 788 return (0); 789 } 790 791 /* 792 * rcv_copy -- 793 * Copy a recovery file. 794 */ 795 static int 796 rcv_copy( 797 SCR *sp, 798 int wfd, 799 char *fname) 800 { 801 int nr, nw, off, rfd; 802 char buf[8 * 1024]; 803 804 if ((rfd = open(fname, O_RDONLY, 0)) == -1) 805 goto err; 806 while ((nr = read(rfd, buf, sizeof(buf))) > 0) 807 for (off = 0; nr; nr -= nw, off += nw) 808 if ((nw = write(wfd, buf + off, nr)) < 0) 809 goto err; 810 if (nr == 0) 811 return (0); 812 813 err: msgq_str(sp, M_SYSERR, fname, "%s"); 814 return (1); 815 } 816 817 /* 818 * rcv_mktemp -- 819 * Paranoid make temporary file routine. 820 */ 821 static int 822 rcv_mktemp( 823 SCR *sp, 824 char *path, 825 char *dname) 826 { 827 int fd; 828 829 if ((fd = mkstemp(path)) == -1) 830 msgq_str(sp, M_SYSERR, dname, "%s"); 831 return (fd); 832 } 833 834 /* 835 * rcv_email -- 836 * Send email. 837 */ 838 static void 839 rcv_email( 840 SCR *sp, 841 char *fname) 842 { 843 char *buf; 844 845 (void)asprintf(&buf, _PATH_SENDMAIL " -odb -t < %s", fname); 846 if (buf == NULL) { 847 msgq_str(sp, M_ERR, strerror(errno), 848 "071|not sending email: %s"); 849 return; 850 } 851 (void)system(buf); 852 free(buf); 853 } 854 855 /* 856 * rcv_dlnwrite -- 857 * Encode a string into an X-vi-data line and write it. 858 */ 859 static int 860 rcv_dlnwrite( 861 SCR *sp, 862 const char *dtype, 863 const char *src, 864 FILE *fp) 865 { 866 char *bp = NULL, *p; 867 size_t blen = 0; 868 size_t dlen, len; 869 int plen, xlen; 870 871 len = strlen(src); 872 dlen = strlen(dtype); 873 GET_SPACE_GOTOC(sp, bp, blen, (len + 2) / 3 * 4 + dlen + 2); 874 (void)memcpy(bp, dtype, dlen); 875 bp[dlen] = ';'; 876 if ((xlen = b64_ntop((u_char *)src, 877 len, bp + dlen + 1, blen)) == -1) 878 goto err; 879 xlen += dlen + 1; 880 881 /* Output as an MIME folding header. */ 882 if ((plen = fprintf(fp, VI_DHEADER " %.*s\n", 883 FMTCOLS - (int)sizeof(VI_DHEADER), bp)) < 0) 884 goto err; 885 plen -= (int)sizeof(VI_DHEADER) + 1; 886 for (p = bp, xlen -= plen; xlen > 0; xlen -= plen) { 887 p += plen; 888 if ((plen = fprintf(fp, " %.*s\n", FMTCOLS - 1, p)) < 0) 889 goto err; 890 plen -= 2; 891 } 892 FREE_SPACE(sp, bp, blen); 893 return (0); 894 895 err: FREE_SPACE(sp, bp, blen); 896 return (1); 897 alloc_err: 898 msgq(sp, M_SYSERR, NULL); 899 return (-1); 900 } 901 902 /* 903 * rcv_dlnread -- 904 * Read an X-vi-data line and decode it. 905 */ 906 static int 907 rcv_dlnread( 908 SCR *sp, 909 char **dtypep, 910 char **datap, /* free *datap if != NULL after use. */ 911 FILE *fp) 912 { 913 int ch; 914 char buf[1024]; 915 char *bp = NULL, *p, *src; 916 size_t blen = 0; 917 size_t len, off, dlen; 918 char *dtype, *data; 919 int xlen; 920 921 if (fgets(buf, sizeof(buf), fp) == NULL) 922 return (1); 923 if (strncmp(buf, VI_DHEADER, sizeof(VI_DHEADER) - 1)) { 924 *dtypep = NULL; 925 *datap = NULL; 926 return (0); 927 } 928 929 /* Fetch an MIME folding header. */ 930 len = strlen(buf) - sizeof(VI_DHEADER) + 1; 931 GET_SPACE_GOTOC(sp, bp, blen, len); 932 (void)memcpy(bp, buf + sizeof(VI_DHEADER) - 1, len); 933 p = bp + len; 934 while ((ch = fgetc(fp)) == ' ') { 935 if (fgets(buf, sizeof(buf), fp) == NULL) 936 goto err; 937 off = strlen(buf); 938 len += off; 939 ADD_SPACE_GOTOC(sp, bp, blen, len); 940 p = bp + len - off; 941 (void)memcpy(p, buf, off); 942 } 943 bp[len] = '\0'; 944 (void)ungetc(ch, fp); 945 946 for (p = bp; *p == ' ' || *p == '\n'; p++); 947 if ((src = strchr(p, ';')) == NULL) 948 goto err; 949 dlen = src - p; 950 src += 1; 951 len -= src - bp; 952 953 /* Memory looks like: "<data>\0<dtype>\0". */ 954 MALLOC(sp, data, char *, dlen + len / 4 * 3 + 2); 955 if (data == NULL) 956 goto err; 957 if ((xlen = (b64_pton(p + dlen + 1, 958 (u_char *)data, len / 4 * 3 + 1))) == -1) { 959 free(data); 960 goto err; 961 } 962 data[xlen] = '\0'; 963 dtype = data + xlen + 1; 964 (void)memcpy(dtype, p, dlen); 965 dtype[dlen] = '\0'; 966 FREE_SPACE(sp, bp, blen); 967 *dtypep = dtype; 968 *datap = data; 969 return (0); 970 971 err: FREE_SPACE(sp, bp, blen); 972 return (1); 973 alloc_err: 974 msgq(sp, M_SYSERR, NULL); 975 return (-1); 976 } 977