/* 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-2019 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/wait.h>
#include <unistd.h>
#include <errno.h>
#ifdef ENABLE_LIBLOGGING_STDLOG
#  include <liblogging/stdlog.h>
#else
#  include <syslog.h>
#endif
#ifdef HAVE_LIBSYSTEMD
#	include <systemd/sd-daemon.h>
#endif

#include "rsyslog.h"
#include "wti.h"
#include "ratelimit.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 "rsconf.h"
#include "cfsysline.h"
#include "datetime.h"
#include "operatingstate.h"
#include "dirty.h"
#include "janitor.h"
#include "parserif.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;

#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;

	reply.svrreply.rtncode = msgno;
/* AIXPORT :  srv was corrected to syslogd */
	strcpy(reply.svrreply.objname, "syslogd");
	snprintf(reply.svrreply.rtnmsg,
		SRCMIN(sizeof(reply.svrreply.rtnmsg)-1, strlen(txt)), "%s", txt);
	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  */


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

extern int yydebug; /* interface to flex */


/* 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 int bChildDied;
static int bHadHUP;
static int doFork = 1; 	/* fork - run in daemon mode - read-only after startup */
int bFinished = 0;	/* used by termination signal handler, read-only except there
			 * is either 0 or the number of the signal that requested the
			 * termination.
			 */
const char *PidFile;
#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.
			*/
qqueue_t *pMsgQueue = NULL;	/* default main message queue */
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 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


static rsRetVal
queryLocalHostname(void)
{
	uchar *LocalHostName = NULL;
	uchar *LocalDomain = NULL;
	uchar *LocalFQDNName;
	DEFiRet;

	CHKiRet(net.getLocalHostname(&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 rsRetVal
writePidFile(void)
{
	FILE *fp;
	DEFiRet;

	const char *tmpPidFile;

	if(!strcmp(PidFile, NO_PIDFILE)) {
		FINALIZE;
	}
	if(asprintf((char **)&tmpPidFile, "%s.tmp", PidFile) == -1) {
		ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
	}
	if(tmpPidFile == NULL)
		tmpPidFile = PidFile;
	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) {
		LogError(errno, iRet, "rsyslog: error writing pid file");
	}
	fclose(fp);
	if(tmpPidFile != PidFile) {
		if(rename(tmpPidFile, PidFile) != 0) {
			perror("rsyslogd: error writing pid file (rename stage)");
		}
		free((void*)tmpPidFile);
	}
finalize_it:
	RETiRet;
}

/* 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;
}

/* 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 */
	const int endClose = getdtablesize();
	close(0);
	for(int i = beginClose ; i <= endClose ; ++i) {
		if((i != dbgGetDbglogFd()) && (i != parentPipeFD)) {
			  aix_close_it(i); /* AIXPORT */
		}
	}
	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
		strcpy(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));
	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
	/* 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, glblIntMsgRateLimitItv, glblIntMsgRateLimitBurst);
	/* 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));

	/* intialize 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 initializiation 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, int *pbShutdownImmediate) {
	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  && !*pbShutdownImmediate ; 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;
}


/* The consumer of dequeued messages. This function is called by the
 * queue engine on dequeueing of a message. It runs on a SEPARATE
 * THREAD. It receives an array of pointers, which it must iterate
 * over. We do not do any further batching, as this is of no benefit
 * for the main queue.
 */
static rsRetVal
msgConsumer(void __attribute__((unused)) *notNeeded, batch_t *pBatch, wti_t *pWti)
{
	DEFiRet;
	assert(pBatch != NULL);
	preprocessBatch(pBatch, pWti->pbShutdownImmediate);
	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  && !*pWti->pbShutdownImmediate ; 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;
	static int qfn_renamenum = 0;
	uchar qfrenamebuf[1024];
	DEFiRet;

	/* 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), pszQueueName);

	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,
						 (pszQueueName == NULL) ? "NONAME" : (char*)pszQueueName);
					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);
	}
	RETiRet;
}

rsRetVal
startMainQueue(qqueue_t *const pQueue)
{
	DEFiRet;
	CHKiRet_Hdlr(qqueueStart(pQueue)) {
		/* no queue is fatal, we need to give up in that case... */
		LogError(0, iRet, "could not start (ruleset) main message queue");
		pQueue->qType = QUEUETYPE_DIRECT;
		CHKiRet_Hdlr(qqueueStart(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 <= glblIntMsgsSeverityFilter) {
		if(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(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(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;

	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;
	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(pri2sev(pri) == LOG_ERR)
			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();
	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;
}


/* submit a message to the main message queue.   This is primarily
 * a hook to prevent the need for callers to know about the main message queue
 * rgerhards, 2008-02-13
 */
rsRetVal
submitMsg2(smsg_t *pMsg)
{
	qqueue_t *pQueue;
	ruleset_t *pRuleset;
	DEFiRet;

	ISOBJ_TYPE_assert(pMsg, msg);

	if(getRawMsgLen(pMsg) > glblGetMaxLine()){
		uchar *rawmsg;
		int dummy;
		getRawMsg(pMsg, &rawmsg, &dummy);
		if(glblReportOversizeMessage()) {
			LogMsg(0, RS_RET_OVERSIZE_MSG, LOG_WARNING,
				"message too long (%d) with configured size %d, begin of "
				"message is: %.80s",
				getRawMsgLen(pMsg), glblGetMaxLine(), rawmsg);
		}
		writeOversizeMessageLog(pMsg);
		if(glblGetOversizeMsgInputMode() == 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() == glblOversizeMsgInputMode_Truncate) {
			MsgTruncateToMaxSize(pMsg);
		} else {
			/* in "accept" mode, we do nothing, simply because "accept" means
			 * to use as-is.
			 */
			assert(glblGetOversizeMsgInputMode() == glblOversizeMsgInputMode_Accept);
		}
	}

	pRuleset = MsgGetRuleset(pMsg);
	pQueue = (pRuleset == NULL) ? 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) ? 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)
{
	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)
{
	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 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);

	/* first, parse the command line options. We do not carry out any actual work, just
	 * see what we should do. This relieves us from certain anomalies and we can process
	 * the parameters down below in the correct order. For example, we must know the
	 * value of -M before we can do the init, but at the same time we need to have
	 * the base classes init before we can process most of the options. Now, with the
	 * split of functionality, this is no longer a problem. Thanks to varmofekoj for
	 * suggesting this algo.
	 * Note: where we just need to set some flags and can do so without knowledge
	 * of other options, we do this during the inital option processing.
	 * rgerhards, 2008-04-04
	 */
#if defined(_AIX)
	while((ch = getopt(argc, argv, "46ACDdf:hi:M:nN:o:qQS:T:u:vwxR")) != EOF) {
#else
	while((ch = getopt(argc, argv, "46ACDdf:hi:M:nN:o:qQS:T:u:vwx")) != EOF) {
#endif
		switch((char)ch) {
		case '4':
		case '6':
		case 'A':
		case 'f': /* configuration file */
		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 */
	}

	/* get our host and domain names - we need to do this early as we may emit
	 * error log messages, which need the correct hostname. -- rgerhards, 2008-04-04
	 * But we need to have imInternal up first!
	 */
	queryLocalHostname();

	/* 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 will go away "
				 "soon.\nPlease use the global(net.ipprotocol=\"ipv4-only\") "
				 "configuration parameter instead.\n");
	                glbl.SetDefPFFamily(PF_INET);
			break;
		case '6':
			fprintf (stderr, "rsyslogd: the -6 command line option will go away "
				 "soon.\nPlease use the global(net.ipprotocol=\"ipv6-only\") "
				 "configuration parameter instead.\n");
			glbl.SetDefPFFamily(PF_INET6);
			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 'o':
			if(fp_rs_full_conf_output != NULL) {
				fprintf(stderr, "warning: -o option given multiple times. Now "
					"using value %s\n", (arg == NULL) ? "-" : arg);
				fclose(fp_rs_full_conf_output);
				fp_rs_full_conf_output = NULL;
			}
			if(arg == NULL || !strcmp(arg, "-")) {
				fp_rs_full_conf_output = stdout;
			} else {
				fp_rs_full_conf_output = fopen(arg, "w");
			}
			if(fp_rs_full_conf_output == NULL) {
				perror(arg);
				fprintf (stderr, "rsyslogd: cannot open config output file %s - "
					"-o option will be ignored\n", arg);
			} 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);
			}
			break;
		case 'q':               /* add hostname if DNS resolving has failed */
			fprintf (stderr, "rsyslogd: the -q command line option will go away "
				 "soon.\nPlease use the global(net.aclAddHostnameOnFail=\"on\") "
				 "configuration parameter instead.\n");
		        *(net.pACLAddHostnameOnFail) = 1;
		        break;
		case 'Q':               /* dont resolve hostnames in ACL to IPs */
			fprintf (stderr, "rsyslogd: the -Q command line option will go away "
				 "soon.\nPlease use the global(net.aclResolveHostname=\"off\") "
				 "configuration parameter instead.\n");
		        *(net.pACLDontResolve) = 1;
		        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 will go away "
					 "soon.\n"
					 "For the 0x01 bit, please use the "
					 "global(parser.parseHostnameAndTag=\"off\") "
					 "configuration parameter instead.\n");
				glbl.SetParseHOSTNAMEandTAG(0);
			}
			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 will go away "
				 "soon.\nPlease use the global(net.permitWarning=\"off\") "
				 "configuration parameter instead.\n");
			glbl.SetOption_DisallowWarning(0);
			break;
		case 'x':		/* disable dns for remote messages */
			fprintf (stderr, "rsyslogd: the -x command line option will go away "
				 "soon.\nPlease use the global(net.enableDNS=\"off\") "
				 "configuration parameter instead.\n");
			glbl.SetDisableDNS(1);
			break;
		case 'h':
		case '?':
		default:
			rsyslogd_usage();
		}
	}

	if(iRet != RS_RET_END_OF_LINKEDLIST)
		FINALIZE;

	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);

	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((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);
	
	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 || glblPermitCtlC) {
		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(ourConf)) {
		/* need to write pid file early as we may loose permissions */
		CHKiRet(writePidFile());
	}

	CHKiRet(rsconf.Activate(ourConf));

	if(ourConf->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(ourConf)) {
		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);
		ourConf->globals.bErrMsgToStderr = 0;
	}

finalize_it:
	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;

	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);
		}
	}
}


/* This takes a received message that must be decoded and submits it to
 * the main message queue. This is a legacy function which is being provided
 * to aid older input plugins that do not support message creation via
 * the new interfaces themselves. It is not recommended to use this
 * function for new plugins. -- rgerhards, 2009-10-12
 */
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.
 * rgerhards, 2008-10-22
 */
DEFFUNC_llExecFunc(doHUPActions)
{
	dbgprintf("doHUP called\n");
	actionCallHUPHdlr((action_t*) pData);
	return RS_RET_OK; /* we ignore errors, we can not do anything either way */
}


/* This function processes a HUP after one has been detected. Note that this
 * is *NOT* the sighup handler. The signal is recorded by the handler, that record
 * detected inside the mainloop and then this function is called to do the
 * real work. -- rgerhards, 2008-10-22
 * Note: 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, 2012-04-11
 */
static void
doHUP(void)
{
	char buf[512];

	if(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(); /* re-read our name */
	ruleset.IterateAllActions(ourConf, doHUPActions, NULL);
	modDoHUP();
	lookupDoHUP();
	errmsgDoHUP();
}

/* rsyslogdDoDie() is a signal handler. If called, it sets the bFinished variable
 * to indicate the program should terminate. However, it does not terminate
 * it itself, because that causes issues with multi-threading. The actual
 * termination is then done on the main thread. This solution might introduce
 * a minimal delay, but it is much cleaner than the approach of doing everything
 * inside the signal handler.
 * rgerhards, 2005-10-26
 * Note:
 * - we do not call DBGPRINTF() as this may cause us to block in case something
 *   with the threading is wrong.
 * - we do not really care about the return state of write(), but we need this
 *   strange check we do to silence compiler warnings (thanks, Ubuntu!)
 */
void
rsyslogdDoDie(int sig)
{
#	define MSG1 "DoDie called.\n"
#	define MSG2 "DoDie called 5 times - unconditional exit\n"
	static int 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(glblDebugOnShutdown) {
		/* 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 void
wait_timeout(const sigset_t *sigmask)
{
	struct timespec tvSelectTimeout;

	tvSelectTimeout.tv_sec = janitorInterval * 60; /* interval is in minutes! */
	tvSelectTimeout.tv_nsec = 0;

#ifdef _AIX
	if(!src_exists) {
		/* it looks like select() is NOT interrupted by HUP, even though
		 * SA_RESTART is not given in the signal setup. As this code is
		 * not expected to be used in production (when running as a
		 * service under src control), we simply make a kind of
		 * "somewhat-busy-wait" algorithm. We compute our own
		 * timeout value, which we count down to zero. We do this
		 * in useful subsecond steps.
		 */
		const long wait_period = 500000000; /* wait period in nanoseconds */
		int timeout = janitorInterval * 60 * (1000000000 / wait_period);

		tvSelectTimeout.tv_sec = 0;
		tvSelectTimeout.tv_nsec = wait_period;
		do {
			if(bFinished || bHadHUP) {
				break;
			}
			pselect(1, NULL, NULL, NULL, &tvSelectTimeout, sigmask);
		} while(--timeout > 0);
	} else {
		char buf[256];
		fd_set rfds;

		FD_ZERO(&rfds);
		FD_SET(SRC_FD, &rfds);
		if(pselect(SRC_FD + 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)
					{
						fprintf(stderr,"%s: ERROR: '%d' recvfrom\n", progname,errno);
						exit(1); //TODO: this needs to be handled gracefully
					} else { /* punt on short read */
						return;
					}

					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);
							return ;
						} 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
	pselect(0, NULL, NULL, NULL, &tvSelectTimeout, sigmask);
#endif /* AIXPORT : SRC end */
}


static void
reapChild(void)
{
	pid_t child;
	do {
		int status;
		child = waitpid(-1, &status, WNOHANG);
		if(child != -1 && child != 0) {
			glblReportChildProcessExit(NULL, child, status);
		}
	} while(child > 0);
}


/* This is the main processing loop. It is called after successful initialization.
 * When it returns, the syslogd terminates.
 * Its sole function is to provide some housekeeping things. The real work is done
 * by the other threads spawned.
 */
static void
mainloop(void)
{
	time_t tTime;
	sigset_t origmask;
	sigset_t sigblockset;

	sigemptyset(&sigblockset);
	sigaddset(&sigblockset, SIGTERM);
	sigaddset(&sigblockset, SIGCHLD);
	sigaddset(&sigblockset, SIGHUP);

	do {
		processImInternal();

		pthread_sigmask(SIG_BLOCK, &sigblockset, &origmask);
		if(bChildDied) {
			reapChild();
			bChildDied = 0;
		}

		if(bHadHUP) {
			doHUP();
			bHadHUP = 0;
		}

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

		wait_timeout(&origmask);
		pthread_sigmask(SIG_UNBLOCK, &sigblockset, NULL);

		janitorRun();

		datetime.GetTime(&tTime);
		checkGoneAwaySenders(tTime);

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

/* Finalize and destruct all actions.
 */
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", 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(), 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(&pMsgQueue);
	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()! */
	if(strcmp(PidFile, NO_PIDFILE)) {
		unlink(PidFile);
	}
}

/* This is the main entry point into rsyslogd. This must be a function in its own
 * right in order to intialize the debug system in a portable way (otherwise we would
 * need to have a statement before variable definitions.
 * rgerhards, 20080-01-28
 */
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.
	 */

	strncpy(progname,argv[0], sizeof(progname)-1);
	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);

	const char *const log_dflt = getenv("RSYSLOG_DFLT_LOG_INTERNAL");
	if(log_dflt != NULL && !strcmp(log_dflt, "1"))
		bProcessInternalMessages = 1;
	dbgClassInit();
	initAll(argc, argv);
#ifdef HAVE_LIBSYSTEMD
	sd_notify(0, "READY=1");
	dbgprintf("done signaling to systemd that we are ready!\n");
#endif
	DBGPRINTF("max message size: %d\n", glblGetMaxLine());
	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();
#ifdef ENABLE_LIBLOGGING_STDLOG
	stdlog_close(stdlog_hdl);
#endif
	osf_close();
	return 0;
}
