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