root/src/noit_console_state.c

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

fixes #126

  • 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 "eventer/eventer.h"
36 #include "utils/noit_log.h"
37 #include "utils/noit_hash.h"
38 #include "noit_listener.h"
39 #include "noit_console.h"
40 #include "noit_tokenizer.h"
41 #include "noit_module.h"
42
43 #include <netinet/in.h>
44 #include <arpa/inet.h>
45 #include <pcre.h>
46
47 int cmd_info_comparek(const void *akv, const void *bv) {
48   char *ak = (char *)akv;
49   cmd_info_t *b = (cmd_info_t *)bv;
50   return strcasecmp(ak, b->name);
51 }
52 int cmd_info_compare(const void *av, const void *bv) {
53   cmd_info_t *a = (cmd_info_t *)av;
54   cmd_info_t *b = (cmd_info_t *)bv;
55   return strcasecmp(a->name, b->name);
56 }
57
58 cmd_info_t console_command_help = {
59   "help", noit_console_help, noit_console_opt_delegate, NULL, NULL
60 };
61 cmd_info_t console_command_exit = {
62   "exit", noit_console_state_pop, NULL, NULL, NULL
63 };
64 cmd_info_t console_command_shutdown = {
65   "shutdown", noit_console_shutdown, NULL, NULL, NULL
66 };
67 cmd_info_t console_command_restart = {
68   "restart", noit_console_restart, NULL, NULL, NULL
69 };
70
71 void
72 noit_console_add_help(const char *topic, console_cmd_func_t topic_func,
73                       console_opt_func_t ac) {
74   noit_console_state_t *s = console_command_help.dstate;
75   if(!s) {
76     console_command_help.dstate = s = calloc(1, sizeof(*s));
77     noit_skiplist_init(&s->cmds);
78     noit_skiplist_set_compare(&s->cmds, cmd_info_compare, cmd_info_comparek);
79   }
80   noit_console_state_add_cmd(s, NCSCMD(topic, topic_func, ac, NULL, NULL));
81 }
82
83 static char *default_prompt = NULL;
84
85 void
86 noit_console_set_default_prompt(const char *prompt) {
87   char *tofree = default_prompt;
88   default_prompt = strdup(prompt);
89   if(tofree) free(tofree);
90 }
91 static char *
92 noit_console_state_prompt(EditLine *el) {
93   static char *tl = "noit# ";
94   if(default_prompt) return default_prompt;
95   return tl;
96 }
97
98 static char *
99 apply_replace(const char *src, const char *name, const char *value) {
100   char *result, *cp;
101   const char *nextpat, *searchstart;
102   char pat[256];
103   int maxlen, patlen, vlen, slen;
104   snprintf(pat, sizeof(pat), "{%s}", name);
105   patlen = strlen(pat);
106   vlen = strlen(value);
107   slen = strlen(src);
108   /* Worst case is just a stream of replacements. */
109   maxlen = (slen / patlen) * MAX(vlen,patlen) + (slen % patlen) + 1;
110   cp = result = malloc(maxlen);
111   searchstart = src;
112   while((nextpat = strstr(searchstart, pat)) != NULL) {
113     memcpy(cp, searchstart, nextpat - searchstart); /* pull the prefix */
114     cp += nextpat - searchstart;                    /* advance destination */
115     memcpy(cp, value, vlen);                        /* copy replacement */
116     cp += vlen;                                     /* advance destination */
117     searchstart = nextpat + patlen;                 /* set new searchstart */
118   }
119   /* Pick up the trailer (plus '\0') */
120   memcpy(cp, searchstart, strlen(searchstart)+1);
121   return result;
122 }
123 static pcre *IP_match = NULL;
124 static pcre *numeric_match = NULL;
125 static int
126 expand_range(const char *range, char ***set, int max_count, const char **err) {
127   int count, erroff, ovector[30], rv;
128   char buff[32]; /* submatches */
129   const char *pcre_err;
130   *err = NULL;
131   if(!IP_match) {
132     IP_match = pcre_compile("^(full:)?(\\d+\\.\\d+\\.\\d+\\.\\d+)/(\\d+)$",
133                             0, &pcre_err, &erroff, NULL);
134     if(!IP_match) {
135       *err = "IP match pattern failed to compile!";
136       noitL(noit_error, "pcre_compiled failed offset %d: %s\n", erroff, pcre_err);
137       return -1;
138     }
139   }
140   if(!numeric_match) {
141     numeric_match = pcre_compile("^(\\d+)(?:,(\\d+))?\\.\\.(\\d+)$",
142                                  0, &pcre_err, &erroff, NULL);
143     if(!numeric_match) {
144       *err = "numeric match pattern failed to compile!";
145       noitL(noit_error, "pcre_compiled failed offset %d: %s\n", erroff, pcre_err);
146       return -1;
147     }
148   }
149   rv = pcre_exec(IP_match, NULL, range, strlen(range), 0, 0, ovector, 30);
150   if(rv >= 0) {
151     int mask, full = 0, i;
152     u_int32_t host_addr;
153     struct in_addr addr;
154     /* 0 is the full monty, 1 is "" or "full:", 2 is the IP, 3 is the mask */
155     pcre_copy_substring(range, ovector, rv, 1, buff, sizeof(buff));
156     full = buff[0] ? 1 : 0;
157     pcre_copy_substring(range, ovector, rv, 3, buff, sizeof(buff));
158     mask = atoi(buff);
159     if(mask == 32) full = 1; /* host implies.. the host */
160     if(mask < 0 || mask > 32) {
161       *err = "invalid netmask";
162       return 0;
163     }
164     count = 1 << (32-mask);
165     pcre_copy_substring(range, ovector, rv, 2, buff, sizeof(buff));
166     if(inet_pton(AF_INET, buff, &addr) != 1) {
167       *err = "could not parse IP address";
168       return 0;
169     }
170     host_addr = ntohl(addr.s_addr);
171     host_addr &= ~((u_int32_t)count - 1);
172
173     if(!full) count -= 2; /* No network or broadcast */
174     if(count > max_count || !count) return -count;
175     if(!full) host_addr++; /* Skip the network address */
176
177     *set = malloc(count * sizeof(**set));
178     for(i=0; i<count; i++)  {
179       addr.s_addr = htonl(host_addr + i);
180       inet_ntop(AF_INET, &addr, buff, sizeof(buff));
181       (*set)[i] = strdup(buff);
182     }
183     return count;
184   }
185   rv = pcre_exec(numeric_match, NULL, range, strlen(range), 0, 0, ovector, 30);
186   if(rv >= 0) {
187     int s, n, e, i;
188     pcre_copy_substring(range, ovector, rv, 1, buff, sizeof(buff));
189     s = atoi(buff);
190     pcre_copy_substring(range, ovector, rv, 3, buff, sizeof(buff));
191     e = atoi(buff);
192     pcre_copy_substring(range, ovector, rv, 2, buff, sizeof(buff));
193     if(buff[0]) n = atoi(buff);
194     else n = (s<e) ? s+1 : s-1;
195
196     /* Ensure that s < n < e */
197     if((s<e && s>n) || (s>e && s<n)) {
198       *err = "mixed up sequence";
199       return 0;
200     }
201     i = n - s; /* Our increment */
202     count = (e - s) / i + 1;
203     *set = malloc(count * sizeof(**set));
204     count = 0;
205     for(; (i>0 && s<=e) || (i<0 && s>=e); s += i) {
206       snprintf(buff, sizeof(buff), "%d", s);
207       (*set)[count] = strdup(buff);
208       count++;
209     }
210     return count;
211   }
212   *err = "cannot understand range";
213   return 0;
214 }
215 int
216 noit_console_generic_apply(noit_console_closure_t ncct,
217                            int argc, char **argv,
218                            noit_console_state_t *dstate,
219                            void *closure) {
220   int i, j, count;
221   char *name, *range;
222   char **nargv, **expanded = NULL;
223   const char *err;
224   int problems = 0;
225   if(argc < 3) {
226     nc_printf(ncct, "apply <name> <range> cmd ...\n");
227     return -1;
228   }
229   name = argv[0];
230   range = argv[1];
231   argc -= 2;
232   argv += 2;
233
234   count = expand_range(range, &expanded, 256, &err);
235   if(!count) {
236     nc_printf(ncct, "apply error: '%s' range produced nothing [%s]\n",
237               range, err ? err : "unknown error");
238     return -1;
239   }
240   if(count < 0) {
241     nc_printf(ncct, "apply error: '%s' range would produce %d items.\n",
242               range, count);
243     return -1;
244   }
245   nargv = malloc(argc * sizeof(*nargv));
246   for(i=0; i<count; i++) {
247     for(j=0; j<argc; j++) nargv[j] = apply_replace(argv[j], name, expanded[i]);
248     if(noit_console_state_do(ncct, argc, nargv)) problems = -1;
249     for(j=0; j<argc; j++) free(nargv[j]);
250     free(expanded[i]);
251   }
252   free(nargv);
253   free(expanded);
254   return problems;
255 }
256
257 int
258 noit_console_render_help(noit_console_closure_t ncct,
259                          noit_console_state_t *dstate) {
260   noit_skiplist_node *iter = NULL;
261   if(!dstate) {
262     nc_printf(ncct, "No help available.\n");
263     return -1;
264   }
265   for(iter = noit_skiplist_getlist(&dstate->cmds); iter;
266       noit_skiplist_next(&dstate->cmds,&iter)) {
267     cmd_info_t *cmd = iter->data;
268     if(strcmp(cmd->name, "help")) nc_printf(ncct, "  ==> '%s'\n", cmd->name);
269   }
270   return 0;
271 }
272
273 int
274 noit_console_state_delegate(noit_console_closure_t ncct,
275                             int argc, char **argv,
276                             noit_console_state_t *dstate,
277                             void *closure) {
278   noit_console_state_stack_t tmps = { 0 };
279
280   if(argc == 0) {
281     noit_console_render_help(ncct, dstate);
282     nc_printf(ncct, "incomplete command.\n");
283     return -1;
284   }
285   if(!dstate) {
286     nc_printf(ncct, "internal error: no delegate state\n");
287     return -1;
288   }
289   tmps.state = dstate;
290   return _noit_console_state_do(ncct, &tmps, argc, argv);
291 }
292
293 int
294 _noit_console_state_do(noit_console_closure_t ncct,
295                        noit_console_state_stack_t *stack,
296                        int argc, char **argv) {
297   noit_skiplist_node *next, *amb = NULL;
298   cmd_info_t *cmd;
299
300   if(!argc) {
301     noit_console_render_help(ncct, stack->state);
302     nc_printf(ncct, "arguments expected\n");
303     return -1;
304   }
305   cmd = noit_skiplist_find_neighbors(&stack->state->cmds, argv[0],
306                                      NULL, NULL, &next);
307   if(!cmd) {
308     int ambiguous = 0;
309     if(next) {
310       cmd_info_t *pcmd = NULL;
311       cmd = next->data;
312       amb = next;
313       noit_skiplist_next(&stack->state->cmds, &amb);
314       if(amb) pcmd = amb->data;
315       /* So cmd is the next in line... pcmd is the one after that.
316        * If they both strncasecmp to 0, we're ambiguous,
317        *    neither, then we're not found.
318        *    only cmd, then we've found a partial, unambiguous.
319        */
320       if(strncasecmp(cmd->name, argv[0], strlen(argv[0])) == 0) {
321         if(pcmd && strncasecmp(pcmd->name, argv[0], strlen(argv[0])) == 0) {
322           cmd = NULL;
323           ambiguous = 1;
324         }
325       }
326       else
327         cmd = NULL;
328     }
329     if(!cmd) {
330       if(ambiguous || !strcmp(argv[0], "?")) {
331         char *partial = ambiguous ? argv[0] : "";
332         if(ambiguous) nc_printf(ncct, "Ambiguous command: '%s'\n", argv[0]);
333         amb = ambiguous ? next : noit_skiplist_getlist(&stack->state->cmds);
334         for(; amb; noit_skiplist_next(&stack->state->cmds, &amb)) {
335           cmd = amb->data;
336           if(!strlen(partial) || strncasecmp(cmd->name, partial, strlen(partial)) == 0)
337             nc_printf(ncct, "\t%s\n", cmd->name);
338           else
339             break;
340         }
341       }
342       else
343         nc_printf(ncct, "No such command: '%s'\n", argv[0]);
344       return -1;
345     }
346   }
347   if(ncct->state_stack->name) free(ncct->state_stack->name);
348   ncct->state_stack->name = strdup(cmd->name);
349   return cmd->func(ncct, argc-1, argv+1, cmd->dstate, cmd->closure);
350 }
351 int
352 noit_console_state_do(noit_console_closure_t ncct, int argc, char **argv) {
353   return _noit_console_state_do(ncct, ncct->state_stack, argc, argv);
354 }
355
356 noit_console_state_t *
357 noit_console_state_alloc(void) {
358   noit_console_state_t *s;
359   s = calloc(1, sizeof(*s));
360   noit_skiplist_init(&s->cmds);
361   noit_skiplist_set_compare(&s->cmds, cmd_info_compare, cmd_info_comparek);
362   noit_console_state_add_cmd(s,
363       NCSCMD("apply", noit_console_generic_apply, NULL, NULL, NULL));
364   noit_console_state_add_cmd(s, &console_command_help);
365   return s;
366 }
367
368 int
369 noit_console_state_add_cmd(noit_console_state_t *state,
370                            cmd_info_t *cmd) {
371   return (noit_skiplist_insert(&state->cmds, cmd) != NULL);
372 }
373
374 cmd_info_t *
375 noit_console_state_get_cmd(noit_console_state_t *state,
376                            const char *name) {
377   cmd_info_t *cmd;
378   cmd = noit_skiplist_find(&state->cmds, name, NULL);
379   return cmd;
380 }
381
382 noit_console_state_t *
383 noit_console_state_build(console_prompt_func_t promptf, cmd_info_t **clist,
384                          state_free_func_t sfreef) {
385   noit_console_state_t *state;
386   state = noit_console_state_alloc();
387   state->console_prompt_function = promptf;
388   while(*clist) {
389     noit_skiplist_insert(&state->cmds, *clist);
390     clist++;
391   }
392   state->statefree = sfreef;
393   return state;
394 }
395
396 cmd_info_t *NCSCMD(const char *name, console_cmd_func_t func,
397                    console_opt_func_t ac,
398                    noit_console_state_t *dstate, void *closure) {
399   cmd_info_t *cmd;
400   cmd = calloc(1, sizeof(*cmd));
401   cmd->name = strdup(name);
402   cmd->func = func;
403   cmd->autocomplete = ac;
404   cmd->dstate = dstate;
405   cmd->closure = closure;
406   return cmd;
407 }
408
409 noit_console_state_t *
410 noit_console_state_initial() {
411   static noit_console_state_t *_top_level_state = NULL;
412   if(!_top_level_state) {
413     static noit_console_state_t *no_state;
414     _top_level_state = noit_console_state_alloc();
415     noit_console_state_add_cmd(_top_level_state, &console_command_exit);
416     noit_console_state_add_cmd(_top_level_state,
417       NCSCMD("show", noit_console_state_delegate, noit_console_opt_delegate,
418              noit_console_state_alloc(), NULL));
419     no_state = noit_console_state_alloc();
420     noit_console_state_add_cmd(_top_level_state,
421       NCSCMD("no", noit_console_state_delegate, noit_console_opt_delegate,
422              no_state, NULL));
423
424     noit_console_state_add_cmd(_top_level_state, &console_command_shutdown);
425     noit_console_state_add_cmd(_top_level_state, &console_command_restart);
426   }
427   return _top_level_state;
428 }
429
430 void
431 noit_console_state_push_state(noit_console_closure_t ncct,
432                               noit_console_state_t *state) {
433   noit_console_state_stack_t *stack;
434   stack = calloc(1, sizeof(*stack));
435   stack->last = ncct->state_stack;
436   stack->state = state;
437   ncct->state_stack = stack;
438 }
439
440 int
441 noit_console_shutdown(noit_console_closure_t ncct, int argc, char **argv,
442                       noit_console_state_t *dstate, void *unused) {
443   exit(2);
444 }
445 int
446 noit_console_restart(noit_console_closure_t ncct, int argc, char **argv,
447                      noit_console_state_t *dstate, void *unused) {
448   exit(1);
449 }
450 int
451 noit_console_help(noit_console_closure_t ncct, int argc, char **argv,
452                   noit_console_state_t *dstate, void *unused) {
453   noit_console_state_stack_t *current;
454   current = ncct->state_stack;
455
456   if(!argc) {
457     noit_console_state_stack_t *i = current;
458     if(!current) {
459       nc_printf(ncct, "no state!\n");
460       return -1;
461     }
462     for(i=current;i;i=i->last) {
463       if(i != current)
464         nc_printf(ncct, " -> '%s'\n", i->name ? i->name : "(null)");
465     }
466     if(dstate) {
467       nc_printf(ncct, "= Topics =\n");
468       noit_console_render_help(ncct, dstate);
469     }
470     if(current->state) {
471       nc_printf(ncct, "\n= Commands =\n");
472       noit_console_render_help(ncct, current->state);
473     }
474     return 0;
475   }
476   else if(argc > 0) {
477     nc_printf(ncct, "Help for '%s':\n", argv[0]);
478     if(noit_console_state_delegate(ncct, argc, argv, dstate, NULL) == 0)
479       return 0;
480   }
481   nc_printf(ncct, "command not understood.\n");
482   return -1;
483 }
484 int
485 noit_console_state_pop(noit_console_closure_t ncct, int argc, char **argv,
486                        noit_console_state_t *dstate, void *unused) {
487   noit_console_state_stack_t *current;
488
489   if(argc) {
490     nc_printf(ncct, "no arguments allowed to this command.\n");
491     return -1;
492   }
493   if(!ncct->state_stack || !ncct->state_stack->last) {
494     ncct->wants_shutdown = 1;
495     return 0;
496   }
497
498   current = ncct->state_stack;
499   ncct->state_stack = current->last;
500   current->last = NULL;
501   if(current->state->statefree) current->state->statefree(current->state);
502   if(current->name) free(current->name);
503   free(current);
504   noit_console_state_init(ncct);
505   return 0;
506 }
507
508 int
509 noit_console_state_init(noit_console_closure_t ncct) {
510   if(ncct->el) {
511     console_prompt_func_t f;
512     f = ncct->state_stack->state->console_prompt_function;
513     el_set(ncct->el, EL_PROMPT, f ? f : noit_console_state_prompt);
514   }
515   return 0;
516 }
Note: See TracBrowser for help on using the browser.