Annotation of coherent/b/STREAMS/conf/loop/src/loop.c, revision 1.1

1.1     ! root        1: /*
        !             2:  * This loop-around driver has been transcribed verbatim from the System V,
        !             3:  * Release 4 Multiprocessor "Programmer's Guide : STREAMS" manual, from
        !             4:  * Chapter 4. This functions as a simple test for compatibility.
        !             5:  *
        !             6:  * Some minor editing has proved necessary due to this driver's use of header
        !             7:  * files not documented in the DDI/DKI. The following files were either
        !             8:  * #included by the original source, but are not present in this system, or
        !             9:  * were never used in the example. Additional imports not present in the
        !            10:  * original code are documented below.
        !            11:  *
        !            12:  * It may well be that the example is correct in importing these headers, and
        !            13:  * the "synopsis" lines in the DDI/DKI documentation are incorrect.
        !            14:  */
        !            15: 
        !            16: #define        _DDI_DKI        1
        !            17: #define        _SYSV4          1
        !            18: 
        !            19: /*
        !            20:  *-IMPORTS:
        !            21:  *     <common/ccompat.h>
        !            22:  *             __USE_PROTO__
        !            23:  *             __ARGS ()
        !            24:  *             __PROTO ()
        !            25:  *     <sys/kmem.h>
        !            26:  *             KM_NOSLEEP
        !            27:  *     <sys/ksynch.h>
        !            28:  *             lkinfo_t
        !            29:  *             lock_t
        !            30:  *             LOCK_ALLOC ()
        !            31:  *             LOCK ()
        !            32:  *             UNLOCK ()
        !            33:  *     <sys/cmn_err.h>
        !            34:  *             CE_PANIC
        !            35:  *             CE_WARN
        !            36:  *             cmn_err ()
        !            37:  *     <sys/errno.h>
        !            38:  *             ENXIO
        !            39:  *             EBUSY
        !            40:  *             EINVAL
        !            41:  *     <stddef.h>
        !            42:  *             NULL
        !            43:  */
        !            44: 
        !            45: #include <common/ccompat.h>
        !            46: #include <sys/kmem.h>
        !            47: #include <sys/ksynch.h>
        !            48: #include <sys/cmn_err.h>
        !            49: #include <sys/errno.h>
        !            50: #include <stddef.h>
        !            51: 
        !            52: /* #include "sys/param.h" */
        !            53: /* #include "sys/sysmacros.h" */
        !            54: /* #include "sys/dir.h" */
        !            55: /* #include "sys/signal.h" */
        !            56: /* #include "sys/user.h" */
        !            57: /* #include "sys/cred.h" */
        !            58: 
        !            59: #include "sys/types.h"
        !            60: #include "sys/stream.h"
        !            61: #include "sys/stropts.h"
        !            62: #include "sys/ddi.h"
        !            63: 
        !            64: 
        !            65: static struct module_info minfo = {
        !            66:        0xEE12, "loop", 0, INFPSZ, 512, 128
        !            67: };
        !            68: 
        !            69: 
        !            70: /*
        !            71:  * The original example code contained declarations for the loop... ()
        !            72:  * routines that did not even declare them as functions, but as integers. This
        !            73:  * has been corrected. The original code would not have compiled with an ISO
        !            74:  * C compiler.
        !            75:  */
        !            76: 
        !            77: static int     loopopen        __PROTO ((queue_t * q, dev_t * devp, int flag,
        !            78:                                          int sflag, cred_t * credp));
        !            79: static int     loopclose       __PROTO ((queue_t * q, int flag,
        !            80:                                          cred_t * credp));
        !            81: static void    loopwput        __PROTO ((queue_t * q, mblk_t * mp));
        !            82: static void    loopwsrv        __PROTO ((queue_t * q));
        !            83: static void    looprsrv        __PROTO ((queue_t * q));
        !            84: 
        !            85: static struct qinit rinit = {
        !            86:        NULL, looprsrv, loopopen, loopclose, NULL, & minfo, NULL
        !            87: };
        !            88: 
        !            89: static struct qinit winit = {
        !            90:        loopwput, loopwsrv, NULL, NULL, NULL, & minfo, NULL
        !            91: };
        !            92: 
        !            93: struct streamtab loopinfo = { & rinit, & winit, NULL, NULL };
        !            94: 
        !            95: 
        !            96: /*
        !            97:  * Import definitions from the "Space.c" file.
        !            98:  */
        !            99: 
        !           100: #include "loop.h"
        !           101: 
        !           102: int    loopdevflag = 0;
        !           103: 
        !           104: 
        !           105: /*
        !           106:  * The example code did not declare this, ever.
        !           107:  */
        !           108: 
        !           109: static lkinfo_t        loop_lkinfo = {
        !           110:        "Loopback driver lock"
        !           111: };
        !           112: 
        !           113: 
        !           114: /*
        !           115:  * Extensions to the original code; as dicussed, there are limitations in the
        !           116:  * multiprocessor code in the original example. We have extracted the code
        !           117:  * that deals with the lock hierarchy into separate functions to make it
        !           118:  * simpler to experiment with different multiprocessor lock strategies.
        !           119:  */
        !           120: 
        !           121: #define        LOOP_HIER(idx)          (hier + 1)
        !           122: 
        !           123: 
        !           124: /*
        !           125:  * Lock a pair of loop entries.
        !           126:  */
        !           127: 
        !           128: #if    __USE_PROTO__
        !           129: static pl_t (LOOP_LOCK) (struct loop * loop, struct loop * other)
        !           130: #else
        !           131: static pl_t
        !           132: LOOP_LOCK __ARGS ((loop, other))
        !           133: struct loop   *        loop;
        !           134: struct loop   *        other;
        !           135: #endif
        !           136: {
        !           137:        pl_t            prev_pl;
        !           138: 
        !           139:        /*
        !           140:         * Since the entries are part of a single array object, we are
        !           141:         * guaranteed that the relational comparison of the pointers will
        !           142:         * yield valid results.
        !           143:         *
        !           144:         * The comparison ensures that we lock the entries in hierarchy order;
        !           145:         * the canonical ordering is needed to avoid deadlock.
        !           146:         */
        !           147: 
        !           148:        if (loop < other) {
        !           149: 
        !           150:                prev_pl = LOCK (loop->lck, plstr);
        !           151:                (void) LOCK (other->lck, plstr);
        !           152:        } else {
        !           153: 
        !           154:                prev_pl = LOCK (other->lck, plstr);
        !           155:                (void) LOCK (loop->lck, plstr);
        !           156:        }
        !           157: 
        !           158:         return prev_pl;
        !           159: }
        !           160: 
        !           161: 
        !           162: /*
        !           163:  * Unlock a pair of loop entries.
        !           164:  */
        !           165: 
        !           166: #if    __USE_PROTO__
        !           167: static void (LOOP_UNLOCK) (struct loop * loop, struct loop * other,
        !           168:                           pl_t prev_pl)
        !           169: #else
        !           170: static void
        !           171: LOOP_UNLOCK __ARGS ((loop, other, prev_pl))
        !           172: struct loop   *        loop;
        !           173: struct loop   *        other;
        !           174: pl_t           prev_pl;
        !           175: #endif
        !           176: {
        !           177:        /*
        !           178:         * The original example always unlocked the minor devices in reverse
        !           179:         * order to the lock order. This isn't necessary; whether it's a good
        !           180:         * idea or not cannot be determined without forbidden knowledge of the
        !           181:         * implementation of the lock primitives, or rigorous profiling.
        !           182:         */
        !           183: 
        !           184:        UNLOCK (loop->lck, plstr);
        !           185:        UNLOCK (other->lck, prev_pl);
        !           186: }
        !           187: 
        !           188: 
        !           189: /*
        !           190:  * Init routine for the driver, called at boot time before system services are
        !           191:  * initialized. This function may not call any DDI/DKI routines other than
        !           192:  * those listed on the init(2D2K) manual page, because only those services
        !           193:  * have been initialized.
        !           194:  */
        !           195: 
        !           196: __EXTERN_C__   
        !           197: #if    __USE_PROTO__
        !           198: void loopinit (void)
        !           199: #else
        !           200: void
        !           201: loopinit ()
        !           202: #endif
        !           203: {
        !           204:        int             hier;
        !           205: 
        !           206:        /*
        !           207:         * Allocate a multiprocessor lock for each minor device.
        !           208:         *
        !           209:         * An arbitrary hierarchy is defined based on position in the
        !           210:         * "loop_loop" table, such that to lock two entries at the same time,
        !           211:         * always lock "loop_loop [n].lck" before "loop_loop [n+m].lck".
        !           212:         */
        !           213: 
        !           214:        /*
        !           215:         * This version of the loopback driver will keep the above scheme from
        !           216:         * the original code, but there are some important restrictions.
        !           217:         *
        !           218:         * This scheme restricts the number of minor devices to the range of
        !           219:         * valid hierarchy values that LOCK_ALLOC () will accept, which is a
        !           220:         * total of 32 entries. This restriction can be lifted by using a
        !           221:         * single hierarchy level and using TRYLOCK () when trying to acquire
        !           222:         * multiple locks, or simply using a single global lock.
        !           223:         *
        !           224:         * Note that the original code allocated the locks without testing the
        !           225:         * return value from LOCK_ALLOC (). Furthermore, the code passed a
        !           226:         * flag value of KM_SLEEP, which is not legal for an init routine,
        !           227:         * according to the init(D2DK) manual page.
        !           228:         */
        !           229: 
        !           230:        if (loop_cnt > 32) {
        !           231: 
        !           232:                cmn_err (CE_WARN, "Only 32 loopback entries can be provided");
        !           233:                loop_cnt = 32;
        !           234:        }
        !           235: 
        !           236:        for (hier = 0 ; hier < loop_cnt ; hier ++) {
        !           237: 
        !           238:                if ((loop_loop [hier].lck =
        !           239:                                LOCK_ALLOC (hier + 1, plstr, & loop_lkinfo,
        !           240:                                            KM_NOSLEEP)) ==  NULL)
        !           241:                        cmn_err (CE_PANIC, "Could not allocate lock in "
        !           242:                                           "loopinit ()");
        !           243:        }
        !           244: }
        !           245: 
        !           246: 
        !           247: #if    __USE_PROTO__
        !           248: int loopopen (queue_t * q, dev_t * devp, int __NOTUSED (flag), int sflag,
        !           249:              cred_t * __NOTUSED (credp))
        !           250: #else
        !           251: int
        !           252: loopopen (q, devp, flag, sflag, credp)
        !           253: queue_t              * q;
        !           254: dev_t        * devp;
        !           255: int            flag;
        !           256: int            sflag;
        !           257: cred_t       * credp;
        !           258: #endif
        !           259: {
        !           260:        struct loop   * loop;
        !           261:        dev_t           newminor;
        !           262: 
        !           263:        /*
        !           264:         * If CLONEOPEN, pick a minor device number to use. Otherwise, check
        !           265:         * the minor device range.
        !           266:         */
        !           267: 
        !           268:        if (sflag == CLONEOPEN) {
        !           269: 
        !           270:                for (newminor = 0 ; newminor < loop_cnt ; newminor ++) {
        !           271: 
        !           272:                        if (loop_loop [newminor].qptr == NULL)
        !           273:                                break;
        !           274:                }
        !           275:        } else
        !           276:                newminor = geteminor (* devp);
        !           277: 
        !           278:        if (newminor >= loop_cnt)
        !           279:                return ENXIO;
        !           280: 
        !           281:        /*
        !           282:         * Construct new device number if this is the first open.
        !           283:         */
        !           284: 
        !           285:        if (q->q_ptr != NULL)
        !           286:                return 0;
        !           287: 
        !           288:        * devp = makedevice (getemajor (* devp), newminor);
        !           289: 
        !           290:        loop = & loop_loop [newminor];
        !           291: 
        !           292:        q->q_ptr = WR (q)->q_ptr = (char *) loop;
        !           293:        loop->qptr = WR (q);
        !           294:        loop->oqptr = NULL;
        !           295: 
        !           296: 
        !           297:        /*
        !           298:         * Enable put and service routines for this queue pair.
        !           299:         */
        !           300: 
        !           301:        qprocson (q);
        !           302:        return 0;
        !           303: }
        !           304: 
        !           305: 
        !           306: #if    __USE_PROTO__
        !           307: void loopwput (queue_t * q, mblk_t * mp)
        !           308: #else
        !           309: void
        !           310: loopwput (q, mp)
        !           311: queue_t              * q;
        !           312: mblk_t       * mp;
        !           313: #endif
        !           314: {
        !           315:        struct loop   * loop;
        !           316:        pl_t            prev_pl;
        !           317:        int             error;
        !           318: 
        !           319:        loop = (struct loop *) q->q_ptr;
        !           320: 
        !           321:        switch (mp->b_datap->db_type) {
        !           322: 
        !           323:        case M_IOCTL: {
        !           324:                struct iocblk * iocp;
        !           325: 
        !           326:                iocp = (struct iocblk *) mp->b_rptr;
        !           327: 
        !           328:                switch (iocp->ioc_cmd) {
        !           329: 
        !           330:                case LOOP_SET: {
        !           331:                        int             to;     /* other minor device */
        !           332:                        struct loop   * other;
        !           333: 
        !           334:                        /*
        !           335:                         * Sanity check: "ioc_count" contains the amount of
        !           336:                         * user-supplied data, and must equal the size of an
        !           337:                         * int.
        !           338:                         */
        !           339: 
        !           340:                        if (iocp->ioc_count != sizeof (int)) {
        !           341: 
        !           342:                                error = EINVAL;
        !           343:                                goto iocnak;
        !           344:                        }
        !           345: 
        !           346: 
        !           347:                        /*
        !           348:                         * Fetch other device from 2nd message block.
        !           349:                         */
        !           350: 
        !           351:                        to = * (int *) mp->b_cont->b_rptr;
        !           352: 
        !           353: 
        !           354:                        /*
        !           355:                         * More sanity checks: the minor must be in range,
        !           356:                         * open already, and both devices must be
        !           357:                         * disconnected.
        !           358:                         *
        !           359:                         * Note that the original code contained a serious
        !           360:                         * flaw in the test logic where the lock on the other
        !           361:                         * minor device was taken out before the range check.
        !           362:                         * In addition, the lock was
        !           363:                         *      oldpri = LOCK (loop_loop [to].qptr, plstr);
        !           364:                         * but it isn't legal to try locking a "queue_t *".
        !           365:                         *
        !           366:                         * The original code allowed a connect attempt to the
        !           367:                         * same minor as the "loop" entry, which will cause
        !           368:                         * deadlock.
        !           369:                         */
        !           370: 
        !           371:                        if (to >= loop_cnt || to < 0 ||
        !           372:                            (other = & loop_loop [to]) == loop) {
        !           373: 
        !           374:                                error = ENXIO;
        !           375:                                goto iocnak;
        !           376:                        }
        !           377: 
        !           378:                        prev_pl = LOOP_LOCK (loop, other);
        !           379: 
        !           380:                        if (other->qptr == NULL || loop->oqptr != NULL ||
        !           381:                            other->oqptr != NULL) {
        !           382: 
        !           383:                                LOOP_UNLOCK (loop, other, prev_pl);
        !           384:                                error = EBUSY;
        !           385:                                goto iocnak;
        !           386:                        }
        !           387: 
        !           388: 
        !           389:                        /*
        !           390:                         * Cross-connect streams via the loop structures.
        !           391:                         */
        !           392: 
        !           393:                        loop->oqptr = RD (other->qptr);
        !           394:                        other->oqptr = RD (q);
        !           395: 
        !           396:                        LOOP_UNLOCK (loop, other, prev_pl);
        !           397: 
        !           398: 
        !           399:                        /*
        !           400:                         * Return a successful ioctl (). Set "ioc_count" to
        !           401:                         * zero, since no data is returned.
        !           402:                         */
        !           403: 
        !           404:                        mp->b_datap->db_type = M_IOCACK;
        !           405:                        iocp->ioc_count = 0;
        !           406:                        break;
        !           407:                    }
        !           408: 
        !           409:                default:
        !           410:                        error = EINVAL;
        !           411: iocnak:
        !           412:                        /*
        !           413:                         * Invalid ioctl (). Setting "ioc_error" causes the
        !           414:                         * ioctl () call to return that particular errno. By
        !           415:                         * default, ioctl () will return EINVAL on failure.
        !           416:                         */
        !           417: 
        !           418:                        mp->b_datap->db_type = M_IOCNAK;
        !           419:                        iocp->ioc_error = error;
        !           420:                }
        !           421: 
        !           422:                qreply (q, mp);
        !           423:                break;
        !           424:            }
        !           425: 
        !           426:        case M_FLUSH: {
        !           427:                queue_t       * tempqptr;
        !           428: 
        !           429:                prev_pl = LOCK (loop->lck, plstr);
        !           430: 
        !           431:                /*
        !           432:                 * The original example code flushed the read queues of this
        !           433:                 * minor and any connected minor, despite the fact that there
        !           434:                 * are never any messages queued there. We'll skip that part.
        !           435:                 */
        !           436: 
        !           437:                if ((* mp->b_rptr & FLUSHW) != 0)
        !           438:                        flushq (q, FLUSHALL);
        !           439: 
        !           440:                if ((* mp->b_rptr & FLUSHR) != 0)
        !           441:                        if (loop->oqptr != NULL)
        !           442:                                flushq (WR (loop->oqptr), FLUSHALL);
        !           443: 
        !           444:                /*
        !           445:                 * Now change around the flush message in a similar fashion to
        !           446:                 * the midpoint of a STREAMS FIFO. The logic in the original
        !           447:                 * example was broken in that it did not cope correctly with
        !           448:                 * the situation where the device was not connected, it did
        !           449:                 * not have a declaration for "tempqptr", and it just fell
        !           450:                 * through into the data message case.
        !           451:                 */
        !           452: 
        !           453:                if ((tempqptr = loop->oqptr) != NULL) {
        !           454: 
        !           455:                        switch (* mp->b_rptr & ~ FLUSHBAND) {
        !           456: 
        !           457:                        case FLUSHW:
        !           458:                        case FLUSHR:
        !           459:                                * mp->b_rptr ^= FLUSHR | FLUSHW;
        !           460:                                break;
        !           461: 
        !           462:                        default:
        !           463:                                break;
        !           464:                        }
        !           465:                } else {
        !           466:                        /*
        !           467:                         * Canonical driver flush processing. We use a NULL
        !           468:                         * value in "tempqptr" to flag that the message should
        !           469:                         * be discarded.
        !           470:                         */
        !           471: 
        !           472:                        * mp->b_rptr &= ~ FLUSHW;
        !           473: 
        !           474:                        tempqptr = (* mp->b_rptr & FLUSHR) != 0 ? RD (q) :
        !           475:                                                NULL;
        !           476:                }
        !           477: 
        !           478:                UNLOCK (loop->lck, prev_pl);
        !           479: 
        !           480:                if (tempqptr != NULL)
        !           481:                        putnext (tempqptr, mp);
        !           482:                else
        !           483:                        freemsg (mp);
        !           484: 
        !           485:                /*
        !           486:                 * The original example fell through into the data-message
        !           487:                 * code, which is clearly incorrect.
        !           488:                 */
        !           489: 
        !           490:                break;
        !           491:            }
        !           492: 
        !           493:        case M_DATA:
        !           494:        case M_PROTO:
        !           495:        case M_PCPROTO:
        !           496:                /*
        !           497:                 * If this stream is not connected, register an error with an
        !           498:                 * M_ERROR message. Otherwise, queue the message for
        !           499:                 * forwarding by the write service procedure.
        !           500:                 */
        !           501: 
        !           502:                prev_pl = LOCK (loop->lck, plstr);
        !           503: 
        !           504:                error = loop->oqptr == NULL ? ENXIO : 0;
        !           505: 
        !           506:                UNLOCK (loop->lck, prev_pl);
        !           507: 
        !           508:                if (error != 0) {
        !           509: 
        !           510:                        freemsg (mp);
        !           511:                        putnextctl1 (RD (q), M_ERROR, error);
        !           512:                } else
        !           513:                        putq (q, mp);
        !           514:                break;
        !           515: 
        !           516:        default:
        !           517:                /*
        !           518:                 * Discard unrecognized messages.
        !           519:                 */
        !           520: 
        !           521:                freemsg (mp);
        !           522:                break;
        !           523:        }
        !           524: }
        !           525: 
        !           526: 
        !           527: #if    __USE_PROTO__
        !           528: void loopwsrv (queue_t * q)
        !           529: #else
        !           530: void
        !           531: loopwsrv (q)
        !           532: queue_t              * q;
        !           533: #endif
        !           534: {
        !           535:        mblk_t        * mp;
        !           536:        struct loop   * loop;
        !           537:        queue_t       * tmpqptr;
        !           538:        pl_t            prev_pl;
        !           539: 
        !           540:        loop = (struct loop *) q->q_ptr;
        !           541: 
        !           542:        while ((mp = getq (q)) != NULL) {
        !           543: 
        !           544:                prev_pl = LOCK (loop->lck, plstr);
        !           545: 
        !           546:                tmpqptr = loop->oqptr;
        !           547: 
        !           548:                UNLOCK (loop->lck, prev_pl);
        !           549: 
        !           550:                /*
        !           551:                 * The original example code implicitly assumed that the loop
        !           552:                 * was connected at this point. It also would never pass a
        !           553:                 * priority message to the other side, and on flow control or
        !           554:                 * a priority message would never unlock the minor device.
        !           555:                 *
        !           556:                 * Note that the disconnect handling in the original example
        !           557:                 * is broken, and this is only a partial fix.
        !           558:                 */
        !           559: 
        !           560:                if (tmpqptr == NULL) {
        !           561: 
        !           562:                        freemsg (mp);
        !           563:                        putnextctl1 (RD (q), M_ERROR, EINVAL);
        !           564:                        continue;
        !           565:                }
        !           566: 
        !           567:                if (! pcmsg (mp->b_datap->db_type) &&
        !           568:                    ! canputnext (tmpqptr)) {
        !           569: 
        !           570:                        putbq (q, mp);
        !           571:                        break;
        !           572:                }
        !           573: 
        !           574:                putnext (tmpqptr, mp);
        !           575:        }
        !           576: }
        !           577: 
        !           578: 
        !           579: #if    __USE_PROTO__
        !           580: void looprsrv (queue_t * q)
        !           581: #else
        !           582: void
        !           583: looprsrv (q)
        !           584: queue_t              * q;
        !           585: #endif
        !           586: {
        !           587:        struct loop   * loop;
        !           588:        pl_t            prev_pl;
        !           589: 
        !           590:        /*
        !           591:         * Deal with being back-enabled by flow control by enabling the write
        !           592:         * side service procedure to retry sending the messages backed up
        !           593:         * there.
        !           594:         */
        !           595: 
        !           596:        loop = (struct loop *) q->q_ptr;
        !           597: 
        !           598:        prev_pl = LOCK (loop->lck, plstr);
        !           599: 
        !           600:        if (loop->oqptr != NULL)
        !           601:                qenable (WR (loop->oqptr));
        !           602: 
        !           603:        UNLOCK (loop->lck, prev_pl);
        !           604: }
        !           605: 
        !           606: 
        !           607: #if    __USE_PROTO__
        !           608: int loopclose (queue_t * q, int __NOTUSED (flag), cred_t * __NOTUSED (credp))
        !           609: #else
        !           610: int
        !           611: loopclose (q, flag, credp)
        !           612: queue_t              * q;
        !           613: int            flag;
        !           614: cred_t       * credp;
        !           615: #endif
        !           616: {
        !           617:        struct loop   * loop;
        !           618: 
        !           619:        /*
        !           620:         * Disable put and service routines for the queue pair. This, plus the
        !           621:         * fact that open and close are single-threaded, makes any further
        !           622:         * locking of this "loop_loop []" entry unnecessary.
        !           623:         */
        !           624: 
        !           625:        qprocsoff (q);
        !           626: 
        !           627:        loop = (struct loop *) q->q_ptr;
        !           628:        loop->qptr = NULL;
        !           629: 
        !           630: 
        !           631:        /*
        !           632:         * If we are connected to another stream, break the linkage and send a
        !           633:         * hangup message. The original example didn't bother performing any
        !           634:         * locking for the disconnect, which is incorrect, because put and
        !           635:         * service routines for the other minor device could still be running.
        !           636:         */
        !           637: 
        !           638:        if (loop->oqptr != NULL) {
        !           639:                struct loop   * other = (struct loop *) loop->oqptr->q_ptr;
        !           640:                pl_t            prev_pl;
        !           641: 
        !           642:                prev_pl = LOCK (other->lck, plstr);
        !           643: 
        !           644:                if (other->oqptr == WR (q)) {
        !           645: 
        !           646:                        putnextctl (loop->oqptr, M_HANGUP);
        !           647:                        other->oqptr = loop->oqptr = NULL;
        !           648:                }
        !           649: 
        !           650:                UNLOCK (other->lck, prev_pl);
        !           651:        }
        !           652: 
        !           653:         return 0;
        !           654: }

unix.superglobalmegacorp.com

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