Merge branch 'collectd-4.10' into collectd-5.0
[collectd.git] / src / snmp.c
index 352075f..d24ca2c 100644 (file)
@@ -69,17 +69,9 @@ struct host_definition_s
   int version;
   void *sess_handle;
   c_complain_t complaint;
-  uint32_t interval;
-  time_t next_update;
+  cdtime_t interval;
   data_definition_t **data_list;
   int data_list_len;
-  enum          /******************************************************/
-  {             /* This host..                                        */
-    STATE_IDLE, /* - just sits there until `next_update < interval_g' */
-    STATE_WAIT, /* - waits to be queried.                             */
-    STATE_BUSY  /* - is currently being queried.                      */
-  } state;      /******************************************************/
-  struct host_definition_s *next;
 };
 typedef struct host_definition_s host_definition_t;
 
@@ -104,20 +96,50 @@ typedef struct csnmp_table_values_s csnmp_table_values_t;
 /*
  * Private variables
  */
-static int do_shutdown = 0;
-
-pthread_t *threads = NULL;
-int threads_num = 0;
-
 static data_definition_t *data_head = NULL;
-static host_definition_t *host_head = NULL;
 
-static pthread_mutex_t host_lock = PTHREAD_MUTEX_INITIALIZER;
-static pthread_cond_t  host_cond = PTHREAD_COND_INITIALIZER;
+/*
+ * Prototypes
+ */
+static int csnmp_read_host (user_data_t *ud);
 
 /*
  * Private functions
  */
+static void csnmp_host_close_session (host_definition_t *host) /* {{{ */
+{
+  if (host->sess_handle == NULL)
+    return;
+
+  snmp_sess_close (host->sess_handle);
+  host->sess_handle = NULL;
+} /* }}} void csnmp_host_close_session */
+
+static void csnmp_host_definition_destroy (void *arg) /* {{{ */
+{
+  host_definition_t *hd;
+
+  hd = arg;
+
+  if (hd == NULL)
+    return;
+
+  if (hd->name != NULL)
+  {
+    DEBUG ("snmp plugin: Destroying host definition for host `%s'.",
+       hd->name);
+  }
+
+  csnmp_host_close_session (hd);
+
+  sfree (hd->name);
+  sfree (hd->address);
+  sfree (hd->community);
+  sfree (hd->data_list);
+
+  sfree (hd);
+} /* }}} void csnmp_host_definition_destroy */
+
 /* Many functions to handle the configuration. {{{ */
 /* First there are many functions which do configuration stuff. It's a big
  * bloated and messy, I'm afraid. */
@@ -137,7 +159,6 @@ static pthread_cond_t  host_cond = PTHREAD_COND_INITIALIZER;
  *      +-> csnmp_config_add_host_community
  *      +-> csnmp_config_add_host_version
  *      +-> csnmp_config_add_host_collect
- *      +-> csnmp_config_add_host_interval
  */
 static void call_snmp_init_once (void)
 {
@@ -521,28 +542,17 @@ static int csnmp_config_add_host_collect (host_definition_t *host,
   return (0);
 } /* int csnmp_config_add_host_collect */
 
-static int csnmp_config_add_host_interval (host_definition_t *hd, oconfig_item_t *ci)
-{
-  if ((ci->values_num != 1)
-      || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
-  {
-    WARNING ("snmp plugin: The `Interval' config option needs exactly one number argument.");
-    return (-1);
-  }
-
-  hd->interval = ci->values[0].value.number >= 0
-    ? (uint32_t) ci->values[0].value.number
-    : 0;
-
-  return (0);
-} /* int csnmp_config_add_host_interval */
-
 static int csnmp_config_add_host (oconfig_item_t *ci)
 {
   host_definition_t *hd;
   int status = 0;
   int i;
 
+  /* Registration stuff. */
+  char cb_name[DATA_MAX_NAME_LEN];
+  user_data_t cb_data;
+  struct timespec cb_interval;
+
   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
   {
     WARNING ("snmp plugin: `Host' needs exactly one string argument.");
@@ -565,8 +575,6 @@ static int csnmp_config_add_host (oconfig_item_t *ci)
 
   hd->sess_handle = NULL;
   hd->interval = 0;
-  hd->next_update = 0;
-  hd->state = STATE_IDLE;
 
   for (i = 0; i < ci->children_num; i++)
   {
@@ -582,7 +590,7 @@ static int csnmp_config_add_host (oconfig_item_t *ci)
     else if (strcasecmp ("Collect", option->key) == 0)
       csnmp_config_add_host_collect (hd, option);
     else if (strcasecmp ("Interval", option->key) == 0)
-      csnmp_config_add_host_interval (hd, option);
+      cf_util_get_cdtime (option, &hd->interval);
     else
     {
       WARNING ("snmp plugin: csnmp_config_add_host: Option `%s' not allowed here.", option->key);
@@ -613,23 +621,29 @@ static int csnmp_config_add_host (oconfig_item_t *ci)
 
   if (status != 0)
   {
-    sfree (hd->name);
-    sfree (hd);
+    csnmp_host_definition_destroy (hd);
     return (-1);
   }
 
   DEBUG ("snmp plugin: hd = { name = %s, address = %s, community = %s, version = %i }",
       hd->name, hd->address, hd->community, hd->version);
 
-  if (host_head == NULL)
-    host_head = hd;
-  else
+  ssnprintf (cb_name, sizeof (cb_name), "snmp-%s", hd->name);
+
+  memset (&cb_data, 0, sizeof (cb_data));
+  cb_data.data = hd;
+  cb_data.free_func = csnmp_host_definition_destroy;
+
+  CDTIME_T_TO_TIMESPEC (hd->interval, &cb_interval);
+
+  status = plugin_register_complex_read (/* group = */ NULL, cb_name,
+      csnmp_read_host, /* interval = */ &cb_interval,
+      /* user_data = */ &cb_data);
+  if (status != 0)
   {
-    host_definition_t *last;
-    last = host_head;
-    while (last->next != NULL)
-      last = last->next;
-    last->next = hd;
+    ERROR ("snmp plugin: Registering complex read function failed.");
+    csnmp_host_definition_destroy (hd);
+    return (-1);
   }
 
   return (0);
@@ -659,15 +673,6 @@ static int csnmp_config (oconfig_item_t *ci)
 
 /* }}} End of the config stuff. Now the interesting part begins */
 
-static void csnmp_host_close_session (host_definition_t *host)
-{
-  if (host->sess_handle == NULL)
-    return;
-
-  snmp_sess_close (host->sess_handle);
-  host->sess_handle = NULL;
-} /* void csnmp_host_close_session */
-
 static void csnmp_host_open_session (host_definition_t *host)
 {
   struct snmp_session sess;
@@ -701,8 +706,11 @@ static value_t csnmp_value_list_to_value (struct variable_list *vl, int type,
     double scale, double shift)
 {
   value_t ret;
-  uint64_t temp = 0;
-  int defined = 1;
+  uint64_t tmp_unsigned = 0;
+  int64_t tmp_signed = 0;
+  _Bool defined = 1;
+  /* Set to true when the original SNMP type appears to have been signed. */
+  _Bool prefer_signed = 0;
 
   if ((vl->type == ASN_INTEGER)
       || (vl->type == ASN_UINTEGER)
@@ -712,15 +720,22 @@ static value_t csnmp_value_list_to_value (struct variable_list *vl, int type,
 #endif
       || (vl->type == ASN_GAUGE))
   {
-    temp = (uint32_t) *vl->val.integer;
-    DEBUG ("snmp plugin: Parsed int32 value is %"PRIu64".", temp);
+    tmp_unsigned = (uint32_t) *vl->val.integer;
+    tmp_signed = (int32_t) *vl->val.integer;
+
+    if ((vl->type == ASN_INTEGER)
+        || (vl->type == ASN_GAUGE))
+      prefer_signed = 1;
+
+    DEBUG ("snmp plugin: Parsed int32 value is %"PRIu64".", tmp_unsigned);
   }
   else if (vl->type == ASN_COUNTER64)
   {
-    temp = (uint32_t) vl->val.counter64->high;
-    temp = temp << 32;
-    temp += (uint32_t) vl->val.counter64->low;
-    DEBUG ("snmp plugin: Parsed int64 value is %"PRIu64".", temp);
+    tmp_unsigned = (uint32_t) vl->val.counter64->high;
+    tmp_unsigned = tmp_unsigned << 32;
+    tmp_unsigned += (uint32_t) vl->val.counter64->low;
+    tmp_signed = (int64_t) tmp_unsigned;
+    DEBUG ("snmp plugin: Parsed int64 value is %"PRIu64".", tmp_unsigned);
   }
   else if (vl->type == ASN_OCTET_STR)
   {
@@ -728,15 +743,28 @@ static value_t csnmp_value_list_to_value (struct variable_list *vl, int type,
   }
   else
   {
-    WARNING ("snmp plugin: I don't know the ASN type `%i'", (int) vl->type);
+    char oid_buffer[1024];
+
+    memset (oid_buffer, 0, sizeof (oid_buffer));
+    snprint_objid (oid_buffer, sizeof (oid_buffer) - 1,
+       vl->name, vl->name_length);
+
+#ifdef ASN_NULL
+    if (vl->type == ASN_NULL)
+      INFO ("snmp plugin: OID \"%s\" is undefined (type ASN_NULL)",
+         oid_buffer);
+    else
+#endif
+      WARNING ("snmp plugin: I don't know the ASN type \"%i\" (OID: %s)",
+         (int) vl->type, oid_buffer);
+
     defined = 0;
   }
 
   if (vl->type == ASN_OCTET_STR)
   {
-    char *endptr;
+    int status = -1;
 
-    endptr = NULL;
     if (vl->val.string != NULL)
     {
       char string[64];
@@ -747,44 +775,70 @@ static value_t csnmp_value_list_to_value (struct variable_list *vl, int type,
        string_length = vl->val_len;
 
       /* The strings we get from the Net-SNMP library may not be null
-       * terminated. That is why we're using `membpy' here and not `strcpy'.
+       * terminated. That is why we're using `memcpy' here and not `strcpy'.
        * `string_length' is set to `vl->val_len' which holds the length of the
        * string.  -octo */
       memcpy (string, vl->val.string, string_length);
       string[string_length] = 0;
 
-      if (type == DS_TYPE_COUNTER)
-      {
-       ret.counter = (counter_t) strtoll (string, &endptr, /* base = */ 0);
-       DEBUG ("snmp plugin: csnmp_value_list_to_value: String to counter: %s -> %llu",
-           string, (unsigned long long) ret.counter);
-      }
-      else if (type == DS_TYPE_GAUGE)
+      status = parse_value (string, &ret, type);
+      if (status != 0)
       {
-       ret.gauge = (gauge_t) strtod (string, &endptr);
-       DEBUG ("snmp plugin: csnmp_value_list_to_value: String to gauge: %s -> %g",
-           string, (double) ret.gauge);
+       ERROR ("snmp plugin: csnmp_value_list_to_value: Parsing string as %s failed: %s",
+           DS_TYPE_TO_STRING (type), string);
       }
     }
 
-    /* Check if an error occurred */
-    if ((vl->val.string == NULL) || (endptr == (char *) vl->val.string))
+    if (status != 0)
     {
-      if (type == DS_TYPE_COUNTER)
-       ret.counter = 0;
-      else if (type == DS_TYPE_GAUGE)
-       ret.gauge = NAN;
+      switch (type)
+      {
+       case DS_TYPE_COUNTER:
+       case DS_TYPE_DERIVE:
+       case DS_TYPE_ABSOLUTE:
+         memset (&ret, 0, sizeof (ret));
+         break;
+
+       case DS_TYPE_GAUGE:
+         ret.gauge = NAN;
+         break;
+
+       default:
+         ERROR ("snmp plugin: csnmp_value_list_to_value: Unknown "
+             "data source type: %i.", type);
+         ret.gauge = NAN;
+      }
     }
-  }
+  } /* if (vl->type == ASN_OCTET_STR) */
   else if (type == DS_TYPE_COUNTER)
   {
-    ret.counter = temp;
+    ret.counter = tmp_unsigned;
   }
   else if (type == DS_TYPE_GAUGE)
   {
+    if (!defined)
+      ret.gauge = NAN;
+    else if (prefer_signed)
+      ret.gauge = (scale * tmp_signed) + shift;
+    else
+      ret.gauge = (scale * tmp_unsigned) + shift;
+  }
+  else if (type == DS_TYPE_DERIVE)
+  {
+    if (prefer_signed)
+      ret.derive = (derive_t) tmp_signed;
+    else
+      ret.derive = (derive_t) tmp_unsigned;
+  }
+  else if (type == DS_TYPE_ABSOLUTE)
+  {
+    ret.absolute = (absolute_t) tmp_unsigned;
+  }
+  else
+  {
+    ERROR ("snmp plugin: csnmp_value_list_to_value: Unknown data source "
+       "type: %i.", type);
     ret.gauge = NAN;
-    if (defined != 0)
-      ret.gauge = (scale * temp) + shift;
   }
 
   return (ret);
@@ -853,6 +907,72 @@ static int csnmp_check_res_left_subtree (const host_definition_t *host,
   return (0);
 } /* int csnmp_check_res_left_subtree */
 
+static int csnmp_strvbcopy_hexstring (char *dst, /* {{{ */
+    const struct variable_list *vb, size_t dst_size)
+{
+  char *buffer_ptr;
+  size_t buffer_free;
+  size_t i;
+
+  buffer_ptr = dst;
+  buffer_free = dst_size;
+
+  for (i = 0; i < vb->val_len; i++)
+  {
+    int status;
+
+    status = snprintf (buffer_ptr, buffer_free,
+       (i == 0) ? "%02x" : ":%02x", (unsigned int) vb->val.bitstring[i]);
+
+    if (status >= buffer_free)
+    {
+      buffer_ptr += (buffer_free - 1);
+      *buffer_ptr = 0;
+      return (dst_size + (buffer_free - status));
+    }
+    else /* if (status < buffer_free) */
+    {
+      buffer_ptr += status;
+      buffer_free -= status;
+    }
+  }
+
+  return ((int) (dst_size - buffer_free));
+} /* }}} int csnmp_strvbcopy_hexstring */
+
+static int csnmp_strvbcopy (char *dst, /* {{{ */
+    const struct variable_list *vb, size_t dst_size)
+{
+  char *src;
+  size_t num_chars;
+  size_t i;
+
+  if (vb->type == ASN_OCTET_STR)
+    src = (char *) vb->val.string;
+  else if (vb->type == ASN_BIT_STR)
+    src = (char *) vb->val.bitstring;
+  else
+  {
+    dst[0] = 0;
+    return (EINVAL);
+  }
+
+  num_chars = dst_size - 1;
+  if (num_chars > vb->val_len)
+    num_chars = vb->val_len;
+
+  for (i = 0; i < num_chars; i++)
+  {
+    /* Check for control characters. */
+    if ((unsigned char)src[i] < 32)
+      return (csnmp_strvbcopy_hexstring (dst, vb, dst_size));
+    dst[i] = src[i];
+  }
+  dst[num_chars] = 0;
+
+  return ((int) vb->val_len);
+} /* }}} int csnmp_strvbcopy */
+
 static int csnmp_instance_list_add (csnmp_list_instances_t **head,
     csnmp_list_instances_t **tail,
     const struct snmp_pdu *res)
@@ -881,17 +1001,8 @@ static int csnmp_instance_list_add (csnmp_list_instances_t **head,
   if ((vb->type == ASN_OCTET_STR) || (vb->type == ASN_BIT_STR))
   {
     char *ptr;
-    size_t instance_len;
-
-    memset (il->instance, 0, sizeof (il->instance));
-    instance_len = sizeof (il->instance) - 1;
-    if (instance_len > vb->val_len)
-      instance_len = vb->val_len;
 
-    sstrncpy (il->instance, (char *) ((vb->type == ASN_OCTET_STR)
-         ? vb->val.string
-         : vb->val.bitstring),
-       instance_len + 1);
+    csnmp_strvbcopy (il->instance, vb, sizeof (il->instance));
 
     for (ptr = il->instance; *ptr != '\0'; ptr++)
     {
@@ -1029,7 +1140,7 @@ static int csnmp_dispatch_table (host_definition_t *host, data_definition_t *dat
       char temp[DATA_MAX_NAME_LEN];
 
       if (instance_list_ptr == NULL)
-       ssnprintf (temp, sizeof (temp), "%u", (uint32_t) subid);
+       ssnprintf (temp, sizeof (temp), "%"PRIu32, (uint32_t) subid);
       else
        sstrncpy (temp, instance_list_ptr->instance, sizeof (temp));
 
@@ -1413,15 +1524,21 @@ static int csnmp_read_value (host_definition_t *host, data_definition_t *data)
   return (0);
 } /* int csnmp_read_value */
 
-static int csnmp_read_host (host_definition_t *host)
+static int csnmp_read_host (user_data_t *ud)
 {
+  host_definition_t *host;
+  cdtime_t time_start;
+  cdtime_t time_end;
+  int status;
+  int success;
   int i;
-  time_t time_start;
-  time_t time_end;
 
-  time_start = time (NULL);
-  DEBUG ("snmp plugin: csnmp_read_host (%s) started at %u;", host->name,
-      (unsigned int) time_start);
+  host = ud->data;
+
+  if (host->interval == 0)
+    host->interval = interval_g;
+
+  time_start = cdtime ();
 
   if (host->sess_handle == NULL)
     csnmp_host_open_session (host);
@@ -1429,184 +1546,51 @@ static int csnmp_read_host (host_definition_t *host)
   if (host->sess_handle == NULL)
     return (-1);
 
+  success = 0;
   for (i = 0; i < host->data_list_len; i++)
   {
     data_definition_t *data = host->data_list[i];
 
     if (data->is_table)
-      csnmp_read_table (host, data);
+      status = csnmp_read_table (host, data);
     else
-      csnmp_read_value (host, data);
+      status = csnmp_read_value (host, data);
+
+    if (status == 0)
+      success++;
   }
 
-  time_end = time (NULL);
-  DEBUG ("snmp plugin: csnmp_read_host (%s) finished at %u;", host->name,
-      (unsigned int) time_end);
-  if ((uint32_t) (time_end - time_start) > host->interval)
+  time_end = cdtime ();
+  if ((time_end - time_start) > host->interval)
   {
-    WARNING ("snmp plugin: Host `%s' should be queried every %i seconds, "
-       "but reading all values takes %u seconds.",
-       host->name, host->interval, (unsigned int) (time_end - time_start));
+    WARNING ("snmp plugin: Host `%s' should be queried every %.3f "
+       "seconds, but reading all values takes %.3f seconds.",
+       host->name,
+       CDTIME_T_TO_DOUBLE (host->interval),
+       CDTIME_T_TO_DOUBLE (time_end - time_start));
   }
 
+  if (success == 0)
+    return (-1);
+
   return (0);
 } /* int csnmp_read_host */
 
-static void *csnmp_read_thread (void __attribute__((unused)) *data)
-{
-  host_definition_t *host;
-
-  pthread_mutex_lock (&host_lock);
-  while (do_shutdown == 0)
-  {
-    pthread_cond_wait (&host_cond, &host_lock);
-
-    for (host = host_head; host != NULL; host = host->next)
-    {
-      if (do_shutdown != 0)
-       break;
-      if (host->state != STATE_WAIT)
-       continue;
-
-      host->state = STATE_BUSY;
-      pthread_mutex_unlock (&host_lock);
-      csnmp_read_host (host);
-      pthread_mutex_lock (&host_lock);
-      host->state = STATE_IDLE;
-    } /* for (host) */
-  } /* while (do_shutdown == 0) */
-  pthread_mutex_unlock (&host_lock);
-
-  pthread_exit ((void *) 0);
-  return ((void *) 0);
-} /* void *csnmp_read_thread */
-
 static int csnmp_init (void)
 {
-  host_definition_t *host;
-  int i;
-
-  if (host_head == NULL)
-  {
-    NOTICE ("snmp plugin: No host has been defined.");
-    return (-1);
-  }
-
   call_snmp_init_once ();
 
-  threads_num = 0;
-  for (host = host_head; host != NULL; host = host->next)
-  {
-    threads_num++;
-    /* We need to initialize `interval' here, because `interval_g' isn't
-     * initialized during `configure'. */
-    host->next_update = time (NULL);
-    if (host->interval == 0)
-    {
-      host->interval = interval_g;
-    }
-    else if (host->interval < (uint32_t) interval_g)
-    {
-      host->interval = interval_g;
-      WARNING ("snmp plugin: Data for host `%s' will be collected every %i seconds.",
-         host->name, host->interval);
-    }
-
-    csnmp_host_open_session (host);
-  } /* for (host) */
-
-  /* Now start the reading threads */
-  if (threads_num > 3)
-  {
-    threads_num = 3 + ((threads_num - 3) / 10);
-    if (threads_num > 10)
-      threads_num = 10;
-  }
-
-  threads = (pthread_t *) malloc (threads_num * sizeof (pthread_t));
-  if (threads == NULL)
-  {
-    ERROR ("snmp plugin: malloc failed.");
-    return (-1);
-  }
-  memset (threads, '\0', threads_num * sizeof (pthread_t));
-
-  for (i = 0; i < threads_num; i++)
-      pthread_create (threads + i, NULL, csnmp_read_thread, (void *) 0);
-
   return (0);
 } /* int csnmp_init */
 
-static int csnmp_read (void)
-{
-  host_definition_t *host;
-  time_t now;
-
-  if (host_head == NULL)
-  {
-    INFO ("snmp plugin: No hosts configured.");
-    return (-1);
-  }
-
-  now = time (NULL);
-
-  pthread_mutex_lock (&host_lock);
-  for (host = host_head; host != NULL; host = host->next)
-  {
-    if (host->state != STATE_IDLE)
-      continue;
-
-    /* Skip this host if the next or a later iteration will be sufficient. */
-    if (host->next_update >= (now + interval_g))
-      continue;
-
-    host->state = STATE_WAIT;
-    host->next_update = now + host->interval;
-  } /* for (host) */
-
-  pthread_cond_broadcast (&host_cond);
-  pthread_mutex_unlock (&host_lock);
-
-  return (0);
-} /* int csnmp_read */
-
 static int csnmp_shutdown (void)
 {
-  host_definition_t *host_this;
-  host_definition_t *host_next;
-
   data_definition_t *data_this;
   data_definition_t *data_next;
 
-  int i;
-
-  pthread_mutex_lock (&host_lock);
-  do_shutdown = 1;
-  pthread_cond_broadcast (&host_cond);
-  pthread_mutex_unlock (&host_lock);
-
-  for (i = 0; i < threads_num; i++)
-    pthread_join (threads[i], NULL);
-
-  /* Now that all the threads have exited, let's free all the global variables.
-   * This isn't really neccessary, I guess, but I think it's good stile to do
-   * so anyway. */
-  host_this = host_head;
-  host_head = NULL;
-  while (host_this != NULL)
-  {
-    host_next = host_this->next;
-
-    csnmp_host_close_session (host_this);
-
-    sfree (host_this->name);
-    sfree (host_this->address);
-    sfree (host_this->community);
-    sfree (host_this->data_list);
-    sfree (host_this);
-
-    host_this = host_next;
-  }
+  /* When we get here, the read threads have been stopped and all the
+   * `host_definition_t' will be freed. */
+  DEBUG ("snmp plugin: Destroying all data definitions.");
 
   data_this = data_head;
   data_head = NULL;
@@ -1629,7 +1613,6 @@ void module_register (void)
 {
   plugin_register_complex_config ("snmp", csnmp_config);
   plugin_register_init ("snmp", csnmp_init);
-  plugin_register_read ("snmp", csnmp_read);
   plugin_register_shutdown ("snmp", csnmp_shutdown);
 } /* void module_register */