xref: /openbmc/u-boot/common/cli_simple.c (revision 83d290c56fab2d38cd1ab4c4cc7099559c1d5046)
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  
cli_simple_parse_line(char * line,char * argv[])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  
cli_simple_process_macros(const char * input,char * output)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   */
cli_simple_run_command(const char * cmd,int flag)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  
cli_simple_loop(void)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  
cli_simple_run_command_list(char * cmd,int flag)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  }
339