/*
 * nqs_generic/all-systems/scancpulim.c
 * Scan a maximum cpu time limit and an optional warning limit.
 */

#include <nqs_generic/license.h>
  /* NQS license information */
#include <nqs_generic/debug.h>
  /* debugging support */
#include <nqs_generic/proto.h>
  /* ANSI C prototype support */

#include <nqs_queue/quolim.h>
  /* for struct cpulimit */

static int scan1cpulim ( unsigned long *ulongp, short *shortp );
static int scancpulim_scan ( short asfracpart );

/*
 *	Token types. Interface between scan1cpulim() and scan().
 */
#define	T_COLON		0		/* : */
#define	T_EOS		1		/* End of string found */
#define	T_ERROR		2		/* Bad token (not overflow) */
#define	T_INT32		3		/* an integer < 2**32 */
#define	T_OVERFLOW	4		/* an integer >= 2**32 */
#define	T_PERIOD	5		/* . */
#define T_SHORT		6		/* 0 <= x <= 1000 (not 999) */
					/* "1000" means "0, carry 1" */
/*
 *	Scan cpu limit return codes.
 *	Interface between scancpulim() and scan1cpulim().
 *	Interface between outside world and scancpulim().
 */
#define	ISUCCESS	0		/* Valid cpu limit */
#define	ESYNTAX		(-1)		/* Syntax error in cpu limit */
#define	EOVERFLOW	(-2)		/* Syntax ok; semantic (e.g. 11:61) */
					/* or overflow error in cpu limit */
#define	EUNDEF		(-3)		/* No cpu limit specified */
#define EBADWARN	(-4)		/* Warning greater than max limit */

/*
 *	Variables local to this module.
 */
static char *scanptr;			/* Next char to be scanned */
static unsigned long t_int32;		/* Scanned seconds value */
static short t_short;			/* Scanned milliseconds value */

/*** scancpulim
 *
 *
 *	int scancpulim():
 *	Scan a maximum cpu limit and an optional warning cpu limit.
 *
 *	Returns:
 *		0: if successful;
 *	       -1: if the specification contains a syntax error;
 *	       -2: if the syntax is correct but the limit is invalid
 *		   (i.e 11:61, 4294967296 (==2**32));
 *	       -3: if no limit was specified;
 *	       -4: if the warning limit was greater than the maximum limit.
 *
 *	There are seven legal formats for a cpu limit:
 *		
 *		HH:MM:SS.MS
 *		HH:MM:SS
 *		MM:SS.MS
 *		MM:SS
 *		SS.MS
 *		SS
 *		.MS
 *		
 *		where HH, MM, SS, and MS are base 10 digit strings
 *		of length anywhere from one digit to 10 digits.
 *		HH stands for hours, MM for minutes, SS for seconds,
 *		and MS for milliseconds.
 * 
 *	HH, MM, and SS, summed as seconds, must fit in an unsigned
 *		32 bit integer.  Furthermore, if hours are indicated,
 *		minutes must be <= 59, and if minutes are indicated,
 *		seconds must be <= 59. Otherwise, minutes or seconds
 *		may exceed 59.
 *
 *	MS will be interpreted as the fractional part of a base 10 number
 *		(i.e., 1.01 means one second and TEN milliseconds,
 *		not one second and ONE millisecond).
 *		If MS is longer than three digits, it will be rounded
 *		to the closest millisecond.
 *
 *	There may be whitespace anywhere except:
 *		within a digit string or
 *		adjacent to a decimal point.
 * 
 *	There must be whitespace between a maximum cpu limit
 *		and a warning cpu limit.
 *
 */
int scancpulim (
	char *cpulimstr,		/* what is to be parsed */
	struct cpulimit *cpulimp,	/* where to put the result */
	short maxonly)			/* if non-zero, warn limit illegal */
{
  int trouble;				/* return value of scan1cpulim() */
  unsigned long myulong;		/* dummy arg; never looked at */
  short myshort;			/* dummy arg; never looked at */

  ENTER_FUNCTION("nqs_generic/all-systems/scancpulim");
    /* debugging info */
  TEST_ARG(cpulimstr != NULL, 1);
  TEST_ARG(cpulimp   != NULL, 2);
    /* ensure we are not dealing with garbage code */
  
  cpulimp->max_seconds = 0;	/* The struct cpulimit is to be */
  cpulimp->max_ms = 0;		/* zeroed in the event of error */
  cpulimp->warn_seconds = 0;	/* return.  We'll assume an	*/
  cpulimp->warn_ms = 0;		/* error return for now.	*/
	
  scanptr = cpulimstr;
	
  /*
   * Below, fill in max_seconds and max_ms.
   * If there is an error, max_seconds and max_ms
   * will not be touched. Scanptr
   * is left pointing to the first character that
   * proves we have reached the end of the first limit.
   */
	
  trouble = scan1cpulim (&cpulimp->max_seconds, &cpulimp->max_ms);
	
  if (trouble != ISUCCESS )
  {
    EXIT_FUNCTION;
    return (trouble);
  }
  
  /*
   * Preset warn limits to max limits
   * for the case where no warn limit is specified.
   */
	 
  cpulimp->warn_seconds = cpulimp->max_seconds;
  cpulimp->warn_ms = cpulimp->max_ms;
	
  /*
   * Check if it is OK for a warn limit to appear.
   * If not, then there had better be nothing left.
   */
	 
  if (maxonly)
  {
    trouble = scan1cpulim (&myulong, &myshort);
    if (trouble != EUNDEF)
    {
      cpulimp->max_seconds = 0;
      cpulimp->max_ms = 0;
      cpulimp->warn_seconds = 0;
      cpulimp->warn_ms = 0;
      EXIT_FUNCTION;
      return (ESYNTAX);
    }
    EXIT_FUNCTION;
    return (ISUCCESS);
  }
	
  /*
   * It is OK for a warn limit to appear.
   * Below, parse it into warn_seconds and warn_ms.
   * If there is an error, warn_seconds and warn_ms
   * will not be touched. If the warn limit is
   * non-null, the scanner's character pointer is 
   * left pointing to the first character that
   * proves we have reached the end of the warn limit.
   */
	 
  trouble = scan1cpulim (&cpulimp->warn_seconds, &cpulimp->warn_ms);
	
  /*
   * If there was an error ( other than no warn limit at all),
   * do not continue.
   */
	 
  if (trouble != ISUCCESS && trouble != EUNDEF)
  {
    cpulimp->max_seconds = 0;
    cpulimp->max_ms = 0;
    cpulimp->warn_seconds = 0;
    cpulimp->warn_ms = 0;
    EXIT_FUNCTION;
    return (trouble);
  }
	
  /*
   * Report error if the warning was greater than the max.
   */

  if (
      (cpulimp->warn_seconds > cpulimp->max_seconds) ||
      (
       (cpulimp->warn_seconds == cpulimp->max_seconds) &&
       (cpulimp->warn_ms > cpulimp->max_ms)
      )
     )
  {
    cpulimp->max_seconds = 0;
    cpulimp->max_ms = 0;
    cpulimp->warn_seconds = 0;
    cpulimp->warn_ms = 0;
    EXIT_FUNCTION;
    return (EBADWARN);
  }
	
  /*
   * Below, there had better not be anything left.
   */
	 
  trouble = scan1cpulim (&myulong, &myshort);
	
  if (trouble != EUNDEF)
  {
    cpulimp->max_seconds = 0;
    cpulimp->max_ms = 0;
    cpulimp->warn_seconds = 0;
    cpulimp->warn_ms = 0;
    EXIT_FUNCTION;
    return (ESYNTAX);
  }
  EXIT_FUNCTION;
  return (ISUCCESS);
}				/* end of scancpulim() */


/*** scan1cpulim
 *
 *
 *	int scan1cpulim():
 *	Assign the seconds and milliseconds parts of the next cpu limit
 *	into the places pointed to by the arguments.
 *
 *	The set of possible return values of scan1cpulim() and
 *	the set of possible return values of scancpulim()
 *	are the same, with the exception that scan1cpulim()
 *	is unable to detect EBADWARN.
 *	
 *	Scan1cpulim() skips over whitespace itself, rather
 *	than having scan() do it. This means that "1.2"
 *	can be three tokens, instead of having to be one token.
 *
 */
static int scan1cpulim (unsigned long *ulongp, short *shortp)
{
  unsigned long seconds;
    /* running count */

  ENTER_FUNCTION("nqs_generic/all-systems/scan1cpulim");
  TEST_ARG(ulongp != NULL, 1);
  TEST_ARG(shortp != NULL, 2);
    /* ensure we are not working with garbage */
  
  while (*scanptr == ' ' || *scanptr == '\t' || *scanptr == ',')
    scanptr++;
  
  switch (scancpulim_scan (FALSE))
  {
   case T_COLON:
   case T_ERROR:
   case T_SHORT:			/* T_SHORT impossible here */
    EXIT_FUNCTION;
    return (ESYNTAX);
   case T_EOS:
    EXIT_FUNCTION;
    return (EUNDEF);
   case T_INT32:			/* seen HH, MM, or SS */
    seconds = t_int32;
    if (*scanptr == '.')
    {
      scanptr++;	/* move scanptr to 1/10ths place */
      if (scancpulim_scan (TRUE) != T_SHORT)
      {
	EXIT_FUNCTION;
	return (ESYNTAX);
      }
      else
      {
	if (*scanptr == '.')
	{	/* a.b. illegal */
	  EXIT_FUNCTION;
	  return (ESYNTAX);
	}
	if ( t_short == 1000)
	{	/* carry */
	  if (seconds == 4294967295UL)
	  {
	    EXIT_FUNCTION;
	    return (EOVERFLOW);
  	  }
	  else
	  {
	    t_short = 0;
	    seconds++;
	  }
        }
        *ulongp = seconds;
        *shortp = t_short;
	EXIT_FUNCTION;
        return (ISUCCESS);
      }
    }
    while (/* move scanptr to first non-white */
	   *scanptr == ' ' ||
	   *scanptr == '\t' ||
	   *scanptr == ','
	  )
    {
      scanptr++;
    }
    if (*scanptr != ':')
    {
      *ulongp = seconds;
      *shortp = 0;
      EXIT_FUNCTION;
      return (ISUCCESS);
    }
    scanptr++;		/* move scanptr past colon */
			/* seen HH: or MM: */
    while (			/* move scanptr to first non-white */
   	   *scanptr == ' ' ||
	   *scanptr == '\t' ||
	   *scanptr == ','
	  )
    {
      scanptr++;
    }
    switch (scancpulim_scan (FALSE))
    {
     case T_COLON:
     case T_EOS:
     case T_ERROR:
     case T_PERIOD:
     case T_SHORT:			/* T_SHORT impossible here */
      EXIT_FUNCTION;
      return (ESYNTAX);
     case T_INT32:			/* seen HH:MM or MM:SS */
      if (	/* overflow check */
		/* (71582788+16/60)*60 is smallest overflow */
	  (t_int32 > 59) ||
	  (seconds > 71582788L) ||
	  ((seconds == 71582788L) && (t_int32 >15))
	 )
      {
	EXIT_FUNCTION;
	return (EOVERFLOW);
      }
      seconds *= 60;
      seconds += t_int32;
      if (*scanptr == '.')
      {
	scanptr++;	/* move scanptr to 1/10ths */
	if (scancpulim_scan (TRUE) != T_SHORT)
	{
	  EXIT_FUNCTION;
	  return (ESYNTAX);
	}
	else
	{
	  if (*scanptr == '.')
	  { /* a:b.c. */
	    EXIT_FUNCTION;
	    return (ESYNTAX);
	  }
	  if ( t_short == 1000)
	  {	/* carry */
		/* 4294967295 = 2**32-1 */
	    if (seconds == 4294967295UL)
	    {
	      EXIT_FUNCTION;
	      return (EOVERFLOW);
	    }
	    else
	    {
	      t_short = 0;
	      seconds++;
	    }
	  }
	  *ulongp = seconds;
	  *shortp = t_short;
	  EXIT_FUNCTION;
	  return (ISUCCESS);
	}
      }
      break;
     case T_OVERFLOW:
      EXIT_FUNCTION;
      return (EOVERFLOW);
    }
    while (			/* move scanptr to first non-white */
       	   *scanptr == ' ' ||
	   *scanptr == '\t' ||
	   *scanptr == ','
	  )
    {
      scanptr++;
    }
    if (*scanptr != ':')
    {
      *ulongp = seconds;
      *shortp = 0;
      EXIT_FUNCTION;
      return (ISUCCESS);
    }
    scanptr++;		/* move scanptr past colon */
			/* seen HH:MM: */
    while (			/* move scanptr to first non-white */
	   *scanptr == ' ' ||
	   *scanptr == '\t' ||
	   *scanptr == ','
	  )
    {
      scanptr++;
    }
    switch (scancpulim_scan (FALSE))
    {
     case T_COLON:
     case T_EOS:
     case T_ERROR:
     case T_PERIOD:
     case T_SHORT:		/* T_SHORT impossible here */
      EXIT_FUNCTION;
      return (ESYNTAX);
     case T_INT32:			/* seen HH:MM:SS */
     if (	/* overflow check */
		/* (71582788+16/60)*60 is smallest overflow */
 	 (t_int32 > 59) ||
	 (seconds > 71582788L) ||
	 ((seconds == 71582788L) &&
	  (t_int32 >15))
	)
      {
	EXIT_FUNCTION;
	return (EOVERFLOW);
      }
      seconds *= 60;
      seconds += t_int32;
      if (*scanptr == '.')
      {
	scanptr++;	/* move scanptr to 1/10ths */
	if (scancpulim_scan (TRUE) != T_SHORT)
	{
	  EXIT_FUNCTION;
	  return (ESYNTAX);
	}
	else
	{
	  if (*scanptr == '.')
	  {
	    /* a:b:c.d. */
	    EXIT_FUNCTION;
	    return (ESYNTAX);
	  }
	  if ( t_short == 1000)
	  {	/* carry */
		/* 4294967295 = 2**32-1 */
	    if (seconds == 4294967295UL)
	    {
	      return (EOVERFLOW);
	    }
	    else
	    {
	      t_short = 0;
	      seconds++;
	    }
	  }
	  *ulongp = seconds;
	  *shortp = t_short;
	  EXIT_FUNCTION;
	  return (ISUCCESS);
	}
      }
      *ulongp = seconds;
      *shortp = 0;
      EXIT_FUNCTION;
      return (ISUCCESS);
     case T_OVERFLOW:
      EXIT_FUNCTION;
      return (EOVERFLOW);
    }
   case T_OVERFLOW:		/* back to top level switch here */
    EXIT_FUNCTION;
    return (EOVERFLOW);
   case T_PERIOD:			/* .MS case */
    if (scancpulim_scan (TRUE) != T_SHORT)
    {
      EXIT_FUNCTION;
      return (ESYNTAX);
    }
    else
    {
      if (*scanptr == '.')
      {	/* .a. */
	EXIT_FUNCTION;
 	return (ESYNTAX);
      }
      if (t_short == 1000)
      {	/* carry */
	*ulongp = 1;
	*shortp = 0;
      }
      else
      {
	*ulongp = 0;
	*shortp = t_short;
      }
      EXIT_FUNCTION;
      return (ISUCCESS);
    }
  }
  NEVER;
  return 0;
}				/* end scan1cpulim() */
/* phew! */

/*** scancpulim_scan
 *
 *
 *	int scancpulim_scan():
 *	Get next token.
 *	See the comment about whitespace above.
 *
 *	Returns: T_<tokentype>.
 */
static int scancpulim_scan (short asfracpart)
{
  register int ch;		/* scan character */
  register unsigned long i32value;/* seconds being computed */
  register short svalue;		/* milliseconds being computed */

  ENTER_FUNCTION("nqs_generic/all-systems/scancpulim_scan");
  
  ENSURE_DATA(scanptr != NULL, UFCM_ERRD_NULLPTR);
    /* ensure that its contents are valid */
  ch = *scanptr;
  switch (ch)
  {
   case '\0':
   case '\n':
    EXIT_FUNCTION;
    return(T_EOS);		/* end of string */
   case ':':
    scanptr++;		/* move scanptr beyond ':' */
    EXIT_FUNCTION;
    return(T_COLON);
   case '.':
    scanptr++;		/* move scanptr beyond '.' */
    EXIT_FUNCTION;
    return(T_PERIOD);
  }
  if (ch >= '0' && ch <= '9')
  {
    /*
     *  Scan digit string.
     */
    if (asfracpart)
    {
      svalue = 100 * (ch - '0');
      ch = *++scanptr;	/* move scanptr to 1/100ths */
      if (ch >= '0' && ch <= '9')
      {
	svalue += 10 * (ch - '0');
	ch = *++scanptr;	/* move to 1/1000ths */
	if (ch >= '0' && ch <= '9')
	{
	  svalue += ch - '0';
	  ch = *++scanptr;  /* to 1/10000ths */
	  if (ch >= '5' && ch <= '9')
	  {
	    svalue++;
	  }
	}
	
	while (ch >= '0' && ch <= '9')
	{
	  ch = *++scanptr;
	    /* move scanptr past digit string */
	}
      }
      t_short = svalue;
      EXIT_FUNCTION;
      return (T_SHORT);
    }
    else
    {			/* HH, MM, or SS */
      i32value = 0;
      do
      {
	i32value *= 10;
	i32value += ch - '0';
	ch = *++scanptr;
      } while (ch >= '0' && ch <= '9' && i32value <= 429496728L);
        /* end of do while */
      /* 429496728*10+6 is smallest overflow */
      if (i32value == 429496729L && ch >= '0' && ch <= '5')
      {
 	i32value *= 10;
	i32value += ch - '0';
	ch = *++scanptr;
      }	
      if (ch >= '0' && ch <= '9')
      {
	EXIT_FUNCTION;
	return (T_OVERFLOW);
      }	/* don't have to move scanptr; fatal error */
      t_int32 = i32value;
      EXIT_FUNCTION;
      return (T_INT32);
    }
  }
  EXIT_FUNCTION;
  return(T_ERROR);		/* Bad token */
}					/* end scan() */
