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