X-Git-Url: https://git.saurik.com/apple/syslog.git/blobdiff_plain/c4fdb7d114b21b1b97b3777dfb8a8053693e7a91..f3df4c032d7a59379e2d8e1a5cf8a8f0e9ea9f63:/syslogd.tproj/asl_action.c diff --git a/syslogd.tproj/asl_action.c b/syslogd.tproj/asl_action.c index e800e65..b33d582 100644 --- a/syslogd.tproj/asl_action.c +++ b/syslogd.tproj/asl_action.c @@ -1,15 +1,15 @@ /* - * Copyright (c) 2004-2009 Apple Inc. All rights reserved. + * Copyright (c) 2004-2013 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,10 +17,11 @@ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. - * + * * @APPLE_LICENSE_HEADER_END@ */ +#include #include #include #include @@ -35,1084 +36,2413 @@ #include #include #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 ACTION_NONE 0 -#define ACTION_IGNORE 1 -#define ACTION_NOTIFY 2 -#define ACTION_BROADCAST 3 -#define ACTION_ACCESS 4 -#define ACTION_STORE 5 -#define ACTION_STORE_DIR 6 -#define ACTION_FORWARD 7 +#define MAX_FAILURES 5 + +#define ACTION_STATUS_ERROR -1 +#define ACTION_STATUS_OK 0 + +#define IDLE_CLOSE 300 + +#define PRIVATE_FLAG_NO_CLOSE 0x00000001 /* File or Store is closed by a cancellation handler */ + +#define DST_CLOSE_CHECKPOINT 0 +#define DST_CLOSE_DELETED 1 +#define DST_CLOSE_ERROR 2 +#define DST_CLOSE_IDLE 3 +#define DST_CLOSE_SHUTDOWN 4 +static const char *why_str[] = +{ + "checkpoint", + "deleted", + "error", + "idle", + "shutdown" +}; -#define IndexNull ((uint32_t)-1) #define forever for(;;) -#define ACT_STORE_FLAG_STAY_OPEN 0x00000001 -#define ACT_STORE_FLAG_EXCLUDE_ASLDB 0x00000002 +static dispatch_queue_t asl_action_queue; +static dispatch_source_t checkpoint_timer; +static time_t sweep_time = 0; -static asl_msg_t *query = NULL; -static int reset = RESET_NONE; -static pthread_mutex_t reset_lock = PTHREAD_MUTEX_INITIALIZER; +#if TARGET_OS_EMBEDDED +#ifndef CRASH_MOVER_SERVICE +#define CRASH_MOVER_SERVICE "com.apple.crash_mover" +#endif +static dispatch_queue_t crashlog_queue; +static time_t crashmover_state = 0; +static int crashmover_token = -1; +#endif -typedef struct action_rule_s +typedef struct asl_file_data { - asl_msg_t *query; - int action; - char *options; - void *data; - struct action_rule_s *next; -} action_rule_t; - -struct store_data + uint64_t next_id; + asl_file_t *aslfile; + time_t last_time; + uint32_t flags; + uint32_t pending; + dispatch_source_t monitor; +} asl_action_asl_file_data_t; + +typedef struct asl_store_data { - asl_file_t *store; FILE *storedata; - char *dir; - char *path; - mode_t mode; - uid_t uid; - gid_t gid; + asl_file_t *aslfile; uint64_t next_id; + time_t last_time; uint32_t flags; + uint32_t pending; uint32_t p_year; uint32_t p_month; uint32_t p_day; -}; - -static action_rule_t *asl_action_rule = NULL; -static action_rule_t *asl_datastore_rule = NULL; -static int filter_token = -1; - -int asl_action_close(); -static int _parse_config_file(const char *); -extern void db_save_message(asl_msg_t *m); + dispatch_source_t storedata_monitor; + dispatch_source_t aslfile_monitor; +} asl_action_asl_store_data_t; -static char * -_next_word(char **s) +typedef struct file_data +{ + int fd; + uint32_t flags; + time_t last_time; + uint32_t last_count; + uint32_t last_hash; + uint32_t pending; + char *last_msg; + dispatch_source_t dup_timer; + dispatch_source_t monitor; +} asl_action_file_data_t; + +typedef struct set_param_data { - char *a, *p, *e, *out; - int quote, len; + int token; +} asl_action_set_param_data_t; - if (s == NULL) return NULL; - if (*s == NULL) return NULL; +static int action_asl_store_count; +static bool store_has_logged; - quote = 0; +extern void db_save_message(asl_msg_t *m); - p = *s; - a = p; - e = p; +/* forward */ +static int _act_file_checkpoint_all(uint32_t force); +static void _asl_action_post_process_rule(asl_out_module_t *m, asl_out_rule_t *r); +static void _asl_action_close_idle_files(time_t idle_time); +static void _act_dst_close(asl_out_rule_t *r, int why); - while (*p != '\0') - { - if (*p == '\\') - { - p++; - e = p; +static void +_act_out_set_param(asl_out_module_t *m, char *x, bool eval) +{ + char *s = x; + char **l; + uint32_t count, intval; - if (*p == '\0') - { - p--; - break; - } + l = explode(s, " \t"); + if (l == NULL) return; - p++; - e = p; - continue; - } + for (count = 0; l[count] != NULL; count++); + if (count == 0) + { + free_string_list(l); + return; + } - if (*p == '"') - { - if (quote == 0) quote = 1; - else quote = 0; - } + if (!strcasecmp(l[0], "enable")) + { + /* = enable [1|0] */ + if (count < 2) intval = 1; + else intval = atoi(l[1]); - if (((*p == ' ') || (*p == '\t')) && (quote == 0)) - { - e = p + 1; - break; - } + if (!eval) intval = (intval == 0) ? 1 : 0; - p++; - e = p; + if (intval == 0) m->flags &= ~MODULE_FLAG_ENABLED; + else m->flags|= MODULE_FLAG_ENABLED; + free_string_list(l); + return; } + else if (!strcasecmp(l[0], "disable")) + { + /* = disable [1|0] */ + if (count < 2) intval = 1; + else intval = atoi(l[1]); - *s = e; + if (!eval) intval = (intval == 0) ? 1 : 0; - len = p - a; - if (len == 0) return NULL; + if (intval != 0) m->flags &= ~MODULE_FLAG_ENABLED; + else m->flags|= MODULE_FLAG_ENABLED; + free_string_list(l); + return; + } - out = malloc(len + 1); - if (out == NULL) return NULL; + free_string_list(l); - memcpy(out, a, len); - out[len] = '\0'; - return out; + if (!strcmp(m->name, ASL_MODULE_NAME)) + { + /* Other parameters may be set by com.apple.asl module */ + control_set_param(x, eval); + } } static void -_do_reset(void) +_act_notify(asl_out_module_t *m, asl_out_rule_t *r) { - pthread_mutex_lock(&reset_lock); + if (m == NULL) return; + if ((m->flags & MODULE_FLAG_ENABLED) == 0) return; - asl_action_close(); - _parse_config_file(_PATH_ASL_CONF); - reset = RESET_NONE; + if (r == NULL) return; + if (r->options == NULL) return; - pthread_mutex_unlock(&reset_lock); + notify_post(r->options); } -/* - * Config File format: - * 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 */ -static char * -_find_action(char *s) +static void +_act_broadcast(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg) { - char *p; - - p = s; - if (p == NULL) return NULL; - if ((*p != 'Q') && (*p != '?') && (*p != '*')) return NULL; +#if !TARGET_OS_EMBEDDED + FILE *pw; + const char *val; - p++; + if (m == NULL) return; + if ((m->flags & MODULE_FLAG_ENABLED) == 0) return; - forever - { - /* Find next [ */ - while ((*p == ' ') || (*p == '\t')) p++; + if (m->name == NULL) return; + if (r == NULL) return; + if (msg == NULL) return; - if (*p == '\0') return NULL; - if (*p != '[') return p; + /* only base module (asl.conf) may broadcast */ + if (strcmp(m->name, ASL_MODULE_NAME)) return; - /* skip to closing ] */ - while (*p != ']') - { - p++; - if (*p == '\\') - { - p++; - if (*p == ']') p++; - } - } + val = r->options; + if (val == NULL) val = asl_msg_get_val_for_key(msg, ASL_KEY_MSG); + if (val == NULL) return; - if (*p == ']') p++; + pw = popen(_PATH_WALL, "w"); + if (pw == NULL) + { + asldebug("%s: error sending wall message: %s\n", MY_ID, strerror(errno)); + return; } - return NULL; + fprintf(pw, "%s", val); + pclose(pw); +#endif } -static int -_parse_query_action(char *s) +static void +_act_set_key(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg) { - char *act, *p; - action_rule_t *out, *rule; + /* Placeholder */ +} - act = _find_action(s); - if (act == NULL) return -1; +static void +_act_unset_key(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg) +{ + /* Placeholder */ +} - out = (action_rule_t *)calloc(1, sizeof(action_rule_t)); - if (out == NULL) return -1; +static void +_act_access_control(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg) +{ + int32_t ruid, rgid; + char *p; - p = strchr(act, ' '); - if (p != NULL) *p = '\0'; + if (m == NULL) return; + if (m->name == NULL) return; + if (r == NULL) return; + if (msg == NULL) return; - 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_STORE; - else if (!strcasecmp(act, "save")) out->action = ACTION_STORE; - else if (!strcasecmp(act, "store_directory")) out->action = ACTION_STORE_DIR; - else if (!strcasecmp(act, "store_dir")) out->action = ACTION_STORE_DIR; - else if (!strcasecmp(act, "forward")) out->action = ACTION_FORWARD; + /* only base module (asl.conf) may set access controls */ + if (strcmp(m->name, ASL_MODULE_NAME)) return; + ruid = atoi(r->options); + rgid = -1; + p = strchr(r->options, ' '); + if (p == NULL) p = strchr(r->options, '\t'); if (p != NULL) { - out->options = strdup(p+1); + *p = '\0'; + p++; + rgid = atoi(p); + } - if (out->options == NULL) - { - free(out); - return -1; - } + if (ruid != -1) asl_msg_set_key_val(msg, ASL_KEY_READ_UID, r->options); + if (p != NULL) + { + if (rgid != -1) asl_msg_set_key_val(msg, ASL_KEY_READ_GID, p); + p--; + *p = ' '; } +} - p = act - 1; +#if TARGET_OS_EMBEDDED +static void +_crashlog_queue_check(void) +{ + /* + * Check whether the crashlog queue has been suspended for too long. + * We allow the crashlog quque to be suspended for 60 seconds. + * After that, we start logging again. This prevents syslogd from + * filling memory due to a suspended queue. CrashMover really shoud + * take no more than a second or two to finish. + */ + if (crashmover_state == 0) return; + if ((time(NULL) - crashmover_state) <= 60) return; + + asldebug("CrashMover timeout: resuming crashlog queue\n"); + dispatch_resume(crashlog_queue); + crashmover_state = 0; +} +#endif - *p = '\0'; +/* + * cover routine for asl_out_dst_file_create_open() + */ +static int +_act_file_create_open(asl_out_dst_data_t *dst) +{ + return asl_out_dst_file_create_open(dst, NULL); +} - if (s[0] == '*') out->query = asl_new(ASL_TYPE_QUERY); - else - { - s[0] = 'Q'; - out->query = asl_msg_from_string(s); - } +/* + * Checks and creates (if required) a directory for an ASL data store. + */ +static int +_asl_dir_create(asl_out_rule_t *r) +{ + struct stat sb; + int status; - if (out->query == NULL) + memset(&sb, 0, sizeof(struct stat)); + status = stat(r->dst->path, &sb); + if (status == 0) { - asldebug("out->query is NULL (ERROR)\n"); - if (out->options != NULL) free(out->options); - free(out); - return -1; + /* Store path must be a directory */ + if (!S_ISDIR(sb.st_mode)) + { + asldebug("_asl_dir_create: expected a directory at path %s\n", r->dst->path); + return -1; + } } - - if ((out->action == ACTION_STORE) && (out->options == NULL)) + else if (errno == ENOENT) { - asldebug("action = ACTION_STORE options = NULL\n"); - if (asl_datastore_rule == NULL) asl_datastore_rule = out; - else + /* Directory doesn't exists - try to create it */ + status = asl_out_mkpath(global.asl_out_module, r); + if (status != 0) { - for (rule = asl_datastore_rule; rule->next != NULL; rule = rule->next); - rule->next = out; + asldebug("_asl_dir_create: asl_out_mkpath failed: %s\n", r->dst->path); + return -1; } } 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; - } + /* Unexpected stat error */ + asldebug("_asl_dir_create: stat error %s\n", strerror(errno)); + return -1; } return 0; } /* - * Used to sed config parameters. - * Line format "= name value" + * Close an ASL Directory StoreData file + * - cancel the dispatch source for the file */ -static int -_parse_set_param(char *s) +static void +_asl_dir_storedata_close(asl_out_rule_t *r) { - char **l; - uint32_t intval, count, v32a, v32b, v32c; + asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private; + if (as_data->storedata == NULL) return; - if (s == NULL) return -1; - if (s[0] == '\0') return 0; + if (as_data->storedata_monitor == NULL) + { + /* + * This should never happen, but _asl_dir_storedata_open allows + * dispatch_source_create to fail silently. If there is no dispatch + * source, we just close the file. + */ + asldebug("close ASL storedata fd %d\n", fileno(as_data->storedata)); + fclose(as_data->storedata); + } + else + { + /* + * The storedata_monitor cancel handler will close the file. + */ + dispatch_source_cancel(as_data->storedata_monitor); + dispatch_release(as_data->storedata_monitor); - /* skip '=' and whitespace */ - s++; - while ((*s == ' ') || (*s == '\t')) s++; + } - l = explode(s, " \t"); - if (l == NULL) return -1; + asldebug("_asl_dir_storedata_close %p\n", as_data->storedata); + as_data->storedata = NULL; +} - for (count = 0; l[count] != NULL; count++); +/* + * Open an ASL Directory StoreData file + * - check directory existance and create it if necessary + * - check for StoreData file and create it (with given xid) if necessary. + * - create a dispatch source to watch for StoreData file deletion + */ +static int +_asl_dir_storedata_open(asl_out_rule_t *r, uint64_t xid) +{ + struct stat sb; + int status; + char dstpath[MAXPATHLEN]; - /* name is required */ - if (count == 0) - { - freeList(l); - return -1; - } + asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private; + if (as_data->storedata != NULL) return 0; - /* value is required */ - if (count == 1) + status = _asl_dir_create(r); + if (status != 0) { - freeList(l); + asldebug("_asl_dir_storedata_open: No directory at path %s\n", r->dst->path); return -1; } - if (!strcasecmp(l[0], "debug")) + /* StoreData file is not open */ + snprintf(dstpath, sizeof(dstpath), "%s/%s", r->dst->path, FILE_ASL_STORE_DATA); + + memset(&sb, 0, sizeof(struct stat)); + status = stat(dstpath, &sb); + if (status == 0) { - /* = debug {0|1} [file] */ - intval = atoi(l[1]); - config_debug(intval, l[2]); + /* StoreData file exists */ + as_data->storedata = fopen(dstpath, "r+"); + if (as_data->storedata == NULL) + { + asldebug("_asl_dir_storedata_open: fopen existing %s: %s\n", dstpath, strerror(errno)); + return -1; + } } - else if (!strcasecmp(l[0], "cutoff")) + else if (errno == ENOENT) { - /* = cutoff level */ - intval = atoi(l[1]); - if (intval > ASL_LEVEL_DEBUG) intval = ASL_LEVEL_DEBUG; - global.asl_log_filter = ASL_FILTER_MASK_UPTO(intval); + /* + * StoreData file does not exist. + * Create a new StoreData with a given xid. + */ + //TODO: This should return a failure if there are any ASL files. + /* that would require reading the directory, thus a performance hit */ + as_data->storedata = fopen(dstpath, "w+"); + if (as_data->storedata == NULL) + { + asldebug("_asl_dir_storedata_open: fopen new %s: %s\n", dstpath, strerror(errno)); + return -1; + } + + uint64_t xout = asl_core_htonq(xid); + status = fwrite(&xout, sizeof(uint64_t), 1, as_data->storedata); + if (status != 1) + { + asldebug("_asl_dir_storedata_open: storedata write failed %d %s\n", errno, strerror(errno)); + return -1; + } + +#if !TARGET_IPHONE_SIMULATOR + if (chown(dstpath, r->dst->uid[0], r->dst->gid[0]) != 0) + { + asldebug("_asl_dir_storedata_open: chown %d %d new %s: %s\n", dstpath, r->dst->uid[0], r->dst->gid[0], strerror(errno)); + return -1; + } +#endif } - else if (!strcasecmp(l[0], "mark_time")) + else { - /* = mark_time seconds */ - OSSpinLockLock(&global.lock); - global.mark_time = atoll(l[1]); - OSSpinLockUnlock(&global.lock); + /* Unexpected stat error */ + asldebug("_asl_dir_storedata_open: stat error %s\n", strerror(errno)); + return -1; } - else if (!strcasecmp(l[0], "dup_delay")) + + /* create storedata_monitor */ + int fd = fileno(as_data->storedata); + FILE *sdfp = as_data->storedata; + + as_data->storedata_monitor = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_DELETE, asl_action_queue); + if (as_data->storedata_monitor != NULL) { - /* = bsd_max_dup_time seconds */ - OSSpinLockLock(&global.lock); - global.bsd_max_dup_time = atoll(l[1]); - OSSpinLockUnlock(&global.lock); + dispatch_source_set_event_handler(as_data->storedata_monitor, ^{ + _act_dst_close(r, DST_CLOSE_DELETED); + }); + + dispatch_source_set_cancel_handler(as_data->storedata_monitor, ^{ + asldebug("cancel/close ASL storedata fd %d\n", fd); + fclose(sdfp); + }); + + dispatch_resume(as_data->storedata_monitor); } - else if (!strcasecmp(l[0], "asl_store_ping_time")) + + asldebug("_asl_dir_storedata_open ASL storedata %s fd %d\n", dstpath, fd); + return 0; +} + +/* + * Close an ASL Directory ASL file + * - cancel the dispatch source for the file + * - clears file name and timestamp in asl_action_asl_store_data_t structue + */ +static void +_asl_dir_today_close(asl_out_rule_t *r) +{ + asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private; + if (as_data->aslfile == NULL) return; + + if (as_data->pending != 0) { - /* NB this is private / unpublished */ - /* = asl_store_ping_time seconds */ - OSSpinLockLock(&global.lock); - global.asl_store_ping_time = atoll(l[1]); - OSSpinLockUnlock(&global.lock); + char *str = NULL; + asprintf(&str, "[Sender syslogd] [Level 4] [PID %u] [Facility syslog] [Message ASL Store %s was closed with %d pending messages]", global.pid, r->dst->fname, as_data->pending); + internal_log_message(str); + free(str); } - else if (!strcasecmp(l[0], "utmp_ttl")) + + if (as_data->aslfile_monitor == NULL) { - /* = utmp_ttl seconds */ - OSSpinLockLock(&global.lock); - global.utmp_ttl = (time_t)atoll(l[1]); - OSSpinLockUnlock(&global.lock); + /* + * This should never happen, but _asl_dir_today_open allows + * dispatch_source_create to fail silently. If there is no dispatch + * source, we just close the file. + */ + asldebug("close ASL fd %d\n", fileno(as_data->aslfile->store)); + asl_file_close(as_data->aslfile); } - else if (!strcasecmp(l[0], "fs_ttl")) + else { - /* = fs_ttl seconds */ - OSSpinLockLock(&global.lock); - global.fs_ttl = (time_t)atoll(l[1]); - OSSpinLockUnlock(&global.lock); + /* + * The aslfile_monitor cancel handler will close the file. + */ + dispatch_source_cancel(as_data->aslfile_monitor); + dispatch_release(as_data->aslfile_monitor); + as_data->aslfile_monitor = NULL; } - else if (!strcasecmp(l[0], "mps_limit")) + + as_data->p_year = 0; + as_data->p_month = 0; + as_data->p_day = 0; + + free(r->dst->fname); + r->dst->fname = NULL; + + as_data->aslfile = NULL; +} + +/* + * Check file size. + */ +static int +_act_checkpoint(asl_out_rule_t *r, uint32_t force) +{ + char tmpfname[MAXPATHLEN], *fn; + + if (r == NULL) return 0; + if (r->dst == NULL) return 0; + + fn = r->dst->fname; + if (fn == NULL) { - /* = mps_limit number */ - OSSpinLockLock(&global.lock); - global.mps_limit = (uint32_t)atol(l[1]); - OSSpinLockUnlock(&global.lock); + if (r->dst->path == NULL) return 0; + asl_make_dst_filename(r->dst, tmpfname, sizeof(tmpfname)); + fn = tmpfname; } - else if (!strcasecmp(l[0], "max_file_size")) - { - /* = max_file_size bytes */ - pthread_mutex_lock(global.db_lock); - if (global.dbtype & DB_TYPE_FILE) - { - asl_store_close(global.file_db); - global.file_db = NULL; - global.db_file_max = atoi(l[1]); - } + if ((force == CHECKPOINT_TEST) && (r->dst->file_max == 0)) return 0; - pthread_mutex_unlock(global.db_lock); - } - else if ((!strcasecmp(l[0], "db")) || (!strcasecmp(l[0], "database")) || (!strcasecmp(l[0], "datastore"))) + if ((r->dst->size == 0) || (r->dst->stamp == 0)) { - /* NB this is private / unpublished */ - /* = db type [max]... */ + struct stat sb; - v32a = 0; - v32b = 0; - v32c = 0; + memset(&sb, 0, sizeof(struct stat)); - if ((l[1][0] >= '0') && (l[1][0] <= '9')) - { - intval = atoi(l[1]); - if ((count >= 3) && (strcmp(l[2], "-"))) v32a = atoi(l[2]); - if ((count >= 4) && (strcmp(l[3], "-"))) v32b = atoi(l[3]); - if ((count >= 5) && (strcmp(l[4], "-"))) v32c = atoi(l[4]); - } - else if (!strcasecmp(l[1], "file")) - { - intval = DB_TYPE_FILE; - if ((count >= 3) && (strcmp(l[2], "-"))) v32a = atoi(l[2]); - } - else if (!strncasecmp(l[1], "mem", 3)) + if (stat(fn, &sb) < 0) { - intval = DB_TYPE_MEMORY; - if ((count >= 3) && (strcmp(l[2], "-"))) v32b = atoi(l[2]); - } - else if (!strncasecmp(l[1], "min", 3)) - { - intval = DB_TYPE_MINI; - if ((count >= 3) && (strcmp(l[2], "-"))) v32c = atoi(l[2]); - } - else - { - freeList(l); + if (errno == ENOENT) return 0; return -1; } - if (v32a == 0) v32a = global.db_file_max; - if (v32b == 0) v32b = global.db_memory_max; - if (v32c == 0) v32c = global.db_mini_max; + if (r->dst->stamp == 0) r->dst->stamp = sb.st_birthtimespec.tv_sec; + if (r->dst->stamp == 0) r->dst->stamp = sb.st_mtimespec.tv_sec; + r->dst->size = sb.st_size; + } + + if ((force == CHECKPOINT_TEST) && (r->dst->size < r->dst->file_max)) return 0; + + if (r->dst->flags & MODULE_FLAG_BASESTAMP) + { + _act_dst_close(r, DST_CLOSE_CHECKPOINT); + } + else + { + char srcpath[MAXPATHLEN]; + char dstpath[MAXPATHLEN]; + char tstamp[32]; + + if (r->dst->stamp == 0) r->dst->stamp = time(NULL); + asl_make_timestamp(r->dst->stamp, r->dst->flags, tstamp, sizeof(tstamp)); - config_data_store(intval, v32a, v32b, v32c); + snprintf(srcpath, sizeof(srcpath), "%s", fn); + snprintf(dstpath, sizeof(dstpath), "%s.%s", fn, tstamp); + + _act_dst_close(r, DST_CLOSE_CHECKPOINT); + rename(srcpath, dstpath); } - freeList(l); - return 0; + r->dst->stamp = 0; + r->dst->size = 0; + return 1; } +/* + * Open today's ASL file in an ASL directory + * - Checks date and closes a currently open file if it has the wrong date + * - Opens today's file + */ static int -_parse_line(char *s) +_asl_dir_today_open(asl_out_rule_t *r, const time_t *tick) { - char *str; int status; + mode_t mask; + struct tm ctm; + time_t now; - if (s == NULL) return -1; - while ((*s == ' ') || (*s == '\t')) s++; + if (r == NULL) return -1; + if (r->dst == NULL) return -1; - /* First non-whitespace char is the rule type */ - switch (*s) + status = _asl_dir_create(r); + if (status != 0) { - case '\0': - case '#': - { - /* Blank Line or Comment */ - return 0; - } - case 'Q': - case '?': - case '*': - { - /* Query-match action */ - status = _parse_query_action(s); - break; - } - case '=': - { - /* Set parameter */ - status = _parse_set_param(s); - break; - } - default: - { - status = -1; - break; - } + asldebug("_asl_dir_today_open: No directory at path %s\n", r->dst->path); + return -1; } - if (status != 0) + asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private; + + memset(&ctm, 0, sizeof(struct tm)); + if (tick == NULL) { - 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); + now = time(NULL); + tick = (const time_t *)&now; + } - asl_log_string(str); - if (str != NULL) free(str); + if (localtime_r(tick, &ctm) == NULL) + { + asldebug("_asl_dir_today_open: localtime_r error %s\n", strerror(errno)); + return -1; } - return status; -} + /* checks file_max and closes if required */ + status = _act_checkpoint(r, CHECKPOINT_TEST); + if (status == 1) trigger_aslmanager(); -static void -_act_notify(action_rule_t *r) -{ - if (r == NULL) return; - if (r->options == NULL) return; + if (as_data->aslfile != NULL) + { + /* Check the date */ + if ((as_data->p_year == ctm.tm_year) && (as_data->p_month == ctm.tm_mon) && (as_data->p_day == ctm.tm_mday)) return 0; - notify_post(r->options); -} + /* Wrong date, close the current file */ + _asl_dir_today_close(r); + } -static void -_act_broadcast(action_rule_t *r, asl_msg_t *msg) -{ - FILE *pw; - const char *val; + /* Open data file */ - if (r == NULL) return; - if (msg == NULL) return; + if (r->dst->flags & MODULE_FLAG_BASESTAMP) + { + char tstamp[32]; - val = r->options; - if (val == NULL) val = asl_get(msg, ASL_KEY_MSG); - if (val == NULL) return; + if (tick == NULL) + { + now = time(NULL); + tick = (const time_t *)&now; + } - pw = popen(_PATH_WALL, "w"); - if (pw < 0) + asl_make_timestamp(now, r->dst->flags, tstamp, sizeof(tstamp)); + asprintf(&(r->dst->fname), "%s/%s.asl", r->dst->path, tstamp); + } + else { - asldebug("%s: error sending wall message: %s\n", MY_ID, strerror(errno)); - return; + asprintf(&(r->dst->fname), "%s/%d.%02d.%02d.asl", r->dst->path, ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday); } - fprintf(pw, "%s", val); - pclose(pw); -} -static void -_act_access_control(action_rule_t *r, asl_msg_t *msg) -{ - int32_t ruid, rgid; - char *p; + if (r->dst->fname == NULL) + { + asldebug("_asl_dir_today_open: asprintf error %s\n", strerror(errno)); + return -1; + } - ruid = atoi(r->options); - rgid = -1; - p = strchr(r->options, ' '); - if (p == NULL) p = strchr(r->options, '\t'); - if (p != NULL) +#if TARGET_IPHONE_SIMULATOR + uid_t uid = -1; + gid_t gid = -1; +#else + uid_t uid = r->dst->uid[0]; + gid_t gid = r->dst->gid[0]; +#endif + + mask = umask(0); + status = asl_file_open_write(r->dst->fname, (r->dst->mode & 00666), uid, gid, &(as_data->aslfile)); + umask(mask); + + if (status != ASL_STATUS_OK) { - *p = '\0'; - p++; - rgid = atoi(p); + asldebug("_asl_dir_today_open: asl_file_open_write %s error %s\n", r->dst->fname, asl_core_error(status)); + free(r->dst->fname); + r->dst->fname = NULL; + return -1; } - if (ruid != -1) asl_set((aslmsg)msg, ASL_KEY_READ_UID, r->options); - if (p != NULL) + if (fseek(as_data->aslfile->store, 0, SEEK_END) != 0) { - if (rgid != -1) asl_set((aslmsg)msg, ASL_KEY_READ_GID, p); - p--; - *p = ' '; + asldebug("_asl_dir_today_open: fseek %s error %s\n", r->dst->fname, strerror(errno)); + free(r->dst->fname); + r->dst->fname = NULL; + return -1; } -} -static uint32_t -_act_store_file_setup(struct store_data *sd) -{ - uint32_t status; + as_data->p_year = ctm.tm_year; + as_data->p_month = ctm.tm_mon; + as_data->p_day = ctm.tm_mday; - 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; + /* create aslfile_monitor */ + int fd = fileno(as_data->aslfile->store); + asl_file_t *aslf = as_data->aslfile; - status = asl_file_read_set_position(sd->store, ASL_FILE_POSITION_LAST); - if (status != ASL_STATUS_OK) return status; + as_data->aslfile_monitor = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_DELETE, asl_action_queue); + if (as_data->aslfile_monitor != NULL) + { + dispatch_source_set_event_handler(as_data->aslfile_monitor, ^{ + _act_dst_close(r, DST_CLOSE_DELETED); + }); - sd->next_id = sd->store->cursor_xid + 1; - if (fseek(sd->store->store, 0, SEEK_END) != 0) return ASL_STATUS_ACCESS_DENIED; + dispatch_source_set_cancel_handler(as_data->aslfile_monitor, ^{ + asldebug("cancel/close ASL file fd %d\n", fd); + asl_file_close(aslf); + }); - return ASL_STATUS_OK; + dispatch_resume(as_data->aslfile_monitor); + } + + asldebug("_asl_dir_today_open ASL file %s fd %d\n", r->dst->fname, fd); + + return 0; } -static uint32_t -_act_store_dir_setup(struct store_data *sd, time_t tick) +static void +_asl_file_close(asl_out_rule_t *r) { - struct tm ctm; - char *path; - struct stat sb; - uint64_t xid; - int status; - - if (sd == NULL) return ASL_STATUS_INVALID_STORE; - if (sd->dir == NULL) return ASL_STATUS_INVALID_STORE; + if (r == NULL) return; + if (r->dst == NULL) return; - /* get / set message id from StoreData file */ - xid = 0; + asl_action_asl_file_data_t *af_data = (asl_action_asl_file_data_t *)r->dst->private; + if (af_data->aslfile == NULL) return; - if (sd->storedata == NULL) + if (af_data->pending != 0) { - 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) - { - /* Unexpected stat error */ - free(path); - return ASL_STATUS_FAILED; - } - else - { - /* StoreData does not exist: create it */ - sd->storedata = fopen(path, "w"); - if (sd->storedata == NULL) - { - free(path); - return ASL_STATUS_FAILED; - } - } + char *str = NULL; + asprintf(&str, "[Sender syslogd] [Level 4] [PID %u] [Facility syslog] [Message ASL File %s was closed with %d pending messages]", global.pid, r->dst->fname, af_data->pending); + internal_log_message(str); + free(str); + } - free(path); + if (af_data->monitor == NULL) + { + /* + * This should never happen, but _asl_file_open allows + * dispatch_source_create to fail silently. If there is no dispatch + * source, we just close the file. + */ + asldebug("close ASL fd %d\n", fileno(af_data->aslfile->store)); + asl_file_close(af_data->aslfile); } else { - rewind(sd->storedata); - if (fread(&xid, sizeof(uint64_t), 1, sd->storedata) != 1) - { - fclose(sd->storedata); - sd->storedata = NULL; - return ASL_STATUS_READ_FAILED; - } + /* + * The monitor cancel handler will close the file. + */ + dispatch_source_cancel(af_data->monitor); + dispatch_release(af_data->monitor); + af_data->monitor = NULL; } - xid = asl_core_ntohq(xid); - xid++; - sd->next_id = xid; + af_data->aslfile = NULL; +} - xid = asl_core_htonq(xid); - rewind(sd->storedata); - status = fwrite(&xid, sizeof(uint64_t), 1, sd->storedata); - if (status != 1) +static int +_asl_file_open(asl_out_rule_t *r) +{ + int fd, status; + + if (r == NULL) return -1; + if (r->dst == NULL) return -1; + + asl_action_asl_file_data_t *af_data = (asl_action_asl_file_data_t *)r->dst->private; + if (af_data->aslfile != NULL) return 0; + + /* create path if necessary */ + status = asl_out_mkpath(global.asl_out_module, r); + if (status != 0) { - fclose(sd->storedata); - sd->storedata = NULL; - return ASL_STATUS_WRITE_FAILED; + asldebug("_asl_file_open: asl_out_mkpath %s failed\n", r->dst->path); + return -1; } - if ((sd->flags & ACT_STORE_FLAG_STAY_OPEN) == 0) + fd = _act_file_create_open(r->dst); + if (fd < 0) { - fclose(sd->storedata); - sd->storedata = NULL; + asldebug("_asl_file_open: _act_file_create_open %s failed %d %s\n", r->dst->fname, errno, strerror(errno)); + return -1; } - memset(&ctm, 0, sizeof(struct tm)); + close(fd); - 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 (r->dst->fname == NULL) return -1; - if (sd->store != NULL) asl_file_close(sd->store); + status = asl_file_open_write(r->dst->fname, 0, -1, -1, &(af_data->aslfile)); + if (status != ASL_STATUS_OK) + { + asldebug("_asl_file_open: asl_file_open_write %s failed %d %s\n", r->dst->fname, errno, strerror(errno)); + return -1; + } - sd->p_year = 0; - sd->p_month = 0; - sd->p_day = 0; + /* create monitor */ + fd = fileno(af_data->aslfile->store); + asl_file_t *aslf = af_data->aslfile; - if (sd->path != NULL) free(sd->path); - sd->path = NULL; + af_data->monitor = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_DELETE, asl_action_queue); + if (af_data->monitor != NULL) + { + dispatch_source_set_event_handler(af_data->monitor, ^{ + _act_dst_close(r, DST_CLOSE_DELETED); + }); - 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; + dispatch_source_set_cancel_handler(af_data->monitor, ^{ + asldebug("cancel/close ASL file fd %d\n", fd); + asl_file_close(aslf); + }); - sd->p_year = ctm.tm_year; - sd->p_month = ctm.tm_mon; - sd->p_day = ctm.tm_mday; + dispatch_resume(af_data->monitor); + } - return ASL_STATUS_OK; + asldebug("_asl_file_open ASL file %s fd %d\n", r->dst->fname, fd); + return 0; } static void -_act_store(action_rule_t *r, asl_msg_t *msg) +_text_file_close(asl_out_rule_t *r) { - struct store_data *sd; - asl_file_t *s; - uint8_t x; - uint32_t status; - uint64_t mid; - mode_t tmp_mode; - char *str, *opts, *p; - const char *val; - time_t tick; - - s = NULL; + asl_action_file_data_t *f_data = (asl_action_file_data_t *)r->dst->private; + if (f_data->fd < 0) return; - /* _act_store is not used for the main ASL data store */ - if (r->options == NULL) return; + if (f_data->pending != 0) + { + char *str = NULL; + asprintf(&str, "[Sender syslogd] [Level 4] [PID %u] [Facility syslog] [Message File %s was closed with %d pending messages]", global.pid, r->dst->fname, f_data->pending); + internal_log_message(str); + free(str); + } - if (r->data == NULL) + if (f_data->monitor == NULL) + { + /* + * This should never happen, but _text_file_open allows + * dispatch_source_create to fail silently. If there is no dispatch + * source, we just close the file. + */ + asldebug("close fd %d\n", f_data->fd); + close(f_data->fd); + } + else { - /* set up store data */ - sd = (struct store_data *)calloc(1, sizeof(struct store_data)); - if (sd == NULL) return; + /* + * The monitor cancel handler will close the file. + */ + dispatch_source_cancel(f_data->monitor); + dispatch_release(f_data->monitor); + f_data->monitor = NULL; + } - opts = r->options; - sd->store = NULL; + f_data->fd = -1; +} - if (r->action == ACTION_STORE) - { - sd->path = _next_word(&opts); - if (sd->path == NULL) - { - free(sd); - r->action = ACTION_NONE; - return; - } - } - else if (r->action == ACTION_STORE_DIR) +static int +_text_file_open(asl_out_rule_t *r) +{ + asl_action_file_data_t *f_data = (asl_action_file_data_t *)r->dst->private; + + if (f_data->fd < 0) + { + f_data->fd = _act_file_create_open(r->dst); + if (f_data->fd < 0) { - sd->dir = _next_word(&opts); - if (sd->dir == NULL) - { - free(sd); - r->action = ACTION_NONE; - return; - } + /* + * lazy path creation: create path and retry + * _act_file_create_open does not create the path + * so we do it here. + */ + int status = asl_out_mkpath(global.asl_out_module, r); + if (status != 0) return -1; + + f_data->fd = _act_file_create_open(r->dst); } - sd->mode = 0644; - sd->next_id = 0; - sd->uid = 0; - sd->gid = 0; - sd->flags = 0; + if (f_data->fd < 0) return -1; - while (NULL != (p = _next_word(&opts))) + f_data->monitor = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, f_data->fd, DISPATCH_VNODE_DELETE, asl_action_queue); + if (f_data->monitor != NULL) { - if (!strcmp(p, "stayopen")) sd->flags |= ACT_STORE_FLAG_STAY_OPEN; - else if (!strcmp(p, "exclude_asldb")) sd->flags |= ACT_STORE_FLAG_EXCLUDE_ASLDB; - else if (!strncmp(p, "mode=0", 6)) - { - sd->mode = 0; - x = *(p + 6); - if ((x < '0') || (x > '7')) - { - free(p); - if (sd->path != NULL) free(sd->path); - if (sd->dir != NULL) free(sd->dir); - free(sd); - r->action = ACTION_NONE; - return; - } + int ffd = f_data->fd; - tmp_mode = x - '0'; - sd->mode += tmp_mode << 6; + dispatch_source_set_event_handler(f_data->monitor, ^{ + asldebug("dispatch_source DISPATCH_VNODE_DELETE fd %d\n", ffd); + _act_dst_close(r, DST_CLOSE_DELETED); + }); - x = *(p + 7); - if ((x < '0') || (x > '7')) - { - free(p); - if (sd->path != NULL) free(sd->path); - if (sd->dir != NULL) free(sd->dir); - free(sd); - r->action = ACTION_NONE; - return; - } + dispatch_source_set_cancel_handler(f_data->monitor, ^{ + asldebug("cancel/close file fd %d\n", ffd); + close(ffd); + }); - tmp_mode = x - '0'; - sd->mode += tmp_mode << 3; + dispatch_resume(f_data->monitor); + } + } - x = *(p + 8); - if ((x < '0') || (x > '7')) - { - free(p); - if (sd->path != NULL) free(sd->path); - if (sd->dir != NULL) free(sd->dir); - free(sd); - r->action = ACTION_NONE; - return; - } + return 0; +} - tmp_mode = x - '0'; - sd->mode += tmp_mode; - } - else if (!strncmp(p, "mode=", 5)) sd->mode = atoi(p+4); - else if (!strncmp(p, "uid=", 4)) sd->uid = atoi(p+4); - else if (!strncmp(p, "gid=", 4)) sd->gid = atoi(p+4); +static int +_act_dst_open(asl_out_rule_t *r, const time_t *tick, uint64_t xid) +{ + if (r == NULL) return -1; + if (r->dst == NULL) return -1; + if (r->dst->private == NULL) return -1; + + if (r->action == ACTION_ASL_DIR) + { + if (_asl_dir_today_open(r, tick) != 0) + { + asldebug("_act_dst_open:_asl_dir_today_open %s FAILED!\n", r->dst->path); + return -1; + } - free(p); - p = NULL; + if (_asl_dir_storedata_open(r, xid) != 0) + { + asldebug("_act_dst_open:_asl_dir_storedata_open %s FAILED! Closing today file\n", r->dst->path); + _asl_dir_today_close(r); + return -1; } - r->data = sd; + return 0; } - else + + if (r->action == ACTION_ASL_FILE) { - sd = (struct store_data *)r->data; + return _asl_file_open(r); } - if (r->action == ACTION_STORE_DIR) + if (r->action == ACTION_FILE) { - val = asl_get(msg, ASL_KEY_TIME); - if (val == NULL) return; + return _text_file_open(r); + } - 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)); + return -1; +} - /* disable further activity */ - asl_file_close(sd->store); - sd->store = NULL; - r->action = ACTION_NONE; - return; - } - } +static void +_act_dst_close(asl_out_rule_t *r, int why) +{ + if (r == NULL) return; + if (r->dst == NULL) return; + if (r->dst->private == NULL) return; - if (sd->store == NULL) + if (r->action == ACTION_ASL_DIR) { - s = NULL; - status = asl_file_open_write(sd->path, sd->mode, sd->uid, sd->gid, &s); - if ((status != ASL_STATUS_OK) || (s == NULL)) - { - asldebug("asl_file_open_write %s failed: %s\n", sd->path, asl_core_error(status)); + asldebug("_act_dst_close: %s ASL DIR %s\n", why_str[why], r->dst->path); + if (why != DST_CLOSE_CHECKPOINT) _asl_dir_storedata_close(r); + _asl_dir_today_close(r); + } + else if (r->action == ACTION_ASL_FILE) + { + asldebug("_act_dst_close: %s ASL FILE %s\n", why_str[why], (r->dst->fname == NULL) ? r->dst->path : r->dst->fname); + _asl_file_close(r); + } + else if (r->action == ACTION_FILE) + { + asldebug("_act_dst_close: %s FILE %s\n", why_str[why], (r->dst->fname == NULL) ? r->dst->path : r->dst->fname); + _text_file_close(r); + } +} - /* disable further activity */ - asl_file_close(sd->store); - sd->store = NULL; - r->action = ACTION_NONE; - return; - } +static uint32_t +_act_store_file_setup(asl_out_module_t *m, asl_out_rule_t *r) +{ + uint32_t status; + asl_action_asl_file_data_t *af_data; - sd->store = s; - } + if (r == NULL) return ASL_STATUS_INVALID_STORE; + if (r->dst == NULL) return ASL_STATUS_INVALID_STORE; + if (r->dst->private == NULL) return ASL_STATUS_INVALID_STORE; - if (r->action != ACTION_STORE_DIR) + af_data = (asl_action_asl_file_data_t *)r->dst->private; + if (af_data->aslfile != NULL) { - 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)); - - /* disable further activity */ - asl_file_close(sd->store); - sd->store = NULL; - r->action = ACTION_NONE; - return; - } + af_data->next_id++; + return ASL_STATUS_OK; } - mid = sd->next_id; + if (_act_dst_open(r, NULL, 0) != 0) return ASL_STATUS_WRITE_FAILED; - status = asl_file_save(sd->store, msg, &mid); + status = asl_file_read_set_position(af_data->aslfile, ASL_FILE_POSITION_LAST); if (status != ASL_STATUS_OK) { - asldebug("asl_file_save %s failed: %s\n", sd->path, asl_core_error(status)); - - /* disable further activity on this file */ - asl_file_close(sd->store); - sd->store = NULL; - r->action = ACTION_NONE; - return; + asldebug("_act_store_file_setup: asl_file_read_set_position failed %d %s\n", status, asl_core_error(status)); + _act_dst_close(r, DST_CLOSE_ERROR); + return status; } - if ((sd->flags & ACT_STORE_FLAG_STAY_OPEN) == 0) + af_data->next_id = af_data->aslfile->cursor_xid + 1; + if (fseek(af_data->aslfile->store, 0, SEEK_END) != 0) { - asl_file_close(sd->store); - sd->store = NULL; + asldebug("_act_store_file_setup: fseek failed %d %s\n", errno, strerror(errno)); + _act_dst_close(r, DST_CLOSE_ERROR); + return ASL_STATUS_WRITE_FAILED; } - if (sd->flags & ACT_STORE_FLAG_EXCLUDE_ASLDB) - { - 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); - } - } - } + return ASL_STATUS_OK; } -static void -_act_forward(action_rule_t *r, asl_msg_t *msg) +/* + * _act_store_dir_setup + * + * Opens StoreData file and today's file + * Reads ASL Message ID from StoreData file + * Writes ASL Message ID + 1 to StoreData file + */ +static uint32_t +_act_store_dir_setup(asl_out_module_t *m, asl_out_rule_t *r, time_t tick) { - /* To do: Add a "forward" action to asl.conf */ -} + uint64_t xid; + int status; + asl_action_asl_store_data_t *as_data; -static void -send_to_asl_store(asl_msg_t *msg) -{ - const char *vlevel, *val; - uint64_t v64; - uint32_t status, level, lmask; - int x, log_me; - action_rule_t *r; + if (r == NULL) return ASL_STATUS_INVALID_STORE; + if (r->dst == NULL) return ASL_STATUS_INVALID_STORE; + if (r->dst->private == NULL) return ASL_STATUS_INVALID_STORE; + if (r->dst->path == NULL) return ASL_STATUS_INVALID_STORE; - /* ASLOption "ignore" keeps a message out of the ASL datastore */ - if (asl_check_option(msg, ASL_OPT_IGNORE) != 0) return; + as_data = (asl_action_asl_store_data_t *)r->dst->private; - if (filter_token == -1) + if (_act_dst_open(r, NULL, as_data->next_id) != 0) { - /* set up com.apple.syslog.asl_filter */ - status = notify_register_check(NOTIFY_SYSTEM_ASL_FILTER, &filter_token); - if (status != NOTIFY_STATUS_OK) - { - filter_token = -1; - } - else - { - status = notify_check(filter_token, &x); - if (status == NOTIFY_STATUS_OK) - { - v64 = global.asl_log_filter; - status = notify_set_state(filter_token, v64); - } - if (status != NOTIFY_STATUS_OK) - { - notify_cancel(filter_token); - filter_token = -1; - } - } + asldebug("_act_store_dir_setup: _act_dst_open %s failed\n", r->dst->path); + return ASL_STATUS_WRITE_FAILED; } - /* ASLOption "store" forces a message to be saved */ - log_me = asl_check_option(msg, ASL_OPT_STORE); - if (log_me == 1) + /* get / set message id from StoreData file */ + xid = 0; + rewind(as_data->storedata); + if (fread(&xid, sizeof(uint64_t), 1, as_data->storedata) != 1) { - db_save_message(msg); - return; + asldebug("_act_store_dir_setup: storedata read failed %d %s\n", errno, strerror(errno)); + _act_dst_close(r, DST_CLOSE_ERROR); + return ASL_STATUS_READ_FAILED; } - log_me = 0; - if (filter_token >= 0) - { - x = 0; - status = notify_check(filter_token, &x); - if ((status == NOTIFY_STATUS_OK) && (x == 1)) - { - v64 = 0; - status = notify_get_state(filter_token, &v64); - if ((status == NOTIFY_STATUS_OK) && (v64 != 0)) global.asl_log_filter = v64; - } - } + xid = asl_core_ntohq(xid); + xid++; + as_data->next_id = xid; - /* PID 0 (kernel) or PID 1 (launchd) messages are saved */ - val = asl_get(msg, ASL_KEY_PID); - if ((val != NULL) && (atoi(val) <= 1)) log_me = 1; - else + xid = asl_core_htonq(xid); + rewind(as_data->storedata); + status = fwrite(&xid, sizeof(uint64_t), 1, as_data->storedata); + if (status != 1) { - vlevel = asl_get(msg, ASL_KEY_LEVEL); - level = 7; - if (vlevel != NULL) level = atoi(vlevel); - lmask = ASL_FILTER_MASK(level); - if ((lmask & global.asl_log_filter) != 0) log_me = 1; + asldebug("_act_store_dir_setup: storedata write failed %d %s\n", errno, strerror(errno)); + _act_dst_close(r, DST_CLOSE_ERROR); + return ASL_STATUS_WRITE_FAILED; } - if (log_me == 0) return; + fflush(as_data->storedata); - /* if there are no rules, save the message */ - if (asl_datastore_rule == NULL) + if (fseek(as_data->aslfile->store, 0, SEEK_END) != 0) { - db_save_message(msg); - return; + asldebug("_act_store_dir_setup: aslfile fseek failed %d %s\n", errno, strerror(errno)); + _act_dst_close(r, DST_CLOSE_ERROR); + return ASL_STATUS_FAILED; } - for (r = asl_datastore_rule; r != NULL; r = r->next) - { - if (asl_msg_cmp(r->query, msg) == 1) - { - /* if any rule matches, save the message (once!) */ - db_save_message(msg); - return; - } - } + return ASL_STATUS_OK; } -int -asl_action_sendmsg(asl_msg_t *msg, const char *outid) +static void +_asl_action_asl_store_data_free(asl_action_asl_store_data_t *as_data) { - action_rule_t *r; + if (as_data == NULL) return; + free(as_data); +} - if (reset == RESET_CONFIG) _do_reset(); +static void +_asl_action_asl_file_data_free(asl_action_asl_file_data_t *af_data) +{ + if (af_data == NULL) return; + free(af_data); +} - if (msg == NULL) return -1; +static void +_asl_action_file_data_free(asl_action_file_data_t *f_data) +{ + if (f_data == NULL) return; - for (r = asl_action_rule; r != NULL; r = r->next) + if (f_data->dup_timer != NULL) { - if (asl_msg_cmp(r->query, msg) == 1) + if (f_data->last_count == 0) { - if (r->action == ACTION_NONE) continue; - else if (r->action == ACTION_IGNORE) return 0; - 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_STORE) _act_store(r, msg); - else if (r->action == ACTION_STORE_DIR) _act_store(r, msg); - else if (r->action == ACTION_BROADCAST) _act_broadcast(r, msg); - else if (r->action == ACTION_FORWARD) _act_forward(r, msg); + /* + * The timer exists, but last_count is zero, so the timer is suspended. + * Sources must not be released in when suspended. + * So we resume it so that we can release it. + */ + dispatch_resume(f_data->dup_timer); } + + dispatch_release(f_data->dup_timer); } - send_to_asl_store(msg); + free(f_data->last_msg); + free(f_data); +} - return 0; +static void +_asl_action_set_param_data_free(asl_action_set_param_data_t *spdata) +{ + if (spdata != NULL) notify_cancel(spdata->token); + free(spdata); } -static int -_parse_config_file(const char *name) +static void +_asl_action_save_failed(const char *where, asl_out_module_t *m, asl_out_rule_t *r, uint32_t status) { - FILE *cf; - char *line; + if (r->dst->flags & MODULE_FLAG_SOFT_WRITE) return; - cf = fopen(name, "r"); - if (cf == NULL) return 1; + r->dst->fails++; + asldebug("%s: %s save to %s failed: %s\n", where, m->name, r->dst->path, asl_core_error(status)); - while (NULL != (line = get_line_from_file(cf))) + /* disable further activity after multiple failures */ + if (r->dst->fails > MAX_FAILURES) { - _parse_line(line); - free(line); - } + char *str = NULL; + asprintf(&str, "[Sender syslogd] [Level 3] [PID %u] [Facility syslog] [Message Disabling module %s writes to %s following %u failures (%s)]", global.pid, m->name, r->dst->path, r->dst->fails, asl_core_error(status)); + internal_log_message(str); + free(str); - fclose(cf); + if (r->action == ACTION_ASL_DIR) _asl_action_asl_store_data_free((asl_action_asl_store_data_t *)r->dst->private); + else if (r->action == ACTION_ASL_FILE) _asl_action_asl_file_data_free((asl_action_asl_file_data_t *)r->dst->private); + else if (r->action == ACTION_FILE) _asl_action_file_data_free((asl_action_file_data_t *)r->dst->private); - return 0; + r->dst->private = NULL; + r->action = ACTION_NONE; + } } -int -asl_action_init(void) -{ +/* + * Save a message in an ASL file. + */ +static int +_act_store_file(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg) +{ + asl_action_asl_file_data_t *af_data; + uint32_t status; + uint64_t mid; + + if (r == NULL) return ACTION_STATUS_ERROR; + if (r->dst == NULL) return ACTION_STATUS_ERROR; + if (r->dst->private == NULL) return ACTION_STATUS_ERROR; + + af_data = (asl_action_asl_file_data_t *)r->dst->private; + if (af_data->pending > 0) af_data->pending--; + + status = _act_store_file_setup(m, r); + if (status == ASL_STATUS_OK) + { + af_data->last_time = time(NULL); + + r->dst->fails = 0; + mid = af_data->next_id; + + /* save message to file and update dst size */ + status = asl_file_save(af_data->aslfile, msg, &mid); + if (status == ASL_STATUS_OK) + { + r->dst->size = af_data->aslfile->file_size; + + if (_act_checkpoint(r, CHECKPOINT_TEST) == 1) trigger_aslmanager(); + } + } + + if (status != ASL_STATUS_OK) + { + _asl_action_save_failed("_act_store_file", m, r, status); + return ACTION_STATUS_ERROR; + } + + return ACTION_STATUS_OK; +} + +/* + * Save a message in an ASL data store. + */ +static int +_act_store_dir(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg) +{ + asl_action_asl_store_data_t *as_data; + uint32_t status; + uint64_t mid; + const char *val; + time_t tick; + + if (r == NULL) return ACTION_STATUS_ERROR; + if (r->dst == NULL) return ACTION_STATUS_ERROR; + if (r->dst->private == NULL) return ACTION_STATUS_ERROR; + + as_data = (asl_action_asl_store_data_t *)r->dst->private; + if (as_data->pending > 0) as_data->pending--; + + val = asl_msg_get_val_for_key(msg, ASL_KEY_TIME); + if (val == NULL) return ACTION_STATUS_ERROR; + + tick = atol(val); + + status = _act_store_dir_setup(m, r, tick); + if (status == ASL_STATUS_OK) + { + as_data->last_time = time(NULL); + + r->dst->fails = 0; + mid = as_data->next_id; + status = asl_file_save(as_data->aslfile, msg, &mid); + if (status == ASL_STATUS_OK) r->dst->size = as_data->aslfile->file_size; + else asldebug("_act_store_dir asl_file_save FAILED %s\n", asl_core_error(status)); + //TODO: checkpoint here? + /* + * Currently, the checkpoint is in _asl_dir_today_open(). + * Moving it here would be in keeping with the way that + * _act_store_file() and _act_file_final() do checkpoints. + */ + } + + if (status != ASL_STATUS_OK) + { + _asl_action_save_failed("_act_store_dir", m, r, status); + return ACTION_STATUS_ERROR; + } + + return ACTION_STATUS_OK; +} + +static void +_act_store_final(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg) +{ + if (r->action == ACTION_ASL_DIR) _act_store_dir(m, r, msg); + else _act_store_file(m, r, msg); +} + +/* + * Save a message to an ASL format file (ACTION_ASL_FILE) + * or to an ASL directory (ACTION_ASL_DIR). + */ +static void +_act_store(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg) +{ + if (r == NULL) return; + if (r->dst == NULL) return; + if (msg == NULL) return; + if (m == NULL) return; + if ((m->flags & MODULE_FLAG_ENABLED) == 0) return; + + if (r->dst->flags & MODULE_FLAG_HAS_LOGGED) return; + + r->dst->flags |= MODULE_FLAG_HAS_LOGGED; + if (r->action == ACTION_ASL_DIR) + { + asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private; + if (as_data != NULL) as_data->pending++; + } + else if (r->action == ACTION_ASL_FILE) + { + asl_action_asl_file_data_t *af_data = (asl_action_asl_file_data_t *)r->dst->private; + if (af_data != NULL) af_data->pending++; + } + +#if TARGET_OS_EMBEDDED + if (r->dst->flags & MODULE_FLAG_CRASHLOG) + { + _crashlog_queue_check(); + asl_msg_retain(msg); + dispatch_async(crashlog_queue, ^{ + dispatch_async(asl_action_queue, ^{ + _act_store_final(m, r, msg); + asl_msg_release(msg); + }); + }); + return; + } +#endif + + _act_store_final(m, r, msg); +} + +static int +_send_repeat_msg(asl_out_rule_t *r) +{ + asl_action_file_data_t *f_data; + char vt[32], *msg; + int len, status; + time_t now = time(NULL); + + if (r == NULL) return -1; + if (r->dst == NULL) return -1; + if (r->dst->private == NULL) return -1; + + f_data = (asl_action_file_data_t *)r->dst->private; + + free(f_data->last_msg); + f_data->last_msg = NULL; + + if (f_data->last_count == 0) return 0; + + /* stop the timer */ + dispatch_suspend(f_data->dup_timer); + + memset(vt, 0, sizeof(vt)); + ctime_r(&now, vt); + vt[19] = '\0'; + + msg = NULL; + asprintf(&msg, "%s --- last message repeated %u time%s ---\n", vt + 4, f_data->last_count, (f_data->last_count == 1) ? "" : "s"); + f_data->last_count = 0; + f_data->last_time = now; + if (msg == NULL) return -1; + + if (f_data->fd < 0) f_data->fd = _act_file_create_open(r->dst); + + len = strlen(msg); + status = write(f_data->fd, msg, len); + free(msg); + + if ((status < 0) || (status < len)) + { + asldebug("%s: error writing repeat message (%s): %s\n", MY_ID, r->dst->fname, strerror(errno)); + return -1; + } + + return 0; +} + +static void +_start_cycling() +{ + struct timespec midnight; + struct tm t; + time_t x; + + x = time(NULL); + + if (checkpoint_timer != NULL) return; + + localtime_r(&x, &t); + + t.tm_sec = 0; + t.tm_min = 0; + t.tm_hour = 0; + t.tm_mday++; + + x = mktime(&t); + midnight.tv_sec = x; + midnight.tv_nsec = 0; + + checkpoint_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, asl_action_queue); + dispatch_source_set_timer(checkpoint_timer, dispatch_walltime(&midnight, 0), NSEC_PER_SEC * SEC_PER_DAY, 0); + dispatch_source_set_event_handler(checkpoint_timer, ^{ _act_file_checkpoint_all(CHECKPOINT_FORCE); }); + dispatch_resume(checkpoint_timer); +} + +/* check if a module path (mpath) matches a user path (upath) */ +static bool +_act_file_equal(const char *mpath, const char *upath) +{ + const char *slash; + + /* NULL upath means user wants to match all files */ + if (upath == NULL) return true; + + if (mpath == NULL) return false; + + /* check for exact match */ + if (!strcmp(mpath, upath)) return true; + + /* upath may be the last component of mpath */ + slash = strrchr(mpath, '/'); + if (slash == NULL) return false; + + if (!strcmp(slash + 1, upath)) return true; + return false; +} + +static int +_act_file_checkpoint(asl_out_module_t *m, const char *path, uint32_t force) +{ + asl_out_rule_t *r; + int did_checkpoint = 0; + + if (m == NULL) return 0; + + + for (r = m->ruleset; r != NULL; r = r->next) + { + if ((r->action == ACTION_FILE) || (r->action == ACTION_ASL_FILE)) + { + if (r->dst->flags & MODULE_FLAG_ROTATE) + { + if (_act_file_equal(r->dst->path, path)) + { + if (force & CHECKPOINT_CRASH) + { + if (r->dst->flags & MODULE_FLAG_CRASHLOG) + { + if (_act_checkpoint(r, CHECKPOINT_FORCE) > 0) + { + did_checkpoint = 1; + _act_dst_close(r, DST_CLOSE_CHECKPOINT); + } + } + } + else + { + if (_act_checkpoint(r, force) > 0) + { + did_checkpoint = 1; + _act_dst_close(r, DST_CLOSE_CHECKPOINT); + } + } + } + } + } + } + + return did_checkpoint; +} + +static int +_act_file_checkpoint_all(uint32_t force) +{ + asl_out_module_t *m; + int did_checkpoint = 0; + + for (m = global.asl_out_module; m != NULL; m = m->next) + { + if (_act_file_checkpoint(m, NULL, force) > 0) did_checkpoint = 1; + } + + trigger_aslmanager(); + + return did_checkpoint; +} + +/* + * Save a message in a plain text file. + */ +static void +_act_file_final(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg) +{ + asl_action_file_data_t *f_data; + int is_dup; + uint32_t len, msg_hash = 0; + char *str; + time_t now; + + if (r->dst->private == NULL) return; + + f_data = (asl_action_file_data_t *)r->dst->private; + if (f_data->pending > 0) f_data->pending--; + + /* + * If print format is std, bsd, or msg, then skip messages with + * no ASL_KEY_MSG, or without a value for it. + */ + if (r->dst->flags & MODULE_FLAG_STD_BSD_MSG) + { + const char *msgval = NULL; + if (asl_msg_lookup(msg, ASL_KEY_MSG, &msgval, NULL) != 0) return; + if (msgval == NULL) return; + } + + now = time(NULL); + + is_dup = 0; + + str = asl_format_message(msg, r->dst->fmt, r->dst->tfmt, ASL_ENCODE_SAFE, &len); + + if (r->dst->flags & MODULE_FLAG_COALESCE) + { + if (f_data->dup_timer == NULL) + { + /* create a timer to flush dups on this file */ + f_data->dup_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, asl_action_queue); + dispatch_source_set_event_handler(f_data->dup_timer, ^{ _send_repeat_msg(r); }); + } + + if ((global.bsd_max_dup_time > 0) && (str != NULL) && (f_data->last_msg != NULL)) + { + msg_hash = asl_core_string_hash(str + 16, len - 16); + if ((f_data->last_hash == msg_hash) && (!strcmp(f_data->last_msg, str + 16))) + { + if ((now - f_data->last_time) < global.bsd_max_dup_time) is_dup = 1; + } + } + } + + if (is_dup == 1) + { + if (f_data->last_count == 0) + { + /* start the timer */ + dispatch_source_set_timer(f_data->dup_timer, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * global.bsd_max_dup_time), DISPATCH_TIME_FOREVER, 0); + dispatch_resume(f_data->dup_timer); + } + + f_data->last_count++; + } + else + { + if (_act_dst_open(r, NULL, 0) != 0) + { + _asl_action_save_failed("_act_file", m, r, ASL_STATUS_FAILED); + free(str); + return; + } + else + { + r->dst->fails = 0; + } + + /* + * The current message is not a duplicate. If f_data->last_count > 0 + * we need to write a "last message repeated N times" log entry. + * _send_repeat_msg will free last_msg and do nothing if + * last_count == 0, but we test and free here to avoid a function call. + */ + if (f_data->last_count > 0) + { + _send_repeat_msg(r); + } + else + { + free(f_data->last_msg); + f_data->last_msg = NULL; + } + + if (str != NULL) f_data->last_msg = strdup(str + 16); + + f_data->last_hash = msg_hash; + f_data->last_count = 0; + f_data->last_time = now; + + if ((str != NULL) && (len > 1)) + { + /* write line to file and update dst size */ + size_t bytes = write(f_data->fd, str, len - 1); + if (bytes > 0) r->dst->size += bytes; + + if (_act_checkpoint(r, CHECKPOINT_TEST) == 1) trigger_aslmanager(); + } + } + + free(str); +} + +static void +_act_file(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg) +{ + asl_action_file_data_t *f_data; + + if (r == NULL) return; + if (msg == NULL) return; + if (m == NULL) return; + if ((m->flags & MODULE_FLAG_ENABLED) == 0) return; + if (r->dst == NULL) return; + if (r->dst->private == NULL) return; + + if (r->dst->flags & MODULE_FLAG_HAS_LOGGED) return; + + r->dst->flags |= MODULE_FLAG_HAS_LOGGED; + f_data = (asl_action_file_data_t *)r->dst->private; + if (f_data != NULL) f_data->pending++; + +#if TARGET_OS_EMBEDDED + if (r->dst->flags & MODULE_FLAG_CRASHLOG) + { + _crashlog_queue_check(); + asl_msg_retain(msg); + dispatch_async(crashlog_queue, ^{ + dispatch_async(asl_action_queue, ^{ + _act_file_final(m, r, msg); + asl_msg_release(msg); + }); + }); + return; + } +#endif + + _act_file_final(m, r, msg); +} + +static void +_act_forward(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg) +{ + /* To do: Add a "forward" action to asl.conf */ +} + +static void +_act_control(asl_out_module_t *m, asl_out_rule_t *r, asl_msg_t *msg) +{ + const char *p; + + if (m == NULL) return; + if (r == NULL) return; + + p = asl_msg_get_val_for_key(msg, ASL_KEY_MODULE); + + if (r->options == NULL) return; + + if (!strcmp(r->options, "enable")) + { + m->flags |= MODULE_FLAG_ENABLED; + } + else if (!strcmp(r->options, "disable")) + { + m->flags &= ~MODULE_FLAG_ENABLED; + } + else if ((!strcmp(r->options, "checkpoint")) || (!strcmp(r->options, "rotate"))) + { + _act_file_checkpoint(m, NULL, CHECKPOINT_FORCE); + } +} + +static void +_send_to_asl_store(asl_msg_t *msg) +{ + if ((global.asl_out_module != NULL) && ((global.asl_out_module->flags & MODULE_FLAG_ENABLED) == 0)) return; + + if (store_has_logged) return; + store_has_logged = true; + + db_save_message(msg); +} + +static int +_asl_out_process_message(asl_out_module_t *m, asl_msg_t *msg) +{ + asl_out_rule_t *r; + + if (m == NULL) return 1; + if (msg == NULL) return 1; + + /* reset flag bit used for duplicate avoidance */ + for (r = m->ruleset; r != NULL; r = r->next) + { + if ((r->action == ACTION_FILE) || (r->action == ACTION_ASL_DIR) || (r->action == ACTION_ASL_FILE)) + { + if (r->dst != NULL) r->dst->flags &= MODULE_FLAG_CLEAR_LOGGED; + } + } + + for (r = m->ruleset; r != NULL; r = r->next) + { + if (r->query == NULL) continue; + + /* ACTION_SET_FILE, ACTION_SET_PLIST, and ACTION_SET_PROF are handled independently */ + if ((r->action == ACTION_SET_FILE) || (r->action == ACTION_SET_PLIST) || (r->action == ACTION_SET_PROF)) continue; + + /* + * ACTION_CLAIM during processing is a filter. It will only be here if the option "only" + * was supplied. In this case we test the message against the query. If it does not + * match, we skip the message. + */ + if (r->action == ACTION_CLAIM) + { + if ((asl_msg_cmp(r->query, msg) != 1)) return 0; + } + + if ((asl_msg_cmp(r->query, msg) == 1)) + { + if (r->action == ACTION_NONE) continue; + else if (r->action == ACTION_IGNORE) return 1; + else if (r->action == ACTION_SKIP) return 0; + else if (r->action == ACTION_ASL_STORE) _send_to_asl_store(msg); + else if (r->action == ACTION_ACCESS) _act_access_control(m, r, msg); + else if (r->action == ACTION_SET_KEY) _act_set_key(m, r, msg); + else if (r->action == ACTION_UNSET_KEY) _act_unset_key(m, r, msg); + else if (r->action == ACTION_NOTIFY) _act_notify(m, r); + else if (r->action == ACTION_BROADCAST) _act_broadcast(m, r, msg); + else if (r->action == ACTION_FORWARD) _act_forward(m, r, msg); + else if (r->action == ACTION_CONTROL) _act_control(m, r, msg); + else if (r->action == ACTION_SET_PARAM) _act_out_set_param(m, r->options, true); + else if ((r->action == ACTION_ASL_FILE) || (r->action == ACTION_ASL_DIR)) _act_store(m, r, msg); + else if (r->action == ACTION_FILE) _act_file(m, r, msg); + } + } + + return 0; +} + +void +asl_out_message(asl_msg_t *msg) +{ + OSAtomicIncrement32(&global.asl_queue_count); + asl_msg_retain(msg); + + dispatch_async(asl_action_queue, ^{ + int ignore = 0; + const char *p; + time_t now = time(NULL); + asl_out_module_t *m = global.asl_out_module; + + store_has_logged = false; + + p = asl_msg_get_val_for_key(msg, ASL_KEY_MODULE); + if (p == NULL) + { + if ((action_asl_store_count == 0) || (asl_check_option(msg, ASL_OPT_STORE) == 1)) _send_to_asl_store(msg); + + ignore = _asl_out_process_message(m, msg); + if (ignore == 0) + { + if (m != NULL) m = m->next; + while (m != NULL) + { + _asl_out_process_message(m, msg); + m = m->next; + } + } + } + else + { + if (m != NULL) m = m->next; + while (m != NULL) + { + if (!strcmp(p, m->name)) _asl_out_process_message(m, msg); + m = m->next; + } + } + + p = asl_msg_get_val_for_key(msg, ASL_KEY_FINAL_NOTIFICATION); + if (p != NULL) asl_msg_set_key_val(msg, ASL_KEY_FREE_NOTE, p); + + asl_msg_release(msg); + OSAtomicDecrement32(&global.asl_queue_count); + + if ((now - sweep_time) >= IDLE_CLOSE) + { + _asl_action_close_idle_files(IDLE_CLOSE); + sweep_time = now; + } + }); +} + +static char * +_asl_action_profile_test(asl_out_module_t *m, asl_out_rule_t *r) +{ + const char *ident; + asl_msg_t *profile; + bool eval; + + /* ident is first message key */ + asl_msg_fetch(r->query, 0, &ident, NULL, NULL); + if (ident == NULL) + { + r->action = ACTION_NONE; + return NULL; + } + + profile = configuration_profile_to_asl_msg(ident); + eval = (asl_msg_cmp(r->query, profile) == 1); + _act_out_set_param(m, r->options, eval); + asl_msg_release(profile); + + return strdup(ident); +} + +static const char * +_asl_action_file_test(asl_out_module_t *m, asl_out_rule_t *r) +{ + const char *path; + struct stat sb; + int status; + bool eval; + + /* path is first message key */ + asl_msg_fetch(r->query, 0, &path, NULL, NULL); + if (path == NULL) + { + r->action = ACTION_NONE; + return NULL; + } + + memset(&sb, 0, sizeof(struct stat)); + status = stat(path, &sb); + eval = (status == 0); + _act_out_set_param(m, r->options, eval); + + return path; +} + +static void +_asl_action_handle_file_change_notification(int t) +{ + asl_out_module_t *m; + asl_out_rule_t *r; + + for (m = global.asl_out_module; m != NULL; m = m->next) + { + for (r = m->ruleset; r != NULL; r = r->next) + { + if (r->action == ACTION_SET_FILE) + { + asl_action_set_param_data_t *spdata = (asl_action_set_param_data_t *)r->private; + if ((spdata != NULL) && (spdata->token == t)) + { + _asl_action_file_test(m, r); + return; + } + } + else if (r->action == ACTION_SET_PLIST) + { + asl_action_set_param_data_t *spdata = (asl_action_set_param_data_t *)r->private; + if ((spdata != NULL) && (spdata->token == t)) + { + char *str = _asl_action_profile_test(m, r); + free(str); + return; + } + } + else if (r->action == ACTION_SET_PROF) + { + asl_action_set_param_data_t *spdata = (asl_action_set_param_data_t *)r->private; + if ((spdata != NULL) && (spdata->token == t)) + { + char *str = _asl_action_profile_test(m, r); + free(str); + return; + } + } + } + } + + asl_out_module_free(m); +} + +static void +_asl_action_post_process_rule(asl_out_module_t *m, asl_out_rule_t *r) +{ + if ((m == NULL) || (r == NULL)) return; + + if (m != global.asl_out_module) + { + /* check if any previous module has used this destination */ + asl_out_module_t *n; + bool search = true; + + if ((r->dst != NULL) && (r->dst->path != NULL)) + { + for (n = global.asl_out_module; search && (n != NULL) && (n != m); n = n->next) + { + asl_out_rule_t *s; + for (s = n->ruleset; search && (s != NULL); s = s->next) + { + if (s->action == ACTION_OUT_DEST) + { + if ((s->dst != NULL) && (s->dst->path != NULL) && (!strcmp(r->dst->path, s->dst->path))) + { + /* rule r of module m is using previously used dst of rule s of module n */ + asl_out_dst_data_release(r->dst); + r->dst = NULL; + + if (r->action == ACTION_OUT_DEST) + { + char *str = NULL; + asprintf(&str, "[Sender syslogd] [Level 5] [PID %u] [Message Configuration Notice:\nASL Module \"%s\" sharing output destination \"%s\" with ASL Module \"%s\".\nOutput parameters from ASL Module \"%s\" override any specified in ASL Module \"%s\".] [UID 0] [GID 0] [Facility syslog]", global.pid, m->name, s->dst->path, n->name, n->name, m->name); + internal_log_message(str); + free(str); + } + else + { + r->dst = asl_out_dst_data_retain(s->dst); + } + + search = false; + } + } + } + } + } + } + + if (r->action == ACTION_SET_PARAM) + { + if (r->query == NULL) _act_out_set_param(m, r->options, true); + } + else if (r->action == ACTION_CLAIM) + { + /* becomes ACTION_SKIP in com.apple.asl config */ + if (m != global.asl_out_module) + { + asl_out_rule_t *rule = (asl_out_rule_t *)calloc(1, sizeof(asl_out_rule_t)); + if (rule != NULL) + { + char *str = NULL; + asprintf(&str, "[Sender syslogd] [Level 5] [PID %u] [Message Configuration Notice:\nASL Module \"%s\" claims selected messages.\nThose messages may not appear in standard system log files or in the ASL database.] [UID 0] [GID 0] [Facility syslog]", global.pid, m->name); + internal_log_message(str); + free(str); + + rule->query = asl_msg_copy(r->query); + rule->action = ACTION_SKIP; + rule->next = global.asl_out_module->ruleset; + global.asl_out_module->ruleset = rule; + } + + /* + * After adding ACTION_SKIP to com.apple.asl module, the claim becomes a no-op in this module + * UNLESS the claim includes the option "only". In that case, the claim becomes a filter: + * any messages that DO NOT match the claim are skipped by this module. + */ + if (r->options == NULL) r->action = ACTION_NONE; + else if (strcmp(r->options, "only") != 0) r->action = ACTION_NONE; + } + } + else if (r->action == ACTION_ASL_STORE) + { + action_asl_store_count++; + } + else if (r->action == ACTION_ASL_DIR) + { + if (r->dst->private == NULL) r->dst->private = (asl_action_asl_store_data_t *)calloc(1, sizeof(asl_action_asl_store_data_t)); + } + else if (r->action == ACTION_ASL_FILE) + { + if (r->dst->private == NULL)r->dst->private = (asl_action_asl_file_data_t *)calloc(1, sizeof(asl_action_asl_file_data_t)); + } + else if (r->action == ACTION_FILE) + { + if (r->dst->private == NULL) r->dst->private = (asl_action_file_data_t *)calloc(1, sizeof(asl_action_file_data_t)); + if (r->dst->private != NULL) ((asl_action_file_data_t *)(r->dst->private))->fd = -1; + } + else if (r->action == ACTION_SET_PLIST) + { + char *ident =_asl_action_profile_test(m, r); + char *notify_key = configuration_profile_create_notification_key(ident); + free(ident); + + if (notify_key != NULL) + { + int status, token; + asl_action_set_param_data_t *spdata; + + status = notify_register_dispatch(notify_key, &token, asl_action_queue, ^(int t){ + _asl_action_handle_file_change_notification(t); + }); + + free(notify_key); + + spdata = (asl_action_set_param_data_t *)calloc(1, sizeof(asl_action_set_param_data_t)); + if (spdata == NULL) + { + notify_cancel(token); + } + else + { + spdata->token = token; + r->private = spdata; + } + } + } + else if (r->action == ACTION_SET_PROF) + { + char *ident =_asl_action_profile_test(m, r); + char *notify_key = configuration_profile_create_notification_key(ident); + free(ident); + + if (notify_key != NULL) + { + int status, token; + asl_action_set_param_data_t *spdata; + + status = notify_register_dispatch(notify_key, &token, asl_action_queue, ^(int t){ + _asl_action_handle_file_change_notification(t); + }); + + free(notify_key); + + spdata = (asl_action_set_param_data_t *)calloc(1, sizeof(asl_action_set_param_data_t)); + if (spdata == NULL) + { + notify_cancel(token); + } + else + { + spdata->token = token; + r->private = spdata; + } + } + } + else if (r->action == ACTION_SET_FILE) + { + char *notify_key; + const char *path =_asl_action_file_test(m, r); + + if (path != NULL) + { + asprintf(¬ify_key, "%s%s", NOTIFY_PATH_SERVICE, path); + if (notify_key != NULL) + { + int status, token; + asl_action_set_param_data_t *spdata; + + status = notify_register_dispatch(notify_key, &token, asl_action_queue, ^(int t){ + _asl_action_handle_file_change_notification(t); + }); + + free(notify_key); + + spdata = (asl_action_set_param_data_t *)calloc(1, sizeof(asl_action_set_param_data_t)); + if (spdata == NULL) + { + notify_cancel(token); + } + else + { + spdata->token = token; + r->private = spdata; + } + } + } + } +} + +static void +_asl_action_configure() +{ + asl_out_rule_t *r; + asl_out_module_t *m; + uint32_t flags = 0; + + if (global.asl_out_module == NULL) global.asl_out_module = asl_out_module_init(); + if (global.asl_out_module == NULL) return; + asldebug("%s: init\n", MY_ID); - query = asl_new(ASL_TYPE_QUERY); - aslevent_addmatch(query, MY_ID); - aslevent_addoutput(asl_action_sendmsg, MY_ID); + action_asl_store_count = 0; + + for (m = global.asl_out_module; m != NULL; m = m->next) + { + for (r = m->ruleset; r != NULL; r = r->next) + { + _asl_action_post_process_rule(m, r); + if (r->dst != NULL) flags |= (r->dst->flags & (MODULE_FLAG_ROTATE | MODULE_FLAG_CRASHLOG)); + } + } + + if (global.debug != 0) + { + FILE *dfp; + if (global.debug_file == NULL) dfp = fopen(_PATH_SYSLOGD_LOG, "a"); + else dfp = fopen(global.debug_file, "a"); + if (dfp != NULL) + { + for (m = global.asl_out_module; m != NULL; m = m->next) + { + fprintf(dfp, "module: %s%s\n", (m->name == NULL) ? "" : m->name, (m->flags & MODULE_FLAG_LOCAL) ? " (local)" : ""); + asl_out_module_print(dfp, m); + fprintf(dfp, "\n"); + } + fclose(dfp); + } + } + + sweep_time = time(NULL); + + if (flags & MODULE_FLAG_ROTATE) + { + _act_file_checkpoint_all(CHECKPOINT_TEST); + if (checkpoint_timer == NULL) _start_cycling(); + } +} + +int +asl_action_init(void) +{ + static dispatch_once_t once; + + dispatch_once(&once, ^{ + asl_action_queue = dispatch_queue_create("ASL Action Queue", NULL); +#if TARGET_OS_EMBEDDED + crashlog_queue = dispatch_queue_create("iOS CrashLog Queue", NULL); + notify_register_dispatch(CRASH_MOVER_SERVICE, &crashmover_token, asl_action_queue, ^(int unused) { + uint64_t cmstate = 0; + uint64_t oldstate = (crashmover_state == 0) ? 0llu : 1llu; + + uint32_t status = notify_get_state(crashmover_token, &cmstate); + if (status == 0) + { + if (cmstate != oldstate) + { + crashmover_state = 0; + if (cmstate == 1) crashmover_state = time(NULL); + + if (crashmover_state == 0) + { + asldebug("CrashMover finished\n"); + dispatch_resume(crashlog_queue); + } + else + { + asldebug("CrashMover active: suspending crashlog queue and closing files\n"); + dispatch_suspend(crashlog_queue); + _asl_action_close_idle_files(0); + } + } + } + }); +#endif + }); + + _asl_action_configure(); + + return 0; +} + +/* + * Close outputs and free modules. + */ +static void +_asl_action_free_modules(asl_out_module_t *m) +{ + asl_out_rule_t *r; + asl_out_module_t *x; + + /* + * asl_common frees a list of modules with asl_out_module_free. + * This loop frees the private data attached some modules. + */ + for (x = m; x != NULL; x = x->next) + { + for (r = x->ruleset; r != NULL; r = r->next) + { + if (r->action == ACTION_ASL_DIR) + { + _act_dst_close(r, DST_CLOSE_SHUTDOWN); + if (r->dst != NULL) + { + _asl_action_asl_store_data_free((asl_action_asl_store_data_t *)r->dst->private); + r->dst->private = NULL; + } + } + else if (r->action == ACTION_ASL_FILE) + { + _act_dst_close(r, DST_CLOSE_SHUTDOWN); + if (r->dst != NULL) + { + _asl_action_asl_file_data_free((asl_action_asl_file_data_t *)r->dst->private); + r->dst->private = NULL; + } + } + else if (r->action == ACTION_FILE) + { + _act_dst_close(r, DST_CLOSE_SHUTDOWN); + if (r->dst != NULL) + { + asl_action_file_data_t *f_data = (asl_action_file_data_t *)r->dst->private; + if (f_data != NULL) + { + /* flush repeat message if necessary */ + if (f_data->last_count > 0) _send_repeat_msg(r); + _asl_action_file_data_free(f_data); + r->dst->private = NULL; + } + } + } + else if (r->action == ACTION_SET_PLIST) + { + _asl_action_set_param_data_free((asl_action_set_param_data_t *)r->private); + } + else if (r->action == ACTION_SET_PROF) + { + _asl_action_set_param_data_free((asl_action_set_param_data_t *)r->private); + } + else if (r->action == ACTION_SET_FILE) + { + _asl_action_set_param_data_free((asl_action_set_param_data_t *)r->private); + } + } + } + + asl_out_module_free(m); +} + +static int +_asl_action_close_internal(void) +{ +#if TARGET_OS_EMBEDDED + if (crashmover_state != 0) + { + dispatch_resume(crashlog_queue); + crashmover_state = 0; + } + + /* wait for the crashlog_queue to flush before _asl_action_free_modules() */ + dispatch_sync(crashlog_queue, ^{ int x = 0; if (x == 1) x = 2; }); +#endif + + _asl_action_free_modules(global.asl_out_module); + global.asl_out_module = NULL; + sweep_time = time(NULL); + + return 0; +} + +static void +_asl_action_close_idle_files(time_t idle_time) +{ + asl_out_module_t *m; + time_t now = time(NULL); + + for (m = global.asl_out_module; m != NULL; m = m->next) + { + asl_out_rule_t *r; + + for (r = m->ruleset; r != NULL; r = r->next) + { + if (idle_time == 0) + { + if ((r->dst != NULL) && (r->dst->flags & MODULE_FLAG_CRASHLOG)) + { + _act_dst_close(r, DST_CLOSE_IDLE); + //TODO: can r->action even be ACTION_ASL_DIR? + /* if not, we can avoid the extra check here */ + if (r->action != ACTION_ASL_DIR) _act_checkpoint(r, CHECKPOINT_FORCE); + } + } + else if (r->action == ACTION_ASL_DIR) + { + if (r->dst != NULL) + { + asl_action_asl_store_data_t *as_data = (asl_action_asl_store_data_t *)r->dst->private; + if ((as_data != NULL) && (as_data->aslfile != NULL) && (as_data->pending == 0) && ((now - as_data->last_time) >= idle_time)) _act_dst_close(r, DST_CLOSE_IDLE); + } + } + else if (r->action == ACTION_ASL_FILE) + { + if (r->dst != NULL) + { + asl_action_asl_file_data_t *af_data = (asl_action_asl_file_data_t *)r->dst->private; + if ((af_data != NULL) && (af_data->aslfile != NULL) && (af_data->pending == 0) && ((now - af_data->last_time) >= idle_time)) _act_dst_close(r, DST_CLOSE_IDLE); + } + } + else if (r->action == ACTION_FILE) + { + if (r->dst != NULL) + { + asl_action_file_data_t *f_data = (asl_action_file_data_t *)r->dst->private; + if ((f_data != NULL) && (f_data->fd >= 0) && (f_data->pending == 0) && ((now - f_data->last_time) >= idle_time)) _act_dst_close(r, DST_CLOSE_IDLE); + } + } + } + } +} + +int +asl_action_close(void) +{ + dispatch_async(asl_action_queue, ^{ + _asl_action_close_internal(); + }); - _parse_config_file(_PATH_ASL_CONF); return 0; } int asl_action_reset(void) { - reset = global.reset; + dispatch_async(asl_action_queue, ^{ + _asl_action_close_internal(); + asl_action_init(); + }); + return 0; } +asl_out_module_t * +_asl_action_module_with_name(const char *name) +{ + asl_out_module_t *m; + + if (global.asl_out_module == NULL) return NULL; + if (name == NULL) return global.asl_out_module; + + for (m = global.asl_out_module; m != NULL; m = m->next) + { + if ((m->name != NULL) && (!strcmp(m->name, name))) return m; + } + + return NULL; +} + +/* + * called from control_message + * Used to control modules dynamically. + * Line format "@ module param [value ...]" + * + * Note this is synchronous on asl_action queue. + */ int -asl_action_close(void) +asl_action_control_set_param(const char *s) { - action_rule_t *r, *n; - struct store_data *sd; - n = NULL; - for (r = asl_action_rule; r != NULL; r = n) + __block char **l; + __block char *p; + uint32_t count = 0; + + if (s == NULL) return -1; + if (s[0] == '\0') return 0; + + /* skip '@' and whitespace */ + if (*s == '@') s++; + while ((*s == ' ') || (*s == '\t')) s++; + + l = explode(s, " \t"); + if (l != NULL) for (count = 0; l[count] != NULL; count++); + + /* at least 2 parameters (l[0] = module, l[1] = param) required */ + if (count < 2) + { + free_string_list(l); + return -1; + } + + if (global.asl_out_module == NULL) { - n = r->next; + asldebug("asl_action_control_set_param: no modules loaded\n"); + free_string_list(l); + return -1; + } - if (((r->action == ACTION_STORE) || (r->action == ACTION_STORE_DIR) || (r->action == ACTION_NONE)) && (r->data != NULL)) + /* create / modify a module */ + if ((!strcasecmp(l[1], "define")) && (strcmp(l[0], "*"))) + { + p = strdup(s); + if (p == NULL) { - sd = (struct store_data *)r->data; - if (sd->store != NULL) asl_file_close(sd->store); - if (sd->storedata != NULL) fclose(sd->storedata); - if (sd->path != NULL) free(sd->path); - if (sd->dir != NULL) free(sd->dir); - sd->store = NULL; - free(sd); + asldebug("asl_action_control_set_param: memory allocation failed\n"); + free_string_list(l); + return -1; } - if (r->query != NULL) asl_free(r->query); - if (r->options != NULL) free(r->options); + dispatch_sync(asl_action_queue, ^{ + asl_out_module_t *m; + asl_out_rule_t *r; - free(r); - } + /* skip name, whitespace, "define" */ + while ((*p != ' ') && (*p != '\t')) p++; + while ((*p == ' ') || (*p == '\t')) p++; + while ((*p != ' ') && (*p != '\t')) p++; - asl_action_rule = NULL; + m = _asl_action_module_with_name(l[0]); + if (m == NULL) + { + asl_out_module_t *x; - n = NULL; - for (r = asl_datastore_rule; r != NULL; r = n) - { - n = r->next; + m = asl_out_module_new(l[0]); + for (x = global.asl_out_module; x->next != NULL; x = x->next); + x->next = m; + } - if (r->query != NULL) asl_free(r->query); - if (r->options != NULL) free(r->options); + r = asl_out_module_parse_line(m, p); + if (r != NULL) + { + _asl_action_post_process_rule(m, r); + if ((r->dst != NULL) && (r->dst->flags & MODULE_FLAG_ROTATE)) + { + _act_file_checkpoint_all(CHECKPOINT_TEST); + if (checkpoint_timer == NULL) _start_cycling(); + } + } + }); - free(r); + free(p); + free_string_list(l); + return 0; } - asl_datastore_rule = NULL; + dispatch_sync(asl_action_queue, ^{ + uint32_t intval; + int do_all = 0; + asl_out_module_t *m; + + if (!strcmp(l[0], "*")) + { + do_all = 1; + m = _asl_action_module_with_name(NULL); + } + else + { + m = _asl_action_module_with_name(l[0]); + } + + while (m != NULL) + { + if (!strcasecmp(l[1], "enable")) + { + intval = 1; + + /* don't do enable for ASL_MODULE_NAME if input name is "*" */ + if ((do_all == 0) || (strcmp(m->name, ASL_MODULE_NAME))) + { + /* @ module enable {0|1} */ + if (count > 2) intval = atoi(l[2]); + + if (intval == 0) m->flags &= ~MODULE_FLAG_ENABLED; + else m->flags |= MODULE_FLAG_ENABLED; + } + } + else if (!strcasecmp(l[1], "checkpoint")) + { + /* @ module checkpoint [file] */ + if (count > 2) _act_file_checkpoint(m, l[2], CHECKPOINT_FORCE); + else _act_file_checkpoint(m, NULL, CHECKPOINT_FORCE); + } + + if (do_all == 1) m = m->next; + else m = NULL; + } + + }); + + free_string_list(l); + return 0; +} + +int +asl_action_file_checkpoint(const char *module, const char *path) +{ + /* Note this is synchronous on asl_action queue */ + dispatch_sync(asl_action_queue, ^{ + asl_out_module_t *m = _asl_action_module_with_name(module); + _act_file_checkpoint(m, path, CHECKPOINT_FORCE); + }); return 0; }