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