Merge pull request #3329 from efuss/fix-3311
[collectd.git] / src / curl_xml.c
index 8931407..4524ac3 100644 (file)
@@ -21,9 +21,9 @@
 
 #include "collectd.h"
 
-#include "common.h"
 #include "plugin.h"
-#include "utils_curl_stats.h"
+#include "utils/common/common.h"
+#include "utils/curl_stats/curl_stats.h"
 #include "utils_llist.h"
 
 #include <libxml/parser.h>
@@ -76,12 +76,13 @@ struct cx_s /* {{{ */
   char *host;
 
   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;
   char *post_body;
   int timeout;
@@ -157,13 +158,14 @@ static void cx_xpath_list_free(llist_t *list) /* {{{ */
   while (le != NULL) {
     llentry_t *le_next = le->next;
 
+    /* this also frees xpath->path used for le->key */
     cx_xpath_free(le->value);
 
     le = le_next;
   }
 
   llist_destroy(list);
-} /* }}} void cx_list_free */
+} /* }}} void cx_xpath_list_free */
 
 static void cx_free(void *arg) /* {{{ */
 {
@@ -239,8 +241,8 @@ static int cx_check_type(const data_set_t *ds, cx_xpath_t *xpath) /* {{{ */
   }
 
   if (ds->ds_num != xpath->values_len) {
-    WARNING("curl_xml plugin: DataSet `%s' requires %zu values, but config "
-            "talks about %zu",
+    WARNING("curl_xml plugin: DataSet `%s' requires %" PRIsz
+            " values, but config talks about %" PRIsz,
             xpath->type, ds->ds_num, xpath->values_len);
     return -1;
   }
@@ -275,6 +277,9 @@ static int cx_if_not_text_node(xmlNodePtr node) /* {{{ */
   return -1;
 } /* }}} cx_if_not_text_node */
 
+/*
+ * Returned value should be freed with xmlFree().
+ */
 static char *cx_get_text_node_value(xmlXPathContextPtr xpath_ctx, /* {{{ */
                                     char *expr, const char *from_option) {
   xmlXPathObjectPtr values_node_obj = cx_evaluate_xpath(xpath_ctx, expr);
@@ -286,18 +291,18 @@ static char *cx_get_text_node_value(xmlXPathContextPtr xpath_ctx, /* {{{ */
 
   if (tmp_size == 0) {
     WARNING("curl_xml plugin: "
-            "relative xpath expression \"%s\" %sdoesn't match "
+            "relative xpath expression \"%s\" from '%s' doesn't match "
             "any of the nodes.",
-            expr, (from_option == NULL) ? "" : from_option);
+            expr, from_option);
     xmlXPathFreeObject(values_node_obj);
     return NULL;
   }
 
   if (tmp_size > 1) {
     WARNING("curl_xml plugin: "
-            "relative xpath expression \"%s\" %sis expected to return "
+            "relative xpath expression \"%s\" from '%s' is expected to return "
             "only one text node. Skipping the node.",
-            expr, (from_option == NULL) ? "" : from_option);
+            expr, from_option);
     xmlXPathFreeObject(values_node_obj);
     return NULL;
   }
@@ -305,10 +310,10 @@ static char *cx_get_text_node_value(xmlXPathContextPtr xpath_ctx, /* {{{ */
   /* ignoring the element if other than textnode/attribute*/
   if (cx_if_not_text_node(values_node->nodeTab[0])) {
     WARNING("curl_xml plugin: "
-            "relative xpath expression \"%s\" %sis expected to return "
+            "relative xpath expression \"%s\" from '%s' is expected to return "
             "only text/attribute node which is not the case. "
             "Skipping the node.",
-            expr, (from_option == NULL) ? "" : from_option);
+            expr, from_option);
     xmlXPathFreeObject(values_node_obj);
     return NULL;
   }
@@ -326,7 +331,7 @@ static int cx_handle_single_value_xpath(xmlXPathContextPtr xpath_ctx, /* {{{ */
                                         value_list_t *vl, int index) {
 
   char *node_value = cx_get_text_node_value(
-      xpath_ctx, xpath->values[index].path, "from 'ValuesFrom' ");
+      xpath_ctx, xpath->values[index].path, "ValuesFrom");
 
   if (node_value == NULL)
     return -1;
@@ -352,7 +357,7 @@ static int cx_handle_single_value_xpath(xmlXPathContextPtr xpath_ctx, /* {{{ */
                                               /* endptr = */ NULL);
   }
 
-  sfree(node_value);
+  xmlFree(node_value);
 
   /* We have reached here which means that
    * we have got something to work */
@@ -385,8 +390,8 @@ static int cx_handle_instance_xpath(xmlXPathContextPtr xpath_ctx, /* {{{ */
 
   /* Handle type instance */
   if (xpath->instance != NULL) {
-    char *node_value = cx_get_text_node_value(xpath_ctx, xpath->instance,
-                                              "from 'InstanceFrom' ");
+    char *node_value =
+        cx_get_text_node_value(xpath_ctx, xpath->instance, "InstanceFrom");
     if (node_value == NULL)
       return -1;
 
@@ -396,7 +401,7 @@ static int cx_handle_instance_xpath(xmlXPathContextPtr xpath_ctx, /* {{{ */
     else
       sstrncpy(vl->type_instance, node_value, sizeof(vl->type_instance));
 
-    sfree(node_value);
+    xmlFree(node_value);
   } else if (xpath->instance_prefix != NULL)
     sstrncpy(vl->type_instance, xpath->instance_prefix,
              sizeof(vl->type_instance));
@@ -404,13 +409,13 @@ static int cx_handle_instance_xpath(xmlXPathContextPtr xpath_ctx, /* {{{ */
   /* Handle plugin instance */
   if (xpath->plugin_instance_from != NULL) {
     char *node_value = cx_get_text_node_value(
-        xpath_ctx, xpath->plugin_instance_from, "from 'PluginInstanceFrom' ");
+        xpath_ctx, xpath->plugin_instance_from, "PluginInstanceFrom");
 
     if (node_value == NULL)
       return -1;
 
     sstrncpy(vl->plugin_instance, node_value, sizeof(vl->plugin_instance));
-    sfree(node_value);
+    xmlFree(node_value);
   }
 
   return 0;
@@ -448,6 +453,7 @@ static int cx_handle_xpath(const cx_t *db, /* {{{ */
           "since the base xpath expression \"%s\" "
           "returned multiple results. Skipping the xpath block...",
           xpath->path);
+    xmlXPathFreeObject(base_node_obj);
     return -1;
   }
 
@@ -691,8 +697,8 @@ static int cx_config_add_namespace(cx_t *db, /* {{{ */
     return EINVAL;
   }
 
-  cx_namespace_t *ns = realloc(
-      db->namespaces, sizeof(*db->namespaces) * (db->namespaces_num + 1));
+  cx_namespace_t *ns = realloc(db->namespaces, sizeof(*db->namespaces) *
+                                                   (db->namespaces_num + 1));
   if (ns == NULL) {
     ERROR("curl_xml plugin: realloc failed.");
     return ENOMEM;
@@ -731,6 +737,7 @@ static int cx_init_curl(cx_t *db) /* {{{ */
   curl_easy_setopt(db->curl, CURLOPT_ERRORBUFFER, db->curl_errbuf);
   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
@@ -809,6 +816,7 @@ static int cx_config_add_url(oconfig_item_t *ci) /* {{{ */
   }
 
   db->timeout = -1;
+  db->address_family = CURL_IPRESOLVE_WHATEVER;
 
   int status = cf_util_get_string(ci, &db->url);
   if (status != 0) {
@@ -818,6 +826,8 @@ static int cx_config_add_url(oconfig_item_t *ci) /* {{{ */
     return status;
   }
 
+  cdtime_t interval = 0;
+
   /* Fill the `cx_t' structure.. */
   for (int i = 0; i < ci->children_num; i++) {
     oconfig_item_t *child = ci->children + i;
@@ -848,12 +858,39 @@ static int cx_config_add_url(oconfig_item_t *ci) /* {{{ */
       status = cf_util_get_string(child, &db->post_body);
     else if (strcasecmp("Namespace", child->key) == 0)
       status = cx_config_add_namespace(db, child);
+    else if (strcasecmp("Interval", child->key) == 0)
+      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 (strcasecmp("AddressFamily", child->key) == 0) {
+      char *af = NULL;
+      status = cf_util_get_string(child, &af);
+      if (status != 0 || af == NULL) {
+        WARNING("curl_xml 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_xml plugin: IPv6 not supported by this libCURL. "
+                  "Using fallback `any'.");
+      } else {
+        WARNING("curl_xml plugin: Unsupported value of `%s' for URL `%s'.",
+                child->key, db->url);
+        status = -1;
+      }
     } else {
       WARNING("curl_xml plugin: Option `%s' not allowed here.", child->key);
       status = -1;
@@ -886,9 +923,10 @@ static int cx_config_add_url(oconfig_item_t *ci) /* {{{ */
   char *cb_name = ssnprintf_alloc("curl_xml-%s-%s", db->instance, db->url);
 
   plugin_register_complex_read(/* group = */ "curl_xml", cb_name, cx_read,
-                               /* interval = */ 0,
+                               /* interval = */ interval,
                                &(user_data_t){
-                                   .data = db, .free_func = cx_free,
+                                   .data = db,
+                                   .free_func = cx_free,
                                });
   sfree(cb_name);
   return 0;