flash: Tidy up coding style for flash functions
[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 <command.h>
15 #include <console.h>
16 #include <env.h>
17 #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
25 int cli_simple_parse_line(char *line, char *argv[])
26 {
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
62 void cli_simple_process_macros(const char *input, char *output)
63 {
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 '''  */
73         char __maybe_unused *output_start = output;
74
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 */
135                                 envval = env_get(envname);
136
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
172  * may be the result from env_get(), which returns a pointer directly to
173  * the environment data, which may change magicly when the command we run
174  * creates or modifies environment variables (like "bootp" does).
175  */
176 int 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 */
241                 cli_simple_process_macros(token, finaltoken);
242
243                 /* Extract arguments */
244                 argc = cli_simple_parse_line(finaltoken, argv);
245                 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
261 void cli_simple_loop(void)
262 {
263         static char lastcommand[CONFIG_SYS_CBSIZE + 1] = { 0, };
264
265         int len;
266         int flag;
267         int rc = 1;
268
269         for (;;) {
270                 if (rc >= 0) {
271                         /* Saw enough of a valid command to
272                          * restart the timeout.
273                          */
274                         bootretry_reset_cmd_timeout();
275                 }
276                 len = cli_readline(CONFIG_SYS_PROMPT);
277
278                 flag = 0;       /* assume no special flags for now */
279                 if (len > 0)
280                         strlcpy(lastcommand, console_buffer,
281                                 CONFIG_SYS_CBSIZE + 1);
282                 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
301                         rc = run_command_repeatable(lastcommand, flag);
302
303                 if (rc <= 0) {
304                         /* invalid command or not repeatable, forget it */
305                         lastcommand[0] = 0;
306                 }
307         }
308 }
309
310 int 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)
337                 rcode = (cli_simple_run_command(line, 0) < 0);
338
339         return rcode;
340 }