1 /* $NetBSD: recover.c,v 1.4 2013/11/30 14:54:29 christos Exp $ */ 2 /*- 3 * Copyright (c) 1993, 1994 4 * The Regents of the University of California. All rights reserved. 5 * Copyright (c) 1993, 1994, 1995, 1996 6 * Keith Bostic. All rights reserved. 7 * 8 * See the LICENSE file for redistribution information. 9 */ 10 11 #include "config.h" 12 13 #ifndef lint 14 static const char sccsid[] = "Id: recover.c,v 10.31 2001/11/01 15:24:44 skimo Exp (Berkeley) Date: 2001/11/01 15:24:44 "; 15 #endif /* not lint */ 16 17 #include <sys/param.h> 18 #include <sys/types.h> /* XXX: param.h may not have included types.h */ 19 #include <sys/queue.h> 20 #include <sys/stat.h> 21 22 /* 23 * We include <sys/file.h>, because the open #defines were found there 24 * on historical systems. We also include <fcntl.h> because the open(2) 25 * #defines are found there on newer systems. 26 */ 27 #include <sys/file.h> 28 29 #include <bitstring.h> 30 #include <dirent.h> 31 #include <errno.h> 32 #include <fcntl.h> 33 #include <limits.h> 34 #include <pwd.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <time.h> 39 #include <unistd.h> 40 41 #include "common.h" 42 #include "pathnames.h" 43 44 /* 45 * Recovery code. 46 * 47 * The basic scheme is as follows. In the EXF structure, we maintain full 48 * paths of a b+tree file and a mail recovery file. The former is the file 49 * used as backing store by the DB package. The latter is the file that 50 * contains an email message to be sent to the user if we crash. The two 51 * simple states of recovery are: 52 * 53 * + first starting the edit session: 54 * the b+tree file exists and is mode 700, the mail recovery 55 * file doesn't exist. 56 * + after the file has been modified: 57 * the b+tree file exists and is mode 600, the mail recovery 58 * file exists, and is exclusively locked. 59 * 60 * In the EXF structure we maintain a file descriptor that is the locked 61 * file descriptor for the mail recovery file. NOTE: we sometimes have to 62 * do locking with fcntl(2). This is a problem because if you close(2) any 63 * file descriptor associated with the file, ALL of the locks go away. Be 64 * sure to remember that if you have to modify the recovery code. (It has 65 * been rhetorically asked of what the designers could have been thinking 66 * when they did that interface. The answer is simple: they weren't.) 67 * 68 * To find out if a recovery file/backing file pair are in use, try to get 69 * a lock on the recovery file. 70 * 71 * To find out if a backing file can be deleted at boot time, check for an 72 * owner execute bit. (Yes, I know it's ugly, but it's either that or put 73 * special stuff into the backing file itself, or correlate the files at 74 * boot time, neither of which looks like fun.) Note also that there's a 75 * window between when the file is created and the X bit is set. It's small, 76 * but it's there. To fix the window, check for 0 length files as well. 77 * 78 * To find out if a file can be recovered, check the F_RCV_ON bit. Note, 79 * this DOES NOT mean that any initialization has been done, only that we 80 * haven't yet failed at setting up or doing recovery. 81 * 82 * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit. 83 * If that bit is not set when ending a file session: 84 * If the EXF structure paths (rcv_path and rcv_mpath) are not NULL, 85 * they are unlink(2)'d, and free(3)'d. 86 * If the EXF file descriptor (rcv_fd) is not -1, it is closed. 87 * 88 * The backing b+tree file is set up when a file is first edited, so that 89 * the DB package can use it for on-disk caching and/or to snapshot the 90 * file. When the file is first modified, the mail recovery file is created, 91 * the backing file permissions are updated, the file is sync(2)'d to disk, 92 * and the timer is started. Then, at RCV_PERIOD second intervals, the 93 * b+tree file is synced to disk. RCV_PERIOD is measured using SIGALRM, which 94 * means that the data structures (SCR, EXF, the underlying tree structures) 95 * must be consistent when the signal arrives. 96 * 97 * The recovery mail file contains normal mail headers, with two additions, 98 * which occur in THIS order, as the FIRST TWO headers: 99 * 100 * X-vi-recover-file: file_name 101 * X-vi-recover-path: recover_path 102 * 103 * Since newlines delimit the headers, this means that file names cannot have 104 * newlines in them, but that's probably okay. As these files aren't intended 105 * to be long-lived, changing their format won't be too painful. 106 * 107 * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX". 108 */ 109 110 #define VI_FHEADER "X-vi-recover-file: " 111 #define VI_PHEADER "X-vi-recover-path: " 112 113 static int rcv_copy __P((SCR *, int, char *)); 114 static void rcv_email __P((SCR *, char *)); 115 static char *rcv_gets __P((char *, size_t, int)); 116 static int rcv_mailfile __P((SCR *, int, char *)); 117 static int rcv_mktemp __P((SCR *, char *, const char *, int)); 118 119 /* 120 * rcv_tmp -- 121 * Build a file name that will be used as the recovery file. 122 * 123 * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *)); 124 */ 125 int 126 rcv_tmp(SCR *sp, EXF *ep, char *name) 127 { 128 struct stat sb; 129 int fd; 130 char path[MAXPATHLEN]; 131 const char *dp; 132 133 /* 134 * !!! 135 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. 136 * 137 * 138 * If the recovery directory doesn't exist, try and create it. As 139 * the recovery files are themselves protected from reading/writing 140 * by other than the owner, the worst that can happen is that a user 141 * would have permission to remove other user's recovery files. If 142 * the sticky bit has the BSD semantics, that too will be impossible. 143 */ 144 if (opts_empty(sp, O_RECDIR, 0)) 145 goto err; 146 dp = O_STR(sp, O_RECDIR); 147 if (stat(dp, &sb)) { 148 if (errno != ENOENT || mkdir(dp, 0)) { 149 msgq(sp, M_SYSERR, "%s", dp); 150 goto err; 151 } 152 (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX); 153 } 154 155 /* Newlines delimit the mail messages. */ 156 if (strchr(name, '\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.XXXXXX", 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 __P((SCR *)); 185 */ 186 int 187 rcv_init(SCR *sp) 188 { 189 EXF *ep; 190 db_recno_t lno; 191 192 ep = sp->ep; 193 194 /* Only do this once. */ 195 F_CLR(ep, F_FIRSTMODIFY); 196 197 /* If we already know the file isn't recoverable, we're done. */ 198 if (!F_ISSET(ep, F_RCV_ON)) 199 return (0); 200 201 /* Turn off recoverability until we figure out if this will work. */ 202 F_CLR(ep, F_RCV_ON); 203 204 /* Test if we're recovering a file, not editing one. */ 205 if (ep->rcv_mpath == NULL) { 206 /* Build a file to mail to the user. */ 207 if (rcv_mailfile(sp, 0, NULL)) 208 goto err; 209 210 /* Force a read of the entire file. */ 211 if (db_last(sp, &lno)) 212 goto err; 213 214 /* Turn on a busy message, and sync it to backing store. */ 215 sp->gp->scr_busy(sp, 216 "057|Copying file for recovery...", BUSY_ON); 217 if (ep->db->sync(ep->db, 0)) { 218 msgq_str(sp, M_SYSERR, ep->rcv_path, 219 "058|Preservation failed: %s"); 220 sp->gp->scr_busy(sp, NULL, BUSY_OFF); 221 goto err; 222 } 223 sp->gp->scr_busy(sp, NULL, BUSY_OFF); 224 } 225 226 /* Turn off the owner execute bit. */ 227 (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR); 228 229 /* We believe the file is recoverable. */ 230 F_SET(ep, F_RCV_ON); 231 return (0); 232 233 err: msgq(sp, M_ERR, 234 "059|Modifications not recoverable if the session fails"); 235 return (1); 236 } 237 238 /* 239 * rcv_sync -- 240 * Sync the file, optionally: 241 * flagging the backup file to be preserved 242 * snapshotting the backup file and send email to the user 243 * sending email to the user if the file was modified 244 * ending the file session 245 * 246 * PUBLIC: int rcv_sync __P((SCR *, u_int)); 247 */ 248 int 249 rcv_sync(SCR *sp, u_int flags) 250 { 251 EXF *ep; 252 int fd, rval; 253 char buf[1024]; 254 const char *dp; 255 256 /* Make sure that there's something to recover/sync. */ 257 ep = sp->ep; 258 if (ep == NULL || !F_ISSET(ep, F_RCV_ON)) 259 return (0); 260 261 /* Sync the file if it's been modified. */ 262 if (F_ISSET(ep, F_MODIFIED)) { 263 /* 264 * If we are using a db1 version of the database, 265 * we want to sync the underlying btree not the 266 * recno tree which is transient anyway. 267 */ 268 #ifndef R_RECNOSYNC 269 #define R_RECNOSYNC 0 270 #endif 271 if (ep->db->sync(ep->db, R_RECNOSYNC)) { 272 F_CLR(ep, F_RCV_ON | F_RCV_NORM); 273 msgq_str(sp, M_SYSERR, 274 ep->rcv_path, "060|File backup failed: %s"); 275 return (1); 276 } 277 278 /* REQUEST: don't remove backing file on exit. */ 279 if (LF_ISSET(RCV_PRESERVE)) 280 F_SET(ep, F_RCV_NORM); 281 282 /* REQUEST: send email. */ 283 if (LF_ISSET(RCV_EMAIL)) 284 rcv_email(sp, ep->rcv_mpath); 285 } 286 287 /* 288 * !!! 289 * Each time the user exec's :preserve, we have to snapshot all of 290 * the recovery information, i.e. it's like the user re-edited the 291 * file. We copy the DB(3) backing file, and then create a new mail 292 * recovery file, it's simpler than exiting and reopening all of the 293 * underlying files. 294 * 295 * REQUEST: snapshot the file. 296 */ 297 rval = 0; 298 if (LF_ISSET(RCV_SNAPSHOT)) { 299 if (opts_empty(sp, O_RECDIR, 0)) 300 goto err; 301 dp = O_STR(sp, O_RECDIR); 302 (void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp); 303 if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1) 304 goto err; 305 sp->gp->scr_busy(sp, 306 "061|Copying file for recovery...", BUSY_ON); 307 if (rcv_copy(sp, fd, ep->rcv_path) || 308 close(fd) || rcv_mailfile(sp, 1, buf)) { 309 (void)unlink(buf); 310 (void)close(fd); 311 rval = 1; 312 } 313 sp->gp->scr_busy(sp, NULL, BUSY_OFF); 314 } 315 if (0) { 316 err: rval = 1; 317 } 318 319 /* REQUEST: end the file session. */ 320 if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1)) 321 rval = 1; 322 323 return (rval); 324 } 325 326 /* 327 * rcv_mailfile -- 328 * Build the file to mail to the user. 329 */ 330 static int 331 rcv_mailfile(SCR *sp, int issync, 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 *p, *t, buf[4096], mpath[MAXPATHLEN]; 341 const char *dp; 342 char *t1, *t2, *t3; 343 344 /* 345 * XXX 346 * MAXHOSTNAMELEN is in various places on various systems, including 347 * <netdb.h> and <sys/socket.h>. If not found, use a large default. 348 */ 349 #ifndef MAXHOSTNAMELEN 350 #define MAXHOSTNAMELEN 1024 351 #endif 352 char host[MAXHOSTNAMELEN]; 353 354 gp = sp->gp; 355 if ((pw = getpwuid(uid = getuid())) == NULL) { 356 msgq(sp, M_ERR, 357 "062|Information on user id %u not found", uid); 358 return (1); 359 } 360 361 if (opts_empty(sp, O_RECDIR, 0)) 362 return (1); 363 dp = O_STR(sp, O_RECDIR); 364 (void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp); 365 if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1) 366 return (1); 367 368 /* 369 * XXX 370 * We keep an open lock on the file so that the recover option can 371 * distinguish between files that are live and those that need to 372 * be recovered. There's an obvious window between the mkstemp call 373 * and the lock, but it's pretty small. 374 */ 375 ep = sp->ep; 376 if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS) 377 msgq(sp, M_SYSERR, "063|Unable to lock recovery file"); 378 if (!issync) { 379 /* Save the recover file descriptor, and mail path. */ 380 ep->rcv_fd = fd; 381 if ((ep->rcv_mpath = strdup(mpath)) == NULL) { 382 msgq(sp, M_SYSERR, NULL); 383 goto err; 384 } 385 cp_path = ep->rcv_path; 386 } 387 388 /* 389 * XXX 390 * We can't use stdio(3) here. The problem is that we may be using 391 * fcntl(2), so if ANY file descriptor into the file is closed, the 392 * lock is lost. So, we could never close the FILE *, even if we 393 * dup'd the fd first. 394 */ 395 t = sp->frp->name; 396 if ((p = strrchr(t, '/')) == NULL) 397 p = t; 398 else 399 ++p; 400 (void)time(&now); 401 (void)gethostname(host, sizeof(host)); 402 len = snprintf(buf, sizeof(buf), 403 "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n", 404 VI_FHEADER, t, /* Non-standard. */ 405 VI_PHEADER, cp_path, /* Non-standard. */ 406 "Reply-To: root", 407 "From: root (Nvi recovery program)", 408 "To: ", pw->pw_name, 409 "Subject: Nvi saved the file ", p, 410 "Precedence: bulk"); /* For vacation(1). */ 411 if (len > sizeof(buf) - 1) 412 goto lerr; 413 if ((size_t)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 __P((SCR *)); 488 */ 489 int 490 rcv_list(SCR *sp) 491 { 492 struct dirent *dp; 493 struct stat sb; 494 DIR *dirp; 495 FILE *fp; 496 int found; 497 char *p, *t; 498 const char *d; 499 char file[MAXPATHLEN], path[MAXPATHLEN]; 500 501 /* Open the recovery directory for reading. */ 502 if (opts_empty(sp, O_RECDIR, 0)) 503 return (1); 504 d = O_STR(sp, O_RECDIR); 505 if (chdir(d) || (dirp = opendir(".")) == NULL) { 506 msgq_str(sp, M_SYSERR, d, "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("%s: No files to recover\n", sp->gp->progname); 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(SCR *sp, FREF *frp) 595 { 596 struct dirent *dp; 597 struct stat sb; 598 DIR *dirp; 599 EXF *ep; 600 time_t rec_mtime; 601 int fd, found, locked = 0, requested, sv_fd; 602 char *name, *p, *t, *recp, *pathp; 603 const char *rp; 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_ERR, 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 free(pathp); 772 return (0); 773 } 774 775 /* 776 * rcv_copy -- 777 * Copy a recovery file. 778 */ 779 static int 780 rcv_copy(SCR *sp, int wfd, char *fname) 781 { 782 int nr, nw, off, rfd; 783 char buf[8 * 1024]; 784 785 if ((rfd = open(fname, O_RDONLY, 0)) == -1) 786 goto err; 787 while ((nr = read(rfd, buf, sizeof(buf))) > 0) 788 for (off = 0; nr; nr -= nw, off += nw) 789 if ((nw = write(wfd, buf + off, nr)) < 0) 790 goto err; 791 if (nr == 0) 792 return (0); 793 794 err: msgq_str(sp, M_SYSERR, fname, "%s"); 795 return (1); 796 } 797 798 /* 799 * rcv_gets -- 800 * Fgets(3) for a file descriptor. 801 */ 802 static char * 803 rcv_gets(char *buf, size_t len, int fd) 804 { 805 int nr; 806 char *p; 807 808 if ((nr = read(fd, buf, len - 1)) == -1) 809 return (NULL); 810 if ((p = strchr(buf, '\n')) == NULL) 811 return (NULL); 812 (void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET); 813 return (buf); 814 } 815 816 /* 817 * rcv_mktemp -- 818 * Paranoid make temporary file routine. 819 */ 820 static int 821 rcv_mktemp(SCR *sp, char *path, const char *dname, int perms) 822 { 823 int fd; 824 825 /* 826 * !!! 827 * We expect mkstemp(3) to set the permissions correctly. On 828 * historic System V systems, mkstemp didn't. Do it here, on 829 * GP's. 830 * 831 * XXX 832 * The variable perms should really be a mode_t, and it would 833 * be nice to use fchmod(2) instead of chmod(2), here. 834 */ 835 if ((fd = mkstemp(path)) == -1) 836 msgq_str(sp, M_SYSERR, dname, "%s"); 837 else 838 (void)chmod(path, perms); 839 return (fd); 840 } 841 842 /* 843 * rcv_email -- 844 * Send email. 845 */ 846 static void 847 rcv_email(SCR *sp, char *fname) 848 { 849 struct stat sb; 850 char buf[MAXPATHLEN * 2 + 20]; 851 852 if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb)) 853 msgq_str(sp, M_SYSERR, 854 _PATH_SENDMAIL, "071|not sending email: %s"); 855 else { 856 /* 857 * !!! 858 * If you need to port this to a system that doesn't have 859 * sendmail, the -t flag causes sendmail to read the message 860 * for the recipients instead of specifying them some other 861 * way. 862 */ 863 (void)snprintf(buf, sizeof(buf), 864 "%s -t < %s", _PATH_SENDMAIL, fname); 865 (void)system(buf); 866 } 867 } 868