ash: eval: Add assignment built-in support again

Upstream commit:

    Date: Sat, 19 May 2018 02:39:52 +0800
    eval: Add assignment built-in support again

    This patch adds assignment built-in support that used to exist
    in dash prior to 0.3.8-15.  This is because it will soon be part
    of POSIX, and the semantics are now much better defined.

    Recognition is done at execution time, so even "command -- export"
    or "var=export; command $var" should work.

    Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/shell/ash.c b/shell/ash.c
index fea4b10..1458962 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -8112,7 +8112,7 @@
 #define DO_ABS          0x02    /* checks absolute paths */
 #define DO_NOFUNC       0x04    /* don't return shell functions, for command */
 #define DO_ALTPATH      0x08    /* using alternate path */
-#define DO_ALTBLTIN     0x20    /* %builtin in alt. path */
+#define DO_REGBLTIN     0x10    /* regular built-ins and functions only */
 
 static void find_command(char *, struct cmdentry *, int, const char *);
 
@@ -8718,24 +8718,43 @@
 }
 
 #if ENABLE_ASH_CMDCMD
-/* Is it "command [-p] PROG ARGS" bltin, no other opts? Return ptr to "PROG" if yes */
-static char **
-parse_command_args(char **argv, const char **path)
+static struct strlist *
+fill_arglist(struct arglist *arglist, union node **argpp)
 {
+	struct strlist **lastp = arglist->lastp;
+	union node *argp;
+
+	while ((argp = *argpp) != NULL) {
+		expandarg(argp, arglist, EXP_FULL | EXP_TILDE);
+		*argpp = argp->narg.next;
+		if (*lastp)
+			break;
+	}
+
+	return *lastp;
+}
+
+/* Is it "command [-p] PROG ARGS" bltin, no other opts? Return ptr to "PROG" if yes */
+static int
+parse_command_args(struct arglist *arglist, union node **argpp, const char **path)
+{
+	struct strlist *sp = arglist->list;
 	char *cp, c;
 
 	for (;;) {
-		cp = *++argv;
-		if (!cp)
-			return NULL;
+		sp = sp->next ? sp->next : fill_arglist(arglist, argpp);
+		if (!sp)
+			return 0;
+		cp = sp->text;
 		if (*cp++ != '-')
 			break;
 		c = *cp++;
 		if (!c)
 			break;
 		if (c == '-' && !*cp) {
-			if (!*++argv)
-				return NULL;
+			if (!sp->next && !fill_arglist(arglist, argpp))
+				return 0;
+			sp = sp->next;
 			break;
 		}
 		do {
@@ -8745,12 +8764,14 @@
 				break;
 			default:
 				/* run 'typecmd' for other options */
-				return NULL;
+				return 0;
 			}
 			c = *cp++;
 		} while (c);
 	}
-	return argv;
+
+	arglist->list = sp;
+	return DO_NOFUNC;
 }
 
 static int FAST_FUNC
@@ -10124,7 +10145,7 @@
 evalcommand(union node *cmd, int flags)
 {
 	static const struct builtincmd null_bltin = {
-		"\0\0", bltincmd /* why three NULs? */
+		BUILTIN_REGULAR "", bltincmd
 	};
 	struct localvar_list *localvar_stop;
 	struct parsefile *file_stop;
@@ -10134,12 +10155,14 @@
 	struct arglist varlist;
 	char **argv;
 	int argc;
+	struct strlist *osp;
 	const struct strlist *sp;
 	struct cmdentry cmdentry;
 	struct job *jp;
 	char *lastarg;
 	const char *path;
 	int spclbltin;
+	int cmd_flag;
 	int status;
 	char **nargv;
 	smallint cmd_is_exec;
@@ -10161,26 +10184,46 @@
 	arglist.lastp = &arglist.list;
 	*arglist.lastp = NULL;
 
+	cmd_flag = 0;
+	cmd_is_exec = 0;
+	spclbltin = -1;
+	path = NULL;
+
 	argc = 0;
-	if (cmd->ncmd.args) {
-		struct builtincmd *bcmd;
-		smallint pseudovarflag;
+	argp = cmd->ncmd.args;
+	osp = fill_arglist(&arglist, &argp);
+	if (osp) {
+		int pseudovarflag = 0;
 
-		bcmd = find_builtin(cmd->ncmd.args->narg.text);
-		pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd);
+		for (;;) {
+			find_command(arglist.list->text, &cmdentry,
+					cmd_flag | DO_REGBLTIN, pathval());
 
-		for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) {
-			struct strlist **spp;
+			/* implement bltin and command here */
+			if (cmdentry.cmdtype != CMDBUILTIN)
+				break;
 
-			spp = arglist.lastp;
-			if (pseudovarflag && isassignment(argp->narg.text))
-				expandarg(argp, &arglist, EXP_VARTILDE);
-			else
-				expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
+			pseudovarflag = IS_BUILTIN_ASSIGN(cmdentry.u.cmd);
+			if (spclbltin < 0) {
+				spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd);
+			}
+			cmd_is_exec = cmdentry.u.cmd == EXECCMD;
+			if (cmdentry.u.cmd != COMMANDCMD)
+				break;
 
-			for (sp = *spp; sp; sp = sp->next)
-				argc++;
+			cmd_flag = parse_command_args(&arglist, &argp, &path);
+			if (!cmd_flag)
+				break;
 		}
+
+		for (; argp; argp = argp->narg.next)
+			expandarg(argp, &arglist,
+					pseudovarflag &&
+					isassignment(argp->narg.text) ?
+					EXP_VARTILDE : EXP_FULL | EXP_TILDE);
+
+		for (sp = arglist.list; sp; sp = sp->next)
+			argc++;
 	}
 
 	/* Reserve one extra spot at the front for shellexec. */
@@ -10210,23 +10253,13 @@
 	}
 	status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH | REDIR_SAVEFD2);
 
-	path = vpath.var_text;
 	for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) {
 		struct strlist **spp;
-		char *p;
 
 		spp = varlist.lastp;
 		expandarg(argp, &varlist, EXP_VARTILDE);
 
 		mklocal((*spp)->text);
-
-		/*
-		 * Modify the command lookup path, if a PATH= assignment
-		 * is present
-		 */
-		p = (*spp)->text;
-		if (varcmp(p, path) == 0)
-			path = p;
 	}
 
 	/* Print the command if xflag is set. */
@@ -10265,46 +10298,16 @@
 		safe_write(preverrout_fd, "\n", 1);
 	}
 
-	cmd_is_exec = 0;
-	spclbltin = -1;
-
 	/* Now locate the command. */
-	if (argc) {
-		int cmd_flag = DO_ERR;
-#if ENABLE_ASH_CMDCMD
-		const char *oldpath = path + 5;
-#endif
-		path += 5;
-		for (;;) {
-			find_command(argv[0], &cmdentry, cmd_flag, path);
-			if (cmdentry.cmdtype == CMDUNKNOWN) {
-				flush_stdout_stderr();
-				status = 127;
-				goto bail;
-			}
-
-			/* implement bltin and command here */
-			if (cmdentry.cmdtype != CMDBUILTIN)
-				break;
-			if (spclbltin < 0)
-				spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd);
-			if (cmdentry.u.cmd == EXECCMD)
-				cmd_is_exec = 1;
-#if ENABLE_ASH_CMDCMD
-			if (cmdentry.u.cmd == COMMANDCMD) {
-				path = oldpath;
-				nargv = parse_command_args(argv, &path);
-				if (!nargv)
-					break;
-				/* It's "command [-p] PROG ARGS" (that is, no -Vv).
-				 * nargv => "PROG". path is updated if -p.
-				 */
-				argc -= nargv - argv;
-				argv = nargv;
-				cmd_flag |= DO_NOFUNC;
-			} else
-#endif
-				break;
+	if (cmdentry.cmdtype != CMDBUILTIN
+	 || !(IS_BUILTIN_REGULAR(cmdentry.u.cmd))
+	) {
+		find_command(argv[0], &cmdentry, cmd_flag | DO_ERR,
+				path ? path : pathval());
+		if (cmdentry.cmdtype == CMDUNKNOWN) {
+			status = 127;
+			flush_stdout_stderr();
+			goto bail;
 		}
 	}
 
@@ -10383,6 +10386,7 @@
 			/* fall through to exec'ing external program */
 		}
 		listsetvar(varlist.list, VEXPORT|VSTACK);
+		path = path ? path : pathval();
 		shellexec(argv[0], argv, path, cmdentry.u.index);
 		/* NOTREACHED */
 	} /* default */
@@ -13538,11 +13542,8 @@
 /* #if ENABLE_FEATURE_SH_STANDALONE... moved after builtin check */
 
 	updatetbl = (path == pathval());
-	if (!updatetbl) {
+	if (!updatetbl)
 		act |= DO_ALTPATH;
-		if (strstr(path, "%builtin") != NULL)
-			act |= DO_ALTBLTIN;
-	}
 
 	/* If name is in the table, check answer will be ok */
 	cmdp = cmdlookup(name, 0);
@@ -13555,16 +13556,19 @@
 			abort();
 #endif
 		case CMDNORMAL:
-			bit = DO_ALTPATH;
+			bit = DO_ALTPATH | DO_REGBLTIN;
 			break;
 		case CMDFUNCTION:
 			bit = DO_NOFUNC;
 			break;
 		case CMDBUILTIN:
-			bit = IS_BUILTIN_REGULAR(cmdp->param.cmd) ? 0 : DO_ALTBLTIN;
+			bit = IS_BUILTIN_REGULAR(cmdp->param.cmd) ? 0 : DO_REGBLTIN;
 			break;
 		}
 		if (act & bit) {
+			if (act & bit & DO_REGBLTIN)
+				goto fail;
+
 			updatetbl = 0;
 			cmdp = NULL;
 		} else if (cmdp->rehash == 0)
@@ -13577,14 +13581,15 @@
 	if (bcmd) {
 		if (IS_BUILTIN_REGULAR(bcmd))
 			goto builtin_success;
-		if (act & DO_ALTPATH) {
-			if (!(act & DO_ALTBLTIN))
-				goto builtin_success;
-		} else if (builtinloc <= 0) {
+		if (act & DO_ALTPATH)
 			goto builtin_success;
-		}
+		if (builtinloc <= 0)
+			goto builtin_success;
 	}
 
+	if (act & DO_REGBLTIN)
+		goto fail;
+
 #if ENABLE_FEATURE_SH_STANDALONE
 	{
 		int applet_no = find_applet_by_name(name);
@@ -13688,6 +13693,7 @@
 #endif
 		ash_msg("%s: %s", name, errmsg(e, "not found"));
 	}
+ fail:
 	entry->cmdtype = CMDUNKNOWN;
 	return;