/* * %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 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 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; }