Rainer Gerhards b826c0d3c5 Merge branch 'v5-devel' & fix bug in BSD imklog driver
Conflicts:
	plugins/imklog/ksym.c
	plugins/imklog/linux.c
2012-01-23 18:19:19 +01:00

295 lines
7.5 KiB
C

/* combined imklog driver for BSD and Linux
*
* This contains OS-specific functionality to read the BSD
* or Linux kernel log. For a general overview, see head comment in
* imklog.c. This started out as the BSD-specific drivers, but it
* turned out that on modern Linux the implementation details
* are very small, and so we use a single driver for both OS's with
* a little help of conditional compilation.
*
* Copyright 2008-2012 Adiscon GmbH
*
* This file is part of rsyslog.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* -or-
* see COPYING.ASL20 in the source distribution
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#ifdef OS_LINUX
# include <sys/klog.h>
#endif
#include "rsyslog.h"
#include "srUtils.h"
#include "debug.h"
#include "imklog.h"
/* globals */
static int fklog = -1; /* kernel log fd */
#ifndef _PATH_KLOG
# ifdef OS_LINUX
# define _PATH_KLOG "/proc/kmsg"
# else
# define _PATH_KLOG "/dev/klog"
# endif
#endif
#ifdef OS_LINUX
/* submit a message to imklog Syslog() API. In this function, we check if
* a kernel timestamp is present and, if so, extract and strip it.
* Note: this is an extra processing step. We should revisit the whole
* idea in v6 and remove all that old stuff that we do not longer need
* (like symbol resolution). <-- TODO
* Note that this is heavily Linux specific and thus is not compiled or
* used for BSD.
* Special thanks to Lennart Poettering for suggesting on how to convert
* the kernel timestamp to a realtime timestamp. This method depends on
* the fact the the kernel timestamp is written using the monotonic clock.
* Shall that change (very unlikely), this code must be changed as well. Note
* that due to the way we generate the delta, we are unable to write the
* absolutely correct timestamp (system call overhead of the clock calls
* prevents us from doing so). However, the difference is very minor.
* rgerhards, 2011-06-24
*/
static void
submitSyslog(int pri, uchar *buf)
{
long secs;
long nsecs;
long secOffs;
long nsecOffs;
unsigned i;
unsigned bufsize;
struct timespec monotonic, realtime;
struct timeval tv;
struct timeval *tp = NULL;
if(buf[3] != '[')
goto done;
DBGPRINTF("imklog: kernel timestamp detected, extracting it\n");
/* we now try to parse the timestamp. iff it parses, we assume
* it is a timestamp. Otherwise we know for sure it is no ts ;)
*/
i = 4; /* first digit after '[' */
secs = 0;
while(buf[i] && isdigit(buf[i])) {
secs = secs * 10 + buf[i] - '0';
++i;
}
if(buf[i] != '.') {
DBGPRINTF("no dot --> no kernel timestamp\n");
goto done; /* no TS! */
}
++i; /* skip dot */
nsecs = 0;
while(buf[i] && isdigit(buf[i])) {
nsecs = nsecs * 10 + buf[i] - '0';
++i;
}
if(buf[i] != ']') {
DBGPRINTF("no trailing ']' --> no kernel timestamp\n");
goto done; /* no TS! */
}
++i; /* skip ']' */
/* we have a timestamp */
DBGPRINTF("kernel timestamp is %ld %ld\n", secs, nsecs);
bufsize= strlen((char*)buf);
memcpy(buf+3, buf+i, bufsize - i + 1);
clock_gettime(CLOCK_MONOTONIC, &monotonic);
clock_gettime(CLOCK_REALTIME, &realtime);
secOffs = realtime.tv_sec - monotonic.tv_sec;
nsecOffs = realtime.tv_nsec - monotonic.tv_nsec;
if(nsecOffs < 0) {
secOffs--;
nsecOffs += 1000000000l;
}
nsecs +=nsecOffs;
if(nsecs > 999999999l) {
secs++;
nsecs -= 1000000000l;
}
secs += secOffs;
tv.tv_sec = secs;
tv.tv_usec = nsecs / 1000;
tp = &tv;
done:
Syslog(pri, buf, tp);
}
#else /* now comes the BSD "code" (just a shim) */
static void
submitSyslog(int pri, uchar *buf)
{
Syslog(pri, buf, NULL);
}
#endif /* #ifdef LINUX */
static uchar *GetPath(modConfData_t *pModConf)
{
return pModConf->pszPath ? pModConf->pszPath : (uchar*) _PATH_KLOG;
}
/* open the kernel log - will be called inside the willRun() imklog
* entry point. -- rgerhards, 2008-04-09
*/
rsRetVal
klogWillRun(modConfData_t *pModConf)
{
char errmsg[2048];
int r;
DEFiRet;
fklog = open((char*)GetPath(pModConf), O_RDONLY, 0);
if (fklog < 0) {
imklogLogIntMsg(RS_RET_ERR_OPEN_KLOG, "imklog: cannot open kernel log(%s): %s.",
GetPath(pModConf), rs_strerror_r(errno, errmsg, sizeof(errmsg)));
ABORT_FINALIZE(RS_RET_ERR_OPEN_KLOG);
}
# ifdef OS_LINUX
/* Set level of kernel console messaging.. */
if(pModConf->console_log_level != -1) {
r = klogctl(8, NULL, pModConf->console_log_level);
if(r != 0) {
imklogLogIntMsg(LOG_WARNING, "imklog: cannot set console log level: %s",
rs_strerror_r(errno, errmsg, sizeof(errmsg)));
/* make sure we do not try to re-set! */
pModConf->console_log_level = -1;
}
}
# endif /* #ifdef OS_LINUX */
finalize_it:
RETiRet;
}
/* Read kernel log while data are available, split into lines.
*/
static void
readklog(void)
{
char *p, *q;
int len, i;
int iMaxLine;
uchar bufRcv[128*1024+1];
char errmsg[2048];
uchar *pRcv = NULL; /* receive buffer */
iMaxLine = klog_getMaxLine();
/* we optimize performance: if iMaxLine is below our fixed size buffer (which
* usually is sufficiently large), we use this buffer. if it is higher, heap memory
* is used. We could use alloca() to achive a similar aspect, but there are so
* many issues with alloca() that I do not want to take that route.
* rgerhards, 2008-09-02
*/
if((size_t) iMaxLine < sizeof(bufRcv) - 1) {
pRcv = bufRcv;
} else {
if((pRcv = (uchar*) MALLOC(sizeof(uchar) * (iMaxLine + 1))) == NULL)
iMaxLine = sizeof(bufRcv) - 1; /* better this than noting */
}
len = 0;
for (;;) {
dbgprintf("imklog(BSD/Linux) waiting for kernel log line\n");
i = read(fklog, pRcv + len, iMaxLine - len);
if (i > 0) {
pRcv[i + len] = '\0';
} else {
if (i < 0 && errno != EINTR && errno != EAGAIN) {
imklogLogIntMsg(LOG_ERR,
"imklog: error reading kernel log - shutting down: %s",
rs_strerror_r(errno, errmsg, sizeof(errmsg)));
fklog = -1;
}
break;
}
for (p = (char*)pRcv; (q = strchr(p, '\n')) != NULL; p = q + 1) {
*q = '\0';
submitSyslog(LOG_INFO, (uchar*) p);
}
len = strlen(p);
if (len >= iMaxLine - 1) {
submitSyslog(LOG_INFO, (uchar*)p);
len = 0;
}
if(len > 0)
memmove(pRcv, p, len + 1);
}
if (len > 0)
submitSyslog(LOG_INFO, pRcv);
if(pRcv != NULL && (size_t) iMaxLine >= sizeof(bufRcv) - 1)
free(pRcv);
}
/* to be called in the module's AfterRun entry point
* rgerhards, 2008-04-09
*/
rsRetVal klogAfterRun(modConfData_t *pModConf)
{
DEFiRet;
if(fklog != -1)
close(fklog);
# ifdef OS_LINUX
/* Turn on logging of messages to console, but only if a log level was speficied */
if(pModConf->console_log_level != -1)
klogctl(7, NULL, 0);
# endif
RETiRet;
}
/* to be called in the module's WillRun entry point, this is the main
* "message pull" mechanism.
* rgerhards, 2008-04-09
*/
rsRetVal klogLogKMsg(modConfData_t __attribute__((unused)) *pModConf)
{
DEFiRet;
readklog();
RETiRet;
}
/* provide the (system-specific) default facility for internal messages
* rgerhards, 2008-04-14
*/
int
klogFacilIntMsg(void)
{
return LOG_SYSLOG;
}