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