Merge from vendor branch LESS:
[dragonfly.git] / contrib / bind-9.3 / lib / bind / isc / logging.c
1 /*
2  * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
3  * Copyright (c) 1996-1999 by Internet Software Consortium.
4  *
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.
8  *
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.
16  */
17
18 #if !defined(LINT) && !defined(CODECENTER)
19 static const char rcsid[] = "$Id: logging.c,v 1.3.2.1.4.2 2004/03/17 01:49:42 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 #ifdef VSPRINTF_CHAR
47 # define VSPRINTF(x) strlen(vsprintf/**/x)
48 #else
49 # define VSPRINTF(x) ((size_t)vsprintf x)
50 #endif
51
52 #include "logging_p.h"
53
54 static const int syslog_priority[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE,
55                                        LOG_WARNING, LOG_ERR, LOG_CRIT };
56
57 static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
58                                 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
59
60 static const char *level_text[] = {
61         "info: ", "notice: ", "warning: ", "error: ", "critical: "
62 };
63
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];
69         
70         ver = chan->out.file.versions;
71         if (ver < 1)
72                 return;
73         if (ver > LOG_MAX_VERSIONS)
74                 ver = LOG_MAX_VERSIONS;
75         /*
76          * Need to have room for '.nn' (XXX assumes LOG_MAX_VERSIONS < 100)
77          */
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);
84         }
85         sprintf(new_name, "%s.0", chan->out.file.name);
86         (void)isc_movefile(chan->out.file.name, new_name);
87 }
88
89 FILE *
90 log_open_stream(log_channel chan) {
91         FILE *stream;
92         int fd, flags;
93         struct stat sb;
94         int regular;
95
96         if (chan == NULL || chan->type != log_file) {
97                 errno = EINVAL;
98                 return (NULL);
99         }
100         
101         /*
102          * Don't open already open streams
103          */
104         if (chan->out.file.stream != NULL)
105                 return (chan->out.file.stream);
106
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);
114                 }
115                 regular = 1;
116         } else
117                 regular = (sb.st_mode & S_IFREG);
118
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);
127                 }
128         }
129
130         flags = O_WRONLY|O_CREAT|O_APPEND;
131
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);
143                 }
144         }
145
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);
153         }
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);
159         }
160         (void) fchown(fd, chan->out.file.owner, chan->out.file.group);
161
162         chan->out.file.stream = stream;
163         return (stream);
164 }
165
166 int
167 log_close_stream(log_channel chan) {
168         FILE *stream;
169
170         if (chan == NULL || chan->type != log_file) {
171                 errno = EINVAL;
172                 return (0);
173         }
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);
179 }
180
181 void
182 log_close_debug_channels(log_context lc) {
183         log_channel_list lcl;
184         int i;
185
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);
192 }
193
194 FILE *
195 log_get_stream(log_channel chan) {
196         if (chan == NULL || chan->type != log_file) {
197                 errno = EINVAL;
198                 return (NULL);
199         }
200         return (chan->out.file.stream);
201 }
202
203 char *
204 log_get_filename(log_channel chan) {
205         if (chan == NULL || chan->type != log_file) {
206                 errno = EINVAL;
207                 return (NULL);
208         }
209         return (chan->out.file.name);
210 }
211
212 int
213 log_check_channel(log_context lc, int level, log_channel chan) {
214         int debugging, chan_level;
215
216         REQUIRE(lc != NULL);
217
218         debugging = ((lc->flags & LOG_OPTION_DEBUG) != 0);
219
220         /*
221          * If not debugging, short circuit debugging messages very early.
222          */
223         if (level > 0 && !debugging)
224                 return (0);
225
226         if ((chan->flags & (LOG_CHANNEL_BROKEN|LOG_CHANNEL_OFF)) != 0)
227                 return (0);
228
229         /* Some channels only log when debugging is on. */
230         if ((chan->flags & LOG_REQUIRE_DEBUG) && !debugging)
231                 return (0);
232
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;
238
239         if (level > chan_level)
240                 return (0);
241
242         return (1);
243 }
244
245 int 
246 log_check(log_context lc, int category, int level) {
247         log_channel_list lcl;
248         int debugging;
249
250         REQUIRE(lc != NULL);
251
252         debugging = ((lc->flags & LOG_OPTION_DEBUG) != 0);
253
254         /*
255          * If not debugging, short circuit debugging messages very early.
256          */
257         if (level > 0 && !debugging)
258                 return (0);
259
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];
266         }
267
268         for ( /* nothing */; lcl != NULL; lcl = lcl->next) {
269                 if (log_check_channel(lc, level, lcl->channel))
270                         return (1);
271         }
272         return (0);
273 }
274
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];
293
294         REQUIRE(lc != NULL);
295
296         debugging = (lc->flags & LOG_OPTION_DEBUG);
297
298         /*
299          * If not debugging, short circuit debugging messages very early.
300          */
301         if (level > 0 && !debugging)
302                 return;
303
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];
311         }
312
313         /*
314          * Get the current time and format it.
315          */
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);
332                 }
333         }
334
335         /*
336          * Make a string representation of the current category and level
337          */
338
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 = "";
344
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;
354         }
355
356         /*
357          * Write the message to channels.
358          */
359         for ( /* nothing */; lcl != NULL; lcl = lcl->next) {
360                 chan = lcl->channel;
361
362                 if (!log_check_channel(lc, level, chan))
363                         continue;
364
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);
371                         }
372                         did_vsprintf = 1;
373                 }
374
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;
396                         }
397                         if (chan->out.file.max_size != ULONG_MAX) {
398                                 long pos;
399                                 
400                                 pos = ftell(stream);
401                                 if (pos >= 0 &&
402                                     (unsigned long)pos >
403                                     chan->out.file.max_size) {
404                                         /*
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)
409                                          */
410                                         log_close_stream(chan);
411                                         version_rename(chan);
412                                         stream = log_open_stream(chan);
413                                         if (stream == NULL)
414                                                 break;
415                                 }
416                         }
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()");
431                 }
432         }
433 }
434
435 void
436 log_write(log_context lc, int category, int level, const char *format, ...) {
437         va_list args;
438
439         va_start(args, format);
440         log_vwrite(lc, category, level, format, args);
441         va_end(args);
442 }
443
444 /*
445  * Functions to create, set, or destroy contexts
446  */
447
448 int
449 log_new_context(int num_categories, char **category_names, log_context *lc) {
450         log_context nlc;
451
452         nlc = memget(sizeof (struct log_context));
453         if (nlc == NULL) {
454                 errno = ENOMEM;
455                 return (-1);
456         }
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);
464         }
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);
471 }
472
473 void
474 log_free_context(log_context lc) {
475         log_channel_list lcl, lcl_next;
476         log_channel chan;
477         int i;
478
479         REQUIRE(lc != NULL);
480
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));
487                 }
488         memput(lc->categories,
489                lc->num_categories * sizeof (log_channel_list));
490         memput(lc, sizeof (struct log_context));
491 }
492
493 int
494 log_add_channel(log_context lc, int category, log_channel chan) {
495         log_channel_list lcl;
496
497         if (lc == NULL || category < 0 || category >= lc->num_categories) {
498                 errno = EINVAL;
499                 return (-1);
500         }
501
502         lcl = memget(sizeof (struct log_channel_list));
503         if (lcl == NULL) {
504                 errno = ENOMEM;
505                 return(-1);
506         }
507         lcl->channel = chan;
508         lcl->next = lc->categories[category];
509         lc->categories[category] = lcl;
510         chan->references++;
511         return (0);
512 }
513
514 int
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;
518
519         if (lc == NULL || category < 0 || category >= lc->num_categories) {
520                 errno = EINVAL;
521                 return (-1);
522         }
523
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));
535                         /*
536                          * We just set found instead of returning because
537                          * the channel might be on the list more than once.
538                          */
539                         found = 1;
540                 } else
541                         prev_lcl = lcl;
542         }
543         if (!found) {
544                 errno = ENOENT;
545                 return (-1);
546         }
547         return (0);
548 }
549
550 int
551 log_option(log_context lc, int option, int value) {
552         if (lc == NULL) {
553                 errno = EINVAL;
554                 return (-1);
555         }
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);
569         }
570         return (0);
571 }
572
573 int
574 log_category_is_active(log_context lc, int category) {
575         if (lc == NULL) {
576                 errno = EINVAL;
577                 return (-1);
578         }
579         if (category >= 0 && category < lc->num_categories &&
580             lc->categories[category] != NULL)
581                 return (1);
582         return (0);
583 }
584
585 log_channel
586 log_new_syslog_channel(unsigned int flags, int level, int facility) {
587         log_channel chan;
588
589         chan = memget(sizeof (struct log_channel));
590         if (chan == NULL) {
591                 errno = ENOMEM;
592                 return (NULL);
593         }
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);
600 }
601
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;
607
608         chan = memget(sizeof (struct log_channel));
609         if (chan == NULL) {
610                 errno = ENOMEM;
611                 return (NULL);
612         }
613         chan->type = log_file;
614         chan->flags = flags;
615         chan->level = level;
616         if (name != NULL) {
617                 size_t len;
618                 
619                 len = strlen(name);
620                 /* 
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.
624                  */
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);
631                 }
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;
637         }
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);
645 }
646
647 int
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);
652         }
653         chan->out.file.owner = owner;
654         chan->out.file.group = group;
655         return (0);
656 }
657
658 log_channel
659 log_new_null_channel() {
660         log_channel chan;
661
662         chan = memget(sizeof (struct log_channel));
663         if (chan == NULL) {
664                 errno = ENOMEM;
665                 return (NULL);
666         }
667         chan->type = log_null;
668         chan->flags = LOG_CHANNEL_OFF;
669         chan->level = log_info;
670         chan->references = 0;
671         return (chan);
672 }
673
674 int
675 log_inc_references(log_channel chan) {
676         if (chan == NULL) {
677                 errno = EINVAL;
678                 return (-1);
679         }
680         chan->references++;
681         return (0);
682 }
683
684 int
685 log_dec_references(log_channel chan) {
686         if (chan == NULL || chan->references <= 0) {
687                 errno = EINVAL;
688                 return (-1);
689         }
690         chan->references--;
691         return (0);
692 }
693
694 log_channel_type
695 log_get_channel_type(log_channel chan) {
696         REQUIRE(chan != NULL);
697         
698         return (chan->type);
699 }
700
701 int
702 log_free_channel(log_channel chan) {
703         if (chan == NULL || chan->references <= 0) {
704                 errno = EINVAL;
705                 return (-1);
706         }
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);
716                 }
717                 memput(chan, sizeof (struct log_channel));
718         }
719         return (0);
720 }