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