/* 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 .
*/
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_PATHS_H
# include
#else
# define _PATH_LOG "/dev/log"
# define _PATH_CONSOLE "/dev/console"
#endif
#include
#include
#include "syslog_async.h"
#ifndef MSG_NOSIGNAL
# define MSG_NOSIGNAL 0
#endif
#ifndef LOG_PERROR
# define LOG_PERROR 0
#endif
#ifndef LOG_PRI
# define LOG_PRI(x) ((x) & LOG_PRIMASK)
#endif
/* 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)
{
return log_fd;
}
void log_write_async(void)
{
ssize_t rc;
int fd, tried_stream = 0;
struct log_entry *tmp;
#if MSG_NOSIGNAL == 0
RETSIGTYPE (*sigfun) (int sig) = signal (SIGPIPE, SIG_IGN);
#endif
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)
break;
/* *BSD, returns this instead of blocking? */
if (errno == ENOBUFS)
{
connection_good = 0;
break;
}
/* 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_UNIX;
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;
break;
}
/* 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;
}
#if MSG_NOSIGNAL == 0
signal (SIGPIPE, sigfun);
#endif
}
static int find_percent_m(const char *format)
{
const char *p = format;
while (*p && (p = strchr(p, '%')))
if (p[1] == 'm')
return p - format;
else
p++;
return -1;
}
static void vsyslog_async_logger(int priority, int ec,
const char *format, va_list ap)
{
struct log_entry *entry;
time_t time_now;
char *p, *q, *r;
size_t len;
char fmtbuf[MAX_MESSAGE];
int mpos;
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;
if (!(priority & LOG_FACMASK))
priority |= log_fac;
p += sprintf(p, "<%d>", priority);
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;
if ((mpos = find_percent_m(format)) != -1 && mpos < MAX_MESSAGE - len) {
const char *errmsg;
int errlen;
int fmtlen;
int rest_len;
memcpy(fmtbuf, format, mpos);
fmtlen = mpos;
mpos += 2;
errmsg = strerror(ec);
errlen = strlen(errmsg);
if (fmtlen + errlen >= sizeof(fmtbuf))
errlen = sizeof(fmtbuf) - fmtlen - 1;
memcpy(fmtbuf + fmtlen, errmsg, errlen);
fmtlen += errlen;
rest_len = strlen(format) - mpos;
if (rest_len >= sizeof(fmtbuf) - fmtlen)
rest_len = sizeof(fmtbuf) - fmtlen - 1;
if (rest_len)
memcpy(fmtbuf + fmtlen, format + mpos, rest_len);
fmtbuf[fmtlen + rest_len] = 0;
format = fmtbuf;
}
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();
}
}
void syslog_async(int priority, const char *format, ...)
{
va_list ap;
int ec = errno;
va_start(ap, format);
vsyslog_async_logger(priority, ec, format, ap);
va_end(ap);
}
void vsyslog_async(int priority, const char *format, va_list ap)
{
vsyslog_async_logger(priority, errno, format, ap);
}