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