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