root/src/noit_conf.c

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

fix xmlGetProp leak

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