[comp.unix.ultrix] My error-log printing program

cks@white.toronto.edu (Chris Siebenmann) (07/31/89)

 Some cautions: this was written on a 2.2 system. It should work on a
3.x system (as long as DEC hasn't changed the format of the error log
file), and maybe even a DECStation 3100. It only prints out the
contents of ASCII messages; other types just have their types printed
out.

 Extensions to handle more types (in one-line messages) or a manual
page are welcome; please send them to me and I'll make up a new
release. The current "documentation" for the program is at the top of
the source code.

[Don't forget to zap my signature at the end!]
#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# If this archive is complete, you will see the following message at the end:
#		"End of shell archive."
#
# Contents:
#   ereport.c Makefile
#
# Wrapped by cks@snow.white on Sun Jul 30 21:49:59 1989
#
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f ereport.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"ereport.c\"
else
echo shar: Extracting \"ereport.c\" \(10225 characters\)
sed "s/^X//" >ereport.c <<'END_OF_ereport.c'
X/*
X * Report errors in an intelligable shorthand fashion, unlike <CENSORED>
X * uerf.
X *
X * Options:
X * 	-f <file>	- pick a different file than
X * 			  /usr/adm/syserr/syserr.<hostname>
X * 	-F		- go forward, not in reverse
X * 	-h		- never print host name
X * 	-H		- don't pring host name if it hasn't changed since
X * 			  the last record.
X *	-t <secs>	- times this close to each other are considered
X *			  the same (defaults to 0).
X *
X * That's it. The report has date, host, and the ascii message;
X * things without ascii messages have their type logged. The output
X * format was chosen over several iterations to look good. If neither the
X * time nor the hostname has changed, no line with date + host are
X * printed, just the ascii message.
X *
X * By: Chris Siebenmann (cks@white.toronto.edu), modeled vague
X * after what uerf -R -o terse gives you.
X *
X *  Last edited: Fri Jul 28 00:24:30 1989 by cks (Chris Siebenmann) on sneezy.white
X */
X
X#include	<stdio.h>
X#include	<time.h>
X#include	<sys/types.h>
X#include	<sys/errlog.h>
X#include	<string.h>
X
Xextern void	perror(), exit();
Xextern int	fprintf(), printf(), getopt(), fread(), fseek(), fclose();
Xextern int	gethostname(), atoi();
X
X/*
X * Error records are highly peculiar. An error record is composed of four
X * parts:
X *
X * 	- a struct el_rhdr
X * 	- a struct el_sub_id
X * 	- a per-type information structure (note that these can sometimes
X * 	  exceed the normal size limits on their structures; in
X * 	  particular, the ascii messages from startup can be larger than
X * 	  256 bytes).
X * 	- a four-byte unique (well, hopefully unique) end-of-record
X * 	  marker, namely "%~<^".
X */
Xtypedef struct {
X	struct el_rhdr		elHdr;		/* how long, etc, this is. */
X	struct el_sub_id	elSubId;	/* type information. */
X} errorHeader;
X
Xextern char	*optarg;
X
Xint		printHname = 1;		/* print host name. */
Xint		allwaysHname = 1;	/* always print host name,
X					   even if it doesn't change. */
Xint		goForward = 0;		/* Go forward instead of backwards
X					   through logs. */
X
Xint		timeDelta = 0;		/* If two times are this close
X					   together, don't print a new
X					   time note. */
X#define	ABS(x)	((x) > 0 ? (x) : -(x))
X
X
X#define	BIGNAMELEN	256
X
Xvoid	findBackwards(), dumpRec();
X
Xvoid
Xmain(argc, argv)
Xint	argc;
Xchar	**argv;
X{
X	char		initial_fname[BIGNAMELEN], namebuf[BIGNAMELEN];
X	char		*filename;
X	FILE		*fp;
X	long		curpos;
X	int		c, errflag = 0;
X	errorHeader	elHead;
X	
X	/*
X	 * We set filename initially..
X	 */
X	if (gethostname(namebuf, BIGNAMELEN) < 0) {
X		perror("gethostname");
X		exit(1);
X	}
X	(void) strcat(strcpy(initial_fname, "/usr/adm/syserr/syserr."),
X		      namebuf);
X	filename = initial_fname;
X	
X	while ((c = getopt(argc, argv, "f:hHFt:")) != EOF) {
X		switch (c) {
X		    case 't':
X			timeDelta = atoi(optarg);
X			break;
X		    case 'f':
X			filename = optarg;
X			break;
X		    case 'h':
X			printHname = 0;
X			break;
X		    case 'H':
X			allwaysHname = 0;
X			break;
X		    case 'F':
X			goForward = 1;
X			break;
X		    default:
X			errflag ++;
X			break;
X		}
X	}
X
X	if (errflag) {
X		(void) fprintf(stderr, "usage: %s [hHF] [-f filename]\n",
X			       argv[0]);
X		exit(1);
X	}
X
X	/*
X	 * Open the file, and determine the initial starting position.
X	 */
X	fp = fopen(filename, "r");
X	if (fp == NULL) {
X		perror(filename);
X		exit(1);
X	}
X
X	if (goForward) { 
X		/*
X		 * Proceed forward through the file, dumping interesting
X		 * information.
X		 */
X		curpos = 0;
X		while (!ferror(fp) && !feof(fp)) {
X			if (fread((char *) &elHead, sizeof(elHead),
X				  1, fp) != 0) {
X				curpos += elHead.elHdr.rhdr_reclen;
X				dumpRec(&elHead, fp);
X				if (fseek(fp, curpos, 0) != 0) {
X					perror("fseek");
X					exit(1);
X				}
X			}
X		}
X		if (ferror(fp)) {
X			perror("reading file");
X			exit(1);
X		}
X	}
X	else {
X		/*
X		 * Going backwards is somewhat more complicated. We start
X		 * at the end, and look backwards for the previous
X		 * record's trailer markers, and read forward from there.
X		 */
X		if (fseek(fp, -5L, 2) != 0) {	/* jump to character before the
X						   last record's trailer. */
X			perror("initial backseek");
X			exit(1);
X		}
X		while (!ferror(fp) && ftell(fp) > 0) {
X			findBackwards(fp);
X			curpos = ftell(fp);
X			if (fread((char *) &elHead, sizeof(elHead),
X				  1, fp) == 0 && ferror(fp)) {
X				perror("reading file");
X				exit(1);
X			}
X			dumpRec(&elHead, fp);
X			(void) fseek(fp, curpos-4, 0);
X		}
X	}
X		
X	/*
X	 * Clean up and exit.
X	 */
X	(void) fclose(fp);
X}
X
X/*
X * Go from error file type to an ascii label for that type.
X */
X/* 100-1xx error labels. */
Xchar *errors100[] = {
X	"Machine check", "Memory crd/rds", "Disk error", "Tape error",
X	"Device controller error", "Adapter error", "Bus error",
X	"Stray interrupt", "Async write error", "Panic exception/fault",
X	"8800 emm exception", "console timeout entry", "stack dump",
X	"ka650 error & status regs",
X};
X
Xchar *
XerrorName(code)
Xu_short	code;
X{
X	if (code >= 100 && code <= 113)	/* blech. magic numbers. */
X		return errors100[code - 100];
X
X	switch (code) {
X	    case ELSW_PNC:
X		return "panic (bug check)";
X	    case ELMSGT_INFO:
X		return "ascii message";
X	    case ELMSGT_SNAP8600:
X		return "8600 snapshot taken";
X	    case ELMSGT_SU:
X		return "Startup message";
X	    case ELMSGT_SD:
X		return "Shutdown message";
X	    case ELMSGT_TIM:
X		return "Time stamp";
X	    default:
X		return "Unknown code";
X	}
X	/*NOTREACHED*/
X}
X
X/*
X * Print a possibly multi-line message with each line indented.
X */
Xvoid
XprintMultiLine(str)
Xregister char	*str;
X{
X	register char	*curChar, *lineStart;
X	int		first;
X	
X	for (curChar = lineStart = str, first = 1; *curChar != 0;
X	     curChar ++) {
X		if (*curChar == '\n' ) {
X			*curChar = 0;
X			if (strlen(lineStart) > 0 || !first) {
X				(void) printf("\t%s\n", lineStart);
X				first = 0;
X			}
X			lineStart = curChar + 1;
X		}
X	}
X	if (strlen(lineStart) > 0)
X		(void) printf("\t%s\n", lineStart);
X}
X
X/*
X * Dump out an individual error record. When this routine is called, the
X * file is positioned right at the per-type record; individual bits are
X * responsible for reading in anything necessary. Works hard (in a
X * complicated way) to only print time and hostname when they change.
X */
Xvoid
XdumpRec(erpt, fp)
XerrorHeader	*erpt;
XFILE		*fp;
X{
X	char		tstring[30];
X	short		msg_len;	/* length */
X	char		msg_asc[8096];	/* a very large buffer. */
X	struct el_pnc	panicRec;
X	char		*msg_start;
X	/* used to handle complicated conditions for printing stuff. */
X	int		printTime, printHostname;
X	static char	hname[13];
X	static time_t	last_time;
X	long		rtime, delta;
X
X	/*
X	 * Get the time it happened in ASCII.
X	 */
X	rtime = erpt->elHdr.rhdr_time;
X	(void) strcpy(tstring, ctime(&rtime));
X	tstring[24] = 0;	/* ctime()'s format is guaranteed */
X
X	/*
X	 * Print the leading host & time indication
X	 */
X	delta = last_time - erpt->elHdr.rhdr_time;
X	if (ABS(delta) > timeDelta) {
X		last_time = erpt->elHdr.rhdr_time;
X		printTime = 1;
X	}
X	else
X		printTime = 0;
X
X	if (printHname && strncmp(hname, erpt->elHdr.rhdr_hname, 12) != 0)
X		printHostname = 1;
X	else
X		printHostname = 0;
X
X	/* time is printed if either time or hostname change. */
X	if (printTime || printHostname)
X		(void) printf("%s", tstring);
X	/* hostname is only printed if it changes. */
X	if (printHostname || (printTime && allwaysHname && printHname)) {
X		(void) strncpy(hname, erpt->elHdr.rhdr_hname, 12);
X		hname[12] = 0;
X		(void) printf(" (%s)", hname);
X		printHostname = 1;
X	}
X	/* if either time or hostname was printed, print a CR to end them. */
X	if (printTime || printHostname)
X		(void) printf("\n");
X
X	/*
X	 * Now, check the type...
X	 */
X	switch (erpt->elSubId.subid_class) {
X	    case ELMSGT_INFO:
X	    case ELMSGT_SU:
X	    case ELMSGT_SD:
X		if (fread((char *) &msg_len, sizeof(msg_len), 1, fp) == 0) {
X			perror("reading length");
X			exit(1);
X		}
X		if (erpt->elSubId.subid_class == ELMSGT_SD) {
X			(void) strcpy(msg_asc, "shutdown: ");
X			msg_start = strlen(msg_asc) + msg_asc;
X		}
X		else
X			msg_start = msg_asc;
X
X		if (fread(msg_start, msg_len * sizeof(char), 1, fp) == 0) {
X			perror("reading ascii");
X			exit(1);
X		}
X		printMultiLine(msg_asc);
X		break;
X
X	    case ELSW_PNC:
X		if (fread((char *) &panicRec, sizeof(panicRec), 1, fp) == 0) {
X			perror("reading panic");
X			exit(1);
X		}
X		(void) strcpy(msg_asc, "panic: ");
X		(void) strcat(msg_asc, panicRec.pnc_asc);
X		printMultiLine(msg_asc);
X		break;
X		
X	    default:
X		(void) strcpy(msg_asc, "binary record: ");
X		(void) strcat(msg_asc, errorName(erpt->elSubId.subid_class));
X		printMultiLine(msg_asc);
X		break;
X	}
X}
X
X/*
X * Seek backwards in the file (starting just after a trailer) for a
X * trailer, and if found, leave the file positioned just after it. For
X * efficienty, reads in a large buffer into memory and manually searches
X * backwards through it.
X */
Xchar	*trailerVar = trailer;
X#define	trailerComp(var)	((var)[0] == trailerVar[0] && \
X				 (var)[1] == trailerVar[1] && \
X				 (var)[2] == trailerVar[2] && \
X				 (var)[3] == trailerVar[3])
X
X#define	BIG_BUFFER	2048
Xvoid
XfindBackwards(fp)
XFILE	*fp;
X{
X	char		buffer[BIG_BUFFER];
X	register char	*bufferP;
X	long		cur_pos, new_delta;
X	int		buffer_size;
X
X	/* Load a large buffer in. */
X	cur_pos = ftell(fp);
X	if (cur_pos > BIG_BUFFER) {
X		if (fseek(fp, (long) -BIG_BUFFER, 1) != 0) {
X			perror("fseek");
X			exit(1);
X		}
X		buffer_size = BIG_BUFFER;
X	}
X	else {
X		/* Not a full buffer left. */
X		buffer_size = cur_pos;
X		if (cur_pos == 0) {
X			(void) fprintf(stderr, "not supposed to happen!\n");
X			exit(1);
X		}
X		if (fseek(fp, 0L, 0) != 0) {
X			perror("fseek");
X			exit(1);
X		}
X	}
X	if (fread(buffer, buffer_size * sizeof(char), 1, fp) == 0 &&
X	    ferror(fp)) {
X		perror("readin");
X		exit(1);
X	}
X
X	/* Scan backwards through the buffer looking for the trailer. */
X	for (bufferP = buffer + buffer_size - 4 - 1;
X	     bufferP > buffer;
X	     bufferP --) {
X		if (trailerComp(bufferP)) {
X			/* Found a match. We must seek to the match
X			   position plus four. */
X			new_delta = buffer_size - (bufferP - buffer) - 4;
X			if (fseek(fp, -new_delta, 1) != 0) {
X				perror("fseek");
X				exit(1);
X			}
X			return;
X		}
X	}
X
X	/* Odd, definetly odd. We have a really long record here. We must
X	   seek backwards to its start, and try again. */
X	if (fseek(fp, (long) -buffer_size, 1) != 0) {
X		perror("fseek");
X		exit(1);
X	}
X	if (ftell(fp) == 0)
X		return;
X	findBackwards(fp);
X}
END_OF_ereport.c
if test 10225 -ne `wc -c <ereport.c`; then
    echo shar: \"ereport.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f Makefile -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"Makefile\"
else
echo shar: Extracting \"Makefile\" \(147 characters\)
sed "s/^X//" >Makefile <<'END_OF_Makefile'
XCC = gcc
XCFLAGS = -O -Wall
X
Xereport: ereport.c
X	$(CC) $(CFLAGS) -o ereport ereport.c
X
Xlint:
X	lint -bh ereport.c
X
Xclean:
X
Xrealclean:
X	rm -f ereport
END_OF_Makefile
if test 147 -ne `wc -c <Makefile`; then
    echo shar: \"Makefile\" unpacked with wrong size!
fi
# end of overwriting check
fi
echo shar: End of shell archive.
exit 0
-- 
	"I shall clasp my hands together and bow to the corners of the world."
			Number Ten Ox, "Bridge of Birds"
Chris Siebenmann		...!utgpu!{ncrcan,ontmoh!moore}!ziebmef!cks
cks@white.toronto.edu	     or ...!utgpu!{,csri!}cks