Home | History | Annotate | Line # | Download | only in src
      1 /* SPDX-License-Identifier: BSD-2-Clause */
      2 /*
      3  * logerr: errx with logging
      4  * Copyright (c) 2006-2025 Roy Marples <roy (at) marples.name>
      5  * All rights reserved
      6 
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
     17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
     20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     26  * SUCH DAMAGE.
     27  */
     28 
     29 #include <sys/socket.h>
     30 #include <sys/time.h>
     31 
     32 #include <errno.h>
     33 #include <stdbool.h>
     34 #include <stdarg.h>
     35 #include <stdio.h>
     36 #include <stdlib.h>
     37 #include <string.h>
     38 #include <syslog.h>
     39 #include <time.h>
     40 #include <unistd.h>
     41 
     42 #include "logerr.h"
     43 
     44 #ifndef	LOGERR_SYSLOG_FACILITY
     45 #define	LOGERR_SYSLOG_FACILITY	LOG_DAEMON
     46 #endif
     47 
     48 #ifdef SMALL
     49 #undef LOGERR_TAG
     50 #endif
     51 
     52 /* syslog protocol is 1k message max, RFC 3164 section 4.1 */
     53 #define LOGERR_SYSLOGBUF	1024 + sizeof(int) + sizeof(pid_t)
     54 
     55 #define UNUSED(a)		(void)(a)
     56 
     57 struct logctx {
     58 	char		 log_buf[BUFSIZ];
     59 	unsigned int	 log_opts;
     60 	int		 log_fd;
     61 	pid_t		 log_pid;
     62 #ifndef SMALL
     63 	FILE		*log_file;
     64 #ifdef LOGERR_TAG
     65 	const char	*log_tag;
     66 #endif
     67 #endif
     68 };
     69 
     70 static struct logctx _logctx = {
     71 	/* syslog style, but without the hostname or tag. */
     72 	.log_opts = LOGERR_LOG | LOGERR_LOG_DATE | LOGERR_LOG_PID,
     73 	.log_fd = -1,
     74 	.log_pid = 0,
     75 };
     76 
     77 #if defined(__linux__)
     78 /* Poor man's getprogname(3). */
     79 static char *_logprog;
     80 static const char *
     81 getprogname(void)
     82 {
     83 	const char *p;
     84 
     85 	/* Use PATH_MAX + 1 to avoid truncation. */
     86 	if (_logprog == NULL) {
     87 		/* readlink(2) does not append a NULL byte,
     88 		 * so zero the buffer. */
     89 		if ((_logprog = calloc(1, PATH_MAX + 1)) == NULL)
     90 			return NULL;
     91 		if (readlink("/proc/self/exe", _logprog, PATH_MAX + 1) == -1) {
     92 			free(_logprog);
     93 			_logprog = NULL;
     94 			return NULL;
     95 		}
     96 	}
     97 	if (_logprog[0] == '[')
     98 		return NULL;
     99 	p = strrchr(_logprog, '/');
    100 	if (p == NULL)
    101 		return _logprog;
    102 	return p + 1;
    103 }
    104 #endif
    105 
    106 #ifndef SMALL
    107 /* Write the time, syslog style. month day time - */
    108 static int
    109 logprintdate(FILE *stream)
    110 {
    111 	struct timeval tv;
    112 	time_t now;
    113 	struct tm tmnow;
    114 	char buf[32];
    115 
    116 	if (gettimeofday(&tv, NULL) == -1)
    117 		return -1;
    118 
    119 	now = tv.tv_sec;
    120 	if (localtime_r(&now, &tmnow) == NULL)
    121 		return -1;
    122 	if (strftime(buf, sizeof(buf), "%b %d %T ", &tmnow) == 0)
    123 		return -1;
    124 	return fprintf(stream, "%s", buf);
    125 }
    126 #endif
    127 
    128 __printflike(3, 0) static int
    129 vlogprintf_r(struct logctx *ctx, FILE *stream, const char *fmt, va_list args)
    130 {
    131 	int len = 0, e;
    132 	va_list a;
    133 #ifndef SMALL
    134 	bool log_pid;
    135 #ifdef LOGERR_TAG
    136 	bool log_tag;
    137 #endif
    138 
    139 	if ((stream == stderr && ctx->log_opts & LOGERR_ERR_DATE) ||
    140 	    (stream != stderr && ctx->log_opts & LOGERR_LOG_DATE))
    141 	{
    142 		if ((e = logprintdate(stream)) == -1)
    143 			return -1;
    144 		len += e;
    145 	}
    146 
    147 #ifdef LOGERR_TAG
    148 	log_tag = ((stream == stderr && ctx->log_opts & LOGERR_ERR_TAG) ||
    149 	    (stream != stderr && ctx->log_opts & LOGERR_LOG_TAG));
    150 	if (log_tag) {
    151 		if (ctx->log_tag == NULL)
    152 			ctx->log_tag = getprogname();
    153 		if ((e = fprintf(stream, "%s", ctx->log_tag)) == -1)
    154 			return -1;
    155 		len += e;
    156 	}
    157 #endif
    158 
    159 	log_pid = ((stream == stderr && ctx->log_opts & LOGERR_ERR_PID) ||
    160 	    (stream != stderr && ctx->log_opts & LOGERR_LOG_PID));
    161 	if (log_pid) {
    162 		pid_t pid;
    163 
    164 		if (ctx->log_pid == 0)
    165 			pid = getpid();
    166 		else
    167 			pid = ctx->log_pid;
    168 		if ((e = fprintf(stream, "[%d]", (int)pid)) == -1)
    169 			return -1;
    170 		len += e;
    171 	}
    172 
    173 #ifdef LOGERR_TAG
    174 	if (log_tag || log_pid)
    175 #else
    176 	if (log_pid)
    177 #endif
    178 	{
    179 		if ((e = fprintf(stream, ": ")) == -1)
    180 			return -1;
    181 		len += e;
    182 	}
    183 #else
    184 	UNUSED(ctx);
    185 #endif
    186 
    187 	va_copy(a, args);
    188 	e = vfprintf(stream, fmt, a);
    189 	if (fputc('\n', stream) == EOF)
    190 		e = -1;
    191 	else if (e != -1)
    192 		e++;
    193 	va_end(a);
    194 
    195 	return e == -1 ? -1 : len + e;
    196 }
    197 
    198 /*
    199  * NetBSD's gcc has been modified to check for the non standard %m in printf
    200  * like functions and warn noisily about it that they should be marked as
    201  * syslog like instead.
    202  * This is all well and good, but our logger also goes via vfprintf and
    203  * when marked as a sysloglike funcion, gcc will then warn us that the
    204  * function should be printflike instead!
    205  * This creates an infinte loop of gcc warnings.
    206  * Until NetBSD solves this issue, we have to disable a gcc diagnostic
    207  * for our fully standards compliant code in the logger function.
    208  */
    209 #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5))
    210 #pragma GCC diagnostic push
    211 #pragma GCC diagnostic ignored "-Wmissing-format-attribute"
    212 #endif
    213 __printflike(2, 0) static int
    214 vlogmessage(int pri, const char *fmt, va_list args)
    215 {
    216 	struct logctx *ctx = &_logctx;
    217 	int len = 0;
    218 
    219 	if (ctx->log_fd != -1) {
    220 		pid_t pid = getpid();
    221 		char buf[LOGERR_SYSLOGBUF];
    222 		struct iovec iov[] = {
    223 			{ .iov_base = &pri, .iov_len = sizeof(pri) },
    224 			{ .iov_base = &pid, .iov_len = sizeof(pid) },
    225 			{ .iov_base = buf },
    226 		};
    227 
    228 		len = vsnprintf(buf, sizeof(buf), fmt, args);
    229 		if (len != -1) {
    230 			if ((size_t)len >= sizeof(buf))
    231 				len = (int)sizeof(buf) - 1;
    232 			iov[2].iov_len = (size_t)(len + 1);
    233 			struct msghdr msg = {
    234 				.msg_iov = iov,
    235 				.msg_iovlen = sizeof(iov) / sizeof(iov[0]),
    236 			};
    237 			len = (int)sendmsg(ctx->log_fd, &msg, MSG_EOR);
    238 		}
    239 		return len;
    240 	}
    241 
    242 	if (ctx->log_opts & LOGERR_ERR &&
    243 	    (pri <= LOG_ERR ||
    244 	    (!(ctx->log_opts & LOGERR_QUIET) && pri <= LOG_INFO) ||
    245 	    (ctx->log_opts & LOGERR_DEBUG && pri <= LOG_DEBUG)))
    246 		len = vlogprintf_r(ctx, stderr, fmt, args);
    247 
    248 #ifndef SMALL
    249 	if (ctx->log_file != NULL &&
    250 	    (pri != LOG_DEBUG || (ctx->log_opts & LOGERR_DEBUG)))
    251 		len = vlogprintf_r(ctx, ctx->log_file, fmt, args);
    252 #endif
    253 
    254 	if (ctx->log_opts & LOGERR_LOG)
    255 		vsyslog(pri, fmt, args);
    256 
    257 	return len;
    258 }
    259 #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5))
    260 #pragma GCC diagnostic pop
    261 #endif
    262 
    263 __printflike(2, 3) void
    264 logmessage(int pri, const char *fmt, ...)
    265 {
    266 	va_list args;
    267 
    268 	va_start(args, fmt);
    269 	vlogmessage(pri, fmt, args);
    270 	va_end(args);
    271 }
    272 
    273 __printflike(2, 0) static void
    274 vlogerrmessage(int pri, const char *fmt, va_list args)
    275 {
    276 	int _errno = errno;
    277 	char buf[1024];
    278 
    279 	vsnprintf(buf, sizeof(buf), fmt, args);
    280 	logmessage(pri, "%s: %s", buf, strerror(_errno));
    281 	errno = _errno;
    282 }
    283 
    284 __printflike(2, 3) void
    285 logerrmessage(int pri, const char *fmt, ...)
    286 {
    287 	va_list args;
    288 
    289 	va_start(args, fmt);
    290 	vlogerrmessage(pri, fmt, args);
    291 	va_end(args);
    292 }
    293 
    294 void
    295 log_debug(const char *fmt, ...)
    296 {
    297 	va_list args;
    298 
    299 	va_start(args, fmt);
    300 	vlogerrmessage(LOG_DEBUG, fmt, args);
    301 	va_end(args);
    302 }
    303 
    304 void
    305 log_debugx(const char *fmt, ...)
    306 {
    307 	va_list args;
    308 
    309 	va_start(args, fmt);
    310 	vlogmessage(LOG_DEBUG, fmt, args);
    311 	va_end(args);
    312 }
    313 
    314 void
    315 log_info(const char *fmt, ...)
    316 {
    317 	va_list args;
    318 
    319 	va_start(args, fmt);
    320 	vlogerrmessage(LOG_INFO, fmt, args);
    321 	va_end(args);
    322 }
    323 
    324 void
    325 log_infox(const char *fmt, ...)
    326 {
    327 	va_list args;
    328 
    329 	va_start(args, fmt);
    330 	vlogmessage(LOG_INFO, fmt, args);
    331 	va_end(args);
    332 }
    333 
    334 void
    335 log_warn(const char *fmt, ...)
    336 {
    337 	va_list args;
    338 
    339 	va_start(args, fmt);
    340 	vlogerrmessage(LOG_WARNING, fmt, args);
    341 	va_end(args);
    342 }
    343 
    344 void
    345 log_warnx(const char *fmt, ...)
    346 {
    347 	va_list args;
    348 
    349 	va_start(args, fmt);
    350 	vlogmessage(LOG_WARNING, fmt, args);
    351 	va_end(args);
    352 }
    353 
    354 void
    355 log_err(const char *fmt, ...)
    356 {
    357 	va_list args;
    358 
    359 	va_start(args, fmt);
    360 	vlogerrmessage(LOG_ERR, fmt, args);
    361 	va_end(args);
    362 }
    363 
    364 void
    365 log_errx(const char *fmt, ...)
    366 {
    367 	va_list args;
    368 
    369 	va_start(args, fmt);
    370 	vlogmessage(LOG_ERR, fmt, args);
    371 	va_end(args);
    372 }
    373 
    374 int
    375 loggetfd(void)
    376 {
    377 	struct logctx *ctx = &_logctx;
    378 
    379 	return ctx->log_fd;
    380 }
    381 
    382 void
    383 logsetfd(int fd)
    384 {
    385 	struct logctx *ctx = &_logctx;
    386 
    387 	ctx->log_fd = fd;
    388 	if (fd != -1)
    389 		closelog();
    390 #ifndef SMALL
    391 	if (fd != -1 && ctx->log_file != NULL) {
    392 		fclose(ctx->log_file);
    393 		ctx->log_file = NULL;
    394 	}
    395 #endif
    396 }
    397 
    398 ssize_t
    399 logreadfd(int fd)
    400 {
    401 	struct logctx *ctx = &_logctx;
    402 	int pri;
    403 	pid_t pid;
    404 	char buf[LOGERR_SYSLOGBUF] = { '\0' };
    405 	struct iovec iov[] = {
    406 		{ .iov_base = &pri, .iov_len = sizeof(pri) },
    407 		{ .iov_base = &pid, .iov_len = sizeof(pid) },
    408 		{ .iov_base = buf,  .iov_len = sizeof(buf) },
    409 	};
    410 	struct msghdr msg = {
    411 		.msg_iov = iov,
    412 		.msg_iovlen = sizeof(iov) / sizeof(iov[0])
    413 	};
    414 	ssize_t len;
    415 
    416 	len = recvmsg(fd, &msg, MSG_WAITALL);
    417 	if (len == -1 || len == 0)
    418 		return len;
    419 	/* Ensure we received the minimum and at least one character to log */
    420 	if ((size_t)len < sizeof(pri) + sizeof(pid) + 1 ||
    421 	    msg.msg_flags & MSG_TRUNC) {
    422 		errno = EMSGSIZE;
    423 		return -1;
    424 	}
    425 	/* Ensure what we receive is NUL terminated */
    426 	buf[(size_t)len - (sizeof(pri) + sizeof(pid)) - 1] = '\0';
    427 
    428 	ctx->log_pid = pid;
    429 	logmessage(pri, "%s", buf);
    430 	ctx->log_pid = 0;
    431 	return len;
    432 }
    433 
    434 unsigned int
    435 loggetopts(void)
    436 {
    437 	struct logctx *ctx = &_logctx;
    438 
    439 	return ctx->log_opts;
    440 }
    441 
    442 void
    443 logsetopts(unsigned int opts)
    444 {
    445 	struct logctx *ctx = &_logctx;
    446 
    447 	ctx->log_opts = opts;
    448 	setlogmask(LOG_UPTO(opts & LOGERR_DEBUG ? LOG_DEBUG : LOG_INFO));
    449 	if (!(ctx->log_opts & LOGERR_LOG))
    450 		closelog();
    451 }
    452 
    453 #ifdef LOGERR_TAG
    454 void
    455 logsettag(const char *tag)
    456 {
    457 #if !defined(SMALL)
    458 	struct logctx *ctx = &_logctx;
    459 
    460 	ctx->log_tag = tag;
    461 #else
    462 	UNUSED(tag);
    463 #endif
    464 }
    465 #endif
    466 
    467 int
    468 logopen(const char *path)
    469 {
    470 	struct logctx *ctx = &_logctx;
    471 	int opts = LOG_NDELAY; /* Ensure openlog gets a fd */
    472 
    473 	/* Cache timezone */
    474 	tzset();
    475 
    476 	(void)setvbuf(stderr, ctx->log_buf, _IOLBF, sizeof(ctx->log_buf));
    477 
    478 #ifndef SMALL
    479 	if (ctx->log_file != NULL) {
    480 		fclose(ctx->log_file);
    481 		ctx->log_file = NULL;
    482 	}
    483 #endif
    484 
    485 	if (ctx->log_opts & LOGERR_LOG_PID)
    486 		opts |= LOG_PID;
    487 	if (ctx->log_opts & LOGERR_LOG)
    488 		openlog(getprogname(), opts, LOGERR_SYSLOG_FACILITY);
    489 	if (path == NULL)
    490 		return 1;
    491 
    492 #ifndef SMALL
    493 	if ((ctx->log_file = fopen(path, "ae")) == NULL)
    494 		return -1;
    495 	setlinebuf(ctx->log_file);
    496 	return fileno(ctx->log_file);
    497 #else
    498 	errno = ENOTSUP;
    499 	return -1;
    500 #endif
    501 }
    502 
    503 void
    504 logclose(void)
    505 {
    506 #ifndef SMALL
    507 	struct logctx *ctx = &_logctx;
    508 #endif
    509 
    510 	closelog();
    511 #if defined(__linux__)
    512 	free(_logprog);
    513 	_logprog = NULL;
    514 #endif
    515 #ifndef SMALL
    516 	if (ctx->log_file == NULL)
    517 		return;
    518 	fclose(ctx->log_file);
    519 	ctx->log_file = NULL;
    520 #endif
    521 }
    522