#ident "@(#)parser.c	1.5 01/04/11"

/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
 * Copyright (C) 2006 Red Hat, Inc.
 * All rights reserved.
 *
 * License: GPL (version 3 or any later version).
 * See LICENSE for details. 
 * END COPYRIGHT BLOCK **/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif


/*
	FILE :		parser.c
	AUTHOR :        Jean-Luc SCHWING
	VERSION :       1.0
	DATE :		19 March 2001
	DESCRIPTION :	
			This file contains the parser functions of ldclt
	LOCAL :		None.
	HISTORY :
---------+--------------+------------------------------------------------------
dd/mm/yy | Author	| Comments
---------+--------------+------------------------------------------------------
19/03/01 | JL Schwing	| Creation
---------+--------------+------------------------------------------------------
21/03/01 | JL Schwing	| 1.2 : Implements variables in "-e object=filename"
---------+--------------+------------------------------------------------------
23/03/01 | JL Schwing	| 1.3 : Implements data file list support in variants.
			| Bug fix : close the file !
			| Implements "-e rdn=value".
---------+--------------+------------------------------------------------------
28/03/01 | JL Schwing	| 1.4 : Support -e commoncounter with -e rdn/object
---------+--------------+------------------------------------------------------
11/04/01 | JL Schwing	| 1.5 : Implement [INCRFROMFILE<NOLOOP>(myfile)]
			| Improved error message.
---------+--------------+------------------------------------------------------
*/


#include <stdio.h>	/* printf(), etc... */
#include <string.h>	/* strcpy(), etc... */
#include <errno.h>	/* errno, etc... */
#include <stdlib.h>	/* malloc(), etc... */
#include <lber.h>	/* ldap C-API BER declarations */
#include <ldap.h>	/* ldap C-API declarations */
#ifdef LDAP_H_FROM_QA_WKA
#include <proto-ldap.h>	/* ldap C-API prototypes */
#endif
#include <unistd.h>	/* close(), etc... */
#include <pthread.h>	/* pthreads(), etc... */

#include "port.h"	/* Portability definitions */
#include "ldclt.h"	/* This tool's include file */
#include "utils.h"	/* Utilities functions */


/* ****************************************************************************
	FUNCTION :	decodeHow
	PURPOSE :	Decode the how field
	INPUT :		how	= field to decode
	OUTPUT :	None.
	RETURN :	-1 if error, how value else.
	DESCRIPTION :
 *****************************************************************************/
int
decodeHow (
	char	*how)
{
  if (mctx.mode & VERY_VERBOSE)
    printf ("decodeHow: how=\"%s\"\n", how);

  if (!strcmp (how, "INCRFROMFILE"))		return (HOW_INCR_FROM_FILE);
  if (!strcmp (how, "INCRFROMFILENOLOOP"))	return (HOW_INCR_FROM_FILE_NL);
  if (!strcmp (how, "INCRN"))			return (HOW_INCR_NB);
  if (!strcmp (how, "INCRNNOLOOP"))		return (HOW_INCR_NB_NOLOOP);
  if (!strcmp (how, "RNDFROMFILE"))		return (HOW_RND_FROM_FILE);
  if (!strcmp (how, "RNDN"))			return (HOW_RND_NUMBER);
  if (!strcmp (how, "RNDS"))			return (HOW_RND_STRING);
  return (-1);
}


/* ****************************************************************************
	FUNCTION :	parseVariant
	PURPOSE :	Parse a variant definition.
	INPUT :		variant	= string to parse
			fname	= file name
			line	= source line
			obj	= object we are parsing and building
	OUTPUT :	field	= field parsed
	RETURN :	-1 if error, 0 else.
	DESCRIPTION :
 *****************************************************************************/
int
parseVariant (
	char		*variant,
	char		*fname,
	char		*line,
	vers_object	*obj,
	vers_field	*field)
{
  int	 start, end;		/* For the loops */
  char	 how[MAX_FILTER];	/* To parse the variant : */
  char	 first[MAX_FILTER];	/*   how(first)              */
  char	 second[MAX_FILTER];	/*   how(first,second)       */
  char	 third[MAX_FILTER];	/*   how(first,second,third) */
  int	 ret;			/* ldclt_mutex_init() return value */

  if (mctx.mode & VERY_VERBOSE)
    printf ("parseVariant: variant=\"%s\"\n", variant);

  /*
   * Maybe a variable ?
   */
  if (variant[1] == '\0')
  {
    if ((variant[0] < VAR_MIN) || (variant[0] > VAR_MAX))
    {
      fprintf (stderr, "Error: bad variable in %s : \"%s\"\n", fname, line);
      fprintf (stderr, "Error: must be in [%c-%c]\n", VAR_MIN, VAR_MAX);
      return (-1);
    }
    field->how = HOW_VARIABLE;
    field->var = variant[0] - VAR_MIN;
    return (0);
  }

  /*
   * Maybe a variable definition ?
   */
  if (variant[1] != '=')
    field->var = -1;
  else
  {
    if ((variant[0] < VAR_MIN) || (variant[0] > VAR_MAX))
    {
      fprintf (stderr, "Error: bad variable in %s : \"%s\"\n", fname, line);
      fprintf (stderr, "Error: must be in [%c-%c]\n", VAR_MIN, VAR_MAX);
      return (-1);
    }
    field->var = variant[0] - VAR_MIN;
    variant++;	/* Skip variable name */
    variant++;	/* Skip '=' */

    /*
     * We need a variable !
     */
    if (obj->var[field->var] == NULL)
      obj->var[field->var] = (char *) malloc (MAX_FILTER);
  }

  /*
   * Find how definition
   */
  for (end=0 ; (variant[end]!='\0') && (variant[end]!='(') ; end++);
  if (variant[end]=='\0')
  {
    fprintf (stderr, "Error: bad variant in %s : \"%s\"\n", fname, line);
    fprintf (stderr, "Error: missing '('\n");
    return (-1);
  }
  strncpy (how, variant, end);
  how[end] = '\0';

  /*
   * Parse the first parameter
   */
  end++;	/* Skip '(' */
  for (start=end ; (variant[end]!='\0') && (variant[end]!=';') 
					&& (variant[end]!=')') ; end++);
  if (variant[end]=='\0')
  {
    fprintf (stderr, "Error: bad variant in %s : \"%s\"\n", fname, line);
    fprintf (stderr, "Error: missing ')'\n");
    return (-1);
  }
  strncpy (first, variant+start, end-start);
  first[end-start] = '\0';

  /*
   * Parse the second parameter
   */
  if (variant[end] == ')')
    second[0] = '\0';
  else
  {
    end++;	/* Skip ';' */
    for (start=end ; (variant[end]!='\0') && (variant[end]!=';') 
					  && (variant[end]!=')') ; end++);
    if (variant[end]=='\0')
    {
      fprintf (stderr, "Error: bad variant in %s : \"%s\"\n", fname, line);
      fprintf (stderr, "Error: missing ')'\n");
      return (-1);
    }
    strncpy (second, variant+start, end-start);
    second[end-start] = '\0';
  }

  /*
   * Parse the third parameter
   */
  if (variant[end]==')')
    third[0]='\0';
  else
  {
    end++;	/* Skip ';' */
    for (start=end ; (variant[end]!='\0') && (variant[end]!=')') ; end++);
    if (variant[end]=='\0')
    {
      fprintf (stderr, "Error: bad variant in %s : \"%s\"\n", fname, line);
      fprintf (stderr, "Error: missing ')'\n");
      return (-1);
    }
    strncpy (third, variant+start, end-start);
    third[end-start] = '\0';
  }

  /*
   * Analyse it
   * Note : first parameter always exist (detected when parsing) while
   *        second and third may not have been provided by the user.
   */
  switch (field->how = decodeHow (how))
  {
    case HOW_INCR_FROM_FILE:
    case HOW_INCR_FROM_FILE_NL:
    case HOW_RND_FROM_FILE:
	if ((field->dlf = dataListFile (first)) == NULL)
	{
	  fprintf (stderr, "Error : bad file in %s : \"%s\"\n", fname, line);
	  return (-1);
	}

	/*
	 * Useless for HOW_RND_FROM_FILE
	 */
	field->cnt  = 0;
	field->low  = 0;
	field->high = field->dlf->strNb - 1;

	/*
	 * Maybe common counter ?
	 */
	if ((mctx.mode & COMMON_COUNTER) &&
	    ((field->how == HOW_INCR_FROM_FILE) || 
	     (field->how == HOW_INCR_FROM_FILE_NL)))
	{
	  if ((ret = ldclt_mutex_init (&(field->cnt_mutex))) != 0)
	  {
	    fprintf (stderr, "ldclt: %s\n", strerror (ret));
	    fprintf (stderr, "Error: cannot initiate cnt_mutex in %s for %s\n",
						fname, line);
	    fflush (stderr);
	    return (-1);
	  }
	}
	break;
    case HOW_INCR_NB:
    case HOW_INCR_NB_NOLOOP:
    case HOW_RND_NUMBER:
	if (third[0] == '\0')
	{
	  fprintf (stderr, "Error : missing parameters in %s : \"%s\"\n",
				fname, line);
	  return (-1);
	}
	field->cnt  = atoi (first);
	field->low  = atoi (first);
	field->high = atoi (second);
	field->nb   = atoi (third);

	/*
	 * Maybe common counter ?
	 */
	if ((mctx.mode & COMMON_COUNTER) &&
	    ((field->how == HOW_INCR_NB) || (field->how == HOW_INCR_NB_NOLOOP)))
	{
	  if ((ret = ldclt_mutex_init (&(field->cnt_mutex))) != 0)
	  {
	    fprintf (stderr, "ldclt: %s\n", strerror (ret));
	    fprintf (stderr, "Error: cannot initiate cnt_mutex in %s for %s\n",
						fname, line);
	    fflush (stderr);
	    return (-1);
	  }
	}
	break;
    case HOW_RND_STRING:
	field->nb = atoi (first);
	break;
    case -1:
	fprintf (stderr, "Error: illegal keyword \"%s\" in %s : \"%s\"\n", 
					how, fname, line);
	return (-1);
	break;
  }

  return (0);
}


/* ****************************************************************************
	FUNCTION :	parseAttribValue
	PURPOSE :	Parse the right part of attribname: attribvalue.
	INPUT :		fname	= file name
			obj	= object where variables are.
			line	= value to parse.
	OUTPUT :	attrib	= attribute where the value should be stored
	RETURN :	-1 if error, 0 else.
	DESCRIPTION :
 *****************************************************************************/
int
parseAttribValue (
	char		*fname,
	vers_object	*obj,
	char		*line,
	vers_attribute	*attrib)
{
  char		 variant[MAX_FILTER];	/* To process the variant */
  int		 start, end;		/* For the loops */
  vers_field	*field;			/* To build the fields */

  if (mctx.mode & VERY_VERBOSE)
    printf ("parseAttribValue: line=\"%s\"\n", line);

  /*
   * We will now parse this line for the different fields.
   */
  field = NULL;
  end = start = 0;
  while (line[end]!='\0')
  {
    /*
     * Allocate a new field
     */
    if (field == NULL)
    {
      field         = (vers_field *) malloc (sizeof (vers_field));
      field->next   = NULL;
      attrib->field = field;
    }
    else
    {
      field->next = (vers_field *) malloc (sizeof (vers_field));
      field       = field->next;
      field->next = NULL;
    }

    /*
     * Is it a variant field ?
     */
    if (line[end] == '[')
    {
      /*
       * Extract the variant definition
       */
      end++;	/* Skip '[' */
      for (start=end ; (line[end]!='\0') && (line[end]!=']') ; end++);
      strncpy (variant, line+start, end-start);
      variant[end-start] = '\0';
      if (line[end]=='\0')
      {
	fprintf (stderr, "Error: missing ']' in %s : \"%s\"\n", fname, line);
	return (-1);
      }
      if (parseVariant (variant, fname, line, obj, field) < 0)
	return (-1);
      end++;	/* Skip ']' */

      /*
       * We need to allocate a buffer in this attribute !
       */
      if (attrib->buf == NULL)
      {
	attrib->buf = (char *) malloc (MAX_FILTER);
	if (mctx.mode & VERY_VERBOSE)
	  printf ("parseAttribValue: buffer allocated\n");
      }
    }
    else
    {
      /*
       * It is a constant field. Find the end : [ or \0
       */
      for (start=end ; (line[end]!='\0') && (line[end]!='[') ; end++);
      field->how = HOW_CONSTANT;
      field->cst = (char *) malloc (1+end-start);
      strncpy (field->cst, line+start, end-start);
      field->cst[end-start] = '\0';
    }
  }

  /*
   * Attribute value is parsed !
   */
  return (0);
}


/* ****************************************************************************
	FUNCTION :	parseLine
	PURPOSE :	Parse the given line to find an attribute definition.
	INPUT :		line	= line to parse
			fname	= file name
	OUTPUT :	obj	= object where the attribute should be added
	RETURN :	-1 if error, 0 else.
	DESCRIPTION :
 *****************************************************************************/
int
parseLine (
	char		*line,
	char		*fname,
	vers_object	*obj)
{
  int	 end;		/* For the loops */

  if (mctx.mode & VERY_VERBOSE)
    printf ("parseLine: line=\"%s\"\n", line);

  /*
   * Empty line ? Comment ?
   * No more place for new attributes ?
   */
  if ((line[0]=='\0') || (line[0]=='#'))
    return (0);
  if (obj->attribsNb == MAX_ATTRIBS)
  {
    fprintf (stderr, "Error: too many attributes in %s, max is %d\n",
			fname, MAX_ATTRIBS);
    return (-1);
  }

  /*
   * Find the attribute name
   *    name:
   */
  for (end=0 ; (line[end]!='\0') && (line[end]!=':') ; end++);
  if (line[end]!=':')
  {
    fprintf (stderr, "Error: can't find attribute name in %s : \"%s\"\n",
			fname, line);
    return (-1);
  }

  /*
   * Initiate the attribute
   */
  obj->attribs[obj->attribsNb].buf  = NULL;
  obj->attribs[obj->attribsNb].src  = strdup (line);
  obj->attribs[obj->attribsNb].name = (char *) malloc (1+end);
  strncpy (obj->attribs[obj->attribsNb].name, line, end);
  obj->attribs[obj->attribsNb].name[end] = '\0';
  for (end++ ; line[end]==' ' ; end++);	/* Skip the leading ' ' */

  /*
   * We will now parse the value of this attribute
   */
  if (parseAttribValue(fname,obj, line+end, &(obj->attribs[obj->attribsNb]))<0)
    return (-1);

  /*
   * Do not forget to increment attributes number !
   */
  obj->attribsNb++;
  return (0);
}


/* ****************************************************************************
	FUNCTION :	readObject
	PURPOSE :	This function will read an object description from the
			file given in argument.
			The object should be already initiated !!!
	INPUT :		None.
	OUTPUT :	obj	= parsed object.
	RETURN :	-1 if error, 0 else.
	DESCRIPTION :
 *****************************************************************************/
int
readObject (
	vers_object	*obj)
{
  FILE	*ifile;			/* The file that contains the object to read */
  char	 line[MAX_FILTER];	/* To read ifile */
  int    rc = 0;

  /*
   * Open the file
   */
  ifile = fopen (obj->fname, "r");
  if (ifile == NULL)
  {
    perror (obj->fname);
    fprintf (stderr, "Error: cannot open file \"%s\"\n", obj->fname);
    rc = -1;
    goto done;
  }

  /*
   * Process each line of the input file.
   * Reminder : the object is initiated by the calling function !
   */
  while (fgets (line, MAX_FILTER, ifile) != NULL)
  {
    if ((strlen (line) > 0) && (line[strlen(line)-1]=='\n'))
      line[strlen(line)-1] = '\0';
    if (parseLine (line, obj->fname, obj) < 0) {
      rc = -1;
      goto done;
    }
  }

done:
  /*
   * Do not forget to close the file !
   */
  if (ifile && fclose (ifile) != 0)
  {
    perror (obj->fname);
    fprintf (stderr, "Error: cannot fclose file \"%s\"\n", obj->fname);
    rc = -1;
  }

  /*
   * End of function
   */
  if (obj->attribsNb == 0)
  {
    fprintf (stderr, "Error: no object found in \"%s\"\n", obj->fname);
    rc = -1;
  }
  return rc;
}

