xref: /illumos-gate/usr/src/lib/libresolv2/common/isc/logging.c (revision 0c44d0008f52b6a42b9c01d3b344661217520a68)
1 /*
2  * Copyright 2006 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 #ifdef SUNW_AVOIDSTDIO_FDLIMIT
162 	stream = fdopen(fd, "aF");
163 #else
164 	stream = fdopen(fd, "a");
165 #endif
166 	if (stream == NULL) {
167 		syslog(LOG_ERR, "log_open_stream: fdopen() failed");
168 		chan->flags |= LOG_CHANNEL_BROKEN;
169 		return (NULL);
170 	}
171 	(void) fchown(fd, chan->out.file.owner, chan->out.file.group);
172 
173 	chan->out.file.stream = stream;
174 	return (stream);
175 }
176 
177 int
178 log_close_stream(log_channel chan) {
179 	FILE *stream;
180 
181 	if (chan == NULL || chan->type != log_file) {
182 		errno = EINVAL;
183 		return (0);
184 	}
185 	stream = chan->out.file.stream;
186 	chan->out.file.stream = NULL;
187 	if (stream != NULL && fclose(stream) == EOF)
188 		return (-1);
189 	return (0);
190 }
191 
192 void
193 log_close_debug_channels(log_context lc) {
194 	log_channel_list lcl;
195 	int i;
196 
197 	for (i = 0; i < lc->num_categories; i++)
198 		for (lcl = lc->categories[i]; lcl != NULL; lcl = lcl->next)
199 			if (lcl->channel->type == log_file &&
200 			    lcl->channel->out.file.stream != NULL &&
201 			    lcl->channel->flags & LOG_REQUIRE_DEBUG)
202 				(void)log_close_stream(lcl->channel);
203 }
204 
205 FILE *
206 log_get_stream(log_channel chan) {
207 	if (chan == NULL || chan->type != log_file) {
208 		errno = EINVAL;
209 		return (NULL);
210 	}
211 	return (chan->out.file.stream);
212 }
213 
214 char *
215 log_get_filename(log_channel chan) {
216 	if (chan == NULL || chan->type != log_file) {
217 		errno = EINVAL;
218 		return (NULL);
219 	}
220 	return (chan->out.file.name);
221 }
222 
223 int
224 log_check_channel(log_context lc, int level, log_channel chan) {
225 	int debugging, chan_level;
226 
227 	REQUIRE(lc != NULL);
228 
229 	debugging = ((lc->flags & LOG_OPTION_DEBUG) != 0);
230 
231 	/*
232 	 * If not debugging, short circuit debugging messages very early.
233 	 */
234 	if (level > 0 && !debugging)
235 		return (0);
236 
237 	if ((chan->flags & (LOG_CHANNEL_BROKEN|LOG_CHANNEL_OFF)) != 0)
238 		return (0);
239 
240 	/* Some channels only log when debugging is on. */
241 	if ((chan->flags & LOG_REQUIRE_DEBUG) && !debugging)
242 		return (0);
243 
244 	/* Some channels use the global level. */
245 	if ((chan->flags & LOG_USE_CONTEXT_LEVEL) != 0) {
246 		chan_level = lc->level;
247 	} else
248 		chan_level = chan->level;
249 
250 	if (level > chan_level)
251 		return (0);
252 
253 	return (1);
254 }
255 
256 int
257 log_check(log_context lc, int category, int level) {
258 	log_channel_list lcl;
259 	int debugging;
260 
261 	REQUIRE(lc != NULL);
262 
263 	debugging = ((lc->flags & LOG_OPTION_DEBUG) != 0);
264 
265 	/*
266 	 * If not debugging, short circuit debugging messages very early.
267 	 */
268 	if (level > 0 && !debugging)
269 		return (0);
270 
271 	if (category < 0 || category > lc->num_categories)
272 		category = 0;		/* use default */
273 	lcl = lc->categories[category];
274 	if (lcl == NULL) {
275 		category = 0;
276 		lcl = lc->categories[0];
277 	}
278 
279 	for ( /* nothing */; lcl != NULL; lcl = lcl->next) {
280 		if (log_check_channel(lc, level, lcl->channel))
281 			return (1);
282 	}
283 	return (0);
284 }
285 
286 void
287 log_vwrite(log_context lc, int category, int level, const char *format,
288 	   va_list args) {
289 	log_channel_list lcl;
290 	int pri, debugging, did_vsprintf = 0;
291 	int original_category;
292 	FILE *stream;
293 	log_channel chan;
294 	struct timeval tv;
295 	struct tm *local_tm;
296 #ifdef HAVE_TIME_R
297 	struct tm tm_tmp;
298 #endif
299 	time_t tt;
300 	const char *category_name;
301 	const char *level_str;
302 	char time_buf[256];
303 	char level_buf[256];
304 
305 	REQUIRE(lc != NULL);
306 
307 	debugging = (lc->flags & LOG_OPTION_DEBUG);
308 
309 	/*
310 	 * If not debugging, short circuit debugging messages very early.
311 	 */
312 	if (level > 0 && !debugging)
313 		return;
314 
315 	if (category < 0 || category > lc->num_categories)
316 		category = 0;		/* use default */
317 	original_category = category;
318 	lcl = lc->categories[category];
319 	if (lcl == NULL) {
320 		category = 0;
321 		lcl = lc->categories[0];
322 	}
323 
324 	/*
325 	 * Get the current time and format it.
326 	 */
327 	time_buf[0]='\0';
328 	if (gettimeofday(&tv, NULL) < 0) {
329 		syslog(LOG_INFO, "gettimeofday failed in log_vwrite()");
330 	} else {
331 		tt = tv.tv_sec;
332 #ifdef HAVE_TIME_R
333 		local_tm = localtime_r(&tt, &tm_tmp);
334 #else
335 		local_tm = localtime(&tt);
336 #endif
337 		if (local_tm != NULL) {
338 			sprintf(time_buf, "%02d-%s-%4d %02d:%02d:%02d.%03ld ",
339 				local_tm->tm_mday, months[local_tm->tm_mon],
340 				local_tm->tm_year+1900, local_tm->tm_hour,
341 				local_tm->tm_min, local_tm->tm_sec,
342 				(long)tv.tv_usec/1000);
343 		}
344 	}
345 
346 	/*
347 	 * Make a string representation of the current category and level
348 	 */
349 
350 	if (lc->category_names != NULL &&
351 	    lc->category_names[original_category] != NULL)
352 		category_name = lc->category_names[original_category];
353 	else
354 		category_name = "";
355 
356 	if (level >= log_critical) {
357 		if (level >= 0) {
358 			sprintf(level_buf, "debug %d: ", level);
359 			level_str = level_buf;
360 		} else
361 			level_str = level_text[-level-1];
362 	} else {
363 		sprintf(level_buf, "level %d: ", level);
364 		level_str = level_buf;
365 	}
366 
367 	/*
368 	 * Write the message to channels.
369 	 */
370 	for ( /* nothing */; lcl != NULL; lcl = lcl->next) {
371 		chan = lcl->channel;
372 
373 		if (!log_check_channel(lc, level, chan))
374 			continue;
375 
376 		if (!did_vsprintf) {
377 			if (VSPRINTF((lc->buffer, format, args)) >
378 			    LOG_BUFFER_SIZE) {
379 				syslog(LOG_CRIT,
380 				       "memory overrun in log_vwrite()");
381 				exit(1);
382 			}
383 			did_vsprintf = 1;
384 		}
385 
386 		switch (chan->type) {
387 		case log_syslog:
388 			if (level >= log_critical)
389 				pri = (level >= 0) ? 0 : -level;
390 			else
391 				pri = -log_critical;
392 			syslog(chan->out.facility|syslog_priority[pri],
393 			       "%s%s%s%s",
394 			       (chan->flags & LOG_TIMESTAMP) ?	time_buf : "",
395 			       (chan->flags & LOG_PRINT_CATEGORY) ?
396 			       category_name : "",
397 			       (chan->flags & LOG_PRINT_LEVEL) ?
398 			       level_str : "",
399 			       lc->buffer);
400 			break;
401 		case log_file:
402 			stream = chan->out.file.stream;
403 			if (stream == NULL) {
404 				stream = log_open_stream(chan);
405 				if (stream == NULL)
406 					break;
407 			}
408 			if (chan->out.file.max_size != ULONG_MAX) {
409 				long pos;
410 
411 				pos = ftell(stream);
412 				if (pos >= 0 &&
413 				    (unsigned long)pos >
414 				    chan->out.file.max_size) {
415 					/*
416 					 * try to roll over the log files,
417 					 * ignoring all all return codes
418 					 * except the open (we don't want
419 					 * to write any more anyway)
420 					 */
421 					log_close_stream(chan);
422 					version_rename(chan);
423 					stream = log_open_stream(chan);
424 					if (stream == NULL)
425 						break;
426 				}
427 			}
428 			fprintf(stream, "%s%s%s%s\n",
429 				(chan->flags & LOG_TIMESTAMP) ?	time_buf : "",
430 				(chan->flags & LOG_PRINT_CATEGORY) ?
431 				category_name : "",
432 				(chan->flags & LOG_PRINT_LEVEL) ?
433 				level_str : "",
434 				lc->buffer);
435 			fflush(stream);
436 			break;
437 		case log_null:
438 			break;
439 		default:
440 			syslog(LOG_ERR,
441 			       "unknown channel type in log_vwrite()");
442 		}
443 	}
444 }
445 
446 void
447 log_write(log_context lc, int category, int level, const char *format, ...) {
448 	va_list args;
449 
450 	va_start(args, format);
451 	log_vwrite(lc, category, level, format, args);
452 	va_end(args);
453 }
454 
455 /*
456  * Functions to create, set, or destroy contexts
457  */
458 
459 int
460 log_new_context(int num_categories, char **category_names, log_context *lc) {
461 	log_context nlc;
462 
463 	nlc = memget(sizeof (struct log_context));
464 	if (nlc == NULL) {
465 		errno = ENOMEM;
466 		return (-1);
467 	}
468 	nlc->num_categories = num_categories;
469 	nlc->category_names = category_names;
470 	nlc->categories = memget(num_categories * sizeof (log_channel_list));
471 	if (nlc->categories == NULL) {
472 		memput(nlc, sizeof (struct log_context));
473 		errno = ENOMEM;
474 		return (-1);
475 	}
476 	memset(nlc->categories, '\0',
477 	       num_categories * sizeof (log_channel_list));
478 	nlc->flags = 0U;
479 	nlc->level = 0;
480 	*lc = nlc;
481 	return (0);
482 }
483 
484 void
485 log_free_context(log_context lc) {
486 	log_channel_list lcl, lcl_next;
487 	log_channel chan;
488 	int i;
489 
490 	REQUIRE(lc != NULL);
491 
492 	for (i = 0; i < lc->num_categories; i++)
493 		for (lcl = lc->categories[i]; lcl != NULL; lcl = lcl_next) {
494 			lcl_next = lcl->next;
495 			chan = lcl->channel;
496 			(void)log_free_channel(chan);
497 			memput(lcl, sizeof (struct log_channel_list));
498 		}
499 	memput(lc->categories,
500 	       lc->num_categories * sizeof (log_channel_list));
501 	memput(lc, sizeof (struct log_context));
502 }
503 
504 int
505 log_add_channel(log_context lc, int category, log_channel chan) {
506 	log_channel_list lcl;
507 
508 	if (lc == NULL || category < 0 || category >= lc->num_categories) {
509 		errno = EINVAL;
510 		return (-1);
511 	}
512 
513 	lcl = memget(sizeof (struct log_channel_list));
514 	if (lcl == NULL) {
515 		errno = ENOMEM;
516 		return(-1);
517 	}
518 	lcl->channel = chan;
519 	lcl->next = lc->categories[category];
520 	lc->categories[category] = lcl;
521 	chan->references++;
522 	return (0);
523 }
524 
525 int
526 log_remove_channel(log_context lc, int category, log_channel chan) {
527 	log_channel_list lcl, prev_lcl, next_lcl;
528 	int found = 0;
529 
530 	if (lc == NULL || category < 0 || category >= lc->num_categories) {
531 		errno = EINVAL;
532 		return (-1);
533 	}
534 
535 	for (prev_lcl = NULL, lcl = lc->categories[category];
536 	     lcl != NULL;
537 	     lcl = next_lcl) {
538 		next_lcl = lcl->next;
539 		if (lcl->channel == chan) {
540 			log_free_channel(chan);
541 			if (prev_lcl != NULL)
542 				prev_lcl->next = next_lcl;
543 			else
544 				lc->categories[category] = next_lcl;
545 			memput(lcl, sizeof (struct log_channel_list));
546 			/*
547 			 * We just set found instead of returning because
548 			 * the channel might be on the list more than once.
549 			 */
550 			found = 1;
551 		} else
552 			prev_lcl = lcl;
553 	}
554 	if (!found) {
555 		errno = ENOENT;
556 		return (-1);
557 	}
558 	return (0);
559 }
560 
561 int
562 log_option(log_context lc, int option, int value) {
563 	if (lc == NULL) {
564 		errno = EINVAL;
565 		return (-1);
566 	}
567 	switch (option) {
568 	case LOG_OPTION_DEBUG:
569 		if (value)
570 			lc->flags |= option;
571 		else
572 			lc->flags &= ~option;
573 		break;
574 	case LOG_OPTION_LEVEL:
575 		lc->level = value;
576 		break;
577 	default:
578 		errno = EINVAL;
579 		return (-1);
580 	}
581 	return (0);
582 }
583 
584 int
585 log_category_is_active(log_context lc, int category) {
586 	if (lc == NULL) {
587 		errno = EINVAL;
588 		return (-1);
589 	}
590 	if (category >= 0 && category < lc->num_categories &&
591 	    lc->categories[category] != NULL)
592 		return (1);
593 	return (0);
594 }
595 
596 log_channel
597 log_new_syslog_channel(unsigned int flags, int level, int facility) {
598 	log_channel chan;
599 
600 	chan = memget(sizeof (struct log_channel));
601 	if (chan == NULL) {
602 		errno = ENOMEM;
603 		return (NULL);
604 	}
605 	chan->type = log_syslog;
606 	chan->flags = flags;
607 	chan->level = level;
608 	chan->out.facility = facility;
609 	chan->references = 0;
610 	return (chan);
611 }
612 
613 log_channel
614 log_new_file_channel(unsigned int flags, int level,
615 		     const char *name, FILE *stream, unsigned int versions,
616 		     unsigned long max_size) {
617 	log_channel chan;
618 
619 	chan = memget(sizeof (struct log_channel));
620 	if (chan == NULL) {
621 		errno = ENOMEM;
622 		return (NULL);
623 	}
624 	chan->type = log_file;
625 	chan->flags = flags;
626 	chan->level = level;
627 	if (name != NULL) {
628 		size_t len;
629 
630 		len = strlen(name);
631 		/*
632 		 * Quantize length to a multiple of 256.  There's space for the
633 		 * NUL, since if len is a multiple of 256, the size chosen will
634 		 * be the next multiple.
635 		 */
636 		chan->out.file.name_size = ((len / 256) + 1) * 256;
637 		chan->out.file.name = memget(chan->out.file.name_size);
638 		if (chan->out.file.name == NULL) {
639 			memput(chan, sizeof (struct log_channel));
640 			errno = ENOMEM;
641 			return (NULL);
642 		}
643 		/* This is safe. */
644 		strcpy(chan->out.file.name, name);
645 	} else {
646 		chan->out.file.name_size = 0;
647 		chan->out.file.name = NULL;
648 	}
649 	chan->out.file.stream = stream;
650 	chan->out.file.versions = versions;
651 	chan->out.file.max_size = max_size;
652 	chan->out.file.owner = getuid();
653 	chan->out.file.group = getgid();
654 	chan->references = 0;
655 	return (chan);
656 }
657 
658 int
659 log_set_file_owner(log_channel chan, uid_t owner, gid_t group) {
660 	if (chan->type != log_file) {
661 		errno = EBADF;
662 		return (-1);
663 	}
664 	chan->out.file.owner = owner;
665 	chan->out.file.group = group;
666 	return (0);
667 }
668 
669 log_channel
670 log_new_null_channel() {
671 	log_channel chan;
672 
673 	chan = memget(sizeof (struct log_channel));
674 	if (chan == NULL) {
675 		errno = ENOMEM;
676 		return (NULL);
677 	}
678 	chan->type = log_null;
679 	chan->flags = LOG_CHANNEL_OFF;
680 	chan->level = log_info;
681 	chan->references = 0;
682 	return (chan);
683 }
684 
685 int
686 log_inc_references(log_channel chan) {
687 	if (chan == NULL) {
688 		errno = EINVAL;
689 		return (-1);
690 	}
691 	chan->references++;
692 	return (0);
693 }
694 
695 int
696 log_dec_references(log_channel chan) {
697 	if (chan == NULL || chan->references <= 0) {
698 		errno = EINVAL;
699 		return (-1);
700 	}
701 	chan->references--;
702 	return (0);
703 }
704 
705 log_channel_type
706 log_get_channel_type(log_channel chan) {
707 	REQUIRE(chan != NULL);
708 
709 	return (chan->type);
710 }
711 
712 int
713 log_free_channel(log_channel chan) {
714 	if (chan == NULL || chan->references <= 0) {
715 		errno = EINVAL;
716 		return (-1);
717 	}
718 	chan->references--;
719 	if (chan->references == 0) {
720 		if (chan->type == log_file) {
721 			if ((chan->flags & LOG_CLOSE_STREAM) &&
722 			    chan->out.file.stream != NULL)
723 				(void)fclose(chan->out.file.stream);
724 			if (chan->out.file.name != NULL)
725 				memput(chan->out.file.name,
726 				       chan->out.file.name_size);
727 		}
728 		memput(chan, sizeof (struct log_channel));
729 	}
730 	return (0);
731 }
732