X-Git-Url: https://git.saurik.com/apple/syslog.git/blobdiff_plain/5dd30d768c7cd795c2a3b974a6a1809dd8a3becf..db78b1bd5c8e2c516f88095d137ca8751a0f734b:/syslogd.tproj/asl_action.c?ds=sidebyside diff --git a/syslogd.tproj/asl_action.c b/syslogd.tproj/asl_action.c index 699413f..2848271 100644 --- a/syslogd.tproj/asl_action.c +++ b/syslogd.tproj/asl_action.c @@ -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@ - * + * * 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, @@ -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. - * + * * @APPLE_LICENSE_HEADER_END@ */ @@ -34,53 +34,176 @@ #include #include #include -#include +#include +#include +#include #include "daemon.h" +#include +#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 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 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; - char *action; + int action; char *options; void *data; - TAILQ_ENTRY(action_rule) entries; -}; + struct action_rule_s *next; +} action_rule_t; struct store_data { - asl_store_t *store; + asl_file_t *store; + FILE *storedata; + char *dir; char *path; + mode_t mode; + uid_t uid; + gid_t gid; + uint64_t next_id; + uint32_t fails; 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: - * 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 */ @@ -91,7 +214,7 @@ _find_action(char *s) p = s; if (p == NULL) return NULL; - if (*p != 'Q') return NULL; + if ((*p != 'Q') && (*p != '?') && (*p != '*')) return NULL; p++; @@ -121,30 +244,31 @@ _find_action(char *s) } static int -_parse_line(char *s) +_parse_query_action(char *s) { 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); - 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'; - 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) { @@ -152,7 +276,6 @@ _parse_line(char *s) if (out->options == NULL) { - free(out->action); free(out); return -1; } @@ -161,93 +284,145 @@ _parse_line(char *s) 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) { - free(out->action); - if (out->options != NULL) free(out->options); + asldebug("out->query is NULL (ERROR)\n"); + free(out->options); 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; } -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; } - - 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; + 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; @@ -263,169 +438,1266 @@ _act_access_control(struct action_rule *r, asl_msg_t *msg) 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 (rgid != -1) asl_set((aslmsg)msg, ASL_KEY_READ_GID, p); + if (rgid != -1) asl_set(msg, ASL_KEY_READ_GID, 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; - 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 { - 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; } +/* + * 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 -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: 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); } + 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; } +