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