root/src/noit_conf.c

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

These are all memory leak fixes.
They are low priority leaks as they only happen at boot and
only consititue a one-time loss of 200 bytes or so on my box.
Cleanliness is cleanliness.

  • 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 <errno.h>
39 #include <assert.h>
40 #include <libxml/parser.h>
41 #include <libxml/tree.h>
42 #include <libxml/xpath.h>
43 #include <zlib.h>
44
45 #include "noit_conf.h"
46 #include "noit_check.h"
47 #include "noit_console.h"
48 #include "utils/noit_hash.h"
49 #include "utils/noit_log.h"
50 #include "utils/noit_b64.h"
51
52 /* tmp hash impl, replace this with something nice */
53 static noit_log_stream_t xml_debug = NULL;
54 #define XML2LOG(log) do { \
55   xmlSetGenericErrorFunc(log, noit_conf_xml_error_func); \
56   xmlSetStructuredErrorFunc(log, noit_conf_xml_error_ext_func); \
57 } while(0)
58 #define XML2CONSOLE(ncct) do { \
59   xmlSetGenericErrorFunc(ncct, noit_conf_xml_console_error_func); \
60   xmlSetStructuredErrorFunc(ncct, noit_conf_xml_console_error_ext_func); \
61 } while(0)
62 static noit_hash_table _tmp_config = NOIT_HASH_EMPTY;
63 static xmlDocPtr master_config = NULL;
64 static int config_include_cnt = -1;
65 static struct {
66   xmlNodePtr insertion_point;
67   xmlNodePtr old_children;
68   xmlDocPtr doc;
69   xmlNodePtr root;
70 } *config_include_nodes = NULL;
71
72 static char *root_node_name = NULL;
73 static char master_config_file[PATH_MAX] = "";
74 static xmlXPathContextPtr xpath_ctxt = NULL;
75
76 /* This is used to notice config changes and journal the config out
77  * using a user-specified function.  It supports allowing multiple config
78  * changed to coalesce so you don't write out 1000 changes in a few seconds.
79  */
80 static u_int32_t __config_gen = 0;
81 static u_int32_t __config_coalesce = 0;
82 static u_int32_t __config_coalesce_time = 0;
83 void noit_conf_coalesce_changes(u_int32_t seconds) {
84   __config_coalesce_time = seconds;
85 }
86 void noit_conf_mark_changed() {
87   /* increment the change counter -- in case anyone cares */
88   __config_gen++;
89   /* reset the coalesce counter.  It is decremented each second and
90    * the journal function fires on a transition from 1 => 0
91    */
92   __config_coalesce = __config_coalesce_time;
93 }
94 struct recurrent_journaler {
95   int (*journal_config)(void *);
96   void *jc_closure;
97 };
98 static int
99 noit_conf_watch_config_and_journal(eventer_t e, int mask, void *closure,
100                                    struct timeval *now) {
101   struct recurrent_journaler *rj = closure;
102   eventer_t newe;
103
104   if(__config_coalesce == 1)
105     rj->journal_config(rj->jc_closure);
106   if(__config_coalesce > 0)
107     __config_coalesce--;
108
109   /* Schedule the same event to fire a second form now */
110   newe = eventer_alloc();
111   gettimeofday(&newe->whence, NULL);
112   newe->whence.tv_sec += 1;
113   newe->mask = EVENTER_TIMER;
114   newe->callback = noit_conf_watch_config_and_journal;
115   newe->closure = closure;
116   eventer_add(newe);
117   return 0;
118 }
119 void
120 noit_conf_watch_and_journal_watchdog(int (*f)(void *), void *c) {
121   static int callbacknamed = 0;
122   struct recurrent_journaler *rj;
123   struct timeval __now;
124
125   if(!callbacknamed) {
126     callbacknamed = 1;
127     eventer_name_callback("noit_conf_watch_config_and_journal",
128                           noit_conf_watch_config_and_journal);
129   }
130   rj = calloc(1, sizeof(*rj));
131   rj->journal_config = f;
132   rj->jc_closure = c;
133   gettimeofday(&__now, NULL);
134   noit_conf_watch_config_and_journal(NULL, EVENTER_TIMER, rj, &__now);
135 }
136
137 static noit_hash_table _compiled_fallback = NOIT_HASH_EMPTY;
138 static struct {
139   const char *key;
140   const char *val;
141 } config_info[] = {
142   /*
143    * These are compile-time fallbacks to be used in the event
144    * that the current running config does not have values for
145    * these config paths.
146    *
147    * PLEASE: keep them alphabetically sorted.
148    */
149   { "/%s/eventer/@implementation", DEFAULT_EVENTER },
150   { "/%s/modules/@directory", MODULES_DIR },
151
152   { NULL, NULL }
153 };
154
155 void noit_conf_xml_console_error_func(void *ctx, const char *format, ...) {
156   noit_console_closure_t ncct = ctx;
157   va_list arg;
158   if(!ncct) return;
159   va_start(arg, format);
160   nc_vprintf(ncct, format, arg);
161   va_end(arg);
162 }
163
164 void noit_conf_xml_console_error_ext_func(void *ctx, xmlErrorPtr err) {
165   noit_console_closure_t ncct = ctx;
166   if(!ctx) return;
167   if(err->file)
168     nc_printf(ncct, "XML error [%d/%d] in %s on line %d %s\n",
169               err->domain, err->code, err->file, err->line, err->message);
170   else
171     nc_printf(ncct, "XML error [%d/%d] %s\n",
172               err->domain, err->code, err->message);
173 }
174
175 void noit_conf_xml_error_func(void *ctx, const char *format, ...) {
176   struct timeval __now;
177   noit_log_stream_t ls = ctx;
178   va_list arg;
179   if(!ls) return;
180   va_start(arg, format);
181   gettimeofday(&__now,  NULL);
182   noit_vlog(ls, &__now, __FILE__, __LINE__, format, arg);
183   va_end(arg);
184 }
185 void noit_conf_xml_error_ext_func(void *ctx, xmlErrorPtr err) {
186   struct timeval __now;
187   noit_log_stream_t ls = ctx;
188   if(!ls) return;
189   gettimeofday(&__now,  NULL);
190   if(err->file)
191     noit_log(ls, &__now, err->file, err->line,
192              "XML error [%d/%d] in %s on line %d %s\n",
193              err->domain, err->code, err->file, err->line, err->message);
194   else
195     noit_log(ls, &__now, err->file, err->line,
196              "XML error [%d/%d] %s\n",
197              err->domain, err->code, err->message);
198 }
199
200
201 DECLARE_CHECKER(name)
202 void noit_conf_init(const char *toplevel) {
203   int i;
204   char keystr[256];
205
206   xml_debug = noit_log_stream_find("debug/xml");
207
208   COMPILE_CHECKER(name, "^[-_\\.:/a-zA-Z0-9]+$");
209   XML2LOG(noit_error);
210   for(i = 0; config_info[i].key != NULL; i++) {
211     snprintf(keystr, sizeof(keystr), config_info[i].key, toplevel);
212     noit_hash_store(&_compiled_fallback,
213                     strdup(keystr), strlen(keystr),
214                     (void *)strdup(config_info[i].val));
215   }
216   xmlKeepBlanksDefault(0);
217   xmlInitParser();
218   xmlXPathInit();
219 }
220
221 void
222 noit_conf_magic_separate(xmlDocPtr doc) {
223   assert(config_include_cnt != -1);
224   if(config_include_nodes) {
225     int i;
226     for(i=0; i<config_include_cnt; i++) {
227       if(config_include_nodes[i].doc) {
228         xmlNodePtr n;
229         for(n=config_include_nodes[i].insertion_point->children;
230             n; n = n->next)
231           n->parent = config_include_nodes[i].root;
232         config_include_nodes[i].insertion_point->children =
233           config_include_nodes[i].old_children;
234         xmlFreeDoc(config_include_nodes[i].doc);
235       }
236     }
237     free(config_include_nodes);
238   }
239   config_include_nodes = NULL;
240   config_include_cnt = -1;
241 }
242 void
243 noit_conf_kansas_city_shuffle_redo(xmlDocPtr doc) {
244   if(config_include_nodes) {
245     int i;
246     for(i=0; i<config_include_cnt; i++) {
247       if(config_include_nodes[i].doc) {
248         xmlNodePtr n;
249         config_include_nodes[i].insertion_point->children =
250           config_include_nodes[i].root->children;
251         for(n=config_include_nodes[i].insertion_point->children;
252             n; n = n->next)
253           n->parent = config_include_nodes[i].insertion_point;
254       }
255     }
256   }
257 }
258 void
259 noit_conf_kansas_city_shuffle_undo(xmlDocPtr doc) {
260   if(config_include_nodes) {
261     int i;
262     for(i=0; i<config_include_cnt; i++) {
263       if(config_include_nodes[i].doc) {
264         xmlNodePtr n;
265         for(n=config_include_nodes[i].insertion_point->children;
266             n; n = n->next)
267           n->parent = config_include_nodes[i].root;
268         config_include_nodes[i].insertion_point->children =
269           config_include_nodes[i].old_children;
270       }
271     }
272   }
273 }
274 int
275 noit_conf_magic_mix(const char *parentfile, xmlDocPtr doc) {
276   xmlXPathContextPtr mix_ctxt = NULL;
277   xmlXPathObjectPtr pobj = NULL;
278   xmlNodePtr node;
279   int i, cnt, rv = 0;
280
281   assert(config_include_cnt == -1);
282
283   config_include_cnt = 0;
284   mix_ctxt = xmlXPathNewContext(doc);
285   pobj = xmlXPathEval((xmlChar *)"//include[@file]", mix_ctxt);
286   if(!pobj) goto out;
287   if(pobj->type != XPATH_NODESET) goto out;
288   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto out;
289   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
290   if(cnt > 0)
291     config_include_nodes = calloc(cnt, sizeof(*config_include_nodes));
292   for(i=0; i<cnt; i++) {
293     char *path, *infile;
294     node = xmlXPathNodeSetItem(pobj->nodesetval, i);
295     path = (char *)xmlGetProp(node, (xmlChar *)"file");
296     if(!path) continue;
297     if(*path == '/') infile = strdup(path);
298     else {
299       char *cp;
300       infile = malloc(PATH_MAX);
301       strlcpy(infile, parentfile, PATH_MAX);
302       for(cp = infile + strlen(infile) - 1; cp > infile; cp--) {
303         if(*cp == '/') { *cp = '\0'; break; }
304         else *cp = '\0';
305       }
306       strlcat(infile, "/", PATH_MAX);
307       strlcat(infile, path, PATH_MAX);
308     }
309     xmlFree(path);
310     config_include_nodes[i].doc = xmlParseFile(infile);
311     if(config_include_nodes[i].doc) {
312       xmlNodePtr n;
313       config_include_nodes[i].insertion_point = node;
314       config_include_nodes[i].root = xmlDocGetRootElement(config_include_nodes[i].doc);
315       config_include_nodes[i].old_children = node->children;
316       node->children = config_include_nodes[i].root->children;
317       for(n=node->children; n; n = n->next)
318         n->parent = config_include_nodes[i].insertion_point;
319     }
320     else {
321       noitL(noit_error, "Could not load: '%s'\n", infile);
322       rv = -1;
323     }
324     free(infile);
325   }
326   config_include_cnt = cnt;
327   noitL(noit_debug, "Processed %d includes\n", config_include_cnt);
328  out:
329   if(pobj) xmlXPathFreeObject(pobj);
330   if(mix_ctxt) xmlXPathFreeContext(mix_ctxt);
331   return rv;
332 }
333
334 static int noit_conf_load_internal(const char *path) {
335   int rv = 0;
336   xmlDocPtr new_config;
337   xmlNodePtr root;
338   new_config = xmlParseFile(path);
339   if(new_config) {
340     root = xmlDocGetRootElement(new_config);
341     if(root_node_name) free(root_node_name);
342     root_node_name = strdup((char *)root->name);
343
344     if(master_config) {
345       /* separate all includes */
346       noit_conf_magic_separate(master_config);
347       xmlFreeDoc(master_config);
348     }
349     if(xpath_ctxt) xmlXPathFreeContext(xpath_ctxt);
350
351     master_config = new_config;
352     /* mixin all the includes */
353     if(noit_conf_magic_mix(path, master_config)) rv = -1;
354
355     xpath_ctxt = xmlXPathNewContext(master_config);
356     if(path != master_config_file)
357       if(realpath(path, master_config_file) == NULL)
358         noitL(noit_error, "realpath failed: %s\n", strerror(errno));
359     noit_conf_mark_changed();
360     return rv;
361   }
362   rv = -1;
363   return rv;
364 }
365 int noit_conf_load(const char *path) {
366   int rv;
367   XML2LOG(noit_error);
368   rv = noit_conf_load_internal(path);
369   XML2LOG(xml_debug);
370   return rv;
371 }
372
373 char *noit_conf_config_filename() {
374   return strdup(master_config_file);
375 }
376
377 int noit_conf_xml_xpath(xmlDocPtr *mc, xmlXPathContextPtr *xp) {
378   if(mc) *mc = master_config;
379   if(xp) *xp = xpath_ctxt;
380   return 0;
381 }
382 int noit_conf_save(const char *path) {
383   return -1;
384 }
385
386 void noit_conf_get_elements_into_hash(noit_conf_section_t section,
387                                       const char *path,
388                                       noit_hash_table *table) {
389   int i, cnt;
390   xmlXPathObjectPtr pobj = NULL;
391   xmlXPathContextPtr current_ctxt;
392   xmlNodePtr current_node = (xmlNodePtr)section;
393   xmlNodePtr node;
394
395   current_ctxt = xpath_ctxt;
396   if(current_node) {
397     current_ctxt = xmlXPathNewContext(master_config);
398     current_ctxt->node = current_node;
399   }
400   pobj = xmlXPathEval((xmlChar *)path, current_ctxt);
401   if(!pobj) goto out;
402   if(pobj->type != XPATH_NODESET) goto out;
403   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto out;
404   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
405   for(i=0; i<cnt; i++) {
406     char *value;
407     node = xmlXPathNodeSetItem(pobj->nodesetval, i);
408     value = (char *)xmlXPathCastNodeToString(node);
409     noit_hash_replace(table,
410                       strdup((char *)node->name), strlen((char *)node->name),
411                       strdup(value), free, free);
412     xmlFree(value);
413   }
414  out:
415   if(pobj) xmlXPathFreeObject(pobj);
416   if(current_ctxt && current_ctxt != xpath_ctxt)
417     xmlXPathFreeContext(current_ctxt);
418 }
419 void noit_conf_get_into_hash(noit_conf_section_t section,
420                              const char *path,
421                              noit_hash_table *table) {
422   unsigned int cnt;
423   xmlXPathObjectPtr pobj = NULL;
424   xmlXPathContextPtr current_ctxt;
425   xmlNodePtr current_node = (xmlNodePtr)section;
426   xmlNodePtr node, parent_node;
427   char xpath_expr[1024];
428   char *inheritid;
429
430   current_ctxt = xpath_ctxt;
431   if(current_node) {
432     current_ctxt = xmlXPathNewContext(master_config);
433     current_ctxt->node = current_node;
434   }
435   if(path[0] == '/')
436     strlcpy(xpath_expr, path, sizeof(xpath_expr));
437   else
438     snprintf(xpath_expr, sizeof(xpath_expr),
439              "ancestor-or-self::node()/%s", path);
440   pobj = xmlXPathEval((xmlChar *)xpath_expr, current_ctxt);
441   if(!pobj) goto out;
442   if(pobj->type != XPATH_NODESET) goto out;
443   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto out;
444   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
445   /* These are in the order of root to leaf
446    * We want to recurse... apply:
447    *   1. our parent's config
448    *   2. our "inherit" config if it exists.
449    *   3. our config.
450    */
451   node = xmlXPathNodeSetItem(pobj->nodesetval, cnt-1);
452   /* 1. */
453   if(cnt > 1 && node) {
454     parent_node = xmlXPathNodeSetItem(pobj->nodesetval, cnt-2);
455     if(parent_node != current_node)
456       noit_conf_get_into_hash(parent_node, (const char *)node->name, table);
457   }
458   /* 2. */
459   inheritid = (char *)xmlGetProp(node, (xmlChar *)"inherit");
460   if(inheritid) {
461     snprintf(xpath_expr, sizeof(xpath_expr), "//*[@id=\"%s\"]", inheritid);
462     noit_conf_get_into_hash(NULL, xpath_expr, table);
463     xmlFree(inheritid);
464   }
465   /* 3. */
466   noit_conf_get_elements_into_hash(node, "*", table);
467
468  out:
469   if(pobj) xmlXPathFreeObject(pobj);
470   if(current_ctxt && current_ctxt != xpath_ctxt)
471     xmlXPathFreeContext(current_ctxt);
472 }
473 noit_hash_table *noit_conf_get_hash(noit_conf_section_t section,
474                                     const char *path) {
475   noit_hash_table *table = NULL;
476
477   table = calloc(1, sizeof(*table));
478   noit_conf_get_into_hash(section, path, table);
479   return table;
480 }
481 noit_conf_section_t noit_conf_get_section(noit_conf_section_t section,
482                                           const char *path) {
483   noit_conf_section_t subsection = NULL;
484   xmlXPathObjectPtr pobj = NULL;
485   xmlXPathContextPtr current_ctxt;
486   xmlNodePtr current_node = (xmlNodePtr)section;
487
488   current_ctxt = xpath_ctxt;
489   if(current_node) {
490     current_ctxt = xmlXPathNewContext(master_config);
491     current_ctxt->node = current_node;
492   }
493   pobj = xmlXPathEval((xmlChar *)path, current_ctxt);
494   if(!pobj) goto out;
495   if(pobj->type != XPATH_NODESET) goto out;
496   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto out;
497   subsection = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
498  out:
499   if(pobj) xmlXPathFreeObject(pobj);
500   if(current_ctxt && current_ctxt != xpath_ctxt)
501     xmlXPathFreeContext(current_ctxt);
502   return subsection;
503 }
504 noit_conf_section_t *noit_conf_get_sections(noit_conf_section_t section,
505                                             const char *path,
506                                             int *cnt) {
507   int i;
508   noit_conf_section_t *sections = NULL;
509   xmlXPathObjectPtr pobj = NULL;
510   xmlXPathContextPtr current_ctxt;
511   xmlNodePtr current_node = (xmlNodePtr)section;
512
513   *cnt = 0;
514   current_ctxt = xpath_ctxt;
515   if(current_node) {
516     current_ctxt = xmlXPathNewContext(master_config);
517     current_ctxt->node = current_node;
518   }
519   pobj = xmlXPathEval((xmlChar *)path, current_ctxt);
520   if(!pobj) goto out;
521   if(pobj->type != XPATH_NODESET) goto out;
522   if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto out;
523   *cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
524   sections = calloc(*cnt, sizeof(*sections));
525   for(i=0; i<*cnt; i++)
526     sections[i] = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, i);
527  out:
528   if(pobj) xmlXPathFreeObject(pobj);
529   if(current_ctxt && current_ctxt != xpath_ctxt)
530     xmlXPathFreeContext(current_ctxt);
531   return sections;
532 }
533 int _noit_conf_get_string(noit_conf_section_t section, xmlNodePtr *vnode,
534                           const char *path, char **value) {
535   const char *str;
536   int rv = 1;
537   unsigned int i;
538   xmlXPathObjectPtr pobj = NULL;
539   xmlXPathContextPtr current_ctxt;
540   xmlNodePtr current_node = (xmlNodePtr)section;
541
542   current_ctxt = xpath_ctxt;
543   if(current_node) {
544     current_ctxt = xmlXPathNewContext(master_config);
545     current_ctxt->node = current_node;
546   }
547   pobj = xmlXPathEval((xmlChar *)path, current_ctxt);
548   if(pobj) {
549     xmlNodePtr node;
550     switch(pobj->type) {
551       case XPATH_NODESET:
552         if(xmlXPathNodeSetIsEmpty(pobj->nodesetval)) goto fallback;
553         i = xmlXPathNodeSetGetLength(pobj->nodesetval);
554         node = xmlXPathNodeSetItem(pobj->nodesetval, i-1);
555         if(vnode) *vnode = node;
556         *value = (char *)xmlXPathCastNodeToString(node);
557         break;
558       default:
559         *value = (char *)xmlXPathCastToString(pobj);
560     }
561     goto found;
562   }
563  fallback:
564   if(noit_hash_retr_str(&_compiled_fallback,
565                         path, strlen(path), &str)) {
566     *value = (char *)xmlStrdup((xmlChar *)str);
567     goto found;
568   }
569   rv = 0;
570  found:
571   if(pobj) xmlXPathFreeObject(pobj);
572   if(current_ctxt && current_ctxt != xpath_ctxt)
573     xmlXPathFreeContext(current_ctxt);
574   return rv;
575 }
576 int noit_conf_get_uuid(noit_conf_section_t section,
577                        const char *path, uuid_t out) {
578   char *str;
579   if(_noit_conf_get_string(section,NULL,path,&str)) {
580     if(uuid_parse(str, out) == 0) return 1;
581     return 0;
582   }
583   return 0;
584 }
585 int noit_conf_get_string(noit_conf_section_t section,
586                          const char *path, char **value) {
587   char *str;
588   if(_noit_conf_get_string(section,NULL,path,&str)) {
589     *value = strdup(str);
590     xmlFree(str);
591     return 1;
592   }
593   return 0;
594 }
595 int noit_conf_get_stringbuf(noit_conf_section_t section,
596                             const char *path, char *buf, int len) {
597   char *str;
598   if(_noit_conf_get_string(section,NULL,path,&str)) {
599     strlcpy(buf, str, len);
600     xmlFree(str);
601     return 1;
602   }
603   return 0;
604 }
605 int noit_conf_set_string(noit_conf_section_t section,
606                          const char *path, const char *value) {
607   noit_hash_replace(&_tmp_config,
608                     strdup(path), strlen(path), (void *)strdup(value),
609                     free, free);
610   return 1;
611 }
612 int noit_conf_string_to_int(const char *str) {
613   int base = 10;
614   if(!str) return 0;
615   if(str[0] == '0') {
616     if(str[1] == 'x') base = 16;
617     else base = 8;
618   }
619   return strtol(str, NULL, base);
620 }
621 int noit_conf_get_int(noit_conf_section_t section,
622                       const char *path, int *value) {
623   char *str;
624   if(_noit_conf_get_string(section,NULL,path,&str)) {
625     *value = (int)noit_conf_string_to_int(str);
626     xmlFree(str);
627     return 1;
628   }
629   return 0;
630 }
631 int noit_conf_set_int(noit_conf_section_t section,
632                       const char *path, int value) {
633   char buffer[32];
634   snprintf(buffer, 32, "%d", value);
635   return noit_conf_set_string(section,path,buffer);
636 }
637 float noit_conf_string_to_float(const char *str) {
638   if(!str) return 0.0;
639   return atof(str);
640 }
641 int noit_conf_get_float(noit_conf_section_t section,
642                         const char *path, float *value) {
643   char *str;
644   if(_noit_conf_get_string(section,NULL,path,&str)) {
645     *value = noit_conf_string_to_float(str);
646     xmlFree(str);
647     return 1;
648   }
649   return 0;
650 }
651 int noit_conf_set_float(noit_conf_section_t section,
652                         const char *path, float value) {
653   char buffer[32];
654   snprintf(buffer, 32, "%f", value);
655   return noit_conf_set_string(section,path,buffer);
656 }
657 noit_boolean noit_conf_string_to_boolean(const char *str) {
658   if(!str) return noit_false;
659   if(!strcasecmp(str, "true") || !strcasecmp(str, "on")) return noit_true;
660   return noit_false;
661 }
662 int noit_conf_get_boolean(noit_conf_section_t section,
663                           const char *path, noit_boolean *value) {
664   char *str;
665   if(_noit_conf_get_string(section,NULL,path,&str)) {
666     *value = noit_conf_string_to_boolean(str);
667     xmlFree(str);
668     return 1;
669   }
670   return 0;
671 }
672 int noit_conf_set_boolean(noit_conf_section_t section,
673                           const char *path, noit_boolean value) {
674   if(value == noit_true)
675     return noit_conf_set_string(section,path,"true");
676   return noit_conf_set_string(section,path,"false");
677 }
678
679 struct config_line_vstr {
680   char *buff;
681   int raw_len;
682   int len;
683   int allocd;
684   enum { CONFIG_RAW = 0, CONFIG_COMPRESSED, CONFIG_B64 } target, encoded;
685 };
686 static int
687 noit_config_log_write_xml(void *vstr, const char *buffer, int len) {
688   struct config_line_vstr *clv = vstr;
689   assert(clv->encoded == CONFIG_RAW);
690   if(!clv->buff) {
691     clv->allocd = 8192;
692     clv->buff = malloc(clv->allocd);
693   }
694   while(len + clv->len > clv->allocd) {
695     char *newbuff;
696     int newsize = clv->allocd;
697     newsize <<= 1;
698     newbuff = realloc(clv->buff, newsize);
699     if(!newbuff) {
700       return -1;
701     }
702     clv->allocd = newsize;
703     clv->buff = newbuff;
704   }
705   memcpy(clv->buff + clv->len, buffer, len);
706   clv->len += len;
707   return len;
708 }
709 static int
710 noit_config_log_close_xml(void *vstr) {
711   struct config_line_vstr *clv = vstr;
712   uLong initial_dlen, dlen;
713   char *compbuff, *b64buff;
714
715   if(clv->buff == NULL) {
716     clv->encoded = clv->target;
717     return 0;
718   }
719   clv->raw_len = clv->len;
720   assert(clv->encoded == CONFIG_RAW);
721   if(clv->encoded == clv->target) return 0;
722
723   /* Compress */
724   initial_dlen = dlen = compressBound(clv->len);
725   compbuff = malloc(initial_dlen);
726   if(!compbuff) return -1;
727   if(Z_OK != compress2((Bytef *)compbuff, &dlen,
728                        (Bytef *)clv->buff, clv->len, 9)) {
729     noitL(noit_error, "Error compressing config for transmission.\n");
730     free(compbuff);
731     return -1;
732   }
733   free(clv->buff);
734   clv->buff = compbuff;
735   clv->allocd = initial_dlen;
736   clv->len = dlen;
737   clv->encoded = CONFIG_COMPRESSED;
738   if(clv->encoded == clv->target) return 0;
739
740   /* Encode */
741   initial_dlen = ((clv->len + 2) / 3) * 4;
742   b64buff = malloc(initial_dlen);
743   dlen = noit_b64_encode((unsigned char *)clv->buff, clv->len,
744                          b64buff, initial_dlen);
745   if(dlen == 0) {
746     free(b64buff);
747     return -1;
748   }
749   free(clv->buff);
750   clv->buff = b64buff;
751   clv->allocd = initial_dlen;
752   clv->len = dlen;
753   clv->encoded = CONFIG_B64;
754   if(clv->encoded == clv->target) return 0;
755   return -1;
756 }
757
758 int
759 noit_conf_reload(noit_console_closure_t ncct,
760                  int argc, char **argv,
761                  noit_console_state_t *state, void *closure) {
762   XML2CONSOLE(ncct);
763   if(noit_conf_load_internal(master_config_file)) {
764     XML2LOG(xml_debug);
765     nc_printf(ncct, "error loading config\n");
766     return -1;
767   }
768   XML2LOG(xml_debug);
769   return 0;
770 }
771 int
772 noit_conf_write_terminal(noit_console_closure_t ncct,
773                          int argc, char **argv,
774                          noit_console_state_t *state, void *closure) {
775   xmlOutputBufferPtr out;
776   xmlCharEncodingHandlerPtr enc;
777   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
778   out = xmlOutputBufferCreateIO(noit_console_write_xml,
779                                 noit_console_close_xml,
780                                 ncct, enc);
781   noit_conf_kansas_city_shuffle_undo(master_config);
782   xmlSaveFormatFileTo(out, master_config, "utf8", 1);
783   noit_conf_kansas_city_shuffle_redo(master_config);
784   return 0;
785 }
786 int
787 noit_conf_write_file_console(noit_console_closure_t ncct,
788                              int argc, char **argv,
789                              noit_console_state_t *state, void *closure) {
790   int rv;
791   char *err = NULL;
792   rv = noit_conf_write_file(&err);
793   nc_printf(ncct, "%s\n", err);
794   if(err) free(err);
795   return rv;
796 }
797 int
798 noit_conf_write_file(char **err) {
799   int fd, len;
800   char master_file_tmp[PATH_MAX];
801   char errstr[1024];
802   xmlOutputBufferPtr out;
803   xmlCharEncodingHandlerPtr enc;
804   struct stat st;
805   mode_t mode = 0640; /* the default */
806
807   if(stat(master_config_file, &st) == 0)
808     mode = st.st_mode;
809   snprintf(master_file_tmp, sizeof(master_file_tmp),
810            "%s.tmp", master_config_file);
811   unlink(master_file_tmp);
812   fd = open(master_file_tmp, O_CREAT|O_EXCL|O_WRONLY, mode);
813   if(fd < 0) {
814     snprintf(errstr, sizeof(errstr), "Failed to open tmp file: %s",
815              strerror(errno));
816     if(err) *err = strdup(errstr);
817     return -1;
818   }
819   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
820   out = xmlOutputBufferCreateFd(fd, enc);
821   if(!out) {
822     close(fd);
823     unlink(master_file_tmp);
824     if(err) *err = strdup("internal error: OutputBufferCreate failed");
825     return -1;
826   }
827   noit_conf_kansas_city_shuffle_undo(master_config);
828   len = xmlSaveFormatFileTo(out, master_config, "utf8", 1);
829   noit_conf_kansas_city_shuffle_redo(master_config);
830   close(fd);
831   if(len <= 0) {
832     if(err) *err = strdup("internal error: writing to tmp file failed.");
833     return -1;
834   }
835   if(rename(master_file_tmp, master_config_file) != 0) {
836     snprintf(errstr, sizeof(errstr), "Failed to replace file: %s",
837              strerror(errno));
838     if(err) *err = strdup(errstr);
839     return -1;
840   }
841   snprintf(errstr, sizeof(errstr), "%d bytes written.", len);
842   if(err) *err = strdup(errstr);
843   return 0;
844 }
845 char *
846 noit_conf_xml_in_mem(size_t *len) {
847   struct config_line_vstr *clv;
848   xmlOutputBufferPtr out;
849   xmlCharEncodingHandlerPtr enc;
850   char *rv;
851
852   clv = calloc(1, sizeof(*clv));
853   clv->target = CONFIG_RAW;
854   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
855   out = xmlOutputBufferCreateIO(noit_config_log_write_xml,
856                                 noit_config_log_close_xml,
857                                 clv, enc);
858   noit_conf_kansas_city_shuffle_undo(master_config);
859   xmlSaveFormatFileTo(out, master_config, "utf8", 1);
860   noit_conf_kansas_city_shuffle_redo(master_config);
861   if(clv->encoded != CONFIG_RAW) {
862     noitL(noit_error, "Error logging configuration\n");
863     if(clv->buff) free(clv->buff);
864     free(clv);
865     return NULL;
866   }
867   rv = clv->buff;
868   *len = clv->len;
869   free(clv);
870   return rv;
871 }
872
873 int
874 noit_conf_write_log() {
875   static u_int32_t last_write_gen = 0;
876   static noit_log_stream_t config_log = NULL;
877   struct timeval __now;
878   xmlOutputBufferPtr out;
879   xmlCharEncodingHandlerPtr enc;
880   struct config_line_vstr *clv;
881   SETUP_LOG(config, return -1);
882
883   /* We know we haven't changed */
884   if(last_write_gen == __config_gen) return 0;
885
886   gettimeofday(&__now, NULL);
887   clv = calloc(1, sizeof(*clv));
888   clv->target = CONFIG_B64;
889   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
890   out = xmlOutputBufferCreateIO(noit_config_log_write_xml,
891                                 noit_config_log_close_xml,
892                                 clv, enc);
893   noit_conf_kansas_city_shuffle_undo(master_config);
894   xmlSaveFormatFileTo(out, master_config, "utf8", 1);
895   noit_conf_kansas_city_shuffle_redo(master_config);
896   if(clv->encoded != CONFIG_B64) {
897     noitL(noit_error, "Error logging configuration\n");
898     if(clv->buff) free(clv->buff);
899     free(clv);
900     return -1;
901   }
902   noitL(config_log, "n\t%lu.%03lu\t%d\t%.*s\n",
903         (unsigned long int)__now.tv_sec,
904         (unsigned long int)__now.tv_usec / 1000UL, clv->raw_len,
905         clv->len, clv->buff);
906   free(clv->buff);
907   free(clv);
908   last_write_gen = __config_gen;
909   return 0;
910 }
911
912 struct log_rotate_crutch {
913   noit_log_stream_t ls;
914   int seconds;
915   size_t max_size;
916 };
917
918 static int
919 noit_conf_log_rotate_size(eventer_t e, int mask, void *closure,
920                           struct timeval *now) {
921   struct log_rotate_crutch *lrc = closure;
922   if(noit_log_stream_written(lrc->ls) > lrc->max_size) {
923     noit_log_stream_rename(lrc->ls, NOIT_LOG_RENAME_AUTOTIME);
924     noit_log_stream_reopen(lrc->ls);
925   }
926   /* Yes the 5 is arbitrary, but this is cheap */
927   eventer_add_in_s_us(noit_conf_log_rotate_size, closure, 5, 0);
928   return 0;
929 }
930 static int
931 noit_conf_log_rotate_time(eventer_t e, int mask, void *closure,
932                           struct timeval *now) {
933   struct timeval lnow;
934   eventer_t newe;
935   struct log_rotate_crutch *lrc = closure;
936
937   if(now) {
938     noit_log_stream_rename(lrc->ls, NOIT_LOG_RENAME_AUTOTIME);
939     noit_log_stream_reopen(lrc->ls);
940   }
941  
942   newe = eventer_alloc();
943   newe->closure = closure;
944   if(!now) { gettimeofday(&lnow, NULL); now = &lnow; }
945   if(e)
946     memcpy(&newe->whence, &e->whence, sizeof(newe->whence));
947   else if(now) {
948     memcpy(&newe->whence, now, sizeof(newe->whence));
949     newe->whence.tv_sec = (newe->whence.tv_sec / lrc->seconds) * lrc->seconds;
950   }
951   newe->whence.tv_sec += lrc->seconds;
952   newe->mask = EVENTER_TIMER;
953   newe->callback = noit_conf_log_rotate_time;
954   eventer_add(newe);
955   return 0;
956 }
957 int
958 noit_conf_log_init_rotate(const char *toplevel, noit_boolean validate) {
959   int i, cnt = 0, max_time, max_size, rv = 0;
960   noit_conf_section_t *log_configs;
961   char path[256];
962
963   snprintf(path, sizeof(path), "/%s/logs//log", toplevel);
964   log_configs = noit_conf_get_sections(NULL, path, &cnt);
965   noitL(noit_debug, "Found %d %s stanzas\n", cnt, path);
966   for(i=0; i<cnt; i++) {
967     noit_log_stream_t ls;
968     char name[256];
969
970     if(!noit_conf_get_stringbuf(log_configs[i],
971                                 "ancestor-or-self::node()/@name",
972                                 name, sizeof(name))) {
973       noitL(noit_error, "log section %d does not have a name attribute\n", i+1);
974       if(validate) { rv = -1; break; }
975       else exit(-2);
976     }
977     ls = noit_log_stream_find(name);
978     if(!ls) continue;
979
980     if(noit_conf_get_int(log_configs[i],   
981                          "ancestor-or-self::node()/@rotate_seconds",
982                          &max_time) && max_time) {
983       struct log_rotate_crutch *lrc;
984       if(max_time < 600) {
985         fprintf(stderr, "rotate_seconds must be >= 600s (10 minutes)\n");
986         if(validate) { rv = -1; break; }
987         else exit(-2);
988       }
989       if(!validate) {
990         lrc = calloc(1, sizeof(*lrc));
991         lrc->ls = ls;
992         lrc->seconds = max_time;
993         noit_conf_log_rotate_time(NULL, EVENTER_TIMER, lrc, NULL);
994       }
995     }
996
997     if(noit_conf_get_int(log_configs[i],   
998                          "ancestor-or-self::node()/@rotate_bytes",
999                          &max_size) && max_size) {
1000       struct log_rotate_crutch *lrc;
1001       if(max_size < 102400) {
1002         fprintf(stderr, "rotate_bytes must be >= 102400 (100k)\n");
1003         if(validate) { rv = -1; break; }
1004         else exit(-2);
1005       }
1006       if(!validate) {
1007         lrc = calloc(1, sizeof(*lrc));
1008         lrc->ls = ls;
1009         lrc->max_size = max_size;
1010         noit_conf_log_rotate_size(NULL, EVENTER_TIMER, lrc, NULL);
1011       }
1012     }
1013   }
1014   free(log_configs);
1015   return rv;
1016 }
1017 void
1018 noit_conf_log_init(const char *toplevel) {
1019   int i, cnt = 0, o, ocnt = 0;
1020   noit_conf_section_t *log_configs, *outlets;
1021   char path[256];
1022
1023   snprintf(path, sizeof(path), "/%s/logs//log", toplevel);
1024   log_configs = noit_conf_get_sections(NULL, path, &cnt);
1025   noitL(noit_debug, "Found %d %s stanzas\n", cnt, path);
1026   for(i=0; i<cnt; i++) {
1027     noit_log_stream_t ls;
1028     char name[256], type[256], path[256];
1029     noit_hash_table *config;
1030     noit_boolean disabled, debug, timestamps;
1031
1032     if(!noit_conf_get_stringbuf(log_configs[i],
1033                                 "ancestor-or-self::node()/@name",
1034                                 name, sizeof(name))) {
1035       noitL(noit_error, "log section %d does not have a name attribute\n", i+1);
1036       exit(-1);
1037     }
1038     if(!noit_conf_get_stringbuf(log_configs[i],
1039                                 "ancestor-or-self::node()/@type",
1040                                 type, sizeof(type))) {
1041       type[0] = '\0';
1042     }
1043     if(!noit_conf_get_stringbuf(log_configs[i],
1044                                 "ancestor-or-self::node()/@path",
1045                                 path, sizeof(path))) {
1046       path[0] = '\0';
1047     }
1048     config = noit_conf_get_hash(log_configs[i],
1049                                 "ancestor-or-self::node()/config");
1050     ls = noit_log_stream_new(name, type[0] ? type : NULL,
1051                              path[0] ? path : NULL, NULL, config);
1052     if(!ls) {
1053       fprintf(stderr, "Error configuring log: %s[%s:%s]\n", name, type, path);
1054       exit(-1);
1055     }
1056
1057     if(noit_conf_get_boolean(log_configs[i],
1058                              "ancestor-or-self::node()/@disabled",
1059                              &disabled) && disabled)
1060       ls->enabled = 0;
1061      
1062     if(noit_conf_get_boolean(log_configs[i],
1063                              "ancestor-or-self::node()/@debug",
1064                              &debug) && debug)
1065       ls->debug = 1;
1066      
1067     if(noit_conf_get_boolean(log_configs[i],
1068                              "ancestor-or-self::node()/@timestamps",
1069                              &timestamps))
1070       ls->timestamps = timestamps ? 1 : 0;
1071  
1072     outlets = noit_conf_get_sections(log_configs[i],
1073                                      "ancestor-or-self::node()/outlet", &ocnt);
1074     noitL(noit_debug, "Found %d outlets for log '%s'\n", ocnt, name);
1075
1076     for(o=0; o<ocnt; o++) {
1077       noit_log_stream_t outlet;
1078       char oname[256];
1079       noit_conf_get_stringbuf(outlets[o], "@name",
1080                               oname, sizeof(oname));
1081       outlet = noit_log_stream_find(oname);
1082       if(!outlet) {
1083         fprintf(stderr, "Cannot find outlet '%s' for %s[%s:%s]\n", oname,
1084               name, type, path);
1085         exit(-1);
1086       }
1087       else
1088         noit_log_stream_add_stream(ls, outlet);
1089     }
1090     if(outlets) free(outlets);
1091   }
1092   if(log_configs) free(log_configs);
1093   if(noit_conf_log_init_rotate(toplevel, noit_true)) exit(-1);
1094 }
1095
1096 static void
1097 conf_t_userdata_free(void *data) {
1098   noit_conf_t_userdata_t *info = data;
1099   if(info) {
1100     if(info->path) free(info->path);
1101     free(info);
1102   }
1103 }
1104
1105 static int
1106 noit_console_state_conf_terminal(noit_console_closure_t ncct,
1107                                  int argc, char **argv,
1108                                  noit_console_state_t *state, void *closure) {
1109   noit_conf_t_userdata_t *info;
1110   if(argc) {
1111     nc_printf(ncct, "extra arguments not expected.\n");
1112     return -1;
1113   }
1114   info = calloc(1, sizeof(*info));
1115   info->path = strdup("/");
1116   noit_console_userdata_set(ncct, NOIT_CONF_T_USERDATA, info,
1117                             conf_t_userdata_free);
1118   noit_console_state_push_state(ncct, state);
1119   noit_console_state_init(ncct);
1120   return 0;
1121 }
1122 static int
1123 noit_console_config_section(noit_console_closure_t ncct,
1124                             int argc, char **argv,
1125                             noit_console_state_t *state, void *closure) {
1126   const char *err = "internal error";
1127   char *path, xpath[1024];
1128   noit_conf_t_userdata_t *info;
1129   xmlXPathObjectPtr pobj = NULL;
1130   xmlXPathContextPtr xpath_ctxt = NULL;
1131   xmlNodePtr node = NULL, newnode;
1132   vpsized_int delete = (vpsized_int)closure;
1133
1134   noit_conf_xml_xpath(NULL, &xpath_ctxt);
1135   if(argc != 1) {
1136     nc_printf(ncct, "requires one argument\n");
1137     return -1;
1138   }
1139   if(strchr(argv[0], '/')) {
1140     nc_printf(ncct, "invalid section name\n");
1141     return -1;
1142   }
1143   if(!strcmp(argv[0], "check") ||
1144      !strcmp(argv[0], "noit") ||
1145      !strcmp(argv[0], "filterset") ||
1146      !strcmp(argv[0], "config")) {
1147     nc_printf(ncct, "%s is reserved.\n", argv[0]);
1148     return -1;
1149   }
1150   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
1151   if(!strcmp(info->path, "/")) {
1152     nc_printf(ncct, "manipulation of toplevel section disallowed\n");
1153     return -1;
1154   }
1155
1156   if(delete) {
1157     /* We cannot delete if we have checks */
1158     snprintf(xpath, sizeof(xpath), "/%s%s/%s//check", root_node_name,
1159              info->path, argv[0]);
1160     pobj = xmlXPathEval((xmlChar *)xpath, xpath_ctxt);
1161     if(!pobj || pobj->type != XPATH_NODESET ||
1162        !xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
1163       err = "cannot delete section, has checks";
1164       goto bad;
1165     }
1166     if(pobj) xmlXPathFreeObject(pobj);
1167   }
1168
1169   snprintf(xpath, sizeof(xpath), "/%s%s/%s", root_node_name,
1170            info->path, argv[0]);
1171   pobj = xmlXPathEval((xmlChar *)xpath, xpath_ctxt);
1172   if(!pobj || pobj->type != XPATH_NODESET) {
1173     err = "internal error: cannot detect section";
1174     goto bad;
1175   }
1176   if(!delete && !xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
1177     if(xmlXPathNodeSetGetLength(pobj->nodesetval) == 1) {
1178       node = xmlXPathNodeSetItem(pobj->nodesetval, 0);
1179       if(info->path) free(info->path);
1180       info->path = strdup((char *)xmlGetNodePath(node) +
1181                           1 + strlen(root_node_name));
1182       goto cdout;
1183     }
1184     err = "cannot create section";
1185     goto bad;
1186   }
1187   if(delete && xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
1188     err = "no such section";
1189     goto bad;
1190   }
1191   if(delete) {
1192     node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
1193     xmlUnlinkNode(node);
1194     noit_conf_mark_changed();
1195     return 0;
1196   }
1197   if(pobj) xmlXPathFreeObject(pobj);
1198   pobj = NULL;
1199
1200   if(!strcmp(argv[0],"include")) {
1201     err = "include is a reserved section name";
1202     goto bad;
1203   }
1204   path = strcmp(info->path, "/") ? info->path : "";
1205   snprintf(xpath, sizeof(xpath), "/%s%s", root_node_name, path);
1206   pobj = xmlXPathEval((xmlChar *)xpath, xpath_ctxt);
1207   if(!pobj || pobj->type != XPATH_NODESET ||
1208      xmlXPathNodeSetGetLength(pobj->nodesetval) != 1) {
1209     err = "path invalid?";
1210     goto bad;
1211   }
1212   node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
1213   if((newnode = xmlNewChild(node, NULL, (xmlChar *)argv[0], NULL)) != NULL) {
1214     noit_conf_mark_changed();
1215     if(info->path) free(info->path);
1216     info->path = strdup((char *)xmlGetNodePath(newnode) + 1 +
1217                         strlen(root_node_name));
1218   }
1219   else {
1220     err = "failed to create section";
1221     goto bad;
1222   }
1223  cdout:
1224   if(pobj) xmlXPathFreeObject(pobj);
1225   return 0;
1226  bad:
1227   if(pobj) xmlXPathFreeObject(pobj);
1228   nc_printf(ncct, "%s\n", err);
1229   return -1;
1230 }
1231
1232 int
1233 noit_console_generic_show(noit_console_closure_t ncct,
1234                           int argc, char **argv,
1235                           noit_console_state_t *state, void *closure) {
1236   int i, cnt, titled = 0, cliplen = 0;
1237   const char *path = "", *basepath = NULL;
1238   char xpath[1024];
1239   noit_conf_t_userdata_t *info = NULL;
1240   xmlXPathObjectPtr pobj = NULL;
1241   xmlXPathContextPtr xpath_ctxt = NULL, current_ctxt;
1242   xmlDocPtr master_config = NULL;
1243   xmlNodePtr node = NULL;
1244
1245   noit_conf_xml_xpath(&master_config, &xpath_ctxt);
1246   if(argc > 1) {
1247     nc_printf(ncct, "too many arguments\n");
1248     return -1;
1249   }
1250
1251   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
1252   if(info && info->path) path = basepath = info->path;
1253   if(!info && argc == 0) {
1254     nc_printf(ncct, "argument required when not in configuration mode\n");
1255     return -1;
1256   }
1257
1258   if(argc == 1) path = argv[0];
1259   if(!basepath) basepath = path;
1260
1261   /* { / } is a special case */
1262   if(!strcmp(basepath, "/")) basepath = "";
1263   if(!strcmp(path, "/")) path = "";
1264
1265   if(!master_config) {
1266     nc_printf(ncct, "no config\n");
1267     return -1;
1268   }
1269
1270   /* { / } is the only path that will end with a /
1271    * in XPath { / / * } means something _entirely different than { / * }
1272    * Ever notice how it is hard to describe xpath in C comments?
1273    */
1274   /* We don't want to show the root node */
1275   cliplen = strlen(root_node_name) + 2; /* /name/ */
1276
1277   /* If we are in configuration mode
1278    * and we are without an argument or the argument is absolute,
1279    * clip the current path off */
1280   if(info && (argc == 0 || path[0] != '/')) cliplen += strlen(basepath);
1281   if(!path[0] || path[0] == '/') /* base only, or absolute path requested */
1282     snprintf(xpath, sizeof(xpath), "/%s%s/@*", root_node_name, path);
1283   else
1284     snprintf(xpath, sizeof(xpath), "/%s%s/%s/@*", root_node_name,
1285              basepath, path);
1286
1287   current_ctxt = xpath_ctxt;
1288   pobj = xmlXPathEval((xmlChar *)xpath, current_ctxt);
1289   if(!pobj || pobj->type != XPATH_NODESET) {
1290     nc_printf(ncct, "no such object\n");
1291     goto bad;
1292   }
1293   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
1294   titled = 0;
1295   for(i=0; i<cnt; i++) {
1296     node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, i);
1297     if(node->children && node->children == xmlGetLastChild(node) &&
1298       xmlNodeIsText(node->children)) {
1299       if(!titled++) nc_printf(ncct, "== Section Settings ==\n");
1300       nc_printf(ncct, "%s: %s\n", xmlGetNodePath(node) + cliplen,
1301                 xmlXPathCastNodeToString(node->children));
1302     }
1303   }
1304   xmlXPathFreeObject(pobj);
1305
1306   /* _shorten string_ turning last { / @ * } to { / * } */
1307   if(!path[0] || path[0] == '/') /* base only, or absolute path requested */
1308     snprintf(xpath, sizeof(xpath), "/%s%s/*", root_node_name, path);
1309   else
1310     snprintf(xpath, sizeof(xpath), "/%s%s/%s/*",
1311              root_node_name, basepath, path);
1312   pobj = xmlXPathEval((xmlChar *)xpath, current_ctxt);
1313   if(!pobj || pobj->type != XPATH_NODESET) {
1314     nc_printf(ncct, "no such object\n");
1315     goto bad;
1316   }
1317   cnt = xmlXPathNodeSetGetLength(pobj->nodesetval);
1318   titled = 0;
1319   for(i=0; i<cnt; i++) {
1320     node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, i);
1321     if(!(node->children && node->children == xmlGetLastChild(node) &&
1322          xmlNodeIsText(node->children))) {
1323       if(!titled++) nc_printf(ncct, "== Subsections ==\n");
1324       nc_printf(ncct, "%s\n", xmlGetNodePath(node) + cliplen);
1325     }
1326   }
1327   xmlXPathFreeObject(pobj);
1328   return 0;
1329  bad:
1330   if(pobj) xmlXPathFreeObject(pobj);
1331   return -1;
1332 }
1333 int
1334 noit_console_config_cd(noit_console_closure_t ncct,
1335                        int argc, char **argv,
1336                        noit_console_state_t *state, void *closure) {
1337   const char *err = "internal error";
1338   char *path, xpath[1024];
1339   noit_conf_t_userdata_t *info;
1340   xmlXPathObjectPtr pobj = NULL;
1341   xmlXPathContextPtr xpath_ctxt = NULL, current_ctxt;
1342   xmlNodePtr node = NULL;
1343   char *dest;
1344
1345   noit_conf_xml_xpath(NULL, &xpath_ctxt);
1346   if(argc != 1 && !closure) {
1347     nc_printf(ncct, "requires one argument\n");
1348     return -1;
1349   }
1350   dest = argc ? argv[0] : (char *)closure;
1351   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
1352   if(dest[0] == '/')
1353     snprintf(xpath, sizeof(xpath), "/%s%s", root_node_name, dest);
1354   else {
1355     snprintf(xpath, sizeof(xpath), "/%s%s/%s", root_node_name,
1356              info->path, dest);
1357   }
1358   if(xpath[strlen(xpath)-1] == '/') xpath[strlen(xpath)-1] = '\0';
1359
1360   current_ctxt = xpath_ctxt;
1361   pobj = xmlXPathEval((xmlChar *)xpath, current_ctxt);
1362   if(!pobj || pobj->type != XPATH_NODESET ||
1363      xmlXPathNodeSetIsEmpty(pobj->nodesetval)) {
1364     err = "no such section";
1365     goto bad;
1366   }
1367   if(xmlXPathNodeSetGetLength(pobj->nodesetval) > 1) {
1368     err = "ambiguous section";
1369     goto bad;
1370   }
1371
1372   node = (noit_conf_section_t)xmlXPathNodeSetItem(pobj->nodesetval, 0);
1373   if(!node) {
1374     err = "internal XML error";
1375     goto bad;
1376   }
1377   if(!strcmp((char *)node->name, "check") ||
1378      !strcmp((char *)node->name, "noit") ||
1379      !strcmp((char *)node->name, "filterset") ||
1380      !strcmp((char *)node->name, "config")) {
1381     err = "reserved word";
1382     goto bad;
1383   }
1384   path = (char *)xmlGetNodePath(node);
1385   if(strlen(path) < strlen(root_node_name) + 1 ||
1386      strncmp(path + 1, root_node_name, strlen(root_node_name)) ||
1387      (path[strlen(root_node_name) + 1] != '/' &&
1388       path[strlen(root_node_name) + 1] != '\0')) {
1389     err = "new path outside out tree";
1390     goto bad;
1391   }
1392   free(info->path);
1393   if(!strcmp(path + 1, root_node_name))
1394     info->path = strdup("/");
1395   else
1396     info->path = strdup((char *)xmlGetNodePath(node) + 1 +
1397                         strlen(root_node_name));
1398   if(pobj) xmlXPathFreeObject(pobj);
1399   if(closure) noit_console_state_pop(ncct, argc, argv, NULL, NULL);
1400   return 0;
1401  bad:
1402   if(pobj) xmlXPathFreeObject(pobj);
1403   nc_printf(ncct, "%s [%s]\n", err, xpath);
1404   return -1;
1405 }
1406
1407 char *
1408 conf_t_prompt(EditLine *el) {
1409   noit_console_closure_t ncct;
1410   noit_conf_t_userdata_t *info;
1411   static char *tl = "noit(conf)# ";
1412   static char *pfmt = "noit(conf:%s%s)# ";
1413   int path_len, max_len;
1414
1415   el_get(el, EL_USERDATA, (void *)&ncct);
1416   if(!ncct) return tl;
1417   info = noit_console_userdata_get(ncct, NOIT_CONF_T_USERDATA);
1418   if(!info) return tl;
1419
1420   path_len = strlen(info->path);
1421   max_len = sizeof(info->prompt) - (strlen(pfmt) - 4 /* %s%s */) - 1 /* \0 */;
1422   if(path_len > max_len)
1423     snprintf(info->prompt, sizeof(info->prompt),
1424              pfmt, "...", info->path + path_len - max_len + 3 /* ... */);
1425   else
1426     snprintf(info->prompt, sizeof(info->prompt), pfmt, "", info->path);
1427   return info->prompt;
1428 }
1429
1430 #define NEW_STATE(a) (a) = noit_console_state_alloc()
1431 #define ADD_CMD(a,cmd,func,ac,ss,c) \
1432   noit_console_state_add_cmd((a), \
1433     NCSCMD(cmd, func, ac, ss, c))
1434 #define DELEGATE_CMD(a,cmd,ac,ss) \
1435   noit_console_state_add_cmd((a), \
1436     NCSCMD(cmd, noit_console_state_delegate, ac, ss, NULL))
1437
1438 void noit_console_conf_init() {
1439   noit_console_state_t *tl, *_conf_state, *_conf_t_state,
1440                        *_write_state, *_unset_state;
1441
1442   tl = noit_console_state_initial();
1443
1444   /* write <terimal|memory|file> */
1445   NEW_STATE(_write_state);
1446   ADD_CMD(_write_state, "terminal", noit_conf_write_terminal, NULL, NULL, NULL);
1447   ADD_CMD(_write_state, "file", noit_conf_write_file_console, NULL, NULL, NULL);
1448   /* write memory?  It's to a file, but I like router syntax */
1449   ADD_CMD(_write_state, "memory", noit_conf_write_file_console, NULL, NULL, NULL);
1450
1451   NEW_STATE(_unset_state);
1452   ADD_CMD(_unset_state, "section",
1453           noit_console_config_section, NULL, NULL, (void *)1);
1454
1455   NEW_STATE(_conf_t_state);
1456   _conf_t_state->console_prompt_function = conf_t_prompt;
1457   noit_console_state_add_cmd(_conf_t_state, &console_command_exit);
1458
1459   ADD_CMD(_conf_t_state, "ls", noit_console_generic_show, NULL, NULL, NULL);
1460   ADD_CMD(_conf_t_state, "cd", noit_console_config_cd, NULL, NULL, NULL);
1461   ADD_CMD(_conf_t_state, "section",
1462           noit_console_config_section, NULL, NULL, (void *)0);
1463
1464   DELEGATE_CMD(_conf_t_state, "write",
1465                noit_console_opt_delegate, _write_state);
1466   DELEGATE_CMD(_conf_t_state, "no", noit_console_opt_delegate, _unset_state);
1467
1468   NEW_STATE(_conf_state);
1469   ADD_CMD(_conf_state, "terminal",
1470           noit_console_state_conf_terminal, NULL, _conf_t_state, NULL);
1471
1472   ADD_CMD(tl, "configure",
1473           noit_console_state_delegate, noit_console_opt_delegate,
1474           _conf_state, NULL);
1475   ADD_CMD(tl, "write",
1476           noit_console_state_delegate, noit_console_opt_delegate,
1477           _write_state, NULL);
1478 }
1479
Note: See TracBrowser for help on using the browser.