/* variables.c - setting control variables from the environment
 *
 * author(s): Tom Lord
 ****************************************************************
 * Copyright (C) 1998 UUNET Technologies, Inc.
 *
 * See the file "COPYING.PIW" for further information
 * about the copyright status of this work.
 */


#include "hackerlab/os/stdlib.h"
#include "hackerlab/os/unistd.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/char/char-class.h"
#include "hackerlab/char/str.h"
#include "hackerlab/mem/mem.h"
#include "hackerlab/piw/variables.h"


/*(h1 "Control Variables"
 *    :includes ("hackerlab/piw/piw.h"))
 * 
 * PIW instrumentation can be parameterized, meaning that it may be
 * controlled by parameters that are specified from the
 * command-line when running an instrumented program, or by setting
 * variables in your program, or by setting variables from a symbolic
 * debugger.
 * 
 * PIW parameters specified from the command-line are not usually
 * specified as arguments to your program because the PIW run-time
 * system has no access to those arguments.  Instead, command-line
 * arguments are specified in the environment variable `PIWFLAGS'.  A
 * standard interface is provided for retrieving values from that
 * environment variable.
 * 
 * The format of the string bound to `PIWFLAGS' is a whitespace
 * separated list of flag specifications:
 * 
 * 	PIWFLAGS_value := empty
 *			|  flag_specification spaces PIWFLAGS_value
 * 
 * 	flag_specification := flag_name
 *			   |  flag_name "=" flag_value
 * 
 * 	flag_name	:= one or more letters or the character `_'
 * 
 * 	flag_value	:= non-negative decimal integer
 *			|  a non-whitespace, non-digit character
 * 
 * 	spaces		:= one or more whitespace characters
 * 
 * Note that the values of flags are restricted to non-negative
 * integer values which may be specified as decimal numbers or
 * as single characters (which are neither whitespace or digits).
 * 
 * If a flag name does not appear in `PIWFLAGS', its value is
 * implicitly 0.  If a flag name does appear in `PIWFLAGS', but no
 * value is explicitly specified, its value is implicitly 1.
 * 
 * Here is an example:
 *
 * 	    % PIWFLAGS="piw_malloc_log 
 * 	                piw_malloc_block_pad=64
 * 		        piw_bitmask_granularity=w"
 * 
 * In that example, the parameter `piw_malloc_log' has the value 1;
 * the parameter `piw_malloc_block_pad' has the value 64;
 * `piw_bitmask_granularity' has the value 119 (ASCII {'w'}).
 * 
 */

static void init_flags ();



/* struct flag_value
 * 
 * A record of a value parsed from `PIWFLAGS'.
 */
struct flag_value
{
  t_uchar * name;
  unsigned int value;
};

/* flags
 * 
 * All of the values parsed from `PIWFLAGS'.
 * 
 * 	flags == 0
 *		PIWFLAGS has not been parsed yet.
 * 
 *	flags == 1
 *		PIWFLAGS is not set in the environment.
 * 
 * 	other values
 *		flags points to an array of flag bindings.
 * 
 */
static struct flag_value * flags = 0;

/* n_flags
 * 
 * If `flags' is not 0, `n_flags' is the number of valid entries
 * in the array `flags'.
 */
static int n_flags = 0;



/*(c piw_get_flag)
 * unsigned int piw_get_flag (t_uchar * name);
 * 
 * Return the value of the named control variable as specified in the
 * value of the environment variable `PIWFLAGS'.  If the variable has
 * no value in `PIWFLAGS', return 0.
 * 
 * The first time this function is called, `PIWFLAGS' is parsed and
 * all variable settings it contains are recorded.  For simplicity, no
 * overflow checking is performed on decimal numbers: specifying an
 * absurdly large value will case `piw_get_flag' to return the
 * wrapped-around value.
 * 
 * If the value of PIWFLAGS contains a syntax error, `piw_get_flag'
 * prints an error message and exits the program with a non-0 status.
 * 
 * `piw_get_flag' must do its work without calling `malloc'.  The
 * implementation currently presumes that `getenv' does not call
 * `malloc'.  `piw_get_flag' may call `sbrk' the first time it is
 * invoked.
 * 
 * By convention, every control variable which can be set in `PIWFLAGS'
 * corresponds to a global variable of the same name.  This convention
 * makes it easier to check or modify the value of a control variable
 * at run time.
 * 
 * To define a new control variable, create a global variable (usually
 * of type `unsigned integer').  Initialize the variable by calling
 * `piw_get_flag':
 * 
 * 	piw_your_flag = piw_get_flag ("piw_your_flag");
 * 
 */
unsigned int
piw_get_flag (t_uchar * name)
{
  int x;

  if (!flags)
    init_flags ();

  for (x = 0; x < n_flags; ++x)
    {
      if (!str_cmp (name, flags[x].name))
	return flags[x].value;
    }
  return 0;
}

static void
init_flags ()
{
  t_uchar * spec;

  spec = (t_uchar *)getenv ("PIWFLAGS");

  if (!spec)
    flags = (struct flag_value *)0x1;
  else
    {
      char * allocation_ptr;
      t_uchar * saved_spec;
      int phase;
      int len;
      t_uchar * id;
      int id_len;
      t_uchar * value;
      int value_len;

      saved_spec = spec;

      /* Process the PIWFLAGS spec in two passes.  In the first pass,
       * count the number of flags specified.  In the second pass,
       * record the values of flags.
       *
       * Between passes, allocate memory to hold `flags'.  This
       * allocation uses `sbrk' because we can not call malloc
       * from this function.
       */
      for (phase = 0; phase < 2; ++phase)
	{
	  spec = saved_spec;
	  len = strlen (spec);

	  if (phase == 1)
	    {
	      int size;

	      /* Storage needed to store saved PIWFLAGS values:
	       *
	       *      // Storage for `flags':
	       *      n_flags * sizeof (struct flag_value)
	       *
	       *      // Storage for flag names:
	       *   +  len + 1
	       *
	       *      // extra to ensure allignment
	       *   +  sizeof (void *)
	       */

	      size = (  (n_flags * sizeof (struct flag_value))
		      + (len + 1)
		      + sizeof (void *));


	      /* It isn't safe to call "malloc" from this function,
	       * because "init_libc_malloc" calls this function and
	       * "malloc" calls "init_libc_malloc".
	       */
	      allocation_ptr = (void *)sbrk (size);

	      if ((long)allocation_ptr == -1)
		panic ("unable to sbrk in init_flags");

	      allocation_ptr = (char *)(  ((long)allocation_ptr + sizeof (void*) - 1)
					& ~((long)sizeof (void*) - 1L));

	      flags = (struct flag_value *)allocation_ptr;

	      allocation_ptr +=  n_flags * sizeof (struct flag_value);

	      n_flags = 0;
	    }
	  while (1)
	    {
	      while (len && char_is_space (*spec))
		{
		  ++spec;
		  --len;
		}
	      if (!len)
		break;
	      id = spec;
	      while (len && (char_is_alnum (*spec) || (*spec == '_')))
		{
		  ++spec;
		  --len;
		}
	      if (spec == id)
		{
		bogus_env:
		  panic ("bad value of $PIWFLAGS");
		}
	      id_len = spec - id;
	      while (len && char_is_space (*spec))
		{
		  ++spec;
		  --len;
		}
	      if (!len || (char_is_alpha (*spec) || ('_' == *spec)))
		{
		  value = "1";
		  value_len = 1;
		}
	      else if ((*spec != '=') || (len == 1))
		goto bogus_env;
	      else
		{
		  ++spec;
		  --len;
		  value = spec;
		  if (len && !char_is_digit (*spec) && !char_is_space (*spec))
		    {
		      ++spec;
		      --len;
		    }
		  else
		    {
		      while (len && char_is_digit (*spec))
			{
			  ++spec;
			  --len;
			}
		    }
		  if (spec == value)
		    goto bogus_env;
		  value_len = spec - value;
		}
	      if (phase == 1)
		{
		  flags[n_flags].name = (t_uchar *)allocation_ptr;
		  allocation_ptr += (id_len + 1);
		  mem_move (flags[n_flags].name, id, id_len);
		  flags[n_flags].name[id_len] = 0;
		  if (!char_is_digit (*value))
		    {
		      flags[n_flags].value = *value;
		      ++value;
		      --value_len;
		    }
		  else
		    {
		      flags[n_flags].value = 0;
		      while (value_len)
			{
			  flags[n_flags].value = flags[n_flags].value * 10 + (*value - '0');
			  ++value;
			  --value_len;
			}
		    }
		}
	      ++n_flags;
	    }
	}
    }
}
