|
|
1.1 ! root 1: /* $Header: /kernel/kersrc/coh.286/RCS/bio.c,v 1.1 92/07/17 15:17:57 bin Exp Locker: bin $ */ ! 2: /* (lgl- ! 3: * The information contained herein is a trade secret of Mark Williams ! 4: * Company, and is confidential information. It is provided under a ! 5: * license agreement, and may be copied or disclosed only under the ! 6: * terms of that agreement. Any reproduction or disclosure of this ! 7: * material without the express written authorization of Mark Williams ! 8: * Company or persuant to the license agreement is unlawful. ! 9: * ! 10: * COHERENT Version 2.3.37 ! 11: * Copyright (c) 1982, 1983, 1984. ! 12: * An unpublished work by Mark Williams Company, Chicago. ! 13: * All rights reserved. ! 14: -lgl) */ ! 15: /* ! 16: * Coherent. ! 17: * Buffered I/O. ! 18: * ! 19: * $Log: bio.c,v $ ! 20: * Revision 1.1 92/07/17 15:17:57 bin ! 21: * Initial revision ! 22: * ! 23: * Revision 1.1 91/11/08 12:46:39 hal ! 24: * Used in COH 3.2.0 ! 25: * ! 26: * Revision 1.1 88/03/24 16:13:29 src ! 27: * Initial revision ! 28: * ! 29: * 87/11/25 Allan Cornish /usr/src/sys/coh/bio.c ! 30: * vaddr_t bp->b_vaddr --> faddr_t bp->b_faddr. ! 31: * ! 32: * 87/11/05 Allan Cornish /usr/src/sys/coh/bio.c ! 33: * New seg struct now used to allow extended addressing. ! 34: * ! 35: * 87/01/05 Allan Cornish /usr/src/sys/coh/bio.c ! 36: * ioreq() now only wakes &stimer if the swap timer is active. ! 37: * ! 38: * 86/12/12 Allan Cornish /usr/src/sys/coh/bio.c ! 39: * Added 3rd arg to dpoll() to specify blocking poll if non-zero. ! 40: * ! 41: * 86/11/19 Allan Cornish /usr/src/sys/coh/bio.c ! 42: * Added dpoll() routine to perform device polls [System V.3 compatible]. ! 43: * ! 44: * 86/07/24 Allan Cornish /usr/src/sys/coh/bio.c ! 45: * Added check in devinit() for null dp->d_conp->c_load function pointer. ! 46: */ ! 47: #include <sys/coherent.h> ! 48: #include <sys/buf.h> ! 49: #include <sys/con.h> ! 50: #include <errno.h> ! 51: #include <sys/io.h> ! 52: #include <sys/proc.h> ! 53: #include <sys/sched.h> ! 54: #include <sys/seg.h> ! 55: #include <sys/stat.h> ! 56: ! 57: /* ! 58: * Initialise buffer headers. ! 59: */ ! 60: bufinit() ! 61: { ! 62: register BUF *bp; ! 63: faddr_t f; ! 64: paddr_t p; ! 65: ! 66: FP_SEL(f) = sds; ! 67: FP_OFF(f) = 0; ! 68: p = blockp; ! 69: FP_OFF(f) = blockp - vtop(f); ! 70: ! 71: bufl = kalloc(NBUF * sizeof(BUF)); ! 72: if (bufl == NULL) ! 73: panic("bufinit: no space for BUF's"); ! 74: ! 75: for (bp=&bufl[NBUF-1]; bp >= bufl; --bp) { ! 76: bp->b_dev = NODEV; ! 77: bp->b_faddr = f; ! 78: bp->b_paddr = p; ! 79: FP_OFF(f) += BSIZE; ! 80: p += BSIZE; ! 81: } ! 82: } ! 83: ! 84: /* ! 85: * Synchronise the buffer cache. ! 86: */ ! 87: bsync() ! 88: { ! 89: register BUF *bp; ! 90: ! 91: for (bp=&bufl[NBUF-1]; bp >= bufl; --bp) { ! 92: if ((bp->b_flag&BFMOD) == 0) ! 93: continue; ! 94: lock(bp->b_gate); ! 95: if ((bp->b_flag&BFMOD) != 0) ! 96: bwrite(bp, 1); ! 97: unlock(bp->b_gate); ! 98: } ! 99: } ! 100: ! 101: /* ! 102: * Synchronise all block for a particular device in the buffer cache ! 103: * and invalidate all references. ! 104: */ ! 105: bflush(dev) ! 106: register dev_t dev; ! 107: { ! 108: register BUF *bp; ! 109: ! 110: for (bp=&bufl[NBUF-1]; bp >= bufl; --bp) { ! 111: if (bp->b_dev != dev) ! 112: continue; ! 113: lock(bp->b_gate); ! 114: if (bp->b_dev == dev) { ! 115: if ((bp->b_flag&BFMOD) != 0) ! 116: bwrite(bp, 1); ! 117: bp->b_dev = NODEV; ! 118: } ! 119: unlock(bp->b_gate); ! 120: } ! 121: } ! 122: ! 123: /* ! 124: * Return a buffer containing the given block from the given device. ! 125: * If `f' is not set, the read is asynchronous and no buffer is returned. ! 126: */ ! 127: BUF * ! 128: bread(dev, bno, f) ! 129: dev_t dev; ! 130: daddr_t bno; ! 131: register int f; ! 132: { ! 133: register BUF *bp; ! 134: register int s; ! 135: ! 136: bp = bclaim(dev, bno); ! 137: if ((bp->b_flag&BFNTP) != 0) { ! 138: if (f != 0) ! 139: bp->b_flag &= ~BFASY; ! 140: else { ! 141: bp->b_flag |= BFASY; ! 142: bumap(bp); ! 143: } ! 144: bp->b_req = BREAD; ! 145: bp->b_count = BSIZE; ! 146: s = sphi(); ! 147: dblock(dev, bp); ! 148: if (f == 0) { ! 149: spl(s); ! 150: return (NULL); ! 151: } ! 152: while ((bp->b_flag&BFNTP) != 0) ! 153: sleep((char *)bp, CVBLKIO, IVBLKIO, SVBLKIO); ! 154: spl(s); ! 155: if ((bp->b_flag&BFERR) != 0) { ! 156: u.u_error = bp->b_err ? bp->b_err : EIO; ! 157: brelease(bp); ! 158: return (NULL); ! 159: } ! 160: if (bp->b_resid == BSIZE) { ! 161: brelease(bp); ! 162: return (NULL); ! 163: } ! 164: } ! 165: if (f == 0) { ! 166: brelease(bp); ! 167: return (NULL); ! 168: } ! 169: u.u_block++; ! 170: return (bp); ! 171: } ! 172: ! 173: /* ! 174: * If the requested buffer is in the buffer cache, return a pointer to ! 175: * it. If not, pick an empty buffer, set it up and return it. ! 176: */ ! 177: BUF * ! 178: bclaim(dev, bno) ! 179: dev_t dev; ! 180: daddr_t bno; ! 181: { ! 182: register BUF *bp; ! 183: register BUF *bp1; ! 184: register unsigned seqn; ! 185: register int s; ! 186: ! 187: again: ! 188: bp1 = NULL; ! 189: seqn = 0; ! 190: for (bp=&bufl[NBUF-1]; bp >= bufl; --bp) { ! 191: if (bp->b_bno == bno && bp->b_dev == dev) { ! 192: lock(bp->b_gate); ! 193: if (bp->b_bno != bno || bp->b_dev != dev) { ! 194: unlock(bp->b_gate); ! 195: goto again; ! 196: } ! 197: if ((bp->b_flag&BFERR) != 0) ! 198: bp->b_flag |= BFNTP; ! 199: bsmap(bp); ! 200: return (bp); ! 201: } ! 202: if (locked(bp->b_gate) == 0) { ! 203: if (bufseqn-bp->b_seqn >= seqn) { ! 204: bp1 = bp; ! 205: seqn = bufseqn - bp->b_seqn; ! 206: } ! 207: } ! 208: } ! 209: if (bp1 == NULL) { ! 210: s = sphi(); ! 211: for (bp=&bufl[NBUF-1]; bp >= bufl; --bp) { ! 212: if (locked(bp->b_gate) == 0) { ! 213: if (bufseqn-bp->b_seqn >= seqn) { ! 214: bp1 = bp; ! 215: seqn = bufseqn - bp->b_seqn; ! 216: } ! 217: } ! 218: } ! 219: if (bp1 == NULL) { ! 220: bufneed = 1; ! 221: sleep((char *)&bufneed, CVBLKIO, IVBLKIO, SVBLKIO); ! 222: spl(s); ! 223: goto again; ! 224: } ! 225: spl(s); ! 226: } ! 227: bp = bp1; ! 228: lock(bp->b_gate); ! 229: if ((bp->b_flag&BFMOD) != 0) { ! 230: bwrite(bp, 0); ! 231: goto again; ! 232: } ! 233: bp->b_flag = BFNTP; ! 234: bp->b_dev = dev; ! 235: bp->b_bno = bno; ! 236: bsmap(bp); ! 237: return (bp); ! 238: } ! 239: ! 240: /* ! 241: * Write the given buffer out. If `f' is set, the write is synchronous, ! 242: * otherwise asynchronous. This routine must be called with the buffer ! 243: * gate locked. ! 244: */ ! 245: bwrite(bp, f) ! 246: register BUF *bp; ! 247: { ! 248: register int s; ! 249: ! 250: if (f != 0) ! 251: bp->b_flag &= ~BFASY; ! 252: else { ! 253: bp->b_flag |= BFASY; ! 254: bumap(bp); ! 255: } ! 256: bp->b_flag |= BFNTP; ! 257: bp->b_req = BWRITE; ! 258: bp->b_count = BSIZE; ! 259: s = sphi(); ! 260: dblock(bp->b_dev, bp); ! 261: if (f == 0) { ! 262: spl(s); ! 263: return; ! 264: } ! 265: while ((bp->b_flag&BFNTP) != 0) ! 266: sleep((char *)bp, CVBLKIO, IVBLKIO, SVBLKIO); ! 267: spl(s); ! 268: } ! 269: ! 270: /* ! 271: * This is called by the driver when I/O has completed on a buffer. ! 272: */ ! 273: bdone(bp) ! 274: register BUF *bp; ! 275: { ! 276: if (bp->b_req == BWRITE) ! 277: bp->b_flag &= ~BFMOD; ! 278: if (bp->b_req == BREAD) { ! 279: if ((bp->b_flag&BFERR) != 0) ! 280: bp->b_dev = NODEV; ! 281: } ! 282: if ((bp->b_flag&BFASY) != 0) { ! 283: bp->b_flag &= ~BFASY; ! 284: brelease(bp); ! 285: } ! 286: bp->b_flag &= ~BFNTP; ! 287: wakeup((char *)bp); ! 288: } ! 289: ! 290: /* ! 291: * Release the given buffer. ! 292: */ ! 293: brelease(bp) ! 294: register BUF *bp; ! 295: { ! 296: if ((bp->b_flag&BFERR) == 0) ! 297: bp->b_seqn = bufseqn++; ! 298: else { ! 299: bp->b_flag &= ~BFERR; ! 300: bp->b_dev = NODEV; ! 301: } ! 302: bp->b_flag &= ~BFNTP; ! 303: bumap(bp); ! 304: unlock(bp->b_gate); ! 305: if (bufneed != 0) { ! 306: bufneed = 0; ! 307: wakeup((char *)&bufneed); ! 308: } ! 309: } ! 310: ! 311: /* ! 312: * Map the given buffer. ! 313: */ ! 314: bsmap(bp) ! 315: register BUF *bp; ! 316: { ! 317: bsave(bp->b_map); ! 318: bp->b_flag |= BFMAP; ! 319: bmapv(bconv(bp->b_paddr)); ! 320: } ! 321: ! 322: /* ! 323: * Unmap the given buffer. ! 324: */ ! 325: bumap(bp) ! 326: register BUF *bp; ! 327: { ! 328: if ((bp->b_flag&BFMAP) == 0) ! 329: return; ! 330: bp->b_flag &= ~BFMAP; ! 331: brest(bp->b_map); ! 332: } ! 333: ! 334: /* ! 335: * Read data from the I/O segment into kernel space. ! 336: */ ! 337: ioread(iop, v, n) ! 338: register IO *iop; ! 339: register char *v; ! 340: register unsigned n; ! 341: { ! 342: switch (iop->io_seg) { ! 343: case IOSYS: ! 344: iop->io_base += kkcopy(iop->io_base, v, n); ! 345: break; ! 346: case IOUSR: ! 347: iop->io_base += ukcopy(iop->io_base, v, n); ! 348: break; ! 349: case IOPHY: ! 350: iop->io_phys += pkcopy(iop->io_phys, v, n); ! 351: break; ! 352: } ! 353: iop->io_ioc -= n; ! 354: } ! 355: ! 356: /* ! 357: * Write data from kernel space to the I/O segment. ! 358: */ ! 359: iowrite(iop, v, n) ! 360: register IO *iop; ! 361: register char *v; ! 362: register unsigned n; ! 363: { ! 364: switch (iop->io_seg) { ! 365: case IOSYS: ! 366: iop->io_base += kkcopy(v, iop->io_base, n); ! 367: break; ! 368: case IOUSR: ! 369: iop->io_base += kucopy(v, iop->io_base, n); ! 370: break; ! 371: case IOPHY: ! 372: iop->io_phys += kpcopy(v, iop->io_phys, n); ! 373: break; ! 374: } ! 375: iop->io_ioc -= n; ! 376: } ! 377: ! 378: /* ! 379: * Get a character from the I/O segment. ! 380: */ ! 381: iogetc(iop) ! 382: register IO *iop; ! 383: { ! 384: register int c; ! 385: ! 386: if (iop->io_ioc == 0) ! 387: return (-1); ! 388: --iop->io_ioc; ! 389: if (iop->io_seg == IOSYS) ! 390: c = *iop->io_base++ & 0377; ! 391: else { ! 392: c = getubd(iop->io_base++); ! 393: if (u.u_error) ! 394: return (-1); ! 395: } ! 396: return (c); ! 397: } ! 398: ! 399: /* ! 400: * Put a character using the I/O segment. ! 401: */ ! 402: ioputc(c, iop) ! 403: register IO *iop; ! 404: { ! 405: if (iop->io_ioc == 0) ! 406: return (-1); ! 407: --iop->io_ioc; ! 408: if (iop->io_seg == IOSYS) ! 409: *iop->io_base++ = c; ! 410: else { ! 411: putubd(iop->io_base++, c); ! 412: if (u.u_error) ! 413: return (-1); ! 414: } ! 415: return (c); ! 416: } ! 417: ! 418: /* ! 419: * Given a buffer pointer, an I/O structure, a device, request type, and ! 420: * a flags word, check the I/O structure and perform the I/O request. ! 421: */ ! 422: ioreq(bp, iop, dev, req, f) ! 423: register BUF *bp; ! 424: register IO *iop; ! 425: dev_t dev; ! 426: { ! 427: register SEG *sp; ! 428: register int n; ! 429: register int s; ! 430: register CON *cp; ! 431: dold_t dold; ! 432: ! 433: if ((cp=drvmap(dev, &dold)) == NULL) ! 434: return; ! 435: lock(bp->b_gate); ! 436: n = cp->c_flag; /* n should do something with that flag */ ! 437: drest(dold); ! 438: sp = NULL; ! 439: if (iop != NULL) { ! 440: if ((f&BFBLK) != 0) { ! 441: if (blocko(iop->io_seek) != 0) { ! 442: u.u_error = EIO; ! 443: goto out; ! 444: } ! 445: } ! 446: if ((f&BFIOC) != 0) { ! 447: if ((sp=iomapvp(iop, bp)) == NULL) { ! 448: u.u_error = EIO; ! 449: goto out; ! 450: } ! 451: } ! 452: } ! 453: bp->b_flag = f|BFNTP; ! 454: bp->b_req = req; ! 455: bp->b_dev = dev; ! 456: if (iop != NULL) { ! 457: bp->b_bno = blockn(iop->io_seek); ! 458: bp->b_count = iop->io_ioc; ! 459: } ! 460: if (sp != NULL) { ! 461: bp->b_faddr = ptov( bp->b_paddr, (fsize_t) bp->b_count ); ! 462: sp->s_lrefc++; ! 463: } ! 464: s = sphi(); ! 465: dblock(dev, bp); ! 466: while ((bp->b_flag&BFNTP) != 0) ! 467: sleep((char *)bp, CVBLKIO, IVBLKIO, SVBLKIO); ! 468: spl(s); ! 469: if (sp != NULL) { ! 470: vrelse( bp->b_faddr ); ! 471: sp->s_lrefc--; ! 472: } ! 473: if (stimer.t_last != 0) ! 474: wakeup((char *)&stimer); ! 475: if ((bp->b_flag&BFERR) != 0) { ! 476: u.u_error = bp->b_err ? bp->b_err : EIO; ! 477: goto out; ! 478: } ! 479: if (iop != NULL) { ! 480: n = iop->io_ioc - bp->b_resid; ! 481: iop->io_seek += n; ! 482: iop->io_ioc -= n; ! 483: } ! 484: out: ! 485: unlock(bp->b_gate); ! 486: } ! 487: ! 488: /* ! 489: * Given an I/O structure and a buffer header, see if the addresses ! 490: * in the I/O structure are valid and set up the buffer header. ! 491: */ ! 492: SEG * ! 493: iomapvp(iop, bp) ! 494: register IO *iop; ! 495: register BUF *bp; ! 496: { ! 497: register SR *srp; ! 498: register SEG *sp; ! 499: register vaddr_t b; ! 500: ! 501: if (iop->io_seg != IOUSR) ! 502: panic("Raw I/O from non user"); ! 503: for (srp=u.u_segl; srp<&u.u_segl[NUSEG]; srp++) { ! 504: if ((sp=srp->sr_segp) == NULL) ! 505: continue; ! 506: if ((srp->sr_flag&SRFDATA) == 0) ! 507: continue; ! 508: /* Yet another bug in the 8000 C compiler ! 509: if ((long)(b=iop->io_base) < (long)srp->sr_base) ! 510: */ ! 511: if ((b=iop->io_base) < srp->sr_base) ! 512: continue; ! 513: if ((long)b+iop->io_ioc > (long)srp->sr_base + sp->s_size) ! 514: continue; ! 515: bp->b_paddr = sp->s_paddr + (vaddr_t) (b - srp->sr_base); ! 516: return (sp); ! 517: } ! 518: return (NULL); ! 519: } ! 520: ! 521: /* ! 522: * Initialise devices. ! 523: */ ! 524: devinit() ! 525: { ! 526: register DRV *dp; ! 527: register int mind; ! 528: ! 529: for ( dp = drvl, mind = 0; mind < drvn; mind++, dp++ ) { ! 530: if ((dp->d_conp != NULL) && (dp->d_conp->c_load != NULL)) { ! 531: (*dp->d_conp->c_load)(); ! 532: } ! 533: } ! 534: } ! 535: ! 536: /* ! 537: * Open a device. ! 538: */ ! 539: dopen(dev, m, f) ! 540: register dev_t dev; ! 541: { ! 542: register CON *cp; ! 543: dold_t dold; ! 544: ! 545: if ((cp=drvmap(dev, &dold)) == NULL) ! 546: return; ! 547: if ((cp->c_flag&f) == 0) { ! 548: u.u_error = ENXIO; ! 549: return; ! 550: } ! 551: (*cp->c_open)(dev, m); ! 552: drest(dold); ! 553: } ! 554: ! 555: /* ! 556: * Close a device. ! 557: */ ! 558: dclose(dev) ! 559: register dev_t dev; ! 560: { ! 561: register CON *cp; ! 562: dold_t dold; ! 563: ! 564: if ((cp=drvmap(dev, &dold)) == NULL) ! 565: return; ! 566: (*cp->c_close)(dev); ! 567: drest(dold); ! 568: } ! 569: ! 570: /* ! 571: * Call the block entry point of a device. ! 572: */ ! 573: dblock(dev, bp) ! 574: dev_t dev; ! 575: BUF *bp; ! 576: { ! 577: register CON *cp; ! 578: dold_t dold; ! 579: ! 580: if ((cp=drvmap(dev, &dold)) == NULL) ! 581: return; ! 582: (*cp->c_block)(bp); ! 583: drest(dold); ! 584: } ! 585: ! 586: /* ! 587: * Read from a device. ! 588: */ ! 589: dread(dev, iop) ! 590: register dev_t dev; ! 591: register IO *iop; ! 592: { ! 593: register CON *cp; ! 594: dold_t dold; ! 595: ! 596: if ((cp=drvmap(dev, &dold)) == NULL) ! 597: return; ! 598: (*cp->c_read)(dev, iop); ! 599: drest(dold); ! 600: } ! 601: ! 602: /* ! 603: * Write to a device. ! 604: */ ! 605: dwrite(dev, iop) ! 606: register dev_t dev; ! 607: register IO *iop; ! 608: { ! 609: register CON *cp; ! 610: dold_t dold; ! 611: ! 612: if ((cp=drvmap(dev, &dold)) == NULL) ! 613: return; ! 614: (*cp->c_write)(dev, iop); ! 615: drest(dold); ! 616: } ! 617: ! 618: /* ! 619: * Call the ioctl function for a device. ! 620: */ ! 621: dioctl(dev, com, vec) ! 622: register dev_t dev; ! 623: union ioctl *vec; ! 624: { ! 625: register CON *cp; ! 626: dold_t dold; ! 627: ! 628: if ((cp=drvmap(dev, &dold)) == NULL) ! 629: return; ! 630: (*cp->c_ioctl)(dev, com, vec); ! 631: drest(dold); ! 632: } ! 633: ! 634: /* ! 635: * Call the powerfail entry point of a device. ! 636: */ ! 637: dpower(dev) ! 638: register dev_t dev; ! 639: { ! 640: register CON *cp; ! 641: dold_t dold; ! 642: ! 643: if ((cp=drvmap(dev, &dold)) == NULL) ! 644: return; ! 645: (*cp->c_power)(dev); ! 646: drest(dold); ! 647: } ! 648: ! 649: /* ! 650: * Call the timeout entry point of a device. ! 651: */ ! 652: dtime(dev) ! 653: register dev_t dev; ! 654: { ! 655: register CON *cp; ! 656: dold_t dold; ! 657: ! 658: if ((cp=drvmap(dev, &dold)) == NULL) ! 659: return; ! 660: (*cp->c_timer)(dev); ! 661: drest(dold); ! 662: } ! 663: ! 664: /* ! 665: * Poll a device. ! 666: */ ! 667: dpoll(dev, ev, msec) ! 668: register dev_t dev; ! 669: int ev; ! 670: int msec; ! 671: { ! 672: register CON *cp; ! 673: dold_t dold; ! 674: ! 675: if ((cp=drvmap(dev, &dold)) == NULL) ! 676: return POLLNVAL; ! 677: ! 678: if ( cp->c_flag & DFPOL ) ! 679: ev = (*cp->c_poll)(dev, ev, msec); ! 680: else ! 681: ev = POLLNVAL; ! 682: ! 683: drest(dold); ! 684: return ev; ! 685: } ! 686: ! 687: /* ! 688: * Given a device, return the flags word. ! 689: */ ! 690: dflag(dev) ! 691: dev_t dev; ! 692: { ! 693: register CON *cp; ! 694: register int f; ! 695: dold_t dold; ! 696: ! 697: if ((cp=drvmap(dev, &dold)) == NULL) ! 698: return (DFERR); ! 699: f = cp->c_flag; ! 700: drest(dold); ! 701: return (f); ! 702: } ! 703: ! 704: /* ! 705: * Given a device, and a pointer to a driver map save area, save the ! 706: * current map in the driver map save area and map in the new device, ! 707: * returning a pointer to the configuration entry for that device. ! 708: */ ! 709: CON * ! 710: drvmap(dev, doldp) ! 711: dev_t dev; ! 712: dold_t *doldp; ! 713: { ! 714: register DRV *dp; ! 715: register unsigned m; ! 716: ! 717: if ((m=major(dev)) >= drvn) { ! 718: u.u_error = ENXIO; ! 719: return (NULL); ! 720: } ! 721: dp = &drvl[m]; ! 722: if (locked(dp->d_gate)) { ! 723: u.u_error = ENXIO; ! 724: return (NULL); ! 725: } ! 726: if (dp->d_conp == NULL) { ! 727: u.u_error = ENXIO; ! 728: return (NULL); ! 729: } ! 730: dsave(*doldp); ! 731: if (dp->d_map != 0) ! 732: dmapv(dp->d_map); ! 733: return (dp->d_conp); ! 734: } ! 735: ! 736: /* ! 737: * Non existant device. ! 738: */ ! 739: nonedev() ! 740: { ! 741: u.u_error = ENXIO; ! 742: } ! 743: ! 744: /* ! 745: * Null device. ! 746: */ ! 747: nulldev() ! 748: { ! 749: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.