1 /*- 2 * Copyright (c) 1980, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * %sccs.include.proprietary.c% 6 */ 7 8 #ifndef lint 9 static char copyright[] = 10 "@(#) Copyright (c) 1980, 1993\n\ 11 The Regents of the University of California. All rights reserved.\n"; 12 #endif /* not lint */ 13 14 #ifndef lint 15 static char sccsid[] = "@(#)ex3.7recover.c 8.1 (Berkeley) 06/09/93"; 16 #endif /* not lint */ 17 18 #include <stdio.h> /* mjm: BUFSIZ: stdio = 512, VMUNIX = 1024 */ 19 #undef BUFSIZ /* mjm: BUFSIZ different */ 20 #undef EOF /* mjm: EOF and NULL effectively the same */ 21 #undef NULL 22 23 #include "ex.h" 24 #include "ex_temp.h" 25 #include "ex_tty.h" 26 #include <sys/dir.h> 27 #if __STDC__ 28 #include <stdarg.h> 29 #else 30 #include <varargs.h> 31 #endif 32 33 char xstr[1]; /* make loader happy */ 34 short tfile = -1; /* ditto */ 35 36 #if __STDC__ 37 void fpr(const char *fmt, ...); 38 #else 39 void fpr(); 40 #endif 41 42 /* 43 * 44 * This program searches through the specified directory and then 45 * the directory _PATH_USRPRESERVE looking for an instance of the specified 46 * file from a crashed editor or a crashed system. 47 * If this file is found, it is unscrambled and written to 48 * the standard output. 49 * 50 * If this program terminates without a "broken pipe" diagnostic 51 * (i.e. the editor doesn't die right away) then the buffer we are 52 * writing from is removed when we finish. This is potentially a mistake 53 * as there is not enough handshaking to guarantee that the file has actually 54 * been recovered, but should suffice for most cases. 55 */ 56 57 /* 58 * For lint's sake... 59 */ 60 #ifndef lint 61 #define ignorl(a) a 62 #endif 63 64 /* 65 * Limit on the number of printed entries 66 * when an, e.g. ``ex -r'' command is given. 67 */ 68 #define NENTRY 50 69 70 char *ctime(); 71 char nb[BUFSIZ]; 72 int vercnt; /* Count number of versions of file found */ 73 74 main(argc, argv) 75 int argc; 76 char *argv[]; 77 { 78 register char *cp; 79 register int b, i; 80 81 /* 82 * Initialize as though the editor had just started. 83 */ 84 fendcore = (line *) sbrk(0); 85 dot = zero = dol = fendcore; 86 one = zero + 1; 87 endcore = fendcore - 2; 88 iblock = oblock = -1; 89 90 /* 91 * If given only a -r argument, then list the saved files. 92 */ 93 if (argc == 2 && eq(argv[1], "-r")) { 94 listfiles(_PATH_PRESERVE); 95 exit(0); 96 } 97 if (argc != 3) 98 error(" Wrong number of arguments to exrecover", 0); 99 100 CP(file, argv[2]); 101 102 /* 103 * Search for this file. 104 */ 105 findtmp(argv[1]); 106 107 /* 108 * Got (one of the versions of) it, write it back to the editor. 109 */ 110 cp = ctime(&H.Time); 111 cp[19] = 0; 112 fpr(" [Dated: %s", cp); 113 fpr(vercnt > 1 ? ", newest of %d saved]" : "]", vercnt); 114 H.Flines++; 115 116 /* 117 * Allocate space for the line pointers from the temp file. 118 */ 119 if ((int) sbrk((int) (H.Flines * sizeof (line))) == -1) 120 /* 121 * Good grief. 122 */ 123 error(" Not enough core for lines", 0); 124 #ifdef DEBUG 125 fpr("%d lines\n", H.Flines); 126 #endif 127 128 /* 129 * Now go get the blocks of seek pointers which are scattered 130 * throughout the temp file, reconstructing the incore 131 * line pointers at point of crash. 132 */ 133 b = 0; 134 while (H.Flines > 0) { 135 ignorl(lseek(tfile, (long) blocks[b] * BUFSIZ, 0)); 136 i = H.Flines < BUFSIZ / sizeof (line) ? 137 H.Flines * sizeof (line) : BUFSIZ; 138 if (read(tfile, (char *) dot, i) != i) { 139 perror(nb); 140 exit(1); 141 } 142 dot += i / sizeof (line); 143 H.Flines -= i / sizeof (line); 144 b++; 145 } 146 dot--; dol = dot; 147 148 /* 149 * Sigh... due to sandbagging some lines may really not be there. 150 * Find and discard such. This shouldn't happen much. 151 */ 152 scrapbad(); 153 154 /* 155 * Now if there were any lines in the recovered file 156 * write them to the standard output. 157 */ 158 if (dol > zero) { 159 addr1 = one; addr2 = dol; io = 1; 160 putfile(0); 161 } 162 163 /* 164 * Trash the saved buffer. 165 * Hopefully the system won't crash before the editor 166 * syncs the new recovered buffer; i.e. for an instant here 167 * you may lose if the system crashes because this file 168 * is gone, but the editor hasn't completed reading the recovered 169 * file from the pipe from us to it. 170 * 171 * This doesn't work if we are coming from an non-absolute path 172 * name since we may have chdir'ed but what the hay, noone really 173 * ever edits with temporaries in "." anyways. 174 */ 175 if (nb[0] == '/') 176 ignore(unlink(nb)); 177 178 /* 179 * Adieu. 180 */ 181 exit(0); 182 } 183 184 /* 185 * Print an error message (notably not in error 186 * message file). If terminal is in RAW mode, then 187 * we should be writing output for "vi", so don't print 188 * a newline which would screw up the screen. 189 */ 190 /*VARARGS2*/ 191 error(str, inf) 192 char *str; 193 int inf; 194 { 195 196 fpr(str, inf); 197 (void)ioctl(2, TIOCGETP, &tty); 198 if ((tty.sg_flags & RAW) == 0) 199 fpr("\n"); 200 exit(1); 201 } 202 203 /* 204 * Here we save the information about files, when 205 * you ask us what files we have saved for you. 206 * We buffer file name, number of lines, and the time 207 * at which the file was saved. 208 */ 209 struct svfile { 210 char sf_name[FNSIZE + 1]; 211 int sf_lines; 212 char sf_entry[MAXNAMLEN + 1]; 213 time_t sf_time; 214 }; 215 216 listfiles(dirname) 217 char *dirname; 218 { 219 register DIR *dir; 220 struct direct *dirent; 221 int ecount, qucmp(); 222 register int f; 223 char *cp; 224 struct svfile *fp, svbuf[NENTRY]; 225 226 /* 227 * Open _PATH_PRESERVE, and go there to make things quick. 228 */ 229 dir = opendir(dirname); 230 if (dir == NULL) { 231 perror(dirname); 232 return; 233 } 234 if (chdir(dirname) < 0) { 235 perror(dirname); 236 return; 237 } 238 239 /* 240 * Look at the candidate files in _PATH_PRESERVE. 241 */ 242 fp = &svbuf[0]; 243 ecount = 0; 244 while ((dirent = readdir(dir)) != NULL) { 245 if (dirent->d_name[0] != 'E') 246 continue; 247 #ifdef DEBUG 248 fpr("considering %s\n", dirent->d_name); 249 #endif 250 /* 251 * Name begins with E; open it and 252 * make sure the uid in the header is our uid. 253 * If not, then don't bother with this file, it can't 254 * be ours. 255 */ 256 f = open(dirent->d_name, 0); 257 if (f < 0) { 258 #ifdef DEBUG 259 fpr("open failed\n"); 260 #endif 261 continue; 262 } 263 if (read(f, (char *) &H, sizeof H) != sizeof H) { 264 #ifdef DEBUG 265 fpr("culdnt read hedr\n"); 266 #endif 267 ignore(close(f)); 268 continue; 269 } 270 ignore(close(f)); 271 if (getuid() != H.Uid) { 272 #ifdef DEBUG 273 fpr("uid wrong\n"); 274 #endif 275 continue; 276 } 277 278 /* 279 * Saved the day! 280 */ 281 enter(fp++, dirent->d_name, ecount); 282 ecount++; 283 #ifdef DEBUG 284 fpr("entered file %s\n", dirent->d_name); 285 #endif 286 } 287 ignore(closedir(dir)); 288 289 /* 290 * If any files were saved, then sort them and print 291 * them out. 292 */ 293 if (ecount == 0) { 294 fpr("No files saved.\n"); 295 return; 296 } 297 qsort(&svbuf[0], ecount, sizeof svbuf[0], qucmp); 298 for (fp = &svbuf[0]; fp < &svbuf[ecount]; fp++) { 299 cp = ctime(&fp->sf_time); 300 cp[10] = 0; 301 fpr("On %s at ", cp); 302 cp[16] = 0; 303 fpr(&cp[11]); 304 fpr(" saved %d lines of file \"%s\"\n", 305 fp->sf_lines, fp->sf_name); 306 } 307 } 308 309 /* 310 * Enter a new file into the saved file information. 311 */ 312 enter(fp, fname, count) 313 struct svfile *fp; 314 char *fname; 315 { 316 register char *cp, *cp2; 317 register struct svfile *f, *fl; 318 time_t curtime, itol(); 319 320 f = 0; 321 if (count >= NENTRY) { 322 /* 323 * My god, a huge number of saved files. 324 * Would you work on a system that crashed this 325 * often? Hope not. So lets trash the oldest 326 * as the most useless. 327 * 328 * (I wonder if this code has ever run?) 329 */ 330 fl = fp - count + NENTRY - 1; 331 curtime = fl->sf_time; 332 for (f = fl; --f > fp-count; ) 333 if (f->sf_time < curtime) 334 curtime = f->sf_time; 335 for (f = fl; --f > fp-count; ) 336 if (f->sf_time == curtime) 337 break; 338 fp = f; 339 } 340 341 /* 342 * Gotcha. 343 */ 344 fp->sf_time = H.Time; 345 fp->sf_lines = H.Flines; 346 for (cp2 = fp->sf_name, cp = savedfile; *cp;) 347 *cp2++ = *cp++; 348 for (cp2 = fp->sf_entry, cp = fname; *cp && cp-fname < 14;) 349 *cp2++ = *cp++; 350 *cp2++ = 0; 351 } 352 353 /* 354 * Do the qsort compare to sort the entries first by file name, 355 * then by modify time. 356 */ 357 qucmp(p1, p2) 358 struct svfile *p1, *p2; 359 { 360 register int t; 361 362 if (t = strcmp(p1->sf_name, p2->sf_name)) 363 return(t); 364 if (p1->sf_time > p2->sf_time) 365 return(-1); 366 return(p1->sf_time < p2->sf_time); 367 } 368 369 /* 370 * Scratch for search. 371 */ 372 char bestnb[BUFSIZ]; /* Name of the best one */ 373 long besttime; /* Time at which the best file was saved */ 374 int bestfd; /* Keep best file open so it dont vanish */ 375 376 /* 377 * Look for a file, both in the users directory option value 378 * (i.e. usually /tmp) and in _PATH_PRESERVE. 379 * Want to find the newest so we search on and on. 380 */ 381 findtmp(dir) 382 char *dir; 383 { 384 385 /* 386 * No name or file so far. 387 */ 388 bestnb[0] = 0; 389 bestfd = -1; 390 391 /* 392 * Search _PATH_PRESERVE and, if we can get there, /tmp 393 * (actually the users "directory" option). 394 */ 395 searchdir(dir); 396 if (chdir(_PATH_PRESERVE) == 0) 397 searchdir(_PATH_PRESERVE); 398 if (bestfd != -1) { 399 /* 400 * Gotcha. 401 * Put the file (which is already open) in the file 402 * used by the temp file routines, and save its 403 * name for later unlinking. 404 */ 405 tfile = bestfd; 406 CP(nb, bestnb); 407 ignorl(lseek(tfile, 0l, 0)); 408 409 /* 410 * Gotta be able to read the header or fall through 411 * to lossage. 412 */ 413 if (read(tfile, (char *) &H, sizeof H) == sizeof H) 414 return; 415 } 416 417 /* 418 * Extreme lossage... 419 */ 420 error(" File not found", 0); 421 } 422 423 /* 424 * Search for the file in directory dirname. 425 * 426 * Don't chdir here, because the users directory 427 * may be ".", and we would move away before we searched it. 428 * Note that we actually chdir elsewhere (because it is too slow 429 * to look around in _PATH_PRESERVE without chdir'ing there) so we 430 * can't win, because we don't know the name of '.' and if the path 431 * name of the file we want to unlink is relative, rather than absolute 432 * we won't be able to find it again. 433 */ 434 searchdir(dirname) 435 char *dirname; 436 { 437 struct direct *dirent; 438 register DIR *dir; 439 char dbuf[BUFSIZ]; 440 441 dir = opendir(dirname); 442 if (dir == NULL) 443 return; 444 while ((dirent = readdir(dir)) != NULL) { 445 if (dirent->d_name[0] != 'E') 446 continue; 447 /* 448 * Got a file in the directory starting with E... 449 * Save a consed up name for the file to unlink 450 * later, and check that this is really a file 451 * we are looking for. 452 */ 453 ignore(strcat(strcat(strcpy(nb, dirname), "/"), dirent->d_name)); 454 if (yeah(nb)) { 455 /* 456 * Well, it is the file we are looking for. 457 * Is it more recent than any version we found before? 458 */ 459 if (H.Time > besttime) { 460 /* 461 * A winner. 462 */ 463 ignore(close(bestfd)); 464 bestfd = dup(tfile); 465 besttime = H.Time; 466 CP(bestnb, nb); 467 } 468 /* 469 * Count versions so user can be told there are 470 * ``yet more pages to be turned''. 471 */ 472 vercnt++; 473 } 474 ignore(close(tfile)); 475 } 476 ignore(closedir(dir)); 477 } 478 479 /* 480 * Given a candidate file to be recovered, see 481 * if its really an editor temporary and of this 482 * user and the file specified. 483 */ 484 yeah(name) 485 char *name; 486 { 487 488 tfile = open(name, 2); 489 if (tfile < 0) 490 return (0); 491 if (read(tfile, (char *) &H, sizeof H) != sizeof H) { 492 nope: 493 ignore(close(tfile)); 494 return (0); 495 } 496 if (!eq(savedfile, file)) 497 goto nope; 498 if (getuid() != H.Uid) 499 goto nope; 500 /* 501 * This is old and stupid code, which 502 * puts a word LOST in the header block, so that lost lines 503 * can be made to point at it. 504 */ 505 ignorl(lseek(tfile, (long)(BUFSIZ*HBLKS-8), 0)); 506 ignore(write(tfile, "LOST", 5)); 507 return (1); 508 } 509 510 preserve() 511 { 512 513 } 514 515 /* 516 * Find the true end of the scratch file, and ``LOSE'' 517 * lines which point into thin air. This lossage occurs 518 * due to the sandbagging of i/o which can cause blocks to 519 * be written in a non-obvious order, different from the order 520 * in which the editor tried to write them. 521 * 522 * Lines which are lost are replaced with the text LOST so 523 * they are easy to find. We work hard at pretty formatting here 524 * as lines tend to be lost in blocks. 525 * 526 * This only seems to happen on very heavily loaded systems, and 527 * not very often. 528 */ 529 scrapbad() 530 { 531 register line *ip; 532 struct stat stbuf; 533 off_t size, maxt; 534 int bno, cnt, bad, was; 535 char bk[BUFSIZ]; 536 537 ignore(fstat(tfile, &stbuf)); 538 size = stbuf.st_size; 539 maxt = (size >> SHFT) | (BNDRY-1); 540 bno = (maxt >> OFFBTS) & BLKMSK; 541 #ifdef DEBUG 542 fpr("size %ld, maxt %o, bno %d\n", size, maxt, bno); 543 #endif 544 545 /* 546 * Look for a null separating two lines in the temp file; 547 * if last line was split across blocks, then it is lost 548 * if the last block is. 549 */ 550 while (bno > 0) { 551 ignorl(lseek(tfile, (long) BUFSIZ * bno, 0)); 552 cnt = read(tfile, (char *) bk, BUFSIZ); 553 while (cnt > 0) 554 if (bk[--cnt] == 0) 555 goto null; 556 bno--; 557 } 558 null: 559 560 /* 561 * Magically calculate the largest valid pointer in the temp file, 562 * consing it up from the block number and the count. 563 */ 564 maxt = ((bno << OFFBTS) | (cnt >> SHFT)) & ~1; 565 #ifdef DEBUG 566 fpr("bno %d, cnt %d, maxt %o\n", bno, cnt, maxt); 567 #endif 568 569 /* 570 * Now cycle through the line pointers, 571 * trashing the Lusers. 572 */ 573 was = bad = 0; 574 for (ip = one; ip <= dol; ip++) 575 if (*ip > maxt) { 576 #ifdef DEBUG 577 fpr("%d bad, %o > %o\n", ip - zero, *ip, maxt); 578 #endif 579 if (was == 0) 580 was = ip - zero; 581 *ip = ((HBLKS*BUFSIZ)-8) >> SHFT; 582 } else if (was) { 583 if (bad == 0) 584 fpr(" [Lost line(s):"); 585 fpr(" %d", was); 586 if ((ip - 1) - zero > was) 587 fpr("-%d", (ip - 1) - zero); 588 bad++; 589 was = 0; 590 } 591 if (was != 0) { 592 if (bad == 0) 593 fpr(" [Lost line(s):"); 594 fpr(" %d", was); 595 if (dol - zero != was) 596 fpr("-%d", dol - zero); 597 bad++; 598 } 599 if (bad) 600 fpr("]"); 601 } 602 603 /* 604 * Aw shucks, if we only had a (void) cast. 605 */ 606 #ifdef lint 607 Ignorl(a) 608 long a; 609 { 610 611 a = a; 612 } 613 614 Ignore(a) 615 char *a; 616 { 617 618 a = a; 619 } 620 621 Ignorf(a) 622 int (*a)(); 623 { 624 625 a = a; 626 } 627 628 ignorl(a) 629 long a; 630 { 631 632 a = a; 633 } 634 #endif 635 636 int cntch, cntln, cntodd, cntnull; 637 /* 638 * Following routines stolen mercilessly from ex. 639 */ 640 putfile() 641 { 642 line *a1; 643 register char *fp, *lp; 644 register int nib; 645 646 a1 = addr1; 647 clrstats(); 648 cntln = addr2 - a1 + 1; 649 if (cntln == 0) 650 return; 651 nib = BUFSIZ; 652 fp = genbuf; 653 do { 654 getline(*a1++); 655 lp = linebuf; 656 for (;;) { 657 if (--nib < 0) { 658 nib = fp - genbuf; 659 if (write(io, genbuf, nib) != nib) 660 wrerror(); 661 cntch += nib; 662 nib = 511; 663 fp = genbuf; 664 } 665 if ((*fp++ = *lp++) == 0) { 666 fp[-1] = '\n'; 667 break; 668 } 669 } 670 } while (a1 <= addr2); 671 nib = fp - genbuf; 672 if (write(io, genbuf, nib) != nib) 673 wrerror(); 674 cntch += nib; 675 } 676 677 wrerror() 678 { 679 680 syserror(); 681 } 682 683 clrstats() 684 { 685 686 ninbuf = 0; 687 cntch = 0; 688 cntln = 0; 689 cntnull = 0; 690 cntodd = 0; 691 } 692 693 #define READ 0 694 #define WRITE 1 695 696 getline(tl) 697 line tl; 698 { 699 register char *bp, *lp; 700 register int nl; 701 702 lp = linebuf; 703 bp = getblock(tl, READ); 704 nl = nleft; 705 tl &= ~OFFMSK; 706 while (*lp++ = *bp++) 707 if (--nl == 0) { 708 bp = getblock(tl += INCRMT, READ); 709 nl = nleft; 710 } 711 } 712 713 int read(); 714 int write(); 715 716 char * 717 getblock(atl, iof) 718 line atl; 719 int iof; 720 { 721 register int bno, off; 722 723 bno = (atl >> OFFBTS) & BLKMSK; 724 off = (atl << SHFT) & LBTMSK; 725 if (bno >= NMBLKS) 726 error(" Tmp file too large"); 727 nleft = BUFSIZ - off; 728 if (bno == iblock) { 729 ichanged |= iof; 730 return (ibuff + off); 731 } 732 if (bno == oblock) 733 return (obuff + off); 734 if (iof == READ) { 735 if (ichanged) 736 blkio(iblock, ibuff, write); 737 ichanged = 0; 738 iblock = bno; 739 blkio(bno, ibuff, read); 740 return (ibuff + off); 741 } 742 if (oblock >= 0) 743 blkio(oblock, obuff, write); 744 oblock = bno; 745 return (obuff + off); 746 } 747 748 blkio(b, buf, iofcn) 749 short b; 750 char *buf; 751 int (*iofcn)(); 752 { 753 754 lseek(tfile, (long) (unsigned) b * BUFSIZ, 0); 755 if ((*iofcn)(tfile, buf, BUFSIZ) != BUFSIZ) 756 syserror(); 757 } 758 759 syserror() 760 { 761 char *strerror(); 762 763 dirtcnt = 0; 764 write(2, " ", 1); 765 error(strerror(errno)); 766 exit(1); 767 } 768 769 /* 770 * Must avoid stdio because expreserve uses sbrk to do memory 771 * allocation and stdio uses malloc. 772 */ 773 void 774 #if __STDC__ 775 fpr(const char *fmt, ...) 776 #else 777 fpr(fmt, va_alist) 778 char *fmt; 779 va_dcl 780 #endif 781 { 782 va_list ap; 783 char buf[BUFSIZ]; 784 785 #if __STDC__ 786 va_start(ap, fmt); 787 #else 788 va_start(ap); 789 #endif 790 (void)vsnprintf(buf, sizeof(buf), fmt, ap); 791 va_end(ap); 792 write(2, buf, strlen(buf)); 793 } 794