xref: /openbmc/u-boot/common/cli_simple.c (revision a3b36c84)
1 /*
2  * (C) Copyright 2000
3  * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
4  *
5  * Add to readline cmdline-editing by
6  * (C) Copyright 2005
7  * JinHua Luo, GuangDong Linux Center, <luo.jinhua@gd-linux.com>
8  *
9  * SPDX-License-Identifier:	GPL-2.0+
10  */
11 
12 #include <common.h>
13 #include <bootretry.h>
14 #include <cli.h>
15 #include <console.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 }
340