root/src/noit_conf.c

Revision 3e27293ae59da2993946ba30455d79d57a66ba91, 72.2 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 2 days ago)

cleanup error logging when snippets are missing

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