/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 1998-2009. All Rights Reserved.
*
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* %CopyrightEnd%
*/
/*
* Module: elog_main
* Purpouse: Main program and logic in the nteventlog program
* which waits for logging events and sends them to erlang.
* At startup, the registry keys for the identifier given on the
* command line are read to see what log record was the last
* displayed for this identifier. All "new" records are then
* displayed. After that the program waits for new events to arrive
* and displays them (sends them to erlang). If stdin is a pipe
* (not a console) an ACK is requested for every entry displayed.
* when the ACK is received, the registry entries for the identifier
* is updated, so that next time the program starts, only the new
* entries are displayed.
*/
#include "elog_global.h"
#include "elog_util.h"
#include "elog_pipe_stdin.h"
#include "elog_format.h"
#include "elog_registry.h"
/*
* A race condition in the event log notification and the
* event log reading results in us having to retry reading the
* eventlog after some time. One second seems to do it...
*/
#define RETRY_TIMEOUT 1000
/*
* Constants for the logging formats.
*/
#define LOG_FORMAT "%s [%s] %s, %s: %s\n"
#define PIPE_LOG_FORMAT "%dH%s%dH%s%dH%s%dH%s%dH%s"
#define PIPE_LOG_ACK "A"
#define PIPE_LOG_EXTRA (5*10) /* 5 int */
#define ACK_MAX 100
#define TIME_FORMAT "%d-%b-%Y %H:%M:%S GMT"
/* 2 3 4 2 2 2 */
#define TIME_BUFSIZ 25
#define CANT_FORMAT_MESSAGE "[EventLogReader unable to format message text]"
/* the default registry identification */
#define DEFAULT_IDENT "DefaultIdent"
/*#define HARDDEBUG*/
/* Flag set if eventlog is purged and need reopening.*/
static int reopen_event_log = 0;
/*
* Calculates the needed buffersize for a record in the eventlog.
* if recordnum == 0, looks at next record, otherwise looks
* at the record with the given number.
*/
static DWORD needed(HANDLE elog, DWORD recordnum){
EVENTLOGRECORD dummy;
DWORD red, need;
DWORD last_error;
DWORD flags =
EVENTLOG_FORWARDS_READ |
((recordnum) ? EVENTLOG_SEEK_READ : EVENTLOG_SEQUENTIAL_READ);
#ifdef HARDDEBUG
fprintf(stderr,"Calculating need when recordnum = %lu\n",recordnum);
#endif
if(!ReadEventLog(elog,
flags,
recordnum,
&dummy,
(DWORD) sizeof(EVENTLOGRECORD),
&red,
&need) &&
(last_error = GetLastError()) == ERROR_INSUFFICIENT_BUFFER){
return need;
} else if(last_error == ERROR_EVENTLOG_FILE_CHANGED){
reopen_event_log = 1;
return (DWORD) 0;
} else {
#ifdef HARDDEBUG
output_error(last_error,"needed() failed to read eventlog");
#endif
return (DWORD) 0;
}
}
/*
* Checks (any type of) stdin for end of file.
* Expects data present on stdin.
*/
BOOL eof_on_stdin(void){
HANDLE in = GetStdHandle(STD_INPUT_HANDLE);
char x[1];
DWORD y;
INPUT_RECORD input;
DWORD red;
static int state = 1; /* Return pressed = 1, ^Z after that = 2 */
if(!console_stdin()){
return peek_pipe_stdin_eof();
}
/* Console input, may be just about every type of event, look for
^Z pressed... */
if(!ReadConsoleInput(in,&input,1,&red))
return FALSE;
if(input.EventType != KEY_EVENT)
return FALSE;
if(input.Event.KeyEvent.bKeyDown){
switch(input.Event.KeyEvent.uChar.AsciiChar){
case 0:
break;
case 13:
x[0] = '\r';
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),"\r\n",2,&y,NULL);
if(state == 2)
return TRUE;
else
state = 1;
break;
case 26:
if(state == 1)
state = 2;
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),"^Z",2,&y,NULL);
break;
default:
if(((unsigned char) input.Event.KeyEvent.uChar.AsciiChar) < ' '){
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),"^",1,&y,NULL);
x[0] = input.Event.KeyEvent.uChar.AsciiChar + '@';
} else
x[0] = input.Event.KeyEvent.uChar.AsciiChar;
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),x,1,&y,NULL);
state = 0;
break;
}
return FALSE;
}
return FALSE;
}
/*
* Writes eventlog entries to erlang and requires ACK for
* each record.
*/
BOOL data_to_pipe(char *string, char *answer, int answer_siz){
unsigned char len[2];
unsigned char *ptr;
int siz = strlen(string);
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
HANDLE in = GetStdHandle(STD_INPUT_HANDLE);
DWORD written, red;
DWORD left;
len[0] = (siz >> 8) & 0xFF;
len[1] = siz & 0xFF;
if(!WriteFile(out, len, 2,
&written, NULL) || written != 2)
return FALSE;
if(!WriteFile(out, string, siz, &written, NULL) ||
written != (DWORD) siz)
return FALSE;
/* Read ACK from erlang */
left = 2;
ptr = len;
for(;;){
if(!(red = read_pipe_stdin(ptr, left)))
return FALSE;
else if(red < left){
ptr += red;
left -= red;
} else {
break;
}
}
siz = len[0] << 8 | len[1];
if(siz >= answer_siz - 1)
return FALSE;
left = siz;
ptr = (unsigned char *) answer;
for(;;){
if(!(red = read_pipe_stdin(ptr, left))){
return FALSE;
} else if(red < left){
ptr += red;
left -= red;
} else {
break;
}
}
answer[siz] = '\0';
return TRUE;
}
/*
* The actual writing of records.
* Behaves differently if stdout is a pipe (erlang)
* or a console (test run).
*/
BOOL output_record(char *category, EVENTLOGRECORD *event){
char *strbeg, *fac, *sev;
char eventfilename[MAX_PATH];
char paramfilename[MAX_PATH];
char bigbuff[BIGBUFSIZ];
MessageFiles mf;
char tbuff[TIME_BUFSIZ];
BOOL ret;
DWORD written;
char *buff;
char ackbuff[ACK_MAX];
mf = get_messagefiles(category,((char *)event)+sizeof(EVENTLOGRECORD),
eventfilename, MAX_PATH,
paramfilename, MAX_PATH);
if(!mf.event){
strcpy(bigbuff, CANT_FORMAT_MESSAGE);
} else {
strbeg = (char *) event;
strbeg += event->StringOffset;
if(!format_message(mf, event->EventID,
strbeg, event->NumStrings, bigbuff, BIGBUFSIZ)){
strcpy(bigbuff, CANT_FORMAT_MESSAGE);
}
}
fac = ((char *)event)+sizeof(EVENTLOGRECORD);
sev = lookup_severity(event->EventType);
if(console_stdin()){
*tbuff = '\0';
strftime(tbuff, (size_t) TIME_BUFSIZ, TIME_FORMAT,
gmtime((time_t *)&(event->TimeGenerated)));
buff =
malloc(strlen(bigbuff) + TIME_BUFSIZ /* date-time */ +
strlen(category) +
strlen(fac) /* facility */ +
strlen(sev) /*severity */+
strlen(LOG_FORMAT) /* More than actually needed */ + 1);
sprintf(buff, LOG_FORMAT, tbuff, fac, category, sev, bigbuff);
ret = WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buff, strlen(buff),
&written, NULL); /* Overlapped structure for stdout? */
free(buff);
} else { /* pipe */
sprintf(tbuff,"%lu", event->TimeGenerated);
buff = malloc(strlen(bigbuff) +
strlen(tbuff) +
strlen(category) +
strlen(fac) +
strlen(sev) +
strlen(PIPE_LOG_FORMAT) +
PIPE_LOG_EXTRA);
sprintf(buff,PIPE_LOG_FORMAT,
strlen(tbuff),tbuff,
strlen(category), category,
strlen(fac), fac,
strlen(sev), sev,
strlen(bigbuff), bigbuff);
ret = data_to_pipe(buff,ackbuff, ACK_MAX);
if(ret && strcmp(ackbuff,PIPE_LOG_ACK))
ret = FALSE;
free(buff);
}
return ret;
}
/*
* Read eventlog entries FOLLOWING the given record
* number and timestamp, and sends them to
* stdout. If timestamp does
* not correspond with record number, the
* log is concidered wrapped around
* and is reread from the beginning.
* time is ignored if 0.
* If record_number is 0, the whole log is read (if there is one).
* If the function is unsuccessful, the global variable
* reopen_event_log may be set to TRUE, which means
* that the eventlog has changed and has to be reopened.
* It's the callers responsibility to set reopen_event_log to
* 0 before calling.
*/
static int read_from(DWORD *record_number, DWORD *time,
HANDLE elog, char *category){
DWORD dummy;
static EVENTLOGRECORD *event = NULL;
static DWORD eventsiz = 0;
DWORD red, need;
/* Always begin reading from record 1, record 0 never exists */
DWORD tmp = (*record_number) ? *record_number : 1;
DWORD ttmp = *time;
int skip = !!(*record_number); /* Dont skip if record_number == 0 */
int maybe_done = 0;
int i;
/* First try seeking to the correct place in the eventlog */
/* the variable skip tells us that we are rereading the */
/* last read eventlog record and the variable */
/* maybe_done tells us that we have read the last eventlog */
/* and an EOF is OK. */
for(;;){
if(!(need = needed(elog, tmp))){
if(maybe_done) /* Has read one correct record
and are satisfied with that. */
return 0;
/* Hmm, could not find the record? Try the oldest... */
if(!GetOldestEventLogRecord(elog, &tmp) ||
!(need = needed(elog, tmp))){
/* Something's terribly wrong. */
#ifdef HARDDEBUG
fprintf(stderr,"Could not get oldest eventlog record!\n",
need);
#endif
return -1;
}
skip = 0;
}
/* need == number of bytes for this record,
tmp == this recordnumber */
if(!event)
event = malloc(eventsiz = need);
else if(eventsiz < need)
event = realloc(event, eventsiz = need);
if(!ReadEventLog(elog,
(DWORD) (EVENTLOG_FORWARDS_READ |
EVENTLOG_SEEK_READ),
tmp,
event,
need,
&red,
&dummy)){
if(GetLastError() == ERROR_EVENTLOG_FILE_CHANGED){
reopen_event_log = 1;
}
#ifdef HARDDEBUG
output_error(GetLastError(),
"Failed first eventlog read in read_from\n");
#endif
return -1;
}
if(skip){
if(ttmp && event->TimeWritten != ttmp){
/* Wrapped around eventlog */
tmp = 1;
} else {
maybe_done = 1;
++tmp;
}
skip = 0;
} else
break;
}
/* Got the first record in buffer, display and continue reading */
/* sequentially from here. */
for(i=1;;++i){
if(!output_record(category, event))
return -1;
*record_number = event->RecordNumber;
*time = event->TimeWritten;
if(!(need = needed(elog,(DWORD) 0)))
break; /* End of log */
if(eventsiz < need)
event = realloc(event, eventsiz = need);
if(!ReadEventLog(elog,
(DWORD) EVENTLOG_FORWARDS_READ |
EVENTLOG_SEQUENTIAL_READ,
(DWORD) 0,
event,
need,
&red,
&dummy)){
if(GetLastError() == ERROR_EVENTLOG_FILE_CHANGED){
reopen_event_log = 1;
}
return -1;
}
}
return i;
}
/*
* Read unread events and wait for new to arrive.
*/
int main(int argc, char **argv){
HANDLE elog[NUM_CATEGORIES];
HANDLE events[NUM_CATEGORIES + 1]; /* The stdin handle goes
in here to */
int eventlen;
char *ident;
DWORD record = 0, time = 0;
RegKeys rk[NUM_CATEGORIES];
int rklen = NUM_CATEGORIES;
int i;
int ret;
int x = 0;
int retry = 0;
if(argc < 2){
ident = DEFAULT_IDENT;
} else {
ident = argv[1];
}
if(!setup_pipe_stdin()){
fprintf(stderr,"%s: Stdin could not be initialized.\n",argv[0]);
return 1;
}
if(get_regkeys(ident, rk, &rklen) != 0){
fprintf(stderr,
"%s: Could not get/create registry parameters.\n", argv[0]);
return 1;
}
for(;;){
for(i=0; i<rklen; ++i){
elog[i] = OpenEventLog(NULL, rk[i].facility_name);
if(elog[i] == NULL){
fprintf(stderr,
"%s: Could not open \"%s\" eventlog.\n",
argv[0], rk[i].facility_name);
return 1;
}
if((events[i] = CreateEvent(NULL,FALSE,FALSE,NULL))
== NULL){
fprintf(stderr,"%s: Could not create event object.\n", argv[0]);
return 1;
}
if(!NotifyChangeEventLog(elog[i],events[i])){
fprintf(stderr,"%s: Could not get eventlog notification.\n", argv[0]);
return 1;
}
if(read_from(&(rk[i].latest_record),
&(rk[i].latest_time),
elog[i],
rk[i].facility_name) > 0)
set_regkeys(ident, rk + i, 1);
}
eventlen = rklen;
events[eventlen] = get_stdin_event();
++eventlen;
for(;;){
#ifdef HARDDEBUG
fprintf(stderr,"Entering Wait...\n");
#endif
reopen_event_log = 0;
ret = WaitForMultipleObjects(eventlen,
events,
FALSE,
(retry) ?
RETRY_TIMEOUT :
INFINITE);
#ifdef HARDDEBUG
fprintf(stderr,"Wait returned!\n");
#endif
if(ret == WAIT_TIMEOUT){
if(!retry){
fprintf(stderr,"%s: Timeout when no such possible!\n",
argv[0]);
return 1;
}
retry = 0;
} else {
if(((int) (ret - WAIT_OBJECT_0)) >= rklen && eof_on_stdin())
goto done;
retry = 1;
}
for(i=0;i<rklen;++i){
if(read_from(&(rk[i].latest_record),
&(rk[i].latest_time),
elog[i],
rk[i].facility_name) > 0)
set_regkeys(ident, rk + i, 1);
}
if(reopen_event_log)
break;
}
for(i=0; i < rklen; ++i){
CloseEventLog(elog[i]);
CloseHandle(events[i]);
}
}
done:
#ifdef DEBUG
fprintf(stderr,"%s: EOF\n", argv[0]);
#endif
for(i=0; i < rklen; ++i)
CloseEventLog(elog[i]);
return 0;
}