Logo Search packages:      
Sourcecode: nagios-plugins version File versions

fetchlog.c

/*****************************************************************************
 *
 * fetchlog.c - logfile fetcher: pick up last new messages of a logfile
 *
 * Copyright (c) 2002 Alexander Haderer (alexander.haderer@charite.de)
 *
 *  Last Update:      $Author: afrika $
 *  Update Date:      $Date: 2002/12/17 18:40:05 $
 *  Source File:      $Source: /home/cvsroot/tools/fetchlog/fetchlog.c,v $
 *  CVS/RCS Revision: $Revision: 1.3 $
 *  Status:           $State: Exp $
 *
 *  CVS/RCS Log at end of file
 *
 * License:
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *****************************************************************************/


#include <stdio.h>      /* sprintf */
#include <stdlib.h>     /* atoi */
#include <ctype.h>      /* isalpha */
#include <string.h>     /* strcat, strcpy */
#include <fcntl.h>      /* open close */
#include <sys/types.h>  /* stat */
#include <sys/stat.h>   /* stat */
#include <sys/mman.h>   /* mmap, madvise */
#include <unistd.h>     /* access */
#include <errno.h>      /* errno */

/*************************************************
 * constants
 *************************************************/

#define LINELEN  160          /* max length of a logfile line we will read */

#define MIN_FIRSTCOL    1     /* Min first col for fetching */
#define MAX_LASTCOL     LINELEN /* Max last col for fetching      */
#define MIN_COLS  20    /* Min no of cols to fetch    */
#define MIN_FETCHLEN    50    /* Min length of fetched data */
#define MAX_FETCHLEN    2000  /* Max length of fetched data */

#define OK_MESSAGE      "OK: no messages"
#define ERR_MESSAGE     "ERROR: fetchlog: "

/* suffix for temp bookmarkfile:  mkstemp() template */
#define FETCH_FILE_SUFFIX "XXXXXX"  

/* conversion flags */
#define CONV_NONE 0
#define CONV_BRACKET    1
#define CONV_PERCENT    2
#define CONV_NEWLINE    4
#define CONV_OKMSG      8
#define CONV_SHELL      16
#define CONV_ALL  (CONV_BRACKET|CONV_PERCENT|CONV_NEWLINE|CONV_OKMSG|CONV_SHELL )

/* return/exit codes */
#ifdef PRE_093_EXIT_CODES
/* RET_ERROR and RET_NEWMSG pre 0.93 version                Nagios:*/
#define RET_OK          0     /* ok, no messages            ok */
#define RET_NEWMSG      1     /* logfile has new messages   warn */
#define RET_ERROR 2     /* internal error             critical */
#define RET_PARAM (-1)  /* wrong parameter, print help */
#else
/* 0.93 and later (Nagios compliant)                        Nagios:*/
#define RET_OK          0     /* ok, no messages            ok */
#define RET_ERROR 1     /* internal error             warn */
#define RET_NEWMSG      2     /* logfile has new messages   critical */
#define RET_PARAM 3     /* wrong parameter, print help */
#endif

/* version info */
#ifdef PRE_093_EXIT_CODES
#define FL_VERSION (FETCHLOG_VERSION_NO " (PRE_0.93_EXIT_CODES)")
#else
#define FL_VERSION FETCHLOG_VERSION_NO
#endif

/*************************************************
 * typedefs
 *************************************************/
typedef struct {
    long start;         /* pos first char of the very last line fetched */
    long last;          /* one behind the last char of the very last line f. */
    long linelen; /* len of stored line 'line' */
    char line[LINELEN]; /* the very last line prev. fetched */
    time_t mtime; /* mtime of logfile when this bookmark was valid */
} bookmark;

/*************************************************
 * prototypes
 *************************************************/
void usage( void );
int fetch_logfile( char *logfilename, char *bookmarkfile, int updbm_flag );
int read_bookmark( char *bmfile, bookmark *bm );
int write_bookmark( char *bmfile, bookmark *bm );
int copyline( int opos, char *obuf, char *ipt, int illen );
int check_farg( char *farg, int *conv );
void perr( char *msg1, char *msg2, int err );

/*************************************************
 * globals
 *************************************************/

/* fetching */
int firstcol_G = MIN_FIRSTCOL;
int lastcol_G = MAX_LASTCOL;
int fetchlen_G = MAX_FETCHLEN;
int conv_G = CONV_NONE;



/*************************************************
 * code...
 *************************************************/

/************************************************
 * perr( .. )
 * print error message to stdout with respect to fetchlen_G
 ************************************************
 */
void perr( char *msg1, char *msg2, int err ) {
    char *msg = NULL;
    int len = 0;

    if( !msg1 ) return;

    len = sizeof( ERR_MESSAGE ) + strlen( msg1 ) + 1; /* 1 == '\n' */
    if( msg2 ) len += strlen( msg2 ) + 2;       /* 2 == ': ' */
    if( err ) len += strlen( strerror( err ) ) + 2;   /* 2 == ': ' */

    if( (msg=malloc( len ) ) == NULL ) { exit( RET_ERROR ); }

    strcpy( msg, ERR_MESSAGE );
    strcat( msg, msg1 );
    if( msg2 ) {
      strcat( msg, ": " );
      strcat( msg, msg2 );
    }
    if( err ) {
      strcat( msg, ": " );
      strcat( msg, strerror( err ) );
    }
    if( len-1 <= fetchlen_G ) {
      strcat( msg, "\n" );
    }else{
      msg[fetchlen_G-1] = '\n';
      msg[fetchlen_G-2] = '~';
      len = fetchlen_G + 1;
    }
    write( STDOUT_FILENO, msg, len-1 );
    free( msg );
}

/************************************************
 * read_bookmark( char *bmfile, bookmark *bm );
 * return: 0: ok    1: error
 ************************************************
 */
int read_bookmark( char *bmfile, bookmark *bm ) {
    int fd = -1;
    struct stat sb;

    fd = open( bmfile, O_RDONLY );
    if( fd == -1 ) {
      if( errno == ENOENT ) {
          /* no bookmarkfile --> acts like infinite old bookmarkfile */
          bm->start = -1;
          bm->last = -1;
          bm->linelen = 0;
          bm->line[0] = '\0';
          bm->mtime = 0;
          return 0;
      }else{
          perr( "open", bmfile, errno );
          return 1;
      }
    }
    if( fstat( fd, &sb ) == -1 ) {
      perr( "stat", bmfile, errno );
      close( fd );
      return 1;
    }
    if( (int)sb.st_size != sizeof(bookmark) || 
      ((sb.st_mode & S_IFMT) != S_IFREG) ) 
    {
      perr( "no file/wrong size", bmfile, 0);
      close( fd );
      return 1;
    }
    if( read( fd, bm, sizeof(bookmark)) != sizeof( bookmark ) ) {
      perr( "file to short", bmfile, 0 );
      close( fd );
      return 1;
    }
    close( fd );
    
    if( bm->start < 0 || bm->last < 0 || 
      bm->linelen < 0 || bm->linelen > LINELEN ) 
    {
      perr( "file damaged", bmfile, 0 );
      return 1;
    }
    return 0;  /* ok */

}   /* read_bookmark() */


/************************************************
 * write_bookmark( char *bmfile, bookmark *bm );
 * return: 0: ok    1: error
 ************************************************
 */
int write_bookmark( char *bmfile, bookmark *bm ) {

    char *nbmfile = NULL; /* new bmfile (a tmp file to be renamed to bmfile) */
    int nbmfd = -1;

    nbmfile = (char*)malloc( strlen(bmfile) + sizeof(FETCH_FILE_SUFFIX) );
    if( nbmfile == NULL ) {
      perr("malloc", NULL, errno );
      return 1;
    }
    strcpy( nbmfile, bmfile );
    strcat( nbmfile, FETCH_FILE_SUFFIX );
    nbmfd = mkstemp( nbmfile );
    if( nbmfd == -1 ) {
      perr( "mkstemp", nbmfile, errno );
      free( nbmfile );
      return 1;
    }
    if( write( nbmfd, bm, sizeof( bookmark ) ) != sizeof( bookmark )  ) {
      perr( "write", nbmfile, errno );
      close( nbmfd );
      unlink( nbmfile );
      free( nbmfile );
      return 1;
    }
    close( nbmfd );
    if( rename( nbmfile, bmfile ) == -1 ) { 
      perr( "rename tmpfile to", bmfile, errno );
      unlink( nbmfile );
      free( nbmfile );
      return 1;
    }
    free( nbmfile );
    return 0;     /* ok */
    
}   /* write_bookmark() */



/************************************************
 * fetch_logfile( char *logfilename, char *bookmarkfile, int updbm_flag ) 
 ************************************************
 */
int fetch_logfile( char *logfile, char *bmfile, int updbm_flag ) {

    bookmark obm, nbm;        /* old, new bookmark */

    char *ibuf = NULL;        /* input buf (--> the logfile) */
    size_t ilen = 0;
    char *ipt  = NULL;
    char *bmpt = NULL;        /* points to first new char if bm exists */

    char *obuf = NULL;        /* output buf (filled backwards) */
    int opos;                 /* first used char pos in obuf */

    int i;
    int fd = -1;
    struct stat sb;
    char *lgfile = NULL;
    int llen = 0;
    int done = 0;
    int logfiletouch = 0;     /* 1: logfile only touched, nothing appended */

    memset( obm.line, 0, LINELEN );
    memset( nbm.line, 0, LINELEN );
    nbm.start = -1;
    nbm.last = -1;
    nbm.linelen = 0;
    nbm.mtime = 0;

    if( read_bookmark( bmfile, &obm ) ) return RET_ERROR;

    if( (fd=open( logfile, O_RDONLY ))  == -1 ) {
      perr( "open", logfile, errno );
      return RET_ERROR;
    }
    if( fstat( fd, &sb ) == -1 ) {
      perr( "stat", logfile, errno );
      close( fd );
      return RET_ERROR;
    }
    if( obm.mtime == sb.st_mtime ) {
      if( conv_G & CONV_OKMSG ) 
          write( STDOUT_FILENO, OK_MESSAGE "\n", sizeof( OK_MESSAGE ) );
      close( fd );
      return RET_OK;
    }
    nbm.mtime = sb.st_mtime;

    /*****************/

    obuf = malloc( fetchlen_G+1 );
    if( obuf == NULL ) {
      perr( "malloc", NULL,  errno );
      close( fd );
      return RET_ERROR;
    }
    opos = fetchlen_G;
    *(obuf + opos) = '\0';    /* dummy: opos -> first used char in obuf */
    if( conv_G & CONV_NEWLINE ) {
      /* when using CONV_NEWLINE the obuf is filled up like this:
           1. init: write '\\'  'n'  '\n' to the end (3 chars)
           2. fill (copyline() ) by first prepend line contents and 
            then prepend '\\' 'n'
           result: An additional '\\'  'n'  at the beginning 
         else
           1. fill (copyline() ) by first prepend newline and then prepend 
            line contents 
      */
      *(obuf + --opos) = '\n';
      *(obuf + --opos) = 'n';
      *(obuf + --opos) = '\\';
    }

    lgfile = (char*)malloc( strlen(logfile) + sizeof(".X") );
    if( lgfile == NULL ) {
      free( obuf );
      perr( "malloc", NULL, errno );
      close( fd );
      return RET_ERROR;
    }

    /* read in all logfiles and backward fill obuf upto fetchlen_G chars */

    for( i=-1; i<10; i++ ) {
      /* i==-1: logfile without suffix, else i==logfile suffix */
      if( i==-1 ) {
          /* lgfile is already open and sb contains the stat */
          strcpy( lgfile, logfile );
      }else{
          sprintf( lgfile, "%s.%1d", logfile, i );
      
          if( (fd=open( lgfile, O_RDONLY )) == -1 ) {
            if( errno==ENOENT && i>-1 ) { break; }
            else{
                perr( "open", lgfile, errno );
                free( obuf ); free( lgfile );
                return RET_ERROR;
            }
          }
          if( fstat( fd, &sb ) == -1 ) {
            perr( "stat", lgfile, errno );
            free( obuf ); free( lgfile ); close( fd );
            return RET_ERROR;
          }
      }
      
      ilen = (size_t) sb.st_size;
      if( ilen == 0 ) {
          close( fd );
          continue;
      }
      ibuf = mmap( NULL, ilen, PROT_READ, MAP_SHARED, fd, (off_t)0 );
      if( ibuf == MAP_FAILED ) {
          perr( "mmap", lgfile, errno );
          free( obuf ); free( lgfile ); close( fd );
          return RET_ERROR;
      }
      
#ifndef NO_MADVISE
      if( madvise( ibuf, ilen, MADV_RANDOM ) ) {
          perr( "madvise", NULL, errno );
          free( obuf ); free( lgfile ); close( fd );
          munmap( ibuf, ilen );
          return RET_ERROR;
      }
#endif
      
      /* setup data for new bookmark */
      if( nbm.start == -1 ) {
          for( ipt=ibuf+ilen-2; ipt>=ibuf; ipt-- ) {
            if( *ipt=='\n' ) break;
          }
          nbm.last = ilen;
          nbm.start = ipt-ibuf+1;
          nbm.linelen = ibuf+ilen-(ipt+1);
          if( nbm.linelen > LINELEN ) nbm.linelen = LINELEN;
          memcpy( nbm.line, ipt+1, nbm.linelen );
      }

      /* check for old bookmark */
      bmpt = NULL;
      if( obm.mtime && obm.start+obm.linelen<=ilen && 
          !memcmp( obm.line, ibuf+obm.start, obm.linelen ) ) 
      {
          /* stop if 1st logfile modified but nothing append since last bm */
          if( i==-1 && obm.last==ilen ) { 
            logfiletouch = 1;
            munmap( ibuf, ilen );
            close( fd );
            break;
          }else{
            bmpt = ibuf+obm.last;
          }
      }

      /* scan backwards for lines but the first */
      done = 0;
      for( llen=1,ipt=ibuf+ilen-2; ipt>=ibuf; llen++,ipt-- ) {
          if( *ipt=='\n' ) {
            if( ipt+1<bmpt ) { done=1; break; }
            opos = copyline( opos, obuf, ipt+1, llen );
            if( opos==0 ) { done=1; break; }
            llen = 0;
          }
      }
      if( ipt+1==ibuf && done==0 ) {      /* copy first line ? */
          if( ipt+1<bmpt ) { done=1; }
          else{ opos = copyline( opos, obuf, ipt+1, llen ); }
          if( opos==0 ) { done=1; }
      }

      munmap( ibuf, ilen );
      close( fd );
      if( done ) break;
    }

    if( updbm_flag && nbm.start != -1 ) {
      if( write_bookmark( bmfile, &nbm ) ) return RET_ERROR;
    }

    if( logfiletouch ) {
      if( conv_G & CONV_OKMSG ) 
          write( STDOUT_FILENO, OK_MESSAGE "\n", sizeof( OK_MESSAGE ) );
      free( obuf ); free( lgfile );
      return RET_OK;
    }else{
      write( STDOUT_FILENO, obuf+opos,fetchlen_G-opos);
    }

    free( obuf ); free( lgfile );
    return RET_NEWMSG;

}   /* fetch_logfile() */


/************************************************
 * copyline( int opos, char *obuf, char *ipt, int illen );
 * trim new line (ipt, illen), copy it to obuf, convert it, return new opos
 ************************************************
 */
int copyline( int opos, char *obuf, char *ipt, int illen ) {

    char *p = NULL;
    int len = 0;
    int i;

    if( conv_G & CONV_NEWLINE ) {
      /* fill obuf: 
         prepend  concat( '\\' + 'n' + iline )   (newline sequence first) */
      if( opos > 2 ) {
          if( illen <= firstcol_G ) {
            *(obuf+opos-1) = 'n'; *(obuf+opos-2) = '\\'; 
            opos -= 2;
          }else{
            len = illen - 1;  /* -1 : skip newline */
            /* Note: lastcol_G means 'show inklusive column lastcol' */
            if( len > lastcol_G+1 )  len = lastcol_G;
            len -= firstcol_G - 1;
            if( len+2 > opos ) {
                memcpy( obuf+2, ipt+firstcol_G-1+len+2-opos, opos-2 );
                len = opos - 2;
            }else{
                memcpy( obuf+opos-len, ipt+firstcol_G-1, len );
            }
            if( illen-1 > lastcol_G+1 ) *(obuf+opos-1) = '~';
            opos -= len+2;
            *(obuf+opos+1) = 'n'; 
            *(obuf+opos+0) = '\\'; 
          }
      }else{
          opos = 0;
      }
      if( opos==0 ) {
          p = obuf;
          *p++='\\'; *p++='n'; *p++='.'; *p++='.'; *p++='.';
          if( obuf[5]=='n' ) { *p++='.'; }
      }
    }else{
      /* without newline conversion */

      /* fill obuf: 
         prepend concat( iline + '\n' )  (newline char last) */
      if( opos > 1 ) {
          if( illen <= firstcol_G ) {
            *(obuf+opos-1) = '\n'; 
            opos -= 1;
          }else{
            len = illen - 1;  /* -1 : skip newline */
            /* Note: lastcol_G means 'show inklusive column lastcol' */
            if( len > lastcol_G+1 )  len = lastcol_G;
            len -= firstcol_G - 1;
            if( len+1 > opos ) {
                memcpy( obuf, ipt+firstcol_G-1+len+1-opos, opos-1 );
                len = opos - 1;
            }else{
                memcpy( obuf+opos-len-1, ipt+firstcol_G-1, len );
            }
            *(obuf+opos-1) = '\n'; 
            if( illen-1 > lastcol_G+1 ) *(obuf+opos-2) = '~';
            opos -= len+1;
          }
      }else{
          opos = 0;
      }
      if( opos==0 ) {
          p = obuf;
          *p++='.'; *p++='.'; *p++='.';
      }
    }

    p = obuf+opos;
    if( conv_G & CONV_NEWLINE )  p += 2;  /* +2: skip '\\' 'n' */

    if( conv_G & CONV_PERCENT ) {
      for( i=0; i<len; i++ ) {
          if( *(p+i) == '%' ) *(p+i) = 'p';
      }
    }

    if( conv_G & CONV_BRACKET ) {
      for( i=0; i<len; i++ ) {
          if(      *(p+i) == '<' ) *(p+i) = '(';
          else if( *(p+i) == '>' ) *(p+i) = ')';
      }
    }

    if( conv_G & CONV_SHELL ) {
      for( i=0; i<len; i++ ) {
          if(      *(p+i) == '$' )  *(p+i) = '_';
          else if( *(p+i) == '\'' ) *(p+i) = '_';
          else if( *(p+i) == '\"' ) *(p+i) = '_';
          else if( *(p+i) == '`' )  *(p+i) = '_';
          else if( *(p+i) == '^' )  *(p+i) = '_';
          else if( *(p+i) == '\\' ) *(p+i) = '/';
      }
    }

    return opos;

}   /* copyline() */


/************************************************
 * check_farg( char *farg ) 
 * check if -f arg is in proper format for sccanf(): nnn:nnn:nnnn:XXXXX
 ************************************************
 */
int check_farg( char *farg, int *conv ) {
    char *pt = farg;
    int numdig = 0;
    int numc = 0;

    *conv = 0;

    for( numdig=0 ; *pt; pt++ ) {
      if( isdigit( (int) *pt ) ) numdig++;
      else if( *pt==':' ) break;
      else return 1;
    }
    if( numdig <1 || numdig > 3 ) return 1;

    for( pt++,numdig=0 ; *pt; pt++ ) {
      if( isdigit( (int) *pt ) ) numdig++;
      else if( *pt==':' ) break;
      else return 1;
    }
    if( numdig <1 || numdig > 3 ) return 1;

    for( pt++,numdig=0 ; *pt; pt++ ) {
      if( isdigit( (int) *pt ) ) numdig++;
      else if( *pt==':' ) break;
      else return 1;
    }
    if( numdig <1 || numdig > 4 ) return 1;

    for( pt++,numc=0 ; *pt; pt++ ) {
      if     ( *pt=='b' ) { *conv |= CONV_BRACKET; numc++; }
      else if( *pt=='p' ) { *conv |= CONV_PERCENT; numc++; }
      else if( *pt=='n' ) { *conv |= CONV_NEWLINE; numc++; }
      else if( *pt=='o' ) { *conv |= CONV_OKMSG;   numc++; }
      else if( *pt=='s' ) { *conv |= CONV_SHELL;   numc++; }
      else return 1;
    }
    if( numc > 5 ) return 1;
    return 0;     /* ok */

}   /* check_farg() */

/*************************************************/
void usage( void ) {

    printf(
      "fetchlog - fetch the last new log messages - version %s \n\n"
      "   usage 1: fetchlog -f firstcol:lastcol:len:conv logfile bmfile\n"
      "   usage 2: fetchlog -F firstcol:lastcol:len:conv logfile bmfile\n"
      "   usage 3: fetchlog [ -V | -h ] \n\n"
      "1: Read all messages of <logfile> newer than the "
      "bookmark in <bmfile>. Print\n"
      "at most <len> bytes from column "
      "<firstcol> upto column <lastcol> of last new \nmessages to stdout. "
      "Adds '...' characters when skipping old lines or '~' when\n"
      "cutting long lines. <conv> sets output conversion:\n"
      "      'b': convert < and > to ( and ) for safe HTML output\n"
      "      'p': convert %% to p for safe printf output\n"
      "      's': convert $'\"`^\\ to _____/ for safe shell parameter input\n"
      "      'n': convert newline to \\n for single line output\n"
      "      'o': show '%s' message if no new messages\n"
      "  0  <  <firstcol>  <  <lastcol>  <  %d\n"
      "  <lastcol> - <firstcol> > %d \n"
      "  <len> valid range %d..%d\n"
      "  <conv> is zero ore more of 'bpsno' \n"
      "  <logfile> absolute path to logfile\n"
      "  <bmfile> absolute path to bookmarkfile\n"
      "2: like 1 and update <bmfile> to mark fetched messages as 'read'\n"
      "3: print version (-V) or print this help message (-h) \n"
      , FL_VERSION, 
      OK_MESSAGE,MAX_LASTCOL+1,MIN_COLS-1,MIN_FETCHLEN, MAX_FETCHLEN
      );

} /* usage() */

/*************************************************/

int main(int argc, char **argv)
{
    int  ret=0;

    /* check args */
    if (argc == 2) {
      if( argv[1][0] == '-' && argv[1][1] == 'V' ) {
          printf( "fetchlog version %s \n", FL_VERSION );
          exit( RET_PARAM );
      }
    }else if (argc == 5) {
      if( argv[1][0] == '-' && 
          argv[3][0] == '/' && isalpha((int)argv[3][1]) &&
          argv[4][0] == '/' && isalpha((int)argv[4][1]) ) {
          if( argv[1][1] == 'f' || argv[1][1] == 'F' ) {
            if( check_farg( argv[2], &conv_G ) ) {
                usage();
                exit( RET_PARAM );
            }
            ret = sscanf( argv[2], "%3d:%3d:%4d", 
                        &firstcol_G, &lastcol_G, &fetchlen_G );
            if( ret != 3 ) {
                usage();
                exit( RET_PARAM );
            }
            if( firstcol_G >= MIN_FIRSTCOL &&
                lastcol_G >= firstcol_G + MIN_COLS &&
                lastcol_G <= MAX_LASTCOL &&
                fetchlen_G >= MIN_FETCHLEN &&
                fetchlen_G <= MAX_FETCHLEN ) {
                
                if( argv[1][1] == 'f' ) {
                  exit( fetch_logfile( argv[3], argv[4], 0 ) );
                }else{
                  exit( fetch_logfile( argv[3], argv[4], 1 ) );
                }
            }
          }
      }
    }
    usage();
    exit( RET_PARAM );

}   /* main() */

/*
 * CVS/RCS Log:
 * $Log: fetchlog.c,v $
 * Revision 1.3  2002/12/17 18:40:05  afrika
 * exit code now nagios compatible
 *
 * Revision 1.2  2002/12/17 18:04:48  afrika
 * - inserted CVS tags
 * - change docs: Netsaint --> Nagios(TM)
 *
 *
 */
 

Generated by  Doxygen 1.6.0   Back to index