1/*
2 * Copyright (c) 2004-2013 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#include <sys/types.h>
26#include <sys/stat.h>
27#include <sys/socket.h>
28#include <sys/un.h>
29#include <sys/uio.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <unistd.h>
34#include <fcntl.h>
35#include <errno.h>
36#include <netdb.h>
37#include <notify.h>
38#include <pthread.h>
39#include <sys/acl.h>
40#include <dirent.h>
41#include <time.h>
42#include <membership.h>
43#include <configuration_profile.h>
44#include "daemon.h"
45#include <xpc/private.h>
46
47#define _PATH_WALL "/usr/bin/wall"
48
49#define MY_ID "asl_action"
50
51#define MAX_FAILURES 5
52
53#define ACTION_STATUS_ERROR  -1
54#define ACTION_STATUS_OK      0
55
56#define IDLE_CLOSE 300
57
58#define PRIVATE_FLAG_NO_CLOSE    0x00000001 /* File or Store is closed by a cancellation handler */
59
60#define DST_CLOSE_CHECKPOINT 0
61#define DST_CLOSE_DELETED 1
62#define DST_CLOSE_ERROR 2
63#define DST_CLOSE_IDLE 3
64#define DST_CLOSE_SHUTDOWN 4
65static const char *why_str[] =
66{
67	"checkpoint",
68	"deleted",
69	"error",
70	"idle",
71	"shutdown"
72};
73
74#define forever for(;;)
75
76static dispatch_queue_t asl_action_queue;
77static dispatch_source_t checkpoint_timer;
78static time_t sweep_time = 0;
79
80#if TARGET_OS_EMBEDDED
81#ifndef CRASH_MOVER_SERVICE
82#define CRASH_MOVER_SERVICE "com.apple.crash_mover"
83#endif
84static dispatch_queue_t crashlog_queue;
85static time_t crashmover_state = 0;
86static int crashmover_token = -1;
87#endif
88
89typedef struct asl_file_data
90{
91	uint64_t next_id;
92	asl_file_t *aslfile;
93	time_t last_time;
94	uint32_t flags;
95	uint32_t pending;
96	dispatch_source_t monitor;
97} asl_action_asl_file_data_t;
98
99typedef struct asl_store_data
100{
101	FILE *storedata;
102	asl_file_t *aslfile;
103	uint64_t next_id;
104	time_t last_time;
105	uint32_t flags;
106	uint32_t pending;
107	uint32_t p_year;
108	uint32_t p_month;
109	uint32_t p_day;
110	dispatch_source_t storedata_monitor;
111	dispatch_source_t aslfile_monitor;
112} asl_action_asl_store_data_t;
113
114typedef struct file_data
115{
116	int fd;
117	uint32_t flags;
118	time_t last_time;
119	uint32_t last_count;
120	uint32_t last_hash;
121	uint32_t pending;
122	char *last_msg;
123	dispatch_source_t dup_timer;
124	dispatch_source_t monitor;
125} asl_action_file_data_t;
126
127typedef struct set_param_data
128{
129	int token;
130} asl_action_set_param_data_t;
131
132static int action_asl_store_count;
133static bool store_has_logged;
134
135extern void db_save_message(asl_msg_t *m);
136
137/* forward */
138static int _act_file_checkpoint_all(uint32_t force);
139static void _asl_action_post_process_rule(asl_out_module_t *m, asl_out_rule_t *r);
140static void _asl_action_close_idle_files(time_t idle_time);
141static void _act_dst_close(asl_out_rule_t *r, int why);
142
143static void
144_act_out_set_param(asl_out_module_t *m, char *x, bool eval)
145{
146	char *s = x;
147	char **l;
148	uint32_t count, intval;
149
150	l = explode(s, " \t");
151	if (l == NULL) return;
152
153	for (count = 0; l[count] != NULL; count++);
154	if (count == 0)
155	{
156		free_string_list(l);
157		return;
158	}
159
160	if (!strcasecmp(l[0], "enable"))
161	{
162		/* = enable [1|0] */
163		if (count < 2) intval = 1;
164		else intval = atoi(l[1]);
165
166		if (!eval) intval = (intval == 0) ? 1 : 0;
167
168		if (intval == 0) m->flags &= ~MODULE_FLAG_ENABLED;
169		else m->flags|= MODULE_FLAG_ENABLED;
170		free_string_list(l);
171		return;
172	}
173	else if (!strcasecmp(l[0], "disable"))
174	{
175		/* = disable [1|0] */
176		if (count < 2) intval = 1;
177		else intval = atoi(l[1]);
178
179		if (!eval) intval = (intval == 0) ? 1 : 0;
180
181		if (intval != 0) m->flags &= ~MODULE_FLAG_ENABLED;
182		else m->flags|= MODULE_FLAG_ENABLED;
183		free_string_list(l);
184		return;
185	}
186
187	free_string_list(l);
188
189	if (!strcmp(m->name, ASL_MODULE_NAME))
190	{
191		/* Other parameters may be set by com.apple.asl module */
192		control_set_param(x, eval);
193	}
194}
195
196static void
197_act_notify(asl_out_module_t *m, asl_out_rule_t *r)
198{
199	if (m == NULL) return;
200	if ((m->flags & MODULE_FLAG_ENABLED) == 0) return;
201
202	if (r == NULL) return;
203	if (r->options == NULL) return;
204
205	notify_post(r->options);
206}
207
208static void
209_act_broadcast(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
210{
211#if !TARGET_OS_EMBEDDED
212	FILE *pw;
213	const char *val;
214
215	if (m == NULL) return;
216	if ((m->flags & MODULE_FLAG_ENABLED) == 0) return;
217
218	if (m->name == NULL) return;
219	if (r == NULL) return;
220	if (msg == NULL) return;
221
222	/* only base module (asl.conf) may broadcast */
223	if (strcmp(m->name, ASL_MODULE_NAME)) return;
224
225	val = r->options;
226	if (val == NULL) val = asl_msg_get_val_for_key(msg, ASL_KEY_MSG);
227	if (val == NULL) return;
228
229	pw = popen(_PATH_WALL, "w");
230	if (pw == NULL)
231	{
232		asldebug("%s: error sending wall message: %s\n", MY_ID, strerror(errno));
233		return;
234	}
235
236	fprintf(pw, "%s", val);
237	pclose(pw);
238#endif
239}
240
241static void
242_act_set_key(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
243{
244	/* Placeholder */
245}
246
247static void
248_act_unset_key(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
249{
250	/* Placeholder */
251}
252
253static void
254_act_access_control(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
255{
256	int32_t ruid, rgid;
257	char *p;
258
259	if (m == NULL) return;
260	if (m->name == NULL) return;
261	if (r == NULL) return;
262	if (msg == NULL) return;
263
264	/* only base module (asl.conf) may set access controls */
265	if (strcmp(m->name, ASL_MODULE_NAME)) return;
266
267	ruid = atoi(r->options);
268	rgid = -1;
269	p = strchr(r->options, ' ');
270	if (p == NULL) p = strchr(r->options, '\t');
271	if (p != NULL)
272	{
273		*p = '\0';
274		p++;
275		rgid = atoi(p);
276	}
277
278	if (ruid != -1) asl_msg_set_key_val(msg, ASL_KEY_READ_UID, r->options);
279	if (p != NULL)
280	{
281		if (rgid != -1) asl_msg_set_key_val(msg, ASL_KEY_READ_GID, p);
282		p--;
283		*p = ' ';
284	}
285}
286
287#if TARGET_OS_EMBEDDED
288static void
289_crashlog_queue_check(void)
290{
291	/*
292	 * Check whether the crashlog queue has been suspended for too long.
293	 * We allow the crashlog quque to be suspended for 60 seconds.
294	 * After that, we start logging again.  This prevents syslogd from
295	 * filling memory due to a suspended queue.  CrashMover really shoud
296	 * take no more than a second or two to finish.
297	 */
298	if (crashmover_state == 0) return;
299	if ((time(NULL) - crashmover_state) <= 60) return;
300
301	asldebug("CrashMover timeout: resuming crashlog queue\n");
302	dispatch_resume(crashlog_queue);
303	crashmover_state = 0;
304}
305#endif
306
307/*
308 * cover routine for asl_out_dst_file_create_open()
309 */
310static int
311_act_file_create_open(asl_out_dst_data_t *dst)
312{
313	return asl_out_dst_file_create_open(dst, NULL);
314}
315
316/*
317 * Checks and creates (if required) a directory for an ASL data store.
318 */
319static int
320_asl_dir_create(asl_out_rule_t *r)
321{
322	struct stat sb;
323	int status;
324
325	memset(&sb, 0, sizeof(struct stat));
326	status = stat(r->dst->path, &sb);
327	if (status == 0)
328	{
329		/* Store path must be a directory */
330		if (!S_ISDIR(sb.st_mode))
331		{
332			asldebug("_asl_dir_create: expected a directory at path %s\n", r->dst->path);
333			return -1;
334		}
335	}
336	else if (errno == ENOENT)
337	{
338		/* Directory doesn't exists - try to create it */
339		status = asl_out_mkpath(global.asl_out_module, r);
340		if (status != 0)
341		{
342			asldebug("_asl_dir_create: asl_out_mkpath failed: %s\n", r->dst->path);
343			return -1;
344		}
345	}
346	else
347	{
348		/* Unexpected stat error */
349		asldebug("_asl_dir_create: stat error %s\n", strerror(errno));
350		return -1;
351	}
352
353	return 0;
354}
355
356/*
357 * Close an ASL Directory StoreData file
358 * - cancel the dispatch source for the file
359 */
360static void
361_asl_dir_storedata_close(asl_out_rule_t *r)
362{
363	asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private;
364	if (as_data->storedata == NULL) return;
365
366	if (as_data->storedata_monitor == NULL)
367	{
368		/*
369		 * This should never happen, but _asl_dir_storedata_open allows
370		 * dispatch_source_create to fail silently.  If there is no dispatch
371		 * source, we just close the file.
372		 */
373		asldebug("close ASL storedata fd %d\n", fileno(as_data->storedata));
374		fclose(as_data->storedata);
375	}
376	else
377	{
378		/*
379		 * The storedata_monitor cancel handler will close the file.
380		 */
381		dispatch_source_cancel(as_data->storedata_monitor);
382		dispatch_release(as_data->storedata_monitor);
383
384	}
385
386	asldebug("_asl_dir_storedata_close %p\n", as_data->storedata);
387	as_data->storedata = NULL;
388}
389
390/*
391 * Open an ASL Directory StoreData file
392 * - check directory existance and create it if necessary
393 * - check for StoreData file and create it (with given xid) if necessary.
394 * - create a dispatch source to watch for StoreData file deletion
395 */
396static int
397_asl_dir_storedata_open(asl_out_rule_t *r, uint64_t xid)
398{
399	struct stat sb;
400	int status;
401	char dstpath[MAXPATHLEN];
402
403	asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private;
404	if (as_data->storedata != NULL) return 0;
405
406	status = _asl_dir_create(r);
407	if (status != 0)
408	{
409		asldebug("_asl_dir_storedata_open: No directory at path %s\n", r->dst->path);
410		return -1;
411	}
412
413	/* StoreData file is not open */
414	snprintf(dstpath, sizeof(dstpath), "%s/%s", r->dst->path, FILE_ASL_STORE_DATA);
415
416	memset(&sb, 0, sizeof(struct stat));
417	status = stat(dstpath, &sb);
418	if (status == 0)
419	{
420		/* StoreData file exists */
421		as_data->storedata = fopen(dstpath, "r+");
422		if (as_data->storedata == NULL)
423		{
424			asldebug("_asl_dir_storedata_open: fopen existing %s: %s\n", dstpath, strerror(errno));
425			return -1;
426		}
427	}
428	else if (errno == ENOENT)
429	{
430		/*
431		 * StoreData file does not exist.
432		 * Create a new StoreData with a given xid.
433		 */
434		//TODO: This should return a failure if there are any ASL files.
435		/* that would require reading the directory, thus a performance hit */
436		as_data->storedata = fopen(dstpath, "w+");
437		if (as_data->storedata == NULL)
438		{
439			asldebug("_asl_dir_storedata_open: fopen new %s: %s\n", dstpath, strerror(errno));
440			return -1;
441		}
442
443		uint64_t xout = asl_core_htonq(xid);
444		status = fwrite(&xout, sizeof(uint64_t), 1, as_data->storedata);
445		if (status != 1)
446		{
447			asldebug("_asl_dir_storedata_open: storedata write failed %d %s\n", errno, strerror(errno));
448			return -1;
449		}
450
451#if !TARGET_IPHONE_SIMULATOR
452		if (chown(dstpath, r->dst->uid[0], r->dst->gid[0]) != 0)
453		{
454			asldebug("_asl_dir_storedata_open: chown %d %d new %s: %s\n", dstpath, r->dst->uid[0], r->dst->gid[0], strerror(errno));
455			return -1;
456		}
457#endif
458	}
459	else
460	{
461		/* Unexpected stat error */
462		asldebug("_asl_dir_storedata_open: stat error %s\n", strerror(errno));
463		return -1;
464	}
465
466	/* create storedata_monitor */
467	int fd = fileno(as_data->storedata);
468	FILE *sdfp = as_data->storedata;
469
470	as_data->storedata_monitor = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_DELETE, asl_action_queue);
471	if (as_data->storedata_monitor != NULL)
472	{
473		dispatch_source_set_event_handler(as_data->storedata_monitor, ^{
474			_act_dst_close(r, DST_CLOSE_DELETED);
475		});
476
477		dispatch_source_set_cancel_handler(as_data->storedata_monitor, ^{
478			asldebug("cancel/close ASL storedata fd %d\n", fd);
479			fclose(sdfp);
480		});
481
482		dispatch_resume(as_data->storedata_monitor);
483	}
484
485	asldebug("_asl_dir_storedata_open ASL storedata %s fd %d\n", dstpath, fd);
486	return 0;
487}
488
489/*
490 * Close an ASL Directory ASL file
491 * - cancel the dispatch source for the file
492 * - clears file name and timestamp in asl_action_asl_store_data_t structue
493 */
494static void
495_asl_dir_today_close(asl_out_rule_t *r)
496{
497	asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private;
498	if (as_data->aslfile == NULL) return;
499
500	if (as_data->pending != 0)
501	{
502		char *str = NULL;
503		asprintf(&str, "[Sender syslogd] [Level 4] [PID %u] [Facility syslog] [Message ASL Store %s was closed with %d pending messages]", global.pid, r->dst->fname, as_data->pending);
504		internal_log_message(str);
505		free(str);
506	}
507
508	if (as_data->aslfile_monitor == NULL)
509	{
510		/*
511		 * This should never happen, but _asl_dir_today_open allows
512		 * dispatch_source_create to fail silently.  If there is no dispatch
513		 * source, we just close the file.
514		 */
515		asldebug("close ASL fd %d\n", fileno(as_data->aslfile->store));
516		asl_file_close(as_data->aslfile);
517	}
518	else
519	{
520		/*
521		 * The aslfile_monitor cancel handler will close the file.
522		 */
523		dispatch_source_cancel(as_data->aslfile_monitor);
524		dispatch_release(as_data->aslfile_monitor);
525		as_data->aslfile_monitor = NULL;
526	}
527
528	as_data->p_year = 0;
529	as_data->p_month = 0;
530	as_data->p_day = 0;
531
532	free(r->dst->fname);
533	r->dst->fname = NULL;
534
535	as_data->aslfile = NULL;
536}
537
538/*
539 * Check file size.
540 */
541static int
542_act_checkpoint(asl_out_rule_t *r, uint32_t force)
543{
544	char tmpfname[MAXPATHLEN], *fn;
545
546	if (r == NULL) return 0;
547	if (r->dst == NULL) return 0;
548
549	fn = r->dst->fname;
550	if (fn == NULL)
551	{
552		if (r->dst->path == NULL) return 0;
553		asl_make_dst_filename(r->dst, tmpfname, sizeof(tmpfname));
554		fn = tmpfname;
555	}
556
557	if ((force == CHECKPOINT_TEST) && (r->dst->file_max == 0)) return 0;
558
559	if ((r->dst->size == 0) || (r->dst->stamp == 0))
560	{
561		struct stat sb;
562
563		memset(&sb, 0, sizeof(struct stat));
564
565		if (stat(fn, &sb) < 0)
566		{
567			if (errno == ENOENT) return 0;
568			return -1;
569		}
570
571		if (r->dst->stamp == 0) r->dst->stamp = sb.st_birthtimespec.tv_sec;
572		if (r->dst->stamp == 0) r->dst->stamp = sb.st_mtimespec.tv_sec;
573		r->dst->size = sb.st_size;
574	}
575
576	if ((force == CHECKPOINT_TEST) && (r->dst->size < r->dst->file_max)) return 0;
577
578	if (r->dst->flags & MODULE_FLAG_BASESTAMP)
579	{
580		_act_dst_close(r, DST_CLOSE_CHECKPOINT);
581	}
582	else
583	{
584		char srcpath[MAXPATHLEN];
585		char dstpath[MAXPATHLEN];
586		char tstamp[32];
587
588		if (r->dst->stamp == 0) r->dst->stamp = time(NULL);
589		asl_make_timestamp(r->dst->stamp, r->dst->flags, tstamp, sizeof(tstamp));
590
591		snprintf(srcpath, sizeof(srcpath), "%s", fn);
592		snprintf(dstpath, sizeof(dstpath), "%s.%s", fn, tstamp);
593
594		_act_dst_close(r, DST_CLOSE_CHECKPOINT);
595		rename(srcpath, dstpath);
596	}
597
598	r->dst->stamp = 0;
599	r->dst->size = 0;
600	return 1;
601}
602
603/*
604 * Open today's ASL file in an ASL directory
605 * - Checks date and closes a currently open file if it has the wrong date
606 * - Opens today's file
607 */
608static int
609_asl_dir_today_open(asl_out_rule_t *r, const time_t *tick)
610{
611	int status;
612	mode_t mask;
613	struct tm ctm;
614	time_t now;
615
616	if (r == NULL) return -1;
617	if (r->dst == NULL) return -1;
618
619	status = _asl_dir_create(r);
620	if (status != 0)
621	{
622		asldebug("_asl_dir_today_open: No directory at path %s\n", r->dst->path);
623		return -1;
624	}
625
626	asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private;
627
628	memset(&ctm, 0, sizeof(struct tm));
629	if (tick == NULL)
630	{
631		now = time(NULL);
632		tick = (const time_t *)&now;
633	}
634
635	if (localtime_r(tick, &ctm) == NULL)
636	{
637		asldebug("_asl_dir_today_open: localtime_r error %s\n", strerror(errno));
638		return -1;
639	}
640
641	/* checks file_max and closes if required */
642	status = _act_checkpoint(r, CHECKPOINT_TEST);
643	if (status == 1) trigger_aslmanager();
644
645	if (as_data->aslfile != NULL)
646	{
647		/* Check the date */
648		if ((as_data->p_year == ctm.tm_year) && (as_data->p_month == ctm.tm_mon) && (as_data->p_day == ctm.tm_mday)) return 0;
649
650		/* Wrong date, close the current file */
651		_asl_dir_today_close(r);
652	}
653
654	/* Open data file */
655
656	if (r->dst->flags & MODULE_FLAG_BASESTAMP)
657	{
658		char tstamp[32];
659
660		if (tick == NULL)
661		{
662			now = time(NULL);
663			tick = (const time_t *)&now;
664		}
665
666		asl_make_timestamp(now, r->dst->flags, tstamp, sizeof(tstamp));
667		asprintf(&(r->dst->fname), "%s/%s.asl", r->dst->path, tstamp);
668	}
669	else
670	{
671		asprintf(&(r->dst->fname), "%s/%d.%02d.%02d.asl", r->dst->path, ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
672	}
673
674
675	if (r->dst->fname == NULL)
676	{
677		asldebug("_asl_dir_today_open: asprintf error %s\n", strerror(errno));
678		return -1;
679	}
680
681#if TARGET_IPHONE_SIMULATOR
682	uid_t uid = -1;
683	gid_t gid = -1;
684#else
685	uid_t uid = r->dst->uid[0];
686	gid_t gid = r->dst->gid[0];
687#endif
688
689	mask = umask(0);
690	status = asl_file_open_write(r->dst->fname, (r->dst->mode & 00666), uid, gid, &(as_data->aslfile));
691	umask(mask);
692
693	if (status != ASL_STATUS_OK)
694	{
695		asldebug("_asl_dir_today_open: asl_file_open_write %s error %s\n", r->dst->fname, asl_core_error(status));
696		free(r->dst->fname);
697		r->dst->fname = NULL;
698		return -1;
699	}
700
701	if (fseek(as_data->aslfile->store, 0, SEEK_END) != 0)
702	{
703		asldebug("_asl_dir_today_open: fseek %s error %s\n", r->dst->fname, strerror(errno));
704		free(r->dst->fname);
705		r->dst->fname = NULL;
706		return -1;
707	}
708
709	as_data->p_year = ctm.tm_year;
710	as_data->p_month = ctm.tm_mon;
711	as_data->p_day = ctm.tm_mday;
712
713	/* create aslfile_monitor */
714	int fd = fileno(as_data->aslfile->store);
715	asl_file_t *aslf = as_data->aslfile;
716
717	as_data->aslfile_monitor = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_DELETE, asl_action_queue);
718	if (as_data->aslfile_monitor != NULL)
719	{
720		dispatch_source_set_event_handler(as_data->aslfile_monitor, ^{
721			_act_dst_close(r, DST_CLOSE_DELETED);
722		});
723
724		dispatch_source_set_cancel_handler(as_data->aslfile_monitor, ^{
725			asldebug("cancel/close ASL file fd %d\n", fd);
726			asl_file_close(aslf);
727		});
728
729		dispatch_resume(as_data->aslfile_monitor);
730	}
731
732	asldebug("_asl_dir_today_open ASL file %s fd %d\n", r->dst->fname, fd);
733
734	return 0;
735}
736
737static void
738_asl_file_close(asl_out_rule_t *r)
739{
740	if (r == NULL) return;
741	if (r->dst == NULL) return;
742
743	asl_action_asl_file_data_t *af_data = (asl_action_asl_file_data_t *)r->dst->private;
744	if (af_data->aslfile == NULL) return;
745
746	if (af_data->pending != 0)
747	{
748		char *str = NULL;
749		asprintf(&str, "[Sender syslogd] [Level 4] [PID %u] [Facility syslog] [Message ASL File %s was closed with %d pending messages]", global.pid, r->dst->fname, af_data->pending);
750		internal_log_message(str);
751		free(str);
752	}
753
754	if (af_data->monitor == NULL)
755	{
756		/*
757		 * This should never happen, but _asl_file_open allows
758		 * dispatch_source_create to fail silently.  If there is no dispatch
759		 * source, we just close the file.
760		 */
761		asldebug("close ASL fd %d\n", fileno(af_data->aslfile->store));
762		asl_file_close(af_data->aslfile);
763	}
764	else
765	{
766		/*
767		 * The monitor cancel handler will close the file.
768		 */
769		dispatch_source_cancel(af_data->monitor);
770		dispatch_release(af_data->monitor);
771		af_data->monitor = NULL;
772	}
773
774	af_data->aslfile = NULL;
775}
776
777static int
778_asl_file_open(asl_out_rule_t *r)
779{
780	int fd, status;
781
782	if (r == NULL) return -1;
783	if (r->dst == NULL) return -1;
784
785	asl_action_asl_file_data_t *af_data = (asl_action_asl_file_data_t *)r->dst->private;
786	if (af_data->aslfile != NULL) return 0;
787
788	/* create path if necessary */
789	status = asl_out_mkpath(global.asl_out_module, r);
790	if (status != 0)
791	{
792		asldebug("_asl_file_open: asl_out_mkpath %s failed\n", r->dst->path);
793		return -1;
794	}
795
796	fd = _act_file_create_open(r->dst);
797	if (fd < 0)
798	{
799		asldebug("_asl_file_open: _act_file_create_open %s failed %d %s\n", r->dst->fname, errno, strerror(errno));
800		return -1;
801	}
802
803	close(fd);
804
805	if (r->dst->fname == NULL) return -1;
806
807	status = asl_file_open_write(r->dst->fname, 0, -1, -1, &(af_data->aslfile));
808	if (status != ASL_STATUS_OK)
809	{
810		asldebug("_asl_file_open: asl_file_open_write %s failed %d %s\n", r->dst->fname, errno, strerror(errno));
811		return -1;
812	}
813
814	/* create monitor */
815	fd = fileno(af_data->aslfile->store);
816	asl_file_t *aslf = af_data->aslfile;
817
818	af_data->monitor = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_DELETE, asl_action_queue);
819	if (af_data->monitor != NULL)
820	{
821		dispatch_source_set_event_handler(af_data->monitor, ^{
822			_act_dst_close(r, DST_CLOSE_DELETED);
823		});
824
825		dispatch_source_set_cancel_handler(af_data->monitor, ^{
826			asldebug("cancel/close ASL file fd %d\n", fd);
827			asl_file_close(aslf);
828		});
829
830		dispatch_resume(af_data->monitor);
831	}
832
833	asldebug("_asl_file_open ASL file %s fd %d\n", r->dst->fname, fd);
834	return 0;
835}
836
837static void
838_text_file_close(asl_out_rule_t *r)
839{
840	asl_action_file_data_t *f_data = (asl_action_file_data_t *)r->dst->private;
841	if (f_data->fd < 0) return;
842
843	if (f_data->pending != 0)
844	{
845		char *str = NULL;
846		asprintf(&str, "[Sender syslogd] [Level 4] [PID %u] [Facility syslog] [Message File %s was closed with %d pending messages]", global.pid, r->dst->fname, f_data->pending);
847		internal_log_message(str);
848		free(str);
849	}
850
851	if (f_data->monitor == NULL)
852	{
853		/*
854		 * This should never happen, but _text_file_open allows
855		 * dispatch_source_create to fail silently.  If there is no dispatch
856		 * source, we just close the file.
857		 */
858		asldebug("close fd %d\n", f_data->fd);
859		close(f_data->fd);
860	}
861	else
862	{
863		/*
864		 * The monitor cancel handler will close the file.
865		 */
866		dispatch_source_cancel(f_data->monitor);
867		dispatch_release(f_data->monitor);
868		f_data->monitor = NULL;
869	}
870
871	f_data->fd = -1;
872}
873
874static int
875_text_file_open(asl_out_rule_t *r)
876{
877	asl_action_file_data_t *f_data = (asl_action_file_data_t *)r->dst->private;
878
879	if (f_data->fd < 0)
880	{
881		f_data->fd = _act_file_create_open(r->dst);
882		if (f_data->fd < 0)
883		{
884			/*
885			 * lazy path creation: create path and retry
886			 * _act_file_create_open does not create the path
887			 * so we do it here.
888			 */
889			int status = asl_out_mkpath(global.asl_out_module, r);
890			if (status != 0) return -1;
891
892			f_data->fd = _act_file_create_open(r->dst);
893		}
894
895		if (f_data->fd < 0) return -1;
896
897		f_data->monitor = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, f_data->fd, DISPATCH_VNODE_DELETE, asl_action_queue);
898		if (f_data->monitor != NULL)
899		{
900			int ffd = f_data->fd;
901
902			dispatch_source_set_event_handler(f_data->monitor, ^{
903				asldebug("dispatch_source DISPATCH_VNODE_DELETE fd %d\n", ffd);
904				_act_dst_close(r, DST_CLOSE_DELETED);
905			});
906
907			dispatch_source_set_cancel_handler(f_data->monitor, ^{
908				asldebug("cancel/close file fd %d\n", ffd);
909				close(ffd);
910			});
911
912			dispatch_resume(f_data->monitor);
913		}
914	}
915
916	return 0;
917}
918
919static int
920_act_dst_open(asl_out_rule_t *r, const time_t *tick, uint64_t xid)
921{
922	if (r == NULL) return -1;
923	if (r->dst == NULL) return -1;
924	if (r->dst->private == NULL) return -1;
925
926	if (r->action == ACTION_ASL_DIR)
927	{
928		if (_asl_dir_today_open(r, tick) != 0)
929		{
930			asldebug("_act_dst_open:_asl_dir_today_open %s FAILED!\n", r->dst->path);
931			return -1;
932		}
933
934		if (_asl_dir_storedata_open(r, xid) != 0)
935		{
936			asldebug("_act_dst_open:_asl_dir_storedata_open %s FAILED!  Closing today file\n", r->dst->path);
937			_asl_dir_today_close(r);
938			return -1;
939		}
940
941		return 0;
942	}
943
944	if (r->action == ACTION_ASL_FILE)
945	{
946		return _asl_file_open(r);
947	}
948
949	if (r->action == ACTION_FILE)
950	{
951		return _text_file_open(r);
952	}
953
954	return -1;
955}
956
957static void
958_act_dst_close(asl_out_rule_t *r, int why)
959{
960	if (r == NULL) return;
961	if (r->dst == NULL) return;
962	if (r->dst->private == NULL) return;
963
964	if (r->action == ACTION_ASL_DIR)
965	{
966		asldebug("_act_dst_close: %s ASL DIR %s\n", why_str[why], r->dst->path);
967		if (why != DST_CLOSE_CHECKPOINT) _asl_dir_storedata_close(r);
968		_asl_dir_today_close(r);
969	}
970	else if (r->action == ACTION_ASL_FILE)
971	{
972		asldebug("_act_dst_close: %s ASL FILE %s\n", why_str[why], (r->dst->fname == NULL) ? r->dst->path : r->dst->fname);
973		_asl_file_close(r);
974	}
975	else if (r->action == ACTION_FILE)
976	{
977		asldebug("_act_dst_close: %s FILE %s\n", why_str[why], (r->dst->fname == NULL) ? r->dst->path : r->dst->fname);
978		_text_file_close(r);
979	}
980}
981
982static uint32_t
983_act_store_file_setup(asl_out_module_t *m, asl_out_rule_t *r)
984{
985	uint32_t status;
986	asl_action_asl_file_data_t *af_data;
987
988	if (r == NULL) return ASL_STATUS_INVALID_STORE;
989	if (r->dst == NULL) return ASL_STATUS_INVALID_STORE;
990	if (r->dst->private == NULL) return ASL_STATUS_INVALID_STORE;
991
992	af_data = (asl_action_asl_file_data_t *)r->dst->private;
993	if (af_data->aslfile != NULL)
994	{
995		af_data->next_id++;
996		return ASL_STATUS_OK;
997	}
998
999	if (_act_dst_open(r, NULL, 0) != 0) return ASL_STATUS_WRITE_FAILED;
1000
1001	status = asl_file_read_set_position(af_data->aslfile, ASL_FILE_POSITION_LAST);
1002	if (status != ASL_STATUS_OK)
1003	{
1004		asldebug("_act_store_file_setup: asl_file_read_set_position failed %d %s\n", status, asl_core_error(status));
1005		_act_dst_close(r, DST_CLOSE_ERROR);
1006		return status;
1007	}
1008
1009	af_data->next_id = af_data->aslfile->cursor_xid + 1;
1010	if (fseek(af_data->aslfile->store, 0, SEEK_END) != 0)
1011	{
1012		asldebug("_act_store_file_setup: fseek failed %d %s\n", errno, strerror(errno));
1013		_act_dst_close(r, DST_CLOSE_ERROR);
1014		return ASL_STATUS_WRITE_FAILED;
1015	}
1016
1017	return ASL_STATUS_OK;
1018}
1019
1020/*
1021 * _act_store_dir_setup
1022 *
1023 * Opens StoreData file and today's file
1024 * Reads ASL Message ID from StoreData file
1025 * Writes ASL Message ID + 1 to StoreData file
1026 */
1027static uint32_t
1028_act_store_dir_setup(asl_out_module_t *m, asl_out_rule_t *r, time_t tick)
1029{
1030	uint64_t xid;
1031	int status;
1032	asl_action_asl_store_data_t *as_data;
1033
1034	if (r == NULL) return ASL_STATUS_INVALID_STORE;
1035	if (r->dst == NULL) return ASL_STATUS_INVALID_STORE;
1036	if (r->dst->private == NULL) return ASL_STATUS_INVALID_STORE;
1037	if (r->dst->path == NULL) return ASL_STATUS_INVALID_STORE;
1038
1039	as_data = (asl_action_asl_store_data_t *)r->dst->private;
1040
1041	if (_act_dst_open(r, NULL, as_data->next_id) != 0)
1042	{
1043		asldebug("_act_store_dir_setup: _act_dst_open %s failed\n", r->dst->path);
1044		return ASL_STATUS_WRITE_FAILED;
1045	}
1046
1047	/* get / set message id from StoreData file */
1048	xid = 0;
1049	rewind(as_data->storedata);
1050	if (fread(&xid, sizeof(uint64_t), 1, as_data->storedata) != 1)
1051	{
1052		asldebug("_act_store_dir_setup: storedata read failed %d %s\n", errno, strerror(errno));
1053		_act_dst_close(r, DST_CLOSE_ERROR);
1054		return ASL_STATUS_READ_FAILED;
1055	}
1056
1057	xid = asl_core_ntohq(xid);
1058	xid++;
1059	as_data->next_id = xid;
1060
1061	xid = asl_core_htonq(xid);
1062	rewind(as_data->storedata);
1063	status = fwrite(&xid, sizeof(uint64_t), 1, as_data->storedata);
1064	if (status != 1)
1065	{
1066		asldebug("_act_store_dir_setup: storedata write failed %d %s\n", errno, strerror(errno));
1067		_act_dst_close(r, DST_CLOSE_ERROR);
1068		return ASL_STATUS_WRITE_FAILED;
1069	}
1070
1071	fflush(as_data->storedata);
1072
1073	if (fseek(as_data->aslfile->store, 0, SEEK_END) != 0)
1074	{
1075		asldebug("_act_store_dir_setup: aslfile fseek failed %d %s\n", errno, strerror(errno));
1076		_act_dst_close(r, DST_CLOSE_ERROR);
1077		return ASL_STATUS_FAILED;
1078	}
1079
1080	return ASL_STATUS_OK;
1081}
1082
1083static void
1084_asl_action_asl_store_data_free(asl_action_asl_store_data_t *as_data)
1085{
1086	if (as_data == NULL) return;
1087	free(as_data);
1088}
1089
1090static void
1091_asl_action_asl_file_data_free(asl_action_asl_file_data_t *af_data)
1092{
1093	if (af_data == NULL) return;
1094	free(af_data);
1095}
1096
1097static void
1098_asl_action_file_data_free(asl_action_file_data_t *f_data)
1099{
1100	if (f_data == NULL) return;
1101
1102	if (f_data->dup_timer != NULL)
1103	{
1104		if (f_data->last_count == 0)
1105		{
1106			/*
1107			 * The timer exists, but last_count is zero, so the timer is suspended.
1108			 * Sources must not be released in when suspended.
1109			 * So we resume it so that we can release it.
1110			 */
1111			dispatch_resume(f_data->dup_timer);
1112		}
1113
1114		dispatch_release(f_data->dup_timer);
1115	}
1116
1117	free(f_data->last_msg);
1118	free(f_data);
1119}
1120
1121static void
1122_asl_action_set_param_data_free(asl_action_set_param_data_t *spdata)
1123{
1124	if (spdata != NULL) notify_cancel(spdata->token);
1125	free(spdata);
1126}
1127
1128static void
1129_asl_action_save_failed(const char *where, asl_out_module_t *m, asl_out_rule_t *r, uint32_t status)
1130{
1131	if (r->dst->flags & MODULE_FLAG_SOFT_WRITE) return;
1132
1133	r->dst->fails++;
1134	asldebug("%s: %s save to %s failed: %s\n", where, m->name, r->dst->path, asl_core_error(status));
1135
1136	/* disable further activity after multiple failures */
1137	if (r->dst->fails > MAX_FAILURES)
1138	{
1139		char *str = NULL;
1140		asprintf(&str, "[Sender syslogd] [Level 3] [PID %u] [Facility syslog] [Message Disabling module %s writes to %s following %u failures (%s)]", global.pid, m->name, r->dst->path, r->dst->fails, asl_core_error(status));
1141		internal_log_message(str);
1142		free(str);
1143
1144		if (r->action == ACTION_ASL_DIR) _asl_action_asl_store_data_free((asl_action_asl_store_data_t *)r->dst->private);
1145		else if (r->action == ACTION_ASL_FILE) _asl_action_asl_file_data_free((asl_action_asl_file_data_t *)r->dst->private);
1146		else if (r->action == ACTION_FILE) _asl_action_file_data_free((asl_action_file_data_t *)r->dst->private);
1147
1148		r->dst->private = NULL;
1149		r->action = ACTION_NONE;
1150	}
1151}
1152
1153/*
1154 * Save a message in an ASL file.
1155 */
1156static int
1157_act_store_file(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
1158{
1159	asl_action_asl_file_data_t *af_data;
1160	uint32_t status;
1161	uint64_t mid;
1162
1163	if (r == NULL) return ACTION_STATUS_ERROR;
1164	if (r->dst == NULL) return ACTION_STATUS_ERROR;
1165	if (r->dst->private == NULL) return ACTION_STATUS_ERROR;
1166
1167	af_data = (asl_action_asl_file_data_t *)r->dst->private;
1168	if (af_data->pending > 0) af_data->pending--;
1169
1170	status = _act_store_file_setup(m, r);
1171	if (status == ASL_STATUS_OK)
1172	{
1173		af_data->last_time = time(NULL);
1174
1175		r->dst->fails = 0;
1176		mid = af_data->next_id;
1177
1178		/* save message to file and update dst size */
1179		status = asl_file_save(af_data->aslfile, msg, &mid);
1180		if (status == ASL_STATUS_OK)
1181		{
1182			r->dst->size = af_data->aslfile->file_size;
1183
1184			if (_act_checkpoint(r, CHECKPOINT_TEST) == 1) trigger_aslmanager();
1185		}
1186	}
1187
1188	if (status != ASL_STATUS_OK)
1189	{
1190		_asl_action_save_failed("_act_store_file", m, r, status);
1191		return ACTION_STATUS_ERROR;
1192	}
1193
1194	return ACTION_STATUS_OK;
1195}
1196
1197/*
1198 * Save a message in an ASL data store.
1199 */
1200static int
1201_act_store_dir(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
1202{
1203	asl_action_asl_store_data_t *as_data;
1204	uint32_t status;
1205	uint64_t mid;
1206	const char *val;
1207	time_t tick;
1208
1209	if (r == NULL) return ACTION_STATUS_ERROR;
1210	if (r->dst == NULL) return ACTION_STATUS_ERROR;
1211	if (r->dst->private == NULL) return ACTION_STATUS_ERROR;
1212
1213	as_data = (asl_action_asl_store_data_t *)r->dst->private;
1214	if (as_data->pending > 0) as_data->pending--;
1215
1216	val = asl_msg_get_val_for_key(msg, ASL_KEY_TIME);
1217	if (val == NULL) return ACTION_STATUS_ERROR;
1218
1219	tick = atol(val);
1220
1221	status = _act_store_dir_setup(m, r, tick);
1222	if (status == ASL_STATUS_OK)
1223	{
1224		as_data->last_time = time(NULL);
1225
1226		r->dst->fails = 0;
1227		mid = as_data->next_id;
1228		status = asl_file_save(as_data->aslfile, msg, &mid);
1229		if (status == ASL_STATUS_OK) r->dst->size = as_data->aslfile->file_size;
1230		else asldebug("_act_store_dir asl_file_save FAILED %s\n", asl_core_error(status));
1231		//TODO: checkpoint here?
1232		/*
1233		 * Currently, the checkpoint is in _asl_dir_today_open().
1234		 * Moving it here would be in keeping with the way that
1235		 * _act_store_file() and _act_file_final() do checkpoints.
1236		 */
1237	}
1238
1239	if (status != ASL_STATUS_OK)
1240	{
1241		_asl_action_save_failed("_act_store_dir", m, r, status);
1242		return ACTION_STATUS_ERROR;
1243	}
1244
1245	return ACTION_STATUS_OK;
1246}
1247
1248static void
1249_act_store_final(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
1250{
1251	if (r->action == ACTION_ASL_DIR) _act_store_dir(m, r, msg);
1252	else _act_store_file(m, r, msg);
1253}
1254
1255/*
1256 * Save a message to an ASL format file (ACTION_ASL_FILE)
1257 * or to an ASL directory (ACTION_ASL_DIR).
1258 */
1259static void
1260_act_store(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
1261{
1262	if (r == NULL) return;
1263	if (r->dst == NULL) return;
1264	if (msg == NULL) return;
1265	if (m == NULL) return;
1266	if ((m->flags & MODULE_FLAG_ENABLED) == 0) return;
1267
1268	if (r->dst->flags & MODULE_FLAG_HAS_LOGGED) return;
1269
1270	r->dst->flags |= MODULE_FLAG_HAS_LOGGED;
1271	if (r->action == ACTION_ASL_DIR)
1272	{
1273		asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private;
1274		if (as_data != NULL) as_data->pending++;
1275	}
1276	else if (r->action == ACTION_ASL_FILE)
1277	{
1278		asl_action_asl_file_data_t *af_data = (asl_action_asl_file_data_t *)r->dst->private;
1279		if (af_data != NULL) af_data->pending++;
1280	}
1281
1282#if TARGET_OS_EMBEDDED
1283	if (r->dst->flags & MODULE_FLAG_CRASHLOG)
1284	{
1285		_crashlog_queue_check();
1286		asl_msg_retain(msg);
1287		dispatch_async(crashlog_queue, ^{
1288			dispatch_async(asl_action_queue, ^{
1289				_act_store_final(m, r, msg);
1290				asl_msg_release(msg);
1291			});
1292		});
1293		return;
1294	}
1295#endif
1296
1297	_act_store_final(m, r, msg);
1298}
1299
1300static int
1301_send_repeat_msg(asl_out_rule_t *r)
1302{
1303	asl_action_file_data_t *f_data;
1304	char vt[32], *msg;
1305	int len, status;
1306	time_t now = time(NULL);
1307
1308	if (r == NULL) return -1;
1309	if (r->dst == NULL) return -1;
1310	if (r->dst->private == NULL) return -1;
1311
1312	f_data = (asl_action_file_data_t *)r->dst->private;
1313
1314	free(f_data->last_msg);
1315	f_data->last_msg = NULL;
1316
1317	if (f_data->last_count == 0) return 0;
1318
1319	/* stop the timer */
1320	dispatch_suspend(f_data->dup_timer);
1321
1322	memset(vt, 0, sizeof(vt));
1323	ctime_r(&now, vt);
1324	vt[19] = '\0';
1325
1326	msg = NULL;
1327	asprintf(&msg, "%s --- last message repeated %u time%s ---\n", vt + 4, f_data->last_count, (f_data->last_count == 1) ? "" : "s");
1328	f_data->last_count = 0;
1329	f_data->last_time = now;
1330	if (msg == NULL) return -1;
1331
1332	if (f_data->fd < 0) f_data->fd = _act_file_create_open(r->dst);
1333
1334	len = strlen(msg);
1335	status = write(f_data->fd, msg, len);
1336	free(msg);
1337
1338	if ((status < 0) || (status < len))
1339	{
1340		asldebug("%s: error writing repeat message (%s): %s\n", MY_ID, r->dst->fname, strerror(errno));
1341		return -1;
1342	}
1343
1344	return 0;
1345}
1346
1347static void
1348_start_cycling()
1349{
1350	struct timespec midnight;
1351	struct tm t;
1352	time_t x;
1353
1354	x = time(NULL);
1355
1356	if (checkpoint_timer != NULL) return;
1357
1358	localtime_r(&x, &t);
1359
1360	t.tm_sec = 0;
1361	t.tm_min = 0;
1362	t.tm_hour = 0;
1363	t.tm_mday++;
1364
1365	x = mktime(&t);
1366	midnight.tv_sec = x;
1367	midnight.tv_nsec = 0;
1368
1369	checkpoint_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, asl_action_queue);
1370	dispatch_source_set_timer(checkpoint_timer, dispatch_walltime(&midnight, 0), NSEC_PER_SEC * SEC_PER_DAY, 0);
1371	dispatch_source_set_event_handler(checkpoint_timer, ^{ _act_file_checkpoint_all(CHECKPOINT_FORCE); });
1372	dispatch_resume(checkpoint_timer);
1373}
1374
1375/* check if a module path (mpath) matches a user path (upath) */
1376static bool
1377_act_file_equal(const char *mpath, const char *upath)
1378{
1379	const char *slash;
1380
1381	/* NULL upath means user wants to match all files */
1382	if (upath == NULL) return true;
1383
1384	if (mpath == NULL) return false;
1385
1386	/* check for exact match */
1387	if (!strcmp(mpath, upath)) return true;
1388
1389	/* upath may be the last component of mpath */
1390	slash = strrchr(mpath, '/');
1391	if (slash == NULL) return false;
1392
1393	if (!strcmp(slash + 1, upath)) return true;
1394	return false;
1395}
1396
1397static int
1398_act_file_checkpoint(asl_out_module_t *m, const char *path, uint32_t force)
1399{
1400	asl_out_rule_t *r;
1401	int did_checkpoint = 0;
1402
1403	if (m == NULL) return 0;
1404
1405
1406	for (r = m->ruleset; r != NULL; r = r->next)
1407	{
1408		if ((r->action == ACTION_FILE) || (r->action == ACTION_ASL_FILE))
1409		{
1410			if (r->dst->flags & MODULE_FLAG_ROTATE)
1411			{
1412				if (_act_file_equal(r->dst->path, path))
1413				{
1414					if (force & CHECKPOINT_CRASH)
1415					{
1416						if (r->dst->flags & MODULE_FLAG_CRASHLOG)
1417						{
1418							if (_act_checkpoint(r, CHECKPOINT_FORCE) > 0)
1419							{
1420								did_checkpoint = 1;
1421								_act_dst_close(r, DST_CLOSE_CHECKPOINT);
1422							}
1423						}
1424					}
1425					else
1426					{
1427						if (_act_checkpoint(r, force) > 0)
1428						{
1429							did_checkpoint = 1;
1430							_act_dst_close(r, DST_CLOSE_CHECKPOINT);
1431						}
1432					}
1433				}
1434			}
1435		}
1436	}
1437
1438	return did_checkpoint;
1439}
1440
1441static int
1442_act_file_checkpoint_all(uint32_t force)
1443{
1444	asl_out_module_t *m;
1445	int did_checkpoint = 0;
1446
1447	for (m = global.asl_out_module; m != NULL; m = m->next)
1448	{
1449		if (_act_file_checkpoint(m, NULL, force) > 0) did_checkpoint = 1;
1450	}
1451
1452	trigger_aslmanager();
1453
1454	return did_checkpoint;
1455}
1456
1457/*
1458 * Save a message in a plain text file.
1459 */
1460static void
1461_act_file_final(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
1462{
1463	asl_action_file_data_t *f_data;
1464	int is_dup;
1465	uint32_t len, msg_hash = 0;
1466	char *str;
1467	time_t now;
1468
1469	if (r->dst->private == NULL) return;
1470
1471	f_data = (asl_action_file_data_t *)r->dst->private;
1472	if (f_data->pending > 0) f_data->pending--;
1473
1474	/*
1475	 * If print format is std, bsd, or msg, then skip messages with
1476	 * no ASL_KEY_MSG, or without a value for it.
1477	 */
1478	if (r->dst->flags & MODULE_FLAG_STD_BSD_MSG)
1479	{
1480		const char *msgval = NULL;
1481		if (asl_msg_lookup(msg, ASL_KEY_MSG, &msgval, NULL) != 0) return;
1482		if (msgval == NULL) return;
1483	}
1484
1485	now = time(NULL);
1486
1487	is_dup = 0;
1488
1489	str = asl_format_message(msg, r->dst->fmt, r->dst->tfmt, ASL_ENCODE_SAFE, &len);
1490
1491	if (r->dst->flags & MODULE_FLAG_COALESCE)
1492	{
1493		if (f_data->dup_timer == NULL)
1494		{
1495			/* create a timer to flush dups on this file */
1496			f_data->dup_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, asl_action_queue);
1497			dispatch_source_set_event_handler(f_data->dup_timer, ^{ _send_repeat_msg(r); });
1498		}
1499
1500		if ((global.bsd_max_dup_time > 0) && (str != NULL) && (f_data->last_msg != NULL))
1501		{
1502			msg_hash = asl_core_string_hash(str + 16, len - 16);
1503			if ((f_data->last_hash == msg_hash) && (!strcmp(f_data->last_msg, str + 16)))
1504			{
1505				if ((now - f_data->last_time) < global.bsd_max_dup_time) is_dup = 1;
1506			}
1507		}
1508	}
1509
1510	if (is_dup == 1)
1511	{
1512		if (f_data->last_count == 0)
1513		{
1514			/* start the timer */
1515			dispatch_source_set_timer(f_data->dup_timer, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * global.bsd_max_dup_time), DISPATCH_TIME_FOREVER, 0);
1516			dispatch_resume(f_data->dup_timer);
1517		}
1518
1519		f_data->last_count++;
1520	}
1521	else
1522	{
1523		if (_act_dst_open(r, NULL, 0) != 0)
1524		{
1525			_asl_action_save_failed("_act_file", m, r, ASL_STATUS_FAILED);
1526			free(str);
1527			return;
1528		}
1529		else
1530		{
1531			r->dst->fails = 0;
1532		}
1533
1534		/*
1535		 * The current message is not a duplicate.  If f_data->last_count > 0
1536		 * we need to write a "last message repeated N times" log entry.
1537		 * _send_repeat_msg will free last_msg and do nothing if
1538		 * last_count == 0, but we test and free here to avoid a function call.
1539		 */
1540		if (f_data->last_count > 0)
1541		{
1542			_send_repeat_msg(r);
1543		}
1544		else
1545		{
1546			free(f_data->last_msg);
1547			f_data->last_msg = NULL;
1548		}
1549
1550		if (str != NULL) f_data->last_msg = strdup(str + 16);
1551
1552		f_data->last_hash = msg_hash;
1553		f_data->last_count = 0;
1554		f_data->last_time = now;
1555
1556		if ((str != NULL) && (len > 1))
1557		{
1558			/* write line to file and update dst size */
1559			size_t bytes = write(f_data->fd, str, len - 1);
1560			if (bytes > 0) r->dst->size += bytes;
1561
1562			if (_act_checkpoint(r, CHECKPOINT_TEST) == 1) trigger_aslmanager();
1563		}
1564	}
1565
1566	free(str);
1567}
1568
1569static void
1570_act_file(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
1571{
1572	asl_action_file_data_t *f_data;
1573
1574	if (r == NULL) return;
1575	if (msg == NULL) return;
1576	if (m == NULL) return;
1577	if ((m->flags & MODULE_FLAG_ENABLED) == 0) return;
1578	if (r->dst == NULL) return;
1579	if (r->dst->private == NULL) return;
1580
1581	if (r->dst->flags & MODULE_FLAG_HAS_LOGGED) return;
1582
1583	r->dst->flags |= MODULE_FLAG_HAS_LOGGED;
1584	f_data = (asl_action_file_data_t *)r->dst->private;
1585	if (f_data != NULL) f_data->pending++;
1586
1587#if TARGET_OS_EMBEDDED
1588	if (r->dst->flags & MODULE_FLAG_CRASHLOG)
1589	{
1590		_crashlog_queue_check();
1591		asl_msg_retain(msg);
1592		dispatch_async(crashlog_queue, ^{
1593			dispatch_async(asl_action_queue, ^{
1594				_act_file_final(m, r, msg);
1595				asl_msg_release(msg);
1596			});
1597		});
1598		return;
1599	}
1600#endif
1601
1602	_act_file_final(m, r, msg);
1603}
1604
1605static void
1606_act_forward(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
1607{
1608	/* To do: <rdar://problem/6130747> Add a "forward" action to asl.conf */
1609}
1610
1611static void
1612_act_control(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
1613{
1614	const char *p;
1615
1616	if (m == NULL) return;
1617	if (r == NULL) return;
1618
1619	p = asl_msg_get_val_for_key(msg, ASL_KEY_MODULE);
1620
1621	if (r->options == NULL) return;
1622
1623	if (!strcmp(r->options, "enable"))
1624	{
1625		m->flags |= MODULE_FLAG_ENABLED;
1626	}
1627	else if (!strcmp(r->options, "disable"))
1628	{
1629		m->flags &= ~MODULE_FLAG_ENABLED;
1630	}
1631	else if ((!strcmp(r->options, "checkpoint")) || (!strcmp(r->options, "rotate")))
1632	{
1633		_act_file_checkpoint(m, NULL, CHECKPOINT_FORCE);
1634	}
1635}
1636
1637static void
1638_send_to_asl_store(asl_msg_t *msg)
1639{
1640	if ((global.asl_out_module != NULL) && ((global.asl_out_module->flags & MODULE_FLAG_ENABLED) == 0)) return;
1641
1642	if (store_has_logged) return;
1643	store_has_logged = true;
1644
1645	db_save_message(msg);
1646}
1647
1648static int
1649_asl_out_process_message(asl_out_module_t *m, asl_msg_t *msg)
1650{
1651	asl_out_rule_t *r;
1652
1653	if (m == NULL) return 1;
1654	if (msg == NULL) return 1;
1655
1656	/* reset flag bit used for duplicate avoidance */
1657	for (r = m->ruleset; r != NULL; r = r->next)
1658	{
1659		if ((r->action == ACTION_FILE) || (r->action == ACTION_ASL_DIR) || (r->action == ACTION_ASL_FILE))
1660		{
1661			if (r->dst != NULL) r->dst->flags &= MODULE_FLAG_CLEAR_LOGGED;
1662		}
1663	}
1664
1665	for (r = m->ruleset; r != NULL; r = r->next)
1666	{
1667		if (r->query == NULL) continue;
1668
1669		/* ACTION_SET_FILE, ACTION_SET_PLIST, and ACTION_SET_PROF are handled independently  */
1670		if ((r->action == ACTION_SET_FILE) || (r->action == ACTION_SET_PLIST) || (r->action == ACTION_SET_PROF)) continue;
1671
1672		/*
1673		 * ACTION_CLAIM during processing is a filter.  It will only be here if the option "only"
1674		 * was supplied.  In this case we test the message against the query.  If it does not
1675		 * match, we skip the message.
1676		 */
1677		if (r->action == ACTION_CLAIM)
1678		{
1679			if ((asl_msg_cmp(r->query, msg) != 1)) return 0;
1680		}
1681
1682		if ((asl_msg_cmp(r->query, msg) == 1))
1683		{
1684			if (r->action == ACTION_NONE) continue;
1685			else if (r->action == ACTION_IGNORE) return 1;
1686			else if (r->action == ACTION_SKIP) return 0;
1687			else if (r->action == ACTION_ASL_STORE) _send_to_asl_store(msg);
1688			else if (r->action == ACTION_ACCESS) _act_access_control(m, r, msg);
1689			else if (r->action == ACTION_SET_KEY) _act_set_key(m, r, msg);
1690			else if (r->action == ACTION_UNSET_KEY) _act_unset_key(m, r, msg);
1691			else if (r->action == ACTION_NOTIFY) _act_notify(m, r);
1692			else if (r->action == ACTION_BROADCAST) _act_broadcast(m, r, msg);
1693			else if (r->action == ACTION_FORWARD) _act_forward(m, r, msg);
1694			else if (r->action == ACTION_CONTROL) _act_control(m, r, msg);
1695			else if (r->action == ACTION_SET_PARAM) _act_out_set_param(m, r->options, true);
1696			else if ((r->action == ACTION_ASL_FILE) || (r->action == ACTION_ASL_DIR)) _act_store(m, r, msg);
1697			else if (r->action == ACTION_FILE) _act_file(m, r, msg);
1698		}
1699	}
1700
1701	return 0;
1702}
1703
1704void
1705asl_out_message(asl_msg_t *msg)
1706{
1707	OSAtomicIncrement32(&global.asl_queue_count);
1708	asl_msg_retain(msg);
1709
1710	dispatch_async(asl_action_queue, ^{
1711		int ignore = 0;
1712		const char *p;
1713		time_t now = time(NULL);
1714		asl_out_module_t *m = global.asl_out_module;
1715
1716		store_has_logged = false;
1717
1718		p = asl_msg_get_val_for_key(msg, ASL_KEY_MODULE);
1719		if (p == NULL)
1720		{
1721			if ((action_asl_store_count == 0) || (asl_check_option(msg, ASL_OPT_STORE) == 1)) _send_to_asl_store(msg);
1722
1723			ignore = _asl_out_process_message(m, msg);
1724			if (ignore == 0)
1725			{
1726				if (m != NULL) m = m->next;
1727				while (m != NULL)
1728				{
1729					_asl_out_process_message(m, msg);
1730					m = m->next;
1731				}
1732			}
1733		}
1734		else
1735		{
1736			if (m != NULL) m = m->next;
1737			while (m != NULL)
1738			{
1739				if (!strcmp(p, m->name)) _asl_out_process_message(m, msg);
1740				m = m->next;
1741			}
1742		}
1743
1744		p = asl_msg_get_val_for_key(msg, ASL_KEY_FINAL_NOTIFICATION);
1745		if (p != NULL) asl_msg_set_key_val(msg, ASL_KEY_FREE_NOTE, p);
1746
1747		asl_msg_release(msg);
1748		OSAtomicDecrement32(&global.asl_queue_count);
1749
1750		if ((now - sweep_time) >= IDLE_CLOSE)
1751		{
1752			_asl_action_close_idle_files(IDLE_CLOSE);
1753			sweep_time = now;
1754		}
1755	});
1756}
1757
1758static char *
1759_asl_action_profile_test(asl_out_module_t *m, asl_out_rule_t *r)
1760{
1761	const char *ident;
1762	asl_msg_t *profile;
1763	bool eval;
1764
1765	/* ident is first message key */
1766	asl_msg_fetch(r->query, 0, &ident, NULL, NULL);
1767	if (ident == NULL)
1768	{
1769		r->action = ACTION_NONE;
1770		return NULL;
1771	}
1772
1773	profile = configuration_profile_to_asl_msg(ident);
1774	eval = (asl_msg_cmp(r->query, profile) == 1);
1775	_act_out_set_param(m, r->options, eval);
1776	asl_msg_release(profile);
1777
1778	return strdup(ident);
1779}
1780
1781static const char *
1782_asl_action_file_test(asl_out_module_t *m, asl_out_rule_t *r)
1783{
1784	const char *path;
1785	struct stat sb;
1786	int status;
1787	bool eval;
1788
1789	/* path is first message key */
1790	asl_msg_fetch(r->query, 0, &path, NULL, NULL);
1791	if (path == NULL)
1792	{
1793		r->action = ACTION_NONE;
1794		return NULL;
1795	}
1796
1797	memset(&sb, 0, sizeof(struct stat));
1798	status = stat(path, &sb);
1799	eval = (status == 0);
1800	_act_out_set_param(m, r->options, eval);
1801
1802	return path;
1803}
1804
1805static void
1806_asl_action_handle_file_change_notification(int t)
1807{
1808	asl_out_module_t *m;
1809	asl_out_rule_t *r;
1810
1811	for (m = global.asl_out_module; m != NULL; m = m->next)
1812	{
1813		for (r = m->ruleset; r != NULL; r = r->next)
1814		{
1815			if (r->action == ACTION_SET_FILE)
1816			{
1817				asl_action_set_param_data_t *spdata = (asl_action_set_param_data_t *)r->private;
1818				if ((spdata != NULL) && (spdata->token == t))
1819				{
1820					_asl_action_file_test(m, r);
1821					return;
1822				}
1823			}
1824			else if (r->action == ACTION_SET_PLIST)
1825			{
1826				asl_action_set_param_data_t *spdata = (asl_action_set_param_data_t *)r->private;
1827				if ((spdata != NULL) && (spdata->token == t))
1828				{
1829					char *str = _asl_action_profile_test(m, r);
1830					free(str);
1831					return;
1832				}
1833			}
1834			else if (r->action == ACTION_SET_PROF)
1835			{
1836				asl_action_set_param_data_t *spdata = (asl_action_set_param_data_t *)r->private;
1837				if ((spdata != NULL) && (spdata->token == t))
1838				{
1839					char *str = _asl_action_profile_test(m, r);
1840					free(str);
1841					return;
1842				}
1843			}
1844		}
1845	}
1846
1847	asl_out_module_free(m);
1848}
1849
1850static void
1851_asl_action_post_process_rule(asl_out_module_t *m, asl_out_rule_t *r)
1852{
1853	if ((m == NULL) || (r == NULL)) return;
1854
1855	if (m != global.asl_out_module)
1856	{
1857		/* check if any previous module has used this destination */
1858		asl_out_module_t *n;
1859		bool search = true;
1860
1861		if ((r->dst != NULL) && (r->dst->path != NULL))
1862		{
1863			for (n = global.asl_out_module; search && (n != NULL) && (n != m); n = n->next)
1864			{
1865				asl_out_rule_t *s;
1866				for (s = n->ruleset; search && (s != NULL); s = s->next)
1867				{
1868					if (s->action == ACTION_OUT_DEST)
1869					{
1870						if ((s->dst != NULL) && (s->dst->path != NULL) && (!strcmp(r->dst->path, s->dst->path)))
1871						{
1872							/* rule r of module m is using previously used dst of rule s of module n */
1873							asl_out_dst_data_release(r->dst);
1874							r->dst = NULL;
1875
1876							if (r->action == ACTION_OUT_DEST)
1877							{
1878								char *str = NULL;
1879								asprintf(&str, "[Sender syslogd] [Level 5] [PID %u] [Message Configuration Notice:\nASL Module \"%s\" sharing output destination \"%s\" with ASL Module \"%s\".\nOutput parameters from ASL Module \"%s\" override any specified in ASL Module \"%s\".] [UID 0] [GID 0] [Facility syslog]", global.pid, m->name, s->dst->path, n->name, n->name, m->name);
1880								internal_log_message(str);
1881								free(str);
1882							}
1883							else
1884							{
1885								r->dst = asl_out_dst_data_retain(s->dst);
1886							}
1887
1888							search = false;
1889						}
1890					}
1891				}
1892			}
1893		}
1894	}
1895
1896	if (r->action == ACTION_SET_PARAM)
1897	{
1898		if (r->query == NULL) _act_out_set_param(m, r->options, true);
1899	}
1900	else if (r->action == ACTION_CLAIM)
1901	{
1902		/* becomes ACTION_SKIP in com.apple.asl config */
1903		if (m != global.asl_out_module)
1904		{
1905			asl_out_rule_t *rule = (asl_out_rule_t *)calloc(1, sizeof(asl_out_rule_t));
1906			if (rule != NULL)
1907			{
1908				char *str = NULL;
1909				asprintf(&str, "[Sender syslogd] [Level 5] [PID %u] [Message Configuration Notice:\nASL Module \"%s\" claims selected messages.\nThose messages may not appear in standard system log files or in the ASL database.] [UID 0] [GID 0] [Facility syslog]", global.pid, m->name);
1910				internal_log_message(str);
1911				free(str);
1912
1913				rule->query = asl_msg_copy(r->query);
1914				rule->action = ACTION_SKIP;
1915				rule->next = global.asl_out_module->ruleset;
1916				global.asl_out_module->ruleset = rule;
1917			}
1918
1919			/*
1920			 * After adding ACTION_SKIP to com.apple.asl module, the claim becomes a no-op in this module
1921			 * UNLESS the claim includes the option "only".  In that case, the claim becomes a filter:
1922			 * any messages that DO NOT match the claim are skipped by this module.
1923			 */
1924			if (r->options == NULL) r->action = ACTION_NONE;
1925			else if (strcmp(r->options, "only") != 0) r->action = ACTION_NONE;
1926		}
1927	}
1928	else if (r->action == ACTION_ASL_STORE)
1929	{
1930		action_asl_store_count++;
1931	}
1932	else if (r->action == ACTION_ASL_DIR)
1933	{
1934		if (r->dst->private == NULL) r->dst->private = (asl_action_asl_store_data_t *)calloc(1, sizeof(asl_action_asl_store_data_t));
1935	}
1936	else if (r->action == ACTION_ASL_FILE)
1937	{
1938		if (r->dst->private == NULL)r->dst->private = (asl_action_asl_file_data_t *)calloc(1, sizeof(asl_action_asl_file_data_t));
1939	}
1940	else if (r->action == ACTION_FILE)
1941	{
1942		if (r->dst->private == NULL) r->dst->private = (asl_action_file_data_t *)calloc(1, sizeof(asl_action_file_data_t));
1943		if (r->dst->private != NULL) ((asl_action_file_data_t *)(r->dst->private))->fd = -1;
1944	}
1945	else if (r->action == ACTION_SET_PLIST)
1946	{
1947		char *ident =_asl_action_profile_test(m, r);
1948		char *notify_key = configuration_profile_create_notification_key(ident);
1949		free(ident);
1950
1951		if (notify_key != NULL)
1952		{
1953			int status, token;
1954			asl_action_set_param_data_t *spdata;
1955
1956			status = notify_register_dispatch(notify_key, &token, asl_action_queue, ^(int t){
1957				_asl_action_handle_file_change_notification(t);
1958			});
1959
1960			free(notify_key);
1961
1962			spdata = (asl_action_set_param_data_t *)calloc(1, sizeof(asl_action_set_param_data_t));
1963			if (spdata == NULL)
1964			{
1965				notify_cancel(token);
1966			}
1967			else
1968			{
1969				spdata->token = token;
1970				r->private = spdata;
1971			}
1972		}
1973	}
1974	else if (r->action == ACTION_SET_PROF)
1975	{
1976		char *ident =_asl_action_profile_test(m, r);
1977		char *notify_key = configuration_profile_create_notification_key(ident);
1978		free(ident);
1979
1980		if (notify_key != NULL)
1981		{
1982			int status, token;
1983			asl_action_set_param_data_t *spdata;
1984
1985			status = notify_register_dispatch(notify_key, &token, asl_action_queue, ^(int t){
1986				_asl_action_handle_file_change_notification(t);
1987			});
1988
1989			free(notify_key);
1990
1991			spdata = (asl_action_set_param_data_t *)calloc(1, sizeof(asl_action_set_param_data_t));
1992			if (spdata == NULL)
1993			{
1994				notify_cancel(token);
1995			}
1996			else
1997			{
1998				spdata->token = token;
1999				r->private = spdata;
2000			}
2001		}
2002	}
2003	else if (r->action == ACTION_SET_FILE)
2004	{
2005		char *notify_key;
2006		const char *path =_asl_action_file_test(m, r);
2007
2008		if (path != NULL)
2009		{
2010			asprintf(&notify_key, "%s%s", NOTIFY_PATH_SERVICE, path);
2011			if (notify_key != NULL)
2012			{
2013				int status, token;
2014				asl_action_set_param_data_t *spdata;
2015
2016				status = notify_register_dispatch(notify_key, &token, asl_action_queue, ^(int t){
2017					_asl_action_handle_file_change_notification(t);
2018				});
2019
2020				free(notify_key);
2021
2022				spdata = (asl_action_set_param_data_t *)calloc(1, sizeof(asl_action_set_param_data_t));
2023				if (spdata == NULL)
2024				{
2025					notify_cancel(token);
2026				}
2027				else
2028				{
2029					spdata->token = token;
2030					r->private = spdata;
2031				}
2032			}
2033		}
2034	}
2035}
2036
2037static void
2038_asl_action_configure()
2039{
2040	asl_out_rule_t *r;
2041	asl_out_module_t *m;
2042	uint32_t flags = 0;
2043
2044	if (global.asl_out_module == NULL) global.asl_out_module = asl_out_module_init();
2045	if (global.asl_out_module == NULL) return;
2046
2047	asldebug("%s: init\n", MY_ID);
2048
2049	action_asl_store_count = 0;
2050
2051	for (m = global.asl_out_module; m != NULL; m = m->next)
2052	{
2053		for (r = m->ruleset; r != NULL; r = r->next)
2054		{
2055			_asl_action_post_process_rule(m, r);
2056			if (r->dst != NULL) flags |= (r->dst->flags & (MODULE_FLAG_ROTATE | MODULE_FLAG_CRASHLOG));
2057		}
2058	}
2059
2060	if (global.debug != 0)
2061	{
2062		FILE *dfp;
2063		if (global.debug_file == NULL) dfp = fopen(_PATH_SYSLOGD_LOG, "a");
2064		else dfp = fopen(global.debug_file, "a");
2065		if (dfp != NULL)
2066		{
2067			for (m = global.asl_out_module; m != NULL; m = m->next)
2068			{
2069				fprintf(dfp, "module: %s%s\n", (m->name == NULL) ? "<unknown>" : m->name, (m->flags & MODULE_FLAG_LOCAL) ? " (local)" : "");
2070				asl_out_module_print(dfp, m);
2071				fprintf(dfp, "\n");
2072			}
2073			fclose(dfp);
2074		}
2075	}
2076
2077	sweep_time = time(NULL);
2078
2079	if (flags & MODULE_FLAG_ROTATE)
2080	{
2081		_act_file_checkpoint_all(CHECKPOINT_TEST);
2082		if (checkpoint_timer == NULL) _start_cycling();
2083	}
2084}
2085
2086int
2087asl_action_init(void)
2088{
2089	static dispatch_once_t once;
2090
2091	dispatch_once(&once, ^{
2092		asl_action_queue = dispatch_queue_create("ASL Action Queue", NULL);
2093#if TARGET_OS_EMBEDDED
2094		crashlog_queue = dispatch_queue_create("iOS CrashLog Queue", NULL);
2095		notify_register_dispatch(CRASH_MOVER_SERVICE, &crashmover_token, asl_action_queue, ^(int unused) {
2096			uint64_t cmstate = 0;
2097			uint64_t oldstate = (crashmover_state == 0) ? 0llu : 1llu;
2098
2099			uint32_t status = notify_get_state(crashmover_token, &cmstate);
2100			if (status == 0)
2101			{
2102				if (cmstate != oldstate)
2103				{
2104					crashmover_state = 0;
2105					if (cmstate == 1) crashmover_state = time(NULL);
2106
2107					if (crashmover_state == 0)
2108					{
2109						asldebug("CrashMover finished\n");
2110						dispatch_resume(crashlog_queue);
2111					}
2112					else
2113					{
2114						asldebug("CrashMover active: suspending crashlog queue and closing files\n");
2115						dispatch_suspend(crashlog_queue);
2116						_asl_action_close_idle_files(0);
2117					}
2118				}
2119			}
2120		});
2121#endif
2122	});
2123
2124	_asl_action_configure();
2125
2126	return 0;
2127}
2128
2129/*
2130 * Close outputs and free modules.
2131 */
2132static void
2133_asl_action_free_modules(asl_out_module_t *m)
2134{
2135	asl_out_rule_t *r;
2136	asl_out_module_t *x;
2137
2138	/*
2139	 * asl_common frees a list of modules with asl_out_module_free.
2140	 * This loop frees the private data attached some modules.
2141	 */
2142	for (x = m; x != NULL; x = x->next)
2143	{
2144		for (r = x->ruleset; r != NULL; r = r->next)
2145		{
2146			if (r->action == ACTION_ASL_DIR)
2147			{
2148				_act_dst_close(r, DST_CLOSE_SHUTDOWN);
2149				if (r->dst != NULL)
2150				{
2151					_asl_action_asl_store_data_free((asl_action_asl_store_data_t *)r->dst->private);
2152					r->dst->private = NULL;
2153				}
2154			}
2155			else if (r->action == ACTION_ASL_FILE)
2156			{
2157				_act_dst_close(r, DST_CLOSE_SHUTDOWN);
2158				if (r->dst != NULL)
2159				{
2160					_asl_action_asl_file_data_free((asl_action_asl_file_data_t *)r->dst->private);
2161					r->dst->private = NULL;
2162				}
2163			}
2164			else if (r->action == ACTION_FILE)
2165			{
2166				_act_dst_close(r, DST_CLOSE_SHUTDOWN);
2167				if (r->dst != NULL)
2168				{
2169					asl_action_file_data_t *f_data = (asl_action_file_data_t *)r->dst->private;
2170					if (f_data != NULL)
2171					{
2172						/* flush repeat message if necessary */
2173						if (f_data->last_count > 0) _send_repeat_msg(r);
2174						_asl_action_file_data_free(f_data);
2175						r->dst->private = NULL;
2176					}
2177				}
2178			}
2179			else if (r->action == ACTION_SET_PLIST)
2180			{
2181				_asl_action_set_param_data_free((asl_action_set_param_data_t *)r->private);
2182			}
2183			else if (r->action == ACTION_SET_PROF)
2184			{
2185				_asl_action_set_param_data_free((asl_action_set_param_data_t *)r->private);
2186			}
2187			else if (r->action == ACTION_SET_FILE)
2188			{
2189				_asl_action_set_param_data_free((asl_action_set_param_data_t *)r->private);
2190			}
2191		}
2192	}
2193
2194	asl_out_module_free(m);
2195}
2196
2197static int
2198_asl_action_close_internal(void)
2199{
2200#if TARGET_OS_EMBEDDED
2201	if (crashmover_state != 0)
2202	{
2203		dispatch_resume(crashlog_queue);
2204		crashmover_state = 0;
2205	}
2206
2207	/* wait for the crashlog_queue to flush before _asl_action_free_modules() */
2208	dispatch_sync(crashlog_queue, ^{ int x = 0; if (x == 1) x = 2; });
2209#endif
2210
2211	_asl_action_free_modules(global.asl_out_module);
2212	global.asl_out_module = NULL;
2213	sweep_time = time(NULL);
2214
2215	return 0;
2216}
2217
2218static void
2219_asl_action_close_idle_files(time_t idle_time)
2220{
2221	asl_out_module_t *m;
2222	time_t now = time(NULL);
2223
2224	for (m = global.asl_out_module; m != NULL; m = m->next)
2225	{
2226		asl_out_rule_t *r;
2227
2228		for (r = m->ruleset; r != NULL; r = r->next)
2229		{
2230			if (idle_time == 0)
2231			{
2232				if ((r->dst != NULL) && (r->dst->flags & MODULE_FLAG_CRASHLOG))
2233				{
2234					_act_dst_close(r, DST_CLOSE_IDLE);
2235					//TODO: can r->action even be ACTION_ASL_DIR?
2236					/* if not, we can avoid the extra check here */
2237					if (r->action != ACTION_ASL_DIR) _act_checkpoint(r, CHECKPOINT_FORCE);
2238				}
2239			}
2240			else if (r->action == ACTION_ASL_DIR)
2241			{
2242				if (r->dst != NULL)
2243				{
2244					asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private;
2245					if ((as_data != NULL) && (as_data->aslfile != NULL) && (as_data->pending == 0) && ((now - as_data->last_time) >= idle_time)) _act_dst_close(r, DST_CLOSE_IDLE);
2246				}
2247			}
2248			else if (r->action == ACTION_ASL_FILE)
2249			{
2250				if (r->dst != NULL)
2251				{
2252					asl_action_asl_file_data_t *af_data = (asl_action_asl_file_data_t *)r->dst->private;
2253					if ((af_data != NULL) && (af_data->aslfile != NULL) && (af_data->pending == 0) && ((now - af_data->last_time) >= idle_time)) _act_dst_close(r, DST_CLOSE_IDLE);
2254				}
2255			}
2256			else if (r->action == ACTION_FILE)
2257			{
2258				if (r->dst != NULL)
2259				{
2260					asl_action_file_data_t *f_data = (asl_action_file_data_t *)r->dst->private;
2261					if ((f_data != NULL) && (f_data->fd >= 0) && (f_data->pending == 0) && ((now - f_data->last_time) >= idle_time)) _act_dst_close(r, DST_CLOSE_IDLE);
2262				}
2263			}
2264		}
2265	}
2266}
2267
2268int
2269asl_action_close(void)
2270{
2271	dispatch_async(asl_action_queue, ^{
2272		_asl_action_close_internal();
2273	});
2274
2275	return 0;
2276}
2277
2278int
2279asl_action_reset(void)
2280{
2281	dispatch_async(asl_action_queue, ^{
2282		_asl_action_close_internal();
2283		asl_action_init();
2284	});
2285
2286	return 0;
2287}
2288
2289asl_out_module_t *
2290_asl_action_module_with_name(const char *name)
2291{
2292	asl_out_module_t *m;
2293
2294	if (global.asl_out_module == NULL) return NULL;
2295	if (name == NULL) return global.asl_out_module;
2296
2297	for (m = global.asl_out_module; m != NULL; m = m->next)
2298	{
2299		if ((m->name != NULL) && (!strcmp(m->name, name))) return m;
2300	}
2301
2302	return NULL;
2303}
2304
2305/*
2306 * called from control_message
2307 * Used to control modules dynamically.
2308 * Line format "@ module param [value ...]"
2309 *
2310 * Note this is synchronous on asl_action queue.
2311 */
2312int
2313asl_action_control_set_param(const char *s)
2314{
2315	__block char **l;
2316	__block char *p;
2317	uint32_t count = 0;
2318
2319	if (s == NULL) return -1;
2320	if (s[0] == '\0') return 0;
2321
2322	/* skip '@' and whitespace */
2323	if (*s == '@') s++;
2324	while ((*s == ' ') || (*s == '\t')) s++;
2325
2326	l = explode(s, " \t");
2327	if (l != NULL) for (count = 0; l[count] != NULL; count++);
2328
2329	/* at least 2 parameters (l[0] = module, l[1] = param) required */
2330	if (count < 2)
2331	{
2332		free_string_list(l);
2333		return -1;
2334	}
2335
2336	if (global.asl_out_module == NULL)
2337	{
2338		asldebug("asl_action_control_set_param: no modules loaded\n");
2339		free_string_list(l);
2340		return -1;
2341	}
2342
2343	/* create / modify a module */
2344	if ((!strcasecmp(l[1], "define")) && (strcmp(l[0], "*")))
2345	{
2346		p = strdup(s);
2347		if (p == NULL)
2348		{
2349			asldebug("asl_action_control_set_param: memory allocation failed\n");
2350			free_string_list(l);
2351			return -1;
2352		}
2353
2354		dispatch_sync(asl_action_queue, ^{
2355			asl_out_module_t *m;
2356			asl_out_rule_t *r;
2357
2358			/* skip name, whitespace, "define" */
2359			while ((*p != ' ') && (*p != '\t')) p++;
2360			while ((*p == ' ') || (*p == '\t')) p++;
2361			while ((*p != ' ') && (*p != '\t')) p++;
2362
2363			m = _asl_action_module_with_name(l[0]);
2364			if (m == NULL)
2365			{
2366				asl_out_module_t *x;
2367
2368				m = asl_out_module_new(l[0]);
2369				for (x = global.asl_out_module; x->next != NULL; x = x->next);
2370				x->next = m;
2371			}
2372
2373			r = asl_out_module_parse_line(m, p);
2374			if (r != NULL)
2375			{
2376				_asl_action_post_process_rule(m, r);
2377				if ((r->dst != NULL) && (r->dst->flags & MODULE_FLAG_ROTATE))
2378				{
2379					_act_file_checkpoint_all(CHECKPOINT_TEST);
2380					if (checkpoint_timer == NULL) _start_cycling();
2381				}
2382			}
2383		});
2384
2385		free(p);
2386		free_string_list(l);
2387		return 0;
2388	}
2389
2390	dispatch_sync(asl_action_queue, ^{
2391		uint32_t intval;
2392		int do_all = 0;
2393		asl_out_module_t *m;
2394
2395		if (!strcmp(l[0], "*"))
2396		{
2397			do_all = 1;
2398			m = _asl_action_module_with_name(NULL);
2399		}
2400		else
2401		{
2402			m = _asl_action_module_with_name(l[0]);
2403		}
2404
2405		while (m != NULL)
2406		{
2407			if (!strcasecmp(l[1], "enable"))
2408			{
2409				intval = 1;
2410
2411				/* don't do enable for ASL_MODULE_NAME if input name is "*" */
2412				if ((do_all == 0) || (strcmp(m->name, ASL_MODULE_NAME)))
2413				{
2414					/* @ module enable {0|1} */
2415					if (count > 2) intval = atoi(l[2]);
2416
2417					if (intval == 0) m->flags &= ~MODULE_FLAG_ENABLED;
2418					else m->flags |= MODULE_FLAG_ENABLED;
2419				}
2420			}
2421			else if (!strcasecmp(l[1], "checkpoint"))
2422			{
2423				/* @ module checkpoint [file] */
2424				if (count > 2) _act_file_checkpoint(m, l[2], CHECKPOINT_FORCE);
2425				else _act_file_checkpoint(m, NULL, CHECKPOINT_FORCE);
2426			}
2427
2428			if (do_all == 1) m = m->next;
2429			else m = NULL;
2430		}
2431
2432	});
2433
2434	free_string_list(l);
2435	return 0;
2436}
2437
2438int
2439asl_action_file_checkpoint(const char *module, const char *path)
2440{
2441	/* Note this is synchronous on asl_action queue */
2442	dispatch_sync(asl_action_queue, ^{
2443		asl_out_module_t *m = _asl_action_module_with_name(module);
2444		_act_file_checkpoint(m, path, CHECKPOINT_FORCE);
2445	});
2446
2447	return 0;
2448}
2449