Merge tag 'collectd-5.9.2'
[collectd.git] / src / curl_json.c
index b9394c2..edfaf00 100644 (file)
 
 #include "collectd.h"
 
-#include "common.h"
 #include "plugin.h"
-#include "utils_avltree.h"
+#include "utils/avltree/avltree.h"
+#include "utils/common/common.h"
+#include "utils/curl_stats/curl_stats.h"
 #include "utils_complain.h"
-#include "utils_curl_stats.h"
 
 #include <sys/types.h>
 #include <sys/un.h>
@@ -44,8 +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,31 +51,53 @@ 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;
+  char *plugin_name;
   char *host;
 
   char *sock;
 
   char *url;
+  int address_family;
   char *user;
   char *pass;
   char *credentials;
-  _Bool digest;
-  _Bool verify_peer;
-  _Bool verify_host;
+  bool digest;
+  bool verify_peer;
+  bool verify_host;
   char *cacert;
   struct curl_slist *headers;
   char *post_body;
-  cdtime_t interval;
   int timeout;
   curl_stats_t *stats;
 
@@ -87,15 +107,7 @@ struct cj_s /* {{{ */
   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; /* }}} */
 
@@ -144,12 +156,10 @@ static size_t cj_curl_callback(void *buf, /* {{{ */
 } /* }}} 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 +192,20 @@ static int cj_load_key(cj_t *db, char const *key) {
 
   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;
@@ -219,7 +218,7 @@ static void cj_advance_array(cj_t *db) {
   db->state[db->depth].index++;
 
   char name[DATA_MAX_NAME_LEN];
-  ssnprintf(name, sizeof(name), "%d", db->state[db->depth].index);
+  snprintf(name, sizeof(name), "%d", db->state[db->depth].index);
   cj_load_key(db, name);
 }
 
@@ -227,44 +226,36 @@ static void cj_advance_array(cj_t *db) {
 #define CJ_CB_ABORT 0
 #define CJ_CB_CONTINUE 1
 
-static int cj_cb_boolean(void *ctx, int boolVal) {
-  cj_advance_array(ctx);
-  return CJ_CB_CONTINUE;
-}
-
 static int cj_cb_null(void *ctx) {
   cj_advance_array(ctx);
   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);
+  buffer[sizeof(buffer) - 1] = '\0';
+
+  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;
   }
@@ -282,7 +273,7 @@ static int cj_cb_map_key(void *ctx, unsigned char const *in_name,
   char name[in_name_len + 1];
 
   memmove(name, in_name, in_name_len);
-  name[sizeof(name) - 1] = 0;
+  name[sizeof(name) - 1] = '\0';
 
   if (cj_load_key(ctx, name) != 0)
     return CJ_CB_ABORT;
@@ -295,9 +286,16 @@ static int cj_cb_string(void *ctx, const unsigned char *val, yajl_len_t len) {
   return cj_cb_number(ctx, (const char *)val, len);
 } /* int cj_cb_string */
 
+static int cj_cb_boolean(void *ctx, int boolVal) {
+  if (boolVal)
+    return cj_cb_number(ctx, "1", 1);
+  else
+    return cj_cb_number(ctx, "0", 1);
+} /* int cj_cb_boolean */
+
 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;
@@ -326,7 +324,7 @@ static int cj_cb_start_array(void *ctx) {
     return CJ_CB_ABORT;
   }
   db->depth++;
-  db->state[db->depth].in_array = 1;
+  db->state[db->depth].in_array = true;
   db->state[db->depth].index = 0;
 
   cj_load_key(db, "0");
@@ -336,7 +334,7 @@ static int cj_cb_start_array(void *ctx) {
 
 static int cj_cb_end_array(void *ctx) {
   cj_t *db = (cj_t *)ctx;
-  db->state[db->depth].in_array = 0;
+  db->state[db->depth].in_array = false;
   return cj_cb_end(ctx);
 }
 
@@ -365,17 +363,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);
@@ -401,6 +398,7 @@ static void cj_free(void *arg) /* {{{ */
   db->tree = NULL;
 
   sfree(db->instance);
+  sfree(db->plugin_name);
   sfree(db->host);
 
   sfree(db->sock);
@@ -469,13 +467,21 @@ static int cj_append_key(cj_t *db, cj_key_t *key) { /* {{{ */
     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;
   }
 
@@ -484,7 +490,13 @@ static int cj_append_key(cj_t *db, cj_key_t *key) { /* {{{ */
     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 */
 
@@ -504,7 +516,6 @@ static int cj_config_add_key(cj_t *db, /* {{{ */
     ERROR("curl_json plugin: calloc failed.");
     return -1;
   }
-  key->magic = CJ_KEY_MAGIC;
 
   if (strcasecmp("Key", ci->key) == 0) {
     status = cf_util_get_string(ci, &key->path);
@@ -570,9 +581,9 @@ static int cj_init_curl(cj_t *db) /* {{{ */
   curl_easy_setopt(db->curl, CURLOPT_WRITEDATA, db);
   curl_easy_setopt(db->curl, CURLOPT_USERAGENT, COLLECTD_USERAGENT);
   curl_easy_setopt(db->curl, CURLOPT_ERRORBUFFER, db->curl_errbuf);
-  curl_easy_setopt(db->curl, CURLOPT_URL, db->url);
   curl_easy_setopt(db->curl, CURLOPT_FOLLOWLOCATION, 1L);
   curl_easy_setopt(db->curl, CURLOPT_MAXREDIRS, 50L);
+  curl_easy_setopt(db->curl, CURLOPT_IPRESOLVE, db->address_family);
 
   if (db->user != NULL) {
 #ifdef HAVE_CURLOPT_USERNAME
@@ -592,8 +603,8 @@ static int cj_init_curl(cj_t *db) /* {{{ */
       return -1;
     }
 
-    ssnprintf(db->credentials, credentials_size, "%s:%s", db->user,
-              (db->pass == NULL) ? "" : db->pass);
+    snprintf(db->credentials, credentials_size, "%s:%s", db->user,
+             (db->pass == NULL) ? "" : db->pass);
     curl_easy_setopt(db->curl, CURLOPT_USERPWD, db->credentials);
 #endif
 
@@ -613,9 +624,6 @@ static int cj_init_curl(cj_t *db) /* {{{ */
 #ifdef HAVE_CURLOPT_TIMEOUT_MS
   if (db->timeout >= 0)
     curl_easy_setopt(db->curl, CURLOPT_TIMEOUT_MS, (long)db->timeout);
-  else if (db->interval > 0)
-    curl_easy_setopt(db->curl, CURLOPT_TIMEOUT_MS,
-                     (long)CDTIME_T_TO_MS(db->interval));
   else
     curl_easy_setopt(db->curl, CURLOPT_TIMEOUT_MS,
                      (long)CDTIME_T_TO_MS(plugin_get_interval()));
@@ -628,6 +636,7 @@ static int cj_config_add_url(oconfig_item_t *ci) /* {{{ */
 {
   cj_t *db;
   int status = 0;
+  cdtime_t interval = 0;
 
   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
     WARNING("curl_json plugin: The `URL' block "
@@ -642,6 +651,7 @@ static int cj_config_add_url(oconfig_item_t *ci) /* {{{ */
   }
 
   db->timeout = -1;
+  db->address_family = CURL_IPRESOLVE_WHATEVER;
 
   if (strcasecmp("URL", ci->key) == 0)
     status = cf_util_get_string(ci, &db->url);
@@ -665,6 +675,8 @@ static int cj_config_add_url(oconfig_item_t *ci) /* {{{ */
 
     if (strcasecmp("Instance", child->key) == 0)
       status = cf_util_get_string(child, &db->instance);
+    else if (strcasecmp("Plugin", child->key) == 0)
+      status = cf_util_get_string(child, &db->plugin_name);
     else if (strcasecmp("Host", child->key) == 0)
       status = cf_util_get_string(child, &db->host);
     else if (db->url && strcasecmp("User", child->key) == 0)
@@ -686,13 +698,38 @@ static int cj_config_add_url(oconfig_item_t *ci) /* {{{ */
     else if (strcasecmp("Key", child->key) == 0)
       status = cj_config_add_key(db, child);
     else if (strcasecmp("Interval", child->key) == 0)
-      status = cf_util_get_cdtime(child, &db->interval);
+      status = cf_util_get_cdtime(child, &interval);
     else if (strcasecmp("Timeout", child->key) == 0)
       status = cf_util_get_int(child, &db->timeout);
     else if (strcasecmp("Statistics", child->key) == 0) {
       db->stats = curl_stats_from_config(child);
       if (db->stats == NULL)
         status = -1;
+    } else if (db->url && strcasecmp("AddressFamily", child->key) == 0) {
+      char *af = NULL;
+      status = cf_util_get_string(child, &af);
+      if (status != 0 || af == NULL) {
+        WARNING("curl_json plugin: Cannot parse value of `%s' for URL `%s'.",
+                child->key, db->url);
+      } else if (strcasecmp("any", af) == 0) {
+        db->address_family = CURL_IPRESOLVE_WHATEVER;
+      } else if (strcasecmp("ipv4", af) == 0) {
+        db->address_family = CURL_IPRESOLVE_V4;
+      } else if (strcasecmp("ipv6", af) == 0) {
+        /* If curl supports ipv6, use it. If not, log a warning and
+         * fall back to default - don't set status to non-zero.
+         */
+        curl_version_info_data *curl_info = curl_version_info(CURLVERSION_NOW);
+        if (curl_info->features & CURL_VERSION_IPV6)
+          db->address_family = CURL_IPRESOLVE_V6;
+        else
+          WARNING("curl_json plugin: IPv6 not supported by this libCURL. "
+                  "Using fallback `any'.");
+      } else {
+        WARNING("curl_json plugin: Unsupported value of `%s' for URL `%s'.",
+                child->key, db->url);
+        status = -1;
+      }
     } else {
       WARNING("curl_json plugin: Option `%s' not allowed here.", child->key);
       status = -1;
@@ -724,10 +761,10 @@ static int cj_config_add_url(oconfig_item_t *ci) /* {{{ */
     cb_name = ssnprintf_alloc("curl_json-%s-%s", db->instance,
                               db->url ? db->url : db->sock);
 
-    plugin_register_complex_read(/* group = */ NULL, cb_name, cj_read,
-                                 /* interval = */ db->interval,
+    plugin_register_complex_read(/* group = */ NULL, cb_name, cj_read, interval,
                                  &(user_data_t){
-                                     .data = db, .free_func = cj_free,
+                                     .data = db,
+                                     .free_func = cj_free,
                                  });
     sfree(cb_name);
   } else {
@@ -792,25 +829,22 @@ static void cj_submit_impl(cj_t *db, cj_key_t *key, value_t *value) /* {{{ */
   if (key->instance == NULL) {
     int len = 0;
     for (int i = 0; i < db->depth; i++)
-      len += ssnprintf(vl.type_instance + len, sizeof(vl.type_instance) - len,
-                       i ? "-%s" : "%s", db->state[i + 1].name);
+      len += snprintf(vl.type_instance + len, sizeof(vl.type_instance) - len,
+                      i ? "-%s" : "%s", db->state[i + 1].name);
   } else
     sstrncpy(vl.type_instance, key->instance, sizeof(vl.type_instance));
 
   sstrncpy(vl.host, cj_host(db), sizeof(vl.host));
-  sstrncpy(vl.plugin, "curl_json", sizeof(vl.plugin));
+  sstrncpy(vl.plugin, (db->plugin_name != NULL) ? db->plugin_name : "curl_json",
+           sizeof(vl.plugin));
   sstrncpy(vl.plugin_instance, db->instance, sizeof(vl.plugin_instance));
   sstrncpy(vl.type, key->type, sizeof(vl.type));
 
-  if (db->interval > 0)
-    vl.interval = db->interval;
-
   plugin_dispatch_values(&vl);
 } /* }}} int cj_submit_impl */
 
 static int cj_sock_perform(cj_t *db) /* {{{ */
 {
-  char errbuf[1024];
   struct sockaddr_un sa_unix = {
       .sun_family = AF_UNIX,
   };
@@ -821,8 +855,7 @@ static int cj_sock_perform(cj_t *db) /* {{{ */
     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)));
+          (db->sock != NULL) ? db->sock : "<null>", STRERRNO);
     close(fd);
     return -1;
   }
@@ -833,8 +866,7 @@ static int cj_sock_perform(cj_t *db) /* {{{ */
     red = read(fd, buffer, sizeof(buffer));
     if (red < 0) {
       ERROR("curl_json plugin: read(%s) failed: %s",
-            (db->sock != NULL) ? db->sock : "<null>",
-            sstrerror(errno, errbuf, sizeof(errbuf)));
+            (db->sock != NULL) ? db->sock : "<null>", STRERRNO);
       close(fd);
       return -1;
     }
@@ -850,12 +882,13 @@ static int cj_curl_perform(cj_t *db) /* {{{ */
   int status;
   long rc;
   char *url;
-  url = db->url;
+
+  curl_easy_setopt(db->curl, CURLOPT_URL, db->url);
 
   status = curl_easy_perform(db->curl);
   if (status != CURLE_OK) {
     ERROR("curl_json plugin: curl_easy_perform failed with status %i: %s (%s)",
-          status, db->curl_errbuf, url);
+          status, db->curl_errbuf, db->url);
     return -1;
   }
   if (db->stats != NULL)
@@ -938,9 +971,19 @@ static int cj_read(user_data_t *ud) /* {{{ */
 
   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) /* {{{ */