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