From 9e569139583178c7e1c35147cef3fd17493a11da Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Tue, 23 Oct 2007 13:02:08 +0000 Subject: Reimplement syslog-async. Thanks Simon Kelley for relicensing it under GPLv3. git-svn-id: file:///svnroot/mailfromd/trunk@1520 7a8a7f39-df28-0410-adc6-e0d955640f24 --- ChangeLog | 7 + NEWS | 24 +++- configure.ac | 25 +++- doc/mailfromd.texi | 83 +++++++++-- src/Makefile.am | 4 +- src/main.c | 69 +++++++-- src/syslog_async.c | 413 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/syslog_async.h | 126 ++++++++++++++++ 8 files changed, 719 insertions(+), 32 deletions(-) create mode 100644 src/syslog_async.c create mode 100644 src/syslog_async.h diff --git a/ChangeLog b/ChangeLog index 053fa59c..c47d8ed7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2007-10-23 Sergey Poznyakoff + + * configure.ac, src/syslog_async.c, src/syslog_async.h, + src/main.c, src/Makefile.am: Reimplement syslog-async. Thanks + Simon Kelley for relicensing it under GPLv3. + * doc/mailfromd.texi, NEWS: Update + 2007-10-21 Sergey Poznyakoff * src/dnsbase.c (cname_loop_body): Return TXT only if explicitly diff --git a/NEWS b/NEWS index fba2e9ff..578b9670 100644 --- a/NEWS +++ b/NEWS @@ -1,17 +1,18 @@ -Mailfromd NEWS -- history of user-visible changes. 2007-09-11 +Mailfromd NEWS -- history of user-visible changes. 2007-10-23 Copyright (C) 2005, 2006, 2007 Sergey Poznyakoff See the end of file for copying conditions. Please send mailfromd bug reports to -Version 4.1.1 (SVN) +Version 4.2 (SVN) * Licensed under the GPLv3 -* Syslog-async code has been withdrawn, due to its incompatibility with -the new license. The similar functionality will probably be introduced -again when I write it. +* New command line options --syslog-async and --no-syslog-async + +These options allow to select the implementation of syslog to use +at run time. * RFC 2821 compatibility @@ -24,7 +25,7 @@ release. * Sample threshold for `rate' function. The `rate' function takes an optional third argument allowing to -specify the minimum number of received mails needed to obtain +specify the minimum number of received mails needed to obtain the sending rate value. The default is 2, which is probably too conservative. The following example raises it to 10: @@ -32,6 +33,17 @@ conservative. The following example raises it to 10: ... fi +* mtasim is improved + +In particular, it accepts MAIL FROM: and RCPT TO: without extra space +after the colon, as required by RFC. The milter interface is also +improved. + +* The `resolve' function ignores TXT records. + +In other words, resolve and primitive_resolve are guaranteed to return +either an A or a PTR record. + Version 4.1, 2007-06-11 diff --git a/configure.ac b/configure.ac index 885a804c..e49a654a 100644 --- a/configure.ac +++ b/configure.ac @@ -16,8 +16,8 @@ AC_PREREQ(2.59) m4_define([MF_VERSION_MAJOR], 4) -m4_define([MF_VERSION_MINOR], 1) -m4_define([MF_VERSION_PATCH], 1) +m4_define([MF_VERSION_MINOR], 2) +dnl m4_define([MF_VERSION_PATCH], 0) AC_INIT([mailfromd], MF_VERSION_MAJOR.MF_VERSION_MINOR[]m4_ifdef([MF_VERSION_PATCH],.MF_VERSION_PATCH), [bug-mailfromd@gnu.org.ua]) @@ -124,9 +124,22 @@ AC_SUBST(lisp_LISP) AM_GNU_GETTEXT([external], [need-formatstring-macros]) AM_GNU_GETTEXT_VERSION([0.16]) -# Syslog -- removed due to version incompatibility -if test "${enable_syslog_async+set}" = set; then - AC_MSG_WARN([The option --enable-syslog-async is no longer supported.]) +# Syslog +AC_ARG_ENABLE([syslog-async], + AC_HELP_STRING([--enable-syslog-async], + [use non-blocking version of syslog by default]), + [case "${enableval}" in + yes) syslog_async=yes ;; + no) syslog_async=no ;; + *) AC_MSG_ERROR([bad value ${enableval} for --enable-syslog-async]) ;; + esac],[syslog_async=no]) + +AH_TEMPLATE([DEFAULT_SYSLOG_ASYNC], + [Define to 1 if syslog-async is being used by default]) +if test $syslog_async = "yes"; then + AC_DEFINE([DEFAULT_SYSLOG_ASYNC], [1]) +else + AC_DEFINE([DEFAULT_SYSLOG_ASYNC], [0]) fi # Check for DBM flavor @@ -560,7 +573,7 @@ Socket.................................... $socket Expiration interval....................... $expire Negative DNS answer expiration interval... $negative_dns_expire Rates expire interval..................... $rates_expire -Syslog implementation..................... $syslog_flavor +Default syslog implementation............. $syslog_flavor Readline (for mtasim)..................... $usereadline Documentation rendition type.............. $rendition ******************************************************************* diff --git a/doc/mailfromd.texi b/doc/mailfromd.texi index 078f722a..6aa44c6b 100644 --- a/doc/mailfromd.texi +++ b/doc/mailfromd.texi @@ -131,6 +131,7 @@ Sender Address Verification. Building the Package +* 410-420:: Upgrading from 4.1 to 4.2 * 400-410:: Upgrading from 4.0 to 4.1 * 31x-400:: Upgrading from 3.1.x to 4.0 * 30x-31x:: Upgrading from 3.0.x to 3.1 @@ -885,7 +886,7 @@ Expiration settings can be changed at run time using @cindex enable-syslog-async, @option{--enable-syslog-async}, @command{configure} option @cindex syslog, non-blocking -@item Select @command{syslog} implementation to use. +@item Select a @command{syslog} implementation to use. @anchor{syslog-async} @command{Mailfromd} uses @code{syslog} for diagnostics output. The @@ -896,7 +897,7 @@ result, when an application calls @code{syslog()}, and the application will hang. @cindex Simon Kelley - In the case of a daemon as @command{mailfromd}, it is more important + For @command{mailfromd}, as for any daemon, it is more important that it continue to run, than that it continue to log. For this purpose, @command{mailfromd} is shipped with a non-blocking @code{syslog} implementation by Simon Kelley. This implementation, @@ -908,8 +909,12 @@ lines are lost, this fact is logged with a message of the form: async_syslog overflow: 5 log entries lost @end smallexample - To enable non-blocking @command{syslog}, use the -@option{--enable-syslog-async} command line option: + You can select which implementation to run when starting +@command{mailfromd}, by using @option{--syslog-async} or +@option{--no-syslog-async} command line options +(@pxref{Logging and Debugging}). When configuring, you can set +the asynchronous syslog as the @emph{default} syslog implementation, +using the @option{--enable-syslog-async} command line option: @smallexample ./configure --enable-syslog-async @@ -945,7 +950,7 @@ Socket.................................... mailfrom Expiration interval....................... 86400 Negative DNS answer expiration interval... 3600 Rates expire interval..................... 300 -Syslog implementation..................... blocking +Default syslog implementation............. blocking Readline (for mtasim)..................... yes Documentation rendition type.............. PROOF ******************************************************************* @@ -969,6 +974,7 @@ the corresponding section below. @end enumerate @menu +* 410-420:: Upgrading from 4.1 to 4.2 * 400-410:: Upgrading from 4.0 to 4.1 * 31x-400:: Upgrading from 3.1.x to 4.0 * 30x-31x:: Upgrading from 3.0.x to 3.1 @@ -976,6 +982,22 @@ the corresponding section below. * 1x-2x:: Upgrading from 1.x to 2.x @end menu +@node 410-420 +@section Upgrading from 4.1 to 4.2 +@cindex Upgrading from 4.1 to 4.2 +@UNREVISED{} + Upgrading to this version does not require any special efforts. You +can use your configuration files and filter scripts without any +changes. The only difference worth noticing is that starting from this +version @command{mailfromd} is always compiled with asynchronous +syslog implementation. The @option{--enable-syslog-async} +configuration file option is still available, but its meaning has +changed: it sets the @emph{default} syslog implementation to use +(@pxref{syslog-async}). Thus, it can be used the same way it was in +previous versions. You can also select the syslog implementation at +run time, see @ref{Logging and Debugging, --syslog-async option}, for +more detailed information. + @node 400-410 @section Upgrading from 4.0 to 4.1 @cindex Upgrading from 4.0 to 4.1 @@ -1011,7 +1033,7 @@ Pragma options: @code{retry}, @code{io-retry}, and @cindex upgrading from 3.1.x to 4.0 Before building this version, please re-read the chapter -@xref{Building}, especially the section @xref{syslog-async, Using +@xref{Building}, especially the section @ref{syslog-async, Using non-blocking syslog}. Starting from the version 4.0, @acronym{MFL} no longer uses the @@ -2156,15 +2178,15 @@ actual @var{email} owner won't be blocked by actions of some spammer abusing his/her address. To control and update sending rates, the @code{rate} function is -provided. It takes two arguments: @code{key}, whose meaning is -described above, and @code{interval}, or the number of seconds, to +provided. It takes two mandatory arguments: @code{key}, whose meaning +is described above, and @code{interval}, or the number of seconds, to which the actual sending rate value is converted. Remember, that it is stored internally as a floating point number, and thus cannot be used in @command{mailfromd} filters, which operate only on integer numbers. To use the rate value, it is first converted to messages per given interval, which is an integer number. For example, the rate @code{0.138888} brought to 1-hour interval gives @code{500} -(messages per hour). +(messages per hour). Wherever the @code{rate} function is called, it recomputes and updates the rate record for the given @var{key}, and returns its @@ -2216,6 +2238,7 @@ with the given key value were detected: @end smallexample For additional information about @code{rate} function, see @ref{rate}. + @node Greylisting @section Greylisting @@ -2420,7 +2443,7 @@ user: mail statedir: /var/run/mailfromd socket: unix:/var/run/mailfromd/mailfrom pidfile: /var/run/mailfromd/mailfromd.pid -syslog: blocking +default syslog: blocking database format: Berkeley DB 2.x dns database: /var/run/mailfromd/dns.db dns negative expiration: 3600 @@ -2830,6 +2853,24 @@ these defaults, two command line options are provided: @option{--stderr} to print everything to standard error and @option{--syslog} to output all diagnostics to syslog. +@cindex syslog, default implementation +@cindex syslog, non-blocking +@cindex syslog, asynchronous +@cindex asynchronous syslog +@cindex non-blocking syslog +@xopindex{syslog-async, introduced} +@xopindex{no-syslog-async, introduced} + The data can be sent to syslog using two ways: the @code{syslog} +function from the system @file{libc} library, which is a @dfn{blocking} +implementation in most cases, and the internal, @dfn{asynchronous} +syslog implementation. @xref{syslog-async, Using non-blocking syslog}, +for more information on these implementations and for information on +how to select the default one. Two options are provided to select the +implementation to use at run time: the @option{--syslog-async} option +instructs @command{mailfromd} to use asynchronous version, and the +@option{--no-syslog-async} option instructs it to use the blocking +version. + @cindex syslog facility, selecting @cindex selecting syslog facility @cindex syslog facility, default @@ -10410,6 +10451,17 @@ Output logs to syslog @var{facility}. @item --log-tag=@var{string} Tag syslog entries with the given @var{string}, instead of the program name. +@opsummary{no-syslog-async} +@item --no-syslog-async +Use system @command{libc} syslog implementation. @xref{Logging and Debugging}, +for more information about two syslog flavors, and see @ref{syslog-async, +Using non-blocking syslog}, for information on how to set default +syslog implementation at compile time. + +To inspect the default syslog implementation, use the +@option{--show-defaults} command line option (@pxref{Databases, +show-defaults}). + @opsummary{stderr} @item -s @itemx --stderr @@ -10460,6 +10512,17 @@ Output logs to syslog. This is the default, unless any of the following options is used: @option{--compact}, @option{--delete}, @option{--expire}, @option{--test}, and @option{--list}. +@opsummary{syslog-async} +@item --syslog-async +Use asynchronous syslog implementation. @xref{Logging and Debugging}, +for more information about two syslog flavors, and see @ref{syslog-async, +Using non-blocking syslog}, for information on how to set default +syslog implementation at compile time. + +To inspect the default syslog implementation, use the +@option{--show-defaults} command line option (@pxref{Databases, +show-defaults}). + @opsummary{xref} @item --xref Same as @option{--dump-xref}. @xref{Logging and Debugging}. diff --git a/src/Makefile.am b/src/Makefile.am index fe86d2b5..ea504d53 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -63,11 +63,13 @@ mailfromd_SOURCES = \ noinst_LIBRARIES=libmf.a libmf_a_SOURCES=\ + syslog_async.c\ + syslog_async.h\ version.c libmf_a_LIBADD=$(LIBOBJS) -mailfromd_LDADD = ./libmf.a $(LDADD) $(SYSLOG_LIBS) +mailfromd_LDADD = ./libmf.a $(LDADD) mtasim_SOURCES = mtasim.c openat-die.c mtasim_LDADD = ./libmf.a $(LDADD) $(READLINE_LIBS) diff --git a/src/main.c b/src/main.c index c277645f..938a82b6 100644 --- a/src/main.c +++ b/src/main.c @@ -39,6 +39,8 @@ #include "mailfromd.h" +#include "syslog_async.h" + /* Configurable options */ @@ -76,6 +78,8 @@ int foreground; /* Stay in foreground */ int single_process_option; /* Run in single process mode. */ unsigned long source_address = INADDR_ANY; /* Source address for TCP connections */ +int use_syslog_async = DEFAULT_SYSLOG_ASYNC; + /* Use asynchronous syslog implementation */ char *syslog_tag; /* Tag to mark syslog entries with. */ char *mailfromd_state_dir; /* see init_names() */ char *pidfile; /* see init_names() */ @@ -121,16 +125,44 @@ time_t response_timeout = 30; int syslog_printer (int prio, const char *fmt, va_list ap) { + if (use_syslog_async) { + vsyslog_async (prio, fmt, ap); + } else { #if HAVE_VSYSLOG - vsyslog (prio, fmt, ap); + vsyslog (prio, fmt, ap); #else - char buf[128]; - vsnprintf (buf, sizeof buf, fmt, ap); - syslog (prio, "%s", buf); + char buf[128]; + vsnprintf (buf, sizeof buf, fmt, ap); + syslog (prio, "%s", buf); #endif + } return 0; } +void +mf_gacopyz_syslog_async_log_printer(int level, char *fmt, va_list ap) +{ + switch (level) { + case SMI_LOG_DEBUG: + level = LOG_DEBUG; + break; + case SMI_LOG_INFO: + level = LOG_INFO; + break; + case SMI_LOG_WARN: + level = LOG_WARNING; + break; + case SMI_LOG_ERR: + level = LOG_ERR; + break; + + case SMI_LOG_FATAL: + default: + level = LOG_EMERG; + } + vsyslog_async(level, fmt, ap); +} + int syslog_error_printer (const char *fmt, va_list ap) { @@ -785,6 +817,7 @@ enum mailfromd_option { OPTION_MILTER_TIMEOUT, OPTION_MTASIM, OPTION_NO_PREPROCESSOR, + OPTION_NO_SYSLOG_ASYNC, OPTION_PIDFILE, OPTION_POSTMASTER_EMAIL, OPTION_PREDICT_NEXT, @@ -795,6 +828,7 @@ enum mailfromd_option { OPTION_STATE_DIRECTORY, OPTION_SOURCE_INFO, OPTION_SYSLOG, + OPTION_SYSLOG_ASYNC, OPTION_TIME_FORMAT, OPTION_TIMEOUT, OPTION_TRACE, @@ -944,6 +978,10 @@ static struct argp_option options[] = { N_("Log to stderr"), GRP+1 }, { "syslog", OPTION_SYSLOG, NULL, 0, N_("Log to syslog (default)"), GRP+1 }, + { "syslog-async", OPTION_SYSLOG_ASYNC, NULL, 0, + N_("Use asynchronous syslog"), GRP+1 }, + { "no-syslog-async", OPTION_NO_SYSLOG_ASYNC, NULL, 0, + N_("Use system syslog"), GRP+1 }, { "log-tag", OPTION_LOG_TAG, N_("STRING"), 0, N_("Set the identifier used in syslog messages to STRING"), GRP+1 }, { "source-info", OPTION_SOURCE_INFO, NULL, 0, @@ -1263,6 +1301,14 @@ parse_opt (int key, char *arg, struct argp_state *state) log_to_stderr = 0; break; + case OPTION_SYSLOG_ASYNC: + use_syslog_async = 1; + break; + + case OPTION_NO_SYSLOG_ASYNC: + use_syslog_async = 0; + break; + case OPTION_TIMEOUT: set_option("timeout", arg, 1); break; @@ -1656,10 +1702,10 @@ mailfromd_show_defaults() printf("statedir: %s\n", mailfromd_state_dir); printf("socket: %s\n", portspec); printf("pidfile: %s\n", pidfile); -#ifdef USE_SYSLOG_ASYNC - printf("syslog: non-blocking\n"); +#if DEFAULT_SYSLOG_ASYNC == 1 + printf("default syslog: non-blocking\n"); #else - printf("syslog: blocking\n"); + printf("default syslog: blocking\n"); #endif printf("database format: "); #if defined WITH_GDBM @@ -1677,8 +1723,13 @@ log_setup(int want_stderr) { /* Set up logging */ if (!want_stderr) { - openlog(syslog_tag, LOG_PID, log_facility); - gacopyz_set_logger(gacopyz_syslog_log_printer); + if (use_syslog_async) { + openlog_async(syslog_tag, LOG_PID, log_facility); + gacopyz_set_logger(mf_gacopyz_syslog_async_log_printer); + } else { + openlog(syslog_tag, LOG_PID, log_facility); + gacopyz_set_logger(gacopyz_syslog_log_printer); + } mu_error_set_print(syslog_error_printer); } else { gacopyz_set_logger(gacopyz_stderr_log_printer); diff --git a/src/syslog_async.c b/src/syslog_async.c new file mode 100644 index 00000000..a168eac9 --- /dev/null +++ b/src/syslog_async.c @@ -0,0 +1,413 @@ +/* syslog_async is Copyright (c) 2007 Simon Kelley + + 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; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + 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, see . +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "syslog_async.h" + +/* From RFC 3164 */ +#define MAX_MESSAGE 1024 + +#define DEF_BACKLOG 5 +#define DEF_DELAY 1000 /* doesn't come into effect until backlog > 10 */ + +static int log_fac = LOG_USER; +static int log_opts = LOG_ODELAY; +static const char *log_tag = "syslog"; +static int log_mask = 0xff; +static int log_backlog = DEF_BACKLOG; +static int log_delay = DEF_DELAY; + +static int log_fd = -1; +static int entries_alloced = 0; +static int entries_lost = 0; +static int connection_good = 1; + +struct log_entry { + int offset, length; + struct log_entry *next; + char payload[MAX_MESSAGE]; +}; + +static struct log_entry *entries = NULL; +static struct log_entry *free_entries = NULL; + +static int mksock(int type) +{ + int flags; + int fd = socket(AF_UNIX, type, 0); + + if (fd != -1) + { + if ((flags = fcntl(fd, F_GETFL)) == -1 || + fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1 || + (flags = fcntl(fd, F_GETFD)) == -1 || + fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + { + close(fd); + fd = -1; + } + } + return fd; +} + +void openlog_async(const char *ident, int option, int facility) +{ + if (ident) + log_tag = ident; + + log_opts = option; + + if (facility != 0 && (facility &~ LOG_FACMASK) == 0) + log_fac = facility; + + if (log_opts & LOG_NDELAY) + log_fd = mksock(SOCK_DGRAM); +} + +int setlogmask_async(int mask) +{ + int old = log_mask; + + if (mask != 0) + log_mask = mask; + + return old; +} + +void tunelog_async(int backlog, int delay) +{ + /* we need at least one buffer, and the + delay calculations overflow for more than 99 */ + if (backlog < 1) + backlog = 1; + else if (backlog > 99) + backlog = 99; + + /* don't lose existing buffers */ + if (backlog < entries_alloced) + log_backlog = entries_alloced; + else + log_backlog = backlog; + + if (delay < 0) + log_delay = 0; + else if (delay > 1000) + log_delay = 1000; + else + log_delay = delay; +} + +void closelog_async(void) +{ + /* maybe last chance to flush */ + log_write_async(); + + if (log_fd != -1) + { + close(log_fd); + log_fd = -1; + } + + /* restore defaults */ + log_fac = LOG_USER; + log_opts = LOG_ODELAY; + log_tag = "syslog"; + log_mask = 0xff; + + log_delay = DEF_DELAY; + + if (entries_alloced < DEF_BACKLOG) + log_backlog = entries_alloced; + else + log_backlog = DEF_BACKLOG; +} + +int log_fd_async(void) +{ + if (!entries || !connection_good) + return -1; + + return log_fd; +} + +void log_write_async(void) +{ + ssize_t rc; + int fd, tried_stream = 0; + struct log_entry *tmp; + + while (entries) + { + if (log_fd == -1 && + (log_fd = mksock(SOCK_DGRAM)) == -1) + goto fail; + + connection_good = 1; + + if ((rc = send(log_fd, + entries->payload + entries->offset, + entries->length, + MSG_NOSIGNAL)) != -1) + { + entries->length -= rc; + entries->offset += rc; + connection_good = 1; + + if (entries->length == 0) + goto free; + + continue; + } + + if (errno == EINTR) + continue; + + if (errno == EAGAIN) + return; + + /* *BSD, returns this instead of blocking? */ + if (errno == ENOBUFS) + { + connection_good = 0; + return; + } + + /* A stream socket closed at the other end goes into EPIPE + forever, close and re-open. */ + if (errno == EPIPE) + goto reopen_stream; + + if (errno == ECONNREFUSED || + errno == ENOTCONN || + errno == EDESTADDRREQ || + errno == ECONNRESET) + { + /* socket went (syslogd down?), try and reconnect. If we fail, + stop trying until the next call to my_syslog() + ECONNREFUSED -> connection went down + ENOTCONN -> nobody listening + (ECONNRESET, EDESTADDRREQ are *BSD equivalents) */ + + struct sockaddr_un logaddr; + + logaddr.sun_family = AF_LOCAL; + strncpy(logaddr.sun_path, _PATH_LOG, sizeof(logaddr.sun_path)); + + /* Got connection back? try again. */ + if (connect(log_fd, (struct sockaddr *)&logaddr, sizeof(logaddr)) != -1) + continue; + + /* errors from connect which mean we should keep trying */ + if (errno == ENOENT || + errno == EALREADY || + errno == ECONNREFUSED || + errno == EISCONN || + errno == EINTR || + errno == EAGAIN) + { + /* try again on next syslog() call */ + connection_good = 0; + return; + } + + /* we start with a SOCK_DGRAM socket, but syslog may want SOCK_STREAM */ + if (!tried_stream && errno == EPROTOTYPE) + { + reopen_stream: + tried_stream = 1; + close(log_fd); + if ((log_fd = mksock(SOCK_STREAM)) != -1) + continue; + } + } + + fail: + tried_stream = 0; + + /* give up - try to write to console if we've been asked + take care not to block in open() or write() */ + if ((log_opts & LOG_CONS) && + (fd = open(_PATH_CONSOLE, O_WRONLY | O_NONBLOCK, 0)) != -1) + { + char *start = strchr(entries->payload, '>') + 1; + int flags = fcntl(fd, F_GETFL); + + if (flags != -1) + fcntl(fd, F_SETFL, flags | O_NONBLOCK); + + entries->length -= start - entries->payload; + /* move down to remove the tag, and make room for the \r\n */ + memmove(entries->payload, start, entries->length); + entries->payload[entries->length - 1] = '\r'; + entries->payload[entries->length] = '\n'; + write(fd, entries->payload, entries->length + 1); + close(fd); + } + + free: + tmp = entries; + entries = tmp->next; + tmp->next = free_entries; + free_entries = tmp; + + if (entries_lost != 0) + { + int e = entries_lost; + entries_lost = 0; /* avoid wild recursion */ + syslog_async(LOG_WARNING, "async_syslog overflow: %d log entries lost", e); + } + continue; + } +} + +void syslog_async(int priority, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vsyslog_async(priority, format, ap); + va_end(ap); +} + +void vsyslog_async(int priority, const char *format, va_list ap) +{ + struct log_entry *entry; + time_t time_now; + char *p, *q, *r; + size_t len; + + if (!(log_mask & LOG_MASK(LOG_PRI(priority))) || (priority &~ (LOG_PRIMASK|LOG_FACMASK))) + return; + + if ((entry = free_entries)) + free_entries = entry->next; + else if (entries_alloced < log_backlog && (entry = malloc(sizeof(struct log_entry)))) + entries_alloced++; + + if (!entry) + entries_lost++; + else + { + /* add to end of list, consumed from the start */ + entry->next = NULL; + if (!entries) + entries = entry; + else + { + struct log_entry *tmp; + for (tmp = entries; tmp->next; tmp = tmp->next); + tmp->next = entry; + } + + time(&time_now); + p = entry->payload; + p += sprintf(p, "<%d>", priority | log_fac); + + q = p; + + if (log_opts & LOG_PID) + p += sprintf(p, "%.15s %s[%d]: ", ctime(&time_now) + 4, log_tag, getpid()); + else + p += sprintf(p, "%.15s %s: ", ctime(&time_now) + 4, log_tag); + + len = p - entry->payload; + len += vsnprintf(p, MAX_MESSAGE - len, format, ap) + 1; /* include zero-terminator */ + entry->length = len > MAX_MESSAGE ? MAX_MESSAGE : len; + + /* remove trailing '\n's passed to us. */ + for (r = &entry->payload[entry->length - 2]; r >= entry->payload; r--) + if (*r == '\n') + entry->length--; + else + break; + + entry->offset = 0; + + if (log_opts & LOG_PERROR) + { + ssize_t rc, s = entry->length - (q - entry->payload); + /* replace terminator with \n */ + entry->payload[entry->length - 1] = '\n'; + + while (s != 0) + if ((rc = write(STDERR_FILENO, q, s)) != -1) + { + s -= rc; + q += rc; + continue; + } + else if (errno == EINTR) + continue; + else + break; + } + entry->payload[entry->length - 1] = 0; + } + + /* almost always, logging won't block, so try and write this now, + to save collecting too many log messages during a select loop. */ + log_write_async(); + + /* Since we're doing things asynchronously, we + can now generate log lines very fast. With a small buffer (desirable), + that means it can overflow the log-buffer very quickly. + To avoid this, we delay here, the delay growing exponentially + with queue length. Delay is limited to 1 second, by default + but can be tuned for less if needed. Note that for a responsive + syslog, the log-line we just created will have been writen by the + call the log_write_async() above, so that this doesn't delay at all. */ + if (entries && log_delay != 0) + { + struct timespec waiter; + int d; + + for (d = 1,entry = entries; entry->next; entry = entry->next) + { + d *= 2; + if (d >= log_delay) /* limit to 999ms */ + { + d = log_delay - 1; + break; + } + } + + waiter.tv_sec = 0; + waiter.tv_nsec = d * 1000000; /* 1 ms */ + nanosleep(&waiter, NULL); + + /* try and write again */ + log_write_async(); + } +} + diff --git a/src/syslog_async.h b/src/syslog_async.h new file mode 100644 index 00000000..9c0836c5 --- /dev/null +++ b/src/syslog_async.h @@ -0,0 +1,126 @@ +/* syslog_async is Copyright (c) 2007 Simon Kelley + + 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; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + 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, see . +*/ + +#ifndef _SYSLOG_ASYNC_H +#define _SYSLOG_ASYNC_H 1 + +#include +#include + +/* Syslog_async is a non-blocking replacement for the + POSIX-standard syslog() system call. Instead of blocking, + log-lines are buffered in memory. The buffer size is limited + and if the buffer overflows log lines are lost. When lines are + lost this fact is logged with a message of the form: + + async_syslog overflow: 5 log entries lost + + In order to limit the probability of buffer overflow + short delays are added to syslog_async() calls when the + queue is getting full. The delay added is strictly + bounded and tunable. + + The API is very close to the standard syslog(), with an + additional call the tune buffer parameters and a couple + of calls into the event loop. + + The code has been tested under Linux and BSD, and with both + the syslog and syslog-ng log daemons. +*/ + + +/* + openlog_async(), closelog_async() and setlogmask_async() are + identical to the POSIX equivalents. +*/ + +void openlog_async(const char *ident, int option, int facility); +void closelog_async(void); +int setlogmask_async(int mask); + + + +/* + syslog_async() and vsyslog_async() are identical to syslog() and vsyslog() + except for their blocking behaviour. The formatting is done using printf(), + so the additional format operator %m is available only if the system + printf() provides it. (GNU printf() does.) +*/ + +void syslog_async(int priority, const char *format, ...); +void vsyslog_async(int priority, const char *format, va_list ap); + + + +/* + log_fd_async() and log_write_async() are the interface between the library + and the daemon event loop. + + log_fd_async() returns a file descriptor which the library needs to write, + or -1 if no write is queued. log_write_async() does the write. + + The result of log_fd_async() is only valid until [v]syslog_async() or + log_write_async() is called, so it should be called each time around the + event loop, just before the call to select() or poll(). + + A typical event loop looks like this: + + while (1) + { + int log_fd; + fd_set read_set, write_set; + + FD_ZERO(&read_set); + FD_ZERO(&write_set); + + ...other stuff.. + + if ((log_fd = log_fd_async()) != -1) + FD_SET(log_fd, &write_set); + + select(..., &read_set, &write_set, ...); + + if (log_fd != -1 && FD_ISSET(log_fd, &write)) + log_write_async(); + + + ...other stuff.... + } +*/ + +int log_fd_async(void); +void log_write_async(void); + + + +/* + tunelog_async() tunes the log-line buffer. Backlog is the limit + on the number of queued log-lines. These are stored in malloc'ed memory + and each line is stored in a fixed-size buffer which is just over 1K bytes. + The library maintains a buffer pool to avoid heap fragmentation. Delay + is the upper bound on the time taken to run syslog_async, in milliseconds. + This delay is added when syslog is busy in order to reduce the probability + of buffer overflow. Backlog is constrained between 1 and 99 and delay + between 1 millisecond and 1000 millisconds. The default for backlog + is 5 and for delay 1000. Note that delay is calculated from queue size as + 2^queue_size (in milliseconds) therefore the maximum delay for the default + queue size is 64ms. Setting delay to zero is allowed, and inhibits the delay + completely. +*/ + +void tunelog_async(int backlog, int delay); + +#endif -- cgit v1.2.1