Annotation of coherent/a/usr/bob/korn/eval.c, revision 1.1.1.1

1.1       root        1: /*
                      2:  * Expansion - quoting, separation, substitution, globbing
                      3:  */
                      4: 
                      5: static char *RCSid = "$Header: /newbits/usr/bin/korn/RCS/eval.c,v 1.2 91/08/01 12:39:55 bin Exp Locker: bin $";
                      6: 
                      7: #include <stddef.h>
                      8: #include <stdio.h>
                      9: #include <string.h>
                     10: #include <errno.h>
                     11: #include <setjmp.h>
                     12: #include <unistd.h>
                     13: #include <sys/types.h>
                     14: #include <dirent.h>
                     15: #include <pwd.h>
                     16: #include "sh.h"
                     17: #include "lex.h"
                     18: #include "tree.h"
                     19: #include "table.h"
                     20: #include "expand.h"
                     21: 
                     22: #if COHERENT
                     23: struct dirent  *readdir();             /* missing from headers */
                     24: DIR    *opendir();
                     25: #endif
                     26: 
                     27: /*
                     28:  * string expansion
                     29:  *
                     30:  * first pass: quoting, IFS separation, ${} and $() substitution.
                     31:  * second pass: filename expansion (*?[]~).
                     32:  */
                     33: 
                     34: /* expansion generator state */
                     35: typedef struct Expand {
                     36:        /* int  type; */        /* see expand() */
                     37:        char   *str;            /* string */
                     38:        union {
                     39:                char  **strv;   /* string[] */
                     40:                FILE   *file;   /* file */
                     41:        } u;                    /* source */
                     42:        short   split;          /* split "$@"*/
                     43: } Expand;
                     44: 
                     45: #define        XBASE   0               /* scanning original */
                     46: #define        XSUB    1               /* expanding ${} string */
                     47: #define        XARGSEP 2               /* ifs0 between "$@" */
                     48: #define        XARG    3               /* expanding $*, $@ */
                     49: #define        XCOM    4               /* expanding $() */
                     50: 
                     51: static void    expand ARGS((char *, XPtrV *, int));
                     52: static int     comsub ARGS((Expand *, char *comm));
                     53: static int     varsub ARGS((Expand *, char *name, int stype));
                     54: static void    glob ARGS((char *cp, XPtrV *wp));
                     55: static void    globit ARGS((char *ds, char *dp, char *sp, XPtrV *wp, int check));
                     56: static char   *tilde ARGS((char *wp));
                     57: static char   *trimsub ARGS((char *str, char *pat, int how));
                     58: 
                     59: int    ifs0 = ' ';             /* todo: first char of $IFS */
                     60: 
                     61: /* compile and expand word */
                     62: char *
                     63: substitute(cp, f)
                     64:        char Const *cp;
                     65:        int f;
                     66: {
                     67:        struct source *s, *sold;
                     68: 
                     69:        sold = source;
                     70:        s = pushs(SWSTR);
                     71:        s->str = (char *) cp;
                     72:        source = s;
                     73:        if (yylex(ONEWORD) != LWORD)
                     74:                errorf("eval:substitute error\n");
                     75:        source = sold;
                     76:        return evalstr(yylval.cp, f);
                     77: }
                     78: 
                     79: /*
                     80:  * expand arg-list
                     81:  */
                     82: char **
                     83: eval(ap, f)
                     84:        register char **ap;
                     85: {
                     86:        XPtrV w;
                     87: 
                     88:        if (*ap == NULL)
                     89:                return ap;
                     90:        XPinit(w, 32);
                     91:        XPput(w, NULL);         /* space for shell name */
                     92:        while (*ap != NULL)
                     93:                expand(*ap++, &w, f);
                     94:        XPput(w, NULL);
                     95:        return (char **) XPclose(w) + 1;
                     96: }
                     97: 
                     98: /*
                     99:  * expand string
                    100:  */
                    101: char *
                    102: evalstr(cp, f)
                    103:        register char *cp;
                    104:        int f;
                    105: {
                    106:        XPtrV w;
                    107: 
                    108:        XPinit(w, 1);
                    109:        expand(cp, &w, f);
                    110:        cp = (XPsize(w) == 0) ? "" : (char*) *XPptrv(w);
                    111:        XPfree(w);
                    112:        return cp;
                    113: }
                    114: 
                    115: /* for nested substitution: ${var:=$var2} */
                    116: typedef struct SubType {
                    117:        short   type;           /* [=+-?%#] action after expanded word */
                    118:        short   base;           /* begin position of expanded word */
                    119:        char   *name;           /* name for ${var=word} */
                    120: } SubType;
                    121: 
                    122: #if 1
                    123: mypr(cp)
                    124: char   *cp;
                    125: {
                    126:        fprintf(stderr, "%r", &cp);
                    127: }
                    128: #endif
                    129: 
                    130: static void
                    131: expand(cp, wp, f)
                    132:        char *cp;               /* input word */
                    133:        register XPtrV *wp;     /* output words */
                    134:        int f;                  /* DO* flags */
                    135: {
                    136:        register int c;
                    137:        register int type = XBASE; /* expansion type */
                    138:        register int quote = 0; /* quoted */
                    139:        XString ds;             /* destination string */
                    140:        register char *dp, *sp; /* dest., source */
                    141:        int fdo, word, combase; /* second pass flags; have word */
                    142:        Expand x;               /* expansion variables */
                    143:        SubType subtype [10];   /* substitution type stack */
                    144:        register SubType *st = subtype + 10;
                    145: 
                    146:        if (cp == NULL)
                    147:                errorf("eval:expand(NULL)\n");
                    148:        if (flag[FNOGLOB]) {
                    149:                f &= ~ DOGLOB;
                    150:        }
                    151:        Xinit(ds, dp, 128);     /* init dest. string */
                    152:        type = XBASE;
                    153:        sp = cp;
                    154:        fdo = 0;
                    155:        word = !(f&DOBLANK);
                    156: 
                    157:        while (1) {
                    158:                Xcheck(ds, dp);
                    159: 
                    160:                switch (type) {
                    161:                  case XBASE:   /* original prefixed string */
                    162:                        c = *sp++;
                    163:                        switch (c) {
                    164:                          case EOS:
                    165:                                c = 0;
                    166:                                break;
                    167:                          case CHAR:
                    168:                                c = *sp++;
                    169:                                break;
                    170:                          case QCHAR:
                    171:                                quote |= 2; /* temporary quote */
                    172:                                c = *sp++;
                    173:                                break;
                    174:                          case OQUOTE:
                    175:                                word = quote = 1;
                    176:                                continue;
                    177:                          case CQUOTE:
                    178:                                quote = 0;
                    179:                                continue;
                    180:                          case COMSUB:
                    181:                                type = comsub(&x, sp);
                    182:                                sp = strchr(sp, 0) + 1;
                    183:                                combase = Xsavepos(ds, dp);
                    184:                                continue;
                    185:                          case OSUBST: /* ${var{:}[=+-?]word} */
                    186:                                cp = sp;                /* variable */
                    187:                                sp = strchr(sp, 0) + 1; /* skip variable */
                    188:                                c = (*sp == CSUBST) ? 0 : *sp++;
                    189:                                if ((c&0x7F) == '#' || (c&0x7F) == '%')
                    190:                                        f |= DOPAT;
                    191:                                type = varsub(&x, cp, c);
                    192:                                if (type == XBASE) {    /* expand? */
                    193:                                        if (st == subtype)
                    194:                                                errorf("ridiculous ${} nesting\n");
                    195:                                        --st;
                    196:                                        st->type = c;
                    197:                                        st->base = Xsavepos(ds, dp);
                    198:                                        st->name = cp;
                    199:                                } else
                    200:                                        /* todo: nested OSUBST/CSUBST */
                    201:                                        sp = wdscan(sp, CSUBST); /* skip word */
                    202:                                continue;
                    203:                          case CSUBST: /* only get here if expanding word */
                    204:                                *dp = 0;
                    205:                                if (f&DOGLOB)
                    206:                                        f &= ~DOPAT;
                    207:                                switch (st->type&0x7F) {
                    208:                                  case '#':
                    209:                                  case '%':
                    210:                                        *dp = 0;
                    211:                                        dp = Xrestpos(ds, dp, st->base);
                    212:                                        x.str = trimsub(x.str, dp, st->type);
                    213:                                        type = XSUB;
                    214:                                        continue;
                    215:                                  case '=':
                    216: #if 0
                    217:                                        if ((x.u.vp->flag&RDONLY))
                    218:                                                errorf("cannot set readonly %s\n", cp);
                    219: #endif
                    220:                                        setstr(global(st->name), Xrestpos(ds, dp, st->base));
                    221:                                        break;
                    222:                                  case '?':
                    223:                                        if (dp == Xrestpos(ds, dp, st->base))
                    224:                                                errorf("missing value for %s\n", cp);
                    225:                                        else
                    226:                                                errorf("%s\n", Xrestpos(ds, dp, st->base));
                    227:                                }
                    228:                                st++;
                    229:                                type = XBASE;
                    230:                                continue;
                    231:                        }
                    232:                        break;
                    233: 
                    234:                  case XSUB:
                    235:                        if ((c = *x.str++) == 0) {
                    236:                                type = XBASE;
                    237:                                continue;
                    238:                        }
                    239:                        break;
                    240: 
                    241:                  case XARGSEP:
                    242:                        type = XARG;
                    243:                        quote = 1;
                    244:                  case XARG:
                    245:                        if ((c = *x.str++) == 0) {
                    246:                                if ((x.str = *x.u.strv++) == NULL) {
                    247:                                        type = XBASE;
                    248:                                        continue;
                    249:                                } else if (quote && x.split) {
                    250:                                        /* terminate word for "$@" */
                    251:                                        type = XARGSEP;
                    252:                                        quote = 0;
                    253:                                }
                    254:                                c = ifs0;
                    255:                        }
                    256:                        break;
                    257: 
                    258:                  case XCOM:
                    259:                        c = getc(x.u.file);
                    260:                        if (quote) {
                    261:                                if (c == EOF) {
                    262:                                        cp = Xrestpos(ds, sp, combase);
                    263:                                        for (dp--; dp >= cp && *dp == '\n'; dp--)
                    264:                                                ;
                    265:                                        dp++;
                    266:                                        fclose(x.u.file);
                    267:                                        if (x.split)
                    268:                                                waitlast();
                    269:                                        type = XBASE;
                    270:                                        continue;
                    271:                                }
                    272:                        } else {        /* this part is probably redundant */
                    273:                                if (c == EOF || c == '\n') {
                    274:                                        while ((c = getc(x.u.file)) == '\n')
                    275:                                                ;
                    276:                                        if (c == EOF) {
                    277:                                                fclose(x.u.file);
                    278:                                                if (x.split)
                    279:                                                        waitlast();
                    280:                                                type = XBASE;
                    281:                                                continue;
                    282:                                        }
                    283:                                        ungetc(c, x.u.file);
                    284:                                        c = ifs0;
                    285:                                }
                    286:                        }
                    287:                        break;
                    288:                }
                    289: 
                    290:                /* check for end of word or IFS separation */
                    291:                if (c == 0 || !quote && (f&DOBLANK) && ctype(c, C_IFS)) {
                    292:                        if (word) {
                    293:                                *dp++ = 0;
                    294:                                cp = Xclose(ds, dp);
                    295:                                if (fdo&DOTILDE)
                    296:                                        cp = tilde(cp);
                    297:                                if (fdo&DOGLOB)
                    298:                                        glob(cp, wp);
                    299:                                else
                    300:                                        {XPput(*wp, cp);}
                    301:                                fdo = word = 0;
                    302:                                if (c != 0)
                    303:                                        Xinit(ds, dp, 128);
                    304:                        } else
                    305:                                ; /* ignore IFS */
                    306:                        if (c == 0)
                    307:                                return;
                    308:                } else {
                    309:                        /* mark any special second pass chars */
                    310:                        if (!quote)
                    311:                                switch (c) {
                    312:                                  case '*':
                    313:                                  case '?':
                    314:                                  case '[':
                    315:                                        if (f&(DOPAT|DOGLOB)) {
                    316:                                                fdo |= (f&DOGLOB);
                    317:                                                *dp++ = MAGIC;
                    318:                                        }
                    319:                                        break;
                    320:                                  case '~':
                    321:                                        if ((f&DOTILDE) && dp == Xstring(ds, dp) ||
                    322:                                            !(f&DOBLANK) && 
                    323:                                            (dp[-1] == '=' || dp[-1] == ':')) {
                    324:                                                fdo |= DOTILDE;
                    325:                                                *dp++ = MAGIC;
                    326:                                        }
                    327:                                        break;
                    328:                                }
                    329:                        else
                    330:                                quote &= ~2; /* undo temporary */
                    331: 
                    332:                        word = 1;
                    333:                        *dp++ = c; /* save output char */
                    334:                }
                    335:        }
                    336: }
                    337: 
                    338: /*
                    339:  * Prepare to generate the string returned by ${} substitution.
                    340:  */
                    341: static int
                    342: varsub(xp, sp, stype)
                    343:        register Expand *xp;
                    344:        register char *sp;
                    345:        int stype;
                    346: {
                    347:        register int c;
                    348:        int type;
                    349: 
                    350:        /* ${#var}, string length or argc */
                    351:        if (sp[0] == '#' && (c = sp[1]) != 0) {
                    352:                c = (c == '*' || c == '@') ? e.loc->argc :
                    353:                        strlen(strval(global(sp+1)));
                    354:                xp->str = strsave(ulton((unsigned long)c, 10), ATEMP);
                    355:                return XSUB;
                    356:        }
                    357: 
                    358:        c = sp[0];
                    359:        if (c == '*' || c == '@') {
                    360:                if (e.loc->argc == 0) {
                    361:                        xp->str = null;
                    362:                        type = XSUB;
                    363:                } else {
                    364:                        xp->u.strv = e.loc->argv + 1;
                    365:                        xp->str = *xp->u.strv++;
                    366:                        xp->split = c == '@'; /* $@ */
                    367:                        type = XARG;
                    368:                }
                    369:        } else {
                    370:                xp->str = strval(global(sp));
                    371:                type = XSUB;
                    372:        }
                    373: 
                    374:        c = stype&0x7F;
                    375:        /* test the compiler's code generator */
                    376:        if (c == '%' || c == '#' ||
                    377:            (((stype&0x80) ? *xp->str=='\0' : xp->str==null) ? /* undef? */
                    378:             c == '=' || c == '-' || c == '?' : c == '+'))
                    379:                type = XBASE;   /* expand word instead of variable value */
                    380:        if (type != XBASE && flag[FNOUNSET] && xp->str == null)
                    381:                errorf("%s: unset variable\n", sp);
                    382:        return type;
                    383: }
                    384: 
                    385: /*
                    386:  * Run the command in $(...) and read its output.
                    387:  */
                    388: static int
                    389: comsub(xp, cp)
                    390:        register Expand *xp;
                    391:        char *cp;
                    392: {
                    393:        Source *s;
                    394:        register struct op *t;
                    395:        FILE *fi;
                    396: 
                    397:        s = pushs(SSTRING);
                    398:        s->str = cp;
                    399:        t = compile(s);
                    400: 
                    401:        if (t != NULL && t->type == TCOM && /* $(<file) */
                    402:            *t->args == NULL && *t->vars == NULL && t->ioact != NULL) {
                    403:                register struct ioword *io = *t->ioact;
                    404: 
                    405:                if (io->flag != IOREAD)
                    406:                        errorf("funny $() command\n");
                    407:                fi = fopen(evalstr(io->name, DOTILDE), "r");
                    408:                if (fi != NULL)
                    409:                        fileno(fi) = savefd(fileno(fi));
                    410:                xp->split = 0;  /* no waitlast() */
                    411:        } else {
                    412:                int ofd1, pv[2];
                    413: 
                    414:                openpipe(pv);
                    415:                fi = fdopen(pv[0], "r");
                    416:                ofd1 = savefd(1);
                    417:                dup2(pv[1], 1);
                    418:                close(pv[1]);
                    419: #if 0
                    420:                exchild(t, XXCOM|XPIPEO);
                    421: #else
                    422:                execute(t, XFORK|XXCOM|XPIPEO);
                    423: #endif
                    424:                dup2(ofd1, 1);
                    425: #if COHERENT
                    426:                close(ofd1);
                    427: #endif
                    428:                xp->split = 1;  /* waitlast() */
                    429:        }       
                    430: 
                    431:        if (fi == NULL)
                    432:                errorf("cannot open $() input\n");
                    433:        setvbuf(fi, (char *)NULL, _IOFBF, BUFSIZ);
                    434:        xp->u.file = fi;
                    435:        return XCOM;
                    436: }
                    437: 
                    438: /*
                    439:  * perform #pattern and %pattern substitution in ${}
                    440:  */
                    441: 
                    442: static char *
                    443: trimsub(str, pat, how)
                    444:        register char *str;
                    445:        char *pat;
                    446:        int how;
                    447: {
                    448:        register char *end = strchr(str, 0);
                    449:        register char *p, c;
                    450: 
                    451:        switch (how) {
                    452:          case '#':             /* shortest at begin */
                    453:                for (p = str; p <= end; p++) {
                    454:                        c = *p; *p = '\0';
                    455:                        if (gmatch(str, pat)) {
                    456:                                *p = c;
                    457:                                return p;
                    458:                        }
                    459:                        *p = c;
                    460:                }
                    461:                break;
                    462:        case '#'|0x80:          /* longest match at begin */
                    463:                for (p = end; p >= str; p--) {
                    464:                        c = *p; *p = '\0';
                    465:                        if (gmatch(str, pat)) {
                    466:                                *p = c;
                    467:                                return p;
                    468:                        }
                    469:                        *p = c;
                    470:                }
                    471:                break;
                    472:          case '%':             /* shortest match at end */
                    473:                for (p = end; p >= str; p--) {
                    474:                        if (gmatch(p, pat)) {
                    475:                                *p = '\0';
                    476:                                return str;
                    477:                        }
                    478:                }
                    479:                break;
                    480:          case '%'|0x80:        /* longest match at end */
                    481:                for (p = str; p <= end; p++) {
                    482:                        if (gmatch(p, pat)) {
                    483:                                *p = '\0';
                    484:                                return str;
                    485:                        }
                    486:                }
                    487:                break;
                    488:        }
                    489: 
                    490:        return str;             /* no match, return string */
                    491: }
                    492: 
                    493: /*
                    494:  * glob
                    495:  * Name derived from V6's /etc/glob, the program that expanded filenames.
                    496:  */
                    497: 
                    498: static char   *debunk();
                    499: 
                    500: static void 
                    501: glob(cp, wp)
                    502:        char *cp;
                    503:        register XPtrV *wp;
                    504: {
                    505:        char path [PATH];
                    506:        register char *sp = cp;
                    507:        int oldsize;
                    508: 
                    509:        oldsize = XPsize(*wp);
                    510:        globit(path, path, sp, wp, 0);
                    511: 
                    512:        if (XPsize(*wp) == oldsize)
                    513:                {XPput(*wp, debunk(cp));}
                    514:        else
                    515:                qsortp(XPptrv(*wp) + oldsize, (size_t)(XPsize(*wp) - oldsize), xstrcmp);
                    516: }
                    517: 
                    518: static void
                    519: globit(ds, dp, sp, wp, check)
                    520:        char *ds;               /* dest path */
                    521:        char *dp;               /* dest end */
                    522:        char *sp;               /* source path */
                    523:        register XPtrV *wp;     /* output list */
                    524:        int check;              /* check dest existence */
                    525: {
                    526:        register char *np;      /* next source component */
                    527:        register char *tsp, *tdp;
                    528: 
                    529:        if (sp == NULL) {       /* end of source path */
                    530:                if (check && access(ds, 0) < 0)
                    531:                        return;
                    532:                XPput(*wp, strsave(ds, ATEMP));
                    533:                return;
                    534:        }
                    535: 
                    536:        if (dp > ds)
                    537:                *dp++ = '/';
                    538:        while (*sp == '/')
                    539:                *dp++ = *sp++;
                    540:        np = strchr(sp, '/');
                    541:        if (np != NULL)
                    542:                *np++ = 0;
                    543: 
                    544:        *dp = 0;
                    545:        if (strchr(sp, MAGIC) == NULL) { /* contains no pattern? */
                    546:                tdp = dp; tsp = sp;
                    547:                while ((*tdp++ = *tsp++) != 0)
                    548:                        ;
                    549:                --tdp;
                    550:                globit(ds, tdp, np, wp, check);
                    551:        } else {
                    552:                DIR *dirp;
                    553:                struct dirent *d;
                    554: 
                    555:                dirp = opendir(ds);
                    556:                if (dirp == NULL)
                    557:                        goto Nodir;
                    558:                while ((d = readdir(dirp)) != NULL) {
                    559:                        tsp = d->d_name;
                    560:                        if (tsp[0] == '.' &&
                    561:                            (tsp[1] == 0 || tsp[1] == '.' && tsp[2] == 0))
                    562:                                continue; /* always ignore . and .. */
                    563:                        if (*tsp == '.' && *sp != '.' || !gmatch(tsp, sp))
                    564:                                continue;
                    565: 
                    566:                        tdp = dp;
                    567:                        while ((*tdp++ = *tsp++) != 0)
                    568:                                ;
                    569:                        --tdp;
                    570:                        globit(ds, tdp, np, wp, np != NULL);
                    571:                }
                    572:                closedir(dirp);
                    573:          Nodir:;
                    574:        }
                    575: 
                    576:        if (np != NULL)
                    577:                *--np = '/';
                    578: }
                    579: 
                    580: /* remove MAGIC from string */
                    581: static char *
                    582: debunk(cp)
                    583:        char *cp;
                    584: {
                    585:        register unsigned char *dp, *sp;
                    586: 
                    587:        for (dp = sp = cp; *sp != 0; sp++)
                    588:                if (*sp != MAGIC)
                    589:                        *dp++ = *sp;
                    590:        *dp = 0;
                    591:        return cp;
                    592: }
                    593: 
                    594: /*
                    595:  * tilde expansion
                    596:  *
                    597:  * based on a version by Arnold Robbins
                    598:  */
                    599: 
                    600: static char *homedir();
                    601: 
                    602: static char *
                    603: tilde(acp)
                    604:        char *acp;
                    605: {
                    606:        register unsigned int c;
                    607:        char path[PATH+1];
                    608:        register unsigned char *cp = acp, *wp = path, *dp;
                    609:        char userid[16+1];
                    610: 
                    611:   Again:
                    612:        while (1) {
                    613:                if ((c = *cp++) == 0) {
                    614:                        *wp = 0;
                    615:                        afree((Void*)acp, ATEMP);
                    616:                        return strsave(path, ATEMP);
                    617:                } else if (c == MAGIC && *cp == '~')
                    618:                        break;
                    619:                else
                    620:                        *wp++ = c;
                    621:        }
                    622: 
                    623:        dp = NULL;      /* no output substitution */
                    624:        if (cp[1] == 0 || cp[1] == '/' || cp[1] == ':') /* ~ or ~/ */
                    625:                dp = strval(global("HOME")), cp += 1;
                    626:        else if (cp[1] == '+' && (cp[2] == '/' || cp[2] == ':' || cp[2] == 0))
                    627:                dp = strval(global("PWD")), cp += 2;
                    628:        else if (cp[1] == '-' && (cp[2] == '/' || cp[2] == ':' || cp[2] == 0))
                    629:                dp = strval(global("OLDPWD")), cp += 2;
                    630:        else if (letter(cp[1])) {
                    631:                char *save = cp;
                    632:                for (dp = userid, cp++; letnum(*cp) && dp < userid+16; )
                    633:                        *dp++ = *cp++;
                    634:                *dp = 0;
                    635:                dp = homedir(userid);
                    636:                if (dp == NULL)
                    637:                        cp = save;
                    638:        }
                    639:        /* substitute */
                    640:        if (dp != NULL)
                    641:                while (*dp != 0)
                    642:                        *wp++ = *dp++;
                    643:        goto Again;
                    644: }
                    645: 
                    646: /*
                    647:  * map userid to user's home directory.
                    648:  * todo: implement a cache with the "homedirs" table.
                    649:  * note that 4.3's getpw adds more than 6K to the shell,
                    650:  * and the YP version probably adds much more.
                    651:  * we might consider our own version of getpwnam() to keep the size down.
                    652:  */
                    653: 
                    654: static char *
                    655: homedir(name)
                    656:        char *name;
                    657: {
                    658:        register struct tbl *ap;
                    659:        register struct passwd *pw;
                    660: 
                    661:        ap = tsearch(&homedirs, name, hash(name));
                    662:        if ((ap != NULL && (ap->flag&ISSET)))
                    663:                return ap->val.s;
                    664:        pw = getpwnam(name);
                    665:        if (pw == NULL)
                    666:                return NULL;
                    667:        return pw->pw_dir;
                    668: }
                    669: 

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.