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