]> git.saurik.com Git - apple/syslog.git/blobdiff - syslogd.tproj/asl_action.c
syslog-385.tar.gz
[apple/syslog.git] / syslogd.tproj / asl_action.c
index 92b7da1e2b0dd3e13891207b9d4220de64bd998f..3e3e9ad1a7ad59031ecf62d837cb381bc2565f2c 100644 (file)
@@ -1,27 +1,27 @@
 /*
- * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2004-2013 Apple Inc. All rights reserved.
  *
  * @APPLE_LICENSE_HEADER_START@
- * 
- * "Portions Copyright (c) 2004 Apple Computer, Inc.  All Rights
- * Reserved.  This file contains Original Code and/or Modifications of
- * Original Code as defined in and that are subject to the Apple Public
- * Source License Version 1.0 (the 'License').  You may not use this file
- * except in compliance with the License.  Please obtain a copy of the
- * License at http://www.apple.com/publicsource and read it before using
- * this file.
- * 
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
  * The Original Code and all software distributed under the License are
  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
- * License for the specific language governing rights and limitations
- * under the License."
- * 
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
  * @APPLE_LICENSE_HEADER_END@
  */
 
+#include <TargetConditionals.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/socket.h>
 #include <errno.h>
 #include <netdb.h>
 #include <notify.h>
+#include <pthread.h>
+#include <sys/acl.h>
+#include <dirent.h>
+#include <time.h>
+#include <membership.h>
+#include <configuration_profile.h>
 #include "daemon.h"
+#include <xpc/private.h>
+
+#define _PATH_WALL "/usr/bin/wall"
 
-#define _PATH_ASL_CONF "/etc/asl.conf"
 #define MY_ID "asl_action"
 
-#define ASL_KEY_FACILITY "Facility"
-#define IndexNull ((uint32_t)-1)
-#define forever for(;;)
+#define MAX_FAILURES 5
+
+#define ACTION_STATUS_ERROR  -1
+#define ACTION_STATUS_OK      0
 
-static asl_msg_t *query = NULL;
-static int reset = 0;
+#define IDLE_CLOSE 300
 
-struct action_rule
+#define PRIVATE_FLAG_NO_CLOSE    0x00000001 /* File or Store is closed by a cancellation handler */
+
+#define DST_CLOSE_CHECKPOINT 0
+#define DST_CLOSE_DELETED 1
+#define DST_CLOSE_ERROR 2
+#define DST_CLOSE_IDLE 3
+#define DST_CLOSE_SHUTDOWN 4
+static const char *why_str[] =
 {
-       asl_msg_t *query;
-       char *action;
-       char *options;
-       TAILQ_ENTRY(action_rule) entries;
+       "checkpoint",
+       "deleted",
+       "error",
+       "idle",
+       "shutdown"
 };
 
-static TAILQ_HEAD(cr, action_rule) asl_action_rule;
+#define forever for(;;)
+
+static dispatch_queue_t asl_action_queue;
+static dispatch_source_t checkpoint_timer;
+static time_t sweep_time = 0;
+
+#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
+#ifndef CRASH_MOVER_SERVICE
+#define CRASH_MOVER_SERVICE "com.apple.crash_mover"
+#endif
+static dispatch_queue_t crashlog_queue;
+static time_t crashmover_state = 0;
+static int crashmover_token = -1;
+#endif
+
+typedef struct asl_file_data
+{
+       uint64_t next_id;
+       asl_file_t *aslfile;
+       time_t last_time;
+       uint32_t flags;
+       uint32_t pending;
+       dispatch_source_t monitor;
+} asl_action_asl_file_data_t;
+
+typedef struct asl_store_data
+{
+       FILE *storedata;
+       asl_file_t *aslfile;
+       uint64_t next_id;
+       time_t last_time;
+       uint32_t flags;
+       uint32_t pending;
+       uint32_t p_year;
+       uint32_t p_month;
+       uint32_t p_day;
+       dispatch_source_t storedata_monitor;
+       dispatch_source_t aslfile_monitor;
+} asl_action_asl_store_data_t;
+
+typedef struct file_data
+{
+       int fd;
+       uint32_t flags;
+       time_t last_time;
+       uint32_t last_count;
+       uint32_t last_hash;
+       uint32_t pending;
+       char *last_msg;
+       dispatch_source_t dup_timer;
+       dispatch_source_t monitor;
+} asl_action_file_data_t;
+
+typedef struct set_param_data
+{
+       int token;
+} asl_action_set_param_data_t;
+
+static int action_asl_store_count;
+static bool store_has_logged;
+
+extern void db_save_message(asl_msg_t *m);
+
+/* forward */
+static int _act_file_checkpoint_all(uint32_t force);
+static void _asl_action_post_process_rule(asl_out_module_t *m, asl_out_rule_t *r);
+static void _asl_action_close_idle_files(time_t idle_time);
+static void _act_dst_close(asl_out_rule_t *r, int why);
+
+static void
+_act_out_set_param(asl_out_module_t *m, char *x, bool eval)
+{
+       char *s = x;
+       char **l;
+       uint32_t count, intval;
+
+       l = explode(s, " \t");
+       if (l == NULL) return;
+
+       for (count = 0; l[count] != NULL; count++);
+       if (count == 0)
+       {
+               free_string_list(l);
+               return;
+       }
+
+       if (!strcasecmp(l[0], "enable"))
+       {
+               /* = enable [1|0] */
+               if (count < 2) intval = 1;
+               else intval = atoi(l[1]);
+
+               if (!eval) intval = (intval == 0) ? 1 : 0;
+
+               if (intval == 0) m->flags &= ~MODULE_FLAG_ENABLED;
+               else m->flags|= MODULE_FLAG_ENABLED;
+               free_string_list(l);
+               return;
+       }
+       else if (!strcasecmp(l[0], "disable"))
+       {
+               /* = disable [1|0] */
+               if (count < 2) intval = 1;
+               else intval = atoi(l[1]);
+
+               if (!eval) intval = (intval == 0) ? 1 : 0;
+
+               if (intval != 0) m->flags &= ~MODULE_FLAG_ENABLED;
+               else m->flags|= MODULE_FLAG_ENABLED;
+               free_string_list(l);
+               return;
+       }
+
+       free_string_list(l);
 
-int asl_action_close();
-static int _parse_notify_file(const char *);
+       if (!strcmp(m->name, ASL_MODULE_NAME))
+       {
+               /* Other parameters may be set by com.apple.asl module */
+               control_set_param(x, eval);
+       }
+}
 
 static void
-_do_reset(void)
+_act_notify(asl_out_module_t *m, asl_out_rule_t *r)
 {
-       asl_action_close();
-       _parse_notify_file(_PATH_ASL_CONF);
+       if (m == NULL) return;
+       if ((m->flags & MODULE_FLAG_ENABLED) == 0) return;
+
+       if (r == NULL) return;
+       if (r->options == NULL) return;
+
+       notify_post(r->options);
 }
 
-/*
- * Config File format:
- * Q [k v] [k v] ... action args...
- */
+static void
+_act_broadcast(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
+{
+#if !(TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
+       FILE *pw;
+       const char *val;
 
-/* Skip over query */
-static char *
-_find_action(char *s)
+       if (m == NULL) return;
+       if ((m->flags & MODULE_FLAG_ENABLED) == 0) return;
+
+       if (m->name == NULL) return;
+       if (r == NULL) return;
+       if (msg == NULL) return;
+
+       /* only base module (asl.conf) may broadcast */
+       if (strcmp(m->name, ASL_MODULE_NAME)) return;
+
+       val = r->options;
+       if (val == NULL) val = asl_msg_get_val_for_key(msg, ASL_KEY_MSG);
+       if (val == NULL) return;
+
+       pw = popen(_PATH_WALL, "w");
+       if (pw == NULL)
+       {
+               asldebug("%s: error sending wall message: %s\n", MY_ID, strerror(errno));
+               return;
+       }
+
+       fprintf(pw, "%s", val);
+       pclose(pw);
+#endif
+}
+
+static void
+_act_set_key(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
+{
+       /* Placeholder */
+}
+
+static void
+_act_unset_key(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
+{
+       /* Placeholder */
+}
+
+static void
+_act_access_control(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
 {
+       int32_t ruid, rgid;
        char *p;
 
-       p = s;
-       if (p == NULL) return NULL;
-       if (*p != 'Q') return NULL;
+       if (m == NULL) return;
+       if (m->name == NULL) return;
+       if (r == NULL) return;
+       if (msg == NULL) return;
+
+       /* only base module (asl.conf) may set access controls */
+       if (strcmp(m->name, ASL_MODULE_NAME)) return;
 
-       p++;
+       ruid = atoi(r->options);
+       rgid = -1;
+       p = strchr(r->options, ' ');
+       if (p == NULL) p = strchr(r->options, '\t');
+       if (p != NULL)
+       {
+               *p = '\0';
+               p++;
+               rgid = atoi(p);
+       }
 
-       forever
+       if (ruid != -1) asl_msg_set_key_val(msg, ASL_KEY_READ_UID, r->options);
+       if (p != NULL)
        {
-               /* Find next [ */
-               while ((*p == ' ') || (*p == '\t')) p++;
+               if (rgid != -1) asl_msg_set_key_val(msg, ASL_KEY_READ_GID, p);
+               p--;
+               *p = ' ';
+       }
+}
+
+#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
+static void
+_crashlog_queue_check(void)
+{
+       /*
+        * Check whether the crashlog queue has been suspended for too long.
+        * We allow the crashlog quque to be suspended for 60 seconds.
+        * After that, we start logging again.  This prevents syslogd from
+        * filling memory due to a suspended queue.  CrashMover really shoud
+        * take no more than a second or two to finish.
+        */
+       if (crashmover_state == 0) return;
+       if ((time(NULL) - crashmover_state) <= 60) return;
+
+       asldebug("CrashMover timeout: resuming crashlog queue\n");
+       dispatch_resume(crashlog_queue);
+       crashmover_state = 0;
+}
+#endif
+
+/*
+ * cover routine for asl_out_dst_file_create_open()
+ */
+static int
+_act_file_create_open(asl_out_dst_data_t *dst)
+{
+       return asl_out_dst_file_create_open(dst, NULL);
+}
 
-               if (*p == '\0') return NULL;
-               if (*p != '[') return p;
+/*
+ * Checks and creates (if required) a directory for an ASL data store.
+ */
+static int
+_asl_dir_create(asl_out_rule_t *r)
+{
+       struct stat sb;
+       int status;
 
-               /* skip to closing ] */
-               while (*p != ']')
+       memset(&sb, 0, sizeof(struct stat));
+       status = stat(r->dst->path, &sb);
+       if (status == 0)
+       {
+               /* Store path must be a directory */
+               if (!S_ISDIR(sb.st_mode))
                {
-                       p++;
-                       if (*p == '\\')
-                       {
-                               p++;
-                               if (*p == ']') p++;
-                       }
+                       asldebug("_asl_dir_create: expected a directory at path %s\n", r->dst->path);
+                       return -1;
                }
-
-               if (*p == ']') p++;
+       }
+       else if (errno == ENOENT)
+       {
+               /* Directory doesn't exists - try to create it */
+               status = asl_out_mkpath(global.asl_out_module, r);
+               if (status != 0)
+               {
+                       asldebug("_asl_dir_create: asl_out_mkpath failed: %s\n", r->dst->path);
+                       return -1;
+               }
+       }
+       else
+       {
+               /* Unexpected stat error */
+               asldebug("_asl_dir_create: stat error %s\n", strerror(errno));
+               return -1;
        }
 
-       return NULL;
+       return 0;
 }
 
-static int
-_parse_line(char *s)
+/*
+ * Close an ASL Directory StoreData file
+ * - cancel the dispatch source for the file
+ */
+static void
+_asl_dir_storedata_close(asl_out_rule_t *r)
 {
-       char *act, *p;
-       struct action_rule *out;
+       asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private;
+       if (as_data->storedata == NULL) return;
 
-       if (s == NULL) return -1;
-       while ((*s == ' ') || (*s == '\t')) s++;
-       if (*s == '#') return -1;
+       if (as_data->storedata_monitor == NULL)
+       {
+               /*
+                * This should never happen, but _asl_dir_storedata_open allows
+                * dispatch_source_create to fail silently.  If there is no dispatch
+                * source, we just close the file.
+                */
+               asldebug("close ASL storedata fd %d\n", fileno(as_data->storedata));
+               fclose(as_data->storedata);
+       }
+       else
+       {
+               /*
+                * The storedata_monitor cancel handler will close the file.
+                */
+               dispatch_source_cancel(as_data->storedata_monitor);
+               dispatch_release(as_data->storedata_monitor);
+
+       }
 
-       act = _find_action(s);
+       asldebug("_asl_dir_storedata_close %p\n", as_data->storedata);
+       as_data->storedata = NULL;
+}
 
-       if (act == NULL) return -1;
-       out = (struct action_rule *)calloc(1, sizeof(struct action_rule));
-       if (out == NULL) return -1;
+/*
+ * Open an ASL Directory StoreData file
+ * - check directory existance and create it if necessary
+ * - check for StoreData file and create it (with given xid) if necessary.
+ * - create a dispatch source to watch for StoreData file deletion
+ */
+static int
+_asl_dir_storedata_open(asl_out_rule_t *r, uint64_t xid)
+{
+       struct stat sb;
+       int status;
+       char dstpath[MAXPATHLEN];
 
-       p = strchr(act, ' ');
-       if (p != NULL) *p = '\0';
-       out->action = strdup(act);
+       asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private;
+       if (as_data->storedata != NULL) return 0;
 
-       if (out->action == NULL)
+       status = _asl_dir_create(r);
+       if (status != 0)
        {
-               free(out);
+               asldebug("_asl_dir_storedata_open: No directory at path %s\n", r->dst->path);
                return -1;
        }
 
-       if (p != NULL)
-       {
-               out->options = strdup(p+1);
+       /* StoreData file is not open */
+       snprintf(dstpath, sizeof(dstpath), "%s/%s", r->dst->path, FILE_ASL_STORE_DATA);
 
-               if (out->options == NULL)
+       memset(&sb, 0, sizeof(struct stat));
+       status = stat(dstpath, &sb);
+       if (status == 0)
+       {
+               /* StoreData file exists */
+               as_data->storedata = fopen(dstpath, "r+");
+               if (as_data->storedata == NULL)
                {
-                       free(out->action);
-                       free(out);
+                       asldebug("_asl_dir_storedata_open: fopen existing %s: %s\n", dstpath, strerror(errno));
                        return -1;
                }
        }
+       else if (errno == ENOENT)
+       {
+               /*
+                * StoreData file does not exist.
+                * Create a new StoreData with a given xid.
+                */
+               //TODO: This should return a failure if there are any ASL files.
+               /* that would require reading the directory, thus a performance hit */
+               as_data->storedata = fopen(dstpath, "w+");
+               if (as_data->storedata == NULL)
+               {
+                       asldebug("_asl_dir_storedata_open: fopen new %s: %s\n", dstpath, strerror(errno));
+                       return -1;
+               }
 
-       p = act - 1;
-
-       *p = '\0';
-       out->query = asl_msg_from_string(s);
+               uint64_t xout = asl_core_htonq(xid);
+               status = fwrite(&xout, sizeof(uint64_t), 1, as_data->storedata);
+               if (status != 1)
+               {
+                       asldebug("_asl_dir_storedata_open: storedata write failed %d %s\n", errno, strerror(errno));
+                       return -1;
+               }
 
-       if (out->query == NULL)
+#if !TARGET_OS_SIMULATOR
+               if (chown(dstpath, r->dst->uid[0], r->dst->gid[0]) != 0)
+               {
+                       asldebug("_asl_dir_storedata_open: chown %d %d new %s: %s\n", dstpath, r->dst->uid[0], r->dst->gid[0], strerror(errno));
+                       return -1;
+               }
+#endif
+       }
+       else
        {
-               free(out->action);
-               if (out->options != NULL) free(out->options);
-               free(out);
+               /* Unexpected stat error */
+               asldebug("_asl_dir_storedata_open: stat error %s\n", strerror(errno));
                return -1;
        }
 
-       TAILQ_INSERT_TAIL(&asl_action_rule, out, entries);
+       /* create storedata_monitor */
+       int fd = fileno(as_data->storedata);
+       FILE *sdfp = as_data->storedata;
+
+       as_data->storedata_monitor = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_DELETE, asl_action_queue);
+       if (as_data->storedata_monitor != NULL)
+       {
+               dispatch_source_set_event_handler(as_data->storedata_monitor, ^{
+                       _act_dst_close(r, DST_CLOSE_DELETED);
+               });
+
+               dispatch_source_set_cancel_handler(as_data->storedata_monitor, ^{
+                       asldebug("cancel/close ASL storedata fd %d\n", fd);
+                       fclose(sdfp);
+               });
+
+               dispatch_resume(as_data->storedata_monitor);
+       }
 
+       asldebug("_asl_dir_storedata_open ASL storedata %s fd %d\n", dstpath, fd);
        return 0;
 }
 
-static void 
-_act_notify(struct action_rule *r)
+/*
+ * Close an ASL Directory ASL file
+ * - cancel the dispatch source for the file
+ * - clears file name and timestamp in asl_action_asl_store_data_t structue
+ */
+static void
+_asl_dir_today_close(asl_out_rule_t *r)
 {
-       if (r == NULL) return;
-       if (r->options == NULL) return;
-       notify_post(r->options);
+       asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private;
+       if (as_data->aslfile == NULL) return;
+
+       if (as_data->pending != 0)
+       {
+               char *str = NULL;
+               asprintf(&str, "[Sender syslogd] [Level 4] [PID %u] [Facility syslog] [Message ASL Store %s was closed with %d pending messages]", global.pid, r->dst->current_name, as_data->pending);
+               internal_log_message(str);
+               free(str);
+       }
+
+       if (as_data->aslfile_monitor == NULL)
+       {
+               /*
+                * This should never happen, but _asl_dir_today_open allows
+                * dispatch_source_create to fail silently.  If there is no dispatch
+                * source, we just close the file.
+                */
+               asldebug("close ASL fd %d\n", fileno(as_data->aslfile->store));
+               asl_file_close(as_data->aslfile);
+       }
+       else
+       {
+               /*
+                * The aslfile_monitor cancel handler will close the file.
+                */
+               dispatch_source_cancel(as_data->aslfile_monitor);
+               dispatch_release(as_data->aslfile_monitor);
+               as_data->aslfile_monitor = NULL;
+       }
+
+       as_data->p_year = 0;
+       as_data->p_month = 0;
+       as_data->p_day = 0;
+
+       free(r->dst->current_name);
+       r->dst->current_name = NULL;
+
+       as_data->aslfile = NULL;
 }
 
-int
-asl_action_sendmsg(asl_msg_t *msg, const char *outid)
+/*
+ * Check file size.
+ */
+static int
+_act_checkpoint(asl_out_rule_t *r, uint32_t force)
 {
-       struct action_rule *r;
+       char tmpcurrent_name[MAXPATHLEN], *fn;
+       bool size_only = false;
 
+       if (r == NULL) return 0;
+       if (r->dst == NULL) return 0;
 
-       if (reset != 0)
+       fn = r->dst->current_name;
+       if (fn == NULL)
        {
-               _do_reset();
-               reset = 0;
+               if (r->dst->path == NULL) return 0;
+               asl_dst_make_current_name(r->dst, 0, tmpcurrent_name, sizeof(tmpcurrent_name));
+               fn = tmpcurrent_name;
        }
 
-       if (msg == NULL) return -1;
+       if ((force == CHECKPOINT_TEST) || (r->dst->flags & MODULE_FLAG_SIZE_ONLY))
+       {
+               size_only = true;
+       }
 
-       for (r = asl_action_rule.tqh_first; r != NULL; r = r->entries.tqe_next)
+       if (size_only && (r->dst->file_max == 0)) return 0;
+
+       if ((r->dst->size == 0) || (r->dst->timestamp == 0))
        {
-               if (asl_msg_cmp(r->query, msg) == 1)
+               struct stat sb;
+
+               memset(&sb, 0, sizeof(struct stat));
+
+               if (stat(fn, &sb) < 0)
                {
-                       if (r->action == NULL) continue;
-                       if (!strcmp(r->action, "notify")) _act_notify(r);
+                       if (errno == ENOENT) return 0;
+                       return -1;
                }
+
+               if (r->dst->timestamp == 0) r->dst->timestamp = sb.st_birthtimespec.tv_sec;
+               if (r->dst->timestamp == 0) r->dst->timestamp = sb.st_mtimespec.tv_sec;
+               r->dst->size = sb.st_size;
        }
+       
+       if (size_only && (r->dst->size < r->dst->file_max)) return 0;
 
-       return 0;
+       if (r->dst->flags & MODULE_FLAG_BASESTAMP)
+       {
+               _act_dst_close(r, DST_CLOSE_CHECKPOINT);
+       }
+       else
+       {
+               char srcpath[MAXPATHLEN];
+               char dstpath[MAXPATHLEN];
+
+               snprintf(srcpath, sizeof(srcpath), "%s", fn);
+
+               r->dst->timestamp = time(NULL);
+               asl_dst_make_current_name(r->dst, MODULE_FLAG_BASESTAMP, dstpath, sizeof(dstpath));
+
+               _act_dst_close(r, DST_CLOSE_CHECKPOINT);
+               if (strneq(srcpath, dstpath))
+               {
+                       rename(srcpath, dstpath);
+                       asldebug("CHECKPOINT RENAME %s %s\n", srcpath, dstpath);
+               }
+       }
+
+       r->dst->timestamp = 0;
+       r->dst->size = 0;
+       return 1;
 }
 
+/*
+ * Open today's ASL file in an ASL directory
+ * - Checks date and closes a currently open file if it has the wrong date
+ * - Opens today's file
+ */
 static int
-_parse_notify_file(const char *name)
+_asl_dir_today_open(asl_out_rule_t *r, const time_t *tick)
 {
-       FILE *cf;
-       char *line;
+       int status;
+       mode_t mask;
+       struct tm ctm;
+       time_t now;
 
-       cf = fopen(name, "r");
-       if (cf == NULL) return 1;
+       if (r == NULL) return -1;
+       if (r->dst == NULL) return -1;
 
-       while (NULL != (line = get_line_from_file(cf)))
+       status = _asl_dir_create(r);
+       if (status != 0)
        {
-               _parse_line(line);
-               free(line);
+               asldebug("_asl_dir_today_open: No directory at path %s\n", r->dst->path);
+               return -1;
        }
 
-       fclose(cf);
+       asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private;
 
-       return 0;
-}
+       memset(&ctm, 0, sizeof(struct tm));
+       if (tick == NULL)
+       {
+               now = time(NULL);
+               tick = (const time_t *)&now;
+       }
 
-int
-asl_action_init(void)
-{
-       asldebug("%s: init\n", MY_ID);
+       if (localtime_r(tick, &ctm) == NULL)
+       {
+               asldebug("_asl_dir_today_open: localtime_r error %s\n", strerror(errno));
+               return -1;
+       }
+
+       /* checks file_max and closes if required */
+       status = _act_checkpoint(r, CHECKPOINT_TEST);
+       if (status == 1) asl_trigger_aslmanager();
+
+       if (as_data->aslfile != NULL)
+       {
+               /* Check the date */
+               if ((as_data->p_year == ctm.tm_year) && (as_data->p_month == ctm.tm_mon) && (as_data->p_day == ctm.tm_mday)) return 0;
+
+               /* Wrong date, close the current file */
+               _asl_dir_today_close(r);
+       }
+
+       /* Open data file */
+
+       if (r->dst->flags & MODULE_FLAG_BASESTAMP)
+       {
+               char tstamp[32];
+
+               if (tick == NULL)
+               {
+                       now = time(NULL);
+                       tick = (const time_t *)&now;
+               }
+
+               asl_make_timestamp(now, r->dst->style_flags, tstamp, sizeof(tstamp));
+               asprintf(&(r->dst->current_name), "%s/%s.asl", r->dst->path, tstamp);
+       }
+       else
+       {
+               asprintf(&(r->dst->current_name), "%s/%d.%02d.%02d.asl", r->dst->path, ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
+       }
+
+
+       if (r->dst->current_name == NULL)
+       {
+               asldebug("_asl_dir_today_open: asprintf error %s\n", strerror(errno));
+               return -1;
+       }
+
+#if TARGET_OS_SIMULATOR
+       uid_t uid = -1;
+       gid_t gid = -1;
+#else
+       uid_t uid = r->dst->uid[0];
+       gid_t gid = r->dst->gid[0];
+#endif
+
+       mask = umask(0);
+       status = asl_file_open_write(r->dst->current_name, (r->dst->mode & 00666), uid, gid, &(as_data->aslfile));
+       umask(mask);
+
+       if (status != ASL_STATUS_OK)
+       {
+               asldebug("_asl_dir_today_open: asl_file_open_write %s error %s\n", r->dst->current_name, asl_core_error(status));
+               free(r->dst->current_name);
+               r->dst->current_name = NULL;
+               return -1;
+       }
 
-       TAILQ_INIT(&asl_action_rule);
+       if (fseek(as_data->aslfile->store, 0, SEEK_END) != 0)
+       {
+               asldebug("_asl_dir_today_open: fseek %s error %s\n", r->dst->current_name, strerror(errno));
+               free(r->dst->current_name);
+               r->dst->current_name = NULL;
+               return -1;
+       }
+
+       as_data->p_year = ctm.tm_year;
+       as_data->p_month = ctm.tm_mon;
+       as_data->p_day = ctm.tm_mday;
+
+       /* create aslfile_monitor */
+       int fd = fileno(as_data->aslfile->store);
+       asl_file_t *aslf = as_data->aslfile;
+
+       as_data->aslfile_monitor = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_DELETE, asl_action_queue);
+       if (as_data->aslfile_monitor != NULL)
+       {
+               dispatch_source_set_event_handler(as_data->aslfile_monitor, ^{
+                       _act_dst_close(r, DST_CLOSE_DELETED);
+               });
+
+               dispatch_source_set_cancel_handler(as_data->aslfile_monitor, ^{
+                       asldebug("cancel/close ASL file fd %d\n", fd);
+                       asl_file_close(aslf);
+               });
 
-       query = asl_new(ASL_TYPE_QUERY);
-       aslevent_addmatch(query, MY_ID);
-       aslevent_addoutput(asl_action_sendmsg, MY_ID);
+               dispatch_resume(as_data->aslfile_monitor);
+       }
+
+       asldebug("_asl_dir_today_open ASL file %s fd %d\n", r->dst->current_name, fd);
 
-       _parse_notify_file(_PATH_ASL_CONF);
        return 0;
 }
 
-int
-asl_action_reset(void)
+static void
+_asl_file_close(asl_out_rule_t *r)
 {
-       reset = 1;
-       return 0;
+       if (r == NULL) return;
+       if (r->dst == NULL) return;
+
+       asl_action_asl_file_data_t *af_data = (asl_action_asl_file_data_t *)r->dst->private;
+       if (af_data->aslfile == NULL) return;
+
+       if (af_data->pending != 0)
+       {
+               char *str = NULL;
+               asprintf(&str, "[Sender syslogd] [Level 4] [PID %u] [Facility syslog] [Message ASL File %s was closed with %d pending messages]", global.pid, r->dst->current_name, af_data->pending);
+               internal_log_message(str);
+               free(str);
+       }
+
+       if (af_data->monitor == NULL)
+       {
+               /*
+                * This should never happen, but _asl_file_open allows
+                * dispatch_source_create to fail silently.  If there is no dispatch
+                * source, we just close the file.
+                */
+               asldebug("close ASL fd %d\n", fileno(af_data->aslfile->store));
+               asl_file_close(af_data->aslfile);
+       }
+       else
+       {
+               /*
+                * The monitor cancel handler will close the file.
+                */
+               dispatch_source_cancel(af_data->monitor);
+               dispatch_release(af_data->monitor);
+               af_data->monitor = NULL;
+       }
+
+       af_data->aslfile = NULL;
 }
 
-int
-asl_action_close(void)
+static int
+_asl_file_open(asl_out_rule_t *r)
 {
-       struct action_rule *r, *n;
+       int fd, status;
 
-       n = NULL;
-       for (r = asl_action_rule.tqh_first; r != NULL; r = n)
-       {
-               n = r->entries.tqe_next;
+       if (r == NULL) return -1;
+       if (r->dst == NULL) return -1;
 
-               if (r->query != NULL) asl_free(r->query);
-               if (r->action != NULL) free(r->action);
-               if (r->options != NULL) free(r->options);
+       asl_action_asl_file_data_t *af_data = (asl_action_asl_file_data_t *)r->dst->private;
+       if (af_data->aslfile != NULL) return 0;
 
-               TAILQ_REMOVE(&asl_action_rule, r, entries);
-               free(r);
+       /* create path if necessary */
+       status = asl_out_mkpath(global.asl_out_module, r);
+       if (status != 0)
+       {
+               asldebug("_asl_file_open: asl_out_mkpath %s failed\n", r->dst->path);
+               return -1;
        }
 
-       return 0;
+       fd = _act_file_create_open(r->dst);
+       if (fd < 0)
+       {
+               asldebug("_asl_file_open: _act_file_create_open %s failed %d %s\n", r->dst->current_name, errno, strerror(errno));
+               return -1;
+       }
+
+       close(fd);
+
+       if (r->dst->current_name == NULL) return -1;
+
+       status = asl_file_open_write(r->dst->current_name, 0, -1, -1, &(af_data->aslfile));
+       if (status != ASL_STATUS_OK)
+       {
+               asldebug("_asl_file_open: asl_file_open_write %s failed %d %s\n", r->dst->current_name, errno, strerror(errno));
+               return -1;
+       }
+
+       /* create monitor */
+       fd = fileno(af_data->aslfile->store);
+       asl_file_t *aslf = af_data->aslfile;
+
+       af_data->monitor = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_DELETE, asl_action_queue);
+       if (af_data->monitor != NULL)
+       {
+               dispatch_source_set_event_handler(af_data->monitor, ^{
+                       _act_dst_close(r, DST_CLOSE_DELETED);
+               });
+
+               dispatch_source_set_cancel_handler(af_data->monitor, ^{
+                       asldebug("cancel/close ASL file fd %d\n", fd);
+                       asl_file_close(aslf);
+               });
+
+               dispatch_resume(af_data->monitor);
+       }
+
+       asldebug("_asl_file_open ASL file %s fd %d\n", r->dst->current_name, fd);
+       return 0;
+}
+
+static void
+_text_file_close(asl_out_rule_t *r)
+{
+       asl_action_file_data_t *f_data = (asl_action_file_data_t *)r->dst->private;
+       if (f_data->fd < 0) return;
+
+       if (f_data->pending != 0)
+       {
+               char *str = NULL;
+               asprintf(&str, "[Sender syslogd] [Level 4] [PID %u] [Facility syslog] [Message File %s was closed with %d pending messages]", global.pid, r->dst->current_name, f_data->pending);
+               internal_log_message(str);
+               free(str);
+       }
+
+       if (f_data->monitor == NULL)
+       {
+               /*
+                * This should never happen, but _text_file_open allows
+                * dispatch_source_create to fail silently.  If there is no dispatch
+                * source, we just close the file.
+                */
+               asldebug("close fd %d\n", f_data->fd);
+               close(f_data->fd);
+       }
+       else
+       {
+               /*
+                * The monitor cancel handler will close the file.
+                */
+               dispatch_source_cancel(f_data->monitor);
+               dispatch_release(f_data->monitor);
+               f_data->monitor = NULL;
+       }
+
+       f_data->fd = -1;
+}
+
+static int
+_text_file_open(asl_out_rule_t *r)
+{
+       asl_action_file_data_t *f_data = (asl_action_file_data_t *)r->dst->private;
+
+       if (f_data->fd < 0)
+       {
+               f_data->fd = _act_file_create_open(r->dst);
+               if (f_data->fd < 0)
+               {
+                       /*
+                        * lazy path creation: create path and retry
+                        * _act_file_create_open does not create the path
+                        * so we do it here.
+                        */
+                       int status = asl_out_mkpath(global.asl_out_module, r);
+                       if (status != 0) return -1;
+
+                       f_data->fd = _act_file_create_open(r->dst);
+               }
+
+               if (f_data->fd < 0) return -1;
+
+               f_data->monitor = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, f_data->fd, DISPATCH_VNODE_DELETE, asl_action_queue);
+               if (f_data->monitor != NULL)
+               {
+                       int ffd = f_data->fd;
+
+                       dispatch_source_set_event_handler(f_data->monitor, ^{
+                               asldebug("dispatch_source DISPATCH_VNODE_DELETE fd %d\n", ffd);
+                               _act_dst_close(r, DST_CLOSE_DELETED);
+                       });
+
+                       dispatch_source_set_cancel_handler(f_data->monitor, ^{
+                               asldebug("cancel/close file fd %d\n", ffd);
+                               close(ffd);
+                       });
+
+                       dispatch_resume(f_data->monitor);
+               }
+       }
+
+       return 0;
+}
+
+static int
+_act_dst_open(asl_out_rule_t *r, const time_t *tick, uint64_t xid)
+{
+       if (r == NULL) return -1;
+       if (r->dst == NULL) return -1;
+       if (r->dst->private == NULL) return -1;
+
+       if (r->action == ACTION_ASL_DIR)
+       {
+               if (_asl_dir_today_open(r, tick) != 0)
+               {
+                       asldebug("_act_dst_open:_asl_dir_today_open %s FAILED!\n", r->dst->path);
+                       return -1;
+               }
+
+               if (_asl_dir_storedata_open(r, xid) != 0)
+               {
+                       asldebug("_act_dst_open:_asl_dir_storedata_open %s FAILED!  Closing today file\n", r->dst->path);
+                       _asl_dir_today_close(r);
+                       return -1;
+               }
+
+               return 0;
+       }
+
+       if (r->action == ACTION_ASL_FILE)
+       {
+               return _asl_file_open(r);
+       }
+
+       if (r->action == ACTION_FILE)
+       {
+               return _text_file_open(r);
+       }
+
+       return -1;
+}
+
+static void
+_act_dst_close(asl_out_rule_t *r, int why)
+{
+       if (r == NULL) return;
+       if (r->dst == NULL) return;
+       if (r->dst->private == NULL) return;
+
+       if (r->action == ACTION_ASL_DIR)
+       {
+               asldebug("_act_dst_close: %s ASL DIR %s\n", why_str[why], r->dst->path);
+               if (why != DST_CLOSE_CHECKPOINT) _asl_dir_storedata_close(r);
+               _asl_dir_today_close(r);
+       }
+       else if (r->action == ACTION_ASL_FILE)
+       {
+               asldebug("_act_dst_close: %s ASL FILE %s\n", why_str[why], (r->dst->current_name == NULL) ? r->dst->path : r->dst->current_name);
+               _asl_file_close(r);
+       }
+       else if (r->action == ACTION_FILE)
+       {
+               asldebug("_act_dst_close: %s FILE %s\n", why_str[why], (r->dst->current_name == NULL) ? r->dst->path : r->dst->current_name);
+               _text_file_close(r);
+       }
+}
+
+static uint32_t
+_act_store_file_setup(asl_out_module_t *m, asl_out_rule_t *r)
+{
+       uint32_t status;
+       asl_action_asl_file_data_t *af_data;
+
+       if (r == NULL) return ASL_STATUS_INVALID_STORE;
+       if (r->dst == NULL) return ASL_STATUS_INVALID_STORE;
+       if (r->dst->private == NULL) return ASL_STATUS_INVALID_STORE;
+
+       af_data = (asl_action_asl_file_data_t *)r->dst->private;
+       if (af_data->aslfile != NULL)
+       {
+               af_data->next_id++;
+               return ASL_STATUS_OK;
+       }
+
+       if (_act_dst_open(r, NULL, 0) != 0) return ASL_STATUS_WRITE_FAILED;
+
+       status = asl_file_read_set_position(af_data->aslfile, ASL_FILE_POSITION_LAST);
+       if (status != ASL_STATUS_OK)
+       {
+               asldebug("_act_store_file_setup: asl_file_read_set_position failed %d %s\n", status, asl_core_error(status));
+               _act_dst_close(r, DST_CLOSE_ERROR);
+               return status;
+       }
+
+       af_data->next_id = af_data->aslfile->cursor_xid + 1;
+       if (fseek(af_data->aslfile->store, 0, SEEK_END) != 0)
+       {
+               asldebug("_act_store_file_setup: fseek failed %d %s\n", errno, strerror(errno));
+               _act_dst_close(r, DST_CLOSE_ERROR);
+               return ASL_STATUS_WRITE_FAILED;
+       }
+
+       return ASL_STATUS_OK;
+}
+
+/*
+ * _act_store_dir_setup
+ *
+ * Opens StoreData file and today's file
+ * Reads ASL Message ID from StoreData file
+ * Writes ASL Message ID + 1 to StoreData file
+ */
+static uint32_t
+_act_store_dir_setup(asl_out_module_t *m, asl_out_rule_t *r, time_t tick)
+{
+       uint64_t xid;
+       int status;
+       asl_action_asl_store_data_t *as_data;
+
+       if (r == NULL) return ASL_STATUS_INVALID_STORE;
+       if (r->dst == NULL) return ASL_STATUS_INVALID_STORE;
+       if (r->dst->private == NULL) return ASL_STATUS_INVALID_STORE;
+       if (r->dst->path == NULL) return ASL_STATUS_INVALID_STORE;
+
+       as_data = (asl_action_asl_store_data_t *)r->dst->private;
+
+       if (_act_dst_open(r, NULL, as_data->next_id) != 0)
+       {
+               asldebug("_act_store_dir_setup: _act_dst_open %s failed\n", r->dst->path);
+               return ASL_STATUS_WRITE_FAILED;
+       }
+
+       /* get / set message id from StoreData file */
+       xid = 0;
+       rewind(as_data->storedata);
+       if (fread(&xid, sizeof(uint64_t), 1, as_data->storedata) != 1)
+       {
+               asldebug("_act_store_dir_setup: storedata read failed %d %s\n", errno, strerror(errno));
+               _act_dst_close(r, DST_CLOSE_ERROR);
+               return ASL_STATUS_READ_FAILED;
+       }
+
+       xid = asl_core_ntohq(xid);
+       xid++;
+       as_data->next_id = xid;
+
+       xid = asl_core_htonq(xid);
+       rewind(as_data->storedata);
+       status = fwrite(&xid, sizeof(uint64_t), 1, as_data->storedata);
+       if (status != 1)
+       {
+               asldebug("_act_store_dir_setup: storedata write failed %d %s\n", errno, strerror(errno));
+               _act_dst_close(r, DST_CLOSE_ERROR);
+               return ASL_STATUS_WRITE_FAILED;
+       }
+
+       fflush(as_data->storedata);
+
+       if (fseek(as_data->aslfile->store, 0, SEEK_END) != 0)
+       {
+               asldebug("_act_store_dir_setup: aslfile fseek failed %d %s\n", errno, strerror(errno));
+               _act_dst_close(r, DST_CLOSE_ERROR);
+               return ASL_STATUS_FAILED;
+       }
+
+       return ASL_STATUS_OK;
+}
+
+static void
+_asl_action_asl_store_data_free(asl_action_asl_store_data_t *as_data)
+{
+       if (as_data == NULL) return;
+       free(as_data);
+}
+
+static void
+_asl_action_asl_file_data_free(asl_action_asl_file_data_t *af_data)
+{
+       if (af_data == NULL) return;
+       free(af_data);
+}
+
+static void
+_asl_action_file_data_free(asl_action_file_data_t *f_data)
+{
+       if (f_data == NULL) return;
+
+       if (f_data->dup_timer != NULL)
+       {
+               if (f_data->last_count == 0)
+               {
+                       /*
+                        * The timer exists, but last_count is zero, so the timer is suspended.
+                        * Sources must not be released in when suspended.
+                        * So we resume it so that we can release it.
+                        */
+                       dispatch_resume(f_data->dup_timer);
+               }
+
+               dispatch_release(f_data->dup_timer);
+       }
+
+       free(f_data->last_msg);
+       free(f_data);
+}
+
+static void
+_asl_action_set_param_data_free(asl_action_set_param_data_t *spdata)
+{
+       if (spdata != NULL) notify_cancel(spdata->token);
+       free(spdata);
+}
+
+static void
+_asl_action_save_failed(const char *where, asl_out_module_t *m, asl_out_rule_t *r, uint32_t status)
+{
+       if (r->dst->flags & MODULE_FLAG_SOFT_WRITE) return;
+
+       r->dst->fails++;
+       asldebug("%s: %s save to %s failed: %s\n", where, m->name, r->dst->path, asl_core_error(status));
+
+       /* disable further activity after multiple failures */
+       if (r->dst->fails > MAX_FAILURES)
+       {
+               char *str = NULL;
+               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));
+               internal_log_message(str);
+               free(str);
+
+               if (r->action == ACTION_ASL_DIR) _asl_action_asl_store_data_free((asl_action_asl_store_data_t *)r->dst->private);
+               else if (r->action == ACTION_ASL_FILE) _asl_action_asl_file_data_free((asl_action_asl_file_data_t *)r->dst->private);
+               else if (r->action == ACTION_FILE) _asl_action_file_data_free((asl_action_file_data_t *)r->dst->private);
+
+               r->dst->private = NULL;
+               r->action = ACTION_NONE;
+       }
+}
+
+/*
+ * Save a message in an ASL file.
+ */
+static int
+_act_store_file(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
+{
+       asl_action_asl_file_data_t *af_data;
+       uint32_t status;
+       uint64_t mid;
+
+       if (r == NULL) return ACTION_STATUS_ERROR;
+       if (r->dst == NULL) return ACTION_STATUS_ERROR;
+       if (r->dst->private == NULL) return ACTION_STATUS_ERROR;
+
+       af_data = (asl_action_asl_file_data_t *)r->dst->private;
+       if (af_data->pending > 0) af_data->pending--;
+
+       status = _act_store_file_setup(m, r);
+       if (status == ASL_STATUS_OK)
+       {
+               af_data->last_time = time(NULL);
+
+               r->dst->fails = 0;
+               mid = af_data->next_id;
+
+               /* save message to file and update dst size */
+               status = asl_file_save(af_data->aslfile, msg, &mid);
+               if (status == ASL_STATUS_OK)
+               {
+                       r->dst->size = af_data->aslfile->file_size;
+
+                       if (_act_checkpoint(r, CHECKPOINT_TEST) == 1) asl_trigger_aslmanager();
+               }
+       }
+
+       if (status != ASL_STATUS_OK)
+       {
+               _asl_action_save_failed("_act_store_file", m, r, status);
+               return ACTION_STATUS_ERROR;
+       }
+
+       return ACTION_STATUS_OK;
+}
+
+/*
+ * Save a message in an ASL data store.
+ */
+static int
+_act_store_dir(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
+{
+       asl_action_asl_store_data_t *as_data;
+       uint32_t status;
+       uint64_t mid;
+       const char *val;
+       time_t tick;
+
+       if (r == NULL) return ACTION_STATUS_ERROR;
+       if (r->dst == NULL) return ACTION_STATUS_ERROR;
+       if (r->dst->private == NULL) return ACTION_STATUS_ERROR;
+
+       as_data = (asl_action_asl_store_data_t *)r->dst->private;
+       if (as_data->pending > 0) as_data->pending--;
+
+       val = asl_msg_get_val_for_key(msg, ASL_KEY_TIME);
+       if (val == NULL) return ACTION_STATUS_ERROR;
+
+       tick = atol(val);
+
+       status = _act_store_dir_setup(m, r, tick);
+       if (status == ASL_STATUS_OK)
+       {
+               as_data->last_time = time(NULL);
+
+               r->dst->fails = 0;
+               mid = as_data->next_id;
+               status = asl_file_save(as_data->aslfile, msg, &mid);
+               if (status == ASL_STATUS_OK) r->dst->size = as_data->aslfile->file_size;
+               else asldebug("_act_store_dir asl_file_save FAILED %s\n", asl_core_error(status));
+               //TODO: checkpoint here?
+               /*
+                * Currently, the checkpoint is in _asl_dir_today_open().
+                * Moving it here would be in keeping with the way that
+                * _act_store_file() and _act_file_final() do checkpoints.
+                */
+       }
+
+       if (status != ASL_STATUS_OK)
+       {
+               _asl_action_save_failed("_act_store_dir", m, r, status);
+               return ACTION_STATUS_ERROR;
+       }
+
+       return ACTION_STATUS_OK;
+}
+
+static void
+_act_store_final(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
+{
+       if (r->action == ACTION_ASL_DIR) _act_store_dir(m, r, msg);
+       else _act_store_file(m, r, msg);
+}
+
+/*
+ * Save a message to an ASL format file (ACTION_ASL_FILE)
+ * or to an ASL directory (ACTION_ASL_DIR).
+ */
+static void
+_act_store(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
+{
+       if (r == NULL) return;
+       if (r->dst == NULL) return;
+       if (msg == NULL) return;
+       if (m == NULL) return;
+       if ((m->flags & MODULE_FLAG_ENABLED) == 0) return;
+
+       if (r->dst->flags & MODULE_FLAG_HAS_LOGGED) return;
+
+       r->dst->flags |= MODULE_FLAG_HAS_LOGGED;
+       if (r->action == ACTION_ASL_DIR)
+       {
+               asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private;
+               if (as_data != NULL) as_data->pending++;
+       }
+       else if (r->action == ACTION_ASL_FILE)
+       {
+               asl_action_asl_file_data_t *af_data = (asl_action_asl_file_data_t *)r->dst->private;
+               if (af_data != NULL) af_data->pending++;
+       }
+
+#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
+       if (r->dst->flags & MODULE_FLAG_CRASHLOG)
+       {
+               _crashlog_queue_check();
+               asl_msg_retain(msg);
+               dispatch_async(crashlog_queue, ^{
+                       dispatch_async(asl_action_queue, ^{
+                               _act_store_final(m, r, msg);
+                               asl_msg_release(msg);
+                       });
+               });
+               return;
+       }
+#endif
+
+       _act_store_final(m, r, msg);
+}
+
+static int
+_send_repeat_msg(asl_out_rule_t *r)
+{
+       asl_action_file_data_t *f_data;
+       char vt[32], *msg;
+       int len, status;
+       time_t now = time(NULL);
+
+       if (r == NULL) return -1;
+       if (r->dst == NULL) return -1;
+       if (r->dst->private == NULL) return -1;
+
+       f_data = (asl_action_file_data_t *)r->dst->private;
+
+       free(f_data->last_msg);
+       f_data->last_msg = NULL;
+
+       if (f_data->last_count == 0) return 0;
+
+       /* stop the timer */
+       dispatch_suspend(f_data->dup_timer);
+
+       memset(vt, 0, sizeof(vt));
+       ctime_r(&now, vt);
+       vt[19] = '\0';
+
+       msg = NULL;
+       asprintf(&msg, "%s --- last message repeated %u time%s ---\n", vt + 4, f_data->last_count, (f_data->last_count == 1) ? "" : "s");
+       f_data->last_count = 0;
+       f_data->last_time = now;
+       if (msg == NULL) return -1;
+
+       if (f_data->fd < 0) f_data->fd = _act_file_create_open(r->dst);
+
+       len = strlen(msg);
+       status = write(f_data->fd, msg, len);
+       free(msg);
+
+       if ((status < 0) || (status < len))
+       {
+               asldebug("%s: error writing repeat message (%s): %s\n", MY_ID, r->dst->current_name, strerror(errno));
+               return -1;
+       }
+
+       return 0;
+}
+
+static void
+_start_cycling()
+{
+       struct timespec midnight;
+       struct tm t;
+       time_t x;
+
+       x = time(NULL);
+
+       if (checkpoint_timer != NULL) return;
+
+       localtime_r(&x, &t);
+
+       t.tm_sec = 0;
+       t.tm_min = 0;
+       t.tm_hour = 0;
+       t.tm_mday++;
+
+       x = mktime(&t);
+       midnight.tv_sec = x;
+       midnight.tv_nsec = 0;
+
+       checkpoint_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, asl_action_queue);
+       dispatch_source_set_timer(checkpoint_timer, dispatch_walltime(&midnight, 0), NSEC_PER_SEC * SEC_PER_DAY, 0);
+       dispatch_source_set_event_handler(checkpoint_timer, ^{ _act_file_checkpoint_all(CHECKPOINT_FORCE); });
+       dispatch_resume(checkpoint_timer);
+}
+
+/* check if a module path (mpath) matches a user path (upath) */
+static bool
+_act_file_equal(const char *mpath, const char *upath)
+{
+       const char *slash;
+
+       /* NULL upath means user wants to match all files */
+       if (upath == NULL) return true;
+
+       if (mpath == NULL) return false;
+
+       /* check for exact match */
+       if (!strcmp(mpath, upath)) return true;
+
+       /* upath may be the last component of mpath */
+       slash = strrchr(mpath, '/');
+       if (slash == NULL) return false;
+
+       if (!strcmp(slash + 1, upath)) return true;
+       return false;
+}
+
+static int
+_act_file_checkpoint(asl_out_module_t *m, const char *path, uint32_t force)
+{
+       asl_out_rule_t *r;
+       int did_checkpoint = 0;
+
+       if (m == NULL) return 0;
+
+       for (r = m->ruleset; r != NULL; r = r->next)
+       {
+               if ((r->action == ACTION_FILE) || (r->action == ACTION_ASL_FILE))
+               {
+                       if (r->dst->flags & MODULE_FLAG_ROTATE)
+                       {
+                               if (_act_file_equal(r->dst->path, path))
+                               {
+                                       if (_act_checkpoint(r, force) > 0)
+                                       {
+                                               did_checkpoint = 1;
+                                               _act_dst_close(r, DST_CLOSE_CHECKPOINT);
+                                       }
+                               }
+                       }
+               }
+       }
+
+       return did_checkpoint;
+}
+
+static int
+_act_file_checkpoint_all(uint32_t force)
+{
+       asl_out_module_t *m;
+       int did_checkpoint = 0;
+
+       for (m = global.asl_out_module; m != NULL; m = m->next)
+       {
+               if (_act_file_checkpoint(m, NULL, force) > 0) did_checkpoint = 1;
+       }
+
+       asl_trigger_aslmanager();
+
+       return did_checkpoint;
+}
+
+/*
+ * Save a message in a plain text file.
+ */
+static void
+_act_file_final(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
+{
+       asl_action_file_data_t *f_data;
+       int is_dup;
+       uint32_t len, msg_hash = 0;
+       char *str;
+       time_t now;
+
+       if (r == NULL) return;
+       if (r->dst == NULL) return;
+       if (r->dst->private == NULL) return;
+
+       f_data = (asl_action_file_data_t *)r->dst->private;
+       if (f_data->pending > 0) f_data->pending--;
+
+       /*
+        * If print format is std, bsd, or msg, then skip messages with
+        * no ASL_KEY_MSG, or without a value for it.
+        */
+       if (r->dst->flags & MODULE_FLAG_STD_BSD_MSG)
+       {
+               const char *msgval = NULL;
+               if (asl_msg_lookup(msg, ASL_KEY_MSG, &msgval, NULL) != 0) return;
+               if (msgval == NULL) return;
+       }
+
+       now = time(NULL);
+
+       is_dup = 0;
+
+       str = asl_format_message(msg, r->dst->fmt, r->dst->tfmt, ASL_ENCODE_SAFE, &len);
+
+       if (r->dst->flags & MODULE_FLAG_COALESCE)
+       {
+               if (f_data->dup_timer == NULL)
+               {
+                       /* create a timer to flush dups on this file */
+                       f_data->dup_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, asl_action_queue);
+                       dispatch_source_set_event_handler(f_data->dup_timer, ^{ _send_repeat_msg(r); });
+               }
+
+               if ((global.bsd_max_dup_time > 0) && (str != NULL) && (f_data->last_msg != NULL))
+               {
+                       msg_hash = asl_core_string_hash(str + 16, len - 16);
+                       if ((f_data->last_hash == msg_hash) && (!strcmp(f_data->last_msg, str + 16)))
+                       {
+                               if ((now - f_data->last_time) < global.bsd_max_dup_time) is_dup = 1;
+                       }
+               }
+       }
+
+       if (is_dup == 1)
+       {
+               if (f_data->last_count == 0)
+               {
+                       /* start the timer */
+                       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);
+                       dispatch_resume(f_data->dup_timer);
+               }
+
+               f_data->last_count++;
+       }
+       else
+       {
+               if (_act_dst_open(r, NULL, 0) != 0)
+               {
+                       _asl_action_save_failed("_act_file", m, r, ASL_STATUS_FAILED);
+                       free(str);
+                       return;
+               }
+               else
+               {
+                       r->dst->fails = 0;
+               }
+
+               /*
+                * The current message is not a duplicate.  If f_data->last_count > 0
+                * we need to write a "last message repeated N times" log entry.
+                * _send_repeat_msg will free last_msg and do nothing if
+                * last_count == 0, but we test and free here to avoid a function call.
+                */
+               if (f_data->last_count > 0)
+               {
+                       _send_repeat_msg(r);
+               }
+               else
+               {
+                       free(f_data->last_msg);
+                       f_data->last_msg = NULL;
+               }
+
+               if (str != NULL) f_data->last_msg = strdup(str + 16);
+
+               f_data->last_hash = msg_hash;
+               f_data->last_count = 0;
+               f_data->last_time = now;
+
+               if ((str != NULL) && (len > 1))
+               {
+                       /* write line to file and update dst size */
+                       size_t bytes = write(f_data->fd, str, len - 1);
+                       if (bytes > 0) r->dst->size += bytes;
+
+                       if (_act_checkpoint(r, CHECKPOINT_TEST) == 1) asl_trigger_aslmanager();
+               }
+       }
+
+       free(str);
+}
+
+static void
+_act_file(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
+{
+       asl_action_file_data_t *f_data;
+
+       if (r == NULL) return;
+       if (msg == NULL) return;
+       if (m == NULL) return;
+       if ((m->flags & MODULE_FLAG_ENABLED) == 0) return;
+       if (r->dst == NULL) return;
+       if (r->dst->private == NULL) return;
+
+       if (r->dst->flags & MODULE_FLAG_HAS_LOGGED) return;
+
+       r->dst->flags |= MODULE_FLAG_HAS_LOGGED;
+       f_data = (asl_action_file_data_t *)r->dst->private;
+       if (f_data != NULL) f_data->pending++;
+
+#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
+       if (r->dst->flags & MODULE_FLAG_CRASHLOG)
+       {
+               _crashlog_queue_check();
+               asl_msg_retain(msg);
+               dispatch_async(crashlog_queue, ^{
+                       dispatch_async(asl_action_queue, ^{
+                               _act_file_final(m, r, msg);
+                               asl_msg_release(msg);
+                       });
+               });
+               return;
+       }
+#endif
+
+       _act_file_final(m, r, msg);
+}
+
+static void
+_act_forward(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
+{
+       /* To do: <rdar://problem/6130747> Add a "forward" action to asl.conf */
+}
+
+static void
+_act_control(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg)
+{
+       const char *p;
+
+       if (m == NULL) return;
+       if (r == NULL) return;
+
+       p = asl_msg_get_val_for_key(msg, ASL_KEY_MODULE);
+
+       if (r->options == NULL) return;
+
+       if (!strcmp(r->options, "enable"))
+       {
+               m->flags |= MODULE_FLAG_ENABLED;
+       }
+       else if (!strcmp(r->options, "disable"))
+       {
+               m->flags &= ~MODULE_FLAG_ENABLED;
+       }
+       else if ((!strcmp(r->options, "checkpoint")) || (!strcmp(r->options, "rotate")))
+       {
+               _act_file_checkpoint(m, NULL, CHECKPOINT_FORCE);
+       }
+}
+
+static void
+_send_to_asl_store(asl_msg_t *msg)
+{
+       if ((global.asl_out_module != NULL) && ((global.asl_out_module->flags & MODULE_FLAG_ENABLED) == 0)) return;
+
+       if (store_has_logged) return;
+       store_has_logged = true;
+
+       db_save_message(msg);
+}
+
+static int
+_asl_out_process_message(asl_out_module_t *m, asl_msg_t *msg)
+{
+       asl_out_rule_t *r;
+
+       if (m == NULL) return 1;
+       if (msg == NULL) return 1;
+
+       /* reset flag bit used for duplicate avoidance */
+       for (r = m->ruleset; r != NULL; r = r->next)
+       {
+               if ((r->action == ACTION_FILE) || (r->action == ACTION_ASL_DIR) || (r->action == ACTION_ASL_FILE))
+               {
+                       if (r->dst != NULL) r->dst->flags &= MODULE_FLAG_CLEAR_LOGGED;
+               }
+       }
+
+       for (r = m->ruleset; r != NULL; r = r->next)
+       {
+               if (r->query == NULL) continue;
+
+               /* ACTION_SET_FILE, ACTION_SET_PLIST, and ACTION_SET_PROF are handled independently  */
+               if ((r->action == ACTION_SET_FILE) || (r->action == ACTION_SET_PLIST) || (r->action == ACTION_SET_PROF)) continue;
+
+               /*
+                * ACTION_CLAIM during processing is a filter.  It will only be here if the option "only"
+                * was supplied.  In this case we test the message against the query.  If it does not
+                * match, we skip the message.
+                */
+               if (r->action == ACTION_CLAIM)
+               {
+                       if ((asl_msg_cmp(r->query, msg) != 1)) return 0;
+               }
+
+               if ((asl_msg_cmp(r->query, msg) == 1))
+               {
+                       if (r->action == ACTION_NONE) continue;
+                       else if (r->action == ACTION_IGNORE) return 1;
+                       else if (r->action == ACTION_SKIP) return 0;
+                       else if (r->action == ACTION_ASL_STORE) _send_to_asl_store(msg);
+                       else if (r->action == ACTION_ACCESS) _act_access_control(m, r, msg);
+                       else if (r->action == ACTION_SET_KEY) _act_set_key(m, r, msg);
+                       else if (r->action == ACTION_UNSET_KEY) _act_unset_key(m, r, msg);
+                       else if (r->action == ACTION_NOTIFY) _act_notify(m, r);
+                       else if (r->action == ACTION_BROADCAST) _act_broadcast(m, r, msg);
+                       else if (r->action == ACTION_FORWARD) _act_forward(m, r, msg);
+                       else if (r->action == ACTION_CONTROL) _act_control(m, r, msg);
+                       else if (r->action == ACTION_SET_PARAM) _act_out_set_param(m, r->options, true);
+                       else if ((r->action == ACTION_ASL_FILE) || (r->action == ACTION_ASL_DIR)) _act_store(m, r, msg);
+                       else if (r->action == ACTION_FILE) _act_file(m, r, msg);
+               }
+       }
+
+       return 0;
+}
+
+void
+asl_out_message(asl_msg_t *msg, int64_t msize)
+{
+       OSAtomicIncrement32(&global.asl_queue_count);
+       asl_msg_retain(msg);
+
+       dispatch_async(asl_action_queue, ^{
+               int ignore = 0;
+               const char *p;
+               time_t now = time(NULL);
+               asl_out_module_t *m = global.asl_out_module;
+
+               store_has_logged = false;
+
+               p = asl_msg_get_val_for_key(msg, ASL_KEY_MODULE);
+               if (p == NULL)
+               {
+                       if ((action_asl_store_count == 0) || (asl_check_option(msg, ASL_OPT_STORE) == 1)) _send_to_asl_store(msg);
+
+                       ignore = _asl_out_process_message(m, msg);
+                       if (ignore == 0)
+                       {
+                               if (m != NULL) m = m->next;
+                               while (m != NULL)
+                               {
+                                       _asl_out_process_message(m, msg);
+                                       m = m->next;
+                               }
+                       }
+               }
+               else 
+               {
+                       if (m != NULL) m = m->next;
+                       while (m != NULL)
+                       {
+                               if (!strcmp(p, m->name)) _asl_out_process_message(m, msg);
+                               m = m->next;
+                       }
+               }
+
+               p = asl_msg_get_val_for_key(msg, ASL_KEY_FINAL_NOTIFICATION);
+               if (p != NULL) asl_msg_set_key_val(msg, ASL_KEY_FREE_NOTE, p);
+
+               /* chain to the next output module (done this way to make queue size accounting easier */
+#if !TARGET_OS_SIMULATOR
+               if (global.bsd_out_enabled) bsd_out_message(msg, msize);
+               else OSAtomicAdd64(-1ll * msize, &global.memory_size);
+#else
+               OSAtomicAdd64(-1ll * msize, &global.memory_size);
+#endif
+
+               asl_msg_release(msg);
+               OSAtomicDecrement32(&global.asl_queue_count);
+
+               if ((now - sweep_time) >= IDLE_CLOSE)
+               {
+                       _asl_action_close_idle_files(IDLE_CLOSE);
+                       sweep_time = now;
+               }
+       });
+}
+
+static char *
+_asl_action_profile_test(asl_out_module_t *m, asl_out_rule_t *r)
+{
+       const char *ident;
+       asl_msg_t *profile;
+       bool eval;
+
+       /* ident is first message key */
+       asl_msg_fetch(r->query, 0, &ident, NULL, NULL);
+       if (ident == NULL)
+       {
+               r->action = ACTION_NONE;
+               return NULL;
+       }
+
+       profile = configuration_profile_to_asl_msg(ident);
+       eval = (asl_msg_cmp(r->query, profile) == 1);
+       _act_out_set_param(m, r->options, eval);
+       asl_msg_release(profile);
+
+       return strdup(ident);
+}
+
+static const char *
+_asl_action_file_test(asl_out_module_t *m, asl_out_rule_t *r)
+{
+       const char *path;
+       struct stat sb;
+       int status;
+       bool eval;
+
+       /* path is first message key */
+       asl_msg_fetch(r->query, 0, &path, NULL, NULL);
+       if (path == NULL)
+       {
+               r->action = ACTION_NONE;
+               return NULL;
+       }
+
+       memset(&sb, 0, sizeof(struct stat));
+       status = stat(path, &sb);
+       eval = (status == 0);
+       _act_out_set_param(m, r->options, eval);
+
+       return path;
+}
+
+static void
+_asl_action_handle_file_change_notification(int t)
+{
+       asl_out_module_t *m;
+       asl_out_rule_t *r;
+
+       for (m = global.asl_out_module; m != NULL; m = m->next)
+       {
+               for (r = m->ruleset; r != NULL; r = r->next)
+               {
+                       if (r->action == ACTION_SET_FILE)
+                       {
+                               asl_action_set_param_data_t *spdata = (asl_action_set_param_data_t *)r->private;
+                               if ((spdata != NULL) && (spdata->token == t))
+                               {
+                                       _asl_action_file_test(m, r);
+                                       return;
+                               }
+                       }
+                       else if (r->action == ACTION_SET_PLIST)
+                       {
+                               asl_action_set_param_data_t *spdata = (asl_action_set_param_data_t *)r->private;
+                               if ((spdata != NULL) && (spdata->token == t))
+                               {
+                                       char *str = _asl_action_profile_test(m, r);
+                                       free(str);
+                                       return;
+                               }
+                       }
+                       else if (r->action == ACTION_SET_PROF)
+                       {
+                               asl_action_set_param_data_t *spdata = (asl_action_set_param_data_t *)r->private;
+                               if ((spdata != NULL) && (spdata->token == t))
+                               {
+                                       char *str = _asl_action_profile_test(m, r);
+                                       free(str);
+                                       return;
+                               }
+                       }
+               }
+       }
+
+       asl_out_module_free(m);
+}
+
+static void
+_asl_action_post_process_rule(asl_out_module_t *m, asl_out_rule_t *r)
+{
+       if ((m == NULL) || (r == NULL)) return;
+
+       if (m != global.asl_out_module)
+       {
+               /* check if any previous module has used this destination */
+               asl_out_module_t *n;
+               bool search = true;
+
+               if ((r->dst != NULL) && (r->dst->path != NULL))
+               {
+                       for (n = global.asl_out_module; search && (n != NULL) && (n != m); n = n->next)
+                       {
+                               asl_out_rule_t *s;
+                               for (s = n->ruleset; search && (s != NULL); s = s->next)
+                               {
+                                       if (s->action == ACTION_OUT_DEST)
+                                       {
+                                               if ((s->dst != NULL) && (s->dst->path != NULL) && (!strcmp(r->dst->path, s->dst->path)))
+                                               {
+                                                       /* rule r of module m is using previously used dst of rule s of module n */
+                                                       asl_out_dst_data_release(r->dst);
+                                                       r->dst = NULL;
+
+                                                       if (r->action == ACTION_OUT_DEST)
+                                                       {
+                                                               char *str = NULL;
+                                                               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);
+                                                               internal_log_message(str);
+                                                               free(str);
+                                                       }
+                                                       else
+                                                       {
+                                                               r->dst = asl_out_dst_data_retain(s->dst);
+                                                       }
+
+                                                       search = false;
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       if (r->action == ACTION_SET_PARAM)
+       {
+               if (r->query == NULL) _act_out_set_param(m, r->options, true);
+       }
+       else if (r->action == ACTION_CLAIM)
+       {
+               /* becomes ACTION_SKIP in com.apple.asl config */
+               if (m != global.asl_out_module)
+               {
+                       asl_out_rule_t *rule = (asl_out_rule_t *)calloc(1, sizeof(asl_out_rule_t));
+                       if (rule != NULL)
+                       {
+                               char *str = NULL;
+                               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);
+                               internal_log_message(str);
+                               free(str);
+
+                               rule->query = asl_msg_copy(r->query);
+                               rule->action = ACTION_SKIP;
+                               rule->next = global.asl_out_module->ruleset;
+                               global.asl_out_module->ruleset = rule;
+                       }
+
+                       /*
+                        * After adding ACTION_SKIP to com.apple.asl module, the claim becomes a no-op in this module
+                        * UNLESS the claim includes the option "only".  In that case, the claim becomes a filter:
+                        * any messages that DO NOT match the claim are skipped by this module.
+                        */
+                       if (r->options == NULL) r->action = ACTION_NONE;
+                       else if (strcmp(r->options, "only") != 0) r->action = ACTION_NONE;
+               }
+       }
+       else if (r->action == ACTION_ASL_STORE)
+       {
+               action_asl_store_count++;
+       }
+       else if (r->action == ACTION_ASL_DIR)
+       {
+               if (r->dst->private == NULL) r->dst->private = (asl_action_asl_store_data_t *)calloc(1, sizeof(asl_action_asl_store_data_t));
+       }
+       else if (r->action == ACTION_ASL_FILE)
+       {
+               if (r->dst->private == NULL)r->dst->private = (asl_action_asl_file_data_t *)calloc(1, sizeof(asl_action_asl_file_data_t));
+       }
+       else if (r->action == ACTION_FILE)
+       {
+               if (r->dst->private == NULL) r->dst->private = (asl_action_file_data_t *)calloc(1, sizeof(asl_action_file_data_t));
+               if (r->dst->private != NULL) ((asl_action_file_data_t *)(r->dst->private))->fd = -1;
+       }
+       else if (r->action == ACTION_SET_PLIST)
+       {
+               char *ident =_asl_action_profile_test(m, r);
+               char *notify_key = configuration_profile_create_notification_key(ident);
+               free(ident);
+
+               if (notify_key != NULL)
+               {
+                       int status, token;
+                       asl_action_set_param_data_t *spdata;
+
+                       status = notify_register_dispatch(notify_key, &token, asl_action_queue, ^(int t){
+                               _asl_action_handle_file_change_notification(t);
+                       });
+
+                       free(notify_key);
+
+                       spdata = (asl_action_set_param_data_t *)calloc(1, sizeof(asl_action_set_param_data_t));
+                       if (spdata == NULL)
+                       {
+                               notify_cancel(token);
+                       }
+                       else
+                       {
+                               spdata->token = token;
+                               r->private = spdata;
+                       }
+               }
+       }
+       else if (r->action == ACTION_SET_PROF)
+       {
+               char *ident =_asl_action_profile_test(m, r);
+               char *notify_key = configuration_profile_create_notification_key(ident);
+               free(ident);
+
+               if (notify_key != NULL)
+               {
+                       int status, token;
+                       asl_action_set_param_data_t *spdata;
+
+                       status = notify_register_dispatch(notify_key, &token, asl_action_queue, ^(int t){
+                               _asl_action_handle_file_change_notification(t);
+                       });
+
+                       free(notify_key);
+
+                       spdata = (asl_action_set_param_data_t *)calloc(1, sizeof(asl_action_set_param_data_t));
+                       if (spdata == NULL)
+                       {
+                               notify_cancel(token);
+                       }
+                       else
+                       {
+                               spdata->token = token;
+                               r->private = spdata;
+                       }
+               }
+       }
+       else if (r->action == ACTION_SET_FILE)
+       {
+               char *notify_key;
+               const char *path =_asl_action_file_test(m, r);
+
+               if (path != NULL)
+               {
+                       asprintf(&notify_key, "%s%s", NOTIFY_PATH_SERVICE, path);
+                       if (notify_key != NULL)
+                       {
+                               int status, token;
+                               asl_action_set_param_data_t *spdata;
+
+                               status = notify_register_dispatch(notify_key, &token, asl_action_queue, ^(int t){
+                                       _asl_action_handle_file_change_notification(t);
+                               });
+
+                               free(notify_key);
+
+                               spdata = (asl_action_set_param_data_t *)calloc(1, sizeof(asl_action_set_param_data_t));
+                               if (spdata == NULL)
+                               {
+                                       notify_cancel(token);
+                               }
+                               else
+                               {
+                                       spdata->token = token;
+                                       r->private = spdata;
+                               }
+                       }
+               }
+       }
+}
+
+static void
+_asl_action_configure()
+{
+       asl_out_rule_t *r;
+       asl_out_module_t *m;
+       uint32_t flags = 0;
+
+       if (global.asl_out_module == NULL) global.asl_out_module = asl_out_module_init();
+       if (global.asl_out_module == NULL) return;
+
+       asldebug("%s: init\n", MY_ID);
+
+       action_asl_store_count = 0;
+
+       for (m = global.asl_out_module; m != NULL; m = m->next)
+       {
+               for (r = m->ruleset; r != NULL; r = r->next)
+               {
+                       _asl_action_post_process_rule(m, r);
+                       if (r->dst != NULL) flags |= (r->dst->flags & (MODULE_FLAG_ROTATE | MODULE_FLAG_CRASHLOG));
+               }
+       }
+
+       if (global.debug != 0)
+       {
+               FILE *dfp;
+               if (global.debug_file == NULL) dfp = fopen(_PATH_SYSLOGD_LOG, "a");
+               else dfp = fopen(global.debug_file, "a");
+               if (dfp != NULL)
+               {
+                       for (m = global.asl_out_module; m != NULL; m = m->next)
+                       {
+                               fprintf(dfp, "module: %s%s\n", (m->name == NULL) ? "<unknown>" : m->name, (m->flags & MODULE_FLAG_LOCAL) ? " (local)" : "");
+                               asl_out_module_print(dfp, m);
+                               fprintf(dfp, "\n");
+                       }
+                       fclose(dfp);
+               }
+       }
+
+       sweep_time = time(NULL);
+
+       if (flags & MODULE_FLAG_ROTATE)
+       {
+               _act_file_checkpoint_all(CHECKPOINT_TEST);
+               if (checkpoint_timer == NULL) _start_cycling();
+       }
+}
+
+int
+asl_action_init(void)
+{
+       static dispatch_once_t once;
+
+       dispatch_once(&once, ^{
+               asl_action_queue = dispatch_queue_create("ASL Action Queue", NULL);
+#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
+               crashlog_queue = dispatch_queue_create("iOS CrashLog Queue", NULL);
+               notify_register_dispatch(CRASH_MOVER_SERVICE, &crashmover_token, asl_action_queue, ^(int unused) {
+                       uint64_t cmstate = 0;
+                       uint64_t oldstate = (crashmover_state == 0) ? 0llu : 1llu;
+
+                       uint32_t status = notify_get_state(crashmover_token, &cmstate);
+                       if (status == 0)
+                       {
+                               if (cmstate != oldstate)
+                               {
+                                       crashmover_state = 0;
+                                       if (cmstate == 1) crashmover_state = time(NULL);
+
+                                       if (crashmover_state == 0)
+                                       {
+                                               asldebug("CrashMover finished\n");
+                                               dispatch_resume(crashlog_queue);
+                                       }
+                                       else
+                                       {
+                                               asldebug("CrashMover active: suspending crashlog queue and closing files\n");
+                                               dispatch_suspend(crashlog_queue);
+                                               _asl_action_close_idle_files(0);
+                                       }
+                               }
+                       }
+               });
+#endif
+       });
+
+       _asl_action_configure();
+
+       return 0;
+}
+
+/*
+ * Close outputs and free modules.
+ */
+static void
+_asl_action_free_modules(asl_out_module_t *m)
+{
+       asl_out_rule_t *r;
+       asl_out_module_t *x;
+
+       /*
+        * asl_common frees a list of modules with asl_out_module_free.
+        * This loop frees the private data attached some modules.
+        */
+       for (x = m; x != NULL; x = x->next)
+       {
+               for (r = x->ruleset; r != NULL; r = r->next)
+               {
+                       if (r->action == ACTION_ASL_DIR)
+                       {
+                               _act_dst_close(r, DST_CLOSE_SHUTDOWN);
+                               if (r->dst != NULL)
+                               {
+                                       _asl_action_asl_store_data_free((asl_action_asl_store_data_t *)r->dst->private);
+                                       r->dst->private = NULL;
+                               }
+                       }
+                       else if (r->action == ACTION_ASL_FILE)
+                       {
+                               _act_dst_close(r, DST_CLOSE_SHUTDOWN);
+                               if (r->dst != NULL)
+                               {
+                                       _asl_action_asl_file_data_free((asl_action_asl_file_data_t *)r->dst->private);
+                                       r->dst->private = NULL;
+                               }
+                       }
+                       else if (r->action == ACTION_FILE)
+                       {
+                               _act_dst_close(r, DST_CLOSE_SHUTDOWN);
+                               if (r->dst != NULL)
+                               {
+                                       asl_action_file_data_t *f_data = (asl_action_file_data_t *)r->dst->private;
+                                       if (f_data != NULL)
+                                       {
+                                               /* flush repeat message if necessary */
+                                               if (f_data->last_count > 0) _send_repeat_msg(r);
+                                               _asl_action_file_data_free(f_data);
+                                               r->dst->private = NULL;
+                                       }
+                               }
+                       }
+                       else if (r->action == ACTION_SET_PLIST)
+                       {
+                               _asl_action_set_param_data_free((asl_action_set_param_data_t *)r->private);
+                       }
+                       else if (r->action == ACTION_SET_PROF)
+                       {
+                               _asl_action_set_param_data_free((asl_action_set_param_data_t *)r->private);
+                       }
+                       else if (r->action == ACTION_SET_FILE)
+                       {
+                               _asl_action_set_param_data_free((asl_action_set_param_data_t *)r->private);
+                       }
+               }
+       }
+
+       asl_out_module_free(m);
+}
+
+static int
+_asl_action_close_internal(void)
+{
+#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
+       if (crashmover_state != 0)
+       {
+               dispatch_resume(crashlog_queue);
+               crashmover_state = 0;
+       }
+
+       /* wait for the crashlog_queue to flush before _asl_action_free_modules() */
+       dispatch_sync(crashlog_queue, ^{ int x = 0; if (x == 1) x = 2; });
+#endif
+
+       _asl_action_free_modules(global.asl_out_module);
+       global.asl_out_module = NULL;
+       sweep_time = time(NULL);
+
+       return 0;
+}
+
+static void
+_asl_action_close_idle_files(time_t idle_time)
+{
+       asl_out_module_t *m;
+       time_t now = time(NULL);
+
+       for (m = global.asl_out_module; m != NULL; m = m->next)
+       {
+               asl_out_rule_t *r;
+
+               for (r = m->ruleset; r != NULL; r = r->next)
+               {
+                       if (idle_time == 0)
+                       {
+                               if ((r->dst != NULL) && (r->dst->flags & MODULE_FLAG_CRASHLOG))
+                               {
+                                       _act_dst_close(r, DST_CLOSE_IDLE);
+                                       //TODO: can r->action even be ACTION_ASL_DIR?
+                                       /* if not, we can avoid the extra check here */
+                                       if (r->action != ACTION_ASL_DIR) _act_checkpoint(r, CHECKPOINT_FORCE);
+                               }
+                       }
+                       else if (r->action == ACTION_ASL_DIR)
+                       {
+                               if (r->dst != NULL)
+                               {
+                                       asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private;
+                                       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);
+                               }
+                       }
+                       else if (r->action == ACTION_ASL_FILE)
+                       {
+                               if (r->dst != NULL)
+                               {
+                                       asl_action_asl_file_data_t *af_data = (asl_action_asl_file_data_t *)r->dst->private;
+                                       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);
+                               }
+                       }
+                       else if (r->action == ACTION_FILE)
+                       {
+                               if (r->dst != NULL)
+                               {
+                                       asl_action_file_data_t *f_data = (asl_action_file_data_t *)r->dst->private;
+                                       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);
+                               }
+                       }
+               }
+       }
+}
+
+int
+asl_action_close(void)
+{
+       dispatch_async(asl_action_queue, ^{
+               _asl_action_close_internal();
+       });
+
+       return 0;
+}
+
+int
+asl_action_reset(void)
+{
+       dispatch_async(asl_action_queue, ^{
+               _asl_action_close_internal();
+               asl_action_init();
+       });
+
+       return 0;
+}
+
+asl_out_module_t *
+_asl_action_module_with_name(const char *name)
+{
+       asl_out_module_t *m;
+
+       if (global.asl_out_module == NULL) return NULL;
+       if (name == NULL) return global.asl_out_module;
+
+       for (m = global.asl_out_module; m != NULL; m = m->next)
+       {
+               if ((m->name != NULL) && (!strcmp(m->name, name))) return m;
+       }
+
+       return NULL;
+}
+
+/*
+ * called from control_message 
+ * Used to control modules dynamically.
+ * Line format "@ module param [value ...]"
+ *
+ * Note this is synchronous on asl_action queue.
+ */
+int
+asl_action_control_set_param(const char *s)
+{
+       __block char **l;
+       uint32_t count = 0;
+
+       if (s == NULL) return -1;
+       if (s[0] == '\0') return 0;
+
+       /* skip '@' and whitespace */
+       if (*s == '@') s++;
+       while ((*s == ' ') || (*s == '\t')) s++;
+
+       l = explode(s, " \t");
+       if (l != NULL) for (count = 0; l[count] != NULL; count++);
+
+       /* at least 2 parameters (l[0] = module, l[1] = param) required */
+       if (count < 2)
+       {
+               free_string_list(l);
+               return -1;
+       }
+
+       if (global.asl_out_module == NULL)
+       {
+               asldebug("asl_action_control_set_param: no modules loaded\n");
+               free_string_list(l);
+               return -1;
+       }
+
+       /* create / modify a module */
+       if ((!strcasecmp(l[1], "define")) && (strcmp(l[0], "*")))
+       {
+               char *str = strdup(s);
+               if (str == NULL)
+               {
+                       asldebug("asl_action_control_set_param: memory allocation failed\n");
+                       free_string_list(l);
+                       return -1;
+               }
+
+               dispatch_sync(asl_action_queue, ^{
+                       asl_out_module_t *m;
+                       asl_out_rule_t *r;
+                       char *p = str;
+
+                       /* skip name, whitespace, "define" */
+                       while ((*p != ' ') && (*p != '\t')) p++;
+                       while ((*p == ' ') || (*p == '\t')) p++;
+                       while ((*p != ' ') && (*p != '\t')) p++;
+
+                       m = _asl_action_module_with_name(l[0]);
+                       if (m == NULL)
+                       {
+                               asl_out_module_t *x;
+
+                               m = asl_out_module_new(l[0]);
+                               for (x = global.asl_out_module; x->next != NULL; x = x->next);
+                               x->next = m;
+                       }
+
+                       r = asl_out_module_parse_line(m, p);
+                       if (r != NULL)
+                       {
+                               _asl_action_post_process_rule(m, r);
+                               if ((r->dst != NULL) && (r->dst->flags & MODULE_FLAG_ROTATE)) 
+                               {
+                                       _act_file_checkpoint_all(CHECKPOINT_TEST);
+                                       if (checkpoint_timer == NULL) _start_cycling();
+                               }
+                       }
+               });
+
+               free(str);
+               free_string_list(l);
+               return 0;
+       }
+
+       dispatch_sync(asl_action_queue, ^{
+               uint32_t intval;
+               int do_all = 0;
+               asl_out_module_t *m;
+
+               if (!strcmp(l[0], "*"))
+               {
+                       do_all = 1;
+                       m = _asl_action_module_with_name(NULL);
+               }
+               else
+               {
+                       m = _asl_action_module_with_name(l[0]);
+               }
+
+               while (m != NULL) 
+               {
+                       if (!strcasecmp(l[1], "enable"))
+                       {
+                               intval = 1;
+
+                               /* don't do enable for ASL_MODULE_NAME if input name is "*" */
+                               if ((do_all == 0) || (strcmp(m->name, ASL_MODULE_NAME)))
+                               {
+                                       /* @ module enable {0|1} */
+                                       if (count > 2) intval = atoi(l[2]);
+
+                                       if (intval == 0) m->flags &= ~MODULE_FLAG_ENABLED;
+                                       else m->flags |= MODULE_FLAG_ENABLED;
+                               }
+                       }
+                       else if (!strcasecmp(l[1], "checkpoint"))
+                       {
+                               /* @ module checkpoint [file] */
+                               if (count > 2) _act_file_checkpoint(m, l[2], CHECKPOINT_FORCE);
+                               else _act_file_checkpoint(m, NULL, CHECKPOINT_FORCE);
+                       }
+
+                       if (do_all == 1) m = m->next;
+                       else m = NULL;
+               }
+
+       });
+
+       free_string_list(l);
+       return 0;
+}
+
+void
+asl_action_out_module_query(asl_msg_t *q, asl_msg_t *m, bool all)
+{
+       dispatch_sync(asl_action_queue, ^{
+               asl_out_module_t *om;
+               const char *val;
+               for (om = global.asl_out_module; om != NULL; om = om->next)
+               {
+                       if (all || (0 == asl_msg_lookup(q, om->name, NULL, NULL)))
+                       {
+                               val = om->flags & MODULE_FLAG_ENABLED ? "enabled" : "disabled";
+                               if (om->name == NULL) asl_msg_set_key_val(m, "asl.conf", val);
+                               else asl_msg_set_key_val(m, om->name, val);
+                       }
+               }
+       });
 }