]> git.saurik.com Git - apple/syslog.git/blobdiff - syslogd.tproj/asl_action.c
syslog-148.7.tar.gz
[apple/syslog.git] / syslogd.tproj / asl_action.c
index 699413f92bbbaf03ab13639d9827892acfe9d70e..2848271154d14250b5bed503fc8bea1fc71c4378 100644 (file)
@@ -1,15 +1,15 @@
 /*
 /*
- * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2004-2011 Apple Inc. All rights reserved.
  *
  * @APPLE_LICENSE_HEADER_START@
  *
  * @APPLE_LICENSE_HEADER_START@
- * 
+ *
  * 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.
  * 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,
  * 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,
@@ -17,7 +17,7 @@
  * 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.
  * 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@
  */
 
  * @APPLE_LICENSE_HEADER_END@
  */
 
 #include <errno.h>
 #include <netdb.h>
 #include <notify.h>
 #include <errno.h>
 #include <netdb.h>
 #include <notify.h>
-#include <asl_store.h>
+#include <pthread.h>
+#include <sys/acl.h>
+#include <membership.h>
 #include "daemon.h"
 #include "daemon.h"
+#include <dispatch/private.h>
 
 
+#define _PATH_WALL "/usr/bin/wall"
 #define _PATH_ASL_CONF "/etc/asl.conf"
 #define MY_ID "asl_action"
 
 #define _PATH_ASL_CONF "/etc/asl.conf"
 #define MY_ID "asl_action"
 
-#define ASL_KEY_FACILITY "Facility"
-#define IndexNull ((uint32_t)-1)
+#define MAX_FAILURES 5
+
+#define ACTION_NONE      0
+#define ACTION_IGNORE    1
+#define ACTION_NOTIFY    2
+#define ACTION_BROADCAST 3
+#define ACTION_ACCESS    4
+#define ACTION_ASL_STORE 5 /* Save in main ASL Database */
+#define ACTION_ASL_FILE  6 /* Save in an ASL format data file */
+#define ACTION_ASL_DIR   7 /* Save in an ASL directory */
+#define ACTION_FILE      8
+#define ACTION_FORWARD   9
+
 #define forever for(;;)
 
 #define forever for(;;)
 
-#define ACT_STORE_FLAG_STAY_OPEN     0x00000001
-#define ACT_STORE_FLAG_EXCLUDE_ASLDB 0x00000002
+#define ACT_FLAG_HAS_LOGGED   0x80000000
+#define ACT_FLAG_CLEAR_LOGGED 0x7fffffff
+
+#define ACT_STORE_FLAG_STAY_OPEN 0x00000001
+#define ACT_STORE_FLAG_CONTINUE  0x00000002
 
 
-static asl_msg_t *query = NULL;
-static int reset = 0;
+#define ACT_FILE_FLAG_DUP_SUPRESS 0x00000001
+#define ACT_FILE_FLAG_ROTATE      0x00000002
 
 
-struct action_rule
+static dispatch_queue_t asl_action_queue;
+static time_t last_file_day;
+
+typedef struct action_rule_s
 {
        asl_msg_t *query;
 {
        asl_msg_t *query;
-       char *action;
+       int action;
        char *options;
        void *data;
        char *options;
        void *data;
-       TAILQ_ENTRY(action_rule) entries;
-};
+       struct action_rule_s *next;
+} action_rule_t;
 
 struct store_data
 {
 
 struct store_data
 {
-       asl_store_t *store;
+       asl_file_t *store;
+       FILE *storedata;
+       char *dir;
        char *path;
        char *path;
+       mode_t mode;
+       uid_t uid;
+       gid_t gid;
+       uint64_t next_id;
+       uint32_t fails;
        uint32_t flags;
        uint32_t flags;
+       uint32_t refcount;
+       uint32_t p_year;
+       uint32_t p_month;
+       uint32_t p_day;
 };
 
 };
 
-static TAILQ_HEAD(cr, action_rule) asl_action_rule;
+struct file_data
+{
+       int fd;
+       char *path;
+       char *fmt;
+       const char *tfmt;
+       mode_t mode;
+       uid_t *uid;
+       uint32_t nuid;
+       gid_t *gid;
+       uint32_t ngid;
+       size_t max_size;
+       uint32_t fails;
+       uint32_t flags;
+       uint32_t refcount;
+       time_t stamp;
+       uint32_t last_hash;
+       uint32_t last_count;
+       time_t last_time;
+       dispatch_source_t dup_timer;
+       char *last_msg;
+};
 
 
-int asl_action_close();
-static int _parse_notify_file(const char *);
+static action_rule_t *asl_action_rule = NULL;
+static action_rule_t *asl_datastore_rule = NULL;
 
 
-static void
-_do_reset(void)
+static int _parse_config_file(const char *);
+extern void db_save_message(aslmsg m);
+
+/* forward */
+int _act_file_open(struct file_data *fdata);
+static void _act_file_init(action_rule_t *r);
+static void _act_store_init(action_rule_t *r);
+
+static char *
+_next_word(char **s)
 {
 {
-       asl_action_close();
-       _parse_notify_file(_PATH_ASL_CONF);
+       char *a, *p, *e, *out;
+       int quote, len;
+
+       if (s == NULL) return NULL;
+       if (*s == NULL) return NULL;
+
+       quote = 0;
+
+       p = *s;
+       a = p;
+       e = p;
+
+       while (*p != '\0')
+       {
+               if (*p == '\\')
+               {
+                       p++;
+                       e = p;
+
+                       if (*p == '\0')
+                       {
+                               p--;
+                               break;
+                       }
+
+                       p++;
+                       e = p;
+                       continue;
+               }
+
+               if (*p == '"')
+               {
+                       if (quote == 0) quote = 1;
+                       else quote = 0;
+               }
+
+               if (((*p == ' ') || (*p == '\t')) && (quote == 0))
+               {
+                       e = p + 1;
+                       break;
+               }
+
+               p++;
+               e = p;
+       }
+
+       *s = e;
+
+       len = p - a;
+       if (len == 0) return NULL;
+
+       out = malloc(len + 1);
+       if (out == NULL) return NULL;
+
+       memcpy(out, a, len);
+       out[len] = '\0';
+       return out;
 }
 
 /*
  * Config File format:
 }
 
 /*
  * Config File format:
- * Q [k v] [k v] ... action args...
+ * Set parameter rule - initializes a parameter.
+ *             = param args...
+ * Query rule - if a message matches the query, then the action is invoked.
+ * The rule may be identified by either "?" or "Q".
+ *             ? [k v] [k v] ... action args...
+ *             Q [k v] [k v] ... action args...
+ * Universal match rule - the action is invoked for all messages
+ *             * action args...
  */
 
 /* Skip over query */
  */
 
 /* Skip over query */
@@ -91,7 +214,7 @@ _find_action(char *s)
 
        p = s;
        if (p == NULL) return NULL;
 
        p = s;
        if (p == NULL) return NULL;
-       if (*p != 'Q') return NULL;
+       if ((*p != 'Q') && (*p != '?') && (*p != '*')) return NULL;
 
        p++;
 
 
        p++;
 
@@ -121,30 +244,31 @@ _find_action(char *s)
 }
 
 static int
 }
 
 static int
-_parse_line(char *s)
+_parse_query_action(char *s)
 {
        char *act, *p;
 {
        char *act, *p;
-       struct action_rule *out;
-
-       if (s == NULL) return -1;
-       while ((*s == ' ') || (*s == '\t')) s++;
-       if (*s == '#') return -1;
+       action_rule_t *out, *rule;
 
        act = _find_action(s);
 
        act = _find_action(s);
-
        if (act == NULL) return -1;
        if (act == NULL) return -1;
-       out = (struct action_rule *)calloc(1, sizeof(struct action_rule));
+
+       out = (action_rule_t *)calloc(1, sizeof(action_rule_t));
        if (out == NULL) return -1;
 
        p = strchr(act, ' ');
        if (p != NULL) *p = '\0';
        if (out == NULL) return -1;
 
        p = strchr(act, ' ');
        if (p != NULL) *p = '\0';
-       out->action = strdup(act);
 
 
-       if (out->action == NULL)
-       {
-               free(out);
-               return -1;
-       }
+       if (!strcasecmp(act, "ignore"))               out->action = ACTION_IGNORE;
+       else if (!strcasecmp(act, "notify"))          out->action = ACTION_NOTIFY;
+       else if (!strcasecmp(act, "broadcast"))       out->action = ACTION_BROADCAST;
+       else if (!strcasecmp(act, "access"))          out->action = ACTION_ACCESS;
+       else if (!strcasecmp(act, "store"))           out->action = ACTION_ASL_STORE;
+       else if (!strcasecmp(act, "save"))            out->action = ACTION_ASL_STORE;
+       else if (!strcasecmp(act, "store_file"))      out->action = ACTION_ASL_FILE;
+       else if (!strcasecmp(act, "store_directory")) out->action = ACTION_ASL_DIR;
+       else if (!strcasecmp(act, "store_dir"))       out->action = ACTION_ASL_DIR;
+       else if (!strcasecmp(act, "file"))            out->action = ACTION_FILE;
+       else if (!strcasecmp(act, "forward"))         out->action = ACTION_FORWARD;
 
        if (p != NULL)
        {
 
        if (p != NULL)
        {
@@ -152,7 +276,6 @@ _parse_line(char *s)
 
                if (out->options == NULL)
                {
 
                if (out->options == NULL)
                {
-                       free(out->action);
                        free(out);
                        return -1;
                }
                        free(out);
                        return -1;
                }
@@ -161,93 +284,145 @@ _parse_line(char *s)
        p = act - 1;
 
        *p = '\0';
        p = act - 1;
 
        *p = '\0';
-       out->query = asl_msg_from_string(s);
+
+       if (s[0] == '*') out->query = asl_msg_new(ASL_TYPE_QUERY);
+       else
+       {
+               s[0] = 'Q';
+               out->query = asl_msg_from_string(s);
+       }
 
        if (out->query == NULL)
        {
 
        if (out->query == NULL)
        {
-               free(out->action);
-               if (out->options != NULL) free(out->options);
+               asldebug("out->query is NULL (ERROR)\n");
+               free(out->options);
                free(out);
                return -1;
        }
 
                free(out);
                return -1;
        }
 
-       TAILQ_INSERT_TAIL(&asl_action_rule, out, entries);
+       /* store /some/path means save to a file */
+       if ((out->action == ACTION_ASL_STORE) && (out->options != NULL)) out->action = ACTION_ASL_FILE;
+
+       if (out->action == ACTION_FILE) _act_file_init(out);
+       else if ((out->action == ACTION_ASL_FILE) || (out->action == ACTION_ASL_DIR)) _act_store_init(out);
+
+       if (out->action == ACTION_ASL_STORE)
+       {
+               asldebug("action = ACTION_ASL_STORE\n");
+               if (asl_datastore_rule == NULL) asl_datastore_rule = out;
+               else
+               {
+                       for (rule = asl_datastore_rule; rule->next != NULL; rule = rule->next);
+                       rule->next = out;
+               }
+       }
+       else
+       {
+               asldebug("action = %d options = %s\n", out->action, out->options);
+               if (asl_action_rule == NULL) asl_action_rule = out;
+               else
+               {
+                       for (rule = asl_action_rule; rule->next != NULL; rule = rule->next);
+                       rule->next = out;
+               }
+       }
 
        return 0;
 }
 
 
        return 0;
 }
 
-static char *
-_next_word(char **s)
+static int
+_parse_line(char *s)
 {
 {
-       char *a, *p, *e, *out;
-       int quote, len;
-
-       if (s == NULL) return NULL;
-       if (*s == NULL) return NULL;
+       char *str;
+       int status;
 
 
-       quote = 0;
-
-       p = *s;
-       a = p;
-       e = p;
+       if (s == NULL) return -1;
+       while ((*s == ' ') || (*s == '\t')) s++;
 
 
-       while (*p != '\0')
+       /* First non-whitespace char is the rule type */
+       switch (*s)
        {
        {
-               if (*p == '\\')
+               case '\0':
+               case '#':
                {
                {
-                       p++;
-                       e = p;
-
-                       if (*p == '\0')
-                       {
-                               p--;
-                               break;
-                       }
-
-                       p++;
-                       e = p;
-                       continue;
+                       /* Blank Line or Comment */
+                       return 0;
                }
                }
-
-               if (*p == '"')
+               case 'Q':
+               case '?':
+               case '*':
                {
                {
-                       if (quote == 0) quote = 1;
-                       else quote = 0;
+                       /* Query-match action */
+                       status = _parse_query_action(s);
+                       break;
                }
                }
-
-               if (((*p == ' ') || (*p == '\t')) && (quote == 0))
+               case '=':
                {
                {
-                       e = p + 1;
+                       /* Set parameter */
+                       status = control_set_param(s);
+                       break;
+               }
+               default:
+               {
+                       status = -1;
                        break;
                }
                        break;
                }
-
-               p++;
-               e = p;
        }
 
        }
 
-       *s = e;
-
-       len = p - a;
-       if (len == 0) return NULL;
-
-       out = malloc(len + 1);
-       if (out == NULL) return NULL;
+       if (status != 0)
+       {
+               str = NULL;
+               asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [%s Ignoring unrecognized entry in %s: %s] [%s 0] [%s 0] [Facility syslog]",
+                                ASL_KEY_SENDER,
+                                ASL_KEY_LEVEL, ASL_LEVEL_ERR,
+                                ASL_KEY_PID, getpid(),
+                                ASL_KEY_MSG, _PATH_ASL_CONF, s,
+                                ASL_KEY_UID, ASL_KEY_GID);
+
+               internal_log_message(str);
+               free(str);
+       }
 
 
-       memcpy(out, a, len);
-       out[len] = '\0';
-       return out;
+       return status;
 }
 
 }
 
-static void 
-_act_notify(struct action_rule *r)
+static void
+_act_notify(action_rule_t *r)
 {
        if (r == NULL) return;
        if (r->options == NULL) return;
 {
        if (r == NULL) return;
        if (r->options == NULL) return;
+
        notify_post(r->options);
 }
 
 static void
        notify_post(r->options);
 }
 
 static void
-_act_access_control(struct action_rule *r, asl_msg_t *msg)
+_act_broadcast(action_rule_t *r, aslmsg msg)
+{
+#ifndef CONFIG_IPHONE
+       FILE *pw;
+       const char *val;
+
+       if (r == NULL) return;
+       if (msg == NULL) return;
+
+       val = r->options;
+       if (val == NULL) val = asl_get(msg, ASL_KEY_MSG);
+       if (val == NULL) return;
+
+       pw = popen(_PATH_WALL, "w");
+       if (pw < 0)
+       {
+               asldebug("%s: error sending wall message: %s\n", MY_ID, strerror(errno));
+               return;
+       }
+
+       fprintf(pw, "%s", val);
+       pclose(pw);
+#endif
+}
+
+static void
+_act_access_control(action_rule_t *r, aslmsg msg)
 {
        int32_t ruid, rgid;
        char *p;
 {
        int32_t ruid, rgid;
        char *p;
@@ -263,169 +438,1266 @@ _act_access_control(struct action_rule *r, asl_msg_t *msg)
                rgid = atoi(p);
        }
 
                rgid = atoi(p);
        }
 
-       if (ruid != -1) asl_set((aslmsg)msg, ASL_KEY_READ_UID, r->options);
+       if (ruid != -1) asl_set(msg, ASL_KEY_READ_UID, r->options);
        if (p != NULL)
        {
        if (p != NULL)
        {
-               if (rgid != -1) asl_set((aslmsg)msg, ASL_KEY_READ_GID, p);
+               if (rgid != -1) asl_set(msg, ASL_KEY_READ_GID, p);
                p--;
                *p = ' ';
        }
 }
 
                p--;
                *p = ' ';
        }
 }
 
-static void 
-_act_store(struct action_rule *r, asl_msg_t *msg)
+static uint32_t
+_act_store_file_setup(struct store_data *sd)
 {
 {
-       struct store_data *sd;
-       asl_store_t *s;
-       char *p, *opts;
        uint32_t status;
        uint32_t status;
-       uint64_t msgid;
 
 
-       if (r == NULL) return;
-       if (r->options == NULL) return;
-       if (r->data == NULL)
+       if (sd == NULL) return ASL_STATUS_INVALID_STORE;
+       if (sd->store == NULL) return ASL_STATUS_INVALID_STORE;
+       if (sd->store->store == NULL) return ASL_STATUS_INVALID_STORE;
+
+       status = asl_file_read_set_position(sd->store, ASL_FILE_POSITION_LAST);
+       if (status != ASL_STATUS_OK) return status;
+
+       sd->next_id = sd->store->cursor_xid + 1;
+       if (fseek(sd->store->store, 0, SEEK_END) != 0) return ASL_STATUS_ACCESS_DENIED;
+
+       return ASL_STATUS_OK;
+}
+
+static uint32_t
+_act_store_dir_setup(struct store_data *sd, time_t tick)
+{
+       struct tm ctm;
+       char *path;
+       struct stat sb;
+       uint64_t xid;
+       int status;
+       mode_t mask;
+
+       if (sd == NULL) return ASL_STATUS_INVALID_STORE;
+       if (sd->dir == NULL) return ASL_STATUS_INVALID_STORE;
+
+       /* get / set message id from StoreData file */
+       xid = 0;
+
+       if (sd->storedata == NULL)
        {
        {
-               /* Set up store data */
-               sd = (struct store_data *)calloc(1, sizeof(struct store_data));
-               if (sd == NULL) return;
+               memset(&sb, 0, sizeof(struct stat));
+               status = stat(sd->dir, &sb);
+               if (status == 0)
+               {
+                       /* must be a directory */
+                       if (!S_ISDIR(sb.st_mode)) return ASL_STATUS_INVALID_STORE;
+               }
+               else if (errno == ENOENT)
+               {
+                       /* doesn't exist - create it */
+                       mask = umask(0);
+                       status = mkdir(sd->dir, sd->mode);
+                       umask(mask);
 
 
-               opts = r->options;
-               sd->store = NULL;
-               sd->path = _next_word(&opts);
-               if (sd->path == NULL)
+                       if (status != 0) return ASL_STATUS_WRITE_FAILED;
+                       if (chown(sd->dir, sd->uid, sd->gid) != 0) return ASL_STATUS_WRITE_FAILED;
+               }
+               else
                {
                {
-                       free(sd);
-                       return;
+                       /* Unexpected stat error */
+                       return ASL_STATUS_FAILED;
+               }
+
+               path = NULL;
+               asprintf(&path, "%s/%s", sd->dir, FILE_ASL_STORE_DATA);
+               if (path == NULL) return ASL_STATUS_NO_MEMORY;
+
+               memset(&sb, 0, sizeof(struct stat));
+               status = stat(path, &sb);
+               if (status == 0)
+               {
+                       /* StoreData exists: open and read last xid */
+                       sd->storedata = fopen(path, "r+");
+                       if (sd->storedata == NULL)
+                       {
+                               free(path);
+                               return ASL_STATUS_FAILED;
+                       }
+
+                       if (fread(&xid, sizeof(uint64_t), 1, sd->storedata) != 1)
+                       {
+                               free(path);
+                               fclose(sd->storedata);
+                               sd->storedata = NULL;
+                               return ASL_STATUS_READ_FAILED;
+                       }
                }
                }
+               else if (errno == ENOENT)
+               {
+                       /* StoreData does not exist: create it */
+                       sd->storedata = fopen(path, "w");
+                       if (sd->storedata == NULL)
+                       {
+                               free(path);
+                               return ASL_STATUS_FAILED;
+                       }
 
 
-               sd->flags = 0;
-               while (NULL != (p = _next_word(&opts)))
+                       if (chown(path, sd->uid, sd->gid) != 0)
+                       {
+                               free(path);
+                               return ASL_STATUS_WRITE_FAILED;
+                       }
+               }
+               else
                {
                {
-                       if (!strcmp(p, "stayopen")) sd->flags |= ACT_STORE_FLAG_STAY_OPEN;
-                       else if (!strcmp(p, "exclude_asldb")) sd->flags |= ACT_STORE_FLAG_EXCLUDE_ASLDB;
-                       free(p);
-                       p = NULL;
+                       /* Unexpected stat error */
+                       free(path);
+                       return ASL_STATUS_FAILED;
                }
                }
+
+               free(path);
        }
        else
        {
        }
        else
        {
-               sd = (struct store_data *)r->data;
+               rewind(sd->storedata);
+               if (fread(&xid, sizeof(uint64_t), 1, sd->storedata) != 1)
+               {
+                       fclose(sd->storedata);
+                       sd->storedata = NULL;
+                       return ASL_STATUS_READ_FAILED;
+               }
        }
 
        }
 
-       if (sd->store == NULL)
+       xid = asl_core_ntohq(xid);
+       xid++;
+       sd->next_id = xid;
+
+       xid = asl_core_htonq(xid);
+       rewind(sd->storedata);
+       status = fwrite(&xid, sizeof(uint64_t), 1, sd->storedata);
+       if (status != 1)
        {
        {
-               s = NULL;
-               status = asl_store_open(sd->path, 0, &s);
-               if (status != ASL_STATUS_OK) return;
-               if (s == NULL) return;
-               sd->store = s;
+               fclose(sd->storedata);
+               sd->storedata = NULL;
+               return ASL_STATUS_WRITE_FAILED;
        }
 
        }
 
-       asl_store_save(sd->store, msg, -1, -1, &msgid);
-       if (!(sd->flags & ACT_STORE_FLAG_STAY_OPEN))
+       if ((sd->flags & ACT_STORE_FLAG_STAY_OPEN) == 0)
        {
        {
-               asl_store_close(sd->store);
-               sd->store = NULL;
+               fclose(sd->storedata);
+               sd->storedata = NULL;
        }
 
        }
 
-       if (sd->flags & ACT_STORE_FLAG_EXCLUDE_ASLDB) asl_set(msg, ASL_KEY_IGNORE, "Yes");
+       memset(&ctm, 0, sizeof(struct tm));
+
+       if (localtime_r((const time_t *)&tick, &ctm) == NULL) return ASL_STATUS_FAILED;
+       if ((sd->p_year == ctm.tm_year) && (sd->p_month == ctm.tm_mon) && (sd->p_day == ctm.tm_mday) && (sd->path != NULL)) return ASL_STATUS_OK;
+
+       if (sd->store != NULL) asl_file_close(sd->store);
+
+       sd->p_year = 0;
+       sd->p_month = 0;
+       sd->p_day = 0;
+
+       free(sd->path);
+       sd->path = NULL;
+
+       asprintf(&(sd->path), "%s/%d.%02d.%02d.asl", sd->dir, ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
+       if (sd->path == NULL) return ASL_STATUS_NO_MEMORY;
+
+       sd->p_year = ctm.tm_year;
+       sd->p_month = ctm.tm_mon;
+       sd->p_day = ctm.tm_mday;
+
+       return ASL_STATUS_OK;
 }
 
 }
 
-int
-asl_action_sendmsg(asl_msg_t *msg, const char *outid)
+static void
+_act_store_init(action_rule_t *r)
 {
 {
-       struct action_rule *r;
+       struct store_data *sd, *xd;
+       char *str, *opts, *p, *path;
+       action_rule_t *x;
+
+       /* check if the store data is already set up */
+       if (r->data != NULL) return;
 
 
-       if (reset != 0)
+       opts = r->options;
+       path = _next_word(&opts);
+
+       if ((path == NULL) || (path[0] != '/'))
        {
        {
-               _do_reset();
-               reset = 0;
+               str = NULL;
+               asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Invalid path for \"%s\" action: %s]",
+                                ASL_KEY_SENDER,
+                                ASL_KEY_LEVEL, ASL_LEVEL_ERR,
+                                ASL_KEY_PID, getpid(),
+                                ASL_KEY_MSG,
+                                (r->action == ACTION_ASL_FILE) ? "store" : "store_directory",
+                                (path == NULL) ? "no path specified" : path);
+
+               internal_log_message(str);
+               free(str);
+               r->action = ACTION_NONE;
+               free(path);
+               return;
        }
 
        }
 
-       if (msg == NULL) return -1;
-
-       for (r = asl_action_rule.tqh_first; r != NULL; r = r->entries.tqe_next)
+       /* check if a previous rule has set up this path (ACTION_ASL_FILE) or dir (ACTION_ASL_DIR) */
+       for (x = asl_action_rule; x != NULL; x = x->next)
        {
        {
-               if (asl_msg_cmp(r->query, msg) == 1)
+               if ((x->action == r->action) && (x->data != NULL))
                {
                {
-                       if (r->action == NULL) continue;
-                       else if (!strcmp(r->action, "access")) _act_access_control(r, msg);
-                       else if (!strcmp(r->action, "notify")) _act_notify(r);
-                       else if (!strcmp(r->action, "store")) _act_store(r, msg);
+                       xd = (struct store_data *)x->data;
+                       p = xd->path;
+                       if (r->action == ACTION_ASL_DIR) p = xd->dir;
+
+                       if ((p != NULL) && (!strcmp(path, p)))
+                       {
+                               free(path);
+                               xd->refcount++;
+                               r->data = x->data;
+                               return;
+                       }
                }
        }
 
                }
        }
 
-       return 0;
-}
+       /* set up store data */
+       sd = (struct store_data *)calloc(1, sizeof(struct store_data));
+       if (sd == NULL) return;
 
 
-static int
-_parse_notify_file(const char *name)
-{
-       FILE *cf;
-       char *line;
+       sd->refcount = 1;
+       sd->mode = 0755;
+       sd->next_id = 0;
+       sd->uid = 0;
+       sd->gid = 0;
+       sd->flags = 0;
 
 
-       cf = fopen(name, "r");
-       if (cf == NULL) return 1;
+       if (r->action == ACTION_ASL_DIR) sd->dir = path;
+       else sd->path = path;
 
 
-       while (NULL != (line = get_line_from_file(cf)))
+       while (NULL != (p = _next_word(&opts)))
        {
        {
-               _parse_line(line);
-               free(line);
-       }
+               if (!strcmp(p, "stayopen"))
+               {
+                       sd->flags |= ACT_STORE_FLAG_STAY_OPEN;
+               }
+               else if (!strcmp(p, "continue"))
+               {
+                       sd->flags |= ACT_STORE_FLAG_CONTINUE;
+               }
+               else if (!strncmp(p, "mode=", 5)) sd->mode = strtol(p+5, NULL, 0);
+               else if (!strncmp(p, "uid=", 4)) sd->uid = atoi(p+4);
+               else if (!strncmp(p, "gid=", 4)) sd->gid = atoi(p+4);
 
 
-       fclose(cf);
+               free(p);
+               p = NULL;
+       }
 
 
-       return 0;
+       r->data = sd;
 }
 
 }
 
-int
-asl_action_init(void)
+/*
+ * Save a message to an ASL format file (ACTION_ASL_FILE)
+ * or to an ASL directory (ACTION_ASL_DIR).
+ */
+static void
+_act_store(action_rule_t *r, aslmsg msg)
 {
 {
-       asldebug("%s: init\n", MY_ID);
+       struct store_data *sd;
+       asl_file_t *s;
+       uint32_t status;
+       uint64_t mid;
+       mode_t mask;
+       char *str, *opts;
+       const char *val;
+       time_t tick;
 
 
-       TAILQ_INIT(&asl_action_rule);
+       s = NULL;
 
 
-       query = asl_new(ASL_TYPE_QUERY);
-       aslevent_addmatch(query, MY_ID);
-       aslevent_addoutput(asl_action_sendmsg, MY_ID);
+       if (r->data == NULL) return;
 
 
-       _parse_notify_file(_PATH_ASL_CONF);
-       return 0;
-}
+       sd = (struct store_data *)r->data;
 
 
-int
-asl_action_reset(void)
+       if (sd->flags & ACT_FLAG_HAS_LOGGED) return;
+       sd->flags |= ACT_FLAG_HAS_LOGGED;
+
+       if (r->action == ACTION_ASL_DIR)
+       {
+               val = asl_get(msg, ASL_KEY_TIME);
+               if (val == NULL) return;
+
+               tick = atol(val);
+               status = _act_store_dir_setup(sd, tick);
+               if (status != ASL_STATUS_OK)
+               {
+                       asldebug("_act_store_dir_setup %s failed: %s\n", sd->path, asl_core_error(status));
+
+                       sd->fails++;
+
+                       /* disable further activity after multiple failures */
+                       if (sd->fails > MAX_FAILURES)
+                       {
+                               char *str = NULL;
+                               asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Disabling writes to path %s following %u failures (%s)]",
+                                                ASL_KEY_SENDER,
+                                                ASL_KEY_LEVEL, ASL_LEVEL_ERR,
+                                                ASL_KEY_PID, getpid(),
+                                                ASL_KEY_MSG, sd->path, sd->fails, asl_core_error(status));
+
+                               internal_log_message(str);
+                               free(str);
+
+                               asl_file_close(sd->store);
+                               sd->store = NULL;
+                               r->action = ACTION_NONE;
+                               return;
+                       }
+               }
+               else
+               {
+                       sd->fails = 0;
+               }
+       }
+
+       if (sd->store == NULL)
+       {
+               s = NULL;
+
+               mask = umask(0);
+               status = asl_file_open_write(sd->path, (sd->mode & 0666), sd->uid, sd->gid, &s);
+               umask(mask);
+
+               if ((status != ASL_STATUS_OK) || (s == NULL))
+               {
+                       asldebug("asl_file_open_write %s failed: %s\n", sd->path, asl_core_error(status));
+
+                       sd->fails++;
+
+                       /* disable further activity after multiple failures */
+                       if (sd->fails > MAX_FAILURES)
+                       {
+                               char *str = NULL;
+                               asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Disabling writes to path %s following %u failures (%s)]",
+                                                ASL_KEY_SENDER,
+                                                ASL_KEY_LEVEL, ASL_LEVEL_ERR,
+                                                ASL_KEY_PID, getpid(),
+                                                ASL_KEY_MSG, sd->path, sd->fails, asl_core_error(status));
+
+                               internal_log_message(str);
+                               free(str);
+
+                               asl_file_close(sd->store);
+                               sd->store = NULL;
+                               r->action = ACTION_NONE;
+                               return;
+                       }
+               }
+               else if (status == ASL_STATUS_OK)
+               {
+                       sd->fails = 0;
+               }
+
+               sd->store = s;
+       }
+
+       if (r->action != ACTION_ASL_DIR)
+       {
+               status = _act_store_file_setup(sd);
+               if (status != ASL_STATUS_OK)
+               {
+                       asldebug("_act_store_file_setup %s failed: %s\n", sd->path, asl_core_error(status));
+
+                       sd->fails++;
+
+                       /* disable further activity after multiple failures */
+                       if (sd->fails > MAX_FAILURES)
+                       {
+                               char *str = NULL;
+                               asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Disabling writes to path %s following %u failures (%s)]",
+                                                ASL_KEY_SENDER,
+                                                ASL_KEY_LEVEL, ASL_LEVEL_ERR,
+                                                ASL_KEY_PID, getpid(),
+                                                ASL_KEY_MSG, sd->path, sd->fails, asl_core_error(status));
+
+                               internal_log_message(str);
+                               free(str);
+
+                               asl_file_close(sd->store);
+                               sd->store = NULL;
+                               r->action = ACTION_NONE;
+                               return;
+                       }
+               }
+               else
+               {
+                       sd->fails = 0;
+               }
+       }
+
+       mid = sd->next_id;
+
+       status = asl_file_save(sd->store, msg, &mid);
+       if (status != ASL_STATUS_OK)
+       {
+               asldebug("asl_file_save %s failed: %s\n", sd->path, asl_core_error(status));
+
+               sd->fails++;
+
+               /* disable further activity after multiple failures */
+               if (sd->fails > MAX_FAILURES)
+               {
+                       char *str = NULL;
+                       asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Disabling writes to path %s following %u failures (%s)]",
+                                        ASL_KEY_SENDER,
+                                        ASL_KEY_LEVEL, ASL_LEVEL_ERR,
+                                        ASL_KEY_PID, getpid(),
+                                        ASL_KEY_MSG, sd->path, sd->fails, asl_core_error(status));
+
+                       internal_log_message(str);
+                       free(str);
+
+                       asl_file_close(sd->store);
+                       sd->store = NULL;
+                       r->action = ACTION_NONE;
+                       return;
+               }
+       }
+       else
+       {
+               sd->fails = 0;
+       }
+
+       if ((sd->flags & ACT_STORE_FLAG_STAY_OPEN) == 0)
+       {
+               asl_file_close(sd->store);
+               sd->store = NULL;
+       }
+
+       if ((sd->flags & ACT_STORE_FLAG_CONTINUE) == 0)
+       {
+               opts = (char *)asl_get(msg, ASL_KEY_OPTION);
+               if (opts == NULL)
+               {
+                       asl_set(msg, ASL_KEY_OPTION, ASL_OPT_IGNORE);
+               }
+               else
+               {
+                       str = NULL;
+                       asprintf(&str, "%s %s", ASL_OPT_IGNORE, opts);
+                       if (str != NULL)
+                       {
+                               asl_set(msg, ASL_KEY_OPTION, str);
+                               free(str);
+                       }
+               }
+       }
+}
+
+static int
+_act_file_send_repeat_msg(struct file_data *fdata)
 {
 {
-       reset = 1;
+       char vt[32], *msg;
+       int len, status, closeit;
+       time_t now = time(NULL);
+
+       if (fdata == NULL) return -1;
+
+       free(fdata->last_msg);
+       fdata->last_msg = NULL;
+
+       if (fdata->last_count == 0) return 0;
+
+       /* stop the timer */
+       dispatch_suspend(fdata->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, fdata->last_count, (fdata->last_count == 1) ? "" : "s");
+       fdata->last_count = 0;
+       if (msg == NULL) return -1;
+
+       closeit = 0;
+       if (fdata->fd < 0)
+       {
+               closeit = 1;
+               fdata->fd = _act_file_open(fdata);
+               if (fdata->fd < 0)
+               {
+                       asldebug("%s: error opening for repeat message (%s): %s\n", MY_ID, fdata->path, strerror(errno));
+                       return -1;
+               }
+       }
+
+       len = strlen(msg);
+       status = write(fdata->fd, msg, len);
+       free(msg);
+       if (closeit != 0)
+       {
+               close(fdata->fd);
+               fdata->fd = -1;
+       }
+
+       if ((status < 0) || (status < len))
+       {
+               asldebug("%s: error writing repeat message (%s): %s\n", MY_ID, fdata->path, strerror(errno));
+               return -1;
+       }
+
        return 0;
 }
 
        return 0;
 }
 
+/*
+ * N.B. This is basic file rotation support.
+ * More rotation options will be added in the future, along
+ * with support in aslmanager for compression and deletion.
+ */
+static void
+_act_file_rotate_file_data(struct file_data *fdata, time_t now)
+{
+       char str[MAXPATHLEN];
+       size_t len;
+       int width;
+
+       if (now == 0) now = time(NULL);
+
+       /* flush duplicates if pending */
+       _act_file_send_repeat_msg(fdata);
+
+       /* sleep to prevent a sub-second rotation */
+       while (now == fdata->stamp)
+       {
+               sleep(1);
+               now = time(NULL);
+       }
+
+       len = strlen(fdata->path);
+       width = len - 4;
+       if ((len > 4) && (!strcasecmp(fdata->path + width, ".log")))
+       {
+               /* ".log" rename: abc.log -> abc.timestamp.log */
+               snprintf(str, sizeof(str), "%.*s.%lu.log", width, fdata->path, fdata->stamp);
+       }
+       else
+       {
+               snprintf(str, sizeof(str), "%s.%lu", fdata->path, fdata->stamp);
+       }
+
+       rename(fdata->path, str);
+
+       fdata->stamp = now;
+}
+
 int
 int
-asl_action_close(void)
+_act_file_open(struct file_data *fdata)
 {
 {
-       struct action_rule *r, *n;
-       struct store_data *sd;
+       acl_t acl;
+       uuid_t uuid;
+       acl_entry_t entry;
+       acl_permset_t perms;
+       int status;
+       int fd = -1;
+       mode_t mask;
+       struct stat sb;
+       uint32_t i;
+
+       memset(&sb, 0, sizeof(struct stat));
+       status = stat(fdata->path, &sb);
+       if (status == 0)
+       {
+               /* must be a regular file */
+               if (!S_ISREG(sb.st_mode)) return -1;
 
 
-       n = NULL;
-       for (r = asl_action_rule.tqh_first; r != NULL; r = n)
+               /* use st_birthtimespec if stamp is zero */
+               if (fdata->stamp == 0) fdata->stamp = sb.st_birthtimespec.tv_sec;
+
+               /* rotate if over size limit */
+               if ((fdata->max_size > 0) && (sb.st_size > fdata->max_size))
+               {
+                       _act_file_rotate_file_data(fdata, 0);
+               }
+               else
+               {
+                       /* open existing file */
+                       fd = open(fdata->path, O_RDWR | O_APPEND | O_EXCL, 0);
+                       return fd;
+               }
+       }
+       else if (errno != ENOENT)
+       {
+               return -1;
+       }
+
+#if TARGET_OS_EMBEDDED
+       return open(fdata->path, O_RDWR | O_CREAT | O_EXCL, (fdata->mode & 0666));
+#else
+
+       acl = acl_init(1);
+
+       for (i = 0; i < fdata->ngid; i++)
+       {
+               status = mbr_gid_to_uuid(fdata->gid[i], uuid);
+               if (status != 0)
+               {
+                       char *str = NULL;
+                       asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Unknown GID %d for \"file\" action: %s]",
+                                        ASL_KEY_SENDER,
+                                        ASL_KEY_LEVEL, ASL_LEVEL_ERR,
+                                        ASL_KEY_PID, getpid(),
+                                        ASL_KEY_MSG, fdata->gid[i], fdata->path);
+
+                       internal_log_message(str);
+                       free(str);
+                       continue;
+               }
+
+               status = acl_create_entry_np(&acl, &entry, ACL_FIRST_ENTRY);
+               if (status != 0) goto asl_file_create_return;
+
+               status = acl_set_tag_type(entry, ACL_EXTENDED_ALLOW);
+               if (status != 0) goto asl_file_create_return;
+
+               status = acl_set_qualifier(entry, &uuid);
+               if (status != 0) goto asl_file_create_return;
+
+               status = acl_get_permset(entry, &perms);
+               if (status != 0) goto asl_file_create_return;
+
+               status = acl_add_perm(perms, ACL_READ_DATA);
+               if (status != 0) goto asl_file_create_return;
+       }
+
+       for (i = 0; i < fdata->nuid; i++)
+       {
+               status = mbr_uid_to_uuid(fdata->uid[i], uuid);
+               if (status != 0)
+               {
+                       char *str = NULL;
+                       asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Unknown UID %d for \"file\" action: %s]",
+                                        ASL_KEY_SENDER,
+                                        ASL_KEY_LEVEL, ASL_LEVEL_ERR,
+                                        ASL_KEY_PID, getpid(),
+                                        ASL_KEY_MSG, fdata->uid[i], fdata->path);
+
+                       internal_log_message(str);
+                       free(str);
+                       continue;
+               }
+
+               status = acl_create_entry_np(&acl, &entry, ACL_FIRST_ENTRY);
+               if (status != 0) goto asl_file_create_return;
+
+               status = acl_set_tag_type(entry, ACL_EXTENDED_ALLOW);
+               if (status != 0) goto asl_file_create_return;
+
+               status = acl_set_qualifier(entry, &uuid);
+               if (status != 0) goto asl_file_create_return;
+
+               status = acl_get_permset(entry, &perms);
+               if (status != 0) goto asl_file_create_return;
+
+               status = acl_add_perm(perms, ACL_READ_DATA);
+               if (status != 0) goto asl_file_create_return;
+       }
+
+       mask = umask(0);
+       fd = open(fdata->path, O_RDWR | O_CREAT | O_EXCL, (fdata->mode & 0666));
+       umask(mask);
+       if (fd < 0) goto asl_file_create_return;
+
+       errno = 0;
+       status = acl_set_fd(fd, acl);
+
+       if (status != 0)
+       {
+               close(fd);
+               fd = -1;
+               unlink(fdata->path);
+       }
+
+asl_file_create_return:
+
+       acl_free(acl);
+       return fd;
+#endif
+}
+
+static void
+_act_file_rotate(const char *path)
+{
+       action_rule_t *r;
+       struct file_data *fdata;
+       time_t now = time(NULL);
+
+       for (r = asl_action_rule; r != NULL; r = r->next)
+       {
+               if (r->action == ACTION_FILE)
+               {
+                       fdata = (struct file_data *)r->data;
+                       if (fdata->flags & ACT_FILE_FLAG_ROTATE)
+                       {
+                               if ((path == NULL) || ((fdata->path != NULL) && !strcmp(fdata->path, path)))
+                               {
+                                       _act_file_rotate_file_data(fdata, now);
+                               }
+                       }
+               }
+       }
+}
+
+static char *
+_act_file_format_string(char *s)
+{
+       char *fmt;
+       size_t i, len, n;
+
+       if (s == NULL) return NULL;
+
+       len = strlen(s);
+       n = 0;
+       for (i = 0; i < len; i++) if (s[i] == '\\') n++;
+
+       fmt = malloc(1 + len - n);
+       if (fmt == NULL) return NULL;
+
+       for (i = 0, n = 0; i < len; i++) if (s[i] != '\\') fmt[n++] = s[i];
+       fmt[n] = '\0';
+       return fmt;
+}
+
+static size_t
+_act_file_max_size(char *s)
+{
+       size_t len, n, max;
+       char x;
+
+       if (s == NULL) return 0;
+
+       len = strlen(s);
+       if (len == 0) return 0;
+
+       n = 1;
+       x = s[len - 1];
+       if (x > 90) x -= 32;
+       if (x == 'K') n = 1ll << 10;
+       else if (x == 'M') n = 1ll << 20;
+       else if (x == 'G') n = 1ll << 30;
+       else if (x == 'T') n = 1ll << 40;
+
+       max = atoll(s) * n;
+       return max;
+}
+
+static void
+_act_file_add_uid(struct file_data *fdata, char *s)
+{
+       if (fdata == NULL) return;
+       if (s == NULL) return;
+
+       fdata->uid = reallocf(fdata->uid, (fdata->nuid + 1) * sizeof(uid_t));
+       if (fdata->uid == NULL)
+       {
+               fdata->nuid = 0;
+               return;
+       }
+
+       fdata->uid[fdata->nuid++] = atoi(s);
+}
+
+static void
+_act_file_add_gid(struct file_data *fdata, char *s)
+{
+       if (fdata == NULL) return;
+       if (s == NULL) return;
+
+       fdata->gid = reallocf(fdata->gid, (fdata->ngid + 1) * sizeof(gid_t));
+       if (fdata->gid == NULL)
+       {
+               fdata->ngid = 0;
+               return;
+       }
+
+       fdata->gid[fdata->ngid++] = atoi(s);
+}
+
+static void
+_act_file_init(action_rule_t *r)
+{
+       struct file_data *fdata, *xdata;
+       char *str, *opts, *p, *path;
+       action_rule_t *x;
+
+       /* check if the file data is already set up */
+       if (r->data != NULL) return;
+
+       /* requires at least a path */
+       if (r->options == NULL) return;
+       opts = r->options;
+       path = _next_word(&opts);
+
+       if ((path == NULL) || (path[0] != '/'))
+       {
+               str = NULL;
+               asprintf(&str, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Invalid path for \"file\" action: %s]",
+                                ASL_KEY_SENDER,
+                                ASL_KEY_LEVEL, ASL_LEVEL_ERR,
+                                ASL_KEY_PID, getpid(),
+                                ASL_KEY_MSG, (path == NULL) ? "no path specified" : path);
+
+               internal_log_message(str);
+               free(str);
+               free(path);
+               r->action = ACTION_NONE;
+               return;
+       }
+
+       /* check if a previous rule has set up this path */
+       for (x = asl_action_rule; x != NULL; x = x->next)
+       {
+               if ((x->action == ACTION_FILE) && (x->data != NULL))
+               {
+                       xdata = (struct file_data *)x->data;
+                       if ((xdata->path != NULL) && (!strcmp(path, xdata->path)))
+                       {
+                               free(path);
+                               xdata->refcount++;
+                               r->data = x->data;
+                               return;
+                       }
+               }
+       }
+
+       /* set up file data */
+       fdata = (struct file_data *)calloc(1, sizeof(struct file_data));
+       if (fdata == NULL) return;
+
+       fdata->refcount = 1;
+       fdata->path = path;
+
+       /*
+        * options:
+        * mode=                        set file creation mode
+        * uid=                         user added to read ACL
+        * gid=                         group added to read ACL
+        * format=                      format string (also fmt=)
+        * no_dup_supress       no duplicate supression
+        *
+        * rotate                       automatic daily rotation
+        *                                      this is basic rotation - more support is TBD
+        */
+       fdata->mode = 0644;
+       fdata->flags = ACT_FILE_FLAG_DUP_SUPRESS;
+
+       while (NULL != (p = _next_word(&opts)))
+       {
+               if (!strncmp(p, "mode=", 5)) fdata->mode = strtol(p+5, NULL, 0);
+               else if (!strncmp(p, "uid=", 4)) _act_file_add_uid(fdata, p+4);
+               else if (!strncmp(p, "gid=", 4)) _act_file_add_gid(fdata, p+4);
+               else if (!strncmp(p, "fmt=", 4)) fdata->fmt = _act_file_format_string(p+4);
+               else if (!strncmp(p, "format=", 7)) fdata->fmt = _act_file_format_string(p+7);
+               else if (!strncmp(p, "no_dup_supress", 14)) fdata->flags &= ~ACT_FILE_FLAG_DUP_SUPRESS;
+               else if (!strncmp(p, "rotate", 6)) fdata->flags |= ACT_FILE_FLAG_ROTATE;
+               else if (!strncmp(p, "max_size=", 9)) fdata->max_size = _act_file_max_size(p+9);
+
+               free(p);
+               p = NULL;
+       }
+
+       if (fdata->fmt == NULL) fdata->fmt = strdup("std");
+
+       /* duplicate compression is only possible for std and bsd formats */
+       if (strcmp(fdata->fmt, "std") && strcmp(fdata->fmt, "bsd")) fdata->flags &= ~ACT_FILE_FLAG_DUP_SUPRESS;
+
+       /* set time format for raw output */
+       if (!strcmp(fdata->fmt, "raw")) fdata->tfmt = "sec";
+
+       r->data = fdata;
+}
+
+static void
+_act_file(action_rule_t *r, aslmsg msg)
+{
+       struct file_data *fdata;
+       int is_dup;
+       uint32_t len, msg_hash = 0;
+       char *str;
+       time_t now, today;
+       struct tm ctm;
+
+       if (r->data == NULL) return;
+
+       fdata = (struct file_data *)r->data;
+
+       now = time(NULL);
+       today = now;
+
+       memset(&ctm, 0, sizeof(struct tm));
+       if (localtime_r((const time_t *)&now, &ctm) != NULL)
+       {
+               ctm.tm_sec = 0;
+               ctm.tm_min = 0;
+               ctm.tm_hour = 0;
+               today = mktime(&ctm);
+       }
+
+       /* check for rotation */
+       if ((last_file_day != 0) && (last_file_day != today))
+       {
+               _act_file_rotate(NULL);
+       }
+
+       last_file_day = today;
+
+
+       /*
+        * asl.conf may contain multuple rules for the same file, eg:
+        *    ? [= Facility zippy] /var/log/abc.log
+        *    ? [= Color purple] /var/log/abc.log
+        *
+        * To prevent duplicates we set a flag bit when a message is logged
+        * to this file, and bail out if it has already been logged.
+        * Note that asl_out_message clears the flag bit in all file_data
+        * structures before processing each message.
+        */
+       if (fdata->flags & ACT_FLAG_HAS_LOGGED) return;
+       fdata->flags |= ACT_FLAG_HAS_LOGGED;
+
+       is_dup = 0;
+
+       str = asl_format_message((asl_msg_t *)msg, fdata->fmt, fdata->tfmt, ASL_ENCODE_SAFE, &len);
+
+       if (fdata->flags & ACT_FILE_FLAG_DUP_SUPRESS)
+       {
+               if (fdata->dup_timer == NULL)
+               {
+                       /* create a timer to flush dups on this file */
+                       fdata->dup_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, asl_action_queue);
+                       dispatch_source_set_event_handler(fdata->dup_timer, ^{ _act_file_send_repeat_msg((struct file_data *)r->data); });
+               }
+
+               if ((global.bsd_max_dup_time > 0) && (str != NULL) && (fdata->last_msg != NULL))
+               {
+                       msg_hash = asl_core_string_hash(str + 16, len - 16);
+                       if ((fdata->last_hash == msg_hash) && (!strcmp(fdata->last_msg, str + 16)))
+                       {
+                               if ((now - fdata->last_time) < global.bsd_max_dup_time) is_dup = 1;
+                       }
+               }
+       }
+
+       if (is_dup == 1)
        {
        {
-               n = r->entries.tqe_next;
+               if (fdata->last_count == 0)
+               {
+                       /* start the timer */
+                       dispatch_source_set_timer(fdata->dup_timer, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * global.bsd_max_dup_time), DISPATCH_TIME_FOREVER, 0);
+                       dispatch_resume(fdata->dup_timer);
+               }
 
 
-               if ((!strcmp(r->action, "store")) && (r->data != NULL))
+               fdata->last_count++;
+       }
+       else
+       {
+               fdata->fd = _act_file_open(fdata);
+               if (fdata->fd < 0)
                {
                {
-                       sd = (struct store_data *)r->data;
-                       if (sd->store != NULL) asl_store_close(sd->store);
-                       if (sd->path != NULL) free(sd->path);
-                       free(r->data);
+                       asldebug("_act_file_open %s failed: %s\n", fdata->path, strerror(errno));
+
+                       fdata->fails++;
+
+                       /* disable further activity after multiple failures */
+                       if (fdata->fails > MAX_FAILURES)
+                       {
+                               char *tmp = NULL;
+                               asprintf(&tmp, "[%s syslogd] [%s %u] [%s %u] [Facility syslog] [%s Disabling writes to path %s following %u failures (%s)]",
+                                                ASL_KEY_SENDER,
+                                                ASL_KEY_LEVEL, ASL_LEVEL_ERR,
+                                                ASL_KEY_PID, getpid(),
+                                                ASL_KEY_MSG, fdata->path, fdata->fails, strerror(errno));
+
+                               internal_log_message(tmp);
+                               free(tmp);
+
+                               r->action = ACTION_NONE;
+                               free(str);
+                               return;
+                       }
+               }
+               else
+               {
+                       fdata->fails = 0;
+               }
+
+               /*
+                * The current message is not a duplicate.  If fdata->last_count > 0
+                * we need to write a "last message repeated N times" log entry.
+                * _act_file_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 (fdata->last_count > 0)
+               {
+                       _act_file_send_repeat_msg(fdata);
                }
                }
+               else
+               {
+                       free(fdata->last_msg);
+                       fdata->last_msg = NULL;
+               }
+
+               if (str != NULL) fdata->last_msg = strdup(str + 16);
+
+               fdata->last_hash = msg_hash;
+               fdata->last_count = 0;
+               fdata->last_time = now;
+
+               if ((str != NULL) && (len > 1)) write(fdata->fd, str, len - 1);
+               close(fdata->fd);
+               fdata->fd = -1;
+       }
 
 
-               if (r->query != NULL) asl_free(r->query);
-               if (r->action != NULL) free(r->action);
-               if (r->options != NULL) free(r->options);
+       free(str);
+}
+
+static void
+_act_forward(action_rule_t *r, aslmsg msg)
+{
+       /* To do: <rdar://problem/6130747> Add a "forward" action to asl.conf */
+}
+
+static void
+_send_to_asl_store(aslmsg msg)
+{
+       int log_me;
+       action_rule_t *r;
+
+       /* ASLOption "store" forces a message to be saved */
+       log_me = asl_check_option(msg, ASL_OPT_STORE);
+       if (log_me == 1)
+       {
+               db_save_message(msg);
+               return;
+       }
+
+       /* if there are no rules, save the message */
+       if (asl_datastore_rule == NULL)
+       {
+               db_save_message(msg);
+               return;
+       }
+
+       for (r = asl_datastore_rule; r != NULL; r = r->next)
+       {
+               if (asl_msg_cmp(r->query, (asl_msg_t *)msg) == 1)
+               {
+                       /* if any rule matches, save the message (once!) */
+                       db_save_message(msg);
+                       return;
+               }
+       }
+}
+
+static void
+_asl_action_message(aslmsg msg)
+{
+       action_rule_t *r;
+
+       if (msg == NULL) return;
+
+       /* reset flag bit used for file duplicate avoidance */
+       for (r = asl_action_rule; r != NULL; r = r->next)
+       {
+               if ((r->action == ACTION_FILE) && (r->data != NULL))
+               {
+                       ((struct file_data *)(r->data))->flags &= ACT_FLAG_CLEAR_LOGGED;
+               }
+               else if (((r->action == ACTION_ASL_DIR) || (r->action == ACTION_ASL_FILE)) && (r->data != NULL))
+               {
+                       ((struct store_data *)(r->data))->flags &= ACT_FLAG_CLEAR_LOGGED;
+               }
+       }
+
+       for (r = asl_action_rule; r != NULL; r = r->next)
+       {
+               if (asl_msg_cmp(r->query, (asl_msg_t *)msg) == 1)
+               {
+                       if ((r->action == ACTION_ASL_FILE) || (r->action == ACTION_ASL_DIR))
+                       {
+                               _act_store(r, msg);
+                               if (asl_check_option(msg, ASL_OPT_IGNORE) != 0) return;
+                       }
+
+                       if (r->action == ACTION_NONE) continue;
+                       else if (r->action == ACTION_IGNORE) return;
+                       else if (r->action == ACTION_ACCESS) _act_access_control(r, msg);
+                       else if (r->action == ACTION_NOTIFY) _act_notify(r);
+                       else if (r->action == ACTION_BROADCAST) _act_broadcast(r, msg);
+                       else if (r->action == ACTION_FILE) _act_file(r, msg);
+                       else if (r->action == ACTION_FORWARD) _act_forward(r, msg);
+               }
+       }
+
+       if (asl_check_option(msg, ASL_OPT_IGNORE) != 0) return;
+
+       _send_to_asl_store(msg);
+}
+
+void
+asl_out_message(aslmsg msg)
+{
+       dispatch_flush_continuation_cache();
+
+       asl_msg_retain((asl_msg_t *)msg);
+
+       dispatch_async(asl_action_queue, ^{
+               _asl_action_message(msg);
+               asl_msg_release((asl_msg_t *)msg);
+       });
+}
+
+static int
+_parse_config_file(const char *name)
+{
+       FILE *cf;
+       char *line;
+
+       cf = fopen(name, "r");
+       if (cf == NULL) return 1;
+
+       while (NULL != (line = get_line_from_file(cf)))
+       {
+               _parse_line(line);
+               free(line);
+       }
+
+       fclose(cf);
+
+       return 0;
+}
+
+int
+asl_action_init(void)
+{
+       static dispatch_once_t once;
+
+       asldebug("%s: init\n", MY_ID);
+       _parse_config_file(_PATH_ASL_CONF);
+
+       dispatch_once(&once, ^{
+               asl_action_queue = dispatch_queue_create("ASL Action Queue", NULL);
+       });
+
+       return 0;
+}
+
+int
+_asl_action_close_internal(void)
+{
+       action_rule_t *r, *n;
+       struct store_data *sd;
+       struct file_data *fdata;
+       n = NULL;
+       for (r = asl_action_rule; r != NULL; r = n)
+       {
+               n = r->next;
+               if (r->data != NULL)
+               {
+                       if (((r->action == ACTION_ASL_FILE) || (r->action == ACTION_ASL_DIR) || (r->action == ACTION_NONE)))
+                       {
+                               sd = (struct store_data *)r->data;
+                               if (sd->refcount > 0) sd->refcount--;
+                               if (sd->refcount == 0)
+                               {
+                                       if (sd->store != NULL) asl_file_close(sd->store);
+                                       if (sd->storedata != NULL) fclose(sd->storedata);
+
+                                       free(sd->dir);
+                                       free(sd->path);
+                                       free(sd);
+                               }
+                       }
+
+                       if (r->action == ACTION_FILE)
+                       {
+                               fdata = (struct file_data *)r->data;
+                               if (fdata->refcount > 0) fdata->refcount--;
+                               if (fdata->refcount == 0)
+                               {
+                                       _act_file_send_repeat_msg(fdata);
+
+                                       if (fdata->dup_timer != NULL)
+                                       {
+                                               dispatch_source_cancel(fdata->dup_timer);
+                                               dispatch_resume(fdata->dup_timer);
+                                               dispatch_release(fdata->dup_timer);
+                                       }
+
+                                       free(fdata->path);
+                                       free(fdata->fmt);
+                                       free(fdata->uid);
+                                       free(fdata->gid);
+                                       free(fdata->last_msg);
+                                       free(fdata);
+                               }
+                       }
+               }
+
+               if (r->query != NULL) asl_msg_release(r->query);
+               free(r->options);
+
+               free(r);
+       }
+
+       asl_action_rule = NULL;
+
+       n = NULL;
+       for (r = asl_datastore_rule; r != NULL; r = n)
+       {
+               n = r->next;
+
+               if (r->query != NULL) asl_msg_release(r->query);
+               free(r->options);
 
 
-               TAILQ_REMOVE(&asl_action_rule, r, entries);
                free(r);
        }
 
                free(r);
        }
 
+       asl_datastore_rule = NULL;
+
+       return 0;
+}
+
+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;
+}
+
+int
+asl_action_file_rotate(const char *path)
+{
+       /*
+        * The caller may want to know when the rotation has been completed,
+        * so this is synchronous. Also ensures the string stays intact while we work.
+        */
+       dispatch_sync(asl_action_queue, ^{
+               _act_file_rotate(path);
+       });
+
        return 0;
 }
        return 0;
 }
+