1 /**
2 * collectd - src/curl_json.c
3 * Copyright (C) 2009 Doug MacEachern
4 * Copyright (C) 2006-2013 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"
26 #include "common.h"
27 #include "plugin.h"
28 #include "utils_avltree.h"
29 #include "utils_complain.h"
30 #include "utils_curl_stats.h"
32 #include <sys/types.h>
33 #include <sys/un.h>
35 #include <curl/curl.h>
37 #include <yajl/yajl_parse.h>
38 #if HAVE_YAJL_YAJL_VERSION_H
39 #include <yajl/yajl_version.h>
40 #endif
42 #if defined(YAJL_MAJOR) && (YAJL_MAJOR > 1)
43 #define HAVE_YAJL_V2 1
44 #endif
46 #define CJ_DEFAULT_HOST "localhost"
47 #define CJ_KEY_MAGIC 0x43484b59UL /* CHKY */
48 #define CJ_IS_KEY(key) ((key)->magic == CJ_KEY_MAGIC)
49 #define CJ_ANY "*"
50 #define COUCH_MIN(x, y) ((x) < (y) ? (x) : (y))
52 struct cj_key_s;
53 typedef struct cj_key_s cj_key_t;
54 struct cj_key_s /* {{{ */
55 {
56 unsigned long magic;
57 char *path;
58 char *type;
59 char *instance;
60 };
61 /* }}} */
63 struct cj_s /* {{{ */
64 {
65 char *instance;
66 char *host;
68 char *sock;
70 char *url;
71 char *user;
72 char *pass;
73 char *credentials;
74 _Bool digest;
75 _Bool verify_peer;
76 _Bool verify_host;
77 char *cacert;
78 struct curl_slist *headers;
79 char *post_body;
80 cdtime_t interval;
81 int timeout;
82 curl_stats_t *stats;
84 CURL *curl;
85 char curl_errbuf[CURL_ERROR_SIZE];
87 yajl_handle yajl;
88 c_avl_tree_t *tree;
89 cj_key_t *key;
90 int depth;
91 struct {
92 union {
93 c_avl_tree_t *tree;
94 cj_key_t *key;
95 };
96 _Bool in_array;
97 int index;
98 char name[DATA_MAX_NAME_LEN];
99 } state[YAJL_MAX_DEPTH];
100 };
101 typedef struct cj_s cj_t; /* }}} */
103 #if HAVE_YAJL_V2
104 typedef size_t yajl_len_t;
105 #else
106 typedef unsigned int yajl_len_t;
107 #endif
109 static int cj_read(user_data_t *ud);
110 static void cj_submit(cj_t *db, cj_key_t *key, value_t *value);
112 static size_t cj_curl_callback(void *buf, /* {{{ */
113 size_t size, size_t nmemb, void *user_data) {
114 cj_t *db;
115 size_t len;
116 yajl_status status;
118 len = size * nmemb;
120 if (len == 0)
121 return (len);
123 db = user_data;
124 if (db == NULL)
125 return (0);
127 status = yajl_parse(db->yajl, (unsigned char *)buf, len);
128 if (status == yajl_status_ok)
129 return (len);
130 #if !HAVE_YAJL_V2
131 else if (status == yajl_status_insufficient_data)
132 return (len);
133 #endif
135 unsigned char *msg =
136 yajl_get_error(db->yajl, /* verbose = */ 1,
137 /* jsonText = */ (unsigned char *)buf, (unsigned int)len);
138 ERROR("curl_json plugin: yajl_parse failed: %s", msg);
139 yajl_free_error(db->yajl, msg);
140 return (0); /* abort write callback */
141 } /* }}} size_t cj_curl_callback */
143 static int cj_get_type(cj_key_t *key) {
144 const data_set_t *ds;
146 if ((key == NULL) || !CJ_IS_KEY(key))
147 return -EINVAL;
149 ds = plugin_get_ds(key->type);
150 if (ds == NULL) {
151 static char type[DATA_MAX_NAME_LEN] = "!!!invalid!!!";
153 assert(key->type != NULL);
154 if (strcmp(type, key->type) != 0) {
155 ERROR("curl_json plugin: Unable to look up DS type \"%s\".", key->type);
156 sstrncpy(type, key->type, sizeof(type));
157 }
159 return -1;
160 } else if (ds->ds_num > 1) {
161 static c_complain_t complaint = C_COMPLAIN_INIT_STATIC;
163 c_complain_once(
164 LOG_WARNING, &complaint,
165 "curl_json plugin: The type \"%s\" has more than one data source. "
166 "This is currently not supported. I will return the type of the "
167 "first data source, but this will likely lead to problems later on.",
168 key->type);
169 }
171 return ds->ds[0].type;
172 }
174 static int cj_cb_map_key(void *ctx, const unsigned char *val, yajl_len_t len);
176 static void cj_cb_inc_array_index(void *ctx, _Bool update_key) {
177 cj_t *db = (cj_t *)ctx;
179 if (!db->state[db->depth].in_array)
180 return;
182 db->state[db->depth].index++;
184 if (update_key) {
185 char name[DATA_MAX_NAME_LEN];
187 ssnprintf(name, sizeof(name), "%d", db->state[db->depth].index - 1);
189 cj_cb_map_key(ctx, (unsigned char *)name, (yajl_len_t)strlen(name));
190 }
191 }
193 /* yajl callbacks */
194 #define CJ_CB_ABORT 0
195 #define CJ_CB_CONTINUE 1
197 static int cj_cb_boolean(void *ctx, int boolVal) {
198 cj_cb_inc_array_index(ctx, /* update_key = */ 0);
199 return (CJ_CB_CONTINUE);
200 }
202 static int cj_cb_null(void *ctx) {
203 cj_cb_inc_array_index(ctx, /* update_key = */ 0);
204 return (CJ_CB_CONTINUE);
205 }
207 static int cj_cb_number(void *ctx, const char *number, yajl_len_t number_len) {
208 char buffer[number_len + 1];
210 cj_t *db = (cj_t *)ctx;
211 cj_key_t *key = db->state[db->depth].key;
212 value_t vt;
213 int type;
214 int status;
216 /* Create a null-terminated version of the string. */
217 memcpy(buffer, number, number_len);
218 buffer[sizeof(buffer) - 1] = 0;
220 if ((key == NULL) || !CJ_IS_KEY(key)) {
221 if (key != NULL &&
222 !db->state[db->depth].in_array /*can be inhomogeneous*/) {
223 NOTICE("curl_json plugin: Found \"%s\", but the configuration expects"
224 " a map.",
225 buffer);
226 return (CJ_CB_CONTINUE);
227 }
229 cj_cb_inc_array_index(ctx, /* update_key = */ 1);
230 key = db->state[db->depth].key;
231 if ((key == NULL) || !CJ_IS_KEY(key)) {
232 return (CJ_CB_CONTINUE);
233 }
234 } else {
235 cj_cb_inc_array_index(ctx, /* update_key = */ 1);
236 }
238 type = cj_get_type(key);
239 status = parse_value(buffer, &vt, type);
240 if (status != 0) {
241 NOTICE("curl_json plugin: Unable to parse number: \"%s\"", buffer);
242 return (CJ_CB_CONTINUE);
243 }
245 cj_submit(db, key, &vt);
246 return (CJ_CB_CONTINUE);
247 } /* int cj_cb_number */
249 /* Queries the key-tree of the parent context for "in_name" and, if found,
250 * updates the "key" field of the current context. Otherwise, "key" is set to
251 * NULL. */
252 static int cj_cb_map_key(void *ctx, unsigned char const *in_name,
253 yajl_len_t in_name_len) {
254 cj_t *db = (cj_t *)ctx;
255 c_avl_tree_t *tree;
257 tree = db->state[db->depth - 1].tree;
259 if (tree != NULL) {
260 cj_key_t *value = NULL;
261 char *name;
262 size_t name_len;
264 /* Create a null-terminated version of the name. */
265 name = db->state[db->depth].name;
266 name_len =
267 COUCH_MIN((size_t)in_name_len, sizeof(db->state[db->depth].name) - 1);
268 memcpy(name, in_name, name_len);
269 name[name_len] = 0;
271 if (c_avl_get(tree, name, (void *)&value) == 0) {
272 if (CJ_IS_KEY((cj_key_t *)value)) {
273 db->state[db->depth].key = value;
274 } else {
275 db->state[db->depth].tree = (c_avl_tree_t *)value;
276 }
277 } else if (c_avl_get(tree, CJ_ANY, (void *)&value) == 0)
278 if (CJ_IS_KEY((cj_key_t *)value)) {
279 db->state[db->depth].key = value;
280 } else {
281 db->state[db->depth].tree = (c_avl_tree_t *)value;
282 }
283 else
284 db->state[db->depth].key = NULL;
285 }
287 return (CJ_CB_CONTINUE);
288 }
290 static int cj_cb_string(void *ctx, const unsigned char *val, yajl_len_t len) {
291 /* Handle the string as if it was a number. */
292 return (cj_cb_number(ctx, (const char *)val, len));
293 } /* int cj_cb_string */
295 static int cj_cb_start(void *ctx) {
296 cj_t *db = (cj_t *)ctx;
297 if (++db->depth >= YAJL_MAX_DEPTH) {
298 ERROR("curl_json plugin: %s depth exceeds max, aborting.",
299 db->url ? db->url : db->sock);
300 return (CJ_CB_ABORT);
301 }
302 return (CJ_CB_CONTINUE);
303 }
305 static int cj_cb_end(void *ctx) {
306 cj_t *db = (cj_t *)ctx;
307 db->state[db->depth].tree = NULL;
308 --db->depth;
309 return (CJ_CB_CONTINUE);
310 }
312 static int cj_cb_start_map(void *ctx) {
313 cj_cb_inc_array_index(ctx, /* update_key = */ 1);
314 return cj_cb_start(ctx);
315 }
317 static int cj_cb_end_map(void *ctx) { return cj_cb_end(ctx); }
319 static int cj_cb_start_array(void *ctx) {
320 cj_t *db = (cj_t *)ctx;
321 cj_cb_inc_array_index(ctx, /* update_key = */ 1);
322 if (db->depth + 1 < YAJL_MAX_DEPTH) {
323 db->state[db->depth + 1].in_array = 1;
324 db->state[db->depth + 1].index = 0;
325 }
326 return cj_cb_start(ctx);
327 }
329 static int cj_cb_end_array(void *ctx) {
330 cj_t *db = (cj_t *)ctx;
331 db->state[db->depth].in_array = 0;
332 return cj_cb_end(ctx);
333 }
335 static yajl_callbacks ycallbacks = {
336 cj_cb_null, /* null */
337 cj_cb_boolean, /* boolean */
338 NULL, /* integer */
339 NULL, /* double */
340 cj_cb_number, cj_cb_string, cj_cb_start_map, cj_cb_map_key,
341 cj_cb_end_map, cj_cb_start_array, cj_cb_end_array};
343 /* end yajl callbacks */
345 static void cj_key_free(cj_key_t *key) /* {{{ */
346 {
347 if (key == NULL)
348 return;
350 sfree(key->path);
351 sfree(key->type);
352 sfree(key->instance);
354 sfree(key);
355 } /* }}} void cj_key_free */
357 static void cj_tree_free(c_avl_tree_t *tree) /* {{{ */
358 {
359 char *name;
360 void *value;
362 while (c_avl_pick(tree, (void *)&name, (void *)&value) == 0) {
363 cj_key_t *key = (cj_key_t *)value;
365 if (CJ_IS_KEY(key))
366 cj_key_free(key);
367 else
368 cj_tree_free((c_avl_tree_t *)value);
370 sfree(name);
371 }
373 c_avl_destroy(tree);
374 } /* }}} void cj_tree_free */
376 static void cj_free(void *arg) /* {{{ */
377 {
378 cj_t *db;
380 DEBUG("curl_json plugin: cj_free (arg = %p);", arg);
382 db = (cj_t *)arg;
384 if (db == NULL)
385 return;
387 if (db->curl != NULL)
388 curl_easy_cleanup(db->curl);
389 db->curl = NULL;
391 if (db->tree != NULL)
392 cj_tree_free(db->tree);
393 db->tree = NULL;
395 sfree(db->instance);
396 sfree(db->host);
398 sfree(db->sock);
400 sfree(db->url);
401 sfree(db->user);
402 sfree(db->pass);
403 sfree(db->credentials);
404 sfree(db->cacert);
405 sfree(db->post_body);
406 curl_slist_free_all(db->headers);
407 curl_stats_destroy(db->stats);
409 sfree(db);
410 } /* }}} void cj_free */
412 /* Configuration handling functions {{{ */
414 static c_avl_tree_t *cj_avl_create(void) {
415 return c_avl_create((int (*)(const void *, const void *))strcmp);
416 }
418 static int cj_config_append_string(const char *name,
419 struct curl_slist **dest, /* {{{ */
420 oconfig_item_t *ci) {
421 struct curl_slist *temp = NULL;
422 if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
423 WARNING("curl_json plugin: `%s' needs exactly one string argument.", name);
424 return (-1);
425 }
427 temp = curl_slist_append(*dest, ci->values[0].value.string);
428 if (temp == NULL)
429 return (-1);
431 *dest = temp;
433 return (0);
434 } /* }}} int cj_config_append_string */
436 static int cj_config_add_key(cj_t *db, /* {{{ */
437 oconfig_item_t *ci) {
438 cj_key_t *key;
439 int status;
441 if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
442 WARNING("curl_json plugin: The `Key' block "
443 "needs exactly one string argument.");
444 return (-1);
445 }
447 key = calloc(1, sizeof(*key));
448 if (key == NULL) {
449 ERROR("curl_json plugin: calloc failed.");
450 return (-1);
451 }
452 key->magic = CJ_KEY_MAGIC;
454 if (strcasecmp("Key", ci->key) == 0) {
455 status = cf_util_get_string(ci, &key->path);
456 if (status != 0) {
457 sfree(key);
458 return (status);
459 }
460 } else {
461 ERROR("curl_json plugin: cj_config: "
462 "Invalid key: %s",
463 ci->key);
464 cj_key_free(key);
465 return (-1);
466 }
468 status = 0;
469 for (int i = 0; i < ci->children_num; i++) {
470 oconfig_item_t *child = ci->children + i;
472 if (strcasecmp("Type", child->key) == 0)
473 status = cf_util_get_string(child, &key->type);
474 else if (strcasecmp("Instance", child->key) == 0)
475 status = cf_util_get_string(child, &key->instance);
476 else {
477 WARNING("curl_json plugin: Option `%s' not allowed here.", child->key);
478 status = -1;
479 }
481 if (status != 0)
482 break;
483 } /* for (i = 0; i < ci->children_num; i++) */
485 if (status != 0) {
486 cj_key_free(key);
487 return (-1);
488 }
490 if (key->type == NULL) {
491 WARNING("curl_json plugin: `Type' missing in `Key' block.");
492 cj_key_free(key);
493 return (-1);
494 }
496 /* store path in a tree that will match the json map structure, example:
497 * "httpd/requests/count",
498 * "httpd/requests/current" ->
499 * { "httpd": { "requests": { "count": $key, "current": $key } } }
500 */
501 char *ptr;
502 char *name;
503 c_avl_tree_t *tree;
505 if (db->tree == NULL)
506 db->tree = cj_avl_create();
508 tree = db->tree;
509 ptr = key->path;
510 if (*ptr == '/')
511 ++ptr;
513 name = ptr;
514 while ((ptr = strchr(name, '/')) != NULL) {
515 char ent[PATH_MAX];
516 c_avl_tree_t *value;
517 size_t len;
519 len = ptr - name;
520 if (len == 0)
521 break;
523 len = COUCH_MIN(len, sizeof(ent) - 1);
524 sstrncpy(ent, name, len + 1);
526 if (c_avl_get(tree, ent, (void *)&value) != 0) {
527 value = cj_avl_create();
528 c_avl_insert(tree, strdup(ent), value);
529 }
531 tree = value;
532 name = ptr + 1;
533 }
535 if (strlen(name) == 0) {
536 ERROR("curl_json plugin: invalid key: %s", key->path);
537 cj_key_free(key);
538 return (-1);
539 }
541 c_avl_insert(tree, strdup(name), key);
542 return (status);
543 } /* }}} int cj_config_add_key */
545 static int cj_init_curl(cj_t *db) /* {{{ */
546 {
547 db->curl = curl_easy_init();
548 if (db->curl == NULL) {
549 ERROR("curl_json plugin: curl_easy_init failed.");
550 return (-1);
551 }
553 curl_easy_setopt(db->curl, CURLOPT_NOSIGNAL, 1L);
554 curl_easy_setopt(db->curl, CURLOPT_WRITEFUNCTION, cj_curl_callback);
555 curl_easy_setopt(db->curl, CURLOPT_WRITEDATA, db);
556 curl_easy_setopt(db->curl, CURLOPT_USERAGENT, COLLECTD_USERAGENT);
557 curl_easy_setopt(db->curl, CURLOPT_ERRORBUFFER, db->curl_errbuf);
558 curl_easy_setopt(db->curl, CURLOPT_URL, db->url);
559 curl_easy_setopt(db->curl, CURLOPT_FOLLOWLOCATION, 1L);
560 curl_easy_setopt(db->curl, CURLOPT_MAXREDIRS, 50L);
562 if (db->user != NULL) {
563 #ifdef HAVE_CURLOPT_USERNAME
564 curl_easy_setopt(db->curl, CURLOPT_USERNAME, db->user);
565 curl_easy_setopt(db->curl, CURLOPT_PASSWORD,
566 (db->pass == NULL) ? "" : db->pass);
567 #else
568 size_t credentials_size;
570 credentials_size = strlen(db->user) + 2;
571 if (db->pass != NULL)
572 credentials_size += strlen(db->pass);
574 db->credentials = malloc(credentials_size);
575 if (db->credentials == NULL) {
576 ERROR("curl_json plugin: malloc failed.");
577 return (-1);
578 }
580 ssnprintf(db->credentials, credentials_size, "%s:%s", db->user,
581 (db->pass == NULL) ? "" : db->pass);
582 curl_easy_setopt(db->curl, CURLOPT_USERPWD, db->credentials);
583 #endif
585 if (db->digest)
586 curl_easy_setopt(db->curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
587 }
589 curl_easy_setopt(db->curl, CURLOPT_SSL_VERIFYPEER, (long)db->verify_peer);
590 curl_easy_setopt(db->curl, CURLOPT_SSL_VERIFYHOST, db->verify_host ? 2L : 0L);
591 if (db->cacert != NULL)
592 curl_easy_setopt(db->curl, CURLOPT_CAINFO, db->cacert);
593 if (db->headers != NULL)
594 curl_easy_setopt(db->curl, CURLOPT_HTTPHEADER, db->headers);
595 if (db->post_body != NULL)
596 curl_easy_setopt(db->curl, CURLOPT_POSTFIELDS, db->post_body);
598 #ifdef HAVE_CURLOPT_TIMEOUT_MS
599 if (db->timeout >= 0)
600 curl_easy_setopt(db->curl, CURLOPT_TIMEOUT_MS, (long)db->timeout);
601 else if (db->interval > 0)
602 curl_easy_setopt(db->curl, CURLOPT_TIMEOUT_MS,
603 (long)CDTIME_T_TO_MS(db->interval));
604 else
605 curl_easy_setopt(db->curl, CURLOPT_TIMEOUT_MS,
606 (long)CDTIME_T_TO_MS(plugin_get_interval()));
607 #endif
609 return (0);
610 } /* }}} int cj_init_curl */
612 static int cj_config_add_url(oconfig_item_t *ci) /* {{{ */
613 {
614 cj_t *db;
615 int status = 0;
617 if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
618 WARNING("curl_json plugin: The `URL' block "
619 "needs exactly one string argument.");
620 return (-1);
621 }
623 db = calloc(1, sizeof(*db));
624 if (db == NULL) {
625 ERROR("curl_json plugin: calloc failed.");
626 return (-1);
627 }
629 db->timeout = -1;
631 if (strcasecmp("URL", ci->key) == 0)
632 status = cf_util_get_string(ci, &db->url);
633 else if (strcasecmp("Sock", ci->key) == 0)
634 status = cf_util_get_string(ci, &db->sock);
635 else {
636 ERROR("curl_json plugin: cj_config: "
637 "Invalid key: %s",
638 ci->key);
639 cj_free(db);
640 return (-1);
641 }
642 if (status != 0) {
643 sfree(db);
644 return (status);
645 }
647 /* Fill the `cj_t' structure.. */
648 for (int i = 0; i < ci->children_num; i++) {
649 oconfig_item_t *child = ci->children + i;
651 if (strcasecmp("Instance", child->key) == 0)
652 status = cf_util_get_string(child, &db->instance);
653 else if (strcasecmp("Host", child->key) == 0)
654 status = cf_util_get_string(child, &db->host);
655 else if (db->url && strcasecmp("User", child->key) == 0)
656 status = cf_util_get_string(child, &db->user);
657 else if (db->url && strcasecmp("Password", child->key) == 0)
658 status = cf_util_get_string(child, &db->pass);
659 else if (strcasecmp("Digest", child->key) == 0)
660 status = cf_util_get_boolean(child, &db->digest);
661 else if (db->url && strcasecmp("VerifyPeer", child->key) == 0)
662 status = cf_util_get_boolean(child, &db->verify_peer);
663 else if (db->url && strcasecmp("VerifyHost", child->key) == 0)
664 status = cf_util_get_boolean(child, &db->verify_host);
665 else if (db->url && strcasecmp("CACert", child->key) == 0)
666 status = cf_util_get_string(child, &db->cacert);
667 else if (db->url && strcasecmp("Header", child->key) == 0)
668 status = cj_config_append_string("Header", &db->headers, child);
669 else if (db->url && strcasecmp("Post", child->key) == 0)
670 status = cf_util_get_string(child, &db->post_body);
671 else if (strcasecmp("Key", child->key) == 0)
672 status = cj_config_add_key(db, child);
673 else if (strcasecmp("Interval", child->key) == 0)
674 status = cf_util_get_cdtime(child, &db->interval);
675 else if (strcasecmp("Timeout", child->key) == 0)
676 status = cf_util_get_int(child, &db->timeout);
677 else if (strcasecmp("Statistics", child->key) == 0) {
678 db->stats = curl_stats_from_config(child);
679 if (db->stats == NULL)
680 status = -1;
681 } else {
682 WARNING("curl_json plugin: Option `%s' not allowed here.", child->key);
683 status = -1;
684 }
686 if (status != 0)
687 break;
688 }
690 if (status == 0) {
691 if (db->tree == NULL) {
692 WARNING("curl_json plugin: No (valid) `Key' block within `%s' \"`%s'\".",
693 db->url ? "URL" : "Sock", db->url ? db->url : db->sock);
694 status = -1;
695 }
696 if (status == 0 && db->url)
697 status = cj_init_curl(db);
698 }
700 /* If all went well, register this database for reading */
701 if (status == 0) {
702 char *cb_name;
704 if (db->instance == NULL)
705 db->instance = strdup("default");
707 DEBUG("curl_json plugin: Registering new read callback: %s", db->instance);
709 cb_name = ssnprintf_alloc("curl_json-%s-%s", db->instance,
710 db->url ? db->url : db->sock);
712 user_data_t ud = {.data = db, .free_func = cj_free};
714 plugin_register_complex_read(/* group = */ NULL, cb_name, cj_read,
715 /* interval = */ db->interval, &ud);
716 sfree(cb_name);
717 } else {
718 cj_free(db);
719 return (-1);
720 }
722 return (0);
723 }
724 /* }}} int cj_config_add_database */
726 static int cj_config(oconfig_item_t *ci) /* {{{ */
727 {
728 int success;
729 int errors;
730 int status;
732 success = 0;
733 errors = 0;
735 for (int i = 0; i < ci->children_num; i++) {
736 oconfig_item_t *child = ci->children + i;
738 if (strcasecmp("Sock", child->key) == 0 ||
739 strcasecmp("URL", child->key) == 0) {
740 status = cj_config_add_url(child);
741 if (status == 0)
742 success++;
743 else
744 errors++;
745 } else {
746 WARNING("curl_json plugin: Option `%s' not allowed here.", child->key);
747 errors++;
748 }
749 }
751 if ((success == 0) && (errors > 0)) {
752 ERROR("curl_json plugin: All statements failed.");
753 return (-1);
754 }
756 return (0);
757 } /* }}} int cj_config */
759 /* }}} End of configuration handling functions */
761 static const char *cj_host(cj_t *db) /* {{{ */
762 {
763 if ((db->host == NULL) || (strcmp("", db->host) == 0) ||
764 (strcmp(CJ_DEFAULT_HOST, db->host) == 0))
765 return hostname_g;
766 return db->host;
767 } /* }}} cj_host */
769 static void cj_submit(cj_t *db, cj_key_t *key, value_t *value) /* {{{ */
770 {
771 value_list_t vl = VALUE_LIST_INIT;
773 vl.values = value;
774 vl.values_len = 1;
776 if (key->instance == NULL) {
777 int len = 0;
778 for (int i = 0; i < db->depth; i++)
779 len += ssnprintf(vl.type_instance + len, sizeof(vl.type_instance) - len,
780 i ? "-%s" : "%s", db->state[i + 1].name);
781 } else
782 sstrncpy(vl.type_instance, key->instance, sizeof(vl.type_instance));
784 sstrncpy(vl.host, cj_host(db), sizeof(vl.host));
785 sstrncpy(vl.plugin, "curl_json", sizeof(vl.plugin));
786 sstrncpy(vl.plugin_instance, db->instance, sizeof(vl.plugin_instance));
787 sstrncpy(vl.type, key->type, sizeof(vl.type));
789 if (db->interval > 0)
790 vl.interval = db->interval;
792 plugin_dispatch_values(&vl);
793 } /* }}} int cj_submit */
795 static int cj_sock_perform(cj_t *db) /* {{{ */
796 {
797 char errbuf[1024];
798 struct sockaddr_un sa_unix = {0};
799 sa_unix.sun_family = AF_UNIX;
800 sstrncpy(sa_unix.sun_path, db->sock, sizeof(sa_unix.sun_path));
802 int fd = socket(AF_UNIX, SOCK_STREAM, 0);
803 if (fd < 0)
804 return (-1);
805 if (connect(fd, (struct sockaddr *)&sa_unix, sizeof(sa_unix)) < 0) {
806 ERROR("curl_json plugin: connect(%s) failed: %s",
807 (db->sock != NULL) ? db->sock : "<null>",
808 sstrerror(errno, errbuf, sizeof(errbuf)));
809 close(fd);
810 return (-1);
811 }
813 ssize_t red;
814 do {
815 unsigned char buffer[4096];
816 red = read(fd, buffer, sizeof(buffer));
817 if (red < 0) {
818 ERROR("curl_json plugin: read(%s) failed: %s",
819 (db->sock != NULL) ? db->sock : "<null>",
820 sstrerror(errno, errbuf, sizeof(errbuf)));
821 close(fd);
822 return (-1);
823 }
824 if (!cj_curl_callback(buffer, red, 1, db))
825 break;
826 } while (red > 0);
827 close(fd);
828 return (0);
829 } /* }}} int cj_sock_perform */
831 static int cj_curl_perform(cj_t *db) /* {{{ */
832 {
833 int status;
834 long rc;
835 char *url;
836 url = db->url;
838 status = curl_easy_perform(db->curl);
839 if (status != CURLE_OK) {
840 ERROR("curl_json plugin: curl_easy_perform failed with status %i: %s (%s)",
841 status, db->curl_errbuf, url);
842 return (-1);
843 }
844 if (db->stats != NULL)
845 curl_stats_dispatch(db->stats, db->curl, cj_host(db), "curl_json",
846 db->instance);
848 curl_easy_getinfo(db->curl, CURLINFO_EFFECTIVE_URL, &url);
849 curl_easy_getinfo(db->curl, CURLINFO_RESPONSE_CODE, &rc);
851 /* The response code is zero if a non-HTTP transport was used. */
852 if ((rc != 0) && (rc != 200)) {
853 ERROR("curl_json plugin: curl_easy_perform failed with "
854 "response code %ld (%s)",
855 rc, url);
856 return (-1);
857 }
858 return (0);
859 } /* }}} int cj_curl_perform */
861 static int cj_perform(cj_t *db) /* {{{ */
862 {
863 int status;
864 yajl_handle yprev = db->yajl;
866 db->yajl = yajl_alloc(&ycallbacks,
867 #if HAVE_YAJL_V2
868 /* alloc funcs = */ NULL,
869 #else
870 /* alloc funcs = */ NULL, NULL,
871 #endif
872 /* context = */ (void *)db);
873 if (db->yajl == NULL) {
874 ERROR("curl_json plugin: yajl_alloc failed.");
875 db->yajl = yprev;
876 return (-1);
877 }
879 if (db->url)
880 status = cj_curl_perform(db);
881 else
882 status = cj_sock_perform(db);
883 if (status < 0) {
884 yajl_free(db->yajl);
885 db->yajl = yprev;
886 return (-1);
887 }
889 #if HAVE_YAJL_V2
890 status = yajl_complete_parse(db->yajl);
891 #else
892 status = yajl_parse_complete(db->yajl);
893 #endif
894 if (status != yajl_status_ok) {
895 unsigned char *errmsg;
897 errmsg = yajl_get_error(db->yajl, /* verbose = */ 0,
898 /* jsonText = */ NULL, /* jsonTextLen = */ 0);
899 ERROR("curl_json plugin: yajl_parse_complete failed: %s", (char *)errmsg);
900 yajl_free_error(db->yajl, errmsg);
901 yajl_free(db->yajl);
902 db->yajl = yprev;
903 return (-1);
904 }
906 yajl_free(db->yajl);
907 db->yajl = yprev;
908 return (0);
909 } /* }}} int cj_perform */
911 static int cj_read(user_data_t *ud) /* {{{ */
912 {
913 cj_t *db;
915 if ((ud == NULL) || (ud->data == NULL)) {
916 ERROR("curl_json plugin: cj_read: Invalid user data.");
917 return (-1);
918 }
920 db = (cj_t *)ud->data;
922 db->depth = 0;
923 memset(&db->state, 0, sizeof(db->state));
924 db->state[db->depth].tree = db->tree;
925 db->key = NULL;
927 return cj_perform(db);
928 } /* }}} int cj_read */
930 static int cj_init(void) /* {{{ */
931 {
932 /* Call this while collectd is still single-threaded to avoid
933 * initialization issues in libgcrypt. */
934 curl_global_init(CURL_GLOBAL_SSL);
935 return (0);
936 } /* }}} int cj_init */
938 void module_register(void) {
939 plugin_register_complex_config("curl_json", cj_config);
940 plugin_register_init("curl_json", cj_init);
941 } /* void module_register */
943 /* vim: set sw=2 sts=2 et fdm=marker : */