7648435b86692d80abcc832e3d9f86fb8a49ea2f
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_listval.h"
32 #include "utils_cmd_putval.h"
33 #include "utils_parse_option.h"
34 #include "daemon/common.h"
36 #include <stdbool.h>
37 #include <string.h>
39 /*
40 * private helper functions
41 */
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;
50 size_t estimate, len;
51 char **fields;
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 }
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 }
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)
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 ();
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. */
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 }
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). */
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 }
148 /* un-escape the next character; skip backslash */
149 string++;
150 }
152 if (! in_field)
153 NEW_FIELD ();
154 else {
155 *field = string[0];
156 field++;
157 }
158 }
160 if (in_quotes)
161 {
162 free (fields);
163 cmd_error (CMD_PARSE_ERROR, err, "Unterminated quoted string.");
164 return (CMD_PARSE_ERROR);
165 }
167 #undef NEW_FIELD
168 #undef END_FIELD
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 */
180 /*
181 * public API
182 */
184 void cmd_error (cmd_status_t status, cmd_error_handler_t *err,
185 const char *format, ...)
186 {
187 va_list ap;
189 if ((err == NULL) || (err->cb == NULL))
190 return;
192 va_start (ap, format);
193 err->cb (err->ud, status, format, ap);
194 va_end (ap);
195 } /* void cmd_error */
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;
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 }
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 }
237 return (CMD_OK);
238 } /* cmd_status_t cmd_parsev */
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;
247 if ((status = cmd_split (buffer, &fields_num, &fields, err)) != CMD_OK)
248 return status;
250 status = cmd_parsev (fields_num, fields, ret_cmd, err);
251 free (fields);
252 return (status);
253 } /* cmd_status_t cmd_parse */
255 void cmd_destroy (cmd_t *cmd)
256 {
257 if (cmd == NULL)
258 return;
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 */
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;
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;
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++;
301 if (ret_key != NULL)
302 *ret_key = key;
303 if (ret_value != NULL)
304 *ret_value = value;
306 return (CMD_OK);
307 } /* cmd_status_t cmd_parse_option */
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];
316 if (status == CMD_OK)
317 code = 0;
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 }
329 fflush (fh);
330 } /* void cmd_error_fh */
332 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */