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