+static char **
+_insertString(char *s, char **l, uint32_t x)
+{
+ int i, len;
+
+ if (s == NULL) return l;
+ if (l == NULL)
+ {
+ l = (char **)malloc(2 * sizeof(char *));
+ if (l == NULL) return NULL;
+
+ l[0] = strdup(s);
+ if (l[0] == NULL)
+ {
+ free(l);
+ return NULL;
+ }
+
+ l[1] = NULL;
+ return l;
+ }
+
+ for (i = 0; l[i] != NULL; i++);
+ len = i + 1; /* count the NULL on the end of the list too! */
+
+ l = (char **)reallocf(l, (len + 1) * sizeof(char *));
+ if (l == NULL) return NULL;
+
+ if ((x >= (len - 1)) || (x == IndexNull))
+ {
+ l[len - 1] = strdup(s);
+ if (l[len - 1] == NULL)
+ {
+ free(l);
+ return NULL;
+ }
+
+ l[len] = NULL;
+ return l;
+ }
+
+ for (i = len; i > x; i--) l[i] = l[i - 1];
+ l[x] = strdup(s);
+ if (l[x] == NULL) return NULL;
+
+ return l;
+}
+
+char **
+explode(const char *s, const char *delim)
+{
+ char **l = NULL;
+ const char *p;
+ char *t, quote;
+ int i, n;
+
+ if (s == NULL) return NULL;
+
+ quote = '\0';
+
+ p = s;
+ while (p[0] != '\0')
+ {
+ /* scan forward */
+ for (i = 0; p[i] != '\0'; i++)
+ {
+ if (quote == '\0')
+ {
+ /* not inside a quoted string: check for delimiters and quotes */
+ if (strchr(delim, p[i]) != NULL) break;
+ else if (p[i] == '\'') quote = p[i];
+ else if (p[i] == '"') quote = p[i];
+ }
+ else
+ {
+ /* inside a quoted string - look for matching quote */
+ if (p[i] == quote) quote = '\0';
+ }
+ }
+
+ n = i;
+ t = malloc(n + 1);
+ if (t == NULL) return NULL;
+
+ for (i = 0; i < n; i++) t[i] = p[i];
+ t[n] = '\0';
+ l = _insertString(t, l, IndexNull);
+ free(t);
+ t = NULL;
+ if (p[i] == '\0') return l;
+ if (p[i + 1] == '\0') l = _insertString("", l, IndexNull);
+ p = p + i + 1;
+ }
+
+ return l;
+}
+
+void
+freeList(char **l)
+{
+ int i;
+
+ if (l == NULL) return;
+ for (i = 0; l[i] != NULL; i++) free(l[i]);
+ free(l);
+}
+
+/*
+ * Quotas are maintained using a very fast fixed-size table.
+ * We hash into the pid table (quota_table_pid) using the last 10
+ * bits of the pid, so the table has 1024 "buckets". The table is
+ * actually just an array with 8 entry slots (for collisions) per bucket.
+ * If there are more than 8 pids that hash to the same bucket, we
+ * re-use the one with the lowest message usage (highest remaining
+ * quota). This can lead to "generosity: if there are nine or more
+ * pids with the same last 10 bits all logging like crazy, we may
+ * end up allowing some of them to log more than their quota.
+ * That would be a remarkably rare occurrence.
+ */
+
+static uint32_t
+quota_check(pid_t pid, time_t now, asl_msg_t *msg)
+{
+ int i, x, maxx, max;
+ char *str;
+
+ if (msg == NULL) return VERIFY_STATUS_INVALID_MESSAGE;
+ if (global.mps_limit == 0) return VERIFY_STATUS_OK;
+
+ OSSpinLockLock(&global.lock);
+
+ if (quota_table_time != now)
+ {
+ memset(quota_table_pid, 0, sizeof(quota_table_pid));
+ quota_table_time = now;
+ }
+
+ /* hash is last 10 bits of the pid, shifted up 3 bits to allow 8 slots per bucket */
+ x = (pid & 0x000003ff) << 3;
+ maxx = x;
+ max = quota_table_quota[x];
+
+ for (i = 0; i < QUOTA_TABLE_SLOTS; i++)
+ {
+ if (quota_table_pid[x] == 0)
+ {
+ quota_table_pid[x] = pid;
+ quota_table_quota[x] = global.mps_limit;
+
+ OSSpinLockUnlock(&global.lock);
+ return VERIFY_STATUS_OK;
+ }
+
+ if (quota_table_pid[x] == pid)
+ {
+ quota_table_quota[x] = quota_table_quota[x] - 1;
+
+ if (quota_table_quota[x] == 0)
+ {
+ quota_table_quota[x] = -1;
+
+ str = NULL;
+ asprintf(&str, QUOTA_EXCEEDED_MESSAGE, (int)pid, global.mps_limit);
+ if (str != NULL)
+ {
+ asl_set(msg, ASL_KEY_MSG, str);
+ free(str);
+ asl_set(msg, ASL_KEY_LEVEL, QUOTA_EXCEEDED_LEVEL);
+ }
+
+ OSSpinLockUnlock(&global.lock);
+ return VERIFY_STATUS_OK;
+ }
+
+ if (quota_table_quota[x] < 0)
+ {
+ OSSpinLockUnlock(&global.lock);
+ return VERIFY_STATUS_EXCEEDED_QUOTA;
+ }
+
+ OSSpinLockUnlock(&global.lock);
+ return VERIFY_STATUS_OK;
+ }
+
+ if (quota_table_quota[x] > max)
+ {
+ maxx = x;
+ max = quota_table_quota[x];
+ }
+
+ x += 1;
+ }
+
+ /* can't find the pid and no slots were available - reuse slot with highest remaining quota */
+ quota_table_pid[maxx] = pid;
+ quota_table_quota[maxx] = global.mps_limit;
+
+ OSSpinLockUnlock(&global.lock);
+ return VERIFY_STATUS_OK;
+}
+
+int
+asl_check_option(asl_msg_t *msg, const char *opt)
+{
+ const char *p;
+ uint32_t len;
+
+ if (msg == NULL) return 0;
+ if (opt == NULL) return 0;
+
+ len = strlen(opt);
+ if (len == 0) return 0;
+
+ p = asl_get(msg, ASL_KEY_OPTION);
+ if (p == NULL) return 0;
+
+ while (*p != '\0')
+ {
+ while ((*p == ' ') || (*p == '\t') || (*p == ',')) p++;
+ if (*p == '\0') return 0;
+
+ if (strncasecmp(p, opt, len) == 0)
+ {
+ p += len;
+ if ((*p == ' ') || (*p == '\t') || (*p == ',') || (*p == '\0')) return 1;
+ }
+
+ while ((*p != ' ') && (*p != '\t') && (*p != ',') && (*p != '\0')) p++;
+ }
+
+ return 0;
+}
+