Home | History | Annotate | Line # | Download | only in src
      1 /*
      2  * logerr: errx with logging
      3  * SPDX-License-Identifier: BSD-2-Clause
      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 <stdarg.h>
     34 #include <stdbool.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 "config.h" // IWYU pragma: keep
     43 #include "logerr.h"
     44 
     45 #ifndef LOGERR_SYSLOG_FACILITY
     46 #define LOGERR_SYSLOG_FACILITY LOG_DAEMON
     47 #endif
     48 
     49 #ifdef SMALL
     50 #undef LOGERR_TAG
     51 #endif
     52 
     53 /* syslog protocol is 1k message max, RFC 3164 section 4.1 */
     54 #define LOGERR_SYSLOGBUF 1024 + sizeof(int) + sizeof(pid_t)
     55 
     56 #define UNUSED(a)	 (void)(a)
     57 
     58 struct logctx {
     59 	char log_buf[BUFSIZ];
     60 	unsigned int log_opts;
     61 	int log_fd;
     62 	pid_t log_pid;
     63 #ifndef SMALL
     64 	FILE *log_file;
     65 #ifdef LOGERR_TAG
     66 	const char *log_tag;
     67 #endif
     68 #endif
     69 };
     70 
     71 static struct logctx _logctx = {
     72 	/* syslog style, but without the hostname or tag. */
     73 	.log_opts = LOGERR_LOG | LOGERR_LOG_DATE | LOGERR_LOG_PID,
     74 	.log_fd = -1,
     75 	.log_pid = 0,
     76 };
     77 
     78 #ifndef SMALL
     79 /* Write the time, syslog style. month day time - */
     80 static int
     81 logprintdate(FILE *stream)
     82 {
     83 	struct timeval tv;
     84 	time_t now;
     85 	struct tm tmnow;
     86 	char buf[32];
     87 
     88 	if (gettimeofday(&tv, NULL) == -1)
     89 		return -1;
     90 
     91 	now = tv.tv_sec;
     92 	if (localtime_r(&now, &tmnow) == NULL)
     93 		return -1;
     94 	if (strftime(buf, sizeof(buf), "%b %d %T ", &tmnow) == 0)
     95 		return -1;
     96 	return fprintf(stream, "%s", buf);
     97 }
     98 #endif
     99 
    100 __printflike(3, 0) static int vlogprintf_r(struct logctx *ctx, FILE *stream,
    101     const char *fmt, va_list args)
    102 {
    103 	int len = 0, e;
    104 	va_list a;
    105 #ifndef SMALL
    106 	bool log_pid;
    107 #ifdef LOGERR_TAG
    108 	bool log_tag;
    109 #endif
    110 
    111 	if ((stream == stderr && ctx->log_opts & LOGERR_ERR_DATE) ||
    112 	    (stream != stderr && ctx->log_opts & LOGERR_LOG_DATE)) {
    113 		if ((e = logprintdate(stream)) == -1)
    114 			return -1;
    115 		len += e;
    116 	}
    117 
    118 #ifdef LOGERR_TAG
    119 	log_tag = ((stream == stderr && ctx->log_opts & LOGERR_ERR_TAG) ||
    120 	    (stream != stderr && ctx->log_opts & LOGERR_LOG_TAG));
    121 	if (log_tag) {
    122 		if (ctx->log_tag == NULL)
    123 			ctx->log_tag = getprogname();
    124 		if ((e = fprintf(stream, "%s", ctx->log_tag)) == -1)
    125 			return -1;
    126 		len += e;
    127 	}
    128 #endif
    129 
    130 	log_pid = ((stream == stderr && ctx->log_opts & LOGERR_ERR_PID) ||
    131 	    (stream != stderr && ctx->log_opts & LOGERR_LOG_PID));
    132 	if (log_pid) {
    133 		pid_t pid;
    134 
    135 		if (ctx->log_pid == 0)
    136 			pid = getpid();
    137 		else
    138 			pid = ctx->log_pid;
    139 		if ((e = fprintf(stream, "[%d]", (int)pid)) == -1)
    140 			return -1;
    141 		len += e;
    142 	}
    143 
    144 #ifdef LOGERR_TAG
    145 	if (log_tag || log_pid)
    146 #else
    147 	if (log_pid)
    148 #endif
    149 	{
    150 		if ((e = fprintf(stream, ": ")) == -1)
    151 			return -1;
    152 		len += e;
    153 	}
    154 #else
    155 	UNUSED(ctx);
    156 #endif
    157 
    158 	va_copy(a, args);
    159 	e = vfprintf(stream, fmt, a);
    160 	if (fputc('\n', stream) == EOF)
    161 		e = -1;
    162 	else if (e != -1)
    163 		e++;
    164 	va_end(a);
    165 
    166 	return e == -1 ? -1 : len + e;
    167 }
    168 
    169 /*
    170  * NetBSD's gcc has been modified to check for the non standard %m in printf
    171  * like functions and warn noisily about it that they should be marked as
    172  * syslog like instead.
    173  * This is all well and good, but our logger also goes via vfprintf and
    174  * when marked as a sysloglike funcion, gcc will then warn us that the
    175  * function should be printflike instead!
    176  * This creates an infinte loop of gcc warnings.
    177  * Until NetBSD solves this issue, we have to disable a gcc diagnostic
    178  * for our fully standards compliant code in the logger function.
    179  */
    180 #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5))
    181 #pragma GCC diagnostic push
    182 #pragma GCC diagnostic ignored "-Wmissing-format-attribute"
    183 #endif
    184 __printflike(2, 0) static int vlogmessage(int pri, const char *fmt,
    185     va_list args)
    186 {
    187 	struct logctx *ctx = &_logctx;
    188 	int len = 0;
    189 
    190 	if (ctx->log_fd != -1) {
    191 		pid_t pid = getpid();
    192 		char buf[LOGERR_SYSLOGBUF];
    193 		size_t mlen = 0;
    194 		struct iovec iov[] = {
    195 			{ .iov_base = &pri, .iov_len = sizeof(pri) },
    196 			{ .iov_base = &pid, .iov_len = sizeof(pid) },
    197 			{ .iov_base = &mlen, .iov_len = sizeof(mlen) },
    198 			{ .iov_base = buf },
    199 		};
    200 
    201 		len = vsnprintf(buf, sizeof(buf), fmt, args);
    202 		if (len != -1) {
    203 			mlen = (size_t)len + 1;
    204 			if (mlen > sizeof(buf))
    205 				mlen = sizeof(buf);
    206 			iov[3].iov_len = mlen;
    207 			struct msghdr msg = {
    208 				.msg_iov = iov,
    209 				.msg_iovlen = sizeof(iov) / sizeof(iov[0]),
    210 			};
    211 			len = (int)sendmsg(ctx->log_fd, &msg, 0);
    212 		}
    213 		return len;
    214 	}
    215 
    216 	if (ctx->log_opts & LOGERR_ERR &&
    217 	    (pri <= LOG_ERR ||
    218 		(!(ctx->log_opts & LOGERR_QUIET) && pri <= LOG_INFO) ||
    219 		(ctx->log_opts & LOGERR_DEBUG && pri <= LOG_DEBUG)))
    220 		len = vlogprintf_r(ctx, stderr, fmt, args);
    221 
    222 #ifndef SMALL
    223 	if (ctx->log_file != NULL &&
    224 	    (pri != LOG_DEBUG || (ctx->log_opts & LOGERR_DEBUG)))
    225 		len = vlogprintf_r(ctx, ctx->log_file, fmt, args);
    226 #endif
    227 
    228 	if (ctx->log_opts & LOGERR_LOG)
    229 		vsyslog(pri, fmt, args);
    230 
    231 	return len;
    232 }
    233 #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5))
    234 #pragma GCC diagnostic pop
    235 #endif
    236 
    237 __printflike(2, 3) void logmessage(int pri, const char *fmt, ...)
    238 {
    239 	va_list args;
    240 
    241 	va_start(args, fmt);
    242 	vlogmessage(pri, fmt, args);
    243 	va_end(args);
    244 }
    245 
    246 __printflike(2, 0) static void vlogerrmessage(int pri, const char *fmt,
    247     va_list args)
    248 {
    249 	int _errno = errno;
    250 	char buf[1024];
    251 
    252 	vsnprintf(buf, sizeof(buf), fmt, args);
    253 	logmessage(pri, "%s: %s", buf, strerror(_errno));
    254 	errno = _errno;
    255 }
    256 
    257 __printflike(2, 3) void logerrmessage(int pri, const char *fmt, ...)
    258 {
    259 	va_list args;
    260 
    261 	va_start(args, fmt);
    262 	vlogerrmessage(pri, fmt, args);
    263 	va_end(args);
    264 }
    265 
    266 void
    267 log_debug(const char *fmt, ...)
    268 {
    269 	va_list args;
    270 
    271 	va_start(args, fmt);
    272 	vlogerrmessage(LOG_DEBUG, fmt, args);
    273 	va_end(args);
    274 }
    275 
    276 void
    277 log_debugx(const char *fmt, ...)
    278 {
    279 	va_list args;
    280 
    281 	va_start(args, fmt);
    282 	vlogmessage(LOG_DEBUG, fmt, args);
    283 	va_end(args);
    284 }
    285 
    286 void
    287 log_info(const char *fmt, ...)
    288 {
    289 	va_list args;
    290 
    291 	va_start(args, fmt);
    292 	vlogerrmessage(LOG_INFO, fmt, args);
    293 	va_end(args);
    294 }
    295 
    296 void
    297 log_infox(const char *fmt, ...)
    298 {
    299 	va_list args;
    300 
    301 	va_start(args, fmt);
    302 	vlogmessage(LOG_INFO, fmt, args);
    303 	va_end(args);
    304 }
    305 
    306 void
    307 log_warn(const char *fmt, ...)
    308 {
    309 	va_list args;
    310 
    311 	va_start(args, fmt);
    312 	vlogerrmessage(LOG_WARNING, fmt, args);
    313 	va_end(args);
    314 }
    315 
    316 void
    317 log_warnx(const char *fmt, ...)
    318 {
    319 	va_list args;
    320 
    321 	va_start(args, fmt);
    322 	vlogmessage(LOG_WARNING, fmt, args);
    323 	va_end(args);
    324 }
    325 
    326 void
    327 log_err(const char *fmt, ...)
    328 {
    329 	va_list args;
    330 
    331 	va_start(args, fmt);
    332 	vlogerrmessage(LOG_ERR, fmt, args);
    333 	va_end(args);
    334 }
    335 
    336 void
    337 log_errx(const char *fmt, ...)
    338 {
    339 	va_list args;
    340 
    341 	va_start(args, fmt);
    342 	vlogmessage(LOG_ERR, fmt, args);
    343 	va_end(args);
    344 }
    345 
    346 int
    347 loggetfd(void)
    348 {
    349 	struct logctx *ctx = &_logctx;
    350 
    351 	return ctx->log_fd;
    352 }
    353 
    354 void
    355 logsetfd(int fd)
    356 {
    357 	struct logctx *ctx = &_logctx;
    358 
    359 	ctx->log_fd = fd;
    360 	if (fd != -1)
    361 		closelog();
    362 #ifndef SMALL
    363 	if (fd != -1 && ctx->log_file != NULL) {
    364 		(void)fclose(ctx->log_file);
    365 		ctx->log_file = NULL;
    366 	}
    367 #endif
    368 }
    369 
    370 ssize_t
    371 logreadfd(int fd)
    372 {
    373 	struct logctx *ctx = &_logctx;
    374 	int pri;
    375 	pid_t pid;
    376 	char buf[LOGERR_SYSLOGBUF] = { '\0' };
    377 	size_t mlen;
    378 	struct iovec iov[] = {
    379 		{ .iov_base = &pri, .iov_len = sizeof(pri) },
    380 		{ .iov_base = &pid, .iov_len = sizeof(pid) },
    381 		{ .iov_base = &mlen, .iov_len = sizeof(mlen) },
    382 	};
    383 	struct msghdr msg = { .msg_iov = iov,
    384 		.msg_iovlen = sizeof(iov) / sizeof(iov[0]) };
    385 	ssize_t len;
    386 
    387 	len = recvmsg(fd, &msg, MSG_WAITALL);
    388 	if (len == -1 || len == 0)
    389 		return len;
    390 	if ((size_t)len != sizeof(pri) + sizeof(pid) + sizeof(mlen)) {
    391 		errno = EMSGSIZE;
    392 		return -1;
    393 	}
    394 	if (mlen > sizeof(buf)) {
    395 		errno = ENOBUFS;
    396 		return -1;
    397 	}
    398 
    399 	len = recv(fd, buf, mlen, MSG_WAITALL);
    400 	if (len == -1)
    401 		return -1;
    402 	if ((size_t)len != mlen) {
    403 		errno = EINVAL;
    404 		return -1;
    405 	}
    406 
    407 	/* Ensure what we receive is NUL terminated */
    408 	buf[mlen == sizeof(buf) ? mlen - 1 : mlen] = '\0';
    409 
    410 	ctx->log_pid = pid;
    411 	logmessage(pri, "%s", buf);
    412 	ctx->log_pid = 0;
    413 	return len;
    414 }
    415 
    416 unsigned int
    417 loggetopts(void)
    418 {
    419 	struct logctx *ctx = &_logctx;
    420 
    421 	return ctx->log_opts;
    422 }
    423 
    424 void
    425 logsetopts(unsigned int opts)
    426 {
    427 	struct logctx *ctx = &_logctx;
    428 
    429 	ctx->log_opts = opts;
    430 	setlogmask(LOG_UPTO(opts & LOGERR_DEBUG ? LOG_DEBUG : LOG_INFO));
    431 	if (!(ctx->log_opts & LOGERR_LOG))
    432 		closelog();
    433 }
    434 
    435 #ifdef LOGERR_TAG
    436 void
    437 logsettag(const char *tag)
    438 {
    439 #if !defined(SMALL)
    440 	struct logctx *ctx = &_logctx;
    441 
    442 	ctx->log_tag = tag;
    443 #else
    444 	UNUSED(tag);
    445 #endif
    446 }
    447 #endif
    448 
    449 int
    450 logopen(const char *path)
    451 {
    452 	struct logctx *ctx = &_logctx;
    453 	int opts = LOG_NDELAY; /* Ensure openlog gets a fd */
    454 
    455 	/* Cache timezone */
    456 	tzset();
    457 
    458 	(void)setvbuf(stderr, ctx->log_buf, _IOLBF, sizeof(ctx->log_buf));
    459 
    460 #ifndef SMALL
    461 	if (ctx->log_file != NULL) {
    462 		fclose(ctx->log_file);
    463 		ctx->log_file = NULL;
    464 	}
    465 #endif
    466 
    467 	if (ctx->log_opts & LOGERR_LOG_PID)
    468 		opts |= LOG_PID;
    469 	if (ctx->log_opts & LOGERR_LOG)
    470 		openlog(getprogname(), opts, LOGERR_SYSLOG_FACILITY);
    471 	if (path == NULL)
    472 		return 1;
    473 
    474 #ifndef SMALL
    475 	if ((ctx->log_file = fopen(path, "ae")) == NULL)
    476 		return -1;
    477 	setlinebuf(ctx->log_file);
    478 	return fileno(ctx->log_file);
    479 #else
    480 	errno = ENOTSUP;
    481 	return -1;
    482 #endif
    483 }
    484 
    485 void
    486 logclose(void)
    487 {
    488 #ifndef SMALL
    489 	struct logctx *ctx = &_logctx;
    490 #endif
    491 
    492 	closelog();
    493 #ifndef SMALL
    494 	if (ctx->log_file == NULL)
    495 		return;
    496 	fclose(ctx->log_file);
    497 	ctx->log_file = NULL;
    498 #endif
    499 }
    500