/*
 *========================================================================
 * $Id: xmlsysd_util.c 96 2006-07-20 17:56:16Z rgb $
 *
 * See copyright in copyright.h and the accompanying file COPYING
 *========================================================================
 */

#include <wulfware/libwulf.h>

char **fields = (char **)0;
#define MAXFIELDNUMBER 12

/*
 * We have to use distinct message scratch buffers for initialization and
 * updates, as they can be occurring "simultaneously" in different threads.  It
 * is convenient and efficient to use large global buffers for this purpose.
 * However, to use a single routine in both threads, the calling routine has to
 * pass a pointer to the right buffer to use to create_xmlsysd_xpath(), which
 * then has to pass it downstream for read routines to use.  Alternatively we
 * could use dynamic memory allocation for this, but this burns cycles
 * and can easily leak.
 *
 * NOTE WELL!  msgbuf MUST have length 64K.
 */

int recv_xmlDoc(Host *hostptr,char *msgbuf)
{

 int msglength;
 xmlNodePtr root;

 if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
   fprintf(OUTFP,"D_XMLSYSD: Starting recv_xmlDoc.  Use -v %d to focus.\n",D_XMLSYSD);
 }

 /*
  * Allocate scratch space to parse up to twelve items in a line, far more than
  * we need.  We only do this the first time through.
  */
 if(fields == 0){
   fields = allocate_fields(MAXFIELDNUMBER,K);
 }

 /*
  * If hostptr->connected == -1, the host is KNOWN to be
  * unknown (unresolvable) and we don't try to read from it.  We
  * just return 0.
  */
 if(hostptr->connected == -1 ) return 0;

 /*
  * This routine:
  *   a) Reads in the lf-terminated first line with readline
  *      and parse out the Content-Length (required).  This
  *      is semi-http-1.1 compliant, although I don't bother
  *      with anything I don't really need.
  *   b) Skips exactly one blank line, required by http-1.1 to
  *      separate the Content-Length header from the message
  *      body.
  *   c) The message body (which had better be exactly Content-Length
  *      long).
  *
  * As we make progress, we'll try to make this moderately robust and
  * add timeouts so that the application can tolerate hosts that die in
  * mid-stream in a variety of ways.
  */

 /* Read in the 1st header line and parse it. */
 if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
   fprintf(OUTFP,"D_XMLSYSD: Ready to read 1st header line from client_fd %d\n",hostptr->client_fd);
   
 }
 if(readline_from_hostptr(hostptr,msgbuf,K64)){
   if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
     fprintf(OUTFP,"D_XMLSYSD: Read in 1st header line: %s\n",msgbuf);
   }
 } else {
   if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
     fprintf(OUTFP,"D_XMLSYSD: NO 1st LINE TO READ!  Oops.\n");
   }
   return(0);
 }

 parse(msgbuf,fields,MAXFIELDNUMBER,K);
 msglength = atoi(fields[1]);
 if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
   fprintf(OUTFP,"D_XMLSYSD:  = %0x, fields = %0x\n",msgbuf,fields);
   fprintf(OUTFP,"D_XMLSYSD: fields[0] = %0x, fields[1] = %0x\n",fields[0],fields[1]);
   fprintf(OUTFP,"D_XMLSYSD: Parsed %s = %s\n",fields[0],fields[1]);
 }

 /* SHOULD read blank line (only) as 2nd header line */
 if(readline_from_hostptr(hostptr,msgbuf,K64)){
   if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
     fprintf(OUTFP,"D_XMLSYSD: Read in 2nd (blank) header line: %s\n",msgbuf);
   }
 } else {
   if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
     fprintf(OUTFP,"D_XMLSYSD: NO 2nd LINE TO READ!  Oops.\n");
   }
   return(0);
 }

 /* Now we should read the actual message */
 if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
   fprintf(OUTFP,"D_XMLSYSD: About to read in message of length %d\n",msglength);
   
 }
 if(msglength > K64){
   fprintf(stderr,"Error:  msglength %d greater than len %d in recv_xmlDoc()\n",
      msglength,K64);
   exit(0);
 }
 /*
  * If we failed to read a line, we'll assume the daemon went away.
  */
 if(msglength == 0){
   return(0);
 }

 /* 
  * Now read in exactly msglength bytes.  Accept no substitutes.
  * This MUST get some error control, as we have to skip the
  * the parse and scream and shout if a host dies during readn or
  * elsewhere.
  *
  * Basically, if readn or readline fail (either one) we need to punt
  * and return(0).
  */
 bzero(msgbuf,K64-1);

 /*
  * These two lines let us test our SO_LINGER settings.  We send
  * commands to the daemons, wait a bit so that their output
  * queues are full, and then exit.  This should leave the daemons
  * with an undelivered message and a dead socket.  If SO_LINGER
  * is set correctly on the daemon side, the daemon instance will
  * discard the queue and exit immediately without entering the
  * Dread CLOSE_WAIT Loop (trying to finish delivering the last
  * message).
 sleep(1);
 fields[31012][0] = (char) 77;
  */

 /*
  * If for any reason we bomb reading in the main messages, we
  * silently return 0 so the caller can mark the socket down.
  * NOTE WELL!  msglength here is the actual length of the message
  * being received, not the size of the buffer.  This needs to
  * be made cleaner and more obvious.
  */
 if( (readn_from_hostptr(hostptr,msgbuf,msglength) == -1) ) {
   if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
     fprintf(OUTFP,"D_XMLSYSD: NO LINE TO READ!  Oops.\n");
   }
   return(0);
 }
 msgbuf[msglength] = (char)NULL;	/* terminate string */
 if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
   fprintf(OUTFP,"D_XMLSYSD: Received message of length %d:\n%s",msglength,msgbuf);
   fprintf(OUTFP,"D_XMLSYSD: About to parse its xml for host %s:\n",hostptr->hostname);
 }

 /* 
  * Parse the xml into this hosts xmlDoc pointer.  We MUST free it
  * later after extracting what we need of this host's information.
  */
 hostptr->doc = xmlParseMemory(msgbuf,msglength);
 if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
   fprintf(OUTFP,"D_XMLSYSD: Ran xmlParseMemory on the buffer.\n");
 }

 /* 
  * We need some error checking in case something went wrong with read 
  * or parse not picked up by readn etc.
  */
 if(hostptr->doc == 0){
   fprintf(stderr,"Error: xml document found in memory not converted to doc.\n");
   fprintf(stderr,"  host being read: %s\n",hostptr->hostname);
   fprintf(stderr,"  Dump of document:\n");
   fprintf(stderr,"%s",msgbuf);
   return(0);
 }

 root = xmlDocGetRootElement(hostptr->doc);

 /* Barf if there is no root element */   
 if(root == NULL) {
   fprintf(stderr,"Error: xml document has no root element.\n");
   fprintf(stderr,"  host being read: %s\n",hostptr->hostname);
   fprintf(stderr,"  Dump of document:\n");
   fprintf(stderr,"%s",msgbuf);
   return(0);
 } 

 /* Barf if it is the WRONG root element */
 if( strcasecmp((char*) root->name, "xmlsysd") !=0 ) {
   fprintf(stderr,"Error: xml root = %s instead of xmlsysd.\n",root->name);
   fprintf(stderr,"  host being read: %s\n",hostptr->hostname);
   fprintf(stderr,"  Dump of document:\n");
   fprintf(stderr,"%s",msgbuf);
   return(0);
 } 

 /* 
  * Pat ourselves on the back and crow if we got the document and
  * successfully parsed it.  If we get all the way through here, we
  * should likely return 1.  Anywhere above that we currently exit(0)
  * we should probably return 0 instead.
  */
 if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
   fprintf(OUTFP,"D_XMLSYSD: Received and parsed %s from %s. Done!\n",root->name,hostptr->hostname);
   
 }

 return(1);

}

/*
 *========================================================================
 * Receive the xmlsysd return, process it until we have a working
 * xpath entry for the remaining parsing of the various entries.
 * Note that Bad Things will happen if dctl state doesn't accurately
 * reflect the state of the remote daemon; for now we initialize that
 * state on changes only and right after e.g. changing displays.
 *========================================================================
 */
void create_xmlsysd_xpath(Host *hostptr,char *msgbuf)
{

 /* 
  * scratch: current timestamp and delta (to microsecond resolution) 
  */
 double timestamp, delta_time, delta_value, new_value;
 long seconds,useconds,delta_sec,delta_usec,new_ivalue;
 unsigned long new_uvalue;

 if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
   fprintf(OUTFP,"D_XMLSYSD: create_xmlsysd_xpath() for host %s.\n",hostptr->hostname);
   fprintf(OUTFP,"D_XMLSYSD: hostptr = %0x\n",hostptr);
 }

 /* trigger a send */
 if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
   fprintf(OUTFP,"D_XMLSYSD: Sending command %s to fd %d\n",commands[SEND],hostptr->client_fd);
   
 }
 send_command_to_host_by_ptr(hostptr,commands[SEND]);

 /* Read reply, put it into xml xpath format */
 if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
   fprintf(OUTFP,"D_XMLSYSD: Getting xml doc from host %s\n",hostptr->hostname);
   
 }
 if(recv_xmlDoc(hostptr,msgbuf) == 0){
   /*
    * The host is either down or it has a broken daemon.
    * We mark it down as best we can and it goes in the queue
    * to be reconnected later.
    */
   if(hostptr->client_fd > 0) close(hostptr->client_fd);
   hostptr->client_fd = 0;
   hostptr->connected = 0;
   if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
     fprintf(OUTFP,"D_XMLSYSD: Failed to receive doc from host %s\n",hostptr->hostname);
     fprintf(OUTFP,"D_XMLSYSD: Closed connection, will try to reopen later.\n");
     
   }
   return;
 }
 if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
   fprintf(OUTFP,"D_XMLSYSD: Received xml doc %0x\n",(int) hostptr->doc);
   fprintf(OUTFP,"D_XMLSYSD: Now we convert it to an xpath context pointer.\n");
   
 }
 hostptr->xp_doc = xmlXPathNewContext(hostptr->doc);
 if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
   fprintf(OUTFP,"D_XMLSYSD: Done getting xpath context %0x for %s\n",hostptr->xp_doc,hostptr->hostname);
 }

}

/*
 *========================================================================
  * Destructor for xmlsysd document's xpath context and xml doc itself.
 *========================================================================
 */
void destroy_xmlsysd_xpath(Host *hostptr)
{

 /*
  * First we free the XPath context pointer for this host.
  */
 if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
   fprintf(OUTFP,"D_XMLSYSD: destroy_xmlsysd_xpath for host %s.\n",hostptr->hostname);
   fprintf(OUTFP,"D_XMLSYSD: Freeing context %0x\n",hostptr->xp_doc);
 }
 if(hostptr->xp_doc == NULL){
   fprintf(OUTFP,"D_XMLSYSD: Well Damn!  What happened to %s's context!\n",hostptr->hostname);
 }
 xmlXPathFreeContext(hostptr->xp_doc);

 /* Then free the xmlDoc itself */
 if(hostptr->doc == NULL){
   fprintf(OUTFP,"D_XMLSYSD: Well Damn!  What happened to %s's document!\n",hostptr->hostname);
 }
 if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
   fprintf(OUTFP,"D_XMLSYSD: Freeing xml doc %0x\n",hostptr->doc);
 }
 xmlFreeDoc(hostptr->doc);

 if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
   fprintf(OUTFP,"D_XMLSYSD: Freed hostptr->doc = %0x hostptr->xp_doc = %0x\n",
     hostptr->doc, hostptr->xp_doc);
   fprintf(OUTFP,"D_XMLSYSD: All freed. Completed destroy_xmlsysd_xpath() for host %s\n",hostptr->hostname);
 }

}
