text: /* SPDX-License-Identifier: BSD-2-Clause */
  /*
   * logerr: errx with logging
   * Copyright (c) 2006-2020 Roy Marples <roy@marples.name>
   * All rights reserved
  
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   */
  
  #include <sys/time.h>
  #include <errno.h>
  #include <stdbool.h>
  #include <stdarg.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <syslog.h>
  #include <time.h>
  #include <unistd.h>
  
  #include "logerr.h"
  
  #ifndef	LOGERR_SYSLOG_FACILITY
  #define	LOGERR_SYSLOG_FACILITY	LOG_DAEMON
  #endif
  
  #ifdef SMALL
  #undef LOGERR_TAG
  #endif
  
  /* syslog protocol is 1k message max, RFC 3164 section 4.1 */
  #define LOGERR_SYSLOGBUF	1024 + sizeof(int) + sizeof(pid_t)
  
  #define UNUSED(a)		(void)(a)
  
  struct logctx {
  	char		 log_buf[BUFSIZ];
  	unsigned int	 log_opts;
  	int		 log_fd;
  	pid_t		 log_pid;
  #ifndef SMALL
  	FILE		*log_file;
  #ifdef LOGERR_TAG
  	const char	*log_tag;
  #endif
  #endif
  };
  
  static struct logctx _logctx = {
  	/* syslog style, but without the hostname or tag. */
  	.log_opts = LOGERR_LOG | LOGERR_LOG_DATE | LOGERR_LOG_PID,
  	.log_fd = -1,
  	.log_pid = 0,
  };
  
  #if defined(__linux__)
  /* Poor man's getprogname(3). */
  static char *_logprog;
  static const char *
  getprogname(void)
  {
  	const char *p;
  
  	/* Use PATH_MAX + 1 to avoid truncation. */
  	if (_logprog == NULL) {
  		/* readlink(2) does not append a NULL byte,
  		 * so zero the buffer. */
  		if ((_logprog = calloc(1, PATH_MAX + 1)) == NULL)
  			return NULL;
  		if (readlink("/proc/self/exe", _logprog, PATH_MAX + 1) == -1) {
  			free(_logprog);
  			_logprog = NULL;
  			return NULL;
  		}
  	}
  	if (_logprog[0] == '[')
  		return NULL;
  	p = strrchr(_logprog, '/');
  	if (p == NULL)
  		return _logprog;
  	return p + 1;
  }
  #endif
  
  #ifndef SMALL
  /* Write the time, syslog style. month day time - */
  static int
  logprintdate(FILE *stream)
  {
  	struct timeval tv;
  	time_t now;
  	struct tm tmnow;
  	char buf[32];
  
  	if (gettimeofday(&tv, NULL) == -1)
  		return -1;
  
  	now = tv.tv_sec;
  	if (localtime_r(&now, &tmnow) == NULL)
  		return -1;
  	if (strftime(buf, sizeof(buf), "%b %d %T ", &tmnow) == 0)
  		return -1;
  	return fprintf(stream, "%s", buf);
  }
  #endif
  
  __printflike(3, 0) static int
  vlogprintf_r(struct logctx *ctx, FILE *stream, const char *fmt, va_list args)
  {
  	int len = 0, e;
  	va_list a;
  #ifndef SMALL
  	bool log_pid;
  #ifdef LOGERR_TAG
  	bool log_tag;
  #endif
  
  	if ((stream == stderr && ctx->log_opts & LOGERR_ERR_DATE) ||
  	    (stream != stderr && ctx->log_opts & LOGERR_LOG_DATE))
  	{
  		if ((e = logprintdate(stream)) == -1)
  			return -1;
  		len += e;
  	}
  
  #ifdef LOGERR_TAG
  	log_tag = ((stream == stderr && ctx->log_opts & LOGERR_ERR_TAG) ||
  	    (stream != stderr && ctx->log_opts & LOGERR_LOG_TAG));
  	if (log_tag) {
  		if (ctx->log_tag == NULL)
  			ctx->log_tag = getprogname();
  		if ((e = fprintf(stream, "%s", ctx->log_tag)) == -1)
  			return -1;
  		len += e;
  	}
  #endif
  
  	log_pid = ((stream == stderr && ctx->log_opts & LOGERR_ERR_PID) ||
  	    (stream != stderr && ctx->log_opts & LOGERR_LOG_PID));
  	if (log_pid) {
  		pid_t pid;
  
  		if (ctx->log_pid == 0)
  			pid = getpid();
  		else
  			pid = ctx->log_pid;
  		if ((e = fprintf(stream, "[%d]", pid)) == -1)
  			return -1;
  		len += e;
  	}
  
  #ifdef LOGERR_TAG
  	if (log_tag || log_pid)
  #else
  	if (log_pid)
  #endif
  	{
  		if ((e = fprintf(stream, ": ")) == -1)
  			return -1;
  		len += e;
  	}
  #else
  	UNUSED(ctx);
  #endif
  
  	va_copy(a, args);
  	e = vfprintf(stream, fmt, a);
  	if (fputc('\n', stream) == EOF)
  		e = -1;
  	else if (e != -1)
  		e++;
  	va_end(a);
  
  	return e == -1 ? -1 : len + e;
  }
  
  /*
   * NetBSD's gcc has been modified to check for the non standard %m in printf
   * like functions and warn noisily about it that they should be marked as
   * syslog like instead.
   * This is all well and good, but our logger also goes via vfprintf and
   * when marked as a sysloglike funcion, gcc will then warn us that the
   * function should be printflike instead!
   * This creates an infinte loop of gcc warnings.
   * Until NetBSD solves this issue, we have to disable a gcc diagnostic
   * for our fully standards compliant code in the logger function.
   */
  #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5))
  #pragma GCC diagnostic push
  #pragma GCC diagnostic ignored "-Wmissing-format-attribute"
  #endif
  __printflike(2, 0) static int
  vlogmessage(int pri, const char *fmt, va_list args)
  {
  	struct logctx *ctx = &_logctx;
  	int len = 0;
  
  	if (ctx->log_fd != -1) {
  		char buf[LOGERR_SYSLOGBUF];
  		pid_t pid;
  
  		memcpy(buf, &pri, sizeof(pri));
  		pid = getpid();
  		memcpy(buf + sizeof(pri), &pid, sizeof(pid));
  		len = vsnprintf(buf + sizeof(pri) + sizeof(pid),
  		    sizeof(buf) - sizeof(pri) - sizeof(pid),
  		    fmt, args);
  		if (len != -1)
  			len = (int)write(ctx->log_fd, buf,
  			    ((size_t)++len) + sizeof(pri) + sizeof(pid));
  		return len;
  	}
  
  	if (ctx->log_opts & LOGERR_ERR &&
  	    (pri <= LOG_ERR ||
  	    (!(ctx->log_opts & LOGERR_QUIET) && pri <= LOG_INFO) ||
  	    (ctx->log_opts & LOGERR_DEBUG && pri <= LOG_DEBUG)))
  		len = vlogprintf_r(ctx, stderr, fmt, args);
  
  #ifndef SMALL
  	if (ctx->log_file != NULL &&
  	    (pri != LOG_DEBUG || (ctx->log_opts & LOGERR_DEBUG)))
  		len = vlogprintf_r(ctx, ctx->log_file, fmt, args);
  #endif
  
  	if (ctx->log_opts & LOGERR_LOG)
  		vsyslog(pri, fmt, args);
  
  	return len;
  }
  #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5))
  #pragma GCC diagnostic pop
  #endif
  
  __printflike(2, 3) void
  logmessage(int pri, const char *fmt, ...)
  {
  	va_list args;
  
  	va_start(args, fmt);
  	vlogmessage(pri, fmt, args);
  	va_end(args);
  }
  
  __printflike(2, 0) static void
  vlogerrmessage(int pri, const char *fmt, va_list args)
  {
  	int _errno = errno;
  	char buf[1024];
  
  	vsnprintf(buf, sizeof(buf), fmt, args);
  	logmessage(pri, "%s: %s", buf, strerror(_errno));
  	errno = _errno;
  }
  
  __printflike(2, 3) void
  logerrmessage(int pri, const char *fmt, ...)
  {
  	va_list args;
  
  	va_start(args, fmt);
  	vlogerrmessage(pri, fmt, args);
  	va_end(args);
  }
  
  void
  log_debug(const char *fmt, ...)
  {
  	va_list args;
  
  	va_start(args, fmt);
  	vlogerrmessage(LOG_DEBUG, fmt, args);
  	va_end(args);
  }
  
  void
  log_debugx(const char *fmt, ...)
  {
  	va_list args;
  
  	va_start(args, fmt);
  	vlogmessage(LOG_DEBUG, fmt, args);
  	va_end(args);
  }
  
  void
  log_info(const char *fmt, ...)
  {
  	va_list args;
  
  	va_start(args, fmt);
  	vlogerrmessage(LOG_INFO, fmt, args);
  	va_end(args);
  }
  
  void
  log_infox(const char *fmt, ...)
  {
  	va_list args;
  
  	va_start(args, fmt);
  	vlogmessage(LOG_INFO, fmt, args);
  	va_end(args);
  }
  
  void
  log_warn(const char *fmt, ...)
  {
  	va_list args;
  
  	va_start(args, fmt);
  	vlogerrmessage(LOG_WARNING, fmt, args);
  	va_end(args);
  }
  
  void
  log_warnx(const char *fmt, ...)
  {
  	va_list args;
  
  	va_start(args, fmt);
  	vlogmessage(LOG_WARNING, fmt, args);
  	va_end(args);
  }
  
  void
  log_err(const char *fmt, ...)
  {
  	va_list args;
  
  	va_start(args, fmt);
  	vlogerrmessage(LOG_ERR, fmt, args);
  	va_end(args);
  }
  
  void
  log_errx(const char *fmt, ...)
  {
  	va_list args;
  
  	va_start(args, fmt);
  	vlogmessage(LOG_ERR, fmt, args);
  	va_end(args);
  }
  
  int
  loggetfd(void)
  {
  	struct logctx *ctx = &_logctx;
  
  	return ctx->log_fd;
  }
  
  void
  logsetfd(int fd)
  {
  	struct logctx *ctx = &_logctx;
  
  	ctx->log_fd = fd;
  #ifndef SMALL
  	if (fd != -1 && ctx->log_file != NULL) {
  		fclose(ctx->log_file);
  		ctx->log_file = NULL;
  	}
  #endif
  }
  
  int
  logreadfd(int fd)
  {
  	struct logctx *ctx = &_logctx;
  	char buf[LOGERR_SYSLOGBUF];
  	int len, pri;
  
  	len = (int)read(fd, buf, sizeof(buf));
  	if (len == -1)
  		return -1;
  
  	/* Ensure we have pri, pid and a terminator */
  	if (len < (int)(sizeof(pri) + sizeof(pid_t) + 1) ||
  	    buf[len - 1] != '\0')
  	{
  		errno = EINVAL;
  		return -1;
  	}
  
  	memcpy(&pri, buf, sizeof(pri));
  	memcpy(&ctx->log_pid, buf + sizeof(pri), sizeof(ctx->log_pid));
  	logmessage(pri, "%s", buf + sizeof(pri) + sizeof(ctx->log_pid));
  	ctx->log_pid = 0;
  	return len;
  }
  
  unsigned int
  loggetopts(void)
  {
  	struct logctx *ctx = &_logctx;
  
  	return ctx->log_opts;
  }
  
  void
  logsetopts(unsigned int opts)
  {
  	struct logctx *ctx = &_logctx;
  
  	ctx->log_opts = opts;
  	setlogmask(LOG_UPTO(opts & LOGERR_DEBUG ? LOG_DEBUG : LOG_INFO));
  }
  
  #ifdef LOGERR_TAG
  void
  logsettag(const char *tag)
  {
  #if !defined(SMALL)
  	struct logctx *ctx = &_logctx;
  
  	ctx->log_tag = tag;
  #else
  	UNUSED(tag);
  #endif
  }
  #endif
  
  int
  logopen(const char *path)
  {
  	struct logctx *ctx = &_logctx;
  	int opts = 0;
  
  	/* Cache timezone */
  	tzset();
  
  	(void)setvbuf(stderr, ctx->log_buf, _IOLBF, sizeof(ctx->log_buf));
  
  #ifndef SMALL
  	if (ctx->log_file != NULL) {
  		fclose(ctx->log_file);
  		ctx->log_file = NULL;
  	}
  #endif
  
  	if (ctx->log_opts & LOGERR_LOG_PID)
  		opts |= LOG_PID;
  	openlog(getprogname(), opts, LOGERR_SYSLOG_FACILITY);
  	if (path == NULL)
  		return 1;
  
  #ifndef SMALL
  	if ((ctx->log_file = fopen(path, "ae")) == NULL)
  		return -1;
  	setlinebuf(ctx->log_file);
  	return fileno(ctx->log_file);
  #else
  	errno = ENOTSUP;
  	return -1;
  #endif
  }
  
  void
  logclose(void)
  {
  #ifndef SMALL
  	struct logctx *ctx = &_logctx;
  #endif
  
  	closelog();
  #if defined(__linux__)
  	free(_logprog);
  	_logprog = NULL;
  #endif
  #ifndef SMALL
  	if (ctx->log_file == NULL)
  		return;
  	fclose(ctx->log_file);
  	ctx->log_file = NULL;
  #endif
  }
  