summaryrefslogtreecommitdiffstats
path: root/source3/lib/replace/test/strptime.c
blob: fade3ecc57567d73ea5dc37ad902384f990f4f70 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172

#ifdef LIBREPLACE_CONFIGURE_TEST_STRPTIME

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define true 1
#define false 0

#ifndef __STRING
#define __STRING(x)    #x
#endif

/* make printf a no-op */
#define printf if(0) printf

#else /* LIBREPLACE_CONFIGURE_TEST_STRPTIME */

#include "replace.h"
#include "system/time.h"

#endif /* LIBREPLACE_CONFIGURE_TEST_STRPTIME */

int libreplace_test_strptime(void)
{
	const char *s = "20070414101546Z";
	char *ret;
	struct tm t, t2;

	memset(&t, 0, sizeof(t));
	memset(&t2, 0, sizeof(t2));

	printf("test: strptime\n");

	ret = strptime(s, "%Y%m%d%H%M%S", &t);
	if ( ret == NULL ) {
		printf("failure: strptime [\n"
		       "returned NULL\n"
		       "]\n");
		return false;
	}

	if ( *ret != 'Z' ) {
		printf("failure: strptime [\n"
		       "ret doesn't point to 'Z'\n"
		       "]\n");
		return false;
	}

	ret = strptime(s, "%Y%m%d%H%M%SZ", &t2);
	if ( ret == NULL ) {
		printf("failure: strptime [\n"
		       "returned NULL with Z\n"
		       "]\n");
		return false;
	}

	if ( *ret != '\0' ) {
		printf("failure: strptime [\n"
		       "ret doesn't point to '\\0'\n"
		       "]\n");
		return false;
	}

#define CMP_TM_ELEMENT(t1,t2,elem) \
	if (t1.elem != t2.elem) { \
		printf("failure: strptime [\n" \
		       "result differs if the format string has a 'Z' at the end\n" \
		       "element: %s %d != %d\n" \
		       "]\n", \
		       __STRING(elen), t1.elem, t2.elem); \
		return false; \
	}

	CMP_TM_ELEMENT(t,t2,tm_sec);
	CMP_TM_ELEMENT(t,t2,tm_min);
	CMP_TM_ELEMENT(t,t2,tm_hour);
	CMP_TM_ELEMENT(t,t2,tm_mday);
	CMP_TM_ELEMENT(t,t2,tm_mon);
	CMP_TM_ELEMENT(t,t2,tm_year);
	CMP_TM_ELEMENT(t,t2,tm_wday);
	CMP_TM_ELEMENT(t,t2,tm_yday);
	CMP_TM_ELEMENT(t,t2,tm_isdst);

	if (t.tm_sec != 46) {
		printf("failure: strptime [\n"
		       "tm_sec: expected: 46, got: %d\n"
		       "]\n",
		       t.tm_sec);
		return false;
	}

	if (t.tm_min != 15) {
		printf("failure: strptime [\n"
		       "tm_min: expected: 15, got: %d\n"
		       "]\n",
		       t.tm_min);
		return false;
	}

	if (t.tm_hour != 10) {
		printf("failure: strptime [\n"
		       "tm_hour: expected: 10, got: %d\n"
		       "]\n",
		       t.tm_hour);
		return false;
	}

	if (t.tm_mday != 14) {
		printf("failure: strptime [\n"
		       "tm_mday: expected: 14, got: %d\n"
		       "]\n",
		       t.tm_mday);
		return false;
	}

	if (t.tm_mon != 3) {
		printf("failure: strptime [\n"
		       "tm_mon: expected: 3, got: %d\n"
		       "]\n",
		       t.tm_mon);
		return false;
	}

	if (t.tm_year != 107) {
		printf("failure: strptime [\n"
		       "tm_year: expected: 107, got: %d\n"
		       "]\n",
		       t.tm_year);
		return false;
	}

	if (t.tm_wday != 6) { /* saturday */
		printf("failure: strptime [\n"
		       "tm_wday: expected: 6, got: %d\n"
		       "]\n",
		       t.tm_wday);
		return false;
	}

	if (t.tm_yday != 103) {
		printf("failure: strptime [\n"
		       "tm_yday: expected: 103, got: %d\n"
		       "]\n",
		       t.tm_yday);
		return false;
	}

	/* we don't test this as it depends on the host configuration
	if (t.tm_isdst != 0) {
		printf("failure: strptime [\n"
		       "tm_isdst: expected: 0, got: %d\n"
		       "]\n",
		       t.tm_isdst);
		return false;
	}*/

	printf("success: strptime\n");

	return true;
}

#ifdef LIBREPLACE_CONFIGURE_TEST_STRPTIME
int main (void)
{
	int ret;
	ret = libreplace_test_strptime();
	if (ret == false) return 1;
	return 0;
}
#endif
a id='n712' href='#n712'>712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823
/* omfile.c
 * This is the implementation of the build-in file output module.
 *
 * Handles: eTypeCONSOLE, eTypeTTY, eTypeFILE, eTypePIPE
 *
 * NOTE: read comments in module-template.h to understand how this file
 *       works!
 *
 * File begun on 2007-07-21 by RGerhards (extracted from syslogd.c)
 * This file is under development and has not yet arrived at being fully
 * self-contained and a real object. So far, it is mostly an excerpt
 * of the "old" message code without any modifications. However, it
 * helps to have things at the right place one we go to the meat of it.
 *
 * Copyright 2007 Rainer Gerhards and Adiscon GmbH.
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * 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 <time.h>
#include <assert.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/file.h>

#include "syslogd.h"
#include "syslogd-types.h"
#include "srUtils.h"
#include "template.h"
#include "outchannel.h"
#include "omfile.h"
#include "cfsysline.h"
#include "module-template.h"

/* internal structures
 */
DEF_OMOD_STATIC_DATA

/* The following structure is a dynafile name cache entry.
 */
struct s_dynaFileCacheEntry {
	uchar *pName;	/* name currently open, if dynamic name */
	short	fd;		/* name associated with file name in cache */
	time_t	lastUsed;	/* for LRU - last access */
};
typedef struct s_dynaFileCacheEntry dynaFileCacheEntry;


/* globals for default values */
static int iDynaFileCacheSize = 10; /* max cache for dynamic files */
static int fCreateMode = 0644; /* mode to use when creating files */
static int fDirCreateMode = 0644; /* mode to use when creating files */
static int	bFailOnChown;	/* fail if chown fails? */
static uid_t	fileUID;	/* UID to be used for newly created files */
static uid_t	fileGID;	/* GID to be used for newly created files */
static uid_t	dirUID;		/* UID to be used for newly created directories */
static uid_t	dirGID;		/* GID to be used for newly created directories */
static int	bCreateDirs;	/* auto-create directories for dynaFiles: 0 - no, 1 - yes */
/* end globals for default values */

typedef struct _instanceData {
	uchar	f_fname[MAXFNAME];/* file or template name (display only) */
	short	fd;		  /* file descriptor for (current) file */
	enum {
		eTypeFILE,
		eTypeTTY,
		eTypeCONSOLE,
		eTypePIPE
	} fileType;	
	struct template *pTpl;	/* pointer to template object */
	char	bDynamicName;	/* 0 - static name, 1 - dynamic name (with properties) */
	int	fCreateMode;	/* file creation mode for open() */
	int	fDirCreateMode;	/* creation mode for mkdir() */
	int	bCreateDirs;	/* auto-create directories? */
	int	bSyncFile;	/* should the file by sync()'ed? 1- yes, 0- no */
	uid_t	fileUID;	/* IDs for creation */
	uid_t	dirUID;
	gid_t	fileGID;
	gid_t	dirGID;
	int	bFailOnChown;	/* fail creation if chown fails? */
	int	iCurrElt;	/* currently active cache element (-1 = none) */
	int	iCurrCacheSize;	/* currently cache size (1-based) */
	int	iDynaFileCacheSize; /* size of file handle cache */
	/* The cache is implemented as an array. An empty element is indicated
	 * by a NULL pointer. Memory is allocated as needed. The following
	 * pointer points to the overall structure.
	 */
	dynaFileCacheEntry **dynCache;
	off_t	f_sizeLimit;		/* file size limit, 0 = no limit */
	char	*f_sizeLimitCmd;	/* command to carry out when size limit is reached */
} instanceData;


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


BEGINdbgPrintInstInfo
CODESTARTdbgPrintInstInfo
	if(pData->bDynamicName) {
		printf("[dynamic]\n\ttemplate='%s'"
		       "\tfile cache size=%d\n"
		       "\tcreate directories: %s\n"
		       "\tfile owner %d, group %d\n"
		       "\tdirectory owner %d, group %d\n"
		       "\tfail if owner/group can not be set: %s\n",
		        pData->f_fname,
			pData->iDynaFileCacheSize,
			pData->bCreateDirs ? "yes" : "no",
			pData->fileUID, pData->fileGID,
			pData->dirUID, pData->dirGID,
			pData->bFailOnChown ? "yes" : "no"
			);
	} else { /* regular file */
		printf("%s", pData->f_fname);
		if (pData->fd == -1)
			printf(" (unused)");
	}
ENDdbgPrintInstInfo


/* set the dynaFile cache size. Does some limit checking.
 * rgerhards, 2007-07-31
 */
rsRetVal setDynaFileCacheSize(void __attribute__((unused)) *pVal, int iNewVal)
{
	DEFiRet;
	uchar errMsg[128];	/* for dynamic error messages */

	if(iNewVal < 1) {
		snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar),
		         "DynaFileCacheSize must be greater 0 (%d given), changed to 1.", iNewVal);
		errno = 0;
		logerror((char*) errMsg);
		iRet = RS_RET_VAL_OUT_OF_RANGE;
		iNewVal = 1;
	} else if(iNewVal > 10000) {
		snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar),
		         "DynaFileCacheSize maximum is 10,000 (%d given), changed to 10,000.", iNewVal);
		errno = 0;
		logerror((char*) errMsg);
		iRet = RS_RET_VAL_OUT_OF_RANGE;
		iNewVal = 10000;
	}

	iDynaFileCacheSize = iNewVal;
	dbgprintf("DynaFileCacheSize changed to %d.\n", iNewVal);

	return iRet;
}


/* Helper to cfline(). Parses a output channel name up until the first
 * comma and then looks for the template specifier. Tries
 * to find that template. Maps the output channel to the 
 * proper filed structure settings. Everything is stored in the
 * filed struct. Over time, the dependency on filed might be
 * removed.
 * rgerhards 2005-06-21
 */
static rsRetVal cflineParseOutchannel(instanceData *pData, uchar* p, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts)
{
	DEFiRet;
	size_t i;
	struct outchannel *pOch;
	char szBuf[128];	/* should be more than sufficient */

	/* this must always be a file, because we can not set a size limit
	 * on a pipe...
	 * rgerhards 2005-06-21: later, this will be a separate type, but let's
	 * emulate things for the time being. When everything runs, we can
	 * extend it...
	 */
	pData->fileType = eTypeFILE;

	++p; /* skip '$' */
	i = 0;
	/* get outchannel name */
	while(*p && *p != ';' && *p != ' ' &&
	      i < sizeof(szBuf) / sizeof(char)) {
	      szBuf[i++] = *p++;
	}
	szBuf[i] = '\0';

	/* got the name, now look up the channel... */
	pOch = ochFind(szBuf, i);

	if(pOch == NULL) {
		char errMsg[128];
		errno = 0;
		snprintf(errMsg, sizeof(errMsg)/sizeof(char),
			 "outchannel '%s' not found - ignoring action line",
			 szBuf);
		logerror(errMsg);
		return RS_RET_NOT_FOUND;
	}

	/* check if there is a file name in the outchannel... */
	if(pOch->pszFileTemplate == NULL) {
		char errMsg[128];
		errno = 0;
		snprintf(errMsg, sizeof(errMsg)/sizeof(char),
			 "outchannel '%s' has no file name template - ignoring action line",
			 szBuf);
		logerror(errMsg);
		return RS_RET_ERR;
	}

	/* OK, we finally got a correct template. So let's use it... */
	strncpy((char*) pData->f_fname, (char*) pOch->pszFileTemplate, MAXFNAME);
	pData->f_sizeLimit = pOch->uSizeLimit;
	/* WARNING: It is dangerous "just" to pass the pointer. As we
	 * never rebuild the output channel description, this is acceptable here.
	 */
	pData->f_sizeLimitCmd = (char*) pOch->cmdOnSizeLimit;

	iRet = cflineParseTemplateName(&p, pOMSR, iEntry, iTplOpts, (uchar*) " TradFmt");

	return(iRet);
}


/* rgerhards 2005-06-21: Try to resolve a size limit
 * situation. This first runs the command, and then
 * checks if we are still above the treshold.
 * returns 0 if ok, 1 otherwise
 * TODO: consider moving the initial check in here, too
 */
int resolveFileSizeLimit(instanceData *pData)
{
	uchar *pParams;
	uchar *pCmd;
	uchar *p;
	off_t actualFileSize;
	assert(pData != NULL);

	if(pData->f_sizeLimitCmd == NULL)
		return 1; /* nothing we can do in this case... */
	
	/* the execProg() below is probably not great, but at least is is
	 * fairly secure now. Once we change the way file size limits are
	 * handled, we should also revisit how this command is run (and
	 * with which parameters).   rgerhards, 2007-07-20
	 */
	/* we first check if we have command line parameters. We assume this, 
	 * when we have a space in the program name. If we find it, everything after
	 * the space is treated as a single argument.
	 */
	if((pCmd = (uchar*)strdup((char*)pData->f_sizeLimitCmd)) == NULL) {
		/* there is not much we can do - we make syslogd close the file in this case */
		glblHadMemShortage = 1;
		return 1;
		}

	for(p = pCmd ; *p && *p != ' ' ; ++p) {
		/* JUST SKIP */
	}

	if(*p == ' ') {
		*p = '\0'; /* pretend string-end */
		pParams = p+1;
	} else
		pParams = NULL;

	execProg(pCmd, 1, pParams);

	pData->fd = open((char*) pData->f_fname, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY,
			pData->fCreateMode);

	actualFileSize = lseek(pData->fd, 0, SEEK_END);
	if(actualFileSize >= pData->f_sizeLimit) {
		/* OK, it didn't work out... */
		return 1;
		}

	return 0;
}


/* This function deletes an entry from the dynamic file name
 * cache. A pointer to the cache must be passed in as well
 * as the index of the to-be-deleted entry. This index may
 * point to an unallocated entry, in whcih case the
 * function immediately returns. Parameter bFreeEntry is 1
 * if the entry should be free()ed and 0 if not.
 */
static void dynaFileDelCacheEntry(dynaFileCacheEntry **pCache, int iEntry, int bFreeEntry)
{
	assert(pCache != NULL);

	if(pCache[iEntry] == NULL)
		return;

	dbgprintf("Removed entry %d for file '%s' from dynaCache.\n", iEntry,
		pCache[iEntry]->pName == NULL ? "[OPEN FAILED]" : (char*)pCache[iEntry]->pName);
	/* if the name is NULL, this is an improperly initilized entry which
	 * needs to be discarded. In this case, neither the file is to be closed
	 * not the name to be freed.
	 */
	if(pCache[iEntry]->pName != NULL) {
		close(pCache[iEntry]->fd);
		free(pCache[iEntry]->pName);
		pCache[iEntry]->pName = NULL;
	}

	if(bFreeEntry) {
		free(pCache[iEntry]);
		pCache[iEntry] = NULL;
	}
}


/* This function frees the dynamic file name cache.
 */
static void dynaFileFreeCache(instanceData *pData)
{
	register int i;
	assert(pData != NULL);

	for(i = 0 ; i < pData->iCurrCacheSize ; ++i) {
		dynaFileDelCacheEntry(pData->dynCache, i, 1);
	}

	free(pData->dynCache);
}


/* This is a shared code for both static and dynamic files.
 */
static void prepareFile(instanceData *pData, uchar *newFileName)
{
	if(access((char*)newFileName, F_OK) == 0) {
		/* file already exists */
		pData->fd = open((char*) newFileName, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY,
				pData->fCreateMode);
	} else {
		/* file does not exist, create it (and eventually parent directories */
		if(pData->bCreateDirs) {
			/* we fist need to create parent dirs if they are missing
			 * We do not report any errors here ourselfs but let the code
			 * fall through to error handler below.
			 */
			if(makeFileParentDirs(newFileName, strlen((char*)newFileName),
			     pData->fDirCreateMode, pData->dirUID,
			     pData->dirGID, pData->bFailOnChown) == 0) {
				pData->fd = open((char*) newFileName, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY,
						pData->fCreateMode);
				if(pData->fd != -1) {
					/* check and set uid/gid */
					if(pData->fileUID != (uid_t)-1 || pData->fileGID != (gid_t) -1) {
						/* we need to set owner/group */
						if(fchown(pData->fd, pData->fileUID,
						          pData->fileGID) != 0) {
							if(pData->bFailOnChown) {
								int eSave = errno;
								close(pData->fd);
								pData->fd = -1;
								errno = eSave;
							}
							/* we will silently ignore the chown() failure
							 * if configured to do so.
							 */
						}
					}
				}
			}
		}
	}
}


/* This function handles dynamic file names. It checks if the
 * requested file name is already open and, if not, does everything
 * needed to switch to the it.
 * Function returns 0 if all went well and non-zero otherwise.
 * On successful return pData->fd must point to the correct file to
 * be written.
 * This is a helper to writeFile(). rgerhards, 2007-07-03
 */
static int prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsgOpts)
{
	time_t ttOldest; /* timestamp of oldest element */
	int iOldest;
	int i;
	int iFirstFree;
	dynaFileCacheEntry **pCache;

	assert(pData != NULL);
	assert(newFileName != NULL);

	pCache = pData->dynCache;

	/* first check, if we still have the current file
	 * I *hope* this will be a performance enhancement.
	 */
	if(   (pData->iCurrElt != -1)
	   && !strcmp((char*) newFileName, (char*) pCache[pData->iCurrElt]->pName)) {
	   	/* great, we are all set */
		pCache[pData->iCurrElt]->lastUsed = time(NULL); /* update timestamp for LRU */
		return 0;
	}

	/* ok, no luck. Now let's search the table if we find a matching spot.
	 * While doing so, we also prepare for creation of a new one.
	 */
	iFirstFree = -1; /* not yet found */
	iOldest = 0; /* we assume the first element to be the oldest - that will change as we loop */
	ttOldest = time(NULL) + 1; /* there must always be an older one */
	for(i = 0 ; i < pData->iCurrCacheSize ; ++i) {
		if(pCache[i] == NULL) {
			if(iFirstFree == -1)
				iFirstFree = i;
		} else { /* got an element, let's see if it matches */
			if(!strcmp((char*) newFileName, (char*) pCache[i]->pName)) {
				/* we found our element! */
				pData->fd = pCache[i]->fd;
				pData->iCurrElt = i;
				pCache[i]->lastUsed = time(NULL); /* update timestamp for LRU */
				return 0;
			}
			/* did not find it - so lets keep track of the counters for LRU */
			if(pCache[i]->lastUsed < ttOldest) {
				ttOldest = pCache[i]->lastUsed;
				iOldest = i;
				}
		}
	}

	/* we have not found an entry */
	if(iFirstFree == -1 && (pData->iCurrCacheSize < pData->iDynaFileCacheSize)) {
		/* there is space left, so set it to that index */
		iFirstFree = pData->iCurrCacheSize++;
	}

	if(iFirstFree == -1) {
		dynaFileDelCacheEntry(pCache, iOldest, 0);
		iFirstFree = iOldest; /* this one *is* now free ;) */
	} else {
		/* we need to allocate memory for the cache structure */
		pCache[iFirstFree] = (dynaFileCacheEntry*) calloc(1, sizeof(dynaFileCacheEntry));
		if(pCache[iFirstFree] == NULL) {
			glblHadMemShortage = TRUE;
			dbgprintf("prepareDynfile(): could not alloc mem, discarding this request\n");
			return -1;
		}
	}

	/* Ok, we finally can open the file */
	prepareFile(pData, newFileName);

	/* file is either open now or an error state set */
	if(pData->fd == -1) {
		/* do not report anything if the message is an internally-generated
		 * message. Otherwise, we could run into a never-ending loop. The bad
		 * news is that we also lose errors on startup messages, but so it is.
		 */
		if(iMsgOpts & INTERNAL_MSG)
			dbgprintf("Could not open dynaFile, discarding message\n");
		else
			logerrorSz("Could not open dynamic file '%s' - discarding message", (char*)newFileName);
		dynaFileDelCacheEntry(pCache, iFirstFree, 1);
		pData->iCurrElt = -1;
		return -1;
	}

	pCache[iFirstFree]->fd = pData->fd;
	pCache[iFirstFree]->pName = (uchar*)strdup((char*)newFileName); /* TODO: check for NULL (very unlikely) */
	pCache[iFirstFree]->lastUsed = time(NULL);
	pData->iCurrElt = iFirstFree;
	dbgprintf("Added new entry %d for file cache, file '%s'.\n",
		iFirstFree, newFileName);

	return 0;
}


/* rgerhards 2004-11-11: write to a file output. This
 * will be called for all outputs using file semantics,
 * for example also for pipes.
 */
static rsRetVal writeFile(uchar **ppString, unsigned iMsgOpts, instanceData *pData)
{
	off_t actualFileSize;
	DEFiRet;

	assert(pData != NULL);

	/* first check if we have a dynamic file name and, if so,
	 * check if it still is ok or a new file needs to be created
	 */
	if(pData->bDynamicName) {
		if(prepareDynFile(pData, ppString[1], iMsgOpts) != 0)
			return RS_RET_ERR;
	}

	/* create the message based on format specified */
again:
	/* check if we have a file size limit and, if so,
	 * obey to it.
	 */
	if(pData->f_sizeLimit != 0) {
		actualFileSize = lseek(pData->fd, 0, SEEK_END);
		if(actualFileSize >= pData->f_sizeLimit) {
			char errMsg[256];
			/* for now, we simply disable a file once it is
			 * beyond the maximum size. This is better than having
			 * us aborted by the OS... rgerhards 2005-06-21
			 */
			(void) close(pData->fd);
			/* try to resolve the situation */
			if(resolveFileSizeLimit(pData) != 0) {
				/* didn't work out, so disable... */
				snprintf(errMsg, sizeof(errMsg),
					 "no longer writing to file %s; grown beyond configured file size of %lld bytes, actual size %lld - configured command did not resolve situation",
					 pData->f_fname, (long long) pData->f_sizeLimit, (long long) actualFileSize);
				errno = 0;
				logerror(errMsg);
				return RS_RET_DISABLE_ACTION;
			} else {
				snprintf(errMsg, sizeof(errMsg),
					 "file %s had grown beyond configured file size of %lld bytes, actual size was %lld - configured command resolved situation",
					 pData->f_fname, (long long) pData->f_sizeLimit, (long long) actualFileSize);
				errno = 0;
				logerror(errMsg);
			}
		}
	}

	if (write(pData->fd, ppString[0], strlen((char*)ppString[0])) < 0) {
		int e = errno;

		/* If a named pipe is full, just ignore it for now
		   - mrn 24 May 96 */
		if (pData->fileType == eTypePIPE && e == EAGAIN)
			return RS_RET_OK;

		/* If the filesystem is filled up, just ignore
		 * it for now and continue writing when possible
		 * based on patch for sysklogd by Martin Schulze on 2007-05-24
		 */
		if (pData->fileType == eTypeFILE && e == ENOSPC)
			return RS_RET_OK;

		(void) close(pData->fd);
		/*
		 * Check for EBADF on TTY's due to vhangup()
		 * Linux uses EIO instead (mrn 12 May 96)
		 */
		if ((pData->fileType == eTypeTTY || pData->fileType == eTypeCONSOLE)
#ifdef linux
			&& e == EIO) {
#else
			&& e == EBADF) {
#endif
			pData->fd = open((char*) pData->f_fname, O_WRONLY|O_APPEND|O_NOCTTY);
			if (pData->fd < 0) {
				iRet = RS_RET_DISABLE_ACTION;
				logerror((char*) pData->f_fname);
			} else {
				untty();
				goto again;
			}
		} else {
			iRet = RS_RET_DISABLE_ACTION;
			errno = e;
			logerror((char*) pData->f_fname);
		}
	} else if (pData->bSyncFile)
		fsync(pData->fd);
	return(iRet);
}


BEGINcreateInstance
CODESTARTcreateInstance
ENDcreateInstance


BEGINfreeInstance
CODESTARTfreeInstance
	if(pData->bDynamicName) {
		dynaFileFreeCache(pData);
	} else 
		close(pData->fd);
ENDfreeInstance


BEGINonSelectReadyWrite
CODESTARTonSelectReadyWrite
ENDonSelectReadyWrite


BEGINneedUDPSocket
CODESTARTneedUDPSocket
ENDneedUDPSocket


BEGINgetWriteFDForSelect
CODESTARTgetWriteFDForSelect
ENDgetWriteFDForSelect


BEGINtryResume
CODESTARTtryResume
ENDtryResume

BEGINdoAction
CODESTARTdoAction
	dbgprintf(" (%s)\n", pData->f_fname);
	/* pData->fd == -1 is an indicator that the we couldn't
	 * open the file at startup. For dynaFiles, this is ok,
	 * all others are doomed.
	 */
	if(pData->bDynamicName || (pData->fd != -1))
		iRet = writeFile(ppString, iMsgOpts, pData);
ENDdoAction


BEGINparseSelectorAct
CODESTARTparseSelectorAct
	/* yes, the if below is redundant, but I need it now. Will go away as
	 * the code further changes.  -- rgerhards, 2007-07-25
	 */
	if(*p == '$' || *p == '?' || *p == '|' || *p == '/' || *p == '-') {
		if((iRet = createInstance(&pData)) != RS_RET_OK)
			return iRet;
	} else {
		/* this is not clean, but we need it for the time being
		 * TODO: remove when cleaning up modularization 
		 */
		return RS_RET_CONFLINE_UNPROCESSED;
	}

	if (*p == '-') {
		pData->bSyncFile = 0;
		p++;
	} else
		pData->bSyncFile = 1;

	pData->f_sizeLimit = 0; /* default value, use outchannels to configure! */

	switch (*p)
	{
        case '$':
		CODE_STD_STRING_REQUESTparseSelectorAct(1)
		/* rgerhards 2005-06-21: this is a special setting for output-channel
		 * definitions. In the long term, this setting will probably replace
		 * anything else, but for the time being we must co-exist with the
		 * traditional mode lines.
		 * rgerhards, 2007-07-24: output-channels will go away. We keep them
		 * for compatibility reasons, but seems to have been a bad idea.
		 */
		if((iRet = cflineParseOutchannel(pData, p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS)) == RS_RET_OK) {
			pData->bDynamicName = 0;
			pData->fCreateMode = fCreateMode; /* preserve current setting */
			pData->fDirCreateMode = fDirCreateMode; /* preserve current setting */
			pData->fd = open((char*) pData->f_fname, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY,
					 pData->fCreateMode);
		}
		break;

	case '?': /* This is much like a regular file handle, but we need to obtain
		   * a template name. rgerhards, 2007-07-03
		   */
		CODE_STD_STRING_REQUESTparseSelectorAct(2)
		++p; /* eat '?' */
		if((iRet = cflineParseFileName(p, (uchar*) pData->f_fname, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS))
		   != RS_RET_OK)
			break;
		/* "filename" is actually a template name, we need this as string 1. So let's add it
		 * to the pOMSR. -- rgerhards, 2007-07-27
		 */
		if((iRet = OMSRsetEntry(*ppOMSR, 1, (uchar*)strdup((char*) pData->f_fname), OMSR_NO_RQD_TPL_OPTS)) != RS_RET_OK)
			break;

		pData->bDynamicName = 1;
		pData->iCurrElt = -1;		  /* no current element */
		pData->fCreateMode = fCreateMode; /* freeze current setting */
		pData->fDirCreateMode = fDirCreateMode; /* preserve current setting */
		pData->bCreateDirs = bCreateDirs;
		pData->bFailOnChown = bFailOnChown;
		pData->fileUID = fileUID;
		pData->fileGID = fileGID;
		pData->dirUID = dirUID;
		pData->dirGID = dirGID;
		pData->iDynaFileCacheSize = iDynaFileCacheSize; /* freeze current setting */
		/* we now allocate the cache table. We use calloc() intentionally, as we 
		 * need all pointers to be initialized to NULL pointers.
		 */
		if((pData->dynCache = (dynaFileCacheEntry**)
		    calloc(iDynaFileCacheSize, sizeof(dynaFileCacheEntry*))) == NULL) {
			iRet = RS_RET_OUT_OF_MEMORY;
			dbgprintf("Could not allocate memory for dynaFileCache - selector disabled.\n");
		}
		break;

        case '|':
	case '/':
		CODE_STD_STRING_REQUESTparseSelectorAct(1)
		/* rgerhards, 2007-0726: first check if file or pipe */
		if(*p == '|') {
			pData->fileType = eTypePIPE;
			++p;
		} else {
			pData->fileType = eTypeFILE;
		}
		/* rgerhards 2004-11-17: from now, we need to have different
		 * processing, because after the first comma, the template name
		 * to use is specified. So we need to scan for the first coma first
		 * and then look at the rest of the line.
		 */
		if((iRet = cflineParseFileName(p, (uchar*) pData->f_fname, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS))
		   != RS_RET_OK)
			break;

		pData->bDynamicName = 0;
		pData->fCreateMode = fCreateMode; /* preserve current setting */
		pData->fDirCreateMode = fDirCreateMode;
		pData->bCreateDirs = bCreateDirs;
		pData->bFailOnChown = bFailOnChown;
		pData->fileUID = fileUID;
		pData->fileGID = fileGID;
		pData->dirUID = dirUID;
		pData->dirGID = dirGID;

		if(pData->fileType == eTypePIPE) {
			pData->fd = open((char*) pData->f_fname, O_RDWR|O_NONBLOCK);
	        } else {
			prepareFile(pData, pData->f_fname);
		}
		        
	  	if ( pData->fd < 0 ){
			pData->fd = -1;
			dbgprintf("Error opening log file: %s\n", pData->f_fname);
			logerror((char*) pData->f_fname);
			break;
		}
		if (isatty(pData->fd)) {
			pData->fileType = eTypeTTY;
			untty();
		}
		if (strcmp((char*) p, ctty) == 0)
			pData->fileType = eTypeCONSOLE;
		break;
	default:
		iRet = RS_RET_CONFLINE_UNPROCESSED;
		break;
	}
CODE_STD_FINALIZERparseSelectorAct
ENDparseSelectorAct


/* Reset config variables for this module to default values.
 * rgerhards, 2007-07-17
 */
static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal)
{
	fileUID = -1;
	fileGID = -1;
	dirUID = -1;
	dirGID = -1;
	bFailOnChown = 1;
	iDynaFileCacheSize = 10;
	fCreateMode = 0644;
	fDirCreateMode = 0644;
	bCreateDirs = 1;

	return RS_RET_OK;
}


BEGINmodExit
CODESTARTmodExit
ENDmodExit


BEGINqueryEtryPt
CODESTARTqueryEtryPt
CODEqueryEtryPt_STD_OMOD_QUERIES
ENDqueryEtryPt


BEGINmodInit(File)
CODESTARTmodInit
	*ipIFVersProvided = 1; /* so far, we only support the initial definition */
CODEmodInit_QueryRegCFSLineHdlr
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"dynafilecachesize", 0, eCmdHdlrInt, (void*) setDynaFileCacheSize, NULL, STD_LOADABLE_MODULE_ID));
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"dirowner", 0, eCmdHdlrUID, NULL, &dirUID, STD_LOADABLE_MODULE_ID));
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"dirgroup", 0, eCmdHdlrGID, NULL, &dirGID, STD_LOADABLE_MODULE_ID));
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"fileowner", 0, eCmdHdlrUID, NULL, &fileUID, STD_LOADABLE_MODULE_ID));
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"filegroup", 0, eCmdHdlrGID, NULL, &fileGID, STD_LOADABLE_MODULE_ID));
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"dircreatemode", 0, eCmdHdlrFileCreateMode, NULL, &fDirCreateMode, STD_LOADABLE_MODULE_ID));
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"filecreatemode", 0, eCmdHdlrFileCreateMode, NULL, &fCreateMode, STD_LOADABLE_MODULE_ID));
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"createdirs", 0, eCmdHdlrBinary, NULL, &bCreateDirs, STD_LOADABLE_MODULE_ID));
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"failonchownfailure", 0, eCmdHdlrBinary, NULL, &bFailOnChown, STD_LOADABLE_MODULE_ID));
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID));
ENDmodInit
/*
 * vi:set ai:
 */