added yours sincerely for files where I feel that I've made non-trivial contributions.
[openocd.git] / 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 }

Linking to existing account procedure

If you already have an account and want to add another login method you MUST first sign in with your existing account and then change URL to read https://review.openocd.org/login/?link to get to this page again but this time it'll work for linking. Thank you.

SSH host keys fingerprints

1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=..              |
|+o..   .         |
|*.o   . .        |
|+B . . .         |
|Bo. = o S        |
|Oo.+ + =         |
|oB=.* = . o      |
| =+=.+   + E     |
|. .=o   . o      |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)