/*

  execute.cc

  program execution 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 "execute.H"
#include "extern.H"

/*}}}*/

/*{{{  execute_program  */
void 
execute_program(gint i, gint f, gchar* line)
{
  gchar* execline = entry[i]->filter[f]->execline;
  gchar* name = (gchar*) entry[i]->buttontext;
  gchar* filename = (gchar*) entry[i]->filename;
  gchar* helptext = (gchar*) entry[i]->help;
  gchar* modeline = NULL;
  switch ( entry[i]->mode ){
  case TAIL_FILE:
    modeline = "TAIL";
    break;
  case CAT_FILE:
    modeline = "CAT";
    break;
  case RUN_FILE:
    modeline = "RUN"; // this is for version 1.5.x ... :-)
    break;
  default:
    modeline = "ERROR";
    break;
  };

  if ( execline == NULL ) return;

  pid_t pid = fork();
  if ( pid != 0 ){
    if ( errno == EAGAIN || errno == ENOMEM ){
      fork_error();
      return;
    }
    
    reference_child(-1); /* we don't care about the end of this one as
			    long as it does get terminated... */
    return;
  }

  /*
    +++ NOW WE ARE A CHILD PROCESS +++
  */

  char** argv = NULL;
  gint argc = 0;

  /*
    split the long line up in it's argv parts...
  */

  gint a = 0;
  char delim = ' ';
  gint length = strlen(execline);  
  gchar* tempstring = new gchar[strlen(execline)+10];
  while ( a < length ){
    gint b = 0;

    while ( a < length && 
	    execline[a] != delim )
      tempstring[b++] = execline[a++];
    tempstring[b] = 0;

    if ( strlen(tempstring) > 0 ){
      
      if ( argc == 0 ){
	argv = new char*[2];
	argv[0] = new char[strlen(tempstring)+2];
	argv[1] = NULL;
      } else {
	char** temp = new char*[argc+2];
	gint x = 0;
	while ( argv[x] != NULL ){
	  temp[x] = argv[x];
	  x++;
	}
	temp[x] = temp[x+1] = NULL;
	delete argv;
	argv = temp;
	argv[argc] = new char[strlen(tempstring)+2];
      }
      
      strcpy(argv[argc], tempstring);
      argc++;

    }

    while ( a < length &&
	    execline[a] == delim ) a++;

    if ( a < length &&
	 ( execline[a] == '"'  ||
	   execline[a] == '`' ||
	   execline[a] == '´' )) delim = execline[a++];
    else delim = ' ';

  }
  delete tempstring;

  /*
    and now see whether there are variables to subsitute
    this is done in this order because it should pose the
    lowest threat to having a variable in a replaced string that
    triggers another replacement later... 
  */
  gint x;

  /* replace all %M */
  x = 0;
  while ( argv[x] != NULL ){
    argv[x] = substitute(argv[x], "%M", modeline);
    x++;
  }
  my_setenv( "XLM_MODE", modeline); // and set environment variable
  
  /* replace all %F */
  x = 0;
  while ( argv[x] != NULL ){
    argv[x] = substitute(argv[x], "%F", filename);
    x++;
  }
  my_setenv( "XLM_FILENAME", filename); // and set environment variable
  
  /* replace all %N */
  x = 0;
  while ( argv[x] != NULL ){
    argv[x] = substitute(argv[x], "%N", name);
    x++;
  }
  my_setenv( "XLM_NAME", name); // and set environment variable
  
  /* replace all %H */
  x = 0;
  while ( argv[x] != NULL ){
    argv[x] = substitute(argv[x], "%H", helptext);
    x++;
  }
  x = my_setenv( "XLM_HELP", helptext); // and set environment variable
  
  /* replace all %L */
  x = 0;
  while ( argv[x] != NULL ){
    argv[x] = substitute(argv[x], "%L", line);
    x++;
  }
  my_setenv( "XLM_LINE", line); // and set environment variable

  /* now we got the argv[] array set up, so it's time to
     start the program... */
  execvp(argv[0], argv);

  /* bail out unceremoniously :-) */
  _exit(0);
}
/*}}}*/
/*{{{  execution subroutines  */
/*
  sets environment variable (first parameter) to value (second parameter).

  Basically a rewrite of the setenv function on some systems because not
  all of them support it.
*/
gint
my_setenv(const gchar* variable, const gchar* value){
  gint x = -1;
#if HAVE_PUTENV
  gchar* envstring = new gchar[strlen(value) + strlen(variable) + 2];
  strcpy( envstring, variable);
  strcat( envstring, "=");
  strcat( envstring, value );
  x = putenv( envstring );
#endif /* HAVE_PUTENV */
  return x;
}
/* replaces all occurences of pattern in target with 
   replacement and gives back pointer to new string */ 
gchar* substitute(gchar* target, gchar* pattern, gchar* replacement){
  gint x,y;
  gint substitutions;
  gint patternlength = strlen(pattern);

  /* see whether there are any replacements to be made */
  substitutions = 0;
  x = 0;
  while ( target[x] != 0 ){

    y = 0;
    while ( target[x+y] == pattern[y] 
	    && target[x+y] != 0 
	    && pattern[y] != 0 ) y++;

    if ( y == patternlength ) substitutions++;
    
    x++;
  }

  /* if no replacements are to be made return the original string */
  if ( substitutions == 0 ) return target;

  /* otherwise allocate enough memory for the new string plus 
     a little spare for safety */
  gchar* string = new gchar[ substitutions * strlen(replacement)
			   + strlen(target) + 10 ];

  /* now do replacement */
  gint a = 0;
  gint b;
  x = 0;
  while ( target[x] != 0 ){
    y = 0;
    while ( target[x+y] == pattern[y] 
	    && target[x+y] != 0 
	    && pattern[y] != 0 ) y++;
    
    if ( y == patternlength ){
      b = 0;
      
      while ( replacement[b] != 0 )
	string[a++] = replacement[b++];
      
      x += patternlength;
    } else string[a++] = target[x++];
  }
  string[a] = 0;

  delete target;
  return string;
}
/*}}}*/



/*{{{  reference_child  */
void reference_child(gint pid){
  pid_reference++;
  if ( cleanup_tag == 0 )
    cleanup_tag = gtk_timeout_add (100,
				   *cleanup_interrupt,
				   (gpointer*) NULL);
  
  /* do we need to track the termination of this one ? */
  if ( pid != -1 )
    pid_list = g_list_append(pid_list, (gpointer) pid );
}
/*}}}*/
/*{{{  check_terminate  */
gint check_terminate(gint pid){
  gint index = g_list_index(pid_list, (gpointer) pid);
  
  if ( index == -1 ) return TRUE; // there is no such pid in the list
  else return FALSE; // it is still in there - the process is still running
}
/*}}}*/
/*{{{  cleanup_interrupt  */
int cleanup_interrupt(gpointer data){
  pid_t result = waitpid(-1, NULL, WNOHANG);
  if ( result < 0 ) return TRUE; // return if nothing exited
  
  pid_reference--;
  
  if ( pid_reference < 0 ){
    cerr << "OOoops in execute.cc::cleanup_interrupt, pid_reference negative..." << endl;
    pid_reference = 0;
  }
  
  gint index = g_list_index(pid_list, (gpointer) result);
  if ( index != -1 ) pid_list = g_list_remove( pid_list, (gpointer) result );
  
  if ( pid_reference == 0 ){
    gtk_timeout_remove(cleanup_tag);
    cleanup_tag = 0;
  }
  return TRUE;
}
/*}}}*/

