diff mbox

exec: Stricter pathopt parsing

Message ID 20180515084951.se6sb5t7pu6iso3t@gondor.apana.org.au (mailing list archive)
State Superseded
Delegated to: Herbert Xu
Headers show

Commit Message

Herbert Xu May 15, 2018, 8:49 a.m. UTC
This patch changes the parsing of pathopt.  First of all only
%builtin and %func (with arbitrary suffixes) will be recognised.
Any other pathopt will be treated as a normal directory.

Furthermore, pathopt can now be specified before the directory,
rather than after it.  In fact, a future version may remove support
for pathopt suffixes.

This is so that it is less likely that a genuine directory containing
a % sign is parsed as a pathopt.

The patch also removes the clearcmdentry optimisation where we
attempt to only partially flush the table where possible.

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

Comments

Jilles Tjoelker May 15, 2018, 9:56 p.m. UTC | #1
On Tue, May 15, 2018 at 04:49:51PM +0800, Herbert Xu wrote:
> This patch changes the parsing of pathopt.  First of all only
> %builtin and %func (with arbitrary suffixes) will be recognised.
> Any other pathopt will be treated as a normal directory.

> Furthermore, pathopt can now be specified before the directory,
> rather than after it.  In fact, a future version may remove support
> for pathopt suffixes.

> This is so that it is less likely that a genuine directory containing
> a % sign is parsed as a pathopt.

Some examples/testcases would make this clearer.

Note that this also affects CDPATH (good) and MAILPATH (breaks its
undocumented feature to replace the "you have mail" text, which seems
unimportant).

> The patch also removes the clearcmdentry optimisation where we
> attempt to only partially flush the table where possible.

That optimisation is not only a waste of code but also not
POSIX-compliant. XCU 2.9.1.1 Command Search and Execution is clear that
the shell is required to forget remembered locations after an assignment
to PATH, and non-normative text with the hash utility remarks that
PATH="$PATH" is an alternative to hash -r that does not need the XSI
option.
diff mbox

Patch

diff --git a/src/exec.c b/src/exec.c
index 04ee2ba..bcac012 100644
--- a/src/exec.c
+++ b/src/exec.c
@@ -92,7 +92,7 @@  STATIC int builtinloc = -1;		/* index in path of %builtin, or -1 */
 
 STATIC void tryexec(char *, char **, char **);
 STATIC void printentry(struct tblentry *);
-STATIC void clearcmdentry(int);
+STATIC void clearcmdentry(void);
 STATIC struct tblentry *cmdlookup(const char *, int);
 STATIC void delete_cmd_entry(void);
 STATIC void addcmdentry(char *, struct cmdentry *);
@@ -168,7 +168,10 @@  repeat:
 	}
 }
 
-
+static char *legal_pathopt(const char *opt)
+{
+	return prefix(opt, "builtin") ?: prefix(opt, "func");
+}
 
 /*
  * Do a path search.  The variable path (passed by reference) should be
@@ -184,33 +187,54 @@  const char *pathopt;
 
 int padvance(const char **path, const char *name)
 {
+	const char *term = "%:";
+	const char *lpathopt;
 	const char *p;
 	char *q;
 	const char *start;
+	size_t qlen;
 	size_t len;
 
 	if (*path == NULL)
 		return -1;
+
+	lpathopt = NULL;
 	start = *path;
-	for (p = start ; *p && *p != ':' && *p != '%' ; p++);
-	len = p - start + strlen(name) + 2;	/* "2" is for '/' and '\0' */
-	q = growstackto(len);
-	if (p != start) {
-		memcpy(q, start, p - start);
-		q += p - start;
-		*q++ = '/';
+
+	if (*start == '%' && (p = legal_pathopt(start + 1))) {
+		lpathopt = start + 1;
+		start = p;
+		term = ":";
 	}
-	strcpy(q, name);
-	pathopt = NULL;
+
+	len = strcspn(start, term);
+	p = start + len;
+
 	if (*p == '%') {
-		pathopt = ++p;
-		while (*p && *p != ':')  p++;
+		size_t extra = strchrnul(p, ':') - p;
+
+		if (legal_pathopt(p + 1))
+			lpathopt = p + 1;
+		else
+			len += extra;
+
+		p += extra;
 	}
-	if (*p == ':')
-		*path = p + 1;
-	else
-		*path = NULL;
-	return len;
+
+	pathopt = lpathopt;
+	*path = *p == ':' ? p + 1 : NULL;
+
+	/* "2" is for '/' and '\0' */
+	qlen = len + strlen(name) + 2;
+	q = growstackto(qlen);
+
+	if (likely(len)) {
+		q = mempcpy(q, start, len);
+		*q++ = '/';
+	}
+	strcpy(q, name);
+
+	return qlen;
 }
 
 
@@ -228,7 +252,7 @@  hashcmd(int argc, char **argv)
 	char *name;
 
 	while ((c = nextopt("r")) != '\0') {
-		clearcmdentry(0);
+		clearcmdentry();
 		return 0;
 	}
 	if (*argptr == NULL) {
@@ -363,15 +387,16 @@  find_command(char *name, struct cmdentry *entry, int act, const char *path)
 	idx = -1;
 loop:
 	while ((len = padvance(&path, name)) >= 0) {
+		const char *lpathopt = pathopt;
+
 		fullname = stackblock();
 		idx++;
-		if (pathopt) {
-			if (prefix(pathopt, "builtin")) {
+		if (lpathopt) {
+			if (*lpathopt == 'b') {
 				if (bcmd)
 					goto builtin_success;
 				continue;
-			} else if (!(act & DO_NOFUNC) &&
-				   prefix(pathopt, "func")) {
+			} else if (!(act & DO_NOFUNC)) {
 				/* handled below */
 			} else {
 				/* ignore unimplemented options */
@@ -397,7 +422,7 @@  loop:
 		e = EACCES;	/* if we fail, this will be the error */
 		if (!S_ISREG(statb.st_mode))
 			continue;
-		if (pathopt) {		/* this is a %func directory */
+		if (lpathopt) {		/* this is a %func directory */
 			stalloc(len);
 			readcmdfile(fullname);
 			if ((cmdp = cmdlookup(name, 0)) == NULL ||
@@ -515,39 +540,26 @@  hashcd(void)
 void
 changepath(const char *newval)
 {
-	const char *old, *new;
+	const char *new;
 	int idx;
-	int firstchange;
 	int bltin;
 
-	old = pathval();
 	new = newval;
-	firstchange = 9999;	/* assume no change */
 	idx = 0;
 	bltin = -1;
 	for (;;) {
-		if (*old != *new) {
-			firstchange = idx;
-			if ((*old == '\0' && *new == ':')
-			 || (*old == ':' && *new == '\0'))
-				firstchange++;
-			old = new;	/* ignore subsequent differences */
-		}
-		if (*new == '\0')
-			break;
-		if (*new == '%' && bltin < 0 && prefix(new + 1, "builtin"))
+		if (*new == '%' && prefix(new + 1, "builtin")) {
 			bltin = idx;
-		if (*new == ':') {
-			idx++;
+			break;
 		}
-		new++, old++;
+		new = strchr(new, ':');
+		if (!new)
+			break;
+		idx++;
+		new++;
 	}
-	if (builtinloc < 0 && bltin >= 0)
-		builtinloc = bltin;		/* zap builtins */
-	if (builtinloc >= 0 && bltin < 0)
-		firstchange = 0;
-	clearcmdentry(firstchange);
 	builtinloc = bltin;
+	clearcmdentry();
 }
 
 
@@ -557,7 +569,7 @@  changepath(const char *newval)
  */
 
 STATIC void
-clearcmdentry(int firstchange)
+clearcmdentry(void)
 {
 	struct tblentry **tblp;
 	struct tblentry **pp;
@@ -567,10 +579,8 @@  clearcmdentry(int firstchange)
 	for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) {
 		pp = tblp;
 		while ((cmdp = *pp) != NULL) {
-			if ((cmdp->cmdtype == CMDNORMAL &&
-			     cmdp->param.index >= firstchange)
-			 || (cmdp->cmdtype == CMDBUILTIN &&
-			     builtinloc >= firstchange)) {
+			if (cmdp->cmdtype == CMDNORMAL ||
+			    (cmdp->cmdtype == CMDBUILTIN && builtinloc > 0)) {
 				*pp = cmdp->next;
 				ckfree(cmdp);
 			} else {