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