/** * 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 * Sebastian 'tokkee' Harl **/ #include "utils/cmds/cmds.h" #include "utils/cmds/flush.h" #include "utils/cmds/getval.h" #include "utils/cmds/listval.h" #include "utils/cmds/parse_option.h" #include "utils/cmds/putval.h" #include "utils/common/common.h" #include #include 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, 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: 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) { WARNING("utils_cmds: failed to write to file-handle #%i: %s", fileno(fh), STRERRNO); return; } fflush(fh); } /* void cmd_error_fh */