root/src/noit_conf.c

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

Fix warning-now-error

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