Annotation of Gnu-Mach/ipc/ipc_hash.c, revision 1.1.1.1

1.1       root        1: /* 
                      2:  * Mach Operating System
                      3:  * Copyright (c) 1991,1990,1989 Carnegie Mellon University
                      4:  * All Rights Reserved.
                      5:  * 
                      6:  * Permission to use, copy, modify and distribute this software and its
                      7:  * documentation is hereby granted, provided that both the copyright
                      8:  * notice and this permission notice appear in all copies of the
                      9:  * software, derivative works or modified versions, and any portions
                     10:  * thereof, and that both notices appear in supporting documentation.
                     11:  * 
                     12:  * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
                     13:  * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
                     14:  * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
                     15:  * 
                     16:  * Carnegie Mellon requests users of this software to return to
                     17:  * 
                     18:  *  Software Distribution Coordinator  or  [email protected]
                     19:  *  School of Computer Science
                     20:  *  Carnegie Mellon University
                     21:  *  Pittsburgh PA 15213-3890
                     22:  * 
                     23:  * any improvements or extensions that they make and grant Carnegie Mellon
                     24:  * the rights to redistribute these changes.
                     25:  */
                     26: /*
                     27:  *     File:   ipc/ipc_hash.c
                     28:  *     Author: Rich Draves
                     29:  *     Date:   1989
                     30:  *
                     31:  *     Entry hash table operations.
                     32:  */
                     33: 
                     34: #include <mach/boolean.h>
                     35: #include <mach/port.h>
                     36: #include <kern/lock.h>
                     37: #include <kern/kalloc.h>
                     38: #include <ipc/port.h>
                     39: #include <ipc/ipc_space.h>
                     40: #include <ipc/ipc_object.h>
                     41: #include <ipc/ipc_entry.h>
                     42: #include <ipc/ipc_hash.h>
                     43: #include <ipc/ipc_init.h>
                     44: #include <ipc/ipc_types.h>
                     45: 
                     46: #include <mach_ipc_debug.h>
                     47: #if    MACH_IPC_DEBUG
                     48: #include <mach/kern_return.h>
                     49: #include <mach_debug/hash_info.h>
                     50: #include <vm/vm_map.h>
                     51: #include <vm/vm_kern.h>
                     52: #include <vm/vm_user.h>
                     53: #endif
                     54: 
                     55: 
                     56: 
                     57: /*
                     58:  *     Routine:        ipc_hash_lookup
                     59:  *     Purpose:
                     60:  *             Converts (space, obj) -> (name, entry).
                     61:  *             Returns TRUE if an entry was found.
                     62:  *     Conditions:
                     63:  *             The space must be locked (read or write) throughout.
                     64:  */
                     65: 
                     66: boolean_t
                     67: ipc_hash_lookup(space, obj, namep, entryp)
                     68:        ipc_space_t space;
                     69:        ipc_object_t obj;
                     70:        mach_port_t *namep;
                     71:        ipc_entry_t *entryp;
                     72: {
                     73:        return (ipc_hash_local_lookup(space, obj, namep, entryp) ||
                     74:                ((space->is_tree_hash > 0) &&
                     75:                 ipc_hash_global_lookup(space, obj, namep,
                     76:                                        (ipc_tree_entry_t *) entryp)));
                     77: }
                     78: 
                     79: /*
                     80:  *     Routine:        ipc_hash_insert
                     81:  *     Purpose:
                     82:  *             Inserts an entry into the appropriate reverse hash table,
                     83:  *             so that ipc_hash_lookup will find it.
                     84:  *     Conditions:
                     85:  *             The space must be write-locked.
                     86:  */
                     87: 
                     88: void
                     89: ipc_hash_insert(
                     90:        ipc_space_t     space,
                     91:        ipc_object_t    obj,
                     92:        mach_port_t     name,
                     93:        ipc_entry_t     entry)
                     94: {
                     95:        mach_port_index_t index;
                     96: 
                     97:        index = MACH_PORT_INDEX(name);
                     98:        if ((index < space->is_table_size) &&
                     99:            (entry == &space->is_table[index]))
                    100:                ipc_hash_local_insert(space, obj, index, entry);
                    101:        else
                    102:                ipc_hash_global_insert(space, obj, name,
                    103:                                       (ipc_tree_entry_t) entry);
                    104: }
                    105: 
                    106: /*
                    107:  *     Routine:        ipc_hash_delete
                    108:  *     Purpose:
                    109:  *             Deletes an entry from the appropriate reverse hash table.
                    110:  *     Conditions:
                    111:  *             The space must be write-locked.
                    112:  */
                    113: 
                    114: void
                    115: ipc_hash_delete(
                    116:        ipc_space_t     space,
                    117:        ipc_object_t    obj,
                    118:        mach_port_t     name,
                    119:        ipc_entry_t     entry)
                    120: {
                    121:        mach_port_index_t index;
                    122: 
                    123:        index = MACH_PORT_INDEX(name);
                    124:        if ((index < space->is_table_size) &&
                    125:            (entry == &space->is_table[index]))
                    126:                ipc_hash_local_delete(space, obj, index, entry);
                    127:        else
                    128:                ipc_hash_global_delete(space, obj, name,
                    129:                                       (ipc_tree_entry_t) entry);
                    130: }
                    131: 
                    132: /*
                    133:  *     The global reverse hash table holds splay tree entries.
                    134:  *     It is a simple open-chaining hash table with singly-linked buckets.
                    135:  *     Each bucket is locked separately, with an exclusive lock.
                    136:  *     Within each bucket, move-to-front is used.
                    137:  */
                    138: 
                    139: typedef natural_t ipc_hash_index_t;
                    140: 
                    141: ipc_hash_index_t ipc_hash_global_size;
                    142: ipc_hash_index_t ipc_hash_global_mask;
                    143: 
                    144: #define IH_GLOBAL_HASH(space, obj)                                     \
                    145:        (((((ipc_hash_index_t) ((vm_offset_t)space)) >> 4) +            \
                    146:          (((ipc_hash_index_t) ((vm_offset_t)obj)) >> 6)) &             \
                    147:         ipc_hash_global_mask)
                    148: 
                    149: typedef struct ipc_hash_global_bucket {
                    150:        decl_simple_lock_data(, ihgb_lock_data)
                    151:        ipc_tree_entry_t ihgb_head;
                    152: } *ipc_hash_global_bucket_t;
                    153: 
                    154: #define        IHGB_NULL       ((ipc_hash_global_bucket_t) 0)
                    155: 
                    156: #define        ihgb_lock_init(ihgb)    simple_lock_init(&(ihgb)->ihgb_lock_data)
                    157: #define        ihgb_lock(ihgb)         simple_lock(&(ihgb)->ihgb_lock_data)
                    158: #define        ihgb_unlock(ihgb)       simple_unlock(&(ihgb)->ihgb_lock_data)
                    159: 
                    160: ipc_hash_global_bucket_t ipc_hash_global_table;
                    161: 
                    162: /*
                    163:  *     Routine:        ipc_hash_global_lookup
                    164:  *     Purpose:
                    165:  *             Converts (space, obj) -> (name, entry).
                    166:  *             Looks in the global table, for splay tree entries.
                    167:  *             Returns TRUE if an entry was found.
                    168:  *     Conditions:
                    169:  *             The space must be locked (read or write) throughout.
                    170:  */
                    171: 
                    172: boolean_t
                    173: ipc_hash_global_lookup(
                    174:        ipc_space_t             space,
                    175:        ipc_object_t            obj,
                    176:        mach_port_t             *namep,
                    177:        ipc_tree_entry_t        *entryp)
                    178: {
                    179:        ipc_hash_global_bucket_t bucket;
                    180:        ipc_tree_entry_t this, *last;
                    181: 
                    182:        assert(space != IS_NULL);
                    183:        assert(obj != IO_NULL);
                    184: 
                    185:        bucket = &ipc_hash_global_table[IH_GLOBAL_HASH(space, obj)];
                    186:        ihgb_lock(bucket);
                    187: 
                    188:        if ((this = bucket->ihgb_head) != ITE_NULL) {
                    189:                if ((this->ite_object == obj) &&
                    190:                    (this->ite_space == space)) {
                    191:                        /* found it at front; no need to move */
                    192: 
                    193:                        *namep = this->ite_name;
                    194:                        *entryp = this;
                    195:                } else for (last = &this->ite_next;
                    196:                            (this = *last) != ITE_NULL;
                    197:                            last = &this->ite_next) {
                    198:                        if ((this->ite_object == obj) &&
                    199:                            (this->ite_space == space)) {
                    200:                                /* found it; move to front */
                    201: 
                    202:                                *last = this->ite_next;
                    203:                                this->ite_next = bucket->ihgb_head;
                    204:                                bucket->ihgb_head = this;
                    205: 
                    206:                                *namep = this->ite_name;
                    207:                                *entryp = this;
                    208:                                break;
                    209:                        }
                    210:                }
                    211:        }
                    212: 
                    213:        ihgb_unlock(bucket);
                    214:        return this != ITE_NULL;
                    215: }
                    216: 
                    217: /*
                    218:  *     Routine:        ipc_hash_global_insert
                    219:  *     Purpose:
                    220:  *             Inserts an entry into the global reverse hash table.
                    221:  *     Conditions:
                    222:  *             The space must be write-locked.
                    223:  */
                    224: 
                    225: void
                    226: ipc_hash_global_insert(
                    227:        ipc_space_t             space,
                    228:        ipc_object_t            obj,
                    229:        mach_port_t             name,
                    230:        ipc_tree_entry_t        entry)
                    231: {
                    232:        ipc_hash_global_bucket_t bucket;
                    233: 
                    234: 
                    235:        assert(entry->ite_name == name);
                    236:        assert(space != IS_NULL);
                    237:        assert(entry->ite_space == space);
                    238:        assert(obj != IO_NULL);
                    239:        assert(entry->ite_object == obj);
                    240: 
                    241:        space->is_tree_hash++;
                    242:        assert(space->is_tree_hash <= space->is_tree_total);
                    243: 
                    244:        bucket = &ipc_hash_global_table[IH_GLOBAL_HASH(space, obj)];
                    245:        ihgb_lock(bucket);
                    246: 
                    247:        /* insert at front of bucket */
                    248: 
                    249:        entry->ite_next = bucket->ihgb_head;
                    250:        bucket->ihgb_head = entry;
                    251: 
                    252:        ihgb_unlock(bucket);
                    253: }
                    254: 
                    255: /*
                    256:  *     Routine:        ipc_hash_global_delete
                    257:  *     Purpose:
                    258:  *             Deletes an entry from the global reverse hash table.
                    259:  *     Conditions:
                    260:  *             The space must be write-locked.
                    261:  */
                    262: 
                    263: void
                    264: ipc_hash_global_delete(
                    265:        ipc_space_t             space,
                    266:        ipc_object_t            obj,
                    267:        mach_port_t             name,
                    268:        ipc_tree_entry_t        entry)
                    269: {
                    270:        ipc_hash_global_bucket_t bucket;
                    271:        ipc_tree_entry_t this, *last;
                    272: 
                    273:        assert(entry->ite_name == name);
                    274:        assert(space != IS_NULL);
                    275:        assert(entry->ite_space == space);
                    276:        assert(obj != IO_NULL);
                    277:        assert(entry->ite_object == obj);
                    278: 
                    279:        assert(space->is_tree_hash > 0);
                    280:        space->is_tree_hash--;
                    281: 
                    282:        bucket = &ipc_hash_global_table[IH_GLOBAL_HASH(space, obj)];
                    283:        ihgb_lock(bucket);
                    284: 
                    285:        for (last = &bucket->ihgb_head;
                    286:             (this = *last) != ITE_NULL;
                    287:             last = &this->ite_next) {
                    288:                if (this == entry) {
                    289:                        /* found it; remove from bucket */
                    290: 
                    291:                        *last = this->ite_next;
                    292:                        break;
                    293:                }
                    294:        }
                    295:        assert(this != ITE_NULL);
                    296: 
                    297:        ihgb_unlock(bucket);
                    298: }
                    299: 
                    300: /*
                    301:  *     Each space has a local reverse hash table, which holds
                    302:  *     entries from the space's table.  In fact, the hash table
                    303:  *     just uses a field (ie_index) in the table itself.
                    304:  *
                    305:  *     The local hash table is an open-addressing hash table,
                    306:  *     which means that when a collision occurs, instead of
                    307:  *     throwing the entry into a bucket, the entry is rehashed
                    308:  *     to another position in the table.  In this case the rehash
                    309:  *     is very simple: linear probing (ie, just increment the position).
                    310:  *     This simple rehash makes deletions tractable (they're still a pain),
                    311:  *     but it means that collisions tend to build up into clumps.
                    312:  *
                    313:  *     Because at least one entry in the table (index 0) is always unused,
                    314:  *     there will always be room in the reverse hash table.  If a table
                    315:  *     with n slots gets completely full, the reverse hash table will
                    316:  *     have one giant clump of n-1 slots and one free slot somewhere.
                    317:  *     Because entries are only entered into the reverse table if they
                    318:  *     are pure send rights (not receive, send-once, port-set,
                    319:  *     or dead-name rights), and free entries of course aren't entered,
                    320:  *     I expect the reverse hash table won't get unreasonably full.
                    321:  *
                    322:  *     Ordered hash tables (Amble & Knuth, Computer Journal, v. 17, no. 2,
                    323:  *     pp. 135-142.) may be desirable here.  They can dramatically help
                    324:  *     unsuccessful lookups.  But unsuccessful lookups are almost always
                    325:  *     followed by insertions, and those slow down somewhat.  They
                    326:  *     also can help deletions somewhat.  Successful lookups aren't affected.
                    327:  *     So possibly a small win; probably nothing significant.
                    328:  */
                    329: 
                    330: #define        IH_LOCAL_HASH(obj, size)                                \
                    331:                ((((mach_port_index_t) (obj)) >> 6) % (size))
                    332: 
                    333: /*
                    334:  *     Routine:        ipc_hash_local_lookup
                    335:  *     Purpose:
                    336:  *             Converts (space, obj) -> (name, entry).
                    337:  *             Looks in the space's local table, for table entries.
                    338:  *             Returns TRUE if an entry was found.
                    339:  *     Conditions:
                    340:  *             The space must be locked (read or write) throughout.
                    341:  */
                    342: 
                    343: boolean_t
                    344: ipc_hash_local_lookup(
                    345:        ipc_space_t     space,
                    346:        ipc_object_t    obj,
                    347:        mach_port_t     *namep,
                    348:        ipc_entry_t     *entryp)
                    349: {
                    350:        ipc_entry_t table;
                    351:        ipc_entry_num_t size;
                    352:        mach_port_index_t hindex, index;
                    353: 
                    354:        assert(space != IS_NULL);
                    355:        assert(obj != IO_NULL);
                    356: 
                    357:        table = space->is_table;
                    358:        size = space->is_table_size;
                    359:        hindex = IH_LOCAL_HASH(obj, size);
                    360: 
                    361:        /*
                    362:         *      Ideally, table[hindex].ie_index is the name we want.
                    363:         *      However, must check ie_object to verify this,
                    364:         *      because collisions can happen.  In case of a collision,
                    365:         *      search farther along in the clump.
                    366:         */
                    367: 
                    368:        while ((index = table[hindex].ie_index) != 0) {
                    369:                ipc_entry_t entry = &table[index];
                    370: 
                    371:                if (entry->ie_object == obj) {
                    372:                        *namep = MACH_PORT_MAKEB(index, entry->ie_bits);
                    373:                        *entryp = entry;
                    374:                        return TRUE;
                    375:                }
                    376: 
                    377:                if (++hindex == size)
                    378:                        hindex = 0;
                    379:        }
                    380: 
                    381:        return FALSE;
                    382: }
                    383: 
                    384: /*
                    385:  *     Routine:        ipc_hash_local_insert
                    386:  *     Purpose:
                    387:  *             Inserts an entry into the space's reverse hash table.
                    388:  *     Conditions:
                    389:  *             The space must be write-locked.
                    390:  */
                    391: 
                    392: void
                    393: ipc_hash_local_insert(
                    394:        ipc_space_t             space,
                    395:        ipc_object_t            obj,
                    396:        mach_port_index_t       index,
                    397:        ipc_entry_t             entry)
                    398: {
                    399:        ipc_entry_t table;
                    400:        ipc_entry_num_t size;
                    401:        mach_port_index_t hindex;
                    402: 
                    403:        assert(index != 0);
                    404:        assert(space != IS_NULL);
                    405:        assert(obj != IO_NULL);
                    406: 
                    407:        table = space->is_table;
                    408:        size = space->is_table_size;
                    409:        hindex = IH_LOCAL_HASH(obj, size);
                    410: 
                    411:        assert(entry == &table[index]);
                    412:        assert(entry->ie_object == obj);
                    413: 
                    414:        /*
                    415:         *      We want to insert at hindex, but there may be collisions.
                    416:         *      If a collision occurs, search for the end of the clump
                    417:         *      and insert there.
                    418:         */
                    419: 
                    420:        while (table[hindex].ie_index != 0) {
                    421:                if (++hindex == size)
                    422:                        hindex = 0;
                    423:        }
                    424: 
                    425:        table[hindex].ie_index = index;
                    426: }
                    427: 
                    428: /*
                    429:  *     Routine:        ipc_hash_local_delete
                    430:  *     Purpose:
                    431:  *             Deletes an entry from the space's reverse hash table.
                    432:  *     Conditions:
                    433:  *             The space must be write-locked.
                    434:  */
                    435: 
                    436: void
                    437: ipc_hash_local_delete(
                    438:        ipc_space_t             space,
                    439:        ipc_object_t            obj,
                    440:        mach_port_index_t       index,
                    441:        ipc_entry_t             entry)
                    442: {
                    443:        ipc_entry_t table;
                    444:        ipc_entry_num_t size;
                    445:        mach_port_index_t hindex, dindex;
                    446: 
                    447:        assert(index != MACH_PORT_NULL);
                    448:        assert(space != IS_NULL);
                    449:        assert(obj != IO_NULL);
                    450: 
                    451:        table = space->is_table;
                    452:        size = space->is_table_size;
                    453:        hindex = IH_LOCAL_HASH(obj, size);
                    454: 
                    455:        assert(entry == &table[index]);
                    456:        assert(entry->ie_object == obj);
                    457: 
                    458:        /*
                    459:         *      First check we have the right hindex for this index.
                    460:         *      In case of collision, we have to search farther
                    461:         *      along in this clump.
                    462:         */
                    463: 
                    464:        while (table[hindex].ie_index != index) {
                    465:                if (table[hindex].ie_index == 0)
                    466:                {
                    467:                        static int gak = 0;
                    468:                        if (gak == 0)
                    469:                        {
                    470:                                printf("gak! entry wasn't in hash table!\n");
                    471:                                gak = 1;
                    472:                        }
                    473:                        return;
                    474:                }
                    475:                if (++hindex == size)
                    476:                        hindex = 0;
                    477:        }
                    478: 
                    479:        /*
                    480:         *      Now we want to set table[hindex].ie_index = 0.
                    481:         *      But if we aren't the last index in a clump,
                    482:         *      this might cause problems for lookups of objects
                    483:         *      farther along in the clump that are displaced
                    484:         *      due to collisions.  Searches for them would fail
                    485:         *      at hindex instead of succeeding.
                    486:         *
                    487:         *      So we must check the clump after hindex for objects
                    488:         *      that are so displaced, and move one up to the new hole.
                    489:         *
                    490:         *              hindex - index of new hole in the clump
                    491:         *              dindex - index we are checking for a displaced object
                    492:         *
                    493:         *      When we move a displaced object up into the hole,
                    494:         *      it creates a new hole, and we have to repeat the process
                    495:         *      until we get to the end of the clump.
                    496:         */
                    497: 
                    498:        for (dindex = hindex; index != 0; hindex = dindex) {
                    499:                for (;;) {
                    500:                        mach_port_index_t tindex;
                    501:                        ipc_object_t tobj;
                    502: 
                    503:                        if (++dindex == size)
                    504:                                dindex = 0;
                    505:                        assert(dindex != hindex);
                    506: 
                    507:                        /* are we at the end of the clump? */
                    508: 
                    509:                        index = table[dindex].ie_index;
                    510:                        if (index == 0)
                    511:                                break;
                    512: 
                    513:                        /* is this a displaced object? */
                    514: 
                    515:                        tobj = table[index].ie_object;
                    516:                        assert(tobj != IO_NULL);
                    517:                        tindex = IH_LOCAL_HASH(tobj, size);
                    518: 
                    519:                        if ((dindex < hindex) ?
                    520:                            ((dindex < tindex) && (tindex <= hindex)) :
                    521:                            ((dindex < tindex) || (tindex <= hindex)))
                    522:                                break;
                    523:                }
                    524: 
                    525:                table[hindex].ie_index = index;
                    526:        }
                    527: }
                    528: 
                    529: /*
                    530:  *     Routine:        ipc_hash_init
                    531:  *     Purpose:
                    532:  *             Initialize the reverse hash table implementation.
                    533:  */
                    534: 
                    535: void
                    536: ipc_hash_init(void)
                    537: {
                    538:        ipc_hash_index_t i;
                    539: 
                    540:        /* if not configured, initialize ipc_hash_global_size */
                    541: 
                    542:        if (ipc_hash_global_size == 0) {
                    543:                ipc_hash_global_size = ipc_tree_entry_max >> 8;
                    544:                if (ipc_hash_global_size < 32)
                    545:                        ipc_hash_global_size = 32;
                    546:        }
                    547: 
                    548:        /* make sure it is a power of two */
                    549: 
                    550:        ipc_hash_global_mask = ipc_hash_global_size - 1;
                    551:        if ((ipc_hash_global_size & ipc_hash_global_mask) != 0) {
                    552:                natural_t bit;
                    553: 
                    554:                /* round up to closest power of two */
                    555: 
                    556:                for (bit = 1;; bit <<= 1) {
                    557:                        ipc_hash_global_mask |= bit;
                    558:                        ipc_hash_global_size = ipc_hash_global_mask + 1;
                    559: 
                    560:                        if ((ipc_hash_global_size & ipc_hash_global_mask) == 0)
                    561:                                break;
                    562:                }
                    563:        }
                    564: 
                    565:        /* allocate ipc_hash_global_table */
                    566: 
                    567:        ipc_hash_global_table = (ipc_hash_global_bucket_t)
                    568:                kalloc((vm_size_t) (ipc_hash_global_size *
                    569:                                    sizeof(struct ipc_hash_global_bucket)));
                    570:        assert(ipc_hash_global_table != IHGB_NULL);
                    571: 
                    572:        /* and initialize it */
                    573: 
                    574:        for (i = 0; i < ipc_hash_global_size; i++) {
                    575:                ipc_hash_global_bucket_t bucket;
                    576: 
                    577:                bucket = &ipc_hash_global_table[i];
                    578:                ihgb_lock_init(bucket);
                    579:                bucket->ihgb_head = ITE_NULL;
                    580:        }
                    581: }
                    582: 
                    583: #if    MACH_IPC_DEBUG
                    584: 
                    585: /*
                    586:  *     Routine:        ipc_hash_info
                    587:  *     Purpose:
                    588:  *             Return information about the global reverse hash table.
                    589:  *             Fills the buffer with as much information as possible
                    590:  *             and returns the desired size of the buffer.
                    591:  *     Conditions:
                    592:  *             Nothing locked.  The caller should provide
                    593:  *             possibly-pageable memory.
                    594:  */
                    595: 
                    596: 
                    597: ipc_hash_index_t
                    598: ipc_hash_info(
                    599:        hash_info_bucket_t      *info,
                    600:        mach_msg_type_number_t count)
                    601: {
                    602:        ipc_hash_index_t i;
                    603: 
                    604:        if (ipc_hash_global_size < count)
                    605:                count = ipc_hash_global_size;
                    606: 
                    607:        for (i = 0; i < count; i++) {
                    608:                ipc_hash_global_bucket_t bucket = &ipc_hash_global_table[i];
                    609:                unsigned int bucket_count = 0;
                    610:                ipc_tree_entry_t entry;
                    611: 
                    612:                ihgb_lock(bucket);
                    613:                for (entry = bucket->ihgb_head;
                    614:                     entry != ITE_NULL;
                    615:                     entry = entry->ite_next)
                    616:                        bucket_count++;
                    617:                ihgb_unlock(bucket);
                    618: 
                    619:                /* don't touch pageable memory while holding locks */
                    620:                info[i].hib_count = bucket_count;
                    621:        }
                    622: 
                    623:        return ipc_hash_global_size;
                    624: }
                    625: 
                    626: #endif /* MACH_IPC_DEBUG */

unix.superglobalmegacorp.com

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