mirror of
https://github.com/rsyslog/rsyslog.git
synced 2026-06-20 01:42:57 +02:00
This commit applies the new canonical formatting style using `clang-format` with custom settings (notably 4-space indentation), as part of our shift toward automated formatting normalization. ⚠️ No functional changes are included — only whitespace and layout modifications as produced by `clang-format`. This change is part of the formatting modernization strategy discussed in: https://github.com/rsyslog/rsyslog/issues/5747 Key context: - Formatting is now treated as a disposable view, normalized via tooling. - The `.clang-format` file defines the canonical style. - A fixup script (`devtools/format-code.sh`) handles remaining edge cases. - Formatting commits are added to `.git-blame-ignore-revs` to reduce noise. - Developers remain free to format code however they prefer locally.
445 lines
15 KiB
C
445 lines
15 KiB
C
/* debug.c
|
|
*
|
|
* This file proides debug support.
|
|
*
|
|
* File begun on 2008-01-22 by RGerhards
|
|
*
|
|
* Some functions are controlled by environment variables:
|
|
*
|
|
* RSYSLOG_DEBUGLOG if set, a debug log file is written to that location
|
|
* RSYSLOG_DEBUG specific debug options
|
|
*
|
|
* For details, visit doc/debug.html
|
|
*
|
|
* Copyright 2008-2018 Rainer Gerhards and Adiscon GmbH.
|
|
*
|
|
* This file is part of the rsyslog runtime library.
|
|
*
|
|
* 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" /* autotools! */
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#ifdef HAVE_SYS_SYSCALL_H
|
|
#include <sys/syscall.h>
|
|
#endif
|
|
#if _POSIX_TIMERS <= 0
|
|
#include <sys/time.h>
|
|
#endif
|
|
|
|
#include "rsyslog.h"
|
|
#include "debug.h"
|
|
#include "cfsysline.h"
|
|
#include "obj.h"
|
|
|
|
|
|
/* static data (some time to be replaced) */
|
|
DEFobjCurrIf(obj) int Debug = DEBUG_OFF; /* debug flag - read-only after startup */
|
|
int debugging_on = 0; /* read-only, except on sig USR1 */
|
|
int dbgTimeoutToStderr = 0;
|
|
static int bPrintTime = 1; /* print a timestamp together with debug message */
|
|
static int bOutputTidToStderr = 0; /* output TID to stderr on thread creation */
|
|
char *pszAltDbgFileName = NULL; /* if set, debug output is *also* sent to here */
|
|
int altdbg = -1; /* and the handle for alternate debug output */
|
|
int stddbg = 1; /* the handle for regular debug output, set to stdout if not forking, -1 otherwise */
|
|
static uint64_t dummy_errcount = 0; /* just to avoid some static analyzer complaints */
|
|
static pthread_key_t keyThrdName;
|
|
|
|
|
|
/* output the current thread ID to "relevant" places
|
|
* (what "relevant" means is determinded by various ways)
|
|
*/
|
|
void dbgOutputTID(char *name __attribute__((unused))) {
|
|
#if defined(HAVE_SYSCALL) && defined(HAVE_SYS_gettid)
|
|
if (bOutputTidToStderr) fprintf(stderr, "thread tid %u, name '%s'\n", (unsigned)syscall(SYS_gettid), name);
|
|
DBGPRINTF("thread created, tid %u, name '%s'\n", (unsigned)syscall(SYS_gettid), name);
|
|
#endif
|
|
}
|
|
|
|
|
|
/* build a string with the thread name. If none is set, the thread ID is
|
|
* used instead. Caller must provide buffer space.
|
|
*/
|
|
static void ATTR_NONNULL() dbgGetThrdName(char *const pszBuf, const size_t lenBuf, const pthread_t thrdID) {
|
|
assert(pszBuf != NULL);
|
|
|
|
const char *const thrdName = pthread_getspecific(keyThrdName);
|
|
if (thrdName == NULL) {
|
|
snprintf(pszBuf, lenBuf, "%lx", (long)thrdID);
|
|
} else {
|
|
snprintf(pszBuf, lenBuf, "%-15s", thrdName);
|
|
}
|
|
}
|
|
|
|
|
|
/* set a name for the current thread */
|
|
void ATTR_NONNULL() dbgSetThrdName(const uchar *const pszName) {
|
|
(void)pthread_setspecific(keyThrdName, strdup((char *)pszName));
|
|
}
|
|
|
|
|
|
/* destructor for thread name (called by pthreads!) */
|
|
static void dbgThrdNameDestruct(void *arg) {
|
|
free(arg);
|
|
}
|
|
|
|
|
|
/* write the debug message. This is a helper to dbgprintf and dbgoprint which
|
|
* contains common code. added 2008-09-26 rgerhards
|
|
* Note: We need to split the function due to the bad nature of POSIX
|
|
* cancel cleanup handlers.
|
|
*/
|
|
static void DBGL_UNUSED dbgprint(obj_t *pObj, char *pszMsg, const char *pszFileName, const size_t lenMsg) {
|
|
uchar *pszObjName = NULL;
|
|
static pthread_t ptLastThrdID = 0;
|
|
static int bWasNL = 0;
|
|
char pszThrdName[64]; /* 64 is to be on the safe side, anything over 20 is bad... */
|
|
char pszWriteBuf[32 * 1024];
|
|
size_t lenCopy;
|
|
size_t offsWriteBuf = 0;
|
|
size_t lenWriteBuf;
|
|
struct timespec t;
|
|
#if _POSIX_TIMERS <= 0
|
|
struct timeval tv;
|
|
#endif
|
|
|
|
if (pObj != NULL) {
|
|
pszObjName = obj.GetName(pObj);
|
|
}
|
|
|
|
/* The bWasNL handler does not really work. It works if no thread
|
|
* switching occurs during non-NL messages. Else, things are messed
|
|
* up. Anyhow, it works well enough to provide useful help during
|
|
* getting this up and running. It is questionable if the extra effort
|
|
* is worth fixing it, giving the limited appliability. -- rgerhards, 2005-10-25
|
|
* I have decided that it is not worth fixing it - especially as it works
|
|
* pretty well. -- rgerhards, 2007-06-15
|
|
*/
|
|
if (ptLastThrdID != pthread_self()) {
|
|
if (!bWasNL) {
|
|
pszWriteBuf[0] = '\n';
|
|
offsWriteBuf = 1;
|
|
bWasNL = 1;
|
|
}
|
|
ptLastThrdID = pthread_self();
|
|
}
|
|
|
|
dbgGetThrdName(pszThrdName, sizeof(pszThrdName), ptLastThrdID);
|
|
|
|
if (bWasNL) {
|
|
if (bPrintTime) {
|
|
#if _POSIX_TIMERS > 0
|
|
/* this is the "regular" code */
|
|
clock_gettime(CLOCK_REALTIME, &t);
|
|
#else
|
|
gettimeofday(&tv, NULL);
|
|
t.tv_sec = tv.tv_sec;
|
|
t.tv_nsec = tv.tv_usec * 1000;
|
|
#endif
|
|
lenWriteBuf = snprintf(pszWriteBuf + offsWriteBuf, sizeof(pszWriteBuf) - offsWriteBuf,
|
|
"%4.4ld.%9.9ld:", (long)(t.tv_sec % 10000), t.tv_nsec);
|
|
offsWriteBuf += lenWriteBuf;
|
|
}
|
|
|
|
lenWriteBuf = snprintf(pszWriteBuf + offsWriteBuf, sizeof(pszWriteBuf) - offsWriteBuf, "%s: ", pszThrdName);
|
|
offsWriteBuf += lenWriteBuf;
|
|
/* print object name header if we have an object */
|
|
if (pszObjName != NULL) {
|
|
lenWriteBuf = snprintf(pszWriteBuf + offsWriteBuf, sizeof(pszWriteBuf) - offsWriteBuf, "%s: ", pszObjName);
|
|
offsWriteBuf += lenWriteBuf;
|
|
}
|
|
lenWriteBuf = snprintf(pszWriteBuf + offsWriteBuf, sizeof(pszWriteBuf) - offsWriteBuf, "%s: ", pszFileName);
|
|
offsWriteBuf += lenWriteBuf;
|
|
}
|
|
if (lenMsg > sizeof(pszWriteBuf) - offsWriteBuf)
|
|
lenCopy = sizeof(pszWriteBuf) - offsWriteBuf;
|
|
else
|
|
lenCopy = lenMsg;
|
|
memcpy(pszWriteBuf + offsWriteBuf, pszMsg, lenCopy);
|
|
offsWriteBuf += lenCopy;
|
|
/* the write is included in an "if" just to silence compiler
|
|
* warnings. Here, we really don't care if the write fails, we
|
|
* have no good response to that in any case... -- rgerhards, 2012-11-28
|
|
*/
|
|
if (stddbg != -1) {
|
|
if (write(stddbg, pszWriteBuf, offsWriteBuf)) {
|
|
++dummy_errcount;
|
|
}
|
|
}
|
|
if (altdbg != -1) {
|
|
if (write(altdbg, pszWriteBuf, offsWriteBuf)) {
|
|
++dummy_errcount;
|
|
}
|
|
}
|
|
|
|
bWasNL = (pszMsg[lenMsg - 1] == '\n') ? 1 : 0;
|
|
}
|
|
|
|
|
|
static int DBGL_UNUSED checkDbgFile(const char *srcname) {
|
|
if (glblDbgFilesNum == 0) {
|
|
return 1;
|
|
}
|
|
if (glblDbgWhitelist) {
|
|
if (bsearch(srcname, glblDbgFiles, glblDbgFilesNum, sizeof(char *), bs_arrcmp_glblDbgFiles) == NULL) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
} else {
|
|
if (bsearch(srcname, glblDbgFiles, glblDbgFilesNum, sizeof(char *), bs_arrcmp_glblDbgFiles) != NULL) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
/* print some debug output when an object is given
|
|
* This is mostly a copy of dbgprintf, but I do not know how to combine it
|
|
* into a single function as we have variable arguments and I don't know how to call
|
|
* from one vararg function into another. I don't dig in this, it is OK for the
|
|
* time being. -- rgerhards, 2008-01-29
|
|
*/
|
|
#ifndef DEBUGLESS
|
|
void r_dbgoprint(const char *srcname, obj_t *pObj, const char *fmt, ...) {
|
|
va_list ap;
|
|
char pszWriteBuf[32 * 1024];
|
|
size_t lenWriteBuf;
|
|
|
|
if (!(Debug && debugging_on)) return;
|
|
|
|
if (!checkDbgFile(srcname)) {
|
|
return;
|
|
}
|
|
|
|
va_start(ap, fmt);
|
|
lenWriteBuf = vsnprintf(pszWriteBuf, sizeof(pszWriteBuf), fmt, ap);
|
|
va_end(ap);
|
|
if (lenWriteBuf >= sizeof(pszWriteBuf)) {
|
|
/* prevent buffer overrruns and garbagge display */
|
|
pszWriteBuf[sizeof(pszWriteBuf) - 5] = '.';
|
|
pszWriteBuf[sizeof(pszWriteBuf) - 4] = '.';
|
|
pszWriteBuf[sizeof(pszWriteBuf) - 3] = '.';
|
|
pszWriteBuf[sizeof(pszWriteBuf) - 2] = '\n';
|
|
pszWriteBuf[sizeof(pszWriteBuf) - 1] = '\0';
|
|
lenWriteBuf = sizeof(pszWriteBuf);
|
|
}
|
|
dbgprint(pObj, pszWriteBuf, srcname, lenWriteBuf);
|
|
}
|
|
#endif
|
|
|
|
/* print some debug output when no object is given
|
|
* WARNING: duplicate code, see dbgoprint above!
|
|
*/
|
|
#ifndef DEBUGLESS
|
|
void r_dbgprintf(const char *srcname, const char *fmt, ...) {
|
|
va_list ap;
|
|
char pszWriteBuf[32 * 1024];
|
|
size_t lenWriteBuf;
|
|
|
|
if (!(Debug && debugging_on)) {
|
|
return;
|
|
}
|
|
|
|
if (!checkDbgFile(srcname)) {
|
|
return;
|
|
}
|
|
|
|
va_start(ap, fmt);
|
|
lenWriteBuf = vsnprintf(pszWriteBuf, sizeof(pszWriteBuf), fmt, ap);
|
|
va_end(ap);
|
|
if (lenWriteBuf >= sizeof(pszWriteBuf)) {
|
|
/* prevent buffer overrruns and garbagge display */
|
|
pszWriteBuf[sizeof(pszWriteBuf) - 5] = '.';
|
|
pszWriteBuf[sizeof(pszWriteBuf) - 4] = '.';
|
|
pszWriteBuf[sizeof(pszWriteBuf) - 3] = '.';
|
|
pszWriteBuf[sizeof(pszWriteBuf) - 2] = '\n';
|
|
pszWriteBuf[sizeof(pszWriteBuf) - 1] = '\0';
|
|
lenWriteBuf = sizeof(pszWriteBuf);
|
|
}
|
|
dbgprint(NULL, pszWriteBuf, srcname, lenWriteBuf);
|
|
}
|
|
#endif
|
|
|
|
/* support system to set debug options at runtime */
|
|
|
|
|
|
/* parse a param/value pair from the current location of the
|
|
* option string. Returns 1 if an option was found, 0
|
|
* otherwise. 0 means there are NO MORE options to be
|
|
* processed. -- rgerhards, 2008-02-28
|
|
*/
|
|
static int ATTR_NONNULL() dbgGetRTOptNamVal(const uchar **const ppszOpt, uchar **const ppOptName) {
|
|
assert(ppszOpt != NULL);
|
|
assert(*ppszOpt != NULL);
|
|
int bRet = 0;
|
|
const uchar *p = *ppszOpt;
|
|
size_t i;
|
|
static uchar optname[128] = "";
|
|
|
|
/* skip whitespace */
|
|
while (*p && isspace(*p)) ++p;
|
|
|
|
/* name: up until whitespace */
|
|
i = 0;
|
|
while (i < (sizeof(optname) - 1) && *p && !isspace(*p)) {
|
|
optname[i++] = *p++;
|
|
}
|
|
|
|
if (i > 0) {
|
|
bRet = 1;
|
|
optname[i] = '\0';
|
|
}
|
|
|
|
/* done */
|
|
*ppszOpt = p;
|
|
*ppOptName = optname;
|
|
return bRet;
|
|
}
|
|
|
|
|
|
/* report fd used for debug log. This is needed in case of
|
|
* auto-backgrounding, where the debug log shall not be closed.
|
|
*/
|
|
int dbgGetDbglogFd(void) {
|
|
return altdbg;
|
|
}
|
|
|
|
/* read in the runtime options
|
|
* rgerhards, 2008-02-28
|
|
*/
|
|
static void dbgGetRuntimeOptions(void) {
|
|
const uchar *pszOpts;
|
|
uchar *optname;
|
|
|
|
/* set some defaults */
|
|
if ((pszOpts = (uchar *)getenv("RSYSLOG_DEBUG")) != NULL) {
|
|
/* we have options set, so let's process them */
|
|
while (dbgGetRTOptNamVal(&pszOpts, &optname)) {
|
|
if (!strcasecmp((char *)optname, "help")) {
|
|
fprintf(stderr, "rsyslogd " VERSION
|
|
" runtime debug support - help requested, "
|
|
"rsyslog terminates\n\nenvironment variables:\n"
|
|
"additional logfile: export RSYSLOG_DEBUGFILE=\"/path/to/file\"\n"
|
|
"to set: export RSYSLOG_DEBUG=\"cmd cmd cmd\"\n\n"
|
|
"Commands are (all case-insensitive):\n"
|
|
"help (this list, terminates rsyslogd)\n"
|
|
"NoLogTimestamp\n"
|
|
"Nostdout\n"
|
|
"OutputTidToStderr\n"
|
|
"DebugOnDemand - enables debugging on USR1, but does not turn on output\n"
|
|
"\nSee debug.html in your doc set or https://www.rsyslog.com for details\n");
|
|
exit(1);
|
|
} else if (!strcasecmp((char *)optname, "debug")) {
|
|
/* this is earlier in the process than the -d option, as such it
|
|
* allows us to spit out debug messages from the very beginning.
|
|
*/
|
|
Debug = DEBUG_FULL;
|
|
debugging_on = 1;
|
|
} else if (!strcasecmp((char *)optname, "debugondemand")) {
|
|
/* Enables debugging, but turns off debug output */
|
|
Debug = DEBUG_ONDEMAND;
|
|
debugging_on = 1;
|
|
dbgprintf(
|
|
"Note: debug on demand turned on via configuration file, "
|
|
"use USR1 signal to activate.\n");
|
|
debugging_on = 0;
|
|
} else if (!strcasecmp((char *)optname, "nologtimestamp")) {
|
|
bPrintTime = 0;
|
|
} else if (!strcasecmp((char *)optname, "nostdout")) {
|
|
stddbg = -1;
|
|
} else if (!strcasecmp((char *)optname, "outputtidtostderr")) {
|
|
bOutputTidToStderr = 1;
|
|
} else {
|
|
fprintf(stderr,
|
|
"rsyslogd " VERSION
|
|
" error: invalid debug option '%s' "
|
|
"- ignored\n",
|
|
optname);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void dbgSetDebugLevel(int level) {
|
|
Debug = level;
|
|
debugging_on = (level == DEBUG_FULL) ? 1 : 0;
|
|
}
|
|
|
|
void dbgSetDebugFile(uchar *fn) {
|
|
if (altdbg != -1) {
|
|
dbgprintf("switching to debug file %s\n", fn);
|
|
close(altdbg);
|
|
}
|
|
if ((altdbg = open((char *)fn, O_WRONLY | O_CREAT | O_TRUNC | O_NOCTTY | O_CLOEXEC, S_IRUSR | S_IWUSR)) == -1) {
|
|
fprintf(stderr, "alternate debug file could not be opened, ignoring. Error: %s\n", strerror(errno));
|
|
}
|
|
}
|
|
|
|
/* end support system to set debug options at runtime */
|
|
|
|
rsRetVal dbgClassInit(void) {
|
|
rsRetVal iRet; /* do not use DEFiRet, as this makes calls into the debug system! */
|
|
|
|
|
|
(void)pthread_key_create(&keyThrdName, dbgThrdNameDestruct);
|
|
|
|
/* while we try not to use any of the real rsyslog code (to avoid infinite loops), we
|
|
* need to have the ability to query object names. Thus, we need to obtain a pointer to
|
|
* the object interface. -- rgerhards, 2008-02-29
|
|
*/
|
|
CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */
|
|
|
|
const char *dbgto2stderr;
|
|
dbgto2stderr = getenv("RSYSLOG_DEBUG_TIMEOUTS_TO_STDERR");
|
|
dbgTimeoutToStderr = (dbgto2stderr != NULL && !strcmp(dbgto2stderr, "on")) ? 1 : 0;
|
|
if (dbgTimeoutToStderr) {
|
|
fprintf(stderr, "rsyslogd: NOTE: RSYSLOG_DEBUG_TIMEOUTS_TO_STDERR activated\n");
|
|
}
|
|
dbgGetRuntimeOptions(); /* init debug system from environment */
|
|
pszAltDbgFileName = getenv("RSYSLOG_DEBUGLOG");
|
|
|
|
if (pszAltDbgFileName != NULL) {
|
|
/* we have a secondary file, so let's open it) */
|
|
if ((altdbg = open(pszAltDbgFileName, O_WRONLY | O_CREAT | O_TRUNC | O_NOCTTY | O_CLOEXEC,
|
|
S_IRUSR | S_IWUSR)) == -1) {
|
|
fprintf(stderr, "alternate debug file could not be opened, ignoring. Error: %s\n", strerror(errno));
|
|
}
|
|
}
|
|
|
|
dbgSetThrdName((uchar *)"main thread");
|
|
|
|
finalize_it:
|
|
return (iRet);
|
|
}
|
|
|
|
|
|
rsRetVal dbgClassExit(void) {
|
|
pthread_key_delete(keyThrdName);
|
|
if (altdbg != -1) close(altdbg);
|
|
return RS_RET_OK;
|
|
}
|