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