1/*
2 * Copyright (c) 2004-2011 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include <TargetConditionals.h>
25
26#if TARGET_IPHONE_SIMULATOR
27struct _not_empty;
28#else
29
30#include <sys/types.h>
31#include <sys/stat.h>
32#include <sys/socket.h>
33#include <sys/un.h>
34#include <sys/uio.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <unistd.h>
39#include <ctype.h>
40#include <fcntl.h>
41#include <errno.h>
42#include <netdb.h>
43#include <pthread.h>
44#include <notify.h>
45#include "daemon.h"
46
47#define MY_ID "bsd_out"
48
49#define _PATH_WALL "/usr/bin/wall"
50#define ASL_KEY_FACILITY "Facility"
51#define FACILITY_KERNEL "kern"
52#define _PATH_CONSOLE "/dev/console"
53
54#define DST_TYPE_NONE 0
55#define DST_TYPE_FILE 1
56#define DST_TYPE_CONS 2
57#define DST_TYPE_SOCK 3
58#define DST_TYPE_WALL 4
59#define DST_TYPE_NOTE 5
60
61#define CLOSE_ON_IDLE_SEC 300
62
63static dispatch_queue_t bsd_out_queue;
64static dispatch_source_t bsd_idle_timer;
65
66struct config_rule
67{
68	uint32_t count;
69	char *dst;
70	int fd;
71	int type;
72	struct sockaddr *addr;
73	char **facility;
74	uint32_t *fac_prefix_len;
75	int *pri;
76	uint32_t last_hash;
77	uint32_t last_count;
78	time_t last_time;
79	dispatch_source_t dup_timer;
80	char *last_msg;
81	TAILQ_ENTRY(config_rule) entries;
82};
83
84static TAILQ_HEAD(cr, config_rule) bsd_out_rule;
85
86extern uint32_t asl_core_string_hash(const char *s, uint32_t inlen);
87
88static int
89_level_for_name(const char *name)
90{
91	if (name == NULL) return -1;
92
93	if (!strcasecmp(name, "emerg")) return ASL_LEVEL_EMERG;
94	if (!strcasecmp(name, "panic")) return ASL_LEVEL_EMERG;
95	if (!strcasecmp(name, "alert")) return ASL_LEVEL_ALERT;
96	if (!strcasecmp(name, "crit")) return ASL_LEVEL_CRIT;
97	if (!strcasecmp(name, "err")) return ASL_LEVEL_ERR;
98	if (!strcasecmp(name, "error")) return ASL_LEVEL_ERR;
99	if (!strcasecmp(name, "warn")) return ASL_LEVEL_WARNING;
100	if (!strcasecmp(name, "warning")) return ASL_LEVEL_WARNING;
101	if (!strcasecmp(name, "notice")) return ASL_LEVEL_NOTICE;
102	if (!strcasecmp(name, "info")) return ASL_LEVEL_INFO;
103	if (!strcasecmp(name, "debug")) return ASL_LEVEL_DEBUG;
104	if (!strcmp(name, "*")) return ASL_LEVEL_DEBUG;
105
106	/* special case */
107	if (!strcasecmp(name, "none")) return -2;
108
109	return -1;
110}
111
112static int
113_syslog_dst_open(struct config_rule *r)
114{
115	int i;
116	char *node, *serv;
117	struct addrinfo hints, *gai, *ai;
118
119	if (r == NULL) return -1;
120	if (r->fd != -1) return 0;
121
122	if (r->dst[0] == '/')
123	{
124		r->fd = open(r->dst, O_WRONLY | O_APPEND | O_CREAT | O_NOCTTY, 0644);
125		if (r->fd < 0)
126		{
127			asldebug("%s: open failed for file: %s (%s)\n", MY_ID, r->dst, strerror(errno));
128			return -1;
129		}
130
131		r->type = DST_TYPE_FILE;
132		if (!strcmp(r->dst, _PATH_CONSOLE)) r->type = DST_TYPE_CONS;
133
134		return 0;
135	}
136
137	if (r->dst[0] == '!')
138	{
139		r->type = DST_TYPE_NOTE;
140		r->fd = -1;
141		return 0;
142	}
143
144	if (r->dst[0] == '@')
145	{
146		node = strdup(r->dst + 1);
147		if (node == NULL) return -1;
148
149		serv = NULL;
150		serv = strrchr(node, ':');
151		if (serv != NULL) *serv++ = '\0';
152		else serv = "syslog";
153
154		memset(&hints, 0, sizeof(hints));
155		hints.ai_family = PF_UNSPEC;
156		hints.ai_socktype = SOCK_DGRAM;
157		i = getaddrinfo(node, serv, &hints, &gai);
158		free(node);
159		if (i != 0)
160		{
161			asldebug("%s: getaddrinfo failed for node %s service %s: (%s)\n", MY_ID, node, serv, gai_strerror(i));
162			return -1;
163		}
164
165		for (ai = gai; ai != NULL; ai = ai->ai_next)
166		{
167			r->fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
168			if (r->fd < 0) continue;
169
170			r->addr = (struct sockaddr *)malloc(ai->ai_addrlen);
171			if (r->addr == NULL) return -1;
172
173			memcpy(r->addr, ai->ai_addr, ai->ai_addrlen);
174
175			break;
176		}
177
178		freeaddrinfo(gai);
179
180		if (r->fd < 0)
181		{
182			asldebug("%s: connection failed for %s\n", MY_ID, (r->dst) + 1);
183			free(r->addr);
184			r->addr = NULL;
185			return -1;
186		}
187
188		if (fcntl(r->fd, F_SETFL, O_NONBLOCK) < 0)
189		{
190			close(r->fd);
191			r->fd = -1;
192			asldebug("%s: couldn't set O_NONBLOCK for fd %d: %s\n", MY_ID, r->fd, strerror(errno));
193			free(r->addr);
194			r->addr = NULL;
195			return -1;
196		}
197
198		r->type = DST_TYPE_SOCK;
199		return 0;
200
201	}
202
203	if (strcmp(r->dst, "*") == 0)
204	{
205		r->type = DST_TYPE_WALL;
206		r->fd = -1;
207		return 0;
208	}
209
210	/* Can't deal with dst! */
211	asldebug("%s: unsupported / unknown output name: %s\n", MY_ID, r->dst);
212	return -1;
213}
214
215static void
216_syslog_dst_close(struct config_rule *r)
217{
218	if (r == NULL) return;
219
220	if (r->addr != NULL)
221	{
222		free(r->addr);
223		r->addr = NULL;
224	}
225
226	switch (r->type)
227	{
228		case DST_TYPE_FILE:
229		case DST_TYPE_CONS:
230		{
231			if (r->fd >= 0) close(r->fd);
232			r->fd = -1;
233			break;
234		}
235
236		case DST_TYPE_SOCK:
237		{
238			if (r->fd >= 0) close(r->fd);
239			r->fd = -1;
240			break;
241		}
242
243		case DST_TYPE_NONE:
244		case DST_TYPE_WALL:
245		case DST_TYPE_NOTE:
246		default:
247		{
248			/* do nothing */
249			return;
250		}
251	}
252}
253
254static char *
255_clean_facility_name(char *s)
256{
257	uint32_t len;
258	char *p, *out;
259
260	if (s == NULL) return NULL;
261	len = strlen(s);
262	if (len == 0) return NULL;
263
264	p = s;
265
266	if ((*s == '\'') || (*s == '"'))
267	{
268		len--;
269		p++;
270		if (p[len - 1] == *s) len --;
271	}
272
273	out = calloc(1, len + 1);
274	if (out == NULL) return NULL;
275
276	memcpy(out, p, len);
277	return out;
278}
279
280static int
281_parse_line(char *s)
282{
283	char **semi, **comma, *star;
284	int i, j, n, lasts, lastc, pri;
285	struct config_rule *out;
286
287	if (s == NULL) return -1;
288	while ((*s == ' ') || (*s == '\t')) s++;
289	if (*s == '#') return -1;
290
291	semi = explode(s, "; \t");
292
293	if (semi == NULL) return -1;
294	out = (struct config_rule *)calloc(1, sizeof(struct config_rule));
295	if (out == NULL) return -1;
296	out->fd = -1;
297
298	n = 0;
299	lasts = -1;
300	for (i = 0; semi[i] != NULL; i++)
301	{
302		if (semi[i][0] == '\0') continue;
303		n++;
304		lasts = i;
305	}
306
307	out->dst = strdup(semi[lasts]);
308	if (out->dst == NULL) return -1;
309
310	for (i = 0; i < lasts; i++)
311	{
312		if (semi[i][0] == '\0') continue;
313		comma = explode(semi[i], ",.");
314		lastc = -1;
315		for (j = 0; comma[j] != NULL; j++)
316		{
317			if (comma[j][0] == '\0') continue;
318			lastc = j;
319		}
320
321		for (j = 0; j < lastc; j++)
322		{
323			if (comma[j][0] == '\0') continue;
324			pri = _level_for_name(comma[lastc]);
325			if (pri == -1) continue;
326
327			if (out->count == 0)
328			{
329				out->facility = (char **)calloc(1, sizeof(char *));
330				out->fac_prefix_len = (uint32_t *)calloc(1, sizeof(uint32_t));
331				out->pri = (int *)calloc(1, sizeof(int));
332			}
333			else
334			{
335				out->facility = (char **)reallocf(out->facility, (out->count + 1) * sizeof(char *));
336				out->fac_prefix_len = (uint32_t *)reallocf(out->fac_prefix_len, (out->count + 1) * sizeof(uint32_t));
337				out->pri = (int *)reallocf(out->pri, (out->count + 1) * sizeof(int));
338			}
339
340			if (out->facility == NULL) return -1;
341			if (out->fac_prefix_len == NULL) return -1;
342			if (out->pri == NULL) return -1;
343
344			out->facility[out->count] = _clean_facility_name(comma[j]);
345			if (out->facility[out->count] == NULL) return -1;
346
347			out->fac_prefix_len[out->count] = 0;
348			star = strchr(out->facility[out->count], '*');
349			if (star != NULL) out->fac_prefix_len[out->count] = (uint32_t)(star - out->facility[out->count]);
350
351			out->pri[out->count] = pri;
352			out->count++;
353		}
354
355		free_string_list(comma);
356	}
357
358	free_string_list(semi);
359
360	TAILQ_INSERT_TAIL(&bsd_out_rule, out, entries);
361
362	return 0;
363}
364
365static int
366_bsd_send_repeat_msg(struct config_rule *r)
367{
368	char vt[32], *msg;
369	time_t tick;
370	int len, status;
371
372	if (r == NULL) return -1;
373	if (r->type != DST_TYPE_FILE) return 0;
374	if (r->last_count == 0) return 0;
375
376	/* stop the timer */
377	dispatch_suspend(r->dup_timer);
378
379	tick = time(NULL);
380	memset(vt, 0, sizeof(vt));
381	ctime_r(&tick, vt);
382	vt[19] = '\0';
383
384	msg = NULL;
385	asprintf(&msg, "%s: --- last message repeated %u time%s ---\n", vt + 4, r->last_count, (r->last_count == 1) ? "" : "s");
386	r->last_count = 0;
387	if (msg == NULL) return -1;
388
389	len = strlen(msg);
390	status = write(r->fd, msg, len);
391	if ((status < 0) || (status < len))
392	{
393		asldebug("%s: error writing repeat message (%s): %s\n", MY_ID, r->dst, strerror(errno));
394
395		/* Try re-opening the file (once) and write again */
396		close(r->fd);
397		r->fd = open(r->dst, O_WRONLY | O_APPEND | O_CREAT | O_NOCTTY, 0644);
398		if (r->fd < 0)
399		{
400			asldebug("%s: re-open failed for file: %s (%s)\n", MY_ID, r->dst, strerror(errno));
401			free(msg);
402			return -1;
403		}
404
405		status = write(r->fd, msg, len);
406		if ((status < 0) || (status < len))
407		{
408			asldebug("%s: error re-writing message (%s): %s\n", MY_ID, r->dst, strerror(errno));
409			free(msg);
410			return -1;
411		}
412	}
413
414	free(msg);
415	return 0;
416}
417
418static int
419_bsd_send(asl_msg_t *msg, struct config_rule *r, char **out, char **fwd, time_t now)
420{
421	char *sf, *outmsg;
422	const char *vlevel, *vfacility;
423	size_t outlen;
424	int pf, fc, status, is_dup, do_write;
425	uint32_t msg_hash, n;
426
427	if (out == NULL) return -1;
428	if (fwd == NULL) return -1;
429	if (r == NULL) return -1;
430
431	_syslog_dst_open(r);
432
433	if (r->type == DST_TYPE_NOTE)
434	{
435		notify_post(r->dst+1);
436		return 0;
437	}
438
439	msg_hash = 0;
440	outmsg = NULL;
441
442	/* Build output string if it hasn't been built by a previous rule-match */
443	if (*out == NULL)
444	{
445		*out = asl_format_message((asl_msg_t *)msg, ASL_MSG_FMT_BSD, ASL_TIME_FMT_LCL, ASL_ENCODE_SAFE, &n);
446		if (*out == NULL) return -1;
447	}
448
449	/* check if message is a duplicate of the last message, and inside the dup time window */
450	is_dup = 0;
451	if ((global.bsd_max_dup_time > 0) && (*out != NULL) && (r->last_msg != NULL))
452	{
453		msg_hash = asl_core_string_hash(*out + 16, strlen(*out + 16));
454		if ((r->last_hash == msg_hash) && (!strcmp(r->last_msg, *out + 16)))
455		{
456			if ((now - r->last_time) < global.bsd_max_dup_time) is_dup = 1;
457		}
458	}
459
460	if ((*fwd == NULL) && (r->type == DST_TYPE_SOCK))
461	{
462		pf = 7;
463		vlevel = asl_msg_get_val_for_key(msg, ASL_KEY_LEVEL);
464		if (vlevel != NULL) pf = atoi(vlevel);
465
466		fc = asl_syslog_faciliy_name_to_num(asl_msg_get_val_for_key(msg, ASL_KEY_FACILITY));
467		if (fc > 0) pf |= fc;
468
469		sf = NULL;
470		asprintf(&sf, "<%d>%s", pf, *out);
471		if (sf == NULL) return -1;
472
473		*fwd = sf;
474	}
475
476	if (r->type == DST_TYPE_SOCK) outlen = strlen(*fwd);
477	else outlen = strlen(*out);
478
479	if ((r->type == DST_TYPE_FILE) || (r->type == DST_TYPE_CONS))
480	{
481		/*
482		 * If current message is NOT a duplicate and r->last_count > 0
483		 * we need to write a "last message was repeated N times" log entry
484		 */
485		if ((r->type == DST_TYPE_FILE) && (is_dup == 0) && (r->last_count > 0)) _bsd_send_repeat_msg(r);
486
487		do_write = 1;
488
489		/*
490		 * Special case for kernel messages.
491		 * Don't write kernel messages to /dev/console.
492		 * The kernel printf routine already sends them to /dev/console
493		 * so writing them here would cause duplicates.
494		 */
495		vfacility = asl_msg_get_val_for_key(msg, ASL_KEY_FACILITY);
496		if ((vfacility != NULL) && (!strcmp(vfacility, FACILITY_KERNEL)) && (r->type == DST_TYPE_CONS)) do_write = 0;
497		if ((do_write == 1) && (r->type == DST_TYPE_FILE) && (is_dup == 1))
498		{
499			do_write = 0;
500
501			if (r->dup_timer == NULL)
502			{
503				/* create a timer to flush dups on this file */
504				r->dup_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, bsd_out_queue);
505				dispatch_source_set_event_handler(r->dup_timer, ^{ _bsd_send_repeat_msg(r); });
506			}
507
508			if (r->last_count == 0)
509			{
510				/* start the timer */
511				dispatch_source_set_timer(r->dup_timer, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * global.bsd_max_dup_time), DISPATCH_TIME_FOREVER, 0);
512				dispatch_resume(r->dup_timer);
513			}
514		}
515
516		if (do_write == 0) status = outlen;
517		else status = write(r->fd, *out, outlen);
518
519		if ((status < 0) || (status < outlen))
520		{
521			asldebug("%s: error writing message (%s): %s\n", MY_ID, r->dst, strerror(errno));
522
523			/* Try re-opening the file (once) and write again */
524			close(r->fd);
525			r->fd = open(r->dst, O_WRONLY | O_APPEND | O_CREAT | O_NOCTTY, 0644);
526			if (r->fd < 0)
527			{
528				asldebug("%s: re-open failed for file: %s (%s)\n", MY_ID, r->dst, strerror(errno));
529				return -1;
530			}
531
532			status = write(r->fd, *out, outlen);
533			if ((status < 0) || (status < outlen))
534			{
535				asldebug("%s: error re-writing message (%s): %s\n", MY_ID, r->dst, strerror(errno));
536			}
537		}
538	}
539	else if ((r->type == DST_TYPE_SOCK) && (r->addr != NULL))
540	{
541		status = sendto(r->fd, *fwd, outlen, 0, r->addr, r->addr->sa_len);
542		if (status < 0) asldebug("%s: error sending message (%s): %s\n", MY_ID, r->dst, strerror(errno));
543	}
544	else if (r->type == DST_TYPE_WALL)
545	{
546#if !TARGET_OS_EMBEDDED
547		FILE *pw = popen(_PATH_WALL, "w");
548		if (pw < 0)
549		{
550			asldebug("%s: error sending wall message: %s\n", MY_ID, strerror(errno));
551			return -1;
552		}
553
554		fprintf(pw, "%s", *out);
555		pclose(pw);
556#endif
557	}
558
559	if (is_dup == 1)
560	{
561		r->last_count++;
562	}
563	else
564	{
565		free(r->last_msg);
566		r->last_msg = NULL;
567
568		if (*out != NULL) r->last_msg = strdup(*out + 16);
569
570		r->last_hash = msg_hash;
571		r->last_count = 0;
572		r->last_time = now;
573	}
574
575	return 0;
576}
577
578static int
579_bsd_rule_match(asl_msg_t *msg, struct config_rule *r)
580{
581	uint32_t i, test, f;
582	int32_t pri;
583	const char *val;
584
585	if (msg == NULL) return 0;
586	if (r == NULL) return 0;
587	if (r->count == 0) return 0;
588
589	test = 0;
590
591	for (i = 0; i < r->count; i++)
592	{
593		if (r->pri[i] == -1) continue;
594
595		if ((test == 1) && (r->pri[i] >= 0)) continue;
596		if ((test == 0) && (r->pri[i] == -2)) continue;
597
598		f = 0;
599		val = asl_msg_get_val_for_key(msg, ASL_KEY_FACILITY);
600
601		if (strcmp(r->facility[i], "*") == 0)
602		{
603			f = 1;
604		}
605		else if ((r->fac_prefix_len[i] > 0) && (strncasecmp(r->facility[i], val, r->fac_prefix_len[i]) == 0))
606		{
607			f = 1;
608		}
609		else if ((val != NULL) && (strcasecmp(r->facility[i], val) == 0))
610		{
611			f = 1;
612		}
613
614		if (f == 0) continue;
615
616		/* Turn off matching facility with priority "none" */
617		if (r->pri[i] == -2)
618		{
619			test = 0;
620			continue;
621		}
622
623		val = asl_msg_get_val_for_key(msg, ASL_KEY_LEVEL);
624		if (val == NULL) continue;
625
626		pri = atoi(val);
627		if (pri < 0) continue;
628
629		if (pri <= r->pri[i]) test = 1;
630	}
631
632	return test;
633}
634
635static int
636_bsd_match_and_send(asl_msg_t *msg)
637{
638	struct config_rule *r;
639	char *out, *fwd;
640	time_t now;
641
642	if (msg == NULL) return -1;
643
644	out = NULL;
645	fwd = NULL;
646
647	now = time(NULL);
648
649	for (r = bsd_out_rule.tqh_first; r != NULL; r = r->entries.tqe_next)
650	{
651		if (_bsd_rule_match(msg, r) == 1) _bsd_send(msg, r, &out, &fwd, now);
652	}
653
654	free(out);
655	free(fwd);
656
657	return 0;
658}
659
660void
661bsd_out_message(asl_msg_t *msg)
662{
663	if (msg == NULL) return;
664
665	OSAtomicIncrement32(&global.bsd_queue_count);
666	asl_msg_retain((asl_msg_t *)msg);
667
668	dispatch_async(bsd_out_queue, ^{
669		_bsd_match_and_send(msg);
670		asl_msg_release((asl_msg_t *)msg);
671		OSAtomicDecrement32(&global.bsd_queue_count);
672	});
673}
674
675static void
676_bsd_close_idle_files()
677{
678	time_t now;
679	struct config_rule *r;
680	uint64_t delta;
681
682	now = time(NULL);
683
684	for (r = bsd_out_rule.tqh_first; r != NULL; r = r->entries.tqe_next)
685	{
686		/* only applies to files */
687		if (r->type != DST_TYPE_FILE) continue;
688
689		/*
690		 * If the last message repeat count is non-zero, a _bsd_flush_duplicates()
691		 * call will occur within 30 seconds.  Don't bother closing the file.
692		 */
693		if (r->last_count > 0) continue;
694
695		delta = now - r->last_time;
696		if (delta > CLOSE_ON_IDLE_SEC) _syslog_dst_close(r);
697	}
698}
699
700static int
701_parse_config_file(const char *confname)
702{
703	FILE *cf;
704	char *line;
705
706	cf = fopen(confname, "r");
707	if (cf == NULL) return 1;
708
709	while (NULL != (line = get_line_from_file(cf)))
710	{
711		_parse_line(line);
712		free(line);
713	}
714
715	fclose(cf);
716
717	return 0;
718}
719
720int
721bsd_out_init(void)
722{
723	static dispatch_once_t once;
724
725	asldebug("%s: init\n", MY_ID);
726
727	TAILQ_INIT(&bsd_out_rule);
728	_parse_config_file(_PATH_SYSLOG_CONF);
729
730	dispatch_once(&once, ^{
731		bsd_out_queue = dispatch_queue_create("BSD Out Queue", NULL);
732
733		/* start a timer to close idle files */
734		bsd_idle_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, bsd_out_queue);
735		dispatch_source_set_event_handler(bsd_idle_timer, ^{ _bsd_close_idle_files(); });
736		dispatch_source_set_timer(bsd_idle_timer, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * CLOSE_ON_IDLE_SEC), NSEC_PER_SEC * CLOSE_ON_IDLE_SEC, 0);
737		dispatch_resume(bsd_idle_timer);
738	});
739
740	return 0;
741}
742
743static int
744_bsd_out_close_internal(void)
745{
746	struct config_rule *r, *n;
747	int i;
748
749	n = NULL;
750	for (r = bsd_out_rule.tqh_first; r != NULL; r = n)
751	{
752		n = r->entries.tqe_next;
753
754 		if (r->dup_timer != NULL)
755		{
756			if (r->last_count > 0) _bsd_send_repeat_msg(r);
757			dispatch_source_cancel(r->dup_timer);
758			dispatch_resume(r->dup_timer);
759			dispatch_release(r->dup_timer);
760		}
761
762		free(r->dst);
763		free(r->addr);
764		free(r->last_msg);
765		free(r->fac_prefix_len);
766		free(r->pri);
767
768		if (r->fd >= 0) close(r->fd);
769
770		if (r->facility != NULL)
771		{
772			for (i = 0; i < r->count; i++)
773                free(r->facility[i]);
774
775			free(r->facility);
776		}
777
778
779		TAILQ_REMOVE(&bsd_out_rule, r, entries);
780		free(r);
781	}
782
783	return 0;
784}
785
786int
787bsd_out_close(void)
788{
789	dispatch_async(bsd_out_queue, ^{
790		_bsd_out_close_internal();
791	});
792
793	return 0;
794}
795
796int
797bsd_out_reset(void)
798{
799	dispatch_async(bsd_out_queue, ^{
800		_bsd_out_close_internal();
801		bsd_out_init();
802	});
803
804	return 0;
805}
806
807#endif /* !TARGET_IPHONE_SIMULATOR */
808