/* This file is part of GNU Pies.
Copyright (C) 2007-2020 Sergey Poznyakoff
GNU Pies 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 3, or (at your option)
any later version.
GNU Pies 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 GNU Pies. If not, see . */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "pies.h"
#include "pies_syslog.h"
struct log_message
{
char *text; /* Message text (no terminating nul). */
size_t len; /* Number of bytes in text */
size_t drop_count; /* Count of messages dropped so far (first message only) */
struct log_message *next;
};
struct log_message_in
{
struct log_message msg;
char buf[PIES_LOG_BUF_SIZE];
};
/* Global variables */
/* Fallback log file is used to log critical messages when syslog
daemon is unavailable. If NULL, stderr will be used. */
char *pies_fallback_file = "/tmp/pies_logger.log";
/* Name of the syslog device. If starts with a slash, it is assumed
to be a UNIX socket name. Otherwise, it is assumed to be a host name
or IPv4 address of the syslog daemon, optionally followed by a colon
and port number or service name. */
static char *pies_log_dev = PIES_LOG_DEV;
/* Log tag */
char *pies_log_tag = "pies";
/* Log facility */
int pies_log_facility = LOG_USER;
/* Maximum capacity of the log message queue */
size_t pies_log_max_queue = PIES_LOG_MAX_QUEUE;
/* Log socket descriptor. */
static int log_fd = -1;
/* Socket address */
static union {
struct sockaddr_in s_in;
struct sockaddr_un s_un;
} log_sa;
/* Socket address length. */
static socklen_t log_salen = 0;
/* Socked address family. */
static int log_family;
static inline int
pri_facility (int pri)
{
return pri & ~0x7;
}
static inline int
pri_severity (int pri)
{
return pri & 0x7;
}
/* Fallback logger */
static void
fallback_log (char const *fmt, ...)
{
FILE *fp = NULL;
va_list ap;
if (pies_fallback_file)
fp = fopen (pies_fallback_file, "a");
if (!fp)
fp = stderr;
fprintf (fp, "pies[%lu]: ", (unsigned long) getpid());
va_start (ap, fmt);
vfprintf (fp, fmt, ap);
va_end (ap);
fputc ('\n', fp);
if (fp != stderr)
fclose (fp);
}
static int
reopen_logger (void)
{
int fd;
int flags;
if (log_salen == 0)
{
if (pies_log_dev[0] == '/')
{
size_t len = strlen (pies_log_dev);
if (len >= sizeof log_sa.s_un.sun_path)
{
fallback_log ("%s: UNIX socket name too long", pies_log_dev);
return -1;
}
strcpy (log_sa.s_un.sun_path, pies_log_dev);
log_sa.s_un.sun_family = AF_UNIX;
log_family = PF_UNIX;
log_salen = sizeof (log_sa.s_un);
}
else
{
struct addrinfo hints;
struct addrinfo *res;
int rc;
char *node;
char *service;
node = strdup (pies_log_dev);
if (!node)
return -1;
service = strchr (node, ':');
if (service)
*service++ = 0;
else
service = "syslog";
memset (&hints, 0, sizeof (hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
rc = getaddrinfo (node, service, &hints, &res);
free(node);
if (rc)
{
fallback_log ("%s: invalid socket address", pies_log_dev);
return -1;
}
memcpy (&log_sa, res->ai_addr, res->ai_addrlen);
log_family = PF_INET;
log_salen = res->ai_addrlen;
freeaddrinfo (res);
}
}
fd = socket (log_family, SOCK_DGRAM, 0);
if (fd == -1)
{
fallback_log ("socket: %s", strerror (errno));
return -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);
return -1;
}
if (connect(fd, (struct sockaddr*)&log_sa, log_salen))
{
fallback_log("socket: %s", strerror(errno));
close(fd);
return -1;
}
log_fd = fd;
return 0;
}
static struct log_message_in *
log_message_in_create (void)
{
struct log_message_in *msg;
if ((msg = malloc (sizeof (*msg))) != NULL)
{
msg->msg.text = msg->buf;
msg->msg.len = 0;
msg->msg.drop_count = 0;
msg->msg.next = NULL;
}
return msg;
}
static void
log_message_in_format (struct log_message_in *msg,
int prio, char const *msgtext, char const *tag,
pid_t pid)
{
char tbuf[sizeof ("Jan 1 00:00:00")];
char hostbuf[256];
struct timeval tv;
struct tm tm;
gettimeofday (&tv, NULL);
localtime_r (&tv.tv_sec, &tm);
strftime (tbuf, sizeof (tbuf), "%b %d %H:%M:%S", &tm);
/* Supply default facility, unless prio already contains one.
Note: this means that we cannot use LOG_KERN, but that doesn't
really matter as we're not a kernel, anyway. */
if (pri_facility (prio) == 0)
prio |= pies_log_facility;
if (log_family == PF_UNIX)
{
msg->msg.len = snprintf (msg->buf, sizeof (msg->buf),
"<%d>%s %s[%lu]: %s",
prio,
tbuf,
tag,
(unsigned long)pid,
msgtext);
}
else
{
gethostname (hostbuf, sizeof (hostbuf));
msg->msg.len = snprintf (msg->buf, sizeof (msg->buf),
"<%d>%s %s %s[%lu]: %s",
prio,
tbuf,
hostbuf,
tag,
(unsigned long)pid,
msgtext);
}
}
static struct log_message *
log_message_create (int prio, char const *msgtext, char const *tag, pid_t pid)
{
struct log_message_in *msg;
if ((msg = log_message_in_create ()) != NULL)
log_message_in_format (msg, prio, msgtext, tag, pid);
return &msg->msg;
}
/* Log message queue */
static struct log_message *log_queue_head, *log_queue_tail;
static size_t log_queue_length;
static void
log_message_putback (struct log_message *msg)
{
msg->next = log_queue_head;
log_queue_head = msg;
if (!log_queue_tail)
log_queue_tail = msg;
log_queue_length++;
}
static struct log_message *
log_message_dequeue (void)
{
struct log_message *msg = log_queue_head;
if (msg)
{
log_queue_head = msg->next;
if (!log_queue_head)
log_queue_tail = log_queue_head;
msg->next = NULL;
log_queue_length--;
}
return msg;
}
static void
log_message_enqueue (struct log_message *inmsg)
{
if (log_queue_length == pies_log_max_queue)
{
struct log_message *msg;
struct log_message_in *tmp;
char buf[PIES_LOG_BUF_SIZE];
/* Dequeue first message */
msg = log_message_dequeue ();
if (msg->drop_count == 0)
{
/* If it is not a drop message, free it and create a new
drop message */
free (msg);
tmp = log_message_in_create ();
tmp->msg.drop_count = 1;
}
else
/* Otherwise, cast it to log_message_in */
tmp = (struct log_message_in *)msg;
/* Dequeue and drop the first message */
free (log_message_dequeue ());
tmp->msg.drop_count++;
/* Reformat the message text */
snprintf (buf, sizeof (buf), "%zu messages dropped", tmp->msg.drop_count);
log_message_in_format (tmp,
LOG_DAEMON|LOG_CRIT,
buf,
pies_log_tag,
getpid ());
log_message_putback (&tmp->msg);
}
if (log_queue_tail)
log_queue_tail->next = inmsg;
else
log_queue_head = inmsg;
log_queue_tail = inmsg;
log_queue_length++;
pies_syslog_flush ();
}
/*
* Flush the message queue to the socket.
* Some fragments borrowed from the excellent syslog_async written by
* Simon Kelley (http://www.thekelleys.org.uk/syslog-async).
*/
void
pies_syslog_flush (void)
{
struct log_message *msg;
int rc;
while ((msg = log_message_dequeue ()) != NULL)
{
if (log_fd == -1)
reopen_logger ();
rc = send (log_fd, msg->text, msg->len, MSG_NOSIGNAL);
if (rc != -1)
{
free (msg);
continue;
}
log_message_putback (msg);
if (errno == EINTR)
continue;//Should not happen??
if (errno == EAGAIN)
break;
/* *BSD, returns this instead of blocking? */
if (errno == ENOBUFS)
break;
/* A stream socket closed at the other end goes into EPIPE
forever, close and re-open. */
if (errno == EPIPE)
{
close (log_fd);
log_fd = -1;
continue;
}
if (errno == ECONNREFUSED || /* connection went down */
errno == ENOTCONN || /* nobody listening */
errno == EDESTADDRREQ || /* BSD equivalents of the above */
errno == ECONNRESET)
{
/* The reader is gone. Try reconnecting. If failed,
retry when called next time. */
if (connect (log_fd, (struct sockaddr *)&log_sa, log_salen) != -1)
/* Connected successfully: retry now */
continue;
if (errno == ENOENT ||
errno == EALREADY ||
errno == ECONNREFUSED ||
errno == EISCONN ||
errno == EINTR ||
errno == EAGAIN)
/* try again when woken up again */
break;
}
/* Else ? */
break;
}
}
void
logger_log (int prio, char const *msgtext, char const *tag, pid_t pid)
{
struct log_message *msg;
msg = log_message_create (prio, msgtext, tag, pid);
if (msg)
log_message_enqueue (msg);
}
/* Upper level logger API */
void
pies_vsyslog (int pri, char const *fmt, va_list ap)
{
char buf[PIES_LOG_BUF_SIZE];
vsnprintf (buf, sizeof(buf), fmt, ap);
logger_log (pri, buf, pies_log_tag, getpid ());
}
void
pies_syslog (int pri, char const *fmt, ...)
{
va_list ap;
va_start (ap, fmt);
pies_vsyslog (pri, fmt, ap);
va_end (ap);
}
int
pies_syslog_open (void)
{
return reopen_logger ();
}
void
pies_syslog_close (void)
{
if (log_fd != -1)
{
close (log_fd);
log_fd = -1;
}
}
void
pies_syslog_message (int prio, char const *text, char const *tag, pid_t pid)
{
log_message_enqueue (log_message_create (prio, text, tag, pid));
}
int
pies_syslog_set_dev (char const *dev)
{
char *p = strdup (dev);
if (!p)
return -1;
pies_log_dev = p;
pies_syslog_close ();
log_salen = 0;
return 0;
}