summaryrefslogtreecommitdiffstats
path: root/plugins/imklog/bsd.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/imklog/bsd.c')
-rw-r--r--plugins/imklog/bsd.c329
1 files changed, 329 insertions, 0 deletions
diff --git a/plugins/imklog/bsd.c b/plugins/imklog/bsd.c
new file mode 100644
index 0000000..85331f2
--- /dev/null
+++ b/plugins/imklog/bsd.c
@@ -0,0 +1,329 @@
+/* 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-2023 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 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(modConfData_t *pModConf, syslog_pri_t pri, uchar *buf)
+{
+ long secs;
+ long usecs;
+ long secOffs;
+ long usecOffs;
+ unsigned i;
+ unsigned bufsize;
+ struct timespec monotonic, realtime;
+ struct timeval tv;
+ struct timeval *tp = NULL;
+
+ /* find end of pri */
+ int endpri = 1;
+ while(buf[endpri] != '>' && endpri < 5)
+ ++endpri;
+ if(endpri > 4 || buf[endpri + 1] != '[')
+ 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 = endpri + 2; /* space or first digit after '[' */
+ while(buf[i] && isspace(buf[i]))
+ ++i; /* skip space */
+ 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 */
+ usecs = 0;
+ while(buf[i] && isdigit(buf[i])) {
+ usecs = usecs * 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, usecs);
+ if(!pModConf->bKeepKernelStamp) {
+ bufsize= strlen((char*)buf);
+ memmove(buf+endpri+1, buf+i, bufsize - i + 1);
+ }
+
+ if(!pModConf->bParseKernelStamp) {
+ DBGPRINTF("imklog/bsd: parseKernelStamp not set, ignoring kernel timestamp\n");
+ goto done;
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &monotonic);
+ clock_gettime(CLOCK_REALTIME, &realtime);
+ secOffs = realtime.tv_sec - monotonic.tv_sec;
+ usecOffs = (realtime.tv_nsec - monotonic.tv_nsec) / 1000;
+ if(usecOffs < 0) {
+ secOffs--;
+ usecOffs += 1000000l;
+ }
+
+ usecs += usecOffs;
+ if(usecs > 999999l) {
+ secs++;
+ usecs -= 1000000l;
+ }
+ secs += secOffs;
+ tv.tv_sec = secs;
+ tv.tv_usec = usecs;
+ tp = &tv;
+
+done:
+ Syslog(pModConf, pri, buf, tp);
+}
+#else /* now comes the BSD "code" (just a shim) */
+static void
+submitSyslog(modConfData_t *pModConf, syslog_pri_t pri, uchar *buf)
+{
+ Syslog(pModConf, 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
+klogWillRunPrePrivDrop(modConfData_t *pModConf)
+{
+ char errmsg[2048];
+ DEFiRet;
+
+ fklog = open((char*)GetPath(pModConf), O_RDONLY, 0);
+ if (fklog < 0) {
+ imklogLogIntMsg(LOG_ERR, "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) {
+ int 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;
+}
+
+/* make sure the kernel log is readable after dropping privileges
+ */
+rsRetVal
+klogWillRunPostPrivDrop(modConfData_t *pModConf)
+{
+ char errmsg[2048];
+ int r;
+ DEFiRet;
+
+ /* this normally returns EINVAL */
+ /* on an OpenVZ VM, we get EPERM */
+ r = read(fklog, NULL, 0);
+ if (r < 0 && errno != EINVAL) {
+ imklogLogIntMsg(LOG_ERR, "imklog: cannot open kernel log (%s): %s.",
+ GetPath(pModConf), rs_strerror_r(errno, errmsg, sizeof(errmsg)));
+ fklog = -1;
+ ABORT_FINALIZE(RS_RET_ERR_OPEN_KLOG);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Read kernel log while data are available, split into lines.
+ */
+static void
+readklog(modConfData_t *pModConf)
+{
+ 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(iMaxLine + 1)) == NULL) {
+ iMaxLine = sizeof(bufRcv) - 1; /* better this than noting */
+ pRcv = bufRcv;
+ }
+ }
+
+ 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(pModConf, LOG_INFO, (uchar*) p);
+ }
+ len = strlen(p);
+ if (len >= iMaxLine - 1) {
+ submitSyslog(pModConf, LOG_INFO, (uchar*)p);
+ len = 0;
+ }
+ if(len > 0)
+ memmove(pRcv, p, len + 1);
+ }
+ if (len > 0)
+ submitSyslog(pModConf, LOG_INFO, pRcv);
+
+ if(pRcv != bufRcv)
+ free(pRcv);
+}
+
+
+/* to be called in the module's AfterRun entry point
+ * rgerhards, 2008-04-09
+ */
+rsRetVal ATTR_NONNULL()
+klogAfterRun(modConfData_t *const pModConf __attribute__((unused)))
+{
+ 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 *pModConf)
+{
+ DEFiRet;
+ readklog(pModConf);
+ RETiRet;
+}
+
+
+/* provide the (system-specific) default facility for internal messages
+ * rgerhards, 2008-04-14
+ */
+int
+klogFacilIntMsg(void)
+{
+ return LOG_SYSLOG;
+}