/* This is the main rsyslogd file.
 * It contains code * that is known to be validly under ASL 2.0,
 * because it was either written from scratch by me (rgerhards) or
 * contributors who agreed to ASL 2.0.
 *
 * Copyright 2004-2026 Rainer Gerhards and Adiscon
 *
 * 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 <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#ifdef ENABLE_LIBLOGGING_STDLOG
    #include <liblogging/stdlog.h>
#else
    #include <syslog.h>
#endif
#ifdef HAVE_LIBSYSTEMD
    #include <systemd/sd-daemon.h>
#endif
#ifdef ENABLE_LIBCAPNG
    #include <cap-ng.h>
#endif
#if defined(HAVE_LINUX_CLOSE_RANGE_H)
    #include <linux/close_range.h>
#endif

#include "rsyslog.h"
#include "wti.h"
#include "ratelimit.h"
#include "queue.h"
#include "parser.h"
#include "linkedlist.h"
#include "ruleset.h"
#include "action.h"
#include "iminternal.h"
#include "errmsg.h"
#include "threads.h"
#include "dnscache.h"
#include "prop.h"
#include "unicode-helper.h"
#include "net.h"
#include "glbl.h"
#include "debug.h"
#include "srUtils.h"
#include "rainerscript.h"
#include "rsconf.h"
#include "translate.h"
#include "cfsysline.h"
#include "datetime.h"
#include "operatingstate.h"
#include "dirty.h"
#include "janitor.h"
#include "parserif.h"
#include "rswatch.h"

/* some global vars we need to differentiate between environments,
 * for TZ-related things see
 * https://github.com/rsyslog/rsyslog/issues/2994
 */
static int runningInContainer = 0;
#ifdef OS_LINUX
static int emitTZWarning = 0;
#else
static int emitTZWarning = 1;
#endif
static pthread_t mainthread = 0;
#ifdef HAVE_LIBSYSTEMD
static int systemdWatchdogEnabled = 0;
static uint64_t systemdWatchdogUsec = 0;
#endif

#if defined(_AIX)
    /* AIXPORT : start
     * The following includes and declarations are for support of the System
     * Resource Controller (SRC) .
     */
    #include <sys/select.h>
    /* AIXPORT : start*/
    #define SRC_FD 13
    #define SRCMSG (sizeof(srcpacket))

static void deinitAll(void);
    #include <spc.h>
static struct srcreq srcpacket;
int cont;
struct srchdr *srchdr;
char progname[128];


/* Normally defined as locals in main
 * But here since the functionality is split
 * across multiple functions, we make it global
 */
static int rc;
static socklen_t addrsz;
static struct sockaddr srcaddr;
int src_exists = TRUE;
    /* src end */

    /*
     * SRC packet processing - .
     */
    #define SRCMIN(a, b) (a < b) ? a : b
void dosrcpacket(msgno, txt, len) int msgno;
char *txt;
int len;
{
    struct srcrep reply;
    const char *msgtxt;

    msgtxt = (txt == NULL) ? "" : txt;
    reply.svrreply.rtncode = msgno;
    /* AIXPORT :  srv was corrected to syslogd */
    RS_COPY_LITERAL(reply.svrreply.objname, "syslogd");
    if (snprintf(reply.svrreply.rtnmsg, sizeof(reply.svrreply.rtnmsg), "%s", msgtxt) < 0) {
        reply.svrreply.rtnmsg[0] = '\0';
    }
    reply.svrreply.rtnmsg[sizeof(reply.svrreply.rtnmsg) - 1] = '\0';
    srchdr = srcrrqs((char *)&srcpacket);
    srcsrpy(srchdr, (char *)&reply, len, cont);
}

    #define AIX_SRC_EXISTS_IF if (!src_exists) {
    #define AIX_SRC_EXISTS_FI }

static void aix_close_it(int i) {
    if (src_exists) {
        if (i != SRC_FD) (void)close(i);
    } else
        close(i);
}


#else

    #define AIX_SRC_EXISTS_IF
    #define AIX_SRC_EXISTS_FI
    #define aix_close_it(x) close(x)
#endif

/* AIXPORT : end  */


static rsRetVal checkConfigOutputDoesNotOverwriteInput(const char *const outputPath, const char *const configPath) {
    struct stat outputStat;
    struct stat configStat;

    if (outputPath == NULL || !strcmp(outputPath, "-") || configPath == NULL) {
        return RS_RET_OK;
    }

    if (!strcmp(outputPath, configPath)) {
        fprintf(stderr, "rsyslogd: -o output path must not be the same as the input config file '%s'\n", configPath);
        return RS_RET_ERR;
    }

    if (stat(configPath, &configStat) != 0) {
        return RS_RET_OK;
    }
    if (stat(outputPath, &outputStat) != 0) {
        return RS_RET_OK;
    }
    if (configStat.st_dev == outputStat.st_dev && configStat.st_ino == outputStat.st_ino) {
        fprintf(stderr, "rsyslogd: -o output path '%s' resolves to the same file as input config file '%s'\n",
                outputPath, configPath);
        return RS_RET_ERR;
    }

    return RS_RET_OK;
}

DEFobjCurrIf(obj) DEFobjCurrIf(prop) DEFobjCurrIf(parser) DEFobjCurrIf(ruleset) DEFobjCurrIf(net) DEFobjCurrIf(rsconf)
    DEFobjCurrIf(module) DEFobjCurrIf(datetime) DEFobjCurrIf(glbl)

        extern int yydebug; /* interface to parser */


/* forward definitions */
void rsyslogd_submitErrMsg(const int severity, const int iErr, const uchar *msg);
void rsyslogdDoDie(int sig);


#ifndef PATH_PIDFILE
    #if defined(_AIX) /* AIXPORT : Add _AIX */
        #define PATH_PIDFILE "/etc/rsyslogd.pid"
    #else
        #define PATH_PIDFILE "/var/run/rsyslogd.pid"
    #endif /*_AIX*/
#endif

#ifndef PATH_CONFFILE
    #define PATH_CONFFILE "/etc/rsyslog.conf"
#endif

/* global data items */
static volatile sig_atomic_t bChildDied = 0;
static volatile sig_atomic_t bHadHUP = 0;
static volatile sig_atomic_t bHUPInProgress = 0;
static int doFork = 1; /* fork - run in daemon mode - read-only after startup */
static volatile sig_atomic_t bFinished = 0; /* signal number requesting termination, or 0 */
const char *PidFile = NULL;
#define NO_PIDFILE "NONE"
int iConfigVerify = 0; /* is this just a config verify run? */
rsconf_t *ourConf = NULL; /* our config object */
int MarkInterval = 20 * 60; /* interval between marks in seconds - read-only after startup */
ratelimit_t *dflt_ratelimiter = NULL; /* ratelimiter for submits without explicit one */
uchar *ConfFile = (uchar *)PATH_CONFFILE;
int bHaveMainQueue = 0; /* set to 1 if the main queue - in queueing mode - is available
                         * If the main queue is either not yet ready or not running in
                         * queueing mode (mode DIRECT!), then this is set to 0.
                         */
prop_t *pInternalInputName = NULL; /* there is only one global inputName for all internally-generated messages */
ratelimit_t *internalMsg_ratelimiter = NULL; /* ratelimiter for rsyslog-own messages */
int send_to_all = 0; /* send message to all IPv4/IPv6 addresses */
static uchar *properTerminationFile = NULL; /* testbench-only final shutdown marker path */

static struct queuefilenames_s {
    struct queuefilenames_s *next;
    uchar *name;
} *queuefilenames = NULL;


static __attribute__((noreturn)) void rsyslogd_usage(void) {
    fprintf(stderr,
            "usage: rsyslogd [options]\n"
            "use \"man rsyslogd\" for details. To run rsyslog "
            "interactively, use \"rsyslogd -n\"\n"
            "to run it in debug mode use \"rsyslogd -dn\"\n"
            "For further information see https://www.rsyslog.com/doc/\n");
    exit(1); /* "good" exit - done to terminate usage() */
}

#ifndef HAVE_SETSID
extern void untty(void); /* in syslogd.c, GPLv3 */
static int setsid(void) {
    untty();
    return 0;
}
#endif

/* helper for imdiag. Returns if HUP processing has been requested or is not
 * yet finished. We know this is racy, but imdiag handles this part by
 * repeating operations. sig_atomic_t keeps access signal-handler safe; callers
 * must treat the answer as a transient observation.
 * 2023-07-26 Rainer Gerhards
 */
int get_bHadHUP(void) {
    const int ret = PREFER_LOAD_INT(&bHadHUP) || PREFER_LOAD_INT(&bHUPInProgress);
    /* note: at this point ret can already be invalid */
    return ret;
}

/* we need a pointer to the conf, because in early startup stage we
 * need to use loadConf, later on runConf.
 */
rsRetVal queryLocalHostname(rsconf_t *const pConf) {
    uchar *LocalHostName = NULL;
    uchar *LocalDomain = NULL;
    uchar *LocalFQDNName;
    DEFiRet;

    assert(net.getLocalHostname != NULL); /* keep clang static analyzer silent - this IS the case */
    CHKiRet(net.getLocalHostname(pConf, &LocalFQDNName));
    uchar *dot = (uchar *)strstr((char *)LocalFQDNName, ".");
    if (dot == NULL) {
        CHKmalloc(LocalHostName = (uchar *)strdup((char *)LocalFQDNName));
        CHKmalloc(LocalDomain = (uchar *)strdup(""));
    } else {
        const size_t lenhn = dot - LocalFQDNName;
        CHKmalloc(LocalHostName = (uchar *)strndup((char *)LocalFQDNName, lenhn));
        CHKmalloc(LocalDomain = (uchar *)strdup((char *)dot + 1));
    }

    glbl.SetLocalFQDNName(LocalFQDNName);
    glbl.SetLocalHostName(LocalHostName);
    glbl.SetLocalDomain(LocalDomain);
    glbl.GenerateLocalHostNameProperty();
    LocalHostName = NULL; /* handed over */
    LocalDomain = NULL; /* handed over */

finalize_it:
    free(LocalHostName);
    free(LocalDomain);
    RETiRet;
}

static void syncPidFileDir(const char *pidFilePath) {
    const char *slash = strrchr(pidFilePath, '/');
    char *dirPath = NULL;
    int dirfd = -1;

    if (slash == NULL) {
        dirPath = strdup(".");
    } else if (slash == pidFilePath) {
        dirPath = strdup("/");
    } else {
        dirPath = strndup(pidFilePath, slash - pidFilePath);
    }

    if (dirPath == NULL) {
        DBGPRINTF("rsyslogd: out of memory syncing pidfile directory\n");
        return;
    }

    dirfd = open(dirPath, O_RDONLY | O_CLOEXEC | O_NOCTTY);
    if (dirfd == -1) {
        DBGPRINTF("rsyslogd: error opening pidfile directory '%s' for sync\n", dirPath);
        goto finalize_it;
    }

    if (fsync(dirfd) != 0) {
        DBGPRINTF("rsyslogd: error syncing pidfile directory '%s'\n", dirPath);
    }

finalize_it:
    if (dirfd != -1) {
        close(dirfd);
    }
    free(dirPath);
}

static rsRetVal writePidFile(void) {
    FILE *fp = NULL;
    DEFiRet;

    const char *tmpPidFile = NULL;
    int fd = -1;

    if (!strcmp(PidFile, NO_PIDFILE)) {
        FINALIZE;
    }
    if (asprintf((char **)&tmpPidFile, "%s.tmp", PidFile) == -1) {
        ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
    }

    DBGPRINTF("rsyslogd: writing pidfile '%s'.\n", tmpPidFile);
    if ((fp = fopen((char *)tmpPidFile, "w")) == NULL) {
        perror("rsyslogd: error writing pid file (creation stage)\n");
        ABORT_FINALIZE(RS_RET_ERR);
    }
    if (fprintf(fp, "%d", (int)glblGetOurPid()) < 0) {
        perror("rsyslogd: error writing pid file");
        ABORT_FINALIZE(RS_RET_ERR);
    }
    if (fflush(fp) != 0) {
        perror("rsyslogd: error flushing pid file");
        ABORT_FINALIZE(RS_RET_ERR);
    }
    fd = fileno(fp);
    if (fd == -1) {
        perror("rsyslogd: error obtaining pid file descriptor");
        ABORT_FINALIZE(RS_RET_ERR);
    }
    if (fsync(fd) != 0) {
        perror("rsyslogd: error syncing pid file");
        ABORT_FINALIZE(RS_RET_ERR);
    }
    if (fclose(fp) != 0) {
        fp = NULL;
        perror("rsyslogd: error closing pid file");
        ABORT_FINALIZE(RS_RET_ERR);
    }
    fp = NULL;
    if (tmpPidFile != PidFile) {
        if (rename(tmpPidFile, PidFile) != 0) {
            perror("rsyslogd: error writing pid file (rename stage)");
            ABORT_FINALIZE(RS_RET_ERR);
        }
        syncPidFileDir(PidFile);
    }

finalize_it:
    if (iRet != RS_RET_OK && tmpPidFile != NULL && tmpPidFile != PidFile) {
        unlink(tmpPidFile);
    }
    if (fp != NULL) {
        fclose(fp);
    }
    if (tmpPidFile != PidFile) {
        free((void *)tmpPidFile);
    }
    RETiRet;
}

static void clearPidFile(void) {
    if (PidFile != NULL) {
        if (strcmp(PidFile, NO_PIDFILE)) {
            unlink(PidFile);
        }
    }
}

/* duplicate startup protection: check, based on pid file, if our instance
 * is already running. This MUST be called before we write our own pid file.
 */
static rsRetVal checkStartupOK(void) {
    FILE *fp = NULL;
    DEFiRet;

    DBGPRINTF("rsyslogd: checking if startup is ok, pidfile '%s'.\n", PidFile);

    if (!strcmp(PidFile, NO_PIDFILE)) {
        dbgprintf("no pid file shall be written, skipping check\n");
        FINALIZE;
    }

    if ((fp = fopen((char *)PidFile, "r")) == NULL) FINALIZE; /* all well, no pid file yet */

    int pf_pid;
    if (fscanf(fp, "%d", &pf_pid) != 1) {
        fprintf(stderr, "rsyslogd: error reading pid file, cannot start up\n");
        ABORT_FINALIZE(RS_RET_ERR);
    }

    /* ok, we got a pid, let's check if the process is running */
    const pid_t pid = (pid_t)pf_pid;
    if (kill(pid, 0) == 0 || errno != ESRCH) {
        fprintf(stderr,
                "rsyslogd: pidfile '%s' and pid %d already exist.\n"
                "If you want to run multiple instances of rsyslog, you need "
                "to specify\n"
                "different pid files for them (-i option).\n",
                PidFile, (int)getpid());
        ABORT_FINALIZE(RS_RET_ERR);
    }

finalize_it:
    if (fp != NULL) fclose(fp);
    RETiRet;
}


/* note: this function is specific to OS'es which provide
 * the ability to read open file descriptors via /proc.
 * returns 0 - success, something else otherwise
 */
static int close_unneeded_open_files(const char *const procdir, const int beginClose, const int parentPipeFD) {
    DIR *dir;
    struct dirent *entry;

    dir = opendir(procdir);
    if (dir == NULL) {
        dbgprintf("closes unneeded files: opendir failed for %s\n", procdir);
        return 1;
    }

    while ((entry = readdir(dir)) != NULL) {
        const int fd = atoi(entry->d_name);
        if (fd >= beginClose && (((fd != dbgGetDbglogFd()) && (fd != parentPipeFD)))) {
            close(fd);
        }
    }

    closedir(dir);
    return 0;
}

/* prepares the background processes (if auto-backbrounding) for
 * operation.
 */
static void prepareBackground(const int parentPipeFD) {
    DBGPRINTF("rsyslogd: in child, finalizing initialization\n");

    dbgTimeoutToStderr = 0; /* we loose stderr when backgrounding! */
    int r = setsid();
    if (r == -1) {
        char err[1024];
        char em[2048];
        rs_strerror_r(errno, err, sizeof(err));
        snprintf(em, sizeof(em) - 1,
                 "rsyslog: error "
                 "auto-backgrounding: %s\n",
                 err);
        dbgprintf("%s\n", em);
        fprintf(stderr, "%s", em);
    }

    int beginClose = 3;

#ifdef HAVE_LIBSYSTEMD
    /* running under systemd? Then we must make sure we "forward" any
     * fds passed by it (adjust the pid).
     */
    if (sd_booted()) {
        const char *lstnPid = getenv("LISTEN_PID");
        if (lstnPid != NULL) {
            char szBuf[64];
            const int lstnPidI = atoi(lstnPid);
            snprintf(szBuf, sizeof(szBuf), "%d", lstnPidI);
            if (!strcmp(szBuf, lstnPid) && lstnPidI == getppid()) {
                snprintf(szBuf, sizeof(szBuf), "%d", (int)getpid());
                setenv("LISTEN_PID", szBuf, 1);
                /* ensure we do not close what systemd provided */
                const int nFds = sd_listen_fds(0);
                if (nFds > 0) {
                    beginClose = SD_LISTEN_FDS_START + nFds;
                }
            }
        }
    }
#endif

    /* close unnecessary open files - first try to use /proc file system,
     * if that is not possible iterate through all potentially open file
     * descriptors. This can be lenghty, but in practice /proc should work
     * for almost all current systems, and the fallback is primarily for
     * Solaris and AIX, where we do expect a decent max numbers of fds.
     */
    close(0); /* always close stdin, we do not need it */

    /* try Linux, Cygwin, NetBSD */
    if (close_unneeded_open_files("/proc/self/fd", beginClose, parentPipeFD) != 0) {
        /* try MacOS, FreeBSD */
        if (close_unneeded_open_files("/proc/fd", beginClose, parentPipeFD) != 0) {
            /* did not work out, so let's close everything... */
            int endClose = (parentPipeFD > dbgGetDbglogFd()) ? parentPipeFD : dbgGetDbglogFd();
            for (int i = beginClose; i <= endClose; ++i) {
                if ((i != dbgGetDbglogFd()) && (i != parentPipeFD)) {
                    aix_close_it(i); /* AIXPORT */
                }
            }
            beginClose = endClose + 1;
            endClose = getdtablesize();
#if defined(HAVE_CLOSE_RANGE)
            if (close_range(beginClose, endClose, 0) != 0) {
                dbgprintf("errno %d after close_range(), fallback to loop\n", errno);
#endif
                for (int i = beginClose; i <= endClose; ++i) {
                    aix_close_it(i); /* AIXPORT */
                }
#if defined(HAVE_CLOSE_RANGE)
            }
#endif
        }
    }
    seedRandomNumberForChild();
}

/* This is called when rsyslog is set to auto-background itself. If so, a child
 * is forked and the parent waits until it is initialized.
 * The parent never returns from this function, only this happens for the child.
 * So if it returns, you know you are in the child.
 * return: file descriptor to which the child needs to write an "OK" or error
 * message.
 */
static int forkRsyslog(void) {
    int pipefd[2];
    pid_t cpid;
    char err[1024];
    char msgBuf[4096];

    dbgprintf("rsyslogd: parent ready for forking\n");
    if (pipe(pipefd) == -1) {
        perror("error creating rsyslog \"fork pipe\" - terminating");
        exit(1);
    }
    AIX_SRC_EXISTS_IF /* AIXPORT */
        cpid = fork();
    if (cpid == -1) {
        perror("error forking rsyslogd process - terminating");
        exit(1);
    }
    AIX_SRC_EXISTS_FI /* AIXPORT */

        if (cpid == 0) {
        prepareBackground(pipefd[1]);
        close(pipefd[0]);
        return pipefd[1];
    }

    /* we are now in the parent. All we need to do here is wait for the
     * startup message, emit it (if necessary) and then terminate.
     */
    close(pipefd[1]);
    dbgprintf("rsyslogd: parent waiting up to 60 seconds to read startup message\n");

    fd_set rfds;
    struct timeval tv;
    int retval;

    FD_ZERO(&rfds);
    FD_SET(pipefd[0], &rfds);
    tv.tv_sec = 60;
    tv.tv_usec = 0;

    retval = select(pipefd[0] + 1, &rfds, NULL, NULL, &tv);
    if (retval == -1)
        rs_strerror_r(errno, err, sizeof(err));
    else
        RS_COPY_LITERAL(err, "OK");
    dbgprintf("rsyslogd: select() returns %d: %s\n", retval, err);
    if (retval == -1) {
        fprintf(stderr, "rsyslog startup failure, select() failed: %s\n", err);
        exit(1);
    } else if (retval == 0) {
        fprintf(stderr,
                "rsyslog startup failure, child did not "
                "respond within startup timeout (60 seconds)\n");
        exit(1);
    }

    int nRead = read(pipefd[0], msgBuf, sizeof(msgBuf) - 1);
    if (nRead > 0) {
        msgBuf[nRead] = '\0';
    } else {
        rs_strerror_r(errno, err, sizeof(err));
        snprintf(msgBuf, sizeof(msgBuf) - 1, "error reading \"fork pipe\": %s", err);
    }
    if (strcmp(msgBuf, "OK")) {
        dbgprintf("rsyslog parent startup failure: %s\n", msgBuf);
        fprintf(stderr, "rsyslog startup failure: %s\n", msgBuf);
        exit(1);
    }
    close(pipefd[0]);
    dbgprintf("rsyslogd: parent terminates after successful child startup\n");
    exit(0);
}

/* startup processing: this signals the waiting parent that the child is ready
 * and the parent may terminate.
 */
static void tellChildReady(const int pipefd, const char *const msg) {
    dbgprintf("rsyslogd: child signaling OK\n");
    const int nWritten = write(pipefd, msg, strlen(msg));
    dbgprintf("rsyslogd: child signalled OK, nWritten %d\n", (int)nWritten);
    close(pipefd);
    sleep(1);
}

/* print version and compile-time setting information */
static void printVersion(void) {
    printf("rsyslogd  " VERSION " (aka %4d.%2.2d) compiled with:\n", 2000 + VERSION_YEAR, VERSION_MONTH);
    printf("\tPLATFORM:\t\t\t\t%s\n", PLATFORM_ID);
    printf("\tPLATFORM (lsb_release -d):\t\t%s\n", PLATFORM_ID_LSB);
#ifdef FEATURE_REGEXP
    printf("\tFEATURE_REGEXP:\t\t\t\tYes\n");
#else
    printf("\tFEATURE_REGEXP:\t\t\t\tNo\n");
#endif
#if defined(SYSLOG_INET) && defined(USE_GSSAPI)
    printf("\tGSSAPI Kerberos 5 support:\t\tYes\n");
#else
    printf("\tGSSAPI Kerberos 5 support:\t\tNo\n");
#endif
#ifndef NDEBUG
    printf("\tFEATURE_DEBUG (debug build, slow code):\tYes\n");
#else
    printf("\tFEATURE_DEBUG (debug build, slow code):\tNo\n");
#endif
#ifdef HAVE_ATOMIC_BUILTINS
    printf("\t32bit Atomic operations supported:\tYes\n");
#else
    printf("\t32bit Atomic operations supported:\tNo\n");
#endif
#ifdef HAVE_ATOMIC_BUILTINS64
    printf("\t64bit Atomic operations supported:\tYes\n");
#else
    printf("\t64bit Atomic operations supported:\tNo\n");
#endif
#ifdef HAVE_JEMALLOC
    printf("\tmemory allocator:\t\t\tjemalloc\n");
#else
    printf("\tmemory allocator:\t\t\tsystem default\n");
#endif
#ifdef RTINST
    printf("\tRuntime Instrumentation (slow code):\tYes\n");
#else
    printf("\tRuntime Instrumentation (slow code):\tNo\n");
#endif
#ifdef USE_LIBUUID
    printf("\tuuid support:\t\t\t\tYes\n");
#else
    printf("\tuuid support:\t\t\t\tNo\n");
#endif
#ifdef HAVE_LIBSYSTEMD
    printf("\tsystemd support:\t\t\tYes\n");
#else
    printf("\tsystemd support:\t\t\tNo\n");
#endif
#ifdef ENABLE_IMPSTATS_PUSH
    printf("\tFEATURE_IMPSTATS_PUSH:\t\t\tYes\n");
#else
    printf("\tFEATURE_IMPSTATS_PUSH:\t\t\tNo\n");
#endif
#if defined(ENABLE_WOLFSSL)
    printf("\tTLS network stream driver:\t\twolfSSL\n");
#elif defined(ENABLE_OPENSSL)
    printf("\tTLS network stream driver:\t\tOpenSSL\n");
#elif defined(ENABLE_GNUTLS)
    printf("\tTLS network stream driver:\t\tGnuTLS\n");
#elif defined(ENABLE_MBEDTLS)
    printf("\tTLS network stream driver:\t\tMbed TLS\n");
#else
    printf("\tTLS network stream driver:\t\tnone\n");
#endif
    /* we keep the following message to so that users don't need
     * to wonder.
     */
    printf("\tConfig file:\t\t\t\t" PATH_CONFFILE "\n");
    printf("\tPID file:\t\t\t\t" PATH_PIDFILE "%s\n",
           PATH_PIDFILE[0] != '/' ? "(relative to global workingdirectory)" : "");
    printf("\tNumber of Bits in RainerScript integers: 64\n");
    printf("\nSee https://www.rsyslog.com for more information.\n");
}

static rsRetVal rsyslogd_InitStdRatelimiters(void) {
    DEFiRet;
    CHKiRet(ratelimitNew(&dflt_ratelimiter, "rsyslogd", "dflt"));
    CHKiRet(ratelimitNew(&internalMsg_ratelimiter, "rsyslogd", "internal_messages"));
    ratelimitSetThreadSafe(internalMsg_ratelimiter);
    ratelimitSetLinuxLike(internalMsg_ratelimiter, loadConf->globals.intMsgRateLimitItv,
                          loadConf->globals.intMsgRateLimitBurst);
    /* TODO: make internalMsg ratelimit settings configurable */
finalize_it:
    RETiRet;
}


/* Method to initialize all global classes and use the objects that we need.
 * rgerhards, 2008-01-04
 * rgerhards, 2008-04-16: the actual initialization is now carried out by the runtime
 */
static rsRetVal rsyslogd_InitGlobalClasses(void) {
    DEFiRet;
    const char *pErrObj; /* tells us which object failed if that happens (useful for troubleshooting!) */

    /* Intialize the runtime system */
    pErrObj = "rsyslog runtime"; /* set in case the runtime errors before setting an object */
    CHKiRet(rsrtInit(&pErrObj, &obj));
    rsrtSetErrLogger(rsyslogd_submitErrMsg);

    /* Now tell the system which classes we need ourselfs */
    pErrObj = "glbl";
    CHKiRet(objUse(glbl, CORE_COMPONENT));
    pErrObj = "module";
    CHKiRet(objUse(module, CORE_COMPONENT));
    pErrObj = "datetime";
    CHKiRet(objUse(datetime, CORE_COMPONENT));
    pErrObj = "ruleset";
    CHKiRet(objUse(ruleset, CORE_COMPONENT));
    pErrObj = "prop";
    CHKiRet(objUse(prop, CORE_COMPONENT));
    pErrObj = "parser";
    CHKiRet(objUse(parser, CORE_COMPONENT));
    pErrObj = "rsconf";
    CHKiRet(objUse(rsconf, CORE_COMPONENT));

    /* initialize some dummy classes that are not part of the runtime */
    pErrObj = "action";
    CHKiRet(actionClassInit());
    pErrObj = "template";
    CHKiRet(templateInit());

    /* TODO: the dependency on net shall go away! -- rgerhards, 2008-03-07 */
    pErrObj = "net";
    CHKiRet(objUse(net, LM_NET_FILENAME));

    dnscacheInit();
    initRainerscript();
    ratelimitModInit();

    /* we need to create the inputName property (only once during our lifetime) */
    CHKiRet(prop.Construct(&pInternalInputName));
    CHKiRet(prop.SetString(pInternalInputName, UCHAR_CONSTANT("rsyslogd"), sizeof("rsyslogd") - 1));
    CHKiRet(prop.ConstructFinalize(pInternalInputName));

finalize_it:
    if (iRet != RS_RET_OK) {
        /* we know we are inside the init sequence, so we can safely emit
         * messages to stderr. -- rgerhards, 2008-04-02
         */
        fprintf(stderr, "Error during class init for object '%s' - failing...\n", pErrObj);
        fprintf(stderr,
                "rsyslogd initialization failed - global classes could not be initialized.\n"
                "Did you do a \"make install\"?\n"
                "Suggested action: run rsyslogd with -d -n options to see what exactly "
                "fails.\n");
    }

    RETiRet;
}

/* preprocess a batch of messages, that is ready them for actual processing. This is done
 * as a first stage and totally in parallel to any other worker active in the system. So
 * it helps us keep up the overall concurrency level.
 * rgerhards, 2010-06-09
 */
static rsRetVal preprocessBatch(batch_t *pBatch, wti_t *pWti) {
    prop_t *ip;
    prop_t *fqdn;
    prop_t *localName;
    int bIsPermitted;
    smsg_t *pMsg;
    int i;
    rsRetVal localRet;
    DEFiRet;

    for (i = 0; i < pBatch->nElem && !wtiIsShutdownImmediate(pWti); i++) {
        pMsg = pBatch->pElem[i].pMsg;
        if ((pMsg->msgFlags & NEEDS_ACLCHK_U) != 0) {
            DBGPRINTF("msgConsumer: UDP ACL must be checked for message (hostname-based)\n");
            if (net.cvthname(pMsg->rcvFrom.pfrominet, &localName, &fqdn, &ip) != RS_RET_OK) continue;
            bIsPermitted = net.isAllowedSender2((uchar *)"UDP", (struct sockaddr *)pMsg->rcvFrom.pfrominet,
                                                (char *)propGetSzStr(fqdn), 1);
            if (!bIsPermitted) {
                DBGPRINTF("Message from '%s' discarded, not a permitted sender host\n", propGetSzStr(fqdn));
                pBatch->eltState[i] = BATCH_STATE_DISC;
            } else {
                /* save some of the info we obtained */
                MsgSetRcvFrom(pMsg, localName);
                CHKiRet(MsgSetRcvFromIP(pMsg, ip));
                pMsg->msgFlags &= ~NEEDS_ACLCHK_U;
            }
        }
        if ((pMsg->msgFlags & NEEDS_PARSING) != 0) {
            if ((localRet = parser.ParseMsg(pMsg)) != RS_RET_OK) {
                DBGPRINTF("Message discarded, parsing error %d\n", localRet);
                pBatch->eltState[i] = BATCH_STATE_DISC;
            }
        }
    }

finalize_it:
    RETiRet;
}


/**
 * @brief Message Consumer (Worker Thread).
 *
 * This function runs on a **separate thread** as part of the Queue Worker Pool.
 * It consumes a batch of messages dequeued from the main message queue.
 *
 * @note No further batching is performed here, as optimization happens in `preprocessBatch`.
 *
 * @param notNeeded Unused argument (required for thread signature).
 * @param pBatch Pointer to the batch of messages to process.
 * @param pWti Pointer to the Worker Thread Instance (WTI) data.
 * @return RS_RET_OK on success.
 */
static rsRetVal msgConsumer(void __attribute__((unused)) * notNeeded, batch_t *pBatch, wti_t *pWti) {
    DEFiRet;
    assert(pBatch != NULL);
    preprocessBatch(pBatch, pWti);
    ruleset.ProcessBatch(pBatch, pWti);
    // TODO: the BATCH_STATE_COMM must be set somewhere down the road, but we
    // do not have this yet and so we emulate -- 2010-06-10
    int i;
    for (i = 0; i < pBatch->nElem && !wtiIsShutdownImmediate(pWti); i++) {
        pBatch->eltState[i] = BATCH_STATE_COMM;
    }
    RETiRet;
}


/* create a main message queue, now also used for ruleset queues. This function
 * needs to be moved to some other module, but it is considered acceptable for
 * the time being (remember that we want to restructure config processing at large!).
 * rgerhards, 2009-10-27
 */
rsRetVal createMainQueue(qqueue_t **ppQueue, uchar *pszQueueName, struct nvlst *lst) {
    struct queuefilenames_s *qfn;
    uchar *qfname = NULL;
    uchar *queueName = pszQueueName;
    uchar *queueNameOverride = NULL;
    static int qfn_renamenum = 0;
    uchar qfrenamebuf[1024];
    DEFiRet;

    if (lst != NULL) {
        static struct cnfparamdescr mainqdescr[] = {{"name", eCmdHdlrString, 0}};
        static struct cnfparamblk mainqpblk = {CNFPARAMBLK_VERSION, sizeof(mainqdescr) / sizeof(mainqdescr[0]),
                                               mainqdescr};
        struct cnfparamvals *pvals;
        int nameIdx;

        pvals = nvlstGetParams(lst, &mainqpblk, NULL);
        if (pvals == NULL) {
            ABORT_FINALIZE(RS_RET_CONFIG_ERROR);
        }
        nameIdx = cnfparamGetIdx(&mainqpblk, "name");
        if (nameIdx != -1 && pvals[nameIdx].bUsed) {
            queueNameOverride = (uchar *)es_str2cstr(pvals[nameIdx].val.d.estr, NULL);
            if (queueNameOverride != NULL && queueNameOverride[0] != '\0') {
                queueName = queueNameOverride;
            }
        }
        cnfparamvalsDestruct(pvals, &mainqpblk);
    }

    /* create message queue */
    CHKiRet_Hdlr(qqueueConstruct(ppQueue, ourConf->globals.mainQ.MainMsgQueType,
                                 ourConf->globals.mainQ.iMainMsgQueueNumWorkers,
                                 ourConf->globals.mainQ.iMainMsgQueueSize, msgConsumer)) {
        /* no queue is fatal, we need to give up in that case... */
        LogError(0, iRet, "could not create (ruleset) main message queue");
    }
    /* name our main queue object (it's not fatal if it fails...) */
    obj.SetName((obj_t *)(*ppQueue), queueName);

    if (lst == NULL) { /* use legacy parameters? */
        /* ... set some properties ... */
#define setQPROP(func, directive, data)          \
    CHKiRet_Hdlr(func(*ppQueue, data)) {         \
        LogError(0, NO_ERRCODE,                  \
                 "Invalid " #directive           \
                 ", error %d. Ignored, "         \
                 "running with default setting", \
                 iRet);                          \
    }
#define setQPROPstr(func, directive, data)                                          \
    CHKiRet_Hdlr(func(*ppQueue, data, (data == NULL) ? 0 : strlen((char *)data))) { \
        LogError(0, NO_ERRCODE,                                                     \
                 "Invalid " #directive                                              \
                 ", error %d. Ignored, "                                            \
                 "running with default setting",                                    \
                 iRet);                                                             \
    }

        if (ourConf->globals.mainQ.pszMainMsgQFName != NULL) {
            /* check if the queue file name is unique, else emit an error */
            for (qfn = queuefilenames; qfn != NULL; qfn = qfn->next) {
                dbgprintf("check queue file name '%s' vs '%s'\n", qfn->name, ourConf->globals.mainQ.pszMainMsgQFName);
                if (!ustrcmp(qfn->name, ourConf->globals.mainQ.pszMainMsgQFName)) {
                    snprintf((char *)qfrenamebuf, sizeof(qfrenamebuf), "%d-%s-%s", ++qfn_renamenum,
                             ourConf->globals.mainQ.pszMainMsgQFName,
                             (queueName == NULL) ? "NONAME" : (char *)queueName);
                    qfname = ustrdup(qfrenamebuf);
                    LogError(0, NO_ERRCODE,
                             "Error: queue file name '%s' already in use "
                             " - using '%s' instead",
                             ourConf->globals.mainQ.pszMainMsgQFName, qfname);
                    break;
                }
            }
            if (qfname == NULL) qfname = ustrdup(ourConf->globals.mainQ.pszMainMsgQFName);
            qfn = malloc(sizeof(struct queuefilenames_s));
            qfn->name = qfname;
            qfn->next = queuefilenames;
            queuefilenames = qfn;
        }

        setQPROP(qqueueSetMaxFileSize, "$MainMsgQueueFileSize", ourConf->globals.mainQ.iMainMsgQueMaxFileSize);
        setQPROP(qqueueSetsizeOnDiskMax, "$MainMsgQueueMaxDiskSpace", ourConf->globals.mainQ.iMainMsgQueMaxDiskSpace);
        setQPROP(qqueueSetiDeqBatchSize, "$MainMsgQueueDequeueBatchSize",
                 ourConf->globals.mainQ.iMainMsgQueDeqBatchSize);
        setQPROPstr(qqueueSetFilePrefix, "$MainMsgQueueFileName", qfname);
        setQPROP(qqueueSetiPersistUpdCnt, "$MainMsgQueueCheckpointInterval",
                 ourConf->globals.mainQ.iMainMsgQPersistUpdCnt);
        setQPROP(qqueueSetbSyncQueueFiles, "$MainMsgQueueSyncQueueFiles",
                 ourConf->globals.mainQ.bMainMsgQSyncQeueFiles);
        setQPROP(qqueueSettoQShutdown, "$MainMsgQueueTimeoutShutdown", ourConf->globals.mainQ.iMainMsgQtoQShutdown);
        setQPROP(qqueueSettoActShutdown, "$MainMsgQueueTimeoutActionCompletion",
                 ourConf->globals.mainQ.iMainMsgQtoActShutdown);
        setQPROP(qqueueSettoWrkShutdown, "$MainMsgQueueWorkerTimeoutThreadShutdown",
                 ourConf->globals.mainQ.iMainMsgQtoWrkShutdown);
        setQPROP(qqueueSettoEnq, "$MainMsgQueueTimeoutEnqueue", ourConf->globals.mainQ.iMainMsgQtoEnq);
        setQPROP(qqueueSetiHighWtrMrk, "$MainMsgQueueHighWaterMark", ourConf->globals.mainQ.iMainMsgQHighWtrMark);
        setQPROP(qqueueSetiLowWtrMrk, "$MainMsgQueueLowWaterMark", ourConf->globals.mainQ.iMainMsgQLowWtrMark);
        setQPROP(qqueueSetiDiscardMrk, "$MainMsgQueueDiscardMark", ourConf->globals.mainQ.iMainMsgQDiscardMark);
        setQPROP(qqueueSetiDiscardSeverity, "$MainMsgQueueDiscardSeverity",
                 ourConf->globals.mainQ.iMainMsgQDiscardSeverity);
        setQPROP(qqueueSetiMinMsgsPerWrkr, "$MainMsgQueueWorkerThreadMinimumMessages",
                 ourConf->globals.mainQ.iMainMsgQWrkMinMsgs);
        setQPROP(qqueueSetbSaveOnShutdown, "$MainMsgQueueSaveOnShutdown",
                 ourConf->globals.mainQ.bMainMsgQSaveOnShutdown);
        setQPROP(qqueueSetiDeqSlowdown, "$MainMsgQueueDequeueSlowdown", ourConf->globals.mainQ.iMainMsgQDeqSlowdown);
        setQPROP(qqueueSetiDeqtWinFromHr, "$MainMsgQueueDequeueTimeBegin",
                 ourConf->globals.mainQ.iMainMsgQueueDeqtWinFromHr);
        setQPROP(qqueueSetiDeqtWinToHr, "$MainMsgQueueDequeueTimeEnd", ourConf->globals.mainQ.iMainMsgQueueDeqtWinToHr);

#undef setQPROP
#undef setQPROPstr
    } else { /* use new style config! */
        qqueueSetDefaultsRulesetQueue(*ppQueue);
        qqueueApplyCnfParam(*ppQueue, lst);
    }
    qqueueCorrectParams(*ppQueue);

finalize_it:
    free(queueNameOverride);
    RETiRet;
}

rsRetVal startMainQueue(rsconf_t *cnf, qqueue_t *const pQueue) {
    DEFiRet;
    CHKiRet_Hdlr(qqueueStart(cnf, pQueue)) {
        /* no queue is fatal, we need to give up in that case... */
        LogError(0, iRet, "could not start (ruleset) main message queue");
        if (runConf->globals.bAbortOnFailedQueueStartup) {
            fprintf(stderr,
                    "rsyslogd: could not start (ruleset) main message queue, "
                    "abortOnFailedQueueStartup is set, so we abort rsyslog now.\n");
            fflush(stderr);
            clearPidFile();
            exit(1); /* "good" exit, this is intended here */
        }
        pQueue->qType = QUEUETYPE_DIRECT;
        CHKiRet_Hdlr(qqueueStart(cnf, pQueue)) {
            /* no queue is fatal, we need to give up in that case... */
            LogError(0, iRet, "fatal error: could not even start queue in direct mode");
        }
    }
    RETiRet;
}


/* this is a special function used to submit an error message. This
 * function is also passed to the runtime library as the generic error
 * message handler. -- rgerhards, 2008-04-17
 */
void rsyslogd_submitErrMsg(const int severity, const int iErr, const uchar *msg) {
    if (glbl.GetGlobalInputTermState() == 1) {
        /* After fork the stderr is unusable (dfltErrLogger uses is internally) */
        if (!doFork) dfltErrLogger(severity, iErr, msg);
    } else {
        logmsgInternal(iErr, LOG_SYSLOG | (severity & 0x07), msg, 0);
    }
}

static inline rsRetVal submitMsgWithDfltRatelimiter(smsg_t *pMsg) {
    return ratelimitAddMsg(dflt_ratelimiter, NULL, pMsg);
}


static void logmsgInternal_doWrite(smsg_t *pMsg) {
    const int pri = getPRIi(pMsg);
    if (pri % 8 <= runConf->globals.intMsgsSeverityFilter) {
        if (runConf->globals.bProcessInternalMessages) {
            submitMsg2(pMsg);
            pMsg = NULL; /* msg obj handed over; do not destruct */
        } else {
            uchar *const msg = getMSG(pMsg);
#ifdef ENABLE_LIBLOGGING_STDLOG
            /* the "emit only once" rate limiter is quick and dirty and not
             * thread safe. However, that's no problem for the current intend
             * and it is not justified to create more robust code for the
             * functionality. -- rgerhards, 2018-05-14
             */
            static warnmsg_emitted = 0;
            if (warnmsg_emitted == 0) {
                stdlog_log(runConf->globals.stdlog_hdl, LOG_WARNING, "%s",
                           "RSYSLOG WARNING: liblogging-stdlog "
                           "functionality will go away soon. For details see "
                           "https://github.com/rsyslog/rsyslog/issues/2706");
                warnmsg_emitted = 1;
            }
            stdlog_log(runConf->globals.stdlog_hdl, pri2sev(pri), "%s", (char *)msg);
#else
            syslog(pri, "%s", msg);
#endif
        }
    }
    if (pMsg != NULL) {
        msgDestruct(&pMsg);
    }
}

/* This function creates a log message object out of the provided
 * message text and forwards it for logging.
 */
static rsRetVal logmsgInternalSubmit(
    const int iErr, const syslog_pri_t pri, const size_t lenMsg, const char *__restrict__ const msg, int flags) {
    uchar pszTag[33];
    smsg_t *pMsg;
    DEFiRet;

    if (glblAbortOnProgramError && iErr == RS_RET_PROGRAM_ERROR) {
        fprintf(stderr,
                "\n\n\n========================================\n"
                "rsyslog reports program error: %s\n"
                "rsyslog is configured to abort in this case, "
                "this will be done now\n",
                msg);
        fflush(stdout);
        abort();
    }

    CHKiRet(msgConstruct(&pMsg));
    MsgSetInputName(pMsg, pInternalInputName);
    MsgSetRawMsg(pMsg, (char *)msg, lenMsg);
    MsgSetHOSTNAME(pMsg, glbl.GetLocalHostName(), ustrlen(glbl.GetLocalHostName()));
    MsgSetRcvFrom(pMsg, glbl.GetLocalHostNameProp());
    MsgSetRcvFromIP(pMsg, glbl.GetLocalHostIP());
    MsgSetMSGoffs(pMsg, 0);
    /* check if we have an error code associated and, if so,
     * adjust the tag. -- rgerhards, 2008-06-27
     */
    if (iErr == NO_ERRCODE) {
        MsgSetTAG(pMsg, UCHAR_CONSTANT("rsyslogd:"), sizeof("rsyslogd:") - 1);
    } else {
        size_t len = snprintf((char *)pszTag, sizeof(pszTag), "rsyslogd%d:", iErr);
        pszTag[32] = '\0'; /* just to make sure... */
        MsgSetTAG(pMsg, pszTag, len);
    }

    flags |= INTERNAL_MSG;
    pMsg->msgFlags = flags;
    msgSetPRI(pMsg, pri);

    iminternalAddMsg(pMsg);
finalize_it:
    RETiRet;
}


/* rgerhards 2004-11-09: the following is a function that can be used
 * to log a message orginating from the syslogd itself.
 */
rsRetVal logmsgInternal(int iErr, const syslog_pri_t pri, const uchar *const msg, int flags) {
    size_t lenMsg;
    unsigned i;
    char *bufModMsg = NULL; /* buffer for modified message, should we need to modify */
    DEFiRet;

    /* we first do a path the remove control characters that may have accidently
     * introduced (program error!). This costs performance, but we do not expect
     * to be called very frequently in any case ;) -- rgerhards, 2013-12-19.
     */
    lenMsg = ustrlen(msg);
    for (i = 0; i < lenMsg; ++i) {
        if (msg[i] < 0x20 || msg[i] == 0x7f) {
            if (bufModMsg == NULL) {
                CHKmalloc(bufModMsg = strdup((char *)msg));
            }
            bufModMsg[i] = ' ';
        }
    }

    CHKiRet(logmsgInternalSubmit(iErr, pri, lenMsg, (bufModMsg == NULL) ? (char *)msg : bufModMsg, flags));

    /* we now check if we should print internal messages out to stderr. This was
     * suggested by HKS as a way to help people troubleshoot rsyslog configuration
     * (by running it interactively. This makes an awful lot of sense, so I add
     * it here. -- rgerhards, 2008-07-28
     * Note that error messages can not be disabled during a config verify. This
     * permits us to process unmodified config files which otherwise contain a
     * supressor statement.
     */
    int emit_to_stderr = (ourConf == NULL) ? 1 : (ourConf->globals.bErrMsgToStderr || ourConf->globals.bAllMsgToStderr);
    int emit_supress_msg = 0;
    if (Debug == DEBUG_FULL || !doFork) {
        emit_to_stderr = 1;
    }
    if (ourConf != NULL && ourConf->globals.maxErrMsgToStderr != -1) {
        if (emit_to_stderr && ourConf->globals.maxErrMsgToStderr != -1 && ourConf->globals.maxErrMsgToStderr) {
            --ourConf->globals.maxErrMsgToStderr;
            if (ourConf->globals.maxErrMsgToStderr == 0) emit_supress_msg = 1;
        } else {
            emit_to_stderr = 0;
        }
    }
    if (emit_to_stderr || iConfigVerify) {
        if ((ourConf != NULL && ourConf->globals.bAllMsgToStderr) || pri2sev(pri) == LOG_ERR ||
            (iConfigVerify && pri2sev(pri) <= LOG_WARNING))
            fprintf(stderr, "rsyslogd: %s\n", (bufModMsg == NULL) ? (char *)msg : bufModMsg);
    }
    if (emit_supress_msg) {
        fprintf(stderr,
                "rsyslogd: configured max number of error messages "
                "to stderr reached, further messages will not be output\n"
                "Consider adjusting\n"
                "    global(errorMessagesToStderr.maxNumber=\"xx\")\n"
                "if you want more.\n");
    }

finalize_it:
    free(bufModMsg);
    RETiRet;
}

rsRetVal submitMsg(smsg_t *pMsg) {
    return submitMsgWithDfltRatelimiter(pMsg);
}


static rsRetVal ATTR_NONNULL() splitOversizeMessage(smsg_t *const pMsg) {
    DEFiRet;
    const char *rawmsg;
    int nsegments;
    int len_rawmsg;
    const int maxlen = glblGetMaxLine(runConf);
    ISOBJ_TYPE_assert(pMsg, msg);

    getRawMsg(pMsg, (uchar **)&rawmsg, &len_rawmsg);
    nsegments = len_rawmsg / maxlen;
    const int len_last_segment = len_rawmsg % maxlen;
    DBGPRINTF(
        "splitting oversize message, size %d, segment size %d, "
        "nsegments %d, bytes in last fragment %d\n",
        len_rawmsg, maxlen, nsegments, len_last_segment);

    smsg_t *pMsg_seg;

    /* process full segments */
    for (int i = 0; i < nsegments; ++i) {
        CHKmalloc(pMsg_seg = MsgDup(pMsg));
        MsgSetRawMsg(pMsg_seg, rawmsg + (i * maxlen), maxlen);
        submitMsg2(pMsg_seg);
    }

    /* if necessary, write partial last segment */
    if (len_last_segment != 0) {
        CHKmalloc(pMsg_seg = MsgDup(pMsg));
        MsgSetRawMsg(pMsg_seg, rawmsg + (nsegments * maxlen), len_last_segment);
        submitMsg2(pMsg_seg);
    }

finalize_it:
    RETiRet;
}


/**
 * @brief Submit a message to the Main Message Queue (Wrapper).
 *
 * A helper function that abstracts the main message queue details from callers.
 * It is commonly used by plugins to inject messages into the processing pipeline.
 *
 * @param pMsg The message object to submit.
 * @return RS_RET_OK on success.
 */
rsRetVal submitMsg2(smsg_t *pMsg) {
    qqueue_t *pQueue;
    ruleset_t *pRuleset;
    DEFiRet;

    ISOBJ_TYPE_assert(pMsg, msg);

    if (getRawMsgLen(pMsg) > glblGetMaxLine(runConf)) {
        uchar *rawmsg;
        int dummy;
        getRawMsg(pMsg, &rawmsg, &dummy);
        if (glblReportOversizeMessage(runConf)) {
            LogMsg(0, RS_RET_OVERSIZE_MSG, LOG_WARNING,
                   "message too long (%d) with configured size %d, begin of "
                   "message is: %.80s",
                   getRawMsgLen(pMsg), glblGetMaxLine(runConf), rawmsg);
        }
        writeOversizeMessageLog(pMsg);
        if (glblGetOversizeMsgInputMode(runConf) == glblOversizeMsgInputMode_Split) {
            splitOversizeMessage(pMsg);
            /* we have submitted the message segments recursively, so we
             * can just deleted the original msg object and terminate.
             */
            msgDestruct(&pMsg);
            FINALIZE;
        } else if (glblGetOversizeMsgInputMode(runConf) == glblOversizeMsgInputMode_Truncate) {
            MsgTruncateToMaxSize(pMsg);
        } else {
            /* in "accept" mode, we do nothing, simply because "accept" means
             * to use as-is.
             */
            assert(glblGetOversizeMsgInputMode(runConf) == glblOversizeMsgInputMode_Accept);
        }
    }

    pRuleset = MsgGetRuleset(pMsg);
    assert(ruleset.GetRulesetQueue != NULL); /* This is only to keep clang static analyzer happy */
    pQueue = (pRuleset == NULL) ? runConf->pMsgQueue : ruleset.GetRulesetQueue(pRuleset);

    /* if a plugin logs a message during shutdown, the queue may no longer exist */
    if (pQueue == NULL) {
        DBGPRINTF(
            "submitMsg2() could not submit message - "
            "queue does (no longer?) exist - ignored\n");
        FINALIZE;
    }

    qqueueEnqMsg(pQueue, pMsg->flowCtlType, pMsg);

finalize_it:
    RETiRet;
}

/* submit multiple messages at once, very similar to submitMsg, just
 * for multi_submit_t. All messages need to go into the SAME queue!
 * rgerhards, 2009-06-16
 */
rsRetVal ATTR_NONNULL() multiSubmitMsg2(multi_submit_t *const pMultiSub) {
    qqueue_t *pQueue;
    ruleset_t *pRuleset;
    DEFiRet;

    if (pMultiSub->nElem == 0) FINALIZE;

    pRuleset = MsgGetRuleset(pMultiSub->ppMsgs[0]);
    pQueue = (pRuleset == NULL) ? runConf->pMsgQueue : ruleset.GetRulesetQueue(pRuleset);

    /* if a plugin logs a message during shutdown, the queue may no longer exist */
    if (pQueue == NULL) {
        DBGPRINTF(
            "multiSubmitMsg() could not submit message - "
            "queue does (no longer?) exist - ignored\n");
        FINALIZE;
    }

    iRet = pQueue->MultiEnq(pQueue, pMultiSub);
    pMultiSub->nElem = 0;

finalize_it:
    RETiRet;
}
rsRetVal multiSubmitMsg(multi_submit_t *pMultiSub) /* backward compat. level */
{
    return multiSubmitMsg2(pMultiSub);
}


/* flush multiSubmit, e.g. at end of read records */
rsRetVal multiSubmitFlush(multi_submit_t *pMultiSub) {
    DEFiRet;
    if (pMultiSub->nElem > 0) {
        iRet = multiSubmitMsg2(pMultiSub);
    }
    RETiRet;
}


/* some support for command line option parsing. Any non-trivial options must be
 * buffered until the complete command line has been parsed. This is necessary to
 * prevent dependencies between the options. That, in turn, means we need to have
 * something that is capable of buffering options and there values. The follwing
 * functions handle that.
 * rgerhards, 2008-04-04
 */
typedef struct bufOpt {
    struct bufOpt *pNext;
    char optchar;
    char *arg;
} bufOpt_t;
static bufOpt_t *bufOptRoot = NULL;
static bufOpt_t *bufOptLast = NULL;

/* add option buffer */
static rsRetVal bufOptAdd(char opt, char *arg) {
    DEFiRet;
    bufOpt_t *pBuf;

    if ((pBuf = malloc(sizeof(bufOpt_t))) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);

    pBuf->optchar = opt;
    pBuf->arg = arg;
    pBuf->pNext = NULL;

    if (bufOptLast == NULL) {
        bufOptRoot = pBuf; /* then there is also no root! */
    } else {
        bufOptLast->pNext = pBuf;
    }
    bufOptLast = pBuf;

finalize_it:
    RETiRet;
}


/* remove option buffer from top of list, return values and destruct buffer itself.
 * returns RS_RET_END_OF_LINKEDLIST when no more options are present.
 * (we use int *opt instead of char *opt to keep consistent with getopt())
 */
static rsRetVal bufOptRemove(int *opt, char **arg) {
    DEFiRet;
    bufOpt_t *pBuf;

    if (bufOptRoot == NULL) ABORT_FINALIZE(RS_RET_END_OF_LINKEDLIST);
    pBuf = bufOptRoot;

    *opt = pBuf->optchar;
    *arg = pBuf->arg;

    bufOptRoot = pBuf->pNext;
    free(pBuf);

finalize_it:
    RETiRet;
}


static void hdlr_sigttin_ou(void) {
    /* this is just a dummy to care for our sigttin input
     * module cancel interface and sigttou internal message
     * notificaton/mainloop wakeup mechanism. The important
     * point is that it actually does *NOTHING*.
     */
}

static void hdlr_enable(int sig, void (*hdlr)()) {
    struct sigaction sigAct;
    memset(&sigAct, 0, sizeof(sigAct));
    sigemptyset(&sigAct.sa_mask);
    sigAct.sa_handler = hdlr;
    sigaction(sig, &sigAct, NULL);
}

static void hdlr_sighup(void) {
    PREFER_STORE_INT(&bHadHUP, 1);
    /* at least on FreeBSD we seem not to necessarily awake the main thread.
     * So let's do it explicitely.
     */
    dbgprintf("awaking mainthread on HUP\n");
    pthread_kill(mainthread, SIGTTIN);
}

static void hdlr_sigchld(void) {
    PREFER_STORE_INT(&bChildDied, 1);
}

static void rsyslogdDebugSwitch(void) {
    time_t tTime;
    struct tm tp;

    datetime.GetTime(&tTime);
    localtime_r(&tTime, &tp);
    if (debugging_on == 0) {
        debugging_on = 1;
        dbgprintf("\n");
        dbgprintf("\n");
        dbgprintf("********************************************************************************\n");
        dbgprintf("Switching debugging_on to true at %2.2d:%2.2d:%2.2d\n", tp.tm_hour, tp.tm_min, tp.tm_sec);
        dbgprintf("********************************************************************************\n");
    } else {
        dbgprintf("********************************************************************************\n");
        dbgprintf("Switching debugging_on to false at %2.2d:%2.2d:%2.2d\n", tp.tm_hour, tp.tm_min, tp.tm_sec);
        dbgprintf("********************************************************************************\n");
        dbgprintf("\n");
        dbgprintf("\n");
        debugging_on = 0;
    }
}


/* This is the main entry point into rsyslogd. Over time, we should try to
 * modularize it a bit more...
 *
 * NOTE on stderr and stdout: they are kept open during a fork. Note that this
 * may introduce subtle security issues: if we are in a jail, one may break out of
 * it via these descriptors. But if I close them earlier, error messages will (once
 * again) not be emitted to the user that starts the daemon. Given that the risk
 * of a break-in is very low in the startup phase, we decide it is more important
 * to emit error messages.
 */
static void initAll(int argc, char **argv) {
    rsRetVal localRet;
    int ch;
    int iHelperUOpt;
    int bChDirRoot = 1; /* change the current working directory to "/"? */
    char *arg; /* for command line option processing */
    char *configOutputPath = NULL;
    enum rsconfTranslateFormat iTranslateFmt = RSCONF_TRANSLATE_NONE;
    char cwdbuf[128]; /* buffer to obtain/display current working directory */
    int parentPipeFD = 0; /* fd of pipe to parent, if auto-backgrounding */
    DEFiRet;

    /* prepare internal signaling */
    hdlr_enable(SIGTTIN, hdlr_sigttin_ou);
    hdlr_enable(SIGTTOU, hdlr_sigttin_ou);

    /*
     * Command-line option parser (Pass 1).
     *
     * Parses command-line options to determine the operating mode and set initial flags.
     *
     * This block implements a "two-pass" strategy:
     * 1. Immediate flags: Options like debug (`-d`) or no-fork (`-n`) are set immediately.
     * 2. Deferred arguments: Complex options are buffered into a list (`bufOptAdd`)
     *    to be processed later by `processStartupOptions`.
     *
     * This separation allows rsyslog to set up the runtime environment (like debug logging)
     * before processing complex configurations that might depend on it.
     */
#if defined(_AIX)
    while ((ch = getopt(argc, argv, "46ACDdf:F:hi:M:nN:o:qQS:T:u:vwxR")) != EOF) {
#else
    while ((ch = getopt(argc, argv, "46ACDdf:F:hi:M:nN:o:qQS:T:u:vwx")) != EOF) {
#endif
        switch ((char)ch) {
            case '4':
            case '6':
            case 'A':
            case 'f': /* configuration file */
            case 'F': /* translation output format */
            case 'i': /* pid file name */
            case 'n': /* don't fork */
            case 'N': /* enable config verify mode */
            case 'q': /* add hostname if DNS resolving has failed */
            case 'Q': /* dont resolve hostnames in ACL to IPs */
            case 'S': /* Source IP for local client to be used on multihomed host */
            case 'T': /* chroot on startup (primarily for testing) */
            case 'u': /* misc user settings */
            case 'w': /* disable disallowed host warnings */
            case 'C':
            case 'o': /* write output config file */
            case 'x': /* disable dns for remote messages */
                CHKiRet(bufOptAdd(ch, optarg));
                break;
#if defined(_AIX)
            case 'R': /* This option is a no-op for AIX */
                break;
#endif
            case 'd': /* debug - must be handled now, so that debug is active during init! */
                debugging_on = 1;
                Debug = 1;
                yydebug = 1;
                break;
            case 'D': /* BISON debug */
                yydebug = 1;
                break;
            case 'M': /* default module load path -- this MUST be carried out immediately! */
                glblModPath = (uchar *)optarg;
                break;
            case 'v': /* MUST be carried out immediately! */
                printVersion();
                exit(0); /* exit for -v option - so this is a "good one" */
            case 'h':
            case '?':
            default:
                rsyslogd_usage();
        }
    }

    if (argc - optind) rsyslogd_usage();

    DBGPRINTF("rsyslogd %s startup, module path '%s', cwd:%s\n", VERSION,
              glblModPath == NULL ? "" : (char *)glblModPath, getcwd(cwdbuf, sizeof(cwdbuf)));

    /* we are done with the initial option parsing and processing. Now we init the system. */

    CHKiRet(rsyslogd_InitGlobalClasses());

    /* doing some core initializations */

    if ((iRet = modInitIminternal()) != RS_RET_OK) {
        fprintf(stderr, "fatal error: could not initialize errbuf object (error code %d).\n", iRet);
        exit(1); /* "good" exit, leaving at init for fatal error */
    }

    /* we now can emit error messages "the regular way" */

    if (getenv("TZ") == NULL) {
        const char *const tz = (access("/etc/localtime", R_OK) == 0) ? "TZ=/etc/localtime" : "TZ=UTC";
        putenv((char *)tz);
        if (emitTZWarning) {
            LogMsg(0, RS_RET_NO_TZ_SET, LOG_WARNING,
                   "environment variable TZ is not "
                   "set, auto correcting this to %s",
                   tz);
        } else {
            dbgprintf("environment variable TZ is not set, auto correcting this to %s\n", tz);
        }
    }

    /* END core initializations - we now come back to carrying out command line options*/

    while ((iRet = bufOptRemove(&ch, &arg)) == RS_RET_OK) {
        DBGPRINTF("deque option %c, optarg '%s'\n", ch, (arg == NULL) ? "" : arg);
        switch ((char)ch) {
            case '4':
                fprintf(stderr,
                        "rsyslogd: the -4 command line option has gone away.\n"
                        "Please use the global(net.ipprotocol=\"ipv4-only\") "
                        "configuration parameter instead.\n");
                break;
            case '6':
                fprintf(stderr,
                        "rsyslogd: the -6 command line option will has gone away.\n"
                        "Please use the global(net.ipprotocol=\"ipv6-only\") "
                        "configuration parameter instead.\n");
                break;
            case 'A':
                fprintf(stderr,
                        "rsyslogd: the -A command line option will go away "
                        "soon.\n"
                        "Please use the omfwd parameter \"upd.sendToAll\" instead.\n");
                send_to_all++;
                break;
            case 'S': /* Source IP for local client to be used on multihomed host */
                fprintf(stderr,
                        "rsyslogd: the -S command line option will go away "
                        "soon.\n"
                        "Please use the omrelp parameter \"localClientIP\" instead.\n");
                if (glbl.GetSourceIPofLocalClient() != NULL) {
                    fprintf(stderr, "rsyslogd: Only one -S argument allowed, the first one is taken.\n");
                } else {
                    glbl.SetSourceIPofLocalClient((uchar *)arg);
                }
                break;
            case 'f': /* configuration file */
                ConfFile = (uchar *)arg;
                break;
            case 'i': /* pid file name */
                free((void *)PidFile);
                PidFile = arg;
                break;
            case 'n': /* don't fork */
                doFork = 0;
                break;
            case 'N': /* enable config verify mode */
                iConfigVerify = (arg == NULL) ? 0 : atoi(arg);
                break;
            case 'F':
                if (arg == NULL) {
                    fprintf(stderr, "rsyslogd: -F requires a format argument\n");
                    exit(1);
                }
                if (!strcasecmp(arg, "yaml")) {
                    iTranslateFmt = RSCONF_TRANSLATE_YAML;
                } else if (!strcasecmp(arg, "rainerscript")) {
                    iTranslateFmt = RSCONF_TRANSLATE_RAINERSCRIPT;
                } else {
                    fprintf(stderr, "rsyslogd: unsupported translation format '%s'\n", arg);
                    exit(1);
                }
                break;
            case 'o':
                if (configOutputPath != NULL) {
                    fprintf(stderr, "warning: -o option given multiple times. Now using value %s\n",
                            (arg == NULL) ? "-" : arg);
                }
                configOutputPath = arg;
                arg = NULL;
                break;
            case 'q': /* add hostname if DNS resolving has failed */
                fprintf(stderr,
                        "rsyslogd: the -q command line option has gone away.\n"
                        "Please use the global(net.aclAddHostnameOnFail=\"on\") "
                        "configuration parameter instead.\n");
                break;
            case 'Q': /* dont resolve hostnames in ACL to IPs */
                fprintf(stderr,
                        "rsyslogd: the -Q command line option has gone away.\n"
                        "Please use the global(net.aclResolveHostname=\"off\") "
                        "configuration parameter instead.\n");
                break;
            case 'T': /* chroot() immediately at program startup, but only for testing, NOT security yet */
                if (arg == NULL) {
                    /* note this case should already be handled by getopt,
                     * but we want to keep the static analyzer happy.
                     */
                    fprintf(stderr, "-T options needs a parameter\n");
                    exit(1);
                }
                if (chroot(arg) != 0) {
                    perror("chroot");
                    exit(1);
                }
                if (chdir("/") != 0) {
                    perror("chdir");
                    exit(1);
                }
                break;
            case 'u': /* misc user settings */
                iHelperUOpt = (arg == NULL) ? 0 : atoi(arg);
                if (iHelperUOpt & 0x01) {
                    fprintf(stderr,
                            "rsyslogd: the -u command line option has gone away.\n"
                            "For the 0x01 bit, please use the "
                            "global(parser.parseHostnameAndTag=\"off\") "
                            "configuration parameter instead.\n");
                }
                if (iHelperUOpt & 0x02) {
                    fprintf(stderr,
                            "rsyslogd: the -u command line option will go away "
                            "soon.\n"
                            "For the 0x02 bit, please use the -C option instead.");
                    bChDirRoot = 0;
                }
                break;
            case 'C':
                bChDirRoot = 0;
                break;
            case 'w': /* disable disallowed host warnigs */
                fprintf(stderr,
                        "rsyslogd: the -w command line option has gone away.\n"
                        "Please use the global(net.permitWarning=\"off\") "
                        "configuration parameter instead.\n");
                break;
            case 'x': /* disable dns for remote messages */
                fprintf(stderr,
                        "rsyslogd: the -x command line option has gone away.\n"
                        "Please use the global(net.enableDNS=\"off\") "
                        "configuration parameter instead.\n");
                break;
            case 'h':
            case '?':
            default:
                rsyslogd_usage();
        }
    }

    if (iRet != RS_RET_END_OF_LINKEDLIST) FINALIZE;

    CHKiRet(checkConfigOutputDoesNotOverwriteInput(configOutputPath, (const char *)ConfFile));

    if (iTranslateFmt != RSCONF_TRANSLATE_NONE) {
        if (!iConfigVerify) {
            fprintf(stderr, "rsyslogd: -F requires -N1 config validation mode\n");
            ABORT_FINALIZE(RS_RET_ERR);
        }
        if (configOutputPath == NULL) {
            fprintf(stderr, "rsyslogd: -F requires -o <path>\n");
            ABORT_FINALIZE(RS_RET_ERR);
        }
        rsconfTranslateConfigure(iTranslateFmt);
    } else if (configOutputPath != NULL) {
        if (!strcmp(configOutputPath, "-")) {
            fp_rs_full_conf_output = stdout;
        } else {
            fp_rs_full_conf_output = fopen(configOutputPath, "w");
        }
        if (fp_rs_full_conf_output == NULL) {
            perror(configOutputPath);
            fprintf(stderr, "rsyslogd: cannot open config output file %s - -o option will be ignored\n",
                    configOutputPath);
        } else {
            time_t tTime;
            struct tm tp;
            datetime.GetTime(&tTime);
            localtime_r(&tTime, &tp);
            fprintf(fp_rs_full_conf_output,
                    "## full conf created by rsyslog version %s at "
                    "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d ##\n",
                    VERSION, tp.tm_year + 1900, tp.tm_mon + 1, tp.tm_mday, tp.tm_hour, tp.tm_min, tp.tm_sec);
        }
    }

    if (iConfigVerify) {
        doFork = 0;
        fprintf(stderr, "rsyslogd: version %s, config validation run (level %d), master config %s\n", VERSION,
                iConfigVerify, ConfFile);
    }

    resetErrMsgsFlag();
    localRet = rsconf.Load(&ourConf, ConfFile);

#ifdef ENABLE_LIBCAPNG
    if (loadConf->globals.bCapabilityDropEnabled) {
        /*
         * Drop capabilities to the necessary set
         */
        int capng_rc, capng_failed = 0;
        typedef struct capabilities_s {
            int capability; /* capability code */
            const char *name; /* name of the capability to be displayed */
            /* is the capability present that is needed by rsyslog? if so we do not drop it */
            sbool present;
            capng_type_t type;
        } capabilities_t;

        // clang-format off
        capabilities_t capabilities[] = {
            { CAP_BLOCK_SUSPEND,    "CAP_BLOCK_SUSPEND",    0, CAPNG_EFFECTIVE | CAPNG_PERMITTED },
            { CAP_NET_RAW,          "CAP_NET_RAW",          0, CAPNG_EFFECTIVE | CAPNG_PERMITTED },
            { CAP_CHOWN,            "CAP_CHOWN",            0, CAPNG_EFFECTIVE | CAPNG_PERMITTED },
            { CAP_LEASE,            "CAP_LEASE",            0, CAPNG_EFFECTIVE | CAPNG_PERMITTED },
            { CAP_NET_ADMIN,        "CAP_NET_ADMIN",        0, CAPNG_EFFECTIVE | CAPNG_PERMITTED },
            { CAP_NET_BIND_SERVICE, "CAP_NET_BIND_SERVICE", 0, CAPNG_EFFECTIVE | CAPNG_PERMITTED },
            { CAP_DAC_OVERRIDE,     "CAP_DAC_OVERRIDE",     0, CAPNG_EFFECTIVE | CAPNG_PERMITTED | CAPNG_BOUNDING_SET },
            { CAP_SETGID,           "CAP_SETGID",           0, CAPNG_EFFECTIVE | CAPNG_PERMITTED },
            { CAP_SETUID,           "CAP_SETUID",           0, CAPNG_EFFECTIVE | CAPNG_PERMITTED },
            { CAP_SYS_ADMIN,        "CAP_SYS_ADMIN",        0, CAPNG_EFFECTIVE | CAPNG_PERMITTED },
            { CAP_SYS_CHROOT,       "CAP_SYS_CHROOT",       0, CAPNG_EFFECTIVE | CAPNG_PERMITTED },
            { CAP_SYS_RESOURCE,     "CAP_SYS_RESOURCE",     0, CAPNG_EFFECTIVE | CAPNG_PERMITTED },
            { CAP_SYSLOG,           "CAP_SYSLOG",           0, CAPNG_EFFECTIVE | CAPNG_PERMITTED }
        };
        // clang-format on

        if (capng_have_capabilities(CAPNG_SELECT_CAPS) > CAPNG_NONE) {
            /* Examine which capabilities are available to us, so we do not try to
                drop something that is not present. We need to do this in two steps,
                because capng_clear clears the capability set. In the second step,
                we add back those caps, which were present before clearing the selected
                posix capabilities set.
            */
            unsigned long caps_len = sizeof(capabilities) / sizeof(capabilities_t);
            for (unsigned long i = 0; i < caps_len; i++) {
                if (capng_have_capability(CAPNG_EFFECTIVE, capabilities[i].capability)) {
                    capabilities[i].present = 1;
                }
            }

            capng_clear(CAPNG_SELECT_BOTH);

            for (unsigned long i = 0; i < caps_len; i++) {
                if (capabilities[i].present) {
                    DBGPRINTF(
                        "The %s capability is present, "
                        "will try to preserve it.\n",
                        capabilities[i].name);
                    if ((capng_rc = capng_update(CAPNG_ADD, capabilities[i].type, capabilities[i].capability)) != 0) {
                        LogError(0, RS_RET_LIBCAPNG_ERR,
                                 "could not update the internal posix capabilities"
                                 " settings based on the options passed to it,"
                                 " capng_update=%d",
                                 capng_rc);
                        capng_failed = 1;
                    }
                } else {
                    DBGPRINTF(
                        "The %s capability is not present, "
                        "will not try to preserve it.\n",
                        capabilities[i].name);
                }
            }

            if ((capng_rc = capng_apply(CAPNG_SELECT_BOTH)) != 0) {
                LogError(0, RS_RET_LIBCAPNG_ERR,
                         "could not transfer the specified internal posix capabilities "
                         "settings to the kernel, capng_apply=%d",
                         capng_rc);
                capng_failed = 1;
            }

            if (capng_failed) {
                DBGPRINTF("Capabilities were not dropped successfully.\n");
                if (loadConf->globals.bAbortOnFailedLibcapngSetup) {
                    ABORT_FINALIZE(RS_RET_LIBCAPNG_ERR);
                }
            } else {
                DBGPRINTF("Capabilities were dropped successfully\n");
            }
        } else {
            DBGPRINTF("No capabilities to drop\n");
        }
    }
#endif

    if (fp_rs_full_conf_output != NULL) {
        if (fp_rs_full_conf_output != stdout) {
            fclose(fp_rs_full_conf_output);
        }
        fp_rs_full_conf_output = NULL;
    }

    /* check for "hard" errors that needs us to abort in any case */
    if ((localRet == RS_RET_CONF_FILE_NOT_FOUND) || (localRet == RS_RET_NO_ACTIONS)) {
        /* for extreme testing, we keep the ability to let rsyslog continue
         * even on hard config errors. Note that this may lead to segfaults
         * or other malfunction further down the road.
         */
        if ((loadConf->globals.glblDevOptions & DEV_OPTION_KEEP_RUNNING_ON_HARD_CONF_ERROR) == 1) {
            fprintf(stderr,
                    "rsyslogd: NOTE: developer-only option set to keep rsyslog "
                    "running where it should abort - this can lead to "
                    "more problems later in the run.\n");
        } else {
            ABORT_FINALIZE(localRet);
        }
    }

    glbl.GenerateLocalHostNameProperty();

    if (hadErrMsgs()) {
        if (loadConf->globals.bAbortOnUncleanConfig) {
            fprintf(stderr,
                    "rsyslogd: global(AbortOnUncleanConfig=\"on\") is set, and "
                    "config is not clean.\n"
                    "Check error log for details, fix errors and restart. As a last\n"
                    "resort, you may want to use global(AbortOnUncleanConfig=\"off\") \n"
                    "to permit a startup with a dirty config.\n");
            exit(2);
        }
        if (iConfigVerify) {
            /* a bit dirty, but useful... */
            exit(1);
        }
        localRet = RS_RET_OK;
    }
    CHKiRet(localRet);

    if (iTranslateFmt != RSCONF_TRANSLATE_NONE) {
        CHKiRet(rsconfTranslateWriteFile(configOutputPath));
        iRet = RS_RET_VALIDATION_RUN;
        FINALIZE;
    }

    CHKiRet(rsyslogd_InitStdRatelimiters());

    if (bChDirRoot) {
        if (chdir("/") != 0) fprintf(stderr, "Can not do 'cd /' - still trying to run\n");
    }

    if (iConfigVerify) FINALIZE;
    /* after this point, we are in a "real" startup */

    thrdInit();
    CHKiRet(checkStartupOK());
    if (doFork) {
        parentPipeFD = forkRsyslog();
    }
    glblSetOurPid(getpid());

    hdlr_enable(SIGPIPE, SIG_IGN);
    hdlr_enable(SIGXFSZ, SIG_IGN);
    if (Debug || loadConf->globals.permitCtlC) {
        hdlr_enable(SIGUSR1, rsyslogdDebugSwitch);
        hdlr_enable(SIGINT, rsyslogdDoDie);
        hdlr_enable(SIGQUIT, rsyslogdDoDie);
    } else {
        hdlr_enable(SIGUSR1, SIG_IGN);
        hdlr_enable(SIGINT, SIG_IGN);
        hdlr_enable(SIGQUIT, SIG_IGN);
    }
    hdlr_enable(SIGTERM, rsyslogdDoDie);
    hdlr_enable(SIGCHLD, hdlr_sigchld);
    hdlr_enable(SIGHUP, hdlr_sighup);

    if (rsconfNeedDropPriv(loadConf)) {
        /* need to write pid file early as we may loose permissions */
        CHKiRet(writePidFile());
    }

    CHKiRet(rsconf.Activate(ourConf));

    if (runConf->globals.bLogStatusMsgs) {
        char bufStartUpMsg[512];
        snprintf(bufStartUpMsg, sizeof(bufStartUpMsg),
                 "[origin software=\"rsyslogd\" "
                 "swVersion=\"" VERSION "\" x-pid=\"%d\" x-info=\"https://www.rsyslog.com\"] start",
                 (int)glblGetOurPid());
        logmsgInternal(NO_ERRCODE, LOG_SYSLOG | LOG_INFO, (uchar *)bufStartUpMsg, 0);
    }

    if (!rsconfNeedDropPriv(runConf)) {
        CHKiRet(writePidFile());
    }

    /* END OF INTIALIZATION */
    DBGPRINTF("rsyslogd: initialization completed, transitioning to regular run mode\n");

    if (doFork) {
        tellChildReady(parentPipeFD, "OK");
        stddbg = -1; /* turn off writing to fd 1 */
        close(1);
        close(2);
        runConf->globals.bErrMsgToStderr = 0;
    }

finalize_it:
    rsconfTranslateCleanup();
    if (iRet == RS_RET_VALIDATION_RUN) {
        fprintf(stderr, "rsyslogd: End of config validation run. Bye.\n");
        exit(0);
    } else if (iRet != RS_RET_OK) {
        fprintf(stderr,
                "rsyslogd: run failed with error %d (see rsyslog.h "
                "or try https://www.rsyslog.com/e/%d to learn what that number means)\n",
                iRet, iRet * -1);
        exit(1);
    }
}


/* this function pulls all internal messages from the buffer
 * and puts them into the processing engine.
 * We can only do limited error handling, as this would not
 * really help us. TODO: add error messages?
 * rgerhards, 2007-08-03
 */
void processImInternal(void) {
    smsg_t *pMsg;
    smsg_t *repMsg;

    assert(internalMsg_ratelimiter != NULL);
    while (iminternalRemoveMsg(&pMsg) == RS_RET_OK) {
        rsRetVal localRet = ratelimitMsg(internalMsg_ratelimiter, pMsg, &repMsg);
        if (repMsg != NULL) {
            logmsgInternal_doWrite(repMsg);
        }
        if (localRet == RS_RET_OK) {
            logmsgInternal_doWrite(pMsg);
        }
    }
}


/**
 * @brief Parse and submit a raw message to the main queue (Legacy).
 *
 * Decodes a received message and submits it to the main message queue.
 *
 * @note **Legacy Function**: This function is provided to support older input plugins that
 * do not support message creation via the new message interfaces.
 * **NEW PLUGINS SHOULD NOT USE THIS FUNCTION.**
 *
 * @param hname Hostname of the sender.
 * @param hnameIP IP address of the sender.
 * @param msg The raw message content.
 * @param len Length of the message content.
 * @param flags Message flags.
 * @param flowCtlType Type of flow control to apply.
 * @param pInputName Name of the input module.
 * @param stTime Syslog time stamp.
 * @param ttGenTime Time the message was generated.
 * @param pRuleset Ruleset to apply to this message.
 * @return RS_RET_OK on success.
 */
rsRetVal parseAndSubmitMessage(const uchar *const hname,
                               const uchar *const hnameIP,
                               const uchar *const msg,
                               const int len,
                               const int flags,
                               const flowControl_t flowCtlType,
                               prop_t *const pInputName,
                               const struct syslogTime *const stTime,
                               const time_t ttGenTime,
                               ruleset_t *const pRuleset) {
    prop_t *pProp = NULL;
    smsg_t *pMsg = NULL;
    DEFiRet;

    /* we now create our own message object and submit it to the queue */
    if (stTime == NULL) {
        CHKiRet(msgConstruct(&pMsg));
    } else {
        CHKiRet(msgConstructWithTime(&pMsg, stTime, ttGenTime));
    }
    if (pInputName != NULL) MsgSetInputName(pMsg, pInputName);
    MsgSetRawMsg(pMsg, (char *)msg, len);
    MsgSetFlowControlType(pMsg, flowCtlType);
    MsgSetRuleset(pMsg, pRuleset);
    pMsg->msgFlags = flags | NEEDS_PARSING;

    MsgSetRcvFromStr(pMsg, hname, ustrlen(hname), &pProp);
    CHKiRet(prop.Destruct(&pProp));
    CHKiRet(MsgSetRcvFromIPStr(pMsg, hnameIP, ustrlen(hnameIP), &pProp));
    CHKiRet(prop.Destruct(&pProp));
    CHKiRet(submitMsg2(pMsg));

finalize_it:
    if (iRet != RS_RET_OK) {
        DBGPRINTF("parseAndSubmitMessage() error, discarding msg: %s\n", msg);
        if (pMsg != NULL) {
            msgDestruct(&pMsg);
        }
    }
    RETiRet;
}


/* helper to doHUP(), this "HUPs" each action. The necessary locking
 * is done inside the action class and nothing we need to take care of.
 */
DEFFUNC_llExecFunc(doHUPActions) {
    actionCallHUPHdlr((action_t *)pData);
    return RS_RET_OK; /* we ignore errors, we can not do anything either way */
}


/** Processes a HUP after detection in the main loop.
 *
 * @brief Handles SIGHUP signal logic.
 *
 * @note This function is *called* by the main loop after a signal handler
 * detected SIGHUP. It is NOT the signal handler itself.
 *
 * @note This function **DOES NOT** reload the main configuration (rsyslog.conf).
 * It is primarily used for **Log Rotation** (closing/reopening output files)
 * and notifying modules to refresh internal state (like lookup tables).
 * To reload configuration, a restart is required.
 *
 * There is a VERY slim chance of a data race when the hostname is reset.
 * We prefer to take this risk rather than sync all accesses, because to the best
 * of my analysis it can not really hurt (the actual property is reference-counted)
 * but the sync would require some extra CPU for *each* message processed.
 * -- rgerhards, 2008-10-22 / 2012-04-11
 */
static void doHUP(void) {
    char buf[512];

    DBGPRINTF("doHUP: doing modules\n");
    if (ourConf != NULL && ourConf->globals.bLogStatusMsgs) {
        snprintf(buf, sizeof(buf),
                 "[origin software=\"rsyslogd\" "
                 "swVersion=\"" VERSION "\" x-pid=\"%d\" x-info=\"https://www.rsyslog.com\"] rsyslogd was HUPed",
                 (int)glblGetOurPid());
        errno = 0;
        logmsgInternal(NO_ERRCODE, LOG_SYSLOG | LOG_INFO, (uchar *)buf, 0);
    }

    queryLocalHostname(runConf); /* re-read our name */
    ruleset.IterateAllActions(ourConf, doHUPActions, NULL);
    DBGPRINTF("doHUP: doing modules\n");
    modDoHUP();
    DBGPRINTF("doHUP: doing lookup tables\n");
    lookupDoHUP();
    DBGPRINTF("doHUP: doing ratelimits\n");
    ratelimitDoHUP();
    DBGPRINTF("doHUP: doing errmsgs\n");
    errmsgDoHUP();
}

/**
 * @brief Signal Handler for termination (SIGTERM, SIGINT).
 *
 * Sets the global `bFinished` flag to indicate the program should terminate.
 *
 * @warning This function runs in a **signal handler context**.
 * It does NOT terminate the process directly, as that would be unsafe in a multi-threaded
 * environment. Instead, it signals the main loop to exit gracefully.
 *
 * @note We avoid using `DBGPRINTF()` or other complex functions here to prevent
 * deadlocks or undefined behavior if the signal interrupted a critical section.
 * The `write()` check exists solely to silence compiler warnings on some platforms (e.g., Ubuntu).
 *
 * @param sig The signal number received.
 */
void rsyslogdDoDie(int sig) {
#define MSG1 "DoDie called.\n"
#define MSG2 "DoDie called 5 times - unconditional exit\n"
    static volatile sig_atomic_t iRetries = 0; /* debug aid */
    dbgprintf(MSG1);
    if (Debug == DEBUG_FULL) {
        if (write(1, MSG1, sizeof(MSG1) - 1) == -1) {
            dbgprintf("%s:%d: write failed\n", __FILE__, __LINE__);
        }
    }
    if (iRetries++ == 4) {
        if (Debug == DEBUG_FULL) {
            if (write(1, MSG2, sizeof(MSG2) - 1) == -1) {
                dbgprintf("%s:%d: write failed\n", __FILE__, __LINE__);
            }
        }
        abort();
    }
    bFinished = sig;
    if (runConf && runConf->globals.debugOnShutdown) {
        /* kind of hackish - set to 0, so that debug_swith will enable
         * and AND emit the "start debug log" message.
         */
        debugging_on = 0;
        rsyslogdDebugSwitch();
    }
#undef MSG1
#undef MSG2
    /* at least on FreeBSD we seem not to necessarily awake the main thread.
     * So let's do it explicitely.
     */
    dbgprintf("awaking mainthread\n");
    pthread_kill(mainthread, SIGTTIN);
}


static uint64_t mainloopMonotonicMs(void) {
    struct timespec ts;

    if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
        return 0;
    }
    return ((uint64_t)ts.tv_sec * 1000ULL) + (uint64_t)(ts.tv_nsec / 1000000ULL);
}

/* The main loop sleeps until the earliest housekeeping deadline among the
 * periodic janitor run, the systemd watchdog ping, and any pending rswatch
 * debounce expiry.
 */
static int mainloopComputeTimeoutMs(uint64_t now_ms, uint64_t next_janitor_run_ms) {
    uint64_t diff = 0;
    int timeout_ms;

    if (next_janitor_run_ms <= now_ms) {
        timeout_ms = 0;
    } else {
        diff = next_janitor_run_ms - now_ms;
        timeout_ms = (diff > (uint64_t)INT_MAX) ? INT_MAX : (int)diff;
    }
#ifdef HAVE_LIBSYSTEMD
    if (systemdWatchdogEnabled && systemdWatchdogUsec > 0) {
        uint64_t watchdogWaitMs = systemdWatchdogUsec / 2000;

        if (watchdogWaitMs == 0) {
            watchdogWaitMs = 1;
        }
        if (watchdogWaitMs < (uint64_t)timeout_ms) {
            timeout_ms = (watchdogWaitMs > (uint64_t)INT_MAX) ? INT_MAX : (int)watchdogWaitMs;
        }
    }
#endif
    return rswatchComputeTimeoutMs(now_ms, timeout_ms);
}

static rsRetVal wait_timeout(const sigset_t *sigmask, int timeout_ms) {
    struct timespec tvSelectTimeout;
    int rswatch_fd;
    DEFiRet;

    if (timeout_ms < 0) {
        timeout_ms = 0;
    }
    tvSelectTimeout.tv_sec = timeout_ms / 1000;
    tvSelectTimeout.tv_nsec = (timeout_ms % 1000) * 1000000L;
    rswatch_fd = rswatchGetWaitFd();

#ifdef _AIX
    if (!src_exists) {
        long remaining_ms = timeout_ms;

        do {
            fd_set rfds;
            int maxfd = -1;
            struct timespec stepTimeout;
            long step_ms = remaining_ms;

            if (step_ms > 500) {
                step_ms = 500;
            }
            if (step_ms < 0) {
                step_ms = 0;
            }
            stepTimeout.tv_sec = step_ms / 1000;
            stepTimeout.tv_nsec = (step_ms % 1000) * 1000000L;

            if (bFinished || PREFER_LOAD_INT(&bHadHUP)) {
                break;
            }
            FD_ZERO(&rfds);
            if (rswatch_fd != -1) {
                FD_SET(rswatch_fd, &rfds);
                maxfd = rswatch_fd;
            }
            pselect(maxfd + 1, maxfd >= 0 ? (fd_set *)&rfds : NULL, NULL, NULL, &stepTimeout, sigmask);
            if (remaining_ms <= 500) {
                break;
            }
            remaining_ms -= 500;
        } while (remaining_ms > 0);
    } else {
        char buf[256];
        fd_set rfds;
        int maxfd = SRC_FD;

        FD_ZERO(&rfds);
        FD_SET(SRC_FD, &rfds);
        if (rswatch_fd != -1) {
            FD_SET(rswatch_fd, &rfds);
            if (rswatch_fd > maxfd) {
                maxfd = rswatch_fd;
            }
        }
        if (pselect(maxfd + 1, (fd_set *)&rfds, NULL, NULL, &tvSelectTimeout, sigmask)) {
            if (FD_ISSET(SRC_FD, &rfds)) {
                rc = recvfrom(SRC_FD, &srcpacket, SRCMSG, 0, &srcaddr, &addrsz);
                if (rc < 0) {
                    if (errno != EINTR) {
                        LogError(errno, NO_ERRCODE, "%s: ERROR: recvfrom failed - disabling AIX SRC", progname);
                        src_exists = FALSE;
                        ABORT_FINALIZE(RS_RET_IO_ERROR);
                    } else {
                        FINALIZE;
                    }
                }

                switch (srcpacket.subreq.action) {
                    case START:
                        dosrcpacket(SRC_SUBMSG,
                                    "ERROR: rsyslogd does not support this "
                                    "option.\n",
                                    sizeof(struct srcrep));
                        break;
                    case STOP:
                        if (srcpacket.subreq.object == SUBSYSTEM) {
                            dosrcpacket(SRC_OK, NULL, sizeof(struct srcrep));
                            (void)snprintf(buf, sizeof(buf) / sizeof(char),
                                           " [origin "
                                           "software=\"rsyslogd\" "
                                           "swVersion=\"" VERSION
                                           "\" x-pid=\"%d\" x-info=\"https://www.rsyslog.com\"]"
                                           " exiting due to stopsrc.",
                                           (int)glblGetOurPid());
                            errno = 0;
                            logmsgInternal(NO_ERRCODE, LOG_SYSLOG | LOG_INFO, (uchar *)buf, 0);
                            FINALIZE;
                        } else {
                            dosrcpacket(SRC_SUBMSG,
                                        "ERROR: rsyslogd does not support "
                                        "this option.\n",
                                        sizeof(struct srcrep));
                        }
                        break;
                    case REFRESH:
                        dosrcpacket(SRC_SUBMSG,
                                    "ERROR: rsyslogd does not support this "
                                    "option.\n",
                                    sizeof(struct srcrep));
                        break;
                    default:
                        dosrcpacket(SRC_SUBICMD, NULL, sizeof(struct srcrep));
                        break;
                }
            }
        }
    }
#else
    if (rswatch_fd != -1) {
        fd_set rfds;
        FD_ZERO(&rfds);
        FD_SET(rswatch_fd, &rfds);
        pselect(rswatch_fd + 1, &rfds, NULL, NULL, &tvSelectTimeout, sigmask);
    } else {
        pselect(0, NULL, NULL, NULL, &tvSelectTimeout, sigmask);
    }
#endif /* AIXPORT : SRC end */

#ifdef _AIX
finalize_it:
#endif
    RETiRet;
}


/**
 * @brief Reap Terminated Child Processes.
 *
 * Loops through all terminated child processes (`waitpid` with `WNOHANG`)
 * and reports their exit status.
 *
 * Useful for monitoring short-lived helper processes or scripts launched by rsyslog.
 * Also needed for proper cleanup of child processes in case of termination.
 */
static void reapChild(void) {
    pid_t child;
    do {
        int status;
        child = waitpid(-1, &status, WNOHANG);
        if (child != -1 && child != 0) {
            glblReportChildProcessExit(runConf, NULL, child, status);
        }
    } while (child > 0);
}


/**
 * @brief Application Main Loop.
 *
 * Handles housekeeping tasks (signal handling, child reaping, time updates)
 * while worker threads perform the actual log processing.
 *
 * @note This function blocks waiting for signals (`wait_timeout`) and only exits when
 * `bFinished` is set (e.g., by `rsyslogdDoDie`).
 */
static void mainloop(void) {
    time_t tTime;
    sigset_t origmask;
    sigset_t sigblockset;
    uint64_t next_janitor_run_ms;

    sigemptyset(&sigblockset);
    sigaddset(&sigblockset, SIGTERM);
    sigaddset(&sigblockset, SIGCHLD);
    sigaddset(&sigblockset, SIGHUP);
    next_janitor_run_ms = mainloopMonotonicMs() + ((uint64_t)runConf->globals.janitorInterval * 60ULL * 1000ULL);

    do {
        uint64_t now_ms;

        sigemptyset(&origmask);
        pthread_sigmask(SIG_BLOCK, &sigblockset, &origmask);
        if (PREFER_LOAD_INT(&bChildDied)) {
            PREFER_STORE_INT(&bChildDied, 0);
            reapChild();
        }

        if (PREFER_LOAD_INT(&bHadHUP)) {
            PREFER_STORE_INT(&bHadHUP, 0);
            PREFER_STORE_INT(&bHUPInProgress, 1);
            doHUP();
            PREFER_STORE_INT(&bHUPInProgress, 0);
        }

        processImInternal();

        if (bFinished) break; /* exit as quickly as possible */

        now_ms = mainloopMonotonicMs();
        wait_timeout(&origmask, mainloopComputeTimeoutMs(now_ms, next_janitor_run_ms));
        pthread_sigmask(SIG_UNBLOCK, &sigblockset, NULL);
        now_ms = mainloopMonotonicMs();

        /* File-watch I/O is handled before due dispatch so newly-read events can
         * arm or extend debounce deadlines within the same wake cycle.
         */
        rswatchProcessIo(now_ms);
        rswatchDispatchDue(now_ms);

#ifdef HAVE_LIBSYSTEMD
        if (systemdWatchdogEnabled) {
            sd_notify(0, "WATCHDOG=1");
        }
#endif

        if (now_ms >= next_janitor_run_ms) {
            janitorRun();

            assert(datetime.GetTime != NULL); /* This is only to keep clang static analyzer happy */
            datetime.GetTime(&tTime);
            checkGoneAwaySenders(tTime);
            next_janitor_run_ms = now_ms + ((uint64_t)runConf->globals.janitorInterval * 60ULL * 1000ULL);
        }

    } while (!bFinished); /* end do ... while() */
}

/* Finalize and destruct all actions.
 */
rsRetVal rsyslogdSetProperTerminationFile(const uchar *path) {
    uchar *newPath = NULL;

    if (path != NULL && path[0] != '\0') {
        const size_t lenPath = strlen((const char *)path) + 1;
        newPath = malloc(lenPath);
        if (newPath == NULL) return RS_RET_OUT_OF_MEMORY;
        memcpy(newPath, path, lenPath);
    }

    free(properTerminationFile);
    properTerminationFile = newPath;
    return RS_RET_OK;
}


int rsyslogdWriteTerminationMarker(const char *status, const char *reason, const char *phase) {
    if (properTerminationFile == NULL || properTerminationFile[0] == '\0') return 0;

    if (status == NULL) status = "unknown";
    if (reason == NULL) reason = "unknown";
    if (phase == NULL) phase = "unknown";

#ifndef O_NOFOLLOW
    fprintf(stderr,
            "rsyslogd: proper termination file '%s' cannot be safely opened: "
            "O_NOFOLLOW is unavailable on this platform\n",
            (const char *)properTerminationFile);
    return 1;
#else
    int fd = open((const char *)properTerminationFile, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW | O_CLOEXEC, 0600);
    if (fd < 0) {
        fprintf(stderr, "rsyslogd: error opening proper termination file '%s': %s\n",
                (const char *)properTerminationFile, strerror(errno));
        return 1;
    }

    #ifdef ENABLE_IMDIAG
    const unsigned overallQueueSize = PREFER_FETCH_32BIT(iOverallQueueSize);
    #else
    const unsigned overallQueueSize = 0;
    #endif
    char buf[512];
    const int len = snprintf(buf, sizeof(buf),
                             "status=%s\nreason=%s\npid=%d\nsignal=%d\nversion=%s\nphase=%s\n"
                             "queue.overall.size=%u\n",
                             status, reason, (int)getpid(), (int)bFinished, VERSION, phase, overallQueueSize);
    if (len < 0 || (size_t)len >= sizeof(buf)) {
        fprintf(stderr, "rsyslogd: error formatting proper termination file '%s'\n",
                (const char *)properTerminationFile);
        close(fd);
        return 1;
    }

    const char *cursor = buf;
    size_t remaining = (size_t)len;
    while (remaining > 0) {
        const ssize_t written = write(fd, cursor, remaining);
        if (written < 0) {
            if (errno == EINTR) continue;
            fprintf(stderr, "rsyslogd: error writing proper termination file '%s': %s\n",
                    (const char *)properTerminationFile, strerror(errno));
            close(fd);
            return 1;
        }
        if (written == 0) {
            fprintf(stderr, "rsyslogd: short write to proper termination file '%s'\n",
                    (const char *)properTerminationFile);
            close(fd);
            return 1;
        }
        cursor += written;
        remaining -= (size_t)written;
    }

    if (close(fd) != 0) {
        fprintf(stderr, "rsyslogd: error closing proper termination file '%s': %s\n",
                (const char *)properTerminationFile, strerror(errno));
        return 1;
    }
    return 0;
#endif
}


static void rsyslogd_destructAllActions(void) {
    ruleset.DestructAllActions(runConf);
    PREFER_STORE_0_TO_INT(&bHaveMainQueue); /* flag that internal messages need to be temporarily stored */
}


/* de-initialize everything, make ready for termination */
static void deinitAll(void) {
    char buf[256];

    DBGPRINTF("exiting on signal %d\n", (int)bFinished);

    /* IMPORTANT: we should close the inputs first, and THEN send our termination
     * message. If we do it the other way around, logmsgInternal() may block on
     * a full queue and the inputs still fill up that queue. Depending on the
     * scheduling order, we may end up with logmsgInternal being held for a quite
     * long time. When the inputs are terminated first, that should not happen
     * because the queue is drained in parallel. The situation could only become
     * an issue with extremely long running actions in a queue full environment.
     * However, such actions are at least considered poorly written, if not
     * outright wrong. So we do not care about this very remote problem.
     * rgerhards, 2008-01-11
     */

    /* close the inputs */
    DBGPRINTF("Terminating input threads...\n");
    glbl.SetGlobalInputTermination();

    thrdTerminateAll();

    /* and THEN send the termination log message (see long comment above) */
    if (bFinished && runConf->globals.bLogStatusMsgs) {
        (void)snprintf(buf, sizeof(buf),
                       "[origin software=\"rsyslogd\" "
                       "swVersion=\"" VERSION
                       "\" x-pid=\"%d\" x-info=\"https://www.rsyslog.com\"]"
                       " exiting on signal %d.",
                       (int)glblGetOurPid(), (int)bFinished);
        errno = 0;
        logmsgInternal(NO_ERRCODE, LOG_SYSLOG | LOG_INFO, (uchar *)buf, 0);
    }
    processImInternal(); /* make sure not-yet written internal messages are processed */
    /* we sleep a couple of ms to give the queue a chance to pick up the late messages
     * (including exit message); otherwise we have seen cases where the message did
     * not make it to log files, even on idle systems.
     */
    srSleep(0, 50);

    /* drain queue (if configured so) and stop main queue worker thread pool */
    DBGPRINTF("Terminating main queue...\n");
    qqueueDestruct(&runConf->pMsgQueue);
    runConf->pMsgQueue = NULL;

    /* Free ressources and close connections. This includes flushing any remaining
     * repeated msgs.
     */
    DBGPRINTF("Terminating outputs...\n");
    rsyslogd_destructAllActions();

    DBGPRINTF("all primary multi-thread sources have been terminated - now doing aux cleanup...\n");

    DBGPRINTF("destructing current config...\n");
    rsconf.Destruct(&runConf);

    modExitIminternal();

    if (pInternalInputName != NULL) prop.Destruct(&pInternalInputName);

    /* the following line cleans up CfSysLineHandlers that were not based on loadable
     * modules. As such, they are not yet cleared.  */
    unregCfSysLineHdlrs();

    /* this is the last spot where this can be done - below output modules are unloaded! */

    parserClassExit();
    rsconfClassExit();
    strExit();
    ratelimitModExit();
    dnscacheDeinit();
    thrdExit();
    objRelease(net, LM_NET_FILENAME);

    module.UnloadAndDestructAll(eMOD_LINK_ALL);

    rsrtExit(); /* runtime MUST always be deinitialized LAST (except for debug system) */
    DBGPRINTF("Clean shutdown completed, bye\n");

    errmsgExit();
    /* dbgClassExit MUST be the last one, because it de-inits the debug system */
    dbgClassExit();

    /* NO CODE HERE - dbgClassExit() must be the last thing before exit()! */
    clearPidFile();
}

/**
 * @brief Application Entry Point.
 *
 * Initializes the debug system and invokes the main initialization sequence.
 * This is kept minimal to ensure the debug system is initialized early and in a portable way.
 *
 * @param argc Number of arguments.
 * @param argv Array of argument strings.
 * @return 0 on clean exit, non-zero on error.
 */
#ifdef RSYSLOG_FUZZ_TARGET
int rsyslogd_main(int argc, char **argv);
#endif
int main(int argc, char **argv) {
#if defined(_AIX)
    /* SRC support : fd 0 (stdin) must be the SRC socket
     * startup.  fd 0 is duped to a new descriptor so that stdin can be used
     * internally by rsyslogd.
     */

    rs_cstr_copy(progname, argv[0], sizeof(progname));
    addrsz = sizeof(srcaddr);
    if ((rc = getsockname(0, &srcaddr, &addrsz)) < 0) {
        fprintf(stderr, "%s: continuing without SRC support\n", progname);
        src_exists = FALSE;
    }
    if (src_exists)
        if (dup2(0, SRC_FD) == -1) {
            fprintf(stderr, "%s: dup2 failed exiting now...\n", progname);
            /* In the unlikely event of dup2 failing we exit */
            exit(-1);
        }
#endif

    mainthread = pthread_self();
    if ((int)getpid() == 1) {
        fprintf(stderr,
                "rsyslogd %s: running as pid 1, enabling "
                "container-specific defaults, press ctl-c to "
                "terminate rsyslog\n",
                VERSION);
        PidFile = strdup("NONE"); /* disables pid file writing */
        glblPermitCtlC = 1;
        runningInContainer = 1;
        emitTZWarning = 1;
    } else {
        /* "dynamic defaults" - non-container case */
        PidFile = strdup(PATH_PIDFILE);
    }
    if (PidFile == NULL) {
        fprintf(stderr,
                "rsyslogd: could not alloc memory for pid file "
                "default name - aborting\n");
        exit(1);
    }

    /* disable case-sensitive comparisons in variable subsystem: */
    fjson_global_do_case_sensitive_comparison(0);

    dbgClassInit();

    initAll(argc, argv);
#ifdef HAVE_LIBSYSTEMD
    if (rsconfShouldDelayReadyNotify(runConf)) {
        rsconfWaitForModulesReady();
    }
    if (sd_watchdog_enabled(0, &systemdWatchdogUsec) > 0) {
        systemdWatchdogEnabled = 1;
        dbgprintf("systemd watchdog enabled with interval %llu usec\n", (unsigned long long)systemdWatchdogUsec);
    }
    sd_notify(0, "READY=1");
    if (rsconfShouldDelayReadyNotify(runConf)) {
        rsconfReadyNotifySent();
    }
    dbgprintf("done signaling to systemd that we are ready!\n");
#endif
    DBGPRINTF("max message size: %d\n", glblGetMaxLine(runConf));
    DBGPRINTF("----RSYSLOGD INITIALIZED\n");
    LogMsg(0, RS_RET_OK, LOG_DEBUG,
           "rsyslogd fully started up and initialized "
           "- begin actual processing");

    mainloop();
    LogMsg(0, RS_RET_OK, LOG_DEBUG, "rsyslogd shutting down");
    deinitAll();
    osf_close();
    return rsyslogdWriteTerminationMarker("ok", "normal", "main-return");
}
