summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 3090a88)
raw | patch | inline | side by side (parent: 3090a88)
author | Sebastian Harl <sh@tokkee.org> | |
Sun, 5 Jun 2016 13:23:41 +0000 (15:23 +0200) | ||
committer | Sebastian Harl <sh@tokkee.org> | |
Sun, 25 Sep 2016 10:42:45 +0000 (12:42 +0200) |
The new interface expects a parsed list of fields. Parsing of input buffers
is now done centrally in the generic code and the existing parser is a
wrapper around this and the new vector-based parser. The actual command
parsers (PUTVAL for now) receive the parsed list of fields.
As a side-effect, this changes the parser behavior a little:
- "foo=a string with spaces or \\ special characters" is now valid syntax
for specifying options in addition to foo="value with spaces, etc.".
- foo= is now a valid option even without quotes around the empty value.
is now done centrally in the generic code and the existing parser is a
wrapper around this and the new vector-based parser. The actual command
parsers (PUTVAL for now) receive the parsed list of fields.
As a side-effect, this changes the parser behavior a little:
- "foo=a string with spaces or \\ special characters" is now valid syntax
for specifying options in addition to foo="value with spaces, etc.".
- foo= is now a valid option even without quotes around the empty value.
src/utils_cmd_putval.c | patch | blob | history | |
src/utils_cmd_putval.h | patch | blob | history | |
src/utils_cmds.c | patch | blob | history | |
src/utils_cmds.h | patch | blob | history |
diff --git a/src/utils_cmd_putval.c b/src/utils_cmd_putval.c
index 36419c6a55e85da02632bc76b7f2e355206542e1..ea3a16ce71d0c6f8310d82f6c79c247a1feed396 100644 (file)
--- a/src/utils_cmd_putval.c
+++ b/src/utils_cmd_putval.c
* public API
*/
-cmd_status_t cmd_parse_putval (char *buffer,
+cmd_status_t cmd_parse_putval (size_t argc, char **argv,
cmd_putval_t *ret_putval, cmd_error_handler_t *err)
{
char *identifier;
const data_set_t *ds;
value_list_t vl = VALUE_LIST_INIT;
- vl.values = NULL;
+ size_t i;
- identifier = NULL;
- status = parse_string (&buffer, &identifier);
- if (status != 0)
+ if (argc < 2)
{
- cmd_error (CMD_PARSE_ERROR, err, "Cannot parse identifier.");
+ cmd_error (CMD_PARSE_ERROR, err,
+ "Missing identifier and/or value-list.");
return (CMD_PARSE_ERROR);
}
- assert (identifier != NULL);
- /* parse_identifier() modifies its first argument,
- * returning pointers into it */
+ identifier = argv[0];
+
+ /* 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);
if (status != 0)
{
DEBUG ("cmd_handle_putval: Cannot parse identifier `%s'.",
- identifier);
+ identifier_copy);
cmd_error (CMD_PARSE_ERROR, err, "Cannot parse identifier `%s'.",
- identifier);
+ identifier_copy);
sfree (identifier_copy);
return (CMD_PARSE_ERROR);
}
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)
{
cmd_error (CMD_ERROR, err, "malloc failed.");
+ sfree (identifier_copy);
return (CMD_ERROR);
}
- ret_putval->identifier = strdup (identifier);
+ ret_putval->identifier = identifier_copy;
if (ret_putval->identifier == NULL)
{
cmd_error (CMD_ERROR, err, "malloc failed.");
return (CMD_ERROR);
}
- /* All the remaining fields are part of the optionlist. */
- while (*buffer != 0)
+ /* All the remaining fields are part of the option list. */
+ for (i = 1; i < argc; ++i)
{
value_list_t *tmp;
- char *string = NULL;
- char *value = NULL;
+ char *key = NULL;
+ char *value = NULL;
- status = parse_option (&buffer, &string, &value);
- if (status < 0)
+ status = cmd_parse_option (argv[i], &key, &value, err);
+ if (status == CMD_OK)
{
- /* parse_option failed, buffer has been modified.
- * => we need to abort */
- cmd_error (CMD_PARSE_ERROR, err, "Misformatted option.");
- cmd_destroy_putval (ret_putval);
- return (CMD_PARSE_ERROR);
- }
- else if (status == 0)
- {
- 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.. */
-
- status = parse_string (&buffer, &string);
- if (status != 0)
+ else if (status != CMD_NO_OPTION)
{
- cmd_error (CMD_PARSE_ERROR, err, "Misformatted value.");
+ /* parse_option failed, buffer has been modified.
+ * => we need to abort */
cmd_destroy_putval (ret_putval);
- return (CMD_PARSE_ERROR);
+ return (status);
}
- assert (string != NULL);
+ /* else: cmd_parse_option did not find an option; treat this as a
+ * value list. */
- status = parse_values (string, &vl, ds);
+ status = parse_values (argv[i], &vl, ds);
if (status != 0)
{
cmd_error (CMD_PARSE_ERROR, err, "Parsing the values string failed.");
diff --git a/src/utils_cmd_putval.h b/src/utils_cmd_putval.h
index bc7763d5e13b0777ca2c2269362e59fa875c1ef1..cd62a1b47c01173efc7a404b977dfdaed525ffdd 100644 (file)
--- a/src/utils_cmd_putval.h
+++ b/src/utils_cmd_putval.h
#include "plugin.h"
#include "utils_cmds.h"
-cmd_status_t cmd_parse_putval (char *buffer,
+cmd_status_t cmd_parse_putval (size_t argc, char **argv,
cmd_putval_t *ret_cmd, cmd_error_handler_t *err);
cmd_status_t cmd_handle_putval (FILE *fh, char *buffer);
diff --git a/src/utils_cmds.c b/src/utils_cmds.c
index 8468267e41cba673a92c205b7eda8fd167e98035..3bc260b4533ca181b8fd38af2f597abb879658c0 100644 (file)
--- a/src/utils_cmds.c
+++ b/src/utils_cmds.c
/**
* 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
* DEALINGS IN THE SOFTWARE.
*
* Authors:
+ * Florian octo Forster <octo at collectd.org>
* Sebastian 'tokkee' Harl <sh at tokkee.org>
**/
#include <stdbool.h>
#include <string.h>
+/*
+ * private helper functions
+ */
+
+static cmd_status_t cmd_split (char *buffer,
+ size_t *ret_len, char ***ret_fields,
+ cmd_error_handler_t *err)
+{
+ char *string, *field;
+ bool in_field, in_quotes;
+
+ size_t estimate, len;
+ char **fields;
+
+ estimate = 0;
+ in_field = false;
+ for (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 (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
*/
va_end (ap);
} /* void cmd_error */
-cmd_status_t cmd_parse (char *buffer,
+cmd_status_t cmd_parsev (size_t argc, char **argv,
cmd_t *ret_cmd, cmd_error_handler_t *err)
{
char *command = NULL;
- int status;
- if ((buffer == NULL) || (ret_cmd == NULL))
+ if ((argc < 1) || (argv == NULL) || (ret_cmd == NULL))
{
errno = EINVAL;
- cmd_error (CMD_ERROR, err, "Invalid arguments to cmd_parse.");
+ cmd_error (CMD_ERROR, err, "Missing command.");
return CMD_ERROR;
}
- if ((status = parse_string (&buffer, &command)) != 0)
- {
- cmd_error (CMD_PARSE_ERROR, err,
- "Failed to extract command from `%s'.", buffer);
- return (CMD_PARSE_ERROR);
- }
- assert (command != NULL);
-
memset (ret_cmd, 0, sizeof (*ret_cmd));
+ command = argv[0];
if (strcasecmp ("PUTVAL", command) == 0)
{
ret_cmd->type = CMD_PUTVAL;
- return cmd_parse_putval (buffer, &ret_cmd->cmd.putval, err);
+ return cmd_parse_putval (argc - 1, argv + 1,
+ &ret_cmd->cmd.putval, err);
}
else
{
}
return (CMD_OK);
+} /* cmd_status_t cmd_parsev */
+
+cmd_status_t cmd_parse (char *buffer,
+ cmd_t *ret_cmd, 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, err);
+ free (fields);
+ return (status);
} /* cmd_status_t cmd_parse */
void cmd_destroy (cmd_t *cmd)
}
} /* 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)
{
diff --git a/src/utils_cmds.h b/src/utils_cmds.h
index f3daa41fae38fc2ba4955f6b64d56f0c5181159d..7dc80fa241183710d15cf84614a77cf30c88e3a1 100644 (file)
--- a/src/utils_cmds.h
+++ b/src/utils_cmds.h
CMD_ERROR = -1,
CMD_PARSE_ERROR = -2,
CMD_UNKNOWN_COMMAND = -3,
+
+ /* Not necessarily fatal errors. */
+ CMD_NO_OPTION = 1,
} cmd_status_t;
/*
cmd_status_t cmd_parse (char *buffer,
cmd_t *ret_cmd, cmd_error_handler_t *err);
+cmd_status_t cmd_parsev (size_t argc, char **argv,
+ cmd_t *ret_cmd, 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