/***************************************************************************** * * 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) * * */