Newer
Older
barebox / common / parser.c
@Sascha Hauer Sascha Hauer on 24 Apr 2019 5 KB Shell: Handle aborting loops better
#include <common.h>
#include <command.h>
#include <password.h>
#include <environment.h>
#include <shell.h>

/*
 * not yet supported
 */
int shell_get_last_return_code(void)
{
	return 0;
}

static int parse_line (char *line, char *argv[])
{
	int nargs = 0;

	pr_debug("parse_line: \"%s\"\n", line);

	while (nargs < CONFIG_MAXARGS) {

		/* skip any white space */
		while ((*line == ' ') || (*line == '\t')) {
			++line;
		}

		if (*line == '\0') {	/* end of line, no more args	*/
			argv[nargs] = NULL;

		pr_debug("parse_line: nargs=%d\n", nargs);

			return (nargs);
		}

		argv[nargs++] = line;	/* begin of argument string	*/

		/* find end of string */
		while (*line && (*line != ' ') && (*line != '\t')) {
			++line;
		}

		if (*line == '\0') {	/* end of line, no more args	*/
			argv[nargs] = NULL;

		pr_debug("parse_line: nargs=%d\n", nargs);

			return (nargs);
		}

		*line++ = '\0';		/* terminate current arg	 */
	}

	printf ("** Too many args (max. %d) **\n", CONFIG_MAXARGS);
	pr_debug("parse_line: nargs=%d\n", nargs);

	return (nargs);
}

static void process_macros (const char *input, char *output)
{
	char c, prev;
	const char *varname_start = NULL;
	int inputcnt = strlen (input);
	int outputcnt = CONFIG_CBSIZE;
	int state = 0;		/* 0 = waiting for '$'  */

	/* 1 = waiting for '(' or '{' */
	/* 2 = waiting for ')' or '}' */
	/* 3 = waiting for '''  */
	char __maybe_unused *output_start = output;

	pr_debug("[PROCESS_MACROS] INPUT len %zu: \"%s\"\n", strlen (input),
		input);

	prev = '\0';		/* previous character   */

	while (inputcnt && outputcnt) {
		c = *input++;
		inputcnt--;

		if (state != 3) {
			/* remove one level of escape characters */
			if ((c == '\\') && (prev != '\\')) {
				if (inputcnt-- == 0)
					break;
				prev = c;
				c = *input++;
			}
		}

		switch (state) {
		case 0:	/* Waiting for (unescaped) $    */
			if ((c == '\'') && (prev != '\\')) {
				state = 3;
				break;
			}
			if ((c == '$') && (prev != '\\')) {
				state++;
			} else {
				*(output++) = c;
				outputcnt--;
			}
			break;
		case 1:	/* Waiting for (        */
			if (c == '(' || c == '{') {
				state++;
				varname_start = input;
			} else {
				state = 0;
				*(output++) = '$';
				outputcnt--;

				if (outputcnt) {
					*(output++) = c;
					outputcnt--;
				}
			}
			break;
		case 2:	/* Waiting for )        */
			if (c == ')' || c == '}') {
				int i;
				char envname[CONFIG_CBSIZE];
				const char *envval;
				int envcnt = input - varname_start - 1;	/* Varname # of chars */

				/* Get the varname */
				for (i = 0; i < envcnt; i++) {
					envname[i] = varname_start[i];
				}
				envname[i] = 0;

				/* Get its value */
				envval = getenv (envname);

				/* Copy into the line if it exists */
				if (envval != NULL)
					while ((*envval) && outputcnt) {
						*(output++) = *(envval++);
						outputcnt--;
					}
				/* Look for another '$' */
				state = 0;
			}
			break;
		case 3:	/* Waiting for '        */
			if ((c == '\'') && (prev != '\\')) {
				state = 0;
			} else {
				*(output++) = c;
				outputcnt--;
			}
			break;
		}
		prev = c;
	}

	if (outputcnt)
		*output = 0;

	pr_debug("[PROCESS_MACROS] OUTPUT len %zu: \"%s\"\n",
		strlen (output_start), output_start);
}

/****************************************************************************
 * returns:
 *	0  - command executed
 *	-1 - not executed (unrecognized, bootd recursion or too many args)
 *           (If cmd is NULL or "" or longer than CONFIG_CBSIZE-1 it is
 *           considered unrecognized)
 *
 * WARNING:
 *
 * We must create a temporary copy of the command since the command we get
 * may be the result from getenv(), which returns a pointer directly to
 * the environment data, which may change magicly when the command we run
 * creates or modifies environment variables (like "bootp" does).
 */

int run_command(const char *cmd)
{
	char cmdbuf[CONFIG_CBSIZE];	/* working copy of cmd		*/
	char *token;			/* start of token in cmdbuf	*/
	char *sep;			/* end of token (separator) in cmdbuf */
	char finaltoken[CONFIG_CBSIZE];
	char *str = cmdbuf;
	char *argv[CONFIG_MAXARGS + 1];	/* NULL terminated	*/
	int argc, inquotes;
	int rc = 0;

#ifdef DEBUG
	pr_debug("[RUN_COMMAND] cmd[%p]=\"", cmd);
	puts (cmd ? cmd : "NULL");	/* use puts - string may be loooong */
	puts ("\"\n");
#endif

	if (!cmd || !*cmd) {
		return -1;	/* empty command */
	}

	if (strlen(cmd) >= CONFIG_CBSIZE) {
		puts ("## Command too long!\n");
		return -1;
	}

	strcpy (cmdbuf, cmd);

	/*
	 * Process separators and check for invalid
	 * repeatable commands
	 */

	pr_debug("[PROCESS_SEPARATORS] %s\n", cmd);

	while (*str) {

		/*
		 * Find separator, or string end
		 * Allow simple escape of ';' by writing "\;"
		 */
		for (inquotes = 0, sep = str; *sep; sep++) {
			if ((*sep=='\'') &&
			    (*(sep-1) != '\\'))
				inquotes=!inquotes;

			if (!inquotes &&
			    (*sep == ';') &&	/* separator		*/
			    ( sep != str) &&	/* past string start	*/
			    (*(sep-1) != '\\'))	/* and NOT escaped	*/
				break;
		}

		/*
		 * Limit the token to data between separators
		 */
		token = str;
		if (*sep) {
			str = sep + 1;	/* start of command for next pass */
			*sep = '\0';
		}
		else {
			str = sep;	/* no more commands for next pass */
		}

		pr_debug("token: \"%s\"\n", token);

		/* find macros in this token and replace them */
		process_macros (token, finaltoken);

		/* Extract arguments */
		if ((argc = parse_line (finaltoken, argv)) == 0) {
			rc = -1;	/* no command at all */
			continue;
		}

		if (execute_command(argc, argv) != COMMAND_SUCCESS)
			rc = -1;
	}

	return rc;
}

static char console_buffer[CONFIG_CBSIZE];		/* console I/O buffer	*/

int run_shell(void)
{
	static char lastcommand[CONFIG_CBSIZE] = { 0, };
	int len;

	login();

	for (;;) {
		len = readline (CONFIG_PROMPT, console_buffer, CONFIG_CBSIZE);

		if (len > 0)
			strcpy (lastcommand, console_buffer);

		if (len == -1) {
			puts ("<INTERRUPT>\n");
		} else {
			const int rc = run_command(lastcommand);
			if (rc < 0) {
				/* invalid command or not repeatable, forget it */
				lastcommand[0] = 0;
			}
			ctrlc_handled();
		}
	}
	return 0;
}