1667de7846cde4085a8f13be3b67c9041e9a7401
[ctdb.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         check_databases();
200         exit(EXIT_SCRIPTFAIL);
201 }
202
203 bool tui_do_command(int argc, char *argv[], bool abort)
204 {
205         struct command *cmd;
206         bool ret = true;
207
208         if ((cmd = find_command(argv[0]))) {
209                 do_pre_commands(cmd->name);
210                 if (!cmd->handler(argc, argv)) {
211                         /* Abort on UNEXPECTED failure. */
212                         if (!log_line(LOG_UI, "%s: command failed", argv[0])
213                             && abort)
214                                 script_fail("%s failed", argv[0]);
215                         ret = false;
216                 }
217                 if (!do_post_commands(cmd->name))
218                         ret = false;
219                 return ret;
220         }
221
222         if (abort)
223                 script_fail("%s not found", argv[0]);
224
225         log_line(LOG_ALWAYS, "%s: command not found", argv[0]);
226         return false;
227 }
228
229 /**
230  * backslash-escape a binary data block into a newly allocated
231  * string
232  *
233  * @param src a pointer to the data block
234  * @param src_len the length of the data block
235  * @return NULL if out of memory, or a pointer to the allocated escaped
236  *    string, which is terminated with a '\0' character
237  */
238 static char *escape(const char *src, size_t src_len)
239 {
240         static const char hexbuf[]= "0123456789abcdef";
241         char *dest, *p;
242         size_t i;
243
244         /* src_len * 4 is always safe, it's the longest escape
245            sequence for all characters */
246         dest = talloc_array(src, char, src_len * 4 + 1);
247         p = dest;
248
249         for (i = 0; i < src_len; i++) {
250                 if (src[i] == '\n') {
251                         *p++ = '\\';
252                         *p++ = 'n';
253                 } else if (src[i] == '\r') {
254                         *p++ = '\\';
255                         *p++ = 'r';
256                 } else if (src[i] == '\0') {
257                         *p++ = '\\';
258                         *p++ = '0';
259                 } else if (src[i] == '\t') {
260                         *p++ = '\\';
261                         *p++ = 't';
262                 } else if (src[i] == '\\') {
263                         *p++ = '\\';
264                         *p++ = '\\';
265                 } else if (src[i] & 0x80 || (src[i] & 0xe0) == 0) {
266                         *p++ = '\\';
267                         *p++ = 'x';
268                         *p++ = hexbuf[(src[i] >> 4) & 0xf];
269                         *p++ = hexbuf[src[i] & 0xf];
270                 } else
271                         *p++ = src[i];
272         }
273
274         *p++ = 0;
275         return dest;
276 }
277
278 /* Process `command`: update off to point to tail backquote */
279 static char *backquote(char *line, unsigned int *off)
280 {
281         char *end, *cmdstr, *str;
282         FILE *cmdfile;
283         size_t used, len, i;
284         int status;
285
286         /* Skip first backquote, look for next one. */
287         (*off)++;
288         end = strchr(line + *off, '`');
289         if (!end)
290                 script_fail("no matching \"`\" found");
291
292         len = end - (line + *off);
293         cmdstr = talloc_asprintf(line, "PATH=%s; %.*s",
294                                  extension_path, (int)len, line + *off);
295         cmdfile = popen(cmdstr, "r");
296         if (!cmdfile)
297                 script_fail("failed to popen '%s': %s\n",
298                             cmdstr, strerror(errno));
299
300        /* Jump to backquote. */
301        *off += len;
302
303         /* Read command output. */
304         used = 0;
305         len = 1024;
306         str = talloc_array(line, char, len);
307
308         while ((i = fread(str + used, 1, len - used, cmdfile)) != 0) {
309                 used += i;
310                 if (used == len) {
311                         if (len > 1024*1024)
312                                 script_fail("command '%s' output too long\n",
313                                             cmdstr);
314                         len *= 2;
315                         str = talloc_realloc(line, str, char, len);
316                 }
317         }
318         status = pclose(cmdfile);
319         if (status == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
320                 script_fail("command '%s' failed\n", cmdstr);
321
322         return escape(str, used);
323 }
324
325 static char *append_char(char **argv, unsigned int argc, char c)
326 {
327         if (!argv[argc])
328                 return talloc_asprintf(argv, "%c", c);
329         return talloc_asprintf_append(argv[argc], "%c", c);
330 }
331
332 static char *append_string(char **argv, unsigned int argc, const char *str)
333 {
334         if (!argv[argc])
335                 return talloc_asprintf(argv, "%s", str);
336         return talloc_asprintf_append(argv[argc], "%s", str);
337 }
338
339 static void process_line(char *line, unsigned int off)
340 {
341         unsigned int argc, i;
342         char **argv;
343
344         if (tui_echo_commands)
345                 printf("%u:%s\n", tui_linenum, line + off);
346
347         /* Talloc argv off line so commands can use it for auto-cleanup. */
348         argv = talloc_zero_array(line, char *, TUI_MAX_ARGS+1);
349         argc = 0;
350         for (i = off; line[i]; i++) {
351                 if (isspace(line[i])) {
352                         /* If anything in this arg, move to next. */
353                         if (argv[argc])
354                                 argc++;
355                 } else if (line[i] == '`') {
356                         char *inside = backquote(line, &i);
357                         argv[argc] = append_string(argv, argc, inside);
358                 } else {
359                         /* If it is a comment, stop before we process `` */
360                         if (!argv[0] && line[i] == '#')
361                                 goto out;
362
363                         argv[argc] = append_char(argv, argc, line[i]);
364                 }
365         }
366
367         if (argv[0]) {
368                 if (argv[argc])
369                         argv[++argc] = NULL;
370                 tui_do_command(argc, argv, tui_abort_on_fail);
371         }
372
373 out:
374         tui_linenum++;
375         return;
376 }
377
378 static void readline_process_line(char *line)
379 {
380         char *talloc_line;
381         if (!line) {
382                 stop = true;
383                 return;
384         }
385
386         add_history(line);
387
388         /* Readline isn't talloc-aware, so copy string: functions can
389          * hang temporary variables off this. */
390         talloc_line = talloc_strdup(NULL, line);
391         process_line(talloc_line, 0);
392         talloc_free(talloc_line);
393 }
394
395 static void run_whole_file(int fd)
396 {
397         char *file, *p;
398         size_t size, len;
399
400         file = grab_fd(fd, &size);
401         if (!file)
402                 err(1, "Grabbing file");
403
404         for (p = file; p < file + size; p += len+1) {
405                 len = strcspn(p, "\n");
406                 p[len] = '\0';
407                 process_line(file, p - file);
408         }
409 }
410
411 void tui_run(int fd)
412 {
413         tui_register_command("exit", tui_exit, tui_exit_help);
414         tui_register_command("quit", tui_exit, tui_exit_help);
415         tui_register_command("q", tui_exit, tui_exit_help);
416         tui_register_command("test", tui_argtest, NULL);
417         tui_register_command("help", tui_help, tui_help_help);
418
419         if (fd == STDIN_FILENO) {
420                 stop = false;
421                 rl_callback_handler_install(tui_quiet ? "" : "> ",
422                                             readline_process_line);
423                 while (!stop)
424                         rl_callback_read_char();
425                 rl_callback_handler_remove();
426                 if (!tui_quiet)
427                         printf("\n");
428         } else
429                 run_whole_file(fd);
430 }
431
432 int tui_register_pre_post_hook(void (*pre)(const char *),
433                                bool (*post)(const char *))
434 {
435         struct pre_post_hook *h;
436
437         h = talloc(NULL, struct pre_post_hook);
438         h->pre = pre;
439         h->post = post;
440         DLIST_ADD(pre_post_hooks, h);
441         return 0;
442 }
443
444 int tui_register_command(const char *command,
445                          bool (*handler)(int, char **),
446                          void (*helpfn)(int, char **))
447 {
448         struct command *cmd;
449
450         assert(strlen(command) < TUI_MAX_CMD_LEN);
451
452         cmd = talloc(NULL, struct command);
453         strncpy(cmd->name, command, TUI_MAX_CMD_LEN);
454         cmd->handler = handler;
455         cmd->helpfn  = helpfn;
456
457         DLIST_ADD(commands, cmd);
458
459         return 0;
460 }