root/src/noit_conf.c

Revision 8c720f9a4bd5979520e9c7ec00d9bea8d305391b, 45.1 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 4 years ago)

this is a big patch. adds an optional extended-id. you should upgrade your iep first, then your stratcon, then your noits. This is most certainly a flag-day, *but* the feature is off by default... refs #331

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