libctdb: test infrastructure
[metze/ctdb/wip.git] / libctdb / test / tui.c
1 /*
2
3 This file is taken from nfsim (http://ozlabs.org/~jk/projects/nfsim/)
4
5 Copyright (c) 2003,2004 Jeremy Kerr & Rusty Russell
6
7 This file is part of nfsim.
8
9 nfsim is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
13
14 nfsim is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with nfsim; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 */
23
24 #include "tui.h"
25 #include "log.h"
26 #include "ctdb-test.h"
27 #include "utils.h"
28
29 #include <sys/types.h>
30 #include <sys/wait.h>
31 #include <stdio.h>
32 #include <stdarg.h>
33 #include <unistd.h>
34 #include <errno.h>
35 #include <err.h>
36 #include <assert.h>
37 #include <readline/readline.h>
38 #include <readline/history.h>
39 #include <talloc.h>
40 #include <dlinklist.h>
41
42 int tui_echo_commands;
43 int tui_abort_on_fail;
44 int tui_quiet;
45 int tui_linenum = 1;
46 char *extension_path;
47 static bool stop;
48
49 struct command {
50         struct command *next, *prev;
51         char    name[TUI_MAX_CMD_LEN+1];
52         bool    (*handler)(int, char **);
53         void    (*helpfn) (int, char **);
54 };
55
56 struct pre_post_hook {
57         struct pre_post_hook *next, *prev;
58         void    (*pre)(const char *);
59         bool    (*post)(const char *);
60 };
61
62 static struct command *commands;
63 static struct pre_post_hook *pre_post_hooks;
64
65 static bool tui_exit(int argc, char **argv)
66 {
67         stop = true;
68         return true;
69 }
70
71 static bool tui_argtest(int argc, char **argv)
72 {
73         int i;
74
75         for (i = 0; i < argc; i++)
76                 log_line(LOG_UI, "argv[%d]: \"%s\"", i, argv[i]);
77
78         return true;
79 }
80
81 static inline struct command *find_command(const char *name)
82 {
83         struct command *cmd;
84         for (cmd = commands; cmd; cmd = cmd->next)
85                 if (!strcmp(name, cmd->name))
86                         return cmd;
87
88         return NULL;
89 }
90
91 bool tui_is_command(const char *name)
92 {
93         return find_command(name) != NULL;
94 }
95
96 static void do_pre_commands(const char *cmd)
97 {
98         struct pre_post_hook *i;
99         for (i = pre_post_hooks; i; i = i->next)
100                 if (i->pre)
101                         i->pre(cmd);
102 }
103
104 static bool do_post_commands(const char *cmd)
105 {
106         struct pre_post_hook *i;
107         bool ret = true;
108
109         for (i = pre_post_hooks; i; i = i->next)
110                 if (i->post && !i->post(cmd))
111                         ret = false;
112         return ret;
113 }
114
115 static bool tui_help(int argc, char **argv)
116 {
117         struct command *cmd;
118
119         if (argc == 1) {
120                 log_line(LOG_UI, "CTDB tester\n"
121                 "help is available on the folowing commands:");
122                 for (cmd = commands; cmd; cmd = cmd->next) {
123                         if (cmd->helpfn)
124                                 log_line(LOG_UI, "\t%s", cmd->name);
125                 }
126         } else {
127                 if (!(cmd = find_command(argv[1]))) {
128                         log_line(LOG_ALWAYS, "No such command '%s'", argv[1]);
129                         return false;
130                 }
131                 if (!cmd->helpfn) {
132                         log_line(LOG_UI, "No help for the '%s' function",
133                                 argv[1]);
134                         return false;
135                 }
136                 cmd->helpfn(argc-1, argv+1);
137         }
138         return true;
139
140
141 }
142
143 static void tui_help_help(int argc, char **argv)
144 {
145 #include "generated-tui-help:help"
146 /*** XML Help:
147     <section id="c:help">
148      <title><command>help</command></title>
149      <para>Displays general help, or help for a specified command</para>
150      <cmdsynopsis>
151       <command>help</command>
152       <arg choice="opt">command</arg>
153      </cmdsynopsis>
154      <para>With no arguments, <command>help</command> will show general system
155       help, and list the available commands. If an argument is specified, then
156       <command>help</command> will show help for that command, if
157       available.</para>
158     </section>
159 */
160 }
161
162 static void tui_exit_help(int argc, char **argv)
163 {
164 #include "generated-tui-help:exit"
165 /*** XML Help:
166     <section id="c:exit">
167      <title><command>exit</command>,
168      <command>quit</command></title>
169      <para>Exit the simulator</para>
170      <cmdsynopsis>
171       <command>exit</command>
172      </cmdsynopsis>
173      <cmdsynopsis>
174       <command>quit</command>
175      </cmdsynopsis>
176
177      <para>The <command>exit</command> and <command>quit</command>
178       commands are synonomous.  They both exit the simulator.
179      </para>
180     </section>
181  */
182 }
183
184 void script_fail(const char *fmt, ...)
185 {
186         char *str;
187         va_list arglist;
188
189         log_line(LOG_ALWAYS, "Script failed at line %i: ", tui_linenum);
190
191         va_start(arglist, fmt);
192         str = talloc_vasprintf(NULL, fmt, arglist);
193         va_end(arglist);
194
195         log_line(LOG_ALWAYS, "%s", str);
196         talloc_free(str);
197
198         check_allocations();
199         exit(EXIT_SCRIPTFAIL);
200 }
201
202 bool tui_do_command(int argc, char *argv[], bool abort)
203 {
204         struct command *cmd;
205         bool ret = true;
206
207         if ((cmd = find_command(argv[0]))) {
208                 do_pre_commands(cmd->name);
209                 if (!cmd->handler(argc, argv)) {
210                         /* Abort on UNEXPECTED failure. */
211                         if (!log_line(LOG_UI, "%s: command failed", argv[0])
212                             && abort)
213                                 script_fail("%s failed", argv[0]);
214                         ret = false;
215                 }
216                 if (!do_post_commands(cmd->name))
217                         ret = false;
218                 return ret;
219         }
220
221         if (abort)
222                 script_fail("%s not found", argv[0]);
223
224         log_line(LOG_ALWAYS, "%s: command not found", argv[0]);
225         return false;
226 }
227
228 /**
229  * backslash-escape a binary data block into a newly allocated
230  * string
231  *
232  * @param src a pointer to the data block
233  * @param src_len the length of the data block
234  * @return NULL if out of memory, or a pointer to the allocated escaped
235  *    string, which is terminated with a '\0' character
236  */
237 static char *escape(const char *src, size_t src_len)
238 {
239         static const char hexbuf[]= "0123456789abcdef";
240         char *dest, *p;
241         size_t i;
242
243         /* src_len * 4 is always safe, it's the longest escape
244            sequence for all characters */
245         dest = talloc_array(src, char, src_len * 4 + 1);
246         p = dest;
247
248         for (i = 0; i < src_len; i++) {
249                 if (src[i] == '\n') {
250                         *p++ = '\\';
251                         *p++ = 'n';
252                 } else if (src[i] == '\r') {
253                         *p++ = '\\';
254                         *p++ = 'r';
255                 } else if (src[i] == '\0') {
256                         *p++ = '\\';
257                         *p++ = '0';
258                 } else if (src[i] == '\t') {
259                         *p++ = '\\';
260                         *p++ = 't';
261                 } else if (src[i] == '\\') {
262                         *p++ = '\\';
263                         *p++ = '\\';
264                 } else if (src[i] & 0x80 || (src[i] & 0xe0) == 0) {
265                         *p++ = '\\';
266                         *p++ = 'x';
267                         *p++ = hexbuf[(src[i] >> 4) & 0xf];
268                         *p++ = hexbuf[src[i] & 0xf];
269                 } else
270                         *p++ = src[i];
271         }
272
273         *p++ = 0;
274         return dest;
275 }
276
277 /* Process `command`: update off to point to tail backquote */
278 static char *backquote(char *line, unsigned int *off)
279 {
280         char *end, *cmdstr, *str;
281         FILE *cmdfile;
282         size_t used, len, i;
283         int status;
284
285         /* Skip first backquote, look for next one. */
286         (*off)++;
287         end = strchr(line + *off, '`');
288         if (!end)
289                 script_fail("no matching \"`\" found");
290
291         len = end - (line + *off);
292         cmdstr = talloc_asprintf(line, "PATH=%s; %.*s",
293                                  extension_path, (int)len, line + *off);
294         cmdfile = popen(cmdstr, "r");
295         if (!cmdfile)
296                 script_fail("failed to popen '%s': %s\n",
297                             cmdstr, strerror(errno));
298
299        /* Jump to backquote. */
300        *off += len;
301
302         /* Read command output. */
303         used = 0;
304         len = 1024;
305         str = talloc_array(line, char, len);
306
307         while ((i = fread(str + used, 1, len - used, cmdfile)) != 0) {
308                 used += i;
309                 if (used == len) {
310                         if (len > 1024*1024)
311                                 script_fail("command '%s' output too long\n",
312                                             cmdstr);
313                         len *= 2;
314                         str = talloc_realloc(line, str, char, len);
315                 }
316         }
317         status = pclose(cmdfile);
318         if (status == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
319                 script_fail("command '%s' failed\n", cmdstr);
320
321         return escape(str, used);
322 }
323
324 static char *append_char(char **argv, unsigned int argc, char c)
325 {
326         if (!argv[argc])
327                 return talloc_asprintf(argv, "%c", c);
328         return talloc_asprintf_append(argv[argc], "%c", c);
329 }
330
331 static char *append_string(char **argv, unsigned int argc, const char *str)
332 {
333         if (!argv[argc])
334                 return talloc_asprintf(argv, "%s", str);
335         return talloc_asprintf_append(argv[argc], "%s", str);
336 }
337
338 static void process_line(char *line, unsigned int off)
339 {
340         unsigned int argc, i;
341         char **argv;
342
343         if (tui_echo_commands)
344                 printf("%u:%s\n", tui_linenum, line + off);
345
346         /* Talloc argv off line so commands can use it for auto-cleanup. */
347         argv = talloc_zero_array(line, char *, TUI_MAX_ARGS+1);
348         argc = 0;
349         for (i = off; line[i]; i++) {
350                 if (isspace(line[i])) {
351                         /* If anything in this arg, move to next. */
352                         if (argv[argc])
353                                 argc++;
354                 } else if (line[i] == '`') {
355                         char *inside = backquote(line, &i);
356                         argv[argc] = append_string(argv, argc, inside);
357                 } else {
358                         /* If it is a comment, stop before we process `` */
359                         if (!argv[0] && line[i] == '#')
360                                 goto out;
361
362                         argv[argc] = append_char(argv, argc, line[i]);
363                 }
364         }
365
366         if (argv[0]) {
367                 if (argv[argc])
368                         argv[++argc] = NULL;
369                 tui_do_command(argc, argv, tui_abort_on_fail);
370         }
371
372 out:
373         tui_linenum++;
374         return;
375 }
376
377 static void readline_process_line(char *line)
378 {
379         char *talloc_line;
380         if (!line) {
381                 stop = true;
382                 return;
383         }
384
385         add_history(line);
386
387         /* Readline isn't talloc-aware, so copy string: functions can
388          * hang temporary variables off this. */
389         talloc_line = talloc_strdup(NULL, line);
390         process_line(talloc_line, 0);
391         talloc_free(talloc_line);
392 }
393
394 static void run_whole_file(int fd)
395 {
396         char *file, *p;
397         size_t size, len;
398
399         file = grab_fd(fd, &size);
400         if (!file)
401                 err(1, "Grabbing file");
402
403         for (p = file; p < file + size; p += len+1) {
404                 len = strcspn(p, "\n");
405                 p[len] = '\0';
406                 process_line(file, p - file);
407         }
408 }
409
410 void tui_run(int fd)
411 {
412         tui_register_command("exit", tui_exit, tui_exit_help);
413         tui_register_command("quit", tui_exit, tui_exit_help);
414         tui_register_command("q", tui_exit, tui_exit_help);
415         tui_register_command("test", tui_argtest, NULL);
416         tui_register_command("help", tui_help, tui_help_help);
417
418         if (fd == STDIN_FILENO) {
419                 stop = false;
420                 rl_callback_handler_install(tui_quiet ? "" : "> ",
421                                             readline_process_line);
422                 while (!stop)
423                         rl_callback_read_char();
424                 rl_callback_handler_remove();
425                 if (!tui_quiet)
426                         printf("\n");
427         } else
428                 run_whole_file(fd);
429 }
430
431 int tui_register_pre_post_hook(void (*pre)(const char *),
432                                bool (*post)(const char *))
433 {
434         struct pre_post_hook *h;
435
436         h = talloc(NULL, struct pre_post_hook);
437         h->pre = pre;
438         h->post = post;
439         DLIST_ADD(pre_post_hooks, h);
440         return 0;
441 }
442
443 int tui_register_command(const char *command,
444                          bool (*handler)(int, char **),
445                          void (*helpfn)(int, char **))
446 {
447         struct command *cmd;
448
449         assert(strlen(command) < TUI_MAX_CMD_LEN);
450
451         cmd = talloc(NULL, struct command);
452         strncpy(cmd->name, command, TUI_MAX_CMD_LEN);
453         cmd->handler = handler;
454         cmd->helpfn  = helpfn;
455
456         DLIST_ADD(commands, cmd);
457
458         return 0;
459 }