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