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