Add BIND 9.2.4rc7.
[dragonfly.git] / contrib / bind-9.2.4rc7 / lib / bind / isc / logging.c
blob1ed56d8f3694ebcad750a5c0c038ad8b1a4f6767
1 /*
2 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
3 * Copyright (c) 1996-1999 by Internet Software Consortium.
5 * Permission to use, copy, modify, and 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.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
15 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #if !defined(LINT) && !defined(CODECENTER)
19 static const char rcsid[] = "$Id: logging.c,v 1.3.2.3 2004/03/17 01:54:23 marka Exp $";
20 #endif /* not lint */
22 #include "port_before.h"
24 #include <sys/types.h>
25 #include <sys/time.h>
26 #include <sys/stat.h>
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>
39 #include <isc/assertions.h>
40 #include <isc/logging.h>
41 #include <isc/memcluster.h>
42 #include <isc/misc.h>
44 #include "port_after.h"
46 #ifdef VSPRINTF_CHAR
47 # define VSPRINTF(x) strlen(vsprintf/**/x)
48 #else
49 # define VSPRINTF(x) ((size_t)vsprintf x)
50 #endif
52 #include "logging_p.h"
54 static const int syslog_priority[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE,
55 LOG_WARNING, LOG_ERR, LOG_CRIT };
57 static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
58 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
60 static const char *level_text[] = {
61 "info: ", "notice: ", "warning: ", "error: ", "critical: "
64 static void
65 version_rename(log_channel chan) {
66 unsigned int ver;
67 char old_name[PATH_MAX+1];
68 char new_name[PATH_MAX+1];
70 ver = chan->out.file.versions;
71 if (ver < 1)
72 return;
73 if (ver > LOG_MAX_VERSIONS)
74 ver = LOG_MAX_VERSIONS;
76 * Need to have room for '.nn' (XXX assumes LOG_MAX_VERSIONS < 100)
78 if (strlen(chan->out.file.name) > (size_t)(PATH_MAX-3))
79 return;
80 for (ver--; ver > 0; ver--) {
81 sprintf(old_name, "%s.%d", chan->out.file.name, ver-1);
82 sprintf(new_name, "%s.%d", chan->out.file.name, ver);
83 (void)isc_movefile(old_name, new_name);
85 sprintf(new_name, "%s.0", chan->out.file.name);
86 (void)isc_movefile(chan->out.file.name, new_name);
89 FILE *
90 log_open_stream(log_channel chan) {
91 FILE *stream;
92 int fd, flags;
93 struct stat sb;
94 int regular;
96 if (chan == NULL || chan->type != log_file) {
97 errno = EINVAL;
98 return (NULL);
102 * Don't open already open streams
104 if (chan->out.file.stream != NULL)
105 return (chan->out.file.stream);
107 if (stat(chan->out.file.name, &sb) < 0) {
108 if (errno != ENOENT) {
109 syslog(LOG_ERR,
110 "log_open_stream: stat of %s failed: %s",
111 chan->out.file.name, strerror(errno));
112 chan->flags |= LOG_CHANNEL_BROKEN;
113 return (NULL);
115 regular = 1;
116 } else
117 regular = (sb.st_mode & S_IFREG);
119 if (chan->out.file.versions) {
120 if (!regular) {
121 syslog(LOG_ERR,
122 "log_open_stream: want versions but %s isn't a regular file",
123 chan->out.file.name);
124 chan->flags |= LOG_CHANNEL_BROKEN;
125 errno = EINVAL;
126 return (NULL);
130 flags = O_WRONLY|O_CREAT|O_APPEND;
132 if ((chan->flags & LOG_TRUNCATE) != 0) {
133 if (regular) {
134 (void)unlink(chan->out.file.name);
135 flags |= O_EXCL;
136 } else {
137 syslog(LOG_ERR,
138 "log_open_stream: want truncation but %s isn't a regular file",
139 chan->out.file.name);
140 chan->flags |= LOG_CHANNEL_BROKEN;
141 errno = EINVAL;
142 return (NULL);
146 fd = open(chan->out.file.name, flags,
147 S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
148 if (fd < 0) {
149 syslog(LOG_ERR, "log_open_stream: open(%s) failed: %s",
150 chan->out.file.name, strerror(errno));
151 chan->flags |= LOG_CHANNEL_BROKEN;
152 return (NULL);
154 stream = fdopen(fd, "a");
155 if (stream == NULL) {
156 syslog(LOG_ERR, "log_open_stream: fdopen() failed");
157 chan->flags |= LOG_CHANNEL_BROKEN;
158 return (NULL);
160 (void) fchown(fd, chan->out.file.owner, chan->out.file.group);
162 chan->out.file.stream = stream;
163 return (stream);
167 log_close_stream(log_channel chan) {
168 FILE *stream;
170 if (chan == NULL || chan->type != log_file) {
171 errno = EINVAL;
172 return (0);
174 stream = chan->out.file.stream;
175 chan->out.file.stream = NULL;
176 if (stream != NULL && fclose(stream) == EOF)
177 return (-1);
178 return (0);
181 void
182 log_close_debug_channels(log_context lc) {
183 log_channel_list lcl;
184 int i;
186 for (i = 0; i < lc->num_categories; i++)
187 for (lcl = lc->categories[i]; lcl != NULL; lcl = lcl->next)
188 if (lcl->channel->type == log_file &&
189 lcl->channel->out.file.stream != NULL &&
190 lcl->channel->flags & LOG_REQUIRE_DEBUG)
191 (void)log_close_stream(lcl->channel);
194 FILE *
195 log_get_stream(log_channel chan) {
196 if (chan == NULL || chan->type != log_file) {
197 errno = EINVAL;
198 return (NULL);
200 return (chan->out.file.stream);
203 char *
204 log_get_filename(log_channel chan) {
205 if (chan == NULL || chan->type != log_file) {
206 errno = EINVAL;
207 return (NULL);
209 return (chan->out.file.name);
213 log_check_channel(log_context lc, int level, log_channel chan) {
214 int debugging, chan_level;
216 REQUIRE(lc != NULL);
218 debugging = ((lc->flags & LOG_OPTION_DEBUG) != 0);
221 * If not debugging, short circuit debugging messages very early.
223 if (level > 0 && !debugging)
224 return (0);
226 if ((chan->flags & (LOG_CHANNEL_BROKEN|LOG_CHANNEL_OFF)) != 0)
227 return (0);
229 /* Some channels only log when debugging is on. */
230 if ((chan->flags & LOG_REQUIRE_DEBUG) && !debugging)
231 return (0);
233 /* Some channels use the global level. */
234 if ((chan->flags & LOG_USE_CONTEXT_LEVEL) != 0) {
235 chan_level = lc->level;
236 } else
237 chan_level = chan->level;
239 if (level > chan_level)
240 return (0);
242 return (1);
245 int
246 log_check(log_context lc, int category, int level) {
247 log_channel_list lcl;
248 int debugging;
250 REQUIRE(lc != NULL);
252 debugging = ((lc->flags & LOG_OPTION_DEBUG) != 0);
255 * If not debugging, short circuit debugging messages very early.
257 if (level > 0 && !debugging)
258 return (0);
260 if (category < 0 || category > lc->num_categories)
261 category = 0; /* use default */
262 lcl = lc->categories[category];
263 if (lcl == NULL) {
264 category = 0;
265 lcl = lc->categories[0];
268 for ( /* nothing */; lcl != NULL; lcl = lcl->next) {
269 if (log_check_channel(lc, level, lcl->channel))
270 return (1);
272 return (0);
275 void
276 log_vwrite(log_context lc, int category, int level, const char *format,
277 va_list args) {
278 log_channel_list lcl;
279 int pri, debugging, did_vsprintf = 0;
280 int original_category;
281 FILE *stream;
282 log_channel chan;
283 struct timeval tv;
284 struct tm *local_tm;
285 #ifdef HAVE_TIME_R
286 struct tm tm_tmp;
287 #endif
288 time_t tt;
289 const char *category_name;
290 const char *level_str;
291 char time_buf[256];
292 char level_buf[256];
294 REQUIRE(lc != NULL);
296 debugging = (lc->flags & LOG_OPTION_DEBUG);
299 * If not debugging, short circuit debugging messages very early.
301 if (level > 0 && !debugging)
302 return;
304 if (category < 0 || category > lc->num_categories)
305 category = 0; /* use default */
306 original_category = category;
307 lcl = lc->categories[category];
308 if (lcl == NULL) {
309 category = 0;
310 lcl = lc->categories[0];
314 * Get the current time and format it.
316 time_buf[0]='\0';
317 if (gettimeofday(&tv, NULL) < 0) {
318 syslog(LOG_INFO, "gettimeofday failed in log_vwrite()");
319 } else {
320 tt = tv.tv_sec;
321 #ifdef HAVE_TIME_R
322 local_tm = localtime_r(&tt, &tm_tmp);
323 #else
324 local_tm = localtime(&tt);
325 #endif
326 if (local_tm != NULL) {
327 sprintf(time_buf, "%02d-%s-%4d %02d:%02d:%02d.%03ld ",
328 local_tm->tm_mday, months[local_tm->tm_mon],
329 local_tm->tm_year+1900, local_tm->tm_hour,
330 local_tm->tm_min, local_tm->tm_sec,
331 (long)tv.tv_usec/1000);
336 * Make a string representation of the current category and level
339 if (lc->category_names != NULL &&
340 lc->category_names[original_category] != NULL)
341 category_name = lc->category_names[original_category];
342 else
343 category_name = "";
345 if (level >= log_critical) {
346 if (level >= 0) {
347 sprintf(level_buf, "debug %d: ", level);
348 level_str = level_buf;
349 } else
350 level_str = level_text[-level-1];
351 } else {
352 sprintf(level_buf, "level %d: ", level);
353 level_str = level_buf;
357 * Write the message to channels.
359 for ( /* nothing */; lcl != NULL; lcl = lcl->next) {
360 chan = lcl->channel;
362 if (!log_check_channel(lc, level, chan))
363 continue;
365 if (!did_vsprintf) {
366 if (VSPRINTF((lc->buffer, format, args)) >
367 (size_t)LOG_BUFFER_SIZE) {
368 syslog(LOG_CRIT,
369 "memory overrun in log_vwrite()");
370 exit(1);
372 did_vsprintf = 1;
375 switch (chan->type) {
376 case log_syslog:
377 if (level >= log_critical)
378 pri = (level >= 0) ? 0 : -level;
379 else
380 pri = -log_critical;
381 syslog(chan->out.facility|syslog_priority[pri],
382 "%s%s%s%s",
383 (chan->flags & LOG_TIMESTAMP) ? time_buf : "",
384 (chan->flags & LOG_PRINT_CATEGORY) ?
385 category_name : "",
386 (chan->flags & LOG_PRINT_LEVEL) ?
387 level_str : "",
388 lc->buffer);
389 break;
390 case log_file:
391 stream = chan->out.file.stream;
392 if (stream == NULL) {
393 stream = log_open_stream(chan);
394 if (stream == NULL)
395 break;
397 if (chan->out.file.max_size != ULONG_MAX) {
398 long pos;
400 pos = ftell(stream);
401 if (pos >= 0 &&
402 (unsigned long)pos >
403 chan->out.file.max_size) {
405 * try to roll over the log files,
406 * ignoring all all return codes
407 * except the open (we don't want
408 * to write any more anyway)
410 log_close_stream(chan);
411 version_rename(chan);
412 stream = log_open_stream(chan);
413 if (stream == NULL)
414 break;
417 fprintf(stream, "%s%s%s%s\n",
418 (chan->flags & LOG_TIMESTAMP) ? time_buf : "",
419 (chan->flags & LOG_PRINT_CATEGORY) ?
420 category_name : "",
421 (chan->flags & LOG_PRINT_LEVEL) ?
422 level_str : "",
423 lc->buffer);
424 fflush(stream);
425 break;
426 case log_null:
427 break;
428 default:
429 syslog(LOG_ERR,
430 "unknown channel type in log_vwrite()");
435 void
436 log_write(log_context lc, int category, int level, const char *format, ...) {
437 va_list args;
439 va_start(args, format);
440 log_vwrite(lc, category, level, format, args);
441 va_end(args);
445 * Functions to create, set, or destroy contexts
449 log_new_context(int num_categories, char **category_names, log_context *lc) {
450 log_context nlc;
452 nlc = memget(sizeof (struct log_context));
453 if (nlc == NULL) {
454 errno = ENOMEM;
455 return (-1);
457 nlc->num_categories = num_categories;
458 nlc->category_names = category_names;
459 nlc->categories = memget(num_categories * sizeof (log_channel_list));
460 if (nlc->categories == NULL) {
461 memput(nlc, sizeof (struct log_context));
462 errno = ENOMEM;
463 return (-1);
465 memset(nlc->categories, '\0',
466 num_categories * sizeof (log_channel_list));
467 nlc->flags = 0U;
468 nlc->level = 0;
469 *lc = nlc;
470 return (0);
473 void
474 log_free_context(log_context lc) {
475 log_channel_list lcl, lcl_next;
476 log_channel chan;
477 int i;
479 REQUIRE(lc != NULL);
481 for (i = 0; i < lc->num_categories; i++)
482 for (lcl = lc->categories[i]; lcl != NULL; lcl = lcl_next) {
483 lcl_next = lcl->next;
484 chan = lcl->channel;
485 (void)log_free_channel(chan);
486 memput(lcl, sizeof (struct log_channel_list));
488 memput(lc->categories,
489 lc->num_categories * sizeof (log_channel_list));
490 memput(lc, sizeof (struct log_context));
494 log_add_channel(log_context lc, int category, log_channel chan) {
495 log_channel_list lcl;
497 if (lc == NULL || category < 0 || category >= lc->num_categories) {
498 errno = EINVAL;
499 return (-1);
502 lcl = memget(sizeof (struct log_channel_list));
503 if (lcl == NULL) {
504 errno = ENOMEM;
505 return(-1);
507 lcl->channel = chan;
508 lcl->next = lc->categories[category];
509 lc->categories[category] = lcl;
510 chan->references++;
511 return (0);
515 log_remove_channel(log_context lc, int category, log_channel chan) {
516 log_channel_list lcl, prev_lcl, next_lcl;
517 int found = 0;
519 if (lc == NULL || category < 0 || category >= lc->num_categories) {
520 errno = EINVAL;
521 return (-1);
524 for (prev_lcl = NULL, lcl = lc->categories[category];
525 lcl != NULL;
526 lcl = next_lcl) {
527 next_lcl = lcl->next;
528 if (lcl->channel == chan) {
529 log_free_channel(chan);
530 if (prev_lcl != NULL)
531 prev_lcl->next = next_lcl;
532 else
533 lc->categories[category] = next_lcl;
534 memput(lcl, sizeof (struct log_channel_list));
536 * We just set found instead of returning because
537 * the channel might be on the list more than once.
539 found = 1;
540 } else
541 prev_lcl = lcl;
543 if (!found) {
544 errno = ENOENT;
545 return (-1);
547 return (0);
551 log_option(log_context lc, int option, int value) {
552 if (lc == NULL) {
553 errno = EINVAL;
554 return (-1);
556 switch (option) {
557 case LOG_OPTION_DEBUG:
558 if (value)
559 lc->flags |= option;
560 else
561 lc->flags &= ~option;
562 break;
563 case LOG_OPTION_LEVEL:
564 lc->level = value;
565 break;
566 default:
567 errno = EINVAL;
568 return (-1);
570 return (0);
574 log_category_is_active(log_context lc, int category) {
575 if (lc == NULL) {
576 errno = EINVAL;
577 return (-1);
579 if (category >= 0 && category < lc->num_categories &&
580 lc->categories[category] != NULL)
581 return (1);
582 return (0);
585 log_channel
586 log_new_syslog_channel(unsigned int flags, int level, int facility) {
587 log_channel chan;
589 chan = memget(sizeof (struct log_channel));
590 if (chan == NULL) {
591 errno = ENOMEM;
592 return (NULL);
594 chan->type = log_syslog;
595 chan->flags = flags;
596 chan->level = level;
597 chan->out.facility = facility;
598 chan->references = 0;
599 return (chan);
602 log_channel
603 log_new_file_channel(unsigned int flags, int level,
604 const char *name, FILE *stream, unsigned int versions,
605 unsigned long max_size) {
606 log_channel chan;
608 chan = memget(sizeof (struct log_channel));
609 if (chan == NULL) {
610 errno = ENOMEM;
611 return (NULL);
613 chan->type = log_file;
614 chan->flags = flags;
615 chan->level = level;
616 if (name != NULL) {
617 size_t len;
619 len = strlen(name);
621 * Quantize length to a multiple of 256. There's space for the
622 * NUL, since if len is a multiple of 256, the size chosen will
623 * be the next multiple.
625 chan->out.file.name_size = ((len / 256) + 1) * 256;
626 chan->out.file.name = memget(chan->out.file.name_size);
627 if (chan->out.file.name == NULL) {
628 memput(chan, sizeof (struct log_channel));
629 errno = ENOMEM;
630 return (NULL);
632 /* This is safe. */
633 strcpy(chan->out.file.name, name);
634 } else {
635 chan->out.file.name_size = 0;
636 chan->out.file.name = NULL;
638 chan->out.file.stream = stream;
639 chan->out.file.versions = versions;
640 chan->out.file.max_size = max_size;
641 chan->out.file.owner = getuid();
642 chan->out.file.group = getgid();
643 chan->references = 0;
644 return (chan);
648 log_set_file_owner(log_channel chan, uid_t owner, gid_t group) {
649 if (chan->type != log_file) {
650 errno = EBADF;
651 return (-1);
653 chan->out.file.owner = owner;
654 chan->out.file.group = group;
655 return (0);
658 log_channel
659 log_new_null_channel() {
660 log_channel chan;
662 chan = memget(sizeof (struct log_channel));
663 if (chan == NULL) {
664 errno = ENOMEM;
665 return (NULL);
667 chan->type = log_null;
668 chan->flags = LOG_CHANNEL_OFF;
669 chan->level = log_info;
670 chan->references = 0;
671 return (chan);
675 log_inc_references(log_channel chan) {
676 if (chan == NULL) {
677 errno = EINVAL;
678 return (-1);
680 chan->references++;
681 return (0);
685 log_dec_references(log_channel chan) {
686 if (chan == NULL || chan->references <= 0) {
687 errno = EINVAL;
688 return (-1);
690 chan->references--;
691 return (0);
694 log_channel_type
695 log_get_channel_type(log_channel chan) {
696 REQUIRE(chan != NULL);
698 return (chan->type);
702 log_free_channel(log_channel chan) {
703 if (chan == NULL || chan->references <= 0) {
704 errno = EINVAL;
705 return (-1);
707 chan->references--;
708 if (chan->references == 0) {
709 if (chan->type == log_file) {
710 if ((chan->flags & LOG_CLOSE_STREAM) &&
711 chan->out.file.stream != NULL)
712 (void)fclose(chan->out.file.stream);
713 if (chan->out.file.name != NULL)
714 memput(chan->out.file.name,
715 chan->out.file.name_size);
717 memput(chan, sizeof (struct log_channel));
719 return (0);