1f53ad1d6a4da8bc966d2c67f024a29d2517ddef
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 "utils_cmds.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_parse_option.h"
35 #include "daemon/common.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,
49 size_t *ret_len, char ***ret_fields,
50 cmd_error_handler_t *err)
51 {
52 char *field;
53 bool in_field, in_quotes;
55 size_t estimate, len;
56 char **fields;
58 estimate = 0;
59 in_field = false;
60 for (char *string = buffer; *string != '\0'; ++string)
61 {
62 /* Make a quick worst-case estimate of the number of fields by
63 * counting spaces and ignoring quotation marks. */
64 if (!isspace ((int)*string))
65 {
66 if (!in_field)
67 {
68 estimate++;
69 in_field = true;
70 }
71 }
72 else
73 {
74 in_field = false;
75 }
76 }
78 /* fields will be NULL-terminated */
79 fields = malloc ((estimate + 1) * sizeof (*fields));
80 if (fields == NULL) {
81 cmd_error (CMD_ERROR, err, "malloc failed.");
82 return (CMD_ERROR);
83 }
85 #define END_FIELD() \
86 do { \
87 *field = '\0'; \
88 field = NULL; \
89 in_field = false; \
90 } while (0)
91 #define NEW_FIELD() \
92 do { \
93 field = string; \
94 in_field = true; \
95 assert (len < estimate); \
96 fields[len] = field; \
97 field++; \
98 len++; \
99 } while (0)
101 len = 0;
102 field = NULL;
103 in_field = false;
104 in_quotes = false;
105 for (char *string = buffer; *string != '\0'; string++)
106 {
107 if (isspace ((int)string[0]))
108 {
109 if (! in_quotes)
110 {
111 if (in_field)
112 END_FIELD ();
114 /* skip space */
115 continue;
116 }
117 }
118 else if (string[0] == '"')
119 {
120 /* Note: Two consecutive quoted fields not separated by space are
121 * treated as different fields. This is the collectd 5.x behavior
122 * around splitting fields. */
124 if (in_quotes)
125 {
126 /* end of quoted field */
127 if (! in_field) /* empty quoted string */
128 NEW_FIELD ();
129 END_FIELD ();
130 in_quotes = false;
131 continue;
132 }
134 in_quotes = true;
135 /* if (! in_field): add new field on next iteration
136 * else: quoted string following an unquoted string (one field)
137 * in either case: skip quotation mark */
138 continue;
139 }
140 else if ((string[0] == '\\') && in_quotes)
141 {
142 /* Outside of quotes, a backslash is a regular character (mostly
143 * for backward compatibility). */
145 if (string[1] == '\0')
146 {
147 free (fields);
148 cmd_error (CMD_PARSE_ERROR, err,
149 "Backslash at end of string.");
150 return (CMD_PARSE_ERROR);
151 }
153 /* un-escape the next character; skip backslash */
154 string++;
155 }
157 if (! in_field)
158 NEW_FIELD ();
159 else {
160 *field = string[0];
161 field++;
162 }
163 }
165 if (in_quotes)
166 {
167 free (fields);
168 cmd_error (CMD_PARSE_ERROR, err, "Unterminated quoted string.");
169 return (CMD_PARSE_ERROR);
170 }
172 #undef NEW_FIELD
173 #undef END_FIELD
175 fields[len] = NULL;
176 if (ret_len != NULL)
177 *ret_len = len;
178 if (ret_fields != NULL)
179 *ret_fields = fields;
180 else
181 free (fields);
182 return (CMD_OK);
183 } /* int cmd_split */
185 /*
186 * public API
187 */
189 void cmd_error (cmd_status_t status, cmd_error_handler_t *err,
190 const char *format, ...)
191 {
192 va_list ap;
194 if ((err == NULL) || (err->cb == NULL))
195 return;
197 va_start (ap, format);
198 err->cb (err->ud, status, format, ap);
199 va_end (ap);
200 } /* void cmd_error */
202 cmd_status_t cmd_parsev (size_t argc, char **argv, cmd_t *ret_cmd,
203 const cmd_options_t *opts, cmd_error_handler_t *err)
204 {
205 char *command = NULL;
206 cmd_status_t status;
208 if ((argc < 1) || (argv == NULL) || (ret_cmd == NULL))
209 {
210 errno = EINVAL;
211 cmd_error (CMD_ERROR, err, "Missing command.");
212 return CMD_ERROR;
213 }
215 if (opts == NULL)
216 opts = &default_options;
218 memset (ret_cmd, 0, sizeof (*ret_cmd));
219 command = argv[0];
220 if (strcasecmp ("FLUSH", command) == 0)
221 {
222 ret_cmd->type = CMD_FLUSH;
223 status = cmd_parse_flush (argc - 1, argv + 1,
224 &ret_cmd->cmd.flush, opts, err);
225 }
226 else if (strcasecmp ("GETVAL", command) == 0)
227 {
228 ret_cmd->type = CMD_GETVAL;
229 status = cmd_parse_getval (argc - 1, argv + 1,
230 &ret_cmd->cmd.getval, opts, err);
231 }
232 else if (strcasecmp ("LISTVAL", command) == 0)
233 {
234 ret_cmd->type = CMD_LISTVAL;
235 status = cmd_parse_listval (argc - 1, argv + 1,
236 &ret_cmd->cmd.listval, opts, err);
237 }
238 else if (strcasecmp ("PUTVAL", command) == 0)
239 {
240 ret_cmd->type = CMD_PUTVAL;
241 status = cmd_parse_putval (argc - 1, argv + 1,
242 &ret_cmd->cmd.putval, opts, err);
243 }
244 else
245 {
246 ret_cmd->type = CMD_UNKNOWN;
247 cmd_error (CMD_UNKNOWN_COMMAND, err,
248 "Unknown command `%s'.", command);
249 return (CMD_UNKNOWN_COMMAND);
250 }
252 if (status != CMD_OK)
253 ret_cmd->type = CMD_UNKNOWN;
254 return (status);
255 } /* cmd_status_t cmd_parsev */
257 cmd_status_t cmd_parse (char *buffer, cmd_t *ret_cmd,
258 const cmd_options_t *opts, cmd_error_handler_t *err)
259 {
260 char **fields = NULL;
261 size_t fields_num = 0;
262 cmd_status_t status;
264 if ((status = cmd_split (buffer, &fields_num, &fields, err)) != CMD_OK)
265 return status;
267 status = cmd_parsev (fields_num, fields, ret_cmd, opts, err);
268 free (fields);
269 return (status);
270 } /* cmd_status_t cmd_parse */
272 void cmd_destroy (cmd_t *cmd)
273 {
274 if (cmd == NULL)
275 return;
277 switch (cmd->type)
278 {
279 case CMD_UNKNOWN:
280 /* nothing to do */
281 break;
282 case CMD_FLUSH:
283 cmd_destroy_flush (&cmd->cmd.flush);
284 break;
285 case CMD_GETVAL:
286 cmd_destroy_getval (&cmd->cmd.getval);
287 break;
288 case CMD_LISTVAL:
289 cmd_destroy_listval (&cmd->cmd.listval);
290 break;
291 case CMD_PUTVAL:
292 cmd_destroy_putval (&cmd->cmd.putval);
293 break;
294 }
295 } /* void cmd_destroy */
297 cmd_status_t cmd_parse_option (char *field,
298 char **ret_key, char **ret_value, cmd_error_handler_t *err)
299 {
300 char *key, *value;
302 if (field == NULL)
303 {
304 errno = EINVAL;
305 cmd_error (CMD_ERROR, err, "Invalid argument to cmd_parse_option.");
306 return (CMD_ERROR);
307 }
308 key = value = field;
310 /* Look for the equal sign. */
311 while (isalnum ((int)value[0]) || (value[0] == '_') || (value[0] == ':'))
312 value++;
313 if ((value[0] != '=') || (value == key))
314 {
315 /* Whether this is a fatal error is up to the caller. */
316 return (CMD_NO_OPTION);
317 }
318 *value = '\0';
319 value++;
321 if (ret_key != NULL)
322 *ret_key = key;
323 if (ret_value != NULL)
324 *ret_value = value;
326 return (CMD_OK);
327 } /* cmd_status_t cmd_parse_option */
329 void cmd_error_fh (void *ud, cmd_status_t status,
330 const char *format, va_list ap)
331 {
332 FILE *fh = ud;
333 int code = -1;
334 char buf[1024];
336 if (status == CMD_OK)
337 code = 0;
339 vsnprintf (buf, sizeof(buf), format, ap);
340 buf[sizeof (buf) - 1] = '\0';
341 if (fprintf (fh, "%i %s\n", code, buf) < 0)
342 {
343 char errbuf[1024];
344 WARNING ("utils_cmds: failed to write to file-handle #%i: %s",
345 fileno (fh), sstrerror (errno, errbuf, sizeof (errbuf)));
346 return;
347 }
349 fflush (fh);
350 } /* void cmd_error_fh */
352 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */