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