root/src/noit_conf.c

Revision 94124b8784e6836e9df15cdf3216eee5a26021c5, 70.9 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 2 months ago)

Provide noit_conf_request_write() to coalesce write requests within one second

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