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