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 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 * 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 366 loggetfd(void) 367 { 368 struct logctx *ctx = &_logctx; 369 370 return ctx->log_fd; 371 } 372 373 void 374 logsetfd(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 389 int 390 logreadfd(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 415 unsigned int 416 loggetopts(void) 417 { 418 struct logctx *ctx = &_logctx; 419 420 return ctx->log_opts; 421 } 422 423 void 424 logsetopts(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 433 void 434 logsettag(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 446 int 447 logopen(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 481 void 482 logclose(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