/* klog for linux, based on the FreeBSD syslogd implementation.
*
* This contains OS-specific functionality to read the BSD
* kernel log. For a general overview, see head comment in
* imklog.c.
*
* This file heavily borrows from the klogd daemon provided by
* the sysklogd project. Many thanks for this piece of software.
*
* 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 .
*
* A copy of the GPL can be found in the file "COPYING" in this distribution.
*/
#include "config.h"
#include "rsyslog.h"
#include
#include
#include
#include
#include
#include
#include "syslogd.h"
#include "cfsysline.h"
#include "template.h"
#include "msg.h"
#include "module-template.h"
#include "imklog.h"
/* Includes. */
#include
#include
#include
#include
#if HAVE_TIME_H
# include
#endif
#include
#include
#include "ksyms.h"
#define __LIBRARY__
#include
#if !defined(__GLIBC__)
# define __NR_ksyslog __NR_syslog
_syscall3(int,ksyslog,int, type, char *, buf, int, len);
#else
#include
#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;
static char log_buffer[LOG_BUFFER_SIZE];
static enum LOGSRC {none, proc, kernel} logsrc;
/* Function prototypes. */
extern int ksyslog(int type, char *buf, int len);
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);
imklogLogIntMsg(LOG_INFO, "Kernel logging (ksyslog) stopped.");
break;
case proc:
close(kmsg);
imklogLogIntMsg(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.
*/
imklogLogIntMsg(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);
imklogLogIntMsg(LOG_INFO, "imklog %s, log source = ksyslog "
"started.", VERSION);
return(kernel);
}
if ( (kmsg = open(_PATH_KLOG, O_RDONLY)) < 0 )
{
char sz[512];
snprintf(sz, sizeof(sz), "imklog: Cannot open proc file system, %d - %s.\n", errno, strerror(errno));
logmsgInternal(LOG_SYSLOG|LOG_ERR, sz, ADDDATE);
ksyslog(7, NULL, 0); /* TODO: check this, implement more */
return(none);
}
imklogLogIntMsg(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( uchar *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]",
* 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 uchar line_buff[LOG_LINE_LENGTH];
static uchar *line =line_buff;
static enum parse_state_enum parse_state = PARSING_TEXT;
static int space = sizeof(line_buff)-1;
static uchar *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 */
//dbgprintf("Line buffer full:\n");
//dbgprintf("\tLine: %s\n", line);
Syslog(LOG_INFO, 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, 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((char*)(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;
}
// TODO: sprintf!!!!
delta = sprintf( (char*) 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 )
{
char sz[512];
if(errno == EINTR)
return;
snprintf(sz, sizeof(sz), "imklog: Error return from sys_sycall: %d - %s\n", errno, strerror(errno));
logmsgInternal(LOG_SYSLOG|LOG_ERR, sz, ADDDATE);
}
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;
imklogLogIntMsg(LOG_ERR, "Cannot read proc file system: %d - %s.", errno, strerror(errno));
} else {
LogLine(log_buffer, rdcnt);
}
return;
}
/* to be called in the module's WillRun entry point
* rgerhards, 2008-04-09
*/
rsRetVal klogLogKMsg(void)
{
DEFiRet;
switch(logsrc) {
case kernel:
LogKernelLine();
break;
case proc:
LogProcLine();
break;
case none:
/* TODO: We need to handle this case here somewhat more intelligent
* This is now at least partly done - code should never reach this point
* as willRun() already checked for the "none" status -- rgerhards, 2007-12-17
*/
pause();
break;
}
RETiRet;
}
/* to be called in the module's WillRun entry point
* rgerhards, 2008-04-09
*/
rsRetVal klogWillRun(void)
{
DEFiRet;
/* Initialize this module. If that fails, we tell the engine we don't like to run */
/* Determine where kernel logging information is to come from. */
logsrc = GetKernelLogSrc();
if(logsrc == none) {
iRet = RS_RET_NO_KERNEL_LOGSRC;
} else {
if (symbol_lookup) {
symbol_lookup = (InitKsyms(symfile) == 1);
symbol_lookup |= InitMsyms();
if (symbol_lookup == 0) {
imklogLogIntMsg(LOG_WARNING, "cannot find any symbols, turning off symbol lookups");
}
}
}
RETiRet;
}
/* to be called in the module's AfterRun entry point
* rgerhards, 2008-04-09
*/
rsRetVal klogAfterRun(void)
{
DEFiRet;
/* cleanup here */
if(logsrc != none)
CloseLogSrc();
DeinitKsyms();
DeinitMsyms();
RETiRet;
}
/* provide the (system-specific) default facility for internal messages
* rgerhards, 2008-04-14
*/
int
klogFacilIntMsg(void)
{
return LOG_KERN;
}
/* vi:set ai:
*/