1 /* 2 * Copyright 2006 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 #ifdef SUNW_AVOIDSTDIO_FDLIMIT 162 stream = fdopen(fd, "aF"); 163 #else 164 stream = fdopen(fd, "a"); 165 #endif 166 if (stream == NULL) { 167 syslog(LOG_ERR, "log_open_stream: fdopen() failed"); 168 chan->flags |= LOG_CHANNEL_BROKEN; 169 return (NULL); 170 } 171 (void) fchown(fd, chan->out.file.owner, chan->out.file.group); 172 173 chan->out.file.stream = stream; 174 return (stream); 175 } 176 177 int 178 log_close_stream(log_channel chan) { 179 FILE *stream; 180 181 if (chan == NULL || chan->type != log_file) { 182 errno = EINVAL; 183 return (0); 184 } 185 stream = chan->out.file.stream; 186 chan->out.file.stream = NULL; 187 if (stream != NULL && fclose(stream) == EOF) 188 return (-1); 189 return (0); 190 } 191 192 void 193 log_close_debug_channels(log_context lc) { 194 log_channel_list lcl; 195 int i; 196 197 for (i = 0; i < lc->num_categories; i++) 198 for (lcl = lc->categories[i]; lcl != NULL; lcl = lcl->next) 199 if (lcl->channel->type == log_file && 200 lcl->channel->out.file.stream != NULL && 201 lcl->channel->flags & LOG_REQUIRE_DEBUG) 202 (void)log_close_stream(lcl->channel); 203 } 204 205 FILE * 206 log_get_stream(log_channel chan) { 207 if (chan == NULL || chan->type != log_file) { 208 errno = EINVAL; 209 return (NULL); 210 } 211 return (chan->out.file.stream); 212 } 213 214 char * 215 log_get_filename(log_channel chan) { 216 if (chan == NULL || chan->type != log_file) { 217 errno = EINVAL; 218 return (NULL); 219 } 220 return (chan->out.file.name); 221 } 222 223 int 224 log_check_channel(log_context lc, int level, log_channel chan) { 225 int debugging, chan_level; 226 227 REQUIRE(lc != NULL); 228 229 debugging = ((lc->flags & LOG_OPTION_DEBUG) != 0); 230 231 /* 232 * If not debugging, short circuit debugging messages very early. 233 */ 234 if (level > 0 && !debugging) 235 return (0); 236 237 if ((chan->flags & (LOG_CHANNEL_BROKEN|LOG_CHANNEL_OFF)) != 0) 238 return (0); 239 240 /* Some channels only log when debugging is on. */ 241 if ((chan->flags & LOG_REQUIRE_DEBUG) && !debugging) 242 return (0); 243 244 /* Some channels use the global level. */ 245 if ((chan->flags & LOG_USE_CONTEXT_LEVEL) != 0) { 246 chan_level = lc->level; 247 } else 248 chan_level = chan->level; 249 250 if (level > chan_level) 251 return (0); 252 253 return (1); 254 } 255 256 int 257 log_check(log_context lc, int category, int level) { 258 log_channel_list lcl; 259 int debugging; 260 261 REQUIRE(lc != NULL); 262 263 debugging = ((lc->flags & LOG_OPTION_DEBUG) != 0); 264 265 /* 266 * If not debugging, short circuit debugging messages very early. 267 */ 268 if (level > 0 && !debugging) 269 return (0); 270 271 if (category < 0 || category > lc->num_categories) 272 category = 0; /* use default */ 273 lcl = lc->categories[category]; 274 if (lcl == NULL) { 275 category = 0; 276 lcl = lc->categories[0]; 277 } 278 279 for ( /* nothing */; lcl != NULL; lcl = lcl->next) { 280 if (log_check_channel(lc, level, lcl->channel)) 281 return (1); 282 } 283 return (0); 284 } 285 286 void 287 log_vwrite(log_context lc, int category, int level, const char *format, 288 va_list args) { 289 log_channel_list lcl; 290 int pri, debugging, did_vsprintf = 0; 291 int original_category; 292 FILE *stream; 293 log_channel chan; 294 struct timeval tv; 295 struct tm *local_tm; 296 #ifdef HAVE_TIME_R 297 struct tm tm_tmp; 298 #endif 299 time_t tt; 300 const char *category_name; 301 const char *level_str; 302 char time_buf[256]; 303 char level_buf[256]; 304 305 REQUIRE(lc != NULL); 306 307 debugging = (lc->flags & LOG_OPTION_DEBUG); 308 309 /* 310 * If not debugging, short circuit debugging messages very early. 311 */ 312 if (level > 0 && !debugging) 313 return; 314 315 if (category < 0 || category > lc->num_categories) 316 category = 0; /* use default */ 317 original_category = category; 318 lcl = lc->categories[category]; 319 if (lcl == NULL) { 320 category = 0; 321 lcl = lc->categories[0]; 322 } 323 324 /* 325 * Get the current time and format it. 326 */ 327 time_buf[0]='\0'; 328 if (gettimeofday(&tv, NULL) < 0) { 329 syslog(LOG_INFO, "gettimeofday failed in log_vwrite()"); 330 } else { 331 tt = tv.tv_sec; 332 #ifdef HAVE_TIME_R 333 local_tm = localtime_r(&tt, &tm_tmp); 334 #else 335 local_tm = localtime(&tt); 336 #endif 337 if (local_tm != NULL) { 338 sprintf(time_buf, "%02d-%s-%4d %02d:%02d:%02d.%03ld ", 339 local_tm->tm_mday, months[local_tm->tm_mon], 340 local_tm->tm_year+1900, local_tm->tm_hour, 341 local_tm->tm_min, local_tm->tm_sec, 342 (long)tv.tv_usec/1000); 343 } 344 } 345 346 /* 347 * Make a string representation of the current category and level 348 */ 349 350 if (lc->category_names != NULL && 351 lc->category_names[original_category] != NULL) 352 category_name = lc->category_names[original_category]; 353 else 354 category_name = ""; 355 356 if (level >= log_critical) { 357 if (level >= 0) { 358 sprintf(level_buf, "debug %d: ", level); 359 level_str = level_buf; 360 } else 361 level_str = level_text[-level-1]; 362 } else { 363 sprintf(level_buf, "level %d: ", level); 364 level_str = level_buf; 365 } 366 367 /* 368 * Write the message to channels. 369 */ 370 for ( /* nothing */; lcl != NULL; lcl = lcl->next) { 371 chan = lcl->channel; 372 373 if (!log_check_channel(lc, level, chan)) 374 continue; 375 376 if (!did_vsprintf) { 377 if (VSPRINTF((lc->buffer, format, args)) > 378 LOG_BUFFER_SIZE) { 379 syslog(LOG_CRIT, 380 "memory overrun in log_vwrite()"); 381 exit(1); 382 } 383 did_vsprintf = 1; 384 } 385 386 switch (chan->type) { 387 case log_syslog: 388 if (level >= log_critical) 389 pri = (level >= 0) ? 0 : -level; 390 else 391 pri = -log_critical; 392 syslog(chan->out.facility|syslog_priority[pri], 393 "%s%s%s%s", 394 (chan->flags & LOG_TIMESTAMP) ? time_buf : "", 395 (chan->flags & LOG_PRINT_CATEGORY) ? 396 category_name : "", 397 (chan->flags & LOG_PRINT_LEVEL) ? 398 level_str : "", 399 lc->buffer); 400 break; 401 case log_file: 402 stream = chan->out.file.stream; 403 if (stream == NULL) { 404 stream = log_open_stream(chan); 405 if (stream == NULL) 406 break; 407 } 408 if (chan->out.file.max_size != ULONG_MAX) { 409 long pos; 410 411 pos = ftell(stream); 412 if (pos >= 0 && 413 (unsigned long)pos > 414 chan->out.file.max_size) { 415 /* 416 * try to roll over the log files, 417 * ignoring all all return codes 418 * except the open (we don't want 419 * to write any more anyway) 420 */ 421 log_close_stream(chan); 422 version_rename(chan); 423 stream = log_open_stream(chan); 424 if (stream == NULL) 425 break; 426 } 427 } 428 fprintf(stream, "%s%s%s%s\n", 429 (chan->flags & LOG_TIMESTAMP) ? time_buf : "", 430 (chan->flags & LOG_PRINT_CATEGORY) ? 431 category_name : "", 432 (chan->flags & LOG_PRINT_LEVEL) ? 433 level_str : "", 434 lc->buffer); 435 fflush(stream); 436 break; 437 case log_null: 438 break; 439 default: 440 syslog(LOG_ERR, 441 "unknown channel type in log_vwrite()"); 442 } 443 } 444 } 445 446 void 447 log_write(log_context lc, int category, int level, const char *format, ...) { 448 va_list args; 449 450 va_start(args, format); 451 log_vwrite(lc, category, level, format, args); 452 va_end(args); 453 } 454 455 /* 456 * Functions to create, set, or destroy contexts 457 */ 458 459 int 460 log_new_context(int num_categories, char **category_names, log_context *lc) { 461 log_context nlc; 462 463 nlc = memget(sizeof (struct log_context)); 464 if (nlc == NULL) { 465 errno = ENOMEM; 466 return (-1); 467 } 468 nlc->num_categories = num_categories; 469 nlc->category_names = category_names; 470 nlc->categories = memget(num_categories * sizeof (log_channel_list)); 471 if (nlc->categories == NULL) { 472 memput(nlc, sizeof (struct log_context)); 473 errno = ENOMEM; 474 return (-1); 475 } 476 memset(nlc->categories, '\0', 477 num_categories * sizeof (log_channel_list)); 478 nlc->flags = 0U; 479 nlc->level = 0; 480 *lc = nlc; 481 return (0); 482 } 483 484 void 485 log_free_context(log_context lc) { 486 log_channel_list lcl, lcl_next; 487 log_channel chan; 488 int i; 489 490 REQUIRE(lc != NULL); 491 492 for (i = 0; i < lc->num_categories; i++) 493 for (lcl = lc->categories[i]; lcl != NULL; lcl = lcl_next) { 494 lcl_next = lcl->next; 495 chan = lcl->channel; 496 (void)log_free_channel(chan); 497 memput(lcl, sizeof (struct log_channel_list)); 498 } 499 memput(lc->categories, 500 lc->num_categories * sizeof (log_channel_list)); 501 memput(lc, sizeof (struct log_context)); 502 } 503 504 int 505 log_add_channel(log_context lc, int category, log_channel chan) { 506 log_channel_list lcl; 507 508 if (lc == NULL || category < 0 || category >= lc->num_categories) { 509 errno = EINVAL; 510 return (-1); 511 } 512 513 lcl = memget(sizeof (struct log_channel_list)); 514 if (lcl == NULL) { 515 errno = ENOMEM; 516 return(-1); 517 } 518 lcl->channel = chan; 519 lcl->next = lc->categories[category]; 520 lc->categories[category] = lcl; 521 chan->references++; 522 return (0); 523 } 524 525 int 526 log_remove_channel(log_context lc, int category, log_channel chan) { 527 log_channel_list lcl, prev_lcl, next_lcl; 528 int found = 0; 529 530 if (lc == NULL || category < 0 || category >= lc->num_categories) { 531 errno = EINVAL; 532 return (-1); 533 } 534 535 for (prev_lcl = NULL, lcl = lc->categories[category]; 536 lcl != NULL; 537 lcl = next_lcl) { 538 next_lcl = lcl->next; 539 if (lcl->channel == chan) { 540 log_free_channel(chan); 541 if (prev_lcl != NULL) 542 prev_lcl->next = next_lcl; 543 else 544 lc->categories[category] = next_lcl; 545 memput(lcl, sizeof (struct log_channel_list)); 546 /* 547 * We just set found instead of returning because 548 * the channel might be on the list more than once. 549 */ 550 found = 1; 551 } else 552 prev_lcl = lcl; 553 } 554 if (!found) { 555 errno = ENOENT; 556 return (-1); 557 } 558 return (0); 559 } 560 561 int 562 log_option(log_context lc, int option, int value) { 563 if (lc == NULL) { 564 errno = EINVAL; 565 return (-1); 566 } 567 switch (option) { 568 case LOG_OPTION_DEBUG: 569 if (value) 570 lc->flags |= option; 571 else 572 lc->flags &= ~option; 573 break; 574 case LOG_OPTION_LEVEL: 575 lc->level = value; 576 break; 577 default: 578 errno = EINVAL; 579 return (-1); 580 } 581 return (0); 582 } 583 584 int 585 log_category_is_active(log_context lc, int category) { 586 if (lc == NULL) { 587 errno = EINVAL; 588 return (-1); 589 } 590 if (category >= 0 && category < lc->num_categories && 591 lc->categories[category] != NULL) 592 return (1); 593 return (0); 594 } 595 596 log_channel 597 log_new_syslog_channel(unsigned int flags, int level, int facility) { 598 log_channel chan; 599 600 chan = memget(sizeof (struct log_channel)); 601 if (chan == NULL) { 602 errno = ENOMEM; 603 return (NULL); 604 } 605 chan->type = log_syslog; 606 chan->flags = flags; 607 chan->level = level; 608 chan->out.facility = facility; 609 chan->references = 0; 610 return (chan); 611 } 612 613 log_channel 614 log_new_file_channel(unsigned int flags, int level, 615 const char *name, FILE *stream, unsigned int versions, 616 unsigned long max_size) { 617 log_channel chan; 618 619 chan = memget(sizeof (struct log_channel)); 620 if (chan == NULL) { 621 errno = ENOMEM; 622 return (NULL); 623 } 624 chan->type = log_file; 625 chan->flags = flags; 626 chan->level = level; 627 if (name != NULL) { 628 size_t len; 629 630 len = strlen(name); 631 /* 632 * Quantize length to a multiple of 256. There's space for the 633 * NUL, since if len is a multiple of 256, the size chosen will 634 * be the next multiple. 635 */ 636 chan->out.file.name_size = ((len / 256) + 1) * 256; 637 chan->out.file.name = memget(chan->out.file.name_size); 638 if (chan->out.file.name == NULL) { 639 memput(chan, sizeof (struct log_channel)); 640 errno = ENOMEM; 641 return (NULL); 642 } 643 /* This is safe. */ 644 strcpy(chan->out.file.name, name); 645 } else { 646 chan->out.file.name_size = 0; 647 chan->out.file.name = NULL; 648 } 649 chan->out.file.stream = stream; 650 chan->out.file.versions = versions; 651 chan->out.file.max_size = max_size; 652 chan->out.file.owner = getuid(); 653 chan->out.file.group = getgid(); 654 chan->references = 0; 655 return (chan); 656 } 657 658 int 659 log_set_file_owner(log_channel chan, uid_t owner, gid_t group) { 660 if (chan->type != log_file) { 661 errno = EBADF; 662 return (-1); 663 } 664 chan->out.file.owner = owner; 665 chan->out.file.group = group; 666 return (0); 667 } 668 669 log_channel 670 log_new_null_channel() { 671 log_channel chan; 672 673 chan = memget(sizeof (struct log_channel)); 674 if (chan == NULL) { 675 errno = ENOMEM; 676 return (NULL); 677 } 678 chan->type = log_null; 679 chan->flags = LOG_CHANNEL_OFF; 680 chan->level = log_info; 681 chan->references = 0; 682 return (chan); 683 } 684 685 int 686 log_inc_references(log_channel chan) { 687 if (chan == NULL) { 688 errno = EINVAL; 689 return (-1); 690 } 691 chan->references++; 692 return (0); 693 } 694 695 int 696 log_dec_references(log_channel chan) { 697 if (chan == NULL || chan->references <= 0) { 698 errno = EINVAL; 699 return (-1); 700 } 701 chan->references--; 702 return (0); 703 } 704 705 log_channel_type 706 log_get_channel_type(log_channel chan) { 707 REQUIRE(chan != NULL); 708 709 return (chan->type); 710 } 711 712 int 713 log_free_channel(log_channel chan) { 714 if (chan == NULL || chan->references <= 0) { 715 errno = EINVAL; 716 return (-1); 717 } 718 chan->references--; 719 if (chan->references == 0) { 720 if (chan->type == log_file) { 721 if ((chan->flags & LOG_CLOSE_STREAM) && 722 chan->out.file.stream != NULL) 723 (void)fclose(chan->out.file.stream); 724 if (chan->out.file.name != NULL) 725 memput(chan->out.file.name, 726 chan->out.file.name_size); 727 } 728 memput(chan, sizeof (struct log_channel)); 729 } 730 return (0); 731 } 732