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