Annotation of coherent/e/bin/pax/extract.c, revision 1.1

1.1     ! root        1: /* $Source: /src386/usr/bin/pax/extract.c,v $
        !             2:  *
        !             3:  * $Revision: 1.1 $
        !             4:  *
        !             5:  * extract.c - Extract files from a tar archive. 
        !             6:  *
        !             7:  * DESCRIPTION
        !             8:  *
        !             9:  * AUTHOR
        !            10:  *
        !            11:  *     Mark H. Colburn, NAPS International ([email protected])
        !            12:  *
        !            13:  * Sponsored by The USENIX Association for public distribution. 
        !            14:  *
        !            15:  * Copyright (c) 1989 Mark H. Colburn.
        !            16:  * All rights reserved.
        !            17:  *
        !            18:  * Redistribution and use in source and binary forms are permitted
        !            19:  * provided that the above copyright notice is duplicated in all such 
        !            20:  * forms and that any documentation, advertising materials, and other 
        !            21:  * materials related to such distribution and use acknowledge that the 
        !            22:  * software was developed * by Mark H. Colburn and sponsored by The 
        !            23:  * USENIX Association. 
        !            24:  *
        !            25:  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
        !            26:  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
        !            27:  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
        !            28:  *
        !            29:  * $Log:       extract.c,v $
        !            30:  * Revision 1.1  92/08/28  08:02:02  bin
        !            31:  * Initial revision
        !            32:  * 
        !            33:  * Revision 1.1  89/02/14  16:47:51  jep
        !            34:  * Initial revision
        !            35:  * 
        !            36:  * Revision 1.1  88/12/23  18:02:07  mark
        !            37:  * Initial revision
        !            38:  * 
        !            39:  */
        !            40: 
        !            41: #ifndef lint
        !            42: static char *ident = "$Id: extract.c,v 1.1 92/08/28 08:02:02 bin Exp Locker: bin $";
        !            43: static char *copyright = "Copyright (c) 1989 Mark H. Colburn.\nAll rights reserved.\n";
        !            44: #endif /* ! lint */
        !            45: 
        !            46: 
        !            47: /* Headers */
        !            48: 
        !            49: #include "pax.h"
        !            50: 
        !            51: 
        !            52: /* Defines */
        !            53: 
        !            54: /*
        !            55:  * Swap bytes. 
        !            56:  */
        !            57: #define        SWAB(n) ((((ushort)(n) >> 8) & 0xff) | (((ushort)(n) << 8) & 0xff00))
        !            58: 
        !            59: 
        !            60: /* Function Prototypes */
        !            61: 
        !            62: #if __STDC__
        !            63: 
        !            64: static int inbinary(char *, char *, Stat *);
        !            65: static int inascii(char *, char *, Stat *);
        !            66: static int inswab(char *, char *, Stat *);
        !            67: static int readtar(char *, Stat *);
        !            68: static int readcpio(char *, Stat *);
        !            69: 
        !            70: #else /* !__STDC__ */
        !            71: 
        !            72: static int inbinary();
        !            73: static int inascii();
        !            74: static int inswab();
        !            75: static int readtar();
        !            76: static int readcpio();
        !            77: 
        !            78: #endif /* __STDC__ */
        !            79: 
        !            80: 
        !            81: /* read_archive - read in an archive
        !            82:  *
        !            83:  * DESCRIPTION
        !            84:  *
        !            85:  *     Read_archive is the central entry point for reading archives.
        !            86:  *     Read_archive determines the proper archive functions to call
        !            87:  *     based upon the archive type being processed.
        !            88:  *
        !            89:  * RETURNS
        !            90:  *
        !            91:  */
        !            92: 
        !            93: #if __STDC__
        !            94: 
        !            95: int read_archive(void)
        !            96: 
        !            97: #else
        !            98:     
        !            99: int read_archive()
        !           100: 
        !           101: #endif
        !           102: {
        !           103:     Stat            sb;
        !           104:     char            name[PATH_MAX + 1];
        !           105:     int             match;
        !           106:     int                    pad;
        !           107: 
        !           108:     name_gather();             /* get names from command line */
        !           109:     name[0] = '\0';
        !           110:     while (get_header(name, &sb) == 0) {
        !           111:        match = name_match(name) ^ f_reverse_match;
        !           112:        if (f_list) {           /* only wanted a table of contents */
        !           113:            if (match) {
        !           114:                print_entry(name, &sb);
        !           115:            }
        !           116:            if (((ar_format == TAR) 
        !           117:                ? buf_skip(ROUNDUP((OFFSET) sb.sb_size, BLOCKSIZE)) 
        !           118:                : buf_skip((OFFSET) sb.sb_size)) < 0) {
        !           119:                warn(name, "File data is corrupt");
        !           120:            }
        !           121:        } else if (match) {
        !           122:            if (rplhead != (Replstr *)NULL) {
        !           123:                rpl_name(name);
        !           124:                if (strlen(name) == 0) {
        !           125:                    continue;
        !           126:                }
        !           127:            }
        !           128:            if (get_disposition("extract", name) || 
        !           129:                 get_newname(name, sizeof(name))) {
        !           130:                /* skip file... */
        !           131:                if (((ar_format == TAR) 
        !           132:                    ? buf_skip(ROUNDUP((OFFSET) sb.sb_size, BLOCKSIZE)) 
        !           133:                    : buf_skip((OFFSET) sb.sb_size)) < 0) {
        !           134:                    warn(name, "File data is corrupt");
        !           135:                }
        !           136:                continue;
        !           137:            } 
        !           138:            if (inentry(name, &sb) < 0) {
        !           139:                warn(name, "File data is corrupt");
        !           140:            }
        !           141:            if (f_verbose) {
        !           142:                print_entry(name, &sb);
        !           143:            }
        !           144:            if (ar_format == TAR && sb.sb_nlink > 1) {
        !           145:                /*
        !           146:                 * This kludge makes sure that the link table is cleared
        !           147:                 * before attempting to process any other links.
        !           148:                 */
        !           149:                if (sb.sb_nlink > 1) {
        !           150:                    linkfrom(name, &sb);
        !           151:                }
        !           152:            }
        !           153:            if (ar_format == TAR && (pad = sb.sb_size % BLOCKSIZE) != 0) {
        !           154:                pad = BLOCKSIZE - pad;
        !           155:                buf_skip((OFFSET) pad);
        !           156:            }
        !           157:        } else {
        !           158:            if (((ar_format == TAR) 
        !           159:                ? buf_skip(ROUNDUP((OFFSET) sb.sb_size, BLOCKSIZE)) 
        !           160:                : buf_skip((OFFSET) sb.sb_size)) < 0) {
        !           161:                warn(name, "File data is corrupt");
        !           162:            }
        !           163:        }
        !           164:     }
        !           165: 
        !           166:     close_archive();
        !           167: }
        !           168: 
        !           169: 
        !           170: 
        !           171: /* get_header - figures which type of header needs to be read.
        !           172:  *
        !           173:  * DESCRIPTION
        !           174:  *
        !           175:  *     This is merely a single entry point for the two types of archive
        !           176:  *     headers which are supported.  The correct header is selected
        !           177:  *     depending on the archive type.
        !           178:  *
        !           179:  * PARAMETERS
        !           180:  *
        !           181:  *     char    *name   - name of the file (passed to header routine)
        !           182:  *     Stat    *asb    - Stat block for the file (passed to header routine)
        !           183:  *
        !           184:  * RETURNS
        !           185:  *
        !           186:  *     Returns the value which was returned by the proper header
        !           187:  *     function.
        !           188:  */
        !           189: 
        !           190: #if __STDC__
        !           191: 
        !           192: int get_header(char *name, Stat *asb)
        !           193: 
        !           194: #else
        !           195:     
        !           196: int get_header(name, asb)
        !           197: char *name;
        !           198: Stat *asb;
        !           199: 
        !           200: #endif
        !           201: {
        !           202:     if (ar_format == TAR) {
        !           203:        return(readtar(name, asb));
        !           204:     } else {
        !           205:        return(readcpio(name, asb));
        !           206:     }
        !           207: }
        !           208: 
        !           209: 
        !           210: /* readtar - read a tar header
        !           211:  *
        !           212:  * DESCRIPTION
        !           213:  *
        !           214:  *     Tar_head read a tar format header from the archive.  The name
        !           215:  *     and asb parameters are modified as appropriate for the file listed
        !           216:  *     in the header.   Name is assumed to be a pointer to an array of
        !           217:  *     at least PATH_MAX bytes.
        !           218:  *
        !           219:  * PARAMETERS
        !           220:  *
        !           221:  *     char    *name   - name of the file for which the header is
        !           222:  *                       for.  This is modified and passed back to
        !           223:  *                       the caller.
        !           224:  *     Stat    *asb    - Stat block for the file for which the header
        !           225:  *                       is for.  The fields of the stat structure are
        !           226:  *                       extracted from the archive header.  This is
        !           227:  *                       also passed back to the caller.
        !           228:  *
        !           229:  * RETURNS
        !           230:  *
        !           231:  *     Returns 0 if a valid header was found, or -1 if EOF is
        !           232:  *     encountered.
        !           233:  */
        !           234: 
        !           235: #if __STDC__
        !           236: 
        !           237: static int readtar(char *name, Stat *asb)
        !           238: 
        !           239: #else
        !           240:     
        !           241: static int readtar(name, asb)
        !           242: char   *name;
        !           243: Stat    *asb;
        !           244: 
        !           245: #endif
        !           246: {
        !           247:     int             status = 3;        /* Initial status at start of archive */
        !           248:     static int      prev_status;
        !           249: 
        !           250:     for (;;) {
        !           251:        prev_status = status;
        !           252:        status = read_header(name, asb);
        !           253:        switch (status) {
        !           254:        case 1:         /* Valid header */
        !           255:                return(0);
        !           256:        case 0:         /* Invalid header */
        !           257:            switch (prev_status) {
        !           258:            case 3:             /* Error on first record */
        !           259:                warn(ar_file, "This doesn't look like a tar archive");
        !           260:                /* FALLTHRU */
        !           261:            case 2:             /* Error after record of zeroes */
        !           262:            case 1:             /* Error after header rec */
        !           263:                warn(ar_file, "Skipping to next file...");
        !           264:                /* FALLTHRU */
        !           265:            default:
        !           266:            case 0:             /* Error after error */
        !           267:                break;
        !           268:            }
        !           269:            break;
        !           270: 
        !           271:        case 2:                 /* Record of zeroes */
        !           272:        case EOF:               /* End of archive */
        !           273:        default:
        !           274:            return(-1);
        !           275:        }
        !           276:     }
        !           277: }
        !           278: 
        !           279: 
        !           280: /* readcpio - read a CPIO header 
        !           281:  *
        !           282:  * DESCRIPTION
        !           283:  *
        !           284:  *     Read in a cpio header.  Understands how to determine and read ASCII, 
        !           285:  *     binary and byte-swapped binary headers.  Quietly translates 
        !           286:  *     old-fashioned binary cpio headers (and arranges to skip the possible 
        !           287:  *     alignment byte). Returns zero if successful, -1 upon archive trailer. 
        !           288:  *
        !           289:  * PARAMETERS
        !           290:  *
        !           291:  *     char    *name   - name of the file for which the header is
        !           292:  *                       for.  This is modified and passed back to
        !           293:  *                       the caller.
        !           294:  *     Stat    *asb    - Stat block for the file for which the header
        !           295:  *                       is for.  The fields of the stat structure are
        !           296:  *                       extracted from the archive header.  This is
        !           297:  *                       also passed back to the caller.
        !           298:  *
        !           299:  * RETURNS
        !           300:  *
        !           301:  *     Returns 0 if a valid header was found, or -1 if EOF is
        !           302:  *     encountered.
        !           303:  */
        !           304: 
        !           305: #if __STDC__
        !           306: 
        !           307: static int readcpio(char *name, Stat *asb)
        !           308: 
        !           309: #else
        !           310:     
        !           311: static int readcpio(name, asb)
        !           312: char           *name;
        !           313: Stat           *asb;
        !           314: 
        !           315: #endif
        !           316: {
        !           317:     OFFSET          skipped;
        !           318:     char            magic[M_STRLEN];
        !           319:     static int      align;
        !           320: 
        !           321:     if (align > 0) {
        !           322:        buf_skip((OFFSET) align);
        !           323:     }
        !           324:     align = 0;
        !           325:     for (;;) {
        !           326:        buf_read(magic, M_STRLEN);
        !           327:        skipped = 0;
        !           328:        while ((align = inascii(magic, name, asb)) < 0
        !           329:               && (align = inbinary(magic, name, asb)) < 0
        !           330:               && (align = inswab(magic, name, asb)) < 0) {
        !           331:            if (++skipped == 1) {
        !           332:                if (total - sizeof(magic) == 0) {
        !           333:                    fatal("Unrecognizable archive");
        !           334:                }
        !           335:                warnarch("Bad magic number", (OFFSET) sizeof(magic));
        !           336:                if (name[0]) {
        !           337:                    warn(name, "May be corrupt");
        !           338:                }
        !           339:            }
        !           340:            memcpy(magic, magic + 1, sizeof(magic) - 1);
        !           341:            buf_read(magic + sizeof(magic) - 1, 1);
        !           342:        }
        !           343:        if (skipped) {
        !           344:            warnarch("Apparently resynchronized", (OFFSET) sizeof(magic));
        !           345:            warn(name, "Continuing");
        !           346:        }
        !           347:        if (strcmp(name, TRAILER) == 0) {
        !           348:            return (-1);
        !           349:        }
        !           350:        if (nameopt(name) >= 0) {
        !           351:            break;
        !           352:        }
        !           353:        buf_skip((OFFSET) asb->sb_size + align);
        !           354:     }
        !           355: #ifdef S_IFLNK
        !           356:     if ((asb->sb_mode & S_IFMT) == S_IFLNK) {
        !           357:        if (buf_read(asb->sb_link, (uint) asb->sb_size) < 0) {
        !           358:            warn(name, "Corrupt symbolic link");
        !           359:            return (readcpio(name, asb));
        !           360:        }
        !           361:        asb->sb_link[asb->sb_size] = '\0';
        !           362:        asb->sb_size = 0;
        !           363:     }
        !           364: #endif                         /* S_IFLNK */
        !           365: 
        !           366:     /* destroy absolute pathnames for security reasons */
        !           367:     if (name[0] == '/') {
        !           368:        if (name[1]) {
        !           369:            while (name[0] = name[1]) {
        !           370:                ++name;
        !           371:            }
        !           372:        } else {
        !           373:            name[0] = '.';
        !           374:        }
        !           375:     }
        !           376:     asb->sb_atime = asb->sb_ctime = asb->sb_mtime;
        !           377:     if (asb->sb_nlink > 1) {
        !           378:        linkto(name, asb);
        !           379:     }
        !           380:     return (0);
        !           381: }
        !           382: 
        !           383: 
        !           384: /* inswab - read a reversed by order binary header
        !           385:  *
        !           386:  * DESCRIPTIONS
        !           387:  *
        !           388:  *     Reads a byte-swapped CPIO binary archive header
        !           389:  *
        !           390:  * PARMAMETERS
        !           391:  *
        !           392:  *     char    *magic  - magic number to match
        !           393:  *     char    *name   - name of the file which is stored in the header.
        !           394:  *                       (modified and passed back to caller).
        !           395:  *     Stat    *asb    - stat block for the file (modified and passed back
        !           396:  *                       to the caller).
        !           397:  *
        !           398:  *
        !           399:  * RETURNS
        !           400:  *
        !           401:  *     Returns the number of trailing alignment bytes to skip; -1 if 
        !           402:  *     unsuccessful. 
        !           403:  *
        !           404:  */
        !           405: 
        !           406: #if __STDC__
        !           407: 
        !           408: static int inswab(char *magic, char *name, Stat *asb)
        !           409: 
        !           410: #else
        !           411:     
        !           412: static int inswab(magic, name, asb)
        !           413: char           *magic;
        !           414: char           *name;
        !           415: Stat           *asb;
        !           416: 
        !           417: #endif
        !           418: {
        !           419:     ushort          namesize;
        !           420:     uint            namefull;
        !           421:     Binary          binary;
        !           422: 
        !           423:     if (*((ushort *) magic) != SWAB(M_BINARY)) {
        !           424:        return (-1);
        !           425:     }
        !           426:     memcpy((char *) &binary,
        !           427:                  magic + sizeof(ushort),
        !           428:                  M_STRLEN - sizeof(ushort));
        !           429:     if (buf_read((char *) &binary + M_STRLEN - sizeof(ushort),
        !           430:                 sizeof(binary) - (M_STRLEN - sizeof(ushort))) < 0) {
        !           431:        warnarch("Corrupt swapped header",
        !           432:                 (OFFSET) sizeof(binary) - (M_STRLEN - sizeof(ushort)));
        !           433:        return (-1);
        !           434:     }
        !           435:     asb->sb_dev = (dev_t) SWAB(binary.b_dev);
        !           436:     asb->sb_ino = (ino_t) SWAB(binary.b_ino);
        !           437:     asb->sb_mode = SWAB(binary.b_mode);
        !           438:     asb->sb_uid = SWAB(binary.b_uid);
        !           439:     asb->sb_gid = SWAB(binary.b_gid);
        !           440:     asb->sb_nlink = SWAB(binary.b_nlink);
        !           441:     asb->sb_rdev = (dev_t) SWAB(binary.b_rdev);
        !           442:     asb->sb_mtime = SWAB(binary.b_mtime[0]) << 16 | SWAB(binary.b_mtime[1]);
        !           443:     asb->sb_size = SWAB(binary.b_size[0]) << 16 | SWAB(binary.b_size[1]);
        !           444:     if ((namesize = SWAB(binary.b_name)) == 0 || namesize >= PATH_MAX) {
        !           445:        warnarch("Bad swapped pathname length",
        !           446:                 (OFFSET) sizeof(binary) - (M_STRLEN - sizeof(ushort)));
        !           447:        return (-1);
        !           448:     }
        !           449:     if (buf_read(name, namefull = namesize + namesize % 2) < 0) {
        !           450:        warnarch("Corrupt swapped pathname", (OFFSET) namefull);
        !           451:        return (-1);
        !           452:     }
        !           453:     if (name[namesize - 1] != '\0') {
        !           454:        warnarch("Bad swapped pathname", (OFFSET) namefull);
        !           455:        return (-1);
        !           456:     }
        !           457:     return (asb->sb_size % 2);
        !           458: }
        !           459: 
        !           460: 
        !           461: /* inascii - read in an ASCII cpio header
        !           462:  *
        !           463:  * DESCRIPTION
        !           464:  *
        !           465:  *     Reads an ASCII format cpio header
        !           466:  *
        !           467:  * PARAMETERS
        !           468:  *
        !           469:  *     char    *magic  - magic number to match
        !           470:  *     char    *name   - name of the file which is stored in the header.
        !           471:  *                       (modified and passed back to caller).
        !           472:  *     Stat    *asb    - stat block for the file (modified and passed back
        !           473:  *                       to the caller).
        !           474:  *
        !           475:  * RETURNS
        !           476:  *
        !           477:  *     Returns zero if successful; -1 otherwise. Assumes that  the entire 
        !           478:  *     magic number has been read. 
        !           479:  */
        !           480: 
        !           481: #if __STDC__
        !           482: 
        !           483: static int inascii(char *magic, char *name, Stat *asb)
        !           484: 
        !           485: #else
        !           486:     
        !           487: static int inascii(magic, name, asb)
        !           488: char           *magic;
        !           489: char           *name;
        !           490: Stat           *asb;
        !           491: 
        !           492: #endif
        !           493: {
        !           494:     uint            namelen;
        !           495:     char            header[H_STRLEN + 1];
        !           496: 
        !           497:     if (strncmp(magic, M_ASCII, M_STRLEN) != 0) {
        !           498:        return (-1);
        !           499:     }
        !           500:     if (buf_read(header, H_STRLEN) < 0) {
        !           501:        warnarch("Corrupt ASCII header", (OFFSET) H_STRLEN);
        !           502:        return (-1);
        !           503:     }
        !           504:     header[H_STRLEN] = '\0';
        !           505:     if (sscanf(header, H_SCAN, &asb->sb_dev,
        !           506:               &asb->sb_ino, &asb->sb_mode, &asb->sb_uid,
        !           507:               &asb->sb_gid, &asb->sb_nlink, &asb->sb_rdev,
        !           508:               &asb->sb_mtime, &namelen, &asb->sb_size) != H_COUNT) {
        !           509:        warnarch("Bad ASCII header", (OFFSET) H_STRLEN);
        !           510:        return (-1);
        !           511:     }
        !           512:     if (namelen == 0 || namelen >= PATH_MAX) {
        !           513:        warnarch("Bad ASCII pathname length", (OFFSET) H_STRLEN);
        !           514:        return (-1);
        !           515:     }
        !           516:     if (buf_read(name, namelen) < 0) {
        !           517:        warnarch("Corrupt ASCII pathname", (OFFSET) namelen);
        !           518:        return (-1);
        !           519:     }
        !           520:     if (name[namelen - 1] != '\0') {
        !           521:        warnarch("Bad ASCII pathname", (OFFSET) namelen);
        !           522:        return (-1);
        !           523:     }
        !           524:     return (0);
        !           525: }
        !           526: 
        !           527: 
        !           528: /* inbinary - read a binary header
        !           529:  *
        !           530:  * DESCRIPTION
        !           531:  *
        !           532:  *     Reads a CPIO format binary header.
        !           533:  *
        !           534:  * PARAMETERS
        !           535:  *
        !           536:  *     char    *magic  - magic number to match
        !           537:  *     char    *name   - name of the file which is stored in the header.
        !           538:  *                       (modified and passed back to caller).
        !           539:  *     Stat    *asb    - stat block for the file (modified and passed back
        !           540:  *                       to the caller).
        !           541:  *
        !           542:  * RETURNS
        !           543:  *
        !           544:  *     Returns the number of trailing alignment bytes to skip; -1 if 
        !           545:  *     unsuccessful. 
        !           546:  */
        !           547: 
        !           548: #if __STDC__
        !           549: 
        !           550: static int inbinary(char *magic, char *name, Stat *asb)
        !           551: 
        !           552: #else
        !           553:     
        !           554: static int inbinary(magic, name, asb)
        !           555: char           *magic;
        !           556: char           *name;
        !           557: Stat           *asb;
        !           558: 
        !           559: #endif
        !           560: {
        !           561:     uint            namefull;
        !           562:     Binary          binary;
        !           563: 
        !           564:     if (*((ushort *) magic) != M_BINARY) {
        !           565:        return (-1);
        !           566:     }
        !           567:     memcpy((char *) &binary,
        !           568:                  magic + sizeof(ushort),
        !           569:                  M_STRLEN - sizeof(ushort));
        !           570:     if (buf_read((char *) &binary + M_STRLEN - sizeof(ushort),
        !           571:                 sizeof(binary) - (M_STRLEN - sizeof(ushort))) < 0) {
        !           572:        warnarch("Corrupt binary header",
        !           573:                 (OFFSET) sizeof(binary) - (M_STRLEN - sizeof(ushort)));
        !           574:        return (-1);
        !           575:     }
        !           576:     asb->sb_dev = binary.b_dev;
        !           577:     asb->sb_ino = binary.b_ino;
        !           578:     asb->sb_mode = binary.b_mode;
        !           579:     asb->sb_uid = binary.b_uid;
        !           580:     asb->sb_gid = binary.b_gid;
        !           581:     asb->sb_nlink = binary.b_nlink;
        !           582:     asb->sb_rdev = binary.b_rdev;
        !           583:     asb->sb_mtime = binary.b_mtime[0] << 16 | binary.b_mtime[1];
        !           584:     asb->sb_size = binary.b_size[0] << 16 | binary.b_size[1];
        !           585:     if (binary.b_name == 0 || binary.b_name >= PATH_MAX) {
        !           586:        warnarch("Bad binary pathname length",
        !           587:                 (OFFSET) sizeof(binary) - (M_STRLEN - sizeof(ushort)));
        !           588:        return (-1);
        !           589:     }
        !           590:     if (buf_read(name, namefull = binary.b_name + binary.b_name % 2) < 0) {
        !           591:        warnarch("Corrupt binary pathname", (OFFSET) namefull);
        !           592:        return (-1);
        !           593:     }
        !           594:     if (name[binary.b_name - 1] != '\0') {
        !           595:        warnarch("Bad binary pathname", (OFFSET) namefull);
        !           596:        return (-1);
        !           597:     }
        !           598:     return (asb->sb_size % 2);
        !           599: }

unix.superglobalmegacorp.com

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