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