root/src/noit_conf.c

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

on-disk backing store implementation

  • 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 <dirent.h>
39 #include <errno.h>
40 #include <assert.h>
41 #include <libxml/parser.h>
42 #include <libxml/tree.h>
43 #include <libxml/xpath.h>
44 #include <zlib.h>
45
46 #include "noit_conf.h"
47 #include "noit_check.h"
48 #include "noit_console.h"
49 #include "noit_xml.h"
50 #include "utils/noit_hash.h"
51 #include "utils/noit_log.h"
52 #include "utils/noit_b64.h"
53
54 /* tmp hash impl, replace this with something nice */
55 static noit_log_stream_t xml_debug = NULL;
56 #define XML2LOG(log) do { \
57   xmlSetGenericErrorFunc(log, noit_conf_xml_error_func); \
58   xmlSetStructuredErrorFunc(log, noit_conf_xml_error_ext_func); \
59 } while(0)
60 #define XML2CONSOLE(ncct) do { \
61   xmlSetGenericErrorFunc(ncct, noit_conf_xml_console_error_func); \
62   xmlSetStructuredErrorFunc(ncct, noit_conf_xml_console_error_ext_func); \
63 } while(0)
64 static noit_hash_table _tmp_config = NOIT_HASH_EMPTY;
65 static xmlDocPtr master_config = NULL;
66 static int config_include_cnt = -1;
67 static int backingstore_include_cnt = -1;
68
69 static struct {
70   xmlNodePtr insertion_point;
71   xmlNodePtr old_children;
72   xmlDocPtr doc;
73   xmlNodePtr root;
74 } *config_include_nodes = NULL,
75   *backingstore_include_nodes = NULL;
76
77 typedef struct noit_xml_userdata {
78   char       *name;
79   char       *path;
80   u_int64_t   dirty_time;
81   struct noit_xml_userdata *freelist;
82 } noit_xml_userdata_t;
83
84 static noit_xml_userdata_t *backingstore_freelist = NULL;
85 static u_int64_t last_config_flush = 0;
86
87 #define is_stopnode_name(n) ((n) && (!strcmp((char *)(n), "check") || !strcmp((char *)(n), "config")))
88 #define is_stopnode(node) ((node) && is_stopnode_name((node)->name))
89
90 static char *root_node_name = NULL;
91 static char master_config_file[PATH_MAX] = "";
92 static xmlXPathContextPtr xpath_ctxt = NULL;
93
94 /* This is used to notice config changes and journal the config out
95  * using a user-specified function.  It supports allowing multiple config
96  * changed to coalesce so you don't write out 1000 changes in a few seconds.
97  */
98 static u_int32_t __config_gen = 0;
99 static u_int32_t __config_coalesce = 0;
100 static u_int32_t __config_coalesce_time = 0;
101 static u_int64_t max_gen_count = 0;
102 void noit_conf_coalesce_changes(u_int32_t seconds) {
103   __config_coalesce_time = seconds;
104 }
105 void noit_conf_mark_changed() {
106   /* increment the change counter -- in case anyone cares */
107   __config_gen++;
108   /* reset the coalesce counter.  It is decremented each second and
109    * the journal function fires on a transition from 1 => 0
110    */
111   __config_coalesce = __config_coalesce_time;
112 }
113 struct recurrent_journaler {
114   int (*journal_config)(void *);
115   void *jc_closure;
116 };
117
118 static void
119 noit_xml_userdata_free(noit_xml_userdata_t *n) {
120   if(n->name) free(n->name);
121   if(n->path) free(n->path);
122 }
123
124 static int
125 noit_conf_watch_config_and_journal(eventer_t e, int mask, void *closure,
126                                    struct timeval *now) {
127   struct recurrent_journaler *rj = closure;
128   eventer_t newe;
129
130   if(__config_coalesce == 1)
131     rj->journal_config(rj->jc_closure);
132   if(__config_coalesce > 0)
133     __config_coalesce--;
134
135   /* Schedule the same event to fire a second form now */
136   newe = eventer_alloc();
137   gettimeofday(&newe->whence, NULL);
138   newe->whence.tv_sec += 1;
139   newe->mask = EVENTER_TIMER;
140   newe->callback = noit_conf_watch_config_and_journal;
141   newe->closure = closure;
142   eventer_add(newe);
143   return 0;
144 }
145 void
146 noit_conf_watch_and_journal_watchdog(int (*f)(void *), void *c) {
147   static int callbacknamed = 0;
148   struct recurrent_journaler *rj;
149   struct timeval __now;
150
151   if(!callbacknamed) {
152     callbacknamed = 1;
153     eventer_name_callback("noit_conf_watch_config_and_journal",
154                           noit_conf_watch_config_and_journal);
155   }
156   rj = calloc(1, sizeof(*rj));
157   rj->journal_config = f;
158   rj->jc_closure = c;
159   gettimeofday(&__now, NULL);
160   noit_conf_watch_config_and_journal(NULL, EVENTER_TIMER, rj, &__now);
161 }
162
163 static noit_hash_table _compiled_fallback = NOIT_HASH_EMPTY;
164 static struct {
165   const char *key;
166   const char *val;
167 } config_info[] = {
168   /*
169    * These are compile-time fallbacks to be used in the event
170    * that the current running config does not have values for
171    * these config paths.
172    *
173    * PLEASE: keep them alphabetically sorted.
174    */
175   { "/%s/eventer/@implementation", DEFAULT_EVENTER },
176   { "/%s/modules/@directory", MODULES_DIR },
177
178   { NULL, NULL }
179 };
180
181 void noit_conf_xml_console_error_func(void *ctx, const char *format, ...) {
182   noit_console_closure_t ncct = ctx;
183   va_list arg;
184   if(!ncct) return;
185   va_start(arg, format);
186   nc_vprintf(ncct, format, arg);
187   va_end(arg);
188 }
189
190 void noit_conf_xml_console_error_ext_func(void *ctx, xmlErrorPtr err) {
191   noit_console_closure_t ncct = ctx;
192   if(!ctx) return;
193   if(err->file)
194     nc_printf(ncct, "XML error [%d/%d] in %s on line %d %s\n",
195               err->domain, err->code, err->file, err->line, err->message);
196   else
197     nc_printf(ncct, "XML error [%d/%d] %s\n",
198               err->domain, err->code, err->message);
199 }
200
201 void noit_conf_xml_error_func(void *ctx, const char *format, ...) {
202   struct timeval __now;
203   noit_log_stream_t ls = ctx;
204   va_list arg;
205   if(!ls) return;
206   va_start(arg, format);
207   gettimeofday(&__now,  NULL);
208   noit_vlog(ls, &__now, __FILE__, __LINE__, format, arg);
209   va_end(arg);
210 }
211 void noit_conf_xml_error_ext_func(void *ctx, xmlErrorPtr err) {
212   struct timeval __now;
213   noit_log_stream_t ls = ctx;
214   if(!ls) return;
215   gettimeofday(&__now,  NULL);
216   if(err->file)
217     noit_log(ls, &__now, err->file, err->line,
218              "XML error [%d/%d] in %s on line %d %s\n",
219              err->domain, err->code, err->file, err->line, err->message);
220   else
221     noit_log(ls, &__now, err->file, err->line,
222              "XML error [%d/%d] %s\n",
223              err->domain, err->code, err->message);
224 }
225
226
227 DECLARE_CHECKER(name)
228 void noit_conf_init(const char *toplevel) {
229   int i;
230   char keystr[256];
231
232   xml_debug = noit_log_stream_find("debug/xml");
233
234   COMPILE_CHECKER(name, "^[-_\\.:/a-zA-Z0-9]+$");
235   XML2LOG(noit_error);
236   for(i = 0; config_info[i].key != NULL; i++) {
237     snprintf(keystr, sizeof(keystr), config_info[i].key, toplevel);
238     noit_hash_store(&_compiled_fallback,
239                     strdup(keystr), strlen(keystr),
240                     (void *)strdup(config_info[i].val));
241   }
242   xmlKeepBlanksDefault(0);
243   xmlInitParser();
244   xmlXPathInit();
245 }
246
247 void
248 noit_conf_magic_separate(xmlDocPtr doc) {
249   assert(config_include_cnt != -1);
250   if(config_include_nodes) {
251     int i;
252     for(i=0; i<config_include_cnt; i++) {
253       if(config_include_nodes[i].doc) {
254         xmlNodePtr n;
255         for(n=config_include_nodes[i].insertion_point->children;
256             n; n = n->next)
257           n->parent = config_include_nodes[i].root;
258         config_include_nodes[i].insertion_point->children =
259           config_include_nodes[i].old_children;
260         xmlFreeDoc(config_include_nodes[i].doc);
261       }
262     }
263     free(config_include_nodes);
264   }
265   config_include_nodes = NULL;
266   config_include_cnt = -1;
267
268   if(backingstore_include_nodes) {
269     int i;
270     for(i=0; i<backingstore_include_cnt; i++) {
271       if(backingstore_include_nodes[i].doc) {
272         xmlNodePtr n;
273         for(n=backingstore_include_nodes[i].insertion_point->children;
274             n; n = n->next)
275           n->parent = backingstore_include_nodes[i].root;
276         backingstore_include_nodes[i].insertion_point->children =
277           backingstore_include_nodes[i].old_children;
278         xmlFreeDoc(backingstore_include_nodes[i].doc);
279       }
280     }
281     free(backingstore_include_nodes);
282   }
283   backingstore_include_nodes = NULL;
284   backingstore_include_cnt = -1;
285 }
286 void
287 noit_conf_kansas_city_shuffle_redo(xmlDocPtr doc) {
288   if(config_include_nodes) {
289     int i;
290     for(i=0; i<config_include_cnt; i++) {
291       if(config_include_nodes[i].doc) {
292         xmlNodePtr n;
293         config_include_nodes[i].insertion_point->children =
294           config_include_nodes[i].root->children;
295         for(n=config_include_nodes[i].insertion_point->children;
296             n; n = n->next)
297           n->parent = config_include_nodes[i].insertion_point;
298       }
299     }
300   }
301 }
302 void
303 noit_conf_kansas_city_shuffle_undo(xmlDocPtr doc) {
304   if(config_include_nodes) {
305     int i;
306     for(i=0; i<config_include_cnt; i++) {
307       if(config_include_nodes[i].doc) {
308         xmlNodePtr n;
309         for(n=config_include_nodes[i].insertion_point->children;
310             n; n = n->next)
311           n->parent = config_include_nodes[i].root;
312         config_include_nodes[i].insertion_point->children =
313           config_include_nodes[i].old_children;
314       }
315     }
316   }
317 }
318 static u_int64_t
319 usec_now() {
320   u_int64_t usec;
321   struct timeval tv;
322   gettimeofday(&tv, NULL);
323   usec = tv.tv_sec * 1000000UL;
324   usec += tv.tv_usec;
325   return usec;
326 }
327 void
328 noit_conf_backingstore_remove(noit_conf_section_t vnode) {
329   xmlNodePtr node = vnode;
330   noit_xml_userdata_t *subctx = node->_private;
331   if(subctx) {
332     noitL(noit_debug, "marking %s for removal\n", subctx->path);
333     if(!backingstore_freelist) backingstore_freelist = subctx;
334     else {
335       noit_xml_userdata_t *fl = backingstore_freelist;
336       while(fl->freelist) fl = fl->freelist;
337       fl->freelist = subctx;
338     }
339     node->_private = NULL;
340   }
341   /* If we're deleted, we'll mark the parent as dirty */
342   if(node->parent) noit_conf_backingstore_dirty(node->parent);
343 }
344 void
345 noit_conf_backingstore_dirty(noit_conf_section_t vnode) {
346   xmlNodePtr node = vnode;
347   noit_xml_userdata_t *subctx = node->_private;
348   if(subctx) {
349     subctx->dirty_time = usec_now();
350     return;
351   }
352   if(node->parent) noit_conf_backingstore_dirty(node->parent);
353 }
354 int
355 noit_conf_backingstore_write(noit_xml_userdata_t *ctx, noit_boolean skip,
356                              xmlAttrPtr attrs, xmlNodePtr node) {
357   int failure = 0;
358   char newpath[PATH_MAX];
359   xmlNodePtr n;
360   snprintf(newpath, sizeof(newpath), "%s/.attrs", ctx->path);
361   if(attrs) {
362     xmlDocPtr tmpdoc;
363     xmlNodePtr tmpnode;
364     noitL(noit_debug, " **> %s\n", newpath);
365     tmpdoc = xmlNewDoc((xmlChar *)"1.0");
366     tmpnode = xmlNewNode(NULL, ctx->name ? (xmlChar *)ctx->name : (xmlChar *)"stub");
367     xmlDocSetRootElement(tmpdoc, tmpnode);
368     tmpnode->properties = attrs;
369     failure = noit_xmlSaveToFile(tmpdoc, newpath);
370     tmpnode->properties = NULL;
371     xmlFreeDoc(tmpdoc);
372     if(failure) return -1;
373   }
374   else if(!skip) {
375     unlink(newpath);
376   }
377   for(n = node; n; n = n->next) {
378     int leaf;
379     noit_xml_userdata_t *subctx;
380     subctx = n->_private;
381     leaf = is_stopnode(n);
382     if(!subctx) { /* This has never been written out */
383       subctx = calloc(1, sizeof(*subctx));
384       subctx->name = strdup((char *)n->name);
385       snprintf(newpath, sizeof(newpath), "%s/%s#%llu", ctx->path, n->name, ++max_gen_count);
386       if(leaf) strlcat(newpath, ".xml", sizeof(newpath));
387       subctx->path = strdup(newpath);
388       subctx->dirty_time = usec_now();
389       n->_private = subctx;
390       noitL(noit_debug, " !!> %s\n", subctx->path);
391     }
392     if(leaf) {
393       xmlDocPtr tmpdoc;
394       xmlNodePtr tmpnode;
395       if(subctx->dirty_time > last_config_flush) {
396         tmpdoc = xmlNewDoc((xmlChar *)"1.0");
397         tmpnode = xmlNewNode(NULL, n->name);
398         xmlDocSetRootElement(tmpdoc, tmpnode);
399         tmpnode->properties = n->properties;
400         tmpnode->children = n->children;
401         failure = noit_xmlSaveToFile(tmpdoc, subctx->path);
402         tmpnode->properties = NULL;
403         tmpnode->children = NULL;
404         xmlFreeDoc(tmpdoc);
405         noitL(noit_debug, " ==> %s\n", subctx->path);
406         if(failure) return -1;
407       }
408     }
409     else {
410       noit_boolean skip_attrs;
411       skip_attrs = leaf || (subctx->dirty_time <= last_config_flush);
412       noitL(noit_debug, " --> %s\n", subctx->path);
413       if(noit_conf_backingstore_write(subctx, skip_attrs, skip_attrs ? NULL : n->properties, n->children))
414         return -1;
415     }
416   }
417   return 0;
418 }
419 void
420 noit_conf_shatter_write(xmlDocPtr doc) {
421   if(backingstore_freelist) {
422     noit_xml_userdata_t *fl, *last;
423     for(fl = backingstore_freelist; fl; ) {
424       last = fl;
425       fl = fl->freelist;
426       /* If it is a file, we'll unlink it, otherwise,
427        * we need to delete the attributes and the directory.
428        */
429       if(unlink(last->path)) {
430         char attrpath[PATH_MAX];
431         snprintf(attrpath, sizeof(attrpath), "%s/.attrs", last->path);
432         unlink(attrpath);
433         if(rmdir(last->path) && errno != ENOENT) {
434           /* This shouldn't happen, but if it does we risk
435            * leaving a mess. Don't do that.
436            */
437           noitL(noit_error, "backingstore mess %s: %s\n",
438                 last->path, strerror(errno));
439         }
440       }
441       noit_xml_userdata_free(last);
442     }
443     backingstore_freelist = NULL;
444   }
445   if(backingstore_include_nodes) {
446     int i;
447     for(i=0; i<backingstore_include_cnt; i++) {
448       if(backingstore_include_nodes[i].doc) {
449         xmlNodePtr n;
450         noit_xml_userdata_t *what = backingstore_include_nodes[i].doc->_private;
451
452         for(n=backingstore_include_nodes[i].insertion_point->children;
453             n; n = n->next)
454           n->parent = backingstore_include_nodes[i].root;
455         backingstore_include_nodes[i].insertion_point->children =
456           backingstore_include_nodes[i].old_children;
457         noit_conf_backingstore_write(what, noit_false, NULL, backingstore_include_nodes[i].root->children);
458       }
459     }
460     last_config_flush = usec_now();
461   }
462 }
463 void
464 noit_conf_shatter_postwrite(xmlDocPtr doc) {
465   if(backingstore_include_nodes) {
466     int i;
467     for(i=0; i<backingstore_include_cnt; i++) {
468       if(backingstore_include_nodes[i].doc) {
469         xmlNodePtr n;
470         backingstore_include_nodes[i].insertion_point->children =
471           backingstore_include_nodes[i].root->children;
472         for(n=backingstore_include_nodes[i].insertion_point->children;
473             n; n = n->next)
474           n->parent = backingstore_include_nodes[i].insertion_point;
475       }
476     }
477   }
478 }
479
480 int
481 noit_conf_read_into_node(xmlNodePtr node, const char *path) {
482   DIR *dirroot;
483   struct dirent *de, *entry;
484   char filepath[PATH_MAX];
485   xmlDocPtr doc;
486   xmlNodePtr root = NULL;
487   xmlAttrPtr a;
488   struct stat sb;
489   int size, rv;
490
491   noitL(noit_debug, "read backing store: %s\n", path);
492   snprintf(filepath, sizeof(filepath), "%s/.attrs", path);
493   while((rv = stat(filepath, &sb)) < 0 && errno == EINTR);
494   if(rv == 0) {
495     doc = xmlParseFile(filepath);
496     if(doc) root = xmlDocGetRootElement(doc);
497     if(doc && root) {
498       node->properties = root->properties;
499       for(a = node->properties; a; a = a->next) {
500         a->parent = node;
501         a->doc = node->doc;
502       }
503       root->properties = NULL;
504       xmlFreeDoc(doc);
505       doc = NULL;
506     }
507   }
508 #ifdef _PC_NAME_MAX
509   size = pathconf(path, _PC_NAME_MAX);
510 #endif
511   size = MAX(size, PATH_MAX + 128);
512   de = alloca(size);
513   dirroot = opendir(path);
514   if(!dirroot) return -1;
515   while(portable_readdir_r(dirroot, de, &entry) == 0 && entry != NULL) {
516     noit_xml_userdata_t *udata;
517     char name[PATH_MAX];
518     char *sep;
519     xmlNodePtr child;
520     u_int64_t gen;
521
522     sep = strchr(entry->d_name, '#');
523     if(!sep) continue;
524     snprintf(filepath, sizeof(filepath), "%s/%s", path, entry->d_name);
525     while((rv = stat(filepath, &sb)) < 0 && errno == EINTR);
526     if(rv == 0) {
527       strlcpy(name, entry->d_name, sizeof(name));
528       name[sep - entry->d_name] = '\0';
529       gen = strtoull(sep+1, NULL, 10);
530       if(gen > max_gen_count) max_gen_count = gen;
531
532       if(S_ISDIR(sb.st_mode)) {
533         noitL(noit_debug, "<DIR< %s\n", entry->d_name);
534         child = xmlNewNode(NULL, (xmlChar *)name);
535         noit_conf_read_into_node(child, filepath);
536         udata = calloc(1, sizeof(*udata));
537         udata->name = strdup(name);
538         udata->path = strdup(filepath);
539         child->_private = udata;
540         xmlAddChild(node, child);
541       }
542       else if(S_ISREG(sb.st_mode)) {
543         xmlDocPtr cdoc;
544         xmlNodePtr cnode = NULL;
545         noitL(noit_debug, "<FILE< %s\n", entry->d_name);
546         cdoc = xmlParseFile(filepath);
547         if(cdoc) {
548           cnode = xmlDocGetRootElement(cdoc);
549           xmlDocSetRootElement(cdoc, xmlNewNode(NULL, (xmlChar *)"dummy"));
550           if(cnode) {
551             udata = calloc(1, sizeof(*udata));
552             udata->name = strdup(name);
553             udata->path = strdup(filepath);
554             cnode->_private = udata;
555             xmlAddChild(node, cnode);
556           }
557           xmlFreeDoc(cdoc);
558         }
559       }
560     }
561   }
562   closedir(dirroot);
563   return 0;
564 }
565
566 xmlDocPtr
567 noit_conf_read_backing_store(const char *path) {
568   xmlDocPtr doc;
569   xmlNodePtr root;
570   noit_xml_userdata_t *what;
571
572   doc = xmlNewDoc((xmlChar *)"1.0");
573   what = calloc(1, sizeof(*what));
574   what->path = strdup(path);
575   doc->_private = what;
576   root = xmlNewNode(NULL, (xmlChar *)"stub");
577   xmlDocSetRootElement(doc, root);
578   noit_conf_read_into_node(root, path);
579   return doc;
580 }
581 int
582 noit_conf_magic_mix(const char *parentfile, xmlDocPtr doc) {
583   xmlXPathContextPtr mix_ctxt = NULL;
584   xmlXPathObjectPtr pobj = NULL;
585   xmlNodePtr node;
586   int i, cnt, rv = 0;
587
588   assert(config_include_cnt == -1);
589   assert(backingstore_include_cnt == -1);
590
591   backingstore_include_cnt = 0;
592   mix_ctxt = xmlXPathNewContext(doc);
593   pobj = xmlXPathEval((xmlChar *)"//*[@backingstore]", mix_ctxt);
594   if(!pobj) goto includes;
595   if(pobj->type != XPATH_NODESET) goto includes;
596   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto includes;
597   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
598   if(cnt > 0)
599     backingstore_include_nodes = calloc(cnt, sizeof(*backingstore_include_nodes));
600   for(i=0; i<cnt; i++) {
601     char *path, *infile;
602     node = xmlXPathNodeSetItem(pobj->nodesetval, i);
603     path = (char *)xmlGetProp(node, (xmlChar *)"backingstore");
604     if(!path) continue;
605     if(*path == '/') infile = strdup(path);
606     else {
607       char *cp;
608       infile = malloc(PATH_MAX);
609       strlcpy(infile, parentfile, PATH_MAX);
610       for(cp = infile + strlen(infile) - 1; cp > infile; cp--) {
611         if(*cp == '/') { *cp = '\0'; break; }
612         else *cp = '\0';
613       }
614       strlcat(infile, "/", PATH_MAX);
615       strlcat(infile, path, PATH_MAX);
616     }
617     xmlFree(path);
618     backingstore_include_nodes[i].doc = noit_conf_read_backing_store(infile);
619     if(backingstore_include_nodes[i].doc) {
620       xmlNodePtr n, lchild;
621       backingstore_include_nodes[i].insertion_point = node;
622       backingstore_include_nodes[i].root = xmlDocGetRootElement(backingstore_include_nodes[i].doc);
623       /* for backing store, they are permanently reattached under the backing store.
624        * so for any children, we need to glue them into the new parent document.
625        */
626       lchild = backingstore_include_nodes[i].root->children;
627       while(lchild && lchild->next) lchild = lchild->next;
628       if(lchild) {
629         lchild->next = node->children;
630         if(node->children) node->children->prev = lchild;
631       }
632       else
633         backingstore_include_nodes[i].root->children = node->children;
634       for(n=node->children; n; n = n->next) {
635         n->parent = backingstore_include_nodes[i].root; /* this gets mapped right back, just for clarity */
636         n->doc = backingstore_include_nodes[i].doc;
637       }
638       backingstore_include_nodes[i].old_children = NULL;
639       node->children = backingstore_include_nodes[i].root->children;
640       for(n=node->children; n; n = n->next)
641         n->parent = backingstore_include_nodes[i].insertion_point;
642     }
643     else {
644       noitL(noit_error, "Could not load: '%s'\n", infile);
645       rv = -1;
646     }
647     free(infile);
648   }
649   mix_ctxt = xmlXPathNewContext(doc);
650   backingstore_include_cnt = cnt;
651   noitL(noit_debug, "Processed %d backing stores.\n", backingstore_include_cnt);
652   if(pobj) xmlXPathFreeObject(pobj);
653
654  includes:
655   config_include_cnt = 0;
656   pobj = xmlXPathEval((xmlChar *)"//include[@file]", mix_ctxt);
657   if(!pobj) goto out;
658   if(pobj->type != XPATH_NODESET) goto out;
659   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto out;
660   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
661   if(cnt > 0)
662     config_include_nodes = calloc(cnt, sizeof(*config_include_nodes));
663   for(i=0; i<cnt; i++) {
664     char *path, *infile;
665     node = xmlXPathNodeSetItem(pobj->nodesetval, i);
666     path = (char *)xmlGetProp(node, (xmlChar *)"file");
667     if(!path) continue;
668     if(*path == '/') infile = strdup(path);
669     else {
670       char *cp;
671       infile = malloc(PATH_MAX);
672       strlcpy(infile, parentfile, PATH_MAX);
673       for(cp = infile + strlen(infile) - 1; cp > infile; cp--) {
674         if(*cp == '/') { *cp = '\0'; break; }
675         else *cp = '\0';
676       }
677       strlcat(infile, "/", PATH_MAX);
678       strlcat(infile, path, PATH_MAX);
679     }
680     xmlFree(path);
681     config_include_nodes[i].doc = xmlParseFile(infile);
682     if(config_include_nodes[i].doc) {
683       xmlNodePtr n;
684       config_include_nodes[i].insertion_point = node;
685       config_include_nodes[i].root = xmlDocGetRootElement(config_include_nodes[i].doc);
686       config_include_nodes[i].old_children = node->children;
687       node->children = config_include_nodes[i].root->children;
688       for(n=node->children; n; n = n->next)
689         n->parent = config_include_nodes[i].insertion_point;
690     }
691     else {
692       noitL(noit_error, "Could not load: '%s'\n", infile);
693       rv = -1;
694     }
695     free(infile);
696   }
697   config_include_cnt = cnt;
698   noitL(noit_debug, "Processed %d includes\n", config_include_cnt);
699  out:
700   if(pobj) xmlXPathFreeObject(pobj);
701   if(mix_ctxt) xmlXPathFreeContext(mix_ctxt);
702   return rv;
703 }
704
705 static int noit_conf_load_internal(const char *path) {
706   int rv = 0;
707   xmlDocPtr new_config;
708   xmlNodePtr root;
709   new_config = xmlParseFile(path);
710   if(new_config) {
711     root = xmlDocGetRootElement(new_config);
712     if(root_node_name) free(root_node_name);
713     root_node_name = strdup((char *)root->name);
714
715     if(master_config) {
716       /* separate all includes */
717       noit_conf_magic_separate(master_config);
718       xmlFreeDoc(master_config);
719     }
720     if(xpath_ctxt) xmlXPathFreeContext(xpath_ctxt);
721
722     master_config = new_config;
723     /* mixin all the includes */
724     if(noit_conf_magic_mix(path, master_config)) rv = -1;
725
726     xpath_ctxt = xmlXPathNewContext(master_config);
727     if(path != master_config_file)
728       if(realpath(path, master_config_file) == NULL)
729         noitL(noit_error, "realpath failed: %s\n", strerror(errno));
730     noit_conf_mark_changed();
731     return rv;
732   }
733   rv = -1;
734   return rv;
735 }
736 int noit_conf_load(const char *path) {
737   int rv;
738   XML2LOG(noit_error);
739   rv = noit_conf_load_internal(path);
740   XML2LOG(xml_debug);
741   return rv;
742 }
743
744 char *noit_conf_config_filename() {
745   return strdup(master_config_file);
746 }
747
748 int noit_conf_xml_xpath(xmlDocPtr *mc, xmlXPathContextPtr *xp) {
749   if(mc) *mc = master_config;
750   if(xp) *xp = xpath_ctxt;
751   return 0;
752 }
753 int noit_conf_save(const char *path) {
754   return -1;
755 }
756
757 void noit_conf_get_elements_into_hash(noit_conf_section_t section,
758                                       const char *path,
759                                       noit_hash_table *table) {
760   int i, cnt;
761   xmlXPathObjectPtr pobj = NULL;
762   xmlXPathContextPtr current_ctxt;
763   xmlNodePtr current_node = (xmlNodePtr)section;
764   xmlNodePtr node;
765
766   current_ctxt = xpath_ctxt;
767   if(current_node) {
768     current_ctxt = xmlXPathNewContext(master_config);
769     current_ctxt->node = current_node;
770   }
771   pobj = xmlXPathEval((xmlChar *)path, current_ctxt);
772   if(!pobj) goto out;
773   if(pobj->type != XPATH_NODESET) goto out;
774   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto out;
775   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
776   for(i=0; i<cnt; i++) {
777     char *value;
778     node = xmlXPathNodeSetItem(pobj->nodesetval, i);
779     value = (char *)xmlXPathCastNodeToString(node);
780     noit_hash_replace(table,
781                       strdup((char *)node->name), strlen((char *)node->name),
782                       strdup(value), free, free);
783     xmlFree(value);
784   }
785  out:
786   if(pobj) xmlXPathFreeObject(pobj);
787   if(current_ctxt && current_ctxt != xpath_ctxt)
788     xmlXPathFreeContext(current_ctxt);
789 }
790 void noit_conf_get_into_hash(noit_conf_section_t section,
791                              const char *path,
792                              noit_hash_table *table) {
793   unsigned int cnt;
794   xmlXPathObjectPtr pobj = NULL;
795   xmlXPathContextPtr current_ctxt;
796   xmlNodePtr current_node = (xmlNodePtr)section;
797   xmlNodePtr node, parent_node;
798   char xpath_expr[1024];
799   char *inheritid;
800
801   current_ctxt = xpath_ctxt;
802   if(current_node) {
803     current_ctxt = xmlXPathNewContext(master_config);
804     current_ctxt->node = current_node;
805   }
806   if(path[0] == '/')
807     strlcpy(xpath_expr, path, sizeof(xpath_expr));
808   else
809     snprintf(xpath_expr, sizeof(xpath_expr),
810              "ancestor-or-self::node()/%s", path);
811   pobj = xmlXPathEval((xmlChar *)xpath_expr, current_ctxt);
812   if(!pobj) goto out;
813   if(pobj->type != XPATH_NODESET) goto out;
814   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto out;
815   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
816   /* These are in the order of root to leaf
817    * We want to recurse... apply:
818    *   1. our parent's config
819    *   2. our "inherit" config if it exists.
820    *   3. our config.
821    */
822   node = xmlXPathNodeSetItem(pobj->nodesetval, cnt-1);
823   /* 1. */
824   if(cnt > 1 && node) {
825     parent_node = xmlXPathNodeSetItem(pobj->nodesetval, cnt-2);
826     if(parent_node != current_node)
827       noit_conf_get_into_hash(parent_node, (const char *)node->name, table);
828   }
829   /* 2. */
830   inheritid = (char *)xmlGetProp(node, (xmlChar *)"inherit");
831   if(inheritid) {
832     snprintf(xpath_expr, sizeof(xpath_expr), "//*[@id=\"%s\"]", inheritid);
833     noit_conf_get_into_hash(NULL, xpath_expr, table);
834     xmlFree(inheritid);
835   }
836   /* 3. */
837   noit_conf_get_elements_into_hash(node, "*", table);
838
839  out:
840   if(pobj) xmlXPathFreeObject(pobj);
841   if(current_ctxt && current_ctxt != xpath_ctxt)
842     xmlXPathFreeContext(current_ctxt);
843 }
844 noit_hash_table *noit_conf_get_hash(noit_conf_section_t section,
845                                     const char *path) {
846   noit_hash_table *table = NULL;
847
848   table = calloc(1, sizeof(*table));
849   noit_conf_get_into_hash(section, path, table);
850   return table;
851 }
852 noit_conf_section_t noit_conf_get_section(noit_conf_section_t section,
853                                           const char *path) {
854   noit_conf_section_t subsection = NULL;
855   xmlXPathObjectPtr pobj = NULL;
856   xmlXPathContextPtr current_ctxt;
857   xmlNodePtr current_node = (xmlNodePtr)section;
858
859   current_ctxt = xpath_ctxt;
860   if(current_node) {
861     current_ctxt = xmlXPathNewContext(master_config);
862     current_ctxt->node = current_node;
863   }
864   pobj = xmlXPathEval((xmlChar *)path, current_ctxt);
865   if(!pobj) goto out;
866   if(pobj->type != XPATH_NODESET) goto out;
867   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto out;
868   subsection = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
869  out:
870   if(pobj) xmlXPathFreeObject(pobj);
871   if(current_ctxt && current_ctxt != xpath_ctxt)
872     xmlXPathFreeContext(current_ctxt);
873   return subsection;
874 }
875 noit_conf_section_t *noit_conf_get_sections(noit_conf_section_t section,
876                                             const char *path,
877                                             int *cnt) {
878   int i;
879   noit_conf_section_t *sections = NULL;
880   xmlXPathObjectPtr pobj = NULL;
881   xmlXPathContextPtr current_ctxt;
882   xmlNodePtr current_node = (xmlNodePtr)section;
883
884   *cnt = 0;
885   current_ctxt = xpath_ctxt;
886   if(current_node) {
887     current_ctxt = xmlXPathNewContext(master_config);
888     current_ctxt->node = current_node;
889   }
890   pobj = xmlXPathEval((xmlChar *)path, current_ctxt);
891   if(!pobj) goto out;
892   if(pobj->type != XPATH_NODESET) goto out;
893   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto out;
894   *cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
895   sections = calloc(*cnt, sizeof(*sections));
896   for(i=0; i<*cnt; i++)
897     sections[i] = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, i);
898  out:
899   if(pobj) xmlXPathFreeObject(pobj);
900   if(current_ctxt && current_ctxt != xpath_ctxt)
901     xmlXPathFreeContext(current_ctxt);
902   return sections;
903 }
904 int _noit_conf_get_string(noit_conf_section_t section, xmlNodePtr *vnode,
905                           const char *path, char **value) {
906   const char *str;
907   int rv = 1;
908   unsigned int i;
909   xmlXPathObjectPtr pobj = NULL;
910   xmlXPathContextPtr current_ctxt;
911   xmlNodePtr current_node = (xmlNodePtr)section;
912
913   current_ctxt = xpath_ctxt;
914   if(current_node) {
915     current_ctxt = xmlXPathNewContext(master_config);
916     current_ctxt->node = current_node;
917   }
918   pobj = xmlXPathEval((xmlChar *)path, current_ctxt);
919   if(pobj) {
920     xmlNodePtr node;
921     switch(pobj->type) {
922       case XPATH_NODESET:
923         if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto fallback;
924         i = xmlXPathNodeSetGetLength(pobj->nodesetval);
925         node = xmlXPathNodeSetItem(pobj->nodesetval, i-1);
926         if(vnode) *vnode = node;
927         *value = (char *)xmlXPathCastNodeToString(node);
928         break;
929       default:
930         *value = (char *)xmlXPathCastToString(pobj);
931     }
932     goto found;
933   }
934  fallback:
935   if(noit_hash_retr_str(&_compiled_fallback,
936                         path, strlen(path), &str)) {
937     *value = (char *)xmlStrdup((xmlChar *)str);
938     goto found;
939   }
940   rv = 0;
941  found:
942   if(pobj) xmlXPathFreeObject(pobj);
943   if(current_ctxt && current_ctxt != xpath_ctxt)
944     xmlXPathFreeContext(current_ctxt);
945   return rv;
946 }
947 int noit_conf_get_uuid(noit_conf_section_t section,
948                        const char *path, uuid_t out) {
949   char *str;
950   if(_noit_conf_get_string(section,NULL,path,&str)) {
951     if(uuid_parse(str, out) == 0) return 1;
952     return 0;
953   }
954   return 0;
955 }
956 int noit_conf_get_string(noit_conf_section_t section,
957                          const char *path, char **value) {
958   char *str;
959   if(_noit_conf_get_string(section,NULL,path,&str)) {
960     *value = strdup(str);
961     xmlFree(str);
962     return 1;
963   }
964   return 0;
965 }
966 int noit_conf_get_stringbuf(noit_conf_section_t section,
967                             const char *path, char *buf, int len) {
968   char *str;
969   if(_noit_conf_get_string(section,NULL,path,&str)) {
970     strlcpy(buf, str, len);
971     xmlFree(str);
972     return 1;
973   }
974   return 0;
975 }
976 int noit_conf_set_string(noit_conf_section_t section,
977                          const char *path, const char *value) {
978   noit_hash_replace(&_tmp_config,
979                     strdup(path), strlen(path), (void *)strdup(value),
980                     free, free);
981   return 1;
982 }
983 int noit_conf_string_to_int(const char *str) {
984   int base = 10;
985   if(!str) return 0;
986   if(str[0] == '0') {
987     if(str[1] == 'x') base = 16;
988     else base = 8;
989   }
990   return strtol(str, NULL, base);
991 }
992 int noit_conf_get_int(noit_conf_section_t section,
993                       const char *path, int *value) {
994   char *str;
995   if(_noit_conf_get_string(section,NULL,path,&str)) {
996     *value = (int)noit_conf_string_to_int(str);
997     xmlFree(str);
998     return 1;
999   }
1000   return 0;
1001 }
1002 int noit_conf_set_int(noit_conf_section_t section,
1003                       const char *path, int value) {
1004   char buffer[32];
1005   snprintf(buffer, 32, "%d", value);
1006   return noit_conf_set_string(section,path,buffer);
1007 }
1008 float noit_conf_string_to_float(const char *str) {
1009   if(!str) return 0.0;
1010   return atof(str);
1011 }
1012 int noit_conf_get_float(noit_conf_section_t section,
1013                         const char *path, float *value) {
1014   char *str;
1015   if(_noit_conf_get_string(section,NULL,path,&str)) {
1016     *value = noit_conf_string_to_float(str);
1017     xmlFree(str);
1018     return 1;
1019   }
1020   return 0;
1021 }
1022 int noit_conf_set_float(noit_conf_section_t section,
1023                         const char *path, float value) {
1024   char buffer[32];
1025   snprintf(buffer, 32, "%f", value);
1026   return noit_conf_set_string(section,path,buffer);
1027 }
1028 noit_boolean noit_conf_string_to_boolean(const char *str) {
1029   if(!str) return noit_false;
1030   if(!strcasecmp(str, "true") || !strcasecmp(str, "on")) return noit_true;
1031   return noit_false;
1032 }
1033 int noit_conf_get_boolean(noit_conf_section_t section,
1034                           const char *path, noit_boolean *value) {
1035   char *str;
1036   if(_noit_conf_get_string(section,NULL,path,&str)) {
1037     *value = noit_conf_string_to_boolean(str);
1038     xmlFree(str);
1039     return 1;
1040   }
1041   return 0;
1042 }
1043 int noit_conf_set_boolean(noit_conf_section_t section,
1044                           const char *path, noit_boolean value) {
1045   if(value == noit_true)
1046     return noit_conf_set_string(section,path,"true");
1047   return noit_conf_set_string(section,path,"false");
1048 }
1049
1050 struct config_line_vstr {
1051   char *buff;
1052   int raw_len;
1053   int len;
1054   int allocd;
1055   enum { CONFIG_RAW = 0, CONFIG_COMPRESSED, CONFIG_B64 } target, encoded;
1056 };
1057 static int
1058 noit_config_log_write_xml(void *vstr, const char *buffer, int len) {
1059   struct config_line_vstr *clv = vstr;
1060   assert(clv->encoded == CONFIG_RAW);
1061   if(!clv->buff) {
1062     clv->allocd = 8192;
1063     clv->buff = malloc(clv->allocd);
1064   }
1065   while(len + clv->len > clv->allocd) {
1066     char *newbuff;
1067     int newsize = clv->allocd;
1068     newsize <<= 1;
1069     newbuff = realloc(clv->buff, newsize);
1070     if(!newbuff) {
1071       return -1;
1072     }
1073     clv->allocd = newsize;
1074     clv->buff = newbuff;
1075   }
1076   memcpy(clv->buff + clv->len, buffer, len);
1077   clv->len += len;
1078   return len;
1079 }
1080 static int
1081 noit_config_log_close_xml(void *vstr) {
1082   struct config_line_vstr *clv = vstr;
1083   uLong initial_dlen, dlen;
1084   char *compbuff, *b64buff;
1085
1086   if(clv->buff == NULL) {
1087     clv->encoded = clv->target;
1088     return 0;
1089   }
1090   clv->raw_len = clv->len;
1091   assert(clv->encoded == CONFIG_RAW);
1092   if(clv->encoded == clv->target) return 0;
1093
1094   /* Compress */
1095   initial_dlen = dlen = compressBound(clv->len);
1096   compbuff = malloc(initial_dlen);
1097   if(!compbuff) return -1;
1098   if(Z_OK != compress2((Bytef *)compbuff, &dlen,
1099                        (Bytef *)clv->buff, clv->len, 9)) {
1100     noitL(noit_error, "Error compressing config for transmission.\n");
1101     free(compbuff);
1102     return -1;
1103   }
1104   free(clv->buff);
1105   clv->buff = compbuff;
1106   clv->allocd = initial_dlen;
1107   clv->len = dlen;
1108   clv->encoded = CONFIG_COMPRESSED;
1109   if(clv->encoded == clv->target) return 0;
1110
1111   /* Encode */
1112   initial_dlen = ((clv->len + 2) / 3) * 4;
1113   b64buff = malloc(initial_dlen);
1114   dlen = noit_b64_encode((unsigned char *)clv->buff, clv->len,
1115                          b64buff, initial_dlen);
1116   if(dlen == 0) {
1117     free(b64buff);
1118     return -1;
1119   }
1120   free(clv->buff);
1121   clv->buff = b64buff;
1122   clv->allocd = initial_dlen;
1123   clv->len = dlen;
1124   clv->encoded = CONFIG_B64;
1125   if(clv->encoded == clv->target) return 0;
1126   return -1;
1127 }
1128
1129 int
1130 noit_conf_reload(noit_console_closure_t ncct,
1131                  int argc, char **argv,
1132                  noit_console_state_t *state, void *closure) {
1133   XML2CONSOLE(ncct);
1134   if(noit_conf_load_internal(master_config_file)) {
1135     XML2LOG(xml_debug);
1136     nc_printf(ncct, "error loading config\n");
1137     return -1;
1138   }
1139   XML2LOG(xml_debug);
1140   return 0;
1141 }
1142 int
1143 noit_conf_write_terminal(noit_console_closure_t ncct,
1144                          int argc, char **argv,
1145                          noit_console_state_t *state, void *closure) {
1146   xmlOutputBufferPtr out;
1147   xmlCharEncodingHandlerPtr enc;
1148   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
1149   out = xmlOutputBufferCreateIO(noit_console_write_xml,
1150                                 noit_console_close_xml,
1151                                 ncct, enc);
1152   noit_conf_kansas_city_shuffle_undo(master_config);
1153   xmlSaveFormatFileTo(out, master_config, "utf8", 1);
1154   noit_conf_kansas_city_shuffle_redo(master_config);
1155   return 0;
1156 }
1157 int
1158 noit_conf_write_file_console(noit_console_closure_t ncct,
1159                              int argc, char **argv,
1160                              noit_console_state_t *state, void *closure) {
1161   int rv;
1162   char *err = NULL;
1163   rv = noit_conf_write_file(&err);
1164   nc_printf(ncct, "%s\n", err);
1165   if(err) free(err);
1166   return rv;
1167 }
1168 int
1169 noit_conf_write_file(char **err) {
1170   int fd, len;
1171   char master_file_tmp[PATH_MAX];
1172   char errstr[1024];
1173   xmlOutputBufferPtr out;
1174   xmlCharEncodingHandlerPtr enc;
1175   struct stat st;
1176   mode_t mode = 0640; /* the default */
1177
1178   if(stat(master_config_file, &st) == 0)
1179     mode = st.st_mode;
1180   snprintf(master_file_tmp, sizeof(master_file_tmp),
1181            "%s.tmp", master_config_file);
1182   unlink(master_file_tmp);
1183   fd = open(master_file_tmp, O_CREAT|O_EXCL|O_WRONLY, mode);
1184   if(fd < 0) {
1185     snprintf(errstr, sizeof(errstr), "Failed to open tmp file: %s",
1186              strerror(errno));
1187     if(err) *err = strdup(errstr);
1188     return -1;
1189   }
1190   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
1191   out = xmlOutputBufferCreateFd(fd, enc);
1192   if(!out) {
1193     close(fd);
1194     unlink(master_file_tmp);
1195     if(err) *err = strdup("internal error: OutputBufferCreate failed");
1196     return -1;
1197   }
1198   noit_conf_kansas_city_shuffle_undo(master_config);
1199   noit_conf_shatter_write(master_config);
1200   len = xmlSaveFormatFileTo(out, master_config, "utf8", 1);
1201   noit_conf_shatter_postwrite(master_config);
1202   noit_conf_kansas_city_shuffle_redo(master_config);
1203   close(fd);
1204   if(len <= 0) {
1205     if(err) *err = strdup("internal error: writing to tmp file failed.");
1206     return -1;
1207   }
1208   if(rename(master_file_tmp, master_config_file) != 0) {
1209     snprintf(errstr, sizeof(errstr), "Failed to replace file: %s",
1210              strerror(errno));
1211     if(err) *err = strdup(errstr);
1212     return -1;
1213   }
1214   snprintf(errstr, sizeof(errstr), "%d bytes written.", len);
1215   if(err) *err = strdup(errstr);
1216   return 0;
1217 }
1218 char *
1219 noit_conf_xml_in_mem(size_t *len) {
1220   struct config_line_vstr *clv;
1221   xmlOutputBufferPtr out;
1222   xmlCharEncodingHandlerPtr enc;
1223   char *rv;
1224
1225   clv = calloc(1, sizeof(*clv));
1226   clv->target = CONFIG_RAW;
1227   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
1228   out = xmlOutputBufferCreateIO(noit_config_log_write_xml,
1229                                 noit_config_log_close_xml,
1230                                 clv, enc);
1231   noit_conf_kansas_city_shuffle_undo(master_config);
1232   xmlSaveFormatFileTo(out, master_config, "utf8", 1);
1233   noit_conf_kansas_city_shuffle_redo(master_config);
1234   if(clv->encoded != CONFIG_RAW) {
1235     noitL(noit_error, "Error logging configuration\n");
1236     if(clv->buff) free(clv->buff);
1237     free(clv);
1238     return NULL;
1239   }
1240   rv = clv->buff;
1241   *len = clv->len;
1242   free(clv);
1243   return rv;
1244 }
1245
1246 int
1247 noit_conf_write_log() {
1248   static u_int32_t last_write_gen = 0;
1249   static noit_log_stream_t config_log = NULL;
1250   struct timeval __now;
1251   xmlOutputBufferPtr out;
1252   xmlCharEncodingHandlerPtr enc;
1253   struct config_line_vstr *clv;
1254   SETUP_LOG(config, return -1);
1255
1256   /* We know we haven't changed */
1257   if(last_write_gen == __config_gen) return 0;
1258
1259   gettimeofday(&__now, NULL);
1260   clv = calloc(1, sizeof(*clv));
1261   clv->target = CONFIG_B64;
1262   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
1263   out = xmlOutputBufferCreateIO(noit_config_log_write_xml,
1264                                 noit_config_log_close_xml,
1265                                 clv, enc);
1266   noit_conf_kansas_city_shuffle_undo(master_config);
1267   xmlSaveFormatFileTo(out, master_config, "utf8", 1);
1268   noit_conf_kansas_city_shuffle_redo(master_config);
1269   if(clv->encoded != CONFIG_B64) {
1270     noitL(noit_error, "Error logging configuration\n");
1271     if(clv->buff) free(clv->buff);
1272     free(clv);
1273     return -1;
1274   }
1275   noitL(config_log, "n\t%lu.%03lu\t%d\t%.*s\n",
1276         (unsigned long int)__now.tv_sec,
1277         (unsigned long int)__now.tv_usec / 1000UL, clv->raw_len,
1278         clv->len, clv->buff);
1279   free(clv->buff);
1280   free(clv);
1281   last_write_gen = __config_gen;
1282   return 0;
1283 }
1284
1285 struct log_rotate_crutch {
1286   noit_log_stream_t ls;
1287   int seconds;
1288   size_t max_size;
1289 };
1290
1291 static int
1292 noit_conf_log_rotate_size(eventer_t e, int mask, void *closure,
1293                           struct timeval *now) {
1294   struct log_rotate_crutch *lrc = closure;
1295   if(noit_log_stream_written(lrc->ls) > lrc->max_size) {
1296     noit_log_stream_rename(lrc->ls, NOIT_LOG_RENAME_AUTOTIME);
1297     noit_log_stream_reopen(lrc->ls);
1298   }
1299   /* Yes the 5 is arbitrary, but this is cheap */
1300   eventer_add_in_s_us(noit_conf_log_rotate_size, closure, 5, 0);
1301   return 0;
1302 }
1303 static int
1304 noit_conf_log_rotate_time(eventer_t e, int mask, void *closure,
1305                           struct timeval *now) {
1306   struct timeval lnow;
1307   eventer_t newe;
1308   struct log_rotate_crutch *lrc = closure;
1309
1310   if(now) {
1311     noit_log_stream_rename(lrc->ls, NOIT_LOG_RENAME_AUTOTIME);
1312     noit_log_stream_reopen(lrc->ls);
1313   }
1314  
1315   newe = eventer_alloc();
1316   newe->closure = closure;
1317   if(!now) { gettimeofday(&lnow, NULL); now = &lnow; }
1318   if(e)
1319     memcpy(&newe->whence, &e->whence, sizeof(newe->whence));
1320   else if(now) {
1321     memcpy(&newe->whence, now, sizeof(newe->whence));
1322     newe->whence.tv_sec = (newe->whence.tv_sec / lrc->seconds) * lrc->seconds;
1323   }
1324   newe->whence.tv_sec += lrc->seconds;
1325   newe->mask = EVENTER_TIMER;
1326   newe->callback = noit_conf_log_rotate_time;
1327   eventer_add(newe);
1328   return 0;
1329 }
1330 int
1331 noit_conf_log_init_rotate(const char *toplevel, noit_boolean validate) {
1332   int i, cnt = 0, max_time, max_size, rv = 0;
1333   noit_conf_section_t *log_configs;
1334   char path[256];
1335
1336   snprintf(path, sizeof(path), "/%s/logs//log", toplevel);
1337   log_configs = noit_conf_get_sections(NULL, path, &cnt);
1338   noitL(noit_debug, "Found %d %s stanzas\n", cnt, path);
1339   for(i=0; i<cnt; i++) {
1340     noit_log_stream_t ls;
1341     char name[256];
1342
1343     if(!noit_conf_get_stringbuf(log_configs[i],
1344                                 "ancestor-or-self::node()/@name",
1345                                 name, sizeof(name))) {
1346       noitL(noit_error, "log section %d does not have a name attribute\n", i+1);
1347       if(validate) { rv = -1; break; }
1348       else exit(-2);
1349     }
1350     ls = noit_log_stream_find(name);
1351     if(!ls) continue;
1352
1353     if(noit_conf_get_int(log_configs[i],   
1354                          "ancestor-or-self::node()/@rotate_seconds",
1355                          &max_time) && max_time) {
1356       struct log_rotate_crutch *lrc;
1357       if(max_time < 600) {
1358         fprintf(stderr, "rotate_seconds must be >= 600s (10 minutes)\n");
1359         if(validate) { rv = -1; break; }
1360         else exit(-2);
1361       }
1362       if(!validate) {
1363         lrc = calloc(1, sizeof(*lrc));
1364         lrc->ls = ls;
1365         lrc->seconds = max_time;
1366         noit_conf_log_rotate_time(NULL, EVENTER_TIMER, lrc, NULL);
1367       }
1368     }
1369
1370     if(noit_conf_get_int(log_configs[i],   
1371                          "ancestor-or-self::node()/@rotate_bytes",
1372                          &max_size) && max_size) {
1373       struct log_rotate_crutch *lrc;
1374       if(max_size < 102400) {
1375         fprintf(stderr, "rotate_bytes must be >= 102400 (100k)\n");
1376         if(validate) { rv = -1; break; }
1377         else exit(-2);
1378       }
1379       if(!validate) {
1380         lrc = calloc(1, sizeof(*lrc));
1381         lrc->ls = ls;
1382         lrc->max_size = max_size;
1383         noit_conf_log_rotate_size(NULL, EVENTER_TIMER, lrc, NULL);
1384       }
1385     }
1386   }
1387   free(log_configs);
1388   return rv;
1389 }
1390 void
1391 noit_conf_log_init(const char *toplevel) {
1392   int i, cnt = 0, o, ocnt = 0;
1393   noit_conf_section_t *log_configs, *outlets;
1394   char path[256];
1395
1396   snprintf(path, sizeof(path), "/%s/logs//log", toplevel);
1397   log_configs = noit_conf_get_sections(NULL, path, &cnt);
1398   noitL(noit_debug, "Found %d %s stanzas\n", cnt, path);
1399   for(i=0; i<cnt; i++) {
1400     noit_log_stream_t ls;
1401     char name[256], type[256], path[256];
1402     noit_hash_table *config;
1403     noit_boolean disabled, debug, timestamps;
1404
1405     if(!noit_conf_get_stringbuf(log_configs[i],
1406                                 "ancestor-or-self::node()/@name",
1407                                 name, sizeof(name))) {
1408       noitL(noit_error, "log section %d does not have a name attribute\n", i+1);
1409       exit(-1);
1410     }
1411     if(!noit_conf_get_stringbuf(log_configs[i],
1412                                 "ancestor-or-self::node()/@type",
1413                                 type, sizeof(type))) {
1414       type[0] = '\0';
1415     }
1416     if(!noit_conf_get_stringbuf(log_configs[i],
1417                                 "ancestor-or-self::node()/@path",
1418                                 path, sizeof(path))) {
1419       path[0] = '\0';
1420     }
1421     config = noit_conf_get_hash(log_configs[i],
1422                                 "ancestor-or-self::node()/config");
1423     ls = noit_log_stream_new(name, type[0] ? type : NULL,
1424                              path[0] ? path : NULL, NULL, config);
1425     if(!ls) {
1426       fprintf(stderr, "Error configuring log: %s[%s:%s]\n", name, type, path);
1427       exit(-1);
1428     }
1429
1430     if(noit_conf_get_boolean(log_configs[i],
1431                              "ancestor-or-self::node()/@disabled",
1432                              &disabled) && disabled)
1433       ls->enabled = 0;
1434      
1435     if(noit_conf_get_boolean(log_configs[i],
1436                              "ancestor-or-self::node()/@debug",
1437                              &debug) && debug)
1438       ls->debug = 1;
1439      
1440     if(noit_conf_get_boolean(log_configs[i],
1441                              "ancestor-or-self::node()/@timestamps",
1442                              &timestamps))
1443       ls->timestamps = timestamps ? 1 : 0;
1444  
1445     outlets = noit_conf_get_sections(log_configs[i],
1446                                      "ancestor-or-self::node()/outlet", &ocnt);
1447     noitL(noit_debug, "Found %d outlets for log '%s'\n", ocnt, name);
1448
1449     for(o=0; o<ocnt; o++) {
1450       noit_log_stream_t outlet;
1451       char oname[256];
1452       noit_conf_get_stringbuf(outlets[o], "@name",
1453                               oname, sizeof(oname));
1454       outlet = noit_log_stream_find(oname);
1455       if(!outlet) {
1456         fprintf(stderr, "Cannot find outlet '%s' for %s[%s:%s]\n", oname,
1457               name, type, path);
1458         exit(-1);
1459       }
1460       else
1461         noit_log_stream_add_stream(ls, outlet);
1462     }
1463     if(outlets) free(outlets);
1464   }
1465   if(log_configs) free(log_configs);
1466   if(noit_conf_log_init_rotate(toplevel, noit_true)) exit(-1);
1467 }
1468
1469 static void
1470 conf_t_userdata_free(void *data) {
1471   noit_conf_t_userdata_t *info = data;
1472   if(info) {
1473     if(info->path) free(info->path);
1474     free(info);
1475   }
1476 }
1477
1478 static int
1479 noit_console_state_conf_terminal(noit_console_closure_t ncct,
1480                                  int argc, char **argv,
1481                                  noit_console_state_t *state, void *closure) {
1482   noit_conf_t_userdata_t *info;
1483   if(argc) {
1484     nc_printf(ncct, "extra arguments not expected.\n");
1485     return -1;
1486   }
1487   info = calloc(1, sizeof(*info));
1488   info->path = strdup("/");
1489   noit_console_userdata_set(ncct, NOIT_CONF_T_USERDATA, info,
1490                             conf_t_userdata_free);
1491   noit_console_state_push_state(ncct, state);
1492   noit_console_state_init(ncct);
1493   return 0;
1494 }
1495 static int
1496 noit_console_config_section(noit_console_closure_t ncct,
1497                             int argc, char **argv,
1498                             noit_console_state_t *state, void *closure) {
1499   const char *err = "internal error";
1500   char *path, xpath[1024];
1501   noit_conf_t_userdata_t *info;
1502   xmlXPathObjectPtr pobj = NULL;
1503   xmlXPathContextPtr xpath_ctxt = NULL;
1504   xmlNodePtr node = NULL, newnode;
1505   vpsized_int delete = (vpsized_int)closure;
1506
1507   noit_conf_xml_xpath(NULL, &xpath_ctxt);
1508   if(argc != 1) {
1509     nc_printf(ncct, "requires one argument\n");
1510     return -1;
1511   }
1512   if(strchr(argv[0], '/')) {
1513     nc_printf(ncct, "invalid section name\n");
1514     return -1;
1515   }
1516   if(!strcmp(argv[0], "check") ||
1517      !strcmp(argv[0], "noit") ||
1518      !strcmp(argv[0], "filterset") ||
1519      !strcmp(argv[0], "config")) {
1520     nc_printf(ncct, "%s is reserved.\n", argv[0]);
1521     return -1;
1522   }
1523   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
1524   if(!strcmp(info->path, "/")) {
1525     nc_printf(ncct, "manipulation of toplevel section disallowed\n");
1526     return -1;
1527   }
1528
1529   if(delete) {
1530     /* We cannot delete if we have checks */
1531     snprintf(xpath, sizeof(xpath), "/%s%s/%s//check", root_node_name,
1532              info->path, argv[0]);
1533     pobj = xmlXPathEval((xmlChar *)xpath, xpath_ctxt);
1534     if(!pobj || pobj->type != XPATH_NODESET ||
1535        !xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
1536       err = "cannot delete section, has checks";
1537       goto bad;
1538     }
1539     if(pobj) xmlXPathFreeObject(pobj);
1540   }
1541
1542   snprintf(xpath, sizeof(xpath), "/%s%s/%s", root_node_name,
1543            info->path, argv[0]);
1544   pobj = xmlXPathEval((xmlChar *)xpath, xpath_ctxt);
1545   if(!pobj || pobj->type != XPATH_NODESET) {
1546     err = "internal error: cannot detect section";
1547     goto bad;
1548   }
1549   if(!delete && !xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
1550     if(xmlXPathNodeSetGetLength(pobj->nodesetval) == 1) {
1551       node = xmlXPathNodeSetItem(pobj->nodesetval, 0);
1552       if(info->path) free(info->path);
1553       info->path = strdup((char *)xmlGetNodePath(node) +
1554                           1 + strlen(root_node_name));
1555       goto cdout;
1556     }
1557     err = "cannot create section";
1558     goto bad;
1559   }
1560   if(delete && xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
1561     err = "no such section";
1562     goto bad;
1563   }
1564   if(delete) {
1565     node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
1566     CONF_REMOVE(node);
1567     xmlUnlinkNode(node);
1568     noit_conf_mark_changed();
1569     return 0;
1570   }
1571   if(pobj) xmlXPathFreeObject(pobj);
1572   pobj = NULL;
1573
1574   if(!strcmp(argv[0],"include")) {
1575     err = "include is a reserved section name";
1576     goto bad;
1577   }
1578   path = strcmp(info->path, "/") ? info->path : "";
1579   snprintf(xpath, sizeof(xpath), "/%s%s", root_node_name, path);
1580   pobj = xmlXPathEval((xmlChar *)xpath, xpath_ctxt);
1581   if(!pobj || pobj->type != XPATH_NODESET ||
1582      xmlXPathNodeSetGetLength(pobj->nodesetval) != 1) {
1583     err = "path invalid?";
1584     goto bad;
1585   }
1586   node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
1587   if((newnode = xmlNewChild(node, NULL, (xmlChar *)argv[0], NULL)) != NULL) {
1588     noit_conf_mark_changed();
1589     if(info->path) free(info->path);
1590     info->path = strdup((char *)xmlGetNodePath(newnode) + 1 +
1591                         strlen(root_node_name));
1592   }
1593   else {
1594     err = "failed to create section";
1595     goto bad;
1596   }
1597  cdout:
1598   if(pobj) xmlXPathFreeObject(pobj);
1599   return 0;
1600  bad:
1601   if(pobj) xmlXPathFreeObject(pobj);
1602   nc_printf(ncct, "%s\n", err);
1603   return -1;
1604 }
1605
1606 int
1607 noit_console_generic_show(noit_console_closure_t ncct,
1608                           int argc, char **argv,
1609                           noit_console_state_t *state, void *closure) {
1610   int i, cnt, titled = 0, cliplen = 0;
1611   const char *path = "", *basepath = NULL;
1612   char xpath[1024];
1613   noit_conf_t_userdata_t *info = NULL;
1614   xmlXPathObjectPtr pobj = NULL;
1615   xmlXPathContextPtr xpath_ctxt = NULL, current_ctxt;
1616   xmlDocPtr master_config = NULL;
1617   xmlNodePtr node = NULL;
1618
1619   noit_conf_xml_xpath(&master_config, &xpath_ctxt);
1620   if(argc > 1) {
1621     nc_printf(ncct, "too many arguments\n");
1622     return -1;
1623   }
1624
1625   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
1626   if(info && info->path) path = basepath = info->path;
1627   if(!info && argc == 0) {
1628     nc_printf(ncct, "argument required when not in configuration mode\n");
1629     return -1;
1630   }
1631
1632   if(argc == 1) path = argv[0];
1633   if(!basepath) basepath = path;
1634
1635   /* { / } is a special case */
1636   if(!strcmp(basepath, "/")) basepath = "";
1637   if(!strcmp(path, "/")) path = "";
1638
1639   if(!master_config) {
1640     nc_printf(ncct, "no config\n");
1641     return -1;
1642   }
1643
1644   /* { / } is the only path that will end with a /
1645    * in XPath { / / * } means something _entirely different than { / * }
1646    * Ever notice how it is hard to describe xpath in C comments?
1647    */
1648   /* We don't want to show the root node */
1649   cliplen = strlen(root_node_name) + 2; /* /name/ */
1650
1651   /* If we are in configuration mode
1652    * and we are without an argument or the argument is absolute,
1653    * clip the current path off */
1654   if(info && (argc == 0 || path[0] != '/')) cliplen += strlen(basepath);
1655   if(!path[0] || path[0] == '/') /* base only, or absolute path requested */
1656     snprintf(xpath, sizeof(xpath), "/%s%s/@*", root_node_name, path);
1657   else
1658     snprintf(xpath, sizeof(xpath), "/%s%s/%s/@*", root_node_name,
1659              basepath, path);
1660
1661   current_ctxt = xpath_ctxt;
1662   pobj = xmlXPathEval((xmlChar *)xpath, current_ctxt);
1663   if(!pobj || pobj->type != XPATH_NODESET) {
1664     nc_printf(ncct, "no such object\n");
1665     goto bad;
1666   }
1667   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
1668   titled = 0;
1669   for(i=0; i<cnt; i++) {
1670     node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, i);
1671     if(node->children && node->children == xmlGetLastChild(node) &&
1672       xmlNodeIsText(node->children)) {
1673       if(!titled++) nc_printf(ncct, "== Section Settings ==\n");
1674       nc_printf(ncct, "%s: %s\n", xmlGetNodePath(node) + cliplen,
1675                 xmlXPathCastNodeToString(node->children));
1676     }
1677   }
1678   xmlXPathFreeObject(pobj);
1679
1680   /* _shorten string_ turning last { / @ * } to { / * } */
1681   if(!path[0] || path[0] == '/') /* base only, or absolute path requested */
1682     snprintf(xpath, sizeof(xpath), "/%s%s/*", root_node_name, path);
1683   else
1684     snprintf(xpath, sizeof(xpath), "/%s%s/%s/*",
1685              root_node_name, basepath, path);
1686   pobj = xmlXPathEval((xmlChar *)xpath, current_ctxt);
1687   if(!pobj || pobj->type != XPATH_NODESET) {
1688     nc_printf(ncct, "no such object\n");
1689     goto bad;
1690   }
1691   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
1692   titled = 0;
1693   for(i=0; i<cnt; i++) {
1694     node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, i);
1695     if(!(node->children && node->children == xmlGetLastChild(node) &&
1696          xmlNodeIsText(node->children))) {
1697       if(!titled++) nc_printf(ncct, "== Subsections ==\n");
1698       nc_printf(ncct, "%s\n", xmlGetNodePath(node) + cliplen);
1699     }
1700   }
1701   xmlXPathFreeObject(pobj);
1702   return 0;
1703  bad:
1704   if(pobj) xmlXPathFreeObject(pobj);
1705   return -1;
1706 }
1707 int
1708 noit_console_config_cd(noit_console_closure_t ncct,
1709                        int argc, char **argv,
1710                        noit_console_state_t *state, void *closure) {
1711   const char *err = "internal error";
1712   char *path, xpath[1024];
1713   noit_conf_t_userdata_t *info;
1714   xmlXPathObjectPtr pobj = NULL;
1715   xmlXPathContextPtr xpath_ctxt = NULL, current_ctxt;
1716   xmlNodePtr node = NULL;
1717   char *dest;
1718
1719   noit_conf_xml_xpath(NULL, &xpath_ctxt);
1720   if(argc != 1 && !closure) {
1721     nc_printf(ncct, "requires one argument\n");
1722     return -1;
1723   }
1724   dest = argc ? argv[0] : (char *)closure;
1725   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
1726   if(dest[0] == '/')
1727     snprintf(xpath, sizeof(xpath), "/%s%s", root_node_name, dest);
1728   else {
1729     snprintf(xpath, sizeof(xpath), "/%s%s/%s", root_node_name,
1730              info->path, dest);
1731   }
1732   if(xpath[strlen(xpath)-1] == '/') xpath[strlen(xpath)-1] = '\0';
1733
1734   current_ctxt = xpath_ctxt;
1735   pobj = xmlXPathEval((xmlChar *)xpath, current_ctxt);
1736   if(!pobj || pobj->type != XPATH_NODESET ||
1737      xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
1738     err = "no such section";
1739     goto bad;
1740   }
1741   if(xmlXPathNodeSetGetLength(pobj->nodesetval) > 1) {
1742     err = "ambiguous section";
1743     goto bad;
1744   }
1745
1746   node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
1747   if(!node) {
1748     err = "internal XML error";
1749     goto bad;
1750   }
1751   if(!strcmp((char *)node->name, "check") ||
1752      !strcmp((char *)node->name, "noit") ||
1753      !strcmp((char *)node->name, "filterset") ||
1754      !strcmp((char *)node->name, "config")) {
1755     err = "reserved word";
1756     goto bad;
1757   }
1758   path = (char *)xmlGetNodePath(node);
1759   if(strlen(path) < strlen(root_node_name) + 1 ||
1760      strncmp(path + 1, root_node_name, strlen(root_node_name)) ||
1761      (path[strlen(root_node_name) + 1] != '/' &&
1762       path[strlen(root_node_name) + 1] != '\0')) {
1763     err = "new path outside out tree";
1764     goto bad;
1765   }
1766   free(info->path);
1767   if(!strcmp(path + 1, root_node_name))
1768     info->path = strdup("/");
1769   else
1770     info->path = strdup((char *)xmlGetNodePath(node) + 1 +
1771                         strlen(root_node_name));
1772   if(pobj) xmlXPathFreeObject(pobj);
1773   if(closure) noit_console_state_pop(ncct, argc, argv, NULL, NULL);
1774   return 0;
1775  bad:
1776   if(pobj) xmlXPathFreeObject(pobj);
1777   nc_printf(ncct, "%s [%s]\n", err, xpath);
1778   return -1;
1779 }
1780
1781 char *
1782 conf_t_prompt(EditLine *el) {
1783   noit_console_closure_t ncct;
1784   noit_conf_t_userdata_t *info;
1785   static char *tl = "noit(conf)# ";
1786   static char *pfmt = "noit(conf:%s%s)# ";
1787   int path_len, max_len;
1788
1789   el_get(el, EL_USERDATA, (void *)&ncct);
1790   if(!ncct) return tl;
1791   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
1792   if(!info) return tl;
1793
1794   path_len = strlen(info->path);
1795   max_len = sizeof(info->prompt) - (strlen(pfmt) - 4 /* %s%s */) - 1 /* \0 */;
1796   if(path_len > max_len)
1797     snprintf(info->prompt, sizeof(info->prompt),
1798              pfmt, "...", info->path + path_len - max_len + 3 /* ... */);
1799   else
1800     snprintf(info->prompt, sizeof(info->prompt), pfmt, "", info->path);
1801   return info->prompt;
1802 }
1803
1804 #define NEW_STATE(a) (a) = noit_console_state_alloc()
1805 #define ADD_CMD(a,cmd,func,ac,ss,c) \
1806   noit_console_state_add_cmd((a), \
1807     NCSCMD(cmd, func, ac, ss, c))
1808 #define DELEGATE_CMD(a,cmd,ac,ss) \
1809   noit_console_state_add_cmd((a), \
1810     NCSCMD(cmd, noit_console_state_delegate, ac, ss, NULL))
1811
1812 void noit_console_conf_init() {
1813   noit_console_state_t *tl, *_conf_state, *_conf_t_state,
1814                        *_write_state, *_unset_state;
1815
1816   tl = noit_console_state_initial();
1817
1818   /* write <terimal|memory|file> */
1819   NEW_STATE(_write_state);
1820   ADD_CMD(_write_state, "terminal", noit_conf_write_terminal, NULL, NULL, NULL);
1821   ADD_CMD(_write_state, "file", noit_conf_write_file_console, NULL, NULL, NULL);
1822   /* write memory?  It's to a file, but I like router syntax */
1823   ADD_CMD(_write_state, "memory", noit_conf_write_file_console, NULL, NULL, NULL);
1824
1825   NEW_STATE(_unset_state);
1826   ADD_CMD(_unset_state, "section",
1827           noit_console_config_section, NULL, NULL, (void *)1);
1828
1829   NEW_STATE(_conf_t_state);
1830   _conf_t_state->console_prompt_function = conf_t_prompt;
1831   noit_console_state_add_cmd(_conf_t_state, &console_command_exit);
1832
1833   ADD_CMD(_conf_t_state, "ls", noit_console_generic_show, NULL, NULL, NULL);
1834   ADD_CMD(_conf_t_state, "cd", noit_console_config_cd, NULL, NULL, NULL);
1835   ADD_CMD(_conf_t_state, "section",
1836           noit_console_config_section, NULL, NULL, (void *)0);
1837
1838   DELEGATE_CMD(_conf_t_state, "write",
1839                noit_console_opt_delegate, _write_state);
1840   DELEGATE_CMD(_conf_t_state, "no", noit_console_opt_delegate, _unset_state);
1841
1842   NEW_STATE(_conf_state);
1843   ADD_CMD(_conf_state, "terminal",
1844           noit_console_state_conf_terminal, NULL, _conf_t_state, NULL);
1845
1846   ADD_CMD(tl, "configure",
1847           noit_console_state_delegate, noit_console_opt_delegate,
1848           _conf_state, NULL);
1849   ADD_CMD(tl, "write",
1850           noit_console_state_delegate, noit_console_opt_delegate,
1851           _write_state, NULL);
1852 }
1853
Note: See TracBrowser for help on using the browser.