Annotation of quake2/server/sv_user.c, revision 1.1.1.2

1.1       root        1: // sv_user.c -- server code for moving users
                      2: 
                      3: #include "server.h"
                      4: 
                      5: edict_t        *sv_player;
                      6: 
                      7: /*
                      8: ============================================================
                      9: 
                     10: USER STRINGCMD EXECUTION
                     11: 
                     12: sv_client and sv_player will be valid.
                     13: ============================================================
                     14: */
                     15: 
                     16: /*
                     17: ==================
                     18: SV_BeginDemoServer
                     19: ==================
                     20: */
                     21: void SV_BeginDemoserver (void)
                     22: {
                     23:        char            name[MAX_OSPATH];
                     24: 
                     25:        Com_sprintf (name, sizeof(name), "demos/%s", sv.name);
                     26:        FS_FOpenFile (name, &sv.demofile);
                     27:        if (!sv.demofile)
                     28:                Com_Error (ERR_DROP, "Couldn't open %s\n", name);
                     29: }
                     30: 
                     31: /*
                     32: ================
                     33: SV_New_f
                     34: 
                     35: Sends the first message from the server to a connected client.
                     36: This will be sent on the initial connection and upon each server load.
                     37: ================
                     38: */
                     39: void SV_New_f (void)
                     40: {
                     41:        char            *gamedir;
                     42:        int                     playernum;
                     43:        edict_t         *ent;
                     44: 
                     45:        Com_DPrintf ("New() from %s\n", sv_client->name);
                     46: 
                     47:        if (sv_client->state != cs_connected)
                     48:        {
                     49:                Com_Printf ("New not valid -- already spawned\n");
                     50:                return;
                     51:        }
                     52: 
                     53:        // demo servers just dump the file message
                     54:        if (sv.state == ss_demo)
                     55:        {
                     56:                SV_BeginDemoserver ();
                     57:                return;
                     58:        }
                     59: 
                     60:        //
                     61:        // serverdata needs to go over for all types of servers
                     62:        // to make sure the protocol is right, and to set the gamedir
                     63:        //
                     64:        gamedir = Cvar_VariableString ("gamedir");
                     65: 
                     66:        // send the serverdata
                     67:        MSG_WriteByte (&sv_client->netchan.message, svc_serverdata);
                     68:        MSG_WriteLong (&sv_client->netchan.message, PROTOCOL_VERSION);
                     69:        MSG_WriteLong (&sv_client->netchan.message, svs.spawncount);
                     70:        MSG_WriteByte (&sv_client->netchan.message, sv.attractloop);
                     71:        MSG_WriteString (&sv_client->netchan.message, gamedir);
                     72: 
                     73:        if (sv.state == ss_cinematic || sv.state == ss_pic)
                     74:                playernum = -1;
                     75:        else
                     76:                playernum = sv_client - svs.clients;
                     77:        MSG_WriteShort (&sv_client->netchan.message, playernum);
                     78: 
                     79:        // send full levelname
                     80:        MSG_WriteString (&sv_client->netchan.message, sv.configstrings[CS_NAME]);
                     81: 
                     82:        //
                     83:        // game server
                     84:        // 
                     85:        if (sv.state == ss_game)
                     86:        {
                     87:                // set up the entity for the client
                     88:                ent = EDICT_NUM(playernum+1);
                     89:                ent->s.number = playernum+1;
                     90:                sv_client->edict = ent;
                     91:                memset (&sv_client->lastcmd, 0, sizeof(sv_client->lastcmd));
                     92: 
                     93:                // begin fetching configstrings
                     94:                MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
                     95:                MSG_WriteString (&sv_client->netchan.message, va("cmd configstrings %i 0\n",svs.spawncount) );
                     96:        }
                     97: 
                     98: }
                     99: 
                    100: /*
                    101: ==================
                    102: SV_Configstrings_f
                    103: ==================
                    104: */
                    105: void SV_Configstrings_f (void)
                    106: {
                    107:        int                     start;
                    108: 
                    109:        Com_DPrintf ("Configstrings() from %s\n", sv_client->name);
                    110: 
                    111:        if (sv_client->state != cs_connected)
                    112:        {
                    113:                Com_Printf ("configstrings not valid -- already spawned\n");
                    114:                return;
                    115:        }
                    116: 
                    117:        // handle the case of a level changing while a client was connecting
                    118:        if ( atoi(Cmd_Argv(1)) != svs.spawncount )
                    119:        {
                    120:                Com_Printf ("SV_Configstrings_f from different level\n");
                    121:                SV_New_f ();
                    122:                return;
                    123:        }
                    124:        
                    125:        start = atoi(Cmd_Argv(2));
                    126: 
                    127:        // write a packet full of data
                    128: 
                    129:        while ( sv_client->netchan.message.cursize < MAX_MSGLEN/2 
                    130:                && start < MAX_CONFIGSTRINGS)
                    131:        {
                    132:                if (sv.configstrings[start][0])
                    133:                {
                    134:                        MSG_WriteByte (&sv_client->netchan.message, svc_configstring);
                    135:                        MSG_WriteShort (&sv_client->netchan.message, start);
                    136:                        MSG_WriteString (&sv_client->netchan.message, sv.configstrings[start]);
                    137:                }
                    138:                start++;
                    139:        }
                    140: 
                    141:        // send next command
                    142: 
                    143:        if (start == MAX_CONFIGSTRINGS)
                    144:        {
                    145:                MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
                    146:                MSG_WriteString (&sv_client->netchan.message, va("cmd baselines %i 0\n",svs.spawncount) );
                    147:        }
                    148:        else
                    149:        {
                    150:                MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
                    151:                MSG_WriteString (&sv_client->netchan.message, va("cmd configstrings %i %i\n",svs.spawncount, start) );
                    152:        }
                    153: }
                    154: 
                    155: /*
                    156: ==================
                    157: SV_Baselines_f
                    158: ==================
                    159: */
                    160: void SV_Baselines_f (void)
                    161: {
                    162:        int             start;
                    163:        entity_state_t  nullstate;
                    164:        entity_state_t  *base;
                    165: 
                    166:        Com_DPrintf ("Baselines() from %s\n", sv_client->name);
                    167: 
                    168:        if (sv_client->state != cs_connected)
                    169:        {
                    170:                Com_Printf ("baselines not valid -- already spawned\n");
                    171:                return;
                    172:        }
                    173:        
                    174:        // handle the case of a level changing while a client was connecting
                    175:        if ( atoi(Cmd_Argv(1)) != svs.spawncount )
                    176:        {
                    177:                Com_Printf ("SV_Baselines_f from different level\n");
                    178:                SV_New_f ();
                    179:                return;
                    180:        }
                    181:        
                    182:        start = atoi(Cmd_Argv(2));
                    183: 
                    184:        memset (&nullstate, 0, sizeof(nullstate));
                    185: 
                    186:        // write a packet full of data
                    187: 
                    188:        while ( sv_client->netchan.message.cursize <  MAX_MSGLEN/2
                    189:                && start < MAX_EDICTS)
                    190:        {
                    191:                base = &sv.baselines[start];
                    192:                if (base->modelindex || base->sound || base->effects)
                    193:                {
                    194:                        MSG_WriteByte (&sv_client->netchan.message, svc_spawnbaseline);
1.1.1.2 ! root      195:                        MSG_WriteDeltaEntity (&nullstate, base, &sv_client->netchan.message, true, true);
1.1       root      196:                }
                    197:                start++;
                    198:        }
                    199: 
                    200:        // send next command
                    201: 
                    202:        if (start == MAX_EDICTS)
                    203:        {
                    204:                MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
1.1.1.2 ! root      205:                MSG_WriteString (&sv_client->netchan.message, va("precache %i\n", svs.spawncount) );
1.1       root      206:        }
                    207:        else
                    208:        {
                    209:                MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
                    210:                MSG_WriteString (&sv_client->netchan.message, va("cmd baselines %i %i\n",svs.spawncount, start) );
                    211:        }
                    212: }
                    213: 
                    214: /*
                    215: ==================
                    216: SV_Begin_f
                    217: ==================
                    218: */
                    219: void SV_Begin_f (void)
                    220: {
                    221:        Com_DPrintf ("Begin() from %s\n", sv_client->name);
                    222: 
                    223:        // handle the case of a level changing while a client was connecting
                    224:        if ( atoi(Cmd_Argv(1)) != svs.spawncount )
                    225:        {
                    226:                Com_Printf ("SV_Begin_f from different level\n");
                    227:                SV_New_f ();
                    228:                return;
                    229:        }
                    230: 
                    231:        sv_client->state = cs_spawned;
1.1.1.2 ! root      232: 
1.1       root      233:        // call the game begin function
                    234:        ge->ClientBegin (sv_player);
                    235: 
                    236:        Cbuf_InsertFromDefer ();
                    237: }
                    238: 
                    239: //=============================================================================
                    240: 
                    241: /*
                    242: ==================
                    243: SV_NextDownload_f
                    244: ==================
                    245: */
                    246: void SV_NextDownload_f (void)
                    247: {
                    248:        int             r;
                    249:        int             percent;
                    250:        int             size;
                    251: 
                    252:        if (!sv_client->download)
                    253:                return;
                    254: 
                    255:        r = sv_client->downloadsize - sv_client->downloadcount;
                    256:        if (r > 1024)
                    257:                r = 1024;
                    258: 
                    259:        MSG_WriteByte (&sv_client->netchan.message, svc_download);
                    260:        MSG_WriteShort (&sv_client->netchan.message, r);
                    261: 
                    262:        sv_client->downloadcount += r;
                    263:        size = sv_client->downloadsize;
                    264:        if (!size)
                    265:                size = 1;
                    266:        percent = sv_client->downloadcount*100/size;
                    267:        MSG_WriteByte (&sv_client->netchan.message, percent);
                    268:        SZ_Write (&sv_client->netchan.message,
                    269:                sv_client->download + sv_client->downloadcount - r, r);
                    270: 
                    271:        if (sv_client->downloadcount != sv_client->downloadsize)
                    272:                return;
                    273: 
                    274:        FS_FreeFile (sv_client->download);
                    275:        sv_client->download = NULL;
                    276: }
                    277: 
                    278: /*
                    279: ==================
                    280: SV_BeginDownload_f
                    281: ==================
                    282: */
                    283: void SV_BeginDownload_f(void)
                    284: {
                    285:        char    *name;
                    286:        extern  cvar_t *allow_download;
1.1.1.2 ! root      287:        extern  cvar_t *allow_download_players;
        !           288:        extern  cvar_t *allow_download_models;
        !           289:        extern  cvar_t *allow_download_sounds;
        !           290:        extern  cvar_t *allow_download_maps;
        !           291:        extern  int             file_from_pak; // ZOID did file come from pak?
        !           292:        int offset = 0;
1.1       root      293: 
                    294:        name = Cmd_Argv(1);
1.1.1.2 ! root      295: 
        !           296:        if (Cmd_Argc() > 2)
        !           297:                offset = atoi(Cmd_Argv(2)); // downloaded offset
        !           298: 
        !           299:        // hacked by zoid to allow more conrol over download
        !           300:        // first off, no .. or global allow check
1.1       root      301:        if (strstr (name, "..") || !allow_download->value
1.1.1.2 ! root      302:                // leading dot is no good
        !           303:                || *name == '.' 
        !           304:                // leading slash bad as well, must be in subdir
        !           305:                || *name == '/'
        !           306:                // next up, skin check
        !           307:                || (strncmp(name, "players/", 6) == 0 && !allow_download_players->value)
        !           308:                // now models
        !           309:                || (strncmp(name, "models/", 6) == 0 && !allow_download_models->value)
        !           310:                // now sounds
        !           311:                || (strncmp(name, "sound/", 6) == 0 && !allow_download_sounds->value)
        !           312:                // now maps (note special case for maps, must not be in pak)
        !           313:                || (strncmp(name, "maps/", 6) == 0 && !allow_download_maps->value)
        !           314:                // MUST be in a subdirectory    
        !           315:                || !strstr (name, "/") )        
1.1       root      316:        {       // don't allow anything with .. path
                    317:                MSG_WriteByte (&sv_client->netchan.message, svc_download);
                    318:                MSG_WriteShort (&sv_client->netchan.message, -1);
                    319:                MSG_WriteByte (&sv_client->netchan.message, 0);
                    320:                return;
                    321:        }
                    322: 
1.1.1.2 ! root      323: 
1.1       root      324:        if (sv_client->download)
                    325:                FS_FreeFile (sv_client->download);
                    326: 
                    327:        sv_client->downloadsize = FS_LoadFile (name, (void **)&sv_client->download);
1.1.1.2 ! root      328:        sv_client->downloadcount = offset;
1.1       root      329: 
1.1.1.2 ! root      330:        if (offset > sv_client->downloadsize)
        !           331:                sv_client->downloadcount = sv_client->downloadsize;
        !           332: 
        !           333:        if (!sv_client->download
        !           334:                // special check for maps, if it came from a pak file, don't allow
        !           335:                // download  ZOID
        !           336:                || (strncmp(name, "maps/", 5) == 0 && file_from_pak))
1.1       root      337:        {
                    338:                Com_DPrintf ("Couldn't download %s to %s\n", name, sv_client->name);
1.1.1.2 ! root      339:                if (sv_client->download) {
        !           340:                        FS_FreeFile (sv_client->download);
        !           341:                        sv_client->download = NULL;
        !           342:                }
        !           343: 
1.1       root      344:                MSG_WriteByte (&sv_client->netchan.message, svc_download);
                    345:                MSG_WriteShort (&sv_client->netchan.message, -1);
                    346:                MSG_WriteByte (&sv_client->netchan.message, 0);
                    347:                return;
                    348:        }
                    349: 
                    350:        SV_NextDownload_f ();
                    351:        Com_DPrintf ("Downloading %s to %s\n", name, sv_client->name);
                    352: }
                    353: 
                    354: 
1.1.1.2 ! root      355: 
1.1       root      356: //============================================================================
                    357: 
                    358: 
                    359: /*
                    360: =================
                    361: SV_Disconnect_f
                    362: 
                    363: The client is going to disconnect, so remove the connection immediately
                    364: =================
                    365: */
                    366: void SV_Disconnect_f (void)
                    367: {
                    368: //     SV_EndRedirect ();
                    369:        SV_DropClient (sv_client);      
                    370: }
                    371: 
                    372: 
                    373: /*
                    374: ==================
                    375: SV_ShowServerinfo_f
                    376: 
                    377: Dumps the serverinfo info string
                    378: ==================
                    379: */
                    380: void SV_ShowServerinfo_f (void)
                    381: {
                    382:        Info_Print (Cvar_Serverinfo());
                    383: }
                    384: 
                    385: 
                    386: void SV_Nextserver (void)
                    387: {
                    388:        char    *v;
                    389: 
1.1.1.2 ! root      390:        //ZOID, ss_pic can be nextserver'd in coop mode
        !           391:        if (sv.state == ss_game || (sv.state == ss_pic && !Cvar_VariableValue("coop")))
1.1       root      392:                return;         // can't nextserver while playing a normal game
                    393: 
                    394:        svs.spawncount++;       // make sure another doesn't sneak in
                    395:        v = Cvar_VariableString ("nextserver");
                    396:        if (!v[0])
                    397:                Cbuf_AddText ("killserver\n");
                    398:        else
                    399:        {
                    400:                Cbuf_AddText (v);
                    401:                Cbuf_AddText ("\n");
                    402:        }
                    403:        Cvar_Set ("nextserver","");
                    404: }
                    405: 
                    406: /*
                    407: ==================
                    408: SV_Nextserver_f
                    409: 
                    410: A cinematic has completed or been aborted by a client, so move
                    411: to the next server,
                    412: ==================
                    413: */
                    414: void SV_Nextserver_f (void)
                    415: {
1.1.1.2 ! root      416:        if ( atoi(Cmd_Argv(1)) != svs.spawncount ) {
        !           417:                Com_DPrintf ("Nextserver() from wrong level, from %s\n", sv_client->name);
1.1       root      418:                return;         // leftover from last server
1.1.1.2 ! root      419:        }
        !           420: 
        !           421:        Com_DPrintf ("Nextserver() from %s\n", sv_client->name);
1.1       root      422: 
                    423:        SV_Nextserver ();
                    424: }
                    425: 
                    426: typedef struct
                    427: {
                    428:        char    *name;
                    429:        void    (*func) (void);
                    430: } ucmd_t;
                    431: 
                    432: ucmd_t ucmds[] =
                    433: {
                    434:        // auto issued
                    435:        {"new", SV_New_f},
                    436:        {"configstrings", SV_Configstrings_f},
                    437:        {"baselines", SV_Baselines_f},
                    438:        {"begin", SV_Begin_f},
                    439: 
                    440:        {"nextserver", SV_Nextserver_f},
                    441: 
                    442:        {"disconnect", SV_Disconnect_f},
                    443: 
                    444:        // issued by hand at client consoles    
                    445:        {"info", SV_ShowServerinfo_f},
                    446: 
                    447:        {"download", SV_BeginDownload_f},
                    448:        {"nextdl", SV_NextDownload_f},
                    449: 
                    450:        {NULL, NULL}
                    451: };
                    452: 
                    453: /*
                    454: ==================
                    455: SV_ExecuteUserCommand
                    456: ==================
                    457: */
                    458: void SV_ExecuteUserCommand (char *s)
                    459: {
                    460:        ucmd_t  *u;
                    461:        
                    462:        Cmd_TokenizeString (s, true);
                    463:        sv_player = sv_client->edict;
                    464: 
                    465: //     SV_BeginRedirect (RD_CLIENT);
                    466: 
                    467:        for (u=ucmds ; u->name ; u++)
                    468:                if (!strcmp (Cmd_Argv(0), u->name) )
                    469:                {
                    470:                        u->func ();
                    471:                        break;
                    472:                }
                    473: 
                    474:        if (!u->name && sv.state == ss_game)
                    475:                ge->ClientCommand (sv_player);
                    476: 
                    477: //     SV_EndRedirect ();
                    478: }
                    479: 
                    480: /*
                    481: ===========================================================================
                    482: 
                    483: USER CMD EXECUTION
                    484: 
                    485: ===========================================================================
                    486: */
                    487: 
                    488: 
1.1.1.2 ! root      489: 
        !           490: void SV_ClientThink (client_t *cl, usercmd_t *cmd)
        !           491: 
1.1       root      492: {
                    493:        cl->commandMsec -= cmd->msec;
1.1.1.2 ! root      494: 
1.1       root      495:        if (cl->commandMsec < 0 && sv_enforcetime->value )
                    496:        {
                    497:                Com_DPrintf ("commandMsec underflow from %s\n", cl->name);
                    498:                return;
                    499:        }
1.1.1.2 ! root      500: 
1.1       root      501:        ge->ClientThink (cl->edict, cmd);
                    502: }
                    503: 
1.1.1.2 ! root      504: 
        !           505: 
1.1       root      506: #define        MAX_STRINGCMDS  8
                    507: /*
                    508: ===================
                    509: SV_ExecuteClientMessage
                    510: 
                    511: The current net_message is parsed for the given client
                    512: ===================
                    513: */
                    514: void SV_ExecuteClientMessage (client_t *cl)
                    515: {
                    516:        int             c;
                    517:        char    *s;
1.1.1.2 ! root      518: 
1.1       root      519:        usercmd_t       nullcmd;
                    520:        usercmd_t       oldest, oldcmd, newcmd;
                    521:        int             net_drop;
                    522:        int             stringCmdCount;
                    523:        int             checksum, calculatedChecksum;
                    524:        int             checksumIndex;
                    525:        qboolean        move_issued;
1.1.1.2 ! root      526:        int             lastframe;
1.1       root      527: 
                    528:        sv_client = cl;
                    529:        sv_player = sv_client->edict;
                    530: 
                    531:        // only allow one move command
                    532:        move_issued = false;
                    533:        stringCmdCount = 0;
                    534: 
                    535:        while (1)
                    536:        {
                    537:                if (net_message.readcount > net_message.cursize)
                    538:                {
                    539:                        Com_Printf ("SV_ReadClientMessage: badread\n");
                    540:                        SV_DropClient (cl);
                    541:                        return;
                    542:                }       
                    543: 
                    544:                c = MSG_ReadByte (&net_message);
                    545:                if (c == -1)
                    546:                        break;
                    547:                                
                    548:                switch (c)
                    549:                {
                    550:                default:
                    551:                        Com_Printf ("SV_ReadClientMessage: unknown command char\n");
                    552:                        SV_DropClient (cl);
                    553:                        return;
                    554:                                                
                    555:                case clc_nop:
                    556:                        break;
                    557: 
                    558:                case clc_userinfo:
                    559:                        strncpy (cl->userinfo, MSG_ReadString (&net_message), sizeof(cl->userinfo)-1);
                    560:                        SV_UserinfoChanged (cl);
                    561:                        break;
                    562: 
                    563:                case clc_move:
                    564:                        if (move_issued)
                    565:                                return;         // someone is trying to cheat...
                    566: 
1.1.1.2 ! root      567:                        move_issued = true;
1.1       root      568:                        checksumIndex = net_message.readcount;
                    569:                        checksum = MSG_ReadByte (&net_message);
1.1.1.2 ! root      570:                        lastframe = MSG_ReadLong (&net_message);
        !           571:                        if (lastframe != cl->lastframe) {
        !           572:                                cl->lastframe = lastframe;
        !           573:                                if (cl->lastframe > 0) {
        !           574:                                        cl->frame_latency[cl->lastframe&(LATENCY_COUNTS-1)] = 
        !           575:                                                svs.realtime - cl->frames[cl->lastframe & UPDATE_MASK].senttime;
        !           576:                                }
        !           577:                        }
1.1       root      578: 
                    579:                        memset (&nullcmd, 0, sizeof(nullcmd));
                    580:                        MSG_ReadDeltaUsercmd (&net_message, &nullcmd, &oldest);
                    581:                        MSG_ReadDeltaUsercmd (&net_message, &oldest, &oldcmd);
                    582:                        MSG_ReadDeltaUsercmd (&net_message, &oldcmd, &newcmd);
                    583: 
                    584:                        if ( cl->state != cs_spawned )
                    585:                        {
                    586:                                cl->lastframe = -1;
                    587:                                break;
                    588:                        }
                    589: 
                    590:                        // if the checksum fails, ignore the rest of the packet
1.1.1.2 ! root      591:                        calculatedChecksum = COM_BlockSequenceCRCByte (
1.1       root      592:                                net_message.data + checksumIndex + 1,
                    593:                                net_message.readcount - checksumIndex - 1,
                    594:                                cl->netchan.incoming_sequence);
                    595: 
                    596:                        if (calculatedChecksum != checksum)
                    597:                        {
1.1.1.2 ! root      598:                                Com_DPrintf ("Failed command checksum for %s (%d != %d)/%d\n", 
        !           599:                                        cl->name, calculatedChecksum, checksum, 
        !           600:                                        cl->netchan.incoming_sequence);
1.1       root      601:                                return;
                    602:                        }
                    603: 
                    604:                        if (!sv_paused->value)
                    605:                        {
                    606:                                net_drop = cl->netchan.dropped;
                    607:                                if (net_drop < 20)
                    608:                                {
1.1.1.2 ! root      609: 
1.1       root      610: //if (net_drop > 2)
1.1.1.2 ! root      611: 
1.1       root      612: //     Com_Printf ("drop %i\n", net_drop);
                    613:                                        while (net_drop > 2)
                    614:                                        {
1.1.1.2 ! root      615:                                                SV_ClientThink (cl, &cl->lastcmd);
        !           616: 
1.1       root      617:                                                net_drop--;
                    618:                                        }
                    619:                                        if (net_drop > 1)
1.1.1.2 ! root      620:                                                SV_ClientThink (cl, &oldest);
        !           621: 
1.1       root      622:                                        if (net_drop > 0)
1.1.1.2 ! root      623:                                                SV_ClientThink (cl, &oldcmd);
        !           624: 
1.1       root      625:                                }
1.1.1.2 ! root      626:                                SV_ClientThink (cl, &newcmd);
1.1       root      627:                        }
                    628: 
                    629:                        cl->lastcmd = newcmd;
                    630:                        break;
                    631: 
                    632:                case clc_stringcmd:     
                    633:                        s = MSG_ReadString (&net_message);
                    634: 
                    635:                        // malicious users may try using too many string commands
                    636:                        if (++stringCmdCount < MAX_STRINGCMDS)
                    637:                                SV_ExecuteUserCommand (s);
1.1.1.2 ! root      638: 
1.1       root      639:                        if (cl->state == cs_zombie)
                    640:                                return; // disconnect command
                    641:                        break;
                    642:                }
                    643:        }
                    644: }
                    645: 

unix.superglobalmegacorp.com

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