f6675969fa55d543dccd7a508d6fc82359d504ae
[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 static cmd_options_t default_options = {
41         /* identifier_default_host = */ NULL,
42 };
43
44 /*
45  * private helper functions
46  */
47
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 *string, *field;
53         bool in_field, in_quotes;
54
55         size_t estimate, len;
56         char **fields;
57
58         estimate = 0;
59         in_field = false;
60         for (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         }
77
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         }
84
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)
100
101         len = 0;
102         field = NULL;
103         in_field = false;
104         in_quotes = false;
105         for (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 ();
113
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. */
123
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                         }
133
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). */
144
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                         }
152
153                         /* un-escape the next character; skip backslash */
154                         string++;
155                 }
156
157                 if (! in_field)
158                         NEW_FIELD ();
159                 else {
160                         *field = string[0];
161                         field++;
162                 }
163         }
164
165         if (in_quotes)
166         {
167                 free (fields);
168                 cmd_error (CMD_PARSE_ERROR, err, "Unterminated quoted string.");
169                 return (CMD_PARSE_ERROR);
170         }
171
172 #undef NEW_FIELD
173 #undef END_FIELD
174
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 */
184
185 /*
186  * public API
187  */
188
189 void cmd_error (cmd_status_t status, cmd_error_handler_t *err,
190                 const char *format, ...)
191 {
192         va_list ap;
193
194         if ((err == NULL) || (err->cb == NULL))
195                 return;
196
197         va_start (ap, format);
198         err->cb (err->ud, status, format, ap);
199         va_end (ap);
200 } /* void cmd_error */
201
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;
207
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         }
214
215         if (opts == NULL)
216                 opts = &default_options;
217
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         }
251
252         if (status != CMD_OK)
253                 ret_cmd->type = CMD_UNKNOWN;
254         return (status);
255 } /* cmd_status_t cmd_parsev */
256
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;
263
264         if ((status = cmd_split (buffer, &fields_num, &fields, err)) != CMD_OK)
265                 return status;
266
267         status = cmd_parsev (fields_num, fields, ret_cmd, opts, err);
268         free (fields);
269         return (status);
270 } /* cmd_status_t cmd_parse */
271
272 void cmd_destroy (cmd_t *cmd)
273 {
274         if (cmd == NULL)
275                 return;
276
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 */
296
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;
301
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;
309
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++;
320
321         if (ret_key != NULL)
322                 *ret_key = key;
323         if (ret_value != NULL)
324                 *ret_value = value;
325
326         return (CMD_OK);
327 } /* cmd_status_t cmd_parse_option */
328
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];
335
336         if (status == CMD_OK)
337                 code = 0;
338
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         }
348
349         fflush (fh);
350 } /* void cmd_error_fh */
351
352 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */