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