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

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)