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