xref: /openbmc/u-boot/common/cli_simple.c (revision ef64e782)
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 }
339