1 /* SPDX-License-Identifier: BSD-2-Clause */ 2 /* 3 * logerr: errx with logging 4 * Copyright (c) 2006-2025 Roy Marples <roy (at) 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/socket.h> 30 #include <sys/time.h> 31 32 #include <errno.h> 33 #include <stdbool.h> 34 #include <stdarg.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <syslog.h> 39 #include <time.h> 40 #include <unistd.h> 41 42 #include "logerr.h" 43 44 #ifndef LOGERR_SYSLOG_FACILITY 45 #define LOGERR_SYSLOG_FACILITY LOG_DAEMON 46 #endif 47 48 #ifdef SMALL 49 #undef LOGERR_TAG 50 #endif 51 52 /* syslog protocol is 1k message max, RFC 3164 section 4.1 */ 53 #define LOGERR_SYSLOGBUF 1024 + sizeof(int) + sizeof(pid_t) 54 55 #define UNUSED(a) (void)(a) 56 57 struct logctx { 58 char log_buf[BUFSIZ]; 59 unsigned int log_opts; 60 int log_fd; 61 pid_t log_pid; 62 #ifndef SMALL 63 FILE *log_file; 64 #ifdef LOGERR_TAG 65 const char *log_tag; 66 #endif 67 #endif 68 }; 69 70 static struct logctx _logctx = { 71 /* syslog style, but without the hostname or tag. */ 72 .log_opts = LOGERR_LOG | LOGERR_LOG_DATE | LOGERR_LOG_PID, 73 .log_fd = -1, 74 .log_pid = 0, 75 }; 76 77 #if defined(__linux__) 78 /* Poor man's getprogname(3). */ 79 static char *_logprog; 80 static const char * 81 getprogname(void) 82 { 83 const char *p; 84 85 /* Use PATH_MAX + 1 to avoid truncation. */ 86 if (_logprog == NULL) { 87 /* readlink(2) does not append a NULL byte, 88 * so zero the buffer. */ 89 if ((_logprog = calloc(1, PATH_MAX + 1)) == NULL) 90 return NULL; 91 if (readlink("/proc/self/exe", _logprog, PATH_MAX + 1) == -1) { 92 free(_logprog); 93 _logprog = NULL; 94 return NULL; 95 } 96 } 97 if (_logprog[0] == '[') 98 return NULL; 99 p = strrchr(_logprog, '/'); 100 if (p == NULL) 101 return _logprog; 102 return p + 1; 103 } 104 #endif 105 106 #ifndef SMALL 107 /* Write the time, syslog style. month day time - */ 108 static int 109 logprintdate(FILE *stream) 110 { 111 struct timeval tv; 112 time_t now; 113 struct tm tmnow; 114 char buf[32]; 115 116 if (gettimeofday(&tv, NULL) == -1) 117 return -1; 118 119 now = tv.tv_sec; 120 if (localtime_r(&now, &tmnow) == NULL) 121 return -1; 122 if (strftime(buf, sizeof(buf), "%b %d %T ", &tmnow) == 0) 123 return -1; 124 return fprintf(stream, "%s", buf); 125 } 126 #endif 127 128 __printflike(3, 0) static int 129 vlogprintf_r(struct logctx *ctx, FILE *stream, const char *fmt, va_list args) 130 { 131 int len = 0, e; 132 va_list a; 133 #ifndef SMALL 134 bool log_pid; 135 #ifdef LOGERR_TAG 136 bool log_tag; 137 #endif 138 139 if ((stream == stderr && ctx->log_opts & LOGERR_ERR_DATE) || 140 (stream != stderr && ctx->log_opts & LOGERR_LOG_DATE)) 141 { 142 if ((e = logprintdate(stream)) == -1) 143 return -1; 144 len += e; 145 } 146 147 #ifdef LOGERR_TAG 148 log_tag = ((stream == stderr && ctx->log_opts & LOGERR_ERR_TAG) || 149 (stream != stderr && ctx->log_opts & LOGERR_LOG_TAG)); 150 if (log_tag) { 151 if (ctx->log_tag == NULL) 152 ctx->log_tag = getprogname(); 153 if ((e = fprintf(stream, "%s", ctx->log_tag)) == -1) 154 return -1; 155 len += e; 156 } 157 #endif 158 159 log_pid = ((stream == stderr && ctx->log_opts & LOGERR_ERR_PID) || 160 (stream != stderr && ctx->log_opts & LOGERR_LOG_PID)); 161 if (log_pid) { 162 pid_t pid; 163 164 if (ctx->log_pid == 0) 165 pid = getpid(); 166 else 167 pid = ctx->log_pid; 168 if ((e = fprintf(stream, "[%d]", (int)pid)) == -1) 169 return -1; 170 len += e; 171 } 172 173 #ifdef LOGERR_TAG 174 if (log_tag || log_pid) 175 #else 176 if (log_pid) 177 #endif 178 { 179 if ((e = fprintf(stream, ": ")) == -1) 180 return -1; 181 len += e; 182 } 183 #else 184 UNUSED(ctx); 185 #endif 186 187 va_copy(a, args); 188 e = vfprintf(stream, fmt, a); 189 if (fputc('\n', stream) == EOF) 190 e = -1; 191 else if (e != -1) 192 e++; 193 va_end(a); 194 195 return e == -1 ? -1 : len + e; 196 } 197 198 /* 199 * NetBSD's gcc has been modified to check for the non standard %m in printf 200 * like functions and warn noisily about it that they should be marked as 201 * syslog like instead. 202 * This is all well and good, but our logger also goes via vfprintf and 203 * when marked as a sysloglike funcion, gcc will then warn us that the 204 * function should be printflike instead! 205 * This creates an infinte loop of gcc warnings. 206 * Until NetBSD solves this issue, we have to disable a gcc diagnostic 207 * for our fully standards compliant code in the logger function. 208 */ 209 #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5)) 210 #pragma GCC diagnostic push 211 #pragma GCC diagnostic ignored "-Wmissing-format-attribute" 212 #endif 213 __printflike(2, 0) static int 214 vlogmessage(int pri, const char *fmt, va_list args) 215 { 216 struct logctx *ctx = &_logctx; 217 int len = 0; 218 219 if (ctx->log_fd != -1) { 220 pid_t pid = getpid(); 221 char buf[LOGERR_SYSLOGBUF]; 222 struct iovec iov[] = { 223 { .iov_base = &pri, .iov_len = sizeof(pri) }, 224 { .iov_base = &pid, .iov_len = sizeof(pid) }, 225 { .iov_base = buf }, 226 }; 227 228 len = vsnprintf(buf, sizeof(buf), fmt, args); 229 if (len != -1) { 230 if ((size_t)len >= sizeof(buf)) 231 len = (int)sizeof(buf) - 1; 232 iov[2].iov_len = (size_t)(len + 1); 233 struct msghdr msg = { 234 .msg_iov = iov, 235 .msg_iovlen = sizeof(iov) / sizeof(iov[0]), 236 }; 237 len = (int)sendmsg(ctx->log_fd, &msg, MSG_EOR); 238 } 239 return len; 240 } 241 242 if (ctx->log_opts & LOGERR_ERR && 243 (pri <= LOG_ERR || 244 (!(ctx->log_opts & LOGERR_QUIET) && pri <= LOG_INFO) || 245 (ctx->log_opts & LOGERR_DEBUG && pri <= LOG_DEBUG))) 246 len = vlogprintf_r(ctx, stderr, fmt, args); 247 248 #ifndef SMALL 249 if (ctx->log_file != NULL && 250 (pri != LOG_DEBUG || (ctx->log_opts & LOGERR_DEBUG))) 251 len = vlogprintf_r(ctx, ctx->log_file, fmt, args); 252 #endif 253 254 if (ctx->log_opts & LOGERR_LOG) 255 vsyslog(pri, fmt, args); 256 257 return len; 258 } 259 #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5)) 260 #pragma GCC diagnostic pop 261 #endif 262 263 __printflike(2, 3) void 264 logmessage(int pri, const char *fmt, ...) 265 { 266 va_list args; 267 268 va_start(args, fmt); 269 vlogmessage(pri, fmt, args); 270 va_end(args); 271 } 272 273 __printflike(2, 0) static void 274 vlogerrmessage(int pri, const char *fmt, va_list args) 275 { 276 int _errno = errno; 277 char buf[1024]; 278 279 vsnprintf(buf, sizeof(buf), fmt, args); 280 logmessage(pri, "%s: %s", buf, strerror(_errno)); 281 errno = _errno; 282 } 283 284 __printflike(2, 3) void 285 logerrmessage(int pri, const char *fmt, ...) 286 { 287 va_list args; 288 289 va_start(args, fmt); 290 vlogerrmessage(pri, fmt, args); 291 va_end(args); 292 } 293 294 void 295 log_debug(const char *fmt, ...) 296 { 297 va_list args; 298 299 va_start(args, fmt); 300 vlogerrmessage(LOG_DEBUG, fmt, args); 301 va_end(args); 302 } 303 304 void 305 log_debugx(const char *fmt, ...) 306 { 307 va_list args; 308 309 va_start(args, fmt); 310 vlogmessage(LOG_DEBUG, fmt, args); 311 va_end(args); 312 } 313 314 void 315 log_info(const char *fmt, ...) 316 { 317 va_list args; 318 319 va_start(args, fmt); 320 vlogerrmessage(LOG_INFO, fmt, args); 321 va_end(args); 322 } 323 324 void 325 log_infox(const char *fmt, ...) 326 { 327 va_list args; 328 329 va_start(args, fmt); 330 vlogmessage(LOG_INFO, fmt, args); 331 va_end(args); 332 } 333 334 void 335 log_warn(const char *fmt, ...) 336 { 337 va_list args; 338 339 va_start(args, fmt); 340 vlogerrmessage(LOG_WARNING, fmt, args); 341 va_end(args); 342 } 343 344 void 345 log_warnx(const char *fmt, ...) 346 { 347 va_list args; 348 349 va_start(args, fmt); 350 vlogmessage(LOG_WARNING, fmt, args); 351 va_end(args); 352 } 353 354 void 355 log_err(const char *fmt, ...) 356 { 357 va_list args; 358 359 va_start(args, fmt); 360 vlogerrmessage(LOG_ERR, fmt, args); 361 va_end(args); 362 } 363 364 void 365 log_errx(const char *fmt, ...) 366 { 367 va_list args; 368 369 va_start(args, fmt); 370 vlogmessage(LOG_ERR, fmt, args); 371 va_end(args); 372 } 373 374 int 375 loggetfd(void) 376 { 377 struct logctx *ctx = &_logctx; 378 379 return ctx->log_fd; 380 } 381 382 void 383 logsetfd(int fd) 384 { 385 struct logctx *ctx = &_logctx; 386 387 ctx->log_fd = fd; 388 if (fd != -1) 389 closelog(); 390 #ifndef SMALL 391 if (fd != -1 && ctx->log_file != NULL) { 392 fclose(ctx->log_file); 393 ctx->log_file = NULL; 394 } 395 #endif 396 } 397 398 ssize_t 399 logreadfd(int fd) 400 { 401 struct logctx *ctx = &_logctx; 402 int pri; 403 pid_t pid; 404 char buf[LOGERR_SYSLOGBUF] = { '\0' }; 405 struct iovec iov[] = { 406 { .iov_base = &pri, .iov_len = sizeof(pri) }, 407 { .iov_base = &pid, .iov_len = sizeof(pid) }, 408 { .iov_base = buf, .iov_len = sizeof(buf) }, 409 }; 410 struct msghdr msg = { 411 .msg_iov = iov, 412 .msg_iovlen = sizeof(iov) / sizeof(iov[0]) 413 }; 414 ssize_t len; 415 416 len = recvmsg(fd, &msg, MSG_WAITALL); 417 if (len == -1 || len == 0) 418 return len; 419 /* Ensure we received the minimum and at least one character to log */ 420 if ((size_t)len < sizeof(pri) + sizeof(pid) + 1 || 421 msg.msg_flags & MSG_TRUNC) { 422 errno = EMSGSIZE; 423 return -1; 424 } 425 /* Ensure what we receive is NUL terminated */ 426 buf[(size_t)len - (sizeof(pri) + sizeof(pid)) - 1] = '\0'; 427 428 ctx->log_pid = pid; 429 logmessage(pri, "%s", buf); 430 ctx->log_pid = 0; 431 return len; 432 } 433 434 unsigned int 435 loggetopts(void) 436 { 437 struct logctx *ctx = &_logctx; 438 439 return ctx->log_opts; 440 } 441 442 void 443 logsetopts(unsigned int opts) 444 { 445 struct logctx *ctx = &_logctx; 446 447 ctx->log_opts = opts; 448 setlogmask(LOG_UPTO(opts & LOGERR_DEBUG ? LOG_DEBUG : LOG_INFO)); 449 if (!(ctx->log_opts & LOGERR_LOG)) 450 closelog(); 451 } 452 453 #ifdef LOGERR_TAG 454 void 455 logsettag(const char *tag) 456 { 457 #if !defined(SMALL) 458 struct logctx *ctx = &_logctx; 459 460 ctx->log_tag = tag; 461 #else 462 UNUSED(tag); 463 #endif 464 } 465 #endif 466 467 int 468 logopen(const char *path) 469 { 470 struct logctx *ctx = &_logctx; 471 int opts = LOG_NDELAY; /* Ensure openlog gets a fd */ 472 473 /* Cache timezone */ 474 tzset(); 475 476 (void)setvbuf(stderr, ctx->log_buf, _IOLBF, sizeof(ctx->log_buf)); 477 478 #ifndef SMALL 479 if (ctx->log_file != NULL) { 480 fclose(ctx->log_file); 481 ctx->log_file = NULL; 482 } 483 #endif 484 485 if (ctx->log_opts & LOGERR_LOG_PID) 486 opts |= LOG_PID; 487 if (ctx->log_opts & LOGERR_LOG) 488 openlog(getprogname(), opts, LOGERR_SYSLOG_FACILITY); 489 if (path == NULL) 490 return 1; 491 492 #ifndef SMALL 493 if ((ctx->log_file = fopen(path, "ae")) == NULL) 494 return -1; 495 setlinebuf(ctx->log_file); 496 return fileno(ctx->log_file); 497 #else 498 errno = ENOTSUP; 499 return -1; 500 #endif 501 } 502 503 void 504 logclose(void) 505 { 506 #ifndef SMALL 507 struct logctx *ctx = &_logctx; 508 #endif 509 510 closelog(); 511 #if defined(__linux__) 512 free(_logprog); 513 _logprog = NULL; 514 #endif 515 #ifndef SMALL 516 if (ctx->log_file == NULL) 517 return; 518 fclose(ctx->log_file); 519 ctx->log_file = NULL; 520 #endif 521 } 522