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