コード例 #1
0
ファイル: noit_filters.c プロジェクト: flyerman/reconnoiter
void
noit_filters_from_conf() {
  mtev_conf_section_t *sets;
  int i, cnt;

  sets = mtev_conf_get_sections(NULL, "/noit/filtersets//filterset", &cnt);
  for(i=0; i<cnt; i++) {
    mtev_watchdog_child_heartbeat();
    noit_filter_compile_add(sets[i]);
  }
  free(sets);
}
コード例 #2
0
ファイル: noit_filters.c プロジェクト: flyerman/reconnoiter
static int
noit_console_filter_show(mtev_console_closure_t ncct,
                         int argc, char **argv,
                         mtev_console_state_t *state,
                         void *closure) {
  mtev_conf_t_userdata_t *info;
  char xpath[1024];
  xmlNodePtr fsnode;
  mtev_conf_section_t *rules;
  int i, rulecnt;

  info = mtev_console_userdata_get(ncct, MTEV_CONF_T_USERDATA);
  snprintf(xpath, sizeof(xpath), "/%s",
           info->path);
  fsnode = mtev_conf_get_section(NULL, xpath);
  if(!fsnode) {
    nc_printf(ncct, "internal error\n");
    return -1;
  }
  rules = mtev_conf_get_sections(fsnode, "rule", &rulecnt);
  for(i=0; i<rulecnt; i++) {
    char val[256];
    val[0] = '\0';
    mtev_conf_get_stringbuf(rules[i], "@type", val, sizeof(val));
    nc_printf(ncct, "Rule %d [%s]:\n", i+1, val);
#define DUMP_ATTR(a) do { \
  char *vstr; \
  mtev_conf_section_t ht; \
  int cnt; \
  ht = mtev_conf_get_sections(rules[i], #a, &cnt); \
  if(ht && cnt) { \
    nc_printf(ncct, "\t%s: hash match of %d items\n", #a, cnt); \
  } \
  else if(mtev_conf_get_string(rules[i], "@" #a, &vstr)) { \
    nc_printf(ncct, "\t%s: /%s/\n", #a, val); \
    free(vstr); \
  } \
  free(ht); \
} while(0)
    DUMP_ATTR(target);
    DUMP_ATTR(module);
    DUMP_ATTR(name);
    DUMP_ATTR(metric);
    DUMP_ATTR(id);
    DUMP_ATTR(skipto);
  }
  if(rules) free(rules);
  return 0;
}
コード例 #3
0
ファイル: mtev_dso.c プロジェクト: esproul/libmtev
void mtev_dso_init(void) {
  mtev_conf_section_t *sections;
  int i, cnt = 0;

  mtev_dso_add_type("loader", mtev_dso_list_loaders);
  mtev_dso_add_type("generic", mtev_dso_list_generics);

  /* Load our generic modules */
  sections = mtev_conf_get_sections(MTEV_CONF_ROOT, "//modules//generic", &cnt);
  for(i=0; i<cnt; i++) {
    char g_name[256];
    mtev_dso_generic_t *gen;
    mtev_conf_section_t *include_sections = NULL;
    int section_cnt;

    if(!mtev_conf_get_stringbuf(sections[i], "ancestor-or-self::node()/@name",
                                g_name, sizeof(g_name))) {
      mtevL(mtev_stderr, "No name defined in generic stanza %d\n", i+1);
      continue;
    }

    if(mtev_conf_env_off(sections[i], NULL)) {
      mtevL(mtev_debug, "generic module %s environmentally disabled.\n", g_name);
      continue;
    }

    gen = (mtev_dso_generic_t *)
      mtev_load_generic_image(&__mtev_image_loader, g_name,
                              sections[i]);
    if(!gen) {
      mtevL(mtev_stderr, "Failed to load generic %s\n", g_name);
      mtev_dso_load_failure_count++;
      continue;
    }
    if(gen->config) {
      int rv;
      mtev_hash_table *config;
      include_sections = mtev_conf_get_sections(sections[i], "include", &section_cnt);
      if ((include_sections) && (section_cnt == 1)) {
        config = mtev_conf_get_hash(*include_sections, "config");
      }
      else {
        config = mtev_conf_get_hash(sections[i], "config");
      }
      mtev_conf_release_sections(include_sections, section_cnt);
      rv = gen->config(gen, config);
      if(rv == 0) {
        mtev_hash_destroy(config, free, free);
        free(config);
      }
      else if(rv < 0) {
        mtevL(mtev_stderr, "Failed to config generic %s\n", g_name);
        continue;
      }
    }
    if(gen->init && gen->init(gen)) {
      mtevL(mtev_stderr, "Failed to init generic %s\n", g_name);
      mtev_dso_load_failure_count++;
    }
    else
      mtevL(mtev_debug, "Generic module %s successfully loaded.\n", g_name);
  }
  mtev_conf_release_sections(sections, cnt);
  /* Load our module loaders */
  sections = mtev_conf_get_sections(MTEV_CONF_ROOT, "//modules//loader", &cnt);
  for(i=0; i<cnt; i++) {
    char loader_name[256];
    mtev_dso_loader_t *loader;
    mtev_conf_section_t *include_sections = NULL;
    int section_cnt;

    if(!mtev_conf_get_stringbuf(sections[i], "ancestor-or-self::node()/@name",
                                loader_name, sizeof(loader_name))) {
      mtevL(mtev_stderr, "No name defined in loader stanza %d\n", i+1);
      continue;
    }

    if(mtev_conf_env_off(sections[i], NULL)) {
      mtevL(mtev_debug, "loader %s environmentally disabled.\n", loader_name);
      continue;
    }

    loader = (mtev_dso_loader_t *)
      mtev_load_loader_image(&__mtev_image_loader, loader_name,
                             sections[i]);
    if(!loader) {
      mtevL(mtev_stderr, "Failed to load loader %s\n", loader_name);
      mtev_dso_load_failure_count++;
      continue;
    }
    if(loader->config) {
      int rv;
      mtev_hash_table *config;
      include_sections = mtev_conf_get_sections(sections[i], "include", &section_cnt);
      if ((include_sections) && (section_cnt == 1)) {
        config = mtev_conf_get_hash(*include_sections, "config");
      }
      else {
        config = mtev_conf_get_hash(sections[i], "config");
      }
      mtev_conf_release_sections(include_sections, section_cnt);
      rv = loader->config(loader, config);
      if(rv == 0) {
        mtev_hash_destroy(config, free, free);
        free(config);
      }
      else if(rv < 0) {
        mtevL(mtev_stderr, "Failed to config loader %s\n", loader_name);
        mtev_dso_load_failure_count++;
        continue;
      }
    }
    if(loader->init && loader->init(loader))
      mtevL(mtev_stderr, "Failed to init loader %s\n", loader_name);
  }
  mtev_conf_release_sections(sections, cnt);
}
コード例 #4
0
void noit_check_resolver_init() {
  int cnt;
  mtev_conf_section_t *servers, *searchdomains;
  eventer_t e;
  if(dns_init(NULL, 0) < 0)
    mtevL(noit_error, "dns initialization failed.\n");
  dns_ctx = dns_new(NULL);
  if(dns_init(dns_ctx, 0) != 0) {
    mtevL(noit_error, "dns initialization failed.\n");
    exit(-1);
  }

  /* Optional servers */
  servers = mtev_conf_get_sections(NULL, "//resolver//server", &cnt);
  if(cnt) {
    int i;
    char server[128];
    dns_add_serv(dns_ctx, NULL); /* reset */
    for(i=0;i<cnt;i++) {
      if(mtev_conf_get_stringbuf(servers[i], "self::node()",
                                 server, sizeof(server))) {
        if(dns_add_serv(dns_ctx, server) < 0) {
          mtevL(noit_error, "Failed adding DNS server: %s\n", server);
        }
      }
    }
    free(servers);
  }
  searchdomains = mtev_conf_get_sections(NULL, "//resolver//search", &cnt);
  if(cnt) {
    int i;
    char search[128];
    dns_add_srch(dns_ctx, NULL); /* reset */
    for(i=0;i<cnt;i++) {
      if(mtev_conf_get_stringbuf(searchdomains[i], "self::node()",
                                 search, sizeof(search))) {
        if(dns_add_srch(dns_ctx, search) < 0) {
          mtevL(noit_error, "Failed adding DNS search path: %s\n", search);
        }
        else if(dns_search_flag) dns_search_flag = 0; /* enable search */
      }
    }
    free(searchdomains);
  }

  if(mtev_conf_get_int(NULL, "//resolver/@ndots", &cnt))
    dns_set_opt(dns_ctx, DNS_OPT_NDOTS, cnt);

  if(mtev_conf_get_int(NULL, "//resolver/@ntries", &cnt))
    dns_set_opt(dns_ctx, DNS_OPT_NTRIES, cnt);

  if(mtev_conf_get_int(NULL, "//resolver/@timeout", &cnt))
    dns_set_opt(dns_ctx, DNS_OPT_TIMEOUT, cnt);

  if(dns_open(dns_ctx) < 0) {
    mtevL(noit_error, "dns open failed.\n");
    exit(-1);
  }
  eventer_name_callback("dns_cache_callback", dns_cache_callback);
  dns_set_tmcbck(dns_ctx, dns_cache_utm_fn, dns_ctx);
  e = eventer_alloc();
  e->mask = EVENTER_READ | EVENTER_EXCEPTION;
  e->closure = dns_ctx;
  e->callback = dns_cache_callback;
  e->fd = dns_sock(dns_ctx);
  eventer_add(e);

  mtev_skiplist_init(&nc_dns_cache);
  mtev_skiplist_set_compare(&nc_dns_cache, name_lookup, name_lookup_k);
  mtev_skiplist_add_index(&nc_dns_cache, refresh_idx, refresh_idx_k);

  /* maybe load it from cache */
  if(noit_resolver_cache_load_hook_exists()) {
    struct timeval now;
    char *key;
    void *data;
    int len;
    gettimeofday(&now, NULL);
    while(noit_resolver_cache_load_hook_invoke(&key, &data, &len) == MTEV_HOOK_CONTINUE) {
      dns_cache_node *n;
      n = calloc(1, sizeof(*n));
      if(dns_cache_node_deserialize(n, data, len) >= 0) {
        n->target = strdup(key);
        /* if the TTL indicates that it will expire in less than 60 seconds
         * (including stuff that should have already expired), then fudge
         * the last_updated time to make it expire some random time within
         * the next 60 seconds.
         */
        if(n->last_needed > now.tv_sec || n->last_updated > now.tv_sec)
          break; /* impossible */

        n->last_needed = now.tv_sec;
        if(n->last_updated + n->ttl < now.tv_sec + 60) {
          int fudge = MIN(60, n->ttl) + 1;
          n->last_updated = now.tv_sec - n->ttl + (lrand48() % fudge);
        }
        DCLOCK();
        mtev_skiplist_insert(&nc_dns_cache, n);
        DCUNLOCK();
        n = NULL;
      }
      else {
        mtevL(noit_error, "Failed to deserialize resolver cache record.\n");
      }
      if(n) dns_cache_node_free(n);
      if(key) free(key);
      if(data) free(data);
    }
  }

  noit_check_resolver_loop(NULL, 0, NULL, NULL);
  register_console_dns_cache_commands();

  mtev_hash_init(&etc_hosts_cache);
  noit_check_etc_hosts_cache_refresh(NULL, 0, NULL, NULL);
}
コード例 #5
0
ファイル: mtev_main.c プロジェクト: JonasKunze/libmtev
int
mtev_main(const char *appname,
          const char *config_filename, int debug, int foreground,
          mtev_lock_op_t lock, const char *_glider,
          const char *drop_to_user, const char *drop_to_group,
          int (*passed_child_main)(void)) {
    mtev_conf_section_t watchdog_conf;
    int fd, lockfd, watchdog_timeout = 0, rv;
    int wait_for_lock;
    char conf_str[1024];
    char lockfile[PATH_MAX];
    char *trace_dir = NULL;
    char appscratch[1024];
    char *glider = (char *)_glider;
    char *watchdog_timeout_str;
    int retry_val;
    int span_val;
    int ret;
    int cnt;
    mtev_conf_section_t root;

    wait_for_lock = (lock == MTEV_LOCK_OP_WAIT) ? 1 : 0;

    mtev_init_globals();

    char *require_invariant_tsc = getenv("MTEV_RDTSC_REQUIRE_INVARIANT");
    if (require_invariant_tsc && strcmp(require_invariant_tsc, "0") == 0) {
        mtev_time_toggle_require_invariant_tsc(mtev_false);
    }

    char *disable_rdtsc = getenv("MTEV_RDTSC_DISABLE");
    if (disable_rdtsc && strcmp(disable_rdtsc, "1") == 0) {
        mtev_time_toggle_tsc(mtev_false);
    }

    char *disable_binding = getenv("MTEV_THREAD_BINDING_DISABLE");
    if (disable_binding && strcmp(disable_binding, "1") == 0) {
        mtev_thread_disable_binding();
    }

    /* First initialize logging, so we can log errors */
    mtev_log_init(debug);
    mtev_log_stream_add_stream(mtev_debug, mtev_stderr);
    mtev_log_stream_add_stream(mtev_error, mtev_stderr);
    mtev_log_stream_add_stream(mtev_notice, mtev_error);

    /* Next load the configs */
    mtev_conf_use_namespace(appname);
    mtev_conf_init(appname);
    if(mtev_conf_load(config_filename) == -1) {
        fprintf(stderr, "Cannot load config: '%s'\n", config_filename);
        exit(-1);
    }

    char* root_section_path = alloca(strlen(appname)+2);
    sprintf(root_section_path, "/%s", appname);
    root = mtev_conf_get_sections(NULL, root_section_path, &cnt);
    free(root);
    if(cnt==0) {
        fprintf(stderr, "The config must have <%s> as its root node\n", appname);
        exit(-1);
    }

    /* Reinitialize the logging system now that we have a config */
    mtev_conf_log_init(appname, drop_to_user, drop_to_group);
    if(debug) {
        mtev_log_stream_set_flags(mtev_debug, mtev_log_stream_get_flags(mtev_debug) | MTEV_LOG_STREAM_ENABLED);
    }
    cli_log_switches();

    snprintf(appscratch, sizeof(appscratch), "/%s/watchdog|/%s/include/watchdog", appname, appname);
    watchdog_conf = mtev_conf_get_section(NULL, appscratch);

    if(!glider) (void) mtev_conf_get_string(watchdog_conf, "@glider", &glider);
    if(mtev_watchdog_glider(glider)) {
        mtevL(mtev_stderr, "Invalid glider, exiting.\n");
        exit(-1);
    }
    (void)mtev_conf_get_string(watchdog_conf, "@tracedir", &trace_dir);
    if(trace_dir) {
        if(mtev_watchdog_glider_trace_dir(trace_dir)) {
            mtevL(mtev_stderr, "Invalid glider tracedir, exiting.\n");
            exit(-1);
        }
    }

    ret = mtev_conf_get_int(watchdog_conf, "@retries", &retry_val);
    if((ret == 0) || (retry_val == 0)) {
        retry_val = 5;
    }
    ret = mtev_conf_get_int(watchdog_conf, "@span", &span_val);
    if((ret == 0) || (span_val == 0)) {
        span_val = 60;
    }

    mtev_watchdog_ratelimit(retry_val, span_val);

    /* Lastly, run through all other system inits */
    snprintf(appscratch, sizeof(appscratch), "/%s/eventer/@implementation", appname);
    if(!mtev_conf_get_stringbuf(NULL, appscratch, conf_str, sizeof(conf_str))) {
        mtevL(mtev_stderr, "Cannot find '%s' in configuration\n", appscratch);
        exit(-1);
    }
    if(eventer_choose(conf_str) == -1) {
        mtevL(mtev_stderr, "Cannot choose eventer %s\n", conf_str);
        exit(-1);
    }
    if(configure_eventer(appname) != 0) {
        mtevL(mtev_stderr, "Cannot configure eventer\n");
        exit(-1);
    }

    mtev_watchdog_prefork_init();

    if(foreground != 1 && chdir("/") != 0) {
        mtevL(mtev_stderr, "Failed chdir(\"/\"): %s\n", strerror(errno));
        exit(-1);
    }

    /* Acquire the lock so that we can throw an error if it doesn't work.
     * If we've started -D, we'll have the lock.
     * If not we will daemon and must reacquire the lock.
     */
    lockfd = -1;
    lockfile[0] = '\0';
    snprintf(appscratch, sizeof(appscratch), "/%s/@lockfile", appname);
    if(lock != MTEV_LOCK_OP_NONE &&
            mtev_conf_get_stringbuf(NULL, appscratch,
                                    lockfile, sizeof(lockfile))) {
        do {
            if((lockfd = mtev_lockfile_acquire(lockfile)) < 0) {
                if(!wait_for_lock) {
                    mtevL(mtev_stderr, "Failed to acquire lock: %s\n", lockfile);
                    exit(-1);
                }
                if(wait_for_lock == 1) {
                    mtevL(mtev_stderr, "%d failed to acquire lock(%s), waiting...\n",
                          (int)getpid(), lockfile);
                    wait_for_lock++;
                }
                usleep(1000);
            }
            else {
                if(wait_for_lock > 1) mtevL(mtev_stderr, "Lock acquired proceeding.\n");
                wait_for_lock = 0;
            }
        } while(wait_for_lock);
    }

    if(foreground == 1) {
        mtev_time_start_tsc();
        mtevL(mtev_notice, "%s booting [unmanaged]\n", appname);
        int rv = passed_child_main();
        mtev_lockfile_release(lockfd);
        return rv;
    }

    watchdog_timeout_str = getenv("WATCHDOG_TIMEOUT");
    if(watchdog_timeout_str) {
        watchdog_timeout = atoi(watchdog_timeout_str);
        mtevL(mtev_error, "Setting watchdog timeout to %d\n",
              watchdog_timeout);
    }

    /* This isn't inherited across forks... */
    if(lockfd >= 0) mtev_lockfile_release(lockfd);
    lockfd = -1;

    if(foreground == 0) {
        fd = open("/dev/null", O_RDONLY);
        if(fd < 0 || dup2(fd, STDIN_FILENO) < 0) {
            fprintf(stderr, "Failed to setup stdin: %s\n", strerror(errno));
            exit(-1);
        }
        close(fd);
        fd = open("/dev/null", O_WRONLY);
        if(fd < 0 || dup2(fd, STDOUT_FILENO) < 0 || dup2(fd, STDERR_FILENO) < 0) {
            fprintf(stderr, "Failed to setup std{out,err}: %s\n", strerror(errno));
            exit(-1);
        }
        close(fd);

        if(fork()) exit(0);
        setsid();
        if(fork()) exit(0);
    }

    /* Reacquire the lock */
    if(*lockfile) {
        if (lock) {
            if((lockfd = mtev_lockfile_acquire(lockfile)) < 0) {
                mtevL(mtev_stderr, "Failed to acquire lock: %s\n", lockfile);
                exit(-1);
            }
        }
    }

    signal(SIGHUP, SIG_IGN);
    mtevL(mtev_notice, "%s booting\n", appname);
    rv = mtev_watchdog_start_child(appname, passed_child_main, watchdog_timeout);
    mtev_lockfile_release(lockfd);
    return rv;
}
コード例 #6
0
ファイル: noit_filters.c プロジェクト: flyerman/reconnoiter
int
noit_filtersets_cull_unused() {
  mtev_hash_table active = MTEV_HASH_EMPTY;
  char *buffer = NULL;
  mtev_conf_section_t *declares;
  int i, n_uses = 0, n_declares = 0, removed = 0;
  const char *declare_xpath = "//filterset[@name and not (@cull='false')]";

  declares = mtev_conf_get_sections(NULL, declare_xpath, &n_declares);
  if(declares) {
    /* store all unit filtersets used */
    for(i=0;i<n_declares;i++) {
      if(!buffer) buffer = malloc(128);
      if(mtev_conf_get_stringbuf(declares[i], "@name", buffer, 128)) {
        if(mtev_hash_store(&active, buffer, strlen(buffer), declares[i])) {
          buffer = NULL;
        }
        else {
          void *vnode = NULL;
          /* We've just hit a duplicate.... check to see if there's an existing
           * entry and if there is, load the latest one and delete the old
           * one. */
          mtev_hash_retrieve(&active, buffer, strlen(buffer), &vnode);
          if (vnode) {
            noit_filter_compile_add(declares[i]);
            CONF_REMOVE(vnode);
            xmlUnlinkNode(vnode);
            xmlFreeNode(vnode);
            removed++;
            if(mtev_hash_replace(&active, buffer, strlen(buffer), declares[i], free, NULL)) {
              buffer = NULL;
            }
          }
        }
      }
    }
    if(buffer) free(buffer);
    free(declares);
  }

  n_uses = noit_poller_do(filterset_accum, &active);

  if(n_uses > 0 && mtev_hash_size(&active) > 0) {
    mtev_hash_iter iter = MTEV_HASH_ITER_ZERO;
    const char *filter_name;
    int filter_name_len;
    void *vnode;
    while(mtev_hash_next(&active, &iter, &filter_name, &filter_name_len,
                         &vnode)) {
      if(noit_filter_remove(vnode)) {
        CONF_REMOVE(vnode);
        xmlUnlinkNode(vnode);
        xmlFreeNode(vnode);
        removed++;
      }
    }
  }

  mtev_hash_destroy(&active, free, NULL);
  return removed;
}
コード例 #7
0
ファイル: noit_filters.c プロジェクト: flyerman/reconnoiter
void
noit_filter_compile_add(mtev_conf_section_t setinfo) {
  mtev_conf_section_t *rules;
  int j, fcnt;
  char filterset_name[256];
  filterset_t *set;
  if(!mtev_conf_get_stringbuf(setinfo, "@name",
                              filterset_name, sizeof(filterset_name))) {
    mtevL(noit_error,
          "filterset with no name, skipping as it cannot be referenced.\n");
    return;
  }
  set = calloc(1, sizeof(*set));
  set->ref_cnt = 1;
  set->name = strdup(filterset_name);

  rules = mtev_conf_get_sections(setinfo, "rule", &fcnt);
  /* Here we will work through the list backwards pushing the rules on
   * the front of the list.  That way we can simply walk them in order
   * for the application process.
   */
  mtevL(noit_debug, "Compiling filterset '%s'\n", set->name);
  for(j=fcnt-1; j>=0; j--) {
    filterrule_t *rule;
    char buffer[256];
    if(!mtev_conf_get_stringbuf(rules[j], "@type", buffer, sizeof(buffer)) ||
       (strcmp(buffer, "accept") && strcmp(buffer, "allow") && strcmp(buffer, "deny") &&
        strncmp(buffer, "skipto:", strlen("skipto:")))) {
      mtevL(noit_error, "rule must have type 'accept' or 'allow' or 'deny' or 'skipto:'\n");
      continue;
    }
    mtevL(noit_debug, "Prepending %s into %s\n", buffer, set->name);
    rule = calloc(1, sizeof(*rule));
    if(!strncasecmp(buffer, "skipto:", strlen("skipto:"))) {
      rule->type = NOIT_FILTER_SKIPTO;
      rule->skipto = strdup(buffer+strlen("skipto:"));
    }
    else {
      rule->type = (!strcmp(buffer, "accept") || !strcmp(buffer, "allow")) ?
                     NOIT_FILTER_ACCEPT : NOIT_FILTER_DENY;
    }

    if(mtev_conf_get_stringbuf(rules[j], "@id", buffer, sizeof(buffer))) {
      rule->ruleid = strdup(buffer);
    }
    /* Compile any hash tables, should they exist */
#define HT_COMPILE(rname) do { \
    mtev_conf_section_t *htentries; \
    int hte_cnt, hti, tablesize = 2, auto_max = 0; \
    char *htstr; \
    htentries = mtev_conf_get_sections(rules[j], #rname, &hte_cnt); \
    mtev_conf_get_int(rules[j], "@" #rname "_auto_add", &auto_max); \
    if(hte_cnt || auto_max > 0) { \
      rule->rname##_auto_hash_max = auto_max; \
      rule->rname##_ht = calloc(1, sizeof(*(rule->rname##_ht))); \
      while(tablesize < hte_cnt) tablesize <<= 1; \
      mtev_hash_init_size(rule->rname##_ht, tablesize); \
      for(hti=0; hti<hte_cnt; hti++) { \
        if(!mtev_conf_get_string(htentries[hti], "self::node()", &htstr)) \
          mtevL(noit_error, "Error fetching text content from filter match.\n"); \
        else \
          mtev_hash_replace(rule->rname##_ht, htstr, strlen(htstr), NULL, free, NULL); \
      } \
    } \
    free(htentries); \
} while(0);
    HT_COMPILE(target);
    HT_COMPILE(module);
    HT_COMPILE(name);
    HT_COMPILE(metric);
    
    /* Compile our rules */
#define RULE_COMPILE(rname) do { \
  char *longre = NULL; \
  if(mtev_conf_get_string(rules[j], "@" #rname, &longre)) { \
    const char *error; \
    int erroffset; \
    rule->rname = pcre_compile(longre, 0, &error, &erroffset, NULL); \
    if(!rule->rname) { \
      mtevL(noit_debug, "set '%s' rule '%s: %s' compile failed: %s\n", \
            set->name, #rname, longre, error ? error : "???"); \
      rule->rname##_override = fallback_no_match; \
    } \
    else { \
      rule->rname##_e = pcre_study(rule->rname, 0, &error); \
    } \
    free(longre); \
  } \
} while(0)

    if(rule->target_ht == NULL)
      RULE_COMPILE(target);
    if(rule->module_ht == NULL)
      RULE_COMPILE(module);
    if(rule->name_ht == NULL)
      RULE_COMPILE(name);
    if(rule->metric_ht == NULL)
      RULE_COMPILE(metric);
    rule->next = set->rules;
    set->rules = rule;
  }

  filterrule_t *cursor;
  for(cursor = set->rules; cursor->next; cursor = cursor->next) {
    if(cursor->skipto) {
      filterrule_t *target;
      for(target = cursor->next; target; target = target->next) {
        if(target->ruleid && !strcmp(cursor->skipto, target->ruleid)) {
          cursor->skipto_rule = target;
          break;
        }
      }
      if(!cursor->skipto_rule)
        mtevL(noit_error, "filterset %s skipto:%s not found\n",
              set->name, cursor->skipto);
    }
  }
  free(rules);
  LOCKFS();
  mtev_hash_replace(filtersets, set->name, strlen(set->name), (void *)set,
                    NULL, filterset_free);
  UNLOCKFS();
}