Annotation of GNUtools/emacs/src/unexconvex.c, revision 1.1.1.1

1.1       root        1: /* Modified version of unexec for convex machines.
                      2:    Copyright (C) 1985, 1986, 1988 Free Software Foundation, Inc.
                      3: 
                      4:    Note that the GNU project considers support for the peculiarities
                      5:    of the Convex operating system a peripheral activity which should
                      6:    not be allowed to divert effort from development of the GNU system.
                      7:    Changes in this code will be installed when Convex system
                      8:    maintainers send them in, but aside from that we don't plan to
                      9:    think about it, or about whether other Emacs maintenance might
                     10:    break it.
                     11: 
                     12:     This program is free software; you can redistribute it and/or modify
                     13:     it under the terms of the GNU General Public License as published by
                     14:     the Free Software Foundation; either version 1, or (at your option)
                     15:     any later version.
                     16: 
                     17:     This program is distributed in the hope that it will be useful,
                     18:     but WITHOUT ANY WARRANTY; without even the implied warranty of
                     19:     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                     20:     GNU General Public License for more details.
                     21: 
                     22:     You should have received a copy of the GNU General Public License
                     23:     along with this program; if not, write to the Free Software
                     24:     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
                     25: 
                     26: In other words, you are welcome to use, share and improve this program.
                     27: You are forbidden to forbid anyone else to use, share and improve
                     28: what you give them.   Help stamp out software-hoarding!  */
                     29: 
                     30: 
                     31: /* modifyed for C-1 arch by jthomp@convex 871103 */
                     32: /* Corrected to support convex SOFF object file formats and thread specific
                     33:  * regions.  streepy@convex 890302
                     34: */
                     35: 
                     36: /*
                     37:  * unexec.c - Convert a running program into an a.out file.
                     38:  *
                     39:  * Author:     Spencer W. Thomas
                     40:  *             Computer Science Dept.
                     41:  *             University of Utah
                     42:  * Date:       Tue Mar  2 1982
                     43:  * Modified heavily since then.
                     44:  *
                     45:  * Synopsis:
                     46:  *     unexec (new_name, a_name, data_start, bss_start, entry_address)
                     47:  *     char *new_name, *a_name;
                     48:  *     unsigned data_start, bss_start, entry_address;
                     49:  *
                     50:  * Takes a snapshot of the program and makes an a.out format file in the
                     51:  * file named by the string argument new_name.
                     52:  * If a_name is non-NULL, the symbol table will be taken from the given file.
                     53:  * On some machines, an existing a_name file is required.
                     54:  *
                     55:  * The boundaries within the a.out file may be adjusted with the data_start
                     56:  * and bss_start arguments.  Either or both may be given as 0 for defaults.
                     57:  *
                     58:  * Data_start gives the boundary between the text segment and the data
                     59:  * segment of the program.  The text segment can contain shared, read-only
                     60:  * program code and literal data, while the data segment is always unshared
                     61:  * and unprotected.  Data_start gives the lowest unprotected address.
                     62:  * The value you specify may be rounded down to a suitable boundary
                     63:  * as required by the machine you are using.
                     64:  *
                     65:  * Specifying zero for data_start means the boundary between text and data
                     66:  * should not be the same as when the program was loaded.
                     67:  * If NO_REMAP is defined, the argument data_start is ignored and the
                     68:  * segment boundaries are never changed.
                     69:  *
                     70:  * Bss_start indicates how much of the data segment is to be saved in the
                     71:  * a.out file and restored when the program is executed.  It gives the lowest
                     72:  * unsaved address, and is rounded up to a page boundary.  The default when 0
                     73:  * is given assumes that the entire data segment is to be stored, including
                     74:  * the previous data and bss as well as any additional storage allocated with
                     75:  * break (2).
                     76:  *
                     77:  * The new file is set up to start at entry_address.
                     78:  *
                     79:  * If you make improvements I'd like to get them too.
                     80:  * harpo!utah-cs!thomas, thomas@Utah-20
                     81:  *
                     82:  */
                     83: 
                     84: /* There are several compilation parameters affecting unexec:
                     85: 
                     86: * COFF
                     87: 
                     88: Define this if your system uses COFF for executables.
                     89: Otherwise we assume you use Berkeley format.
                     90: 
                     91: * NO_REMAP
                     92: 
                     93: Define this if you do not want to try to save Emacs's pure data areas
                     94: as part of the text segment.
                     95: 
                     96: Saving them as text is good because it allows users to share more.
                     97: 
                     98: However, on machines that locate the text area far from the data area,
                     99: the boundary cannot feasibly be moved.  Such machines require
                    100: NO_REMAP.
                    101: 
                    102: Also, remapping can cause trouble with the built-in startup routine
                    103: /lib/crt0.o, which defines `environ' as an initialized variable.
                    104: Dumping `environ' as pure does not work!  So, to use remapping,
                    105: you must write a startup routine for your machine in Emacs's crt0.c.
                    106: If NO_REMAP is defined, Emacs uses the system's crt0.o.
                    107: 
                    108: * SECTION_ALIGNMENT
                    109: 
                    110: Some machines that use COFF executables require that each section
                    111: start on a certain boundary *in the COFF file*.  Such machines should
                    112: define SECTION_ALIGNMENT to a mask of the low-order bits that must be
                    113: zero on such a boundary.  This mask is used to control padding between
                    114: segments in the COFF file.
                    115: 
                    116: If SECTION_ALIGNMENT is not defined, the segments are written
                    117: consecutively with no attempt at alignment.  This is right for
                    118: unmodified system V.
                    119: 
                    120: * SEGMENT_MASK
                    121: 
                    122: Some machines require that the beginnings and ends of segments
                    123: *in core* be on certain boundaries.  For most machines, a page
                    124: boundary is sufficient.  That is the default.  When a larger
                    125: boundary is needed, define SEGMENT_MASK to a mask of
                    126: the bits that must be zero on such a boundary.
                    127: 
                    128: * A_TEXT_OFFSET(HDR)
                    129: 
                    130: Some machines count the a.out header as part of the size of the text
                    131: segment (a_text); they may actually load the header into core as the
                    132: first data in the text segment.  Some have additional padding between
                    133: the header and the real text of the program that is counted in a_text.
                    134: 
                    135: For these machines, define A_TEXT_OFFSET(HDR) to examine the header
                    136: structure HDR and return the number of bytes to add to `a_text'
                    137: before writing it (above and beyond the number of bytes of actual
                    138: program text).  HDR's standard fields are already correct, except that
                    139: this adjustment to the `a_text' field has not yet been made;
                    140: thus, the amount of offset can depend on the data in the file.
                    141:   
                    142: * A_TEXT_SEEK(HDR)
                    143: 
                    144: If defined, this macro specifies the number of bytes to seek into the
                    145: a.out file before starting to write the text segment.a
                    146: 
                    147: * EXEC_MAGIC
                    148: 
                    149: For machines using COFF, this macro, if defined, is a value stored
                    150: into the magic number field of the output file.
                    151: 
                    152: * ADJUST_EXEC_HEADER
                    153: 
                    154: This macro can be used to generate statements to adjust or
                    155: initialize nonstandard fields in the file header
                    156: 
                    157: * ADDR_CORRECT(ADDR)
                    158: 
                    159: Macro to correct an int which is the bit pattern of a pointer to a byte
                    160: into an int which is the number of a byte.
                    161: 
                    162: This macro has a default definition which is usually right.
                    163: This default definition is a no-op on most machines (where a
                    164: pointer looks like an int) but not on all machines.
                    165: 
                    166: */
                    167: 
                    168: #include "config.h"
                    169: #define PERROR(file) report_error (file, new)
                    170: 
                    171: #include <a.out.h>
                    172: /* Define getpagesize () if the system does not.
                    173:    Note that this may depend on symbols defined in a.out.h
                    174:  */
                    175: #include "getpagesize.h"
                    176: 
                    177: #include <sys/types.h>
                    178: #include <stdio.h>
                    179: #include <sys/stat.h>
                    180: #include <errno.h>
                    181: 
                    182: extern char *start_of_text ();         /* Start of text */
                    183: extern char *start_of_data ();         /* Start of initialized data */
                    184: 
                    185: #include <machine/filehdr.h>
                    186: #include <machine/opthdr.h>
                    187: #include <machine/scnhdr.h>
                    188: #include <machine/pte.h>
                    189: 
                    190: static long block_copy_start;  /* Old executable start point */
                    191: static struct filehdr f_hdr;   /* File header */
                    192: static struct opthdr f_ohdr;   /* Optional file header (a.out) */
                    193: long bias;                     /* Bias to add for growth */
                    194: #define SYMS_START block_copy_start
                    195: 
                    196: static long text_scnptr;
                    197: static long data_scnptr;
                    198: 
                    199: static int pagemask;
                    200: static int pagesz;
                    201: 
                    202: static
                    203: report_error (file, fd)
                    204:      char *file;
                    205:      int fd;
                    206: {
                    207:     if (fd)
                    208:        close (fd);
                    209:     error ("Failure operating on %s", file);
                    210: }
                    211: 
                    212: #define ERROR0(msg) report_error_1 (new, msg, 0, 0); return -1
                    213: #define ERROR1(msg,x) report_error_1 (new, msg, x, 0); return -1
                    214: #define ERROR2(msg,x,y) report_error_1 (new, msg, x, y); return -1
                    215: 
                    216: static
                    217: report_error_1 (fd, msg, a1, a2)
                    218: int fd;
                    219: char *msg;
                    220: int a1, a2;
                    221: {
                    222:     close (fd);
                    223:     error (msg, a1, a2);
                    224: }
                    225: 
                    226: /* ****************************************************************
                    227:  * unexec
                    228:  *
                    229:  * driving logic.
                    230:  */
                    231: unexec (new_name, a_name, data_start, bss_start, entry_address)
                    232: char *new_name, *a_name;
                    233: unsigned data_start, bss_start, entry_address;
                    234: {
                    235:     int new, a_out = -1;
                    236: 
                    237:     if (a_name && (a_out = open (a_name, 0)) < 0) {
                    238:        PERROR (a_name);
                    239:     }
                    240:     if ((new = creat (new_name, 0666)) < 0) {
                    241:        PERROR (new_name);
                    242:     }
                    243: 
                    244:     if (make_hdr (new, a_out, data_start, bss_start, entry_address, a_name, new_name) < 0
                    245:       || copy_text_and_data (new) < 0
                    246:       || copy_sym (new, a_out, a_name, new_name) < 0 ) {
                    247:        close (new);
                    248:        return -1;      
                    249:     }
                    250: 
                    251:     close (new);
                    252:     if (a_out >= 0)
                    253:        close (a_out);
                    254:     mark_x (new_name);
                    255:     return 0;
                    256: }
                    257: 
                    258: /* ****************************************************************
                    259:  * make_hdr
                    260:  *
                    261:  * Make the header in the new a.out from the header in core.
                    262:  * Modify the text and data sizes.
                    263:  */
                    264: 
                    265:  struct scnhdr *stbl;          /* Table of all scnhdr's */
                    266:  struct scnhdr *f_thdr;                /* Text section header */
                    267:  struct scnhdr *f_dhdr;                /* Data section header */
                    268:  struct scnhdr *f_tdhdr;       /* Thread Data section header */
                    269:  struct scnhdr *f_bhdr;                /* Bss section header */
                    270:  struct scnhdr *f_tbhdr;       /* Thread Bss section header */
                    271: 
                    272: static int
                    273: make_hdr (new, a_out, data_start, bss_start, entry_address, a_name, new_name)
                    274:      int new, a_out;
                    275:      unsigned data_start, bss_start, entry_address;
                    276:      char *a_name;
                    277:      char *new_name;
                    278: {
                    279:     register int scns;
                    280:     unsigned int bss_end;
                    281:     unsigned int eo_data;      /* End of initialized data in new exec file */
                    282:     int scntype;               /* Section type */
                    283:     int i;                     /* Var for sorting by vaddr */
                    284:     struct scnhdr scntemp;     /* For swapping entries in sort */
                    285:     extern char *start_of_data();
                    286: 
                    287:     pagemask = (pagesz = getpagesize()) - 1;
                    288: 
                    289:     /* Adjust text/data boundary. */
                    290:     if (!data_start)
                    291:        data_start = (unsigned) start_of_data ();
                    292: 
                    293:     data_start = data_start & ~pagemask; /* (Down) to page boundary. */
                    294: 
                    295:     bss_end = (sbrk(0) + pagemask) & ~pagemask;
                    296: 
                    297:     /* Adjust data/bss boundary. */
                    298:     if (bss_start != 0) {
                    299:        bss_start = (bss_start + pagemask) & ~pagemask;/* (Up) to page bdry. */
                    300:        if (bss_start > bss_end) {
                    301:            ERROR1 ("unexec: Specified bss_start (%x) is past end of program",
                    302:                    bss_start);
                    303:        }
                    304:     } else
                    305:        bss_start = bss_end;
                    306: 
                    307:     if (data_start > bss_start)        { /* Can't have negative data size. */
                    308:        ERROR2 ("unexec: data_start (%x) can't be greater than bss_start (%x)",
                    309:                data_start, bss_start);
                    310:     }
                    311: 
                    312:     /* Salvage as much info from the existing file as possible */
                    313:     if (a_out < 0) {
                    314:        ERROR0 ("can't build a COFF file from scratch yet");
                    315:        /*NOTREACHED*/
                    316:     }
                    317: 
                    318:     if (read (a_out, &f_hdr, sizeof (f_hdr)) != sizeof (f_hdr)) {
                    319:        PERROR (a_name);
                    320:     }
                    321:     block_copy_start += sizeof (f_hdr);
                    322:     if (f_hdr.h_opthdr > 0) {
                    323:        if (read (a_out, &f_ohdr, sizeof (f_ohdr)) != sizeof (f_ohdr)) {
                    324:            PERROR (a_name);
                    325:        }
                    326:        block_copy_start += sizeof (f_ohdr);
                    327:     }
                    328: 
                    329:     /* Allocate room for scn headers */
                    330:     stbl = (struct scnhdr *)malloc( sizeof(struct scnhdr) * f_hdr.h_nscns );
                    331:     if( stbl == NULL ) {
                    332:        ERROR0( "unexec: malloc of stbl failed" );
                    333:     }
                    334: 
                    335:     f_tdhdr = f_tbhdr = NULL;
                    336: 
                    337:     /* Loop through section headers, copying them in */
                    338:     for (scns = 0; scns < f_hdr.h_nscns; scns++) {
                    339: 
                    340:        if( read( a_out, &stbl[scns], sizeof(*stbl)) != sizeof(*stbl)) {
                    341:            PERROR (a_name);
                    342:        }
                    343: 
                    344:        scntype = stbl[scns].s_flags & S_TYPMASK; /* What type of section */
                    345: 
                    346:        if( stbl[scns].s_scnptr > 0L) {
                    347:            if( block_copy_start < stbl[scns].s_scnptr + stbl[scns].s_size )
                    348:                block_copy_start = stbl[scns].s_scnptr + stbl[scns].s_size;
                    349:        }
                    350: 
                    351:        if( scntype == S_TEXT) {
                    352:            f_thdr = &stbl[scns];
                    353:        } else if( scntype == S_DATA) {
                    354:            f_dhdr = &stbl[scns];
                    355: #ifdef S_TDATA
                    356:        } else if( scntype == S_TDATA ) {
                    357:            f_tdhdr = &stbl[scns];
                    358:        } else if( scntype == S_TBSS ) {
                    359:            f_tbhdr = &stbl[scns];
                    360: #endif /* S_TDATA (thread stuff) */
                    361: 
                    362:        } else if( scntype == S_BSS) {
                    363:            f_bhdr = &stbl[scns];
                    364:        }
                    365: 
                    366:     }
                    367: 
                    368:     /* We will now convert TEXT and DATA into TEXT, BSS into DATA, and leave
                    369:      * all thread stuff alone.
                    370:      */
                    371: 
                    372:     /* Now we alter the contents of all the f_*hdr variables
                    373:        to correspond to what we want to dump.  */
                    374: 
                    375:     f_thdr->s_vaddr = (long) start_of_text ();
                    376:     f_thdr->s_size = data_start - f_thdr->s_vaddr;
                    377:     f_thdr->s_scnptr = pagesz;
                    378:     f_thdr->s_relptr = 0;
                    379:     f_thdr->s_nrel = 0;
                    380: 
                    381:     eo_data = f_thdr->s_scnptr + f_thdr->s_size;
                    382: 
                    383:     if( f_tdhdr ) {            /* Process thread data */
                    384: 
                    385:        f_tdhdr->s_vaddr = data_start;
                    386:        f_tdhdr->s_size += f_dhdr->s_size - (data_start - f_dhdr->s_vaddr);
                    387:        f_tdhdr->s_scnptr = eo_data;
                    388:        f_tdhdr->s_relptr = 0;
                    389:        f_tdhdr->s_nrel = 0;
                    390: 
                    391:        eo_data += f_tdhdr->s_size;
                    392: 
                    393:        /* And now for DATA */
                    394: 
                    395:        f_dhdr->s_vaddr = f_bhdr->s_vaddr; /* Take BSS start address */
                    396:        f_dhdr->s_size = bss_end - f_bhdr->s_vaddr;
                    397:        f_dhdr->s_scnptr = eo_data;
                    398:        f_dhdr->s_relptr = 0;
                    399:        f_dhdr->s_nrel = 0;
                    400: 
                    401:        eo_data += f_dhdr->s_size;
                    402: 
                    403:     } else {
                    404: 
                    405:        f_dhdr->s_vaddr = data_start;
                    406:        f_dhdr->s_size = bss_start - data_start;
                    407:        f_dhdr->s_scnptr = eo_data;
                    408:        f_dhdr->s_relptr = 0;
                    409:        f_dhdr->s_nrel = 0;
                    410: 
                    411:        eo_data += f_dhdr->s_size;
                    412: 
                    413:     }
                    414: 
                    415:     f_bhdr->s_vaddr = bss_start;
                    416:     f_bhdr->s_size = bss_end - bss_start + pagesz /* fudge */;
                    417:     f_bhdr->s_scnptr = 0;
                    418:     f_bhdr->s_relptr = 0;
                    419:     f_bhdr->s_nrel = 0;
                    420: 
                    421:     text_scnptr = f_thdr->s_scnptr;
                    422:     data_scnptr = f_dhdr->s_scnptr;
                    423:     bias = eo_data - block_copy_start;
                    424: 
                    425:     if (f_ohdr.o_symptr > 0L) {
                    426:        f_ohdr.o_symptr += bias;
                    427:     }
                    428: 
                    429:     if (f_hdr.h_strptr > 0) {
                    430:        f_hdr.h_strptr += bias;
                    431:     }
                    432: 
                    433:     if (write (new, &f_hdr, sizeof (f_hdr)) != sizeof (f_hdr)) {
                    434:        PERROR (new_name);
                    435:     }
                    436: 
                    437:     if (write (new, &f_ohdr, sizeof (f_ohdr)) != sizeof (f_ohdr)) {
                    438:        PERROR (new_name);
                    439:     }
                    440: 
                    441:     for( scns = 0; scns < f_hdr.h_nscns; scns++ ) {
                    442: 
                    443:        /* This is a cheesey little loop to write out the section headers
                    444:         * in order of increasing virtual address. Dull but effective.
                    445:         */
                    446: 
                    447:        for( i = scns+1; i < f_hdr.h_nscns; i++ ) {
                    448:            if( stbl[i].s_vaddr < stbl[scns].s_vaddr ) { /* Swap */
                    449:                scntemp = stbl[i];
                    450:                stbl[i] = stbl[scns];
                    451:                stbl[scns] = scntemp;
                    452:            }
                    453:        }
                    454: 
                    455:     }
                    456: 
                    457:     for( scns = 0; scns < f_hdr.h_nscns; scns++ ) {
                    458: 
                    459:        if( write( new, &stbl[scns], sizeof(*stbl)) != sizeof(*stbl)) {
                    460:            PERROR (new_name);
                    461:        }
                    462: 
                    463:     }
                    464: 
                    465:     return (0);
                    466: 
                    467: }
                    468: 
                    469: /* ****************************************************************
                    470:  * copy_text_and_data
                    471:  *
                    472:  * Copy the text and data segments from memory to the new a.out
                    473:  */
                    474: static int
                    475: copy_text_and_data (new)
                    476: int new;
                    477: {
                    478:     register int scns;
                    479: 
                    480:     for( scns = 0; scns < f_hdr.h_nscns; scns++ )
                    481:        write_segment( new, &stbl[scns] );
                    482: 
                    483:     return 0;
                    484: }
                    485: 
                    486: write_segment( new, sptr )
                    487: int new;
                    488: struct scnhdr *sptr;
                    489: {
                    490:     register char *ptr, *end;
                    491:     register int nwrite, ret;
                    492:     char buf[80];
                    493:     extern int errno;
                    494:     char zeros[128];
                    495: 
                    496:     if( sptr->s_scnptr == 0 )
                    497:        return;                 /* Nothing to do */
                    498: 
                    499:     if( lseek( new, (long) sptr->s_scnptr, 0 ) == -1 )
                    500:        PERROR( "unexecing" );
                    501: 
                    502:     bzero (zeros, sizeof zeros);
                    503: 
                    504:     ptr = (char *) sptr->s_vaddr;
                    505:     end = ptr + sptr->s_size;
                    506: 
                    507:     while( ptr < end ) {
                    508: 
                    509:        /* distance to next multiple of 128.  */
                    510:        nwrite = (((int) ptr + 128) & -128) - (int) ptr;
                    511:        /* But not beyond specified end.  */
                    512:        if (nwrite > end - ptr) nwrite = end - ptr;
                    513:        ret = write (new, ptr, nwrite);
                    514:        /* If write gets a page fault, it means we reached
                    515:           a gap between the old text segment and the old data segment.
                    516:           This gap has probably been remapped into part of the text segment.
                    517:           So write zeros for it.  */
                    518:        if (ret == -1 && errno == EFAULT)
                    519:            write (new, zeros, nwrite);
                    520:        else if (nwrite != ret) {
                    521:            sprintf (buf,
                    522:                     "unexec write failure: addr 0x%x, fileno %d, size 0x%x, wrote 0x%x, errno %d",
                    523:                     ptr, new, nwrite, ret, errno);
                    524:            PERROR (buf);
                    525:        }
                    526:        ptr += nwrite;
                    527:     }
                    528: }
                    529: 
                    530: /* ****************************************************************
                    531:  * copy_sym
                    532:  *
                    533:  * Copy the relocation information and symbol table from the a.out to the new
                    534:  */
                    535: static int
                    536: copy_sym (new, a_out, a_name, new_name)
                    537:      int new, a_out;
                    538:      char *a_name, *new_name;
                    539: {
                    540:     char page[1024];
                    541:     int n;
                    542: 
                    543:     if (a_out < 0)
                    544:        return 0;
                    545: 
                    546:     if (SYMS_START == 0L)
                    547:        return 0;
                    548: 
                    549:     lseek (a_out, SYMS_START, 0);      /* Position a.out to symtab. */
                    550:     lseek( new, (long)f_ohdr.o_symptr, 0 );
                    551: 
                    552:     while ((n = read (a_out, page, sizeof page)) > 0) {
                    553:        if (write (new, page, n) != n) {
                    554:            PERROR (new_name);
                    555:        }
                    556:     }
                    557:     if (n < 0) {
                    558:        PERROR (a_name);
                    559:     }
                    560:     return 0;
                    561: }
                    562: 
                    563: /* ****************************************************************
                    564:  * mark_x
                    565:  *
                    566:  * After succesfully building the new a.out, mark it executable
                    567:  */
                    568: static
                    569: mark_x (name)
                    570: char *name;
                    571: {
                    572:     struct stat sbuf;
                    573:     int um;
                    574:     int new = 0;  /* for PERROR */
                    575: 
                    576:     um = umask (777);
                    577:     umask (um);
                    578:     if (stat (name, &sbuf) == -1) {
                    579:        PERROR (name);
                    580:     }
                    581:     sbuf.st_mode |= 0111 & ~um;
                    582:     if (chmod (name, sbuf.st_mode) == -1)
                    583:        PERROR (name);
                    584: }
                    585: 
                    586: /* Find the first pty letter.  This is usually 'p', as in ptyp0, but
                    587:    is sometimes configured down to 'm', 'n', or 'o' for some reason. */
                    588: 
                    589: first_pty_letter ()
                    590: {
                    591:   struct stat buf;
                    592:   char pty_name[16];
                    593:   char c;
                    594: 
                    595:   for (c = 'o'; c >= 'a'; c--)
                    596:     {
                    597:       sprintf (pty_name, "/dev/pty%c0", c);
                    598:       if (stat (pty_name, &buf) < 0)
                    599:        return c + 1;
                    600:     }
                    601:   return 'a';
                    602: }
                    603: 

unix.superglobalmegacorp.com

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