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"
29 #include "utils_complain.h"
31 #include <curl/curl.h>
32 #include <yajl/yajl_parse.h>
33 #if HAVE_YAJL_YAJL_VERSION_H
34 # include <yajl/yajl_version.h>
35 #endif
37 #if defined(YAJL_MAJOR) && (YAJL_MAJOR > 1)
38 # define HAVE_YAJL_V2 1
39 #endif
41 #define CJ_DEFAULT_HOST "localhost"
42 #define CJ_KEY_MAGIC 0x43484b59UL /* CHKY */
43 #define CJ_IS_KEY(key) ((key)->magic == CJ_KEY_MAGIC)
44 #define CJ_ANY "*"
45 #define COUCH_MIN(x,y) ((x) < (y) ? (x) : (y))
47 struct cj_key_s;
48 typedef struct cj_key_s cj_key_t;
49 struct cj_key_s /* {{{ */
50 {
51 char *path;
52 char *type;
53 char *instance;
54 unsigned long magic;
55 };
56 /* }}} */
58 struct cj_s /* {{{ */
59 {
60 char *instance;
61 char *host;
63 char *url;
64 char *user;
65 char *pass;
66 char *credentials;
67 _Bool verify_peer;
68 _Bool verify_host;
69 char *cacert;
71 CURL *curl;
72 char curl_errbuf[CURL_ERROR_SIZE];
74 yajl_handle yajl;
75 c_avl_tree_t *tree;
76 cj_key_t *key;
77 int depth;
78 struct {
79 union {
80 c_avl_tree_t *tree;
81 cj_key_t *key;
82 };
83 char name[DATA_MAX_NAME_LEN];
84 } state[YAJL_MAX_DEPTH];
85 };
86 typedef struct cj_s cj_t; /* }}} */
88 #if HAVE_YAJL_V2
89 typedef size_t yajl_len_t;
90 #else
91 typedef unsigned int yajl_len_t;
92 #endif
94 static int cj_read (user_data_t *ud);
95 static int cj_curl_perform (cj_t *db, CURL *curl);
96 static void cj_submit (cj_t *db, cj_key_t *key, value_t *value);
98 static size_t cj_curl_callback (void *buf, /* {{{ */
99 size_t size, size_t nmemb, void *user_data)
100 {
101 cj_t *db;
102 size_t len;
103 yajl_status status;
105 len = size * nmemb;
107 if (len <= 0)
108 return (len);
110 db = user_data;
111 if (db == NULL)
112 return (0);
114 status = yajl_parse(db->yajl, (unsigned char *) buf, len);
115 if (status == yajl_status_ok)
116 {
117 #if HAVE_YAJL_V2
118 status = yajl_complete_parse(db->yajl);
119 #else
120 status = yajl_parse_complete(db->yajl);
121 #endif
122 return (len);
123 }
124 #if !HAVE_YAJL_V2
125 else if (status == yajl_status_insufficient_data)
126 return (len);
127 #endif
129 if (status != yajl_status_ok)
130 {
131 unsigned char *msg =
132 yajl_get_error(db->yajl, /* verbose = */ 1,
133 /* jsonText = */ (unsigned char *) buf, (unsigned int) len);
134 ERROR ("curl_json plugin: yajl_parse failed: %s", msg);
135 yajl_free_error(db->yajl, msg);
136 return (0); /* abort write callback */
137 }
139 return (len);
140 } /* }}} size_t cj_curl_callback */
142 static int cj_get_type (cj_key_t *key)
143 {
144 const data_set_t *ds;
146 ds = plugin_get_ds (key->type);
147 if (ds == NULL)
148 {
149 static char type[DATA_MAX_NAME_LEN] = "!!!invalid!!!";
151 assert (key->type != NULL);
152 if (strcmp (type, key->type) != 0)
153 {
154 ERROR ("curl_json plugin: Unable to look up DS type \"%s\".",
155 key->type);
156 sstrncpy (type, key->type, sizeof (type));
157 }
159 return -1;
160 }
161 else if (ds->ds_num > 1)
162 {
163 static c_complain_t complaint = C_COMPLAIN_INIT_STATIC;
165 c_complain_once (LOG_WARNING, &complaint,
166 "curl_json plugin: The type \"%s\" has more than one data source. "
167 "This is currently not supported. I will return the type of the "
168 "first data source, but this will likely lead to problems later on.",
169 key->type);
170 }
172 return ds->ds[0].type;
173 }
175 /* yajl callbacks */
176 #define CJ_CB_ABORT 0
177 #define CJ_CB_CONTINUE 1
179 /* "number" may not be null terminated, so copy it into a buffer before
180 * parsing. */
181 static int cj_cb_number (void *ctx,
182 const char *number, yajl_len_t number_len)
183 {
184 char buffer[number_len + 1];
186 cj_t *db = (cj_t *)ctx;
187 cj_key_t *key = db->state[db->depth].key;
188 value_t vt;
189 int type;
190 int status;
192 if ((key == NULL) || !CJ_IS_KEY (key))
193 return (CJ_CB_CONTINUE);
195 memcpy (buffer, number, number_len);
196 buffer[sizeof (buffer) - 1] = 0;
198 type = cj_get_type (key);
199 status = parse_value (buffer, &vt, type);
200 if (status != 0)
201 {
202 NOTICE ("curl_json plugin: Unable to parse number: \"%s\"", buffer);
203 return (CJ_CB_CONTINUE);
204 }
206 cj_submit (db, key, &vt);
207 return (CJ_CB_CONTINUE);
208 } /* int cj_cb_number */
210 static int cj_cb_map_key (void *ctx, const unsigned char *val,
211 yajl_len_t len)
212 {
213 cj_t *db = (cj_t *)ctx;
214 c_avl_tree_t *tree;
216 tree = db->state[db->depth-1].tree;
218 if (tree != NULL)
219 {
220 cj_key_t *value;
221 char *name;
223 name = db->state[db->depth].name;
224 len = COUCH_MIN(len, sizeof (db->state[db->depth].name)-1);
225 sstrncpy (name, (char *)val, len+1);
227 if (c_avl_get (tree, name, (void *) &value) == 0)
228 db->state[db->depth].key = value;
229 else if (c_avl_get (tree, CJ_ANY, (void *) &value) == 0)
230 db->state[db->depth].key = value;
231 else
232 db->state[db->depth].key = NULL;
233 }
235 return (CJ_CB_CONTINUE);
236 }
238 static int cj_cb_string (void *ctx, const unsigned char *val,
239 yajl_len_t len)
240 {
241 cj_t *db = (cj_t *)ctx;
242 char str[len + 1];
244 /* Create a null-terminated version of the string. */
245 memcpy (str, val, len);
246 str[len] = 0;
248 /* No configuration for this string -> simply return. */
249 if (db->state[db->depth].key == NULL)
250 return (CJ_CB_CONTINUE);
252 if (!CJ_IS_KEY (db->state[db->depth].key))
253 {
254 NOTICE ("curl_json plugin: Found string \"%s\", but the configuration "
255 "expects a map here.", str);
256 return (CJ_CB_CONTINUE);
257 }
259 /* Handle the string as if it was a number. */
260 return (cj_cb_number (ctx, (const char *) val, len));
261 } /* int cj_cb_string */
263 static int cj_cb_start (void *ctx)
264 {
265 cj_t *db = (cj_t *)ctx;
266 if (++db->depth >= YAJL_MAX_DEPTH)
267 {
268 ERROR ("curl_json plugin: %s depth exceeds max, aborting.", db->url);
269 return (CJ_CB_ABORT);
270 }
271 return (CJ_CB_CONTINUE);
272 }
274 static int cj_cb_end (void *ctx)
275 {
276 cj_t *db = (cj_t *)ctx;
277 db->state[db->depth].tree = NULL;
278 --db->depth;
279 return (CJ_CB_CONTINUE);
280 }
282 static int cj_cb_start_map (void *ctx)
283 {
284 return cj_cb_start (ctx);
285 }
287 static int cj_cb_end_map (void *ctx)
288 {
289 return cj_cb_end (ctx);
290 }
292 static int cj_cb_start_array (void * ctx)
293 {
294 return cj_cb_start (ctx);
295 }
297 static int cj_cb_end_array (void * ctx)
298 {
299 return cj_cb_end (ctx);
300 }
302 static yajl_callbacks ycallbacks = {
303 NULL, /* null */
304 NULL, /* boolean */
305 NULL, /* integer */
306 NULL, /* double */
307 cj_cb_number,
308 cj_cb_string,
309 cj_cb_start_map,
310 cj_cb_map_key,
311 cj_cb_end_map,
312 cj_cb_start_array,
313 cj_cb_end_array
314 };
316 /* end yajl callbacks */
318 static void cj_key_free (cj_key_t *key) /* {{{ */
319 {
320 if (key == NULL)
321 return;
323 sfree (key->path);
324 sfree (key->type);
325 sfree (key->instance);
327 sfree (key);
328 } /* }}} void cj_key_free */
330 static void cj_tree_free (c_avl_tree_t *tree) /* {{{ */
331 {
332 char *name;
333 void *value;
335 while (c_avl_pick (tree, (void *) &name, (void *) &value) == 0)
336 {
337 cj_key_t *key = (cj_key_t *)value;
339 if (CJ_IS_KEY(key))
340 cj_key_free (key);
341 else
342 cj_tree_free ((c_avl_tree_t *)value);
344 sfree (name);
345 }
347 c_avl_destroy (tree);
348 } /* }}} void cj_tree_free */
350 static void cj_free (void *arg) /* {{{ */
351 {
352 cj_t *db;
354 DEBUG ("curl_json plugin: cj_free (arg = %p);", arg);
356 db = (cj_t *) arg;
358 if (db == NULL)
359 return;
361 if (db->curl != NULL)
362 curl_easy_cleanup (db->curl);
363 db->curl = NULL;
365 if (db->tree != NULL)
366 cj_tree_free (db->tree);
367 db->tree = NULL;
369 sfree (db->instance);
370 sfree (db->host);
372 sfree (db->url);
373 sfree (db->user);
374 sfree (db->pass);
375 sfree (db->credentials);
376 sfree (db->cacert);
378 sfree (db);
379 } /* }}} void cj_free */
381 /* Configuration handling functions {{{ */
383 static c_avl_tree_t *cj_avl_create(void)
384 {
385 return c_avl_create ((int (*) (const void *, const void *)) strcmp);
386 }
388 static int cj_config_add_key (cj_t *db, /* {{{ */
389 oconfig_item_t *ci)
390 {
391 cj_key_t *key;
392 int status;
393 int i;
395 if ((ci->values_num != 1)
396 || (ci->values[0].type != OCONFIG_TYPE_STRING))
397 {
398 WARNING ("curl_json plugin: The `Key' block "
399 "needs exactly one string argument.");
400 return (-1);
401 }
403 key = (cj_key_t *) malloc (sizeof (*key));
404 if (key == NULL)
405 {
406 ERROR ("curl_json plugin: malloc failed.");
407 return (-1);
408 }
409 memset (key, 0, sizeof (*key));
410 key->magic = CJ_KEY_MAGIC;
412 if (strcasecmp ("Key", ci->key) == 0)
413 {
414 status = cf_util_get_string (ci, &key->path);
415 if (status != 0)
416 {
417 sfree (key);
418 return (status);
419 }
420 }
421 else
422 {
423 ERROR ("curl_json plugin: cj_config: "
424 "Invalid key: %s", ci->key);
425 return (-1);
426 }
428 status = 0;
429 for (i = 0; i < ci->children_num; i++)
430 {
431 oconfig_item_t *child = ci->children + i;
433 if (strcasecmp ("Type", child->key) == 0)
434 status = cf_util_get_string (child, &key->type);
435 else if (strcasecmp ("Instance", child->key) == 0)
436 status = cf_util_get_string (child, &key->instance);
437 else
438 {
439 WARNING ("curl_json plugin: Option `%s' not allowed here.", child->key);
440 status = -1;
441 }
443 if (status != 0)
444 break;
445 } /* for (i = 0; i < ci->children_num; i++) */
447 while (status == 0)
448 {
449 if (key->type == NULL)
450 {
451 WARNING ("curl_json plugin: `Type' missing in `Key' block.");
452 status = -1;
453 }
455 break;
456 } /* while (status == 0) */
458 /* store path in a tree that will match the json map structure, example:
459 * "httpd/requests/count",
460 * "httpd/requests/current" ->
461 * { "httpd": { "requests": { "count": $key, "current": $key } } }
462 */
463 if (status == 0)
464 {
465 char *ptr;
466 char *name;
467 char ent[PATH_MAX];
468 c_avl_tree_t *tree;
470 if (db->tree == NULL)
471 db->tree = cj_avl_create();
473 tree = db->tree;
474 name = key->path;
475 ptr = key->path;
476 if (*ptr == '/')
477 ++ptr;
479 name = ptr;
480 while (*ptr)
481 {
482 if (*ptr == '/')
483 {
484 c_avl_tree_t *value;
485 int len;
487 len = ptr-name;
488 if (len == 0)
489 break;
490 sstrncpy (ent, name, len+1);
492 if (c_avl_get (tree, ent, (void *) &value) != 0)
493 {
494 value = cj_avl_create ();
495 c_avl_insert (tree, strdup (ent), value);
496 }
498 tree = value;
499 name = ptr+1;
500 }
501 ++ptr;
502 }
503 if (*name)
504 c_avl_insert (tree, strdup(name), key);
505 else
506 {
507 ERROR ("curl_json plugin: invalid key: %s", key->path);
508 status = -1;
509 }
510 }
512 return (status);
513 } /* }}} int cj_config_add_key */
515 static int cj_init_curl (cj_t *db) /* {{{ */
516 {
517 db->curl = curl_easy_init ();
518 if (db->curl == NULL)
519 {
520 ERROR ("curl_json plugin: curl_easy_init failed.");
521 return (-1);
522 }
524 curl_easy_setopt (db->curl, CURLOPT_NOSIGNAL, 1);
525 curl_easy_setopt (db->curl, CURLOPT_WRITEFUNCTION, cj_curl_callback);
526 curl_easy_setopt (db->curl, CURLOPT_WRITEDATA, db);
527 curl_easy_setopt (db->curl, CURLOPT_USERAGENT,
528 PACKAGE_NAME"/"PACKAGE_VERSION);
529 curl_easy_setopt (db->curl, CURLOPT_ERRORBUFFER, db->curl_errbuf);
530 curl_easy_setopt (db->curl, CURLOPT_URL, db->url);
532 if (db->user != NULL)
533 {
534 size_t credentials_size;
536 credentials_size = strlen (db->user) + 2;
537 if (db->pass != NULL)
538 credentials_size += strlen (db->pass);
540 db->credentials = (char *) malloc (credentials_size);
541 if (db->credentials == NULL)
542 {
543 ERROR ("curl_json plugin: malloc failed.");
544 return (-1);
545 }
547 ssnprintf (db->credentials, credentials_size, "%s:%s",
548 db->user, (db->pass == NULL) ? "" : db->pass);
549 curl_easy_setopt (db->curl, CURLOPT_USERPWD, db->credentials);
550 }
552 curl_easy_setopt (db->curl, CURLOPT_SSL_VERIFYPEER, (int) db->verify_peer);
553 curl_easy_setopt (db->curl, CURLOPT_SSL_VERIFYHOST,
554 (int) (db->verify_host ? 2 : 0));
555 if (db->cacert != NULL)
556 curl_easy_setopt (db->curl, CURLOPT_CAINFO, db->cacert);
558 return (0);
559 } /* }}} int cj_init_curl */
561 static int cj_config_add_url (oconfig_item_t *ci) /* {{{ */
562 {
563 cj_t *db;
564 int status = 0;
565 int i;
567 if ((ci->values_num != 1)
568 || (ci->values[0].type != OCONFIG_TYPE_STRING))
569 {
570 WARNING ("curl_json plugin: The `URL' block "
571 "needs exactly one string argument.");
572 return (-1);
573 }
575 db = (cj_t *) malloc (sizeof (*db));
576 if (db == NULL)
577 {
578 ERROR ("curl_json plugin: malloc failed.");
579 return (-1);
580 }
581 memset (db, 0, sizeof (*db));
583 if (strcasecmp ("URL", ci->key) == 0)
584 {
585 status = cf_util_get_string (ci, &db->url);
586 if (status != 0)
587 {
588 sfree (db);
589 return (status);
590 }
591 }
592 else
593 {
594 ERROR ("curl_json plugin: cj_config: "
595 "Invalid key: %s", ci->key);
596 return (-1);
597 }
599 /* Fill the `cj_t' structure.. */
600 for (i = 0; i < ci->children_num; i++)
601 {
602 oconfig_item_t *child = ci->children + i;
604 if (strcasecmp ("Instance", child->key) == 0)
605 status = cf_util_get_string (child, &db->instance);
606 else if (strcasecmp ("Host", child->key) == 0)
607 status = cf_util_get_string (child, &db->host);
608 else if (strcasecmp ("User", child->key) == 0)
609 status = cf_util_get_string (child, &db->user);
610 else if (strcasecmp ("Password", child->key) == 0)
611 status = cf_util_get_string (child, &db->pass);
612 else if (strcasecmp ("VerifyPeer", child->key) == 0)
613 status = cf_util_get_boolean (child, &db->verify_peer);
614 else if (strcasecmp ("VerifyHost", child->key) == 0)
615 status = cf_util_get_boolean (child, &db->verify_host);
616 else if (strcasecmp ("CACert", child->key) == 0)
617 status = cf_util_get_string (child, &db->cacert);
618 else if (strcasecmp ("Key", child->key) == 0)
619 status = cj_config_add_key (db, child);
620 else
621 {
622 WARNING ("curl_json plugin: Option `%s' not allowed here.", child->key);
623 status = -1;
624 }
626 if (status != 0)
627 break;
628 }
630 if (status == 0)
631 {
632 if (db->tree == NULL)
633 {
634 WARNING ("curl_json plugin: No (valid) `Key' block "
635 "within `URL' block `%s'.", db->url);
636 status = -1;
637 }
638 if (status == 0)
639 status = cj_init_curl (db);
640 }
642 /* If all went well, register this database for reading */
643 if (status == 0)
644 {
645 user_data_t ud;
646 char cb_name[DATA_MAX_NAME_LEN];
648 if (db->instance == NULL)
649 db->instance = strdup("default");
651 DEBUG ("curl_json plugin: Registering new read callback: %s",
652 db->instance);
654 memset (&ud, 0, sizeof (ud));
655 ud.data = (void *) db;
656 ud.free_func = cj_free;
658 ssnprintf (cb_name, sizeof (cb_name), "curl_json-%s-%s",
659 db->instance, db->url);
661 plugin_register_complex_read (/* group = */ NULL, cb_name, cj_read,
662 /* interval = */ NULL, &ud);
663 }
664 else
665 {
666 cj_free (db);
667 return (-1);
668 }
670 return (0);
671 }
672 /* }}} int cj_config_add_database */
674 static int cj_config (oconfig_item_t *ci) /* {{{ */
675 {
676 int success;
677 int errors;
678 int status;
679 int i;
681 success = 0;
682 errors = 0;
684 for (i = 0; i < ci->children_num; i++)
685 {
686 oconfig_item_t *child = ci->children + i;
688 if (strcasecmp ("URL", child->key) == 0)
689 {
690 status = cj_config_add_url (child);
691 if (status == 0)
692 success++;
693 else
694 errors++;
695 }
696 else
697 {
698 WARNING ("curl_json plugin: Option `%s' not allowed here.", child->key);
699 errors++;
700 }
701 }
703 if ((success == 0) && (errors > 0))
704 {
705 ERROR ("curl_json plugin: All statements failed.");
706 return (-1);
707 }
709 return (0);
710 } /* }}} int cj_config */
712 /* }}} End of configuration handling functions */
714 static void cj_submit (cj_t *db, cj_key_t *key, value_t *value) /* {{{ */
715 {
716 value_list_t vl = VALUE_LIST_INIT;
717 char *host;
719 vl.values = value;
720 vl.values_len = 1;
722 if ((db->host == NULL)
723 || (strcmp ("", db->host) == 0)
724 || (strcmp (CJ_DEFAULT_HOST, db->host) == 0))
725 host = hostname_g;
726 else
727 host = db->host;
729 if (key->instance == NULL)
730 {
731 if ((db->depth == 0) || (strcmp ("", db->state[db->depth-1].name) == 0))
732 sstrncpy (vl.type_instance, db->state[db->depth].name, sizeof (vl.type_instance));
733 else
734 ssnprintf (vl.type_instance, sizeof (vl.type_instance), "%s-%s",
735 db->state[db->depth-1].name, db->state[db->depth].name);
736 }
737 else
738 sstrncpy (vl.type_instance, key->instance, sizeof (vl.type_instance));
740 sstrncpy (vl.host, host, sizeof (vl.host));
741 sstrncpy (vl.plugin, "curl_json", sizeof (vl.plugin));
742 sstrncpy (vl.plugin_instance, db->instance, sizeof (vl.plugin_instance));
743 sstrncpy (vl.type, key->type, sizeof (vl.type));
745 plugin_dispatch_values (&vl);
746 } /* }}} int cj_submit */
748 static int cj_curl_perform (cj_t *db, CURL *curl) /* {{{ */
749 {
750 int status;
751 long rc;
752 char *url;
753 yajl_handle yprev = db->yajl;
755 db->yajl = yajl_alloc (&ycallbacks,
756 #if HAVE_YAJL_V2
757 /* alloc funcs = */ NULL,
758 #else
759 /* alloc funcs = */ NULL, NULL,
760 #endif
761 /* context = */ (void *)db);
762 if (db->yajl == NULL)
763 {
764 ERROR ("curl_json plugin: yajl_alloc failed.");
765 db->yajl = yprev;
766 return (-1);
767 }
769 url = NULL;
770 curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);
772 status = curl_easy_perform (curl);
773 if (status != 0)
774 {
775 ERROR ("curl_json plugin: curl_easy_perform failed with status %i: %s (%s)",
776 status, db->curl_errbuf, (url != NULL) ? url : "<null>");
777 yajl_free (db->yajl);
778 db->yajl = yprev;
779 return (-1);
780 }
782 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rc);
784 /* The response code is zero if a non-HTTP transport was used. */
785 if ((rc != 0) && (rc != 200))
786 {
787 ERROR ("curl_json plugin: curl_easy_perform failed with "
788 "response code %ld (%s)", rc, url);
789 yajl_free (db->yajl);
790 db->yajl = yprev;
791 return (-1);
792 }
794 #if HAVE_YAJL_V2
795 status = yajl_complete_parse(db->yajl);
796 #else
797 status = yajl_parse_complete(db->yajl);
798 #endif
799 if (status != yajl_status_ok)
800 {
801 unsigned char *errmsg;
803 errmsg = yajl_get_error (db->yajl, /* verbose = */ 0,
804 /* jsonText = */ NULL, /* jsonTextLen = */ 0);
805 ERROR ("curl_json plugin: yajl_parse_complete failed: %s",
806 (char *) errmsg);
807 yajl_free_error (db->yajl, errmsg);
808 yajl_free (db->yajl);
809 db->yajl = yprev;
810 return (-1);
811 }
813 yajl_free (db->yajl);
814 db->yajl = yprev;
815 return (0);
816 } /* }}} int cj_curl_perform */
818 static int cj_read (user_data_t *ud) /* {{{ */
819 {
820 cj_t *db;
822 if ((ud == NULL) || (ud->data == NULL))
823 {
824 ERROR ("curl_json plugin: cj_read: Invalid user data.");
825 return (-1);
826 }
828 db = (cj_t *) ud->data;
830 db->depth = 0;
831 memset (&db->state, 0, sizeof(db->state));
832 db->state[db->depth].tree = db->tree;
833 db->key = NULL;
835 return cj_curl_perform (db, db->curl);
836 } /* }}} int cj_read */
838 void module_register (void)
839 {
840 plugin_register_complex_config ("curl_json", cj_config);
841 } /* void module_register */
843 /* vim: set sw=2 sts=2 et fdm=marker : */