]> cloudbase.mooo.com Git - z180-stamp.git/blob - avr/cli.c
_USE_LABEL --> FF_USE_LABEL
[z180-stamp.git] / avr / cli.c
1 /*
2 * (C) Copyright 2014-2016 Leo C. <erbl259-lmu@yahoo.de>
3 *
4 * (C) Copyright 2000
5 * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
6 *
7 * Add to readline cmdline-editing by
8 * (C) Copyright 2005
9 * JinHua Luo, GuangDong Linux Center, <luo.jinhua@gd-linux.com>
10 *
11 * SPDX-License-Identifier: GPL-2.0
12 */
13
14 #include "cli.h"
15 #include "command.h"
16 #include <ctype.h>
17
18 #include "config.h"
19 #include "debug.h"
20 #include "env.h"
21 #include "cli_readline.h"
22 #include "con-utils.h"
23
24
25 /* FIXME: Quoting problems */
26
27 #define DEBUG_PARSER 0 /* set to 1 to debug */
28
29 #define debug_parser(fmt, args...) \
30 debug_cond(DEBUG_PARSER, fmt, ##args)
31
32
33 static int_least8_t exec_flags;
34 #define OPT_XTRACE 1
35 #define OPT_VERBOSE 2
36
37 static int_least8_t command_level;
38
39 static void cli_trace_cmd(int_fast8_t level, int argc, char *argv[])
40 {
41 while (level-- > 0)
42 putchar('+');
43 for (int_fast8_t i = 0; i < argc; i++)
44 printf_P(PSTR(" %s"), argv[i]);
45 putchar('\n');
46 }
47
48
49
50
51 static int cli_parse_line(char *line, char *argv[])
52 {
53 uint_fast8_t state = 0;
54 uint_fast8_t nargs = 0;
55 char *inp, *outp;
56 char c, quote;
57
58 debug_parser("%s: \"%s\"\n", __func__, line);
59
60 for (outp = inp = line, quote = '\0'; (c = *inp) != '\0'; inp++) {
61
62 switch (state) {
63 case 0: /* before arg string, waiting for arg start */
64 if (isblank(c))
65 continue;
66
67 argv[nargs++] = inp; /* begin of argument string */
68 outp = inp;
69 state = 1;
70 /* fall thru */
71
72 case 1: /* in arg string, waiting for end of arg string */
73 if (c == '\\') {
74 ++state;
75 continue;
76 }
77 if (c == '\"' || c == '\'') {
78 quote = c;
79 state = 3;
80 continue;
81 }
82 if (isblank(c)) {
83 c = '\0';
84 state = 0;
85 }
86 break;
87
88 case 3: /* in quote */
89 if (c == '\\' && quote == '\"') {
90 ++state;
91 continue;
92 }
93 if (c == quote) {
94 state = 1;
95 continue;
96 }
97 break;
98
99 case 2: /* waiting for next char */
100 case 4:
101 --state;
102 break;
103
104 }
105
106 if (nargs > CONFIG_SYS_MAXARGS) {
107 --nargs;
108 break;
109 }
110 *outp++ = c;
111 }
112
113 if (*inp != '\0')
114 printf_P(PSTR("** Too many args (max. %d) **\n"), CONFIG_SYS_MAXARGS);
115
116 *outp = '\0';
117 argv[nargs] = NULL;
118 debug_parser("%s: nargs=%d\n", __func__, nargs);
119 #if 0
120 for (int i = 0; i < nargs; i++)
121 debug_parser("%s: arg %d: >%s<\n", __func__, i, argv[i]);
122 #endif
123 return nargs;
124
125 }
126
127 static
128 void append_char(uint_fast8_t pass, char **p, char c)
129 {
130
131 if (pass) {
132 **p = c;
133 }
134 ++(*p);
135 }
136
137 static
138 char *process_macros(char *input)
139 {
140 char c, prev, *inp;
141 char *output = NULL;
142 char *outp = NULL;
143 const char *varname = NULL;
144
145 debug_parser("[PROCESS_MACROS] INPUT len %d: \"%s\"\n",
146 strlen(input), input);
147
148 for(uint_fast8_t pass = 0; pass < 2; pass++)
149 {
150 uint_fast8_t state = 0;
151 /* 0 = waiting for '$' */
152 /* 1 = waiting for '{' */
153 /* 2 = waiting for '}' */
154 /* 3 = waiting for ''' */
155
156 if (pass > 0) {
157 size_t outputlen = outp - (char *) NULL;
158 output = (char *) malloc(outputlen);
159 if (output == NULL) {
160 printf_P(PSTR("** Can't process command: Out of memory! **\n"));
161 return NULL;
162 }
163 outp = output;
164 }
165
166 inp = input;
167
168 for (prev = '\0'; (c = *inp++) != '\0'; prev = c) {
169
170
171
172 switch (state) {
173 case 0: /* Waiting for (unescaped) $ */
174 if ((c == '\'') && (prev != '\\')) {
175 state = 3;
176 break;
177 }
178 if ((c == '$') && (prev != '\\')) {
179 state++;
180 continue;
181 }
182 break;
183 case 1: /* Waiting for { */
184 if (c == '{') {
185 state++;
186 varname = inp;
187 continue;
188 } else {
189 state = 0;
190 append_char(pass, &outp, '$');
191 }
192 break;
193 case 2: /* Waiting for } */
194 if (c == '}') {
195 /* Terminate variable name */
196 *(inp-1) = '\0';
197 const char *envval = getenv_str(varname);
198 *(inp-1) = '}';
199 /* Copy into the line if it exists */
200 if (envval != NULL)
201 while (*envval)
202 append_char(pass, &outp, *(envval++));
203 /* Look for another '$' */
204 state = 0;
205 }
206 continue;
207 case 3: /* Waiting for ' */
208 if (c == '\'')
209 state = 0;
210 break;
211 }
212 append_char(pass, &outp, c);
213 }
214
215 append_char(pass, &outp, 0);
216 }
217
218 debug_parser("[PROCESS_MACROS] OUTPUT len %d: \"%s\"\n",
219 strlen(output), output);
220
221 return output;
222 }
223
224 /**
225 *
226 *
227 * WARNING:
228 *
229 * We must create a temporary copy of the command since the command we get
230 * may be the result from getenv_str(), which returns a pointer directly to
231 * the environment data, which may change magicly when the command we run
232 * creates or modifies environment variables (like "bootp" does).
233 *
234 *
235 * @param cmd
236 * @param flag
237 * @returns
238 *
239 */
240 static int cli_run_command(const char *cmd, uint_fast8_t flag)
241 {
242 char cmdbuf[strlen(cmd) + 1]; /* working copy of cmd */
243 char *token; /* start of token in cmdbuf */
244 char *sep; /* end of token (separator) in cmdbuf */
245 char *finaltoken = NULL; /* token after macro expansion */
246 char *str;
247 char *argv[CONFIG_SYS_MAXARGS + 1]; /* NULL terminated */
248 int argc;
249 uint_fast8_t inquotes, repeatable = 1;
250 int rc = 0;
251
252 exec_flags = 0;
253 char *optenv = getenv_str(PSTR("cli"));
254 if (optenv) {
255 if (strstr_P(optenv, PSTR("verbose")) != NULL)
256 exec_flags |= OPT_VERBOSE;
257 if (strstr_P(optenv, PSTR("xtrace")) != NULL)
258 exec_flags |= OPT_XTRACE;
259 }
260
261 debug_parser("[RUN_COMMAND] cmd[%p]=\"%s\"\n",
262 cmd, cmd ? cmd : "NULL");
263
264 if (exec_flags & OPT_VERBOSE)
265 printf_P(PSTR("%s\n"), cmd, cmd ? cmd : "");
266
267 if (!cmd || !*cmd)
268 return -1; /* empty command */
269
270 clear_ctrlc(); /* forget any previous Control C */
271
272 str = strcpy(cmdbuf, cmd);
273 //str = cmdbuf;
274 ++command_level;
275
276 /* Process separators and check for invalid
277 * repeatable commands
278 */
279
280 debug_parser("[PROCESS_SEPARATORS] \"%s\"\n", cmd);
281 while (*str) {
282 /*
283 * Find separator, or string end
284 * Allow simple escape of ';' by writing "\;"
285 */
286 for (inquotes = 0, sep = str; *sep; sep++) {
287 if ((*sep == '\'') &&
288 (sep != str) && /* past string start */
289 (*(sep - 1) != '\\')) /* and NOT escaped */
290 inquotes = !inquotes;
291
292 if (!inquotes &&
293 (*sep == ';' || *sep == '\n' /* separator */
294 || *sep == '#') && /* or start of comment */
295 ((sep == str) || /* string start */
296 (*(sep - 1) != '\\'))) /* or NOT escaped */
297 break;
298 }
299
300 /* no more commands after unescaped '#' token */
301 if (*sep == '#')
302 *sep = '\0';
303
304 /* Limit the token to data between separators */
305 token = str;
306 if (*sep) {
307 str = sep + 1; /* start of command for next pass */
308 *sep = '\0';
309 } else {
310 str = sep; /* no more commands for next pass */
311 }
312 debug_parser("token: \"%s\"\n", token);
313
314 /* find macros in this token and replace them */
315 finaltoken = process_macros(token);
316 if (finaltoken == NULL) {
317 rc = -1; /* no command at all */
318 continue;
319 }
320
321 /* Extract arguments */
322 argc = cli_parse_line(finaltoken, argv);
323 if (argc == 0) {
324 rc = -1; /* no command at all */
325 free(finaltoken);
326 continue;
327 }
328
329 if (exec_flags & OPT_XTRACE)
330 cli_trace_cmd(command_level, argc, argv);
331
332 rc = cmd_process(flag, argc, argv, &repeatable);
333 free(finaltoken);
334 if (rc != CMD_RET_SUCCESS) {
335 if (exec_flags & OPT_VERBOSE)
336 printf_P(PSTR("Command failed, result=%d\n"), rc);
337 rc = -1;
338 }
339
340 /* Did the user stop this? */
341 if (had_ctrlc()) {
342 rc = -1; /* if stopped then not repeatable */
343 break;
344 }
345 }
346
347 --command_level;
348
349 return rc ? rc : repeatable;
350 }
351
352 static int cli_run_command_list(const char *cmd)
353 {
354 return (cli_run_command(cmd, 0) < 0);
355 }
356
357 /******************************************************************************/
358
359
360 /*
361 * Run a command.
362 *
363 * @param cmd Command to run
364 * @param flag Execution flags (CMD_FLAG_...)
365 * @return 0 on success, or != 0 on error.
366 */
367 int run_command(const char *cmd, uint_fast8_t flag)
368 {
369 /*
370 * cli_run_command can return 0 or 1 for success, so clean up
371 * its result.
372 */
373 if (cli_run_command(cmd, flag) == -1)
374 return 1;
375
376 return 0;
377 }
378
379 /*
380 * Run a command using the selected parser, and check if it is repeatable.
381 *
382 * @param cmd Command to run
383 * @param flag Execution flags (CMD_FLAG_...)
384 * @return 0 (not repeatable) or 1 (repeatable) on success, -1 on error.
385 */
386 static int run_command_repeatable(const char *cmd, uint_fast8_t flag)
387 {
388 return cli_run_command(cmd, flag);
389 }
390
391 int run_command_list(const char *cmd, int len)
392 {
393 (void) len;
394
395 return cli_run_command_list(cmd);
396 }
397
398 /****************************************************************************/
399
400
401 void cli_loop(void)
402 {
403 char *lastcommand = NULL;
404 int len;
405 uint_fast8_t flag;
406 int rc = 1;
407
408 for (;;) {
409 len = cli_readline(lastcommand ? PSTR(CONFIG_SYS_PROMPT_REPEAT) : PSTR(CONFIG_SYS_PROMPT), 1);
410
411 flag = 0; /* assume no special flags for now */
412 if (len > 0) {
413 free (lastcommand);
414 lastcommand = strdup(console_buffer);
415 } else if (len == 0)
416 flag |= CMD_FLAG_REPEAT;
417
418 if (len == -1) {
419 if (exec_flags & OPT_VERBOSE)
420 my_puts_P(PSTR("<INTERRUPT>\n"));
421 } else
422 rc = run_command_repeatable(lastcommand, flag);
423
424 if (rc <= 0) {
425 /* invalid command or not repeatable, forget it */
426 free(lastcommand);
427 lastcommand = NULL;
428 }
429 }
430 }