/
githubmirror
/
rsyslog
Обзор
Документация
Войти
/
githubmirror
/
rsyslog
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
plugins/imjournal/imjournal.c
1 826 строк
64 KB
Rainer Gerhards
Merge pull request #7366 from rmetrich/imjournal_empty_message
20 июл 2026, 09:54
Не верифицирован
20 июл 2026, 09:54
d3f857c
Код
Авторство
О чём код?
/* The systemd journal import module * * To test under Linux: * emmit log message into systemd journal * * Copyright (C) 2008-2019 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. */ #include "config.h" #include "rsyslog.h" #include <stdio.h> #include <dirent.h> #include <assert.h> #include <string.h> #include <stdarg.h> #include <ctype.h> #include <stdlib.h> #include <time.h> #include <sys/poll.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include <errno.h> #include <systemd/sd-journal.h> #include <fcntl.h> #include "dirty.h" #include "cfsysline.h" #include "obj.h" #include "msg.h" #include "module-template.h" #include "datetime.h" #include "net.h" #include "glbl.h" #include "statsobj.h" #include "ruleset.h" #include "parser.h" #include "prop.h" #include "errmsg.h" #include "srUtils.h" #include "unicode-helper.h" #include "ratelimit.h" #ifndef O_CLOEXEC #define O_CLOEXEC 0 #endif #ifndef O_NOCTTY #define O_NOCTTY 0 #endif #ifndef O_NOFOLLOW #define O_NOFOLLOW 0 #endif MODULE_TYPE_INPUT; MODULE_TYPE_NOKEEP; MODULE_CNFNAME("imjournal") /* Module static data */ DEF_IMOD_STATIC_DATA; DEFobjCurrIf(datetime) DEFobjCurrIf(glbl) DEFobjCurrIf(parser) DEFobjCurrIf(prop) DEFobjCurrIf(net) DEFobjCurrIf(ruleset) DEFobjCurrIf(statsobj) /* Module static data */ typedef struct journal_etry_s { pthread_t tid; /* the worker's thread ID */ ruleset_t *pBindRuleset; char *stateFile; struct journalContext_s *journalContext; struct journal_etry_s *next; } journal_etry_t; static journal_etry_t *journal_root = NULL; static int n_journal = 0; struct modConfData_s { rsconf_t *pConf; instanceConf_t *root, *tail; char *pszNamespace; }; static struct configSettings_s { char *stateFile; int fCreateMode; /* default mode to use when creating new files, e.g. stateFile */ int iPersistStateInterval; int ratelimitInterval; int ratelimitBurst; char *pszRatelimitName; int bIgnorePrevious; int bIgnoreNonValidStatefile; int iDfltSeverity; int iDfltFacility; int bUseJnlPID; char *usePid; int bWorkAroundJournalBug; /* deprecated, left for backwards compatibility only */ int bFsync; int bRemote; char *dfltTag; } cs; static rsRetVal facilityHdlr(uchar **pp, void *pVal); /* module-global parameters */ static struct cnfparamdescr modpdescr[] = {{"statefile", eCmdHdlrGetWord, 0}, {"filecreatemode", eCmdHdlrFileCreateMode, 0}, {"ratelimit.interval", eCmdHdlrInt, 0}, {"ratelimit.burst", eCmdHdlrInt, 0}, {"ratelimit.name", eCmdHdlrString, 0}, {"persiststateinterval", eCmdHdlrInt, 0}, {"ignorepreviousmessages", eCmdHdlrBinary, 0}, {"ignorenonvalidstatefile", eCmdHdlrBinary, 0}, {"defaultseverity", eCmdHdlrSeverity, 0}, {"defaultfacility", eCmdHdlrString, 0}, {"usepidfromsystem", eCmdHdlrBinary, 0}, {"usepid", eCmdHdlrString, 0}, {"workaroundjournalbug", eCmdHdlrBinary, 0}, {"fsync", eCmdHdlrBinary, 0}, {"remote", eCmdHdlrBinary, 0}, {"namespace", eCmdHdlrString, 0}, {"defaulttag", eCmdHdlrGetWord, 0}}; static struct cnfparamblk modpblk = {CNFPARAMBLK_VERSION, sizeof(modpdescr) / sizeof(struct cnfparamdescr), modpdescr}; /* input instance parameters */ static struct cnfparamdescr inppdescr[] = { {"ruleset", eCmdHdlrString, 0}, {"main", eCmdHdlrBinary, 0}, }; static struct cnfparamblk inppblk = {CNFPARAMBLK_VERSION, sizeof(inppdescr) / sizeof(struct cnfparamdescr), inppdescr}; struct instanceConf_s { struct instanceConf_s *next; char *stateFile; uchar *pszBindRuleset; ruleset_t *pBindRuleset; /* ruleset to bind listener to (use system default if unspecified) */ int bMain; }; #include "im-helper.h" /* must be included AFTER the type definitions! */ #define DFLT_persiststateinterval 10 #define DFLT_SEVERITY pri2sev(LOG_NOTICE) #define DFLT_FACILITY pri2fac(LOG_USER) #define DFLT_TAG "journal" #define FUTURE_JOURNAL_WARN_SKEW_USEC (60ULL * 1000000ULL) static int bLegacyCnfModGlobalsPermitted = 1; /* are legacy module-global config parameters permitted? */ static prop_t *pInputName = NULL; /* there is only one global inputName for all messages generated by this module */ static prop_t *pLocalHostIP = NULL; /* a pseudo-constant propterty for 127.0.0.1 */ static const char *pidFieldName; /* read-only after startup */ static int bPidFallBack; static ratelimit_t *ratelimiter = NULL; static struct { statsobj_t *stats; STATSCOUNTER_DEF(ctrSubmitted, mutCtrSubmitted) STATSCOUNTER_DEF(ctrRead, mutCtrRead); STATSCOUNTER_DEF(ctrDiscarded, mutCtrDiscarded); STATSCOUNTER_DEF(ctrFailed, mutCtrFailed); STATSCOUNTER_DEF(ctrRotations, mutCtrRotations); STATSCOUNTER_DEF(ctrRecoveryAttempts, mutCtrRecoveryAttempts); uint64 ratelimitDiscardedInInterval; uint64 diskUsageBytes; DEF_ATOMIC_HELPER_MUT64(mut_diskUsageBytes); } statsCounter; struct journalContext_s { /* structure encapsulating all the journald_API-related stuff */ sd_journal *j; /* main object encapsulating journal for us, has to be used in every sd_journal*() call */ sbool reloaded; /* we have reloaded journal after detecting rotation */ sbool atHead; /* true if we are at start of journal (no seek was done) */ sbool warnedFutureJournalTime; /* warning already emitted for journal entries ahead of current system time */ uint64_t nextFutureJournalProbeUsec; /* next permitted future-time diagnostic probe */ char *cursor; /* should point to last valid journald entry we processed */ }; #define MAX_JOURNAL 8 static struct journalContext_s journalContextArray[MAX_JOURNAL] = { {NULL, 0, 1, 0, 0, NULL}, {NULL, 0, 1, 0, 0, NULL}, {NULL, 0, 1, 0, 0, NULL}, {NULL, 0, 1, 0, 0, NULL}, {NULL, 0, 1, 0, 0, NULL}, {NULL, 0, 1, 0, 0, NULL}, {NULL, 0, 1, 0, 0, NULL}, {NULL, 0, 1, 0, 0, NULL}, }; static modConfData_t *loadModConf = NULL; /* modConf ptr to use for the current load process */ static modConfData_t *runModConf = NULL; /* modConf ptr to use for run process */ #define J_PROCESS_PERIOD 1024 /* Call sd_journal_process() every 1,024 records */ static rsRetVal persistJournalState(struct journalContext_s *journalContext, char *stateFile); static rsRetVal loadJournalState(struct journalContext_s *journalContext, char *stateFile); /** * Create a unique temporary state file next to the final cursor file. * * The temp file must live in the target directory so that the later rename() * stays atomic on the same filesystem. Callers receive ownership of *tmpPath * and must free it. On success, *fd is an open descriptor positioned at the * beginning of an empty file created with the configured mode and protected * against symlink traversal. */ static rsRetVal openStateFileTemp(const char *stateFile, char **tmpPath, int *fd) { struct timespec ts; char *candidate = NULL; int localFd = -1; int attempt; DEFiRet; if (clock_gettime(CLOCK_REALTIME, &ts) != 0) { ts.tv_sec = time(NULL); ts.tv_nsec = 0; } for (attempt = 0; attempt < 100; ++attempt) { free(candidate); candidate = NULL; if (asprintf(&candidate, "%s.tmp.%ld.%ld.%d", stateFile, (long)getpid(), (long)ts.tv_nsec, attempt) == -1) { LogError(0, RS_RET_OUT_OF_MEMORY, "imjournal: asprintf failed\n"); ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } localFd = open(candidate, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW, cs.fCreateMode); if (localFd >= 0) { *tmpPath = candidate; *fd = localFd; candidate = NULL; FINALIZE; } if (errno != EEXIST) { LogError(errno, RS_RET_FILE_OPEN_ERROR, "imjournal: open() failed for path: '%s'", candidate); ABORT_FINALIZE(RS_RET_FILE_OPEN_ERROR); } } LogError(0, RS_RET_FILE_OPEN_ERROR, "imjournal: could not create a unique temp state file for '%s'", stateFile); ABORT_FINALIZE(RS_RET_FILE_OPEN_ERROR); finalize_it: free(candidate); RETiRet; } /** * Fsync the directory that contains the persisted cursor file. * * After the caller has fsync'd the file contents and renamed the temporary * file into place, syncing the parent directory is required to make the name * update durable across power loss. The path may be absolute or relative. */ static rsRetVal fsyncStateFileParentDir(const char *stateFile) { DIR *dir = NULL; char *dirPath = NULL; const char *slash; int dfd; DEFiRet; slash = strrchr(stateFile, '/'); if (slash == NULL) { CHKmalloc(dirPath = strdup(".")); } else if (slash == stateFile) { CHKmalloc(dirPath = strdup("/")); } else { const size_t len = (size_t)(slash - stateFile); CHKmalloc(dirPath = strndup(stateFile, len)); } if ((dir = opendir(dirPath)) == NULL) { LogError(errno, RS_RET_IO_ERROR, "imjournal: failed to open '%s' directory", dirPath); ABORT_FINALIZE(RS_RET_IO_ERROR); } dfd = dirfd(dir); if (dfd < 0) { LogError(errno, RS_RET_IO_ERROR, "imjournal: failed to get '%s' directory fd", dirPath); ABORT_FINALIZE(RS_RET_IO_ERROR); } if (fsync(dfd) != 0) { LogError(errno, RS_RET_IO_ERROR, "imjournal: fsync on '%s' failed", dirPath); ABORT_FINALIZE(RS_RET_IO_ERROR); } finalize_it: if (dir != NULL) { closedir(dir); } free(dirPath); RETiRet; } static int openJournalHandle(sd_journal **j) { const int flags = cs.bRemote ? 0 : SD_JOURNAL_LOCAL_ONLY; const char *const pszNamespace = runModConf == NULL ? NULL : runModConf->pszNamespace; if (pszNamespace != NULL) { #ifdef HAVE_SD_JOURNAL_OPEN_NAMESPACE return sd_journal_open_namespace(j, pszNamespace, flags); #else return -ENOSYS; #endif } return sd_journal_open(j, flags); } static rsRetVal openJournal(struct journalContext_s *journalContext) { int r; DEFiRet; if (journalContext->j) { LogMsg(0, RS_RET_OK_WARN, LOG_WARNING, "imjournal: opening journal when already opened.\n"); } if ((r = openJournalHandle(&journalContext->j)) < 0) { const char *const pszNamespace = runModConf == NULL ? NULL : runModConf->pszNamespace; if (pszNamespace != NULL) { LogError(-r, RS_RET_IO_ERROR, "imjournal: sd_journal_open_namespace() failed for namespace '%s'", pszNamespace); } else { LogError(-r, RS_RET_IO_ERROR, "imjournal: sd_journal_open() failed"); } ABORT_FINALIZE(RS_RET_IO_ERROR); } if ((r = sd_journal_set_data_threshold(journalContext->j, glbl.GetMaxLine(runModConf->pConf))) < 0) { LogError(-r, RS_RET_IO_ERROR, "imjournal: sd_journal_set_data_threshold() failed"); ABORT_FINALIZE(RS_RET_IO_ERROR); } journalContext->atHead = 1; finalize_it: RETiRet; } /* trySave shoulod only be true if there is no journald error preceeding this call */ static void closeJournal(struct journalContext_s *journalContext) { sd_journal *j_to_close = journalContext->j; if (!j_to_close) { LogMsg(0, RS_RET_OK_WARN, LOG_WARNING, "imjournal: closing NULL journal.\n"); } else { journalContext->j = NULL; /* sd_journal_close() is a cancellation point. If we are cancelled * here, journalContext->j is already NULL, preventing double-free. */ sd_journal_close(j_to_close); } } static int journalGetData(struct journalContext_s *journalContext, const char *field, const void **data, size_t *length) { int ret; ret = sd_journal_get_data(journalContext->j, field, data, length); if (ret == -EADDRNOTAVAIL) { LogError(-ret, RS_RET_ERR, "imjournal: Tried to get data without a 'next' call.\n"); if ((ret = sd_journal_next(journalContext->j)) < 0) { LogError(-ret, RS_RET_ERR, "imjournal: sd_journal_next() failed\n"); } else { ret = sd_journal_get_data(journalContext->j, field, data, length); } } return ret; } /* ugly workaround to handle facility numbers; values * derived from names need to be eight times smaller, * i.e.: 0..23 */ static rsRetVal facilityHdlr(uchar **pp, void *pVal) { DEFiRet; char *p; skipWhiteSpace(pp); p = (char *)*pp; if (isdigit((int)*p)) { *((int *)pVal) = (int)strtol(p, (char **)pp, 10); } else { int len; syslogName_t *c; for (len = 0; p[len] && !isspace((int)p[len]); len++) /* noop */ ; for (c = syslogFacNames; c->c_name; c++) { if (!strncasecmp(p, (char *)c->c_name, len)) { *((int *)pVal) = pri2fac(c->c_val); break; } } *pp += len; } RETiRet; } /* Currently just replaces '\0' with ' '. Not doing so would cause * the value to be truncated. New space is allocated for the resulting * string. */ static rsRetVal sanitizeValue(const char *in, size_t len, char **out) { char *buf, *p; DEFiRet; CHKmalloc(p = buf = malloc(len + 1)); memcpy(buf, in, len); buf[len] = '\0'; while ((p = memchr(p, '\0', len + buf - p)) != NULL) { *p++ = ' '; } *out = buf; finalize_it: RETiRet; } /* Read JSON part of single journald message and return it as JSON object */ static rsRetVal readJSONfromJournalMsg(struct journalContext_s *journalContext, struct fjson_object **json) { DEFiRet; const void *get; const void *equal_sign; struct fjson_object *jval; size_t l; long prefixlen = 0; CHKmalloc(*json = fjson_object_new_object()); SD_JOURNAL_FOREACH_DATA(journalContext->j, get, l) { char *data; char *name; /* locate equal sign, this is always present */ equal_sign = memchr(get, '=', l); /* ... but we know better than to trust the specs */ if (equal_sign == NULL) { LogError(0, RS_RET_ERR, "SD_JOURNAL_FOREACH_DATA()" " returned a malformed field without '=' (length %zu)", l); continue; /* skip the entry */ } /* get length of journal data prefix */ prefixlen = ((char *)equal_sign - (char *)get); CHKmalloc(name = strndup(get, prefixlen)); prefixlen++; /* remove '=' */ CHKiRet_Hdlr(sanitizeValue(((const char *)get) + prefixlen, l - prefixlen, &data)) { free(name); FINALIZE; } /* and save them to json object */ jval = fjson_object_new_string((char *)data); fjson_object_object_add(*json, name, jval); free(data); free(name); } finalize_it: if (iRet != RS_RET_OK) { if (*json != NULL) { fjson_object_put(*json); *json = NULL; } } RETiRet; } /* Try to obtain current journald cursor and save it to journalContext struct. */ static rsRetVal updateJournalCursor(struct journalContext_s *journalContext) { DEFiRet; char *c = NULL; int r; if ((r = sd_journal_get_cursor(journalContext->j, &c)) < 0) { LogError(-r, RS_RET_ERR, "imjournal: Could not get journald cursor!\n"); ABORT_FINALIZE(RS_RET_ERR); } /* save journal cursor (at this point we can be sure it is valid) */ free(journalContext->cursor); journalContext->cursor = c; finalize_it: RETiRet; } /* enqueue the the journal message into the message queue. * The provided msg string is not freed - thus must be done * by the caller. */ static rsRetVal enqMsg(uchar *msg, uchar *pszTag, int iFacility, int iSeverity, struct timeval *tp, struct fjson_object *json, int sharedJsonProperties, ruleset_t *pBindRuleset) { struct syslogTime st; smsg_t *pMsg = NULL; size_t len; DEFiRet; assert(msg != NULL); assert(pszTag != NULL); if (tp == NULL) { CHKiRet(msgConstruct(&pMsg)); } else { datetime.timeval2syslogTime(tp, &st, TIME_IN_LOCALTIME); CHKiRet(msgConstructWithTime(&pMsg, &st, tp->tv_sec)); } MsgSetFlowControlType(pMsg, eFLOWCTL_LIGHT_DELAY); MsgSetInputName(pMsg, pInputName); len = strlen((char *)msg); MsgSetRawMsg(pMsg, (char *)msg, len); if (len > 0) parser.SanitizeMsg(pMsg); MsgSetMSGoffs(pMsg, 0); /* we do not have a header... */ MsgSetRcvFrom(pMsg, glbl.GetLocalHostNameProp()); MsgSetRcvFromIP(pMsg, pLocalHostIP); MsgSetHOSTNAME(pMsg, glbl.GetLocalHostName(), ustrlen(glbl.GetLocalHostName())); MsgSetTAG(pMsg, pszTag, ustrlen(pszTag)); if (pBindRuleset != NULL) { MsgSetRuleset(pMsg, pBindRuleset); } pMsg->iFacility = iFacility; pMsg->iSeverity = iSeverity; if (json != NULL) { iRet = msgAddJSON(pMsg, (uchar *)"!", json, 0, sharedJsonProperties); json = NULL; CHKiRet(iRet); } CHKiRet(ratelimitAddMsg(ratelimiter, NULL, pMsg)); STATSCOUNTER_INC(statsCounter.ctrSubmitted, statsCounter.mutCtrSubmitted); finalize_it: if (iRet == RS_RET_DISCARDMSG) { STATSCOUNTER_INC(statsCounter.ctrDiscarded, statsCounter.mutCtrDiscarded); } else if (iRet != RS_RET_OK) { LogError(0, RS_RET_ERR, "imjournal: error during enqMsg().\n"); if (pMsg != NULL) { msgDestruct(&pMsg); } if (json != NULL) { fjson_object_put(json); } } RETiRet; } /* Read journal log while data are available, each read() reads one journald record. */ static rsRetVal readjournal(struct journalContext_s *journalContext, ruleset_t *pBindRuleset) { DEFiRet; struct timeval tv; uint64_t timestamp; struct fjson_object *json = NULL; int r; /* Information from messages */ char *message = NULL; char *sys_iden; char *sys_iden_help = NULL; const void *get; const void *pidget; size_t length; size_t pidlength; int severity = cs.iDfltSeverity; int facility = cs.iDfltFacility; /* Get message text */ r = journalGetData(journalContext, "MESSAGE", &get, &length); if (r < 0) { LogMsg(-r, RS_RET_OK_WARN, LOG_WARNING, "imjournal: failed to retrieve 'MESSAGE' field from journal entry " ", submitting empty message"); CHKmalloc(message = strdup("")); } else { CHKiRet(sanitizeValue(((const char *)get) + 8, length - 8, &message)); } STATSCOUNTER_INC(statsCounter.ctrRead, statsCounter.mutCtrRead); /* Get message severity ("priority" in journald's terminology) */ if (journalGetData(journalContext, "PRIORITY", &get, &length) >= 0) { /* value is "PRIORITY=x", so we need to skip 9 characters */ if (length > 9) { char prio_buf[12]; char *p; size_t val_len = length - 9; if (val_len >= sizeof(prio_buf)) { val_len = sizeof(prio_buf) - 1; } memcpy(prio_buf, (const char *)get + 9, val_len); prio_buf[val_len] = '\0'; long priority = strtol(prio_buf, &p, 10); /* check for conversion errors */ if (p > prio_buf) { if (priority < 0 || 7 < priority) { /* Only log invalid values if they are numeric. * We use LOG_INFO to avoid flooding the log if there is a * persistent issue with the journal data. */ LogMsg(0, RS_RET_OK, LOG_INFO, "imjournal: the value of the 'PRIORITY' field is " "out of bounds: %ld, resetting", priority); severity = cs.iDfltSeverity; } else { severity = (int)priority; } } else { /* We silently ignore non-parsable priority values. * This is robust against unexpected journal data formats. */ severity = cs.iDfltSeverity; } } else { /* We silently ignore unexpected length. * This prevents log flooding if the journal data is malformed. */ severity = cs.iDfltSeverity; } } /* Get syslog facility */ if (journalGetData(journalContext, "SYSLOG_FACILITY", &get, &length) >= 0) { // Note: the journal frequently contains invalid facilities! if (length == 17 || length == 18) { facility = ((char *)get)[16] - '0'; if (length == 18) { facility *= 10; facility += ((char *)get)[17] - '0'; } if (facility < 0 || 23 < facility) { DBGPRINTF( "The value of the 'FACILITY' field is " "out of bounds: %d, resetting\n", facility); facility = cs.iDfltFacility; } } else { DBGPRINTF( "The value of the 'FACILITY' field has an " "unexpected length: %zu\n", length); } } /* Get message identifier, client pid and add ':' */ if (journalGetData(journalContext, "SYSLOG_IDENTIFIER", &get, &length) >= 0) { CHKiRet(sanitizeValue(((const char *)get) + 18, length - 18, &sys_iden)); } else if (journalGetData(journalContext, "_COMM", &get, &length) >= 0) { CHKiRet(sanitizeValue(((const char *)get) + 6, length - 6, &sys_iden)); } else { CHKmalloc(sys_iden = strdup(cs.dfltTag)); } /* trying to get PID, default is "SYSLOG_PID" property */ if (journalGetData(journalContext, pidFieldName, &pidget, &pidlength) >= 0) { char *sys_pid; int val_ofs; val_ofs = strlen(pidFieldName) + 1; /* name + '=' */ CHKiRet_Hdlr(sanitizeValue(((const char *)pidget) + val_ofs, pidlength - val_ofs, &sys_pid)) { free(sys_iden); FINALIZE; } r = asprintf(&sys_iden_help, "%s[%s]:", sys_iden, sys_pid); free(sys_pid); } else { /* this is fallback, "SYSLOG_PID" doesn't exist so trying to get "_PID" property */ if (bPidFallBack && journalGetData(journalContext, "_PID", &pidget, &pidlength) >= 0) { char *sys_pid; int val_ofs; val_ofs = strlen("_PID") + 1; /* name + '=' */ CHKiRet_Hdlr(sanitizeValue(((const char *)pidget) + val_ofs, pidlength - val_ofs, &sys_pid)) { free(sys_iden); FINALIZE; } r = asprintf(&sys_iden_help, "%s[%s]:", sys_iden, sys_pid); free(sys_pid); } else { /* there is no PID property available */ r = asprintf(&sys_iden_help, "%s:", sys_iden); } } free(sys_iden); if (-1 == r) { STATSCOUNTER_INC(statsCounter.ctrFailed, statsCounter.mutCtrFailed); ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } CHKiRet(readJSONfromJournalMsg(journalContext, &json)); /* calculate timestamp */ if (sd_journal_get_realtime_usec(journalContext->j, ×tamp) >= 0) { tv.tv_sec = timestamp / 1000000; tv.tv_usec = timestamp % 1000000; } CHKiRet(updateJournalCursor(journalContext)); /* submit message */ iRet = enqMsg((uchar *)message, (uchar *)sys_iden_help, facility, severity, &tv, json, 0, pBindRuleset); json = NULL; if (iRet == RS_RET_DISCARDMSG) { iRet = RS_RET_OK; } CHKiRet(iRet); finalize_it: if (iRet != RS_RET_OK) { if (json != NULL) { fjson_object_put(json); } } free(sys_iden_help); free(message); RETiRet; } /* This function saves journal cursor into state file. * It must be checked that stateFile is configured prior to calling this. */ static rsRetVal persistJournalState(struct journalContext_s *journalContext, char *stateFile) { DEFiRet; char *tmp_sf = NULL; int fd = -1; size_t len; ssize_t wr_ret; DBGPRINTF("Persisting journal position, cursor: %s, at head? %d\n", journalContext->cursor, journalContext->atHead); /* first check that we have valid cursor */ if (!journalContext->cursor) { DBGPRINTF("Journal cursor is not valid, ok...\n"); ABORT_FINALIZE(RS_RET_OK); } CHKiRet(openStateFileTemp(stateFile, &tmp_sf, &fd)); len = strlen(journalContext->cursor); wr_ret = write(fd, journalContext->cursor, len); if (wr_ret != (ssize_t)len) { LogError(errno, RS_RET_IO_ERROR, "imjournal: failed to save cursor to: '%s'," "write returned %zd, expected %zu", stateFile, wr_ret, len); ABORT_FINALIZE(RS_RET_IO_ERROR); } if (cs.bFsync) { if (fsync(fd) != 0) { LogError(errno, RS_RET_IO_ERROR, "imjournal: fsync on '%s' failed", tmp_sf); ABORT_FINALIZE(RS_RET_IO_ERROR); } } if (close(fd) == -1) { LogError(errno, RS_RET_IO_ERROR, "imjournal: close() failed for path: '%s'", tmp_sf); fd = -1; ABORT_FINALIZE(RS_RET_IO_ERROR); } fd = -1; /* change the name of the file to the configured one */ if (rename(tmp_sf, stateFile) < 0) { LogError(errno, iRet, "imjournal: rename() failed for new path: '%s'", stateFile); ABORT_FINALIZE(RS_RET_IO_ERROR); } if (cs.bFsync) { CHKiRet(fsyncStateFileParentDir(stateFile)); } DBGPRINTF("Persisted journal to '%s'\n", stateFile); finalize_it: if (fd != -1) { if (close(fd) == -1) { LogError(errno, RS_RET_IO_ERROR, "imjournal: close() failed for path: '%s'", tmp_sf); iRet = RS_RET_IO_ERROR; } } if (iRet != RS_RET_OK && tmp_sf != NULL) { unlink(tmp_sf); } free(tmp_sf); RETiRet; } static rsRetVal skipOldMessages(struct journalContext_s *journalContext); static rsRetVal restoreJournalPosition(struct journalContext_s *journalContext, char *stateFile) { DEFiRet; int r; /* outside error scenarios we should always have a cursor available at this point */ if (!journalContext->cursor) { if (stateFile) { iRet = loadJournalState(journalContext, stateFile); } else if (cs.bIgnorePrevious) { /* Seek to the very end of the journal and ignore all older messages. */ iRet = skipOldMessages(journalContext); } FINALIZE; } if ((r = sd_journal_seek_cursor(journalContext->j, journalContext->cursor)) != 0) { LogError(-r, RS_RET_ERR, "imjournal: " "couldn't seek to cursor `%s'\n", journalContext->cursor); LogMsg(0, RS_RET_OK, LOG_NOTICE, "imjournal: saved cursor is unavailable, seeking to the head of journal\n"); if ((r = sd_journal_seek_head(journalContext->j)) < 0) { LogError(-r, RS_RET_ERR, "imjournal: " "sd_journal_seek_head() failed while recovering cursor position\n"); iRet = RS_RET_ERR; FINALIZE; } journalContext->atHead = 1; } else { journalContext->atHead = 0; } finalize_it: RETiRet; } static rsRetVal handleRotation(struct journalContext_s *journalContext, char *stateFile) { DEFiRet; LogMsg(0, RS_RET_OK, LOG_NOTICE, "imjournal: journal files changed, reloading...\n"); STATSCOUNTER_INC(statsCounter.ctrRotations, statsCounter.mutCtrRotations); CHKiRet(restoreJournalPosition(journalContext, stateFile)); finalize_it: RETiRet; } #define POLL_TIMEOUT 900000 /* timeout for poll is 900ms */ static rsRetVal reopenJournal(struct journalContext_s *journalContext) { DEFiRet; int waitRet; closeJournal(journalContext); CHKiRet(openJournal(journalContext)); /* The first sd_journal_wait() on a newly opened handle intentionally * returns immediately after installing its watches. Consume that initial * state here; otherwise an INVALIDATE-triggered reopen causes every fresh * handle to report another INVALIDATE and imjournal busy-loops forever. * Cursor restoration follows this call, so sequential delivery still * resumes from the last processed entry. */ waitRet = sd_journal_wait(journalContext->j, 0); if (waitRet < 0) { LogError(-waitRet, RS_RET_ERR, "imjournal: sd_journal_wait() failed while initializing reopened journal"); closeJournal(journalContext); ABORT_FINALIZE(RS_RET_ERR); } finalize_it: RETiRet; } static rsRetVal pollJournal(struct journalContext_s *journalContext, char *stateFile) { DEFiRet; int err; err = sd_journal_wait(journalContext->j, POLL_TIMEOUT); if (err == SD_JOURNAL_INVALIDATE) { const int processRet = sd_journal_process(journalContext->j); if (processRet < 0) { LogError(-processRet, RS_RET_ERR, "imjournal: sd_journal_process() failed during rotation handling"); ABORT_FINALIZE(RS_RET_ERR); } CHKiRet(reopenJournal(journalContext)); CHKiRet(handleRotation(journalContext, stateFile)); } else if (err < 0) { LogError(-err, RS_RET_ERR, "imjournal: sd_journal_wait() failed"); ABORT_FINALIZE(RS_RET_ERR); } finalize_it: RETiRet; } static rsRetVal skipOldMessages(struct journalContext_s *journalContext) { int r; DEFiRet; if ((r = sd_journal_seek_tail(journalContext->j)) < 0) { LogError(-r, RS_RET_ERR, "imjournal: sd_journal_seek_tail() failed"); ABORT_FINALIZE(RS_RET_ERR); } journalContext->atHead = 0; if ((r = sd_journal_previous(journalContext->j)) < 0) { LogError(-r, RS_RET_ERR, "imjournal: sd_journal_previous() failed"); ABORT_FINALIZE(RS_RET_ERR); } finalize_it: RETiRet; } static void warnIfNewestJournalEntryIsInFuture(struct journalContext_s *journalContext) { sd_journal *probe = NULL; struct timespec now; uint64_t tail_usec; uint64_t now_usec; int r; if (journalContext->warnedFutureJournalTime) { return; } if (clock_gettime(CLOCK_REALTIME, &now) != 0) { DBGPRINTF("imjournal: future-time probe clock_gettime() failed: %d\n", errno); goto done; } now_usec = ((uint64_t)now.tv_sec * 1000000ULL) + ((uint64_t)now.tv_nsec / 1000ULL); if (now_usec < journalContext->nextFutureJournalProbeUsec) { if (journalContext->nextFutureJournalProbeUsec - now_usec <= FUTURE_JOURNAL_WARN_SKEW_USEC) { return; } DBGPRINTF("imjournal: future-time probe detected backward clock jump; resetting throttle\n"); } journalContext->nextFutureJournalProbeUsec = now_usec + FUTURE_JOURNAL_WARN_SKEW_USEC; r = openJournalHandle(&probe); if (r < 0) { DBGPRINTF("imjournal: future-time probe journal open failed: %d\n", r); goto done; } r = sd_journal_seek_tail(probe); if (r < 0) { DBGPRINTF("imjournal: future-time probe sd_journal_seek_tail() failed: %d\n", r); goto done; } r = sd_journal_previous(probe); if (r <= 0) { if (r < 0) { DBGPRINTF("imjournal: future-time probe sd_journal_previous() failed: %d\n", r); } goto done; } r = sd_journal_get_realtime_usec(probe, &tail_usec); if (r < 0) { DBGPRINTF("imjournal: future-time probe sd_journal_get_realtime_usec() failed: %d\n", r); goto done; } if (tail_usec > now_usec + FUTURE_JOURNAL_WARN_SKEW_USEC) { LogMsg(0, RS_RET_OK_WARN, LOG_WARNING, "imjournal: sd_journal_next() reports no new entries, " "but the newest journal entry timestamp is %llu seconds ahead of current system time; " "journal delivery may remain stalled until system time catches up or the journal is repaired", (unsigned long long)((tail_usec - now_usec) / 1000000ULL)); journalContext->warnedFutureJournalTime = 1; } done: if (probe != NULL) { sd_journal_close(probe); } } /* This function loads a journal cursor from the state file. */ static rsRetVal loadJournalState(struct journalContext_s *journalContext, char *stateFile) { DEFiRet; struct stat st; int r; int fd = -1; FILE *r_sf; DBGPRINTF("Loading journal position, at head? %d, reloaded? %d\n", journalContext->atHead, journalContext->reloaded); fd = open(stateFile, O_RDONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW); if (fd == -1 && errno == ENOENT) { if (cs.bIgnorePrevious) { /* Seek to the very end of the journal and ignore all older messages. */ skipOldMessages(journalContext); } LogMsg(errno, RS_RET_FILE_NOT_FOUND, LOG_NOTICE, "imjournal: No statefile exists, " "%s will be created (ignore if this is first run)", stateFile); FINALIZE; } if (fd == -1) { LogError(errno, RS_RET_FOPEN_FAILURE, "imjournal: open on state file `%s' failed\n", stateFile); if (cs.bIgnorePrevious) { /* Seek to the very end of the journal and ignore all older messages. */ skipOldMessages(journalContext); } FINALIZE; } if (fstat(fd, &st) != 0) { LogError(errno, RS_RET_IO_ERROR, "imjournal: fstat on state file `%s' failed\n", stateFile); iRet = RS_RET_IO_ERROR; FINALIZE; } if (!S_ISREG(st.st_mode)) { LogError(0, RS_RET_IO_ERROR, "imjournal: state file `%s' is not a regular file\n", stateFile); iRet = RS_RET_IO_ERROR; FINALIZE; } if ((r_sf = fdopen(fd, "rb")) != NULL) { fd = -1; char readCursor[128 + 1]; if (fscanf(r_sf, "%128s\n", readCursor) != EOF) { if (sd_journal_seek_cursor(journalContext->j, readCursor) != 0) { LogError(0, RS_RET_ERR, "imjournal: " "couldn't seek to cursor `%s'\n", readCursor); iRet = RS_RET_ERR; } else { journalContext->atHead = 0; char *tmp_cursor = NULL; sd_journal_next(journalContext->j); /* * This is resolving the situation when system is after reboot and boot_id * doesn't match so cursor pointing into "future". * Usually sd_journal_next jump to head of journal due to journal aproximation, * but when system time goes backwards and cursor is still invalid, rsyslog stops logging. * We use sd_journal_get_cursor to validate our cursor. * When cursor is invalid we are trying to jump to the head of journal * This problem with time should not affect persistent journal, * but if cursor has been intentionally compromised it could stop logging even * with persistent journal. * */ if ((r = sd_journal_get_cursor(journalContext->j, &tmp_cursor)) < 0) { LogError(-r, RS_RET_IO_ERROR, "imjournal: " "loaded invalid cursor, seeking to the head of journal\n"); if ((r = sd_journal_seek_head(journalContext->j)) < 0) { LogError(-r, RS_RET_ERR, "imjournal: " "sd_journal_seek_head() failed, when cursor is invalid\n"); iRet = RS_RET_ERR; } journalContext->atHead = 1; } free(tmp_cursor); } } else { LogError(0, RS_RET_IO_ERROR, "imjournal: " "fscanf on state file `%s' failed\n", stateFile); iRet = RS_RET_IO_ERROR; } fclose(r_sf); if (iRet != RS_RET_OK && cs.bIgnoreNonValidStatefile) { /* ignore state file errors */ iRet = RS_RET_OK; LogError(0, NO_ERRCODE, "imjournal: ignoring invalid state file %s", stateFile); if (cs.bIgnorePrevious) { skipOldMessages(journalContext); } } } else { LogError(errno, RS_RET_FOPEN_FAILURE, "imjournal: fdopen on state file `%s' failed\n", stateFile); if (cs.bIgnorePrevious) { /* Seek to the very end of the journal and ignore all older messages. */ skipOldMessages(journalContext); } } finalize_it: if (fd != -1) { close(fd); } RETiRet; } static rsRetVal tryRecover(struct journalContext_s *journalContext, char *stateFile) { DEFiRet; do { LogMsg(0, RS_RET_OK, LOG_INFO, "imjournal: trying to recover from journal error"); STATSCOUNTER_INC(statsCounter.ctrRecoveryAttempts, statsCounter.mutCtrRecoveryAttempts); srSleep(0, 200000); // do not hammer machine with too-frequent retries iRet = reopenJournal(journalContext); if (iRet == RS_RET_OK) { iRet = restoreJournalPosition(journalContext, stateFile); } if (iRet == RS_RET_OK) { FINALIZE; } LogError(0, iRet, "imjournal: journal recovery attempt failed, will retry"); } while (glbl.GetGlobalInputTermState() == 0); LogError(0, iRet, "imjournal: journal recovery stopped before success because shutdown was requested"); finalize_it: RETiRet; } static rsRetVal addListner(instanceConf_t *inst, u_int8_t index) { DEFiRet; if (index >= MAX_JOURNAL) { iRet = RS_RET_NO_MORE_DATA; RETiRet; } journal_etry_t *etry = NULL; CHKmalloc(etry = (journal_etry_t *)calloc(1, sizeof(journal_etry_t))); etry->journalContext = &journalContextArray[index]; if (inst) { etry->pBindRuleset = inst->pBindRuleset; etry->stateFile = inst->stateFile; } /* Link into the global list only after success is guaranteed */ etry->next = journal_root; journal_root = etry; ++n_journal; finalize_it: if (iRet != RS_RET_OK) { LogError(0, NO_ERRCODE, "imjournal: error %d trying to add listener", iRet); free(etry); } RETiRet; } static rsRetVal doRun(journal_etry_t const *etry) { DEFiRet; uint64_t count = 0; char *stateFile = cs.stateFile; if (etry->stateFile) { stateFile = etry->stateFile; } if (stateFile) { /* Load our position in the journal from the state file. */ CHKiRet(loadJournalState(etry->journalContext, stateFile)); } else if (cs.bIgnorePrevious) { /* Seek to the very end of the journal and ignore all older messages. */ skipOldMessages(etry->journalContext); } if (cs.dfltTag == NULL) { cs.dfltTag = strdup(DFLT_TAG); } if (cs.usePid && (strcmp(cs.usePid, "system") == 0)) { pidFieldName = "_PID"; bPidFallBack = 0; } else if (cs.usePid && (strcmp(cs.usePid, "syslog") == 0)) { pidFieldName = "SYSLOG_PID"; bPidFallBack = 0; } else { pidFieldName = "SYSLOG_PID"; bPidFallBack = 1; if (cs.usePid && (strcmp(cs.usePid, "both") != 0)) { LogError(0, RS_RET_OK, "option \"usepid\"" " should contain one of system|syslog|both and no '%s'", cs.usePid); } } /* this is an endless loop - it is terminated when the thread is * signalled to do so. This, however, is handled by the framework. */ while (glbl.GetGlobalInputTermState() == 0) { int r; /* read journal entries until we are at the end of the journal */ while ((r = sd_journal_next(etry->journalContext->j)) > 0 && glbl.GetGlobalInputTermState() == 0) { /* We use sd_journal_next to move the read pointer forward by one entry. * However, this does not always ensure that the cursor advances to the next * entry, particularly after a journal rotation. If sd_journal_test_cursor() * returns 1, indicating the current entry matches the specified cursor, * we need to manually advance the cursor. This is because, after calling sd_journal_next, * the cursor should point to a new entry; otherwise, we read the same entry twice. */ if (etry->journalContext->cursor != NULL) { int test = sd_journal_test_cursor(etry->journalContext->j, etry->journalContext->cursor); if (test == 1) { DBGPRINTF("sd_journal_next did not move cursor, skipping message\n"); continue; } else if (test < 0) { LogError(-test, RS_RET_ERR, "imjournal: sd_journal_test_cursor() failed"); CHKiRet(tryRecover(etry->journalContext, stateFile)); continue; } } /* * update journal disk usage before reading the new message. */ uint64_t usage; const int e = sd_journal_get_usage(etry->journalContext->j, &usage); if (e < 0) { LogError(-e, RS_RET_ERR, "imjournal: sd_get_usage() failed"); } else { ATOMIC_STORE_uint64(&statsCounter.diskUsageBytes, &statsCounter.mut_diskUsageBytes, (uint64)usage); } if (readjournal(etry->journalContext, etry->pBindRuleset) != RS_RET_OK) { CHKiRet(tryRecover(etry->journalContext, stateFile)); continue; } count++; etry->journalContext->atHead = 0; etry->journalContext->warnedFutureJournalTime = 0; if (stateFile) { /* TODO: This could use some finer metric. */ if ((count % cs.iPersistStateInterval) == 0) { persistJournalState(etry->journalContext, stateFile); } } } if (r < 0) { LogError(-r, RS_RET_ERR, "imjournal: sd_journal_next() failed"); CHKiRet(tryRecover(etry->journalContext, stateFile)); continue; } /* At this point r == 0, which means no new messages are available. */ if (etry->journalContext->atHead) { LogMsg(0, RS_RET_OK, LOG_WARNING, "imjournal: " "Journal indicates no msgs when positioned at head.\n"); } warnIfNewestJournalEntryIsInFuture(etry->journalContext); /* No new messages, wait for activity. */ if (pollJournal(etry->journalContext, stateFile) != RS_RET_OK) { CHKiRet(tryRecover(etry->journalContext, stateFile)); } } finalize_it: RETiRet; } static void *RunServerThread(void *myself) { DEFiRet; journal_etry_t *const etry = (journal_etry_t *)myself; iRet = doRun(etry); if (iRet != RS_RET_OK) { LogError(0, iRet, "imjournal: error while stopping journal processing; " "rsyslog may hang on shutdown"); } return NULL; } /* support for running multiple servers on multiple threads (one server per thread) */ static void startSrvWrkr(journal_etry_t *const etry) { int r; pthread_attr_t sessThrdAttr; /* We need to temporarily block all signals because the new thread * inherits our signal mask. There is a race if we do not block them * now, and we have seen in practice that this race causes grief. * So we 1. save the current set, 2. block evertyhing, 3. start * threads, and 4 reset the current set to saved state. * rgerhards, 2019-08-16 */ sigset_t sigSet, sigSetSave; sigfillset(&sigSet); /* enable signals we still need */ sigdelset(&sigSet, SIGTTIN); sigdelset(&sigSet, SIGSEGV); pthread_sigmask(SIG_SETMASK, &sigSet, &sigSetSave); pthread_attr_init(&sessThrdAttr); pthread_attr_setstacksize(&sessThrdAttr, 4096 * 1024); r = pthread_create(&etry->tid, &sessThrdAttr, RunServerThread, etry); if (r != 0) { LogError(r, NO_ERRCODE, "imjournal: error creating imjournal thread"); /* we do NOT abort, as other servers may run - after all, we logged an error */ } pthread_attr_destroy(&sessThrdAttr); pthread_sigmask(SIG_SETMASK, &sigSetSave, NULL); } /* stop server worker thread */ static void stopSrvWrkr(journal_etry_t *const etry) { DBGPRINTF("Wait for thread shutdown etry %p\n", etry); pthread_kill(etry->tid, SIGTTIN); pthread_join(etry->tid, NULL); DBGPRINTF("input %p terminated\n", etry); } BEGINrunInput CODESTARTrunInput; if (cs.pszRatelimitName != NULL) { CHKiRet(ratelimitNewFromConfig(&ratelimiter, runModConf->pConf, cs.pszRatelimitName, "imjournal", NULL)); } else { CHKiRet(ratelimitNew(&ratelimiter, "imjournal", NULL)); dbgprintf("imjournal: ratelimiting burst %u, interval %u\n", (unsigned)cs.ratelimitBurst, (unsigned)cs.ratelimitInterval); ratelimitSetLinuxLike(ratelimiter, (unsigned)cs.ratelimitInterval, (unsigned)cs.ratelimitBurst); } ratelimitSetNoTimeCache(ratelimiter); /* handling old "usepidfromsystem" option */ if (cs.bUseJnlPID != -1) { free(cs.usePid); cs.usePid = strdup("system"); LogError(0, RS_RET_DEPRECATED, "\"usepidfromsystem\" is deprecated, use \"usepid\" instead"); } journal_etry_t *etry = journal_root->next; while (etry != NULL) { startSrvWrkr(etry); etry = etry->next; } CHKiRet(doRun(journal_root)); etry = journal_root->next; while (etry != NULL) { stopSrvWrkr(etry); etry = etry->next; } finalize_it: ENDrunInput BEGINbeginCnfLoad CODESTARTbeginCnfLoad; loadModConf = pModConf; pModConf->pConf = pConf; bLegacyCnfModGlobalsPermitted = 1; cs.bIgnoreNonValidStatefile = 1; cs.iPersistStateInterval = DFLT_persiststateinterval; cs.stateFile = NULL; cs.fCreateMode = 0644; cs.ratelimitBurst = -1; cs.ratelimitInterval = -1; cs.pszRatelimitName = NULL; cs.iDfltSeverity = DFLT_SEVERITY; cs.iDfltFacility = DFLT_FACILITY; cs.bUseJnlPID = -1; cs.usePid = NULL; cs.bWorkAroundJournalBug = 1; cs.bFsync = 0; cs.bRemote = 0; pModConf->pszNamespace = NULL; cs.dfltTag = NULL; ENDbeginCnfLoad BEGINendCnfLoad CODESTARTendCnfLoad; /* bad trick to handle old and new style config all in old-style var */ if (cs.stateFile != NULL && cs.stateFile[0] != '/') { char *new_stateFile; if (-1 == asprintf(&new_stateFile, "%s/%s", (char *)glbl.GetWorkDir(loadModConf->pConf), cs.stateFile)) { LogError(0, RS_RET_OUT_OF_MEMORY, "imjournal: asprintf failed\n"); ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } free(cs.stateFile); cs.stateFile = new_stateFile; } finalize_it: ENDendCnfLoad BEGINcheckCnf instanceConf_t *inst; CODESTARTcheckCnf; for (inst = pModConf->root; inst != NULL; inst = inst->next) { std_checkRuleset(pModConf, inst); } if (pModConf->pszNamespace != NULL) { if (pModConf->pszNamespace[0] == '\0') { LogError(0, RS_RET_INVALID_PARAMS, "imjournal: Namespace must not be empty"); ABORT_FINALIZE(RS_RET_INVALID_PARAMS); } if (cs.bRemote) { LogError(0, RS_RET_INVALID_PARAMS, "imjournal: Namespace and Remote cannot be enabled together"); ABORT_FINALIZE(RS_RET_INVALID_PARAMS); } #ifndef HAVE_SD_JOURNAL_OPEN_NAMESPACE LogError(0, RS_RET_NOT_IMPLEMENTED, "imjournal: Namespace requires libsystemd support for sd_journal_open_namespace()"); ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); #endif } finalize_it: ENDcheckCnf BEGINactivateCnf instanceConf_t *inst; instanceConf_t *root_inst = NULL; u_int8_t index = 0; CODESTARTactivateCnf; runModConf = pModConf; /* support statistic gathering */ CHKiRet(statsobj.Construct(&(statsCounter.stats))); CHKiRet(statsobj.SetName(statsCounter.stats, (uchar *)"imjournal")); CHKiRet(statsobj.SetOrigin(statsCounter.stats, (uchar *)"imjournal")); STATSCOUNTER_INIT(statsCounter.ctrSubmitted, statsCounter.mutCtrSubmitted); CHKiRet(statsobj.AddCounter(statsCounter.stats, UCHAR_CONSTANT("submitted"), ctrType_IntCtr, CTR_FLAG_RESETTABLE, &(statsCounter.ctrSubmitted))); STATSCOUNTER_INIT(statsCounter.ctrRead, statsCounter.mutCtrRead); CHKiRet(statsobj.AddCounter(statsCounter.stats, UCHAR_CONSTANT("read"), ctrType_IntCtr, CTR_FLAG_RESETTABLE, &(statsCounter.ctrRead))); STATSCOUNTER_INIT(statsCounter.ctrDiscarded, statsCounter.mutCtrDiscarded); CHKiRet(statsobj.AddCounter(statsCounter.stats, UCHAR_CONSTANT("discarded"), ctrType_IntCtr, CTR_FLAG_RESETTABLE, &(statsCounter.ctrDiscarded))); STATSCOUNTER_INIT(statsCounter.ctrFailed, statsCounter.mutCtrFailed); CHKiRet(statsobj.AddCounter(statsCounter.stats, UCHAR_CONSTANT("failed"), ctrType_IntCtr, CTR_FLAG_RESETTABLE, &(statsCounter.ctrFailed))); STATSCOUNTER_INIT(statsCounter.ctrRotations, statsCounter.mutCtrRotations); CHKiRet(statsobj.AddCounter(statsCounter.stats, UCHAR_CONSTANT("rotations"), ctrType_IntCtr, CTR_FLAG_RESETTABLE, &(statsCounter.ctrRotations))); STATSCOUNTER_INIT(statsCounter.ctrRecoveryAttempts, statsCounter.mutCtrRecoveryAttempts); CHKiRet(statsobj.AddCounter(statsCounter.stats, UCHAR_CONSTANT("recovery_attempts"), ctrType_IntCtr, CTR_FLAG_RESETTABLE, &(statsCounter.ctrRecoveryAttempts))); CHKiRet(statsobj.AddCounter(statsCounter.stats, UCHAR_CONSTANT("ratelimit_discarded_in_interval"), ctrType_IntCtr, CTR_FLAG_NONE, &(statsCounter.ratelimitDiscardedInInterval))); INIT_ATOMIC_HELPER_MUT64(statsCounter.mut_diskUsageBytes); statsCounter.diskUsageBytes = 0; CHKiRet(statsobj.AddCounter(statsCounter.stats, UCHAR_CONSTANT("disk_usage_bytes"), ctrType_IntCtr, CTR_FLAG_NONE, &(statsCounter.diskUsageBytes))); CHKiRet(statsobj.ConstructFinalize(statsCounter.stats)); /* end stats counter */ for (inst = runModConf->root; inst != NULL; inst = inst->next) { if (cs.stateFile) { char *new_stateFile; if (-1 == asprintf(&new_stateFile, "%s/%s", cs.stateFile, inst->pszBindRuleset)) { LogError(0, RS_RET_OUT_OF_MEMORY, "imjournal: asprintf failed\n"); ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } free(inst->stateFile); inst->stateFile = new_stateFile; ; } // Only the first input module with main enabled will be treated as // the main process. if (inst->bMain && root_inst == NULL) { root_inst = inst; } else { if (addListner(inst, index++) != RS_RET_OK) { LogError(0, RS_RET_NO_MORE_DATA, "imjournal: Can only support up to %i journals\n", MAX_JOURNAL); ABORT_FINALIZE(RS_RET_NO_MORE_DATA); } } } // Add all state files as a subfile of original cs.stateFile. if (runModConf->root != NULL && cs.stateFile) { char *new_stateFile; if (-1 == asprintf(&new_stateFile, "%s/default", cs.stateFile)) { LogError(0, RS_RET_OUT_OF_MEMORY, "imjournal: asprintf failed\n"); ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } // TODO(need to delete old state file and create the folder. DIR *stateDir; if (!(stateDir = opendir(cs.stateFile))) { remove(cs.stateFile); mkdir(cs.stateFile, 0700); } else { closedir(stateDir); } free(cs.stateFile); cs.stateFile = new_stateFile; ; } // Default Handlers. Will be used as the main process if no `main` // property is set in the input modules. if (addListner(NULL, index++) != RS_RET_OK) { LogError(0, RS_RET_NO_MORE_DATA, "imjournal: Can only support up to %i journals\n", MAX_JOURNAL); ABORT_FINALIZE(RS_RET_NO_MORE_DATA); } // Main process will be the top of journal_root. if (root_inst != NULL) { if (addListner(root_inst, index++) != RS_RET_OK) { LogError(0, RS_RET_NO_MORE_DATA, "imjournal: Can only support up to %i journals\n", MAX_JOURNAL); ABORT_FINALIZE(RS_RET_NO_MORE_DATA); } } finalize_it: ENDactivateCnf BEGINfreeCnf instanceConf_t *inst, *del; CODESTARTfreeCnf; for (inst = pModConf->root; inst != NULL;) { free(inst->pszBindRuleset); free(inst->stateFile); del = inst; inst = inst->next; free(del); } free(cs.stateFile); free(cs.usePid); free(cs.dfltTag); free(pModConf->pszNamespace); free(cs.pszRatelimitName); cs.pszRatelimitName = NULL; statsobj.Destruct(&(statsCounter.stats)); ENDfreeCnf /* open journal */ BEGINwillRun journal_etry_t *etry = journal_root; CODESTARTwillRun; while (etry != NULL) { CHKiRet(openJournal(etry->journalContext)); etry = etry->next; } finalize_it: ENDwillRun /* close journal */ BEGINafterRun journal_etry_t *etry = journal_root; journal_etry_t *del; CODESTARTafterRun; while (etry != NULL) { char *stateFile = cs.stateFile; if (etry->stateFile) { stateFile = etry->stateFile; } if (stateFile) { /* can't persist without a state file */ persistJournalState(etry->journalContext, stateFile); } closeJournal(etry->journalContext); free(etry->journalContext->cursor); // TODO: check iRet, reprot error del = etry; etry = etry->next; free(del); } if (ratelimiter) { ratelimitDestruct(ratelimiter); } free(cs.pszRatelimitName); cs.pszRatelimitName = NULL; ENDafterRun BEGINmodExit CODESTARTmodExit; if (pInputName != NULL) prop.Destruct(&pInputName); if (pLocalHostIP != NULL) prop.Destruct(&pLocalHostIP); /* release objects we used */ objRelease(statsobj, CORE_COMPONENT); objRelease(glbl, CORE_COMPONENT); objRelease(net, CORE_COMPONENT); objRelease(datetime, CORE_COMPONENT); objRelease(parser, CORE_COMPONENT); objRelease(prop, CORE_COMPONENT); objRelease(ruleset, CORE_COMPONENT); ENDmodExit BEGINsetModCnf struct cnfparamvals *pvals = NULL; int i; CODESTARTsetModCnf; pvals = nvlstGetParams(lst, &modpblk, NULL); if (pvals == NULL) { LogError(0, RS_RET_MISSING_CNFPARAMS, "error processing module " "config parameters [module(...)]"); ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); } if (Debug) { dbgprintf("module (global) param blk for imjournal:\n"); cnfparamsPrint(&modpblk, pvals); } for (i = 0; i < modpblk.nParams; ++i) { if (!pvals[i].bUsed) continue; if (!strcmp(modpblk.descr[i].name, "persiststateinterval")) { cs.iPersistStateInterval = (int)pvals[i].val.d.n; } else if (!strcmp(modpblk.descr[i].name, "statefile")) { CHKmalloc(cs.stateFile = (char *)es_str2cstr(pvals[i].val.d.estr, NULL)); } else if (!strcmp(modpblk.descr[i].name, "filecreatemode")) { cs.fCreateMode = (int)pvals[i].val.d.n; } else if (!strcmp(modpblk.descr[i].name, "ratelimit.burst")) { cs.ratelimitBurst = (int)pvals[i].val.d.n; } else if (!strcmp(modpblk.descr[i].name, "ratelimit.interval")) { cs.ratelimitInterval = (int)pvals[i].val.d.n; } else if (!strcmp(modpblk.descr[i].name, "ratelimit.name")) { free(cs.pszRatelimitName); CHKmalloc(cs.pszRatelimitName = (char *)es_str2cstr(pvals[i].val.d.estr, NULL)); } else if (!strcmp(modpblk.descr[i].name, "ignorepreviousmessages")) { cs.bIgnorePrevious = (int)pvals[i].val.d.n; } else if (!strcmp(modpblk.descr[i].name, "ignorenonvalidstatefile")) { cs.bIgnoreNonValidStatefile = (int)pvals[i].val.d.n; } else if (!strcmp(modpblk.descr[i].name, "defaultseverity")) { cs.iDfltSeverity = (int)pvals[i].val.d.n; } else if (!strcmp(modpblk.descr[i].name, "defaultfacility")) { /* ugly workaround to handle facility numbers; values derived from names need to be eight times smaller */ char *fac, *p; CHKmalloc(fac = p = es_str2cstr(pvals[i].val.d.estr, NULL)); facilityHdlr((uchar **)&p, (void *)&cs.iDfltFacility); free(fac); } else if (!strcmp(modpblk.descr[i].name, "usepidfromsystem")) { cs.bUseJnlPID = (int)pvals[i].val.d.n; } else if (!strcmp(modpblk.descr[i].name, "usepid")) { CHKmalloc(cs.usePid = (char *)es_str2cstr(pvals[i].val.d.estr, NULL)); } else if (!strcmp(modpblk.descr[i].name, "workaroundjournalbug")) { cs.bWorkAroundJournalBug = (int)pvals[i].val.d.n; } else if (!strcmp(modpblk.descr[i].name, "fsync")) { cs.bFsync = (int)pvals[i].val.d.n; } else if (!strcmp(modpblk.descr[i].name, "remote")) { cs.bRemote = (int)pvals[i].val.d.n; } else if (!strcmp(modpblk.descr[i].name, "namespace")) { free(loadModConf->pszNamespace); CHKmalloc(loadModConf->pszNamespace = (char *)es_str2cstr(pvals[i].val.d.estr, NULL)); } else if (!strcmp(modpblk.descr[i].name, "defaulttag")) { CHKmalloc(cs.dfltTag = (char *)es_str2cstr(pvals[i].val.d.estr, NULL)); } else { dbgprintf( "imjournal: program error, non-handled " "param '%s' in beginCnfLoad\n", modpblk.descr[i].name); } } if (cs.pszRatelimitName != NULL) { if (cs.ratelimitInterval != -1 || cs.ratelimitBurst != -1) { LogError(0, RS_RET_INVALID_PARAMS, "imjournal: ratelimit.name is mutually exclusive with " "ratelimit.interval and ratelimit.burst - using ratelimit.name"); } } else { if (cs.ratelimitInterval == -1) cs.ratelimitInterval = 600; if (cs.ratelimitBurst == -1) cs.ratelimitBurst = 20000; } finalize_it: if (pvals != NULL) cnfparamvalsDestruct(pvals, &modpblk); ENDsetModCnf /* create input instance, set default parameters, and * add it to the list of instances. */ static rsRetVal ATTR_NONNULL(1) createInstance(instanceConf_t **const pinst) { instanceConf_t *inst; DEFiRet; CHKmalloc(inst = calloc(1, sizeof(instanceConf_t))); /* node created, let's add to config */ if (loadModConf->tail == NULL) { loadModConf->tail = loadModConf->root = inst; } else { loadModConf->tail->next = inst; loadModConf->tail = inst; } *pinst = inst; finalize_it: if (iRet != RS_RET_OK) { free(inst); } RETiRet; } BEGINnewInpInst struct cnfparamvals *pvals; instanceConf_t *inst; int i; CODESTARTnewInpInst; DBGPRINTF("newInpInst (imjournal)\n"); pvals = nvlstGetParams(lst, &inppblk, NULL); if (pvals == NULL) { ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); } if (Debug) { DBGPRINTF("input param blk in imjournal:\n"); cnfparamsPrint(&inppblk, pvals); } CHKiRet(createInstance(&inst)); for (i = 0; i < inppblk.nParams; ++i) { if (!pvals[i].bUsed) continue; if (!strcmp(inppblk.descr[i].name, "ruleset")) { CHKmalloc(inst->pszBindRuleset = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); } else if (!strcmp(inppblk.descr[i].name, "main")) { inst->bMain = (int)pvals[i].val.d.n; } else { DBGPRINTF( "program error, non-handled " "param '%s'\n", inppblk.descr[i].name); } } finalize_it: CODE_STD_FINALIZERnewInpInst if (pvals != NULL) cnfparamvalsDestruct(pvals, &inppblk); ENDnewInpInst BEGINisCompatibleWithFeature CODESTARTisCompatibleWithFeature; if (eFeat == sFEATURENonCancelInputTermination) iRet = RS_RET_OK; ENDisCompatibleWithFeature BEGINqueryEtryPt CODESTARTqueryEtryPt; CODEqueryEtryPt_STD_IMOD_QUERIES; CODEqueryEtryPt_STD_CONF2_QUERIES; CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES; CODEqueryEtryPt_STD_CONF2_IMOD_QUERIES; CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES; ENDqueryEtryPt static inline void std_checkRuleset_genErrMsg(__attribute__((unused)) modConfData_t *modConf, instanceConf_t *inst) { LogError(0, NO_ERRCODE, "imjournal: ruleset '%s' not found - " "using default ruleset instead", inst->pszBindRuleset); } BEGINmodInit() CODESTARTmodInit; *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ CODEmodInit_QueryRegCFSLineHdlr CHKiRet(objUse(datetime, CORE_COMPONENT)); CHKiRet(objUse(glbl, CORE_COMPONENT)); CHKiRet(objUse(parser, CORE_COMPONENT)); CHKiRet(objUse(prop, CORE_COMPONENT)); CHKiRet(objUse(net, CORE_COMPONENT)); CHKiRet(objUse(statsobj, CORE_COMPONENT)); CHKiRet(objUse(ruleset, CORE_COMPONENT)); /* we need to create the inputName property (only once during our lifetime) */ CHKiRet(prop.CreateStringProp(&pInputName, UCHAR_CONSTANT("imjournal"), sizeof("imjournal") - 1)); CHKiRet(prop.CreateStringProp(&pLocalHostIP, UCHAR_CONSTANT("127.0.0.1"), sizeof("127.0.0.1") - 1)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"imjournalpersiststateinterval", 0, eCmdHdlrInt, NULL, &cs.iPersistStateInterval, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"imjournalratelimitinterval", 0, eCmdHdlrInt, NULL, &cs.ratelimitInterval, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"imjournalratelimitburst", 0, eCmdHdlrInt, NULL, &cs.ratelimitBurst, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"imjournalstatefile", 0, eCmdHdlrGetWord, NULL, &cs.stateFile, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"imjournalignorepreviousmessages", 0, eCmdHdlrBinary, NULL, &cs.bIgnorePrevious, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"imjournaldefaultseverity", 0, eCmdHdlrSeverity, NULL, &cs.iDfltSeverity, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"imjournaldefaultfacility", 0, eCmdHdlrCustomHandler, facilityHdlr, &cs.iDfltFacility, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"imjournalusepidfromsystem", 0, eCmdHdlrBinary, NULL, &cs.bUseJnlPID, STD_LOADABLE_MODULE_ID)); ENDmodInit /* vim:set ai: */