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