]> git.sur5r.net Git - openocd/blob - src/helper/command.c
handle end of line comments to improve compatibility with event scripts
[openocd] / src / helper / command.c
1 /***************************************************************************
2  *   Copyright (C) 2005 by Dominic Rath                                    *
3  *   Dominic.Rath@gmx.de                                                   *
4  *                                                                         *
5  *   part of this file is taken from libcli (libcli.sourceforge.net)       *
6  *   Copyright (C) David Parrish (david@dparrish.com)                      *
7  *                                                                         *
8  *   This program is free software; you can redistribute it and/or modify  *
9  *   it under the terms of the GNU General Public License as published by  *
10  *   the Free Software Foundation; either version 2 of the License, or     *
11  *   (at your option) any later version.                                   *
12  *                                                                         *
13  *   This program is distributed in the hope that it will be useful,       *
14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
16  *   GNU General Public License for more details.                          *
17  *                                                                         *
18  *   You should have received a copy of the GNU General Public License     *
19  *   along with this program; if not, write to the                         *
20  *   Free Software Foundation, Inc.,                                       *
21  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
22  ***************************************************************************/
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include "replacements.h"
28 #include "target.h"
29 #include "command.h"
30 #include "configuration.h"
31
32 #include "log.h"
33 #include "time_support.h"
34
35 #include <stdlib.h>
36 #include <string.h>
37 #include <ctype.h>
38 #include <stdarg.h>
39 #include <stdio.h>
40 #include <unistd.h>
41 #include <errno.h>
42
43 int fast_and_dangerous = 0;
44 Jim_Interp *interp = NULL;
45
46 int handle_sleep_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc);
47 int handle_fast_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc);
48
49 int run_command(command_context_t *context, command_t *c, char *words[], int num_words);
50
51 static void tcl_output(void *privData, const char *file, int line, const char *function, const char *string)
52 {               
53         Jim_Obj *tclOutput=(Jim_Obj *)privData;
54
55         Jim_AppendString(interp, tclOutput, string, strlen(string));
56 }
57
58 static int script_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
59 {
60         /* the private data is stashed in the interp structure */
61         command_t *c;
62         command_context_t *context;
63         int *retval;
64         int i;
65         int nwords;
66         char **words;
67
68         target_call_timer_callbacks_now();
69         LOG_USER_N("%s", ""); /* Keep GDB connection alive*/ 
70         
71         c = interp->cmdPrivData;
72         LOG_DEBUG("script_command - %s", c->name);
73
74         words = malloc(sizeof(char *) * argc);
75         for (i = 0; i < argc; i++)
76         {
77                 int len;
78                 char *w=Jim_GetString(argv[i], &len);
79                 if (*w=='#')
80                 {
81                         /* hit an end of line comment */
82                         break;
83                 }
84                 words[i] = strdup(w);
85                 if (words[i] == NULL) 
86                 {
87                         return JIM_ERR;
88                 }
89                 LOG_DEBUG("script_command - %s, argv[%u]=%s", c->name, i, words[i]);
90         }
91         nwords = i;
92
93         /* grab the command context from the associated data */
94         context = Jim_GetAssocData(interp, "context");
95         retval = Jim_GetAssocData(interp, "retval"); 
96         if (context != NULL && retval != NULL)
97         {
98                 /* capture log output and return it */
99                 Jim_Obj *tclOutput = Jim_NewStringObj(interp, "", 0);
100                 log_add_callback(tcl_output, tclOutput);
101                 
102                 *retval = run_command(context, c, words, nwords);
103                 
104                 log_remove_callback(tcl_output, tclOutput);
105                 
106                 /* We dump output into this local variable */
107                 Jim_SetVariableStr(interp, "ocd_output", tclOutput);
108         }
109
110         for (i = 0; i < nwords; i++)
111                 free(words[i]);
112         free(words);
113
114         return (*retval==ERROR_OK)?JIM_OK:JIM_ERR;
115 }
116
117 command_t* register_command(command_context_t *context, command_t *parent, char *name, int (*handler)(struct command_context_s *context, char* name, char** args, int argc), enum command_mode mode, char *help)
118 {
119         command_t *c, *p;
120         
121         if (!context || !name)
122                 return NULL;
123                                 
124         c = malloc(sizeof(command_t));
125         
126         c->name = strdup(name);
127         c->parent = parent;
128         c->children = NULL;
129         c->handler = handler;
130         c->mode = mode;
131         if (!help)
132                 help="";
133         c->next = NULL;
134         
135         /* place command in tree */
136         if (parent)
137         {
138                 if (parent->children)
139                 {
140                         /* find last child */
141                         for (p = parent->children; p && p->next; p = p->next);
142                         if (p)
143                                 p->next = c;
144                 }
145                 else
146                 {
147                         parent->children = c;
148                 }
149         }
150         else
151         {
152                 if (context->commands)
153                 {
154                         /* find last command */
155                         for (p = context->commands; p && p->next; p = p->next);
156                         if (p)
157                                 p->next = c;
158                 }
159                 else
160                 {
161                         context->commands = c;
162                 }
163         }
164         
165         /* just a placeholder, no handler */
166         if (c->handler==NULL)
167                 return c;
168
169         /* If this is a two level command, e.g. "flash banks", then the
170          * "unknown" proc in startup.tcl must redirect to  this command.
171          * 
172          * "flash banks" is translated by "unknown" to "flash_banks"
173          * if such a proc exists
174          */
175         /* Print help for command */
176         const char *t1="";
177         const char *t2="";
178         const char *t3="";
179         /* maximum of two levels :-) */
180         if (c->parent!=NULL)
181         {
182                 t1=c->parent->name;
183                 t2="_";
184         }
185         t3=c->name;
186         const char *full_name=alloc_printf("ocd_%s%s%s", t1, t2, t3);
187         Jim_CreateCommand(interp, full_name, script_command, c, NULL);
188         free((void *)full_name);
189         
190         /* we now need to add an overrideable proc */
191         const char *override_name=alloc_printf("proc %s%s%s {args} {return [eval \"ocd_%s%s%s $args\"]}", t1, t2, t3, t1, t2, t3);
192         Jim_Eval(interp, override_name);        
193         free((void *)override_name);
194         
195         /* accumulate help text in Tcl helptext list.  */
196     Jim_Obj *helptext=Jim_GetGlobalVariableStr(interp, "ocd_helptext", JIM_ERRMSG);
197     if (Jim_IsShared(helptext))
198         helptext = Jim_DuplicateObj(interp, helptext);
199         Jim_Obj *cmd_entry=Jim_NewListObj(interp, NULL, 0);
200         
201         Jim_Obj *cmd_list=Jim_NewListObj(interp, NULL, 0);
202
203         /* maximum of two levels :-) */
204         if (c->parent!=NULL)
205         {
206                 Jim_ListAppendElement(interp, cmd_list, Jim_NewStringObj(interp, c->parent->name, -1));
207         } 
208         Jim_ListAppendElement(interp, cmd_list, Jim_NewStringObj(interp, c->name, -1));
209         
210         Jim_ListAppendElement(interp, cmd_entry, cmd_list);
211         Jim_ListAppendElement(interp, cmd_entry, Jim_NewStringObj(interp, help, -1));
212         Jim_ListAppendElement(interp, helptext, cmd_entry);
213         return c;
214 }
215
216 int unregister_all_commands(command_context_t *context)
217 {
218         command_t *c, *c2;
219         
220         if (context == NULL)
221                 return ERROR_OK;
222         
223         while(NULL != context->commands)
224         {
225                 c = context->commands;
226                 
227                 while(NULL != c->children)
228                 {
229                         c2 = c->children;
230                         c->children = c->children->next;
231                         free(c2->name);
232                         c2->name = NULL;
233                         free(c2);
234                         c2 = NULL;
235                 }
236                 
237                 context->commands = context->commands->next;
238                 
239                 free(c->name);
240                 c->name = NULL;
241                 free(c);
242                 c = NULL;               
243         }
244         
245         return ERROR_OK;
246 }
247
248 int unregister_command(command_context_t *context, char *name)
249 {
250         command_t *c, *p = NULL, *c2;
251         
252         if ((!context) || (!name))
253                 return ERROR_INVALID_ARGUMENTS;
254         
255         /* find command */
256         for (c = context->commands; c; c = c->next)
257         {
258                 if (strcmp(name, c->name) == 0)
259                 {
260                         /* unlink command */
261                         if (p)
262                         {
263                                 p->next = c->next;
264                         }
265                         else
266                         {
267                                 context->commands = c->next;
268                         }
269                         
270                         /* unregister children */
271                         if (c->children)
272                         {
273                                 for (c2 = c->children; c2; c2 = c2->next)
274                                 {
275                                         free(c2->name);
276                                         free(c2);
277                                 }
278                         }
279                         
280                         /* delete command */
281                         free(c->name);
282                         free(c);
283                 }
284                 
285                 /* remember the last command for unlinking */
286                 p = c;
287         }
288         
289         return ERROR_OK;
290 }
291
292 void command_output_text(command_context_t *context, const char *data)
293 {
294         if( context && context->output_handler && data  ){
295                 context->output_handler( context, data );
296         }
297 }
298
299 void command_print_n(command_context_t *context, char *format, ...)
300 {
301         char *string;
302         
303         va_list ap;
304         va_start(ap, format);
305
306         string = alloc_vprintf(format, ap);
307         if (string != NULL)
308         {
309                 /* we want this collected in the log + we also want to pick it up as a tcl return
310                  * value.
311                  * 
312                  * The latter bit isn't precisely neat, but will do for now.
313                  */
314                 LOG_USER_N("%s", string);
315                 // We already printed it above
316                 //command_output_text(context, string);
317                 free(string);
318         }
319
320         va_end(ap);
321 }
322
323 void command_print(command_context_t *context, char *format, ...)
324 {
325         char *string;
326
327         va_list ap;
328         va_start(ap, format);
329
330         string = alloc_vprintf(format, ap);
331         if (string != NULL)
332         {
333                 strcat(string, "\n"); /* alloc_vprintf guaranteed the buffer to be at least one char longer */
334                 /* we want this collected in the log + we also want to pick it up as a tcl return
335                  * value.
336                  * 
337                  * The latter bit isn't precisely neat, but will do for now.
338                  */
339                 LOG_USER_N("%s", string);
340                 // We already printed it above
341                 //command_output_text(context, string);
342                 free(string);
343         }
344
345         va_end(ap);
346 }
347
348 int run_command(command_context_t *context, command_t *c, char *words[], int num_words)
349 {
350         int start_word=0;
351         if (!((context->mode == COMMAND_CONFIG) || (c->mode == COMMAND_ANY) || (c->mode == context->mode) ))
352         {
353                 /* Config commands can not run after the config stage */
354                 LOG_ERROR("Illegal mode for command");
355                 return ERROR_FAIL;
356         }
357         
358         int retval = c->handler(context, c->name, words + start_word + 1, num_words - start_word - 1);
359         if (retval == ERROR_COMMAND_SYNTAX_ERROR)
360         {
361                 /* Print help for command */
362                 const char *t1="";
363                 const char *t2="";
364                 const char *t3="";
365                 /* maximum of two levels :-) */
366                 if (c->parent!=NULL)
367                 {
368                         t1=c->parent->name;
369                         t2=" ";
370                 }
371                 t3=c->name;
372                 command_run_linef(context, "help {%s%s%s}", t1, t2, t3);
373         }
374         else if (retval == ERROR_COMMAND_CLOSE_CONNECTION)
375         {
376                 /* just fall through for a shutdown request */
377         }
378         else if (retval != ERROR_OK)
379         {
380                 /* we do not print out an error message because the command *should*
381                  * have printed out an error
382                  */
383                 LOG_DEBUG("Command failed with error code %d", retval); 
384         }
385         
386         return retval; 
387 }
388
389 int command_run_line(command_context_t *context, char *line)
390 {
391         /* all the parent commands have been registered with the interpreter
392          * so, can just evaluate the line as a script and check for
393          * results
394          */
395         /* run the line thru a script engine */
396         int retval;
397         int retcode;
398         Jim_DeleteAssocData(interp, "context"); /* remove existing */
399         retcode = Jim_SetAssocData(interp, "context", NULL, context);
400         if (retcode != JIM_OK)
401                 return ERROR_FAIL;
402
403         /* associated the return value */
404         retval = ERROR_OK;
405         Jim_DeleteAssocData(interp, "retval"); /* remove existing */
406         retcode = Jim_SetAssocData(interp, "retval", NULL, &retval);
407         if (retcode != JIM_OK)
408                 return ERROR_FAIL;
409
410         retcode = Jim_Eval(interp, line);       
411         if (retcode == JIM_ERR) {
412                 if (retval!=ERROR_COMMAND_CLOSE_CONNECTION)
413                 {
414                         /* We do not print the connection closed error message */
415                         Jim_PrintErrorMessage(interp);
416                 }
417                 if (retval==ERROR_OK)
418                 {
419                         /* It wasn't a low level OpenOCD command that failed */
420                         return ERROR_FAIL; 
421                 }
422                 return retval;
423         } else if (retcode == JIM_EXIT) {
424                 /* ignore. */
425                 /* exit(Jim_GetExitCode(interp)); */
426         } else {
427                 const char *result;
428                 int reslen;
429
430                 result = Jim_GetString(Jim_GetResult(interp), &reslen);
431                 if (reslen) {
432                         int i;
433                         char buff[256+1];
434                         for (i = 0; i < reslen; i += 256)
435                         {
436                                 int chunk;
437                                 chunk = reslen - i;
438                                 if (chunk > 256)
439                                         chunk = 256;
440                                 strncpy(buff, result+i, chunk);
441                                 buff[chunk] = 0; 
442                                 LOG_USER_N("%s", buff);
443                         }
444                         LOG_USER_N("%s", "\n");
445                 }
446         }
447         return retval;
448 }
449
450 int command_run_linef(command_context_t *context, char *format, ...)
451 {
452         int retval=ERROR_FAIL;
453         char *string;
454         va_list ap;
455         va_start(ap, format);
456         string = alloc_vprintf(format, ap);
457         if (string!=NULL)
458         {
459                 retval=command_run_line(context, string);
460         }
461         va_end(ap);
462         return retval;
463 }
464
465 void command_set_output_handler(command_context_t* context, int (*output_handler)(struct command_context_s *context, const char* line), void *priv)
466 {
467         context->output_handler = output_handler;
468         context->output_handler_priv = priv;
469 }
470
471 command_context_t* copy_command_context(command_context_t* context)
472 {
473         command_context_t* copy_context = malloc(sizeof(command_context_t));
474
475         *copy_context = *context;
476
477         return copy_context;
478 }
479
480 int command_done(command_context_t *context)
481 {
482         free(context);
483         context = NULL;
484         
485         return ERROR_OK;
486 }
487
488 /* find full path to file */
489 static int jim_find(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
490 {
491         if (argc != 2)
492                 return JIM_ERR;
493         const char *file = Jim_GetString(argv[1], NULL);
494         char *full_path = find_file(file);
495         if (full_path == NULL)
496                 return JIM_ERR;
497         Jim_Obj *result = Jim_NewStringObj(interp, full_path, strlen(full_path));
498         free(full_path);
499         
500         Jim_SetResult(interp, result);
501         return JIM_OK;
502 }
503
504 static int jim_echo(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
505 {
506         if (argc != 2)
507                 return JIM_ERR;
508         const char *str = Jim_GetString(argv[1], NULL);
509         LOG_USER("%s", str);
510         return JIM_OK;
511 }
512
513 static size_t openocd_jim_fwrite(const void *_ptr, size_t size, size_t n, void *cookie)
514 {
515         size_t nbytes;
516         const char *ptr;
517         Jim_Interp *interp;
518         command_context_t *context;
519
520         /* make it a char easier to read code */
521         ptr = _ptr;
522         interp = cookie;
523         nbytes = size * n;
524         if (ptr == NULL || interp == NULL || nbytes == 0) {
525                 return 0;
526         }
527
528         context = Jim_GetAssocData(interp, "context");
529         if (context == NULL)
530         {
531                 LOG_ERROR("openocd_jim_fwrite: no command context");
532                 /* TODO: Where should this go? */               
533                 return n;
534         }
535
536         /* do we have to chunk it? */
537         if (ptr[nbytes] == 0)
538         {
539                 /* no it is a C style string */
540                 command_output_text(context, ptr);
541                 return strlen(ptr);
542         }
543         /* GRR we must chunk - not null terminated */
544         while (nbytes) {
545                 char chunk[128+1];
546                 int x;
547
548                 x = nbytes;
549                 if (x > 128) {
550                         x = 128;
551                 }
552                 /* copy it */
553                 memcpy(chunk, ptr, x);
554                 /* terminate it */
555                 chunk[n] = 0;
556                 /* output it */
557                 command_output_text(context, chunk);
558                 ptr += x;
559                 nbytes -= x;
560         }
561         
562         return n;
563 }
564
565 static size_t openocd_jim_fread(void *ptr, size_t size, size_t n, void *cookie)
566 {
567         /* TCL wants to read... tell him no */
568         return 0;
569 }
570
571 static int openocd_jim_vfprintf(void *cookie, const char *fmt, va_list ap)
572 {
573         char *cp;
574         int n;
575         Jim_Interp *interp;
576         command_context_t *context;
577
578         n = -1;
579         interp = cookie;
580         if (interp == NULL)
581                 return n;
582
583         context = Jim_GetAssocData(interp, "context");
584         if (context == NULL)
585         {
586                 LOG_ERROR("openocd_jim_vfprintf: no command context");
587                 return n;
588         }
589
590         cp = alloc_vprintf(fmt, ap);
591         if (cp)
592         {
593                 command_output_text(context, cp);
594                 n = strlen(cp);
595                 free(cp);
596         }
597         return n;
598 }
599
600 static int openocd_jim_fflush(void *cookie)
601 {
602         /* nothing to flush */
603         return 0;
604 }
605
606 static char* openocd_jim_fgets(char *s, int size, void *cookie)
607 {
608         /* not supported */
609         errno = ENOTSUP;
610         return NULL;
611 }
612
613 command_context_t* command_init()
614 {
615         command_context_t* context = malloc(sizeof(command_context_t));
616         extern unsigned const char startup_tcl[];
617
618         context->mode = COMMAND_EXEC;
619         context->commands = NULL;
620         context->current_target = 0;
621         context->output_handler = NULL;
622         context->output_handler_priv = NULL;
623
624 #ifdef JIM_EMBEDDED
625         Jim_InitEmbedded();
626         /* Create an interpreter */
627         interp = Jim_CreateInterp();
628         /* Add all the Jim core commands */
629         Jim_RegisterCoreCommands(interp);
630 #endif
631
632         Jim_CreateCommand(interp, "ocd_find", jim_find, NULL, NULL);
633         Jim_CreateCommand(interp, "echo", jim_echo, NULL, NULL);
634
635         /* Set Jim's STDIO */
636         interp->cookie_stdin = interp;
637         interp->cookie_stdout = interp;
638         interp->cookie_stderr = interp;
639         interp->cb_fwrite = openocd_jim_fwrite;
640         interp->cb_fread = openocd_jim_fread ;
641         interp->cb_vfprintf = openocd_jim_vfprintf;
642         interp->cb_fflush = openocd_jim_fflush;
643         interp->cb_fgets = openocd_jim_fgets;
644         
645         add_default_dirs();
646
647         if (Jim_Eval(interp, startup_tcl)==JIM_ERR)
648         {
649                 LOG_ERROR("Failed to run startup.tcl (embedded into OpenOCD compile time)");
650                 Jim_PrintErrorMessage(interp);
651                 exit(-1);
652         }
653
654         register_command(context, NULL, "sleep", handle_sleep_command,
655                                          COMMAND_ANY, "sleep for <n> milliseconds");
656         
657         register_command(context, NULL, "fast", handle_fast_command,
658                                          COMMAND_ANY, "fast <enable/disable> - place at beginning of config files. Sets defaults to fast and dangerous.");
659         
660         return context;
661 }
662
663 int command_context_mode(command_context_t *cmd_ctx, enum command_mode mode)
664 {
665         if (!cmd_ctx)
666                 return ERROR_INVALID_ARGUMENTS;
667
668         cmd_ctx->mode = mode;
669         return ERROR_OK;
670 }
671
672 /* sleep command sleeps for <n> miliseconds
673  * this is useful in target startup scripts
674  */
675 int handle_sleep_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc)
676 {
677         unsigned long duration = 0;
678         
679         if (argc == 1)
680         {
681                 duration = strtoul(args[0], NULL, 0);
682                 usleep(duration * 1000);
683         }
684
685         return ERROR_OK;
686 }
687
688 int handle_fast_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc)
689 {
690         if (argc!=1)
691                 return ERROR_COMMAND_SYNTAX_ERROR;
692         
693         fast_and_dangerous = strcmp("enable", args[0])==0;
694         
695         return ERROR_OK;
696 }
697
698 void register_jim(struct command_context_s *cmd_ctx, const char *name, int (*cmd)(Jim_Interp *interp, int argc, Jim_Obj *const *argv), const char *help)
699 {
700         Jim_CreateCommand(interp, name, cmd, NULL, NULL);
701
702         /* FIX!!! it would be prettier to invoke add_help_text... 
703            accumulate help text in Tcl helptext list.  */
704         Jim_Obj *helptext=Jim_GetGlobalVariableStr(interp, "ocd_helptext", JIM_ERRMSG);
705         if (Jim_IsShared(helptext))
706                 helptext = Jim_DuplicateObj(interp, helptext);
707     
708         Jim_Obj *cmd_entry=Jim_NewListObj(interp, NULL, 0);
709         
710         Jim_Obj *cmd_list=Jim_NewListObj(interp, NULL, 0);
711         Jim_ListAppendElement(interp, cmd_list, Jim_NewStringObj(interp, name, -1));
712         
713         Jim_ListAppendElement(interp, cmd_entry, cmd_list);
714         Jim_ListAppendElement(interp, cmd_entry, Jim_NewStringObj(interp, help, -1));
715         Jim_ListAppendElement(interp, helptext, cmd_entry);
716 }