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 #if !defined(LINT) && !defined(CODECENTER)
19 static const char rcsid[] = "$Id: logging.c,v 1.9 2008/11/14 02:36:51 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
version_rename(log_channel chan)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 *
log_open_stream(log_channel chan)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
log_close_stream(log_channel chan)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
log_close_debug_channels(log_context lc)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 *
log_get_stream(log_channel chan)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 *
log_get_filename(log_channel chan)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
log_check_channel(log_context lc,int level,log_channel chan)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
log_check(log_context lc,int category,int level)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
log_vwrite(log_context lc,int category,int level,const char * format,va_list args)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
log_write(log_context lc,int category,int level,const char * format,...)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
log_new_context(int num_categories,char ** category_names,log_context * lc)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
log_free_context(log_context lc)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
log_add_channel(log_context lc,int category,log_channel chan)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
log_remove_channel(log_context lc,int category,log_channel chan)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
log_option(log_context lc,int option,int value)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
log_category_is_active(log_context lc,int category)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
log_new_syslog_channel(unsigned int flags,int level,int facility)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
log_new_file_channel(unsigned int flags,int level,const char * name,FILE * stream,unsigned int versions,unsigned long max_size)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
log_set_file_owner(log_channel chan,uid_t owner,gid_t group)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
log_new_null_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
log_inc_references(log_channel chan)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
log_dec_references(log_channel chan)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
log_get_channel_type(log_channel chan)689 log_get_channel_type(log_channel chan) {
690 REQUIRE(chan != NULL);
691
692 return (chan->type);
693 }
694
695 int
log_free_channel(log_channel chan)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 */
717