handle end of line comments to improve compatibility with event scripts
[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 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 }

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)