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