/* omprog.c
 * This output plugin enables rsyslog to execute a program and
 * feed it the message stream as standard input.
 *
 * NOTE: read comments in module-template.h for more specifics!
 *
 * File begun on 2009-04-01 by RGerhards
 *
 * Copyright 2009 Rainer Gerhards and Adiscon GmbH.
 *
 * This file is part of rsyslog.
 *
 * Rsyslog is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Rsyslog is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Rsyslog.  If not, see <http://www.gnu.org/licenses/>.
 *
 * A copy of the GPL can be found in the file "COPYING" in this distribution.
 */
#include "config.h"
#include "rsyslog.h"
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <wait.h>
#include "conf.h"
#include "syslogd-types.h"
#include "srUtils.h"
#include "template.h"
#include "module-template.h"
#include "errmsg.h"
#include "cfsysline.h"

MODULE_TYPE_OUTPUT

/* internal structures
 */
DEF_OMOD_STATIC_DATA
DEFobjCurrIf(errmsg)

typedef struct _instanceData {
	uchar *szBinary;	/* name of binary to call */
	pid_t pid;		/* pid of currently running process */
	int fdPipe;		/* file descriptor to write to */
	int bIsRunning;		/* is binary currently running? 0-no, 1-yes */
} instanceData;

/* config settings */
static uchar *szBinary = NULL;	/* name of binary to call */

BEGINcreateInstance
CODESTARTcreateInstance
ENDcreateInstance


BEGINisCompatibleWithFeature
CODESTARTisCompatibleWithFeature
	if(eFeat == sFEATURERepeatedMsgReduction)
		iRet = RS_RET_OK;
ENDisCompatibleWithFeature


BEGINfreeInstance
CODESTARTfreeInstance
	if(pData->szBinary != NULL)
		free(pData->szBinary);
ENDfreeInstance


BEGINdbgPrintInstInfo
CODESTARTdbgPrintInstInfo
ENDdbgPrintInstInfo


BEGINtryResume
CODESTARTtryResume
ENDtryResume


/* execute the child process (must be called in child context
 * after fork).
 */

static void execBinary(instanceData *pData, int fdStdin)
{
	int i;
	struct sigaction sigAct;
	char *newargv[] = { NULL };
	char *newenviron[] = { NULL };

	assert(pData != NULL);

	fclose(stdin);
	dup(fdStdin);
	//fclose(stdout);

	/* we close all file handles as we fork soon
	 * Is there a better way to do this? - mail me! rgerhards@adiscon.com
	 */
#	ifndef VALGRIND /* we can not use this with valgrind - too many errors... */
	for(i = 3 ; i <= 65535 ; ++i)
		close(i);
#	endif

	/* reset signal handlers to default */
	memset(&sigAct, 0, sizeof(sigAct));
	sigfillset(&sigAct.sa_mask);
	sigAct.sa_handler = SIG_DFL;
	for(i = 1 ; i < NSIG ; ++i)
		sigaction(i, &sigAct, NULL);

	alarm(0);

	/* finally exec child */
	execve((char*)pData->szBinary, newargv, newenviron);
	/* switch to?
	execlp((char*)program, (char*) program, (char*)arg, NULL);
	*/

	/* we should never reach this point, but if we do, we terminate */
	exit(1);
}


/* creates a pipe and starts program, uses pipe as stdin for program.
 * rgerhards, 2009-04-01
 */
static rsRetVal
openPipe(instanceData *pData)
{
	int pipefd[2];
	pid_t cpid;
	DEFiRet;

	assert(pData != NULL);

	if(pipe(pipefd) == -1) {
		ABORT_FINALIZE(RS_RET_ERR_CREAT_PIPE);
	}

	DBGPRINTF("executing program '%s'\n", pData->szBinary);

	/* NO OUTPUT AFTER FORK! */

	cpid = fork();
	if(cpid == -1) {
		ABORT_FINALIZE(RS_RET_ERR_FORK);
	}

	if(cpid == 0) {    
		/* we are now the child, just set the right selectors and
		 * exec the binary. If that fails, there is not much we can do.
		 */
		close(pipefd[1]);
		execBinary(pData, pipefd[0]);
		/*NO CODE HERE - WILL NEVER BE REACHED!*/
	}

	DBGPRINTF("child has pid %d\n", cpid);
	pData->fdPipe = pipefd[1];
	pData->pid = cpid;
	close(pipefd[0]);
	pData->bIsRunning = 1;
finalize_it:
	RETiRet;
}


/* clean up after a terminated child
 */
static inline rsRetVal
cleanup(instanceData *pData)
{
	int status;
	int ret;
	char errStr[1024];
	DEFiRet;

	assert(pData != NULL);
	assert(pData->bIsRunning == 1);
RUNLOG_VAR("%d", pData->pid);
	ret = waitpid(pData->pid, &status, 0);
	if(ret != pData->pid) {
		/* if waitpid() fails, we can not do much - try to ignore it... */
		DBGPRINTF("waitpid() returned state %d[%s], future malfunction may happen\n", ret,
			   rs_strerror_r(errno, errStr, sizeof(errStr)));
	} else {
		/* check if we should print out some diagnostic information */
		DBGPRINTF("waitpid status return for program '%s': %2.2x\n",
			  pData->szBinary, status);
		if(WIFEXITED(status)) {
			errmsg.LogError(0, NO_ERRCODE, "program '%s' exited normally, state %d",
					pData->szBinary, WEXITSTATUS(status));
		} else if(WIFSIGNALED(status)) {
			errmsg.LogError(0, NO_ERRCODE, "program '%s' terminated by signal %d.",
					pData->szBinary, WTERMSIG(status));
		}
	}

	pData->bIsRunning = 0;
	RETiRet;
}


/* try to restart the binary when it has stopped.
 */
static inline rsRetVal
tryRestart(instanceData *pData)
{
	DEFiRet;
	assert(pData != NULL);
	assert(pData->bIsRunning == 0);

	iRet = openPipe(pData);
	RETiRet;
}


/* write to pipe
 * note that we do not try to run block-free. If the users fears something
 * may block (and this not be acceptable), the action should be run on its
 * own action queue.
 */
static rsRetVal
writePipe(instanceData *pData, uchar *szMsg)
{
	int lenWritten;
	int lenWrite;
	int writeOffset;
	char errStr[1024];
	DEFiRet;
	
	assert(pData != NULL);

	lenWrite = strlen((char*)szMsg);
	writeOffset = 0;

	do
	{
		lenWritten = write(pData->fdPipe, ((char*)szMsg)+writeOffset, lenWrite);
		if(lenWritten == -1) {
			switch(errno) {
				case EPIPE:
					DBGPRINTF("Program '%s' terminated, trying to restart\n",
						  pData->szBinary);
					CHKiRet(cleanup(pData));
					CHKiRet(tryRestart(pData));
					break;
				default:
					DBGPRINTF("error %d writing to pipe: %s\n", errno,
						   rs_strerror_r(errno, errStr, sizeof(errStr)));
					ABORT_FINALIZE(RS_RET_ERR_WRITE_PIPE);
					break;
			}
		} else {
			writeOffset += lenWritten;
		}
	} while(lenWritten != lenWrite);


finalize_it:
	RETiRet;
}


BEGINdoAction
CODESTARTdoAction
	if(pData->bIsRunning == 0) {
		openPipe(pData);
	}
	
	iRet = writePipe(pData, ppString[0]);

	if(iRet != RS_RET_OK)
		iRet = RS_RET_SUSPENDED;
ENDdoAction


BEGINparseSelectorAct
CODESTARTparseSelectorAct
CODE_STD_STRING_REQUESTparseSelectorAct(1)
	/* first check if this config line is actually for us */
	if(strncmp((char*) p, ":omprog:", sizeof(":omprog:") - 1)) {
		ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED);
	}

	/* ok, if we reach this point, we have something for us */
	p += sizeof(":omprog:") - 1; /* eat indicator sequence  (-1 because of '\0'!) */
	CHKiRet(createInstance(&pData));

	CHKmalloc(pData->szBinary = (uchar*) strdup((char*)szBinary));
	/* check if a non-standard template is to be applied */
	if(*(p-1) == ';')
		--p;
	CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, 0, (uchar*) "RSYSLOG_FileFormat"));
CODE_STD_FINALIZERparseSelectorAct
ENDparseSelectorAct


BEGINmodExit
CODESTARTmodExit
	if(szBinary != NULL) {
		free(szBinary);
		szBinary = NULL;
	}
	CHKiRet(objRelease(errmsg, CORE_COMPONENT));
finalize_it:
ENDmodExit


BEGINqueryEtryPt
CODESTARTqueryEtryPt
CODEqueryEtryPt_STD_OMOD_QUERIES
ENDqueryEtryPt



/* Reset config variables for this module to default values.
 */
static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal)
{
	DEFiRet;

	if(szBinary != NULL) {
		free(szBinary);
		szBinary = NULL;
	}

	RETiRet;
}


BEGINmodInit()
CODESTARTmodInit
	*ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
CODEmodInit_QueryRegCFSLineHdlr
	CHKiRet(objUse(errmsg, CORE_COMPONENT));
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionomprogbinary", 0, eCmdHdlrGetWord, NULL, &szBinary, STD_LOADABLE_MODULE_ID));
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID));
CODEmodInit_QueryRegCFSLineHdlr
ENDmodInit

/* vi:set ai:
 */
