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