+ /* 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)