Merge remote-tracking branch 'github/pr/2285'
authorFlorian Forster <octo@collectd.org>
Fri, 19 May 2017 06:04:44 +0000 (08:04 +0200)
committerFlorian Forster <octo@collectd.org>
Fri, 19 May 2017 06:04:44 +0000 (08:04 +0200)
1  2 
src/curl_json.c

diff --combined src/curl_json.c
@@@ -44,8 -44,6 +44,6 @@@
  #endif
  
  #define CJ_DEFAULT_HOST "localhost"
- #define CJ_KEY_MAGIC 0x43484b59UL /* CHKY */
- #define CJ_IS_KEY(key) ((key)->magic == CJ_KEY_MAGIC)
  #define CJ_ANY "*"
  #define COUCH_MIN(x, y) ((x) < (y) ? (x) : (y))
  
@@@ -53,13 -51,34 +51,34 @@@ struct cj_key_s
  typedef struct cj_key_s cj_key_t;
  struct cj_key_s /* {{{ */
  {
-   unsigned long magic;
    char *path;
    char *type;
    char *instance;
  };
  /* }}} */
  
+ /* cj_tree_entry_t is a union of either a metric configuration ("key") or a tree
+  * mapping array indexes / map keys to a descendant cj_tree_entry_t*. */
+ typedef struct {
+   enum { KEY, TREE } type;
+   union {
+     c_avl_tree_t *tree;
+     cj_key_t *key;
+   };
+ } cj_tree_entry_t;
+ /* cj_state_t is a stack providing the configuration relevant for the context
+  * that is currently being parsed. If entry->type == KEY, the parser should
+  * expect a metric (a numeric value). If entry->type == TREE, the parser should
+  * expect an array of map to descent into. If entry == NULL, no configuration
+  * exists for this part of the JSON structure. */
+ typedef struct {
+   cj_tree_entry_t *entry;
+   _Bool in_array;
+   int index;
+   char name[DATA_MAX_NAME_LEN];
+ } cj_state_t;
  struct cj_s /* {{{ */
  {
    char *instance;
    yajl_handle yajl;
    c_avl_tree_t *tree;
    int depth;
-   struct {
-     union {
-       c_avl_tree_t *tree;
-       cj_key_t *key;
-     };
-     _Bool in_array;
-     int index;
-     char name[DATA_MAX_NAME_LEN];
-   } state[YAJL_MAX_DEPTH];
+   cj_state_t state[YAJL_MAX_DEPTH];
  };
  typedef struct cj_s cj_t; /* }}} */
  
@@@ -121,18 -132,18 +132,18 @@@ static size_t cj_curl_callback(void *bu
    len = size * nmemb;
  
    if (len == 0)
 -    return (len);
 +    return len;
  
    db = user_data;
    if (db == NULL)
 -    return (0);
 +    return 0;
  
    status = yajl_parse(db->yajl, (unsigned char *)buf, len);
    if (status == yajl_status_ok)
 -    return (len);
 +    return len;
  #if !HAVE_YAJL_V2
    else if (status == yajl_status_insufficient_data)
 -    return (len);
 +    return len;
  #endif
  
    unsigned char *msg =
                       /* jsonText = */ (unsigned char *)buf, (unsigned int)len);
    ERROR("curl_json plugin: yajl_parse failed: %s", msg);
    yajl_free_error(db->yajl, msg);
 -  return (0); /* abort write callback */
 +  return 0; /* abort write callback */
  } /* }}} size_t cj_curl_callback */
  
  static int cj_get_type(cj_key_t *key) {
-   const data_set_t *ds;
-   if ((key == NULL) || !CJ_IS_KEY(key))
+   if (key == NULL)
      return -EINVAL;
  
-   ds = plugin_get_ds(key->type);
+   const data_set_t *ds = plugin_get_ds(key->type);
    if (ds == NULL) {
      static char type[DATA_MAX_NAME_LEN] = "!!!invalid!!!";
  
@@@ -182,31 -191,20 +191,20 @@@ static int cj_load_key(cj_t *db, char c
  
    sstrncpy(db->state[db->depth].name, key, sizeof(db->state[db->depth].name));
  
-   c_avl_tree_t *tree = db->state[db->depth - 1].tree;
-   if (tree == NULL) {
+   if (db->state[db->depth - 1].entry == NULL ||
+       db->state[db->depth - 1].entry->type != TREE) {
      return 0;
    }
  
-   /* the parent has a key, so the tree pointer is invalid. */
-   if (CJ_IS_KEY(db->state[db->depth - 1].key)) {
-     return 0;
-   }
+   c_avl_tree_t *tree = db->state[db->depth - 1].entry->tree;
+   cj_tree_entry_t *e = NULL;
  
-   void *value = NULL;
-   if (c_avl_get(tree, key, (void *)&value) == 0) {
-     if (CJ_IS_KEY((cj_key_t *)value)) {
-       db->state[db->depth].key = value;
-     } else {
-       db->state[db->depth].tree = value;
-     }
-   } else if (c_avl_get(tree, CJ_ANY, (void *)&value) == 0) {
-     if (CJ_IS_KEY((cj_key_t *)value)) {
-       db->state[db->depth].key = value;
-     } else {
-       db->state[db->depth].tree = value;
-     }
+   if (c_avl_get(tree, key, (void *)&e) == 0) {
+     db->state[db->depth].entry = e;
+   } else if (c_avl_get(tree, CJ_ANY, (void *)&e) == 0) {
+     db->state[db->depth].entry = e;
    } else {
-     db->state[db->depth].key = NULL;
+     db->state[db->depth].entry = NULL;
    }
  
    return 0;
@@@ -229,49 -227,47 +227,47 @@@ static void cj_advance_array(cj_t *db) 
  
  static int cj_cb_boolean(void *ctx, int boolVal) {
    cj_advance_array(ctx);
 -  return (CJ_CB_CONTINUE);
 +  return CJ_CB_CONTINUE;
  }
  
  static int cj_cb_null(void *ctx) {
    cj_advance_array(ctx);
 -  return (CJ_CB_CONTINUE);
 +  return CJ_CB_CONTINUE;
  }
  
  static int cj_cb_number(void *ctx, const char *number, yajl_len_t number_len) {
-   char buffer[number_len + 1];
    cj_t *db = (cj_t *)ctx;
-   cj_key_t *key = db->state[db->depth].key;
  
    /* Create a null-terminated version of the string. */
+   char buffer[number_len + 1];
    memcpy(buffer, number, number_len);
    buffer[sizeof(buffer) - 1] = 0;
  
-   if (key == NULL) {
-     /* no config for this element. */
-     cj_advance_array(ctx);
-     return CJ_CB_CONTINUE;
-   } else if (!CJ_IS_KEY(key)) {
-     /* the config expects a map or an array. */
-     NOTICE(
-         "curl_json plugin: Found \"%s\", but the configuration expects a map.",
-         buffer);
+   if (db->state[db->depth].entry == NULL ||
+       db->state[db->depth].entry->type != KEY) {
+     if (db->state[db->depth].entry != NULL) {
+       NOTICE("curl_json plugin: Found \"%s\", but the configuration expects a "
+              "map.",
+              buffer);
+     }
      cj_advance_array(ctx);
      return CJ_CB_CONTINUE;
    }
  
+   cj_key_t *key = db->state[db->depth].entry->key;
    int type = cj_get_type(key);
    value_t vt;
    int status = parse_value(buffer, &vt, type);
    if (status != 0) {
      NOTICE("curl_json plugin: Unable to parse number: \"%s\"", buffer);
      cj_advance_array(ctx);
 -    return (CJ_CB_CONTINUE);
 +    return CJ_CB_CONTINUE;
    }
  
    cj_submit(db, key, &vt);
    cj_advance_array(ctx);
 -  return (CJ_CB_CONTINUE);
 +  return CJ_CB_CONTINUE;
  } /* int cj_cb_number */
  
  /* Queries the key-tree of the parent context for "in_name" and, if found,
@@@ -292,15 -288,15 +288,15 @@@ static int cj_cb_map_key(void *ctx, uns
  
  static int cj_cb_string(void *ctx, const unsigned char *val, yajl_len_t len) {
    /* Handle the string as if it was a number. */
 -  return (cj_cb_number(ctx, (const char *)val, len));
 +  return cj_cb_number(ctx, (const char *)val, len);
  } /* int cj_cb_string */
  
  static int cj_cb_end(void *ctx) {
    cj_t *db = (cj_t *)ctx;
-   db->state[db->depth].tree = NULL;
+   memset(&db->state[db->depth], 0, sizeof(db->state[db->depth]));
    db->depth--;
    cj_advance_array(ctx);
 -  return (CJ_CB_CONTINUE);
 +  return CJ_CB_CONTINUE;
  }
  
  static int cj_cb_start_map(void *ctx) {
    if ((db->depth + 1) >= YAJL_MAX_DEPTH) {
      ERROR("curl_json plugin: %s depth exceeds max, aborting.",
            db->url ? db->url : db->sock);
 -    return (CJ_CB_ABORT);
 +    return CJ_CB_ABORT;
    }
    db->depth++;
 -  return (CJ_CB_CONTINUE);
 +  return CJ_CB_CONTINUE;
  }
  
  static int cj_cb_end_map(void *ctx) { return cj_cb_end(ctx); }
@@@ -365,17 -361,16 +361,16 @@@ static void cj_key_free(cj_key_t *key) 
  static void cj_tree_free(c_avl_tree_t *tree) /* {{{ */
  {
    char *name;
-   void *value;
+   cj_tree_entry_t *e;
  
-   while (c_avl_pick(tree, (void *)&name, (void *)&value) == 0) {
-     cj_key_t *key = (cj_key_t *)value;
+   while (c_avl_pick(tree, (void *)&name, (void *)&e) == 0) {
+     sfree(name);
  
-     if (CJ_IS_KEY(key))
-       cj_key_free(key);
+     if (e->type == KEY)
+       cj_key_free(e->key);
      else
-       cj_tree_free((c_avl_tree_t *)value);
-     sfree(name);
+       cj_tree_free(e->tree);
+     sfree(e);
    }
  
    c_avl_destroy(tree);
@@@ -429,16 -424,16 +424,16 @@@ static int cj_config_append_string(cons
    struct curl_slist *temp = NULL;
    if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
      WARNING("curl_json plugin: `%s' needs exactly one string argument.", name);
 -    return (-1);
 +    return -1;
    }
  
    temp = curl_slist_append(*dest, ci->values[0].value.string);
    if (temp == NULL)
 -    return (-1);
 +    return -1;
  
    *dest = temp;
  
 -  return (0);
 +  return 0;
  } /* }}} int cj_config_append_string */
  
  /* cj_append_key adds key to the configuration stored in db.
@@@ -469,13 -464,21 +464,21 @@@ static int cj_append_key(cj_t *db, cj_k
      len = COUCH_MIN(len, sizeof(name) - 1);
      sstrncpy(name, start, len + 1);
  
-     c_avl_tree_t *value;
-     if (c_avl_get(tree, name, (void *)&value) != 0) {
-       value = cj_avl_create();
-       c_avl_insert(tree, strdup(name), value);
+     cj_tree_entry_t *e;
+     if (c_avl_get(tree, name, (void *)&e) != 0) {
+       e = calloc(1, sizeof(*e));
+       if (e == NULL)
+         return ENOMEM;
+       e->type = TREE;
+       e->tree = cj_avl_create();
+       c_avl_insert(tree, strdup(name), e);
      }
  
-     tree = value;
+     if (e->type != TREE)
+       return EINVAL;
+     tree = e->tree;
      start = end + 1;
    }
  
      return -1;
    }
  
-   c_avl_insert(tree, strdup(start), key);
+   cj_tree_entry_t *e = calloc(1, sizeof(*e));
+   if (e == NULL)
+     return ENOMEM;
+   e->type = KEY;
+   e->key = key;
+   c_avl_insert(tree, strdup(start), e);
    return 0;
  } /* }}} int cj_append_key */
  
@@@ -496,28 -505,27 +505,27 @@@ static int cj_config_add_key(cj_t *db, 
    if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
      WARNING("curl_json plugin: The `Key' block "
              "needs exactly one string argument.");
 -    return (-1);
 +    return -1;
    }
  
    key = calloc(1, sizeof(*key));
    if (key == NULL) {
      ERROR("curl_json plugin: calloc failed.");
 -    return (-1);
 +    return -1;
    }
-   key->magic = CJ_KEY_MAGIC;
  
    if (strcasecmp("Key", ci->key) == 0) {
      status = cf_util_get_string(ci, &key->path);
      if (status != 0) {
        sfree(key);
 -      return (status);
 +      return status;
      }
    } else {
      ERROR("curl_json plugin: cj_config: "
            "Invalid key: %s",
            ci->key);
      cj_key_free(key);
 -    return (-1);
 +    return -1;
    }
  
    status = 0;
  
    if (status != 0) {
      cj_key_free(key);
 -    return (-1);
 +    return -1;
    }
  
    if (key->type == NULL) {
      WARNING("curl_json plugin: `Type' missing in `Key' block.");
      cj_key_free(key);
 -    return (-1);
 +    return -1;
    }
  
    status = cj_append_key(db, key);
@@@ -562,7 -570,7 +570,7 @@@ static int cj_init_curl(cj_t *db) /* {{
    db->curl = curl_easy_init();
    if (db->curl == NULL) {
      ERROR("curl_json plugin: curl_easy_init failed.");
 -    return (-1);
 +    return -1;
    }
  
    curl_easy_setopt(db->curl, CURLOPT_NOSIGNAL, 1L);
      db->credentials = malloc(credentials_size);
      if (db->credentials == NULL) {
        ERROR("curl_json plugin: malloc failed.");
 -      return (-1);
 +      return -1;
      }
  
      ssnprintf(db->credentials, credentials_size, "%s:%s", db->user,
                       (long)CDTIME_T_TO_MS(plugin_get_interval()));
  #endif
  
 -  return (0);
 +  return 0;
  } /* }}} int cj_init_curl */
  
  static int cj_config_add_url(oconfig_item_t *ci) /* {{{ */
    if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
      WARNING("curl_json plugin: The `URL' block "
              "needs exactly one string argument.");
 -    return (-1);
 +    return -1;
    }
  
    db = calloc(1, sizeof(*db));
    if (db == NULL) {
      ERROR("curl_json plugin: calloc failed.");
 -    return (-1);
 +    return -1;
    }
  
    db->timeout = -1;
            "Invalid key: %s",
            ci->key);
      cj_free(db);
 -    return (-1);
 +    return -1;
    }
    if (status != 0) {
      sfree(db);
 -    return (status);
 +    return status;
    }
  
    /* Fill the `cj_t' structure.. */
      sfree(cb_name);
    } else {
      cj_free(db);
 -    return (-1);
 +    return -1;
    }
  
 -  return (0);
 +  return 0;
  }
  /* }}} int cj_config_add_database */
  
@@@ -766,10 -774,10 +774,10 @@@ static int cj_config(oconfig_item_t *ci
  
    if ((success == 0) && (errors > 0)) {
      ERROR("curl_json plugin: All statements failed.");
 -    return (-1);
 +    return -1;
    }
  
 -  return (0);
 +  return 0;
  } /* }}} int cj_config */
  
  /* }}} End of configuration handling functions */
@@@ -818,13 -826,13 +826,13 @@@ static int cj_sock_perform(cj_t *db) /
  
    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (fd < 0)
 -    return (-1);
 +    return -1;
    if (connect(fd, (struct sockaddr *)&sa_unix, sizeof(sa_unix)) < 0) {
      ERROR("curl_json plugin: connect(%s) failed: %s",
            (db->sock != NULL) ? db->sock : "<null>",
            sstrerror(errno, errbuf, sizeof(errbuf)));
      close(fd);
 -    return (-1);
 +    return -1;
    }
  
    ssize_t red;
              (db->sock != NULL) ? db->sock : "<null>",
              sstrerror(errno, errbuf, sizeof(errbuf)));
        close(fd);
 -      return (-1);
 +      return -1;
      }
      if (!cj_curl_callback(buffer, red, 1, db))
        break;
    } while (red > 0);
    close(fd);
 -  return (0);
 +  return 0;
  } /* }}} int cj_sock_perform */
  
  static int cj_curl_perform(cj_t *db) /* {{{ */
    if (status != CURLE_OK) {
      ERROR("curl_json plugin: curl_easy_perform failed with status %i: %s (%s)",
            status, db->curl_errbuf, url);
 -    return (-1);
 +    return -1;
    }
    if (db->stats != NULL)
      curl_stats_dispatch(db->stats, db->curl, cj_host(db), "curl_json",
      ERROR("curl_json plugin: curl_easy_perform failed with "
            "response code %ld (%s)",
            rc, url);
 -    return (-1);
 +    return -1;
    }
 -  return (0);
 +  return 0;
  } /* }}} int cj_curl_perform */
  
  static int cj_perform(cj_t *db) /* {{{ */
    if (db->yajl == NULL) {
      ERROR("curl_json plugin: yajl_alloc failed.");
      db->yajl = yprev;
 -    return (-1);
 +    return -1;
    }
  
    if (db->url)
    if (status < 0) {
      yajl_free(db->yajl);
      db->yajl = yprev;
 -    return (-1);
 +    return -1;
    }
  
  #if HAVE_YAJL_V2
      yajl_free_error(db->yajl, errmsg);
      yajl_free(db->yajl);
      db->yajl = yprev;
 -    return (-1);
 +    return -1;
    }
  
    yajl_free(db->yajl);
    db->yajl = yprev;
 -  return (0);
 +  return 0;
  } /* }}} int cj_perform */
  
  static int cj_read(user_data_t *ud) /* {{{ */
  
    if ((ud == NULL) || (ud->data == NULL)) {
      ERROR("curl_json plugin: cj_read: Invalid user data.");
 -    return (-1);
 +    return -1;
    }
  
    db = (cj_t *)ud->data;
  
    db->depth = 0;
    memset(&db->state, 0, sizeof(db->state));
-   db->state[db->depth].tree = db->tree;
  
-   return cj_perform(db);
+   /* This is not a compound literal because EPEL6's GCC is not cool enough to
+    * handle anonymous unions within compound literals. */
+   cj_tree_entry_t root = {0};
+   root.type = TREE;
+   root.tree = db->tree;
+   db->state[0].entry = &root;
+   int status = cj_perform(db);
+   db->state[0].entry = NULL;
+   return status;
  } /* }}} int cj_read */
  
  static int cj_init(void) /* {{{ */
    /* Call this while collectd is still single-threaded to avoid
     * initialization issues in libgcrypt. */
    curl_global_init(CURL_GLOBAL_SSL);
 -  return (0);
 +  return 0;
  } /* }}} int cj_init */
  
  void module_register(void) {