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