/*

  watchdog.cc

  background watching 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 "logclass.H"
#include "watchdog.H"
#include "extern.H"

/*}}}*/

/*{{{  Logfile handling (only class0 filters)  */
/*{{{  activate()  */
void activate(){
  if ( display_logs == DISABLED ) return;
  /* speedup fadedown for activated logfile */
  syslog[active]->fadestep = -1 * ( fade_base / 3 );
  
  if ( syslog[active]->filterclass & CLASS1_FILTER ){
/*{{{  There are CLASS1 filters for this one...  */
    
    if ( syslog[active]->got == -1 ){
      file_error(syslog[active]->filename);
      return;
    }

    /* Freeze for output: */
    gtk_text_freeze (GTK_TEXT(textwindow));
    
    if ( syslog[active]->mode == TAIL_FILE ){
      output_text(textwindow, syslog[active]->buffer, syslog[active]->length);
    }  else {
      // for cat the data stays in the read buffer... we don't keep a history
      // anyways.
      output_text(textwindow, syslog[active]->rbuffer, syslog[active]->length);
    }

    /* And now unfreeze again */
    gtk_text_thaw(GTK_TEXT(textwindow));
    
    gtk_adjustment_set_value(GTK_TEXT(textwindow)->vadj,
			     GTK_TEXT(textwindow)->vadj->upper - 
			     GTK_TEXT(textwindow)->vadj->page_size);

    syslog[active]->active = TRUE;

/*}}}*/
  } else {

    if ( syslog[active]->mode == TAIL_FILE ){
/*{{{  Startup of "tail" mode  */
      syslog[active]->fd = open(syslog[active]->filename, O_RDONLY);
      if ( syslog[active]->fd == -1 ){
	file_error(syslog[active]->filename);
	return;
      }
      
      lseek(syslog[active]->fd, -maxtext, SEEK_END);
      
      long got = read(syslog[active]->fd, read_buffer, maxtext);
      
      /* Freeze for output: */
      gtk_text_freeze (GTK_TEXT(textwindow));
      
      output_text(textwindow, read_buffer, got);
      
      /* And now unfreeze again */
      gtk_text_thaw(GTK_TEXT(textwindow));
      
      gtk_adjustment_set_value(GTK_TEXT(textwindow)->vadj,
			       GTK_TEXT(textwindow)->vadj->upper - 
			       GTK_TEXT(textwindow)->vadj->page_size);
      
      // Memorize size of logfile...
      fstat( syslog[active]->fd,  &status );
      syslog[active]->last_size = status.st_size;
      
      syslog[active]->tag = gtk_timeout_add (syslog[active]->interval*100,
					     *tail_interrupt,
					     (gpointer*) active);
      
      syslog[active]->active = TRUE;

/*}}}*/
    } else if ( syslog[active]->mode == CAT_FILE ){
/*{{{  Startup of "cat" mode  */
      syslog[active]->fd = open(syslog[active]->filename, O_RDONLY);
      if ( syslog[active]->fd == -1 ){
	file_error(syslog[active]->filename);
	return;
      }
      
      long got = read(syslog[active]->fd, read_buffer, maxtext);
      
      close(syslog[active]->fd);
      
      /* Freeze for output: */
      gtk_text_freeze (GTK_TEXT(textwindow));
      
      output_text(textwindow, read_buffer, got);
      
      /* And now unfreeze again */
      gtk_text_thaw(GTK_TEXT(textwindow));
      
      gtk_adjustment_set_value(GTK_TEXT(textwindow)->vadj,
			       GTK_TEXT(textwindow)->vadj->lower);
      
      syslog[active]->tag = gtk_timeout_add (syslog[active]->interval*100,
					     *cat_interrupt,
					     (gpointer*) active);

      syslog[active]->active = TRUE;  
 
/*}}}*/
    }

  }
  // Set title to button text:
  gtk_window_set_title (GTK_WINDOW (window), syslog[active]->buttontext );  
}
/*}}}*/
/*{{{  deactivate()  */
void deactivate(){
  /*
    We need to disable interrupt-routines...
  */
  syslog[active]->active = FALSE;

  if ( ! ( syslog[active]->filterclass & CLASS1_FILTER ) ){
    if ( syslog[active]->tag != 0 ){
      gtk_timeout_remove(syslog[active]->tag);
      syslog[active]->tag=0;
    }
    if ( syslog[active]->mode == TAIL_FILE )
      close(syslog[active]->fd);
  }

  /*
    We always want to clear the textdisplay:
  */
  gtk_text_freeze (GTK_TEXT(textwindow));
  gtk_text_set_point (GTK_TEXT(textwindow),0);
  gtk_text_forward_delete (GTK_TEXT(textwindow),
			   gtk_text_get_length(GTK_TEXT(textwindow)));
  gtk_text_thaw(GTK_TEXT(textwindow));
  gtk_adjustment_set_value(GTK_TEXT(textwindow)->vadj, 0);
}
/*}}}*/
/*{{{  tail_interrupt  */
int tail_interrupt(gpointer data){ 
  // if the size didn't change we don't need to do anything...
  fstat( syslog[active]->fd,  &status );
  if ( status.st_size == syslog[active]->last_size ) return TRUE;
  /* if file shrunk there was probably a logfile rotation
     so just try to reopen the new one */ 
  if ( status.st_size < syslog[active]->last_size )
    if ( reopen_logfile(active) == FALSE ) return TRUE;

  long got = read(syslog[active]->fd, read_buffer, maxtext);

  while ( got > 0 ){
    /* Freeze for these actions: */
    gtk_text_freeze (GTK_TEXT(textwindow));
    
    /*
      More than maxtext bytes ? Then delete some...
    */
    long text_length = gtk_text_get_length(GTK_TEXT(textwindow));
    gfloat old_value = ( GTK_TEXT(textwindow)->vadj->upper - 
			 GTK_TEXT(textwindow)->vadj->value);
    if ( got + text_length > maxtext ){
      gtk_text_set_point (GTK_TEXT(textwindow),0);
      gtk_text_forward_delete (GTK_TEXT(textwindow), 
			       (text_length+got) - maxtext );
      gtk_text_set_point (GTK_TEXT(textwindow),
			  gtk_text_get_length(GTK_TEXT(textwindow)));
    }
    
    /*
      Now append the data we just read...
    */

    output_text(textwindow, read_buffer, got);

    /* And now unfreeze again */
    gtk_text_thaw(GTK_TEXT(textwindow));
    
    /* Now put scrollbar on same position relative to end */
    gtk_adjustment_set_value(GTK_TEXT(textwindow)->vadj,
			     GTK_TEXT(textwindow)->vadj->upper - 
			     old_value);
    
    got = read(syslog[active]->fd, read_buffer, maxtext);
  }

  fstat( syslog[active]->fd,  &status );
  syslog[active]->last_size = status.st_size;

  return TRUE;
}
/*}}}*/
/*{{{  cat_interrupt  */
int cat_interrupt(gpointer data){
  syslog[active]->fd = open(syslog[active]->filename, O_RDONLY);
  if ( syslog[active]->fd == -1 ){
    deactivate();
    file_error(syslog[active]->filename);
    return FALSE;
  }
  long got = read(syslog[active]->fd, read_buffer, maxtext);
  close( syslog[active]->fd );
  
  /* Freeze textwindow: */
  gtk_text_freeze (GTK_TEXT(textwindow));
  /* Memorize old position */
  gfloat old_value = ( GTK_TEXT(textwindow)->vadj->upper - 
		       GTK_TEXT(textwindow)->vadj->value);
  /* Clear window: */
  gtk_text_set_point (GTK_TEXT(textwindow),0);
  gtk_text_forward_delete (GTK_TEXT(textwindow), 
			   gtk_text_get_length(GTK_TEXT(textwindow)));
  
  if ( got > 0 ) output_text(textwindow, read_buffer, got);

  /* And now unfreeze again */
  gtk_text_thaw(GTK_TEXT(textwindow));
  
  /* Now put scrollbar on same position relative to end */
  gtk_adjustment_set_value(GTK_TEXT(textwindow)->vadj,
			   GTK_TEXT(textwindow)->vadj->upper - 
			   old_value);
  
}
/*}}}*/
/*}}}*/

/*{{{  shared routines  */
/* try to reopen logfile */
int reopen_logfile(int i ){
  close(syslog[i]->fd);
  syslog[i]->fd = open(syslog[i]->filename, O_RDONLY);
 
  if ( syslog[active]->fd > -1 ){
    // now we need to go to the beginning of the file:
    lseek(syslog[i]->fd, 0, SEEK_SET);
    // everything clear... return
    return TRUE;
  }

  /* we got a file opening error */

  syslog[i]->got = -1;

  if ( i == active ) deactivate();

  if ( syslog[i]->filterclass & CLASS1_FILTER  ){
    gtk_timeout_remove(syslog[i]->wd_tag);
    syslog[i]->wd_tag = 0;
    if ( syslog[i]->buffer != NULL ) delete syslog[i]->buffer;
    syslog[i]->buffer = NULL;
    if ( syslog[i]->rbuffer != NULL ) delete syslog[i]->rbuffer;
    syslog[i]->rbuffer = NULL;
    syslog[i]->length = 0;
  }

  file_error(syslog[i]->filename);
  return FALSE;
}
/*}}}*/

/*{{{  Watchdog (background log watching) on/off  */
void start_watchdog(){
  int i;
  for ( i = 0 ; i < syslogs ; i++ ){
    if ( syslog[i]->filterclass & CLASS1_FILTER ){
      // we need this buffer ONLY for the tail mode !
      if ( syslog[i]->mode == TAIL_FILE ){
	syslog[i]->buffer = new char[maxtext];
      }
      syslog[i]->rbuffer = new char[syslog[i]->chunk];
      syslog[i]->length = 0;
      
      syslog[i]->fd = open(syslog[i]->filename, O_RDONLY);
      
      if ( syslog[i]->mode == TAIL_FILE ){
	lseek(syslog[i]->fd, -maxtext, SEEK_END);
	syslog[i]->length = read(syslog[i]->fd, syslog[i]->buffer, maxtext);
      } else {
	syslog[i]->length = read(syslog[i]->fd, syslog[i]->rbuffer, maxtext);
      }  
      
      if ( syslog[i]->mode == TAIL_FILE ){
	// Memorize size of logfile...
	fstat( syslog[i]->fd,  &status );
	syslog[i]->last_size = status.st_size;
      } else {
	close(syslog[i]->fd);
      }
      
      syslog[i]->wd_tag = gtk_timeout_add (syslog[i]->interval*100,
					   *watchdog_interrupt,
					   (gpointer*) i);
      
    } else {
      if ( syslog[i]->buffer != NULL ) delete syslog[i]->buffer;
      syslog[i]->buffer = NULL;
      if ( syslog[i]->rbuffer != NULL ) delete syslog[i]->rbuffer;
      syslog[i]->rbuffer = NULL;
    }
  }
}
void stop_watchdog(){
  int i;
  for ( i = 0 ; i < syslogs ; i++ )
    if ( syslog[i]->filterclass & CLASS1_FILTER ){
      gtk_timeout_remove(syslog[i]->wd_tag);
      syslog[i]->wd_tag = 0;
     
      if ( syslog[i]->mode == TAIL_FILE )
	close(syslog[i]->fd);
      
      if ( syslog[i]->buffer != NULL ) delete syslog[i]->buffer;
      syslog[i]->buffer = NULL;
      if ( syslog[i]->rbuffer != NULL ) delete syslog[i]->rbuffer;
      syslog[i]->rbuffer = NULL;
      syslog[i]->length = 0;
    } 
}
/*}}}*/

/*{{{  watchdog_interrupt  */
int watchdog_interrupt(gpointer data){ 
  int i = (int) data;

  if ( syslog[i]->mode == TAIL_FILE ){
    if ( syslog[i]->fd == -1 ){
      watchdog_file_error(i);
      return TRUE;
    }

    /* tail file */
    fstat( syslog[i]->fd,  &status );
    if ( status.st_size == syslog[i]->last_size ) return TRUE;
    /* if file shrunk there was probably a logfile rotation
       so just try to reopen the new one */ 
    if ( status.st_size < syslog[i]->last_size )
      if ( reopen_logfile(i) == FALSE ) return TRUE;

  } else {

    /* cat file */
    syslog[i]->fd = open(syslog[i]->filename, O_RDONLY);    
    if ( syslog[i]->fd == -1 ){
      /* we got a file opening error */
      watchdog_file_error(i);
      return TRUE;      
    } 
    
  }
  
  long got = read(syslog[i]->fd, syslog[i]->rbuffer, syslog[i]->chunk);

  if ( syslog[i]->mode == CAT_FILE ) 
    close( syslog[i]->fd );

  while ( got > 0 ){
    long overlap;

    if ( syslog[i]->mode == TAIL_FILE ){

      overlap= ( got + syslog[i]->length ) - maxtext;
      if ( overlap < 0 ) overlap = 0;
      if ( overlap > 0 ){
	for ( long y = overlap ; y < syslog[i]->length ; y++ )
	  syslog[i]->buffer[ y - overlap] = syslog[i]->buffer[ y ];
      }
      
      long target = syslog[i]->length - overlap;
      for ( long z = 0 ; z < got ; z++ )
	syslog[i]->buffer[ z + target ] = syslog[i]->rbuffer[z];
      syslog[i]->length += got;
      if ( syslog[i]->length > maxtext ) syslog[i]->length = maxtext;

    } else {
      
      syslog[i]->length = got;
      
    }
    
    long a;
    int f;

    /*
      Now search for Class 1 Filters and take according actions
    */
    
    /* NOTICE */
    for ( a = 0 ; a < got ; a++ ){
      f = 0;
      while ( syslog[i]->filter[f] != NULL ){
	if ( syslog[i]->filter[f]->mode & NOTICE ){
	  if ( does_match(syslog[i]->rbuffer, syslog[i]->filter[f]->pattern,
			  a, got ) == TRUE ){
	    long b,c;
	    b = c = a;
	    while ( b > 0 && syslog[i]->rbuffer[b] != '\n' && syslog[i]->rbuffer[b] != '\r' ) b--;
	    if ( b != 0 ) b++;
	    while ( c < got && syslog[i]->rbuffer[c] != '\n' && syslog[i]->rbuffer[c] != '\r' ) c++;
	    
	    gchar* string = new gchar[(c-b)+4];
	    long d = 0;
	    for ( ; b < c ; b++ ) string[d++] = syslog[i]->rbuffer[b];
	    string[d++] = 0;
	    
	    notice_alert(syslog[i]->buttontext , string);
	    delete string;
	    play(NOTICE);
	  }
	}
	f++;
      }
    }
    
    /* ALERT */
    for ( a = 0 ; a < got ; a++ ){
      f = 0;
      while ( syslog[i]->filter[f] != NULL ){
	if ( syslog[i]->filter[f]->mode & ALERT ){
	  if ( does_match(syslog[i]->rbuffer, syslog[i]->filter[f]->pattern,
			  a, got ) == TRUE ){
	    trigger_alert(i);
	    play(ALERT);
	  }
	}
	f++;
      }
    }
   
    /* UNICONIFY */
    for ( a = 0 ; a < got ; a++ ){
      f = 0;
      while ( syslog[i]->filter[f] != NULL ){
	if ( syslog[i]->filter[f]->mode & UNICONIFY ){
	  if ( does_match(syslog[i]->rbuffer, syslog[i]->filter[f]->pattern,
			  a, got ) == TRUE ){
	    /* This maps the window, which also de-iconifies it according to ICCCM. */
	    gdk_window_show (GTK_WIDGET (window)->window);
	    gdk_window_raise (GTK_WIDGET (window)->window);
	    play(UNICONIFY);
	  }
	}
	f++;
      }
    }

    /* EXECUTE */
    for ( a = 0 ; a < got ; a++ ){
      f = 0;
      while ( syslog[i]->filter[f] != NULL ){
	if ( syslog[i]->filter[f]->mode & EXECUTE ){
	  if ( does_match(syslog[i]->rbuffer, syslog[i]->filter[f]->pattern,
			  a, got ) == TRUE ){
	    long b,c;
	    b = c = a;
	    while ( b > 0 && syslog[i]->rbuffer[b] != '\n' && syslog[i]->rbuffer[b] != '\r' ) b--;
	    if ( b != 0 ) b++;
	    while ( c < got && syslog[i]->rbuffer[c] != '\n' && syslog[i]->rbuffer[c] != '\r' ) c++;
	    
	    gchar* string = new gchar[(c-b)+4];
	    long d = 0;
	    for ( ; b < c ; b++ ) string[d++] = syslog[i]->rbuffer[b];
	    string[d++] = 0;
	    
	    execute_program(i, f , string);
	    delete string;
	  }
	}
	f++;
      }
    }


    /*
      Is the log active ? Then display it ...
    */
    if ( syslog[i]->active == TRUE ){
      if ( syslog[i]->mode == TAIL_FILE )
	append_text(syslog[i]->rbuffer, got);
      else
	replace_text(syslog[i]->rbuffer, got);

    }
    
    if ( syslog[i]->mode == TAIL_FILE ) got = read(syslog[i]->fd, syslog[i]->rbuffer, syslog[i]->chunk);
    else got = 0;
  }
  
  if ( syslog[i]->mode == TAIL_FILE ){
    fstat( syslog[i]->fd,  &status );
    syslog[i]->last_size = status.st_size;
  }
  
  return TRUE;
}
void append_text(char* buffer, long amount){
  /* Freeze for these actions: */
  gtk_text_freeze (GTK_TEXT(textwindow));
  
  /*
    More than maxtext bytes ? Then delete some...
  */
  long text_length = gtk_text_get_length(GTK_TEXT(textwindow));
  gfloat old_value = ( GTK_TEXT(textwindow)->vadj->upper - 
		       GTK_TEXT(textwindow)->vadj->value);
  if ( amount + text_length > maxtext ){
    gtk_text_set_point (GTK_TEXT(textwindow),0);
    gtk_text_forward_delete (GTK_TEXT(textwindow), 
			     (text_length+amount) - maxtext );
    gtk_text_set_point (GTK_TEXT(textwindow),
			gtk_text_get_length(GTK_TEXT(textwindow)));
  }
  
  /*
    Now append the data we just read...
  */
  
  output_text(textwindow, buffer, amount);
  
  /* And now unfreeze again */
  gtk_text_thaw(GTK_TEXT(textwindow));
  
  /* Now put scrollbar on same position relative to end */
  gtk_adjustment_set_value(GTK_TEXT(textwindow)->vadj,
			   GTK_TEXT(textwindow)->vadj->upper - 
			   old_value);
}
void replace_text(char* buffer, long amount){
  /* Freeze textwindow: */
  gtk_text_freeze (GTK_TEXT(textwindow));
  /* Memorize old position */
  gfloat old_value = ( GTK_TEXT(textwindow)->vadj->upper - 
		       GTK_TEXT(textwindow)->vadj->value);
  /* Clear window: */
  gtk_text_set_point (GTK_TEXT(textwindow),0);
  gtk_text_forward_delete (GTK_TEXT(textwindow), 
			   gtk_text_get_length(GTK_TEXT(textwindow)));
  
  if ( amount > 0 ) output_text(textwindow, buffer, amount);
  
  /* And now unfreeze again */
  gtk_text_thaw(GTK_TEXT(textwindow));
  
  /* Now put scrollbar on same position relative to end */
  gtk_adjustment_set_value(GTK_TEXT(textwindow)->vadj,
			   GTK_TEXT(textwindow)->vadj->upper - 
			   old_value);
}
/*}}}*/
/*{{{  error subroutines for watchdog  */
void watchdog_file_error(gint i){
  syslog[i]->got = -1;      
  if ( i == active ) deactivate();
  if ( syslog[i]->wd_tag ) gtk_timeout_remove(syslog[i]->wd_tag);
  syslog[i]->wd_tag = 0;
  if ( syslog[i]->buffer != NULL ) delete syslog[i]->buffer;
  syslog[i]->buffer = NULL;
  if ( syslog[i]->rbuffer != NULL ) delete syslog[i]->rbuffer;
  syslog[i]->rbuffer = NULL;
  syslog[i]->length = 0;

  file_error(syslog[i]->filename);
}
/*}}}*/
