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