1 /* $NetBSD: logging.c,v 1.1.1.1 2009/04/12 15:33:47 christos Exp $ */ 2 3 /* 4 * Copyright (C) 2004, 2005, 2008 Internet Systems Consortium, Inc. ("ISC") 5 * Copyright (C) 1996-1999, 2001, 2003 Internet Software Consortium. 6 * 7 * Permission to use, copy, modify, and/or distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH 12 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 13 * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, 14 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 15 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 16 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 * PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 #if !defined(LINT) && !defined(CODECENTER) 21 static const char rcsid[] = "Id: logging.c,v 1.9 2008/11/14 02:36:51 marka Exp"; 22 #endif /* not lint */ 23 24 #include "port_before.h" 25 26 #include <sys/types.h> 27 #include <sys/time.h> 28 #include <sys/stat.h> 29 30 #include <fcntl.h> 31 #include <limits.h> 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <stdarg.h> 36 #include <syslog.h> 37 #include <errno.h> 38 #include <time.h> 39 #include <unistd.h> 40 41 #include <isc/assertions.h> 42 #include <isc/logging.h> 43 #include <isc/memcluster.h> 44 #include <isc/misc.h> 45 46 #include "port_after.h" 47 48 #include "logging_p.h" 49 50 static const int syslog_priority[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE, 51 LOG_WARNING, LOG_ERR, LOG_CRIT }; 52 53 static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", 54 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; 55 56 static const char *level_text[] = { 57 "info: ", "notice: ", "warning: ", "error: ", "critical: " 58 }; 59 60 static void 61 version_rename(log_channel chan) { 62 unsigned int ver; 63 char old_name[PATH_MAX+1]; 64 char new_name[PATH_MAX+1]; 65 66 ver = chan->out.file.versions; 67 if (ver < 1) 68 return; 69 if (ver > LOG_MAX_VERSIONS) 70 ver = LOG_MAX_VERSIONS; 71 /* 72 * Need to have room for '.nn' (XXX assumes LOG_MAX_VERSIONS < 100) 73 */ 74 if (strlen(chan->out.file.name) > (size_t)(PATH_MAX-3)) 75 return; 76 for (ver--; ver > 0; ver--) { 77 sprintf(old_name, "%s.%d", chan->out.file.name, ver-1); 78 sprintf(new_name, "%s.%d", chan->out.file.name, ver); 79 (void)isc_movefile(old_name, new_name); 80 } 81 sprintf(new_name, "%s.0", chan->out.file.name); 82 (void)isc_movefile(chan->out.file.name, new_name); 83 } 84 85 FILE * 86 log_open_stream(log_channel chan) { 87 FILE *stream; 88 int fd, flags; 89 struct stat sb; 90 int regular; 91 92 if (chan == NULL || chan->type != log_file) { 93 errno = EINVAL; 94 return (NULL); 95 } 96 97 /* 98 * Don't open already open streams 99 */ 100 if (chan->out.file.stream != NULL) 101 return (chan->out.file.stream); 102 103 if (stat(chan->out.file.name, &sb) < 0) { 104 if (errno != ENOENT) { 105 syslog(LOG_ERR, 106 "log_open_stream: stat of %s failed: %s", 107 chan->out.file.name, strerror(errno)); 108 chan->flags |= LOG_CHANNEL_BROKEN; 109 return (NULL); 110 } 111 regular = 1; 112 } else 113 regular = (sb.st_mode & S_IFREG); 114 115 if (chan->out.file.versions) { 116 if (!regular) { 117 syslog(LOG_ERR, 118 "log_open_stream: want versions but %s isn't a regular file", 119 chan->out.file.name); 120 chan->flags |= LOG_CHANNEL_BROKEN; 121 errno = EINVAL; 122 return (NULL); 123 } 124 } 125 126 flags = O_WRONLY|O_CREAT|O_APPEND; 127 128 if ((chan->flags & LOG_TRUNCATE) != 0) { 129 if (regular) { 130 (void)unlink(chan->out.file.name); 131 flags |= O_EXCL; 132 } else { 133 syslog(LOG_ERR, 134 "log_open_stream: want truncation but %s isn't a regular file", 135 chan->out.file.name); 136 chan->flags |= LOG_CHANNEL_BROKEN; 137 errno = EINVAL; 138 return (NULL); 139 } 140 } 141 142 fd = open(chan->out.file.name, flags, 143 S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH); 144 if (fd < 0) { 145 syslog(LOG_ERR, "log_open_stream: open(%s) failed: %s", 146 chan->out.file.name, strerror(errno)); 147 chan->flags |= LOG_CHANNEL_BROKEN; 148 return (NULL); 149 } 150 stream = fdopen(fd, "a"); 151 if (stream == NULL) { 152 syslog(LOG_ERR, "log_open_stream: fdopen() failed"); 153 chan->flags |= LOG_CHANNEL_BROKEN; 154 return (NULL); 155 } 156 (void) fchown(fd, chan->out.file.owner, chan->out.file.group); 157 158 chan->out.file.stream = stream; 159 return (stream); 160 } 161 162 int 163 log_close_stream(log_channel chan) { 164 FILE *stream; 165 166 if (chan == NULL || chan->type != log_file) { 167 errno = EINVAL; 168 return (0); 169 } 170 stream = chan->out.file.stream; 171 chan->out.file.stream = NULL; 172 if (stream != NULL && fclose(stream) == EOF) 173 return (-1); 174 return (0); 175 } 176 177 void 178 log_close_debug_channels(log_context lc) { 179 log_channel_list lcl; 180 int i; 181 182 for (i = 0; i < lc->num_categories; i++) 183 for (lcl = lc->categories[i]; lcl != NULL; lcl = lcl->next) 184 if (lcl->channel->type == log_file && 185 lcl->channel->out.file.stream != NULL && 186 lcl->channel->flags & LOG_REQUIRE_DEBUG) 187 (void)log_close_stream(lcl->channel); 188 } 189 190 FILE * 191 log_get_stream(log_channel chan) { 192 if (chan == NULL || chan->type != log_file) { 193 errno = EINVAL; 194 return (NULL); 195 } 196 return (chan->out.file.stream); 197 } 198 199 char * 200 log_get_filename(log_channel chan) { 201 if (chan == NULL || chan->type != log_file) { 202 errno = EINVAL; 203 return (NULL); 204 } 205 return (chan->out.file.name); 206 } 207 208 int 209 log_check_channel(log_context lc, int level, log_channel chan) { 210 int debugging, chan_level; 211 212 REQUIRE(lc != NULL); 213 214 debugging = ((lc->flags & LOG_OPTION_DEBUG) != 0); 215 216 /* 217 * If not debugging, short circuit debugging messages very early. 218 */ 219 if (level > 0 && !debugging) 220 return (0); 221 222 if ((chan->flags & (LOG_CHANNEL_BROKEN|LOG_CHANNEL_OFF)) != 0) 223 return (0); 224 225 /* Some channels only log when debugging is on. */ 226 if ((chan->flags & LOG_REQUIRE_DEBUG) && !debugging) 227 return (0); 228 229 /* Some channels use the global level. */ 230 if ((chan->flags & LOG_USE_CONTEXT_LEVEL) != 0) { 231 chan_level = lc->level; 232 } else 233 chan_level = chan->level; 234 235 if (level > chan_level) 236 return (0); 237 238 return (1); 239 } 240 241 int 242 log_check(log_context lc, int category, int level) { 243 log_channel_list lcl; 244 int debugging; 245 246 REQUIRE(lc != NULL); 247 248 debugging = ((lc->flags & LOG_OPTION_DEBUG) != 0); 249 250 /* 251 * If not debugging, short circuit debugging messages very early. 252 */ 253 if (level > 0 && !debugging) 254 return (0); 255 256 if (category < 0 || category > lc->num_categories) 257 category = 0; /*%< use default */ 258 lcl = lc->categories[category]; 259 if (lcl == NULL) { 260 category = 0; 261 lcl = lc->categories[0]; 262 } 263 264 for ( /* nothing */; lcl != NULL; lcl = lcl->next) { 265 if (log_check_channel(lc, level, lcl->channel)) 266 return (1); 267 } 268 return (0); 269 } 270 271 void 272 log_vwrite(log_context lc, int category, int level, const char *format, 273 va_list args) { 274 log_channel_list lcl; 275 int pri, debugging, did_vsprintf = 0; 276 int original_category; 277 FILE *stream; 278 log_channel chan; 279 struct timeval tv; 280 struct tm *local_tm; 281 #ifdef HAVE_TIME_R 282 struct tm tm_tmp; 283 #endif 284 time_t tt; 285 const char *category_name; 286 const char *level_str; 287 char time_buf[256]; 288 char level_buf[256]; 289 290 REQUIRE(lc != NULL); 291 292 debugging = (lc->flags & LOG_OPTION_DEBUG); 293 294 /* 295 * If not debugging, short circuit debugging messages very early. 296 */ 297 if (level > 0 && !debugging) 298 return; 299 300 if (category < 0 || category > lc->num_categories) 301 category = 0; /*%< use default */ 302 original_category = category; 303 lcl = lc->categories[category]; 304 if (lcl == NULL) { 305 category = 0; 306 lcl = lc->categories[0]; 307 } 308 309 /* 310 * Get the current time and format it. 311 */ 312 time_buf[0]='\0'; 313 if (gettimeofday(&tv, NULL) < 0) { 314 syslog(LOG_INFO, "gettimeofday failed in log_vwrite()"); 315 } else { 316 tt = tv.tv_sec; 317 #ifdef HAVE_TIME_R 318 local_tm = localtime_r(&tt, &tm_tmp); 319 #else 320 local_tm = localtime(&tt); 321 #endif 322 if (local_tm != NULL) { 323 sprintf(time_buf, "%02d-%s-%4d %02d:%02d:%02d.%03ld ", 324 local_tm->tm_mday, months[local_tm->tm_mon], 325 local_tm->tm_year+1900, local_tm->tm_hour, 326 local_tm->tm_min, local_tm->tm_sec, 327 (long)tv.tv_usec/1000); 328 } 329 } 330 331 /* 332 * Make a string representation of the current category and level 333 */ 334 335 if (lc->category_names != NULL && 336 lc->category_names[original_category] != NULL) 337 category_name = lc->category_names[original_category]; 338 else 339 category_name = ""; 340 341 if (level >= log_critical) { 342 if (level >= 0) { 343 sprintf(level_buf, "debug %d: ", level); 344 level_str = level_buf; 345 } else 346 level_str = level_text[-level-1]; 347 } else { 348 sprintf(level_buf, "level %d: ", level); 349 level_str = level_buf; 350 } 351 352 /* 353 * Write the message to channels. 354 */ 355 for ( /* nothing */; lcl != NULL; lcl = lcl->next) { 356 chan = lcl->channel; 357 358 if (!log_check_channel(lc, level, chan)) 359 continue; 360 361 if (!did_vsprintf) { 362 (void)vsprintf(lc->buffer, format, args); 363 if (strlen(lc->buffer) > (size_t)LOG_BUFFER_SIZE) { 364 syslog(LOG_CRIT, 365 "memory overrun in log_vwrite()"); 366 exit(1); 367 } 368 did_vsprintf = 1; 369 } 370 371 switch (chan->type) { 372 case log_syslog: 373 if (level >= log_critical) 374 pri = (level >= 0) ? 0 : -level; 375 else 376 pri = -log_critical; 377 syslog(chan->out.facility|syslog_priority[pri], 378 "%s%s%s%s", 379 (chan->flags & LOG_TIMESTAMP) ? time_buf : "", 380 (chan->flags & LOG_PRINT_CATEGORY) ? 381 category_name : "", 382 (chan->flags & LOG_PRINT_LEVEL) ? 383 level_str : "", 384 lc->buffer); 385 break; 386 case log_file: 387 stream = chan->out.file.stream; 388 if (stream == NULL) { 389 stream = log_open_stream(chan); 390 if (stream == NULL) 391 break; 392 } 393 if (chan->out.file.max_size != ULONG_MAX) { 394 long pos; 395 396 pos = ftell(stream); 397 if (pos >= 0 && 398 (unsigned long)pos > 399 chan->out.file.max_size) { 400 /* 401 * try to roll over the log files, 402 * ignoring all all return codes 403 * except the open (we don't want 404 * to write any more anyway) 405 */ 406 log_close_stream(chan); 407 version_rename(chan); 408 stream = log_open_stream(chan); 409 if (stream == NULL) 410 break; 411 } 412 } 413 fprintf(stream, "%s%s%s%s\n", 414 (chan->flags & LOG_TIMESTAMP) ? time_buf : "", 415 (chan->flags & LOG_PRINT_CATEGORY) ? 416 category_name : "", 417 (chan->flags & LOG_PRINT_LEVEL) ? 418 level_str : "", 419 lc->buffer); 420 fflush(stream); 421 break; 422 case log_null: 423 break; 424 default: 425 syslog(LOG_ERR, 426 "unknown channel type in log_vwrite()"); 427 } 428 } 429 } 430 431 void 432 log_write(log_context lc, int category, int level, const char *format, ...) { 433 va_list args; 434 435 va_start(args, format); 436 log_vwrite(lc, category, level, format, args); 437 va_end(args); 438 } 439 440 /*% 441 * Functions to create, set, or destroy contexts 442 */ 443 444 int 445 log_new_context(int num_categories, char **category_names, log_context *lc) { 446 log_context nlc; 447 448 nlc = memget(sizeof (struct log_context)); 449 if (nlc == NULL) { 450 errno = ENOMEM; 451 return (-1); 452 } 453 nlc->num_categories = num_categories; 454 nlc->category_names = category_names; 455 nlc->categories = memget(num_categories * sizeof (log_channel_list)); 456 if (nlc->categories == NULL) { 457 memput(nlc, sizeof (struct log_context)); 458 errno = ENOMEM; 459 return (-1); 460 } 461 memset(nlc->categories, '\0', 462 num_categories * sizeof (log_channel_list)); 463 nlc->flags = 0U; 464 nlc->level = 0; 465 *lc = nlc; 466 return (0); 467 } 468 469 void 470 log_free_context(log_context lc) { 471 log_channel_list lcl, lcl_next; 472 log_channel chan; 473 int i; 474 475 REQUIRE(lc != NULL); 476 477 for (i = 0; i < lc->num_categories; i++) 478 for (lcl = lc->categories[i]; lcl != NULL; lcl = lcl_next) { 479 lcl_next = lcl->next; 480 chan = lcl->channel; 481 (void)log_free_channel(chan); 482 memput(lcl, sizeof (struct log_channel_list)); 483 } 484 memput(lc->categories, 485 lc->num_categories * sizeof (log_channel_list)); 486 memput(lc, sizeof (struct log_context)); 487 } 488 489 int 490 log_add_channel(log_context lc, int category, log_channel chan) { 491 log_channel_list lcl; 492 493 if (lc == NULL || category < 0 || category >= lc->num_categories) { 494 errno = EINVAL; 495 return (-1); 496 } 497 498 lcl = memget(sizeof (struct log_channel_list)); 499 if (lcl == NULL) { 500 errno = ENOMEM; 501 return(-1); 502 } 503 lcl->channel = chan; 504 lcl->next = lc->categories[category]; 505 lc->categories[category] = lcl; 506 chan->references++; 507 return (0); 508 } 509 510 int 511 log_remove_channel(log_context lc, int category, log_channel chan) { 512 log_channel_list lcl, prev_lcl, next_lcl; 513 int found = 0; 514 515 if (lc == NULL || category < 0 || category >= lc->num_categories) { 516 errno = EINVAL; 517 return (-1); 518 } 519 520 for (prev_lcl = NULL, lcl = lc->categories[category]; 521 lcl != NULL; 522 lcl = next_lcl) { 523 next_lcl = lcl->next; 524 if (lcl->channel == chan) { 525 log_free_channel(chan); 526 if (prev_lcl != NULL) 527 prev_lcl->next = next_lcl; 528 else 529 lc->categories[category] = next_lcl; 530 memput(lcl, sizeof (struct log_channel_list)); 531 /* 532 * We just set found instead of returning because 533 * the channel might be on the list more than once. 534 */ 535 found = 1; 536 } else 537 prev_lcl = lcl; 538 } 539 if (!found) { 540 errno = ENOENT; 541 return (-1); 542 } 543 return (0); 544 } 545 546 int 547 log_option(log_context lc, int option, int value) { 548 if (lc == NULL) { 549 errno = EINVAL; 550 return (-1); 551 } 552 switch (option) { 553 case LOG_OPTION_DEBUG: 554 if (value) 555 lc->flags |= option; 556 else 557 lc->flags &= ~option; 558 break; 559 case LOG_OPTION_LEVEL: 560 lc->level = value; 561 break; 562 default: 563 errno = EINVAL; 564 return (-1); 565 } 566 return (0); 567 } 568 569 int 570 log_category_is_active(log_context lc, int category) { 571 if (lc == NULL) { 572 errno = EINVAL; 573 return (-1); 574 } 575 if (category >= 0 && category < lc->num_categories && 576 lc->categories[category] != NULL) 577 return (1); 578 return (0); 579 } 580 581 log_channel 582 log_new_syslog_channel(unsigned int flags, int level, int facility) { 583 log_channel chan; 584 585 chan = memget(sizeof (struct log_channel)); 586 if (chan == NULL) { 587 errno = ENOMEM; 588 return (NULL); 589 } 590 chan->type = log_syslog; 591 chan->flags = flags; 592 chan->level = level; 593 chan->out.facility = facility; 594 chan->references = 0; 595 return (chan); 596 } 597 598 log_channel 599 log_new_file_channel(unsigned int flags, int level, 600 const char *name, FILE *stream, unsigned int versions, 601 unsigned long max_size) { 602 log_channel chan; 603 604 chan = memget(sizeof (struct log_channel)); 605 if (chan == NULL) { 606 errno = ENOMEM; 607 return (NULL); 608 } 609 chan->type = log_file; 610 chan->flags = flags; 611 chan->level = level; 612 if (name != NULL) { 613 size_t len; 614 615 len = strlen(name); 616 /* 617 * Quantize length to a multiple of 256. There's space for the 618 * NUL, since if len is a multiple of 256, the size chosen will 619 * be the next multiple. 620 */ 621 chan->out.file.name_size = ((len / 256) + 1) * 256; 622 chan->out.file.name = memget(chan->out.file.name_size); 623 if (chan->out.file.name == NULL) { 624 memput(chan, sizeof (struct log_channel)); 625 errno = ENOMEM; 626 return (NULL); 627 } 628 /* This is safe. */ 629 strcpy(chan->out.file.name, name); 630 } else { 631 chan->out.file.name_size = 0; 632 chan->out.file.name = NULL; 633 } 634 chan->out.file.stream = stream; 635 chan->out.file.versions = versions; 636 chan->out.file.max_size = max_size; 637 chan->out.file.owner = getuid(); 638 chan->out.file.group = getgid(); 639 chan->references = 0; 640 return (chan); 641 } 642 643 int 644 log_set_file_owner(log_channel chan, uid_t owner, gid_t group) { 645 if (chan->type != log_file) { 646 errno = EBADF; 647 return (-1); 648 } 649 chan->out.file.owner = owner; 650 chan->out.file.group = group; 651 return (0); 652 } 653 654 log_channel 655 log_new_null_channel() { 656 log_channel chan; 657 658 chan = memget(sizeof (struct log_channel)); 659 if (chan == NULL) { 660 errno = ENOMEM; 661 return (NULL); 662 } 663 chan->type = log_null; 664 chan->flags = LOG_CHANNEL_OFF; 665 chan->level = log_info; 666 chan->references = 0; 667 return (chan); 668 } 669 670 int 671 log_inc_references(log_channel chan) { 672 if (chan == NULL) { 673 errno = EINVAL; 674 return (-1); 675 } 676 chan->references++; 677 return (0); 678 } 679 680 int 681 log_dec_references(log_channel chan) { 682 if (chan == NULL || chan->references <= 0) { 683 errno = EINVAL; 684 return (-1); 685 } 686 chan->references--; 687 return (0); 688 } 689 690 log_channel_type 691 log_get_channel_type(log_channel chan) { 692 REQUIRE(chan != NULL); 693 694 return (chan->type); 695 } 696 697 int 698 log_free_channel(log_channel chan) { 699 if (chan == NULL || chan->references <= 0) { 700 errno = EINVAL; 701 return (-1); 702 } 703 chan->references--; 704 if (chan->references == 0) { 705 if (chan->type == log_file) { 706 if ((chan->flags & LOG_CLOSE_STREAM) && 707 chan->out.file.stream != NULL) 708 (void)fclose(chan->out.file.stream); 709 if (chan->out.file.name != NULL) 710 memput(chan->out.file.name, 711 chan->out.file.name_size); 712 } 713 memput(chan, sizeof (struct log_channel)); 714 } 715 return (0); 716 } 717 718 /*! \file */ 719