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 #if defined HAVE_STRUCT_STAT_ST_MTIMESPEC 705 timespeccmp(&rec_mtim, &sb.st_mtimespec, <)) { 706 #elif defined HAVE_STRUCT_STAT_ST_MTIM 707 timespeccmp(&rec_mtim, &sb.st_mtim, <)) { 708 #else 709 rec_mtim.tv_sec < sb.st_mtime) { 710 #endif 711 p = recp; 712 t = pathp; 713 recp = recpath; 714 pathp = path; 715 if (p != NULL) { 716 free(p); 717 free(t); 718 } 719 #if defined HAVE_STRUCT_STAT_ST_MTIMESPEC 720 rec_mtim = sb.st_mtimespec; 721 #elif defined HAVE_STRUCT_STAT_ST_MTIM 722 rec_mtim = sb.st_mtim; 723 #else 724 rec_mtim.tv_sec = sb.st_mtime; 725 #endif 726 if (sv_fd != -1) 727 (void)close(sv_fd); 728 sv_fd = dup(fileno(fp)); 729 } else { 730 next: free(recpath); 731 free(path); 732 } 733 (void)fclose(fp); 734 free(file); 735 } 736 (void)closedir(dirp); 737 738 if (recp == NULL) { 739 msgq_str(sp, M_INFO, name, 740 "068|No files named %s, readable by you, to recover"); 741 return (1); 742 } 743 if (found) { 744 if (requested > 1) 745 msgq(sp, M_INFO, 746 "069|There are older versions of this file for you to recover"); 747 if (found > requested) 748 msgq(sp, M_INFO, 749 "070|There are other files for you to recover"); 750 } 751 752 /* 753 * Create the FREF structure, start the btree file. 754 * 755 * XXX 756 * file_init() is going to set ep->rcv_path. 757 */ 758 if (file_init(sp, frp, pathp, 0)) { 759 free(recp); 760 free(pathp); 761 (void)close(sv_fd); 762 return (1); 763 } 764 free(pathp); 765 766 /* 767 * We keep an open lock on the file so that the recover option can 768 * distinguish between files that are live and those that need to 769 * be recovered. The lock is already acquired, just copy it. 770 */ 771 ep = sp->ep; 772 ep->rcv_mpath = recp; 773 ep->rcv_fd = sv_fd; 774 if (!locked) 775 F_SET(frp, FR_UNLOCKED); 776 777 /* We believe the file is recoverable. */ 778 F_SET(ep, F_RCV_ON); 779 return (0); 780 } 781 782 /* 783 * rcv_copy -- 784 * Copy a recovery file. 785 */ 786 static int 787 rcv_copy(SCR *sp, int wfd, char *fname) 788 { 789 int nr, nw, off, rfd; 790 char buf[8 * 1024]; 791 792 if ((rfd = open(fname, O_RDONLY, 0)) == -1) 793 goto err; 794 while ((nr = read(rfd, buf, sizeof(buf))) > 0) 795 for (off = 0; nr; nr -= nw, off += nw) 796 if ((nw = write(wfd, buf + off, nr)) < 0) 797 goto err; 798 if (nr == 0) 799 return (0); 800 801 err: msgq_str(sp, M_SYSERR, fname, "%s"); 802 return (1); 803 } 804 805 /* 806 * rcv_mktemp -- 807 * Paranoid make temporary file routine. 808 */ 809 static int 810 rcv_mktemp(SCR *sp, char *path, char *dname) 811 { 812 int fd; 813 814 if ((fd = mkstemp(path)) == -1) 815 msgq_str(sp, M_SYSERR, dname, "%s"); 816 return (fd); 817 } 818 819 /* 820 * rcv_email -- 821 * Send email. 822 */ 823 static void 824 rcv_email(SCR *sp, char *fname) 825 { 826 char *buf; 827 828 if (asprintf(&buf, _PATH_SENDMAIL " -odb -t < %s", fname) == -1) { 829 msgq_str(sp, M_ERR, strerror(errno), 830 "071|not sending email: %s"); 831 return; 832 } 833 (void)system(buf); 834 free(buf); 835 } 836 837 /* 838 * rcv_dlnwrite -- 839 * Encode a string into an X-vi-data line and write it. 840 */ 841 static int 842 rcv_dlnwrite(SCR *sp, const char *dtype, const char *src, FILE *fp) 843 { 844 char *bp = NULL, *p; 845 size_t blen = 0; 846 size_t dlen, len; 847 int plen, xlen; 848 849 len = strlen(src); 850 dlen = strlen(dtype); 851 GET_SPACE_GOTOC(sp, bp, blen, (len + 2) / 3 * 4 + dlen + 2); 852 (void)memcpy(bp, dtype, dlen); 853 bp[dlen] = ';'; 854 if ((xlen = b64_ntop((u_char *)src, 855 len, bp + dlen + 1, blen)) == -1) 856 goto err; 857 xlen += dlen + 1; 858 859 /* Output as an MIME folding header. */ 860 if ((plen = fprintf(fp, VI_DHEADER " %.*s\n", 861 FMTCOLS - (int)sizeof(VI_DHEADER), bp)) < 0) 862 goto err; 863 plen -= (int)sizeof(VI_DHEADER) + 1; 864 for (p = bp, xlen -= plen; xlen > 0; xlen -= plen) { 865 p += plen; 866 if ((plen = fprintf(fp, " %.*s\n", FMTCOLS - 1, p)) < 0) 867 goto err; 868 plen -= 2; 869 } 870 FREE_SPACE(sp, bp, blen); 871 return (0); 872 873 err: FREE_SPACE(sp, bp, blen); 874 return (1); 875 alloc_err: 876 msgq(sp, M_SYSERR, NULL); 877 return (-1); 878 } 879 880 /* 881 * rcv_dlnread -- 882 * Read an X-vi-data line and decode it. 883 */ 884 static int 885 rcv_dlnread(SCR *sp, char **dtypep, 886 char **datap, /* free *datap if != NULL after use. */ 887 FILE *fp) 888 { 889 int ch; 890 char buf[1024]; 891 char *bp = NULL, *p, *src; 892 size_t blen = 0; 893 size_t len, off, dlen; 894 char *dtype, *data; 895 int xlen; 896 897 if (fgets(buf, sizeof(buf), fp) == NULL) 898 return (1); 899 if (strncmp(buf, VI_DHEADER, sizeof(VI_DHEADER) - 1)) { 900 *dtypep = NULL; 901 *datap = NULL; 902 return (0); 903 } 904 905 /* Fetch an MIME folding header. */ 906 len = strlen(buf) - sizeof(VI_DHEADER) + 1; 907 GET_SPACE_GOTOC(sp, bp, blen, len); 908 (void)memcpy(bp, buf + sizeof(VI_DHEADER) - 1, len); 909 p = bp + len; 910 while ((ch = fgetc(fp)) == ' ') { 911 if (fgets(buf, sizeof(buf), fp) == NULL) 912 goto err; 913 off = strlen(buf); 914 len += off; 915 ADD_SPACE_GOTOC(sp, bp, blen, len); 916 p = bp + len - off; 917 (void)memcpy(p, buf, off); 918 } 919 bp[len] = '\0'; 920 (void)ungetc(ch, fp); 921 922 for (p = bp; *p == ' ' || *p == '\n'; p++); 923 if ((src = strchr(p, ';')) == NULL) 924 goto err; 925 dlen = src - p; 926 src += 1; 927 len -= src - bp; 928 929 /* Memory looks like: "<data>\0<dtype>\0". */ 930 MALLOC(sp, data, dlen + len / 4 * 3 + 2); 931 if (data == NULL) 932 goto err; 933 if ((xlen = (b64_pton(p + dlen + 1, 934 (u_char *)data, len / 4 * 3 + 1))) == -1) { 935 free(data); 936 goto err; 937 } 938 data[xlen] = '\0'; 939 dtype = data + xlen + 1; 940 (void)memcpy(dtype, p, dlen); 941 dtype[dlen] = '\0'; 942 FREE_SPACE(sp, bp, blen); 943 *dtypep = dtype; 944 *datap = data; 945 return (0); 946 947 err: FREE_SPACE(sp, bp, blen); 948 return (1); 949 alloc_err: 950 msgq(sp, M_SYSERR, NULL); 951 return (-1); 952 } 953