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