root/src/noit_conf.c

Revision 80f47472f0d3f59e9aceccab41a0940edd7946d3, 60.4 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 6 years ago)

Merge branch 'shatter'

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