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