diff --git a/config b/config
index 9bcdb41..236e8e5 100644
--- a/config
+++ b/config
@@ -32,5 +32,8 @@
source "scripts/paths"
# Project-specific variables below.
+[ -z "${LIBISO8601_CFLAGS}" ] && LIBISO8601_CFLAGS="$(libiso8601-config --cflags)"
+[ -z "${LIBISO8601_LIBS}" ] && LIBISO8601_LIBS="$(libiso8601-config --libs)"
+
[ -z "${CC}" ] && CC="gcc"
[ -z "${CFLAGS}" ] && CFLAGS="-g -O2 -W -Wall"
diff --git a/src/docs/MainPage.dox b/src/docs/MainPage.dox
index 9f68fcd..a3cf0c7 100644
--- a/src/docs/MainPage.dox
+++ b/src/docs/MainPage.dox
@@ -5,7 +5,25 @@
* http://www.gnu.org/copyleft/gpl.html for details.
*/
-/*! \mainpage
+/*! \mainpage Syslog message parsing library
+
+This library contains a set of routines helpful in parsing syslog messages and
+is intended for any consumer of syslog messages (e.g. a syslog relay or a log
+analyser, etc.).
+
+\section standards Standards
+
+The parser is designed around RFC
+3164 messages, which it will parse perfectly. However, very few actual
+syslog implementations seem to match this exactly, so the parsing routine is
+more flexible; it can cope with various different date/time formats etc. In
+addition, it will also recover a process ID in square brackets after the
+application tag if one is present.
+
+The parser is not (yet) RFC
+5424 compliant, although it will be able to read the priority, timestamp
+and tag and PROCID fields correctly. RFC 5424 parsing is a future goal of the
+library; it has not been implemented due to time constraints.
*/
diff --git a/src/libsyslogp/000_TopHeader.h b/src/libsyslogp/000_TopHeader.h
index a8ec3f2..7de79c5 100644
--- a/src/libsyslogp/000_TopHeader.h
+++ b/src/libsyslogp/000_TopHeader.h
@@ -9,6 +9,7 @@
#define HEADER_libsyslogp
/* standard includes, or includes needed for type declarations */
+#include
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
diff --git a/src/libsyslogp/000_TopSource.c b/src/libsyslogp/000_TopSource.c
index 149cf3b..5242e3a 100644
--- a/src/libsyslogp/000_TopSource.c
+++ b/src/libsyslogp/000_TopSource.c
@@ -8,6 +8,10 @@
#include "syslogp.h"
/* Below are all the includes used throughout the library. */
+#include
+#include
+#include
+#include
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
diff --git a/src/libsyslogp/500_syslogp.c b/src/libsyslogp/500_syslogp.c
new file mode 100644
index 0000000..aed6174
--- /dev/null
+++ b/src/libsyslogp/500_syslogp.c
@@ -0,0 +1,465 @@
+/* libsyslogp/src/libsyslogp/500_syslogp.c
+ *
+ * (c)2009, Laurence Withers, .
+ * Released under the GNU GPLv3. See file COPYING or
+ * http://www.gnu.org/copyleft/gpl.html for details.
+*/
+
+
+
+static char*
+syslog_parse_pri(struct syslog_msg* msg, char* str)
+{
+ char* rd;
+ unsigned int pri = 0;
+
+ rd = str;
+ if(*rd++ != '<') goto error_out;
+
+ if(*rd == '0') {
+ if(rd[1] != '>') goto error_out;
+ } else {
+ do {
+ if(*rd < '0' || *rd > '9') goto error_out;
+ pri *= 10;
+ pri += *rd - '0';
+ ++rd;
+ }while(*rd != '>');
+ if(rd - str > 4) goto error_out;
+ }
+
+ switch(pri & 7) {
+ case 0: msg->level = LOG_EMERG; break;
+ case 1: msg->level = LOG_ALERT; break;
+ case 2: msg->level = LOG_CRIT; break;
+ case 3: msg->level = LOG_ERR; break;
+ case 4: msg->level = LOG_WARNING; break;
+ case 5: msg->level = LOG_NOTICE; break;
+ case 6: msg->level = LOG_INFO; break;
+ case 7: msg->level = LOG_DEBUG; break;
+ }
+
+ switch(pri >> 3) {
+ case 0: msg->facility = LOG_KERN; break;
+ case 1: msg->facility = LOG_USER; break;
+ case 2: msg->facility = LOG_MAIL; break;
+ case 3: msg->facility = LOG_DAEMON; break;
+ case 4: msg->facility = LOG_AUTHPRIV; break;
+ case 5: msg->facility = LOG_SYSLOG; break;
+ case 6: msg->facility = LOG_LPR; break;
+ case 7: msg->facility = LOG_NEWS; break;
+ case 8: msg->facility = LOG_UUCP; break;
+ case 9: msg->facility = LOG_CRON; break;
+ case 10: msg->facility = LOG_AUTHPRIV; break;
+ case 11: msg->facility = LOG_FTP; break;
+ case 12: msg->facility = LOG_CRON; break;
+ case 13: msg->facility = LOG_SYSLOG; break;
+ case 14: msg->facility = LOG_SYSLOG; break;
+ case 15: msg->facility = LOG_CRON; break;
+ case 16: msg->facility = LOG_LOCAL0; break;
+ case 17: msg->facility = LOG_LOCAL1; break;
+ case 18: msg->facility = LOG_LOCAL2; break;
+ case 19: msg->facility = LOG_LOCAL3; break;
+ case 20: msg->facility = LOG_LOCAL4; break;
+ case 21: msg->facility = LOG_LOCAL5; break;
+ case 22: msg->facility = LOG_LOCAL6; break;
+ case 23: msg->facility = LOG_LOCAL7; break;
+ default: goto error_out;
+ }
+
+ return ++rd;
+
+ error_out:
+ msg->level = LOG_NOTICE;
+ msg->facility = LOG_USER;
+ return str;
+}
+
+
+
+static int
+syslog_parse_month(const char* str, int* month)
+{
+ if(!strncmp(str, "Jan", 3)) *month = 1;
+ else if(!strncmp(str, "Feb", 3)) *month = 2;
+ else if(!strncmp(str, "Mar", 3)) *month = 3;
+ else if(!strncmp(str, "Apr", 3)) *month = 4;
+ else if(!strncmp(str, "May", 3)) *month = 5;
+ else if(!strncmp(str, "Jun", 3)) *month = 6;
+ else if(!strncmp(str, "Jul", 3)) *month = 7;
+ else if(!strncmp(str, "Aug", 3)) *month = 8;
+ else if(!strncmp(str, "Sep", 3)) *month = 9;
+ else if(!strncmp(str, "Oct", 3)) *month = 10;
+ else if(!strncmp(str, "Nov", 3)) *month = 11;
+ else if(!strncmp(str, "Dec", 3)) *month = 12;
+ else return -1;
+
+ return 0;
+}
+
+
+
+static int
+syslog_parse_implied_year = 0;
+
+
+
+void
+syslog_parse_set_implied_year(int year)
+{
+ syslog_parse_implied_year = year;
+}
+
+
+
+static char*
+syslog_parse_date(struct syslog_msg* msg, char* str)
+{
+ int seen_t, len, year, month, day, hour, min, sec;
+ char* p;
+ time_t t;
+ struct tm* tm;
+
+ len = strlen(str);
+
+ /* skip leading whitespace */
+ for(; *str == ' '; ++str) { }
+
+ /* check for an ISO8601 date and time (don't just try parsing the string as
+ * something starting e.g. "2009 " would parse OK, but be wrong) */
+ seen_t = 0;
+ for(p = str; *p != ' '; ++p) {
+ switch(*p) {
+ case 'T':
+ if(seen_t) goto not_iso;
+ seen_t = 1;
+ break;
+
+ case '0' ... '9':
+ case '-':
+ case ':':
+ case '.':
+ case 'Z':
+ case '+':
+ break;
+
+ default:
+ goto not_iso;
+ }
+ }
+ if(!seen_t) goto not_iso;
+
+ *p = 0;
+ if(iso8601_parse(str, &msg->timestamp, 0, 0)) {
+ *p = ' ';
+ goto not_iso;
+ }
+ return p + 1;
+
+ /* fall back to older BSD-style or similar timestamps */
+ not_iso:
+ if(len >= 21 && str[3] == ' ' && str[6] == ' ' && str[11] == ' ' &&
+ str[14] == ':' && str[17] == ':')
+ {
+ /* PIX timestamp, expected format: MMM DD YYYY HH:MM:SS: */
+
+ if(syslog_parse_month(str, &month)) goto not_pix;
+
+ day = strtol(str + 4, &p, 10);
+ if(*p != ' ') goto not_pix;
+
+ year = strtol(str + 7, &p, 10);
+ if(*p != ' ') goto not_pix;
+
+ hour = strtol(str + 12, &p, 10);
+ if(*p != ':') goto not_pix;
+
+ min = strtol(str + 15, &p, 10);
+ if(*p != ':') goto not_pix;
+
+ sec = strtol(str + 18, &p, 10);
+ if(*p != ' ' && *p != ':') goto not_pix;
+
+ if(iso8601_from_cal(&msg->timestamp, year, month, day) ||
+ iso8601_from_clocktime(&msg->timestamp, hour, min, sec))
+ {
+ goto not_pix;
+ }
+
+ return str + ((str[20] == ' ') ? 21 : 20);
+ }
+
+ not_pix:
+ if(len >= 21 && str[3] == ' ' && str[6] == ' ' && str[9] == ':' &&
+ str[12] == ':' && str[15] == ' ' && str[20] == ' ')
+ {
+ /* Linksys timestamp, expected format: MMM DD HH:MM:SS YYYY */
+
+ if(syslog_parse_month(str, &month)) goto not_linksys;
+
+ day = strtol(str + 4, &p, 10);
+ if(*p != ' ') goto not_linksys;
+
+ hour = strtol(str + 7, &p, 10);
+ if(*p != ':') goto not_linksys;
+
+ min = strtol(str + 10, &p, 10);
+ if(*p != ':') goto not_linksys;
+
+ sec = strtol(str + 13, &p, 10);
+ if(*p != ' ') goto not_linksys;
+
+ year = strtol(str + 16, &p, 10);
+ if(*p != ' ' || year < 1970 || year > 2100) goto not_linksys;
+
+ if(iso8601_from_cal(&msg->timestamp, year, month, day) ||
+ iso8601_from_clocktime(&msg->timestamp, hour, min, sec))
+ {
+ goto not_linksys;
+ }
+
+ return str + 21;
+ }
+
+ not_linksys:
+ if(len >= 15 && str[3] == ' ' && str[6] == ' ' && str[9] == ':' &&
+ str[12] == ':')
+ {
+ /* RFC 3164 timestamp, expected format: MMM DD HH:MM:SS ... */
+
+ if(syslog_parse_month(str, &month)) goto not_bsd;
+
+ day = strtol(str + 4, &p, 10);
+ if(*p != ' ') goto not_bsd;
+
+ hour = strtol(str + 7, &p, 10);
+ if(*p != ':') goto not_bsd;
+
+ min = strtol(str + 10, &p, 10);
+ if(*p != ':') goto not_bsd;
+
+ sec = strtol(str + 13, &p, 10);
+ if(*p != ' ' && *p != ':' && *p != '.') goto not_bsd;
+
+ /* get year */
+ if(syslog_parse_implied_year) {
+ year = syslog_parse_implied_year;
+ } else {
+ time(&t);
+ tm = gmtime(&t);
+ year = tm->tm_year + 1900;
+ }
+
+ if(iso8601_from_cal(&msg->timestamp, year, month, day) ||
+ iso8601_from_clocktime(&msg->timestamp, hour, min, sec))
+ {
+ goto not_bsd;
+ }
+
+ /* check for fractional seconds */
+ p = str + 15;
+ if(*p != '.') return str + 16; /* skip trailing ':' or space */
+
+ sec = 0;
+ min = 0;
+ ++p;
+ do {
+ if(*p < '0' || *p > '9') return str + 15;
+ ++min;
+ sec *= 10;
+ sec += *p - '0';
+ ++p;
+ }while(*p && *p != ' ');
+
+ if(min > 9) return str + 15;
+ for(msg->timestamp.nsec = sec; min < 9; ++min) {
+ msg->timestamp.nsec *= 10;
+ }
+
+ return p;
+ }
+
+ not_bsd:
+ return str;
+}
+
+
+
+static char*
+syslog_parse_spacefield(char** ptr, char* str)
+{
+ /* skip whitespace */
+ for(; *str == ' '; ++str) { }
+ if(!*str) return str;
+
+ /* store pointer to string */
+ *ptr = str;
+
+ /* move to end of string */
+ for(; *str != ' '; ++str) {
+ if(!*str) return str;
+ }
+
+ /* null terminate and move to next field */
+ *str++ = 0;
+ return str;
+}
+
+
+
+static char*
+syslog_parse_tag_id(struct syslog_msg* msg, char* str)
+{
+ char* p;
+
+ /* skip whitespace */
+ for(; *str == ' '; ++str) { }
+ if(!*str) return str;
+
+ /* grab tag */
+ msg->tag = str;
+
+ /* search for tag delimiter */
+ for(;; ++str) {
+ switch(*str) {
+ case ':':
+ case '.':
+ case '[':
+ case '{':
+ case '<':
+ goto found_id;
+
+ case ' ':
+ case 0:
+ goto after_id;
+ }
+ }
+
+ /* try to extract an ID */
+ found_id:
+ p = 0;
+ switch(*str) {
+ case ':':
+ if(str[1] == ' ') goto after_id;
+ /* fall through */
+ case '.':
+ *str++ = 0;
+ msg->id = str;
+ for(; *str && *str != ':' && *str != ' '; ++str) { }
+ goto after_id;
+
+ case '[':
+ p = strchr(str, ']');
+ break;
+
+ case '{':
+ p = strchr(str, '}');
+ break;
+
+ case '<':
+ p = strchr(str, '>');
+ break;
+ }
+ if(!p) return str;
+
+ *str++ = 0;
+ msg->id = str;
+ *p = 0;
+ str = p + 1;
+ if(*str == ':') ++str;
+ if(*str == ' ') ++str;
+ return str;
+
+ after_id:
+ if(!*str) return str;
+ *str++ = 0;
+ if(*str == ' ') ++str;
+ return str;
+}
+
+
+
+void
+syslog_parse(struct syslog_msg* msg, char* str)
+{
+ int version = 0, len;
+
+ memset(msg, 0, sizeof(struct syslog_msg));
+ str = syslog_parse_pri(msg, str);
+
+ /* check for an RFC5424 version 1 message */
+ if(!strncmp(str, "1 ", 2)) {
+ str += 2;
+ version = 1;
+ }
+
+ str = syslog_parse_date(msg, str);
+ str = syslog_parse_spacefield(&msg->hostname, str);
+
+ if(version == 1) {
+ str = syslog_parse_spacefield(&msg->tag, str);
+ str = syslog_parse_spacefield(&msg->id, str);
+ } else {
+ str = syslog_parse_tag_id(msg, str);
+ }
+
+ /* get message part, removing any trailing \n */
+ len = strlen(str);
+ if(len && str[len - 1] == '\n') str[len - 1] = 0;
+ msg->msg = str;
+}
+
+
+
+const char*
+syslog_level_name(int level)
+{
+ switch(level) {
+ case LOG_EMERG: return "emergency";
+ case LOG_ALERT: return "alert";
+ case LOG_CRIT: return "critical";
+ case LOG_ERR: return "error";
+ case LOG_WARNING: return "warning";
+ case LOG_NOTICE: return "notice";
+ case LOG_INFO: return "info";
+ case LOG_DEBUG: return "debug";
+ }
+ return 0;
+}
+
+
+
+const char*
+syslog_facility_name(int facility)
+{
+ switch(facility) {
+ case LOG_AUTH:
+ case LOG_AUTHPRIV: return "auth";
+ case LOG_CRON: return "cron";
+ case LOG_DAEMON: return "daemon";
+ case LOG_FTP: return "ftp";
+ case LOG_KERN: return "kernel";
+ case LOG_LOCAL0: return "local0";
+ case LOG_LOCAL1: return "local1";
+ case LOG_LOCAL2: return "local2";
+ case LOG_LOCAL3: return "local3";
+ case LOG_LOCAL4: return "local4";
+ case LOG_LOCAL5: return "local5";
+ case LOG_LOCAL6: return "local6";
+ case LOG_LOCAL7: return "local7";
+ case LOG_LPR: return "printer";
+ case LOG_MAIL: return "mail";
+ case LOG_NEWS: return "news";
+ case LOG_SYSLOG: return "syslog";
+ case LOG_USER: return "user";
+ case LOG_UUCP: return "uucp";
+ }
+ return 0;
+}
+
+
+
+/* options for text editors
+kate: replace-trailing-space-save true; space-indent true; tab-width 4;
+vim: expandtab:ts=4:sw=4
+*/
diff --git a/src/libsyslogp/500_syslogp.h b/src/libsyslogp/500_syslogp.h
new file mode 100644
index 0000000..f1dbc04
--- /dev/null
+++ b/src/libsyslogp/500_syslogp.h
@@ -0,0 +1,171 @@
+/* libsyslogp/src/libsyslogp/500_syslogp.h
+ *
+ * (c)2009, Laurence Withers, .
+ * Released under the GNU GPLv3. See file COPYING or
+ * http://www.gnu.org/copyleft/gpl.html for details.
+*/
+
+
+
+/*! \defgroup syslogp syslog message parser
+
+This set of routines allows parsing of a syslog message and processing of the
+result. \ref syslog_parse() is called on the raw message data and the component
+parts of the message are then stored in a struct \ref syslog_msg.
+
+Note: BSD-syslog style messages don't generally contain a year in the timestamp
+field. This means that \ref syslog_parse() calls \c time(3) to find the current
+year whenever it parses such a message. This is expensive and, potentially,
+incaccurate. You can set the year implied in such messages with
+\ref syslog_parse_set_implied_year().
+
+*/
+/*!@{*/
+
+
+
+/*! \brief Parsed syslog message.
+
+The component parts of an RFC3164 syslog message. The MSGID and SD (structured
+data) parts of an RFC5424 syslog message will not be parsed and will be
+considered part of the \a msg text.
+
+*/
+struct syslog_msg {
+ /*! \brief Message severity level.
+
+ This is one of the \c LOG_INFO/LOG_WARNING etc. types from \c syslog(3). It
+ can be turned into a friendly name via \ref syslog_level_name(). It will be
+ set to \c LOG_NOTICE if the priority field could not be recovered from the
+ original message.
+
+ */
+ int level;
+
+ /*! \brief Message facility.
+
+ This is one of the \c LOG_DAEMON/LOG_LOCAL0 etc. types from \c syslog(3). It
+ can be turned into a friendly name via \ref syslog_facility_name(). It will
+ be set to \c LOG_USER if the priority field could not be recovered from the
+ original message.
+
+ */
+ int facility;
+
+ /*! \brief Timestamp.
+
+ The timestamp of the message. It will be all 0 if the timestamp could not
+ be recovered from the original message.
+
+ */
+ struct iso8601_date timestamp;
+
+ /*! \brief Hostname.
+
+ The hostname as specified by the producing application. If this could not be
+ recovered, it will be set to 0.
+
+ */
+ char* hostname;
+
+ /*! \brief Producing application name (tag).
+
+ The name (or tag) of the application which produced the original message. If
+ this could not be recovered, it will be set to 0.
+
+ */
+ char* tag;
+
+ /*! \brief Producing application instance ID.
+
+ Many (but not all) RFC3164 messages place an application instance ID in
+ square brackets immediately after the tag. RFC5424 has a field specifically
+ for this purpose. If discovered, this will point to the data in that field.
+ If not, it will be 0.
+
+ */
+ char* id;
+
+ /*! \brief Message text.
+
+ This points to the remaining message text once the header and tag have been
+ stripped off. It may be 0 or a pointer to an empty string if the message was
+ truly empty.
+
+ */
+ char* msg;
+};
+
+
+
+/*! \brief Parse a syslog message.
+
+\param[out] msg Structure into which parsed message data is written.
+\param str Pointer to message string. Null terminated. Will be altered!
+
+This function parses the syslog message written into \a str, which is a null
+terminated C string containing the syslog message. \a str will be altered by
+this process (e.g. some characters will be overwritten with ASCII NULs). The
+parsed message data is written into \a msg.
+
+\a msg contains several string pointers (such as \a tag, \a id and \a msg).
+After parsing, these will be set to be pointers into \a str. Each component
+will be null-terminated (which is why \a str will be altered).
+
+*/
+void syslog_parse(struct syslog_msg* msg, char* str)
+#ifndef DOXYGEN
+ __attribute__((nonnull))
+#endif
+;
+
+
+
+/*! \brief Set year implied in BSD-style timestamps.
+
+\param year Year to assume (or 0 to query the system clock).
+
+BSD-style timestamps do not contain the current year. libsyslogp will thus query
+the system clock when it is trying to parse such a timestamp. This is expensive
+and, potentially, inaccurate. This function can be used to set the implied year.
+
+*/
+void syslog_parse_set_implied_year(int year);
+
+
+
+/*! \brief Get friendly name for syslog level.
+
+\param level \c syslog(3) log level constant.
+\returns Pointer to string constant.
+\retval 0 if \a level is invalid.
+
+This function interprets a \c syslog(3) log level constant (such as \c LOG_INFO
+or \c LOG_WARNING) and returns a friendly string ("info", "warning"). If the
+value of \a level is invalid, it returns a null pointer.
+
+*/
+const char* syslog_level_name(int level);
+
+
+
+/*! \brief Get friendly name for syslog facility.
+
+\param facility \c syslog(3) log facility constant.
+\returns Pointer to string constant.
+\retval 0 if \a facility is invalid.
+
+This function interprets a \c syslog(3) log facility constant (such as
+\c LOG_DAEMON or \c LOG_USER) and returns a friendly string ("daemon", "user").
+If the value of \a facility is invalid, it returns a null pointer.
+
+*/
+const char* syslog_facility_name(int facility);
+
+
+
+/*!@}*/
+/* options for text editors
+kate: replace-trailing-space-save true; space-indent true; tab-width 4;
+vim: expandtab:ts=4:sw=4:syntax=c.doxygen
+*/
diff --git a/src/libsyslogp/build.lib b/src/libsyslogp/build.lib
index c9a7a79..21fbd7f 100644
--- a/src/libsyslogp/build.lib
+++ b/src/libsyslogp/build.lib
@@ -12,9 +12,9 @@ then
source src/libsyslogp/soversion
libsyslogp="obj/${libsyslogp_BASE}.so.${SOMAJOR}.${SOMICRO}"
- libsyslogp_DEP_CFLAGS="" # @TODO@ cflags
- libsyslogp_DEP_LIBS="" # @TODO@ libs
- SO_EXTRA="${libsyslogp_DEP_CFLAGS} ${libsyslogp_DEP_LIBS} -lc"
+ libsyslogp_DEP_CFLAGS="${LIBISO8601_CFLAGS}"
+ libsyslogp_DEP_LIBS="${LIBISO8601_LIBS}"
+ SO_EXTRA="-D_GNU_SOURCE -std=gnu99 ${libsyslogp_DEP_CFLAGS} ${libsyslogp_DEP_LIBS} -lc"
echo "Building library ${libsyslogp}..."
diff --git a/src/tests/build.tests b/src/tests/build.tests
index 246f586..0f39c20 100644
--- a/src/tests/build.tests
+++ b/src/tests/build.tests
@@ -7,7 +7,7 @@ build_target libsyslogp || return 1
if [ -z ${tests_BUILT} ]
then
LIBS="${libsyslogp} ${libsyslogp_DEP_CFLAGS} ${libsyslogp_DEP_LIBS} "
- EXTRAS="" # @TODO@ libs, cflags
+ EXTRAS="-D_GNU_SOURCE -std=gnu99"
echo "Building test programs..."
do_cmd mkdir -p obj/tests || return 1
diff --git a/src/tests/regr.c b/src/tests/regr.c
new file mode 100644
index 0000000..0233b5a
--- /dev/null
+++ b/src/tests/regr.c
@@ -0,0 +1,230 @@
+/* libsyslogp/src/tests/regr.c
+ *
+ * (c)2009, Laurence Withers, .
+ * Released under the GNU GPLv3. See file COPYING or
+ * http://www.gnu.org/copyleft/gpl.html for details.
+*/
+
+#include "syslogp.h"
+
+#include
+#include
+#include
+#include
+
+
+
+#define ISO_DAY_20081106 (733717)
+#define ISO_SEC_1700 (17 * 3600)
+
+
+
+const struct syslog_msg exp_simplecron = {
+ .level = LOG_NOTICE,
+ .facility = LOG_USER,
+ .timestamp = {
+ .day = ISO_DAY_20081106,
+ .sec = ISO_SEC_1700,
+ },
+ .hostname = "amethyst",
+ .tag = "cron",
+ .id = "31311",
+ .msg = "(root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)",
+};
+
+
+
+const struct syslog_msg exp_rfc5424cron = {
+ .level = LOG_NOTICE,
+ .facility = LOG_USER,
+ .timestamp = {
+ .day = ISO_DAY_20081106,
+ .sec = ISO_SEC_1700,
+ },
+ .hostname = "amethyst",
+ .tag = "cron",
+ .id = "31311",
+ .msg = "- (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)",
+};
+
+
+
+struct test {
+ const char* name;
+ const char* orig;
+ char* string;
+ const struct syslog_msg* exp;
+};
+
+
+
+struct test
+tests[] = {
+ {
+ .name = "bsd",
+ .orig = "<13>Nov 6 17:00:00 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)",
+ .exp = &exp_simplecron,
+ },
+
+ {
+ .name = "space_bsd",
+ .orig = "<13> Nov 6 17:00:00 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)",
+ .exp = &exp_simplecron,
+ },
+
+ {
+ .name = "bsd_newline",
+ .orig = "<13>Nov 6 17:00:00 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n",
+ .exp = &exp_simplecron,
+ },
+
+ {
+ .name = "nopri_bsd_newline",
+ .orig = "Nov 6 17:00:00 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n",
+ .exp = &exp_simplecron,
+ },
+
+ {
+ .name = "bsd_secfrac",
+ .orig = "<13>Nov 6 17:00:00.000 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n",
+ .exp = &exp_simplecron,
+ },
+
+ {
+ .name = "linksys",
+ .orig = "<13>Nov 6 17:00:00 2008 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n",
+ .exp = &exp_simplecron,
+ },
+
+ {
+ .name = "space_linksys",
+ .orig = "<13> Nov 6 17:00:00 2008 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n",
+ .exp = &exp_simplecron,
+ },
+
+ {
+ .name = "pix",
+ .orig = "<13>Nov 6 2008 17:00:00 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n",
+ .exp = &exp_simplecron,
+ },
+
+ {
+ .name = "space_pix",
+ .orig = "<13> Nov 6 2008 17:00:00 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n",
+ .exp = &exp_simplecron,
+ },
+
+ {
+ .name = "iso",
+ .orig = "<13>2008-11-06T17:00:00Z amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n",
+ .exp = &exp_simplecron,
+ },
+
+ {
+ .name = "_iso",
+ .orig = "<13> 2008-11-06T17:00:00Z amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n",
+ .exp = &exp_simplecron,
+ },
+
+ {
+ .name = "rfc5424",
+ .orig = "<13>1 2008-11-06T17:00:00Z amethyst cron 31311 - (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n",
+ .exp = &exp_rfc5424cron,
+ },
+
+ {
+ .name = 0,
+ }
+};
+
+
+
+int
+compare_msg(const struct syslog_msg* a, const struct syslog_msg* b)
+{
+ if(a->level != b->level) return -1;
+ if(a->facility != b->facility) return -1;
+ if(iso8601_cmp(&a->timestamp, &b->timestamp)) return -1;
+#define CMP_STR(str) do { \
+ if((a->str && !b->str) || (b->str && !a->str)) return -1; \
+ if(a->str && strcmp(a->str, b->str)) return -1; \
+}while(0)
+ CMP_STR(hostname);
+ CMP_STR(tag);
+ CMP_STR(id);
+ CMP_STR(msg);
+#undef CMP_STR
+ return 0;
+}
+
+
+
+struct iso8601_details
+iso_details = {
+ .date_prec = iso8601_prec_day,
+ .time_prec = iso8601_prec_secfrac,
+ .extended = 1,
+};
+
+
+
+void
+dump_msg(const char* label, const struct syslog_msg* msg)
+{
+ char isodate[40];
+
+ printf("======== %s ========\n"
+ "Level : %d (%s)\n"
+ "Facility : %d (%s)\n"
+ "Timestamp: %s\n"
+ "Hostname : '%s'\n"
+ "Tag : '%s'\n"
+ "ID : '%s'\n"
+ "Message : “%s”\n\n",
+ label,
+ msg->level, syslog_level_name(msg->level),
+ msg->facility, syslog_facility_name(msg->facility),
+ iso8601_print(isodate, sizeof(isodate), &msg->timestamp, &iso_details),
+ msg->hostname, msg->tag, msg->id, msg->msg
+ );
+}
+
+
+
+int main(int argc, char* argv[])
+{
+ int ret = 0;
+ struct test* test;
+ struct syslog_msg msg_result;
+
+ if(argc == 2 && !strcmp(argv[1], "--print-summary")) {
+ fputs("Automated regression test.\n", stdout);
+ return 0;
+ }
+
+ syslog_parse_set_implied_year(2008);
+
+ for(test = tests; test->name; ++test) {
+ /* operate on a copy of the string (constant is read only) */
+ test->string = strdup(test->orig);
+ syslog_parse(&msg_result, test->string);
+
+ /* compare the result, testing for and dumping errors */
+ ret = compare_msg(&msg_result, test->exp);
+ printf("%s: %s\n", test->name, ret ? "FAILED" : "passed");
+
+ if(ret) {
+ printf("*** Test failed while parsing:\n“%s”\n\n", test->orig);
+ dump_msg("Expected result", test->exp);
+ dump_msg("Actual result", &msg_result);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+/* options for text editors
+kate: replace-trailing-space-save true; space-indent true; tab-width 4;
+vim: expandtab:ts=4:sw=4
+*/
diff --git a/src/tests/stdin_parser.c b/src/tests/stdin_parser.c
new file mode 100644
index 0000000..08558c8
--- /dev/null
+++ b/src/tests/stdin_parser.c
@@ -0,0 +1,74 @@
+/* libsyslogp/src/tests/stdin_parser.c
+ *
+ * (c)2009, Laurence Withers, .
+ * Released under the GNU GPLv3. See file COPYING or
+ * http://www.gnu.org/copyleft/gpl.html for details.
+*/
+
+#include "syslogp.h"
+
+#include
+#include
+#include
+
+
+
+struct iso8601_details
+iso_details = {
+ .date_prec = iso8601_prec_day,
+ .time_prec = iso8601_prec_secfrac,
+ .extended = 1,
+};
+
+
+
+void
+test_parse(char* ln)
+{
+ struct syslog_msg msg;
+ char isodate[40];
+
+ syslog_parse(&msg, ln);
+
+ printf("\n"
+ "Level : %d (%s)\n"
+ "Facility : %d (%s)\n"
+ "Timestamp: %s\n"
+ "Hostname : '%s'\n"
+ "Tag : '%s'\n"
+ "ID : '%s'\n"
+ "Message : “%s”\n",
+ msg.level, syslog_level_name(msg.level),
+ msg.facility, syslog_facility_name(msg.facility),
+ iso8601_print(isodate, sizeof(isodate), &msg.timestamp, &iso_details),
+ msg.hostname, msg.tag, msg.id, msg.msg
+ );
+}
+
+
+
+int
+main(int argc, char* argv[])
+{
+ char* ln = 0;
+ size_t lnsize = 0;
+ ssize_t rd;
+
+ if(argc == 2 && !strcmp(argv[1], "--print-summary")) {
+ fputs("Parses syslog lines presented on stdin.\n", stdout);
+ return 0;
+ }
+
+ while(!feof(stdin)) {
+ rd = getline(&ln, &lnsize, stdin);
+ if(rd == -1) return 0;
+ test_parse(ln);
+ }
+
+ return 0;
+}
+
+/* options for text editors
+kate: replace-trailing-space-save true; space-indent true; tab-width 4;
+vim: expandtab:ts=4:sw=4
+*/