blob: 358e9b7fe11f28ccc473744bbc6fac932757f8bf [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001// SPDX-License-Identifier: GPL-2.0+
Simon Glass6493ccc2014-04-10 20:01:26 -06002/*
3 * (C) Copyright 2000
4 * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
5 *
6 * Add to readline cmdline-editing by
7 * (C) Copyright 2005
8 * JinHua Luo, GuangDong Linux Center, <luo.jinhua@gd-linux.com>
Simon Glass6493ccc2014-04-10 20:01:26 -06009 */
10
11#include <common.h>
Simon Glass0098e172014-04-10 20:01:30 -060012#include <bootretry.h>
Simon Glass6493ccc2014-04-10 20:01:26 -060013#include <cli.h>
Simon Glass288b29e2019-11-14 12:57:43 -070014#include <command.h>
Simon Glass24b852a2015-11-08 23:47:45 -070015#include <console.h>
Simon Glass7b51b572019-08-01 09:46:52 -060016#include <env.h>
Simon Glass6493ccc2014-04-10 20:01:26 -060017#include <linux/ctype.h>
18
19#define DEBUG_PARSER 0 /* set to 1 to debug */
20
21#define debug_parser(fmt, args...) \
22 debug_cond(DEBUG_PARSER, fmt, ##args)
23
24
Simon Glasse1bf8242014-04-10 20:01:27 -060025int cli_simple_parse_line(char *line, char *argv[])
Simon Glass6493ccc2014-04-10 20:01:26 -060026{
27 int nargs = 0;
28
29 debug_parser("%s: \"%s\"\n", __func__, line);
30 while (nargs < CONFIG_SYS_MAXARGS) {
31 /* skip any white space */
32 while (isblank(*line))
33 ++line;
34
35 if (*line == '\0') { /* end of line, no more args */
36 argv[nargs] = NULL;
37 debug_parser("%s: nargs=%d\n", __func__, nargs);
38 return nargs;
39 }
40
41 argv[nargs++] = line; /* begin of argument string */
42
43 /* find end of string */
44 while (*line && !isblank(*line))
45 ++line;
46
47 if (*line == '\0') { /* end of line, no more args */
48 argv[nargs] = NULL;
49 debug_parser("parse_line: nargs=%d\n", nargs);
50 return nargs;
51 }
52
53 *line++ = '\0'; /* terminate current arg */
54 }
55
56 printf("** Too many args (max. %d) **\n", CONFIG_SYS_MAXARGS);
57
58 debug_parser("%s: nargs=%d\n", __func__, nargs);
59 return nargs;
60}
61
Hans de Goedea06be2d2014-08-06 09:37:38 +020062void cli_simple_process_macros(const char *input, char *output)
Simon Glass6493ccc2014-04-10 20:01:26 -060063{
64 char c, prev;
65 const char *varname_start = NULL;
66 int inputcnt = strlen(input);
67 int outputcnt = CONFIG_SYS_CBSIZE;
68 int state = 0; /* 0 = waiting for '$' */
69
70 /* 1 = waiting for '(' or '{' */
71 /* 2 = waiting for ')' or '}' */
72 /* 3 = waiting for ''' */
Heiko Schocher80402f32015-06-29 09:10:46 +020073 char __maybe_unused *output_start = output;
Simon Glass6493ccc2014-04-10 20:01:26 -060074
75 debug_parser("[PROCESS_MACROS] INPUT len %zd: \"%s\"\n", strlen(input),
76 input);
77
78 prev = '\0'; /* previous character */
79
80 while (inputcnt && outputcnt) {
81 c = *input++;
82 inputcnt--;
83
84 if (state != 3) {
85 /* remove one level of escape characters */
86 if ((c == '\\') && (prev != '\\')) {
87 if (inputcnt-- == 0)
88 break;
89 prev = c;
90 c = *input++;
91 }
92 }
93
94 switch (state) {
95 case 0: /* Waiting for (unescaped) $ */
96 if ((c == '\'') && (prev != '\\')) {
97 state = 3;
98 break;
99 }
100 if ((c == '$') && (prev != '\\')) {
101 state++;
102 } else {
103 *(output++) = c;
104 outputcnt--;
105 }
106 break;
107 case 1: /* Waiting for ( */
108 if (c == '(' || c == '{') {
109 state++;
110 varname_start = input;
111 } else {
112 state = 0;
113 *(output++) = '$';
114 outputcnt--;
115
116 if (outputcnt) {
117 *(output++) = c;
118 outputcnt--;
119 }
120 }
121 break;
122 case 2: /* Waiting for ) */
123 if (c == ')' || c == '}') {
124 int i;
125 char envname[CONFIG_SYS_CBSIZE], *envval;
126 /* Varname # of chars */
127 int envcnt = input - varname_start - 1;
128
129 /* Get the varname */
130 for (i = 0; i < envcnt; i++)
131 envname[i] = varname_start[i];
132 envname[i] = 0;
133
134 /* Get its value */
Simon Glass00caae62017-08-03 12:22:12 -0600135 envval = env_get(envname);
Simon Glass6493ccc2014-04-10 20:01:26 -0600136
137 /* Copy into the line if it exists */
138 if (envval != NULL)
139 while ((*envval) && outputcnt) {
140 *(output++) = *(envval++);
141 outputcnt--;
142 }
143 /* Look for another '$' */
144 state = 0;
145 }
146 break;
147 case 3: /* Waiting for ' */
148 if ((c == '\'') && (prev != '\\')) {
149 state = 0;
150 } else {
151 *(output++) = c;
152 outputcnt--;
153 }
154 break;
155 }
156 prev = c;
157 }
158
159 if (outputcnt)
160 *output = 0;
161 else
162 *(output - 1) = 0;
163
164 debug_parser("[PROCESS_MACROS] OUTPUT len %zd: \"%s\"\n",
165 strlen(output_start), output_start);
166}
167
168 /*
169 * WARNING:
170 *
171 * We must create a temporary copy of the command since the command we get
Simon Glass00caae62017-08-03 12:22:12 -0600172 * may be the result from env_get(), which returns a pointer directly to
Simon Glass6493ccc2014-04-10 20:01:26 -0600173 * the environment data, which may change magicly when the command we run
174 * creates or modifies environment variables (like "bootp" does).
175 */
176int cli_simple_run_command(const char *cmd, int flag)
177{
178 char cmdbuf[CONFIG_SYS_CBSIZE]; /* working copy of cmd */
179 char *token; /* start of token in cmdbuf */
180 char *sep; /* end of token (separator) in cmdbuf */
181 char finaltoken[CONFIG_SYS_CBSIZE];
182 char *str = cmdbuf;
183 char *argv[CONFIG_SYS_MAXARGS + 1]; /* NULL terminated */
184 int argc, inquotes;
185 int repeatable = 1;
186 int rc = 0;
187
188 debug_parser("[RUN_COMMAND] cmd[%p]=\"", cmd);
189 if (DEBUG_PARSER) {
190 /* use puts - string may be loooong */
191 puts(cmd ? cmd : "NULL");
192 puts("\"\n");
193 }
194 clear_ctrlc(); /* forget any previous Control C */
195
196 if (!cmd || !*cmd)
197 return -1; /* empty command */
198
199 if (strlen(cmd) >= CONFIG_SYS_CBSIZE) {
200 puts("## Command too long!\n");
201 return -1;
202 }
203
204 strcpy(cmdbuf, cmd);
205
206 /* Process separators and check for invalid
207 * repeatable commands
208 */
209
210 debug_parser("[PROCESS_SEPARATORS] %s\n", cmd);
211 while (*str) {
212 /*
213 * Find separator, or string end
214 * Allow simple escape of ';' by writing "\;"
215 */
216 for (inquotes = 0, sep = str; *sep; sep++) {
217 if ((*sep == '\'') &&
218 (*(sep - 1) != '\\'))
219 inquotes = !inquotes;
220
221 if (!inquotes &&
222 (*sep == ';') && /* separator */
223 (sep != str) && /* past string start */
224 (*(sep - 1) != '\\')) /* and NOT escaped */
225 break;
226 }
227
228 /*
229 * Limit the token to data between separators
230 */
231 token = str;
232 if (*sep) {
233 str = sep + 1; /* start of command for next pass */
234 *sep = '\0';
235 } else {
236 str = sep; /* no more commands for next pass */
237 }
238 debug_parser("token: \"%s\"\n", token);
239
240 /* find macros in this token and replace them */
Hans de Goedea06be2d2014-08-06 09:37:38 +0200241 cli_simple_process_macros(token, finaltoken);
Simon Glass6493ccc2014-04-10 20:01:26 -0600242
243 /* Extract arguments */
Simon Glasse1bf8242014-04-10 20:01:27 -0600244 argc = cli_simple_parse_line(finaltoken, argv);
Simon Glass6493ccc2014-04-10 20:01:26 -0600245 if (argc == 0) {
246 rc = -1; /* no command at all */
247 continue;
248 }
249
250 if (cmd_process(flag, argc, argv, &repeatable, NULL))
251 rc = -1;
252
253 /* Did the user stop this? */
254 if (had_ctrlc())
255 return -1; /* if stopped then not repeatable */
256 }
257
258 return rc ? rc : repeatable;
259}
260
Simon Glassc1bb2cd2014-04-10 20:01:34 -0600261void cli_simple_loop(void)
Simon Glass6493ccc2014-04-10 20:01:26 -0600262{
Imran Zamanca7def62015-09-07 11:24:08 +0300263 static char lastcommand[CONFIG_SYS_CBSIZE + 1] = { 0, };
Simon Glass6493ccc2014-04-10 20:01:26 -0600264
265 int len;
266 int flag;
267 int rc = 1;
268
269 for (;;) {
Simon Glass6493ccc2014-04-10 20:01:26 -0600270 if (rc >= 0) {
271 /* Saw enough of a valid command to
272 * restart the timeout.
273 */
Simon Glassb26440f2014-04-10 20:01:31 -0600274 bootretry_reset_cmd_timeout();
Simon Glass6493ccc2014-04-10 20:01:26 -0600275 }
Simon Glasse1bf8242014-04-10 20:01:27 -0600276 len = cli_readline(CONFIG_SYS_PROMPT);
Simon Glass6493ccc2014-04-10 20:01:26 -0600277
278 flag = 0; /* assume no special flags for now */
279 if (len > 0)
Peng Fanbb08a6e2016-01-10 13:01:22 +0800280 strlcpy(lastcommand, console_buffer,
281 CONFIG_SYS_CBSIZE + 1);
Simon Glass6493ccc2014-04-10 20:01:26 -0600282 else if (len == 0)
283 flag |= CMD_FLAG_REPEAT;
284#ifdef CONFIG_BOOT_RETRY_TIME
285 else if (len == -2) {
286 /* -2 means timed out, retry autoboot
287 */
288 puts("\nTimed out waiting for command\n");
289# ifdef CONFIG_RESET_TO_RETRY
290 /* Reinit board to run initialization code again */
291 do_reset(NULL, 0, 0, NULL);
292# else
293 return; /* retry autoboot */
294# endif
295 }
296#endif
297
298 if (len == -1)
299 puts("<INTERRUPT>\n");
300 else
Thomas Betker52715f82014-06-05 20:07:58 +0200301 rc = run_command_repeatable(lastcommand, flag);
Simon Glass6493ccc2014-04-10 20:01:26 -0600302
303 if (rc <= 0) {
304 /* invalid command or not repeatable, forget it */
305 lastcommand[0] = 0;
306 }
307 }
308}
309
310int cli_simple_run_command_list(char *cmd, int flag)
311{
312 char *line, *next;
313 int rcode = 0;
314
315 /*
316 * Break into individual lines, and execute each line; terminate on
317 * error.
318 */
319 next = cmd;
320 line = cmd;
321 while (*next) {
322 if (*next == '\n') {
323 *next = '\0';
324 /* run only non-empty commands */
325 if (*line) {
326 debug("** exec: \"%s\"\n", line);
327 if (cli_simple_run_command(line, 0) < 0) {
328 rcode = 1;
329 break;
330 }
331 }
332 line = next + 1;
333 }
334 ++next;
335 }
336 if (rcode == 0 && *line)
Simon Glass4eb580b2014-05-30 14:41:51 -0600337 rcode = (cli_simple_run_command(line, 0) < 0);
Simon Glass6493ccc2014-04-10 20:01:26 -0600338
339 return rcode;
340}