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