root/src/noit_conf.c

Revision d5db0b89dd6ef5ab8baaa06225cd92898bd0dcea, 61.1 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 2 years ago)

automatic registration of module namespaces

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