/* 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"; /* 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; /* 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. If NULL, PIES_LOG_DEV is assumed. The user can modify this value using the pies_syslog_set_dev call. */ static char *log_dev; /* 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) { char *dev = log_dev ? log_dev : PIES_LOG_DEV; if (dev[0] == '/') { size_t len = strlen (dev); if (len >= sizeof log_sa.s_un.sun_path) { fallback_log ("%s: UNIX socket name too long", dev); return -1; } strcpy (log_sa.s_un.sun_path, 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 (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", 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_syslog_close (); free (log_dev); log_dev = p; log_salen = 0; return 0; }