root/src/noit_conf.c

Revision d857961480f92ca18f2038fff87f7ae88713872c, 37.0 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 5 years ago)

register this callback before we use it the first time

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