root/src/noit_conf.c

Revision dd70a5f3aa29668a783b4e0da5cee3a1d3aab102, 62.3 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 1 month ago)

some starter support for SOCK_CLOEXEC and O_CLOEXEC

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