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