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