/**\file srUtils.c
 * \brief General utilties that fit nowhere else.
 *
 * The namespace for this file is "srUtil".
 *
 * \author  Rainer Gerhards <rgerhards@adiscon.com>
 * \date    2003-09-09
 *          Coding begun.
 *
 * Copyright 2003-2008 Rainer Gerhards and Adiscon GmbH.
 *
 * This file is part of the rsyslog runtime library.
 *
 * The rsyslog runtime library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * The rsyslog runtime library 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the rsyslog runtime library.  If not, see <http://www.gnu.org/licenses/>.
 *
 * A copy of the GPL can be found in the file "COPYING" in this distribution.
 * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
 */
#include "config.h"

#include "rsyslog.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
#include <assert.h>
#include <sys/wait.h>
#include <ctype.h>
#define TRUE 1
#define FALSE 0
#include "srUtils.h"
#include "obj.h"


/* here we host some syslog specific names. There currently is no better place
 * to do it, but over here is also not ideal... -- rgerhards, 2008-02-14
 * rgerhards, 2008-04-16: note in LGPL move: the code tables below exist in
 * the same way in BSD, so it is not a problem to move them from GPLv3 to LGPL.
 */
syslogName_t	syslogPriNames[] = {
	{"alert",	LOG_ALERT},
	{"crit",	LOG_CRIT},
	{"debug",	LOG_DEBUG},
	{"emerg",	LOG_EMERG},
	{"err",		LOG_ERR},
	{"error",	LOG_ERR},		/* DEPRECATED */
	{"info",	LOG_INFO},
	{"none",	INTERNAL_NOPRI},	/* INTERNAL */
	{"notice",	LOG_NOTICE},
	{"panic",	LOG_EMERG},		/* DEPRECATED */
	{"warn",	LOG_WARNING},		/* DEPRECATED */
	{"warning",	LOG_WARNING},
	{"*",		TABLE_ALLPRI},
	{NULL,		-1}
};

#ifndef LOG_AUTHPRIV
#	define LOG_AUTHPRIV LOG_AUTH
#endif
syslogName_t	syslogFacNames[] = {
	{"auth",         LOG_AUTH},
	{"authpriv",     LOG_AUTHPRIV},
	{"cron",         LOG_CRON},
	{"daemon",       LOG_DAEMON},
	{"kern",         LOG_KERN},
	{"lpr",          LOG_LPR},
	{"mail",         LOG_MAIL},
	{"mark",         LOG_MARK},		/* INTERNAL */
	{"news",         LOG_NEWS},
	{"security",     LOG_AUTH},		/* DEPRECATED */
	{"syslog",       LOG_SYSLOG},
	{"user",         LOG_USER},
	{"uucp",         LOG_UUCP},
#if defined(LOG_FTP)
	{"ftp",          LOG_FTP},
#endif
	{"local0",       LOG_LOCAL0},
	{"local1",       LOG_LOCAL1},
	{"local2",       LOG_LOCAL2},
	{"local3",       LOG_LOCAL3},
	{"local4",       LOG_LOCAL4},
	{"local5",       LOG_LOCAL5},
	{"local6",       LOG_LOCAL6},
	{"local7",       LOG_LOCAL7},
	{NULL,           -1},
};

/* ################################################################# *
 * private members                                                   *
 * ################################################################# */

/* As this is not a "real" object, there won't be any private
 * members in this file.
 */

/* ################################################################# *
 * public members                                                    *
 * ################################################################# */

rsRetVal srUtilItoA(char *pBuf, int iLenBuf, number_t iToConv)
{
	int i;
	int bIsNegative;
	char szBuf[64];	/* sufficiently large for my lifespan and those of my children... ;) */

	assert(pBuf != NULL);
	assert(iLenBuf > 1);	/* This is actually an app error and as thus checked for... */

	if(iToConv < 0)
	{
		bIsNegative = TRUE;
		iToConv *= -1;
	}
	else
		bIsNegative = FALSE;

	/* first generate a string with the digits in the reverse direction */
	i = 0;
	do
	{
		szBuf[i++] = iToConv % 10 + '0';
		iToConv /= 10;
	} while(iToConv > 0);	/* warning: do...while()! */
	--i; /* undo last increment - we were pointing at NEXT location */

	/* make sure we are within bounds... */
	if(i + 2 > iLenBuf)	/* +2 because: a) i starts at zero! b) the \0 byte */
		return RS_RET_PROVIDED_BUFFER_TOO_SMALL;

	/* then move it to the right direction... */
	if(bIsNegative == TRUE)
		*pBuf++ = '-';
	while(i >= 0)
		*pBuf++ = szBuf[i--];
	*pBuf = '\0';	/* terminate it!!! */

	return RS_RET_OK;
}

uchar *srUtilStrDup(uchar *pOld, size_t len)
{
	uchar *pNew;

	assert(pOld != NULL);
	
	if((pNew = malloc(len + 1)) != NULL)
		memcpy(pNew, pOld, len + 1);

	return pNew;
}


/* creates a path recursively
 * Return 0 on success, -1 otherwise. On failure, errno
 * hold the last OS error.
 * Param "mode" holds the mode that all non-existing directories
 * are to be created with.
 */
int makeFileParentDirs(uchar *szFile, size_t lenFile, mode_t mode,
		       uid_t uid, gid_t gid, int bFailOnChownFail)
{
        uchar *p;
        uchar *pszWork;
        size_t len;
	int bErr = 0;

	assert(szFile != NULL);
	assert(lenFile > 0);

        len = lenFile + 1; /* add one for '\0'-byte */
	if((pszWork = malloc(sizeof(uchar) * len)) == NULL)
		return -1;
        memcpy(pszWork, szFile, len);
        for(p = pszWork+1 ; *p ; p++)
                if(*p == '/') {
			/* temporarily terminate string, create dir and go on */
                        *p = '\0';
                        if(access((char*)pszWork, F_OK)) {
                                if(mkdir((char*)pszWork, mode) == 0) {
					if(uid != (uid_t) -1 || gid != (gid_t) -1) {
						/* we need to set owner/group */
						if(chown((char*)pszWork, uid, gid) != 0)
							if(bFailOnChownFail)
								bErr = 1;
							/* silently ignore if configured
							 * to do so.
							 */
					}
				} else
					bErr = 1;
				if(bErr) {
					int eSave = errno;
					free(pszWork);
					errno = eSave;
					return -1;
				}
			}
                        *p = '/';
                }
	free(pszWork);
	return 0;
}


/* execute a program with a single argument
 * returns child pid if everything ok, 0 on failure. if
 * it fails, errno is set. if it fails after the fork(), the caller
 * can not be notfied for obvious reasons. if bwait is set to 1,
 * the code waits until the child terminates - that potentially takes
 * a lot of time.
 * implemented 2007-07-20 rgerhards
 */
int execProg(uchar *program, int bWait, uchar *arg)
{
        int pid;
	int sig;
	struct sigaction sigAct;

	dbgprintf("exec program '%s' with param '%s'\n", program, arg);
        pid = fork();
        if (pid < 0) {
                return 0;
        }

        if(pid) {       /* Parent */
		if(bWait)
			if(waitpid(pid, NULL, 0) == -1)
				if(errno != ECHILD) {
					/* we do not use logerror(), because
					 * that might bring us into an endless
					 * loop. At some time, we may
					 * reconsider this behaviour.
					 */
					dbgprintf("could not wait on child after executing '%s'",
					        (char*)program);
				}
                return pid;
	}
        /* Child */
	alarm(0); /* create a clean environment before we exec the real child */

	memset(&sigAct, 0, sizeof(sigAct));
	sigemptyset(&sigAct.sa_mask);
	sigAct.sa_handler = SIG_DFL;

	for(sig = 1 ; sig < NSIG ; ++sig)
		sigaction(sig, &sigAct, NULL);

	execlp((char*)program, (char*) program, (char*)arg, NULL);
	/* In the long term, it's a good idea to implement some enhanced error
	 * checking here. However, it can not easily be done. For starters, we
	 * may run into endless loops if we log to syslog. The next problem is
	 * that output is typically not seen by the user. For the time being,
	 * we use no error reporting, which is quite consitent with the old
	 * system() way of doing things. rgerhards, 2007-07-20
	 */
	perror("exec");
	exit(1); /* not much we can do in this case */
}


/* skip over whitespace in a standard C string. The
 * provided pointer is advanced to the first non-whitespace
 * charater or the \0 byte, if there is none. It is never
 * moved past the \0.
 */
void skipWhiteSpace(uchar **pp)
{
	register uchar *p;

	assert(pp != NULL);
	assert(*pp != NULL);

	p = *pp;
	while(*p && isspace((int) *p))
		++p;
	*pp = p;
}


/* generate a file name from four parts:
 * <directory name>/<name>.<number>
 * If number is negative, it is not used. If any of the strings is
 * NULL, an empty string is used instead. Length must be provided.
 * lNumDigits is the minimum number of digits that lNum should have. This
 * is to pretty-print the file name, e.g. lNum = 3, lNumDigits= 4 will
 * result in "0003" being used inside the file name. Set lNumDigits to 0
 * to use as few space as possible.
 * rgerhards, 2008-01-03
 */
rsRetVal genFileName(uchar **ppName, uchar *pDirName, size_t lenDirName, uchar *pFName,
		     size_t lenFName, long lNum, int lNumDigits)
{
	DEFiRet;
	uchar *pName;
	uchar *pNameWork;
	size_t lenName;
	uchar szBuf[128];	/* buffer for number */
	char szFmtBuf[32];	/* buffer for snprintf format */
	size_t lenBuf;

	if(lNum < 0) {
		szBuf[0] = '\0';
		lenBuf = 0;
	} else {
		if(lNumDigits > 0) {
			snprintf(szFmtBuf, sizeof(szFmtBuf), ".%%0%dld", lNumDigits);
			lenBuf = snprintf((char*)szBuf, sizeof(szBuf), szFmtBuf, lNum);
		} else
			lenBuf = snprintf((char*)szBuf, sizeof(szBuf), ".%ld", lNum);
	}

	lenName = lenDirName + 1 + lenFName + lenBuf + 1; /* last +1 for \0 char! */
	if((pName = malloc(sizeof(uchar) * lenName)) == NULL)
		ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
	
	/* got memory, now construct string */
	memcpy(pName, pDirName, lenDirName);
	pNameWork = pName + lenDirName;
	*pNameWork++ = '/';
	memcpy(pNameWork, pFName, lenFName);
	pNameWork += lenFName;
	if(lenBuf > 0) {
		memcpy(pNameWork, szBuf, lenBuf);
		pNameWork += lenBuf;
	}
	*pNameWork = '\0';

	*ppName = pName;

finalize_it:
	RETiRet;
}

/* get the number of digits required to represent a given number. We use an
 * iterative approach as we do not like to draw in the floating point
 * library just for log(). -- rgerhards, 2008-01-10
 */
int getNumberDigits(long lNum)
{
	int iDig;

	if(lNum == 0)
		iDig = 1;
	else
		for(iDig = 0 ; lNum != 0 ; ++iDig)
			lNum /= 10;

	return iDig;
}


/* compute an absolute time timeout suitable for calls to pthread_cond_timedwait()
 * iTimeout is in milliseconds
 * rgerhards, 2008-01-14
 */
rsRetVal
timeoutComp(struct timespec *pt, long iTimeout)
{
	BEGINfunc
	assert(pt != NULL);
	/* compute timeout */
	clock_gettime(CLOCK_REALTIME, pt);
	pt->tv_sec += iTimeout / 1000;
	pt->tv_nsec += (iTimeout % 1000) * 1000000; /* think INTEGER arithmetic! */
	if(pt->tv_nsec > 999999999) { /* overrun? */
		pt->tv_nsec -= 1000000000;
		++pt->tv_sec;
	}
	ENDfunc
	return RS_RET_OK; /* so far, this is static... */
}


/* This function is kind of the reverse of timeoutComp() - it takes an absolute
 * timeout value and computes how far this is in the future. If the value is already
 * in the past, 0 is returned. The return value is in ms.
 * rgerhards, 2008-01-25
 */
long
timeoutVal(struct timespec *pt)
{
	struct timespec t;
	long iTimeout;
	BEGINfunc

	assert(pt != NULL);
	/* compute timeout */
	clock_gettime(CLOCK_REALTIME, &t);
	iTimeout = (pt->tv_nsec - t.tv_nsec) / 1000000;
	iTimeout += (pt->tv_sec - t.tv_sec) * 1000;

	if(iTimeout < 0)
		iTimeout = 0;

	ENDfunc
	return iTimeout;
}


/* cancellation cleanup handler - frees provided mutex
 * rgerhards, 2008-01-14
 */
void
mutexCancelCleanup(void *arg)
{
	BEGINfunc
	assert(arg != NULL);
	d_pthread_mutex_unlock((pthread_mutex_t*) arg);
	ENDfunc
}


/* rsSleep() - a fairly portable way to to sleep. It 
 * will wake up when
 * a) the wake-time is over
 * rgerhards, 2008-01-28
 */
void
srSleep(int iSeconds, int iuSeconds)
{
	struct timeval tvSelectTimeout;

	BEGINfunc
	tvSelectTimeout.tv_sec = iSeconds;
	tvSelectTimeout.tv_usec = iuSeconds; /* micro seconds */
	select(0, NULL, NULL, NULL, &tvSelectTimeout);
	ENDfunc
}


/* From varmojfekoj's mail on why he provided rs_strerror_r():
 * There are two problems with strerror_r():
 * I see you've rewritten some of the code which calls it to use only
 * the supplied buffer; unfortunately the GNU implementation sometimes
 * doesn't use the buffer at all and returns a pointer to some
 * immutable string instead, as noted in the man page.
 *
 * The other problem is that on some systems strerror_r() has a return
 * type of int.
 *
 * So I've written a wrapper function rs_strerror_r(), which should
 * take care of all this and be used instead.
 *
 * Added 2008-01-30
 */
char *rs_strerror_r(int errnum, char *buf, size_t buflen) {
#ifndef HAVE_STRERROR_R
	char *pszErr;
	pszErr = strerror(errnum);
	snprintf(buf, buflen, "%s", pszErr);
#else
#	ifdef STRERROR_R_CHAR_P
		char *p = strerror_r(errnum, buf, buflen);
		if (p != buf) {
			strncpy(buf, p, buflen);
			buf[buflen - 1] = '\0';
		}
#	else
		strerror_r(errnum, buf, buflen);
#	endif
#endif /* #ifdef __hpux */
	return buf;
}


/*  Decode a symbolic name to a numeric value
 */
int decodeSyslogName(uchar *name, syslogName_t *codetab)
{
	register syslogName_t *c;
	register uchar *p;
	uchar buf[80];

	ASSERT(name != NULL);
	ASSERT(codetab != NULL);

	dbgprintf("symbolic name: %s", name);
	if (isdigit((int) *name))
	{
		dbgprintf("\n");
		return (atoi((char*) name));
	}
	strncpy((char*) buf, (char*) name, 79);
	for (p = buf; *p; p++)
		if (isupper((int) *p))
			*p = tolower((int) *p);
	for (c = codetab; c->c_name; c++)
		if (!strcmp((char*) buf, (char*) c->c_name))
		{
			dbgprintf(" ==> %d\n", c->c_val);
			return (c->c_val);
		}
	return (-1);
}


/**
 * getSubString
 *
 * Copy a string byte by byte until the occurrence  
 * of a given separator.
 *
 * \param ppSrc		Pointer to a pointer of the source array of characters. If a
			separator detected the Pointer points to the next char after the
			separator. Except if the end of the string is dedected ('\n'). 
			Then it points to the terminator char. 
 * \param pDst		Pointer to the destination array of characters. Here the substing
			will be stored.
 * \param DstSize	Maximum numbers of characters to store.
 * \param cSep		Separator char.
 * \ret int		Returns 0 if no error occured.
 *
 * rgerhards, 2008-02-12: some notes are due... I will once again fix this function, this time
 * so that it treats ' ' as a request for whitespace. But in general, the function and its callers
 * should be changed over time, this is not really very good code...
 */
int getSubString(uchar **ppSrc,  char *pDst, size_t DstSize, char cSep)
{
	uchar *pSrc = *ppSrc;
	int iErr = 0; /* 0 = no error, >0 = error */
	while((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0' && DstSize>1) {
		*pDst++ = *(pSrc)++;
		DstSize--;
	}
	/* check if the Dst buffer was to small */
	if ((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0') { 
		dbgprintf("in getSubString, error Src buffer > Dst buffer\n");
		iErr = 1;
	}	
	if (*pSrc == '\0' || *pSrc == '\n')
		/* this line was missing, causing ppSrc to be invalid when it
		 * was returned in case of end-of-string. rgerhards 2005-07-29
		 */
		*ppSrc = pSrc;
	else
		*ppSrc = pSrc+1;
	*pDst = '\0';
	return iErr;
}


/* get the size of a file or return appropriate error code. If an error is returned,
 * *pSize content is undefined.
 * rgerhards, 2009-06-12
 */
rsRetVal
getFileSize(uchar *pszName, off_t *pSize)
{
	int ret;
	struct stat statBuf;
	DEFiRet;

	ret = stat((char*) pszName, &statBuf);
	if(ret == -1) {
		switch(errno) {
			case EACCES: ABORT_FINALIZE(RS_RET_NO_FILE_ACCESS);
			case ENOTDIR:
			case ENOENT:  ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
			default:      ABORT_FINALIZE(RS_RET_FILE_NO_STAT);
		}
	}

	*pSize = statBuf.st_size;

finalize_it:
	RETiRet;
}


/* vim:set ai:
 */
