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