root/src/noit_conf.c

Revision 550b086230370dc14c332e232a14797d12a6003d, 76.0 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 3 weeks ago)

Prevent spurious XML errors from being logged to stderr.

The error functions in libxml are thread local, so the error loggers
might not be set yet. Set them before we do something that might
generate error messages.

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