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