--- a/src/shared/shell.c	2024-05-13 13:59:50.477347842 +0800
+++ b/src/shared/shell.c	2024-05-13 14:11:43.277134799 +0800
@@ -13,6 +13,9 @@
 #endif

 #define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/wait.h>
 #include <stdio.h>
 #include <errno.h>
 #include <syslog.h>
@@ -24,6 +27,10 @@
 #include <sys/signalfd.h>
 #include <android/compat/wordexp.h>
 #include <getopt.h>
+#include <stdint.h>
+#include <string.h>
+#include <fcntl.h>
+#include <paths.h>

 #include <readline/readline.h>
 #include <readline/history.h>
@@ -46,6 +53,11 @@
		printf(COLOR_BLUE "%s %-*s " COLOR_OFF "%s\n", \
			cmd, (int)(CMD_LENGTH - strlen(cmd)), "", desc)

+/*
+ * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
+ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
+ */
+#define MUL_NO_OVERFLOW	((size_t)1 << (sizeof(size_t) * 4))
 struct bt_shell_env {
	char *name;
	void *value;
@@ -88,6 +100,20 @@
 static void shell_print_menu(void);
 static void shell_print_menu_zsh_complete(void);

+static void * _reallocarray(void *optr, size_t nmemb, size_t size)
+{
+
+	if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+	    nmemb > 0 && SIZE_MAX / nmemb < size) {
+		errno = ENOMEM;
+		return (NULL);
+	}
+	return (realloc(optr, size * nmemb));
+}
+
+static int	we_askshell(const char *, wordexp_t *, int);
+static int	we_check(const char *);
+
 static void cmd_version(int argc, char *argv[])
 {
	bt_shell_printf("Version %s\n", VERSION);
@@ -1416,3 +1442,367 @@

	return env->value;
 }
+
+
+/*
+ * wordexp --
+ *	Perform shell word expansion on `words' and place the resulting list
+ *	of words in `we'. See wordexp(3).
+ *
+ *	Specified by IEEE Std. 1003.1-2001.
+ */
+int
+wordexp(const char * __restrict words, wordexp_t * __restrict we, int flags)
+{
+	int error;
+
+	if (flags & WRDE_REUSE)
+		wordfree(we);
+	if ((flags & WRDE_APPEND) == 0) {
+		we->we_wordc = 0;
+		we->we_wordv = NULL;
+		we->we_strings = NULL;
+		we->we_nbytes = 0;
+	}
+	if ((error = we_check(words)) != 0) {
+		wordfree(we);
+		return (error);
+	}
+	if ((error = we_askshell(words, we, flags)) != 0) {
+		wordfree(we);
+		return (error);
+	}
+	return (0);
+}
+
+static size_t
+weread_fully(int fd, char *buffer, size_t len)
+{
+	size_t done;
+	ssize_t nread;
+
+	done = 0;
+	do {
+		nread = read(fd, buffer + done, len - done);
+		if (nread == -1 && errno == EINTR)
+			continue;
+		if (nread <= 0)
+			break;
+		done += nread;
+	} while (done != len);
+	return done;
+}
+
+static bool
+wewrite_fully(int fd, const char *buffer, size_t len)
+{
+	size_t done;
+	ssize_t nwritten;
+
+	done = 0;
+	do {
+		nwritten = write(fd, buffer + done, len - done);
+		if (nwritten == -1 && errno == EINTR)
+			continue;
+		if (nwritten <= 0)
+			return (false);
+		done += nwritten;
+	} while (done != len);
+	return (true);
+}
+
+/*
+ * we_askshell --
+ *	Use the `freebsd_wordexp' /bin/sh builtin function to do most of the
+ *	work in expanding the word string. This function is complicated by
+ *	memory management.
+ */
+static int
+we_askshell(const char *words, wordexp_t *we, int flags)
+{
+	int pdesw[2];			/* Pipe for writing words */
+	int pdes[2];			/* Pipe for reading output */
+	char wfdstr[sizeof(int) * 3 + 1];
+	char buf[35];			/* Buffer for byte and word count */
+	size_t nwords, nbytes;		/* Number of words, bytes from child */
+	size_t i;				/* Handy integer */
+	size_t sofs;			/* Offset into we->we_strings */
+	size_t vofs;			/* Offset into we->we_wordv */
+	pid_t pid;			/* Process ID of child */
+	pid_t wpid;			/* waitpid return value */
+	int status;			/* Child exit status */
+	int error;			/* Our return value */
+	int serrno;			/* errno to return */
+	char *np, *p;			/* Handy pointers */
+	char *nstrings;			/* Temporary for realloc() */
+	char **nwv;			/* Temporary for realloc() */
+	sigset_t newsigblock, oldsigblock;
+	const char *ifs;
+
+	serrno = errno;
+	ifs = getenv("IFS");
+
+	if (pipe2(pdesw, O_CLOEXEC) < 0)
+		return (WRDE_NOSPACE);	/* XXX */
+	snprintf(wfdstr, sizeof(wfdstr), "%d", pdesw[0]);
+	if (pipe2(pdes, O_CLOEXEC) < 0) {
+		close(pdesw[0]);
+		close(pdesw[1]);
+		return (WRDE_NOSPACE);	/* XXX */
+	}
+	(void)sigemptyset(&newsigblock);
+	(void)sigaddset(&newsigblock, SIGCHLD);
+	(void)sigprocmask(SIG_BLOCK, &newsigblock, &oldsigblock);
+	if ((pid = fork()) < 0) {
+		serrno = errno;
+		close(pdesw[0]);
+		close(pdesw[1]);
+		close(pdes[0]);
+		close(pdes[1]);
+		(void)sigprocmask(SIG_SETMASK, &oldsigblock, NULL);
+		errno = serrno;
+		return (WRDE_NOSPACE);	/* XXX */
+	}
+	else if (pid == 0) {
+		/*
+		 * We are the child; make /bin/sh expand `words'.
+		 */
+		(void)sigprocmask(SIG_SETMASK, &oldsigblock, NULL);
+		if ((pdes[1] != STDOUT_FILENO ?
+		    dup2(pdes[1], STDOUT_FILENO) :
+		    fcntl(pdes[1], F_SETFD, 0)) < 0)
+			_exit(1);
+		if (fcntl(pdesw[0], F_SETFD, 0) < 0)
+			_exit(1);
+		execl(_PATH_BSHELL, "sh", flags & WRDE_UNDEF ? "-u" : "+u",
+		    "-c", "IFS=$1;eval \"$2\";"
+		    "freebsd_wordexp -f \"$3\" ${4:+\"$4\"}",
+		    "",
+		    ifs != NULL ? ifs : " \t\n",
+		    flags & WRDE_SHOWERR ? "" : "exec 2>/dev/null",
+		    wfdstr,
+		    flags & WRDE_NOCMD ? "-p" : "",
+		    (char *)NULL);
+		_exit(1);
+	}
+
+	/*
+	 * We are the parent; write the words.
+	 */
+	close(pdes[1]);
+	close(pdesw[0]);
+	if (!wewrite_fully(pdesw[1], words, strlen(words))) {
+		close(pdesw[1]);
+		error = WRDE_SYNTAX;
+		goto cleanup;
+	}
+	close(pdesw[1]);
+	/*
+	 * Read the output of the shell wordexp function,
+	 * which is a byte indicating that the words were parsed successfully,
+	 * a 64-bit hexadecimal word count, a dummy byte, a 64-bit hexadecimal
+	 * byte count (not including terminating null bytes), followed by the
+	 * expanded words separated by nulls.
+	 */
+	switch (weread_fully(pdes[0], buf, 34)) {
+	case 1:
+		error = buf[0] == 'C' ? WRDE_CMDSUB : WRDE_BADVAL;
+		serrno = errno;
+		goto cleanup;
+	case 34:
+		break;
+	default:
+		error = WRDE_SYNTAX;
+		serrno = errno;
+		goto cleanup;
+	}
+	buf[17] = '\0';
+	nwords = strtol(buf + 1, NULL, 16);
+	buf[34] = '\0';
+	nbytes = strtol(buf + 18, NULL, 16) + nwords;
+
+	/*
+	 * Allocate or reallocate (when flags & WRDE_APPEND) the word vector
+	 * and string storage buffers for the expanded words we're about to
+	 * read from the child.
+	 */
+	sofs = we->we_nbytes;
+	vofs = we->we_wordc;
+	if ((flags & (WRDE_DOOFFS|WRDE_APPEND)) == (WRDE_DOOFFS|WRDE_APPEND))
+		vofs += we->we_offs;
+	we->we_wordc += nwords;
+	we->we_nbytes += nbytes;
+	if ((nwv = _reallocarray(we->we_wordv, (we->we_wordc + 1 +
+	    (flags & WRDE_DOOFFS ? we->we_offs : 0)),
+	    sizeof(char *))) == NULL) {
+		error = WRDE_NOSPACE;
+		goto cleanup;
+	}
+	we->we_wordv = nwv;
+	if ((nstrings = realloc(we->we_strings, we->we_nbytes)) == NULL) {
+		error = WRDE_NOSPACE;
+		goto cleanup;
+	}
+	for (i = 0; i < vofs; i++)
+		if (we->we_wordv[i] != NULL)
+			we->we_wordv[i] += nstrings - we->we_strings;
+	we->we_strings = nstrings;
+
+	if (weread_fully(pdes[0], we->we_strings + sofs, nbytes) != nbytes) {
+		error = WRDE_NOSPACE; /* abort for unknown reason */
+		serrno = errno;
+		goto cleanup;
+	}
+
+	error = 0;
+cleanup:
+	close(pdes[0]);
+	do
+		wpid = waitpid(pid, &status, 0);
+	while (wpid < 0 && errno == EINTR);
+	(void)sigprocmask(SIG_SETMASK, &oldsigblock, NULL);
+	if (error != 0) {
+		errno = serrno;
+		return (error);
+	}
+	if (wpid < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
+		return (WRDE_NOSPACE); /* abort for unknown reason */
+
+	/*
+	 * Break the null-terminated expanded word strings out into
+	 * the vector.
+	 */
+	if (vofs == 0 && flags & WRDE_DOOFFS)
+		while (vofs < we->we_offs)
+			we->we_wordv[vofs++] = NULL;
+	p = we->we_strings + sofs;
+	while (nwords-- != 0) {
+		we->we_wordv[vofs++] = p;
+		if ((np = memchr(p, '\0', nbytes)) == NULL)
+			return (WRDE_NOSPACE);	/* XXX */
+		nbytes -= np - p + 1;
+		p = np + 1;
+	}
+	we->we_wordv[vofs] = NULL;
+
+	return (0);
+}
+
+/*
+ * we_check --
+ *	Check that the string contains none of the following unquoted
+ *	special characters: <newline> |&;<>(){}
+ *	This mainly serves for {} which are normally legal in sh.
+ *	It deliberately does not attempt to model full sh syntax.
+ */
+static int
+we_check(const char *words)
+{
+	char c;
+	/* Saw \ or $, possibly not special: */
+	bool quote = false, dollar = false;
+	/* Saw ', ", ${, ` or $(, possibly not special: */
+	bool have_sq = false, have_dq = false, have_par_begin = false;
+	bool have_cmd = false;
+	/* Definitely saw a ', ", ${, ` or $(, need a closing character: */
+	bool need_sq = false, need_dq = false, need_par_end = false;
+	bool need_cmd_old = false, need_cmd_new = false;
+
+	while ((c = *words++) != '\0') {
+		switch (c) {
+		case '\\':
+			quote = !quote;
+			continue;
+		case '$':
+			if (quote)
+				quote = false;
+			else
+				dollar = !dollar;
+			continue;
+		case '\'':
+			if (!quote && !have_sq && !have_dq)
+				need_sq = true;
+			else
+				need_sq = false;
+			have_sq = true;
+			break;
+		case '"':
+			if (!quote && !have_sq && !have_dq)
+				need_dq = true;
+			else
+				need_dq = false;
+			have_dq = true;
+			break;
+		case '`':
+			if (!quote && !have_sq && !have_cmd)
+				need_cmd_old = true;
+			else
+				need_cmd_old = false;
+			have_cmd = true;
+			break;
+		case '{':
+			if (!quote && !dollar && !have_sq && !have_dq &&
+			    !have_cmd)
+				return (WRDE_BADCHAR);
+			if (dollar) {
+				if (!quote && !have_sq)
+					need_par_end = true;
+				have_par_begin = true;
+			}
+			break;
+		case '}':
+			if (!quote && !have_sq && !have_dq && !have_par_begin &&
+			    !have_cmd)
+				return (WRDE_BADCHAR);
+			need_par_end = false;
+			break;
+		case '(':
+			if (!quote && !dollar && !have_sq && !have_dq &&
+			    !have_cmd)
+				return (WRDE_BADCHAR);
+			if (dollar) {
+				if (!quote && !have_sq)
+					need_cmd_new = true;
+				have_cmd = true;
+			}
+			break;
+		case ')':
+			if (!quote && !have_sq && !have_dq && !have_cmd)
+				return (WRDE_BADCHAR);
+			need_cmd_new = false;
+			break;
+		case '|': case '&': case ';': case '<': case '>': case '\n':
+			if (!quote && !have_sq && !have_dq && !have_cmd)
+				return (WRDE_BADCHAR);
+			break;
+		default:
+			break;
+		}
+		quote = dollar = false;
+	}
+	if (quote || dollar || need_sq || need_dq || need_par_end ||
+	    need_cmd_old || need_cmd_new)
+		return (WRDE_SYNTAX);
+
+	return (0);
+}
+
+/*
+ * wordfree --
+ *	Free the result of wordexp(). See wordexp(3).
+ *
+ *	Specified by IEEE Std. 1003.1-2001.
+ */
+void
+wordfree(wordexp_t *we)
+{
+
+	if (we == NULL)
+		return;
+	free(we->we_wordv);
+	free(we->we_strings);
+	we->we_wordv = NULL;
+	we->we_strings = NULL;
+	we->we_nbytes = 0;
+	we->we_wordc = 0;
+}
