summaryrefslogtreecommitdiffstats
path: root/plugins/imklog/imklog.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/imklog/imklog.c')
-rw-r--r--plugins/imklog/imklog.c753
1 files changed, 753 insertions, 0 deletions
diff --git a/plugins/imklog/imklog.c b/plugins/imklog/imklog.c
new file mode 100644
index 00000000..a6e2a856
--- /dev/null
+++ b/plugins/imklog/imklog.c
@@ -0,0 +1,753 @@
+/* The kernel log input module for Linux. This file heavily
+ * borrows from the klogd daemon provided by the sysklogd project.
+ * Many thanks for this piece of software.
+ *
+ * Please note that this file replaces the klogd daemon that was
+ * also present in pre-v3 versions of rsyslog.
+ *
+ * I have begun to convert this to an input module on 2007-12-17.
+ * IMPORTANT: more than a single instance is currently not supported. This
+ * needs to be revisited once the config file and input module interface
+ * supports multiple instances!
+ *
+ * This file is part of rsyslog.
+ *
+ * Rsyslog 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+*/
+#include "config.h"
+#include "rsyslog.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <signal.h>
+#include <string.h>
+#include <pthread.h>
+#include "syslogd.h"
+#include "cfsysline.h"
+#include "template.h"
+#include "msg.h"
+#include "module-template.h"
+
+MODULE_TYPE_INPUT
+TERM_SYNC_TYPE(eTermSync_SIGNAL)
+
+/* defines */
+#define DEFAULT_MARK_PERIOD (20 * 60)
+
+/* Module static data */
+DEF_IMOD_STATIC_DATA
+typedef struct _instanceData {
+} instanceData;
+
+
+/* Includes. */
+#include <unistd.h>
+#include <errno.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#if !defined(__GLIBC__)
+#include <linux/time.h>
+#endif /* __GLIBC__ */
+#include <stdarg.h>
+#include <paths.h>
+#include "ksyms.h"
+
+#define __LIBRARY__
+#include <linux/unistd.h>
+#if !defined(__GLIBC__)
+# define __NR_ksyslog __NR_syslog
+_syscall3(int,ksyslog,int, type, char *, buf, int, len);
+#else
+#include <sys/klog.h>
+#define ksyslog klogctl
+#endif
+
+#ifndef _PATH_KLOG
+#define _PATH_KLOG "/proc/kmsg"
+#endif
+
+#define LOG_BUFFER_SIZE 4096
+#define LOG_LINE_LENGTH 1000
+
+static int kmsg,
+ console_log_level = -1;
+
+static int use_syscall = 0,
+ symbol_lookup = 1;
+
+static char *symfile = (char *) 0,
+ log_buffer[LOG_BUFFER_SIZE];
+
+static enum LOGSRC {none, proc, kernel} logsrc;
+
+int debugging = 0;
+int symbols_twice = 0;
+
+
+/* Function prototypes. */
+extern int ksyslog(int type, char *buf, int len);
+static enum LOGSRC GetKernelLogSrc(void);
+static void LogLine(char *ptr, int len);
+static void LogKernelLine(void);
+static void LogProcLine(void);
+
+
+
+/* Write a message to the message queue.
+ * returns -1 if it fails, something else otherwise
+ */
+static rsRetVal writeSyslogV(int iPRI, const char *szFmt, va_list va)// __attribute__((format(printf,2, 3)));
+{
+ DEFiRet;
+ int iChars;
+ int iLen;
+ time_t tNow;
+ char msgBuf[2048]; /* we use the same size as sysklogd to remain compatible */
+ msg_t *pMsg;
+
+ assert(szFmt != NULL);
+
+ /* build the message */
+ time(&tNow);
+ /* we can use sprintf safely below, because we know the size of the constants.
+ * By doing so, we save some cpu cycles and code complexity (for unnecessary
+ * error checking).
+ */
+ iLen = sprintf(msgBuf, "<%d>%.15s kernel: ", iPRI, ctime(&tNow) + 4);
+
+ iChars = vsnprintf(msgBuf + iLen, sizeof(msgBuf) / sizeof(char) - iLen, szFmt, va);
+
+ /* here we must create our message object and supply it to the message queue
+ */
+ if((pMsg = MsgConstruct()) == NULL){
+ /* There is not much we can do in this case - we discard the message
+ * then.
+ */
+ dbgprintf("Memory shortage in imklogd: could not construct Msg object.\n");
+ return RS_RET_OUT_OF_MEMORY;
+ }
+
+dbgprintf("klogd logging Raw: '%s',\nmsg: '%s'\n", msgBuf, msgBuf + iLen);
+ MsgSetUxTradMsg(pMsg, msgBuf);
+ MsgSetRawMsg(pMsg, msgBuf);
+ MsgSetMSG(pMsg, (msgBuf + iLen));
+ MsgSetHOSTNAME(pMsg, LocalHostName);
+ MsgSetTAG(pMsg, "kernel:");
+ pMsg->iFacility = LOG_FAC(LOG_KERN);
+ pMsg->iSeverity = LOG_PRI(iPRI);
+ pMsg->bParseHOSTNAME = 0;
+ getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */
+
+ /* provide message to the queue engine */
+ logmsg(iPRI, pMsg, INTERNAL_MSG);
+
+ return iRet;
+}
+
+/* And now the same with variable arguments */
+static int writeSyslog(int iPRI, const char *szFmt, ...)
+{
+ int iRet;
+ va_list va;
+
+ assert(szFmt != NULL);
+ va_start(va, szFmt);
+ iRet = writeSyslogV(iPRI, szFmt, va);
+ va_end(va);
+
+ return(iRet);
+}
+
+rsRetVal Syslog(int priority, char *fmt, ...) __attribute__((format(printf,2, 3)));
+rsRetVal Syslog(int priority, char *fmt, ...)
+{
+ DEFiRet;
+ va_list ap;
+ char *argl;
+
+ /* Output using syslog. */
+ if (!strcmp(fmt, "%s")) {
+ va_start(ap, fmt);
+ argl = va_arg(ap, char *);
+ if (argl[0] == '<' && argl[1] && argl[2] == '>') {
+ switch ( argl[1] )
+ {
+ case '0':
+ priority = LOG_EMERG;
+ break;
+ case '1':
+ priority = LOG_ALERT;
+ break;
+ case '2':
+ priority = LOG_CRIT;
+ break;
+ case '3':
+ priority = LOG_ERR;
+ break;
+ case '4':
+ priority = LOG_WARNING;
+ break;
+ case '5':
+ priority = LOG_NOTICE;
+ break;
+ case '6':
+ priority = LOG_INFO;
+ break;
+ case '7':
+ default:
+ priority = LOG_DEBUG;
+ }
+ argl += 3;
+ }
+ iRet = writeSyslog(priority, fmt, argl);
+ va_end(ap);
+ } else {
+ va_start(ap, fmt);
+ iRet = writeSyslogV(priority, fmt, ap);
+ va_end(ap);
+ }
+
+ return iRet;
+}
+
+
+static void CloseLogSrc(void)
+{
+ /* Turn on logging of messages to console, but only if we had the -c
+ * option -- rgerhards, 2007-08-01
+ */
+ if (console_log_level != -1)
+ ksyslog(7, NULL, 0);
+
+ /* Shutdown the log sources. */
+ switch ( logsrc )
+ {
+ case kernel:
+ ksyslog(0, 0, 0);
+ Syslog(LOG_INFO, "Kernel logging (ksyslog) stopped.");
+ break;
+ case proc:
+ close(kmsg);
+ Syslog(LOG_INFO, "Kernel logging (proc) stopped.");
+ break;
+ case none:
+ break;
+ }
+
+ return;
+}
+
+
+static enum LOGSRC GetKernelLogSrc(void)
+{
+ auto struct stat sb;
+
+ /* Set level of kernel console messaging.. */
+ if ( (console_log_level != -1) &&
+ (ksyslog(8, NULL, console_log_level) < 0) &&
+ (errno == EINVAL) )
+ {
+ /*
+ * An invalid arguement error probably indicates that
+ * a pre-0.14 kernel is being run. At this point we
+ * issue an error message and simply shut-off console
+ * logging completely.
+ */
+ Syslog(LOG_WARNING, "Cannot set console log level - disabling "
+ "console output.");
+ }
+
+ /*
+ * First do a stat to determine whether or not the proc based
+ * file system is available to get kernel messages from.
+ */
+ if ( use_syscall ||
+ ((stat(_PATH_KLOG, &sb) < 0) && (errno == ENOENT)) )
+ {
+ /* Initialize kernel logging. */
+ ksyslog(1, NULL, 0);
+ Syslog(LOG_INFO, "imklogd %s, log source = ksyslog "
+ "started.", VERSION);
+ return(kernel);
+ }
+
+ if ( (kmsg = open(_PATH_KLOG, O_RDONLY)) < 0 )
+ {
+ fprintf(stderr, "rklogd: Cannot open proc file system, " \
+ "%d - %s.\n", errno, strerror(errno));
+ ksyslog(7, NULL, 0);
+ exit(1);
+ }
+
+ Syslog(LOG_INFO, "imklog %s, log source = %s started.", \
+ VERSION, _PATH_KLOG);
+ return(proc);
+}
+
+
+/* Copy characters from ptr to line until a char in the delim
+ * string is encountered or until min( space, len ) chars have
+ * been copied.
+ *
+ * Returns the actual number of chars copied.
+ */
+static int copyin( char *line, int space,
+ const char *ptr, int len,
+ const char *delim )
+{
+ auto int i;
+ auto int count;
+
+ count = len < space ? len : space;
+
+ for(i=0; i<count && !strchr(delim, *ptr); i++ ) {
+ *line++ = *ptr++;
+ }
+
+ return( i );
+}
+
+/*
+ * Messages are separated by "\n". Messages longer than
+ * LOG_LINE_LENGTH are broken up.
+ *
+ * Kernel symbols show up in the input buffer as : "[<aaaaaa>]",
+ * where "aaaaaa" is the address. These are replaced with
+ * "[symbolname+offset/size]" in the output line - symbolname,
+ * offset, and size come from the kernel symbol table.
+ *
+ * If a kernel symbol happens to fall at the end of a message close
+ * in length to LOG_LINE_LENGTH, the symbol will not be expanded.
+ * (This should never happen, since the kernel should never generate
+ * messages that long.
+ *
+ * To preserve the original addresses, lines containing kernel symbols
+ * are output twice. Once with the symbols converted and again with the
+ * original text. Just in case somebody wants to run their own Oops
+ * analysis on the syslog, e.g. ksymoops.
+ */
+static void LogLine(char *ptr, int len)
+{
+ enum parse_state_enum {
+ PARSING_TEXT,
+ PARSING_SYMSTART, /* at < */
+ PARSING_SYMBOL,
+ PARSING_SYMEND /* at ] */
+ };
+
+ static char line_buff[LOG_LINE_LENGTH];
+
+ static char *line =line_buff;
+ static enum parse_state_enum parse_state = PARSING_TEXT;
+ static int space = sizeof(line_buff)-1;
+
+ static char *sym_start; /* points at the '<' of a symbol */
+
+ auto int delta = 0; /* number of chars copied */
+ auto int symbols_expanded = 0; /* 1 if symbols were expanded */
+ auto int skip_symbol_lookup = 0; /* skip symbol lookup on this pass */
+ auto char *save_ptr = ptr; /* save start of input line */
+ auto int save_len = len; /* save length at start of input line */
+
+ while( len > 0 )
+ {
+ if( space == 0 ) /* line buffer is full */
+ {
+ /*
+ ** Line too long. Start a new line.
+ */
+ *line = 0; /* force null terminator */
+
+ if ( debugging )
+ {
+ fputs("Line buffer full:\n", stderr);
+ fprintf(stderr, "\tLine: %s\n", line);
+ }
+
+ Syslog( LOG_INFO, "%s", line_buff );
+ line = line_buff;
+ space = sizeof(line_buff)-1;
+ parse_state = PARSING_TEXT;
+ symbols_expanded = 0;
+ skip_symbol_lookup = 0;
+ save_ptr = ptr;
+ save_len = len;
+ }
+
+ switch( parse_state )
+ {
+ case PARSING_TEXT:
+ delta = copyin( line, space, ptr, len, "\n[" );
+ line += delta;
+ ptr += delta;
+ space -= delta;
+ len -= delta;
+
+ if( space == 0 || len == 0 )
+ {
+ break; /* full line_buff or end of input buffer */
+ }
+
+ if( *ptr == '\0' ) /* zero byte */
+ {
+ ptr++; /* skip zero byte */
+ space -= 1;
+ len -= 1;
+
+ break;
+ }
+
+ if( *ptr == '\n' ) /* newline */
+ {
+ ptr++; /* skip newline */
+ space -= 1;
+ len -= 1;
+
+ *line = 0; /* force null terminator */
+ Syslog( LOG_INFO, "%s", line_buff );
+ line = line_buff;
+ space = sizeof(line_buff)-1;
+ if (symbols_twice) {
+ if (symbols_expanded) {
+ /* reprint this line without symbol lookup */
+ symbols_expanded = 0;
+ skip_symbol_lookup = 1;
+ ptr = save_ptr;
+ len = save_len;
+ }
+ else
+ {
+ skip_symbol_lookup = 0;
+ save_ptr = ptr;
+ save_len = len;
+ }
+ }
+ break;
+ }
+ if( *ptr == '[' ) /* possible kernel symbol */
+ {
+ *line++ = *ptr++;
+ space -= 1;
+ len -= 1;
+ if (!skip_symbol_lookup)
+ parse_state = PARSING_SYMSTART; /* at < */
+ break;
+ }
+ /* Now that line_buff is no longer fed to *printf as format
+ * string, '%'s are no longer "dangerous".
+ */
+ break;
+
+ case PARSING_SYMSTART:
+ if( *ptr != '<' )
+ {
+ parse_state = PARSING_TEXT; /* not a symbol */
+ break;
+ }
+
+ /*
+ ** Save this character for now. If this turns out to
+ ** be a valid symbol, this char will be replaced later.
+ ** If not, we'll just leave it there.
+ */
+
+ sym_start = line; /* this will point at the '<' */
+
+ *line++ = *ptr++;
+ space -= 1;
+ len -= 1;
+ parse_state = PARSING_SYMBOL; /* symbol... */
+ break;
+
+ case PARSING_SYMBOL:
+ delta = copyin( line, space, ptr, len, ">\n[" );
+ line += delta;
+ ptr += delta;
+ space -= delta;
+ len -= delta;
+ if( space == 0 || len == 0 )
+ {
+ break; /* full line_buff or end of input buffer */
+ }
+ if( *ptr != '>' )
+ {
+ parse_state = PARSING_TEXT;
+ break;
+ }
+
+ *line++ = *ptr++; /* copy the '>' */
+ space -= 1;
+ len -= 1;
+
+ parse_state = PARSING_SYMEND;
+
+ break;
+
+ case PARSING_SYMEND:
+ if( *ptr != ']' )
+ {
+ parse_state = PARSING_TEXT; /* not a symbol */
+ break;
+ }
+
+ /*
+ ** It's really a symbol! Replace address with the
+ ** symbol text.
+ */
+ {
+ auto int sym_space;
+
+ unsigned long value;
+ auto struct symbol sym;
+ auto char *symbol;
+
+ *(line-1) = 0; /* null terminate the address string */
+ value = strtoul(sym_start+1, (char **) 0, 16);
+ *(line-1) = '>'; /* put back delim */
+
+ if ( !symbol_lookup || (symbol = LookupSymbol(value, &sym)) == (char *)0 )
+ {
+ parse_state = PARSING_TEXT;
+ break;
+ }
+
+ /*
+ ** verify there is room in the line buffer
+ */
+ sym_space = space + ( line - sym_start );
+ if( (unsigned) sym_space < strlen(symbol) + 30 ) /*(30 should be overkill)*/
+ {
+ parse_state = PARSING_TEXT; /* not enough space */
+ break;
+ }
+
+ delta = sprintf( sym_start, "%s+%d/%d]",
+ symbol, sym.offset, sym.size );
+
+ space = sym_space + delta;
+ line = sym_start + delta;
+ symbols_expanded = 1;
+ }
+ ptr++;
+ len--;
+ parse_state = PARSING_TEXT;
+ break;
+
+ default: /* Can't get here! */
+ parse_state = PARSING_TEXT;
+
+ }
+ }
+
+ return;
+}
+
+
+static void LogKernelLine(void)
+{
+ auto int rdcnt;
+
+ /*
+ * Zero-fill the log buffer. This should cure a multitude of
+ * problems with klogd logging the tail end of the message buffer
+ * which will contain old messages. Then read the kernel log
+ * messages into this fresh buffer.
+ */
+ memset(log_buffer, '\0', sizeof(log_buffer));
+ if ( (rdcnt = ksyslog(2, log_buffer, sizeof(log_buffer)-1)) < 0 )
+ {
+ if ( errno == EINTR )
+ return;
+ fprintf(stderr, "rklogd: Error return from sys_sycall: " \
+ "%d - %s\n", errno, strerror(errno));
+ }
+ else
+ LogLine(log_buffer, rdcnt);
+ return;
+}
+
+
+static void LogProcLine(void)
+{
+ auto int rdcnt;
+
+ /*
+ * Zero-fill the log buffer. This should cure a multitude of
+ * problems with klogd logging the tail end of the message buffer
+ * which will contain old messages. Then read the kernel messages
+ * from the message pseudo-file into this fresh buffer.
+ */
+ memset(log_buffer, '\0', sizeof(log_buffer));
+ if ( (rdcnt = read(kmsg, log_buffer, sizeof(log_buffer)-1)) < 0 )
+ {
+ if ( errno == EINTR )
+ return;
+ Syslog(LOG_ERR, "Cannot read proc file system: %d - %s.", \
+ errno, strerror(errno));
+ }
+ else
+ LogLine(log_buffer, rdcnt);
+
+ return;
+}
+
+
+#if 0
+int main(int argc, char *argv[])
+{
+ int ch,
+ use_output = 0;
+
+ char *log_level = (char *) 0,
+ *output = (char *) 0;
+
+ /* Parse the command-line. */
+ while ((ch = getopt(argc, argv, "c:df:iIk:nopsvx2")) != EOF)
+ switch((char)ch)
+ {
+ case '2': /* Print lines with symbols twice. */
+ symbols_twice = 1;
+ break;
+ case 'c': /* Set console message level. */
+ log_level = optarg;
+ break;
+ case 'd': /* Activity debug mode. */
+ debugging = 1;
+ break;
+ case 'f': /* Define an output file. */
+ output = optarg;
+ use_output++;
+ break;
+ case 'k': /* Kernel symbol file. */
+ symfile = optarg;
+ break;
+ case 'p':
+ SetParanoiaLevel(1); /* Load symbols on oops. */
+ break;
+ case 's': /* Use syscall interface. */
+ use_syscall = 1;
+ break;
+ case 'x':
+ symbol_lookup = 0;
+ break;
+ }
+
+
+ /* Set console logging level. */
+ if ( log_level != (char *) 0 )
+ {
+ if ( (strlen(log_level) > 1) || \
+ (strchr("12345678", *log_level) == (char *) 0) )
+ {
+ fprintf(stderr, "rklogd: Invalid console logging "
+ "level <%s> specified.\n", log_level);
+ return(1);
+ }
+ console_log_level = *log_level - '0';
+ }
+}
+#endif
+
+BEGINrunInput
+CODESTARTrunInput
+ /* Determine where kernel logging information is to come from. */
+ logsrc = GetKernelLogSrc();
+ if (symbol_lookup) {
+ symbol_lookup = (InitKsyms(symfile) == 1);
+ symbol_lookup |= InitMsyms();
+ if (symbol_lookup == 0) {
+ Syslog(LOG_WARNING, "cannot find any symbols, turning off symbol lookups\n");
+ }
+ }
+
+ /* this is an endless loop - it is terminated when the thread is
+ * signalled to do so. This, however, is handled by the framework,
+ * right into the sleep below.
+ */
+ while(!pThrd->bShallStop) {
+ /* we do not need to handle the RS_RET_TERMINATE_NOW case any
+ * special because we just need to terminate. This may be different
+ * if a cleanup is needed. But for now, we can just use CHKiRet().
+ * rgerhards, 2007-12-17
+ */
+ switch ( logsrc )
+ {
+ case kernel:
+ LogKernelLine();
+ break;
+ case proc:
+ LogProcLine();
+ break;
+ case none:
+ pause();
+ break;
+ }
+// CHKiRet(thrdSleep(pThrd, iMarkMessagePeriod, 0)); /* seconds, micro seconds */
+ }
+finalize_it:
+ /* cleanup here */
+ CloseLogSrc();
+
+ return iRet;
+ENDrunInput
+
+
+BEGINwillRun
+CODESTARTwillRun
+ENDwillRun
+
+
+BEGINfreeInstance
+CODESTARTfreeInstance
+ENDfreeInstance
+
+
+BEGINdbgPrintInstInfo
+CODESTARTdbgPrintInstInfo
+ENDdbgPrintInstInfo
+
+
+BEGINmodExit
+CODESTARTmodExit
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_IMOD_QUERIES
+ENDqueryEtryPt
+
+static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal)
+{
+
+ return RS_RET_OK;
+}
+
+BEGINmodInit()
+CODESTARTmodInit
+ *ipIFVersProvided = 1; /* so far, we only support the initial definition */
+CODEmodInit_QueryRegCFSLineHdlr
+ //CHKiRet(omsdRegCFSLineHdlr((uchar *)"markmessageperiod", 0, eCmdHdlrInt, NULL, &iMarkMessagePeriod, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID));
+ENDmodInit
+/*
+ * Local variables:
+ * c-indent-level: 8
+ * c-basic-offset: 8
+ * tab-width: 8
+ * End:
+ * vi:set ai:
+ */