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