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