1 /* SPDX-License-Identifier: BSD-2-Clause */
2 /*
3  * logerr: errx with logging
4  * Copyright (c) 2006-2021 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 
55 struct 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 
68 static 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). */
77 static char *_logprog;
78 static const char *
getprogname(void)79 getprogname(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 - */
106 static int
logprintdate(FILE * stream)107 logprintdate(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
vlogprintf_r(struct logctx * ctx,FILE * stream,const char * fmt,va_list args)127 vlogprintf_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
vlogmessage(int pri,const char * fmt,va_list args)212 vlogmessage(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
logmessage(int pri,const char * fmt,...)255 logmessage(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
vlogerrmessage(int pri,const char * fmt,va_list args)265 vlogerrmessage(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
logerrmessage(int pri,const char * fmt,...)276 logerrmessage(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 
285 void
log_debug(const char * fmt,...)286 log_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 
295 void
log_debugx(const char * fmt,...)296 log_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 
305 void
log_info(const char * fmt,...)306 log_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 
315 void
log_infox(const char * fmt,...)316 log_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 
325 void
log_warn(const char * fmt,...)326 log_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 
335 void
log_warnx(const char * fmt,...)336 log_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 
345 void
log_err(const char * fmt,...)346 log_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 
355 void
log_errx(const char * fmt,...)356 log_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 
365 int
loggetfd(void)366 loggetfd(void)
367 {
368 	struct logctx *ctx = &_logctx;
369 
370 	return ctx->log_fd;
371 }
372 
373 void
logsetfd(int fd)374 logsetfd(int fd)
375 {
376 	struct logctx *ctx = &_logctx;
377 
378 	ctx->log_fd = fd;
379 #ifndef SMALL
380 	if (fd != -1 && ctx->log_file != NULL) {
381 		fclose(ctx->log_file);
382 		ctx->log_file = NULL;
383 	}
384 #endif
385 }
386 
387 int
logreadfd(int fd)388 logreadfd(int fd)
389 {
390 	struct logctx *ctx = &_logctx;
391 	char buf[LOGERR_SYSLOGBUF];
392 	int len, pri;
393 
394 	len = (int)read(fd, buf, sizeof(buf));
395 	if (len == -1)
396 		return -1;
397 
398 	/* Ensure we have pri, pid and a terminator */
399 	if (len < (int)(sizeof(pri) + sizeof(pid_t) + 1) ||
400 	    buf[len - 1] != '\0')
401 	{
402 		errno = EINVAL;
403 		return -1;
404 	}
405 
406 	memcpy(&pri, buf, sizeof(pri));
407 	memcpy(&ctx->log_pid, buf + sizeof(pri), sizeof(ctx->log_pid));
408 	logmessage(pri, "%s", buf + sizeof(pri) + sizeof(ctx->log_pid));
409 	ctx->log_pid = 0;
410 	return len;
411 }
412 
413 unsigned int
loggetopts(void)414 loggetopts(void)
415 {
416 	struct logctx *ctx = &_logctx;
417 
418 	return ctx->log_opts;
419 }
420 
421 void
logsetopts(unsigned int opts)422 logsetopts(unsigned int opts)
423 {
424 	struct logctx *ctx = &_logctx;
425 
426 	ctx->log_opts = opts;
427 	setlogmask(LOG_UPTO(opts & LOGERR_DEBUG ? LOG_DEBUG : LOG_INFO));
428 }
429 
430 #ifdef LOGERR_TAG
431 void
logsettag(const char * tag)432 logsettag(const char *tag)
433 {
434 #if !defined(SMALL)
435 	struct logctx *ctx = &_logctx;
436 
437 	ctx->log_tag = tag;
438 #else
439 	UNUSED(tag);
440 #endif
441 }
442 #endif
443 
444 int
logopen(const char * path)445 logopen(const char *path)
446 {
447 	struct logctx *ctx = &_logctx;
448 	int opts = 0;
449 
450 	/* Cache timezone */
451 	tzset();
452 
453 	(void)setvbuf(stderr, ctx->log_buf, _IOLBF, sizeof(ctx->log_buf));
454 
455 #ifndef SMALL
456 	if (ctx->log_file != NULL) {
457 		fclose(ctx->log_file);
458 		ctx->log_file = NULL;
459 	}
460 #endif
461 
462 	if (ctx->log_opts & LOGERR_LOG_PID)
463 		opts |= LOG_PID;
464 	openlog(getprogname(), opts, LOGERR_SYSLOG_FACILITY);
465 	if (path == NULL)
466 		return 1;
467 
468 #ifndef SMALL
469 	if ((ctx->log_file = fopen(path, "ae")) == NULL)
470 		return -1;
471 	setlinebuf(ctx->log_file);
472 	return fileno(ctx->log_file);
473 #else
474 	errno = ENOTSUP;
475 	return -1;
476 #endif
477 }
478 
479 void
logclose(void)480 logclose(void)
481 {
482 #ifndef SMALL
483 	struct logctx *ctx = &_logctx;
484 #endif
485 
486 	closelog();
487 #if defined(__linux__)
488 	free(_logprog);
489 	_logprog = NULL;
490 #endif
491 #ifndef SMALL
492 	if (ctx->log_file == NULL)
493 		return;
494 	fclose(ctx->log_file);
495 	ctx->log_file = NULL;
496 #endif
497 }
498