summaryrefslogtreecommitdiffstats
path: root/runtime/datetime.c
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/datetime.c')
-rw-r--r--runtime/datetime.c681
1 files changed, 681 insertions, 0 deletions
diff --git a/runtime/datetime.c b/runtime/datetime.c
new file mode 100644
index 00000000..aeb5fac5
--- /dev/null
+++ b/runtime/datetime.c
@@ -0,0 +1,681 @@
+/* The datetime object. It contains date and time related functions.
+ *
+ * Module begun 2008-03-05 by Rainer Gerhards, based on some code
+ * from syslogd.c. The main intension was to move code out of syslogd.c
+ * in a useful manner. It is still undecided if all functions will continue
+ * to stay here or some will be moved into parser modules (once we have them).
+ *
+ * Copyright 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <assert.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+
+#include "rsyslog.h"
+#include "obj.h"
+#include "modules.h"
+#include "datetime.h"
+#include "sysvar.h"
+#include "srUtils.h"
+#include "stringbuf.h"
+#include "errmsg.h"
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(errmsg)
+
+
+/* ------------------------------ methods ------------------------------ */
+
+
+/**
+ * Get the current date/time in the best resolution the operating
+ * system has to offer (well, actually at most down to the milli-
+ * second level.
+ *
+ * The date and time is returned in separate fields as this is
+ * most portable and removes the need for additional structures
+ * (but I have to admit it is somewhat "bulky";)).
+ *
+ * Obviously, all caller-provided pointers must not be NULL...
+ */
+static void getCurrTime(struct syslogTime *t)
+{
+ struct timeval tp;
+ struct tm *tm;
+ struct tm tmBuf;
+ long lBias;
+# if defined(__hpux)
+ struct timezone tz;
+# endif
+
+ assert(t != NULL);
+# if defined(__hpux)
+ /* TODO: check this: under HP UX, the tz information is actually valid
+ * data. So we need to obtain and process it there.
+ */
+ gettimeofday(&tp, &tz);
+# else
+ gettimeofday(&tp, NULL);
+# endif
+ tm = localtime_r((time_t*) &(tp.tv_sec), &tmBuf);
+
+ t->year = tm->tm_year + 1900;
+ t->month = tm->tm_mon + 1;
+ t->day = tm->tm_mday;
+ t->hour = tm->tm_hour;
+ t->minute = tm->tm_min;
+ t->second = tm->tm_sec;
+ t->secfrac = tp.tv_usec;
+ t->secfracPrecision = 6;
+
+# if __sun
+ /* Solaris uses a different method of exporting the time zone.
+ * It is UTC - localtime, which is the opposite sign of mins east of GMT.
+ */
+ lBias = -(daylight ? altzone : timezone);
+# elif defined(__hpux)
+ lBias = tz.tz_dsttime ? - tz.tz_minuteswest : 0;
+# else
+ lBias = tm->tm_gmtoff;
+# endif
+ if(lBias < 0)
+ {
+ t->OffsetMode = '-';
+ lBias *= -1;
+ }
+ else
+ t->OffsetMode = '+';
+ t->OffsetHour = lBias / 3600;
+ t->OffsetMinute = lBias % 3600;
+}
+
+
+
+
+/*******************************************************************
+ * BEGIN CODE-LIBLOGGING *
+ *******************************************************************
+ * Code in this section is borrowed from liblogging. This is an
+ * interim solution. Once liblogging is fully integrated, this is
+ * to be removed (see http://www.monitorware.com/liblogging for
+ * more details. 2004-11-16 rgerhards
+ *
+ * Please note that the orginal liblogging code is modified so that
+ * it fits into the context of the current version of syslogd.c.
+ *
+ * DO NOT PUT ANY OTHER CODE IN THIS BEGIN ... END BLOCK!!!!
+ */
+
+/**
+ * Parse a 32 bit integer number from a string.
+ *
+ * \param ppsz Pointer to the Pointer to the string being parsed. It
+ * must be positioned at the first digit. Will be updated
+ * so that on return it points to the first character AFTER
+ * the integer parsed.
+ * \retval The number parsed.
+ */
+
+static int srSLMGParseInt32(char** ppsz)
+{
+ int i;
+
+ i = 0;
+ while(isdigit((int) **ppsz))
+ {
+ i = i * 10 + **ppsz - '0';
+ ++(*ppsz);
+ }
+
+ return i;
+}
+
+
+/**
+ * Parse a TIMESTAMP-3339.
+ * updates the parse pointer position.
+ */
+static int
+ParseTIMESTAMP3339(struct syslogTime *pTime, char** ppszTS)
+{
+ char *pszTS = *ppszTS;
+
+ assert(pTime != NULL);
+ assert(ppszTS != NULL);
+ assert(pszTS != NULL);
+
+ pTime->year = srSLMGParseInt32(&pszTS);
+
+ /* We take the liberty to accept slightly malformed timestamps e.g. in
+ * the format of 2003-9-1T1:0:0. This doesn't hurt on receiving. Of course,
+ * with the current state of affairs, we would never run into this code
+ * here because at postion 11, there is no "T" in such cases ;)
+ */
+ if(*pszTS++ != '-')
+ return FALSE;
+ pTime->month = srSLMGParseInt32(&pszTS);
+ if(pTime->month < 1 || pTime->month > 12)
+ return FALSE;
+
+ if(*pszTS++ != '-')
+ return FALSE;
+ pTime->day = srSLMGParseInt32(&pszTS);
+ if(pTime->day < 1 || pTime->day > 31)
+ return FALSE;
+
+ if(*pszTS++ != 'T')
+ return FALSE;
+
+ pTime->hour = srSLMGParseInt32(&pszTS);
+ if(pTime->hour < 0 || pTime->hour > 23)
+ return FALSE;
+
+ if(*pszTS++ != ':')
+ return FALSE;
+ pTime->minute = srSLMGParseInt32(&pszTS);
+ if(pTime->minute < 0 || pTime->minute > 59)
+ return FALSE;
+
+ if(*pszTS++ != ':')
+ return FALSE;
+ pTime->second = srSLMGParseInt32(&pszTS);
+ if(pTime->second < 0 || pTime->second > 60)
+ return FALSE;
+
+ /* Now let's see if we have secfrac */
+ if(*pszTS == '.')
+ {
+ char *pszStart = ++pszTS;
+ pTime->secfrac = srSLMGParseInt32(&pszTS);
+ pTime->secfracPrecision = (int) (pszTS - pszStart);
+ }
+ else
+ {
+ pTime->secfracPrecision = 0;
+ pTime->secfrac = 0;
+ }
+
+ /* check the timezone */
+ if(*pszTS == 'Z')
+ {
+ pszTS++; /* eat Z */
+ pTime->OffsetMode = 'Z';
+ pTime->OffsetHour = 0;
+ pTime->OffsetMinute = 0;
+ }
+ else if((*pszTS == '+') || (*pszTS == '-'))
+ {
+ pTime->OffsetMode = *pszTS;
+ pszTS++;
+
+ pTime->OffsetHour = srSLMGParseInt32(&pszTS);
+ if(pTime->OffsetHour < 0 || pTime->OffsetHour > 23)
+ return FALSE;
+
+ if(*pszTS++ != ':')
+ return FALSE;
+ pTime->OffsetMinute = srSLMGParseInt32(&pszTS);
+ if(pTime->OffsetMinute < 0 || pTime->OffsetMinute > 59)
+ return FALSE;
+ }
+ else
+ /* there MUST be TZ information */
+ return FALSE;
+
+ /* OK, we actually have a 3339 timestamp, so let's indicated this */
+ if(*pszTS == ' ')
+ ++pszTS;
+ else
+ return FALSE;
+
+ /* update parse pointer */
+ *ppszTS = pszTS;
+
+ return TRUE;
+}
+
+
+/**
+ * Parse a TIMESTAMP-3164.
+ * Returns TRUE on parse OK, FALSE on parse error.
+ */
+static int
+ParseTIMESTAMP3164(struct syslogTime *pTime, char** ppszTS)
+{
+ char *pszTS;
+
+ assert(ppszTS != NULL);
+ pszTS = *ppszTS;
+ assert(pszTS != NULL);
+ assert(pTime != NULL);
+
+ getCurrTime(pTime); /* obtain the current year and UTC offsets! */
+
+ /* If we look at the month (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec),
+ * we may see the following character sequences occur:
+ *
+ * J(an/u(n/l)), Feb, Ma(r/y), A(pr/ug), Sep, Oct, Nov, Dec
+ *
+ * We will use this for parsing, as it probably is the
+ * fastest way to parse it.
+ *
+ * 2005-07-18, well sometimes it pays to be a bit more verbose, even in C...
+ * Fixed a bug that lead to invalid detection of the data. The issue was that
+ * we had an if(++pszTS == 'x') inside of some of the consturcts below. However,
+ * there were also some elseifs (doing the same ++), which than obviously did not
+ * check the orginal character but the next one. Now removed the ++ and put it
+ * into the statements below. Was a really nasty bug... I didn't detect it before
+ * june, when it first manifested. This also lead to invalid parsing of the rest
+ * of the message, as the time stamp was not detected to be correct. - rgerhards
+ */
+ switch(*pszTS++)
+ {
+ case 'J':
+ if(*pszTS == 'a') {
+ ++pszTS;
+ if(*pszTS == 'n') {
+ ++pszTS;
+ pTime->month = 1;
+ } else
+ return FALSE;
+ } else if(*pszTS == 'u') {
+ ++pszTS;
+ if(*pszTS == 'n') {
+ ++pszTS;
+ pTime->month = 6;
+ } else if(*pszTS == 'l') {
+ ++pszTS;
+ pTime->month = 7;
+ } else
+ return FALSE;
+ } else
+ return FALSE;
+ break;
+ case 'F':
+ if(*pszTS == 'e') {
+ ++pszTS;
+ if(*pszTS == 'b') {
+ ++pszTS;
+ pTime->month = 2;
+ } else
+ return FALSE;
+ } else
+ return FALSE;
+ break;
+ case 'M':
+ if(*pszTS == 'a') {
+ ++pszTS;
+ if(*pszTS == 'r') {
+ ++pszTS;
+ pTime->month = 3;
+ } else if(*pszTS == 'y') {
+ ++pszTS;
+ pTime->month = 5;
+ } else
+ return FALSE;
+ } else
+ return FALSE;
+ break;
+ case 'A':
+ if(*pszTS == 'p') {
+ ++pszTS;
+ if(*pszTS == 'r') {
+ ++pszTS;
+ pTime->month = 4;
+ } else
+ return FALSE;
+ } else if(*pszTS == 'u') {
+ ++pszTS;
+ if(*pszTS == 'g') {
+ ++pszTS;
+ pTime->month = 8;
+ } else
+ return FALSE;
+ } else
+ return FALSE;
+ break;
+ case 'S':
+ if(*pszTS == 'e') {
+ ++pszTS;
+ if(*pszTS == 'p') {
+ ++pszTS;
+ pTime->month = 9;
+ } else
+ return FALSE;
+ } else
+ return FALSE;
+ break;
+ case 'O':
+ if(*pszTS == 'c') {
+ ++pszTS;
+ if(*pszTS == 't') {
+ ++pszTS;
+ pTime->month = 10;
+ } else
+ return FALSE;
+ } else
+ return FALSE;
+ break;
+ case 'N':
+ if(*pszTS == 'o') {
+ ++pszTS;
+ if(*pszTS == 'v') {
+ ++pszTS;
+ pTime->month = 11;
+ } else
+ return FALSE;
+ } else
+ return FALSE;
+ break;
+ case 'D':
+ if(*pszTS == 'e') {
+ ++pszTS;
+ if(*pszTS == 'c') {
+ ++pszTS;
+ pTime->month = 12;
+ } else
+ return FALSE;
+ } else
+ return FALSE;
+ break;
+ default:
+ return FALSE;
+ }
+
+ /* done month */
+
+ if(*pszTS++ != ' ')
+ return FALSE;
+
+ /* we accept a slightly malformed timestamp when receiving. This is
+ * we accept one-digit days
+ */
+ if(*pszTS == ' ')
+ ++pszTS;
+
+ pTime->day = srSLMGParseInt32(&pszTS);
+ if(pTime->day < 1 || pTime->day > 31)
+ return FALSE;
+
+ if(*pszTS++ != ' ')
+ return FALSE;
+ pTime->hour = srSLMGParseInt32(&pszTS);
+ if(pTime->hour < 0 || pTime->hour > 23)
+ return FALSE;
+
+ if(*pszTS++ != ':')
+ return FALSE;
+ pTime->minute = srSLMGParseInt32(&pszTS);
+ if(pTime->minute < 0 || pTime->minute > 59)
+ return FALSE;
+
+ if(*pszTS++ != ':')
+ return FALSE;
+ pTime->second = srSLMGParseInt32(&pszTS);
+ if(pTime->second < 0 || pTime->second > 60)
+ return FALSE;
+
+ /* we provide support for an exter ":" after the date. While this is an
+ * invalid format, it occurs frequently enough (e.g. with Cisco devices)
+ * to permit it as a valid case. -- rgerhards, 2008-09-12
+ */
+ if(*pszTS++ == ':')
+ ++pszTS;
+
+ /* OK, we actually have a 3164 timestamp, so let's indicate this
+ * and fill the rest of the properties. */
+ pTime->timeType = 1;
+ pTime->secfracPrecision = 0;
+ pTime->secfrac = 0;
+ *ppszTS = pszTS; /* provide updated parse position back to caller */
+ return TRUE;
+}
+
+/*******************************************************************
+ * END CODE-LIBLOGGING *
+ *******************************************************************/
+
+/**
+ * Format a syslogTimestamp into format required by MySQL.
+ * We are using the 14 digits format. For example 20041111122600
+ * is interpreted as '2004-11-11 12:26:00'.
+ * The caller must provide the timestamp as well as a character
+ * buffer that will receive the resulting string. The function
+ * returns the size of the timestamp written in bytes (without
+ * the string terminator). If 0 is returend, an error occured.
+ */
+int formatTimestampToMySQL(struct syslogTime *ts, char* pDst, size_t iLenDst)
+{
+ /* currently we do not consider localtime/utc. This may later be
+ * added. If so, I recommend using a property replacer option
+ * and/or a global configuration option. However, we should wait
+ * on user requests for this feature before doing anything.
+ * rgerhards, 2007-06-26
+ */
+ assert(ts != NULL);
+ assert(pDst != NULL);
+
+ if (iLenDst < 15) /* we need at least 14 bytes
+ 14 digits for timestamp + '\n' */
+ return(0);
+
+ return(snprintf(pDst, iLenDst, "%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d",
+ ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second));
+
+}
+
+int formatTimestampToPgSQL(struct syslogTime *ts, char *pDst, size_t iLenDst)
+{
+ /* see note in formatTimestampToMySQL, applies here as well */
+ assert(ts != NULL);
+ assert(pDst != NULL);
+
+ if (iLenDst < 21) /* we need 20 bytes + '\n' */
+ return(0);
+
+ return(snprintf(pDst, iLenDst, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d",
+ ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second));
+}
+
+
+/**
+ * Format a syslogTimestamp to just the fractional seconds.
+ * The caller must provide the timestamp as well as a character
+ * buffer that will receive the resulting string. The function
+ * returns the size of the timestamp written in bytes (without
+ * the string terminator). If 0 is returend, an error occured.
+ * The buffer must be at least 10 bytes large.
+ * rgerhards, 2008-06-06
+ */
+int formatTimestampSecFrac(struct syslogTime *ts, char* pBuf, size_t iLenBuf)
+{
+ int lenRet;
+ char szFmtStr[64];
+
+ assert(ts != NULL);
+ assert(pBuf != NULL);
+ assert(iLenBuf >= 10);
+
+ if(ts->secfracPrecision > 0)
+ { /* We must look at
+ * the precision specified. For example, if we have millisec precision (3 digits), a
+ * secFrac value of 12 is not equivalent to ".12" but ".012". Obviously, this
+ * is a huge difference ;). To avoid this, we first create a format string with
+ * the specific precision and *then* use that format string to do the actual formating.
+ */
+ /* be careful: there is ONE actual %d in the format string below ;) */
+ snprintf(szFmtStr, sizeof(szFmtStr), "%%0%dd", ts->secfracPrecision);
+ lenRet = snprintf(pBuf, iLenBuf, szFmtStr, ts->secfrac);
+ } else {
+ pBuf[0] = '0';
+ pBuf[1] = '\0';
+ lenRet = 1;
+ }
+
+ return(lenRet);
+}
+
+
+/**
+ * Format a syslogTimestamp to a RFC3339 timestamp string (as
+ * specified in syslog-protocol).
+ * The caller must provide the timestamp as well as a character
+ * buffer that will receive the resulting string. The function
+ * returns the size of the timestamp written in bytes (without
+ * the string terminator). If 0 is returend, an error occured.
+ */
+int formatTimestamp3339(struct syslogTime *ts, char* pBuf, size_t iLenBuf)
+{
+ int iRet;
+ char szTZ[7]; /* buffer for TZ information */
+
+ assert(ts != NULL);
+ assert(pBuf != NULL);
+
+ if(iLenBuf < 20)
+ return(0); /* we NEED at least 20 bytes */
+
+ /* do TZ information first, this is easier to take care of "Z" zone in rfc3339 */
+ if(ts->OffsetMode == 'Z') {
+ szTZ[0] = 'Z';
+ szTZ[1] = '\0';
+ } else {
+ snprintf(szTZ, sizeof(szTZ) / sizeof(char), "%c%2.2d:%2.2d",
+ ts->OffsetMode, ts->OffsetHour, ts->OffsetMinute);
+ }
+
+ if(ts->secfracPrecision > 0)
+ { /* we now need to include fractional seconds. While doing so, we must look at
+ * the precision specified. For example, if we have millisec precision (3 digits), a
+ * secFrac value of 12 is not equivalent to ".12" but ".012". Obviously, this
+ * is a huge difference ;). To avoid this, we first create a format string with
+ * the specific precision and *then* use that format string to do the actual
+ * formating (mmmmhhh... kind of self-modifying code... ;)).
+ */
+ char szFmtStr[64];
+ /* be careful: there is ONE actual %d in the format string below ;) */
+ snprintf(szFmtStr, sizeof(szFmtStr),
+ "%%04d-%%02d-%%02dT%%02d:%%02d:%%02d.%%0%dd%%s",
+ ts->secfracPrecision);
+ iRet = snprintf(pBuf, iLenBuf, szFmtStr, ts->year, ts->month, ts->day,
+ ts->hour, ts->minute, ts->second, ts->secfrac, szTZ);
+ }
+ else
+ iRet = snprintf(pBuf, iLenBuf,
+ "%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d%s",
+ ts->year, ts->month, ts->day,
+ ts->hour, ts->minute, ts->second, szTZ);
+ return(iRet);
+}
+
+/**
+ * Format a syslogTimestamp to a RFC3164 timestamp sring.
+ * The caller must provide the timestamp as well as a character
+ * buffer that will receive the resulting string. The function
+ * returns the size of the timestamp written in bytes (without
+ * the string termnator). If 0 is returend, an error occured.
+ */
+int formatTimestamp3164(struct syslogTime *ts, char* pBuf, size_t iLenBuf)
+{
+ static char* monthNames[13] = {"ERR", "Jan", "Feb", "Mar",
+ "Apr", "May", "Jun", "Jul",
+ "Aug", "Sep", "Oct", "Nov", "Dec"};
+ assert(ts != NULL);
+ assert(pBuf != NULL);
+
+ if(iLenBuf < 16)
+ return(0); /* we NEED 16 bytes */
+ return(snprintf(pBuf, iLenBuf, "%s %2d %2.2d:%2.2d:%2.2d",
+ monthNames[ts->month], ts->day, ts->hour,
+ ts->minute, ts->second
+ ));
+}
+
+/**
+ * Format a syslogTimestamp to a text format.
+ * The caller must provide the timestamp as well as a character
+ * buffer that will receive the resulting string. The function
+ * returns the size of the timestamp written in bytes (without
+ * the string termnator). If 0 is returend, an error occured.
+ */
+#if 0 /* This method is currently not called, be we like to preserve it */
+static int formatTimestamp(struct syslogTime *ts, char* pBuf, size_t iLenBuf)
+{
+ assert(ts != NULL);
+ assert(pBuf != NULL);
+
+ if(ts->timeType == 1) {
+ return(formatTimestamp3164(ts, pBuf, iLenBuf));
+ }
+
+ if(ts->timeType == 2) {
+ return(formatTimestamp3339(ts, pBuf, iLenBuf));
+ }
+
+ return(0);
+}
+#endif
+/* queryInterface function
+ * rgerhards, 2008-03-05
+ */
+BEGINobjQueryInterface(datetime)
+CODESTARTobjQueryInterface(datetime)
+ if(pIf->ifVersion != datetimeCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->getCurrTime = getCurrTime;
+ pIf->ParseTIMESTAMP3339 = ParseTIMESTAMP3339;
+ pIf->ParseTIMESTAMP3164 = ParseTIMESTAMP3164;
+ pIf->formatTimestampToMySQL = formatTimestampToMySQL;
+ pIf->formatTimestampToPgSQL = formatTimestampToPgSQL;
+ pIf->formatTimestampSecFrac = formatTimestampSecFrac;
+ pIf->formatTimestamp3339 = formatTimestamp3339;
+ pIf->formatTimestamp3164 = formatTimestamp3164;
+finalize_it:
+ENDobjQueryInterface(datetime)
+
+
+/* Initialize the datetime class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINAbstractObjClassInit(datetime, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+
+ENDObjClassInit(datetime)
+
+/* vi:set ai:
+ */