/***************************************
  $Revision: 1.29 $

  Error reporting (er) er.c - library of functions to uniformly report errors.

  Status: NOT REVUED, PARTLY TESTED

  NOTE: MALLOC ALERT!!! THE REPORTING FUNCTIONS MAY NOT USE DYNAMIC MEMORY!!!
  for one: they wouldn't work if we run out of memory...
  for two: the memory wrappers may have logging enabled, and it would loop.

  Design and implementation by: Marek Bukowy

  ******************/ /******************
  Copyright (c) 1999,2000,2001                        RIPE NCC
 
  All Rights Reserved
  
  Permission to use, copy, modify, and distribute this software and its
  documentation for any purpose and without fee is hereby granted,
  provided that the above copyright notice appear in all copies and that
  both that copyright notice and this permission notice appear in
  supporting documentation, and that the name of the author not be
  used in advertising or publicity pertaining to distribution of the
  software without specific, written prior permission.
  
  THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
  ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
  AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
  DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
  AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  ***************************************/

#define ER_IMPL
#include "rip.h"

#include <pthread.h>
#include <time.h>

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>


/*++++++++++++++++++++++++++++++++++++++
  Simple check if the error code is a success (severity = 0).
  Detects (some) invalid codes - those whose facility part is 0.

  int NOERR      returns 1 if the code is valid and is a success
                 returns 0 if the code is invalid or is a real error
		 (the severity is non-zero).

  er_ret_t a     error code to be checked.
  ++++++++++++++++++++++++++++++++++++++*/
int NOERR(er_ret_t a) 
{
  return (    ((a & 0xFFFF) == 0 )          /* the error part is 0 */
	      && ((a & 0xFFFF0000) != 0) ); /* the facility is non-zero */
}


/*++++++++++++++++++++++++++++++++++++++
  
  Message selection routine. Checks if the message coming from the
  current thread, tagged with the given error code, facility and
  aspect matches the given filter.

  int er_msgsel             returns 1 for a match, 0 otherwise.

  er_filter_t *filtptr      filter to be matched

  er_fac_code_t facwhere    message's facility of call

  er_mask_t asp             message's aspect (if severity is INFO or DEBUG)

  er_ret_t errcode          message's error code
  ++++++++++++++++++++++++++++++++++++++*/

int er_msgsel( er_filter_t *filtptr, 
	       er_fac_code_t facwhere, 
	       er_mask_t asp, 
	       er_ret_t errcode)
{
  if( ! MA_isset( filtptr->fac_mask, facwhere) ) {
        return 0;
  }
  
  /* check aspect only for DEBUG and INFO messages */
  if( ( errcode == ER_SEV_D || errcode == ER_SEV_I )
      && ! (asp & filtptr->asp_mask) ) {
    return 0;
  }
  
  if(    (errcode & 0xff000000) < filtptr->sev_min 
      || (errcode & 0xff000000) > filtptr->sev_max ) {
    return 0;
  }

  if(    filtptr->thr_id != 0 
      && filtptr->thr_id != pthread_self() ) {
    return 0;
  }

  return 1;
}





/*++++++++++++++++++++++++++++++++++++++
  Fork & exec a program specified with argv, the print msg
  on its stdin and exit. No redirection of stdout/stderr is done.
  
  MT-note: Solaris fork1() duplicates only the calling thread.
  So does Posix fork().
  
  char **argv       argv array for the exec call

  char *msg         text payload to be printed on the stdin of forked process

  int usepath       flag indicating if the PATH environmental variable 
                    should be used by the exec call.
  ++++++++++++++++++++++++++++++++++++++*/
void er_forkexec(char **argv, char *msg, int usepath)
{
  int PipeEnds[2];
  int status, cpid;
 
  pipe(PipeEnds);	       
  
#define PIP_WR 1
#define PIP_RD 0

#ifdef _POSIX_PTHREAD_SEMANTICS 
#define fork1 fork
#endif

  if((cpid=fork1()) == 0)	        /* child */
    {       
      dup2( PipeEnds[PIP_RD], 0 ); 
      close( PipeEnds[PIP_WR] );	/* pipe input */
      if( usepath ) {
	execvp(argv[0], argv);
      }
      else {
	execv(argv[0], argv);
      }
      perror("Exec failed: ");
      exit(-1);
    }
  close( PipeEnds[PIP_RD] );
  
  write( PipeEnds[PIP_WR], msg, strlen(msg) );
  close( PipeEnds[PIP_WR] );

  wait(&status);
}


/*++++++++++++++++++++++++++++++++++++++
  
  Main function logging a message to a path. The formatted message
  parts and the message text itself are given separately to avoid
  wasting time to remake them, because the message text may be the
  same while the path formats are different.
  
  er_path_t *pathptr        pointer to the path structure where the message
                            should go.
  
  char *form                format part of the message
  
  char *msg                 payload part of the message
  ++++++++++++++++++++++++++++++++++++++*/
static
void
er_logtopath(er_path_t *pathptr, char *form, char *msg)
{ 
  
  char fullline[ER_MSGLEN+ER_ERRLEN+4];

  /* MUTEX :

     So, while the most of the work is done composing the message
     according to the format set in the path descriptor (mode),
     the output should also be locked.

     here the mutex associated with the path should be set.
     However, another mutex should be already used to protect other threads 
     from reading the path description while it is modified by the master
     thread. An RW lock can be used for this.
          
     Fortunately, fputs is MT-Safe in Solaris.
  */
 
  int fd;

  /* bound checking done already for form & msg */
  strcpy(fullline, form);
  strcat(fullline, msg);
  strcat(fullline, "\n");

  switch(pathptr->type) {
  case ER_PATH_SOCK:
    fd = pathptr->descr.sock.fd;
    /* XXX: write on a socket may write less than the requested number
            of bytes and not be an error - shane */
    if( write(fd, fullline, strlen(fullline)) == -1 ) {
      perror("ER logging ");
    }    
    break;
  case ER_PATH_NAME:
    {
      char *filename;
      char constructed[128], datestr[10];
      struct timeval tval;
      struct tm tmstr;

      if( pathptr->descr.name.date == 0 ) {
	filename = pathptr->descr.name.filename;
      }
      else {  
	/* construct the filename for the paths with DATE option */
	strcpy( constructed, pathptr->descr.name.filename );

	gettimeofday(&tval, NULL);
	localtime_r( & tval.tv_sec,   &tmstr);
	strftime(datestr, 10, ".%Y%m%d", &tmstr);

	strcat( constructed, datestr );
	filename = constructed;
      } 
      fd=open(filename, O_WRONLY|O_APPEND|O_CREAT, 0666 );
      if( fd > 0 ) {
	/* XXX lock ? According to SK, not needed as long as it's on one
	 machine - the 'append' mode will make sure things are not garbled.
         Note: Two months of operation and no a single garbled line yet ;) SK
	*/
	if(   write(fd, fullline, strlen(fullline)) == -1 ) {
	  perror("ER logging ");
	}    
	/* XXX unlock ? */
	close(fd);
      }
      else {
	fprintf(stderr, "ER: cannot open log file %s ",  
		pathptr->descr.name.filename);
	perror("");
      }
    }
    break;
    
  case ER_PATH_EXEC:
      er_forkexec(pathptr->descr.exec.argv, 
		  fullline, 
		  pathptr->descr.exec.usepath );
      break;
  default:
    die; /* not implemented */
  }   
}


/*++++++++++++++++++++++++++++++++++++++
  
  Internal message construction. Message parts are gathered according to the
  specified format bitmask. The resulting message consisting of those parts
  as well as the printf-style constructed message text itself is printed into 
  the given buffer.

  char *buf         pointer to the predefined buffer
  
  unsigned buflen   buffer length
  
  char *fmttxt      printf-style format 
  
  va_list args      printf-style arguments, if any

  ++++++++++++++++++++++++++++++++++++++*/
void
er_getmsg_parts(char *buf, unsigned buflen, char *fmttxt, va_list args)
{
  /* build the error message using vsnprintf */  
  vsnprintf(buf, buflen, fmttxt, args);
}



/* TWO CONSTANTS DEFINE THE LENGTH OF STRINGS HERE:
   ER_MSGLEN - max length of the line to be logged
   ER_ERRLEN - max length of the error message
*/
char *er_format_line(char *erbuf, er_fac_code_t  facwhere, 
		     er_mask_t asp, int mode,  int errcode, 
		     char *tmbuf)
{
int fac, err, sev;
int facidx, erridx;
char thr_str[10], *ermne, *txtlong="";

/* init to "" */
erbuf[0] = 0;
ermne = "";

sev = ( errcode & 0xff000000 );		/* not shifted */
fac = ( errcode & 0x00ff0000 ) >> 16;
err = ( errcode & 0x0000ffff );		/* not shifted */

/* take the overridden value (facwhere) in case of doubt */ 
 if(facwhere != fac) {
   fac = facwhere;
 }

  for (facidx=0; facidx<FAC_LAST; facidx++) {
    if( er_fac_err[facidx].code == fac ) {
      break;
    }
  }

  /* now, if we got to the last one and it's not the right one, 
     the system is not configured properly */
  if(facidx==FAC_LAST) {
     assert( er_fac_err[facidx].code == fac );	/* just bail out. */
  }

  /* still alive ? OK, build the message ...*/

  /* ... using facidx/erridx if it's not a DEBUG or INFO */
  switch( sev ) {
    case ER_SEV_D:
	ermne = "DEBUG";
	break;
    case ER_SEV_I:
	ermne = "INFO";
	break;
    default:
    /* OK, go to the module table. bail out if not initialized */
    assert( er_fac_err[facidx].errs != NULL );

    for(erridx=0; er_fac_err[facidx].errs[erridx].code != -1; erridx++) {
      if( er_fac_err[facidx].errs[erridx].code == errcode ) {
       	/* FOUND! now set the error message format using facidx and erridx */

    	/* long error message without arguments */
	txtlong = er_fac_err[facidx].errs[erridx].text;
    	
	/* set the mnemonic pointer if necessary */ 
	if( mode & ER_M_MNEMONIC ) {
	  ermne = er_fac_err[facidx].errs[erridx].mnem;
        }
	break;
      }
    }
    /*	return ""; */
    /* no, do not return: bail out if the code is not defined */
    assert( er_fac_err[facidx].errs[erridx].code != -1 );
  }

  
  
  sprintf(thr_str, "%lu", (long int)pthread_self() );

  /* build the actual log message */
  snprintf(erbuf, ER_MSGLEN, "%s %s-%s/%s %s-%s-%s %s ",
	   ( mode & ER_M_DATETIME ) ? tmbuf : "",
	   (mode & ER_M_PROGNAME) ? er_progname : "",
	   (mode & ER_M_PIDFULL)  ? er_pid : "",
	   (mode & ER_M_THR_ID )  ? thr_str : "",
	   (mode & ER_M_FACSYMB)  ? er_getfacsym(facwhere) : "",
	   er_getsevsym(sev, mode),
	   (mode & ER_M_MNEMONIC) ? ermne : "",
	   (mode & ER_M_TEXTLONG) ? txtlong : ""
	   );	
  return erbuf;
}


/*++++++++++++++++++++++++++++++++++++++
  
  Browses the list of available paths, runs the filter check on every
  path and logs the given message to all suitable paths.

  er_fac_code_t  facwhere     message's facility of call

  er_mask_t asp               message's aspect (if severity is INFO or DEBUG)

  int errcode                 message's error code

  char *msg                   text payload of the message
  ++++++++++++++++++++++++++++++++++++++*/
void er_logit(er_fac_code_t  facwhere, er_mask_t asp, int errcode, char *msg)
{
  char 	formbuf[ER_MSGLEN], tmbuf[32];
  struct timeval tval;
  struct tm tmstr;

  


  TH_acquire_read_lock( &er_paths_lock );
  {
    GList *pitem, *fitem;
      
    for( pitem = g_list_first(er_pathlist);
	 pitem != NULL;
	 pitem = g_list_next(pitem)) {
	
      er_path_t *pathptr = (er_path_t *)pitem->data;

	
      if( pathptr->active ) {

	for( fitem = g_list_first(pathptr->filters);
	     fitem != NULL;
	     fitem = g_list_next(fitem)) {
	  
	  er_filter_t *filtptr = (er_filter_t *) fitem->data;

	  
	  if( er_msgsel( filtptr, facwhere, asp, errcode) ) {
	    if ( pathptr->format & ER_M_DATETIME ) {
	      gettimeofday(&tval, NULL);
	      
	      localtime_r( & tval.tv_sec, & tmstr);

	      strftime(tmbuf, sizeof(tmbuf), "%Y%m%d %H:%M:%S", &tmstr);
	    } else {
	      tmbuf[0]=0;
	    }
	    
	    er_format_line( formbuf, 
			    facwhere, asp, pathptr->format, errcode, tmbuf);
	    
	    er_logtopath( pathptr, formbuf, msg );
	    break; /* go to next path */
	  }
	}
      }
    }
  } 
  TH_release_read_lock( &er_paths_lock );
}

/* check if anyone traces this particular aspect for this facility,
   whether on DEBUG or INFO level */
int ER_is_traced(er_fac_code_t  facwhere, er_mask_t asp) 
{
  int res;

  TH_acquire_read_lock( &er_paths_lock );
  res = (er_asparray[facwhere] & asp );
  TH_release_read_lock( &er_paths_lock );

  return res;
}


/* check if anyone traces this particular error for this facility.
   For the moment, hardcoded to "always true".
 */
int ER_is_errorlogged(er_fac_code_t  facwhere, int errcode)
{
int i = 1;

 return i;
}

int er_get_printmode(er_path_t *pathstruct) 
{
  return pathstruct->format;
}


/*++++++++++++++++++++++++++++++++++++++
  
  Entry point for predefined errors - this function will display a
  predefined message for this error along with additional text.

  er_fac_code_t facwhere code of facility from which the error is reported

  int errcode            error code

  char *format           printf-style format for additional text, or ""

  ...                    printf-style arguments, if any

  ++++++++++++++++++++++++++++++++++++++*/
void ER_perror(er_fac_code_t  facwhere, int errcode, char *format, ...)
{
  char 	erbuf[ER_MSGLEN];
  va_list ap;

  if( ER_is_errorlogged( facwhere, errcode ) ) {      /* uses pathlist mutex */

    /* now, this takes most time: */
    va_start(ap, format);
    er_getmsg_parts(erbuf, sizeof(erbuf), format, ap );
    va_end(ap);
    
    /* actually, here will be a loop once there are more paths possible. */
    er_logit(facwhere, 
	   0,			       /* empty aspect mask for errors */
	   errcode, 
	   erbuf);				/* empty debug message */
  }
}


/*++++++++++++++++++++++++++++++++++++++
  
  Internal function for message collection.

  er_fac_code_t facwhere code of facility from which the error is reported

  int sev                severity of the message

  er_mask_t asp          aspect of the message

  char *txt              printf-style format 

  va_list args           printf-style arguments, if any
  ++++++++++++++++++++++++++++++++++++++*/
static
void er_asp_va(er_fac_code_t  facwhere, int sev,  er_mask_t asp, char *txt, 
		va_list args)
{
    char    erbuf[ER_MSGLEN];

    er_getmsg_parts(erbuf, sizeof(erbuf), txt, args );
    er_logit(facwhere, asp, sev, erbuf);
}

/*++++++++++++++++++++++++++++++++++++++
  
  Entry point for informational messages - this function will display a
  message tagged with the INFO severity.

  er_fac_code_t facwhere code of facility from which the error is reported

  int sev                severity of the message

  er_mask_t asp          aspect of the message

  char *txt              printf-style format 

  ...                    printf-style arguments, if any
  ++++++++++++++++++++++++++++++++++++++*/
void ER_inf_va(er_fac_code_t  facwhere, er_mask_t asp, char *txt, ...)
{
    va_list   ap;
    va_start(ap, txt);
    er_asp_va( facwhere, ER_SEV_I, asp, txt, ap );
    va_end(ap);
}

/*++++++++++++++++++++++++++++++++++++++
  
  Entry point for debug/trace messages - this function will display a
  message tagged with the DEBUG severity.

  er_fac_code_t facwhere code of facility from which the error is reported

  int sev                severity of the message

  er_mask_t asp          aspect of the message

  char *txt              printf-style format 

  ...                    printf-style arguments, if any
  ++++++++++++++++++++++++++++++++++++++*/
void ER_dbg_va(er_fac_code_t  facwhere, er_mask_t asp, char *txt, ...)
{
  char    erbuf[ER_MSGLEN];
  va_list   ap;

  if( ER_is_traced( facwhere, asp ) ) {
    
    va_start(ap, txt);
    er_getmsg_parts(erbuf, sizeof(erbuf), txt, ap );
    va_end(ap);
    
    er_logit(facwhere, asp,  ER_SEV_D, erbuf);
  }
}


/*++++++++++++++++++++++++++++++++++++++
  
  Initialisation function - setting global variables.
  Can be done only by the master thread 

  char *progname    program name to be used in the log entries

  int processdefs   flag if the error definitions of the CA config file
                    should be processed.
  ++++++++++++++++++++++++++++++++++++++*/
void ER_init(char *progname, int processdefs)
{
  /* create the hash with hashing and equality testing functions
     specific for strings 
  */ 
  er_macro_hash = g_hash_table_new(g_str_hash, g_str_equal);
  
  TH_init_read_write_lock( &er_paths_lock ); /* created in open state */

  strncpy(er_progname, progname, 31);
  er_progname[31] = 0;

  snprintf(er_pid, 10, "%d", getpid());

  /* now error definitions: first predefine macros */
  ER_macro_predef();
  /* then override them */
  ER_proc_ca_macro();

  if( processdefs ) {
    /* now process the definitions if allowed */
    ER_proc_ca_err();
  }  
}
