root/src/noit_conf.c

Revision 2357078164b5f1a9354030797e4a9287098a60c2, 46.4 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 3 years ago)

make the noit_conf_load switch to error logging, load, then xml_debugg logging for xml errors

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  *       notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  *       copyright notice, this list of conditions and the following
13  *       disclaimer in the documentation and/or other materials provided
14  *       with the distribution.
15  *     * Neither the name OmniTI Computer Consulting, Inc. nor the names
16  *       of its contributors may be used to endorse or promote products
17  *       derived from this software without specific prior written
18  *       permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "noit_defines.h"
34
35 #include <stdio.h>
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <errno.h>
39 #include <assert.h>
40 #include <libxml/parser.h>
41 #include <libxml/tree.h>
42 #include <libxml/xpath.h>
43 #include <zlib.h>
44
45 #include "noit_conf.h"
46 #include "noit_check.h"
47 #include "noit_console.h"
48 #include "utils/noit_hash.h"
49 #include "utils/noit_log.h"
50 #include "utils/noit_b64.h"
51
52 /* tmp hash impl, replace this with something nice */
53 static noit_log_stream_t xml_debug = NULL;
54 #define XML2LOG(log) do { \
55   xmlSetGenericErrorFunc(log, noit_conf_xml_error_func); \
56   xmlSetStructuredErrorFunc(log, noit_conf_xml_error_ext_func); \
57 } while(0)
58 #define XML2CONSOLE(ncct) do { \
59   xmlSetGenericErrorFunc(ncct, noit_conf_xml_console_error_func); \
60   xmlSetStructuredErrorFunc(ncct, noit_conf_xml_console_error_ext_func); \
61 } while(0)
62 static noit_hash_table _tmp_config = NOIT_HASH_EMPTY;
63 static xmlDocPtr master_config = NULL;
64 static int config_include_cnt = -1;
65 static struct {
66   xmlNodePtr insertion_point;
67   xmlNodePtr old_children;
68   xmlDocPtr doc;
69   xmlNodePtr root;
70 } *config_include_nodes = NULL;
71
72 static char *root_node_name = NULL;
73 static char master_config_file[PATH_MAX] = "";
74 static xmlXPathContextPtr xpath_ctxt = NULL;
75
76 /* This is used to notice config changes and journal the config out
77  * using a user-specified function.  It supports allowing multiple config
78  * changed to coalesce so you don't write out 1000 changes in a few seconds.
79  */
80 static u_int32_t __config_gen = 0;
81 static u_int32_t __config_coalesce = 0;
82 static u_int32_t __config_coalesce_time = 0;
83 void noit_conf_coalesce_changes(u_int32_t seconds) {
84   __config_coalesce_time = seconds;
85 }
86 void noit_conf_mark_changed() {
87   /* increment the change counter -- in case anyone cares */
88   __config_gen++;
89   /* reset the coalesce counter.  It is decremented each second and
90    * the journal function fires on a transition from 1 => 0
91    */
92   __config_coalesce = __config_coalesce_time;
93 }
94 struct recurrent_journaler {
95   int (*journal_config)(void *);
96   void *jc_closure;
97 };
98 static int
99 noit_conf_watch_config_and_journal(eventer_t e, int mask, void *closure,
100                                    struct timeval *now) {
101   struct recurrent_journaler *rj = closure;
102   eventer_t newe;
103
104   if(__config_coalesce == 1)
105     rj->journal_config(rj->jc_closure);
106   if(__config_coalesce > 0)
107     __config_coalesce--;
108
109   /* Schedule the same event to fire a second form now */
110   newe = eventer_alloc();
111   gettimeofday(&newe->whence, NULL);
112   newe->whence.tv_sec += 1;
113   newe->mask = EVENTER_TIMER;
114   newe->callback = noit_conf_watch_config_and_journal;
115   newe->closure = closure;
116   eventer_add(newe);
117   return 0;
118 }
119 void
120 noit_conf_watch_and_journal_watchdog(int (*f)(void *), void *c) {
121   static int callbacknamed = 0;
122   struct recurrent_journaler *rj;
123   struct timeval __now;
124
125   if(!callbacknamed) {
126     callbacknamed = 1;
127     eventer_name_callback("noit_conf_watch_config_and_journal",
128                           noit_conf_watch_config_and_journal);
129   }
130   rj = calloc(1, sizeof(*rj));
131   rj->journal_config = f;
132   rj->jc_closure = c;
133   gettimeofday(&__now, NULL);
134   noit_conf_watch_config_and_journal(NULL, EVENTER_TIMER, rj, &__now);
135 }
136
137 static noit_hash_table _compiled_fallback = NOIT_HASH_EMPTY;
138 static struct {
139   const char *key;
140   const char *val;
141 } config_info[] = {
142   /*
143    * These are compile-time fallbacks to be used in the event
144    * that the current running config does not have values for
145    * these config paths.
146    *
147    * PLEASE: keep them alphabetically sorted.
148    */
149   { "/%s/eventer/@implementation", DEFAULT_EVENTER },
150   { "/%s/modules/@directory", MODULES_DIR },
151
152   { NULL, NULL }
153 };
154
155 void noit_conf_xml_console_error_func(void *ctx, const char *format, ...) {
156   noit_console_closure_t ncct = ctx;
157   va_list arg;
158   if(!ncct) return;
159   va_start(arg, format);
160   nc_vprintf(ncct, format, arg);
161   va_end(arg);
162 }
163
164 void noit_conf_xml_console_error_ext_func(void *ctx, xmlErrorPtr err) {
165   noit_console_closure_t ncct = ctx;
166   if(!ctx) return;
167   if(err->file)
168     nc_printf(ncct, "XML error [%d/%d] in %s on line %d %s\n",
169               err->domain, err->code, err->file, err->line, err->message);
170   else
171     nc_printf(ncct, "XML error [%d/%d] %s\n",
172               err->domain, err->code, err->message);
173 }
174
175 void noit_conf_xml_error_func(void *ctx, const char *format, ...) {
176   struct timeval __now;
177   noit_log_stream_t ls = ctx;
178   va_list arg;
179   if(!ls) return;
180   va_start(arg, format);
181   gettimeofday(&__now,  NULL);
182   noit_vlog(ls, &__now, __FILE__, __LINE__, format, arg);
183   va_end(arg);
184 }
185 void noit_conf_xml_error_ext_func(void *ctx, xmlErrorPtr err) {
186   struct timeval __now;
187   noit_log_stream_t ls = ctx;
188   if(!ls) return;
189   gettimeofday(&__now,  NULL);
190   if(err->file)
191     noit_log(ls, &__now, err->file, err->line,
192              "XML error [%d/%d] in %s on line %d %s\n",
193              err->domain, err->code, err->file, err->line, err->message);
194   else
195     noit_log(ls, &__now, err->file, err->line,
196              "XML error [%d/%d] %s\n",
197              err->domain, err->code, err->message);
198 }
199
200
201 DECLARE_CHECKER(name)
202 void noit_conf_init(const char *toplevel) {
203   int i;
204   char keystr[256];
205
206   xml_debug = noit_log_stream_find("debug/xml");
207
208   COMPILE_CHECKER(name, "^[-_\\.:/a-zA-Z0-9]+$");
209   XML2LOG(noit_error);
210   for(i = 0; config_info[i].key != NULL; i++) {
211     snprintf(keystr, sizeof(keystr), config_info[i].key, toplevel);
212     noit_hash_store(&_compiled_fallback,
213                     strdup(keystr), strlen(keystr),
214                     (void *)strdup(config_info[i].val));
215   }
216   xmlKeepBlanksDefault(0);
217   xmlInitParser();
218   xmlXPathInit();
219 }
220
221 void
222 noit_conf_magic_separate(xmlDocPtr doc) {
223   assert(config_include_cnt != -1);
224   if(config_include_nodes) {
225     int i;
226     for(i=0; i<config_include_cnt; i++) {
227       if(config_include_nodes[i].doc) {
228         xmlNodePtr n;
229         for(n=config_include_nodes[i].insertion_point->children;
230             n; n = n->next)
231           n->parent = config_include_nodes[i].root;
232         config_include_nodes[i].insertion_point->children =
233           config_include_nodes[i].old_children;
234         xmlFreeDoc(config_include_nodes[i].doc);
235       }
236     }
237     free(config_include_nodes);
238   }
239   config_include_nodes = NULL;
240   config_include_cnt = -1;
241 }
242 void
243 noit_conf_kansas_city_shuffle_redo(xmlDocPtr doc) {
244   if(config_include_nodes) {
245     int i;
246     for(i=0; i<config_include_cnt; i++) {
247       if(config_include_nodes[i].doc) {
248         xmlNodePtr n;
249         config_include_nodes[i].insertion_point->children =
250           config_include_nodes[i].root->children;
251         for(n=config_include_nodes[i].insertion_point->children;
252             n; n = n->next)
253           n->parent = config_include_nodes[i].insertion_point;
254       }
255     }
256   }
257 }
258 void
259 noit_conf_kansas_city_shuffle_undo(xmlDocPtr doc) {
260   if(config_include_nodes) {
261     int i;
262     for(i=0; i<config_include_cnt; i++) {
263       if(config_include_nodes[i].doc) {
264         xmlNodePtr n;
265         for(n=config_include_nodes[i].insertion_point->children;
266             n; n = n->next)
267           n->parent = config_include_nodes[i].root;
268         config_include_nodes[i].insertion_point->children =
269           config_include_nodes[i].old_children;
270       }
271     }
272   }
273 }
274 int
275 noit_conf_magic_mix(const char *parentfile, xmlDocPtr doc) {
276   xmlXPathContextPtr mix_ctxt = NULL;
277   xmlXPathObjectPtr pobj = NULL;
278   xmlNodePtr node;
279   int i, cnt, rv = 0;
280
281   assert(config_include_cnt == -1);
282
283   config_include_cnt = 0;
284   mix_ctxt = xmlXPathNewContext(doc);
285   pobj = xmlXPathEval((xmlChar *)"//include[@file]", mix_ctxt);
286   if(!pobj) goto out;
287   if(pobj->type != XPATH_NODESET) goto out;
288   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto out;
289   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
290   if(cnt > 0)
291     config_include_nodes = calloc(cnt, sizeof(*config_include_nodes));
292   for(i=0; i<cnt; i++) {
293     char *path, *infile;
294     node = xmlXPathNodeSetItem(pobj->nodesetval, i);
295     path = (char *)xmlGetProp(node, (xmlChar *)"file");
296     if(!path) continue;
297     if(*path == '/') infile = strdup(path);
298     else {
299       char *cp;
300       infile = malloc(PATH_MAX);
301       strlcpy(infile, parentfile, PATH_MAX);
302       for(cp = infile + strlen(infile) - 1; cp > infile; cp--) {
303         if(*cp == '/') { *cp = '\0'; break; }
304         else *cp = '\0';
305       }
306       strlcat(infile, "/", PATH_MAX);
307       strlcat(infile, path, PATH_MAX);
308     }
309     config_include_nodes[i].doc = xmlParseFile(infile);
310     if(config_include_nodes[i].doc) {
311       xmlNodePtr n;
312       config_include_nodes[i].insertion_point = node;
313       config_include_nodes[i].root = xmlDocGetRootElement(config_include_nodes[i].doc);
314       config_include_nodes[i].old_children = node->children;
315       node->children = config_include_nodes[i].root->children;
316       for(n=node->children; n; n = n->next)
317         n->parent = config_include_nodes[i].insertion_point;
318     }
319     else {
320       noitL(noit_error, "Could not load: '%s'\n", infile);
321       rv = -1;
322     }
323     free(infile);
324   }
325   config_include_cnt = cnt;
326   noitL(noit_debug, "Processed %d includes\n", config_include_cnt);
327  out:
328   if(pobj) xmlXPathFreeObject(pobj);
329   if(mix_ctxt) xmlXPathFreeContext(mix_ctxt);
330   return rv;
331 }
332
333 static int noit_conf_load_internal(const char *path) {
334   int rv = 0;
335   xmlDocPtr new_config;
336   xmlNodePtr root;
337   new_config = xmlParseFile(path);
338   if(new_config) {
339     root = xmlDocGetRootElement(new_config);
340     if(root_node_name) free(root_node_name);
341     root_node_name = strdup((char *)root->name);
342
343     if(master_config) {
344       /* separate all includes */
345       noit_conf_magic_separate(master_config);
346       xmlFreeDoc(master_config);
347     }
348     if(xpath_ctxt) xmlXPathFreeContext(xpath_ctxt);
349
350     master_config = new_config;
351     /* mixin all the includes */
352     if(noit_conf_magic_mix(path, master_config)) rv = -1;
353
354     xpath_ctxt = xmlXPathNewContext(master_config);
355     if(path != master_config_file)
356       if(realpath(path, master_config_file) == NULL)
357         noitL(noit_error, "realpath failed: %s\n", strerror(errno));
358     noit_conf_mark_changed();
359     return rv;
360   }
361   rv = -1;
362   return rv;
363 }
364 int noit_conf_load(const char *path) {
365   int rv;
366   XML2LOG(noit_error);
367   rv = noit_conf_load_internal(path);
368   XML2LOG(xml_debug);
369   return rv;
370 }
371
372 char *noit_conf_config_filename() {
373   return strdup(master_config_file);
374 }
375
376 int noit_conf_xml_xpath(xmlDocPtr *mc, xmlXPathContextPtr *xp) {
377   if(mc) *mc = master_config;
378   if(xp) *xp = xpath_ctxt;
379   return 0;
380 }
381 int noit_conf_save(const char *path) {
382   return -1;
383 }
384
385 void noit_conf_get_elements_into_hash(noit_conf_section_t section,
386                                       const char *path,
387                                       noit_hash_table *table) {
388   int i, cnt;
389   xmlXPathObjectPtr pobj = NULL;
390   xmlXPathContextPtr current_ctxt;
391   xmlNodePtr current_node = (xmlNodePtr)section;
392   xmlNodePtr node;
393
394   current_ctxt = xpath_ctxt;
395   if(current_node) {
396     current_ctxt = xmlXPathNewContext(master_config);
397     current_ctxt->node = current_node;
398   }
399   pobj = xmlXPathEval((xmlChar *)path, current_ctxt);
400   if(!pobj) goto out;
401   if(pobj->type != XPATH_NODESET) goto out;
402   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto out;
403   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
404   for(i=0; i<cnt; i++) {
405     char *value;
406     node = xmlXPathNodeSetItem(pobj->nodesetval, i);
407     value = (char *)xmlXPathCastNodeToString(node);
408     noit_hash_replace(table,
409                       strdup((char *)node->name), strlen((char *)node->name),
410                       strdup(value), free, free);
411     xmlFree(value);
412   }
413  out:
414   if(pobj) xmlXPathFreeObject(pobj);
415   if(current_ctxt && current_ctxt != xpath_ctxt)
416     xmlXPathFreeContext(current_ctxt);
417 }
418 void noit_conf_get_into_hash(noit_conf_section_t section,
419                              const char *path,
420                              noit_hash_table *table) {
421   unsigned int cnt;
422   xmlXPathObjectPtr pobj = NULL;
423   xmlXPathContextPtr current_ctxt;
424   xmlNodePtr current_node = (xmlNodePtr)section;
425   xmlNodePtr node, parent_node;
426   char xpath_expr[1024];
427   char *inheritid;
428
429   current_ctxt = xpath_ctxt;
430   if(current_node) {
431     current_ctxt = xmlXPathNewContext(master_config);
432     current_ctxt->node = current_node;
433   }
434   if(path[0] == '/')
435     strlcpy(xpath_expr, path, sizeof(xpath_expr));
436   else
437     snprintf(xpath_expr, sizeof(xpath_expr),
438              "ancestor-or-self::node()/%s", path);
439   pobj = xmlXPathEval((xmlChar *)xpath_expr, current_ctxt);
440   if(!pobj) goto out;
441   if(pobj->type != XPATH_NODESET) goto out;
442   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto out;
443   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
444   /* These are in the order of root to leaf
445    * We want to recurse... apply:
446    *   1. our parent's config
447    *   2. our "inherit" config if it exists.
448    *   3. our config.
449    */
450   node = xmlXPathNodeSetItem(pobj->nodesetval, cnt-1);
451   /* 1. */
452   if(cnt > 1 && node) {
453     parent_node = xmlXPathNodeSetItem(pobj->nodesetval, cnt-2);
454     if(parent_node != current_node)
455       noit_conf_get_into_hash(parent_node, (const char *)node->name, table);
456   }
457   /* 2. */
458   inheritid = (char *)xmlGetProp(node, (xmlChar *)"inherit");
459   if(inheritid) {
460     snprintf(xpath_expr, sizeof(xpath_expr), "//*[@id=\"%s\"]", inheritid);
461     noit_conf_get_into_hash(NULL, xpath_expr, table);
462   }
463   /* 3. */
464   noit_conf_get_elements_into_hash(node, "*", table);
465
466  out:
467   if(pobj) xmlXPathFreeObject(pobj);
468   if(current_ctxt && current_ctxt != xpath_ctxt)
469     xmlXPathFreeContext(current_ctxt);
470 }
471 noit_hash_table *noit_conf_get_hash(noit_conf_section_t section,
472                                     const char *path) {
473   noit_hash_table *table = NULL;
474
475   table = calloc(1, sizeof(*table));
476   noit_conf_get_into_hash(section, path, table);
477   return table;
478 }
479 noit_conf_section_t noit_conf_get_section(noit_conf_section_t section,
480                                           const char *path) {
481   noit_conf_section_t subsection = NULL;
482   xmlXPathObjectPtr pobj = NULL;
483   xmlXPathContextPtr current_ctxt;
484   xmlNodePtr current_node = (xmlNodePtr)section;
485
486   current_ctxt = xpath_ctxt;
487   if(current_node) {
488     current_ctxt = xmlXPathNewContext(master_config);
489     current_ctxt->node = current_node;
490   }
491   pobj = xmlXPathEval((xmlChar *)path, current_ctxt);
492   if(!pobj) goto out;
493   if(pobj->type != XPATH_NODESET) goto out;
494   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto out;
495   subsection = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
496  out:
497   if(pobj) xmlXPathFreeObject(pobj);
498   if(current_ctxt && current_ctxt != xpath_ctxt)
499     xmlXPathFreeContext(current_ctxt);
500   return subsection;
501 }
502 noit_conf_section_t *noit_conf_get_sections(noit_conf_section_t section,
503                                             const char *path,
504                                             int *cnt) {
505   int i;
506   noit_conf_section_t *sections = NULL;
507   xmlXPathObjectPtr pobj = NULL;
508   xmlXPathContextPtr current_ctxt;
509   xmlNodePtr current_node = (xmlNodePtr)section;
510
511   *cnt = 0;
512   current_ctxt = xpath_ctxt;
513   if(current_node) {
514     current_ctxt = xmlXPathNewContext(master_config);
515     current_ctxt->node = current_node;
516   }
517   pobj = xmlXPathEval((xmlChar *)path, current_ctxt);
518   if(!pobj) goto out;
519   if(pobj->type != XPATH_NODESET) goto out;
520   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto out;
521   *cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
522   sections = calloc(*cnt, sizeof(*sections));
523   for(i=0; i<*cnt; i++)
524     sections[i] = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, i);
525  out:
526   if(pobj) xmlXPathFreeObject(pobj);
527   if(current_ctxt && current_ctxt != xpath_ctxt)
528     xmlXPathFreeContext(current_ctxt);
529   return sections;
530 }
531 int _noit_conf_get_string(noit_conf_section_t section, xmlNodePtr *vnode,
532                           const char *path, char **value) {
533   const char *str;
534   int rv = 1;
535   unsigned int i;
536   xmlXPathObjectPtr pobj = NULL;
537   xmlXPathContextPtr current_ctxt;
538   xmlNodePtr current_node = (xmlNodePtr)section;
539
540   current_ctxt = xpath_ctxt;
541   if(current_node) {
542     current_ctxt = xmlXPathNewContext(master_config);
543     current_ctxt->node = current_node;
544   }
545   pobj = xmlXPathEval((xmlChar *)path, current_ctxt);
546   if(pobj) {
547     xmlNodePtr node;
548     switch(pobj->type) {
549       case XPATH_NODESET:
550         if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto fallback;
551         i = xmlXPathNodeSetGetLength(pobj->nodesetval);
552         node = xmlXPathNodeSetItem(pobj->nodesetval, i-1);
553         if(vnode) *vnode = node;
554         *value = (char *)xmlXPathCastNodeToString(node);
555         break;
556       default:
557         *value = (char *)xmlXPathCastToString(pobj);
558     }
559     goto found;
560   }
561  fallback:
562   if(noit_hash_retr_str(&_compiled_fallback,
563                         path, strlen(path), &str)) {
564     *value = (char *)xmlStrdup((xmlChar *)str);
565     goto found;
566   }
567   rv = 0;
568  found:
569   if(pobj) xmlXPathFreeObject(pobj);
570   if(current_ctxt && current_ctxt != xpath_ctxt)
571     xmlXPathFreeContext(current_ctxt);
572   return rv;
573 }
574 int noit_conf_get_uuid(noit_conf_section_t section,
575                        const char *path, uuid_t out) {
576   char *str;
577   if(_noit_conf_get_string(section,NULL,path,&str)) {
578     if(uuid_parse(str, out) == 0) return 1;
579     return 0;
580   }
581   return 0;
582 }
583 int noit_conf_get_string(noit_conf_section_t section,
584                          const char *path, char **value) {
585   char *str;
586   if(_noit_conf_get_string(section,NULL,path,&str)) {
587     *value = strdup(str);
588     xmlFree(str);
589     return 1;
590   }
591   return 0;
592 }
593 int noit_conf_get_stringbuf(noit_conf_section_t section,
594                             const char *path, char *buf, int len) {
595   char *str;
596   if(_noit_conf_get_string(section,NULL,path,&str)) {
597     strlcpy(buf, str, len);
598     xmlFree(str);
599     return 1;
600   }
601   return 0;
602 }
603 int noit_conf_set_string(noit_conf_section_t section,
604                          const char *path, const char *value) {
605   noit_hash_replace(&_tmp_config,
606                     strdup(path), strlen(path), (void *)strdup(value),
607                     free, free);
608   return 1;
609 }
610 int noit_conf_string_to_int(const char *str) {
611   int base = 10;
612   if(!str) return 0;
613   if(str[0] == '0') {
614     if(str[1] == 'x') base = 16;
615     else base = 8;
616   }
617   return strtol(str, NULL, base);
618 }
619 int noit_conf_get_int(noit_conf_section_t section,
620                       const char *path, int *value) {
621   char *str;
622   if(_noit_conf_get_string(section,NULL,path,&str)) {
623     *value = (int)noit_conf_string_to_int(str);
624     xmlFree(str);
625     return 1;
626   }
627   return 0;
628 }
629 int noit_conf_set_int(noit_conf_section_t section,
630                       const char *path, int value) {
631   char buffer[32];
632   snprintf(buffer, 32, "%d", value);
633   return noit_conf_set_string(section,path,buffer);
634 }
635 float noit_conf_string_to_float(const char *str) {
636   if(!str) return 0.0;
637   return atof(str);
638 }
639 int noit_conf_get_float(noit_conf_section_t section,
640                         const char *path, float *value) {
641   char *str;
642   if(_noit_conf_get_string(section,NULL,path,&str)) {
643     *value = noit_conf_string_to_float(str);
644     xmlFree(str);
645     return 1;
646   }
647   return 0;
648 }
649 int noit_conf_set_float(noit_conf_section_t section,
650                         const char *path, float value) {
651   char buffer[32];
652   snprintf(buffer, 32, "%f", value);
653   return noit_conf_set_string(section,path,buffer);
654 }
655 noit_boolean noit_conf_string_to_boolean(const char *str) {
656   if(!str) return noit_false;
657   if(!strcasecmp(str, "true") || !strcasecmp(str, "on")) return noit_true;
658   return noit_false;
659 }
660 int noit_conf_get_boolean(noit_conf_section_t section,
661                           const char *path, noit_boolean *value) {
662   char *str;
663   if(_noit_conf_get_string(section,NULL,path,&str)) {
664     *value = noit_conf_string_to_boolean(str);
665     xmlFree(str);
666     return 1;
667   }
668   return 0;
669 }
670 int noit_conf_set_boolean(noit_conf_section_t section,
671                           const char *path, noit_boolean value) {
672   if(value == noit_true)
673     return noit_conf_set_string(section,path,"true");
674   return noit_conf_set_string(section,path,"false");
675 }
676
677 struct config_line_vstr {
678   char *buff;
679   int raw_len;
680   int len;
681   int allocd;
682   enum { CONFIG_RAW = 0, CONFIG_COMPRESSED, CONFIG_B64 } target, encoded;
683 };
684 static int
685 noit_config_log_write_xml(void *vstr, const char *buffer, int len) {
686   struct config_line_vstr *clv = vstr;
687   assert(clv->encoded == CONFIG_RAW);
688   if(!clv->buff) {
689     clv->allocd = 8192;
690     clv->buff = malloc(clv->allocd);
691   }
692   while(len + clv->len > clv->allocd) {
693     char *newbuff;
694     int newsize = clv->allocd;
695     newsize <<= 1;
696     newbuff = realloc(clv->buff, newsize);
697     if(!newbuff) {
698       return -1;
699     }
700     clv->allocd = newsize;
701     clv->buff = newbuff;
702   }
703   memcpy(clv->buff + clv->len, buffer, len);
704   clv->len += len;
705   return len;
706 }
707 static int
708 noit_config_log_close_xml(void *vstr) {
709   struct config_line_vstr *clv = vstr;
710   uLong initial_dlen, dlen;
711   char *compbuff, *b64buff;
712
713   if(clv->buff == NULL) {
714     clv->encoded = clv->target;
715     return 0;
716   }
717   clv->raw_len = clv->len;
718   assert(clv->encoded == CONFIG_RAW);
719   if(clv->encoded == clv->target) return 0;
720
721   /* Compress */
722   initial_dlen = dlen = compressBound(clv->len);
723   compbuff = malloc(initial_dlen);
724   if(!compbuff) return -1;
725   if(Z_OK != compress2((Bytef *)compbuff, &dlen,
726                        (Bytef *)clv->buff, clv->len, 9)) {
727     noitL(noit_error, "Error compressing config for transmission.\n");
728     free(compbuff);
729     return -1;
730   }
731   free(clv->buff);
732   clv->buff = compbuff;
733   clv->allocd = initial_dlen;
734   clv->len = dlen;
735   clv->encoded = CONFIG_COMPRESSED;
736   if(clv->encoded == clv->target) return 0;
737
738   /* Encode */
739   initial_dlen = ((clv->len + 2) / 3) * 4;
740   b64buff = malloc(initial_dlen);
741   dlen = noit_b64_encode((unsigned char *)clv->buff, clv->len,
742                          b64buff, initial_dlen);
743   if(dlen == 0) {
744     free(b64buff);
745     return -1;
746   }
747   free(clv->buff);
748   clv->buff = b64buff;
749   clv->allocd = initial_dlen;
750   clv->len = dlen;
751   clv->encoded = CONFIG_B64;
752   if(clv->encoded == clv->target) return 0;
753   return -1;
754 }
755
756 int
757 noit_conf_reload(noit_console_closure_t ncct,
758                  int argc, char **argv,
759                  noit_console_state_t *state, void *closure) {
760   XML2CONSOLE(ncct);
761   if(noit_conf_load_internal(master_config_file)) {
762     XML2LOG(xml_debug);
763     nc_printf(ncct, "error loading config\n");
764     return -1;
765   }
766   XML2LOG(xml_debug);
767   return 0;
768 }
769 int
770 noit_conf_write_terminal(noit_console_closure_t ncct,
771                          int argc, char **argv,
772                          noit_console_state_t *state, void *closure) {
773   xmlOutputBufferPtr out;
774   xmlCharEncodingHandlerPtr enc;
775   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
776   out = xmlOutputBufferCreateIO(noit_console_write_xml,
777                                 noit_console_close_xml,
778                                 ncct, enc);
779   noit_conf_kansas_city_shuffle_undo(master_config);
780   xmlSaveFormatFileTo(out, master_config, "utf8", 1);
781   noit_conf_kansas_city_shuffle_redo(master_config);
782   return 0;
783 }
784 int
785 noit_conf_write_file_console(noit_console_closure_t ncct,
786                              int argc, char **argv,
787                              noit_console_state_t *state, void *closure) {
788   int rv;
789   char *err = NULL;
790   rv = noit_conf_write_file(&err);
791   nc_printf(ncct, "%s\n", err);
792   if(err) free(err);
793   return rv;
794 }
795 int
796 noit_conf_write_file(char **err) {
797   int fd, len;
798   char master_file_tmp[PATH_MAX];
799   char errstr[1024];
800   xmlOutputBufferPtr out;
801   xmlCharEncodingHandlerPtr enc;
802   struct stat st;
803   mode_t mode = 0640; /* the default */
804
805   if(stat(master_config_file, &st) == 0)
806     mode = st.st_mode;
807   snprintf(master_file_tmp, sizeof(master_file_tmp),
808            "%s.tmp", master_config_file);
809   unlink(master_file_tmp);
810   fd = open(master_file_tmp, O_CREAT|O_EXCL|O_WRONLY, mode);
811   if(fd < 0) {
812     snprintf(errstr, sizeof(errstr), "Failed to open tmp file: %s",
813              strerror(errno));
814     if(err) *err = strdup(errstr);
815     return -1;
816   }
817   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
818   out = xmlOutputBufferCreateFd(fd, enc);
819   if(!out) {
820     close(fd);
821     unlink(master_file_tmp);
822     if(err) *err = strdup("internal error: OutputBufferCreate failed");
823     return -1;
824   }
825   noit_conf_kansas_city_shuffle_undo(master_config);
826   len = xmlSaveFormatFileTo(out, master_config, "utf8", 1);
827   noit_conf_kansas_city_shuffle_redo(master_config);
828   close(fd);
829   if(len <= 0) {
830     if(err) *err = strdup("internal error: writing to tmp file failed.");
831     return -1;
832   }
833   if(rename(master_file_tmp, master_config_file) != 0) {
834     snprintf(errstr, sizeof(errstr), "Failed to replace file: %s",
835              strerror(errno));
836     if(err) *err = strdup(errstr);
837     return -1;
838   }
839   snprintf(errstr, sizeof(errstr), "%d bytes written.", len);
840   if(err) *err = strdup(errstr);
841   return 0;
842 }
843 char *
844 noit_conf_xml_in_mem(size_t *len) {
845   struct config_line_vstr *clv;
846   xmlOutputBufferPtr out;
847   xmlCharEncodingHandlerPtr enc;
848   char *rv;
849
850   clv = calloc(1, sizeof(*clv));
851   clv->target = CONFIG_RAW;
852   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
853   out = xmlOutputBufferCreateIO(noit_config_log_write_xml,
854                                 noit_config_log_close_xml,
855                                 clv, enc);
856   noit_conf_kansas_city_shuffle_undo(master_config);
857   xmlSaveFormatFileTo(out, master_config, "utf8", 1);
858   noit_conf_kansas_city_shuffle_redo(master_config);
859   if(clv->encoded != CONFIG_RAW) {
860     noitL(noit_error, "Error logging configuration\n");
861     if(clv->buff) free(clv->buff);
862     free(clv);
863     return NULL;
864   }
865   rv = clv->buff;
866   *len = clv->len;
867   free(clv);
868   return rv;
869 }
870
871 int
872 noit_conf_write_log() {
873   static u_int32_t last_write_gen = 0;
874   static noit_log_stream_t config_log = NULL;
875   struct timeval __now;
876   xmlOutputBufferPtr out;
877   xmlCharEncodingHandlerPtr enc;
878   struct config_line_vstr *clv;
879   SETUP_LOG(config, return -1);
880
881   /* We know we haven't changed */
882   if(last_write_gen == __config_gen) return 0;
883
884   gettimeofday(&__now, NULL);
885   clv = calloc(1, sizeof(*clv));
886   clv->target = CONFIG_B64;
887   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
888   out = xmlOutputBufferCreateIO(noit_config_log_write_xml,
889                                 noit_config_log_close_xml,
890                                 clv, enc);
891   noit_conf_kansas_city_shuffle_undo(master_config);
892   xmlSaveFormatFileTo(out, master_config, "utf8", 1);
893   noit_conf_kansas_city_shuffle_redo(master_config);
894   if(clv->encoded != CONFIG_B64) {
895     noitL(noit_error, "Error logging configuration\n");
896     if(clv->buff) free(clv->buff);
897     free(clv);
898     return -1;
899   }
900   noitL(config_log, "n\t%lu.%03lu\t%d\t%.*s\n",
901         (unsigned long int)__now.tv_sec,
902         (unsigned long int)__now.tv_usec / 1000UL, clv->raw_len,
903         clv->len, clv->buff);
904   free(clv->buff);
905   free(clv);
906   last_write_gen = __config_gen;
907   return 0;
908 }
909
910 struct log_rotate_crutch {
911   noit_log_stream_t ls;
912   int seconds;
913   size_t max_size;
914 };
915
916 static int
917 noit_conf_log_rotate_size(eventer_t e, int mask, void *closure,
918                           struct timeval *now) {
919   struct log_rotate_crutch *lrc = closure;
920   if(noit_log_stream_written(lrc->ls) > lrc->max_size) {
921     noit_log_stream_rename(lrc->ls, NOIT_LOG_RENAME_AUTOTIME);
922     noit_log_stream_reopen(lrc->ls);
923   }
924   /* Yes the 5 is arbitrary, but this is cheap */
925   eventer_add_in_s_us(noit_conf_log_rotate_size, closure, 5, 0);
926   return 0;
927 }
928 static int
929 noit_conf_log_rotate_time(eventer_t e, int mask, void *closure,
930                           struct timeval *now) {
931   struct timeval lnow;
932   eventer_t newe;
933   struct log_rotate_crutch *lrc = closure;
934
935   if(now) {
936     noit_log_stream_rename(lrc->ls, NOIT_LOG_RENAME_AUTOTIME);
937     noit_log_stream_reopen(lrc->ls);
938   }
939  
940   newe = eventer_alloc();
941   newe->closure = closure;
942   if(!now) { gettimeofday(&lnow, NULL); now = &lnow; }
943   if(e)
944     memcpy(&newe->whence, &e->whence, sizeof(newe->whence));
945   else if(now) {
946     memcpy(&newe->whence, now, sizeof(newe->whence));
947     newe->whence.tv_sec = (newe->whence.tv_sec / lrc->seconds) * lrc->seconds;
948   }
949   newe->whence.tv_sec += lrc->seconds;
950   newe->mask = EVENTER_TIMER;
951   newe->callback = noit_conf_log_rotate_time;
952   eventer_add(newe);
953   return 0;
954 }
955 int
956 noit_conf_log_init_rotate(const char *toplevel, noit_boolean validate) {
957   int i, cnt = 0, max_time, max_size, rv = 0;
958   noit_conf_section_t *log_configs;
959   char path[256];
960
961   snprintf(path, sizeof(path), "/%s/logs//log", toplevel);
962   log_configs = noit_conf_get_sections(NULL, path, &cnt);
963   noitL(noit_debug, "Found %d %s stanzas\n", cnt, path);
964   for(i=0; i<cnt; i++) {
965     noit_log_stream_t ls;
966     char name[256];
967
968     if(!noit_conf_get_stringbuf(log_configs[i],
969                                 "ancestor-or-self::node()/@name",
970                                 name, sizeof(name))) {
971       noitL(noit_error, "log section %d does not have a name attribute\n", i+1);
972       if(validate) { rv = -1; break; }
973       else exit(-2);
974     }
975     ls = noit_log_stream_find(name);
976     if(!ls) continue;
977
978     if(noit_conf_get_int(log_configs[i],   
979                          "ancestor-or-self::node()/@rotate_seconds",
980                          &max_time) && max_time) {
981       struct log_rotate_crutch *lrc;
982       if(max_time < 600) {
983         fprintf(stderr, "rotate_seconds must be >= 600s (10 minutes)\n");
984         if(validate) { rv = -1; break; }
985         else exit(-2);
986       }
987       if(!validate) {
988         lrc = calloc(1, sizeof(*lrc));
989         lrc->ls = ls;
990         lrc->seconds = max_time;
991         noit_conf_log_rotate_time(NULL, EVENTER_TIMER, lrc, NULL);
992       }
993     }
994
995     if(noit_conf_get_int(log_configs[i],   
996                          "ancestor-or-self::node()/@rotate_bytes",
997                          &max_size) && max_size) {
998       struct log_rotate_crutch *lrc;
999       if(max_size < 102400) {
1000         fprintf(stderr, "rotate_bytes must be >= 102400 (100k)\n");
1001         if(validate) { rv = -1; break; }
1002         else exit(-2);
1003       }
1004       if(!validate) {
1005         lrc = calloc(1, sizeof(*lrc));
1006         lrc->ls = ls;
1007         lrc->max_size = max_size;
1008         noit_conf_log_rotate_size(NULL, EVENTER_TIMER, lrc, NULL);
1009       }
1010     }
1011   }
1012   free(log_configs);
1013   return rv;
1014 }
1015 void
1016 noit_conf_log_init(const char *toplevel) {
1017   int i, cnt = 0, o, ocnt = 0;
1018   noit_conf_section_t *log_configs, *outlets;
1019   char path[256];
1020
1021   snprintf(path, sizeof(path), "/%s/logs//log", toplevel);
1022   log_configs = noit_conf_get_sections(NULL, path, &cnt);
1023   noitL(noit_debug, "Found %d %s stanzas\n", cnt, path);
1024   for(i=0; i<cnt; i++) {
1025     noit_log_stream_t ls;
1026     char name[256], type[256], path[256];
1027     noit_hash_table *config;
1028     noit_boolean disabled, debug, timestamps;
1029
1030     if(!noit_conf_get_stringbuf(log_configs[i],
1031                                 "ancestor-or-self::node()/@name",
1032                                 name, sizeof(name))) {
1033       noitL(noit_error, "log section %d does not have a name attribute\n", i+1);
1034       exit(-1);
1035     }
1036     if(!noit_conf_get_stringbuf(log_configs[i],
1037                                 "ancestor-or-self::node()/@type",
1038                                 type, sizeof(type))) {
1039       type[0] = '\0';
1040     }
1041     if(!noit_conf_get_stringbuf(log_configs[i],
1042                                 "ancestor-or-self::node()/@path",
1043                                 path, sizeof(path))) {
1044       path[0] = '\0';
1045     }
1046     config = noit_conf_get_hash(log_configs[i],
1047                                 "ancestor-or-self::node()/config");
1048     ls = noit_log_stream_new(name, type[0] ? type : NULL,
1049                              path[0] ? path : NULL, NULL, config);
1050     if(!ls) {
1051       fprintf(stderr, "Error configuring log: %s[%s:%s]\n", name, type, path);
1052       exit(-1);
1053     }
1054
1055     if(noit_conf_get_boolean(log_configs[i],
1056                              "ancestor-or-self::node()/@disabled",
1057                              &disabled) && disabled)
1058       ls->enabled = 0;
1059      
1060     if(noit_conf_get_boolean(log_configs[i],
1061                              "ancestor-or-self::node()/@debug",
1062                              &debug) && debug)
1063       ls->debug = 1;
1064      
1065     if(noit_conf_get_boolean(log_configs[i],
1066                              "ancestor-or-self::node()/@timestamps",
1067                              &timestamps))
1068       ls->timestamps = timestamps ? 1 : 0;
1069  
1070     outlets = noit_conf_get_sections(log_configs[i],
1071                                      "ancestor-or-self::node()/outlet", &ocnt);
1072     noitL(noit_debug, "Found %d outlets for log '%s'\n", ocnt, name);
1073
1074     for(o=0; o<ocnt; o++) {
1075       noit_log_stream_t outlet;
1076       char oname[256];
1077       noit_conf_get_stringbuf(outlets[o], "@name",
1078                               oname, sizeof(oname));
1079       outlet = noit_log_stream_find(oname);
1080       if(!outlet) {
1081         fprintf(stderr, "Cannot find outlet '%s' for %s[%s:%s]\n", oname,
1082               name, type, path);
1083         exit(-1);
1084       }
1085       else
1086         noit_log_stream_add_stream(ls, outlet);
1087     }
1088     if(outlets) free(outlets);
1089   }
1090   if(log_configs) free(log_configs);
1091   if(noit_conf_log_init_rotate(toplevel, noit_true)) exit(-1);
1092 }
1093
1094 static void
1095 conf_t_userdata_free(void *data) {
1096   noit_conf_t_userdata_t *info = data;
1097   if(info) {
1098     if(info->path) free(info->path);
1099     free(info);
1100   }
1101 }
1102
1103 static int
1104 noit_console_state_conf_terminal(noit_console_closure_t ncct,
1105                                  int argc, char **argv,
1106                                  noit_console_state_t *state, void *closure) {
1107   noit_conf_t_userdata_t *info;
1108   if(argc) {
1109     nc_printf(ncct, "extra arguments not expected.\n");
1110     return -1;
1111   }
1112   info = calloc(1, sizeof(*info));
1113   info->path = strdup("/");
1114   noit_console_userdata_set(ncct, NOIT_CONF_T_USERDATA, info,
1115                             conf_t_userdata_free);
1116   noit_console_state_push_state(ncct, state);
1117   noit_console_state_init(ncct);
1118   return 0;
1119 }
1120 static int
1121 noit_console_config_section(noit_console_closure_t ncct,
1122                             int argc, char **argv,
1123                             noit_console_state_t *state, void *closure) {
1124   const char *err = "internal error";
1125   char *path, xpath[1024];
1126   noit_conf_t_userdata_t *info;
1127   xmlXPathObjectPtr pobj = NULL;
1128   xmlXPathContextPtr xpath_ctxt = NULL;
1129   xmlNodePtr node = NULL, newnode;
1130   vpsized_int delete = (vpsized_int)closure;
1131
1132   noit_conf_xml_xpath(NULL, &xpath_ctxt);
1133   if(argc != 1) {
1134     nc_printf(ncct, "requires one argument\n");
1135     return -1;
1136   }
1137   if(strchr(argv[0], '/')) {
1138     nc_printf(ncct, "invalid section name\n");
1139     return -1;
1140   }
1141   if(!strcmp(argv[0], "check") ||
1142      !strcmp(argv[0], "noit") ||
1143      !strcmp(argv[0], "filterset") ||
1144      !strcmp(argv[0], "config")) {
1145     nc_printf(ncct, "%s is reserved.\n", argv[0]);
1146     return -1;
1147   }
1148   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
1149   if(!strcmp(info->path, "/")) {
1150     nc_printf(ncct, "manipulation of toplevel section disallowed\n");
1151     return -1;
1152   }
1153
1154   if(delete) {
1155     /* We cannot delete if we have checks */
1156     snprintf(xpath, sizeof(xpath), "/%s%s/%s//check", root_node_name,
1157              info->path, argv[0]);
1158     pobj = xmlXPathEval((xmlChar *)xpath, xpath_ctxt);
1159     if(!pobj || pobj->type != XPATH_NODESET ||
1160        !xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
1161       err = "cannot delete section, has checks";
1162       goto bad;
1163     }
1164     if(pobj) xmlXPathFreeObject(pobj);
1165   }
1166
1167   snprintf(xpath, sizeof(xpath), "/%s%s/%s", root_node_name,
1168            info->path, argv[0]);
1169   pobj = xmlXPathEval((xmlChar *)xpath, xpath_ctxt);
1170   if(!pobj || pobj->type != XPATH_NODESET) {
1171     err = "internal error: cannot detect section";
1172     goto bad;
1173   }
1174   if(!delete && !xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
1175     if(xmlXPathNodeSetGetLength(pobj->nodesetval) == 1) {
1176       node = xmlXPathNodeSetItem(pobj->nodesetval, 0);
1177       if(info->path) free(info->path);
1178       info->path = strdup((char *)xmlGetNodePath(node) +
1179                           1 + strlen(root_node_name));
1180       goto cdout;
1181     }
1182     err = "cannot create section";
1183     goto bad;
1184   }
1185   if(delete && xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
1186     err = "no such section";
1187     goto bad;
1188   }
1189   if(delete) {
1190     node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
1191     xmlUnlinkNode(node);
1192     noit_conf_mark_changed();
1193     return 0;
1194   }
1195   if(pobj) xmlXPathFreeObject(pobj);
1196   pobj = NULL;
1197
1198   if(!strcmp(argv[0],"include")) {
1199     err = "include is a reserved section name";
1200     goto bad;
1201   }
1202   path = strcmp(info->path, "/") ? info->path : "";
1203   snprintf(xpath, sizeof(xpath), "/%s%s", root_node_name, path);
1204   pobj = xmlXPathEval((xmlChar *)xpath, xpath_ctxt);
1205   if(!pobj || pobj->type != XPATH_NODESET ||
1206      xmlXPathNodeSetGetLength(pobj->nodesetval) != 1) {
1207     err = "path invalid?";
1208     goto bad;
1209   }
1210   node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
1211   if((newnode = xmlNewChild(node, NULL, (xmlChar *)argv[0], NULL)) != NULL) {
1212     noit_conf_mark_changed();
1213     if(info->path) free(info->path);
1214     info->path = strdup((char *)xmlGetNodePath(newnode) + 1 +
1215                         strlen(root_node_name));
1216   }
1217   else {
1218     err = "failed to create section";
1219     goto bad;
1220   }
1221  cdout:
1222   if(pobj) xmlXPathFreeObject(pobj);
1223   return 0;
1224  bad:
1225   if(pobj) xmlXPathFreeObject(pobj);
1226   nc_printf(ncct, "%s\n", err);
1227   return -1;
1228 }
1229
1230 int
1231 noit_console_generic_show(noit_console_closure_t ncct,
1232                           int argc, char **argv,
1233                           noit_console_state_t *state, void *closure) {
1234   int i, cnt, titled = 0, cliplen = 0;
1235   const char *path = "", *basepath = NULL;
1236   char xpath[1024];
1237   noit_conf_t_userdata_t *info = NULL;
1238   xmlXPathObjectPtr pobj = NULL;
1239   xmlXPathContextPtr xpath_ctxt = NULL, current_ctxt;
1240   xmlDocPtr master_config = NULL;
1241   xmlNodePtr node = NULL;
1242
1243   noit_conf_xml_xpath(&master_config, &xpath_ctxt);
1244   if(argc > 1) {
1245     nc_printf(ncct, "too many arguments\n");
1246     return -1;
1247   }
1248
1249   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
1250   if(info && info->path) path = basepath = info->path;
1251   if(!info && argc == 0) {
1252     nc_printf(ncct, "argument required when not in configuration mode\n");
1253     return -1;
1254   }
1255
1256   if(argc == 1) path = argv[0];
1257   if(!basepath) basepath = path;
1258
1259   /* { / } is a special case */
1260   if(!strcmp(basepath, "/")) basepath = "";
1261   if(!strcmp(path, "/")) path = "";
1262
1263   if(!master_config) {
1264     nc_printf(ncct, "no config\n");
1265     return -1;
1266   }
1267
1268   /* { / } is the only path that will end with a /
1269    * in XPath { / / * } means something _entirely different than { / * }
1270    * Ever notice how it is hard to describe xpath in C comments?
1271    */
1272   /* We don't want to show the root node */
1273   cliplen = strlen(root_node_name) + 2; /* /name/ */
1274
1275   /* If we are in configuration mode
1276    * and we are without an argument or the argument is absolute,
1277    * clip the current path off */
1278   if(info && (argc == 0 || path[0] != '/')) cliplen += strlen(basepath);
1279   if(!path[0] || path[0] == '/') /* base only, or absolute path requested */
1280     snprintf(xpath, sizeof(xpath), "/%s%s/@*", root_node_name, path);
1281   else
1282     snprintf(xpath, sizeof(xpath), "/%s%s/%s/@*", root_node_name,
1283              basepath, path);
1284
1285   current_ctxt = xpath_ctxt;
1286   pobj = xmlXPathEval((xmlChar *)xpath, current_ctxt);
1287   if(!pobj || pobj->type != XPATH_NODESET) {
1288     nc_printf(ncct, "no such object\n");
1289     goto bad;
1290   }
1291   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
1292   titled = 0;
1293   for(i=0; i<cnt; i++) {
1294     node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, i);
1295     if(node->children && node->children == xmlGetLastChild(node) &&
1296       xmlNodeIsText(node->children)) {
1297       if(!titled++) nc_printf(ncct, "== Section Settings ==\n");
1298       nc_printf(ncct, "%s: %s\n", xmlGetNodePath(node) + cliplen,
1299                 xmlXPathCastNodeToString(node->children));
1300     }
1301   }
1302   xmlXPathFreeObject(pobj);
1303
1304   /* _shorten string_ turning last { / @ * } to { / * } */
1305   if(!path[0] || path[0] == '/') /* base only, or absolute path requested */
1306     snprintf(xpath, sizeof(xpath), "/%s%s/*", root_node_name, path);
1307   else
1308     snprintf(xpath, sizeof(xpath), "/%s%s/%s/*",
1309              root_node_name, basepath, path);
1310   pobj = xmlXPathEval((xmlChar *)xpath, current_ctxt);
1311   if(!pobj || pobj->type != XPATH_NODESET) {
1312     nc_printf(ncct, "no such object\n");
1313     goto bad;
1314   }
1315   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
1316   titled = 0;
1317   for(i=0; i<cnt; i++) {
1318     node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, i);
1319     if(!(node->children && node->children == xmlGetLastChild(node) &&
1320          xmlNodeIsText(node->children))) {
1321       if(!titled++) nc_printf(ncct, "== Subsections ==\n");
1322       nc_printf(ncct, "%s\n", xmlGetNodePath(node) + cliplen);
1323     }
1324   }
1325   xmlXPathFreeObject(pobj);
1326   return 0;
1327  bad:
1328   if(pobj) xmlXPathFreeObject(pobj);
1329   return -1;
1330 }
1331 int
1332 noit_console_config_cd(noit_console_closure_t ncct,
1333                        int argc, char **argv,
1334                        noit_console_state_t *state, void *closure) {
1335   const char *err = "internal error";
1336   char *path, xpath[1024];
1337   noit_conf_t_userdata_t *info;
1338   xmlXPathObjectPtr pobj = NULL;
1339   xmlXPathContextPtr xpath_ctxt = NULL, current_ctxt;
1340   xmlNodePtr node = NULL;
1341   char *dest;
1342
1343   noit_conf_xml_xpath(NULL, &xpath_ctxt);
1344   if(argc != 1 && !closure) {
1345     nc_printf(ncct, "requires one argument\n");
1346     return -1;
1347   }
1348   dest = argc ? argv[0] : (char *)closure;
1349   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
1350   if(dest[0] == '/')
1351     snprintf(xpath, sizeof(xpath), "/%s%s", root_node_name, dest);
1352   else {
1353     snprintf(xpath, sizeof(xpath), "/%s%s/%s", root_node_name,
1354              info->path, dest);
1355   }
1356   if(xpath[strlen(xpath)-1] == '/') xpath[strlen(xpath)-1] = '\0';
1357
1358   current_ctxt = xpath_ctxt;
1359   pobj = xmlXPathEval((xmlChar *)xpath, current_ctxt);
1360   if(!pobj || pobj->type != XPATH_NODESET ||
1361      xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
1362     err = "no such section";
1363     goto bad;
1364   }
1365   if(xmlXPathNodeSetGetLength(pobj->nodesetval) > 1) {
1366     err = "ambiguous section";
1367     goto bad;
1368   }
1369
1370   node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
1371   if(!node) {
1372     err = "internal XML error";
1373     goto bad;
1374   }
1375   if(!strcmp((char *)node->name, "check") ||
1376      !strcmp((char *)node->name, "noit") ||
1377      !strcmp((char *)node->name, "filterset") ||
1378      !strcmp((char *)node->name, "config")) {
1379     err = "reserved word";
1380     goto bad;
1381   }
1382   path = (char *)xmlGetNodePath(node);
1383   if(strlen(path) < strlen(root_node_name) + 1 ||
1384      strncmp(path + 1, root_node_name, strlen(root_node_name)) ||
1385      (path[strlen(root_node_name) + 1] != '/' &&
1386       path[strlen(root_node_name) + 1] != '\0')) {
1387     err = "new path outside out tree";
1388     goto bad;
1389   }
1390   free(info->path);
1391   if(!strcmp(path + 1, root_node_name))
1392     info->path = strdup("/");
1393   else
1394     info->path = strdup((char *)xmlGetNodePath(node) + 1 +
1395                         strlen(root_node_name));
1396   if(pobj) xmlXPathFreeObject(pobj);
1397   if(closure) noit_console_state_pop(ncct, argc, argv, NULL, NULL);
1398   return 0;
1399  bad:
1400   if(pobj) xmlXPathFreeObject(pobj);
1401   nc_printf(ncct, "%s [%s]\n", err, xpath);
1402   return -1;
1403 }
1404
1405 char *
1406 conf_t_prompt(EditLine *el) {
1407   noit_console_closure_t ncct;
1408   noit_conf_t_userdata_t *info;
1409   static char *tl = "noit(conf)# ";
1410   static char *pfmt = "noit(conf:%s%s)# ";
1411   int path_len, max_len;
1412
1413   el_get(el, EL_USERDATA, (void *)&ncct);
1414   if(!ncct) return tl;
1415   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
1416   if(!info) return tl;
1417
1418   path_len = strlen(info->path);
1419   max_len = sizeof(info->prompt) - (strlen(pfmt) - 4 /* %s%s */) - 1 /* \0 */;
1420   if(path_len > max_len)
1421     snprintf(info->prompt, sizeof(info->prompt),
1422              pfmt, "...", info->path + path_len - max_len + 3 /* ... */);
1423   else
1424     snprintf(info->prompt, sizeof(info->prompt), pfmt, "", info->path);
1425   return info->prompt;
1426 }
1427
1428 #define NEW_STATE(a) (a) = noit_console_state_alloc()
1429 #define ADD_CMD(a,cmd,func,ac,ss,c) \
1430   noit_console_state_add_cmd((a), \
1431     NCSCMD(cmd, func, ac, ss, c))
1432 #define DELEGATE_CMD(a,cmd,ac,ss) \
1433   noit_console_state_add_cmd((a), \
1434     NCSCMD(cmd, noit_console_state_delegate, ac, ss, NULL))
1435
1436 void noit_console_conf_init() {
1437   noit_console_state_t *tl, *_conf_state, *_conf_t_state,
1438                        *_write_state, *_unset_state;
1439
1440   tl = noit_console_state_initial();
1441
1442   /* write <terimal|memory|file> */
1443   NEW_STATE(_write_state);
1444   ADD_CMD(_write_state, "terminal", noit_conf_write_terminal, NULL, NULL, NULL);
1445   ADD_CMD(_write_state, "file", noit_conf_write_file_console, NULL, NULL, NULL);
1446   /* write memory?  It's to a file, but I like router syntax */
1447   ADD_CMD(_write_state, "memory", noit_conf_write_file_console, NULL, NULL, NULL);
1448
1449   NEW_STATE(_unset_state);
1450   ADD_CMD(_unset_state, "section",
1451           noit_console_config_section, NULL, NULL, (void *)1);
1452
1453   NEW_STATE(_conf_t_state);
1454   _conf_t_state->console_prompt_function = conf_t_prompt;
1455   noit_console_state_add_cmd(_conf_t_state, &console_command_exit);
1456
1457   ADD_CMD(_conf_t_state, "ls", noit_console_generic_show, NULL, NULL, NULL);
1458   ADD_CMD(_conf_t_state, "cd", noit_console_config_cd, NULL, NULL, NULL);
1459   ADD_CMD(_conf_t_state, "section",
1460           noit_console_config_section, NULL, NULL, (void *)0);
1461
1462   DELEGATE_CMD(_conf_t_state, "write",
1463                noit_console_opt_delegate, _write_state);
1464   DELEGATE_CMD(_conf_t_state, "no", noit_console_opt_delegate, _unset_state);
1465
1466   NEW_STATE(_conf_state);
1467   ADD_CMD(_conf_state, "terminal",
1468           noit_console_state_conf_terminal, NULL, _conf_t_state, NULL);
1469
1470   ADD_CMD(tl, "configure",
1471           noit_console_state_delegate, noit_console_opt_delegate,
1472           _conf_state, NULL);
1473   ADD_CMD(tl, "write",
1474           noit_console_state_delegate, noit_console_opt_delegate,
1475           _write_state, NULL);
1476 }
1477
Note: See TracBrowser for help on using the browser.