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