|
|
1.1 ! root 1: /* Copyright (c) 1979 Regents of the University of California */ ! 2: #include "ex.h" ! 3: #include "ex_temp.h" ! 4: #include "ex_tty.h" ! 5: #include "local/uparm.h" ! 6: ! 7: #undef BUFSIZ ! 8: #undef EOF ! 9: #undef NULL ! 10: ! 11: #include <stdio.h> ! 12: #include <sys/dir.h> ! 13: ! 14: /* ! 15: * Ex recovery program ! 16: * exrecover dir name ! 17: * exrecover -r ! 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(); ! 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: gtty(2, &tty); ! 179: if ((tty.sg_flags & RAW) == 0) ! 180: fprintf(stderr, "\n"); ! 181: exit(1); ! 182: } ! 183: ! 184: /* ! 185: * Here we save the information about files, when ! 186: * you ask us what files we have saved for you. ! 187: * We buffer file name, number of lines, and the time ! 188: * at which the file was saved. ! 189: */ ! 190: struct svfile { ! 191: char sf_name[FNSIZE + 1]; ! 192: int sf_lines; ! 193: char sf_entry[DIRSIZ + 1]; ! 194: time_t sf_time; ! 195: }; ! 196: ! 197: listfiles(dirname) ! 198: char *dirname; ! 199: { ! 200: register FILE *dir; ! 201: struct direct dirent; ! 202: int ecount, qucmp(); ! 203: register int f; ! 204: char *cp; ! 205: struct svfile *fp, svbuf[NENTRY]; ! 206: ! 207: /* ! 208: * Open usrpath(preserve), and go there to make things quick. ! 209: */ ! 210: dir = fopen(dirname, "r"); ! 211: if (dir == NULL) { ! 212: perror(dirname); ! 213: return; ! 214: } ! 215: if (chdir(dirname) < 0) { ! 216: perror(dirname); ! 217: return; ! 218: } ! 219: ! 220: /* ! 221: * Look at the candidate files in usrpath(preserve). ! 222: */ ! 223: fp = &svbuf[0]; ! 224: ecount = 0; ! 225: while (fread((char *) &dirent, sizeof dirent, 1, dir) == 1) { ! 226: if (dirent.d_ino == 0) ! 227: continue; ! 228: if (dirent.d_name[0] != 'E') ! 229: continue; ! 230: #ifdef DEBUG ! 231: fprintf(stderr, "considering %s\n", dirent.d_name); ! 232: #endif ! 233: /* ! 234: * Name begins with E; open it and ! 235: * make sure the uid in the header is our uid. ! 236: * If not, then don't bother with this file, it can't ! 237: * be ours. ! 238: */ ! 239: f = open(dirent.d_name, 0); ! 240: if (f < 0) { ! 241: #ifdef DEBUG ! 242: fprintf(stderr, "open failed\n"); ! 243: #endif ! 244: continue; ! 245: } ! 246: if (read(f, (char *) &H, sizeof H) != sizeof H) { ! 247: #ifdef DEBUG ! 248: fprintf(stderr, "culdnt read hedr\n"); ! 249: #endif ! 250: ignore(close(f)); ! 251: continue; ! 252: } ! 253: ignore(close(f)); ! 254: if (getuid() != H.Uid) { ! 255: #ifdef DEBUG ! 256: fprintf(stderr, "uid wrong\n"); ! 257: #endif ! 258: continue; ! 259: } ! 260: ! 261: /* ! 262: * Saved the day! ! 263: */ ! 264: enter(fp++, dirent.d_name, ecount); ! 265: ecount++; ! 266: #ifdef DEBUG ! 267: fprintf(stderr, "entered file %s\n", dirent.d_name); ! 268: #endif ! 269: } ! 270: ignore(fclose(dir)); ! 271: ! 272: /* ! 273: * If any files were saved, then sort them and print ! 274: * them out. ! 275: */ ! 276: if (ecount == 0) { ! 277: fprintf(stderr, "No files saved.\n"); ! 278: return; ! 279: } ! 280: qsort(&svbuf[0], ecount, sizeof svbuf[0], qucmp); ! 281: for (fp = &svbuf[0]; fp < &svbuf[ecount]; fp++) { ! 282: cp = ctime(&fp->sf_time); ! 283: cp[10] = 0; ! 284: fprintf(stderr, "On %s at ", cp); ! 285: cp[16] = 0; ! 286: fprintf(stderr, &cp[11]); ! 287: fprintf(stderr, " saved %d lines of file \"%s\"\n", ! 288: fp->sf_lines, fp->sf_name); ! 289: } ! 290: } ! 291: ! 292: /* ! 293: * Enter a new file into the saved file information. ! 294: */ ! 295: enter(fp, fname, count) ! 296: struct svfile *fp; ! 297: char *fname; ! 298: { ! 299: register char *cp, *cp2; ! 300: register struct svfile *f, *fl; ! 301: time_t curtime, itol(); ! 302: ! 303: f = 0; ! 304: if (count >= NENTRY) { ! 305: /* ! 306: * My god, a huge number of saved files. ! 307: * Would you work on a system that crashed this ! 308: * often? Hope not. So lets trash the oldest ! 309: * as the most useless. ! 310: * ! 311: * (I wonder if this code has ever run?) ! 312: */ ! 313: fl = fp - count + NENTRY - 1; ! 314: curtime = fl->sf_time; ! 315: for (f = fl; --f > fp-count; ) ! 316: if (f->sf_time < curtime) ! 317: curtime = f->sf_time; ! 318: for (f = fl; --f > fp-count; ) ! 319: if (f->sf_time == curtime) ! 320: break; ! 321: fp = f; ! 322: } ! 323: ! 324: /* ! 325: * Gotcha. ! 326: */ ! 327: fp->sf_time = H.Time; ! 328: fp->sf_lines = H.Flines; ! 329: for (cp2 = fp->sf_name, cp = savedfile; *cp;) ! 330: *cp2++ = *cp++; ! 331: for (cp2 = fp->sf_entry, cp = fname; *cp && cp-fname < 14;) ! 332: *cp2++ = *cp++; ! 333: *cp2++ = 0; ! 334: } ! 335: ! 336: /* ! 337: * Do the qsort compare to sort the entries first by file name, ! 338: * then by modify time. ! 339: */ ! 340: qucmp(p1, p2) ! 341: struct svfile *p1, *p2; ! 342: { ! 343: register int t; ! 344: ! 345: if (t = strcmp(p1->sf_name, p2->sf_name)) ! 346: return(t); ! 347: if (p1->sf_time > p2->sf_time) ! 348: return(-1); ! 349: return(p1->sf_time < p2->sf_time); ! 350: } ! 351: ! 352: /* ! 353: * Scratch for search. ! 354: */ ! 355: char bestnb[BUFSIZ]; /* Name of the best one */ ! 356: long besttime; /* Time at which the best file was saved */ ! 357: int bestfd; /* Keep best file open so it dont vanish */ ! 358: ! 359: /* ! 360: * Look for a file, both in the users directory option value ! 361: * (i.e. usually /tmp) and in usrpath(preserve). ! 362: * Want to find the newest so we search on and on. ! 363: */ ! 364: findtmp(dir) ! 365: char *dir; ! 366: { ! 367: ! 368: /* ! 369: * No name or file so far. ! 370: */ ! 371: bestnb[0] = 0; ! 372: bestfd = -1; ! 373: ! 374: /* ! 375: * Search usrpath(preserve) and, if we can get there, /tmp ! 376: * (actually the users "directory" option). ! 377: */ ! 378: searchdir(dir); ! 379: if (chdir(mydir) == 0) ! 380: searchdir(mydir); ! 381: if (bestfd != -1) { ! 382: /* ! 383: * Gotcha. ! 384: * Put the file (which is already open) in the file ! 385: * used by the temp file routines, and save its ! 386: * name for later unlinking. ! 387: */ ! 388: tfile = bestfd; ! 389: CP(nb, bestnb); ! 390: ignorl(lseek(tfile, 0l, 0)); ! 391: ! 392: /* ! 393: * Gotta be able to read the header or fall through ! 394: * to lossage. ! 395: */ ! 396: if (read(tfile, (char *) &H, sizeof H) == sizeof H) ! 397: return; ! 398: } ! 399: ! 400: /* ! 401: * Extreme lossage... ! 402: */ ! 403: error(" File not found", 0); ! 404: } ! 405: ! 406: /* ! 407: * Search for the file in directory dirname. ! 408: * ! 409: * Don't chdir here, because the users directory ! 410: * may be ".", and we would move away before we searched it. ! 411: * Note that we actually chdir elsewhere (because it is too slow ! 412: * to look around in usrpath(preserve) without chdir'ing there) so we ! 413: * can't win, because we don't know the name of '.' and if the path ! 414: * name of the file we want to unlink is relative, rather than absolute ! 415: * we won't be able to find it again. ! 416: */ ! 417: searchdir(dirname) ! 418: char *dirname; ! 419: { ! 420: struct direct dirent; ! 421: register FILE *dir; ! 422: char dbuf[BUFSIZ]; ! 423: ! 424: dir = fopen(dirname, "r"); ! 425: if (dir == NULL) ! 426: return; ! 427: setbuf(dir, dbuf); ! 428: while (fread((char *) &dirent, sizeof dirent, 1, dir) == 1) { ! 429: if (dirent.d_ino == 0) ! 430: continue; ! 431: if (dirent.d_name[0] != 'E' || dirent.d_name[DIRSIZ - 1] != 0) ! 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(fclose(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: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.