xref: /illumos-gate/usr/src/lib/libresolv2/common/isc/logging.c (revision e4d060fb4c00d44cd578713eb9a921f594b733b8)
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
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 */
717