1 /* Copyright (c) 1980 Regents of the University of California */ 2 static char *sccsid = "@(#)ex3.7recover.c 6.1 10/19/80"; 3 #include "ex.h" 4 #include "ex_temp.h" 5 #include "ex_tty.h" 6 #include "local/uparm.h" 7 8 #undef BUFSIZ 9 #undef EOF 10 #undef NULL 11 12 #include <stdio.h> 13 #include <sys/dir.h> 14 15 /* 16 * Ex recovery program 17 * exrecover dir name 18 * exrecover -r 19 * 20 * This program searches through the specified directory and then 21 * the directory usrpath(preserve) looking for an instance of the specified 22 * file from a crashed editor or a crashed system. 23 * If this file is found, it is unscrambled and written to 24 * the standard output. 25 * 26 * If this program terminates without a "broken pipe" diagnostic 27 * (i.e. the editor doesn't die right away) then the buffer we are 28 * writing from is removed when we finish. This is potentially a mistake 29 * as there is not enough handshaking to guarantee that the file has actually 30 * been recovered, but should suffice for most cases. 31 */ 32 33 /* 34 * For lint's sake... 35 */ 36 #ifndef lint 37 #define ignorl(a) a 38 #endif 39 40 /* 41 * This directory definition also appears (obviously) in expreserve.c. 42 * Change both if you change either. 43 */ 44 char mydir[] = usrpath(preserve); 45 46 /* 47 * Limit on the number of printed entries 48 * when an, e.g. ``ex -r'' command is given. 49 */ 50 #define NENTRY 50 51 52 char *ctime(); 53 char nb[BUFSIZ]; 54 int vercnt; /* Count number of versions of file found */ 55 56 main(argc, argv) 57 int argc; 58 char *argv[]; 59 { 60 register char *cp; 61 register int b, i; 62 63 /* 64 * Initialize as though the editor had just started. 65 */ 66 fendcore = (line *) sbrk(0); 67 dot = zero = dol = fendcore; 68 one = zero + 1; 69 endcore = fendcore - 2; 70 iblock = oblock = -1; 71 72 /* 73 * If given only a -r argument, then list the saved files. 74 */ 75 if (argc == 2 && eq(argv[1], "-r")) { 76 listfiles(mydir); 77 exit(0); 78 } 79 if (argc != 3) 80 error(" Wrong number of arguments to exrecover", 0); 81 82 CP(file, argv[2]); 83 84 /* 85 * Search for this file. 86 */ 87 findtmp(argv[1]); 88 89 /* 90 * Got (one of the versions of) it, write it back to the editor. 91 */ 92 cp = ctime(&H.Time); 93 cp[19] = 0; 94 fprintf(stderr, " [Dated: %s", cp); 95 fprintf(stderr, vercnt > 1 ? ", newest of %d saved]" : "]", vercnt); 96 H.Flines++; 97 98 /* 99 * Allocate space for the line pointers from the temp file. 100 */ 101 if ((int) sbrk((int) (H.Flines * sizeof (line))) == -1) 102 /* 103 * Good grief. 104 */ 105 error(" Not enough core for lines", 0); 106 #ifdef DEBUG 107 fprintf(stderr, "%d lines\n", H.Flines); 108 #endif 109 110 /* 111 * Now go get the blocks of seek pointers which are scattered 112 * throughout the temp file, reconstructing the incore 113 * line pointers at point of crash. 114 */ 115 b = 0; 116 while (H.Flines > 0) { 117 ignorl(lseek(tfile, (long) blocks[b] * BUFSIZ, 0)); 118 i = H.Flines < BUFSIZ / sizeof (line) ? 119 H.Flines * sizeof (line) : BUFSIZ; 120 if (read(tfile, (char *) dot, i) != i) { 121 perror(nb); 122 exit(1); 123 } 124 dot += i / sizeof (line); 125 H.Flines -= i / sizeof (line); 126 b++; 127 } 128 dot--; dol = dot; 129 130 /* 131 * Sigh... due to sandbagging some lines may really not be there. 132 * Find and discard such. This shouldn't happen much. 133 */ 134 scrapbad(); 135 136 /* 137 * Now if there were any lines in the recovered file 138 * write them to the standard output. 139 */ 140 if (dol > zero) { 141 addr1 = one; addr2 = dol; io = 1; 142 putfile(); 143 } 144 145 /* 146 * Trash the saved buffer. 147 * Hopefully the system won't crash before the editor 148 * syncs the new recovered buffer; i.e. for an instant here 149 * you may lose if the system crashes because this file 150 * is gone, but the editor hasn't completed reading the recovered 151 * file from the pipe from us to it. 152 * 153 * This doesn't work if we are coming from an non-absolute path 154 * name since we may have chdir'ed but what the hay, noone really 155 * ever edits with temporaries in "." anyways. 156 */ 157 if (nb[0] == '/') 158 ignore(unlink(nb)); 159 160 /* 161 * Adieu. 162 */ 163 exit(0); 164 } 165 166 /* 167 * Print an error message (notably not in error 168 * message file). If terminal is in RAW mode, then 169 * we should be writing output for "vi", so don't print 170 * a newline which would screw up the screen. 171 */ 172 /*VARARGS2*/ 173 error(str, inf) 174 char *str; 175 int inf; 176 { 177 178 fprintf(stderr, str, inf); 179 #ifndef USG3TTY 180 gtty(2, &tty); 181 if ((tty.sg_flags & RAW) == 0) 182 #else 183 ioctl(2, TCGETA, &tty); 184 if (tty.c_lflag & ICANON) 185 #endif 186 fprintf(stderr, "\n"); 187 exit(1); 188 } 189 190 /* 191 * Here we save the information about files, when 192 * you ask us what files we have saved for you. 193 * We buffer file name, number of lines, and the time 194 * at which the file was saved. 195 */ 196 struct svfile { 197 char sf_name[FNSIZE + 1]; 198 int sf_lines; 199 char sf_entry[DIRSIZ + 1]; 200 time_t sf_time; 201 }; 202 203 listfiles(dirname) 204 char *dirname; 205 { 206 register FILE *dir; 207 struct direct dirent; 208 int ecount, qucmp(); 209 register int f; 210 char *cp; 211 struct svfile *fp, svbuf[NENTRY]; 212 213 /* 214 * Open usrpath(preserve), and go there to make things quick. 215 */ 216 dir = fopen(dirname, "r"); 217 if (dir == NULL) { 218 perror(dirname); 219 return; 220 } 221 if (chdir(dirname) < 0) { 222 perror(dirname); 223 return; 224 } 225 226 /* 227 * Look at the candidate files in usrpath(preserve). 228 */ 229 fp = &svbuf[0]; 230 ecount = 0; 231 while (fread((char *) &dirent, sizeof dirent, 1, dir) == 1) { 232 if (dirent.d_ino == 0) 233 continue; 234 if (dirent.d_name[0] != 'E') 235 continue; 236 #ifdef DEBUG 237 fprintf(stderr, "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 fprintf(stderr, "open failed\n"); 249 #endif 250 continue; 251 } 252 if (read(f, (char *) &H, sizeof H) != sizeof H) { 253 #ifdef DEBUG 254 fprintf(stderr, "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 fprintf(stderr, "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 fprintf(stderr, "entered file %s\n", dirent.d_name); 274 #endif 275 } 276 ignore(fclose(dir)); 277 278 /* 279 * If any files were saved, then sort them and print 280 * them out. 281 */ 282 if (ecount == 0) { 283 fprintf(stderr, "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 fprintf(stderr, "On %s at ", cp); 291 cp[16] = 0; 292 fprintf(stderr, &cp[11]); 293 fprintf(stderr, " 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 usrpath(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 usrpath(preserve) and, if we can get there, /tmp 382 * (actually the users "directory" option). 383 */ 384 searchdir(dir); 385 if (chdir(mydir) == 0) 386 searchdir(mydir); 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 usrpath(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 FILE *dir; 428 char dbuf[BUFSIZ]; 429 430 dir = fopen(dirname, "r"); 431 if (dir == NULL) 432 return; 433 setbuf(dir, dbuf); 434 while (fread((char *) &dirent, sizeof dirent, 1, dir) == 1) { 435 if (dirent.d_ino == 0) 436 continue; 437 if (dirent.d_name[0] != 'E' || dirent.d_name[DIRSIZ - 1] != 0) 438 continue; 439 /* 440 * Got a file in the directory starting with E... 441 * Save a consed up name for the file to unlink 442 * later, and check that this is really a file 443 * we are looking for. 444 */ 445 ignore(strcat(strcat(strcpy(nb, dirname), "/"), dirent.d_name)); 446 if (yeah(nb)) { 447 /* 448 * Well, it is the file we are looking for. 449 * Is it more recent than any version we found before? 450 */ 451 if (H.Time > besttime) { 452 /* 453 * A winner. 454 */ 455 ignore(close(bestfd)); 456 bestfd = dup(tfile); 457 besttime = H.Time; 458 CP(bestnb, nb); 459 } 460 /* 461 * Count versions so user can be told there are 462 * ``yet more pages to be turned''. 463 */ 464 vercnt++; 465 } 466 ignore(close(tfile)); 467 } 468 ignore(fclose(dir)); 469 } 470 471 /* 472 * Given a candidate file to be recovered, see 473 * if its really an editor temporary and of this 474 * user and the file specified. 475 */ 476 yeah(name) 477 char *name; 478 { 479 480 tfile = open(name, 2); 481 if (tfile < 0) 482 return (0); 483 if (read(tfile, (char *) &H, sizeof H) != sizeof H) { 484 nope: 485 ignore(close(tfile)); 486 return (0); 487 } 488 if (!eq(savedfile, file)) 489 goto nope; 490 if (getuid() != H.Uid) 491 goto nope; 492 /* 493 * This is old and stupid code, which 494 * puts a word LOST in the header block, so that lost lines 495 * can be made to point at it. 496 */ 497 ignorl(lseek(tfile, (long)(BUFSIZ*HBLKS-8), 0)); 498 ignore(write(tfile, "LOST", 5)); 499 return (1); 500 } 501 502 preserve() 503 { 504 505 } 506 507 /* 508 * Find the true end of the scratch file, and ``LOSE'' 509 * lines which point into thin air. This lossage occurs 510 * due to the sandbagging of i/o which can cause blocks to 511 * be written in a non-obvious order, different from the order 512 * in which the editor tried to write them. 513 * 514 * Lines which are lost are replaced with the text LOST so 515 * they are easy to find. We work hard at pretty formatting here 516 * as lines tend to be lost in blocks. 517 * 518 * This only seems to happen on very heavily loaded systems, and 519 * not very often. 520 */ 521 scrapbad() 522 { 523 register line *ip; 524 struct stat stbuf; 525 off_t size, maxt; 526 int bno, cnt, bad, was; 527 char bk[BUFSIZ]; 528 529 ignore(fstat(tfile, &stbuf)); 530 size = stbuf.st_size; 531 maxt = (size >> SHFT) | (BNDRY-1); 532 bno = (maxt >> OFFBTS) & BLKMSK; 533 #ifdef DEBUG 534 fprintf(stderr, "size %ld, maxt %o, bno %d\n", size, maxt, bno); 535 #endif 536 537 /* 538 * Look for a null separating two lines in the temp file; 539 * if last line was split across blocks, then it is lost 540 * if the last block is. 541 */ 542 while (bno > 0) { 543 ignorl(lseek(tfile, (long) BUFSIZ * bno, 0)); 544 cnt = read(tfile, (char *) bk, BUFSIZ); 545 while (cnt > 0) 546 if (bk[--cnt] == 0) 547 goto null; 548 bno--; 549 } 550 null: 551 552 /* 553 * Magically calculate the largest valid pointer in the temp file, 554 * consing it up from the block number and the count. 555 */ 556 maxt = ((bno << OFFBTS) | (cnt >> SHFT)) & ~1; 557 #ifdef DEBUG 558 fprintf(stderr, "bno %d, cnt %d, maxt %o\n", bno, cnt, maxt); 559 #endif 560 561 /* 562 * Now cycle through the line pointers, 563 * trashing the Lusers. 564 */ 565 was = bad = 0; 566 for (ip = one; ip <= dol; ip++) 567 if (*ip > maxt) { 568 #ifdef DEBUG 569 fprintf(stderr, "%d bad, %o > %o\n", ip - zero, *ip, maxt); 570 #endif 571 if (was == 0) 572 was = ip - zero; 573 *ip = ((HBLKS*BUFSIZ)-8) >> SHFT; 574 } else if (was) { 575 if (bad == 0) 576 fprintf(stderr, " [Lost line(s):"); 577 fprintf(stderr, " %d", was); 578 if ((ip - 1) - zero > was) 579 fprintf(stderr, "-%d", (ip - 1) - zero); 580 bad++; 581 was = 0; 582 } 583 if (was != 0) { 584 if (bad == 0) 585 fprintf(stderr, " [Lost line(s):"); 586 fprintf(stderr, " %d", was); 587 if (dol - zero != was) 588 fprintf(stderr, "-%d", dol - zero); 589 bad++; 590 } 591 if (bad) 592 fprintf(stderr, "]"); 593 } 594 595 /* 596 * Aw shucks, if we only had a (void) cast. 597 */ 598 #ifdef lint 599 Ignorl(a) 600 long a; 601 { 602 603 a = a; 604 } 605 606 Ignore(a) 607 char *a; 608 { 609 610 a = a; 611 } 612 613 Ignorf(a) 614 int (*a)(); 615 { 616 617 a = a; 618 } 619 620 ignorl(a) 621 long a; 622 { 623 624 a = a; 625 } 626 #endif 627 628 int cntch, cntln, cntodd, cntnull; 629 /* 630 * Following routines stolen mercilessly from ex. 631 */ 632 putfile() 633 { 634 line *a1; 635 register char *fp, *lp; 636 register int nib; 637 638 a1 = addr1; 639 clrstats(); 640 cntln = addr2 - a1 + 1; 641 if (cntln == 0) 642 return; 643 nib = BUFSIZ; 644 fp = genbuf; 645 do { 646 getline(*a1++); 647 lp = linebuf; 648 for (;;) { 649 if (--nib < 0) { 650 nib = fp - genbuf; 651 if (write(io, genbuf, nib) != nib) 652 wrerror(); 653 cntch += nib; 654 nib = 511; 655 fp = genbuf; 656 } 657 if ((*fp++ = *lp++) == 0) { 658 fp[-1] = '\n'; 659 break; 660 } 661 } 662 } while (a1 <= addr2); 663 nib = fp - genbuf; 664 if (write(io, genbuf, nib) != nib) 665 wrerror(); 666 cntch += nib; 667 } 668 669 wrerror() 670 { 671 672 syserror(); 673 } 674 675 clrstats() 676 { 677 678 ninbuf = 0; 679 cntch = 0; 680 cntln = 0; 681 cntnull = 0; 682 cntodd = 0; 683 } 684 685 #define READ 0 686 #define WRITE 1 687 688 getline(tl) 689 line tl; 690 { 691 register char *bp, *lp; 692 register int nl; 693 694 lp = linebuf; 695 bp = getblock(tl, READ); 696 nl = nleft; 697 tl &= ~OFFMSK; 698 while (*lp++ = *bp++) 699 if (--nl == 0) { 700 bp = getblock(tl += INCRMT, READ); 701 nl = nleft; 702 } 703 } 704 705 int read(); 706 int write(); 707 708 char * 709 getblock(atl, iof) 710 line atl; 711 int iof; 712 { 713 register int bno, off; 714 715 bno = (atl >> OFFBTS) & BLKMSK; 716 off = (atl << SHFT) & LBTMSK; 717 if (bno >= NMBLKS) 718 error(" Tmp file too large"); 719 nleft = BUFSIZ - off; 720 if (bno == iblock) { 721 ichanged |= iof; 722 return (ibuff + off); 723 } 724 if (bno == oblock) 725 return (obuff + off); 726 if (iof == READ) { 727 if (ichanged) 728 blkio(iblock, ibuff, write); 729 ichanged = 0; 730 iblock = bno; 731 blkio(bno, ibuff, read); 732 return (ibuff + off); 733 } 734 if (oblock >= 0) 735 blkio(oblock, obuff, write); 736 oblock = bno; 737 return (obuff + off); 738 } 739 740 blkio(b, buf, iofcn) 741 short b; 742 char *buf; 743 int (*iofcn)(); 744 { 745 746 lseek(tfile, (long) (unsigned) b * BUFSIZ, 0); 747 if ((*iofcn)(tfile, buf, BUFSIZ) != BUFSIZ) 748 syserror(); 749 } 750 751 syserror() 752 { 753 extern int sys_nerr; 754 extern char *sys_errlist[]; 755 756 dirtcnt = 0; 757 write(2, " ", 1); 758 if (errno >= 0 && errno <= sys_nerr) 759 error(sys_errlist[errno]); 760 else 761 error("System error %d", errno); 762 exit(1); 763 } 764