/*

  file.cc

  file access subroutines for xlogmaster.cc
  Copyright (C) 1998 Georg C. F. Greve
  This is a GNU program
  
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
  
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  
  
  Contact: 
           mailto:xlogmaster-bugs@gnu.org
           http://www.gnu.org/software/xlogmaster/
  Secondary sources:
           http://porter.desy.de/~greve/xlogmaster/
           http://www.fusebox.hanse.de/xlogmaster/
	 
*/

/*{{{  Header  */

#include "../config.h"
#include "sysinc.H"
#include "../import/import.h"
#include "logclass.H"
#include "file.H"
#include "extern.H"

/*}}}*/

/*{{{  Read Configuration  */
/*
  This function reads a Xlogmaster-Style configuration file
  
  The parameter is the filename (absolute !!) and the results are
  given in the "fillup-structure"

*/
int 
read_configuration(char* fname)
{
  int fd = open(fname, O_RDONLY);
  if ( fd == -1 ) return FALSE;
  fstat( fd,  &status );
  int length = (int) status.st_size;
  char* buffer = new char[length+2];

  int got = (int) read(fd, buffer, length);
  close(fd);
  if ( got != length ){
    delete buffer;
    return FALSE;
  }
  buffer[length] = 0; // Make it zero terminated

  /*
    And now start browsing the file
  */

  create_regex_patterns();

  gint x = 0;
  gchar* line = buffer;
  gint a;

  while ( x < length )
    {

      while ( x < length &&
	      buffer[x] != '\r' &&
	      buffer[x] != '\n' ) x++;
      
      buffer[x] = 0;
      a = 0;
      gint result = -1;

      while ( mode_regex[a] != NULL &&
	      result < 0 )
	{
	  
	  result = re_search( *(mode_regex[a]),
			      line,
			      strlen(line),
			      0,
			      strlen(line),
			      NULL);
	  
	  if ( result < 0 ) a++;
	}
      
      if ( result >= 0 )
	{
	  /*
	    Line is a mode line
	  */
	  
	  gint new_mode = mode_flags[a];
	  
	  guint index = searchfor('{', line, 0);
	  if ( index >= strlen(line) ){
	    free_regex_patterns();	    
	    delete buffer;
	    return TRUE;
	  }
	  index++;
	
	  /*
	    retrieve filename
	  */
	  gchar* new_filename = getstring(',', line, index);
	  if ( new_filename == NULL ){
	    free_regex_patterns();
	    delete buffer;
	    return TRUE;
	  }
	  index += strlen(new_filename)+1;
	  new_filename = clean_string(new_filename);

	  /*
	    retrieve time interval
	  */
	  gint new_interval = 0;
	  gchar* new_int_string = getstring(',', line, index);
	  if ( new_int_string == NULL ){
	    delete new_filename;
	    free_regex_patterns();
	    delete buffer;
	    return TRUE;
	  }
	  index += strlen(new_int_string)+1;
	  new_int_string = clean_string(new_int_string);
	  gint y = 0;
	  while ( new_int_string[y] != 0 )
	    {
	      gint c = new_int_string[y] - '0';
	      if ( c >= 0 && c <= 9 )
		{
		  new_interval *= 10;
		  new_interval += c;
		}
	      y++;
	    }
	  if ( new_interval == 0 ||
	       strlen(new_int_string) == 0 ) new_interval = INTERVAL;
	  delete new_int_string;

	  /*
	    retrieve buttontext
	  */
	  gchar* new_buttontext = getstring(',', line, index);
	  if ( new_buttontext == NULL ){
	    delete new_filename;
	    free_regex_patterns();
	    delete buffer;
	    return TRUE;
	  }
	  index += strlen(new_buttontext)+1;
	  new_buttontext = clean_string(new_buttontext);
	  
	  /*
	    retrieve helptext
	  */
	  gchar* new_help = getstring('}', line, index);
	  if ( new_help == NULL ){
	    delete new_buttontext;
	    delete new_filename;
	    free_regex_patterns();
	    delete buffer;
	    return TRUE;
	  }
	  index += strlen(new_help)+1;
	  new_help = clean_string(new_help);
	  if ( strlen(new_help) == 0 ){
	    delete new_help;
	    new_help = new char[strlen(new_filename)+1];
	    strcpy(new_help, new_filename);
	  }

	  /*
	    and now add the entry...
	  */
	  add_mode_entry(new_mode, new_filename, new_help, new_buttontext, new_interval);

	}
      else
	{
	  /*
	    Line is NOT a mode line, so we need to check whether
	    it is a filter line...
	  */

	  a = 0;
	  gint new_filter_mode = 0;

	  while ( filter_regex[a] != NULL )
	    {
	      
	      result = re_search( *(filter_regex[a]),
				  line,
				  strlen(line),
				  0,
				  strlen(line),
				  NULL);

	      /*
		if we have a match add the according flag to
		the filter flags
	      */
	      
	      if ( result >= 0 )
		{
		  new_filter_mode |= filter_flags[a];
		}
	      
	      a++;
	    }

	  if ( new_filter_mode != 0 )
	    {
	      /*
		This line contains a valid filter entry, we just need
		the string(s) now...
	      */

	      /*
		The string will never be longer than our line
		and since this is only a very temporary array
		the waste of space shouldn't bother us too much.
	      */
	      gchar* new_filter_string = new gchar[strlen(line)];
	      gchar* new_filter_execline = NULL;

	      gint index = strlen(line);
	      while ( index >= 0 &&
		      line[index] != '}' ) index--;

	      gint end;
	      gint counter;
	      gint x;

	      /*
		Now we have the position of the last `}`
		in the line which is the delimiter of the
		REGEX or EXECUTE string, so first we need
		to check for the EXECUTE flag and fetch the
		string if present.

		these routines expect EXECUTE strings to
		contain no '{' !!!
	      */

	      if ( new_filter_mode & EXECUTE )
		{
		  new_filter_execline = new gchar[strlen(line)];
		  end = index;
		  
		  while ( index >= 0 &&
			  line[index] != '{' ) index--;
		  
		  counter = index+1;
		  x = 0;
		  
		  while ( counter < end )
		    new_filter_execline[x++] = line[counter++];

		  /*
		    zero termination is crucial
		  */
		  new_filter_execline[x] = 0;
		}

	      /*
		and now retrieve the REGEX in the same way,
		it is the expression that is standing between
		the '}' at the end of the line (after the EXECUTE
		line has been taken out if neccessary) and the
		first '{' from the beginning of the line.
	      */

	      while ( index >= 0 &&
		      line[index] != '}' ) index--;
	      
	      end = index;
	      counter = searchfor('{', line, 0) + 1;

	      x = 0;

	      while ( counter < end )
		new_filter_string[x++] = line[counter++];
	      
	      /*
		zero termination is crucial
	      */
	      new_filter_string[x] = 0;
	      
	      /*
		And now add the filter entry to the current
		log entry
	      */
	      add_filter_entry(new_filter_string, new_filter_mode, new_filter_execline);
	      
	    } 
	}
      
      /*
	Now step to the beginning of the next line
      */
      
      while ( x < length && 
	      ( buffer[x] == 0 ||
		buffer[x] == '\r' ||
		buffer[x] == '\n'  )) x++;
      
      line = &buffer[x];
      
    }
  
  free_regex_patterns();
  delete buffer;
  return TRUE;
}
/*
  This routine adds a new entry to the array that
  is used to set up the real Log Class entries

  The parameters are the values of the entries that are to be added to the
  array.

*/
void
add_mode_entry(gint new_mode, gchar* new_filename, gchar* new_help, gchar* new_buttontext, gint new_interval)
{
  if ( fillups == 0 )
    {
      fillups = 1;
      mode = new int[fillups];
      filename = new char* [fillups];
      help = new char* [fillups];
      buttontext = new char* [fillups];
      interval = new int[fillups];
      commandline = new char**[fillups];
      filter = new char**[fillups];
      filter_exec = new char**[fillups];
      filter_mode = new int*[fillups];
      mode[fillups-1] = new_mode;
      filename[fillups-1] = new_filename;
      help[fillups-1] = new_help;
      buttontext[fillups-1] = new_buttontext;
      interval[fillups-1] = new_interval;
      commandline[fillups-1] = NULL;
      filter[fillups-1] = NULL;
      filter_exec[fillups-1] = NULL;
      filter_mode[fillups-1] = 0;
    }
  else
    {
      int *tmp_mode= mode;                 
      char **tmp_filename = filename;
      char **tmp_help = help;  
      char **tmp_buttontext = buttontext;  
      int *tmp_interval = interval;
      char ***tmp_commandline = commandline;
      char ***tmp_filter = filter;
      char ***tmp_filter_exec = filter_exec;
      int **tmp_filter_mode = filter_mode;
      fillups++;
      mode = new int[fillups];
      filename = new char* [fillups];
      help = new char* [fillups];
      buttontext = new char* [fillups];
      interval = new int[fillups];
      commandline = new char**[fillups];
      filter = new char**[fillups];
      filter_exec = new char**[fillups];
      filter_mode = new int*[fillups];
      for ( int i = 0 ; i < fillups-1 ; i++ )
	{
	  mode[i]= tmp_mode[i];                 
	  filename[i] = tmp_filename[i];
	  help[i] = tmp_help[i];  
	  buttontext[i] = tmp_buttontext[i];  
	  interval[i] = tmp_interval[i];
	  commandline[i] = tmp_commandline[i];
	  filter[i] = tmp_filter[i];
	  filter_exec[i] = tmp_filter_exec[i];
	  filter_mode[i] = tmp_filter_mode[i];
	}
      mode[fillups-1] = new_mode;
      filename[fillups-1] = new_filename;
      help[fillups-1] = new_help;
      buttontext[fillups-1] = new_buttontext;
      interval[fillups-1] = new_interval;
      commandline[fillups-1] = NULL;
      filter[fillups-1] = NULL;
      filter_exec[fillups-1] = NULL;
      filter_mode[fillups-1] = 0;
      delete tmp_mode;
      delete tmp_filename;
      delete tmp_help;
      delete tmp_buttontext;
      delete tmp_interval;
      delete tmp_commandline;
      delete tmp_filter;
      delete tmp_filter_exec;
      delete tmp_filter_mode;
    }
}
/*
  Adds another filter entry to entry that is currently being
  read
*/
void
add_filter_entry(gchar* _string, gint _mode, gchar* _execline)
{
  if ( fillups < 1 ) return;

  if ( filter[fillups-1] == NULL )
    {
      filter[fillups-1] = new gchar*[2];
      filter_exec[fillups-1] = new gchar*[2];
      filter_mode[fillups-1] = new gint[2];

      (filter[fillups-1])[0] = _string;
      (filter_exec[fillups-1])[0] = _execline;
      (filter_mode[fillups-1])[0] = _mode;

      (filter[fillups-1])[1] = NULL;
      (filter_exec[fillups-1])[1] = NULL;
      (filter_mode[fillups-1])[1] = 0;
    }
  else 
    {
      gint x = 0;
      while ( (filter[fillups-1])[x] != NULL ) x++;

      char** tmp_new_filter = new char*[x+2];
      char** tmp_new_filter_exec = new char*[x+2];
      int* tmp_new_filter_mode = new int[x+2];

      x = 0;
      while ( (filter[fillups-1])[x] != NULL )
	{
	  tmp_new_filter[x] = (filter[fillups-1])[x];
	  tmp_new_filter_exec[x] = (filter_exec[fillups-1])[x];
	  tmp_new_filter_mode[x] = (filter_mode[fillups-1])[x];
	  x++;
	}

      delete (filter[fillups-1]);
      delete (filter_exec[fillups-1]);
      delete (filter_mode[fillups-1]);

      (filter[fillups-1]) = tmp_new_filter;
      (filter_exec[fillups-1]) = tmp_new_filter_exec;
      (filter_mode[fillups-1]) =  tmp_new_filter_mode;

      (filter[fillups-1])[x] = _string;
      (filter_exec[fillups-1])[x] = _execline;
      (filter_mode[fillups-1])[x] = _mode;

      (filter[fillups-1])[x+1] = NULL;
      (filter_exec[fillups-1])[x+1] = NULL;
      (filter_mode[fillups-1])[x+1] = 0;

    }
}

/*}}}*/

/*{{{  Matching subroutines  */

// legalize filter mode, only one of the CLASS0 filters is allowed
gint legalize(gint fmode, gint def){
  gint activated = 0;
  gint x = 0;
  while ( class0_filters[x] != -1 ){
    if ( fmode & class0_filters[x] ) activated++;
    x++;
  }

  if ( activated > 1 ){
    /* leave class1 and special mode things untouched 
       and delete all class0 */
    fmode &= ( CLASS1_MASK |  SPECIAL_MODE_MASK ); 
    fmode |= def; // set default
  }
 
  return fmode;
}
// tries to find "string" in "buffer" at position "x"
int match(char* string, char* buffer, int x){
  int i = 0;
  while( string[i] != 0 )
    if ( string[i++] != buffer[x++] )
      return FALSE;
  
  return TRUE;
}
// searches for char 'c' in buffer starting at x and returning position
int searchfor(char c, char* buffer, int x){
  while( buffer[x] != c && buffer[x] != 0 ) x++;
  return x;
}
// tries to create string from x to position of 'delim'
// returns NULL if out of bounds...
char* getstring(char delim, char* buffer, int x){
  int y = searchfor( delim, buffer, x);
  if ( buffer[y] == 0 ) return NULL;
  char* string = new char[y-x+1];
  int i = 0;
  while ( i < y-x ){
    string[i] = buffer[x+i];
    i++;
  }
  string[y-x] = 0;
  return string;
}
char* clean_string(char* source){
  if ( source == NULL ) return NULL;
  char* target = new char [strlen(source)+1];
  // eliminate leading and trailing spaces + all CR/LF's
  int a = 0;  // source
  int b = 0;  // target
  while ( source[a] == ' ' || source[a] == 10 || source[a] == 13 ) a++;
  while ( source[a] != 0 ){
    if ( source[a] != 10 && source[a] != 13 ) target[b++] = source[a++]; 
    else a++;
  }
 target[b--] = 0;
  while ( target[b] == ' ' || target[b] == 10 || target[b] == 13 ) target[b--] = 0;
  char* result = new char[strlen(target)+1];
  strcpy(result, target);
  delete source;
  delete target;

  return result;
}
/*}}}*/

/*{{{  REGEX subroutines  */
/*
  This routine does nothing else than initializing the
  patterns for the matching process
*/
void
create_regex_patterns()
{
  gint x = 0;

  while ( all_string[x] != NULL )
    {
      
      (*(all_regex[x])) = new re_pattern_buffer;
      (*(all_regex[x]))->buffer = NULL;
      (*(all_regex[x]))->allocated = 0;
      (*(all_regex[x]))->translate = NULL;
      (*(all_regex[x]))->re_nsub = 0;
      (*(all_regex[x]))->se_params = NULL; 
      (*(all_regex[x]))->syntax_parens = NULL;
      (*(all_regex[x]))->start = NULL;
      re_set_syntax( RE_SYNTAX_EMACS );
      (*(all_regex[x]))->fastmap = NULL;
      
      re_compile_pattern(all_string[x], strlen(all_string[x]), (*(all_regex[x])));
      
      (*(all_regex[x]))->fastmap = new gchar[256];
      re_compile_fastmap( (*(all_regex[x])) );
      
      x++;
    }
  
}
/*
  This routine frees the allocted patterns again...
*/
void
free_regex_patterns()
{
  gint x = 0;
  
  while ( all_string[x] != NULL )
    {
      
      disassemble_regex(*(all_regex[x]));
      (*(all_regex[x])) = NULL;
      
      x++;
    }
  
}

/*}}}*/

/*{{{  Write Configuration  */
/*
  This routine writes an Xlogmaster style configuration file
  into the file fname (only parameter).
*/
int 
write_configuration(char* fname)
{
  ofstream file;
  file.open(fname, ios::out);
  if ( file.fail() ) return FALSE;
  
  for ( int i = 0 ; i < syslogs ; i++ )
    {
      if ( entry[i]->mode == TAIL_FILE ) file << "TAIL{";
      else if ( entry[i]->mode == CAT_FILE ) file << "CAT{";
      file << entry[i]->filename << ",";
      file << entry[i]->interval << ",";
      file << entry[i]->buttontext << ",";
      file << entry[i]->help << "}" << endl;
      
      if ( entry[i]->filter != NULL )
	{
	  int a = 0;
	  while ( entry[i]->filter[a] != NULL )
	    {

	      int fe = 0;
	      if ( entry[i]->filter[a]->mode & RAISE )
		{
		  file << "RAISE";
		  fe++;
		}
	
	      if ( entry[i]->filter[a]->mode & LOWER )
		{
		  if ( fe > 0 ) file << ",";
		  file << "LOWER";
		  fe++;
		}

	      if ( entry[i]->filter[a]->mode & HIDE )
		{
		  if ( fe > 0 ) file << ",";
		  file << "HIDE";
		  fe++;
		}
	
	      if ( entry[i]->filter[a]->mode & ALERT )
		{
		  if ( fe > 0 ) file << ",";
		  file << "ALERT";
		  fe++;
		}
	
	      if ( entry[i]->filter[a]->mode & NOTICE )
		{
		  if ( fe > 0 ) file << ",";
		  file << "NOTICE";
		  fe++;
		}
	
	      if ( entry[i]->filter[a]->mode & UNICONIFY )
		{
		  if ( fe > 0 ) file << ",";
		  file << "UNICONIFY";
		  fe++;
		}

	      if ( entry[i]->filter[a]->mode & EXECUTE )
		{
		  if ( fe > 0 ) file << ",";
		  file << "EXECUTE";
		  fe++;
		}

	      if ( entry[i]->filter[a]->mode & INVERT )
		{
		  if ( fe > 0 ) file << ",";
		  file << "INVERT";
		  fe++;
		}

	      if ( entry[i]->filter[a]->mode & CASE_SENSITIVE )
		{
		  if ( fe > 0 ) file << ",";
		  file << "CASE_SENSITIVE";
		  fe++;
		}
	
	      file << "{" << entry[i]->filter[a]->string << "}";
	      
	      if ( entry[i]->filter[a]->mode & EXECUTE )
		file << "{" << entry[i]->filter[a]->execline << "}";
	
	      file << endl;
	
	      a++;
	      
	    }
	}
    }
  
  file.close();
  return TRUE;
}
/*}}}*/
