
/****************************************************************************/
/*                                                                          */
/*      NNstat -- Internet Statistics Collection Package                    */
/*                                                                          */
/*            Written by: Bob Braden & Annette DeSchon                      */
/*            USC Information Sciences Institute                            */
/*            Marina del Rey, California                                    */
/*                                                                          */
/*      Copyright (c) 1991 University of Southern California.               */
/*      All rights reserved.                                                */
/*                                                                          */
/*      Redistribution and use in source and binary forms are permitted     */
/*      provided that the above copyright notice and this paragraph are     */
/*      duplicated in all such forms and that any documentation,            */
/*      advertising materials, and other materials related to such          */
/*      distribution and use acknowledge that the software was              */
/*      developed by the University of Southern California, Information     */
/*      Sciences Institute.  The name of the University may not be used     */
/*      to endorse or promote products derived from this software           */
/*      without specific prior written permission.                          */
/*      THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR        */
/*      IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED      */
/*      WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR          */
/*      PURPOSE.                                                            */
/*                                                                          */
/****************************************************************************/ 

static char rcsid[]=
  "$Header: analyze.c,v 1.7 93/10/11 18:40:53 mogul Exp $";

/*                           analyze.c
 *
 *     This file contains many of the control routines needed for the
 *     analysis phase of statspy (the rest are in attach.c).  The main
 *     routines here are:
 * 
 *       Remote command decoder:
 *            Do_Request(), Get_attach()
 *         
 *       Basic interpreter for pseudo-program:
 *            interpret()
 *
 *       Command routines:
 *            Detach_SOBJ()
 *            Read_SOBJ(), Read_Names()
 *            Clear_SOBJ()
 *            Show_Fields(), Show_FNames()
 *            Do_Subnet(), Show_Subnets()
 *
 *       Read Response generator (common to all objects):
 *            PutReadResp()
 *
 */

/* CHANGES:
 *     21Nov88 ISI: Move DeleteSOPC to attach.c
 *     22Nov88 ISI: Detach reinitializes Ether driver for new config
 *     28Nov88 ISI: Generalize pseudo-program for case construction
 *     13Oct89 Merit: Access control; also fix some RT compiler noises.
 *     17Oct89 AKS/ISI: Add Subnet(), Show_Subnets()
 *       Rel 2.4:
 *     01Nov89 ISI: Add InitFields()
 *     29Nov89 OSU: Fix Check_restrict call.
 *     30Nov89 ISI: Add Version remote operation.
 *
 *       Rel 3.0:
 *     ISI: Move some routines around, and add printForest().
 *     ISI: Revise tree_prune() to handle indirection correctly.
 *     ISI: Move InitFields() to parse.c
 *     DEC: Ultrix changes
 *       Rel 3.02:
 *     DEC: Little-endian fix in Do_Subnet() 
 *
 *	Aug93/DECWRL: XDR is a pointer type; Alpha/OSF port
 */
 
#include <stdio.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include "stat.h" 
#include "sobj.h"

extern struct FI_info Fields[];  /* Everything there is to know about fields*/

char *Rmt_opn[] = {  /* Following is for debugging... it must match
                      * "Request & Response Operation Codes" in stat.h
                      */
    "End", "ATTACH", "DETACH", "READ", "CLEAR", "READCLEAR",
    "SHOW", "SHOWnames", "SUBNET",  "Error",
    "Record", "If", "Ifnot", "IfInv", "StartList", "AppendList", 
    "And", "Or", "SymIf", "SymIfNot", 
    "StartTSubl", "StartFSubl", "EndSubl", "Version",
    "NotIfInv", "SelInv", "Case:", "Select", "Default:"};
            
    /*  ACCESS CONTROL:
        Array element nonzero if corresponding operation requires
        write access (index corresponds to op code).  This list must
        match the available opcodes in stat.h! */
char WriteRqd[] = {
    0,      1,      1,      0,      1,      1,      0,      0,   
/*  End     Attach  Detach  Read    Clear   ReadCl  Show    Showname */
    1  };
/* Subnet */

    /* Head of list of SOBJ's */
SOBJ *SOBJ_list ;

extern long Timeout;
extern boolean HistSw;
extern char remotehost[];
extern Subnet SubHdr, *SubTail;
extern char *version;

char *SOBJ_error ;
char  Error_msg[256] ;
void  Global_stats();
extern boolean if_stats();

/*
 * Note: this whole business is nonsense.  Different systems
 * could have different tick rates
 */
#define       TICKSPERSEC  50 /* Sun Clock Frequency */
#ifdef    ULTRIX
#ifdef        vax
#undef		TICKSPERSEC
#define         TICKSPERSEC     100
#endif        vax
#ifdef        mips
#undef		TICKSPERSEC
#define         TICKSPERSEC     256
#endif        mips
#endif    ULTRIX
#ifdef	DECOSF
#ifdef	  __alpha
#undef		TICKSPERSEC
#define         TICKSPERSEC     1000
#endif	  __alpha
#endif	DECOSF
   
    /********************************************************************
     *
     *        BASIC INTERPRETER ENGINE
     *
     *******************************************************************/
 
interpret( sopcp)
    register SOPC *sopcp ;
    {
    register FIELD *FIptr;
    register SOBJ *sobjp;
    register SOPC *tp;
                                             
    while (sopcp) {             
            /* Call the generic Write routine
             *    specified by this SOPC
             */
        FIptr = sopcp->sopc_FIptr ;
        sobjp = sopcp->sopc_sobjp;
        
        if (sopcp->sopc_indir) {
                /* Need to evaluate filter object that was already evaluated
                 *     earlier in tree parse; just pick up its return code.
                 */
            sopcp->sopc_rc = sopcp->sopc_indir->sopc_rc;
        }
        else if (sobjp->sob_lock) {
            sobjp->sob_orphans++;
            sopcp->sopc_rc = WRRET_OK;
        }
        else {
            sobjp->sob_totalno++;
            sopcp->sopc_rc =  (sopcp->sopc_FIptr2) ?
                       /* Binary Object */
                (*(sobjp->sob_genop.write_xx)) (
                   sobjp, FIptr->field_ptr, FIptr->field_len,
                               sopcp->sopc_FIptr2->field_ptr, 
                               sopcp->sopc_FIptr2->field_len) :         
                       /* Unary Object */      
                (*(sobjp->sob_genop.write_xx)) (
                   sobjp, FIptr->field_ptr, FIptr->field_len);
        }
                /* For a record operation, ncases == 0 and rc == 0.
                 * Otherwise (filter operation), 
                 *     interpret rc-th sublist recursively.
                 */
        if (sopcp->sopc_rc < sopcp->sopc_ncases) {
            if (tp = sopcp->sopc_sublist[sopcp->sopc_rc])
                interpret(tp);
        }
               /* On to the next SOPC for this field... 
                */
        sopcp = sopcp->sopc_next;
    }
}


    /**************************************************************
     * 
     *     DECODER FOR REQUESTS FROM NETWORK. 
     * 
     *     Called when new input arrives on TCP connection,
     *     with open XDR streams for input, output.
     *
     **************************************************************/
     
Do_Request(xpin, xpout)
    XDR *xpin, *xpout;  /* (XDR *) */
    {
    int Req_op;
    extern char ObjName[];  
    char *sp = ObjName;
    int rc, len;
    struct subnet_parm Sparm, *addrmp = &Sparm;
    extern struct sockaddr_in fromaddr;
    void trunc();       
        /* Poor man's discriminated union */
    extern int Get_attach();
        
    if (!xdr_int(xpin, &Req_op)) {
        netclose();
        return;
    }
    else {
        if (HistSw) {
            printf("=>Rmt %s --  Host=%s  %s",
                Rmt_opn[Req_op], remotehost, ctime(&CurrTime));
        }
        rc = FALSE;
        /*  Check for access restrictions.  Disallow all functions if
            no access is allowed.  Disallow write functions if readwrite
            access isn't allowed. */
        if (Req_op < sizeof(WriteRqd)) 
            {
            if (!Check_restrict(fromaddr.sin_addr.s_addr, WriteRqd[Req_op] ))                       {
                Err_reply(xpout, "Access restrictions in effect.");
                netclose();
                return;
            }
        }
        
        switch (Req_op) {
        
        case READ_op:
        case READCL_op:
            if (xdr_string(xpin, &sp, MAX_OBJNAME))  {
                trunc(&sp);
                rc = Read_SOBJ(xpout, sp, (Req_op == READCL_op));
            }
            break;
        
        case CLEAR_op:
            if (xdr_string(xpin, &sp, MAX_OBJNAME)) {
                trunc(&sp); 
                rc = Clear_SOBJ(xpout, sp);
            }  
            break;
        
        case DETACH_op:
            if (xdr_string(xpin, &sp, MAX_OBJNAME)) {
                trunc(&sp); 
                rc = Detach_SOBJ(xpout, sp); 
            }
            break;
        
        case SHOW_op:
        case SHOWnames_op:
            if (xdr_string(xpin, &sp, MAX_FLDNAME)) {
                trunc(&sp); 
                rc = Show_Fields(xpout, sp);
            } 
            break;
            
        case ATTACH_op:
            rc = Get_attach(xpin);
            if (rc == CMD_EOF) {
                rc = FALSE;  /* XDR conn failed */
                break;
            }
            else if (rc == CMD_OK)
                Cmd_reply(xpout, ATTACH_op, "");
            else  
                Err_reply(xpout, SOBJ_error);
            return;             
            
        case SUBNET_op:
            if (xdr_bytes(xpin, &addrmp, &len, sizeof(struct subnet_parm))) 
                rc = Do_Subnet(xpout, Sparm);
            break;
            
        case VERSION_op: /* Ask for our version; no operand */
            Cmd_reply(xpout, VERSION_op, version);
            rc = TRUE;
            break;
            
        default:
            Err_reply(xpout, "Unimplemented Op");
            netclose();
            return;
        }
        if (!rc && HistSw)                                  
            printf("=>Conn failed/XDR out  Host=%s\n", remotehost);
        if (rc) Flush(xpout);
    /***netclose(); ***19Feb88***/
    }           
} /* Do_Request() */   

    /*
     *   Get_attach() -- decode and perform remote attach request
     *
     */   
int Get_attach(xpin)
    XDR *xpin;  /* (XDR *) */
    {
    boolean isnot;
    int code, caseno;
    struct invokes Invokes;
    u_int32 Values[MAXPARMS*LONGPERVAL];
    
    Invokes.inv_nparms = MAXPARMS*LONGPERVAL;
    Invokes.inv_parmp = Values;
    
    if (Xdebug)
         printf("\nATTACH:\n");
    while (1) {
        if (!xdr_int(xpin, &code)) {
            CleanUp(0);
            return(CMD_EOF);
        } 
        if (Xdebug) {
             if (code > sizeof (Rmt_opn)/sizeof(Rmt_opn[0]))
                 printf ("-->op= %d (out of range)\n", code);
             else
                 printf("-->op= %d %s\n", code,  Rmt_opn[code]);
         }                     
        isnot = FALSE;
        switch (code) {
        
        case EndofReq:      
            if (!Install_it())
                break;  /* failed... go cleanup and return error */
            return(CMD_OK);
            
        case Error:
            SOBJ_error = "Terminated by error";
            CleanUp(0);
            return(CMD_ERR);
            
        case StartList:
            doNull();
            continue;
            
        case AppendList:
            doAppend();
            continue;
            
        case Ifnot:
            isnot = TRUE;
        case If:
            if (!doIf(isnot))  break;
            continue;
            
        case SymIf:
            if (!doSymIf()) break;
            continue;
            
        case And:
            if (!doAnd()) break;
            continue;
            
        case Or:
            if (!doOr()) break;
            continue;
            
        case Record:
        case IfInvoke:
        case NotIfInv:
        case SelInvoke:
            if (!xdr_invoke(xpin, &Invokes) ||
                !doInvoke(code, &Invokes)) 
                break;
            continue;
        
        case Case:
            if (!xdr_case(xpin, &caseno, &Invokes) ||
                !doCase(caseno, &Invokes)) 
                break;
            continue;
            
        case Select:
            if (!doSelect())
                break;
            continue;
         
        default:
            SOBJ_error = "Request format error";
            break;
        }
            /* Get here if we detected error which must be reported.
             *    Play through the rest of the request first...
             */ 
        CleanUp(0);
        while (code != EndofReq && code != Error) 
            if (!xdr_int(xpin, &code)) return(CMD_EOF);
        return(CMD_ERR);           
    }
} /* Get_attach() */
    



    /*************************************************************              
     *
     *    ROUTINES TO PERFORM DETACH, CLEAR, READ, AND SHOW COMMANDS
     *
     *      Primary routines are:
     *        Detach_SOBJ
     *        Clear_SOBJ
     *        Read_SOBJ
     *        Show_Fields
     *
     *    Each takes as parms an output file handle and a parm string.
     *      The output file handle will be either the local console
     *      ((ISTTY(fh)) == TRUE) or else an XDR write handle.
     *    Each returns FALSE if an XDR write fails, else returns TRUE.
     *
     *************************************************************/

#define MAX_DELETE 2048
        
/*
 *
 *   Detach_SOBJ(fh,  obj-spec )
 *
 *     Detach matching objects.  fh is output file handle.
 *
 */
boolean Detach_SOBJ(fh, objspec )
    XDR *fh;
    char *objspec ;
    {
    register FIELD *FIptr;
    boolean toomany = FALSE;
    char rep_buf[MAX_DELETE];
    struct ODV rep_odv;
    int place;
    
    rep_odv.odv_left = MAX_DELETE;
    rep_odv.odv_cp = rep_buf;
    rep_odv.odv_file = (fh == NULL) ? 0 : 1;
    
    deinitdevice();  /* Stop packet filter */
    sprintf(rep_buf, "DELETED OBJECT(S) @ %s", ctime(&CurrTime));
    ODVance(&rep_odv);
    place = rep_odv.odv_left;
    
    for (FIptr = &Fields[1]; FIptr->field_name; FIptr++) {
        if (!tree_prune(&rep_odv, &FIptr->field_opcs, objspec)) {
            toomany = TRUE;
            break;
        }
    }
            
    if (toomany)
        Err_reply(fh, "TOO MANY DETACHES!!\n%s", rep_buf);
    else if (rep_odv.odv_left > place) 
        Cmd_reply(fh, DETACH_op, rep_buf);
    else 
        Err_reply(fh,  "detach: No matching objects");
    ComputeCuminv();
    SetEtherType(); /* Restart packet filter */
    return(TRUE);
} /* boolean Detach_SOBJ() */ 

    
/*
 *   Recursive invocation deletion ... prune call subtrees.  Called with
 *     the address of a list of SOPC's and an object spec; for each call in
 *     list which matches the object spec,  detach all calls in subtree.
 *
 *     This routine is called explicitly from a detach command,
 *     or implicitly when an attach command fails part way through.
 */
boolean tree_prune(odvp, sopp, objspec)
    struct ODV *odvp;
    SOPC **sopp;
    char  *objspec;
    {
    register SOPC *sopcp, *ip;
    int i;
    char *submatch;
    boolean allgone, matches;   

    while (sopcp = *sopp) {
        if ((odvp->odv_cp)&& odvp->odv_left < MAX_OBJNAME+3) 
            return(FALSE);  /* no room for another entry */
        
        ip = sopcp->sopc_indir;
        if (ip && ip->sopc_FIptr1 == NULL) { /* This was indirect to SOPC that
                                              *  is marked to be deleted. */
             sopcp->sopc_indir = NULL;
             if (--ip->sopc_irefs == 0)
                 DeleteSOPC(ip);
        }                
        matches = FALSE;
        submatch = (objspec == NULL)? NULL: 
                   (matches =  match(objspec, sopcp->sopc_sobjp->sob_name)) ? 
                                                 "*"  : objspec ;
                                            
                /* Prune ALL sublists (if any), of SOPC */ 
        allgone = TRUE;
        for (i=0; i<sopcp->sopc_ncases; i++) {
            if ((int) sopcp->sopc_sublist[i] > MAX_STUB) {            
                if (!tree_prune(odvp, &sopcp->sopc_sublist[i], submatch))
                    return(FALSE);  /* return FALSE if run out of space... */
                if (sopcp->sopc_sublist[i]) allgone = FALSE;
            }
        }
            
        if (matches || (sopcp->sopc_ncases)&&allgone) {
                /*  Unchain from list and delete SOPC (unless some other SOPC
                 *    is still indirect from this one...)
                 */
            if (!PrSOPCdel(odvp, sopcp)) return(FALSE);
           *sopp = sopcp->sopc_next;  /* unlink it */
            if (sopcp->sopc_irefs)
                sopcp->sopc_FIptr1 = NULL;  /* Mark for deferred deletion */
            else
                DeleteSOPC(sopcp);
            continue; /* (use *sopp again) */
        }
        
            /* Else continue with next in list... */
        sopp = &sopcp->sopc_next ;
    }
    return(TRUE);
} /* int tree_prune() */


    /* Detach messages: 
     *   If output file is local console, display message.
     *   If object is to be deleted, append its name to delete name
     *   string.
     */
int PrSOPCdel(odvp, sopcp)
    struct ODV *odvp;
    register SOPC *sopcp;
    {
    SOBJ *sobjp = sopcp->sopc_sobjp;
    
    if (ISTTY(odvp->odv_file)) {
        printf("DETACH OBJECT %.32s%s FROM INVOCATION BY FIELD %.32s", 
             sobjp->sob_name, 
             (sopcp->sopc_indir)? "/*" : "", 
             sopcp->sopc_FIptr->field_name) ;
         
        if (sobjp->sob_opccnt < 2) 
            printf(" AND DELETE OBJECT\n", sobjp->sob_name);
        else
            printf("\n");
    }
    if ((odvp->odv_cp)&&sobjp->sob_opccnt < 2) {
        sprintf(odvp->odv_cp, " %s", sobjp->sob_name);
        ODVance(odvp);
    }
    return(TRUE);
} /* int PrSOPCdel() */


     
#define MAX_CLEAR  2000

/*
 *   Clear_SOBJ( fh, object-spec )
 * 
 *              fh = output file handle
 *
 *     Clear all objects whose names match objname-spec.
 *
 */
boolean Clear_SOBJ(fh, objspec )
    XDR *fh;
    char *objspec ;
    {
    SOBJ *sobjp = SOBJ_list;
    int found = 0;
    char repbuf[MAX_CLEAR];
        
    sprintf(repbuf, "CLEARED OBJECT(S) @ %s", ctime(&CurrTime));
    
    while (sobjp) {
        if (  match(objspec, sobjp->sob_name) 
                && (sobjp->sob_genop.clear_xx)) {
                
            found = 1;
            if (strlen(repbuf)+MAX_OBJNAME+2 >= MAX_CLEAR) {
                Err_reply(fh, "TOO MANY!!\n%s", repbuf);
                return(TRUE);
            }
            strcat(repbuf, sobjp->sob_name);
            if (sobjp->sob_lock) {
                    /* Object is locked by someone reading it. Set flag and
                     *  defer clear until it is unlocked.
                     */
                sobjp->sob_flags |= CLEAR_REQ;
                strcat(repbuf, "* ");
            }
            else {              
                Clear_it(sobjp);
                strcat(repbuf, "  ");
            }
        }
        sobjp = sobjp->sob_next ;
    }
    
    if (found) 
            /* We did find some matching object names to clear */
        Cmd_reply(fh, CLEAR_op, repbuf);
    else
        Err_reply(fh, "clear: No matching objects");
    return(TRUE);
} /* boolean Clear_SOBJ() */
 
 
/*
 *
 *   Read_SOBJ( fh, objname-spec, isclear)
 *
 *              fh = output file handle
 *
 *     Read all objects named objects whose names match objname-spec.
 *     If 'isclear' is true, clear objects after read.
 *     Return FALSE if XDR write fails.
 *
 */
boolean Read_SOBJ( fh, objspec, isclear )
    XDR *fh;
    char *objspec;
    int isclear;
    {
    register SOBJ *sobjp = SOBJ_list ;
    int   rc;
    char  stats_buf[256];
    boolean found = FALSE;
            
    if (ISTTY(fh)) {
            /* On local console, display global statistics */
        Global_stats(stats_buf);
        printf("\n%s", stats_buf);
            /* and interface statistics, if available */
        if (if_stats(stats_buf))
            printf("%s", stats_buf);
    }
        
   
    while (sobjp) {
        if (sobjp->sob_genop.read_xx) {  /* object is readable */
            if (*objspec == '\0') {
                   /* read ?  */
                if (!found && !Put_OK(fh)) return(FALSE) ;
                found = TRUE;                       
                if (!PutReadResp(fh, 0, sobjp))  /* Don't send any data! */
                    return(FALSE);
            }
            else if (match(objspec, sobjp->sob_name)) {
                if (strcmp(sobjp->sob_name, NONAME) == 0) {
                    /* Unnamed object. Don't read it, but may clear it... */
                    if (isclear)  Clear_it(sobjp);
                }
                else {
                    if (!found) {
                        if (!ISTTY(fh)&& !Put_OK(fh)) return(FALSE);
                        found = 1;
                    }
                    if (!ISTTY(fh)) alarm(Timeout);  /* Reset timeout */
                    sobjp->sob_lock++;  
                    if (isclear) sobjp->sob_flags |= CLEAR_REQ; 
            
                        /* Call generic read routine for object */      
                    rc = (*(sobjp->sob_genop.read_xx))(fh, sobjp);
            
                    sobjp->sob_lock--;
                    if (!rc) return(FALSE); /* XDR/Write Error */
                    if ((sobjp->sob_lock == 0) && (sobjp->sob_flags&CLEAR_REQ)) 
                        {
                            /* Someone tried to clear object while
                             *   we had it locked.  Clear it now.
                             */
                        Clear_it(sobjp);
                    }
                } 
            }
        }
        sobjp = sobjp->sob_next ;
    }  /* outer while loop */

    return(EndResponse(fh, (isclear)?READCL_op:READ_op, found));
} /* boolean Read_SOBJ() */

 
                    
Clear_it(sobjp)
    SOBJ *sobjp;
    {
    (*(sobjp->sob_genop.clear_xx))(sobjp);
    sobjp->sob_totalno = sobjp->sob_orphans = 0;            
    sobjp->sob_cleartime = CurrTime;
    sobjp->sob_flags &= ~CLEAR_REQ;
}



    /* 
     *    Show_Fields():  'show <field spec>'
     *
     *   Format pseudo-program of analyzer
     */

boolean Show_Fields(fh, fields)
    XDR *fh;
    char *fields;
    {
    register FIELD *FIptr;
    boolean  found = FALSE;
    int   op = SHOW_op;
    
    if (*fields == '\0') {  /* 'show ?' => just list field names */
        return(Show_FNames(fh));
    }
    
    for (FIptr = &Fields[1]; FIptr->field_name; FIptr++) {
        if (FIptr->field_opcs && match(fields, FIptr->field_name)) {       
            if (!found && !Put_OK(fh)) 
                return(FALSE);
            found = TRUE;
            if (!ISTTY(fh)&&!xdr_int(fh, &op)) 
                return(FALSE);
            if (!SHOWfield(fh, FIptr->field_name, FIptr->field_fino,
                               (ISTTY(fh))))    
                return(FALSE);
                
            if (!show_tree(fh, FIptr->field_opcs, 0))
                return(FALSE);
        }
    }
    
    return(EndResponse(fh, SHOW_op, found));
} /* boolean Show_Fields() */
    

boolean show_tree(fh, sopcp, level)
    register SOPC  *sopcp ;
    int  level ;
    XDR *fh ;
    {
    int  code; 
    struct invokes Invokes;
    int  islog = (ISTTY(fh));
    int  i;
            
    while (sopcp) {
	bzero(&Invokes, sizeof(Invokes));
        Invokes.inv_classno = sopcp->sopc_sobjp->sob_class;
        strcpy(Invokes.inv_field1, sopcp->sopc_FIptr->field_name);
        Invokes.inv_field2[0] = '\0';
        if (sopcp->sopc_FIptr2)
            strcpy(Invokes.inv_field2, sopcp->sopc_FIptr2->field_name);
        strcpy(Invokes.inv_objname, sopcp->sopc_sobjp->sob_name);      
        if (sopcp->sopc_indir) strcat(Invokes.inv_objname, "/*");
            /* Mark indirect */
            
        code = (sopcp->sopc_ncases == 0)? Record:
               (sopcp->sopc_sobjp->sob_class == SELECTOBJ)? Select : If;
        if (!ISTTY(fh) && !xdr_int(fh, &code)) return(FALSE);
        if (!SHOWinv(fh, &Invokes, level, code, islog))
                return(FALSE);
                
        for (i=sopcp->sopc_ncases-1; i >= 0; i--)
            if (sopcp->sopc_sublist[i]) {
                if (!SHOWsublist(fh, level+1, 
                        (code == Select)? ((i)?Case:Default) : 
                                          ((i)?StartTsubl:StartFsubl), 
                        islog) ||
                    !show_tree(fh, sopcp->sopc_sublist[i], level+1))
                        return(FALSE);
            }
            
        sopcp = sopcp->sopc_next ;
    }
    
    code = EndofSubl;
    if (!ISTTY(fh))
        if (!xdr_int(fh, &code)) return(FALSE);
    return(TRUE);
} /* boolean show_tree() */
  
         
#define MAX_FSHOW  1000   /* Max buffer for show ? command reply */

    /*
     *  Show ?  => output simple list of field names
     */
boolean Show_FNames(fh)
    XDR *fh;
    {
    char showbuf[MAX_FSHOW];
    register FIELD *FIptr;   
    register n, showoff;        
         
    Global_stats(showbuf);
    if_stats(endof(showbuf));
    strcat(showbuf, "Fields are: \n");
    showoff = strlen(showbuf);          
    for (FIptr = &Fields[1]; FIptr->field_name; FIptr++) {
        if (FIptr->field_fino) {
            n = strlen(FIptr->field_name);
            if (showoff+n+3 >= MAX_FSHOW) break;
            strcpy(&showbuf[showoff],  FIptr->field_name);
            strcpy(&showbuf[showoff+=n], "  ");      
            showoff += 2;
        }
        
    }         
    if (ISTTY(fh))
        printf( "%s\n", showbuf) ;
    else  
        Cmd_reply(fh, SHOWnames_op, showbuf);
    return(TRUE);
} /* Show_FNames() */ 

    
    /* 
     *   Subnet command issued locally or remotely
     */    
boolean Do_Subnet(fh, Sparm)
    XDR *fh;  /* output file handle */
    struct subnet_parm Sparm;
    {
#ifdef SUBNETS
    register Subnet *subp;
    char *reply = "Subnet replaced.\n";
    extern Subnet *FindSubnet();
    
    if (Sparm.addr == 0)
        return(Show_Subnets(fh));
        
        /* Request to add new (addr, mask) pair.  Check for address already
         *  in table, and if so just replace mask; otherwise, make new entry.
         */    
    if ((subp = FindSubnet(Sparm.addr)) == NULL) {
        subp = (Subnet *) getspace(sizeof(Subnet));
        SubTail->Link = subp;
        SubTail = subp;
#ifdef LITTLEEND
	subp->Addr = Sparm.addr;
#else
        subp->Addr = htonl(Sparm.addr);
#endif
        reply = "Subnet added.\n";
    }
#ifdef LITTLEEND
    subp->Mask = Sparm.mask;
#else
    subp->Mask = htonl(Sparm.mask);
#endif
    Cmd_reply(fh, SUBNET_op, reply);
    
#else
    Err_reply(fh, "Subnets not supported");
#endif
    return(TRUE);
} /* Do_Subnet() */

#define MAX_SUBNET_SP 256
    /*
     * subnet ?  => output list of subnetted networks and masks
     *
     */
boolean Show_Subnets(fh)
    XDR *fh;
    {
    register Subnet *snp = SubHdr.Link;
    char sub_buf[MAX_SUBNET_SP];
    
    if (snp) {
        strcpy(sub_buf, "Subnetted nets and masks:\n");
        for (snp = SubHdr.Link; snp; snp = snp->Link) {
            if (strlen(sub_buf)+40 > MAX_SUBNET_SP) break;
            print_val(endof(sub_buf), &snp->Addr, DTYP_IP, 4);
            strcat(sub_buf, "\t");
            print_val(endof(sub_buf), &snp->Mask, DTYP_bits, 4);
            strcat(sub_buf, "\n");
        }
    }
    else
        strcpy(sub_buf, "No subnets\n");
        
    Cmd_reply(fh, SUBNET_op, sub_buf);
    return(TRUE);
} /* Show_Subnets() */

       
 /*  ComputeCuminv
  *     Compute for each node of the Field Parse Tree (FPT) the 
  *     cumulative invocation count, i.e., the number of nodes
  *     at or below the present node to which invocations attached.
  */
ComputeCuminv()
    {
    register int i;
    register FIELD *Fp;
    
    for (Fp = &Fields[1]; Fp->field_name; Fp++) Fp->field_cuminv = 0;
    
    for (Fp = &Fields[1]; Fp->field_name; Fp++)
        if ((i = Fp->field_fino) &&Fp->field_opcs) {
            Fp->field_cuminv++;
            while ((i = Fields[i].field_parent) > 0)
	    	Fields[i].field_cuminv++;
        }
}
      
   
    /***********************************************************
     *
     *   UTILITY ROUTINES
     *
     ***********************************************************/  
 
      
void
Global_stats(outp)
    char *outp;
    {
    extern u_long packetcnt, MaxTickRun, MaxSecRun;
    extern time_t StarTime;
    time_t delta = CurrTime - StarTime;

    sprintf(outp,
        "Acquired %d packets in %d secs => %d(avg) %d(max) %d(inst) /sec\n",
            packetcnt, delta, (delta)?(packetcnt/delta):packetcnt,
                  MaxSecRun, MaxTickRun*TICKSPERSEC);
}  /*Global_stats() */ 


Flush(fh)
XDR *fh;
    {
    extern FILE *noutfp;
    
    if (ISTTY(fh)) {
        printf("\n");
        fflush(stdout);
    }
    else    
        fflush(noutfp);
} /* Flush() */


char *savestring(cp) 
    char *cp ;
    {
    char *sp ;
    
    if (sp = (char *) malloc(strlen(cp)+1)) 
        strcpy(sp, cp) ;
    return(sp) ;
} /* savestring() */

    /*
     *  Map Field Name into Pointer
     */
FIELD *find_field(fname)
    register char *fname;
    {
    register FIELD *FIptr;
    
    for (FIptr = &Fields[1]; FIptr->field_name; FIptr++)
        if (strcmp(fname, FIptr->field_name) == 0) return(FIptr) ;
    return(NULL);
} /* find_field() */       


attach_error(msgp, namep)
    char *msgp, *namep;
    {
    sprintf(SOBJ_error = Error_msg, "ATTACH error -- %s: %s", 
           msgp, namep);
}  /* attach_error() */


void trunc(cpp)  /* truncate leading and trailing blank/semicolon */
    char **cpp;
    {
    int n;
    register char *cp = *cpp;
    
    while (*cp == ' ') cp++;
    *cpp = cp;
    n = strcspn(cp, " ;");
    *(cp+n) = '\0'; 
} /* trunc() */
    

boolean Put_OK(fh) 
    XDR *fh;
    {
    int  resp_ok = RESP_OK;
    if (ISTTY(fh)) {
        puts("OK\n") ;
        return(TRUE);
    }
    else
        return(xdr_int(fh, &resp_ok));
} /* boolean Put_OK() */


/* End multi-segment reponse (READ/SHOW)
 */
boolean EndResponse(fh, op, found)
    XDR *fh;
    boolean found;
    {   
    if (!found) 
        Err_reply(fh, "(None)");        
    else if (ISTTY(fh))
        printf("\n");
    else {
        int Last = EndofResp;   
        xdr_int(fh, &Last);  /* End-of-response indicator */
        Flush(fh);
        if (HistSw)
            printf("=>%s OK --  Host=%s\n", Rmt_opn[op], remotehost);
    }
    return(TRUE);
} /* EndResponse() */
            

Cmd_reply(fd, op, cp)
    XDR *fd;
    int op;
    char *cp;
    {   
    int  code = RESP_OK;
    
    if (ISTTY(fd)) {
        printf("OK: %s\n", cp);
    }
    else {
        xdr_int(fd, &code);
        xdr_int(fd, &op);
        xdr_string(fd, &cp, 65535);
        Flush(fd);
        if (HistSw)
            printf("=>OK  -- Op=%d --  Host=%s\n", op, remotehost);
    }
} /* Cmd_reply() */
        

Err_reply(fd, format, v1, v2, v3, v4)
    XDR   *fd;
    char *format;
    int  v1, v2, v3, v4;
    {
    char replybuff[256], *rbp = replybuff;
    int  code = RESP_ERROR;
         
    sprintf(replybuff, format, v1, v2, v3, v4);
    
    if (ISTTY(fd)) {
        printf("ERROR: %s\n", replybuff);
        fflush(stdout);
    }
    else {
        xdr_int(fd, &code);
        xdr_string(fd, &rbp, 65535);
        Flush(fd);
        if (HistSw)
            printf("=>Err: %s -- Host=%s\n", rbp, remotehost);
    }
}
    
ODVance(odvp)
    register struct ODV *odvp;
    {
    int i = strlen(odvp->odv_cp);
    if ((odvp->odv_left -= i) >= 0)
        odvp->odv_cp += i;
}
