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