1 /**
2 * collectd - src/curl_json.c
3 * Copyright (C) 2009 Doug MacEachern
4 * Copyright (C) 2006-2011 Florian octo Forster
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; only version 2 of the License is applicable.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 * Authors:
20 * Doug MacEachern <dougm at hyperic.com>
21 * Florian octo Forster <octo at collectd.org>
22 **/
24 #include "collectd.h"
25 #include "common.h"
26 #include "plugin.h"
27 #include "configfile.h"
28 #include "utils_avltree.h"
30 #include <curl/curl.h>
31 #include <yajl/yajl_parse.h>
32 #if HAVE_YAJL_YAJL_VERSION_H
33 # include <yajl/yajl_version.h>
34 #endif
36 #if defined(YAJL_MAJOR) && (YAJL_MAJOR > 1)
37 # define HAVE_YAJL_V2 1
38 #endif
40 #define CJ_DEFAULT_HOST "localhost"
41 #define CJ_KEY_MAGIC 0x43484b59UL /* CHKY */
42 #define CJ_IS_KEY(key) ((key)->magic == CJ_KEY_MAGIC)
43 #define CJ_ANY "*"
44 #define COUCH_MIN(x,y) ((x) < (y) ? (x) : (y))
46 struct cj_key_s;
47 typedef struct cj_key_s cj_key_t;
48 struct cj_key_s /* {{{ */
49 {
50 char *path;
51 char *type;
52 char *instance;
53 unsigned long magic;
54 };
55 /* }}} */
57 struct cj_s /* {{{ */
58 {
59 char *instance;
60 char *host;
62 char *url;
63 char *user;
64 char *pass;
65 char *credentials;
66 _Bool verify_peer;
67 _Bool verify_host;
68 char *cacert;
70 CURL *curl;
71 char curl_errbuf[CURL_ERROR_SIZE];
73 yajl_handle yajl;
74 c_avl_tree_t *tree;
75 cj_key_t *key;
76 int depth;
77 struct {
78 union {
79 c_avl_tree_t *tree;
80 cj_key_t *key;
81 };
82 char name[DATA_MAX_NAME_LEN];
83 } state[YAJL_MAX_DEPTH];
84 };
85 typedef struct cj_s cj_t; /* }}} */
87 #if HAVE_YAJL_V2
88 typedef size_t yajl_len_t;
89 #else
90 typedef unsigned int yajl_len_t;
91 #endif
93 static int cj_read (user_data_t *ud);
94 static int cj_curl_perform (cj_t *db, CURL *curl);
95 static void cj_submit (cj_t *db, cj_key_t *key, value_t *value);
97 static size_t cj_curl_callback (void *buf, /* {{{ */
98 size_t size, size_t nmemb, void *user_data)
99 {
100 cj_t *db;
101 size_t len;
102 yajl_status status;
104 len = size * nmemb;
106 if (len <= 0)
107 return (len);
109 db = user_data;
110 if (db == NULL)
111 return (0);
113 status = yajl_parse(db->yajl, (unsigned char *) buf, len);
114 if (status == yajl_status_ok)
115 {
116 #if HAVE_YAJL_V2
117 status = yajl_complete_parse(db->yajl);
118 #else
119 status = yajl_parse_complete(db->yajl);
120 #endif
121 return (len);
122 }
123 #if !HAVE_YAJL_V2
124 else if (status == yajl_status_insufficient_data)
125 return (len);
126 #endif
128 if (status != yajl_status_ok)
129 {
130 unsigned char *msg =
131 yajl_get_error(db->yajl, /* verbose = */ 1,
132 /* jsonText = */ (unsigned char *) buf, (unsigned int) len);
133 ERROR ("curl_json plugin: yajl_parse failed: %s", msg);
134 yajl_free_error(db->yajl, msg);
135 return (0); /* abort write callback */
136 }
138 return (len);
139 } /* }}} size_t cj_curl_callback */
141 static int cj_get_type (cj_key_t *key)
142 {
143 const data_set_t *ds;
145 ds = plugin_get_ds (key->type);
146 if (ds == NULL)
147 return -1; /* let plugin_write do the complaining */
148 else
149 return ds->ds[0].type; /* XXX support ds->ds_len > 1 */
150 }
152 /* yajl callbacks */
153 #define CJ_CB_ABORT 0
154 #define CJ_CB_CONTINUE 1
156 /* "number" may not be null terminated, so copy it into a buffer before
157 * parsing. */
158 static int cj_cb_number (void *ctx,
159 const char *number, yajl_len_t number_len)
160 {
161 char buffer[number_len + 1];
163 cj_t *db = (cj_t *)ctx;
164 cj_key_t *key = db->state[db->depth].key;
165 value_t vt;
166 int type;
167 int status;
169 if ((key == NULL) || !CJ_IS_KEY (key))
170 return (CJ_CB_CONTINUE);
172 memcpy (buffer, number, number_len);
173 buffer[sizeof (buffer) - 1] = 0;
175 type = cj_get_type (key);
176 status = parse_value (buffer, &vt, type);
177 if (status != 0)
178 {
179 NOTICE ("curl_json plugin: Unable to parse number: \"%s\"", buffer);
180 return (CJ_CB_CONTINUE);
181 }
183 cj_submit (db, key, &vt);
184 return (CJ_CB_CONTINUE);
185 } /* int cj_cb_number */
187 static int cj_cb_map_key (void *ctx, const unsigned char *val,
188 yajl_len_t len)
189 {
190 cj_t *db = (cj_t *)ctx;
191 c_avl_tree_t *tree;
193 tree = db->state[db->depth-1].tree;
195 if (tree != NULL)
196 {
197 cj_key_t *value;
198 char *name;
200 name = db->state[db->depth].name;
201 len = COUCH_MIN(len, sizeof (db->state[db->depth].name)-1);
202 sstrncpy (name, (char *)val, len+1);
204 if (c_avl_get (tree, name, (void *) &value) == 0)
205 db->state[db->depth].key = value;
206 else if (c_avl_get (tree, CJ_ANY, (void *) &value) == 0)
207 db->state[db->depth].key = value;
208 else
209 db->state[db->depth].key = NULL;
210 }
212 return (CJ_CB_CONTINUE);
213 }
215 static int cj_cb_string (void *ctx, const unsigned char *val,
216 yajl_len_t len)
217 {
218 cj_t *db = (cj_t *)ctx;
219 char str[len + 1];
221 /* Create a null-terminated version of the string. */
222 memcpy (str, val, len);
223 str[len] = 0;
225 /* No configuration for this string -> simply return. */
226 if (db->state[db->depth].key == NULL)
227 return (CJ_CB_CONTINUE);
229 if (!CJ_IS_KEY (db->state[db->depth].key))
230 {
231 NOTICE ("curl_json plugin: Found string \"%s\", but the configuration "
232 "expects a map here.", str);
233 return (CJ_CB_CONTINUE);
234 }
236 /* Handle the string as if it was a number. */
237 return (cj_cb_number (ctx, (const char *) val, len));
238 } /* int cj_cb_string */
240 static int cj_cb_start (void *ctx)
241 {
242 cj_t *db = (cj_t *)ctx;
243 if (++db->depth >= YAJL_MAX_DEPTH)
244 {
245 ERROR ("curl_json plugin: %s depth exceeds max, aborting.", db->url);
246 return (CJ_CB_ABORT);
247 }
248 return (CJ_CB_CONTINUE);
249 }
251 static int cj_cb_end (void *ctx)
252 {
253 cj_t *db = (cj_t *)ctx;
254 db->state[db->depth].tree = NULL;
255 --db->depth;
256 return (CJ_CB_CONTINUE);
257 }
259 static int cj_cb_start_map (void *ctx)
260 {
261 return cj_cb_start (ctx);
262 }
264 static int cj_cb_end_map (void *ctx)
265 {
266 return cj_cb_end (ctx);
267 }
269 static int cj_cb_start_array (void * ctx)
270 {
271 return cj_cb_start (ctx);
272 }
274 static int cj_cb_end_array (void * ctx)
275 {
276 return cj_cb_end (ctx);
277 }
279 static yajl_callbacks ycallbacks = {
280 NULL, /* null */
281 NULL, /* boolean */
282 NULL, /* integer */
283 NULL, /* double */
284 cj_cb_number,
285 cj_cb_string,
286 cj_cb_start_map,
287 cj_cb_map_key,
288 cj_cb_end_map,
289 cj_cb_start_array,
290 cj_cb_end_array
291 };
293 /* end yajl callbacks */
295 static void cj_key_free (cj_key_t *key) /* {{{ */
296 {
297 if (key == NULL)
298 return;
300 sfree (key->path);
301 sfree (key->type);
302 sfree (key->instance);
304 sfree (key);
305 } /* }}} void cj_key_free */
307 static void cj_tree_free (c_avl_tree_t *tree) /* {{{ */
308 {
309 char *name;
310 void *value;
312 while (c_avl_pick (tree, (void *) &name, (void *) &value) == 0)
313 {
314 cj_key_t *key = (cj_key_t *)value;
316 if (CJ_IS_KEY(key))
317 cj_key_free (key);
318 else
319 cj_tree_free ((c_avl_tree_t *)value);
321 sfree (name);
322 }
324 c_avl_destroy (tree);
325 } /* }}} void cj_tree_free */
327 static void cj_free (void *arg) /* {{{ */
328 {
329 cj_t *db;
331 DEBUG ("curl_json plugin: cj_free (arg = %p);", arg);
333 db = (cj_t *) arg;
335 if (db == NULL)
336 return;
338 if (db->curl != NULL)
339 curl_easy_cleanup (db->curl);
340 db->curl = NULL;
342 if (db->tree != NULL)
343 cj_tree_free (db->tree);
344 db->tree = NULL;
346 sfree (db->instance);
347 sfree (db->host);
349 sfree (db->url);
350 sfree (db->user);
351 sfree (db->pass);
352 sfree (db->credentials);
353 sfree (db->cacert);
355 sfree (db);
356 } /* }}} void cj_free */
358 /* Configuration handling functions {{{ */
360 static c_avl_tree_t *cj_avl_create(void)
361 {
362 return c_avl_create ((int (*) (const void *, const void *)) strcmp);
363 }
365 static int cj_config_add_key (cj_t *db, /* {{{ */
366 oconfig_item_t *ci)
367 {
368 cj_key_t *key;
369 int status;
370 int i;
372 if ((ci->values_num != 1)
373 || (ci->values[0].type != OCONFIG_TYPE_STRING))
374 {
375 WARNING ("curl_json plugin: The `Key' block "
376 "needs exactly one string argument.");
377 return (-1);
378 }
380 key = (cj_key_t *) malloc (sizeof (*key));
381 if (key == NULL)
382 {
383 ERROR ("curl_json plugin: malloc failed.");
384 return (-1);
385 }
386 memset (key, 0, sizeof (*key));
387 key->magic = CJ_KEY_MAGIC;
389 if (strcasecmp ("Key", ci->key) == 0)
390 {
391 status = cf_util_get_string (ci, &key->path);
392 if (status != 0)
393 {
394 sfree (key);
395 return (status);
396 }
397 }
398 else
399 {
400 ERROR ("curl_json plugin: cj_config: "
401 "Invalid key: %s", ci->key);
402 return (-1);
403 }
405 status = 0;
406 for (i = 0; i < ci->children_num; i++)
407 {
408 oconfig_item_t *child = ci->children + i;
410 if (strcasecmp ("Type", child->key) == 0)
411 status = cf_util_get_string (child, &key->type);
412 else if (strcasecmp ("Instance", child->key) == 0)
413 status = cf_util_get_string (child, &key->instance);
414 else
415 {
416 WARNING ("curl_json plugin: Option `%s' not allowed here.", child->key);
417 status = -1;
418 }
420 if (status != 0)
421 break;
422 } /* for (i = 0; i < ci->children_num; i++) */
424 while (status == 0)
425 {
426 if (key->type == NULL)
427 {
428 WARNING ("curl_json plugin: `Type' missing in `Key' block.");
429 status = -1;
430 }
432 break;
433 } /* while (status == 0) */
435 /* store path in a tree that will match the json map structure, example:
436 * "httpd/requests/count",
437 * "httpd/requests/current" ->
438 * { "httpd": { "requests": { "count": $key, "current": $key } } }
439 */
440 if (status == 0)
441 {
442 char *ptr;
443 char *name;
444 char ent[PATH_MAX];
445 c_avl_tree_t *tree;
447 if (db->tree == NULL)
448 db->tree = cj_avl_create();
450 tree = db->tree;
451 name = key->path;
452 ptr = key->path;
453 if (*ptr == '/')
454 ++ptr;
456 name = ptr;
457 while (*ptr)
458 {
459 if (*ptr == '/')
460 {
461 c_avl_tree_t *value;
462 int len;
464 len = ptr-name;
465 if (len == 0)
466 break;
467 sstrncpy (ent, name, len+1);
469 if (c_avl_get (tree, ent, (void *) &value) != 0)
470 {
471 value = cj_avl_create ();
472 c_avl_insert (tree, strdup (ent), value);
473 }
475 tree = value;
476 name = ptr+1;
477 }
478 ++ptr;
479 }
480 if (*name)
481 c_avl_insert (tree, strdup(name), key);
482 else
483 {
484 ERROR ("curl_json plugin: invalid key: %s", key->path);
485 status = -1;
486 }
487 }
489 return (status);
490 } /* }}} int cj_config_add_key */
492 static int cj_init_curl (cj_t *db) /* {{{ */
493 {
494 db->curl = curl_easy_init ();
495 if (db->curl == NULL)
496 {
497 ERROR ("curl_json plugin: curl_easy_init failed.");
498 return (-1);
499 }
501 curl_easy_setopt (db->curl, CURLOPT_NOSIGNAL, 1);
502 curl_easy_setopt (db->curl, CURLOPT_WRITEFUNCTION, cj_curl_callback);
503 curl_easy_setopt (db->curl, CURLOPT_WRITEDATA, db);
504 curl_easy_setopt (db->curl, CURLOPT_USERAGENT,
505 PACKAGE_NAME"/"PACKAGE_VERSION);
506 curl_easy_setopt (db->curl, CURLOPT_ERRORBUFFER, db->curl_errbuf);
507 curl_easy_setopt (db->curl, CURLOPT_URL, db->url);
509 if (db->user != NULL)
510 {
511 size_t credentials_size;
513 credentials_size = strlen (db->user) + 2;
514 if (db->pass != NULL)
515 credentials_size += strlen (db->pass);
517 db->credentials = (char *) malloc (credentials_size);
518 if (db->credentials == NULL)
519 {
520 ERROR ("curl_json plugin: malloc failed.");
521 return (-1);
522 }
524 ssnprintf (db->credentials, credentials_size, "%s:%s",
525 db->user, (db->pass == NULL) ? "" : db->pass);
526 curl_easy_setopt (db->curl, CURLOPT_USERPWD, db->credentials);
527 }
529 curl_easy_setopt (db->curl, CURLOPT_SSL_VERIFYPEER, (int) db->verify_peer);
530 curl_easy_setopt (db->curl, CURLOPT_SSL_VERIFYHOST,
531 (int) (db->verify_host ? 2 : 0));
532 if (db->cacert != NULL)
533 curl_easy_setopt (db->curl, CURLOPT_CAINFO, db->cacert);
535 return (0);
536 } /* }}} int cj_init_curl */
538 static int cj_config_add_url (oconfig_item_t *ci) /* {{{ */
539 {
540 cj_t *db;
541 int status = 0;
542 int i;
544 if ((ci->values_num != 1)
545 || (ci->values[0].type != OCONFIG_TYPE_STRING))
546 {
547 WARNING ("curl_json plugin: The `URL' block "
548 "needs exactly one string argument.");
549 return (-1);
550 }
552 db = (cj_t *) malloc (sizeof (*db));
553 if (db == NULL)
554 {
555 ERROR ("curl_json plugin: malloc failed.");
556 return (-1);
557 }
558 memset (db, 0, sizeof (*db));
560 if (strcasecmp ("URL", ci->key) == 0)
561 {
562 status = cf_util_get_string (ci, &db->url);
563 if (status != 0)
564 {
565 sfree (db);
566 return (status);
567 }
568 }
569 else
570 {
571 ERROR ("curl_json plugin: cj_config: "
572 "Invalid key: %s", ci->key);
573 return (-1);
574 }
576 /* Fill the `cj_t' structure.. */
577 for (i = 0; i < ci->children_num; i++)
578 {
579 oconfig_item_t *child = ci->children + i;
581 if (strcasecmp ("Instance", child->key) == 0)
582 status = cf_util_get_string (child, &db->instance);
583 else if (strcasecmp ("Host", child->key) == 0)
584 status = cf_util_get_string (child, &db->host);
585 else if (strcasecmp ("User", child->key) == 0)
586 status = cf_util_get_string (child, &db->user);
587 else if (strcasecmp ("Password", child->key) == 0)
588 status = cf_util_get_string (child, &db->pass);
589 else if (strcasecmp ("VerifyPeer", child->key) == 0)
590 status = cf_util_get_boolean (child, &db->verify_peer);
591 else if (strcasecmp ("VerifyHost", child->key) == 0)
592 status = cf_util_get_boolean (child, &db->verify_host);
593 else if (strcasecmp ("CACert", child->key) == 0)
594 status = cf_util_get_string (child, &db->cacert);
595 else if (strcasecmp ("Key", child->key) == 0)
596 status = cj_config_add_key (db, child);
597 else
598 {
599 WARNING ("curl_json plugin: Option `%s' not allowed here.", child->key);
600 status = -1;
601 }
603 if (status != 0)
604 break;
605 }
607 if (status == 0)
608 {
609 if (db->tree == NULL)
610 {
611 WARNING ("curl_json plugin: No (valid) `Key' block "
612 "within `URL' block `%s'.", db->url);
613 status = -1;
614 }
615 if (status == 0)
616 status = cj_init_curl (db);
617 }
619 /* If all went well, register this database for reading */
620 if (status == 0)
621 {
622 user_data_t ud;
623 char cb_name[DATA_MAX_NAME_LEN];
624 struct timespec cb_interval;
626 if (db->instance == NULL)
627 db->instance = strdup("default");
629 DEBUG ("curl_json plugin: Registering new read callback: %s",
630 db->instance);
632 memset (&ud, 0, sizeof (ud));
633 ud.data = (void *) db;
634 ud.free_func = cj_free;
636 ssnprintf (cb_name, sizeof (cb_name), "curl_json-%s-%s",
637 db->instance, db->url);
639 CDTIME_T_TO_TIMESPEC (plugin_interval, &cb_interval);
641 plugin_register_complex_read (/* group = */ NULL,
642 /* name = */ cb_name,
643 /* callback = */ cj_read,
644 /* interval = */ (plugin_interval != 0)
645 ? &cb_interval : NULL,
646 /* user_data = */ &ud);
647 }
648 else
649 {
650 cj_free (db);
651 return (-1);
652 }
654 return (0);
655 }
656 /* }}} int cj_config_add_database */
658 static int cj_config (oconfig_item_t *ci) /* {{{ */
659 {
660 int success;
661 int errors;
662 int status;
663 int i;
665 success = 0;
666 errors = 0;
668 for (i = 0; i < ci->children_num; i++)
669 {
670 oconfig_item_t *child = ci->children + i;
672 if (strcasecmp ("URL", child->key) == 0)
673 {
674 status = cj_config_add_url (child);
675 if (status == 0)
676 success++;
677 else
678 errors++;
679 }
680 else
681 {
682 WARNING ("curl_json plugin: Option `%s' not allowed here.", child->key);
683 errors++;
684 }
685 }
687 if ((success == 0) && (errors > 0))
688 {
689 ERROR ("curl_json plugin: All statements failed.");
690 return (-1);
691 }
693 return (0);
694 } /* }}} int cj_config */
696 /* }}} End of configuration handling functions */
698 static void cj_submit (cj_t *db, cj_key_t *key, value_t *value) /* {{{ */
699 {
700 value_list_t vl = VALUE_LIST_INIT (plugin_interval);
701 char *host;
703 vl.values = value;
704 vl.values_len = 1;
706 if ((db->host == NULL)
707 || (strcmp ("", db->host) == 0)
708 || (strcmp (CJ_DEFAULT_HOST, db->host) == 0))
709 host = hostname_g;
710 else
711 host = db->host;
713 if (key->instance == NULL)
714 ssnprintf (vl.type_instance, sizeof (vl.type_instance), "%s-%s",
715 db->state[db->depth-1].name, db->state[db->depth].name);
716 else
717 sstrncpy (vl.type_instance, key->instance, sizeof (vl.type_instance));
719 sstrncpy (vl.host, host, sizeof (vl.host));
720 sstrncpy (vl.plugin, "curl_json", sizeof (vl.plugin));
721 sstrncpy (vl.plugin_instance, db->instance, sizeof (vl.plugin_instance));
722 sstrncpy (vl.type, key->type, sizeof (vl.type));
724 plugin_dispatch_values (&vl);
725 } /* }}} int cj_submit */
727 static int cj_curl_perform (cj_t *db, CURL *curl) /* {{{ */
728 {
729 int status;
730 long rc;
731 char *url;
732 yajl_handle yprev = db->yajl;
734 db->yajl = yajl_alloc (&ycallbacks,
735 #if HAVE_YAJL_V2
736 /* alloc funcs = */ NULL,
737 #else
738 /* alloc funcs = */ NULL, NULL,
739 #endif
740 /* context = */ (void *)db);
741 if (db->yajl == NULL)
742 {
743 ERROR ("curl_json plugin: yajl_alloc failed.");
744 db->yajl = yprev;
745 return (-1);
746 }
748 url = NULL;
749 curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);
751 status = curl_easy_perform (curl);
752 if (status != 0)
753 {
754 ERROR ("curl_json plugin: curl_easy_perform failed with status %i: %s (%s)",
755 status, db->curl_errbuf, (url != NULL) ? url : "<null>");
756 yajl_free (db->yajl);
757 db->yajl = yprev;
758 return (-1);
759 }
761 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rc);
763 /* The response code is zero if a non-HTTP transport was used. */
764 if ((rc != 0) && (rc != 200))
765 {
766 ERROR ("curl_json plugin: curl_easy_perform failed with "
767 "response code %ld (%s)", rc, url);
768 yajl_free (db->yajl);
769 db->yajl = yprev;
770 return (-1);
771 }
773 #if HAVE_YAJL_V2
774 status = yajl_complete_parse(db->yajl);
775 #else
776 status = yajl_parse_complete(db->yajl);
777 #endif
778 if (status != yajl_status_ok)
779 {
780 unsigned char *errmsg;
782 errmsg = yajl_get_error (db->yajl, /* verbose = */ 0,
783 /* jsonText = */ NULL, /* jsonTextLen = */ 0);
784 ERROR ("curl_json plugin: yajl_parse_complete failed: %s",
785 (char *) errmsg);
786 yajl_free_error (db->yajl, errmsg);
787 yajl_free (db->yajl);
788 db->yajl = yprev;
789 return (-1);
790 }
792 yajl_free (db->yajl);
793 db->yajl = yprev;
794 return (0);
795 } /* }}} int cj_curl_perform */
797 static int cj_read (user_data_t *ud) /* {{{ */
798 {
799 cj_t *db;
801 if ((ud == NULL) || (ud->data == NULL))
802 {
803 ERROR ("curl_json plugin: cj_read: Invalid user data.");
804 return (-1);
805 }
807 db = (cj_t *) ud->data;
809 db->depth = 0;
810 memset (&db->state, 0, sizeof(db->state));
811 db->state[db->depth].tree = db->tree;
812 db->key = NULL;
814 return cj_curl_perform (db, db->curl);
815 } /* }}} int cj_read */
817 void module_register (plugin_loaddata_t *data)
818 {
819 PLUGIN_INIT_INTERVAL (data);
820 plugin_register_complex_config ("curl_json", cj_config);
821 } /* void module_register */
823 /* vim: set sw=2 sts=2 et fdm=marker : */