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