1 /* $OpenBSD: recover.c,v 1.14 2008/09/25 11:37:03 sobrado 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(SCR *, int, char *); 115 static void rcv_email(SCR *, char *); 116 static char *rcv_gets(char *, size_t, int); 117 static int rcv_mailfile(SCR *, int, char *); 118 static int rcv_mktemp(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(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.XXXXXXXXXX", 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(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(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.XXXXXXXXXX", 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.XXXXXXXXXX", 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%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 "Auto-Submitted: auto-generated"); 415 if (len > sizeof(buf) - 1) 416 goto lerr; 417 if (write(fd, buf, len) != len) 418 goto werr; 419 420 len = snprintf(buf, sizeof(buf), 421 "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n", 422 "On ", ctime(&now), ", the user ", pw->pw_name, 423 " was editing a file named ", t, " on the machine ", 424 host, ", when it was saved for recovery. ", 425 "You can recover most, if not all, of the changes ", 426 "to this file using the -r option to ", gp->progname, ":\n\n\t", 427 gp->progname, " -r ", t); 428 if (len > sizeof(buf) - 1) { 429 lerr: msgq(sp, M_ERR, "064|Recovery file buffer overrun"); 430 goto err; 431 } 432 433 /* 434 * Format the message. (Yes, I know it's silly.) 435 * Requires that the message end in a <newline>. 436 */ 437 #define FMTCOLS 60 438 for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) { 439 /* Check for a short length. */ 440 if (len <= FMTCOLS) { 441 t2 = t1 + (len - 1); 442 goto wout; 443 } 444 445 /* Check for a required <newline>. */ 446 t2 = strchr(t1, '\n'); 447 if (t2 - t1 <= FMTCOLS) 448 goto wout; 449 450 /* Find the closest space, if any. */ 451 for (t3 = t2; t2 > t1; --t2) 452 if (*t2 == ' ') { 453 if (t2 - t1 <= FMTCOLS) 454 goto wout; 455 t3 = t2; 456 } 457 t2 = t3; 458 459 /* t2 points to the last character to display. */ 460 wout: *t2++ = '\n'; 461 462 /* t2 points one after the last character to display. */ 463 if (write(fd, t1, t2 - t1) != t2 - t1) 464 goto werr; 465 } 466 467 if (issync) { 468 rcv_email(sp, mpath); 469 if (close(fd)) { 470 werr: msgq(sp, M_SYSERR, "065|Recovery file"); 471 goto err; 472 } 473 } 474 return (0); 475 476 err: if (!issync) 477 ep->rcv_fd = -1; 478 if (fd != -1) 479 (void)close(fd); 480 return (1); 481 } 482 483 /* 484 * people making love 485 * never exactly the same 486 * just like a snowflake 487 * 488 * rcv_list -- 489 * List the files that can be recovered by this user. 490 * 491 * PUBLIC: int rcv_list(SCR *); 492 */ 493 int 494 rcv_list(sp) 495 SCR *sp; 496 { 497 struct dirent *dp; 498 struct stat sb; 499 DIR *dirp; 500 FILE *fp; 501 int found; 502 char *p, *t, file[MAXPATHLEN], path[MAXPATHLEN]; 503 504 /* Open the recovery directory for reading. */ 505 if (opts_empty(sp, O_RECDIR, 0)) 506 return (1); 507 p = O_STR(sp, O_RECDIR); 508 if (chdir(p) || (dirp = opendir(".")) == NULL) { 509 msgq_str(sp, M_SYSERR, p, "recdir: %s"); 510 return (1); 511 } 512 513 /* Read the directory. */ 514 for (found = 0; (dp = readdir(dirp)) != NULL;) { 515 if (strncmp(dp->d_name, "recover.", 8)) 516 continue; 517 518 /* 519 * If it's readable, it's recoverable. 520 * 521 * XXX 522 * Should be "r", we don't want to write the file. However, 523 * if we're using fcntl(2), there's no way to lock a file 524 * descriptor that's not open for writing. 525 */ 526 if ((fp = fopen(dp->d_name, "r+")) == NULL) 527 continue; 528 529 switch (file_lock(sp, NULL, NULL, fileno(fp), 1)) { 530 case LOCK_FAILED: 531 /* 532 * XXX 533 * Assume that a lock can't be acquired, but that we 534 * should permit recovery anyway. If this is wrong, 535 * and someone else is using the file, we're going to 536 * die horribly. 537 */ 538 break; 539 case LOCK_SUCCESS: 540 break; 541 case LOCK_UNAVAIL: 542 /* If it's locked, it's live. */ 543 (void)fclose(fp); 544 continue; 545 } 546 547 /* Check the headers. */ 548 if (fgets(file, sizeof(file), fp) == NULL || 549 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) || 550 (p = strchr(file, '\n')) == NULL || 551 fgets(path, sizeof(path), fp) == NULL || 552 strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) || 553 (t = strchr(path, '\n')) == NULL) { 554 msgq_str(sp, M_ERR, dp->d_name, 555 "066|%s: malformed recovery file"); 556 goto next; 557 } 558 *p = *t = '\0'; 559 560 /* 561 * If the file doesn't exist, it's an orphaned recovery file, 562 * toss it. 563 * 564 * XXX 565 * This can occur if the backup file was deleted and we crashed 566 * before deleting the email file. 567 */ 568 errno = 0; 569 if (stat(path + sizeof(VI_PHEADER) - 1, &sb) && 570 errno == ENOENT) { 571 (void)unlink(dp->d_name); 572 goto next; 573 } 574 575 /* Get the last modification time and display. */ 576 (void)fstat(fileno(fp), &sb); 577 (void)printf("%.24s: %s\n", 578 ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1); 579 found = 1; 580 581 /* Close, discarding lock. */ 582 next: (void)fclose(fp); 583 } 584 if (found == 0) 585 (void)printf("%s: No files to recover\n", sp->gp->progname); 586 (void)closedir(dirp); 587 return (0); 588 } 589 590 /* 591 * rcv_read -- 592 * Start a recovered file as the file to edit. 593 * 594 * PUBLIC: int rcv_read(SCR *, FREF *); 595 */ 596 int 597 rcv_read(sp, frp) 598 SCR *sp; 599 FREF *frp; 600 { 601 struct dirent *dp; 602 struct stat sb; 603 DIR *dirp; 604 EXF *ep; 605 time_t rec_mtime; 606 int fd, found, locked, requested, sv_fd; 607 char *name, *p, *t, *rp, *recp, *pathp; 608 char file[MAXPATHLEN], path[MAXPATHLEN], recpath[MAXPATHLEN]; 609 610 if (opts_empty(sp, O_RECDIR, 0)) 611 return (1); 612 rp = O_STR(sp, O_RECDIR); 613 if ((dirp = opendir(rp)) == NULL) { 614 msgq_str(sp, M_SYSERR, rp, "%s"); 615 return (1); 616 } 617 618 name = frp->name; 619 sv_fd = -1; 620 rec_mtime = 0; 621 recp = pathp = NULL; 622 for (found = requested = 0; (dp = readdir(dirp)) != NULL;) { 623 if (strncmp(dp->d_name, "recover.", 8)) 624 continue; 625 (void)snprintf(recpath, 626 sizeof(recpath), "%s/%s", rp, dp->d_name); 627 628 /* 629 * If it's readable, it's recoverable. It would be very 630 * nice to use stdio(3), but, we can't because that would 631 * require closing and then reopening the file so that we 632 * could have a lock and still close the FP. Another tip 633 * of the hat to fcntl(2). 634 * 635 * XXX 636 * Should be O_RDONLY, we don't want to write it. However, 637 * if we're using fcntl(2), there's no way to lock a file 638 * descriptor that's not open for writing. 639 */ 640 if ((fd = open(recpath, O_RDWR, 0)) == -1) 641 continue; 642 643 switch (file_lock(sp, NULL, NULL, fd, 1)) { 644 case LOCK_FAILED: 645 /* 646 * XXX 647 * Assume that a lock can't be acquired, but that we 648 * should permit recovery anyway. If this is wrong, 649 * and someone else is using the file, we're going to 650 * die horribly. 651 */ 652 locked = 0; 653 break; 654 case LOCK_SUCCESS: 655 locked = 1; 656 break; 657 case LOCK_UNAVAIL: 658 /* If it's locked, it's live. */ 659 (void)close(fd); 660 continue; 661 } 662 663 /* Check the headers. */ 664 if (rcv_gets(file, sizeof(file), fd) == NULL || 665 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) || 666 (p = strchr(file, '\n')) == NULL || 667 rcv_gets(path, sizeof(path), fd) == NULL || 668 strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) || 669 (t = strchr(path, '\n')) == NULL) { 670 msgq_str(sp, M_ERR, recpath, 671 "067|%s: malformed recovery file"); 672 goto next; 673 } 674 *p = *t = '\0'; 675 ++found; 676 677 /* 678 * If the file doesn't exist, it's an orphaned recovery file, 679 * toss it. 680 * 681 * XXX 682 * This can occur if the backup file was deleted and we crashed 683 * before deleting the email file. 684 */ 685 errno = 0; 686 if (stat(path + sizeof(VI_PHEADER) - 1, &sb) && 687 errno == ENOENT) { 688 (void)unlink(dp->d_name); 689 goto next; 690 } 691 692 /* Check the file name. */ 693 if (strcmp(file + sizeof(VI_FHEADER) - 1, name)) 694 goto next; 695 696 ++requested; 697 698 /* 699 * If we've found more than one, take the most recent. 700 * 701 * XXX 702 * Since we're using st_mtime, for portability reasons, 703 * we only get a single second granularity, instead of 704 * getting it right. 705 */ 706 (void)fstat(fd, &sb); 707 if (recp == NULL || rec_mtime < sb.st_mtime) { 708 p = recp; 709 t = pathp; 710 if ((recp = strdup(recpath)) == NULL) { 711 msgq(sp, M_SYSERR, NULL); 712 recp = p; 713 goto next; 714 } 715 if ((pathp = strdup(path)) == NULL) { 716 msgq(sp, M_SYSERR, NULL); 717 free(recp); 718 recp = p; 719 pathp = t; 720 goto next; 721 } 722 if (p != NULL) { 723 free(p); 724 free(t); 725 } 726 rec_mtime = sb.st_mtime; 727 if (sv_fd != -1) 728 (void)close(sv_fd); 729 sv_fd = fd; 730 } else 731 next: (void)close(fd); 732 } 733 (void)closedir(dirp); 734 735 if (recp == NULL) { 736 msgq_str(sp, M_INFO, name, 737 "068|No files named %s, readable by you, to recover"); 738 return (1); 739 } 740 if (found) { 741 if (requested > 1) 742 msgq(sp, M_INFO, 743 "069|There are older versions of this file for you to recover"); 744 if (found > requested) 745 msgq(sp, M_INFO, 746 "070|There are other files for you to recover"); 747 } 748 749 /* 750 * Create the FREF structure, start the btree file. 751 * 752 * XXX 753 * file_init() is going to set ep->rcv_path. 754 */ 755 if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) { 756 free(recp); 757 free(pathp); 758 (void)close(sv_fd); 759 return (1); 760 } 761 762 /* 763 * We keep an open lock on the file so that the recover option can 764 * distinguish between files that are live and those that need to 765 * be recovered. The lock is already acquired, just copy it. 766 */ 767 ep = sp->ep; 768 ep->rcv_mpath = recp; 769 ep->rcv_fd = sv_fd; 770 if (!locked) 771 F_SET(frp, FR_UNLOCKED); 772 773 /* We believe the file is recoverable. */ 774 F_SET(ep, F_RCV_ON); 775 return (0); 776 } 777 778 /* 779 * rcv_copy -- 780 * Copy a recovery file. 781 */ 782 static int 783 rcv_copy(sp, wfd, fname) 784 SCR *sp; 785 int wfd; 786 char *fname; 787 { 788 int nr, nw, off, rfd; 789 char buf[8 * 1024]; 790 791 if ((rfd = open(fname, O_RDONLY, 0)) == -1) 792 goto err; 793 while ((nr = read(rfd, buf, sizeof(buf))) > 0) 794 for (off = 0; nr; nr -= nw, off += nw) 795 if ((nw = write(wfd, buf + off, nr)) < 0) 796 goto err; 797 if (nr == 0) 798 return (0); 799 800 err: msgq_str(sp, M_SYSERR, fname, "%s"); 801 return (1); 802 } 803 804 /* 805 * rcv_gets -- 806 * Fgets(3) for a file descriptor. 807 */ 808 static char * 809 rcv_gets(buf, len, fd) 810 char *buf; 811 size_t len; 812 int fd; 813 { 814 int nr; 815 char *p; 816 817 if ((nr = read(fd, buf, len - 1)) == -1) 818 return (NULL); 819 buf[nr] = '\0'; 820 if ((p = strchr(buf, '\n')) == NULL) 821 return (NULL); 822 (void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET); 823 return (buf); 824 } 825 826 /* 827 * rcv_mktemp -- 828 * Paranoid make temporary file routine. 829 */ 830 static int 831 rcv_mktemp(sp, path, dname, perms) 832 SCR *sp; 833 char *path, *dname; 834 int perms; 835 { 836 int fd; 837 838 /* 839 * !!! 840 * We expect mkstemp(3) to set the permissions correctly. On 841 * historic System V systems, mkstemp didn't. Do it here, on 842 * GP's. This also protects us from users with stupid umasks. 843 * 844 * XXX 845 * The variable perms should really be a mode_t. 846 */ 847 if ((fd = mkstemp(path)) == -1 || fchmod(fd, perms) == -1) { 848 msgq_str(sp, M_SYSERR, dname, "%s"); 849 if (fd != -1) { 850 close(fd); 851 unlink(path); 852 fd = -1; 853 } 854 } 855 return (fd); 856 } 857 858 /* 859 * rcv_email -- 860 * Send email. 861 */ 862 static void 863 rcv_email(sp, fname) 864 SCR *sp; 865 char *fname; 866 { 867 struct stat sb; 868 char buf[MAXPATHLEN * 2 + 20]; 869 870 if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb)) 871 msgq_str(sp, M_SYSERR, 872 _PATH_SENDMAIL, "071|not sending email: %s"); 873 else { 874 /* 875 * !!! 876 * If you need to port this to a system that doesn't have 877 * sendmail, the -t flag causes sendmail to read the message 878 * for the recipients instead of specifying them some other 879 * way. 880 */ 881 (void)snprintf(buf, sizeof(buf), 882 "%s -t < %s", _PATH_SENDMAIL, fname); 883 (void)system(buf); 884 } 885 } 886