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