summaryrefslogtreecommitdiffstats
path: root/omfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'omfile.c')
-rw-r--r--omfile.c437
1 files changed, 437 insertions, 0 deletions
diff --git a/omfile.c b/omfile.c
new file mode 100644
index 00000000..f2d7814a
--- /dev/null
+++ b/omfile.c
@@ -0,0 +1,437 @@
+/* omfile.c
+ * This is the implementation of the build-in file output module.
+ *
+ * Handles: F_CONSOLE, F_TTY, F_FILE, F_PIPE
+ *
+ * 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 <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <unistd.h>
+#include <sys/file.h>
+//#include <sys/param.h>
+
+#include "rsyslog.h"
+#include "syslogd.h"
+#include "syslogd-types.h"
+#include "srUtils.h"
+#include "template.h"
+#include "omfile.h"
+
+
+/* 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(selector_t *f)
+{
+ uchar *pParams;
+ uchar *pCmd;
+ uchar *p;
+ off_t actualFileSize;
+ assert(f != NULL);
+
+ if(f->f_un.f_file.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*)f->f_un.f_file.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);
+
+ f->f_file = open(f->f_un.f_file.f_fname, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY,
+ f->f_un.f_file.fCreateMode);
+
+ actualFileSize = lseek(f->f_file, 0, SEEK_END);
+ if(actualFileSize >= f->f_un.f_file.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;
+
+ dprintf("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(selector_t *f)
+{
+ register int i;
+ assert(f != NULL);
+
+ for(i = 0 ; i < f->f_un.f_file.iCurrCacheSize ; ++i) {
+ dynaFileDelCacheEntry(f->f_un.f_file.dynCache, i, 1);
+ }
+
+ free(f->f_un.f_file.dynCache);
+}
+
+
+/* This function handles dynamic file names. It generates a new one
+ * based on the current message, checks if that file is already open
+ * and, if not, does everything needed to switch to the new one.
+ * Function returns 0 if all went well and non-zero otherwise.
+ * On successful return f->f_file must point to the correct file to
+ * be written.
+ * This is a helper to writeFile(). rgerhards, 2007-07-03
+ */
+static int prepareDynFile(selector_t *f)
+{
+ uchar *newFileName;
+ time_t ttOldest; /* timestamp of oldest element */
+ int iOldest;
+ int i;
+ int iFirstFree;
+ dynaFileCacheEntry **pCache;
+
+ assert(f != NULL);
+ if((newFileName = tplToString(f->f_un.f_file.pTpl, f->f_pMsg)) == NULL) {
+ /* memory shortage - there is nothing we can do to resolve it.
+ * We silently ignore it, this is probably the best we can do.
+ */
+ glblHadMemShortage = TRUE;
+ dprintf("prepareDynfile(): could not create file name, discarding this request\n");
+ return -1;
+ }
+
+ pCache = f->f_un.f_file.dynCache;
+
+ /* first check, if we still have the current file
+ * I *hope* this will be a performance enhancement.
+ */
+ if( (f->f_un.f_file.iCurrElt != -1)
+ && !strcmp((char*) newFileName,
+ (char*) pCache[f->f_un.f_file.iCurrElt])) {
+ /* great, we are all set */
+ free(newFileName);
+ pCache[f->f_un.f_file.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 < f->f_un.f_file.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! */
+ f->f_file = pCache[i]->fd;
+ f->f_un.f_file.iCurrElt = i;
+ free(newFileName);
+ 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 && (f->f_un.f_file.iCurrCacheSize < f->f_un.f_file.iDynaFileCacheSize)) {
+ /* there is space left, so set it to that index */
+ iFirstFree = f->f_un.f_file.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;
+ dprintf("prepareDynfile(): could not alloc mem, discarding this request\n");
+ free(newFileName);
+ return -1;
+ }
+ }
+
+ /* Ok, we finally can open the file */
+ if(access((char*)newFileName, F_OK) == 0) {
+ /* file already exists */
+ f->f_file = open((char*) newFileName, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY,
+ f->f_un.f_file.fCreateMode);
+ } else {
+ /* file does not exist, create it (and eventually parent directories */
+ if(f->f_un.f_file.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),
+ f->f_un.f_file.fDirCreateMode, f->f_un.f_file.dirUID,
+ f->f_un.f_file.dirGID, f->f_un.f_file.bFailOnChown) == 0) {
+ f->f_file = open((char*) newFileName, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY,
+ f->f_un.f_file.fCreateMode);
+ if(f->f_file != -1) {
+ /* check and set uid/gid */
+ if(f->f_un.f_file.fileUID != (uid_t)-1 || f->f_un.f_file.fileGID != (gid_t) -1) {
+ /* we need to set owner/group */
+ if(fchown(f->f_file, f->f_un.f_file.fileUID,
+ f->f_un.f_file.fileGID) != 0) {
+ if(f->f_un.f_file.bFailOnChown) {
+ int eSave = errno;
+ close(f->f_file);
+ f->f_file = -1;
+ errno = eSave;
+ }
+ /* we will silently ignore the chown() failure
+ * if configured to do so.
+ */
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /* file is either open now or an error state set */
+ if(f->f_file == -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(f->f_pMsg->msgFlags & INTERNAL_MSG)
+ dprintf("Could not open dynaFile, discarding message\n");
+ else
+ logerrorSz("Could not open dynamic file '%s' - discarding message", (char*)newFileName);
+ free(newFileName);
+ dynaFileDelCacheEntry(pCache, iFirstFree, 1);
+ return -1;
+ }
+
+ pCache[iFirstFree]->fd = f->f_file;
+ pCache[iFirstFree]->pName = newFileName;
+ pCache[iFirstFree]->lastUsed = time(NULL);
+ f->f_un.f_file.iCurrElt = iFirstFree;
+ dprintf("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.
+ */
+void writeFile(selector_t *f)
+{
+ off_t actualFileSize;
+
+ assert(f != 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(f->f_un.f_file.bDynamicName) {
+ if(prepareDynFile(f) != 0)
+ return;
+ }
+
+ /* create the message based on format specified */
+ iovCreate(f);
+again:
+ /* check if we have a file size limit and, if so,
+ * obey to it.
+ */
+ if(f->f_un.f_file.f_sizeLimit != 0) {
+ actualFileSize = lseek(f->f_file, 0, SEEK_END);
+ if(actualFileSize >= f->f_un.f_file.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(f->f_file);
+ /* try to resolve the situation */
+ if(resolveFileSizeLimit(f) != 0) {
+ /* didn't work out, so disable... */
+ f->f_type = F_UNUSED;
+ 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",
+ f->f_un.f_file.f_fname, (long long) f->f_un.f_file.f_sizeLimit, (long long) actualFileSize);
+ errno = 0;
+ logerror(errMsg);
+ return;
+ } else {
+ snprintf(errMsg, sizeof(errMsg),
+ "file %s had grown beyond configured file size of %lld bytes, actual size was %lld - configured command resolved situation",
+ f->f_un.f_file.f_fname, (long long) f->f_un.f_file.f_sizeLimit, (long long) actualFileSize);
+ errno = 0;
+ logerror(errMsg);
+ }
+ }
+ }
+
+ if (writev(f->f_file, f->f_iov, f->f_iIovUsed) < 0) {
+ int e = errno;
+
+ /* If a named pipe is full, just ignore it for now
+ - mrn 24 May 96 */
+ if (f->f_type == F_PIPE && e == EAGAIN)
+ return;
+
+ /* 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 (f->f_type == F_FILE && e == ENOSPC)
+ return;
+
+ (void) close(f->f_file);
+ /*
+ * Check for EBADF on TTY's due to vhangup()
+ * Linux uses EIO instead (mrn 12 May 96)
+ */
+ if ((f->f_type == F_TTY || f->f_type == F_CONSOLE)
+#ifdef linux
+ && e == EIO) {
+#else
+ && e == EBADF) {
+#endif
+ f->f_file = open(f->f_un.f_file.f_fname, O_WRONLY|O_APPEND|O_NOCTTY);
+ if (f->f_file < 0) {
+ f->f_type = F_UNUSED;
+ logerror(f->f_un.f_file.f_fname);
+ } else {
+ untty();
+ goto again;
+ }
+ } else {
+ f->f_type = F_UNUSED;
+ errno = e;
+ logerror(f->f_un.f_file.f_fname);
+ }
+ } else if (f->f_flags & SYNC_FILE)
+ fsync(f->f_file);
+}
+
+
+/* free an instance
+ * returns 0 if it succeeds, something else otherwise
+ */
+int freeInstanceFile(selector_t *f)
+{
+ assert(f != NULL);
+ if(f->f_un.f_file.bDynamicName) {
+ dynaFileFreeCache(f);
+ } else
+ close(f->f_file);
+ return 0;
+}
+
+
+/* call the shell action
+ * returns 0 if it succeeds, something else otherwise
+ */
+int doActionFile(selector_t *f)
+{
+ assert(f != NULL);
+
+ /* f->f_file == -1 is an indicator that the we couldn't
+ * open the file at startup. For dynaFiles, this is ok,
+ * all others are doomed.
+ */
+ if(f->f_un.f_file.bDynamicName || (f->f_file != -1))
+ writeFile(f);
+ return 0;
+}
+/*
+ * vi:set ai:
+ */