1 /**
2 * collectd - src/utils_cmds.c
3 * Copyright (C) 2008 Florian Forster
4 * Copyright (C) 2016 Sebastian 'tokkee' Harl
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *
24 * Authors:
25 * Florian octo Forster <octo at collectd.org>
26 * Sebastian 'tokkee' Harl <sh at tokkee.org>
27 **/
29 #include "daemon/common.h"
30 #include "utils_cmd_flush.h"
31 #include "utils_cmd_getval.h"
32 #include "utils_cmd_listval.h"
33 #include "utils_cmd_putval.h"
34 #include "utils_cmds.h"
35 #include "utils_parse_option.h"
37 #include <stdbool.h>
38 #include <string.h>
40 static cmd_options_t default_options = {
41 /* identifier_default_host = */ NULL,
42 };
44 /*
45 * private helper functions
46 */
48 static cmd_status_t cmd_split(char *buffer, size_t *ret_len, char ***ret_fields,
49 cmd_error_handler_t *err) {
50 char *field;
51 bool in_field, in_quotes;
53 size_t estimate, len;
54 char **fields;
56 estimate = 0;
57 in_field = false;
58 for (char *string = buffer; *string != '\0'; ++string) {
59 /* Make a quick worst-case estimate of the number of fields by
60 * counting spaces and ignoring quotation marks. */
61 if (!isspace((int)*string)) {
62 if (!in_field) {
63 estimate++;
64 in_field = true;
65 }
66 } else {
67 in_field = false;
68 }
69 }
71 /* fields will be NULL-terminated */
72 fields = malloc((estimate + 1) * sizeof(*fields));
73 if (fields == NULL) {
74 cmd_error(CMD_ERROR, err, "malloc failed.");
75 return (CMD_ERROR);
76 }
78 #define END_FIELD() \
79 do { \
80 *field = '\0'; \
81 field = NULL; \
82 in_field = false; \
83 } while (0)
84 #define NEW_FIELD() \
85 do { \
86 field = string; \
87 in_field = true; \
88 assert(len < estimate); \
89 fields[len] = field; \
90 field++; \
91 len++; \
92 } while (0)
94 len = 0;
95 field = NULL;
96 in_field = false;
97 in_quotes = false;
98 for (char *string = buffer; *string != '\0'; string++) {
99 if (isspace((int)string[0])) {
100 if (!in_quotes) {
101 if (in_field)
102 END_FIELD();
104 /* skip space */
105 continue;
106 }
107 } else if (string[0] == '"') {
108 /* Note: Two consecutive quoted fields not separated by space are
109 * treated as different fields. This is the collectd 5.x behavior
110 * around splitting fields. */
112 if (in_quotes) {
113 /* end of quoted field */
114 if (!in_field) /* empty quoted string */
115 NEW_FIELD();
116 END_FIELD();
117 in_quotes = false;
118 continue;
119 }
121 in_quotes = true;
122 /* if (! in_field): add new field on next iteration
123 * else: quoted string following an unquoted string (one field)
124 * in either case: skip quotation mark */
125 continue;
126 } else if ((string[0] == '\\') && in_quotes) {
127 /* Outside of quotes, a backslash is a regular character (mostly
128 * for backward compatibility). */
130 if (string[1] == '\0') {
131 free(fields);
132 cmd_error(CMD_PARSE_ERROR, err, "Backslash at end of string.");
133 return (CMD_PARSE_ERROR);
134 }
136 /* un-escape the next character; skip backslash */
137 string++;
138 }
140 if (!in_field)
141 NEW_FIELD();
142 else {
143 *field = string[0];
144 field++;
145 }
146 }
148 if (in_quotes) {
149 free(fields);
150 cmd_error(CMD_PARSE_ERROR, err, "Unterminated quoted string.");
151 return (CMD_PARSE_ERROR);
152 }
154 #undef NEW_FIELD
155 #undef END_FIELD
157 fields[len] = NULL;
158 if (ret_len != NULL)
159 *ret_len = len;
160 if (ret_fields != NULL)
161 *ret_fields = fields;
162 else
163 free(fields);
164 return (CMD_OK);
165 } /* int cmd_split */
167 /*
168 * public API
169 */
171 void cmd_error(cmd_status_t status, cmd_error_handler_t *err,
172 const char *format, ...) {
173 va_list ap;
175 if ((err == NULL) || (err->cb == NULL))
176 return;
178 va_start(ap, format);
179 err->cb(err->ud, status, format, ap);
180 va_end(ap);
181 } /* void cmd_error */
183 cmd_status_t cmd_parsev(size_t argc, char **argv, cmd_t *ret_cmd,
184 const cmd_options_t *opts, cmd_error_handler_t *err) {
185 char *command = NULL;
186 cmd_status_t status;
188 if ((argc < 1) || (argv == NULL) || (ret_cmd == NULL)) {
189 errno = EINVAL;
190 cmd_error(CMD_ERROR, err, "Missing command.");
191 return CMD_ERROR;
192 }
194 if (opts == NULL)
195 opts = &default_options;
197 memset(ret_cmd, 0, sizeof(*ret_cmd));
198 command = argv[0];
199 if (strcasecmp("FLUSH", command) == 0) {
200 ret_cmd->type = CMD_FLUSH;
201 status =
202 cmd_parse_flush(argc - 1, argv + 1, &ret_cmd->cmd.flush, opts, err);
203 } else if (strcasecmp("GETVAL", command) == 0) {
204 ret_cmd->type = CMD_GETVAL;
205 status =
206 cmd_parse_getval(argc - 1, argv + 1, &ret_cmd->cmd.getval, opts, err);
207 } else if (strcasecmp("LISTVAL", command) == 0) {
208 ret_cmd->type = CMD_LISTVAL;
209 status =
210 cmd_parse_listval(argc - 1, argv + 1, &ret_cmd->cmd.listval, opts, err);
211 } else if (strcasecmp("PUTVAL", command) == 0) {
212 ret_cmd->type = CMD_PUTVAL;
213 status =
214 cmd_parse_putval(argc - 1, argv + 1, &ret_cmd->cmd.putval, opts, err);
215 } else {
216 ret_cmd->type = CMD_UNKNOWN;
217 cmd_error(CMD_UNKNOWN_COMMAND, err, "Unknown command `%s'.", command);
218 return (CMD_UNKNOWN_COMMAND);
219 }
221 if (status != CMD_OK)
222 ret_cmd->type = CMD_UNKNOWN;
223 return (status);
224 } /* cmd_status_t cmd_parsev */
226 cmd_status_t cmd_parse(char *buffer, cmd_t *ret_cmd, const cmd_options_t *opts,
227 cmd_error_handler_t *err) {
228 char **fields = NULL;
229 size_t fields_num = 0;
230 cmd_status_t status;
232 if ((status = cmd_split(buffer, &fields_num, &fields, err)) != CMD_OK)
233 return status;
235 status = cmd_parsev(fields_num, fields, ret_cmd, opts, err);
236 free(fields);
237 return (status);
238 } /* cmd_status_t cmd_parse */
240 void cmd_destroy(cmd_t *cmd) {
241 if (cmd == NULL)
242 return;
244 switch (cmd->type) {
245 case CMD_UNKNOWN:
246 /* nothing to do */
247 break;
248 case CMD_FLUSH:
249 cmd_destroy_flush(&cmd->cmd.flush);
250 break;
251 case CMD_GETVAL:
252 cmd_destroy_getval(&cmd->cmd.getval);
253 break;
254 case CMD_LISTVAL:
255 cmd_destroy_listval(&cmd->cmd.listval);
256 break;
257 case CMD_PUTVAL:
258 cmd_destroy_putval(&cmd->cmd.putval);
259 break;
260 }
261 } /* void cmd_destroy */
263 cmd_status_t cmd_parse_option(char *field, char **ret_key, char **ret_value,
264 cmd_error_handler_t *err) {
265 char *key, *value;
267 if (field == NULL) {
268 errno = EINVAL;
269 cmd_error(CMD_ERROR, err, "Invalid argument to cmd_parse_option.");
270 return (CMD_ERROR);
271 }
272 key = value = field;
274 /* Look for the equal sign. */
275 while (isalnum((int)value[0]) || (value[0] == '_') || (value[0] == ':'))
276 value++;
277 if ((value[0] != '=') || (value == key)) {
278 /* Whether this is a fatal error is up to the caller. */
279 return (CMD_NO_OPTION);
280 }
281 *value = '\0';
282 value++;
284 if (ret_key != NULL)
285 *ret_key = key;
286 if (ret_value != NULL)
287 *ret_value = value;
289 return (CMD_OK);
290 } /* cmd_status_t cmd_parse_option */
292 void cmd_error_fh(void *ud, cmd_status_t status, const char *format,
293 va_list ap) {
294 FILE *fh = ud;
295 int code = -1;
296 char buf[1024];
298 if (status == CMD_OK)
299 code = 0;
301 vsnprintf(buf, sizeof(buf), format, ap);
302 buf[sizeof(buf) - 1] = '\0';
303 if (fprintf(fh, "%i %s\n", code, buf) < 0) {
304 char errbuf[1024];
305 WARNING("utils_cmds: failed to write to file-handle #%i: %s", fileno(fh),
306 sstrerror(errno, errbuf, sizeof(errbuf)));
307 return;
308 }
310 fflush(fh);
311 } /* void cmd_error_fh */
313 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */