1 /* $OpenBSD: recover.c,v 1.32 2022/02/20 19:45:51 tb Exp $ */ 2 3 /*- 4 * Copyright (c) 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * Copyright (c) 1993, 1994, 1995, 1996 7 * Keith Bostic. All rights reserved. 8 * 9 * See the LICENSE file for redistribution information. 10 */ 11 12 #include "config.h" 13 14 #include <sys/queue.h> 15 #include <sys/stat.h> 16 #include <sys/time.h> 17 #include <sys/wait.h> 18 19 /* 20 * We include <sys/file.h>, because the open #defines were found there 21 * on historical systems. We also include <fcntl.h> because the open(2) 22 * #defines are found there on newer systems. 23 */ 24 #include <sys/file.h> 25 26 #include <bitstring.h> 27 #include <dirent.h> 28 #include <errno.h> 29 #include <fcntl.h> 30 #include <limits.h> 31 #include <paths.h> 32 #include <pwd.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <time.h> 37 #include <unistd.h> 38 39 #include "common.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. NOTE: we sometimes have to 59 * do locking with fcntl(2). This is a problem because if you close(2) any 60 * file descriptor associated with the file, ALL of the locks go away. Be 61 * sure to remember that if you have to modify the recovery code. (It has 62 * been rhetorically asked of what the designers could have been thinking 63 * when they did that interface. The answer is simple: they weren't.) 64 * 65 * To find out if a recovery file/backing file pair are in use, try to get 66 * a lock on the recovery file. 67 * 68 * To find out if a backing file can be deleted at boot time, check for an 69 * owner execute bit. (Yes, I know it's ugly, but it's either that or put 70 * special stuff into the backing file itself, or correlate the files at 71 * boot time, neither of which looks like fun.) Note also that there's a 72 * window between when the file is created and the X bit is set. It's small, 73 * but it's there. To fix the window, check for 0 length files as well. 74 * 75 * To find out if a file can be recovered, check the F_RCV_ON bit. Note, 76 * this DOES NOT mean that any initialization has been done, only that we 77 * haven't yet failed at setting up or doing recovery. 78 * 79 * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit. 80 * If that bit is not set when ending a file session: 81 * If the EXF structure paths (rcv_path and rcv_mpath) are not NULL, 82 * they are unlink(2)'d, and free(3)'d. 83 * If the EXF file descriptor (rcv_fd) is not -1, it is closed. 84 * 85 * The backing b+tree file is set up when a file is first edited, so that 86 * the DB package can use it for on-disk caching and/or to snapshot the 87 * file. When the file is first modified, the mail recovery file is created, 88 * the backing file permissions are updated, the file is sync(2)'d to disk, 89 * and the timer is started. Then, at RCV_PERIOD second intervals, the 90 * b+tree file is synced to disk. RCV_PERIOD is measured using SIGALRM, which 91 * means that the data structures (SCR, EXF, the underlying tree structures) 92 * must be consistent when the signal arrives. 93 * 94 * The recovery mail file contains normal mail headers, with two additions, 95 * which occur in THIS order, as the FIRST TWO headers: 96 * 97 * X-vi-recover-file: file_name 98 * X-vi-recover-path: recover_path 99 * 100 * Since newlines delimit the headers, this means that file names cannot have 101 * newlines in them, but that's probably okay. As these files aren't intended 102 * to be long-lived, changing their format won't be too painful. 103 * 104 * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX". 105 */ 106 107 #define VI_FHEADER "X-vi-recover-file: " 108 #define VI_PHEADER "X-vi-recover-path: " 109 110 static int rcv_copy(SCR *, int, char *); 111 static void rcv_email(SCR *, int); 112 static char *rcv_gets(char *, size_t, int); 113 static int rcv_mailfile(SCR *, int, char *); 114 static int rcv_mktemp(SCR *, char *, char *, int); 115 static int rcv_openat(SCR *, int, const char *, int *); 116 117 /* 118 * rcv_tmp -- 119 * Build a file name that will be used as the recovery file. 120 * 121 * PUBLIC: int rcv_tmp(SCR *, EXF *, char *); 122 */ 123 int 124 rcv_tmp(SCR *sp, EXF *ep, char *name) 125 { 126 struct stat sb; 127 static int warned = 0; 128 int fd; 129 char *dp, *p, path[PATH_MAX]; 130 131 /* 132 * !!! 133 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. 134 */ 135 if (opts_empty(sp, O_RECDIR, 0)) 136 goto err; 137 dp = O_STR(sp, O_RECDIR); 138 if (stat(dp, &sb)) { 139 if (!warned) { 140 warned = 1; 141 msgq(sp, M_SYSERR, "%s", dp); 142 goto err; 143 } 144 return 1; 145 } 146 147 /* Newlines delimit the mail messages. */ 148 for (p = name; *p; ++p) 149 if (*p == '\n') { 150 msgq(sp, M_ERR, 151 "Files with newlines in the name are unrecoverable"); 152 goto err; 153 } 154 155 (void)snprintf(path, sizeof(path), "%s/vi.XXXXXXXXXX", dp); 156 if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1) 157 goto err; 158 (void)close(fd); 159 160 if ((ep->rcv_path = strdup(path)) == NULL) { 161 msgq(sp, M_SYSERR, NULL); 162 (void)unlink(path); 163 err: msgq(sp, M_ERR, 164 "Modifications not recoverable if the session fails"); 165 return (1); 166 } 167 168 /* We believe the file is recoverable. */ 169 F_SET(ep, F_RCV_ON); 170 return (0); 171 } 172 173 /* 174 * rcv_init -- 175 * Force the file to be snapshotted for recovery. 176 * 177 * PUBLIC: int rcv_init(SCR *); 178 */ 179 int 180 rcv_init(SCR *sp) 181 { 182 EXF *ep; 183 recno_t lno; 184 185 ep = sp->ep; 186 187 /* Only do this once. */ 188 F_CLR(ep, F_FIRSTMODIFY); 189 190 /* If we already know the file isn't recoverable, we're done. */ 191 if (!F_ISSET(ep, F_RCV_ON)) 192 return (0); 193 194 /* Turn off recoverability until we figure out if this will work. */ 195 F_CLR(ep, F_RCV_ON); 196 197 /* Test if we're recovering a file, not editing one. */ 198 if (ep->rcv_mpath == NULL) { 199 /* Build a file to mail to the user. */ 200 if (rcv_mailfile(sp, 0, NULL)) 201 goto err; 202 203 /* Force a read of the entire file. */ 204 if (db_last(sp, &lno)) 205 goto err; 206 207 /* Turn on a busy message, and sync it to backing store. */ 208 sp->gp->scr_busy(sp, 209 "Copying file for recovery...", BUSY_ON); 210 if (ep->db->sync(ep->db, R_RECNOSYNC)) { 211 msgq_str(sp, M_SYSERR, ep->rcv_path, 212 "Preservation failed: %s"); 213 sp->gp->scr_busy(sp, NULL, BUSY_OFF); 214 goto err; 215 } 216 sp->gp->scr_busy(sp, NULL, BUSY_OFF); 217 } 218 219 /* Turn off the owner execute bit. */ 220 (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR); 221 222 /* We believe the file is recoverable. */ 223 F_SET(ep, F_RCV_ON); 224 return (0); 225 226 err: msgq(sp, M_ERR, 227 "Modifications not recoverable if the session fails"); 228 return (1); 229 } 230 231 /* 232 * rcv_sync -- 233 * Sync the file, optionally: 234 * flagging the backup file to be preserved 235 * snapshotting the backup file and send email to the user 236 * sending email to the user if the file was modified 237 * ending the file session 238 * 239 * PUBLIC: int rcv_sync(SCR *, u_int); 240 */ 241 int 242 rcv_sync(SCR *sp, u_int flags) 243 { 244 EXF *ep; 245 int fd, rval; 246 char *dp, buf[1024]; 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 /* Clear recovery sync flag. */ 256 F_CLR(ep, F_RCV_SYNC); 257 if (ep->db->sync(ep->db, R_RECNOSYNC)) { 258 F_CLR(ep, F_RCV_ON | F_RCV_NORM); 259 msgq_str(sp, M_SYSERR, 260 ep->rcv_path, "File backup failed: %s"); 261 return (1); 262 } 263 264 /* REQUEST: don't remove backing file on exit. */ 265 if (LF_ISSET(RCV_PRESERVE)) 266 F_SET(ep, F_RCV_NORM); 267 268 /* REQUEST: send email. */ 269 if (LF_ISSET(RCV_EMAIL)) 270 rcv_email(sp, ep->rcv_fd); 271 } 272 273 /* 274 * !!! 275 * Each time the user exec's :preserve, we have to snapshot all of 276 * the recovery information, i.e. it's like the user re-edited the 277 * file. We copy the DB(3) backing file, and then create a new mail 278 * recovery file, it's simpler than exiting and reopening all of the 279 * underlying files. 280 * 281 * REQUEST: snapshot the file. 282 */ 283 rval = 0; 284 if (LF_ISSET(RCV_SNAPSHOT)) { 285 if (opts_empty(sp, O_RECDIR, 0)) 286 goto err; 287 dp = O_STR(sp, O_RECDIR); 288 (void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXXXXXX", dp); 289 if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1) 290 goto err; 291 sp->gp->scr_busy(sp, 292 "Copying file for recovery...", BUSY_ON); 293 if (rcv_copy(sp, fd, ep->rcv_path) || 294 close(fd) || rcv_mailfile(sp, 1, buf)) { 295 (void)unlink(buf); 296 (void)close(fd); 297 rval = 1; 298 } 299 sp->gp->scr_busy(sp, NULL, BUSY_OFF); 300 } 301 if (0) { 302 err: rval = 1; 303 } 304 305 /* REQUEST: end the file session. */ 306 if (LF_ISSET(RCV_ENDSESSION)) 307 F_SET(sp, SC_EXIT_FORCE); 308 309 return (rval); 310 } 311 312 /* 313 * rcv_mailfile -- 314 * Build the file to mail to the user. 315 */ 316 static int 317 rcv_mailfile(SCR *sp, int issync, char *cp_path) 318 { 319 EXF *ep; 320 GS *gp; 321 struct passwd *pw; 322 size_t len; 323 time_t now; 324 uid_t uid; 325 int fd; 326 char *dp, *p, *t, buf[4096], mpath[PATH_MAX]; 327 char *t1, *t2, *t3; 328 char host[HOST_NAME_MAX+1]; 329 330 gp = sp->gp; 331 if ((pw = getpwuid(uid = getuid())) == NULL) { 332 msgq(sp, M_ERR, 333 "Information on user id %u not found", uid); 334 return (1); 335 } 336 337 if (opts_empty(sp, O_RECDIR, 0)) 338 return (1); 339 dp = O_STR(sp, O_RECDIR); 340 (void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXXXXXX", dp); 341 if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1) 342 return (1); 343 344 /* 345 * XXX 346 * We keep an open lock on the file so that the recover option can 347 * distinguish between files that are live and those that need to 348 * be recovered. There's an obvious window between the mkstemp call 349 * and the lock, but it's pretty small. 350 */ 351 ep = sp->ep; 352 if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS) 353 msgq(sp, M_SYSERR, "Unable to lock recovery file"); 354 if (!issync) { 355 /* Save the recover file descriptor, and mail path. */ 356 ep->rcv_fd = fd; 357 if ((ep->rcv_mpath = strdup(mpath)) == NULL) { 358 msgq(sp, M_SYSERR, NULL); 359 goto err; 360 } 361 cp_path = ep->rcv_path; 362 } 363 364 /* 365 * XXX 366 * We can't use stdio(3) here. The problem is that we may be using 367 * fcntl(2), so if ANY file descriptor into the file is closed, the 368 * lock is lost. So, we could never close the FILE *, even if we 369 * dup'd the fd first. 370 */ 371 t = sp->frp->name; 372 if ((p = strrchr(t, '/')) == NULL) 373 p = t; 374 else 375 ++p; 376 (void)time(&now); 377 (void)gethostname(host, sizeof(host)); 378 len = snprintf(buf, sizeof(buf), 379 "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n%s\n\n", 380 VI_FHEADER, t, /* Non-standard. */ 381 VI_PHEADER, cp_path, /* Non-standard. */ 382 "Reply-To: root", 383 "From: root (Nvi recovery program)", 384 "To: ", pw->pw_name, 385 "Subject: Nvi saved the file ", p, 386 "Precedence: bulk", /* For vacation(1). */ 387 "Auto-Submitted: auto-generated"); 388 if (len > sizeof(buf) - 1) 389 goto lerr; 390 if (write(fd, buf, len) != len) 391 goto werr; 392 393 len = snprintf(buf, sizeof(buf), 394 "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n", 395 "On ", ctime(&now), ", the user ", pw->pw_name, 396 " was editing a file named ", t, " on the machine ", 397 host, ", when it was saved for recovery. ", 398 "You can recover most, if not all, of the changes ", 399 "to this file using the -r option to ", getprogname(), ":\n\n\t", 400 getprogname(), " -r ", t); 401 if (len > sizeof(buf) - 1) { 402 lerr: msgq(sp, M_ERR, "Recovery file buffer overrun"); 403 goto err; 404 } 405 406 /* 407 * Format the message. (Yes, I know it's silly.) 408 * Requires that the message end in a <newline>. 409 */ 410 #define FMTCOLS 60 411 for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) { 412 /* Check for a short length. */ 413 if (len <= FMTCOLS) { 414 t2 = t1 + (len - 1); 415 goto wout; 416 } 417 418 /* Check for a required <newline>. */ 419 t2 = strchr(t1, '\n'); 420 if (t2 - t1 <= FMTCOLS) 421 goto wout; 422 423 /* Find the closest space, if any. */ 424 for (t3 = t2; t2 > t1; --t2) 425 if (*t2 == ' ') { 426 if (t2 - t1 <= FMTCOLS) 427 goto wout; 428 t3 = t2; 429 } 430 t2 = t3; 431 432 /* t2 points to the last character to display. */ 433 wout: *t2++ = '\n'; 434 435 /* t2 points one after the last character to display. */ 436 if (write(fd, t1, t2 - t1) != t2 - t1) 437 goto werr; 438 } 439 440 if (issync) { 441 rcv_email(sp, fd); 442 if (close(fd)) { 443 werr: msgq(sp, M_SYSERR, "Recovery file"); 444 goto err; 445 } 446 } 447 return (0); 448 449 err: if (!issync) 450 ep->rcv_fd = -1; 451 if (fd != -1) 452 (void)close(fd); 453 return (1); 454 } 455 456 /* 457 * rcv_openat -- 458 * Open a recovery file in the specified dir and lock it. 459 * 460 * PUBLIC: int rcv_openat(SCR *, int, const char *, int *) 461 */ 462 static int 463 rcv_openat(SCR *sp, int dfd, const char *name, int *locked) 464 { 465 struct stat sb; 466 int fd, dummy; 467 468 /* 469 * If it's readable, it's recoverable. 470 * Note: file_lock() sets the close on exec flag for us. 471 */ 472 fd = openat(dfd, name, O_RDONLY|O_NOFOLLOW|O_NONBLOCK); 473 if (fd == -1) 474 goto bad; 475 476 /* 477 * Real vi recovery files are created with mode 0600. 478 * If not a regular file or the mode has changed, skip it. 479 */ 480 if (fstat(fd, &sb) == -1 || !S_ISREG(sb.st_mode) || 481 (sb.st_mode & ALLPERMS) != (S_IRUSR | S_IWUSR)) 482 goto bad; 483 484 if (locked == NULL) 485 locked = &dummy; 486 switch ((*locked = file_lock(sp, NULL, NULL, fd, 0))) { 487 case LOCK_FAILED: 488 /* 489 * XXX 490 * Assume that a lock can't be acquired, but that we 491 * should permit recovery anyway. If this is wrong, 492 * and someone else is using the file, we're going to 493 * die horribly. 494 */ 495 break; 496 case LOCK_SUCCESS: 497 break; 498 case LOCK_UNAVAIL: 499 /* If it's locked, it's live. */ 500 goto bad; 501 } 502 return fd; 503 bad: 504 if (fd != -1) 505 close(fd); 506 return -1; 507 } 508 509 /* 510 * people making love 511 * never exactly the same 512 * just like a snowflake 513 * 514 * rcv_list -- 515 * List the files that can be recovered by this user. 516 * 517 * PUBLIC: int rcv_list(SCR *); 518 */ 519 int 520 rcv_list(SCR *sp) 521 { 522 struct dirent *dp; 523 struct stat sb; 524 DIR *dirp; 525 int fd; 526 FILE *fp; 527 int found; 528 char *p, *t, file[PATH_MAX], path[PATH_MAX]; 529 530 /* Open the recovery directory for reading. */ 531 if (opts_empty(sp, O_RECDIR, 0)) 532 return (1); 533 p = O_STR(sp, O_RECDIR); 534 if ((dirp = opendir(p)) == NULL) { 535 msgq_str(sp, M_SYSERR, p, "recdir: %s"); 536 return (1); 537 } 538 539 /* Read the directory. */ 540 for (found = 0; (dp = readdir(dirp)) != NULL;) { 541 if (strncmp(dp->d_name, "recover.", 8)) 542 continue; 543 544 if ((fd = rcv_openat(sp, dirfd(dirp), dp->d_name, NULL)) == -1) 545 continue; 546 547 /* Check the headers. */ 548 if ((fp = fdopen(fd, "r")) == NULL) { 549 close(fd); 550 continue; 551 } 552 if (fgets(file, sizeof(file), fp) == NULL || 553 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) || 554 (p = strchr(file, '\n')) == NULL || 555 fgets(path, sizeof(path), fp) == NULL || 556 strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) || 557 (t = strchr(path, '\n')) == NULL) { 558 msgq_str(sp, M_ERR, dp->d_name, 559 "%s: malformed recovery file"); 560 goto next; 561 } 562 *p = *t = '\0'; 563 564 /* 565 * If the file doesn't exist, it's an orphaned recovery file, 566 * toss it. 567 * 568 * XXX 569 * This can occur if the backup file was deleted and we crashed 570 * before deleting the email file. 571 */ 572 errno = 0; 573 if (stat(path + sizeof(VI_PHEADER) - 1, &sb) && 574 errno == ENOENT) { 575 (void)unlinkat(dirfd(dirp), dp->d_name, 0); 576 goto next; 577 } 578 579 /* Get the last modification time and display. */ 580 (void)fstat(fd, &sb); 581 (void)printf("%.24s: %s\n", 582 ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1); 583 found = 1; 584 585 /* Close, discarding lock. */ 586 next: (void)fclose(fp); 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 EXF *ep; 607 struct timespec rec_mtim; 608 int fd, found, lck, requested, sv_fd; 609 char *name, *p, *t, *rp, *recp, *pathp; 610 char file[PATH_MAX], path[PATH_MAX], recpath[PATH_MAX]; 611 612 if (opts_empty(sp, O_RECDIR, 0)) 613 return (1); 614 rp = O_STR(sp, O_RECDIR); 615 if ((dirp = opendir(rp)) == NULL) { 616 msgq_str(sp, M_SYSERR, rp, "%s"); 617 return (1); 618 } 619 620 name = frp->name; 621 sv_fd = -1; 622 rec_mtim.tv_sec = rec_mtim.tv_nsec = 0; 623 recp = pathp = NULL; 624 for (found = requested = 0; (dp = readdir(dirp)) != NULL;) { 625 if (strncmp(dp->d_name, "recover.", 8)) 626 continue; 627 if ((size_t)snprintf(recpath, sizeof(recpath), "%s/%s", 628 rp, dp->d_name) >= sizeof(recpath)) 629 continue; 630 631 if ((fd = rcv_openat(sp, dirfd(dirp), dp->d_name, &lck)) == -1) 632 continue; 633 634 /* Check the headers. */ 635 if (rcv_gets(file, sizeof(file), fd) == NULL || 636 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) || 637 (p = strchr(file, '\n')) == NULL || 638 rcv_gets(path, sizeof(path), fd) == NULL || 639 strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) || 640 (t = strchr(path, '\n')) == NULL) { 641 msgq_str(sp, M_ERR, recpath, 642 "%s: malformed recovery file"); 643 goto next; 644 } 645 *p = *t = '\0'; 646 ++found; 647 648 /* 649 * If the file doesn't exist, it's an orphaned recovery file, 650 * toss it. 651 * 652 * XXX 653 * This can occur if the backup file was deleted and we crashed 654 * before deleting the email file. 655 */ 656 errno = 0; 657 if (stat(path + sizeof(VI_PHEADER) - 1, &sb) && 658 errno == ENOENT) { 659 (void)unlink(dp->d_name); 660 goto next; 661 } 662 663 /* Check the file name. */ 664 if (strcmp(file + sizeof(VI_FHEADER) - 1, name)) 665 goto next; 666 667 ++requested; 668 669 /* 670 * If we've found more than one, take the most recent. 671 */ 672 (void)fstat(fd, &sb); 673 if (recp == NULL || 674 timespeccmp(&rec_mtim, &sb.st_mtim, <)) { 675 p = recp; 676 t = pathp; 677 if ((recp = strdup(recpath)) == NULL) { 678 msgq(sp, M_SYSERR, NULL); 679 recp = p; 680 goto next; 681 } 682 if ((pathp = strdup(path)) == NULL) { 683 msgq(sp, M_SYSERR, NULL); 684 free(recp); 685 recp = p; 686 pathp = t; 687 goto next; 688 } 689 if (p != NULL) { 690 free(p); 691 free(t); 692 } 693 rec_mtim = sb.st_mtim; 694 if (sv_fd != -1) 695 (void)close(sv_fd); 696 sv_fd = fd; 697 } else 698 next: (void)close(fd); 699 } 700 (void)closedir(dirp); 701 702 if (recp == NULL) { 703 msgq_str(sp, M_INFO, name, 704 "No files named %s, readable by you, to recover"); 705 return (1); 706 } 707 if (found) { 708 if (requested > 1) 709 msgq(sp, M_INFO, 710 "There are older versions of this file for you to recover"); 711 if (found > requested) 712 msgq(sp, M_INFO, 713 "There are other files for you to recover"); 714 } 715 716 /* 717 * Create the FREF structure, start the btree file. 718 * 719 * XXX 720 * file_init() is going to set ep->rcv_path. 721 */ 722 if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) { 723 free(recp); 724 free(pathp); 725 (void)close(sv_fd); 726 return (1); 727 } 728 729 /* 730 * We keep an open lock on the file so that the recover option can 731 * distinguish between files that are live and those that need to 732 * be recovered. The lock is already acquired, just copy it. 733 */ 734 ep = sp->ep; 735 ep->rcv_mpath = recp; 736 ep->rcv_fd = sv_fd; 737 if (lck != LOCK_SUCCESS) 738 F_SET(frp, FR_UNLOCKED); 739 740 /* We believe the file is recoverable. */ 741 F_SET(ep, F_RCV_ON); 742 return (0); 743 } 744 745 /* 746 * rcv_copy -- 747 * Copy a recovery file. 748 */ 749 static int 750 rcv_copy(SCR *sp, int wfd, char *fname) 751 { 752 int nr, nw, off, rfd; 753 char buf[8 * 1024]; 754 755 if ((rfd = open(fname, O_RDONLY)) == -1) 756 goto err; 757 while ((nr = read(rfd, buf, sizeof(buf))) > 0) 758 for (off = 0; nr; nr -= nw, off += nw) 759 if ((nw = write(wfd, buf + off, nr)) < 0) 760 goto err; 761 if (nr == 0) 762 return (0); 763 764 err: msgq_str(sp, M_SYSERR, fname, "%s"); 765 return (1); 766 } 767 768 /* 769 * rcv_gets -- 770 * Fgets(3) for a file descriptor. 771 */ 772 static char * 773 rcv_gets(char *buf, size_t len, int fd) 774 { 775 int nr; 776 char *p; 777 778 if ((nr = read(fd, buf, len - 1)) == -1) 779 return (NULL); 780 buf[nr] = '\0'; 781 if ((p = strchr(buf, '\n')) == NULL) 782 return (NULL); 783 (void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET); 784 return (buf); 785 } 786 787 /* 788 * rcv_mktemp -- 789 * Paranoid make temporary file routine. 790 */ 791 static int 792 rcv_mktemp(SCR *sp, char *path, char *dname, int perms) 793 { 794 int fd; 795 796 /* 797 * !!! 798 * We expect mkstemp(3) to set the permissions correctly. On 799 * historic System V systems, mkstemp didn't. Do it here, on 800 * GP's. This also protects us from users with stupid umasks. 801 * 802 * XXX 803 * The variable perms should really be a mode_t. 804 */ 805 if ((fd = mkstemp(path)) == -1 || fchmod(fd, perms) == -1) { 806 msgq_str(sp, M_SYSERR, dname, "%s"); 807 if (fd != -1) { 808 close(fd); 809 unlink(path); 810 fd = -1; 811 } 812 } 813 return (fd); 814 } 815 816 /* 817 * rcv_email -- 818 * Send email. 819 */ 820 static void 821 rcv_email(SCR *sp, int fd) 822 { 823 struct stat sb; 824 pid_t pid; 825 826 /* 827 * In secure mode, our pledge(2) includes neither "proc" 828 * nor "exec". So simply skip sending the mail. 829 * Later vi -r still works because rcv_mailfile() 830 * already did all the necessary setup. 831 */ 832 if (O_ISSET(sp, O_SECURE)) 833 return; 834 835 if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb) == -1) 836 msgq_str(sp, M_SYSERR, 837 _PATH_SENDMAIL, "not sending email: %s"); 838 else { 839 /* 840 * !!! 841 * If you need to port this to a system that doesn't have 842 * sendmail, the -t flag causes sendmail to read the message 843 * for the recipients instead of specifying them some other 844 * way. 845 */ 846 switch (pid = fork()) { 847 case -1: /* Error. */ 848 msgq(sp, M_SYSERR, "fork"); 849 break; 850 case 0: /* Sendmail. */ 851 if (lseek(fd, 0, SEEK_SET) == -1) { 852 msgq(sp, M_SYSERR, "lseek"); 853 _exit(127); 854 } 855 if (fd != STDIN_FILENO) { 856 if (dup2(fd, STDIN_FILENO) == -1) { 857 msgq(sp, M_SYSERR, "dup2"); 858 _exit(127); 859 } 860 close(fd); 861 } 862 execl(_PATH_SENDMAIL, "sendmail", "-t", (char *)NULL); 863 msgq(sp, M_SYSERR, _PATH_SENDMAIL); 864 _exit(127); 865 default: /* Parent. */ 866 while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) 867 continue; 868 break; 869 } 870 871 } 872 } 873