]> cloudbase.mooo.com Git - z180-stamp.git/blob - avr/cli.c
Allow comments on command lines. Comment token is '#' .
[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 "common.h"
16
17 #include <string.h>
18 #include <ctype.h>
19 #include <stdlib.h>
20 #include <stdio.h>
21
22 #include "config.h"
23 #include "command.h"
24 #include "xmalloc.h"
25 #include "debug.h"
26 #include "env.h"
27 #include "cli_readline.h"
28 #include "con-utils.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_char(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_char(), 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 == '#') && /* or start of comment */
268 ((sep == str) || /* string start */
269 (*(sep - 1) != '\\'))) /* or NOT escaped */
270 break;
271 }
272
273 /* no more commands after unescaped '#' token */
274 if (*sep == '#')
275 *sep = '\0';
276
277 /* Limit the token to data between separators */
278 token = str;
279 if (*sep) {
280 str = sep + 1; /* start of command for next pass */
281 *sep = '\0';
282 } else {
283 str = sep; /* no more commands for next pass */
284 }
285 debug_parser("token: \"%s\"\n", token);
286
287 /* find macros in this token and replace them */
288 finaltoken = process_macros(token, finaltoken);
289
290 /* Extract arguments */
291 argc = cli_parse_line(finaltoken, argv);
292 if (argc == 0) {
293 rc = -1; /* no command at all */
294 continue;
295 }
296
297 if (cmd_process(flag, argc, argv, &repeatable) != CMD_RET_SUCCESS)
298 rc = -1;
299
300 /* Did the user stop this? */
301 if (had_ctrlc()) {
302 rc = -1; /* if stopped then not repeatable */
303 break;
304 }
305 }
306
307 free(cmdbuf);
308 free(finaltoken);
309
310 return rc ? rc : repeatable;
311 }
312
313 static int cli_run_command_list(const char *cmd)
314 {
315 return (cli_run_command(cmd, 0) < 0);
316 }
317
318 /******************************************************************************/
319
320
321 /*
322 * Run a command.
323 *
324 * @param cmd Command to run
325 * @param flag Execution flags (CMD_FLAG_...)
326 * @return 0 on success, or != 0 on error.
327 */
328 int run_command(const char *cmd, int flag)
329 {
330 /*
331 * cli_run_command can return 0 or 1 for success, so clean up
332 * its result.
333 */
334 if (cli_run_command(cmd, flag) == -1)
335 return 1;
336
337 return 0;
338 }
339
340 /*
341 * Run a command using the selected parser, and check if it is repeatable.
342 *
343 * @param cmd Command to run
344 * @param flag Execution flags (CMD_FLAG_...)
345 * @return 0 (not repeatable) or 1 (repeatable) on success, -1 on error.
346 */
347 static int run_command_repeatable(const char *cmd, int flag)
348 {
349 return cli_run_command(cmd, flag);
350 }
351
352 int run_command_list(const char *cmd, int len)
353 {
354 (void) len;
355
356 return cli_run_command_list(cmd);
357 }
358
359 /****************************************************************************/
360
361
362 void cli_loop(void)
363 {
364 char *lastcommand = NULL;
365 int len;
366 int flag;
367 int rc = 1;
368
369 for (;;) {
370 len = cli_readline(PSTR(CONFIG_SYS_PROMPT), 1);
371
372 flag = 0; /* assume no special flags for now */
373 if (len > 0) {
374 free (lastcommand);
375 lastcommand = strdup(console_buffer);
376 } else if (len == 0)
377 flag |= CMD_FLAG_REPEAT;
378
379 if (len == -1)
380 my_puts_P(PSTR("<INTERRUPT>\n"));
381 else
382 rc = run_command_repeatable(lastcommand, flag);
383
384 if (rc <= 0) {
385 /* invalid command or not repeatable, forget it */
386 free(lastcommand);
387 lastcommand = NULL;
388 }
389 }
390 }