/*
 *========================================================================
 * $Id: util.c 88 2004-09-28 22:49:38Z rgb $
 *
 * See copyright in copyright.h and the accompanying file COPYING
 *========================================================================
 */

#include <wulfware/libwulf.h>

#define TIMEOUT 5

/* 
 *==================================================================
 * Utility routines that might be reusable.
 *==================================================================
 */


/*
 * readn_from_hostptr - read exactly n bytes using the hostptr
 * data and locks.
 */
int readn_from_hostptr(Host *hostptr, char *msgbuf, size_t len)
{

 int cnt;
 int rc;
 struct timeval waittime;
 fd_set in;

 /*
  * Do not try to read if for any reason the host is marked not connected.
  */
 if(! hostptr->connected) return(-1);

 waittime.tv_sec = TIMEOUT;
 waittime.tv_usec = 0;
 FD_ZERO(&in);
 FD_SET(hostptr->client_fd, &in);

 cnt = len;
 while ( cnt > 0 ) {
   if(verbose == D_UTIL){
     fprintf(OUTFP,"At start of loop\n");
     
   }

   if(select(hostptr->client_fd+1, &in, 0, 0, &waittime)){
     rc = recv(hostptr->client_fd, msgbuf, cnt, 0 );
     if(verbose == D_UTIL){
       fprintf(OUTFP,"readn_from_host got %d characters:\n%s\n",rc,msgbuf);
     }
     if( rc < 0 )	{				/* read error? */
       if( errno == EINTR )	/* interrupted? */
         continue;			/* restart the read */
       if( errno == EAGAIN )	/* would block? */
         continue;			/* restart the read */
       return -1;				/* return error */
     }
     if( rc == 0 ) {				/* EOF? */
       if(verbose == 14){
         fprintf(OUTFP,"Finished: readn_from_hostptr() got %d characters:\n",rc);
       }
       return len - cnt;		/* return short count */
     }
     msgbuf += rc;
     cnt -= rc;
     if(verbose == D_UTIL){
       fprintf(OUTFP,"At end of readn_from_hostptr() loop\n");
     }
   } else {
     return(-1);
   }
 }
   
 if(verbose == D_UTIL){
   fprintf(OUTFP,"Finished: readn got %d characters:\n",rc);
 }
 return len;

}

/*
 * readn - read exactly n bytes 
 */
int readn(int fd, char *msgbuf, size_t len)
{

 int cnt;
 int rc;
 struct timeval waittime;
 fd_set in;

 waittime.tv_sec = TIMEOUT;
 waittime.tv_usec = 0;
 FD_ZERO(&in);
 FD_SET(fd, &in);

 cnt = len;
 while ( cnt > 0 ) {
   if(verbose == 14){
     fprintf(OUTFP,"At start of loop\n");
     
   }

   /* 
    * The following fragment SHOULD cause a timeout after one second
    * of inactivity on the part of the server.  However, as it happens
    * it appears to be impossible to call this in time for data not to
    * already be available to read, which results in it doing absolutely
    * nothing useful but hang the read.  If we called this on a read
    * descriptor only right BEFORE sending the command, we might possibly
    * be able to make the read/write cycle robust, so we won't throw
    * the fragment away.  Yet.
   do {
     rc = select_fd (fd, 10, 0);
   } while (rc == -1 && errno == EINTR);
   if(rc <= 0) {
     if (rc == 0)
       errno = ETIMEDOUT;
     return -1;
   }
    */
   if(select(fd+1, &in, 0, 0, &waittime)){
     rc = recv( fd, msgbuf, cnt, 0 );
     if(verbose == 14){
       fprintf(OUTFP,"readn got %d characters:\n%s\n",rc,msgbuf);
     }
     if( rc < 0 )	{				/* read error? */
       if( errno == EINTR )	/* interrupted? */
         continue;			/* restart the read */
       if( errno == EAGAIN )	/* would block? */
         continue;			/* restart the read */
       return -1;				/* return error */
     }
     if( rc == 0 ) {				/* EOF? */
       if(verbose == 14){
         fprintf(OUTFP,"Finished: readn got %d characters:\n",rc);
       }
       return len - cnt;		/* return short count */
     }
     msgbuf += rc;
     cnt -= rc;
     if(verbose == 14){
       fprintf(OUTFP,"At end of loop\n");
     }
   } else {
     return(-1);
   }
 }
   
 if(verbose == 14){
   fprintf(OUTFP,"Finished: readn got %d characters:\n",rc);
 }
 return len;

}

/*
 * readline() reads one LF NULL terminate line and converts all
 * terminating LF's to NULL
 */
int readline(int fd, char *msgbuf, int maxstrlen)
{

 int n,rc,nread = 0;

 /* Read characters until NULL or end-of-input */
 do{
   /* This socket should now NOT block on the recv attempt */
   n = read(fd,msgbuf,1);	/* read in next character */
   nread += n;		/* increment the count on success */
 } while (n > 0 && *msgbuf++ != (char) 10 && nread < maxstrlen);
 /* step back to last character and strip of any trailing LF's */
 *msgbuf--;
 while(*msgbuf == (char)10){
    *msgbuf-- = (char) 0;	/* Terminate the string over the LF */
    nread--;		/* decrement the count */
 }
 return nread;

}

/*
 * readline_from_hostptr() calls readline() for the particular
 * host with a one second timeout.
 */
int readline_from_hostptr(Host *hostptr,char *msgbuf,size_t maxstrlen)
{

 /*
  * A purely local line buffer.  Our lines had best
  * be less than a page long.
  */
 struct timeval waittime;
 fd_set in;

 waittime.tv_sec = TIMEOUT;
 waittime.tv_usec = 0;
 FD_ZERO(&in);
 FD_SET(hostptr->client_fd, &in);
 if(select(hostptr->client_fd+1, &in, 0, 0, &waittime)){
   readline(hostptr->client_fd,msgbuf,maxstrlen);
   return(1);
 } else {
   if((verbose == D_ALL) || (verbose == D_XMLSYSD)){
     fprintf(OUTFP,"readline_from_hostptr(): %d second read failed on %s.  Closing connection\n",TIMEOUT,hostptr->hostname);
   }
   close(hostptr->client_fd);
   hostptr->client_fd = 0;
   hostptr->connected = 0;
   return(0);
 }

}

/*
 * This is a fairly standard bit of code (straight from the select
 * man page) for ensuring that a read or write attempt on a socket
 * will not block.  Safer and smarter than using non-blocking i/o.
 * timeout is a timeval struct with the total time (to microsecond
 * resolution) to wait.
 *
 * Using this on EACH read or write, we should be able to deal with 
 * client/server death or delay without hanging the application.
 */
int select_fd (int fd, int seconds, int microseconds)
{

  fd_set rfds,wfds;
  struct timeval tv;
  int retval = 0;

  FD_ZERO (&rfds);
  FD_SET (fd, &rfds);
  FD_ZERO (&wfds);
  FD_SET (fd, &wfds);
  tv.tv_sec = seconds;
  tv.tv_usec = microseconds;
  retval = select(1, &rfds, &wfds, NULL, &tv);
  return retval;
  
}

/*
 * Routine used to scale integer sizes into K, M, G ranges
 */
char *scale_k(long unsigned int k,long unsigned int width,long unsigned int unit) 
{

 /* kilobytes, megabytes, gigabytes, too-big-for-int-bytes */
 static double scale[]={1024,1024*1024,1024*1024*1024,0};
 /* kilo, mega, giga, tera */
 static char unitletters[]={'K','M','G','T',0};
 static char buf[100];
 char *up;
 double *dp;

 /* Try successively higher units until it fits */

 sprintf(buf,"%d",k);
 if(strlen(buf)<=width) 
    return buf;

 for (up=unitletters+unit,dp=scale ; *dp ; ++dp,++up) {
   sprintf(buf,"%d%c",(int)(k / *dp),*up);
   if (strlen(buf)<=width) 
     return buf;
 }

 /* Give up; give them what we got on our shortest attempt */
 return buf;

}

/*
 * Routine to compute the amount of uptime from the raw numbers.
 */
void make_uptime(double total_secs,char *uptime)
{

 int uptime_secs,updays,uphours,upminutes;
 int pos = 0;

 /* 
  * read and calculate the amount of uptime.  This code was wholeheartedly
  * lifted from uptime itself.
  */
 updays = (int) total_secs / 86400;
 upminutes = (int) total_secs / 60;
 uphours = (int) upminutes / 60;
 uphours = uphours % 24;
 upminutes = upminutes % 60;
 uptime_secs = (int) (total_secs - updays*86400 - uphours*3600 - upminutes*60);

 /* Return the uptime string */
 sprintf(uptime, "%1dd:%02dh:%02dm:%02ds",
       updays,uphours,upminutes,uptime_secs);

}



/* 
 *==================================================================
 * EVERYTHING BELOW THIS LINE IS UNUSED/CRUFT.
 *
 * From wget sources.  We do need to think about using some of this
 * stuff to guard against timeouts.
 */

/*
 * Also from wget sources.  Note utilization of timeout set
 * by previous select command.  This is the kind of thing
 * I need in recv_xmlDoc().
 */
int XXXwgetread(int fd, char *buf, int len)
{

 int res;

 do {
   do {
     res = select_fd (fd, 1, 0);
   } while (res == -1 && errno == EINTR);

   if(res <= 0) {
     /* Set errno to ETIMEDOUT on timeout.  */
     if (res == 0)
       errno = ETIMEDOUT;
     return -1;
   }

   res = read (fd, buf, len);
 } while (res == -1 && errno == EINTR);

 return res;

}

