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