Merge remote-tracking branch 'github/pr/1749'
authorFlorian Forster <octo@collectd.org>
Fri, 14 Oct 2016 20:51:04 +0000 (22:51 +0200)
committerFlorian Forster <octo@collectd.org>
Fri, 14 Oct 2016 20:51:04 +0000 (22:51 +0200)
28 files changed:
src/Makefile.am
src/aggregation.c
src/amqp.c
src/daemon/common.c
src/daemon/common.h
src/daemon/plugin.h
src/daemon/plugin_mock.c
src/daemon/utils_cache_mock.c
src/exec.c
src/testing.h
src/unixsock.c
src/utils_cmd_flush.c
src/utils_cmd_flush.h
src/utils_cmd_getthreshold.c
src/utils_cmd_getval.c
src/utils_cmd_getval.h
src/utils_cmd_listval.c
src/utils_cmd_listval.h
src/utils_cmd_putval.c
src/utils_cmd_putval.h
src/utils_cmds.c [new file with mode: 0644]
src/utils_cmds.h [new file with mode: 0644]
src/utils_cmds_test.c [new file with mode: 0644]
src/utils_vl_lookup.c
src/utils_vl_lookup.h
src/utils_vl_lookup_test.c
src/valgrind.FreeBSD.suppress
src/write_kafka.c

index 344fcfd..7254bd9 100644 (file)
@@ -49,6 +49,21 @@ TESTS += test_utils_latency
 test_utils_latency_SOURCES = utils_latency_test.c testing.h
 test_utils_latency_LDADD = liblatency.la daemon/libplugin_mock.la -lm
 
+noinst_LTLIBRARIES += libcmds.la
+libcmds_la_SOURCES = utils_cmds.c utils_cmds.h \
+               utils_cmd_flush.c utils_cmd_flush.h \
+               utils_cmd_getval.c utils_cmd_getval.h \
+               utils_cmd_listval.c utils_cmd_listval.h \
+               utils_cmd_putval.c utils_cmd_putval.h \
+               utils_parse_option.c
+libcmds_la_LIBADD = daemon/libcommon.la daemon/libmetadata.la \
+               daemon/libplugin_mock.la -lm
+check_PROGRAMS += test_utils_cmds
+TESTS += test_utils_cmds
+test_utils_cmds_SOURCES = utils_cmds_test.c testing.h
+test_utils_cmds_LDADD = libcmds.la \
+               daemon/libplugin_mock.la daemon/libmetadata.la
+
 noinst_LTLIBRARIES += liblookup.la
 liblookup_la_SOURCES = utils_vl_lookup.c utils_vl_lookup.h
 liblookup_la_LIBADD = daemon/libavltree.la
@@ -154,8 +169,9 @@ endif
 if BUILD_PLUGIN_AMQP
 pkglib_LTLIBRARIES += amqp.la
 amqp_la_SOURCES = amqp.c \
+                 utils_cmds.c utils_cmds.h \
                  utils_cmd_putval.c utils_cmd_putval.h \
-                 utils_parse_option.c utils_parse_option.h \
+                 utils_parse_option.c utils_parse_option.h \
                  utils_format_graphite.c utils_format_graphite.h
 amqp_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBRABBITMQ_LDFLAGS)
 amqp_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBRABBITMQ_CPPFLAGS)
@@ -419,6 +435,7 @@ endif
 if BUILD_PLUGIN_EXEC
 pkglib_LTLIBRARIES += exec.la
 exec_la_SOURCES = exec.c \
+                 utils_cmds.c utils_cmds.h \
                  utils_cmd_putnotif.c utils_cmd_putnotif.h \
                  utils_cmd_putval.c utils_cmd_putval.h \
                  utils_parse_option.h utils_parse_option.c
@@ -1189,6 +1206,7 @@ endif
 if BUILD_PLUGIN_UNIXSOCK
 pkglib_LTLIBRARIES += unixsock.la
 unixsock_la_SOURCES = unixsock.c \
+                     utils_cmds.c utils_cmds.h \
                      utils_cmd_flush.h utils_cmd_flush.c \
                      utils_cmd_getval.h utils_cmd_getval.c \
                      utils_cmd_getthreshold.h utils_cmd_getthreshold.c \
@@ -1288,6 +1306,7 @@ if BUILD_PLUGIN_WRITE_KAFKA
 pkglib_LTLIBRARIES += write_kafka.la
 write_kafka_la_SOURCES = write_kafka.c \
                         utils_format_graphite.c utils_format_graphite.h \
+                        utils_cmds.c utils_cmds.h \
                         utils_cmd_putval.c utils_cmd_putval.h \
                         utils_crc32.c utils_crc32.h
 write_kafka_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBRDKAFKA_CPPFLAGS)
index 2744c89..bb37b85 100644 (file)
@@ -38,7 +38,7 @@
 
 struct aggregation_s /* {{{ */
 {
-  identifier_t ident;
+  lookup_identifier_t ident;
   unsigned int group_by;
 
   unsigned int regex_fields;
@@ -62,7 +62,7 @@ typedef struct agg_instance_s agg_instance_t;
 struct agg_instance_s /* {{{ */
 {
   pthread_mutex_t lock;
-  identifier_t ident;
+  lookup_identifier_t ident;
 
   int ds_type;
 
index 06fd1f1..f9777a9 100644 (file)
@@ -599,9 +599,9 @@ static int camqp_read_body (camqp_config_t *conf, /* {{{ */
 
     if (strcasecmp ("text/collectd", content_type) == 0)
     {
-        status = handle_putval (stderr, body);
+        status = cmd_handle_putval (stderr, body);
         if (status != 0)
-            ERROR ("amqp plugin: handle_putval failed with status %i.",
+            ERROR ("amqp plugin: cmd_handle_putval failed with status %i.",
                     status);
         return (status);
     }
@@ -838,10 +838,10 @@ static int camqp_write (const data_set_t *ds, const value_list_t *vl, /* {{{ */
 
     if (conf->format == CAMQP_FORMAT_COMMAND)
     {
-        status = create_putval (buffer, sizeof (buffer), ds, vl);
+        status = cmd_create_putval (buffer, sizeof (buffer), ds, vl);
         if (status != 0)
         {
-            ERROR ("amqp plugin: create_putval failed with status %i.",
+            ERROR ("amqp plugin: cmd_create_putval failed with status %i.",
                     status);
             return (status);
         }
index c80abcb..3d1420d 100644 (file)
@@ -1016,7 +1016,8 @@ int format_values (char *ret, size_t ret_len, /* {{{ */
 
 int parse_identifier (char *str, char **ret_host,
                char **ret_plugin, char **ret_plugin_instance,
-               char **ret_type, char **ret_type_instance)
+               char **ret_type, char **ret_type_instance,
+               char *default_host)
 {
        char *hostname = NULL;
        char *plugin = NULL;
@@ -1035,8 +1036,19 @@ int parse_identifier (char *str, char **ret_host,
 
        type = strchr (plugin, '/');
        if (type == NULL)
-               return (-1);
-       *type = '\0'; type++;
+       {
+               if (default_host == NULL)
+                       return (-1);
+               /* else: no host specified; use default */
+               type = plugin;
+               plugin = hostname;
+               hostname = default_host;
+       }
+       else
+       {
+               *type = '\0';
+               type++;
+       }
 
        plugin_instance = strchr (plugin, '-');
        if (plugin_instance != NULL)
@@ -1077,7 +1089,8 @@ int parse_identifier_vl (const char *str, value_list_t *vl) /* {{{ */
 
        status = parse_identifier (str_copy, &host,
                        &plugin, &plugin_instance,
-                       &type, &type_instance);
+                       &type, &type_instance,
+                       /* default_host = */ NULL);
        if (status != 0)
                return (status);
 
index 6d01ca4..67ca1c1 100644 (file)
@@ -320,7 +320,8 @@ int format_values (char *ret, size_t ret_len,
 
 int parse_identifier (char *str, char **ret_host,
                char **ret_plugin, char **ret_plugin_instance,
-               char **ret_type, char **ret_type_instance);
+               char **ret_type, char **ret_type_instance,
+               char *default_host);
 int parse_identifier_vl (const char *str, value_list_t *vl);
 int parse_value (const char *value, value_t *ret_value, int ds_type);
 int parse_values (char *buffer, value_list_t *vl, const data_set_t *ds);
index 2882ab9..5a4e4c7 100644 (file)
 /*
  * Public data types
  */
+struct identifier_s
+{
+       char *host;
+       char *plugin;
+       char *plugin_instance;
+       char *type;
+       char *type_instance;
+};
+typedef struct identifier_s identifier_t;
+
 typedef unsigned long long counter_t;
 typedef double gauge_t;
 typedef int64_t derive_t;
index b6efa3a..e01e256 100644 (file)
@@ -57,6 +57,21 @@ int plugin_dispatch_values (value_list_t const *vl)
   return ENOTSUP;
 }
 
+int plugin_flush (const char *plugin, cdtime_t timeout, const char *identifier)
+{
+  return ENOTSUP;
+}
+
+static data_source_t magic_ds[] = {{ "value", DS_TYPE_DERIVE, 0.0, NAN }};
+static data_set_t magic = { "MAGIC", 1, magic_ds };
+const data_set_t *plugin_get_ds (const char *name)
+{
+  if (strcmp (name, "MAGIC"))
+    return NULL;
+
+  return &magic;
+}
+
 void plugin_log (int level, char const *format, ...)
 {
   char buffer[1024];
index 1080c80..3a14d04 100644 (file)
  */
 
 #include "utils_cache.h"
+#include <errno.h>
 
 gauge_t *uc_get_rate (__attribute__((unused)) data_set_t const *ds,
                       __attribute__((unused)) value_list_t const *vl)
 {
+  errno = ENOTSUP;
   return (NULL);
 }
+
+int uc_get_rate_by_name (const char *name, gauge_t **ret_values, size_t *ret_values_num)
+{
+  return (ENOTSUP);
+}
+
+int uc_get_names (char ***ret_names, cdtime_t **ret_times, size_t *ret_number)
+{
+  return (ENOTSUP);
+}
index 3a09cb7..fc40d4a 100644 (file)
@@ -547,7 +547,7 @@ failed:
 static int parse_line (char *buffer) /* {{{ */
 {
   if (strncasecmp ("PUTVAL", buffer, strlen ("PUTVAL")) == 0)
-    return (handle_putval (stdout, buffer));
+    return (cmd_handle_putval (stdout, buffer));
   else if (strncasecmp ("PUTNOTIF", buffer, strlen ("PUTNOTIF")) == 0)
     return (handle_putnotif (stdout, buffer));
   else
index 1bcc276..1bc966c 100644 (file)
@@ -48,9 +48,12 @@ static int check_count__ = 0;
 
 #define END_TEST exit ((fail_count__ == 0) ? 0 : 1);
 
+#define LOG(result, text) \
+  printf ("%s %i - %s\n", result ? "ok" : "not ok", ++check_count__, text)
+
 #define OK1(cond, text) do { \
   _Bool result = (cond); \
-  printf ("%s %i - %s\n", result ? "ok" : "not ok", ++check_count__, text); \
+  LOG (result, text); \
   if (!result) { return -1; } \
 } while (0)
 #define OK(cond) OK1(cond, #cond)
index 808ba98..73037e4 100644 (file)
@@ -289,7 +289,7 @@ static void *us_handle_client (void *arg)
 
                if (strcasecmp (fields[0], "getval") == 0)
                {
-                       handle_getval (fhout, buffer);
+                       cmd_handle_getval (fhout, buffer);
                }
                else if (strcasecmp (fields[0], "getthreshold") == 0)
                {
@@ -297,11 +297,11 @@ static void *us_handle_client (void *arg)
                }
                else if (strcasecmp (fields[0], "putval") == 0)
                {
-                       handle_putval (fhout, buffer);
+                       cmd_handle_putval (fhout, buffer);
                }
                else if (strcasecmp (fields[0], "listval") == 0)
                {
-                       handle_listval (fhout, buffer);
+                       cmd_handle_listval (fhout, buffer);
                }
                else if (strcasecmp (fields[0], "putnotif") == 0)
                {
@@ -309,7 +309,7 @@ static void *us_handle_client (void *arg)
                }
                else if (strcasecmp (fields[0], "flush") == 0)
                {
-                       handle_flush (fhout, buffer);
+                       cmd_handle_flush (fhout, buffer);
                }
                else
                {
index 1876150..9ef50ff 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/utils_cmd_flush.c
- * Copyright (C) 2008       Sebastian Harl
+ * Copyright (C) 2008, 2016 Sebastian Harl
  * Copyright (C) 2008       Florian Forster
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
 #include "utils_parse_option.h"
 #include "utils_cmd_flush.h"
 
-int handle_flush (FILE *fh, char *buffer)
+cmd_status_t cmd_parse_flush (size_t argc, char **argv,
+               cmd_flush_t *ret_flush, const cmd_options_t *opts,
+               cmd_error_handler_t *err)
 {
-       int success = 0;
-       int error   = 0;
-
-       double timeout = 0.0;
-       char **plugins = NULL;
-       size_t plugins_num = 0;
-       char **identifiers = NULL;
-       size_t identifiers_num = 0;
-
-#define PRINT_TO_SOCK(fh, ...) \
-       do { \
-               if (fprintf (fh, __VA_ARGS__) < 0) { \
-                       char errbuf[1024]; \
-                       WARNING ("handle_flush: failed to write to socket #%i: %s", \
-                                       fileno (fh), sstrerror (errno, errbuf, sizeof (errbuf))); \
-                       strarray_free (plugins, plugins_num); \
-                       strarray_free (identifiers, identifiers_num); \
-                       return -1; \
-               } \
-               fflush(fh); \
-       } while (0)
-
-       if ((fh == NULL) || (buffer == NULL))
-               return (-1);
-
-       DEBUG ("utils_cmd_flush: handle_flush (fh = %p, buffer = %s);",
-                       (void *) fh, buffer);
 
-       if (strncasecmp ("FLUSH", buffer, strlen ("FLUSH")) != 0)
+       if ((ret_flush == NULL) || (opts == NULL))
        {
-               PRINT_TO_SOCK (fh, "-1 Cannot parse command.\n");
-               return (-1);
+               errno = EINVAL;
+               cmd_error (CMD_ERROR, err, "Invalid arguments to cmd_parse_flush.");
+               return (CMD_ERROR);
        }
-       buffer += strlen ("FLUSH");
 
-       while (*buffer != 0)
+       for (size_t i = 0; i < argc; i++)
        {
                char *opt_key;
                char *opt_value;
@@ -78,66 +53,133 @@ int handle_flush (FILE *fh, char *buffer)
 
                opt_key = NULL;
                opt_value = NULL;
-               status = parse_option (&buffer, &opt_key, &opt_value);
+               status = cmd_parse_option (argv[i], &opt_key, &opt_value, err);
                if (status != 0)
                {
-                       PRINT_TO_SOCK (fh, "-1 Parsing options failed.\n");
-                       strarray_free (plugins, plugins_num);
-                       strarray_free (identifiers, identifiers_num);
-                       return (-1);
+                       if (status == CMD_NO_OPTION)
+                               cmd_error (CMD_PARSE_ERROR, err,
+                                               "Invalid option string `%s'.", argv[i]);
+                       cmd_destroy_flush (ret_flush);
+                       return (CMD_PARSE_ERROR);
                }
 
                if (strcasecmp ("plugin", opt_key) == 0)
-                       strarray_add (&plugins, &plugins_num, opt_value);
+               {
+                       strarray_add (&ret_flush->plugins, &ret_flush->plugins_num,
+                                       opt_value);
+               }
                else if (strcasecmp ("identifier", opt_key) == 0)
-                       strarray_add (&identifiers, &identifiers_num, opt_value);
+               {
+                       identifier_t *id = realloc (ret_flush->identifiers,
+                                       (ret_flush->identifiers_num + 1) * sizeof (*id));
+                       if (id == NULL)
+                       {
+                               cmd_error (CMD_ERROR, err, "realloc failed.");
+                               cmd_destroy_flush (ret_flush);
+                               return (CMD_ERROR);
+                       }
+
+                       ret_flush->identifiers = id;
+                       id = ret_flush->identifiers + ret_flush->identifiers_num;
+                       ret_flush->identifiers_num++;
+                       if (parse_identifier (opt_value,
+                                               &id->host, &id->plugin, &id->plugin_instance,
+                                               &id->type, &id->type_instance,
+                                               opts->identifier_default_host) != 0)
+                       {
+                               cmd_error (CMD_PARSE_ERROR, err,
+                                               "Invalid identifier `%s'.", opt_value);
+                               cmd_destroy_flush (ret_flush);
+                               return (CMD_PARSE_ERROR);
+                       }
+               }
                else if (strcasecmp ("timeout", opt_key) == 0)
                {
                        char *endptr;
 
                        errno = 0;
                        endptr = NULL;
-                       timeout = strtod (opt_value, &endptr);
+                       ret_flush->timeout = strtod (opt_value, &endptr);
 
-                       if ((endptr == opt_value) || (errno != 0) || (!isfinite (timeout)))
+                       if ((endptr == opt_value) || (errno != 0)
+                                       || (!isfinite (ret_flush->timeout)))
                        {
-                               PRINT_TO_SOCK (fh, "-1 Invalid value for option `timeout': "
-                                               "%s\n", opt_value);
-                               strarray_free (plugins, plugins_num);
-                               strarray_free (identifiers, identifiers_num);
-                               return (-1);
+                               cmd_error (CMD_PARSE_ERROR, err,
+                                               "Invalid value for option `timeout': %s",
+                                               opt_value);
+                               cmd_destroy_flush (ret_flush);
+                               return (CMD_PARSE_ERROR);
                        }
-                       else if (timeout < 0.0)
+                       else if (ret_flush->timeout < 0.0)
                        {
-                               timeout = 0.0;
+                               ret_flush->timeout = 0.0;
                        }
                }
                else
                {
-                       PRINT_TO_SOCK (fh, "-1 Cannot parse option %s\n", opt_key);
-                       strarray_free (plugins, plugins_num);
-                       strarray_free (identifiers, identifiers_num);
-                       return (-1);
+                       cmd_error (CMD_PARSE_ERROR, err,
+                                       "Cannot parse option `%s'.", opt_key);
+                       cmd_destroy_flush (ret_flush);
+                       return (CMD_PARSE_ERROR);
                }
-       } /* while (*buffer != 0) */
+       }
+
+       return (CMD_OK);
+} /* cmd_status_t cmd_parse_flush */
 
-       for (size_t i = 0; (i == 0) || (i < plugins_num); i++)
+cmd_status_t cmd_handle_flush (FILE *fh, char *buffer)
+{
+       cmd_error_handler_t err = { cmd_error_fh, fh };
+       cmd_t cmd;
+
+       int success = 0;
+       int error   = 0;
+       int status;
+
+       if ((fh == NULL) || (buffer == NULL))
+               return (-1);
+
+       DEBUG ("utils_cmd_flush: cmd_handle_flush (fh = %p, buffer = %s);",
+                       (void *) fh, buffer);
+
+       if ((status = cmd_parse (buffer, &cmd, NULL, &err)) != CMD_OK)
+               return (status);
+       if (cmd.type != CMD_FLUSH)
+       {
+               cmd_error (CMD_UNKNOWN_COMMAND, &err, "Unexpected command: `%s'.",
+                               CMD_TO_STRING (cmd.type));
+               cmd_destroy (&cmd);
+               return (CMD_UNKNOWN_COMMAND);
+       }
+
+       for (size_t i = 0; (i == 0) || (i < cmd.cmd.flush.plugins_num); i++)
        {
                char *plugin = NULL;
 
-               if (plugins_num != 0)
-                       plugin = plugins[i];
+               if (cmd.cmd.flush.plugins_num != 0)
+                       plugin = cmd.cmd.flush.plugins[i];
 
-               for (size_t j = 0; (j == 0) || (j < identifiers_num); j++)
+               for (size_t j = 0; (j == 0) || (j < cmd.cmd.flush.identifiers_num); j++)
                {
                        char *identifier = NULL;
+                       char buffer[1024];
                        int status;
 
-                       if (identifiers_num != 0)
-                               identifier = identifiers[j];
+                       if (cmd.cmd.flush.identifiers_num != 0)
+                       {
+                               identifier_t *id = cmd.cmd.flush.identifiers + j;
+                               if (format_name (buffer, sizeof (buffer),
+                                                       id->host, id->plugin, id->plugin_instance,
+                                                       id->type, id->type_instance) != 0)
+                               {
+                                       error++;
+                                       continue;
+                               }
+                               identifier = buffer;
+                       }
 
                        status = plugin_flush (plugin,
-                                       DOUBLE_TO_CDTIME_T (timeout),
+                                       DOUBLE_TO_CDTIME_T (cmd.cmd.flush.timeout),
                                        identifier);
                        if (status == 0)
                                success++;
@@ -146,14 +188,26 @@ int handle_flush (FILE *fh, char *buffer)
                }
        }
 
-       PRINT_TO_SOCK (fh, "0 Done: %i successful, %i errors\n",
+       cmd_error (CMD_OK, &err, "Done: %i successful, %i errors",
                        success, error);
 
-       strarray_free (plugins, plugins_num);
-       strarray_free (identifiers, identifiers_num);
+       cmd_destroy (&cmd);
        return (0);
 #undef PRINT_TO_SOCK
-} /* int handle_flush */
+} /* cmd_status_t cmd_handle_flush */
+
+void cmd_destroy_flush (cmd_flush_t *flush)
+{
+       if (flush == NULL)
+               return;
+
+       strarray_free (flush->plugins, flush->plugins_num);
+       flush->plugins = NULL;
+       flush->plugins_num = 0;
+
+       sfree (flush->identifiers);
+       flush->identifiers_num = 0;
+} /* void cmd_destroy_flush */
 
 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */
 
index f43b257..9dbff20 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/utils_cmd_flush.h
- * Copyright (C) 2008       Sebastian Harl
+ * Copyright (C) 2008, 2016 Sebastian Harl
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
 
 #include <stdio.h>
 
-int handle_flush (FILE *fh, char *buffer);
+#include "utils_cmds.h"
+
+cmd_status_t cmd_parse_flush (size_t argc, char **argv,
+               cmd_flush_t *ret_flush, const cmd_options_t *opts,
+               cmd_error_handler_t *err);
+
+cmd_status_t cmd_handle_flush (FILE *fh, char *buffer);
+
+void cmd_destroy_flush (cmd_flush_t *flush);
 
 #endif /* UTILS_CMD_FLUSH_H */
 
index b2a0a23..e6086f2 100644 (file)
@@ -101,7 +101,8 @@ int handle_getthreshold (FILE *fh, char *buffer)
 
   status = parse_identifier (identifier_copy, &host,
       &plugin, &plugin_instance,
-      &type, &type_instance);
+      &type, &type_instance,
+      /* default_host = */ NULL);
   if (status != 0)
   {
     DEBUG ("handle_getthreshold: Cannot parse identifier `%s'.", identifier);
index cd88760..f29680a 100644 (file)
 #include "utils_parse_option.h"
 #include "utils_cmd_getval.h"
 
+cmd_status_t cmd_parse_getval (size_t argc, char **argv,
+    cmd_getval_t *ret_getval, const cmd_options_t *opts,
+    cmd_error_handler_t *err)
+{
+  char *identifier_copy;
+  int status;
+
+  if ((ret_getval == NULL) || (opts == NULL))
+  {
+    errno = EINVAL;
+    cmd_error (CMD_ERROR, err, "Invalid arguments to cmd_parse_getval.");
+    return (CMD_ERROR);
+  }
+
+  if (argc != 1)
+  {
+    if (argc == 0)
+      cmd_error (CMD_PARSE_ERROR, err, "Missing identifier.");
+    else
+      cmd_error (CMD_PARSE_ERROR, err,
+         "Garbage after identifier: `%s'.", argv[1]);
+    return (CMD_PARSE_ERROR);
+  }
+
+  /* parse_identifier() modifies its first argument,
+   * returning pointers into it */
+  identifier_copy = sstrdup (argv[0]);
+
+  status = parse_identifier (argv[0], &ret_getval->identifier.host,
+      &ret_getval->identifier.plugin, &ret_getval->identifier.plugin_instance,
+      &ret_getval->identifier.type, &ret_getval->identifier.type_instance,
+      opts->identifier_default_host);
+  if (status != 0)
+  {
+    DEBUG ("cmd_parse_getval: Cannot parse identifier `%s'.", identifier_copy);
+    cmd_error (CMD_PARSE_ERROR, err,
+       "Cannot parse identifier `%s'.", identifier_copy);
+    sfree (identifier_copy);
+    return (CMD_PARSE_ERROR);
+  }
+
+  ret_getval->raw_identifier = identifier_copy;
+  return (CMD_OK);
+} /* cmd_status_t cmd_parse_getval */
+
 #define print_to_socket(fh, ...) \
   do { \
     if (fprintf (fh, __VA_ARGS__) < 0) { \
       char errbuf[1024]; \
-      WARNING ("handle_getval: failed to write to socket #%i: %s", \
+      WARNING ("cmd_handle_getval: failed to write to socket #%i: %s", \
           fileno (fh), sstrerror (errno, errbuf, sizeof (errbuf))); \
       return -1; \
     } \
     fflush(fh); \
   } while (0)
 
-int handle_getval (FILE *fh, char *buffer)
+cmd_status_t cmd_handle_getval (FILE *fh, char *buffer)
 {
-  char *command;
-  char *identifier;
-  char *identifier_copy;
+  cmd_error_handler_t err = { cmd_error_fh, fh };
+  cmd_status_t status;
+  cmd_t cmd;
 
-  char *hostname;
-  char *plugin;
-  char *plugin_instance;
-  char *type;
-  char *type_instance;
   gauge_t *values;
   size_t values_num;
 
   const data_set_t *ds;
 
-  int   status;
 
   if ((fh == NULL) || (buffer == NULL))
     return (-1);
 
-  DEBUG ("utils_cmd_getval: handle_getval (fh = %p, buffer = %s);",
+  DEBUG ("utils_cmd_getval: cmd_handle_getval (fh = %p, buffer = %s);",
       (void *) fh, buffer);
 
-  command = NULL;
-  status = parse_string (&buffer, &command);
-  if (status != 0)
-  {
-    print_to_socket (fh, "-1 Cannot parse command.\n");
-    return (-1);
-  }
-  assert (command != NULL);
-
-  if (strcasecmp ("GETVAL", command) != 0)
-  {
-    print_to_socket (fh, "-1 Unexpected command: `%s'.\n", command);
-    return (-1);
-  }
-
-  identifier = NULL;
-  status = parse_string (&buffer, &identifier);
-  if (status != 0)
-  {
-    print_to_socket (fh, "-1 Cannot parse identifier.\n");
-    return (-1);
-  }
-  assert (identifier != NULL);
-
-  if (*buffer != 0)
-  {
-    print_to_socket (fh, "-1 Garbage after end of command: %s\n", buffer);
-    return (-1);
-  }
-
-  /* parse_identifier() modifies its first argument,
-   * returning pointers into it */
-  identifier_copy = sstrdup (identifier);
-
-  status = parse_identifier (identifier_copy, &hostname,
-      &plugin, &plugin_instance,
-      &type, &type_instance);
-  if (status != 0)
+  if ((status = cmd_parse (buffer, &cmd, NULL, &err)) != CMD_OK)
+    return (status);
+  if (cmd.type != CMD_GETVAL)
   {
-    DEBUG ("handle_getval: Cannot parse identifier `%s'.", identifier);
-    print_to_socket (fh, "-1 Cannot parse identifier `%s'.\n", identifier);
-    sfree (identifier_copy);
-    return (-1);
+    cmd_error (CMD_UNKNOWN_COMMAND, &err,
+       "Unexpected command: `%s'.", CMD_TO_STRING (cmd.type));
+    cmd_destroy (&cmd);
+    return (CMD_UNKNOWN_COMMAND);
   }
 
-  ds = plugin_get_ds (type);
+  ds = plugin_get_ds (cmd.cmd.getval.identifier.type);
   if (ds == NULL)
   {
-    DEBUG ("handle_getval: plugin_get_ds (%s) == NULL;", type);
-    print_to_socket (fh, "-1 Type `%s' is unknown.\n", type);
-    sfree (identifier_copy);
+    DEBUG ("cmd_handle_getval: plugin_get_ds (%s) == NULL;",
+       cmd.cmd.getval.identifier.type);
+    cmd_error (CMD_ERROR, &err, "Type `%s' is unknown.\n",
+       cmd.cmd.getval.identifier.type);
+    cmd_destroy (&cmd);
     return (-1);
   }
 
   values = NULL;
   values_num = 0;
-  status = uc_get_rate_by_name (identifier, &values, &values_num);
+  status = uc_get_rate_by_name (cmd.cmd.getval.raw_identifier, &values, &values_num);
   if (status != 0)
   {
-    print_to_socket (fh, "-1 No such value\n");
-    sfree (identifier_copy);
-    return (-1);
+    cmd_error (CMD_ERROR, &err, "No such value.");
+    cmd_destroy (&cmd);
+    return (CMD_ERROR);
   }
 
   if (ds->ds_num != values_num)
@@ -137,10 +143,10 @@ int handle_getval (FILE *fh, char *buffer)
     ERROR ("ds[%s]->ds_num = %zu, "
        "but uc_get_rate_by_name returned %zu values.",
        ds->type, ds->ds_num, values_num);
-    print_to_socket (fh, "-1 Error reading value from cache.\n");
+    cmd_error (CMD_ERROR, &err, "Error reading value from cache.");
     sfree (values);
-    sfree (identifier_copy);
-    return (-1);
+    cmd_destroy (&cmd);
+    return (CMD_ERROR);
   }
 
   print_to_socket (fh, "%zu Value%s found\n", values_num,
@@ -159,9 +165,17 @@ int handle_getval (FILE *fh, char *buffer)
   }
 
   sfree (values);
-  sfree (identifier_copy);
+  cmd_destroy (&cmd);
+
+  return (CMD_OK);
+} /* cmd_status_t cmd_handle_getval */
+
+void cmd_destroy_getval (cmd_getval_t *getval)
+{
+  if (getval == NULL)
+    return;
 
-  return (0);
-} /* int handle_getval */
+  sfree (getval->raw_identifier);
+} /* void cmd_destroy_getval */
 
 /* vim: set sw=2 sts=2 ts=8 : */
index 5e12f69..b0c64be 100644 (file)
 
 #include <stdio.h>
 
-int handle_getval (FILE *fh, char *buffer);
+#include "utils_cmds.h"
+
+cmd_status_t cmd_parse_getval (size_t argc, char **argv,
+    cmd_getval_t *ret_getval, const cmd_options_t *opts,
+    cmd_error_handler_t *err);
+
+cmd_status_t cmd_handle_getval (FILE *fh, char *buffer);
+
+void cmd_destroy_getval (cmd_getval_t *getval);
 
 #endif /* UTILS_CMD_GETVAL_H */
 
index afbd01d..27b88cb 100644 (file)
 #include "utils_cache.h"
 #include "utils_parse_option.h"
 
+cmd_status_t cmd_parse_listval (size_t argc, char **argv,
+    cmd_listval_t *ret_listval __attribute__((unused)),
+    const cmd_options_t *opts __attribute__((unused)),
+    cmd_error_handler_t *err)
+{
+  if (argc != 0)
+  {
+    cmd_error (CMD_PARSE_ERROR, err,
+       "Garbage after end of command: `%s'.", argv[0]);
+    return (CMD_PARSE_ERROR);
+  }
+
+  return (CMD_OK);
+} /* cmd_status_t cmd_parse_listval */
+
 #define free_everything_and_return(status) do { \
     for (size_t j = 0; j < number; j++) { \
       sfree(names[j]); \
       char errbuf[1024]; \
       WARNING ("handle_listval: failed to write to socket #%i: %s", \
           fileno (fh), sstrerror (errno, errbuf, sizeof (errbuf))); \
-      free_everything_and_return (-1); \
+      free_everything_and_return (CMD_ERROR); \
     } \
     fflush(fh); \
   } while (0)
 
-int handle_listval (FILE *fh, char *buffer)
+cmd_status_t cmd_handle_listval (FILE *fh, char *buffer)
 {
-  char *command;
+  cmd_error_handler_t err = { cmd_error_fh, fh };
+  cmd_status_t status;
+  cmd_t cmd;
+
   char **names = NULL;
   cdtime_t *times = NULL;
   size_t number = 0;
-  int status;
 
   DEBUG ("utils_cmd_listval: handle_listval (fh = %p, buffer = %s);",
       (void *) fh, buffer);
 
-  command = NULL;
-  status = parse_string (&buffer, &command);
-  if (status != 0)
+  if ((status = cmd_parse (buffer, &cmd, NULL, &err)) != CMD_OK)
+    return (status);
+  if (cmd.type != CMD_LISTVAL)
   {
-    print_to_socket (fh, "-1 Cannot parse command.\n");
-    free_everything_and_return (-1);
-  }
-  assert (command != NULL);
-
-  if (strcasecmp ("LISTVAL", command) != 0)
-  {
-    print_to_socket (fh, "-1 Unexpected command: `%s'.\n", command);
-    free_everything_and_return (-1);
-  }
-
-  if (*buffer != 0)
-  {
-    print_to_socket (fh, "-1 Garbage after end of command: %s\n", buffer);
-    free_everything_and_return (-1);
+    cmd_error (CMD_UNKNOWN_COMMAND, &err,
+       "Unexpected command: `%s'.", CMD_TO_STRING (cmd.type));
+    free_everything_and_return (CMD_UNKNOWN_COMMAND);
   }
 
   status = uc_get_names (&names, &times, &number);
   if (status != 0)
   {
     DEBUG ("command listval: uc_get_names failed with status %i", status);
-    print_to_socket (fh, "-1 uc_get_names failed.\n");
-    free_everything_and_return (-1);
+    cmd_error (CMD_ERROR, &err, "uc_get_names failed.");
+    free_everything_and_return (CMD_ERROR);
   }
 
   print_to_socket (fh, "%i Value%s found\n",
@@ -100,7 +105,12 @@ int handle_listval (FILE *fh, char *buffer)
     print_to_socket (fh, "%.3f %s\n", CDTIME_T_TO_DOUBLE (times[i]),
                names[i]);
 
-  free_everything_and_return (0);
-} /* int handle_listval */
+  free_everything_and_return (CMD_OK);
+} /* cmd_status_t cmd_handle_listval */
+
+void cmd_destroy_listval (cmd_listval_t *listval __attribute__((unused)))
+{
+  /* nothing to do */
+} /* void cmd_destroy_listval */
 
 /* vim: set sw=2 sts=2 ts=8 : */
index fc125bc..895878c 100644 (file)
 
 #include <stdio.h>
 
-int handle_listval (FILE *fh, char *buffer);
+#include "utils_cmds.h"
+
+cmd_status_t cmd_parse_listval (size_t argc, char **argv,
+    cmd_listval_t *ret_listval, const cmd_options_t *opts,
+    cmd_error_handler_t *err);
+
+cmd_status_t cmd_handle_listval (FILE *fh, char *buffer);
+
+void cmd_destroy_listval (cmd_listval_t *listval);
 
 #endif /* UTILS_CMD_LISTVAL_H */
 
index bf3e2b6..691f1f8 100644 (file)
@@ -1,6 +1,7 @@
 /**
  * collectd - src/utils_cmd_putval.c
  * Copyright (C) 2007-2009  Florian octo Forster
+ * Copyright (C) 2016       Sebastian tokkee Harl
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
@@ -22,6 +23,7 @@
  *
  * Authors:
  *   Florian octo Forster <octo at collectd.org>
+ *   Sebastian tokkee Harl <sh at tokkee.org>
  **/
 
 #include "collectd.h"
 #include "common.h"
 #include "plugin.h"
 
+#include "utils_cmds.h"
+#include "utils_cmd_putval.h"
 #include "utils_parse_option.h"
 #include "utils_cmd_putval.h"
 
-#define print_to_socket(fh, ...) \
-    do { \
-        if (fprintf (fh, __VA_ARGS__) < 0) { \
-            char errbuf[1024]; \
-            WARNING ("handle_putval: failed to write to socket #%i: %s", \
-                    fileno (fh), sstrerror (errno, errbuf, sizeof (errbuf))); \
-            sfree (vl.values); \
-            return -1; \
-        } \
-        fflush(fh); \
-    } while (0)
+/*
+ * private helper functions
+ */
 
 static int set_option (value_list_t *vl, const char *key, const char *value)
 {
@@ -66,11 +62,18 @@ static int set_option (value_list_t *vl, const char *key, const char *value)
                return (1);
 
        return (0);
-} /* int parse_option */
+} /* int set_option */
+
+/*
+ * public API
+ */
 
-int handle_putval (FILE *fh, char *buffer)
+cmd_status_t cmd_parse_putval (size_t argc, char **argv,
+               cmd_putval_t *ret_putval, const cmd_options_t *opts,
+               cmd_error_handler_t *err)
 {
-       char *command;
+       cmd_status_t result;
+
        char *identifier;
        char *hostname;
        char *plugin;
@@ -78,56 +81,44 @@ int handle_putval (FILE *fh, char *buffer)
        char *type;
        char *type_instance;
        int   status;
-       int   values_submitted;
 
        char *identifier_copy;
 
        const data_set_t *ds;
        value_list_t vl = VALUE_LIST_INIT;
-       vl.values = NULL;
-
-       DEBUG ("utils_cmd_putval: handle_putval (fh = %p, buffer = %s);",
-                       (void *) fh, buffer);
 
-       command = NULL;
-       status = parse_string (&buffer, &command);
-       if (status != 0)
+       if ((ret_putval == NULL) || (opts == NULL))
        {
-               print_to_socket (fh, "-1 Cannot parse command.\n");
-               return (-1);
+               errno = EINVAL;
+               cmd_error (CMD_ERROR, err, "Invalid arguments to cmd_parse_putval.");
+               return (CMD_ERROR);
        }
-       assert (command != NULL);
 
-       if (strcasecmp ("PUTVAL", command) != 0)
+       if (argc < 2)
        {
-               print_to_socket (fh, "-1 Unexpected command: `%s'.\n", command);
-               return (-1);
+               cmd_error (CMD_PARSE_ERROR, err,
+                               "Missing identifier and/or value-list.");
+               return (CMD_PARSE_ERROR);
        }
 
-       identifier = NULL;
-       status = parse_string (&buffer, &identifier);
-       if (status != 0)
-       {
-               print_to_socket (fh, "-1 Cannot parse identifier.\n");
-               return (-1);
-       }
-       assert (identifier != NULL);
+       identifier = argv[0];
 
-       /* parse_identifier() modifies its first argument,
-        * returning pointers into it */
+       /* parse_identifier() modifies its first argument, returning pointers into
+        * it; retain the old value for later. */
        identifier_copy = sstrdup (identifier);
 
-       status = parse_identifier (identifier_copy, &hostname,
+       status = parse_identifier (identifier, &hostname,
                        &plugin, &plugin_instance,
-                       &type, &type_instance);
+                       &type, &type_instance,
+                       opts->identifier_default_host);
        if (status != 0)
        {
-               DEBUG ("handle_putval: Cannot parse identifier `%s'.",
-                               identifier);
-               print_to_socket (fh, "-1 Cannot parse identifier `%s'.\n",
-                               identifier);
+               DEBUG ("cmd_handle_putval: Cannot parse identifier `%s'.",
+                               identifier_copy);
+               cmd_error (CMD_PARSE_ERROR, err, "Cannot parse identifier `%s'.",
+                               identifier_copy);
                sfree (identifier_copy);
-               return (-1);
+               return (CMD_PARSE_ERROR);
        }
 
        if ((strlen (hostname) >= sizeof (vl.host))
@@ -137,9 +128,9 @@ int handle_putval (FILE *fh, char *buffer)
                        || ((type_instance != NULL)
                                && (strlen (type_instance) >= sizeof (vl.type_instance))))
        {
-               print_to_socket (fh, "-1 Identifier too long.\n");
+               cmd_error (CMD_PARSE_ERROR, err, "Identifier too long.");
                sfree (identifier_copy);
-               return (-1);
+               return (CMD_PARSE_ERROR);
        }
 
        sstrncpy (vl.host, hostname, sizeof (vl.host));
@@ -151,84 +142,148 @@ int handle_putval (FILE *fh, char *buffer)
                sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
 
        ds = plugin_get_ds (type);
-       if (ds == NULL) {
-               print_to_socket (fh, "-1 Type `%s' isn't defined.\n", type);
+       if (ds == NULL)
+       {
+               cmd_error (CMD_PARSE_ERROR, err, "1 Type `%s' isn't defined.", type);
                sfree (identifier_copy);
-               return (-1);
+               return (CMD_PARSE_ERROR);
        }
 
-       /* Free identifier_copy */
        hostname = NULL;
        plugin = NULL; plugin_instance = NULL;
        type = NULL;   type_instance = NULL;
-       sfree (identifier_copy);
 
        vl.values_len = ds->ds_num;
        vl.values = malloc (vl.values_len * sizeof (*vl.values));
        if (vl.values == NULL)
        {
-               print_to_socket (fh, "-1 malloc failed.\n");
-               return (-1);
+               cmd_error (CMD_ERROR, err, "malloc failed.");
+               sfree (identifier_copy);
+               return (CMD_ERROR);
        }
 
-       /* All the remaining fields are part of the optionlist. */
-       values_submitted = 0;
-       while (*buffer != 0)
+       ret_putval->raw_identifier = identifier_copy;
+       if (ret_putval->raw_identifier == NULL)
        {
-               char *string = NULL;
-               char *value  = NULL;
+               cmd_error (CMD_ERROR, err, "malloc failed.");
+               cmd_destroy_putval (ret_putval);
+               sfree (vl.values);
+               return (CMD_ERROR);
+       }
 
-               status = parse_option (&buffer, &string, &value);
-               if (status < 0)
-               {
-                       /* parse_option failed, buffer has been modified.
-                        * => we need to abort */
-                       print_to_socket (fh, "-1 Misformatted option.\n");
-                       sfree (vl.values);
-                       return (-1);
-               }
-               else if (status == 0)
+       /* All the remaining fields are part of the option list. */
+       result = CMD_OK;
+       for (size_t i = 1; i < argc; ++i)
+       {
+               value_list_t *tmp;
+
+               char *key   = NULL;
+               char *value = NULL;
+
+               status = cmd_parse_option (argv[i], &key, &value, err);
+               if (status == CMD_OK)
                {
-                       assert (string != NULL);
+                       assert (key != NULL);
                        assert (value != NULL);
-                       set_option (&vl, string, value);
+                       set_option (&vl, key, value);
                        continue;
                }
-               /* else: parse_option but buffer has not been modified. This is
-                * the default if no `=' is found.. */
+               else if (status != CMD_NO_OPTION)
+               {
+                       /* parse_option failed, buffer has been modified.
+                        * => we need to abort */
+                       result = status;
+                       break;
+               }
+               /* else: cmd_parse_option did not find an option; treat this as a
+                * value list. */
 
-               status = parse_string (&buffer, &string);
+               status = parse_values (argv[i], &vl, ds);
                if (status != 0)
                {
-                       print_to_socket (fh, "-1 Misformatted value.\n");
-                       sfree (vl.values);
-                       return (-1);
+                       cmd_error (CMD_PARSE_ERROR, err, "Parsing the values string failed.");
+                       result = CMD_PARSE_ERROR;
+                       break;
                }
-               assert (string != NULL);
 
-               status = parse_values (string, &vl, ds);
-               if (status != 0)
+               tmp = (value_list_t *) realloc (ret_putval->vl,
+                               (ret_putval->vl_num + 1) * sizeof(*ret_putval->vl));
+               if (tmp == NULL)
                {
-                       print_to_socket (fh, "-1 Parsing the values string failed.\n");
-                       sfree (vl.values);
-                       return (-1);
+                       cmd_error (CMD_ERROR, err, "realloc failed.");
+                       cmd_destroy_putval (ret_putval);
+                       result = CMD_ERROR;
+                       break;
                }
 
-               plugin_dispatch_values (&vl);
-               values_submitted++;
+               ret_putval->vl = tmp;
+               ret_putval->vl_num++;
+               memcpy (&ret_putval->vl[ret_putval->vl_num - 1], &vl, sizeof (vl));
        } /* while (*buffer != 0) */
        /* Done parsing the options. */
 
-       if (fh!=stdout)
-               print_to_socket (fh, "0 Success: %i %s been dispatched.\n",
-                       values_submitted,
-                       (values_submitted == 1) ? "value has" : "values have");
+       if (result != CMD_OK)
+       {
+               if (ret_putval->vl_num == 0)
+                       sfree (vl.values);
+               cmd_destroy_putval (ret_putval);
+       }
+
+       return (result);
+} /* cmd_status_t cmd_parse_putval */
 
-       sfree (vl.values);
-       return (0);
-} /* int handle_putval */
+void cmd_destroy_putval (cmd_putval_t *putval)
+{
+       if (putval == NULL)
+               return;
+
+       sfree (putval->raw_identifier);
+
+       for (size_t i = 0; i < putval->vl_num; ++i)
+       {
+               if (i == 0) /* values is shared between all entries */
+                       sfree (putval->vl[i].values);
+               meta_data_destroy (putval->vl[i].meta);
+               putval->vl[i].meta = NULL;
+       }
+       sfree (putval->vl);
+       putval->vl = NULL;
+       putval->vl_num = 0;
+} /* void cmd_destroy_putval */
+
+cmd_status_t cmd_handle_putval (FILE *fh, char *buffer)
+{
+       cmd_error_handler_t err = { cmd_error_fh, fh };
+       cmd_t cmd;
+
+       int status;
+
+       DEBUG ("utils_cmd_putval: cmd_handle_putval (fh = %p, buffer = %s);",
+                       (void *) fh, buffer);
+
+       if ((status = cmd_parse (buffer, &cmd, NULL, &err)) != CMD_OK)
+               return (status);
+       if (cmd.type != CMD_PUTVAL)
+       {
+               cmd_error (CMD_UNKNOWN_COMMAND, &err, "Unexpected command: `%s'.",
+                               CMD_TO_STRING (cmd.type));
+               cmd_destroy (&cmd);
+               return (CMD_UNKNOWN_COMMAND);
+       }
+
+       for (size_t i = 0; i < cmd.cmd.putval.vl_num; ++i)
+               plugin_dispatch_values (&cmd.cmd.putval.vl[i]);
+
+       if (fh != stdout)
+               cmd_error (CMD_OK, &err, "Success: %i %s been dispatched.",
+                               (int)cmd.cmd.putval.vl_num,
+                               (cmd.cmd.putval.vl_num == 1) ? "value has" : "values have");
+
+       cmd_destroy (&cmd);
+       return (CMD_OK);
+} /* int cmd_handle_putval */
 
-int create_putval (char *ret, size_t ret_len, /* {{{ */
+int cmd_create_putval (char *ret, size_t ret_len, /* {{{ */
        const data_set_t *ds, const value_list_t *vl)
 {
        char buffer_ident[6 * DATA_MAX_NAME_LEN];
@@ -255,4 +310,4 @@ int create_putval (char *ret, size_t ret_len, /* {{{ */
                        buffer_values);
 
        return (0);
-} /* }}} int create_putval */
+} /* }}} int cmd_create_putval */
index 795409e..bb0b227 100644 (file)
 #include <stdio.h>
 
 #include "plugin.h"
+#include "utils_cmds.h"
 
-int handle_putval (FILE *fh, char *buffer);
+cmd_status_t cmd_parse_putval (size_t argc, char **argv,
+               cmd_putval_t *ret_putval, const cmd_options_t *opts,
+               cmd_error_handler_t *err);
 
-int create_putval (char *ret, size_t ret_len,
+cmd_status_t cmd_handle_putval (FILE *fh, char *buffer);
+
+void cmd_destroy_putval (cmd_putval_t *putval);
+
+int cmd_create_putval (char *ret, size_t ret_len,
                const data_set_t *ds, const value_list_t *vl);
 
 #endif /* UTILS_CMD_PUTVAL_H */
diff --git a/src/utils_cmds.c b/src/utils_cmds.c
new file mode 100644 (file)
index 0000000..1f53ad1
--- /dev/null
@@ -0,0 +1,352 @@
+/**
+ * collectd - src/utils_cmds.c
+ * Copyright (C) 2008       Florian Forster
+ * Copyright (C) 2016       Sebastian 'tokkee' Harl
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *   Florian octo Forster <octo at collectd.org>
+ *   Sebastian 'tokkee' Harl <sh at tokkee.org>
+ **/
+
+#include "utils_cmds.h"
+#include "utils_cmd_flush.h"
+#include "utils_cmd_getval.h"
+#include "utils_cmd_listval.h"
+#include "utils_cmd_putval.h"
+#include "utils_parse_option.h"
+#include "daemon/common.h"
+
+#include <stdbool.h>
+#include <string.h>
+
+static cmd_options_t default_options = {
+       /* identifier_default_host = */ NULL,
+};
+
+/*
+ * private helper functions
+ */
+
+static cmd_status_t cmd_split (char *buffer,
+               size_t *ret_len, char ***ret_fields,
+               cmd_error_handler_t *err)
+{
+       char *field;
+       bool in_field, in_quotes;
+
+       size_t estimate, len;
+       char **fields;
+
+       estimate = 0;
+       in_field = false;
+       for (char *string = buffer; *string != '\0'; ++string)
+       {
+               /* Make a quick worst-case estimate of the number of fields by
+                * counting spaces and ignoring quotation marks. */
+               if (!isspace ((int)*string))
+               {
+                       if (!in_field)
+                       {
+                               estimate++;
+                               in_field = true;
+                       }
+               }
+               else
+               {
+                       in_field = false;
+               }
+       }
+
+       /* fields will be NULL-terminated */
+       fields = malloc ((estimate + 1) * sizeof (*fields));
+       if (fields == NULL) {
+               cmd_error (CMD_ERROR, err, "malloc failed.");
+               return (CMD_ERROR);
+       }
+
+#define END_FIELD() \
+       do { \
+               *field = '\0'; \
+               field = NULL; \
+               in_field = false; \
+       } while (0)
+#define NEW_FIELD() \
+       do { \
+               field = string; \
+               in_field = true; \
+               assert (len < estimate); \
+               fields[len] = field; \
+               field++; \
+               len++; \
+       } while (0)
+
+       len = 0;
+       field = NULL;
+       in_field = false;
+       in_quotes = false;
+       for (char *string = buffer; *string != '\0'; string++)
+       {
+               if (isspace ((int)string[0]))
+               {
+                       if (! in_quotes)
+                       {
+                               if (in_field)
+                                       END_FIELD ();
+
+                               /* skip space */
+                               continue;
+                       }
+               }
+               else if (string[0] == '"')
+               {
+                       /* Note: Two consecutive quoted fields not separated by space are
+                        * treated as different fields. This is the collectd 5.x behavior
+                        * around splitting fields. */
+
+                       if (in_quotes)
+                       {
+                               /* end of quoted field */
+                               if (! in_field) /* empty quoted string */
+                                       NEW_FIELD ();
+                               END_FIELD ();
+                               in_quotes = false;
+                               continue;
+                       }
+
+                       in_quotes = true;
+                       /* if (! in_field): add new field on next iteration
+                        * else: quoted string following an unquoted string (one field)
+                        * in either case: skip quotation mark */
+                       continue;
+               }
+               else if ((string[0] == '\\') && in_quotes)
+               {
+                       /* Outside of quotes, a backslash is a regular character (mostly
+                        * for backward compatibility). */
+
+                       if (string[1] == '\0')
+                       {
+                               free (fields);
+                               cmd_error (CMD_PARSE_ERROR, err,
+                                               "Backslash at end of string.");
+                               return (CMD_PARSE_ERROR);
+                       }
+
+                       /* un-escape the next character; skip backslash */
+                       string++;
+               }
+
+               if (! in_field)
+                       NEW_FIELD ();
+               else {
+                       *field = string[0];
+                       field++;
+               }
+       }
+
+       if (in_quotes)
+       {
+               free (fields);
+               cmd_error (CMD_PARSE_ERROR, err, "Unterminated quoted string.");
+               return (CMD_PARSE_ERROR);
+       }
+
+#undef NEW_FIELD
+#undef END_FIELD
+
+       fields[len] = NULL;
+       if (ret_len != NULL)
+               *ret_len = len;
+       if (ret_fields != NULL)
+               *ret_fields = fields;
+       else
+               free (fields);
+       return (CMD_OK);
+} /* int cmd_split */
+
+/*
+ * public API
+ */
+
+void cmd_error (cmd_status_t status, cmd_error_handler_t *err,
+               const char *format, ...)
+{
+       va_list ap;
+
+       if ((err == NULL) || (err->cb == NULL))
+               return;
+
+       va_start (ap, format);
+       err->cb (err->ud, status, format, ap);
+       va_end (ap);
+} /* void cmd_error */
+
+cmd_status_t cmd_parsev (size_t argc, char **argv, cmd_t *ret_cmd,
+               const cmd_options_t *opts, cmd_error_handler_t *err)
+{
+       char *command = NULL;
+       cmd_status_t status;
+
+       if ((argc < 1) || (argv == NULL) || (ret_cmd == NULL))
+       {
+               errno = EINVAL;
+               cmd_error (CMD_ERROR, err, "Missing command.");
+               return CMD_ERROR;
+       }
+
+       if (opts == NULL)
+               opts = &default_options;
+
+       memset (ret_cmd, 0, sizeof (*ret_cmd));
+       command = argv[0];
+       if (strcasecmp ("FLUSH", command) == 0)
+       {
+               ret_cmd->type = CMD_FLUSH;
+               status = cmd_parse_flush (argc - 1, argv + 1,
+                               &ret_cmd->cmd.flush, opts, err);
+       }
+       else if (strcasecmp ("GETVAL", command) == 0)
+       {
+               ret_cmd->type = CMD_GETVAL;
+               status = cmd_parse_getval (argc - 1, argv + 1,
+                               &ret_cmd->cmd.getval, opts, err);
+       }
+       else if (strcasecmp ("LISTVAL", command) == 0)
+       {
+               ret_cmd->type = CMD_LISTVAL;
+               status = cmd_parse_listval (argc - 1, argv + 1,
+                               &ret_cmd->cmd.listval, opts, err);
+       }
+       else if (strcasecmp ("PUTVAL", command) == 0)
+       {
+               ret_cmd->type = CMD_PUTVAL;
+               status = cmd_parse_putval (argc - 1, argv + 1,
+                               &ret_cmd->cmd.putval, opts, err);
+       }
+       else
+       {
+               ret_cmd->type = CMD_UNKNOWN;
+               cmd_error (CMD_UNKNOWN_COMMAND, err,
+                               "Unknown command `%s'.", command);
+               return (CMD_UNKNOWN_COMMAND);
+       }
+
+       if (status != CMD_OK)
+               ret_cmd->type = CMD_UNKNOWN;
+       return (status);
+} /* cmd_status_t cmd_parsev */
+
+cmd_status_t cmd_parse (char *buffer, cmd_t *ret_cmd,
+               const cmd_options_t *opts, cmd_error_handler_t *err)
+{
+       char **fields = NULL;
+       size_t fields_num = 0;
+       cmd_status_t status;
+
+       if ((status = cmd_split (buffer, &fields_num, &fields, err)) != CMD_OK)
+               return status;
+
+       status = cmd_parsev (fields_num, fields, ret_cmd, opts, err);
+       free (fields);
+       return (status);
+} /* cmd_status_t cmd_parse */
+
+void cmd_destroy (cmd_t *cmd)
+{
+       if (cmd == NULL)
+               return;
+
+       switch (cmd->type)
+       {
+               case CMD_UNKNOWN:
+                       /* nothing to do */
+                       break;
+               case CMD_FLUSH:
+                       cmd_destroy_flush (&cmd->cmd.flush);
+                       break;
+               case CMD_GETVAL:
+                       cmd_destroy_getval (&cmd->cmd.getval);
+                       break;
+               case CMD_LISTVAL:
+                       cmd_destroy_listval (&cmd->cmd.listval);
+                       break;
+               case CMD_PUTVAL:
+                       cmd_destroy_putval (&cmd->cmd.putval);
+                       break;
+       }
+} /* void cmd_destroy */
+
+cmd_status_t cmd_parse_option (char *field,
+               char **ret_key, char **ret_value, cmd_error_handler_t *err)
+{
+       char *key, *value;
+
+       if (field == NULL)
+       {
+               errno = EINVAL;
+               cmd_error (CMD_ERROR, err, "Invalid argument to cmd_parse_option.");
+               return (CMD_ERROR);
+       }
+       key = value = field;
+
+       /* Look for the equal sign. */
+       while (isalnum ((int)value[0]) || (value[0] == '_') || (value[0] == ':'))
+               value++;
+       if ((value[0] != '=') || (value == key))
+       {
+               /* Whether this is a fatal error is up to the caller. */
+               return (CMD_NO_OPTION);
+       }
+       *value = '\0';
+       value++;
+
+       if (ret_key != NULL)
+               *ret_key = key;
+       if (ret_value != NULL)
+               *ret_value = value;
+
+       return (CMD_OK);
+} /* cmd_status_t cmd_parse_option */
+
+void cmd_error_fh (void *ud, cmd_status_t status,
+               const char *format, va_list ap)
+{
+       FILE *fh = ud;
+       int code = -1;
+       char buf[1024];
+
+       if (status == CMD_OK)
+               code = 0;
+
+       vsnprintf (buf, sizeof(buf), format, ap);
+       buf[sizeof (buf) - 1] = '\0';
+       if (fprintf (fh, "%i %s\n", code, buf) < 0)
+       {
+               char errbuf[1024];
+               WARNING ("utils_cmds: failed to write to file-handle #%i: %s",
+                               fileno (fh), sstrerror (errno, errbuf, sizeof (errbuf)));
+               return;
+       }
+
+       fflush (fh);
+} /* void cmd_error_fh */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
diff --git a/src/utils_cmds.h b/src/utils_cmds.h
new file mode 100644 (file)
index 0000000..205e89a
--- /dev/null
@@ -0,0 +1,211 @@
+/**
+ * collectd - src/utils_cmds.h
+ * Copyright (C) 2016 Sebastian 'tokkee' Harl
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *   Sebastian 'tokkee' Harl <sh at tokkee.org>
+ **/
+
+#ifndef UTILS_CMDS_H
+#define UTILS_CMDS_H 1
+
+#include "plugin.h"
+
+#include <stdarg.h>
+
+typedef enum {
+       CMD_UNKNOWN = 0,
+       CMD_FLUSH   = 1,
+       CMD_GETVAL  = 2,
+       CMD_LISTVAL = 3,
+       CMD_PUTVAL  = 4,
+} cmd_type_t;
+#define CMD_TO_STRING(type) \
+       ((type) == CMD_FLUSH) ? "FLUSH" \
+               : ((type) == CMD_GETVAL) ? "GETVAL" \
+               : ((type) == CMD_LISTVAL) ? "LISTVAL" \
+               : ((type) == CMD_PUTVAL) ? "PUTVAL" \
+               : "UNKNOWN"
+
+typedef struct {
+       double timeout;
+
+       char **plugins;
+       size_t plugins_num;
+       identifier_t *identifiers;
+       size_t identifiers_num;
+} cmd_flush_t;
+
+typedef struct {
+       char *raw_identifier;
+       identifier_t identifier;
+} cmd_getval_t;
+
+typedef struct {
+} cmd_listval_t;
+
+typedef struct {
+       /* The raw identifier as provided by the user. */
+       char *raw_identifier;
+
+       /* An array of the fully parsed identifier and all value lists, and their
+        * options as provided by the user. */
+       value_list_t *vl;
+       size_t vl_num;
+} cmd_putval_t;
+
+/*
+ * NAME
+ *   cmd_t
+ *
+ * DESCRIPTION
+ *   The representation of a fully parsed command.
+ */
+typedef struct {
+       cmd_type_t type;
+       union {
+               cmd_flush_t flush;
+               cmd_getval_t getval;
+               cmd_listval_t listval;
+               cmd_putval_t putval;
+       } cmd;
+} cmd_t;
+
+/*
+ * NAME
+ *   cmd_options_t
+ *
+ * DESCRIPTIONS
+ *   Optional settings for tuning the parser behavior.
+ */
+typedef struct {
+       /* identifier_default_host: If non-NULL, the hostname is optional and will
+        * default to the specified value. */
+       char *identifier_default_host;
+} cmd_options_t;
+
+/*
+ * NAME
+ *   cmd_status_t
+ *
+ * DESCRIPTION
+ *   Status codes describing the parse result.
+ */
+typedef enum {
+       CMD_OK              =  0,
+       CMD_ERROR           = -1,
+       CMD_PARSE_ERROR     = -2,
+       CMD_UNKNOWN_COMMAND = -3,
+
+       /* Not necessarily fatal errors. */
+       CMD_NO_OPTION       =  1,
+} cmd_status_t;
+
+/*
+ * NAME
+ *   cmd_error_handler_t
+ *
+ * DESCRIPTION
+ *   An error handler describes a callback to be invoked when the parser
+ *   encounters an error. The user data pointer will be passed to the callback
+ *   as the first argument.
+ */
+typedef struct {
+       void (*cb) (void *, cmd_status_t, const char *, va_list);
+       void *ud;
+} cmd_error_handler_t;
+
+/*
+ * NAME:
+ *   cmd_error
+ *
+ * DESCRIPTION
+ *   Reports an error via the specified error handler (if set).
+ */
+void cmd_error (cmd_status_t status, cmd_error_handler_t *err,
+               const char *format, ...);
+
+/*
+ * NAME
+ *   cmd_parse
+ *
+ * DESCRIPTION
+ *   Parse a command string and populate a command object.
+ *
+ * PARAMETERS
+ *   `buffer'  The command string to be parsed.
+ *   `ret_cmd' The parse result will be stored at this location.
+ *   `opts'    Parser options. If NULL, defaults will be used.
+ *   `err'     An optional error handler to invoke on error.
+ *
+ * RETURN VALUE
+ *   CMD_OK on success or the respective error code otherwise.
+ */
+cmd_status_t cmd_parse (char *buffer, cmd_t *ret_cmd,
+               const cmd_options_t *opts, cmd_error_handler_t *err);
+
+cmd_status_t cmd_parsev (size_t argc, char **argv, cmd_t *ret_cmd,
+               const cmd_options_t *opts, cmd_error_handler_t *err);
+
+void cmd_destroy (cmd_t *cmd);
+
+/*
+ * NAME
+ *   cmd_parse_option
+ *
+ * DESCRIPTION
+ *   Parses a command option which must be of the form:
+ *     name=value with \ and spaces
+ *
+ * PARAMETERS
+ *   `field'     The parsed input field with any quotes removed and special
+ *               characters unescaped.
+ *   `ret_key'   The parsed key will be stored at this location.
+ *   `ret_value' The parsed value will be stored at this location.
+ *
+ * RETURN VALUE
+ *   CMD_OK on success or an error code otherwise.
+ *   CMD_NO_OPTION if `field' does not represent an option at all (missing
+ *   equal sign).
+ */
+cmd_status_t cmd_parse_option (char *field,
+               char **ret_key, char **ret_value, cmd_error_handler_t *err);
+
+/*
+ * NAME
+ *   cmd_error_fh
+ *
+ * DESCRIPTION
+ *   An error callback writing the message to an open file handle using the
+ *   format expected by the unixsock or exec plugins.
+ *
+ * PARAMETERS
+ *   `ud'     Error handler user-data pointer. This must be an open
+ *            file-handle (FILE *).
+ *   `status' The error status code.
+ *   `format' Printf-style format string.
+ *   `ap'     Variable argument list providing the arguments for the format
+ *            string.
+ */
+void cmd_error_fh (void *ud, cmd_status_t status,
+               const char *format, va_list ap);
+
+#endif /* UTILS_CMDS_H */
diff --git a/src/utils_cmds_test.c b/src/utils_cmds_test.c
new file mode 100644 (file)
index 0000000..9e9eae3
--- /dev/null
@@ -0,0 +1,320 @@
+/**
+ * collectd - src/tests/utils_cmds_test.c
+ * Copyright (C) 2016       Sebastian 'tokkee' Harl
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *   Sebastian 'tokkee' Harl <sh at tokkee.org>
+ **/
+
+#include "common.h"
+#include "testing.h"
+#include "utils_cmds.h"
+
+static void error_cb (void *ud, cmd_status_t status,
+               const char *format, va_list ap)
+{
+       if (status == CMD_OK)
+               return;
+
+       printf ("ERROR[%d]: ", status);
+       vprintf (format, ap);
+       printf ("\n");
+       fflush (stdout);
+} /* void error_cb */
+
+static cmd_options_t default_host_opts = {
+       /* identifier_default_host = */ "dummy-host",
+};
+
+static struct {
+       char *input;
+       cmd_options_t *opts;
+       cmd_status_t expected_status;
+       cmd_type_t expected_type;
+} parse_data[] = {
+       /* Valid FLUSH commands. */
+       {
+               "FLUSH",
+               NULL,
+               CMD_OK,
+               CMD_FLUSH,
+       },
+       {
+               "FLUSH identifier=myhost/magic/MAGIC",
+               NULL,
+               CMD_OK,
+               CMD_FLUSH,
+       },
+       {
+               "FLUSH identifier=magic/MAGIC",
+               &default_host_opts,
+               CMD_OK,
+               CMD_FLUSH,
+       },
+       {
+               "FLUSH timeout=123 plugin=\"A\"",
+               NULL,
+               CMD_OK,
+               CMD_FLUSH,
+       },
+       /* Invalid FLUSH commands. */
+       {
+               /* Missing hostname; no default. */
+               "FLUSH identifier=magic/MAGIC",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+       {
+               /* Missing 'identifier' key. */
+               "FLUSH myhost/magic/MAGIC",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+       {
+               /* Invalid timeout. */
+               "FLUSH timeout=A",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+       {
+               /* Invalid identifier. */
+               "FLUSH identifier=invalid",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+       {
+               /* Invalid option. */
+               "FLUSH invalid=option",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+
+       /* Valid GETVAL commands. */
+       {
+               "GETVAL myhost/magic/MAGIC",
+               NULL,
+               CMD_OK,
+               CMD_GETVAL,
+       },
+       {
+               "GETVAL magic/MAGIC",
+               &default_host_opts,
+               CMD_OK,
+               CMD_GETVAL,
+       },
+
+       /* Invalid GETVAL commands. */
+       {
+               "GETVAL magic/MAGIC",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+       {
+               "GETVAL",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+       {
+               "GETVAL invalid",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+
+       /* Valid LISTVAL commands. */
+       {
+               "LISTVAL",
+               NULL,
+               CMD_OK,
+               CMD_LISTVAL,
+       },
+
+       /* Invalid LISTVAL commands. */
+       {
+               "LISTVAL invalid",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+
+       /* Valid PUTVAL commands. */
+       {
+               "PUTVAL magic/MAGIC N:42",
+               &default_host_opts,
+               CMD_OK,
+               CMD_PUTVAL,
+       },
+       {
+               "PUTVAL myhost/magic/MAGIC N:42",
+               NULL,
+               CMD_OK,
+               CMD_PUTVAL,
+       },
+       {
+               "PUTVAL myhost/magic/MAGIC 1234:42",
+               NULL,
+               CMD_OK,
+               CMD_PUTVAL,
+       },
+       {
+               "PUTVAL myhost/magic/MAGIC 1234:42 2345:23",
+               NULL,
+               CMD_OK,
+               CMD_PUTVAL,
+       },
+       {
+               "PUTVAL myhost/magic/MAGIC interval=2 1234:42",
+               NULL,
+               CMD_OK,
+               CMD_PUTVAL,
+       },
+       {
+               "PUTVAL myhost/magic/MAGIC interval=2 1234:42 interval=5 2345:23",
+               NULL,
+               CMD_OK,
+               CMD_PUTVAL,
+       },
+
+       /* Invalid PUTVAL commands. */
+       {
+               "PUTVAL magic/MAGIC N:42",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+       {
+               "PUTVAL",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+       {
+               "PUTVAL invalid N:42",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+       {
+               "PUTVAL myhost/magic/MAGIC A:42",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+       {
+               "PUTVAL myhost/magic/MAGIC 1234:A",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+       {
+               "PUTVAL myhost/magic/MAGIC",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+       {
+               "PUTVAL 1234:A",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+       {
+               "PUTVAL myhost/magic/UNKNOWN 1234:42",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+       /*
+        * As of collectd 5.x, PUTVAL accepts invalid options.
+       {
+               "PUTVAL myhost/magic/MAGIC invalid=2 1234:42",
+               NULL,
+               CMD_PARSE_ERROR,
+               CMD_UNKNOWN,
+       },
+       */
+
+       /* Invalid commands. */
+       {
+               "INVALID",
+               NULL,
+               CMD_UNKNOWN_COMMAND,
+               CMD_UNKNOWN,
+       },
+       {
+               "INVALID interval=2",
+               NULL,
+               CMD_UNKNOWN_COMMAND,
+               CMD_UNKNOWN,
+       },
+};
+
+DEF_TEST(parse)
+{
+       cmd_error_handler_t err = { error_cb, NULL };
+       int test_result = 0;
+
+       for (size_t i = 0; i < STATIC_ARRAY_SIZE (parse_data); i++) {
+               char *input = strdup (parse_data[i].input);
+
+               char description[1024];
+               cmd_status_t status;
+               cmd_t cmd;
+
+               _Bool result;
+
+               memset (&cmd, 0, sizeof (cmd));
+
+               status = cmd_parse (input, &cmd, parse_data[i].opts, &err);
+               snprintf (description, sizeof (description),
+                               "cmd_parse (\"%s\", opts=%p) = %d (type=%d [%s]); want %d (type=%d [%s])",
+                               parse_data[i].input, parse_data[i].opts, status,
+                               cmd.type, CMD_TO_STRING (cmd.type),
+                               parse_data[i].expected_status,
+                               parse_data[i].expected_type,
+                               CMD_TO_STRING (parse_data[i].expected_type));
+               result = (status == parse_data[i].expected_status)
+                               && (cmd.type == parse_data[i].expected_type);
+               LOG (result, description);
+
+               /* Run all tests before failing. */
+               if (! result)
+                       test_result = -1;
+
+               cmd_destroy (&cmd);
+               free (input);
+       }
+
+       return (test_result);
+}
+
+int main (int argc, char **argv)
+{
+       RUN_TEST(parse);
+       END_TEST;
+}
index 2ca9ddf..226ba0b 100644 (file)
@@ -85,7 +85,7 @@ typedef struct user_obj_s user_obj_t;
 struct user_obj_s
 {
   void *user_obj;
-  identifier_t ident;
+  lookup_identifier_t ident;
 
   user_obj_t *next;
 };
@@ -175,7 +175,7 @@ static int lu_copy_ident_to_match_part (part_match_t *match_part, /* {{{ */
 } /* }}} int lu_copy_ident_to_match_part */
 
 static int lu_copy_ident_to_match (identifier_match_t *match, /* {{{ */
-    identifier_t const *ident, unsigned int group_by)
+    lookup_identifier_t const *ident, unsigned int group_by)
 {
   memset (match, 0, sizeof (*match));
 
@@ -609,7 +609,7 @@ void lookup_destroy (lookup_t *obj) /* {{{ */
 } /* }}} void lookup_destroy */
 
 int lookup_add (lookup_t *obj, /* {{{ */
-    identifier_t const *ident, unsigned int group_by, void *user_class)
+    lookup_identifier_t const *ident, unsigned int group_by, void *user_class)
 {
   by_type_entry_t *by_type = NULL;
   user_class_list_t *user_class_obj;
index 1d01ebd..e46e763 100644 (file)
@@ -53,7 +53,7 @@ typedef void (*lookup_free_class_callback_t) (void *user_class);
  * freed. */
 typedef void (*lookup_free_obj_callback_t) (void *user_obj);
 
-struct identifier_s
+struct lookup_identifier_s
 {
   char host[DATA_MAX_NAME_LEN];
   char plugin[DATA_MAX_NAME_LEN];
@@ -61,7 +61,7 @@ struct identifier_s
   char type[DATA_MAX_NAME_LEN];
   char type_instance[DATA_MAX_NAME_LEN];
 };
-typedef struct identifier_s identifier_t;
+typedef struct lookup_identifier_s lookup_identifier_t;
 
 #define LU_GROUP_BY_HOST            0x01
 #define LU_GROUP_BY_PLUGIN          0x02
@@ -80,7 +80,7 @@ lookup_t *lookup_create (lookup_class_callback_t,
 void lookup_destroy (lookup_t *obj);
 
 int lookup_add (lookup_t *obj,
-    identifier_t const *ident, unsigned int group_by, void *user_class);
+    lookup_identifier_t const *ident, unsigned int group_by, void *user_class);
 
 /* TODO(octo): Pass lookup_obj_callback_t to lookup_search()? */
 int lookup_search (lookup_t *obj,
index a99dad0..b5a8e61 100644 (file)
@@ -32,8 +32,8 @@
 static _Bool expect_new_obj = 0;
 static _Bool have_new_obj = 0;
 
-static identifier_t last_class_ident;
-static identifier_t last_obj_ident;
+static lookup_identifier_t last_class_ident;
+static lookup_identifier_t last_obj_ident;
 
 static data_source_t dsrc_test = { "value", DS_TYPE_DERIVE, 0.0, NAN };
 static data_set_t const ds_test = { "test", 1, &dsrc_test };
@@ -45,8 +45,8 @@ static int lookup_obj_callback (data_set_t const *ds,
     value_list_t const *vl,
     void *user_class, void *user_obj)
 {
-  identifier_t *class = user_class;
-  identifier_t *obj = user_obj;
+  lookup_identifier_t *class = user_class;
+  lookup_identifier_t *obj = user_obj;
 
   OK1(expect_new_obj == have_new_obj,
       (expect_new_obj ? "New obj is created." : "Updating existing obj."));
@@ -63,8 +63,8 @@ static int lookup_obj_callback (data_set_t const *ds,
 static void *lookup_class_callback (data_set_t const *ds,
     value_list_t const *vl, void *user_class)
 {
-  identifier_t *class = user_class;
-  identifier_t *obj;
+  lookup_identifier_t *class = user_class;
+  lookup_identifier_t *obj;
 
   assert (expect_new_obj);
 
@@ -88,7 +88,7 @@ static int checked_lookup_add (lookup_t *obj, /* {{{ */
     char const *type, char const *type_instance,
     unsigned int group_by)
 {
-  identifier_t ident;
+  lookup_identifier_t ident;
   void *user_class;
 
   strncpy (ident.host, host, sizeof (ident.host));
index 28ea677..7a23817 100644 (file)
@@ -3,6 +3,6 @@
    Memcheck:Addr4
    fun:parse_value
    fun:parse_values
-   fun:test_parse_values
+   ...
    fun:main
 }
index 75da6aa..4b42231 100644 (file)
@@ -172,9 +172,9 @@ static int kafka_write(const data_set_t *ds, /* {{{ */
 
     switch (ctx->format) {
     case KAFKA_FORMAT_COMMAND:
-        status = create_putval(buffer, sizeof(buffer), ds, vl);
+        status = cmd_create_putval(buffer, sizeof(buffer), ds, vl);
         if (status != 0) {
-            ERROR("write_kafka plugin: create_putval failed with status %i.",
+            ERROR("write_kafka plugin: cmd_create_putval failed with status %i.",
                   status);
             return status;
         }