root/src/noit_conf.c

Revision c6c11c3827cd812cc0f9cc92cf6de8140b859416, 47.3 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 2 years ago)

Support generic module configuration with using XML namespaces and
allow modules to register themselves as as interested in such for
all checks.

  • 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     xmlFree(path);
310     config_include_nodes[i].doc = xmlParseFile(infile);
311     if(config_include_nodes[i].doc) {
312       xmlNodePtr n;
313       config_include_nodes[i].insertion_point = node;
314       config_include_nodes[i].root = xmlDocGetRootElement(config_include_nodes[i].doc);
315       config_include_nodes[i].old_children = node->children;
316       node->children = config_include_nodes[i].root->children;
317       for(n=node->children; n; n = n->next)
318         n->parent = config_include_nodes[i].insertion_point;
319     }
320     else {
321       noitL(noit_error, "Could not load: '%s'\n", infile);
322       rv = -1;
323     }
324     free(infile);
325   }
326   config_include_cnt = cnt;
327   noitL(noit_debug, "Processed %d includes\n", config_include_cnt);
328  out:
329   if(pobj) xmlXPathFreeObject(pobj);
330   if(mix_ctxt) xmlXPathFreeContext(mix_ctxt);
331   return rv;
332 }
333
334 static int noit_conf_load_internal(const char *path) {
335   int rv = 0;
336   xmlDocPtr new_config;
337   xmlNodePtr root;
338   new_config = xmlParseFile(path);
339   if(new_config) {
340     root = xmlDocGetRootElement(new_config);
341     if(root_node_name) free(root_node_name);
342     root_node_name = strdup((char *)root->name);
343
344     if(master_config) {
345       /* separate all includes */
346       noit_conf_magic_separate(master_config);
347       xmlFreeDoc(master_config);
348     }
349     if(xpath_ctxt) xmlXPathFreeContext(xpath_ctxt);
350
351     master_config = new_config;
352     /* mixin all the includes */
353     if(noit_conf_magic_mix(path, master_config)) rv = -1;
354
355     xpath_ctxt = xmlXPathNewContext(master_config);
356     if(path != master_config_file)
357       if(realpath(path, master_config_file) == NULL)
358         noitL(noit_error, "realpath failed: %s\n", strerror(errno));
359     noit_conf_mark_changed();
360     return rv;
361   }
362   rv = -1;
363   return rv;
364 }
365 int noit_conf_load(const char *path) {
366   int rv;
367   XML2LOG(noit_error);
368   rv = noit_conf_load_internal(path);
369   XML2LOG(xml_debug);
370   return rv;
371 }
372
373 char *noit_conf_config_filename() {
374   return strdup(master_config_file);
375 }
376
377 int noit_conf_xml_xpath(xmlDocPtr *mc, xmlXPathContextPtr *xp) {
378   if(mc) *mc = master_config;
379   if(xp) *xp = xpath_ctxt;
380   return 0;
381 }
382 int noit_conf_save(const char *path) {
383   return -1;
384 }
385
386 void noit_conf_get_elements_into_hash(noit_conf_section_t section,
387                                       const char *path,
388                                       noit_hash_table *table,
389                                       const char *namespace) {
390   int i, cnt;
391   xmlXPathObjectPtr pobj = NULL;
392   xmlXPathContextPtr current_ctxt;
393   xmlNodePtr current_node = (xmlNodePtr)section;
394   xmlNodePtr node;
395
396   current_ctxt = xpath_ctxt;
397   if(current_node) {
398     current_ctxt = xmlXPathNewContext(master_config);
399     current_ctxt->node = current_node;
400   }
401   pobj = xmlXPathEval((xmlChar *)path, 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   for(i=0; i<cnt; i++) {
407     char *value;
408     node = xmlXPathNodeSetItem(pobj->nodesetval, i);
409     if(namespace && node->ns && !strcmp((char *)node->ns->prefix, namespace)) {
410       value = (char *)xmlXPathCastNodeToString(node);
411       noit_hash_replace(table,
412                         strdup((char *)node->name), strlen((char *)node->name),
413                         strdup(value), free, free);
414       xmlFree(value);
415     }
416     else if(!namespace && !node->ns) {
417       value = (char *)xmlXPathCastNodeToString(node);
418       noit_hash_replace(table,
419                         strdup((char *)node->name), strlen((char *)node->name),
420                         strdup(value), free, free);
421       xmlFree(value);
422     }
423   }
424  out:
425   if(pobj) xmlXPathFreeObject(pobj);
426   if(current_ctxt && current_ctxt != xpath_ctxt)
427     xmlXPathFreeContext(current_ctxt);
428 }
429 void noit_conf_get_into_hash(noit_conf_section_t section,
430                              const char *path,
431                              noit_hash_table *table,
432                              const char *namespace) {
433   unsigned int cnt;
434   xmlXPathObjectPtr pobj = NULL;
435   xmlXPathContextPtr current_ctxt;
436   xmlNodePtr current_node = (xmlNodePtr)section;
437   xmlNodePtr node, parent_node;
438   char xpath_expr[1024];
439   char *inheritid;
440
441   current_ctxt = xpath_ctxt;
442   if(current_node) {
443     current_ctxt = xmlXPathNewContext(master_config);
444     current_ctxt->node = current_node;
445   }
446   if(path[0] == '/')
447     strlcpy(xpath_expr, path, sizeof(xpath_expr));
448   else
449     snprintf(xpath_expr, sizeof(xpath_expr),
450              "ancestor-or-self::node()/%s", path);
451   pobj = xmlXPathEval((xmlChar *)xpath_expr, current_ctxt);
452   if(!pobj) goto out;
453   if(pobj->type != XPATH_NODESET) goto out;
454   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto out;
455   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
456   /* These are in the order of root to leaf
457    * We want to recurse... apply:
458    *   1. our parent's config
459    *   2. our "inherit" config if it exists.
460    *   3. our config.
461    */
462   node = xmlXPathNodeSetItem(pobj->nodesetval, cnt-1);
463   /* 1. */
464   if(cnt > 1 && node) {
465     parent_node = xmlXPathNodeSetItem(pobj->nodesetval, cnt-2);
466     if(parent_node != current_node)
467       noit_conf_get_into_hash(parent_node, (const char *)node->name, table, namespace);
468   }
469   /* 2. */
470   inheritid = (char *)xmlGetProp(node, (xmlChar *)"inherit");
471   if(inheritid) {
472     snprintf(xpath_expr, sizeof(xpath_expr), "//*[@id=\"%s\"]", inheritid);
473     noit_conf_get_into_hash(NULL, xpath_expr, table, namespace);
474     xmlFree(inheritid);
475   }
476   /* 3. */
477   noit_conf_get_elements_into_hash(node, "*", table, namespace);
478
479  out:
480   if(pobj) xmlXPathFreeObject(pobj);
481   if(current_ctxt && current_ctxt != xpath_ctxt)
482     xmlXPathFreeContext(current_ctxt);
483 }
484 noit_hash_table *noit_conf_get_namespaced_hash(noit_conf_section_t section,
485                                                const char *path, const char *ns) {
486   noit_hash_table *table = NULL;
487
488   table = calloc(1, sizeof(*table));
489   noit_conf_get_into_hash(section, path, table, ns);
490   if(table->size == 0) {
491     noit_hash_destroy(table, free, free);
492     free(table);
493     table = NULL;
494   }
495   return table;
496 }
497 noit_hash_table *noit_conf_get_hash(noit_conf_section_t section,
498                                     const char *path) {
499   noit_hash_table *table = NULL;
500
501   table = calloc(1, sizeof(*table));
502   noit_conf_get_into_hash(section, path, table, NULL);
503   return table;
504 }
505 noit_conf_section_t noit_conf_get_section(noit_conf_section_t section,
506                                           const char *path) {
507   noit_conf_section_t subsection = NULL;
508   xmlXPathObjectPtr pobj = NULL;
509   xmlXPathContextPtr current_ctxt;
510   xmlNodePtr current_node = (xmlNodePtr)section;
511
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   subsection = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
522  out:
523   if(pobj) xmlXPathFreeObject(pobj);
524   if(current_ctxt && current_ctxt != xpath_ctxt)
525     xmlXPathFreeContext(current_ctxt);
526   return subsection;
527 }
528 noit_conf_section_t *noit_conf_get_sections(noit_conf_section_t section,
529                                             const char *path,
530                                             int *cnt) {
531   int i;
532   noit_conf_section_t *sections = NULL;
533   xmlXPathObjectPtr pobj = NULL;
534   xmlXPathContextPtr current_ctxt;
535   xmlNodePtr current_node = (xmlNodePtr)section;
536
537   *cnt = 0;
538   current_ctxt = xpath_ctxt;
539   if(current_node) {
540     current_ctxt = xmlXPathNewContext(master_config);
541     current_ctxt->node = current_node;
542   }
543   pobj = xmlXPathEval((xmlChar *)path, current_ctxt);
544   if(!pobj) goto out;
545   if(pobj->type != XPATH_NODESET) goto out;
546   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto out;
547   *cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
548   sections = calloc(*cnt, sizeof(*sections));
549   for(i=0; i<*cnt; i++)
550     sections[i] = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, i);
551  out:
552   if(pobj) xmlXPathFreeObject(pobj);
553   if(current_ctxt && current_ctxt != xpath_ctxt)
554     xmlXPathFreeContext(current_ctxt);
555   return sections;
556 }
557 int _noit_conf_get_string(noit_conf_section_t section, xmlNodePtr *vnode,
558                           const char *path, char **value) {
559   const char *str;
560   int rv = 1;
561   unsigned int i;
562   xmlXPathObjectPtr pobj = NULL;
563   xmlXPathContextPtr current_ctxt;
564   xmlNodePtr current_node = (xmlNodePtr)section;
565
566   current_ctxt = xpath_ctxt;
567   if(current_node) {
568     current_ctxt = xmlXPathNewContext(master_config);
569     current_ctxt->node = current_node;
570   }
571   pobj = xmlXPathEval((xmlChar *)path, current_ctxt);
572   if(pobj) {
573     xmlNodePtr node;
574     switch(pobj->type) {
575       case XPATH_NODESET:
576         if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto fallback;
577         i = xmlXPathNodeSetGetLength(pobj->nodesetval);
578         node = xmlXPathNodeSetItem(pobj->nodesetval, i-1);
579         if(vnode) *vnode = node;
580         *value = (char *)xmlXPathCastNodeToString(node);
581         break;
582       default:
583         *value = (char *)xmlXPathCastToString(pobj);
584     }
585     goto found;
586   }
587  fallback:
588   if(noit_hash_retr_str(&_compiled_fallback,
589                         path, strlen(path), &str)) {
590     *value = (char *)xmlStrdup((xmlChar *)str);
591     goto found;
592   }
593   rv = 0;
594  found:
595   if(pobj) xmlXPathFreeObject(pobj);
596   if(current_ctxt && current_ctxt != xpath_ctxt)
597     xmlXPathFreeContext(current_ctxt);
598   return rv;
599 }
600 int noit_conf_get_uuid(noit_conf_section_t section,
601                        const char *path, uuid_t out) {
602   char *str;
603   if(_noit_conf_get_string(section,NULL,path,&str)) {
604     if(uuid_parse(str, out) == 0) return 1;
605     return 0;
606   }
607   return 0;
608 }
609 int noit_conf_get_string(noit_conf_section_t section,
610                          const char *path, char **value) {
611   char *str;
612   if(_noit_conf_get_string(section,NULL,path,&str)) {
613     *value = strdup(str);
614     xmlFree(str);
615     return 1;
616   }
617   return 0;
618 }
619 int noit_conf_get_stringbuf(noit_conf_section_t section,
620                             const char *path, char *buf, int len) {
621   char *str;
622   if(_noit_conf_get_string(section,NULL,path,&str)) {
623     strlcpy(buf, str, len);
624     xmlFree(str);
625     return 1;
626   }
627   return 0;
628 }
629 int noit_conf_set_string(noit_conf_section_t section,
630                          const char *path, const char *value) {
631   noit_hash_replace(&_tmp_config,
632                     strdup(path), strlen(path), (void *)strdup(value),
633                     free, free);
634   return 1;
635 }
636 int noit_conf_string_to_int(const char *str) {
637   int base = 10;
638   if(!str) return 0;
639   if(str[0] == '0') {
640     if(str[1] == 'x') base = 16;
641     else base = 8;
642   }
643   return strtol(str, NULL, base);
644 }
645 int noit_conf_get_int(noit_conf_section_t section,
646                       const char *path, int *value) {
647   char *str;
648   if(_noit_conf_get_string(section,NULL,path,&str)) {
649     *value = (int)noit_conf_string_to_int(str);
650     xmlFree(str);
651     return 1;
652   }
653   return 0;
654 }
655 int noit_conf_set_int(noit_conf_section_t section,
656                       const char *path, int value) {
657   char buffer[32];
658   snprintf(buffer, 32, "%d", value);
659   return noit_conf_set_string(section,path,buffer);
660 }
661 float noit_conf_string_to_float(const char *str) {
662   if(!str) return 0.0;
663   return atof(str);
664 }
665 int noit_conf_get_float(noit_conf_section_t section,
666                         const char *path, float *value) {
667   char *str;
668   if(_noit_conf_get_string(section,NULL,path,&str)) {
669     *value = noit_conf_string_to_float(str);
670     xmlFree(str);
671     return 1;
672   }
673   return 0;
674 }
675 int noit_conf_set_float(noit_conf_section_t section,
676                         const char *path, float value) {
677   char buffer[32];
678   snprintf(buffer, 32, "%f", value);
679   return noit_conf_set_string(section,path,buffer);
680 }
681 noit_boolean noit_conf_string_to_boolean(const char *str) {
682   if(!str) return noit_false;
683   if(!strcasecmp(str, "true") || !strcasecmp(str, "on")) return noit_true;
684   return noit_false;
685 }
686 int noit_conf_get_boolean(noit_conf_section_t section,
687                           const char *path, noit_boolean *value) {
688   char *str;
689   if(_noit_conf_get_string(section,NULL,path,&str)) {
690     *value = noit_conf_string_to_boolean(str);
691     xmlFree(str);
692     return 1;
693   }
694   return 0;
695 }
696 int noit_conf_set_boolean(noit_conf_section_t section,
697                           const char *path, noit_boolean value) {
698   if(value == noit_true)
699     return noit_conf_set_string(section,path,"true");
700   return noit_conf_set_string(section,path,"false");
701 }
702
703 struct config_line_vstr {
704   char *buff;
705   int raw_len;
706   int len;
707   int allocd;
708   enum { CONFIG_RAW = 0, CONFIG_COMPRESSED, CONFIG_B64 } target, encoded;
709 };
710 static int
711 noit_config_log_write_xml(void *vstr, const char *buffer, int len) {
712   struct config_line_vstr *clv = vstr;
713   assert(clv->encoded == CONFIG_RAW);
714   if(!clv->buff) {
715     clv->allocd = 8192;
716     clv->buff = malloc(clv->allocd);
717   }
718   while(len + clv->len > clv->allocd) {
719     char *newbuff;
720     int newsize = clv->allocd;
721     newsize <<= 1;
722     newbuff = realloc(clv->buff, newsize);
723     if(!newbuff) {
724       return -1;
725     }
726     clv->allocd = newsize;
727     clv->buff = newbuff;
728   }
729   memcpy(clv->buff + clv->len, buffer, len);
730   clv->len += len;
731   return len;
732 }
733 static int
734 noit_config_log_close_xml(void *vstr) {
735   struct config_line_vstr *clv = vstr;
736   uLong initial_dlen, dlen;
737   char *compbuff, *b64buff;
738
739   if(clv->buff == NULL) {
740     clv->encoded = clv->target;
741     return 0;
742   }
743   clv->raw_len = clv->len;
744   assert(clv->encoded == CONFIG_RAW);
745   if(clv->encoded == clv->target) return 0;
746
747   /* Compress */
748   initial_dlen = dlen = compressBound(clv->len);
749   compbuff = malloc(initial_dlen);
750   if(!compbuff) return -1;
751   if(Z_OK != compress2((Bytef *)compbuff, &dlen,
752                        (Bytef *)clv->buff, clv->len, 9)) {
753     noitL(noit_error, "Error compressing config for transmission.\n");
754     free(compbuff);
755     return -1;
756   }
757   free(clv->buff);
758   clv->buff = compbuff;
759   clv->allocd = initial_dlen;
760   clv->len = dlen;
761   clv->encoded = CONFIG_COMPRESSED;
762   if(clv->encoded == clv->target) return 0;
763
764   /* Encode */
765   initial_dlen = ((clv->len + 2) / 3) * 4;
766   b64buff = malloc(initial_dlen);
767   dlen = noit_b64_encode((unsigned char *)clv->buff, clv->len,
768                          b64buff, initial_dlen);
769   if(dlen == 0) {
770     free(b64buff);
771     return -1;
772   }
773   free(clv->buff);
774   clv->buff = b64buff;
775   clv->allocd = initial_dlen;
776   clv->len = dlen;
777   clv->encoded = CONFIG_B64;
778   if(clv->encoded == clv->target) return 0;
779   return -1;
780 }
781
782 int
783 noit_conf_reload(noit_console_closure_t ncct,
784                  int argc, char **argv,
785                  noit_console_state_t *state, void *closure) {
786   XML2CONSOLE(ncct);
787   if(noit_conf_load_internal(master_config_file)) {
788     XML2LOG(xml_debug);
789     nc_printf(ncct, "error loading config\n");
790     return -1;
791   }
792   XML2LOG(xml_debug);
793   return 0;
794 }
795 int
796 noit_conf_write_terminal(noit_console_closure_t ncct,
797                          int argc, char **argv,
798                          noit_console_state_t *state, void *closure) {
799   xmlOutputBufferPtr out;
800   xmlCharEncodingHandlerPtr enc;
801   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
802   out = xmlOutputBufferCreateIO(noit_console_write_xml,
803                                 noit_console_close_xml,
804                                 ncct, enc);
805   noit_conf_kansas_city_shuffle_undo(master_config);
806   xmlSaveFormatFileTo(out, master_config, "utf8", 1);
807   noit_conf_kansas_city_shuffle_redo(master_config);
808   return 0;
809 }
810 int
811 noit_conf_write_file_console(noit_console_closure_t ncct,
812                              int argc, char **argv,
813                              noit_console_state_t *state, void *closure) {
814   int rv;
815   char *err = NULL;
816   rv = noit_conf_write_file(&err);
817   nc_printf(ncct, "%s\n", err);
818   if(err) free(err);
819   return rv;
820 }
821 int
822 noit_conf_write_file(char **err) {
823   int fd, len;
824   char master_file_tmp[PATH_MAX];
825   char errstr[1024];
826   xmlOutputBufferPtr out;
827   xmlCharEncodingHandlerPtr enc;
828   struct stat st;
829   mode_t mode = 0640; /* the default */
830
831   if(stat(master_config_file, &st) == 0)
832     mode = st.st_mode;
833   snprintf(master_file_tmp, sizeof(master_file_tmp),
834            "%s.tmp", master_config_file);
835   unlink(master_file_tmp);
836   fd = open(master_file_tmp, O_CREAT|O_EXCL|O_WRONLY, mode);
837   if(fd < 0) {
838     snprintf(errstr, sizeof(errstr), "Failed to open tmp file: %s",
839              strerror(errno));
840     if(err) *err = strdup(errstr);
841     return -1;
842   }
843   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
844   out = xmlOutputBufferCreateFd(fd, enc);
845   if(!out) {
846     close(fd);
847     unlink(master_file_tmp);
848     if(err) *err = strdup("internal error: OutputBufferCreate failed");
849     return -1;
850   }
851   noit_conf_kansas_city_shuffle_undo(master_config);
852   len = xmlSaveFormatFileTo(out, master_config, "utf8", 1);
853   noit_conf_kansas_city_shuffle_redo(master_config);
854   close(fd);
855   if(len <= 0) {
856     if(err) *err = strdup("internal error: writing to tmp file failed.");
857     return -1;
858   }
859   if(rename(master_file_tmp, master_config_file) != 0) {
860     snprintf(errstr, sizeof(errstr), "Failed to replace file: %s",
861              strerror(errno));
862     if(err) *err = strdup(errstr);
863     return -1;
864   }
865   snprintf(errstr, sizeof(errstr), "%d bytes written.", len);
866   if(err) *err = strdup(errstr);
867   return 0;
868 }
869 char *
870 noit_conf_xml_in_mem(size_t *len) {
871   struct config_line_vstr *clv;
872   xmlOutputBufferPtr out;
873   xmlCharEncodingHandlerPtr enc;
874   char *rv;
875
876   clv = calloc(1, sizeof(*clv));
877   clv->target = CONFIG_RAW;
878   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
879   out = xmlOutputBufferCreateIO(noit_config_log_write_xml,
880                                 noit_config_log_close_xml,
881                                 clv, enc);
882   noit_conf_kansas_city_shuffle_undo(master_config);
883   xmlSaveFormatFileTo(out, master_config, "utf8", 1);
884   noit_conf_kansas_city_shuffle_redo(master_config);
885   if(clv->encoded != CONFIG_RAW) {
886     noitL(noit_error, "Error logging configuration\n");
887     if(clv->buff) free(clv->buff);
888     free(clv);
889     return NULL;
890   }
891   rv = clv->buff;
892   *len = clv->len;
893   free(clv);
894   return rv;
895 }
896
897 int
898 noit_conf_write_log() {
899   static u_int32_t last_write_gen = 0;
900   static noit_log_stream_t config_log = NULL;
901   struct timeval __now;
902   xmlOutputBufferPtr out;
903   xmlCharEncodingHandlerPtr enc;
904   struct config_line_vstr *clv;
905   SETUP_LOG(config, return -1);
906
907   /* We know we haven't changed */
908   if(last_write_gen == __config_gen) return 0;
909
910   gettimeofday(&__now, NULL);
911   clv = calloc(1, sizeof(*clv));
912   clv->target = CONFIG_B64;
913   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
914   out = xmlOutputBufferCreateIO(noit_config_log_write_xml,
915                                 noit_config_log_close_xml,
916                                 clv, enc);
917   noit_conf_kansas_city_shuffle_undo(master_config);
918   xmlSaveFormatFileTo(out, master_config, "utf8", 1);
919   noit_conf_kansas_city_shuffle_redo(master_config);
920   if(clv->encoded != CONFIG_B64) {
921     noitL(noit_error, "Error logging configuration\n");
922     if(clv->buff) free(clv->buff);
923     free(clv);
924     return -1;
925   }
926   noitL(config_log, "n\t%lu.%03lu\t%d\t%.*s\n",
927         (unsigned long int)__now.tv_sec,
928         (unsigned long int)__now.tv_usec / 1000UL, clv->raw_len,
929         clv->len, clv->buff);
930   free(clv->buff);
931   free(clv);
932   last_write_gen = __config_gen;
933   return 0;
934 }
935
936 struct log_rotate_crutch {
937   noit_log_stream_t ls;
938   int seconds;
939   size_t max_size;
940 };
941
942 static int
943 noit_conf_log_rotate_size(eventer_t e, int mask, void *closure,
944                           struct timeval *now) {
945   struct log_rotate_crutch *lrc = closure;
946   if(noit_log_stream_written(lrc->ls) > lrc->max_size) {
947     noit_log_stream_rename(lrc->ls, NOIT_LOG_RENAME_AUTOTIME);
948     noit_log_stream_reopen(lrc->ls);
949   }
950   /* Yes the 5 is arbitrary, but this is cheap */
951   eventer_add_in_s_us(noit_conf_log_rotate_size, closure, 5, 0);
952   return 0;
953 }
954 static int
955 noit_conf_log_rotate_time(eventer_t e, int mask, void *closure,
956                           struct timeval *now) {
957   struct timeval lnow;
958   eventer_t newe;
959   struct log_rotate_crutch *lrc = closure;
960
961   if(now) {
962     noit_log_stream_rename(lrc->ls, NOIT_LOG_RENAME_AUTOTIME);
963     noit_log_stream_reopen(lrc->ls);
964   }
965  
966   newe = eventer_alloc();
967   newe->closure = closure;
968   if(!now) { gettimeofday(&lnow, NULL); now = &lnow; }
969   if(e)
970     memcpy(&newe->whence, &e->whence, sizeof(newe->whence));
971   else if(now) {
972     memcpy(&newe->whence, now, sizeof(newe->whence));
973     newe->whence.tv_sec = (newe->whence.tv_sec / lrc->seconds) * lrc->seconds;
974   }
975   newe->whence.tv_sec += lrc->seconds;
976   newe->mask = EVENTER_TIMER;
977   newe->callback = noit_conf_log_rotate_time;
978   eventer_add(newe);
979   return 0;
980 }
981 int
982 noit_conf_log_init_rotate(const char *toplevel, noit_boolean validate) {
983   int i, cnt = 0, max_time, max_size, rv = 0;
984   noit_conf_section_t *log_configs;
985   char path[256];
986
987   snprintf(path, sizeof(path), "/%s/logs//log", toplevel);
988   log_configs = noit_conf_get_sections(NULL, path, &cnt);
989   noitL(noit_debug, "Found %d %s stanzas\n", cnt, path);
990   for(i=0; i<cnt; i++) {
991     noit_log_stream_t ls;
992     char name[256];
993
994     if(!noit_conf_get_stringbuf(log_configs[i],
995                                 "ancestor-or-self::node()/@name",
996                                 name, sizeof(name))) {
997       noitL(noit_error, "log section %d does not have a name attribute\n", i+1);
998       if(validate) { rv = -1; break; }
999       else exit(-2);
1000     }
1001     ls = noit_log_stream_find(name);
1002     if(!ls) continue;
1003
1004     if(noit_conf_get_int(log_configs[i],   
1005                          "ancestor-or-self::node()/@rotate_seconds",
1006                          &max_time) && max_time) {
1007       struct log_rotate_crutch *lrc;
1008       if(max_time < 600) {
1009         fprintf(stderr, "rotate_seconds must be >= 600s (10 minutes)\n");
1010         if(validate) { rv = -1; break; }
1011         else exit(-2);
1012       }
1013       if(!validate) {
1014         lrc = calloc(1, sizeof(*lrc));
1015         lrc->ls = ls;
1016         lrc->seconds = max_time;
1017         noit_conf_log_rotate_time(NULL, EVENTER_TIMER, lrc, NULL);
1018       }
1019     }
1020
1021     if(noit_conf_get_int(log_configs[i],   
1022                          "ancestor-or-self::node()/@rotate_bytes",
1023                          &max_size) && max_size) {
1024       struct log_rotate_crutch *lrc;
1025       if(max_size < 102400) {
1026         fprintf(stderr, "rotate_bytes must be >= 102400 (100k)\n");
1027         if(validate) { rv = -1; break; }
1028         else exit(-2);
1029       }
1030       if(!validate) {
1031         lrc = calloc(1, sizeof(*lrc));
1032         lrc->ls = ls;
1033         lrc->max_size = max_size;
1034         noit_conf_log_rotate_size(NULL, EVENTER_TIMER, lrc, NULL);
1035       }
1036     }
1037   }
1038   free(log_configs);
1039   return rv;
1040 }
1041 void
1042 noit_conf_log_init(const char *toplevel) {
1043   int i, cnt = 0, o, ocnt = 0;
1044   noit_conf_section_t *log_configs, *outlets;
1045   char path[256];
1046
1047   snprintf(path, sizeof(path), "/%s/logs//log", toplevel);
1048   log_configs = noit_conf_get_sections(NULL, path, &cnt);
1049   noitL(noit_debug, "Found %d %s stanzas\n", cnt, path);
1050   for(i=0; i<cnt; i++) {
1051     noit_log_stream_t ls;
1052     char name[256], type[256], path[256];
1053     noit_hash_table *config;
1054     noit_boolean disabled, debug, timestamps;
1055
1056     if(!noit_conf_get_stringbuf(log_configs[i],
1057                                 "ancestor-or-self::node()/@name",
1058                                 name, sizeof(name))) {
1059       noitL(noit_error, "log section %d does not have a name attribute\n", i+1);
1060       exit(-1);
1061     }
1062     if(!noit_conf_get_stringbuf(log_configs[i],
1063                                 "ancestor-or-self::node()/@type",
1064                                 type, sizeof(type))) {
1065       type[0] = '\0';
1066     }
1067     if(!noit_conf_get_stringbuf(log_configs[i],
1068                                 "ancestor-or-self::node()/@path",
1069                                 path, sizeof(path))) {
1070       path[0] = '\0';
1071     }
1072     config = noit_conf_get_hash(log_configs[i],
1073                                 "ancestor-or-self::node()/config");
1074     ls = noit_log_stream_new(name, type[0] ? type : NULL,
1075                              path[0] ? path : NULL, NULL, config);
1076     if(!ls) {
1077       fprintf(stderr, "Error configuring log: %s[%s:%s]\n", name, type, path);
1078       exit(-1);
1079     }
1080
1081     if(noit_conf_get_boolean(log_configs[i],
1082                              "ancestor-or-self::node()/@disabled",
1083                              &disabled) && disabled)
1084       ls->enabled = 0;
1085      
1086     if(noit_conf_get_boolean(log_configs[i],
1087                              "ancestor-or-self::node()/@debug",
1088                              &debug) && debug)
1089       ls->debug = 1;
1090      
1091     if(noit_conf_get_boolean(log_configs[i],
1092                              "ancestor-or-self::node()/@timestamps",
1093                              &timestamps))
1094       ls->timestamps = timestamps ? 1 : 0;
1095  
1096     outlets = noit_conf_get_sections(log_configs[i],
1097                                      "ancestor-or-self::node()/outlet", &ocnt);
1098     noitL(noit_debug, "Found %d outlets for log '%s'\n", ocnt, name);
1099
1100     for(o=0; o<ocnt; o++) {
1101       noit_log_stream_t outlet;
1102       char oname[256];
1103       noit_conf_get_stringbuf(outlets[o], "@name",
1104                               oname, sizeof(oname));
1105       outlet = noit_log_stream_find(oname);
1106       if(!outlet) {
1107         fprintf(stderr, "Cannot find outlet '%s' for %s[%s:%s]\n", oname,
1108               name, type, path);
1109         exit(-1);
1110       }
1111       else
1112         noit_log_stream_add_stream(ls, outlet);
1113     }
1114     if(outlets) free(outlets);
1115   }
1116   if(log_configs) free(log_configs);
1117   if(noit_conf_log_init_rotate(toplevel, noit_true)) exit(-1);
1118 }
1119
1120 static void
1121 conf_t_userdata_free(void *data) {
1122   noit_conf_t_userdata_t *info = data;
1123   if(info) {
1124     if(info->path) free(info->path);
1125     free(info);
1126   }
1127 }
1128
1129 static int
1130 noit_console_state_conf_terminal(noit_console_closure_t ncct,
1131                                  int argc, char **argv,
1132                                  noit_console_state_t *state, void *closure) {
1133   noit_conf_t_userdata_t *info;
1134   if(argc) {
1135     nc_printf(ncct, "extra arguments not expected.\n");
1136     return -1;
1137   }
1138   info = calloc(1, sizeof(*info));
1139   info->path = strdup("/");
1140   noit_console_userdata_set(ncct, NOIT_CONF_T_USERDATA, info,
1141                             conf_t_userdata_free);
1142   noit_console_state_push_state(ncct, state);
1143   noit_console_state_init(ncct);
1144   return 0;
1145 }
1146 static int
1147 noit_console_config_section(noit_console_closure_t ncct,
1148                             int argc, char **argv,
1149                             noit_console_state_t *state, void *closure) {
1150   const char *err = "internal error";
1151   char *path, xpath[1024];
1152   noit_conf_t_userdata_t *info;
1153   xmlXPathObjectPtr pobj = NULL;
1154   xmlXPathContextPtr xpath_ctxt = NULL;
1155   xmlNodePtr node = NULL, newnode;
1156   vpsized_int delete = (vpsized_int)closure;
1157
1158   noit_conf_xml_xpath(NULL, &xpath_ctxt);
1159   if(argc != 1) {
1160     nc_printf(ncct, "requires one argument\n");
1161     return -1;
1162   }
1163   if(strchr(argv[0], '/')) {
1164     nc_printf(ncct, "invalid section name\n");
1165     return -1;
1166   }
1167   if(!strcmp(argv[0], "check") ||
1168      !strcmp(argv[0], "noit") ||
1169      !strcmp(argv[0], "filterset") ||
1170      !strcmp(argv[0], "config")) {
1171     nc_printf(ncct, "%s is reserved.\n", argv[0]);
1172     return -1;
1173   }
1174   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
1175   if(!strcmp(info->path, "/")) {
1176     nc_printf(ncct, "manipulation of toplevel section disallowed\n");
1177     return -1;
1178   }
1179
1180   if(delete) {
1181     /* We cannot delete if we have checks */
1182     snprintf(xpath, sizeof(xpath), "/%s%s/%s//check", root_node_name,
1183              info->path, argv[0]);
1184     pobj = xmlXPathEval((xmlChar *)xpath, xpath_ctxt);
1185     if(!pobj || pobj->type != XPATH_NODESET ||
1186        !xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
1187       err = "cannot delete section, has checks";
1188       goto bad;
1189     }
1190     if(pobj) xmlXPathFreeObject(pobj);
1191   }
1192
1193   snprintf(xpath, sizeof(xpath), "/%s%s/%s", root_node_name,
1194            info->path, argv[0]);
1195   pobj = xmlXPathEval((xmlChar *)xpath, xpath_ctxt);
1196   if(!pobj || pobj->type != XPATH_NODESET) {
1197     err = "internal error: cannot detect section";
1198     goto bad;
1199   }
1200   if(!delete && !xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
1201     if(xmlXPathNodeSetGetLength(pobj->nodesetval) == 1) {
1202       node = xmlXPathNodeSetItem(pobj->nodesetval, 0);
1203       if(info->path) free(info->path);
1204       info->path = strdup((char *)xmlGetNodePath(node) +
1205                           1 + strlen(root_node_name));
1206       goto cdout;
1207     }
1208     err = "cannot create section";
1209     goto bad;
1210   }
1211   if(delete && xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
1212     err = "no such section";
1213     goto bad;
1214   }
1215   if(delete) {
1216     node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
1217     xmlUnlinkNode(node);
1218     noit_conf_mark_changed();
1219     return 0;
1220   }
1221   if(pobj) xmlXPathFreeObject(pobj);
1222   pobj = NULL;
1223
1224   if(!strcmp(argv[0],"include")) {
1225     err = "include is a reserved section name";
1226     goto bad;
1227   }
1228   path = strcmp(info->path, "/") ? info->path : "";
1229   snprintf(xpath, sizeof(xpath), "/%s%s", root_node_name, path);
1230   pobj = xmlXPathEval((xmlChar *)xpath, xpath_ctxt);
1231   if(!pobj || pobj->type != XPATH_NODESET ||
1232      xmlXPathNodeSetGetLength(pobj->nodesetval) != 1) {
1233     err = "path invalid?";
1234     goto bad;
1235   }
1236   node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
1237   if((newnode = xmlNewChild(node, NULL, (xmlChar *)argv[0], NULL)) != NULL) {
1238     noit_conf_mark_changed();
1239     if(info->path) free(info->path);
1240     info->path = strdup((char *)xmlGetNodePath(newnode) + 1 +
1241                         strlen(root_node_name));
1242   }
1243   else {
1244     err = "failed to create section";
1245     goto bad;
1246   }
1247  cdout:
1248   if(pobj) xmlXPathFreeObject(pobj);
1249   return 0;
1250  bad:
1251   if(pobj) xmlXPathFreeObject(pobj);
1252   nc_printf(ncct, "%s\n", err);
1253   return -1;
1254 }
1255
1256 int
1257 noit_console_generic_show(noit_console_closure_t ncct,
1258                           int argc, char **argv,
1259                           noit_console_state_t *state, void *closure) {
1260   int i, cnt, titled = 0, cliplen = 0;
1261   const char *path = "", *basepath = NULL;
1262   char xpath[1024];
1263   noit_conf_t_userdata_t *info = NULL;
1264   xmlXPathObjectPtr pobj = NULL;
1265   xmlXPathContextPtr xpath_ctxt = NULL, current_ctxt;
1266   xmlDocPtr master_config = NULL;
1267   xmlNodePtr node = NULL;
1268
1269   noit_conf_xml_xpath(&master_config, &xpath_ctxt);
1270   if(argc > 1) {
1271     nc_printf(ncct, "too many arguments\n");
1272     return -1;
1273   }
1274
1275   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
1276   if(info && info->path) path = basepath = info->path;
1277   if(!info && argc == 0) {
1278     nc_printf(ncct, "argument required when not in configuration mode\n");
1279     return -1;
1280   }
1281
1282   if(argc == 1) path = argv[0];
1283   if(!basepath) basepath = path;
1284
1285   /* { / } is a special case */
1286   if(!strcmp(basepath, "/")) basepath = "";
1287   if(!strcmp(path, "/")) path = "";
1288
1289   if(!master_config) {
1290     nc_printf(ncct, "no config\n");
1291     return -1;
1292   }
1293
1294   /* { / } is the only path that will end with a /
1295    * in XPath { / / * } means something _entirely different than { / * }
1296    * Ever notice how it is hard to describe xpath in C comments?
1297    */
1298   /* We don't want to show the root node */
1299   cliplen = strlen(root_node_name) + 2; /* /name/ */
1300
1301   /* If we are in configuration mode
1302    * and we are without an argument or the argument is absolute,
1303    * clip the current path off */
1304   if(info && (argc == 0 || path[0] != '/')) cliplen += strlen(basepath);
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/@*", root_node_name,
1309              basepath, path);
1310
1311   current_ctxt = xpath_ctxt;
1312   pobj = xmlXPathEval((xmlChar *)xpath, current_ctxt);
1313   if(!pobj || pobj->type != XPATH_NODESET) {
1314     nc_printf(ncct, "no such object\n");
1315     goto bad;
1316   }
1317   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
1318   titled = 0;
1319   for(i=0; i<cnt; i++) {
1320     node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, i);
1321     if(node->children && node->children == xmlGetLastChild(node) &&
1322       xmlNodeIsText(node->children)) {
1323       if(!titled++) nc_printf(ncct, "== Section Settings ==\n");
1324       nc_printf(ncct, "%s: %s\n", xmlGetNodePath(node) + cliplen,
1325                 xmlXPathCastNodeToString(node->children));
1326     }
1327   }
1328   xmlXPathFreeObject(pobj);
1329
1330   /* _shorten string_ turning last { / @ * } to { / * } */
1331   if(!path[0] || path[0] == '/') /* base only, or absolute path requested */
1332     snprintf(xpath, sizeof(xpath), "/%s%s/*", root_node_name, path);
1333   else
1334     snprintf(xpath, sizeof(xpath), "/%s%s/%s/*",
1335              root_node_name, basepath, path);
1336   pobj = xmlXPathEval((xmlChar *)xpath, current_ctxt);
1337   if(!pobj || pobj->type != XPATH_NODESET) {
1338     nc_printf(ncct, "no such object\n");
1339     goto bad;
1340   }
1341   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
1342   titled = 0;
1343   for(i=0; i<cnt; i++) {
1344     node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, i);
1345     if(!(node->children && node->children == xmlGetLastChild(node) &&
1346          xmlNodeIsText(node->children))) {
1347       if(!titled++) nc_printf(ncct, "== Subsections ==\n");
1348       nc_printf(ncct, "%s\n", xmlGetNodePath(node) + cliplen);
1349     }
1350   }
1351   xmlXPathFreeObject(pobj);
1352   return 0;
1353  bad:
1354   if(pobj) xmlXPathFreeObject(pobj);
1355   return -1;
1356 }
1357 int
1358 noit_console_config_cd(noit_console_closure_t ncct,
1359                        int argc, char **argv,
1360                        noit_console_state_t *state, void *closure) {
1361   const char *err = "internal error";
1362   char *path, xpath[1024];
1363   noit_conf_t_userdata_t *info;
1364   xmlXPathObjectPtr pobj = NULL;
1365   xmlXPathContextPtr xpath_ctxt = NULL, current_ctxt;
1366   xmlNodePtr node = NULL;
1367   char *dest;
1368
1369   noit_conf_xml_xpath(NULL, &xpath_ctxt);
1370   if(argc != 1 && !closure) {
1371     nc_printf(ncct, "requires one argument\n");
1372     return -1;
1373   }
1374   dest = argc ? argv[0] : (char *)closure;
1375   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
1376   if(dest[0] == '/')
1377     snprintf(xpath, sizeof(xpath), "/%s%s", root_node_name, dest);
1378   else {
1379     snprintf(xpath, sizeof(xpath), "/%s%s/%s", root_node_name,
1380              info->path, dest);
1381   }
1382   if(xpath[strlen(xpath)-1] == '/') xpath[strlen(xpath)-1] = '\0';
1383
1384   current_ctxt = xpath_ctxt;
1385   pobj = xmlXPathEval((xmlChar *)xpath, current_ctxt);
1386   if(!pobj || pobj->type != XPATH_NODESET ||
1387      xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
1388     err = "no such section";
1389     goto bad;
1390   }
1391   if(xmlXPathNodeSetGetLength(pobj->nodesetval) > 1) {
1392     err = "ambiguous section";
1393     goto bad;
1394   }
1395
1396   node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
1397   if(!node) {
1398     err = "internal XML error";
1399     goto bad;
1400   }
1401   if(!strcmp((char *)node->name, "check") ||
1402      !strcmp((char *)node->name, "noit") ||
1403      !strcmp((char *)node->name, "filterset") ||
1404      !strcmp((char *)node->name, "config")) {
1405     err = "reserved word";
1406     goto bad;
1407   }
1408   path = (char *)xmlGetNodePath(node);
1409   if(strlen(path) < strlen(root_node_name) + 1 ||
1410      strncmp(path + 1, root_node_name, strlen(root_node_name)) ||
1411      (path[strlen(root_node_name) + 1] != '/' &&
1412       path[strlen(root_node_name) + 1] != '\0')) {
1413     err = "new path outside out tree";
1414     goto bad;
1415   }
1416   free(info->path);
1417   if(!strcmp(path + 1, root_node_name))
1418     info->path = strdup("/");
1419   else
1420     info->path = strdup((char *)xmlGetNodePath(node) + 1 +
1421                         strlen(root_node_name));
1422   if(pobj) xmlXPathFreeObject(pobj);
1423   if(closure) noit_console_state_pop(ncct, argc, argv, NULL, NULL);
1424   return 0;
1425  bad:
1426   if(pobj) xmlXPathFreeObject(pobj);
1427   nc_printf(ncct, "%s [%s]\n", err, xpath);
1428   return -1;
1429 }
1430
1431 char *
1432 conf_t_prompt(EditLine *el) {
1433   noit_console_closure_t ncct;
1434   noit_conf_t_userdata_t *info;
1435   static char *tl = "noit(conf)# ";
1436   static char *pfmt = "noit(conf:%s%s)# ";
1437   int path_len, max_len;
1438
1439   el_get(el, EL_USERDATA, (void *)&ncct);
1440   if(!ncct) return tl;
1441   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
1442   if(!info) return tl;
1443
1444   path_len = strlen(info->path);
1445   max_len = sizeof(info->prompt) - (strlen(pfmt) - 4 /* %s%s */) - 1 /* \0 */;
1446   if(path_len > max_len)
1447     snprintf(info->prompt, sizeof(info->prompt),
1448              pfmt, "...", info->path + path_len - max_len + 3 /* ... */);
1449   else
1450     snprintf(info->prompt, sizeof(info->prompt), pfmt, "", info->path);
1451   return info->prompt;
1452 }
1453
1454 #define NEW_STATE(a) (a) = noit_console_state_alloc()
1455 #define ADD_CMD(a,cmd,func,ac,ss,c) \
1456   noit_console_state_add_cmd((a), \
1457     NCSCMD(cmd, func, ac, ss, c))
1458 #define DELEGATE_CMD(a,cmd,ac,ss) \
1459   noit_console_state_add_cmd((a), \
1460     NCSCMD(cmd, noit_console_state_delegate, ac, ss, NULL))
1461
1462 void noit_console_conf_init() {
1463   noit_console_state_t *tl, *_conf_state, *_conf_t_state,
1464                        *_write_state, *_unset_state;
1465
1466   tl = noit_console_state_initial();
1467
1468   /* write <terimal|memory|file> */
1469   NEW_STATE(_write_state);
1470   ADD_CMD(_write_state, "terminal", noit_conf_write_terminal, NULL, NULL, NULL);
1471   ADD_CMD(_write_state, "file", noit_conf_write_file_console, NULL, NULL, NULL);
1472   /* write memory?  It's to a file, but I like router syntax */
1473   ADD_CMD(_write_state, "memory", noit_conf_write_file_console, NULL, NULL, NULL);
1474
1475   NEW_STATE(_unset_state);
1476   ADD_CMD(_unset_state, "section",
1477           noit_console_config_section, NULL, NULL, (void *)1);
1478
1479   NEW_STATE(_conf_t_state);
1480   _conf_t_state->console_prompt_function = conf_t_prompt;
1481   noit_console_state_add_cmd(_conf_t_state, &console_command_exit);
1482
1483   ADD_CMD(_conf_t_state, "ls", noit_console_generic_show, NULL, NULL, NULL);
1484   ADD_CMD(_conf_t_state, "cd", noit_console_config_cd, NULL, NULL, NULL);
1485   ADD_CMD(_conf_t_state, "section",
1486           noit_console_config_section, NULL, NULL, (void *)0);
1487
1488   DELEGATE_CMD(_conf_t_state, "write",
1489                noit_console_opt_delegate, _write_state);
1490   DELEGATE_CMD(_conf_t_state, "no", noit_console_opt_delegate, _unset_state);
1491
1492   NEW_STATE(_conf_state);
1493   ADD_CMD(_conf_state, "terminal",
1494           noit_console_state_conf_terminal, NULL, _conf_t_state, NULL);
1495
1496   ADD_CMD(tl, "configure",
1497           noit_console_state_delegate, noit_console_opt_delegate,
1498           _conf_state, NULL);
1499   ADD_CMD(tl, "write",
1500           noit_console_state_delegate, noit_console_opt_delegate,
1501           _write_state, NULL);
1502 }
1503
Note: See TracBrowser for help on using the browser.