Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 21 Jun 2010 15:12:50 +0200
From:      =?utf-8?Q?Dag-Erling_Sm=C3=B8rgrav?= <des@des.no>
To:        jhell <jhell@dataix.net>
Cc:        "Sam Fourman Jr." <sfourman@gmail.com>, Brandon Gooch <jamesbrandongooch@gmail.com>, FreeBSD Current <freebsd-current@freebsd.org>, jilles@stack.nl
Subject:   Re: [MFC REQUEST] Filename completion in sh(1)
Message-ID:  <86pqzkfs19.fsf@ds4.des.no>
In-Reply-To: <4C1F6148.9010300@dataix.net> (jhell@dataix.net's message of "Mon, 21 Jun 2010 08:55:36 -0400")
References:  <AANLkTilp7vQWC1Ae-ttAUusrT2Pb_-VsM4Ms3YbEo88G@mail.gmail.com> <4C18304A.1080601@dataix.net> <4C18322A.6050007@dataix.net> <AANLkTillAajdePsc635p9r2jRAjhppF-aNhdrFilXoV9@mail.gmail.com> <AANLkTileO47fQqUpaRKj55uqi8o65Ju8WV7crtyRizgf@mail.gmail.com> <AANLkTik7tUdJ1Im9dRQZt6FZw7sCId0a8qwJgGQKjPyf@mail.gmail.com> <86wrtz5mk6.fsf@ds4.des.no> <4C1F576F.9090901@dataix.net> <86y6e8fuo1.fsf@ds4.des.no> <4C1F6148.9010300@dataix.net>

next in thread | previous in thread | raw e-mail | index | archive | help
--=-=-=
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

jhell <jhell@dataix.net> writes:
> Sorry but he only mention of filecomplete in your patch is in this
> section. That is exactly the same in the patch I had originally
> generated using SVN.

Umm, the copy I have on my disk has those files.  Just to be sure, I
just generated a new diff from the tree I tested it in.

DES
--=20
Dag-Erling Sm=C3=B8rgrav - des@des.no


--=-=-=
Content-Type: text/x-patch
Content-Disposition: inline; filename=sh-tab-completion-stable-8.diff

Index: include/histedit.h
===================================================================
--- include/histedit.h	(revision 209229)
+++ include/histedit.h	(working copy)
@@ -105,9 +105,8 @@
  */
 int		 el_set(EditLine *, int, ...);
 int		 el_get(EditLine *, int, ...);
-#if 0
 unsigned char	_el_fn_complete(EditLine *, int);
-#endif
+unsigned char	_el_fn_sh_complete(EditLine *, int);
 
 /*
  * el_set/el_get parameters
Index: lib/libedit/filecomplete.c
===================================================================
--- lib/libedit/filecomplete.c	(revision 0)
+++ lib/libedit/filecomplete.c	(revision 0)
@@ -0,0 +1,667 @@
+/*-
+ * Copyright (c) 1997 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ *	$NetBSD: filecomplete.c,v 1.19 2010/06/01 18:20:26 christos Exp $
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <string.h>
+#include <pwd.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <vis.h>
+#include "el.h"
+#include "fcns.h"		/* for EL_NUM_FCNS */
+#include "histedit.h"
+#include "filecomplete.h"
+
+static char break_chars[] = { ' ', '\t', '\n', '"', '\\', '\'', '`',
+    '>', '<', '=', ';', '|', '&', '{', '(', '\0' };
+/* Tilde is deliberately omitted here, we treat it specially. */
+static char extra_quote_chars[] = { ')', '}', '*', '?', '[', '$', '\0' };
+
+
+/********************************/
+/* completion functions */
+
+/*
+ * does tilde expansion of strings of type ``~user/foo''
+ * if ``user'' isn't valid user name or ``txt'' doesn't start
+ * w/ '~', returns pointer to strdup()ed copy of ``txt''
+ *
+ * it's callers's responsibility to free() returned string
+ */
+char *
+fn_tilde_expand(const char *txt)
+{
+	struct passwd pwres, *pass;
+	char *temp;
+	size_t len = 0;
+	char pwbuf[1024];
+
+	if (txt[0] != '~')
+		return (strdup(txt));
+
+	temp = strchr(txt + 1, '/');
+	if (temp == NULL) {
+		temp = strdup(txt + 1);
+		if (temp == NULL)
+			return NULL;
+	} else {
+		len = temp - txt + 1;	/* text until string after slash */
+		temp = malloc(len);
+		if (temp == NULL)
+			return NULL;
+		(void)strncpy(temp, txt + 1, len - 2);
+		temp[len - 2] = '\0';
+	}
+	if (temp[0] == 0) {
+		if (getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf), &pass) != 0)
+			pass = NULL;
+	} else {
+		if (getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf), &pass) != 0)
+			pass = NULL;
+	}
+	free(temp);		/* value no more needed */
+	if (pass == NULL)
+		return (strdup(txt));
+
+	/* update pointer txt to point at string immediately following */
+	/* first slash */
+	txt += len;
+
+	temp = malloc(strlen(pass->pw_dir) + 1 + strlen(txt) + 1);
+	if (temp == NULL)
+		return NULL;
+	(void)sprintf(temp, "%s/%s", pass->pw_dir, txt);
+
+	return (temp);
+}
+
+
+/*
+ * return first found file name starting by the ``text'' or NULL if no
+ * such file can be found
+ * value of ``state'' is ignored
+ *
+ * it's caller's responsibility to free returned string
+ */
+char *
+fn_filename_completion_function(const char *text, int state)
+{
+	static DIR *dir = NULL;
+	static char *filename = NULL, *dirname = NULL, *dirpath = NULL;
+	static size_t filename_len = 0;
+	struct dirent *entry;
+	char *temp;
+	size_t len;
+
+	if (state == 0 || dir == NULL) {
+		temp = strrchr(text, '/');
+		if (temp) {
+			char *nptr;
+			temp++;
+			nptr = realloc(filename, strlen(temp) + 1);
+			if (nptr == NULL) {
+				free(filename);
+				filename = NULL;
+				return NULL;
+			}
+			filename = nptr;
+			(void)strcpy(filename, temp);
+			len = temp - text;	/* including last slash */
+
+			nptr = realloc(dirname, len + 1);
+			if (nptr == NULL) {
+				free(dirname);
+				dirname = NULL;
+				return NULL;
+			}
+			dirname = nptr;
+			(void)strncpy(dirname, text, len);
+			dirname[len] = '\0';
+		} else {
+			free(filename);
+			if (*text == 0)
+				filename = NULL;
+			else {
+				filename = strdup(text);
+				if (filename == NULL)
+					return NULL;
+			}
+			free(dirname);
+			dirname = NULL;
+		}
+
+		if (dir != NULL) {
+			(void)closedir(dir);
+			dir = NULL;
+		}
+
+		/* support for ``~user'' syntax */
+
+		free(dirpath);
+		dirpath = NULL;
+		if (dirname == NULL) {
+			if ((dirname = strdup("")) == NULL)
+				return NULL;
+			dirpath = strdup("./");
+		} else if (*dirname == '~')
+			dirpath = fn_tilde_expand(dirname);
+		else
+			dirpath = strdup(dirname);
+
+		if (dirpath == NULL)
+			return NULL;
+
+		dir = opendir(dirpath);
+		if (!dir)
+			return (NULL);	/* cannot open the directory */
+
+		/* will be used in cycle */
+		filename_len = filename ? strlen(filename) : 0;
+	}
+
+	/* find the match */
+	while ((entry = readdir(dir)) != NULL) {
+		/* skip . and .. */
+		if (entry->d_name[0] == '.' && (!entry->d_name[1]
+		    || (entry->d_name[1] == '.' && !entry->d_name[2])))
+			continue;
+		if (filename_len == 0)
+			break;
+		/* otherwise, get first entry where first */
+		/* filename_len characters are equal	  */
+		if (entry->d_name[0] == filename[0]
+		    && entry->d_namlen >= filename_len
+		    && strncmp(entry->d_name, filename,
+			filename_len) == 0)
+			break;
+	}
+
+	if (entry) {		/* match found */
+		len = entry->d_namlen;
+
+		temp = malloc(strlen(dirname) + len + 1);
+		if (temp == NULL)
+			return NULL;
+		(void)sprintf(temp, "%s%s", dirname, entry->d_name);
+	} else {
+		(void)closedir(dir);
+		dir = NULL;
+		temp = NULL;
+	}
+
+	return (temp);
+}
+
+
+static const char *
+append_char_function(const char *name)
+{
+	struct stat stbuf;
+	char *expname = *name == '~' ? fn_tilde_expand(name) : NULL;
+	const char *rs = " ";
+
+	if (stat(expname ? expname : name, &stbuf) == -1)
+		goto out;
+	if (S_ISDIR(stbuf.st_mode))
+		rs = "/";
+out:
+	if (expname)
+		free(expname);
+	return rs;
+}
+
+
+/*
+ * returns list of completions for text given
+ * non-static for readline.
+ */
+char ** completion_matches(const char *, char *(*)(const char *, int));
+char **
+completion_matches(const char *text, char *(*genfunc)(const char *, int))
+{
+	char **match_list = NULL, *retstr, *prevstr;
+	size_t match_list_len, max_equal, which, i;
+	size_t matches;
+
+	matches = 0;
+	match_list_len = 1;
+	while ((retstr = (*genfunc) (text, (int)matches)) != NULL) {
+		/* allow for list terminator here */
+		if (matches + 3 >= match_list_len) {
+			char **nmatch_list;
+			while (matches + 3 >= match_list_len)
+				match_list_len <<= 1;
+			nmatch_list = realloc(match_list,
+			    match_list_len * sizeof(char *));
+			if (nmatch_list == NULL) {
+				free(match_list);
+				return NULL;
+			}
+			match_list = nmatch_list;
+
+		}
+		match_list[++matches] = retstr;
+	}
+
+	if (!match_list)
+		return NULL;	/* nothing found */
+
+	/* find least denominator and insert it to match_list[0] */
+	which = 2;
+	prevstr = match_list[1];
+	max_equal = strlen(prevstr);
+	for (; which <= matches; which++) {
+		for (i = 0; i < max_equal &&
+		    prevstr[i] == match_list[which][i]; i++)
+			continue;
+		max_equal = i;
+	}
+
+	retstr = malloc(max_equal + 1);
+	if (retstr == NULL) {
+		free(match_list);
+		return NULL;
+	}
+	(void)strncpy(retstr, match_list[1], max_equal);
+	retstr[max_equal] = '\0';
+	match_list[0] = retstr;
+
+	/* add NULL as last pointer to the array */
+	match_list[matches + 1] = (char *) NULL;
+
+	return (match_list);
+}
+
+
+/*
+ * Sort function for qsort(). Just wrapper around strcasecmp().
+ */
+static int
+_fn_qsort_string_compare(const void *i1, const void *i2)
+{
+	const char *s1 = ((const char * const *)i1)[0];
+	const char *s2 = ((const char * const *)i2)[0];
+
+	return strcasecmp(s1, s2);
+}
+
+
+/*
+ * Display list of strings in columnar format on readline's output stream.
+ * 'matches' is list of strings, 'len' is number of strings in 'matches',
+ * 'max' is maximum length of string in 'matches'.
+ */
+void
+fn_display_match_list(EditLine *el, char **matches, size_t len, size_t max)
+{
+	size_t i, idx, limit, count;
+	int screenwidth = el->el_term.t_size.h;
+
+	/*
+	 * Find out how many entries can be put on one line, count
+	 * with two spaces between strings.
+	 */
+	limit = screenwidth / (max + 2);
+	if (limit == 0)
+		limit = 1;
+
+	/* how many lines of output */
+	count = len / limit;
+	if (count * limit < len)
+		count++;
+
+	/* Sort the items if they are not already sorted. */
+	qsort(&matches[1], len, sizeof(char *), _fn_qsort_string_compare);
+
+	idx = 1;
+	for(; count > 0; count--) {
+		int more = limit > 0 && matches[0];
+		for(i = 0; more; idx++) {
+			more = ++i < limit && matches[idx + 1];
+			(void)fprintf(el->el_outfile, "%-*s%s", (int)max,
+			    matches[idx], more ? " " : "");
+		}
+		(void)fprintf(el->el_outfile, "\n");
+	}
+}
+
+
+/*
+ * Complete the word at or before point,
+ * 'what_to_do' says what to do with the completion.
+ * \t   means do standard completion.
+ * `?' means list the possible completions.
+ * `*' means insert all of the possible completions.
+ * `!' means to do standard completion, and list all possible completions if
+ * there is more than one.
+ *
+ * Note: '*' support is not implemented
+ *       '!' could never be invoked
+ */
+int
+fn_complete(EditLine *el,
+	char *(*complet_func)(const char *, int),
+	char **(*attempted_completion_function)(const char *, int, int),
+	const char *word_break, const char *special_prefixes,
+	const char *(*app_func)(const char *), size_t query_items,
+	int *completion_type, int *over, int *point, int *end,
+	const char *(*find_word_start_func)(const char *, const char *),
+	char *(*dequoting_func)(const char *),
+	char *(*quoting_func)(const char *))
+{
+	const LineInfo *li;
+	char *temp;
+	char *dequoted_temp;
+	char **matches;
+	const char *ctemp;
+	size_t len;
+	int what_to_do = '\t';
+	int retval = CC_NORM;
+
+	if (el->el_state.lastcmd == el->el_state.thiscmd)
+		what_to_do = '?';
+
+	/* readline's rl_complete() has to be told what we did... */
+	if (completion_type != NULL)
+		*completion_type = what_to_do;
+
+	if (!complet_func)
+		complet_func = fn_filename_completion_function;
+	if (!app_func)
+		app_func = append_char_function;
+
+	/* We now look backwards for the start of a filename/variable word */
+	li = el_line(el);
+	if (find_word_start_func)
+		ctemp = find_word_start_func(li->buffer, li->cursor);
+	else {
+		ctemp = li->cursor;
+		while (ctemp > li->buffer
+		    && !strchr(word_break, ctemp[-1])
+		    && (!special_prefixes || !strchr(special_prefixes, ctemp[-1]) ) )
+			ctemp--;
+	}
+
+	len = li->cursor - ctemp;
+#if defined(__SSP__) || defined(__SSP_ALL__)
+	temp = malloc(sizeof(*temp) * (len + 1));
+	if (temp == NULL)
+		return retval;
+#else
+	temp = alloca(sizeof(*temp) * (len + 1));
+#endif
+	(void)strncpy(temp, ctemp, len);
+	temp[len] = '\0';
+
+	if (dequoting_func) {
+		dequoted_temp = dequoting_func(temp);
+		if (dequoted_temp == NULL)
+			return retval;
+	} else
+		dequoted_temp = NULL;
+
+	/* these can be used by function called in completion_matches() */
+	/* or (*attempted_completion_function)() */
+	if (point != 0)
+		*point = (int)(li->cursor - li->buffer);
+	if (end != NULL)
+		*end = (int)(li->lastchar - li->buffer);
+
+	if (attempted_completion_function) {
+		int cur_off = (int)(li->cursor - li->buffer);
+		matches = (*attempted_completion_function) (dequoted_temp ? dequoted_temp : temp,
+		    (int)(cur_off - len), cur_off);
+	} else
+		matches = 0;
+	if (!attempted_completion_function || 
+	    (over != NULL && !*over && !matches))
+		matches = completion_matches(dequoted_temp ? dequoted_temp : temp, complet_func);
+
+	if (over != NULL)
+		*over = 0;
+
+	if (matches) {
+		int i;
+		size_t matches_num, maxlen, match_len, match_display=1;
+
+		retval = CC_REFRESH;
+		/*
+		 * Only replace the completed string with common part of
+		 * possible matches if there is possible completion.
+		 */
+		if (matches[0][0] != '\0') {
+			char *quoted_match;
+			if (quoting_func) {
+				quoted_match = quoting_func(matches[0]);
+				if (quoted_match == NULL)
+					goto free_matches;
+			} else
+				quoted_match = NULL;
+
+			el_deletestr(el, (int) len);
+			el_insertstr(el, quoted_match ? quoted_match : matches[0]);
+
+			free(quoted_match);
+		}
+
+		if (what_to_do == '?')
+			goto display_matches;
+
+		if (matches[2] == NULL && strcmp(matches[0], matches[1]) == 0) {
+			/*
+			 * We found exact match. Add a space after
+			 * it, unless we do filename completion and the
+			 * object is a directory.
+			 */
+			el_insertstr(el, (*app_func)(matches[0]));
+		} else if (what_to_do == '!') {
+    display_matches:
+			/*
+			 * More than one match and requested to list possible
+			 * matches.
+			 */
+
+			for(i = 1, maxlen = 0; matches[i]; i++) {
+				match_len = strlen(matches[i]);
+				if (match_len > maxlen)
+					maxlen = match_len;
+			}
+			matches_num = i - 1;
+				
+			/* newline to get on next line from command line */
+			(void)fprintf(el->el_outfile, "\n");
+
+			/*
+			 * If there are too many items, ask user for display
+			 * confirmation.
+			 */
+			if (matches_num > query_items) {
+				(void)fprintf(el->el_outfile,
+				    "Display all %zu possibilities? (y or n) ",
+				    matches_num);
+				(void)fflush(el->el_outfile);
+				if (getc(stdin) != 'y')
+					match_display = 0;
+				(void)fprintf(el->el_outfile, "\n");
+			}
+
+			if (match_display)
+				fn_display_match_list(el, matches, matches_num,
+				    maxlen);
+			retval = CC_REDISPLAY;
+		} else if (matches[0][0]) {
+			/*
+			 * There was some common match, but the name was
+			 * not complete enough. Next tab will print possible
+			 * completions.
+			 */
+			el_beep(el);
+		} else {
+			/* lcd is not a valid object - further specification */
+			/* is needed */
+			el_beep(el);
+			retval = CC_NORM;
+		}
+
+free_matches:
+		/* free elements of array and the array itself */
+		for (i = 0; matches[i]; i++)
+			free(matches[i]);
+		free(matches);
+		matches = NULL;
+	}
+	free(dequoted_temp);
+#if defined(__SSP__) || defined(__SSP_ALL__)
+	free(temp);
+#endif
+	return retval;
+}
+
+
+/*
+ * el-compatible wrapper around rl_complete; needed for key binding
+ */
+/* ARGSUSED */
+unsigned char
+_el_fn_complete(EditLine *el, int ch __attribute__((__unused__)))
+{
+	return (unsigned char)fn_complete(el, NULL, NULL,
+	    break_chars, NULL, NULL, 100,
+	    NULL, NULL, NULL, NULL,
+	    NULL, NULL, NULL);
+}
+
+
+static const char *
+sh_find_word_start(const char *buffer, const char *cursor)
+{
+	const char *word_start = buffer;
+
+	while (buffer < cursor) {
+		if (*buffer == '\\')
+			buffer++;
+		else if (strchr(break_chars, *buffer))
+			word_start = buffer + 1;
+
+		buffer++;
+	}
+
+	return word_start;
+}
+
+
+static char *
+sh_quote(const char *str)
+{
+	const char *src;
+	int extra_len = 0;
+	char *quoted_str, *dst;
+
+	if (*str == '-' || *str == '+')
+		extra_len += 2;
+	for (src = str; *src != '\0'; src++)
+		if (strchr(break_chars, *src) ||
+		    strchr(extra_quote_chars, *src))
+			extra_len++;
+
+	quoted_str = malloc(sizeof(*quoted_str) *
+	    (strlen(str) + extra_len + 1));
+	if (quoted_str == NULL)
+		return NULL;
+
+	dst = quoted_str;
+	if (*str == '-' || *str == '+')
+		*dst++ = '.', *dst++ = '/';
+	for (src = str; *src != '\0'; src++) {
+		if (strchr(break_chars, *src) ||
+		    strchr(extra_quote_chars, *src))
+			*dst++ = '\\';
+		*dst++ = *src;
+	}
+	*dst = '\0';
+
+	return quoted_str;
+}
+
+
+static char *
+sh_dequote(const char *str)
+{
+	char *dequoted_str, *dst;
+
+	/* save extra space to replace \~ with ./~ */
+	dequoted_str = malloc(sizeof(*dequoted_str) * (strlen(str) + 1 + 1));
+	if (dequoted_str == NULL)
+		return NULL;
+
+	dst = dequoted_str;
+
+	/* dequote \~ at start as ./~ */
+	if (*str == '\\' && str[1] == '~') {
+		str++;
+		*dst++ = '.';
+		*dst++ = '/';
+	}
+
+	while (*str) {
+		if (*str == '\\')
+			str++;
+		if (*str)
+			*dst++ = *str++;
+	}
+	*dst = '\0';
+
+	return dequoted_str;
+}
+
+
+/*
+ * completion function using sh quoting rules; for key binding
+ */
+/* ARGSUSED */
+unsigned char
+_el_fn_sh_complete(EditLine *el, int ch __attribute__((__unused__)))
+{
+	return (unsigned char)fn_complete(el, NULL, NULL,
+	    break_chars, NULL, NULL, 100,
+	    NULL, NULL, NULL, NULL,
+	    sh_find_word_start, sh_dequote, sh_quote);
+}

Property changes on: lib/libedit/filecomplete.c
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Index: lib/libedit/filecomplete.h
===================================================================
--- lib/libedit/filecomplete.h	(revision 0)
+++ lib/libedit/filecomplete.h	(revision 0)
@@ -0,0 +1,48 @@
+/*-
+ * Copyright (c) 1997 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jaromir Dolecek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ *	$NetBSD: filecomplete.h,v 1.9 2009/12/30 22:37:40 christos Exp $
+ * $FreeBSD$
+ */
+#ifndef _FILECOMPLETE_H_
+#define _FILECOMPLETE_H_
+
+int fn_complete(EditLine *,
+    char *(*)(const char *, int),
+    char **(*)(const char *, int, int),
+    const char *, const char *, const char *(*)(const char *), size_t,
+    int *, int *, int *, int *,
+    const char *(*)(const char *, const char *),
+    char *(*)(const char *),
+    char *(*)(const char *));
+
+void fn_display_match_list(EditLine *, char **, size_t, size_t);
+char *fn_tilde_expand(const char *);
+char *fn_filename_completion_function(const char *, int);
+
+#endif

Property changes on: lib/libedit/filecomplete.h
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Index: lib/libedit/read.h
===================================================================
--- lib/libedit/read.h	(revision 209229)
+++ lib/libedit/read.h	(working copy)
@@ -13,9 +13,6 @@
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of The NetBSD Foundation nor the names of its
- *    contributors may be used to endorse or promote products derived
- *    from this software without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
Index: lib/libedit/Makefile
===================================================================
--- lib/libedit/Makefile	(revision 209229)
+++ lib/libedit/Makefile	(working copy)
@@ -6,7 +6,8 @@
 SHLIB_MAJOR=	7
 SHLIBDIR?= /lib
 
-OSRCS=	chared.c common.c el.c emacs.c fcns.c help.c hist.c key.c map.c \
+OSRCS=	chared.c common.c el.c emacs.c fcns.c filecomplete.c help.c \
+	hist.c key.c map.c \
 	parse.c prompt.c read.c refresh.c search.c sig.c term.c tty.c vi.c
 
 DPADD=	${LIBNCURSES}
@@ -35,6 +36,8 @@
 CFLAGS+= #-DDEBUG_TTY -DDEBUG_KEY -DDEBUG_READ -DDEBUG -DDEBUG_REFRESH
 CFLAGS+= #-DDEBUG_PASTE -DDEBUG_EDIT
 
+WARNS?=	1
+
 AHDR=	vi.h emacs.h common.h
 ASRC=	${.CURDIR}/vi.c ${.CURDIR}/emacs.c ${.CURDIR}/common.c
 
Index: lib/libedit/editrc.5
===================================================================
--- lib/libedit/editrc.5	(revision 209229)
+++ lib/libedit/editrc.5	(working copy)
@@ -13,9 +13,6 @@
 .\" 2. Redistributions in binary form must reproduce the above copyright
 .\"    notice, this list of conditions and the following disclaimer in the
 .\"    documentation and/or other materials provided with the distribution.
-.\" 3. Neither the name of The NetBSD Foundation nor the names of its
-.\"    contributors may be used to endorse or promote products derived
-.\"    from this software without specific prior written permission.
 .\"
 .\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 .\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
@@ -32,8 +29,8 @@
 .\" $FreeBSD$
 .\"
 .Dd October 18, 2003
-.Os
 .Dt EDITRC 5
+.Os
 .Sh NAME
 .Nm editrc
 .Nd configuration file for editline library
Index: lib/libedit/editline.3
===================================================================
--- lib/libedit/editline.3	(revision 209229)
+++ lib/libedit/editline.3	(working copy)
@@ -13,9 +13,6 @@
 .\" 2. Redistributions in binary form must reproduce the above copyright
 .\"    notice, this list of conditions and the following disclaimer in the
 .\"    documentation and/or other materials provided with the distribution.
-.\" 3. Neither the name of The NetBSD Foundation nor the names of its
-.\"    contributors may be used to endorse or promote products derived
-.\"    from this software without specific prior written permission.
 .\"
 .\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 .\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
@@ -32,8 +29,8 @@
 .\" $FreeBSD$
 .\"
 .Dd January 12, 2007
-.Os
 .Dt EDITLINE 3
+.Os
 .Sh NAME
 .Nm editline ,
 .Nm el_init ,
Index: bin/sh/memalloc.h
===================================================================
--- bin/sh/memalloc.h	(revision 209229)
+++ bin/sh/memalloc.h	(working copy)
@@ -51,7 +51,7 @@
 pointer ckmalloc(size_t);
 pointer ckrealloc(pointer, int);
 void ckfree(pointer);
-char *savestr(char *);
+char *savestr(const char *);
 pointer stalloc(int);
 void stunalloc(pointer);
 void setstackmark(struct stackmark *);
Index: bin/sh/alias.h
===================================================================
--- bin/sh/alias.h	(revision 209229)
+++ bin/sh/alias.h	(working copy)
@@ -42,7 +42,7 @@
 	int flag;
 };
 
-struct alias *lookupalias(char *, int);
+struct alias *lookupalias(const char *, int);
 int aliascmd(int, char **);
 int unaliascmd(int, char **);
 void rmaliases(void);
Index: bin/sh/mkbuiltins
===================================================================
--- bin/sh/mkbuiltins	(revision 209229)
+++ bin/sh/mkbuiltins	(working copy)
@@ -88,7 +88,7 @@
 	awk '{	printf "#define %s %d\n", $1, NR-1}'
 echo '
 struct builtincmd {
-      char *name;
+      const char *name;
       int code;
       int special;
 };
Index: bin/sh/options.h
===================================================================
--- bin/sh/options.h	(revision 209229)
+++ bin/sh/options.h	(working copy)
@@ -102,7 +102,7 @@
 extern struct shparam shellparam;  /* $@ */
 extern char **argptr;		/* argument list for builtin commands */
 extern char *shoptarg;		/* set by nextopt */
-extern char *optptr;		/* used by nextopt */
+extern char *nextopt_optptr;	/* used by nextopt */
 
 void procargs(int, char **);
 void optschanged(void);
@@ -111,5 +111,5 @@
 int shiftcmd(int, char **);
 int setcmd(int, char **);
 int getoptscmd(int, char **);
-int nextopt(char *);
+int nextopt(const char *);
 void getoptsreset(const char *);
Index: bin/sh/output.c
===================================================================
--- bin/sh/output.c	(revision 209229)
+++ bin/sh/output.c	(working copy)
@@ -71,7 +71,7 @@
 static int doformat_wr(void *, const char *, int);
 
 struct output output = {NULL, 0, NULL, OUTBUFSIZ, 1, 0};
-struct output errout = {NULL, 0, NULL, 100, 2, 0};
+struct output errout = {NULL, 0, NULL, 256, 2, 0};
 struct output memout = {NULL, 0, NULL, 0, MEM_OUT, 0};
 struct output *out1 = &output;
 struct output *out2 = &errout;
@@ -124,8 +124,6 @@
 {
 	while (*p)
 		outc(*p++, file);
-	if (file == out2)
-		flushout(file);
 }
 
 /* Like outstr(), but quote for re-input into the shell. */
@@ -255,7 +253,7 @@
 }
 
 void
-dprintf(const char *fmt, ...)
+out2fmt_flush(const char *fmt, ...)
 {
 	va_list ap;
 
@@ -316,7 +314,7 @@
  */
 
 int
-xwrite(int fd, char *buf, int nbytes)
+xwrite(int fd, const char *buf, int nbytes)
 {
 	int ntry;
 	int i;
Index: bin/sh/parser.h
===================================================================
--- bin/sh/parser.h	(revision 209229)
+++ bin/sh/parser.h	(working copy)
@@ -73,9 +73,10 @@
 extern int tokpushback;
 #define NEOF ((union node *)&tokpushback)
 extern int whichprompt;		/* 1 == PS1, 2 == PS2 */
+extern const char *const parsekwd[];
 
 
 union node *parsecmd(int);
 void fixredir(union node *, const char *, int);
-int goodname(char *);
+int goodname(const char *);
 char *getprompt(void *);
Index: bin/sh/output.h
===================================================================
--- bin/sh/output.h	(revision 209229)
+++ bin/sh/output.h	(working copy)
@@ -46,13 +46,13 @@
 	short flags;
 };
 
-extern struct output output;
-extern struct output errout;
+extern struct output output; /* to fd 1 */
+extern struct output errout; /* to fd 2 */
 extern struct output memout;
-extern struct output *out1;
-extern struct output *out2;
+extern struct output *out1; /* &memout if backquote, otherwise &output */
+extern struct output *out2; /* &memout if backquote with 2>&1, otherwise
+			       &errout */
 
-void open_mem(char *, int, struct output *);
 void out1str(const char *);
 void out1qstr(const char *);
 void out2str(const char *);
@@ -65,10 +65,10 @@
 void freestdout(void);
 void outfmt(struct output *, const char *, ...) __printflike(2, 3);
 void out1fmt(const char *, ...) __printflike(1, 2);
-void dprintf(const char *, ...) __printflike(1, 2);
+void out2fmt_flush(const char *, ...) __printflike(1, 2);
 void fmtstr(char *, int, const char *, ...) __printflike(3, 4);
 void doformat(struct output *, const char *, va_list) __printflike(2, 0);
-int xwrite(int, char *, int);
+int xwrite(int, const char *, int);
 
 #define outc(c, file)	(--(file)->nleft < 0? (emptyoutbuf(file), *(file)->nextc++ = (c)) : (*(file)->nextc++ = (c)))
 #define out1c(c)	outc(c, out1);
Index: bin/sh/mystring.c
===================================================================
--- bin/sh/mystring.c	(revision 209229)
+++ bin/sh/mystring.c	(working copy)
@@ -108,7 +108,7 @@
 number(const char *s)
 {
 	if (! is_number(s))
-		error("Illegal number: %s", (char *)s);
+		error("Illegal number: %s", s);
 	return atoi(s);
 }
 
Index: bin/sh/histedit.c
===================================================================
--- bin/sh/histedit.c	(revision 209229)
+++ bin/sh/histedit.c	(working copy)
@@ -92,12 +92,14 @@
 			if (hist != NULL)
 				sethistsize(histsizeval());
 			else
-				out2str("sh: can't initialize history\n");
+				out2fmt_flush("sh: can't initialize history\n");
 		}
 		if (editing && !el && isatty(0)) { /* && isatty(2) ??? */
 			/*
 			 * turn editing on
 			 */
+			char *term;
+
 			INTOFF;
 			if (el_in == NULL)
 				el_in = fdopen(0, "r");
@@ -107,14 +109,22 @@
 				el_out = fdopen(2, "w");
 			if (el_in == NULL || el_err == NULL || el_out == NULL)
 				goto bad;
+			term = lookupvar("TERM");
+			if (term)
+				setenv("TERM", term, 1);
+			else
+				unsetenv("TERM");
 			el = el_init(arg0, el_in, el_out, el_err);
 			if (el != NULL) {
 				if (hist)
 					el_set(el, EL_HIST, history, hist);
 				el_set(el, EL_PROMPT, getprompt);
+				el_set(el, EL_ADDFN, "sh-complete",
+				    "Filename completion",
+				    _el_fn_sh_complete);
 			} else {
 bad:
-				out2str("sh: can't initialize editing\n");
+				out2fmt_flush("sh: can't initialize editing\n");
 			}
 			INTON;
 		} else if (!editing && el) {
@@ -128,6 +138,7 @@
 				el_set(el, EL_EDITOR, "vi");
 			else if (Eflag)
 				el_set(el, EL_EDITOR, "emacs");
+			el_set(el, EL_BIND, "^I", "sh-complete", NULL);
 			el_source(el, NULL);
 		}
 	} else {
@@ -160,23 +171,30 @@
 	}
 }
 
+void
+setterm(const char *term)
+{
+	if (rootshell && el != NULL && term != NULL)
+		el_set(el, EL_TERMINAL, term);
+}
+
 int
 histcmd(int argc, char **argv)
 {
 	int ch;
-	char *editor = NULL;
+	const char *editor = NULL;
 	HistEvent he;
 	int lflg = 0, nflg = 0, rflg = 0, sflg = 0;
 	int i, retval;
-	char *firststr, *laststr;
+	const char *firststr, *laststr;
 	int first, last, direction;
-	char *pat = NULL, *repl;
+	char *pat = NULL, *repl = NULL;
 	static int active = 0;
 	struct jmploc jmploc;
 	struct jmploc *savehandler;
 	char editfilestr[PATH_MAX];
 	char *volatile editfile;
-	FILE *efp;
+	FILE *efp = NULL;
 	int oldhistnum;
 
 	if (hist == NULL)
@@ -336,6 +354,7 @@
 			if (sflg) {
 				if (displayhist) {
 					out2str(s);
+					flushout(out2);
 				}
 				evalstring(s, 0);
 				if (displayhist && hist) {
@@ -405,7 +424,7 @@
 }
 
 int
-not_fcnumber(char *s)
+not_fcnumber(const char *s)
 {
 	if (s == NULL)
 		return (0);
@@ -415,10 +434,10 @@
 }
 
 int
-str_to_event(char *str, int last)
+str_to_event(const char *str, int last)
 {
 	HistEvent he;
-	char *s = str;
+	const char *s = str;
 	int relative = 0;
 	int i, retval;
 
Index: bin/sh/show.c
===================================================================
--- bin/sh/show.c	(revision 209229)
+++ bin/sh/show.c	(working copy)
@@ -307,7 +307,7 @@
 
 
 void
-trputs(char *s)
+trputs(const char *s)
 {
 	if (tracefile == NULL)
 		return;
Index: bin/sh/show.h
===================================================================
--- bin/sh/show.h	(revision 209229)
+++ bin/sh/show.h	(working copy)
@@ -35,6 +35,6 @@
 void sh_trace(const char *, ...) __printflike(1, 2);
 void trargs(char **);
 void trputc(int);
-void trputs(char *);
+void trputs(const char *);
 void opentrace(void);
 #endif
Index: bin/sh/arith_lex.l
===================================================================
--- bin/sh/arith_lex.l	(revision 209229)
+++ bin/sh/arith_lex.l	(working copy)
@@ -51,13 +51,6 @@
 
 int yylex(void);
 
-struct varname
-{
-	struct varname *next;
-	char name[1];
-};
-static struct varname *varnames;
-
 #undef YY_INPUT
 #define YY_INPUT(buf,result,max) \
 	result = (*buf = *arith_buf++) ? 1 : YY_NULL;
@@ -87,14 +80,11 @@
 			 * If variable doesn't exist, we should initialize
 			 * it to zero.
 			 */
-			struct varname *temp;
+			char *temp;
 			if (lookupvar(yytext) == NULL)
 				setvarsafe(yytext, "0", 0);
-			temp = ckmalloc(sizeof(struct varname) +
-			    strlen(yytext));
-			temp->next = varnames;
-			varnames = temp;
-			yylval.s_value = strcpy(temp->name, yytext);
+			temp = stalloc(strlen(yytext) + 1);
+			yylval.s_value = strcpy(temp, yytext);
 
 			return ARITH_VAR;
 		}
@@ -140,15 +130,5 @@
 void
 arith_lex_reset(void)
 {
-	struct varname *name, *next;
-
 	YY_NEW_FILE;
-
-	name = varnames;
-	while (name != NULL) {
-		next = name->next;
-		ckfree(name);
-		name = next;
-	}
-	varnames = NULL;
 }
Index: bin/sh/myhistedit.h
===================================================================
--- bin/sh/myhistedit.h	(revision 209229)
+++ bin/sh/myhistedit.h	(working copy)
@@ -38,8 +38,9 @@
 
 void histedit(void);
 void sethistsize(const char *);
+void setterm(const char *);
 int histcmd(int, char **);
-int not_fcnumber(char *);
-int str_to_event(char *, int);
+int not_fcnumber(const char *);
+int str_to_event(const char *, int);
 int bindcmd(int, char **);
 
Index: bin/sh/sh.1
===================================================================
--- bin/sh/sh.1	(revision 209229)
+++ bin/sh/sh.1	(working copy)
@@ -32,7 +32,7 @@
 .\"	from: @(#)sh.1	8.6 (Berkeley) 5/4/95
 .\" $FreeBSD$
 .\"
-.Dd January 8, 2010
+.Dd May 24, 2010
 .Dt SH 1
 .Os
 .Sh NAME
@@ -210,7 +210,8 @@
 .Xr emacs 1
 command line editor (disables the
 .Fl V
-option if it has been set).
+option if it has been set;
+set automatically when interactive on terminals).
 .It Fl e Li errexit
 Exit immediately if any untested command fails in non-interactive mode.
 The exit status of a command is considered to be
@@ -296,7 +297,10 @@
 .Ed
 .It Fl u Li nounset
 Write a message to standard error when attempting
-to expand a variable that is not set, and if the
+to expand a variable, a positional parameter or
+the special parameter
+.Va \&!
+that is not set, and if the
 shell is not interactive, exit immediately.
 .It Fl V Li vi
 Enable the built-in
@@ -411,11 +415,11 @@
 .Pq Ql \en .
 A backslash preceding a newline is treated as a line continuation.
 .El
-.Ss Reserved Words
-Reserved words are words that have special meaning to the
+.Ss Keywords
+Keywords or reserved words are words that have special meaning to the
 shell and are recognized at the beginning of a line and
 after a control operator.
-The following are reserved words:
+The following are keywords:
 .Bl -column "doneXX" "elifXX" "elseXX" "untilXX" "whileX" -offset center
 .It Li \&! Ta { Ta } Ta Ic case Ta Ic do
 .It Ic done Ta Ic elif Ta Ic else Ta Ic esac Ta Ic fi
@@ -425,8 +429,8 @@
 An alias is a name and corresponding value set using the
 .Ic alias
 built-in command.
-Whenever a reserved word may occur (see above),
-and after checking for reserved words, the shell
+Whenever a keyword may occur (see above),
+and after checking for keywords, the shell
 checks the word to see if it matches an alias.
 If it does, it replaces it in the input stream with its value.
 For example, if there is an alias called
@@ -465,7 +469,7 @@
 document).
 Essentially though, a line is read and if
 the first word of the line (or after a control operator)
-is not a reserved word, then the shell has recognized a
+is not a keyword, then the shell has recognized a
 simple command.
 Otherwise, a complex command or some
 other special construct may have been recognized.
@@ -661,11 +665,11 @@
 performing any searches.
 .It
 The shell searches each entry in the
-.Ev PATH
-environment variable
+.Va PATH
+variable
 in turn for the command.
 The value of the
-.Ev PATH
+.Va PATH
 variable should be a series of
 entries separated by colons.
 Each entry consists of a
@@ -691,7 +695,7 @@
 .In sys/signal.h .
 .Ss Complex Commands
 Complex commands are combinations of simple commands
-with control operators or reserved words, together creating a larger complex
+with control operators or keywords, together creating a larger complex
 command.
 More generally, a command is one of the following:
 .Bl -item -offset indent
@@ -735,7 +739,7 @@
 If the pipeline is not in the background (discussed later),
 the shell waits for all commands to complete.
 .Pp
-If the reserved word
+If the keyword
 .Ic !\&
 does not precede the pipeline, the
 exit status is the exit status of the last command specified
@@ -1036,13 +1040,9 @@
 .Ic set
 built-in command can also be used to set or reset them.
 .Ss Special Parameters
-A special parameter is a parameter denoted by a special one-character
-name.
-The special parameters recognized by the
-.Nm
-shell of
-.Fx
-are shown in the following list, exactly as they would appear in input
+Special parameters are parameters denoted by a single special character
+or the digit zero.
+They are shown in the following list, exactly as they would appear in input
 typed by the user or in the source of a shell script.
 .Bl -hang
 .It Li $*
@@ -1109,6 +1109,84 @@
 .It Li $0
 (zero) Expands to the name of the shell or shell script.
 .El
+.Ss Special Variables
+The following variables are set by the shell or
+have special meaning to it:
+.Bl -tag -width ".Va HISTSIZE"
+.It Va CDPATH
+The search path used with the
+.Ic cd
+built-in.
+.It Va EDITOR
+The fallback editor used with the
+.Ic fc
+built-in.
+If not set, the default editor is
+.Xr ed 1 .
+.It Va FCEDIT
+The default editor used with the
+.Ic fc
+built-in.
+.It Va HISTSIZE
+The number of previous commands that are accessible.
+.It Va HOME
+The user's home directory,
+used in tilde expansion and as a default directory for the
+.Ic cd
+built-in.
+.It Va IFS
+Input Field Separators.
+This is normally set to
+.Aq space ,
+.Aq tab ,
+and
+.Aq newline .
+See the
+.Sx White Space Splitting
+section for more details.
+.It Va LINENO
+The current line number in the script or function.
+.It Va MAIL
+The name of a mail file, that will be checked for the arrival of new
+mail.
+Overridden by
+.Va MAILPATH .
+.It Va MAILPATH
+A colon
+.Pq Ql \&:
+separated list of file names, for the shell to check for incoming
+mail.
+This variable overrides the
+.Va MAIL
+setting.
+There is a maximum of 10 mailboxes that can be monitored at once.
+.It Va PATH
+The default search path for executables.
+See the
+.Sx Path Search
+section for details.
+.It Va PPID
+The parent process ID of the invoked shell.
+This is set at startup
+unless this variable is in the environment.
+A later change of parent process ID is not reflected.
+A subshell retains the same value of
+.Va PPID .
+.It Va PS1
+The primary prompt string, which defaults to
+.Dq Li "$ " ,
+unless you are the superuser, in which case it defaults to
+.Dq Li "# " .
+.It Va PS2
+The secondary prompt string, which defaults to
+.Dq Li "> " .
+.It Va PS4
+The prefix for the trace output (if
+.Fl x
+is active).
+The default is
+.Dq Li "+ " .
+.El
 .Ss Word Expansions
 This clause describes the various expansions that are
 performed on words.
@@ -1162,7 +1240,7 @@
 username is missing (as in
 .Pa ~/foobar ) ,
 the tilde is replaced with the value of the
-.Ev HOME
+.Va HOME
 variable (the current user's home directory).
 .Ss Parameter Expansion
 The format for parameter expansion is as follows:
@@ -1175,10 +1253,20 @@
 .Ql } .
 Any
 .Ql }
-escaped by a backslash or within a quoted string, and characters in
+escaped by a backslash or within a single-quoted string, and characters in
 embedded arithmetic expansions, command substitutions, and variable
 expansions, are not examined in determining the matching
 .Ql } .
+Except for the variants with
+.Ql + ,
+.Ql - ,
+.Ql =
+or
+.Ql ?\& ,
+any
+.Ql }
+within a double-quoted string is also not examined in determining the matching
+.Ql } .
 .Pp
 The simplest form for parameter expansion is:
 .Pp
@@ -1262,6 +1350,14 @@
 In the parameter expansions shown previously, use of the colon in the
 format results in a test for a parameter that is unset or null; omission
 of the colon results in a test for a parameter that is only unset.
+.Pp
+The
+.Ar word
+inherits the type of quoting
+(unquoted, double-quoted or here-document)
+from the surroundings,
+with the exception that a backslash that quotes a closing brace is removed
+during quote removal.
 .Bl -tag -width indent
 .It Li ${# Ns Ar parameter Ns Li }
 String Length.
@@ -1469,10 +1565,10 @@
 .Ql /
 characters, it is used as is.
 Otherwise, the shell searches the
-.Ev PATH
+.Va PATH
 for the file.
 If it is not found in the
-.Ev PATH ,
+.Va PATH ,
 it is sought in the current working directory.
 .It Ic \&[
 A built-in equivalent of
@@ -1516,7 +1612,7 @@
 Switch to the specified
 .Ar directory ,
 or to the directory specified in the
-.Ev HOME
+.Va HOME
 environment variable if no
 .Ar directory
 is specified.
@@ -1527,17 +1623,17 @@
 or
 .Pa .. ,
 then the directories listed in the
-.Ev CDPATH
+.Va CDPATH
 variable will be
 searched for the specified
 .Ar directory .
 If
-.Ev CDPATH
+.Va CDPATH
 is unset, the current directory is searched.
 The format of
-.Ev CDPATH
+.Va CDPATH
 is the same as that of
-.Ev PATH .
+.Va PATH .
 In an interactive shell,
 the
 .Ic cd
@@ -1545,7 +1641,7 @@
 that it actually switched to
 if this is different from the name that the user gave.
 These may be different either because the
-.Ev CDPATH
+.Va CDPATH
 mechanism was used or because a symbolic link was crossed.
 .Pp
 If the
@@ -1568,16 +1664,18 @@
 .It Ic command Oo Fl p Oc Op Ar utility Op Ar argument ...
 .It Ic command Oo Fl v | V Oc Op Ar utility
 The first form of invocation executes the specified
+.Ar utility ,
+ignoring shell functions in the search.
+If
 .Ar utility
-as a simple command (see the
-.Sx Simple Commands
-section).
+is a special builtin,
+it is executed as if it were a regular builtin.
 .Pp
 If the
 .Fl p
 option is specified, the command search is performed using a
 default value of
-.Ev PATH
+.Va PATH
 that is guaranteed to find all of the standard utilities.
 .Pp
 If the
@@ -1736,20 +1834,20 @@
 .Ar editor
 string is a command name,
 subject to search via the
-.Ev PATH
+.Va PATH
 variable.
 The value in the
-.Ev FCEDIT
+.Va FCEDIT
 variable is used as a default when
 .Fl e
 is not specified.
 If
-.Ev FCEDIT
+.Va FCEDIT
 is null or unset, the value of the
-.Ev EDITOR
+.Va EDITOR
 variable is used.
 If
-.Ev EDITOR
+.Va EDITOR
 is null or unset,
 .Xr ed 1
 is used as the editor.
@@ -1783,7 +1881,7 @@
 Select the commands to list or edit.
 The number of previous commands that can be accessed
 are determined by the value of the
-.Ev HISTSIZE
+.Va HISTSIZE
 variable.
 The value of
 .Ar first
@@ -1814,12 +1912,12 @@
 .El
 .El
 .Pp
-The following environment variables affect the execution of
+The following variables affect the execution of
 .Ic fc :
-.Bl -tag -width ".Ev HISTSIZE"
-.It Ev FCEDIT
+.Bl -tag -width ".Va HISTSIZE"
+.It Va FCEDIT
 Name of the editor to use for history editing.
-.It Ev HISTSIZE
+.It Va HISTSIZE
 The number of previous commands that are accessible.
 .El
 .It Ic fg Op Ar job
@@ -2285,74 +2383,27 @@
 .Sh ENVIRONMENT
 The following environment variables affect the execution of
 .Nm :
-.Bl -tag -width ".Ev HISTSIZE"
-.It Ev CDPATH
-The search path used with the
-.Ic cd
-built-in.
-.It Ev EDITOR
-The fallback editor used with the
-.Ic fc
-built-in.
-If not set, the default editor is
-.Xr ed 1 .
-.It Ev FCEDIT
-The default editor used with the
-.Ic fc
-built-in.
-.It Ev HISTSIZE
-The number of previous commands that are accessible.
-.It Ev HOME
-The starting directory of
-.Nm .
-.It Ev IFS
-Input Field Separators.
-This is normally set to
-.Aq space ,
-.Aq tab ,
-and
-.Aq newline .
-See the
-.Sx White Space Splitting
-section for more details.
-.It Ev MAIL
-The name of a mail file, that will be checked for the arrival of new
-mail.
-Overridden by
-.Ev MAILPATH .
-.It Ev MAILPATH
-A colon
-.Pq Ql \&:
-separated list of file names, for the shell to check for incoming
-mail.
-This environment setting overrides the
-.Ev MAIL
-setting.
-There is a maximum of 10 mailboxes that can be monitored at once.
-.It Ev PATH
-The default search path for executables.
-See the
-.Sx Path Search
-section for details.
-.It Ev PS1
-The primary prompt string, which defaults to
-.Dq Li "$ " ,
-unless you are the superuser, in which case it defaults to
-.Dq Li "# " .
-.It Ev PS2
-The secondary prompt string, which defaults to
-.Dq Li "> " .
-.It Ev PS4
-The prefix for the trace output (if
-.Fl x
-is active).
-The default is
-.Dq Li "+ " .
+.Bl -tag -width ".Ev LANGXXXXXX"
+.It Ev ENV
+Initialization file for interactive shells.
+.It Ev LANG , Ev LC_*
+Locale settings.
+These are inherited by children of the shell,
+and is used in a limited manner by the shell itself.
+.It Ev PWD
+An absolute pathname for the current directory,
+possibly containing symbolic links.
+This is used and updated by the shell.
 .It Ev TERM
 The default terminal setting for the shell.
 This is inherited by children of the shell, and is used in the history
 editing modes.
 .El
+.Pp
+Additionally, all environment variables are turned into shell variables
+at startup,
+which may affect the shell as described under
+.Sx Special Variables .
 .Sh EXIT STATUS
 Errors that are detected by the shell, such as a syntax error, will
 cause the shell to exit with a non-zero exit status.
Index: bin/sh/error.c
===================================================================
--- bin/sh/error.c	(revision 209229)
+++ bin/sh/error.c	(working copy)
@@ -67,17 +67,21 @@
 char *commandname;
 
 
-static void exverror(int, const char *, va_list) __printf0like(2, 0);
+static void exverror(int, const char *, va_list) __printf0like(2, 0) __dead2;
 
 /*
  * Called to raise an exception.  Since C doesn't include exceptions, we
  * just do a longjmp to the exception handler.  The type of exception is
  * stored in the global variable "exception".
+ *
+ * Interrupts are disabled; they should be reenabled when the exception is
+ * caught.
  */
 
 void
 exraise(int e)
 {
+	INTOFF;
 	if (handler == NULL)
 		abort();
 	exception = e;
@@ -138,8 +142,15 @@
 static void
 exverror(int cond, const char *msg, va_list ap)
 {
-	CLEAR_PENDING_INT;
-	INTOFF;
+	/*
+	 * An interrupt trumps an error.  Certain places catch error
+	 * exceptions or transform them to a plain nonzero exit code
+	 * in child processes, and if an error exception can be handled,
+	 * an interrupt can be handled as well.
+	 *
+	 * exraise() will disable interrupts for the exception handler.
+	 */
+	FORCEINTON;
 
 #ifdef DEBUG
 	if (msg)
@@ -149,8 +160,8 @@
 #endif
 	if (msg) {
 		if (commandname)
-			outfmt(&errout, "%s: ", commandname);
-		doformat(&errout, msg, ap);
+			outfmt(out2, "%s: ", commandname);
+		doformat(out2, msg, ap);
 		out2c('\n');
 	}
 	flushall();
Index: bin/sh/error.h
===================================================================
--- bin/sh/error.h	(revision 209229)
+++ bin/sh/error.h	(working copy)
@@ -72,14 +72,16 @@
 
 #define INTOFF suppressint++
 #define INTON { if (--suppressint == 0 && intpending) onint(); }
+#define is_int_on() suppressint
+#define SETINTON(s) suppressint = (s)
 #define FORCEINTON {suppressint = 0; if (intpending) onint();}
 #define CLEAR_PENDING_INT intpending = 0
 #define int_pending() intpending
 
-void exraise(int);
+void exraise(int) __dead2;
 void onint(void);
-void error(const char *, ...) __printf0like(1, 2);
-void exerror(int, const char *, ...) __printf0like(2, 3);
+void error(const char *, ...) __printf0like(1, 2) __dead2;
+void exerror(int, const char *, ...) __printf0like(2, 3) __dead2;
 
 
 /*
Index: bin/sh/input.c
===================================================================
--- bin/sh/input.c	(revision 209229)
+++ bin/sh/input.c	(working copy)
@@ -93,7 +93,7 @@
 
 
 int plinno = 1;			/* input line number */
-MKINIT int parsenleft;		/* copy of parsefile->nleft */
+int parsenleft;			/* copy of parsefile->nleft */
 MKINIT int parselleft;		/* copy of parsefile->lleft */
 char *parsenextc;		/* copy of parsefile->nextc */
 MKINIT struct parsefile basepf;	/* top level input file */
@@ -111,9 +111,9 @@
 INCLUDE "input.h"
 INCLUDE "error.h"
 
+MKINIT char basebuf[];
+
 INIT {
-	extern char basebuf[];
-
 	basepf.nextc = basepf.buf = basebuf;
 }
 
@@ -215,7 +215,7 @@
                                 if (flags >= 0 && flags & O_NONBLOCK) {
                                         flags &=~ O_NONBLOCK;
                                         if (fcntl(0, F_SETFL, flags) >= 0) {
-						out2str("sh: turning off NDELAY mode\n");
+						out2fmt_flush("sh: turning off NDELAY mode\n");
                                                 goto retry;
                                         }
                                 }
@@ -359,7 +359,7 @@
 	struct strpush *sp;
 
 	INTOFF;
-/*dprintf("*** calling pushstring: %s, %d\n", s, len);*/
+/*out2fmt_flush("*** calling pushstring: %s, %d\n", s, len);*/
 	if (parsefile->strpush) {
 		sp = ckmalloc(sizeof (struct strpush));
 		sp->prev = parsefile->strpush;
@@ -386,7 +386,7 @@
 	parsenextc = sp->prevstring;
 	parsenleft = sp->prevnleft;
 	parselleft = sp->prevlleft;
-/*dprintf("*** calling popstring: restoring to '%s'\n", parsenextc);*/
+/*out2fmt_flush("*** calling popstring: restoring to '%s'\n", parsenextc);*/
 	if (sp->ap)
 		sp->ap->flag &= ~ALIASINUSE;
 	parsefile->strpush = sp->prev;
@@ -401,7 +401,7 @@
  */
 
 void
-setinputfile(char *fname, int push)
+setinputfile(const char *fname, int push)
 {
 	int fd;
 	int fd2;
@@ -509,6 +509,32 @@
 
 
 /*
+ * Return current file (to go back to it later using popfilesupto()).
+ */
+
+struct parsefile *
+getcurrentfile(void)
+{
+	return parsefile;
+}
+
+
+/*
+ * Pop files until the given file is on top again. Useful for regular
+ * builtins that read shell commands from files or strings.
+ * If the given file is not an active file, an error is raised.
+ */
+
+void
+popfilesupto(struct parsefile *file)
+{
+	while (parsefile != file && parsefile != &basepf)
+		popfile();
+	if (parsefile != file)
+		error("popfilesupto() misused");
+}
+
+/*
  * Return to top level.
  */
 
Index: bin/sh/input.h
===================================================================
--- bin/sh/input.h	(revision 209229)
+++ bin/sh/input.h	(working copy)
@@ -45,6 +45,8 @@
 extern char *parsenextc;	/* next character in input buffer */
 extern int init_editline;	/* 0 == not setup, 1 == OK, -1 == failed */
 
+struct parsefile;
+
 char *pfgets(char *, int);
 int pgetc(void);
 int preadbuffer(void);
@@ -52,10 +54,12 @@
 void pungetc(void);
 void pushstring(char *, int, void *);
 void popstring(void);
-void setinputfile(char *, int);
+void setinputfile(const char *, int);
 void setinputfd(int, int);
 void setinputstring(char *, int);
 void popfile(void);
+struct parsefile *getcurrentfile(void);
+void popfilesupto(struct parsefile *);
 void popallfiles(void);
 void closescript(void);
 
Index: bin/sh/redir.c
===================================================================
--- bin/sh/redir.c	(revision 209229)
+++ bin/sh/redir.c	(working copy)
@@ -63,6 +63,7 @@
 
 
 #define EMPTY -2		/* marks an unused slot in redirtab */
+#define CLOSED -1		/* fd was not open before redir */
 #define PIPESIZE 4096		/* amount of buffering in a pipe */
 
 
@@ -101,7 +102,6 @@
 	struct redirtab *sv = NULL;
 	int i;
 	int fd;
-	int try;
 	char memory[10];	/* file descriptors to write to memory */
 
 	for (i = 10 ; --i >= 0 ; )
@@ -116,38 +116,30 @@
 	}
 	for (n = redir ; n ; n = n->nfile.next) {
 		fd = n->nfile.fd;
-		try = 0;
 		if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) &&
 		    n->ndup.dupfd == fd)
 			continue; /* redirect from/to same file descriptor */
 
 		if ((flags & REDIR_PUSH) && sv->renamed[fd] == EMPTY) {
 			INTOFF;
-again:
 			if ((i = fcntl(fd, F_DUPFD, 10)) == -1) {
 				switch (errno) {
 				case EBADF:
-					if (!try) {
-						openredirect(n, memory);
-						try++;
-						goto again;
-					}
-					/* FALLTHROUGH*/
+					i = CLOSED;
+					break;
 				default:
 					INTON;
 					error("%d: %s", fd, strerror(errno));
 					break;
 				}
-			}
-			if (!try) {
-				sv->renamed[fd] = i;
-			}
+			} else
+				(void)fcntl(i, F_SETFD, FD_CLOEXEC);
+			sv->renamed[fd] = i;
 			INTON;
 		}
 		if (fd == 0)
 			fd0_redirected++;
-		if (!try)
-			openredirect(n, memory);
+		openredirect(n, memory);
 	}
 	if (memory[1])
 		out1 = &memout;
@@ -166,8 +158,11 @@
 
 	/*
 	 * We suppress interrupts so that we won't leave open file
-	 * descriptors around.  This may not be such a good idea because
-	 * an open of a device or a fifo can block indefinitely.
+	 * descriptors around.  Because the signal handler remains
+	 * installed and we do not use system call restart, interrupts
+	 * will still abort blocking opens such as fifos (they will fail
+	 * with EINTR). There is, however, a race condition if an interrupt
+	 * arrives after INTOFF and before open blocks.
 	 */
 	INTOFF;
 	memory[fd] = 0;
Index: bin/sh/trap.c
===================================================================
--- bin/sh/trap.c	(revision 209229)
+++ bin/sh/trap.c	(working copy)
@@ -149,6 +149,7 @@
 {
 	char *action;
 	int signo;
+	int errors = 0;
 
 	if (argc <= 1) {
 		for (signo = 0 ; signo < sys_nsig ; signo++) {
@@ -183,8 +184,10 @@
 		}
 	}
 	while (*argv) {
-		if ((signo = sigstring_to_signum(*argv)) == -1)
-			error("bad signal %s", *argv);
+		if ((signo = sigstring_to_signum(*argv)) == -1) {
+			out2fmt_flush("trap: bad signal %s\n", *argv);
+			errors = 1;
+		}
 		INTOFF;
 		if (action)
 			action = savestr(action);
@@ -196,7 +199,7 @@
 		INTON;
 		argv++;
 	}
-	return 0;
+	return errors;
 }
 
 
@@ -244,7 +247,8 @@
 setsignal(int signo)
 {
 	int action;
-	sig_t sig, sigact = SIG_DFL;
+	sig_t sigact = SIG_DFL;
+	struct sigaction sa;
 	char *t;
 
 	if ((t = trap[signo]) == NULL)
@@ -320,9 +324,10 @@
 		case S_IGN:	sigact = SIG_IGN;	break;
 	}
 	*t = action;
-	sig = signal(signo, sigact);
-	if (sig != SIG_ERR && action == S_CATCH)
-		siginterrupt(signo, 1);
+	sa.sa_handler = sigact;
+	sa.sa_flags = 0;
+	sigemptyset(&sa.sa_mask);
+	sigaction(signo, &sa, NULL);
 }
 
 
Index: bin/sh/trap.h
===================================================================
--- bin/sh/trap.h	(revision 209229)
+++ bin/sh/trap.h	(working copy)
@@ -45,4 +45,4 @@
 void onsig(int);
 void dotrap(void);
 void setinteractive(int);
-void exitshell(int);
+void exitshell(int) __dead2;
Index: bin/sh/arith.h
===================================================================
--- bin/sh/arith.h	(revision 209229)
+++ bin/sh/arith.h	(working copy)
@@ -34,8 +34,8 @@
 
 #define DIGITS(var) (3 + (2 + CHAR_BIT * sizeof((var))) / 3)
 
-extern char *arith_buf, *arith_startbuf;
+extern const char *arith_buf, *arith_startbuf;
 
-arith_t arith(char *);
+arith_t arith(const char *);
 void arith_lex_reset(void);
 int expcmd(int, char **);
Index: bin/sh/mksyntax.c
===================================================================
--- bin/sh/mksyntax.c	(revision 209229)
+++ bin/sh/mksyntax.c	(working copy)
@@ -55,8 +55,8 @@
 
 
 struct synclass {
-	char *name;
-	char *comment;
+	const char *name;
+	const char *comment;
 };
 
 /* Syntax classes */
@@ -101,16 +101,16 @@
 
 static FILE *cfile;
 static FILE *hfile;
-static char *syntax[513];
+static const char *syntax[513];
 static int base;
 static int size;	/* number of values which a char variable can have */
 static int nbits;	/* number of bits in a character */
 static int digit_contig;/* true if digits are contiguous */
 
-static void filltable(char *);
+static void filltable(const char *);
 static void init(void);
-static void add(char *, char *);
-static void print(char *);
+static void add(const char *, const char *);
+static void print(const char *);
 static void output_type_macros(void);
 static void digit_convert(void);
 
@@ -232,7 +232,6 @@
 	add("\n", "CNL");
 	add("\\", "CBACK");
 	add("`", "CBQUOTE");
-	add("'", "CSQUOTE");
 	add("\"", "CDQUOTE");
 	add("$", "CVAR");
 	add("}", "CENDVAR");
@@ -259,7 +258,7 @@
  */
 
 static void
-filltable(char *dftval)
+filltable(const char *dftval)
 {
 	int i;
 
@@ -293,7 +292,7 @@
  */
 
 static void
-add(char *p, char *type)
+add(const char *p, const char *type)
 {
 	while (*p)
 		syntax[*p++ + base] = type;
@@ -306,7 +305,7 @@
  */
 
 static void
-print(char *name)
+print(const char *name)
 {
 	int i;
 	int col;
@@ -338,7 +337,7 @@
  * contiguous, we can test for them quickly.
  */
 
-static char *macro[] = {
+static const char *macro[] = {
 	"#define is_digit(c)\t((is_type+SYNBASE)[c] & ISDIGIT)",
 	"#define is_eof(c)\t((c) == PEOF)",
 	"#define is_alpha(c)\t(((c) < CTLESC || (c) > CTLQUOTEMARK) && isalpha((unsigned char) (c)))",
@@ -351,7 +350,7 @@
 static void
 output_type_macros(void)
 {
-	char **pp;
+	const char **pp;
 
 	if (digit_contig)
 		macro[0] = "#define is_digit(c)\t((unsigned int)((c) - '0') <= 9)";
Index: bin/sh/expand.c
===================================================================
--- bin/sh/expand.c	(revision 209229)
+++ bin/sh/expand.c	(working copy)
@@ -109,7 +109,7 @@
 STATIC void addfname(char *);
 STATIC struct strlist *expsort(struct strlist *);
 STATIC struct strlist *msort(struct strlist *, int);
-STATIC int pmatch(char *, char *, int);
+STATIC int pmatch(const char *, const char *, int);
 STATIC char *cvtnum(int, char *);
 STATIC int collate_range_cmp(int, int);
 
@@ -273,8 +273,12 @@
 
 	while ((c = *p) != '\0') {
 		switch(c) {
-		case CTLESC:
-			return (startp);
+		case CTLESC: /* This means CTL* are always considered quoted. */
+		case CTLVAR:
+		case CTLBACKQ:
+		case CTLBACKQ | CTLQUOTE:
+		case CTLARI:
+		case CTLENDARI:
 		case CTLQUOTEMARK:
 			return (startp);
 		case ':':
@@ -282,6 +286,7 @@
 				goto done;
 			break;
 		case '/':
+		case CTLENDVAR:
 			goto done;
 		}
 		p++;
@@ -357,7 +362,7 @@
 void
 expari(int flag)
 {
-	char *p, *start;
+	char *p, *q, *start;
 	arith_t result;
 	int begoff;
 	int quotes = flag & (EXP_FULL | EXP_CASE | EXP_REDIR);
@@ -395,7 +400,9 @@
 	removerecordregions(begoff);
 	if (quotes)
 		rmescapes(p+2);
+	q = grabstackstr(expdest);
 	result = arith(p+2);
+	ungrabstackstr(q, expdest);
 	fmtstr(p, DIGITS(result), ARITH_FORMAT_STR, result);
 	while (*p++)
 		;
@@ -503,7 +510,9 @@
 	int amount;
 
 	herefd = -1;
-	argstr(p, 0);
+	argstr(p, (subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX ||
+	    subtype == VSTRIMRIGHT || subtype == VSTRIMRIGHTMAX ?
+	    EXP_CASE : 0) | EXP_TILDE);
 	STACKSTRNUL(expdest);
 	herefd = saveherefd;
 	argbackq = saveargbackq;
@@ -523,7 +532,7 @@
 
 	case VSQUESTION:
 		if (*p != CTLENDVAR) {
-			outfmt(&errout, "%s\n", startp);
+			outfmt(out2, "%s\n", startp);
 			error((char *)NULL);
 		}
 		error("%.*s: parameter %snot set", (int)(p - str - 1),
@@ -659,7 +668,7 @@
 	}
 	varlen = 0;
 	startloc = expdest - stackblock();
-	if (!set && uflag) {
+	if (!set && uflag && *var != '@' && *var != '*') {
 		switch (subtype) {
 		case VSNORMAL:
 		case VSTRIMLEFT:
@@ -852,7 +861,6 @@
 	int num;
 	char *p;
 	int i;
-	extern int oexitstatus;
 	char sep;
 	char **ap;
 	char const *syntax;
@@ -976,7 +984,7 @@
 	char *start;
 	char *p;
 	char *q;
-	char *ifs;
+	const char *ifs;
 	const char *ifsspc;
 	int had_param_ch = 0;
 
@@ -1344,7 +1352,7 @@
  */
 
 int
-patmatch(char *pattern, char *string, int squoted)
+patmatch(const char *pattern, const char *string, int squoted)
 {
 #ifdef notdef
 	if (pattern[0] == '!' && pattern[1] == '!')
@@ -1356,9 +1364,9 @@
 
 
 STATIC int
-pmatch(char *pattern, char *string, int squoted)
+pmatch(const char *pattern, const char *string, int squoted)
 {
-	char *p, *q;
+	const char *p, *q;
 	char c;
 
 	p = pattern;
@@ -1406,7 +1414,7 @@
 			} while (*q++ != '\0');
 			return 0;
 		case '[': {
-			char *endp;
+			const char *endp;
 			int invert, found;
 			char chr;
 
@@ -1510,7 +1518,7 @@
  */
 
 int
-casematch(union node *pattern, char *val)
+casematch(union node *pattern, const char *val)
 {
 	struct stackmark smark;
 	int result;
Index: bin/sh/mail.c
===================================================================
--- bin/sh/mail.c	(revision 209229)
+++ bin/sh/mail.c	(working copy)
@@ -72,7 +72,7 @@
 chkmail(int silent)
 {
 	int i;
-	char *mpath;
+	const char *mpath;
 	char *p;
 	char *q;
 	struct stackmark smark;
Index: bin/sh/main.c
===================================================================
--- bin/sh/main.c	(revision 209229)
+++ bin/sh/main.c	(working copy)
@@ -75,8 +75,9 @@
 
 int rootpid;
 int rootshell;
+struct jmploc main_handler;
 
-STATIC void read_profile(char *);
+STATIC void read_profile(const char *);
 STATIC char *find_dot_file(char *);
 
 /*
@@ -90,14 +91,13 @@
 int
 main(int argc, char *argv[])
 {
-	struct jmploc jmploc;
 	struct stackmark smark;
 	volatile int state;
 	char *shinit;
 
 	(void) setlocale(LC_ALL, "");
 	state = 0;
-	if (setjmp(jmploc.loc)) {
+	if (setjmp(main_handler.loc)) {
 		/*
 		 * When a shell procedure is executed, we raise the
 		 * exception EXSHELLPROC to clean up before executing
@@ -143,7 +143,7 @@
 		else
 			goto state4;
 	}
-	handler = &jmploc;
+	handler = &main_handler;
 #ifdef DEBUG
 	opentrace();
 	trputs("Shell args:  ");  trargs(argv);
@@ -153,10 +153,7 @@
 	init();
 	setstackmark(&smark);
 	procargs(argc, argv);
-	if (getpwd() == NULL && iflag)
-		out2str("sh: cannot determine working directory\n");
-	if (getpwd() != NULL)
-		setvar ("PWD", getpwd(), VEXPORT);
+	pwd_init(iflag);
 	if (iflag)
 		chkmail(1);
 	if (argv[0] && argv[0][0] == '-') {
@@ -225,7 +222,7 @@
 			if (!stoppedjobs()) {
 				if (!Iflag)
 					break;
-				out2str("\nUse \"exit\" to leave shell.\n");
+				out2fmt_flush("\nUse \"exit\" to leave shell.\n");
 			}
 			numeof++;
 		} else if (n != NULL && nflag == 0) {
@@ -250,7 +247,7 @@
  */
 
 STATIC void
-read_profile(char *name)
+read_profile(const char *name)
 {
 	int fd;
 
@@ -271,7 +268,7 @@
  */
 
 void
-readcmdfile(char *name)
+readcmdfile(const char *name)
 {
 	int fd;
 
@@ -298,7 +295,7 @@
 {
 	static char localname[FILENAME_MAX+1];
 	char *fullname;
-	char *path = pathval();
+	const char *path = pathval();
 	struct stat statb;
 
 	/* don't try this for absolute or relative paths */
@@ -317,14 +314,20 @@
 int
 dotcmd(int argc, char **argv)
 {
-	char *fullname;
+	char *filename, *fullname;
 
 	if (argc < 2)
 		error("missing filename");
 
 	exitstatus = 0;
 
-	fullname = find_dot_file(argv[1]);
+	/*
+	 * Because we have historically not supported any options,
+	 * only treat "--" specially.
+	 */
+	filename = argc > 2 && strcmp(argv[1], "--") == 0 ? argv[2] : argv[1];
+
+	fullname = find_dot_file(filename);
 	setinputfile(fullname, 1);
 	commandname = fullname;
 	cmdloop(0);
@@ -336,8 +339,6 @@
 int
 exitcmd(int argc, char **argv)
 {
-	extern int oexitstatus;
-
 	if (stoppedjobs())
 		return 0;
 	if (argc > 1)
Index: bin/sh/expand.h
===================================================================
--- bin/sh/expand.h	(revision 209229)
+++ bin/sh/expand.h	(working copy)
@@ -58,7 +58,7 @@
 void expandhere(union node *, int);
 void expandarg(union node *, struct arglist *, int);
 void expari(int);
-int patmatch(char *, char *, int);
+int patmatch(const char *, const char *, int);
 void rmescapes(char *);
-int casematch(union node *, char *);
+int casematch(union node *, const char *);
 int wordexpcmd(int, char **);
Index: bin/sh/exec.c
===================================================================
--- bin/sh/exec.c	(revision 209229)
+++ bin/sh/exec.c	(working copy)
@@ -98,7 +98,7 @@
 
 STATIC void tryexec(char *, char **, char **);
 STATIC void printentry(struct tblentry *, int);
-STATIC struct tblentry *cmdlookup(char *, int);
+STATIC struct tblentry *cmdlookup(const char *, int);
 STATIC void delete_cmd_entry(void);
 
 
@@ -109,7 +109,7 @@
  */
 
 void
-shellexec(char **argv, char **envp, char *path, int index)
+shellexec(char **argv, char **envp, const char *path, int idx)
 {
 	char *cmdname;
 	int e;
@@ -120,7 +120,7 @@
 	} else {
 		e = ENOENT;
 		while ((cmdname = padvance(&path, argv[0])) != NULL) {
-			if (--index < 0 && pathopt == NULL) {
+			if (--idx < 0 && pathopt == NULL) {
 				tryexec(cmdname, argv, envp);
 				if (errno != ENOENT && errno != ENOTDIR)
 					e = errno;
@@ -175,13 +175,13 @@
  * NULL.
  */
 
-char *pathopt;
+const char *pathopt;
 
 char *
-padvance(char **path, char *name)
+padvance(const char **path, const char *name)
 {
-	char *p, *q;
-	char *start;
+	const char *p, *start;
+	char *q;
 	int len;
 
 	if (*path == NULL)
@@ -248,14 +248,14 @@
 		 && (cmdp->cmdtype == CMDNORMAL
 		     || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0)))
 			delete_cmd_entry();
-		find_command(name, &entry, 1, pathval());
+		find_command(name, &entry, DO_ERR, pathval());
 		if (verbose) {
 			if (entry.cmdtype != CMDUNKNOWN) {	/* if no error msg */
 				cmdp = cmdlookup(name, 0);
 				if (cmdp != NULL)
 					printentry(cmdp, verbose);
 				else
-					outfmt(&errout, "%s: not found\n", name);
+					outfmt(out2, "%s: not found\n", name);
 			}
 			flushall();
 		}
@@ -268,17 +268,17 @@
 STATIC void
 printentry(struct tblentry *cmdp, int verbose)
 {
-	int index;
-	char *path;
+	int idx;
+	const char *path;
 	char *name;
 
 	if (cmdp->cmdtype == CMDNORMAL) {
-		index = cmdp->param.index;
+		idx = cmdp->param.index;
 		path = pathval();
 		do {
 			name = padvance(&path, cmdp->cmdname);
 			stunalloc(name);
-		} while (--index >= 0);
+		} while (--idx >= 0);
 		out1str(name);
 	} else if (cmdp->cmdtype == CMDBUILTIN) {
 		out1fmt("builtin %s", cmdp->cmdname);
@@ -310,10 +310,11 @@
  */
 
 void
-find_command(char *name, struct cmdentry *entry, int printerr, char *path)
+find_command(const char *name, struct cmdentry *entry, int act,
+    const char *path)
 {
-	struct tblentry *cmdp;
-	int index;
+	struct tblentry *cmdp, loc_cmd;
+	int idx;
 	int prev;
 	char *fullname;
 	struct stat statb;
@@ -329,13 +330,19 @@
 	}
 
 	/* If name is in the table, and not invalidated by cd, we're done */
-	if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->rehash == 0)
-		goto success;
+	if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->rehash == 0) {
+		if (cmdp->cmdtype == CMDFUNCTION && act & DO_NOFUNC)
+			cmdp = NULL;
+		else
+			goto success;
+	}
 
 	/* If %builtin not in path, check for builtin next */
 	if (builtinloc < 0 && (i = find_builtin(name, &spec)) >= 0) {
 		INTOFF;
 		cmdp = cmdlookup(name, 1);
+		if (cmdp->cmdtype == CMDFUNCTION)
+			cmdp = &loc_cmd;
 		cmdp->cmdtype = CMDBUILTIN;
 		cmdp->param.index = i;
 		cmdp->special = spec;
@@ -353,17 +360,19 @@
 	}
 
 	e = ENOENT;
-	index = -1;
+	idx = -1;
 loop:
 	while ((fullname = padvance(&path, name)) != NULL) {
 		stunalloc(fullname);
-		index++;
+		idx++;
 		if (pathopt) {
 			if (prefix("builtin", pathopt)) {
 				if ((i = find_builtin(name, &spec)) < 0)
 					goto loop;
 				INTOFF;
 				cmdp = cmdlookup(name, 1);
+				if (cmdp->cmdtype == CMDFUNCTION)
+					cmdp = &loc_cmd;
 				cmdp->cmdtype = CMDBUILTIN;
 				cmdp->param.index = i;
 				cmdp->special = spec;
@@ -376,8 +385,8 @@
 			}
 		}
 		/* if rehash, don't redo absolute path names */
-		if (fullname[0] == '/' && index <= prev) {
-			if (index < prev)
+		if (fullname[0] == '/' && idx <= prev) {
+			if (idx < prev)
 				goto loop;
 			TRACE(("searchexec \"%s\": no change\n", name));
 			goto success;
@@ -413,22 +422,25 @@
 		TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname));
 		INTOFF;
 		cmdp = cmdlookup(name, 1);
+		if (cmdp->cmdtype == CMDFUNCTION)
+			cmdp = &loc_cmd;
 		cmdp->cmdtype = CMDNORMAL;
-		cmdp->param.index = index;
+		cmdp->param.index = idx;
 		INTON;
 		goto success;
 	}
 
 	/* We failed.  If there was an entry for this command, delete it */
-	if (cmdp)
+	if (cmdp && cmdp->cmdtype != CMDFUNCTION)
 		delete_cmd_entry();
-	if (printerr) {
+	if (act & DO_ERR) {
 		if (e == ENOENT || e == ENOTDIR)
 			outfmt(out2, "%s: not found\n", name);
 		else
 			outfmt(out2, "%s: %s\n", name, strerror(e));
 	}
 	entry->cmdtype = CMDUNKNOWN;
+	entry->u.index = 0;
 	return;
 
 success:
@@ -445,7 +457,7 @@
  */
 
 int
-find_builtin(char *name, int *special)
+find_builtin(const char *name, int *special)
 {
 	const struct builtincmd *bp;
 
@@ -492,18 +504,18 @@
 changepath(const char *newval)
 {
 	const char *old, *new;
-	int index;
+	int idx;
 	int firstchange;
 	int bltin;
 
 	old = pathval();
 	new = newval;
 	firstchange = 9999;	/* assume no change */
-	index = 0;
+	idx = 0;
 	bltin = -1;
 	for (;;) {
 		if (*old != *new) {
-			firstchange = index;
+			firstchange = idx;
 			if ((*old == '\0' && *new == ':')
 			 || (*old == ':' && *new == '\0'))
 				firstchange++;
@@ -512,9 +524,9 @@
 		if (*new == '\0')
 			break;
 		if (*new == '%' && bltin < 0 && prefix("builtin", new + 1))
-			bltin = index;
+			bltin = idx;
 		if (*new == ':') {
-			index++;
+			idx++;
 		}
 		new++, old++;
 	}
@@ -607,10 +619,10 @@
 
 
 STATIC struct tblentry *
-cmdlookup(char *name, int add)
+cmdlookup(const char *name, int add)
 {
 	int hashval;
-	char *p;
+	const char *p;
 	struct tblentry *cmdp;
 	struct tblentry **pp;
 
@@ -663,7 +675,7 @@
  */
 
 void
-addcmdentry(char *name, struct cmdentry *entry)
+addcmdentry(const char *name, struct cmdentry *entry)
 {
 	struct tblentry *cmdp;
 
@@ -683,7 +695,7 @@
  */
 
 void
-defun(char *name, union node *func)
+defun(const char *name, union node *func)
 {
 	struct cmdentry entry;
 
@@ -700,7 +712,7 @@
  */
 
 int
-unsetfunc(char *name)
+unsetfunc(const char *name)
 {
 	struct tblentry *cmdp;
 
@@ -718,19 +730,21 @@
  */
 
 int
-typecmd_impl(int argc, char **argv, int cmd)
+typecmd_impl(int argc, char **argv, int cmd, const char *path)
 {
 	struct cmdentry entry;
 	struct tblentry *cmdp;
-	char **pp;
+	const char *const *pp;
 	struct alias *ap;
 	int i;
-	int error = 0;
-	extern char *const parsekwd[];
+	int error1 = 0;
 
+	if (path != pathval())
+		clearcmdentry(0);
+
 	for (i = 1; i < argc; i++) {
 		/* First look at the keywords */
-		for (pp = (char **)parsekwd; *pp; pp++)
+		for (pp = parsekwd; *pp; pp++)
 			if (**pp == *argv[i] && equal(*pp, argv[i]))
 				break;
 
@@ -760,16 +774,17 @@
 		}
 		else {
 			/* Finally use brute force */
-			find_command(argv[i], &entry, 0, pathval());
+			find_command(argv[i], &entry, 0, path);
 		}
 
 		switch (entry.cmdtype) {
 		case CMDNORMAL: {
 			if (strchr(argv[i], '/') == NULL) {
-				char *path = pathval(), *name;
+				const char *path2 = path;
+				char *name;
 				int j = entry.u.index;
 				do {
-					name = padvance(&path, argv[i]);
+					name = padvance(&path2, argv[i]);
 					stunalloc(name);
 				} while (--j >= 0);
 				if (cmd == TYPECMD_SMALLV)
@@ -790,7 +805,7 @@
 					if (cmd != TYPECMD_SMALLV)
 						outfmt(out2, "%s: %s\n",
 						    argv[i], strerror(errno));
-					error |= 127;
+					error1 |= 127;
 				}
 			}
 			break;
@@ -815,11 +830,15 @@
 		default:
 			if (cmd != TYPECMD_SMALLV)
 				outfmt(out2, "%s: not found\n", argv[i]);
-			error |= 127;
+			error1 |= 127;
 			break;
 		}
 	}
-	return error;
+
+	if (path != pathval())
+		clearcmdentry(0);
+
+	return error1;
 }
 
 /*
@@ -829,5 +848,5 @@
 int
 typecmd(int argc, char **argv)
 {
-	return typecmd_impl(argc, argv, TYPECMD_TYPE);
+	return typecmd_impl(argc, argv, TYPECMD_TYPE, bltinlookup("PATH", 1));
 }
Index: bin/sh/miscbltin.c
===================================================================
--- bin/sh/miscbltin.c	(revision 209229)
+++ bin/sh/miscbltin.c	(working copy)
@@ -93,7 +93,7 @@
 	char c;
 	int rflag;
 	char *prompt;
-	char *ifs;
+	const char *ifs;
 	char *p;
 	int startword;
 	int status;
@@ -254,7 +254,7 @@
 
 
 int
-umaskcmd(int argc __unused, char **argv)
+umaskcmd(int argc __unused, char **argv __unused)
 {
 	char *ap;
 	int mask;
Index: bin/sh/cd.c
===================================================================
--- bin/sh/cd.c	(revision 209229)
+++ bin/sh/cd.c	(working copy)
@@ -70,6 +70,7 @@
 STATIC char *getcomponent(void);
 STATIC char *findcwd(char *);
 STATIC void updatepwd(char *);
+STATIC char *getpwd(void);
 STATIC char *getpwd2(void);
 
 STATIC char *curdir = NULL;	/* current working directory */
@@ -79,8 +80,8 @@
 int
 cdcmd(int argc, char **argv)
 {
-	char *dest;
-	char *path;
+	const char *dest;
+	const char *path;
 	char *p;
 	struct stat statb;
 	int ch, phys, print = 0;
@@ -351,7 +352,7 @@
 /*
  * Get the current directory and cache the result in curdir.
  */
-char *
+STATIC char *
 getpwd(void)
 {
 	char *p;
@@ -374,7 +375,6 @@
 STATIC char *
 getpwd2(void)
 {
-	struct stat stdot, stpwd;
 	char *pwd;
 	int i;
 
@@ -387,12 +387,29 @@
 			break;
 	}
 
-	pwd = getenv("PWD");
+	return NULL;
+}
+
+/*
+ * Initialize PWD in a new shell.
+ * If the shell is interactive, we need to warn if this fails.
+ */
+void
+pwd_init(int warn)
+{
+	char *pwd;
+	struct stat stdot, stpwd;
+
+	pwd = lookupvar("PWD");
 	if (pwd && *pwd == '/' && stat(".", &stdot) != -1 &&
 	    stat(pwd, &stpwd) != -1 &&
 	    stdot.st_dev == stpwd.st_dev &&
 	    stdot.st_ino == stpwd.st_ino) {
-		return pwd;
+		if (curdir)
+			ckfree(curdir);
+		curdir = savestr(pwd);
 	}
-	return NULL;
+	if (getpwd() == NULL && warn)
+		out2fmt_flush("sh: cannot determine working directory\n");
+	setvar("PWD", curdir, VEXPORT);
 }
Index: bin/sh/eval.c
===================================================================
--- bin/sh/eval.c	(revision 209229)
+++ bin/sh/eval.c	(working copy)
@@ -74,7 +74,7 @@
 #endif
 
 
-MKINIT int evalskip;		/* set if we are skipping commands */
+int evalskip;			/* set if we are skipping commands */
 STATIC int skipcount;		/* number of levels to skip */
 MKINIT int loopnest;		/* current loop nesting level */
 int funcnest;			/* depth of function calls */
@@ -91,6 +91,7 @@
 STATIC void evalfor(union node *, int);
 STATIC void evalcase(union node *, int);
 STATIC void evalsubshell(union node *, int);
+STATIC void evalredir(union node *, int);
 STATIC void expredir(union node *);
 STATIC void evalpipe(union node *);
 STATIC void evalcommand(union node *, int, struct backcmd *);
@@ -221,10 +222,7 @@
 		evaltree(n->nbinary.ch2, flags);
 		break;
 	case NREDIR:
-		expredir(n->nredir.redirect);
-		redirect(n->nredir.redirect, REDIR_PUSH);
-		evaltree(n->nredir.n, flags);
-		popredir();
+		evalredir(n, flags);
 		break;
 	case NSUBSHELL:
 		evalsubshell(n, flags);
@@ -407,8 +405,7 @@
 			flags &=~ EV_TESTED;
 		redirect(n->nredir.redirect, 0);
 		evaltree(n->nredir.n, flags | EV_EXIT);	/* never returns */
-	}
-	if (! backgnd) {
+	} else if (! backgnd) {
 		INTOFF;
 		exitstatus = waitforjob(jp, (int *)NULL);
 		INTON;
@@ -416,7 +413,47 @@
 }
 
 
+/*
+ * Evaluate a redirected compound command.
+ */
 
+STATIC void
+evalredir(union node *n, int flags)
+{
+	struct jmploc jmploc;
+	struct jmploc *savehandler;
+	volatile int in_redirect = 1;
+
+	expredir(n->nredir.redirect);
+	savehandler = handler;
+	if (setjmp(jmploc.loc)) {
+		int e;
+
+		handler = savehandler;
+		e = exception;
+		if (e == EXERROR || e == EXEXEC) {
+			popredir();
+			if (in_redirect) {
+				exitstatus = 2;
+				return;
+			}
+		}
+		longjmp(handler->loc, 1);
+	} else {
+		INTOFF;
+		handler = &jmploc;
+		redirect(n->nredir.redirect, REDIR_PUSH);
+		in_redirect = 0;
+		INTON;
+		evaltree(n->nredir.n, flags);
+	}
+	INTOFF;
+	handler = savehandler;
+	popredir();
+	INTON;
+}
+
+
 /*
  * Compute the names of the files in a redirection list.
  */
@@ -593,10 +630,12 @@
 	char *savecmdname;
 	struct shparam saveparam;
 	struct localvar *savelocalvars;
+	struct parsefile *savetopfile;
 	volatile int e;
 	char *lastarg;
 	int realstatus;
 	int do_clearcmdentry;
+	char *path = pathval();
 
 	/* First expand the arguments. */
 	TRACE(("evalcommand(%p, %d) called\n", (void *)cmd, flags));
@@ -646,7 +685,7 @@
 		out2str(ps4val());
 		for (sp = varlist.list ; sp ; sp = sp->next) {
 			if (sep != 0)
-				outc(' ', &errout);
+				out2c(' ');
 			p = sp->text;
 			while (*p != '=' && *p != '\0')
 				out2c(*p++);
@@ -658,7 +697,7 @@
 		}
 		for (sp = arglist.list ; sp ; sp = sp->next) {
 			if (sep != 0)
-				outc(' ', &errout);
+				out2c(' ');
 			/* Disambiguate command looking like assignment. */
 			if (sp == arglist.list &&
 					strchr(sp->text, '=') != NULL &&
@@ -670,7 +709,7 @@
 				out2qstr(sp->text);
 			sep = ' ';
 		}
-		outc('\n', &errout);
+		out2c('\n');
 		flushout(&errout);
 	}
 
@@ -679,10 +718,10 @@
 		/* Variable assignment(s) without command */
 		cmdentry.cmdtype = CMDBUILTIN;
 		cmdentry.u.index = BLTINCMD;
-		cmdentry.special = 1;
+		cmdentry.special = 0;
 	} else {
 		static const char PATH[] = "PATH=";
-		char *path = pathval();
+		int cmd_flags = 0, bltinonly = 0;
 
 		/*
 		 * Modify the command lookup path, if a PATH= assignment
@@ -713,42 +752,79 @@
 				do_clearcmdentry = 1;
 			}
 
-		find_command(argv[0], &cmdentry, 1, path);
-		if (cmdentry.cmdtype == CMDUNKNOWN) {	/* command not found */
-			exitstatus = 127;
-			flushout(&errout);
-			return;
-		}
-		/* implement the bltin builtin here */
-		if (cmdentry.cmdtype == CMDBUILTIN && cmdentry.u.index == BLTINCMD) {
-			for (;;) {
+		for (;;) {
+			if (bltinonly) {
+				cmdentry.u.index = find_builtin(*argv, &cmdentry.special);
+				if (cmdentry.u.index < 0) {
+					cmdentry.u.index = BLTINCMD;
+					argv--;
+					argc++;
+					break;
+				}
+			} else
+				find_command(argv[0], &cmdentry, cmd_flags, path);
+			/* implement the bltin and command builtins here */
+			if (cmdentry.cmdtype != CMDBUILTIN)
+				break;
+			if (cmdentry.u.index == BLTINCMD) {
+				if (argc == 1)
+					break;
 				argv++;
-				if (--argc == 0)
+				argc--;
+				bltinonly = 1;
+			} else if (cmdentry.u.index == COMMANDCMD) {
+				if (argc == 1)
 					break;
-				if ((cmdentry.u.index = find_builtin(*argv,
-				    &cmdentry.special)) < 0) {
-					outfmt(&errout, "%s: not found\n", *argv);
-					exitstatus = 127;
-					flushout(&errout);
-					return;
+				if (!strcmp(argv[1], "-p")) {
+					if (argc == 2)
+						break;
+					if (argv[2][0] == '-') {
+						if (strcmp(argv[2], "--"))
+							break;
+						if (argc == 3)
+							break;
+						argv += 3;
+						argc -= 3;
+					} else {
+						argv += 2;
+						argc -= 2;
+					}
+					path = _PATH_STDPATH;
+					clearcmdentry(0);
+					do_clearcmdentry = 1;
+				} else if (!strcmp(argv[1], "--")) {
+					if (argc == 2)
+						break;
+					argv += 2;
+					argc -= 2;
+				} else if (argv[1][0] == '-')
+					break;
+				else {
+					argv++;
+					argc--;
 				}
-				if (cmdentry.u.index != BLTINCMD)
-					break;
-			}
+				cmd_flags |= DO_NOFUNC;
+				bltinonly = 0;
+			} else
+				break;
 		}
+		/*
+		 * Special builtins lose their special properties when
+		 * called via 'command'.
+		 */
+		if (cmd_flags & DO_NOFUNC)
+			cmdentry.special = 0;
 	}
 
 	/* Fork off a child process if necessary. */
 	if (cmd->ncmd.backgnd
-	 || (cmdentry.cmdtype == CMDNORMAL
+	 || ((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN)
 	    && ((flags & EV_EXIT) == 0 || have_traps()))
 	 || ((flags & EV_BACKCMD) != 0
 	    && (cmdentry.cmdtype != CMDBUILTIN
 		 || cmdentry.u.index == CDCMD
 		 || cmdentry.u.index == DOTCMD
-		 || cmdentry.u.index == EVALCMD))
-	 || (cmdentry.cmdtype == CMDBUILTIN &&
-	    cmdentry.u.index == COMMANDCMD)) {
+		 || cmdentry.u.index == EVALCMD))) {
 		jp = makejob(cmd, 1);
 		mode = cmd->ncmd.backgnd;
 		if (flags & EV_BACKCMD) {
@@ -775,7 +851,6 @@
 #ifdef DEBUG
 		trputs("Shell function:  ");  trargs(argv);
 #endif
-		redirect(cmd->ncmd.redirect, REDIR_PUSH);
 		saveparam = shellparam;
 		shellparam.malloc = 0;
 		shellparam.reset = 1;
@@ -786,7 +861,6 @@
 		savelocalvars = localvars;
 		localvars = NULL;
 		reffunc(cmdentry.u.func);
-		INTON;
 		savehandler = handler;
 		if (setjmp(jmploc.loc)) {
 			if (exception == EXSHELLPROC)
@@ -794,23 +868,27 @@
 			else {
 				freeparam(&shellparam);
 				shellparam = saveparam;
+				if (exception == EXERROR || exception == EXEXEC)
+					popredir();
 			}
 			unreffunc(cmdentry.u.func);
 			poplocalvars();
 			localvars = savelocalvars;
+			funcnest--;
 			handler = savehandler;
 			longjmp(handler->loc, 1);
 		}
 		handler = &jmploc;
+		funcnest++;
+		redirect(cmd->ncmd.redirect, REDIR_PUSH);
+		INTON;
 		for (sp = varlist.list ; sp ; sp = sp->next)
 			mklocal(sp->text);
-		funcnest++;
 		exitstatus = oexitstatus;
 		if (flags & EV_TESTED)
 			evaltree(getfuncnode(cmdentry.u.func), EV_TESTED);
 		else
 			evaltree(getfuncnode(cmdentry.u.func), 0);
-		funcnest--;
 		INTOFF;
 		unreffunc(cmdentry.u.func);
 		poplocalvars();
@@ -818,6 +896,7 @@
 		freeparam(&shellparam);
 		shellparam = saveparam;
 		handler = savehandler;
+		funcnest--;
 		popredir();
 		INTON;
 		if (evalskip == SKIPFUNC) {
@@ -836,8 +915,10 @@
 			memout.nextc = memout.buf;
 			memout.bufsize = 64;
 			mode |= REDIR_BACKQ;
+			cmdentry.special = 0;
 		}
 		savecmdname = commandname;
+		savetopfile = getcurrentfile();
 		cmdenviron = varlist.list;
 		e = -1;
 		savehandler = handler;
@@ -848,15 +929,25 @@
 		}
 		handler = &jmploc;
 		redirect(cmd->ncmd.redirect, mode);
+		/*
+		 * If there is no command word, redirection errors should
+		 * not be fatal but assignment errors should.
+		 */
+		if (argc == 0 && !(flags & EV_BACKCMD))
+			cmdentry.special = 1;
 		if (cmdentry.special)
 			listsetvar(cmdenviron);
+		if (argc > 0)
+			bltinsetlocale();
 		commandname = argv[0];
 		argptr = argv + 1;
-		optptr = NULL;			/* initialize nextopt */
+		nextopt_optptr = NULL;		/* initialize nextopt */
 		builtin_flags = flags;
 		exitstatus = (*builtinfunc[cmdentry.u.index])(argc, argv);
 		flushall();
 cmddone:
+		if (argc > 0)
+			bltinunsetlocale();
 		cmdenviron = NULL;
 		out1 = &output;
 		out2 = &errout;
@@ -868,29 +959,31 @@
 			}
 		}
 		handler = savehandler;
+		if (flags == EV_BACKCMD) {
+			backcmd->buf = memout.buf;
+			backcmd->nleft = memout.nextc - memout.buf;
+			memout.buf = NULL;
+		}
+		if (cmdentry.u.index != EXECCMD &&
+				(e == -1 || e == EXERROR || e == EXEXEC))
+			popredir();
 		if (e != -1) {
 			if ((e != EXERROR && e != EXEXEC)
 			    || cmdentry.special)
 				exraise(e);
-			FORCEINTON;
+			popfilesupto(savetopfile);
+			if (flags != EV_BACKCMD)
+				FORCEINTON;
 		}
-		if (cmdentry.u.index != EXECCMD)
-			popredir();
-		if (flags == EV_BACKCMD) {
-			backcmd->buf = memout.buf;
-			backcmd->nleft = memout.nextc - memout.buf;
-			memout.buf = NULL;
-		}
 	} else {
 #ifdef DEBUG
 		trputs("normal command:  ");  trargs(argv);
 #endif
-		clearredir();
 		redirect(cmd->ncmd.redirect, 0);
 		for (sp = varlist.list ; sp ; sp = sp->next)
 			setvareq(sp->text, VEXPORT|VSTACK);
 		envp = environment();
-		shellexec(argv, envp, pathval(), cmdentry.u.index);
+		shellexec(argv, envp, path, cmdentry.u.index);
 		/*NOTREACHED*/
 	}
 	goto out;
@@ -946,12 +1039,17 @@
  */
 
 /*
- * No command given, or a bltin command with no arguments.
+ * No command given, a bltin command with no arguments, or a bltin command
+ * with an invalid name.
  */
 
 int
-bltincmd(int argc __unused, char **argv __unused)
+bltincmd(int argc, char **argv)
 {
+	if (argc > 1) {
+		out2fmt_flush("%s: not found\n", argv[1]);
+		return 127;
+	}
 	/*
 	 * Preserve exitstatus of a previous possible redirection
 	 * as POSIX mandates
@@ -991,23 +1089,18 @@
 int
 commandcmd(int argc, char **argv)
 {
-	static char stdpath[] = _PATH_STDPATH;
-	struct jmploc loc, *old;
-	struct strlist *sp;
-	char *path;
+	const char *path;
 	int ch;
 	int cmd = -1;
 
-	for (sp = cmdenviron; sp ; sp = sp->next)
-		setvareq(sp->text, VEXPORT|VSTACK);
-	path = pathval();
+	path = bltinlookup("PATH", 1);
 
 	optind = optreset = 1;
 	opterr = 0;
 	while ((ch = getopt(argc, argv, "pvV")) != -1) {
 		switch (ch) {
 		case 'p':
-			path = stdpath;
+			path = _PATH_STDPATH;
 			break;
 		case 'v':
 			cmd = TYPECMD_SMALLV;
@@ -1026,24 +1119,16 @@
 	if (cmd != -1) {
 		if (argc != 1)
 			error("wrong number of arguments");
-		return typecmd_impl(2, argv - 1, cmd);
+		return typecmd_impl(2, argv - 1, cmd, path);
 	}
-	if (argc != 0) {
-		old = handler;
-		handler = &loc;
-		if (setjmp(handler->loc) == 0)
-			shellexec(argv, environment(), path, 0);
-		handler = old;
-		if (exception == EXEXEC)
-			exit(exerrno);
-		exraise(exception);
-	}
+	if (argc != 0)
+		error("commandcmd() called while it should not be");
 
 	/*
 	 * Do nothing successfully if no command was specified;
 	 * ksh also does this.
 	 */
-	exit(0);
+	return 0;
 }
 
 
@@ -1085,6 +1170,12 @@
 int
 execcmd(int argc, char **argv)
 {
+	/*
+	 * Because we have historically not supported any options,
+	 * only treat "--" specially.
+	 */
+	if (argc > 1 && strcmp(argv[1], "--") == 0)
+		argc--, argv++;
 	if (argc > 1) {
 		struct strlist *sp;
 
Index: bin/sh/var.c
===================================================================
--- bin/sh/var.c	(revision 209229)
+++ bin/sh/var.c	(working copy)
@@ -73,13 +73,14 @@
 struct varinit {
 	struct var *var;
 	int flags;
-	char *text;
+	const char *text;
 	void (*func)(const char *);
 };
 
 
 #ifndef NO_HISTORY
 struct var vhistsize;
+struct var vterm;
 #endif
 struct var vifs;
 struct var vmail;
@@ -94,27 +95,31 @@
 
 STATIC const struct varinit varinit[] = {
 #ifndef NO_HISTORY
-	{ &vhistsize,	VSTRFIXED|VTEXTFIXED|VUNSET,	"HISTSIZE=",
+	{ &vhistsize,	VUNSET,				"HISTSIZE=",
 	  sethistsize },
 #endif
-	{ &vifs,	VSTRFIXED|VTEXTFIXED,		"IFS= \t\n",
+	{ &vifs,	0,				"IFS= \t\n",
 	  NULL },
-	{ &vmail,	VSTRFIXED|VTEXTFIXED|VUNSET,	"MAIL=",
+	{ &vmail,	VUNSET,				"MAIL=",
 	  NULL },
-	{ &vmpath,	VSTRFIXED|VTEXTFIXED|VUNSET,	"MAILPATH=",
+	{ &vmpath,	VUNSET,				"MAILPATH=",
 	  NULL },
-	{ &vpath,	VSTRFIXED|VTEXTFIXED,		"PATH=" _PATH_DEFPATH,
+	{ &vpath,	0,				"PATH=" _PATH_DEFPATH,
 	  changepath },
-	{ &vppid,	VSTRFIXED|VTEXTFIXED|VUNSET,	"PPID=",
+	{ &vppid,	VUNSET,				"PPID=",
 	  NULL },
 	/*
 	 * vps1 depends on uid
 	 */
-	{ &vps2,	VSTRFIXED|VTEXTFIXED,		"PS2=> ",
+	{ &vps2,	0,				"PS2=> ",
 	  NULL },
-	{ &vps4,	VSTRFIXED|VTEXTFIXED,		"PS4=+ ",
+	{ &vps4,	0,				"PS4=+ ",
 	  NULL },
-	{ &voptind,	VSTRFIXED|VTEXTFIXED,		"OPTIND=1",
+#ifndef NO_HISTORY
+	{ &vterm,	VUNSET,				"TERM=",
+	  setterm },
+#endif
+	{ &voptind,	0,				"OPTIND=1",
 	  getoptsreset },
 	{ NULL,	0,				NULL,
 	  NULL }
@@ -122,19 +127,27 @@
 
 STATIC struct var *vartab[VTABSIZE];
 
-STATIC struct var **hashvar(char *);
-STATIC int varequal(char *, char *);
-STATIC int localevar(char *);
+STATIC const char *const locale_names[7] = {
+	"LC_COLLATE", "LC_CTYPE", "LC_MONETARY",
+	"LC_NUMERIC", "LC_TIME", "LC_MESSAGES", NULL
+};
+STATIC const int locale_categories[7] = {
+	LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME, LC_MESSAGES, 0
+};
 
+STATIC struct var **hashvar(const char *);
+STATIC int varequal(const char *, const char *);
+STATIC int localevar(const char *);
+
 /*
  * Initialize the variable symbol tables and import the environment.
  */
 
 #ifdef mkinit
 INCLUDE "var.h"
+MKINIT char **environ;
 INIT {
 	char **envp;
-	extern char **environ;
 
 	initvar();
 	for (envp = environ ; *envp ; envp++) {
@@ -164,8 +177,8 @@
 			vpp = hashvar(ip->text);
 			vp->next = *vpp;
 			*vpp = vp;
-			vp->text = ip->text;
-			vp->flags = ip->flags;
+			vp->text = __DECONST(char *, ip->text);
+			vp->flags = ip->flags | VSTRFIXED | VTEXTFIXED;
 			vp->func = ip->func;
 		}
 	}
@@ -176,7 +189,7 @@
 		vpp = hashvar("PS1=");
 		vps1.next = *vpp;
 		*vpp = &vps1;
-		vps1.text = geteuid() ? "PS1=$ " : "PS1=# ";
+		vps1.text = __DECONST(char *, geteuid() ? "PS1=$ " : "PS1=# ");
 		vps1.flags = VSTRFIXED|VTEXTFIXED;
 	}
 	if ((vppid.flags & VEXPORT) == 0) {
@@ -190,12 +203,14 @@
  */
 
 int
-setvarsafe(char *name, char *val, int flags)
+setvarsafe(const char *name, const char *val, int flags)
 {
 	struct jmploc jmploc;
 	struct jmploc *const savehandler = handler;
 	int err = 0;
+	int inton;
 
+	inton = is_int_on();
 	if (setjmp(jmploc.loc))
 		err = 1;
 	else {
@@ -203,6 +218,7 @@
 		setvar(name, val, flags);
 	}
 	handler = savehandler;
+	SETINTON(inton);
 	return err;
 }
 
@@ -212,9 +228,9 @@
  */
 
 void
-setvar(char *name, char *val, int flags)
+setvar(const char *name, const char *val, int flags)
 {
-	char *p, *q;
+	const char *p;
 	int len;
 	int namelen;
 	char *nameeq;
@@ -242,25 +258,20 @@
 	} else {
 		len += strlen(val);
 	}
-	p = nameeq = ckmalloc(len);
-	q = name;
-	while (--namelen >= 0)
-		*p++ = *q++;
-	*p++ = '=';
-	*p = '\0';
+	nameeq = ckmalloc(len);
+	memcpy(nameeq, name, namelen);
+	nameeq[namelen] = '=';
 	if (val)
-		scopy(val, p);
+		scopy(val, nameeq + namelen + 1);
+	else
+		nameeq[namelen + 1] = '\0';
 	setvareq(nameeq, flags);
 }
 
 STATIC int
-localevar(char *s)
+localevar(const char *s)
 {
-	static char *lnames[7] = {
-		"ALL", "COLLATE", "CTYPE", "MONETARY",
-		"NUMERIC", "TIME", NULL
-	};
-	char **ss;
+	const char *const *ss;
 
 	if (*s != 'L')
 		return 0;
@@ -268,8 +279,10 @@
 		return 1;
 	if (strncmp(s + 1, "C_", 2) != 0)
 		return 0;
-	for (ss = lnames; *ss ; ss++)
-		if (varequal(s + 3, *ss))
+	if (varequal(s + 3, "ALL"))
+		return 1;
+	for (ss = locale_names; *ss ; ss++)
+		if (varequal(s + 3, *ss + 3))
 			return 1;
 	return 0;
 }
@@ -280,7 +293,7 @@
  * pointer into environ where the string should not be manipulated.
  */
 static void
-change_env(char *s, int set)
+change_env(const char *s, int set)
 {
 	char *eqp;
 	char *ss;
@@ -391,7 +404,7 @@
  */
 
 char *
-lookupvar(char *name)
+lookupvar(const char *name)
 {
 	struct var *v;
 
@@ -414,7 +427,7 @@
  */
 
 char *
-bltinlookup(char *name, int doall)
+bltinlookup(const char *name, int doall)
 {
 	struct strlist *sp;
 	struct var *v;
@@ -435,8 +448,63 @@
 }
 
 
+/*
+ * Set up locale for a builtin (LANG/LC_* assignments).
+ */
+void
+bltinsetlocale(void)
+{
+	struct strlist *lp;
+	int act = 0;
+	char *loc, *locdef;
+	int i;
 
+	for (lp = cmdenviron ; lp ; lp = lp->next) {
+		if (localevar(lp->text)) {
+			act = 1;
+			break;
+		}
+	}
+	if (!act)
+		return;
+	loc = bltinlookup("LC_ALL", 0);
+	INTOFF;
+	if (loc != NULL) {
+		setlocale(LC_ALL, loc);
+		INTON;
+		return;
+	}
+	locdef = bltinlookup("LANG", 0);
+	for (i = 0; locale_names[i] != NULL; i++) {
+		loc = bltinlookup(locale_names[i], 0);
+		if (loc == NULL)
+			loc = locdef;
+		if (loc != NULL)
+			setlocale(locale_categories[i], loc);
+	}
+	INTON;
+}
+
 /*
+ * Undo the effect of bltinlocaleset().
+ */
+void
+bltinunsetlocale(void)
+{
+	struct strlist *lp;
+
+	INTOFF;
+	for (lp = cmdenviron ; lp ; lp = lp->next) {
+		if (localevar(lp->text)) {
+			setlocale(LC_ALL, "");
+			return;
+		}
+	}
+	INTON;
+}
+
+
+/*
  * Generate a list of exported variables.  This routine is used to construct
  * the third argument to execve when executing a program.
  */
@@ -472,9 +540,9 @@
  * VSTACK set since these are currently allocated on the stack.
  */
 
-#ifdef mkinit
 MKINIT void shprocvar(void);
 
+#ifdef mkinit
 SHELLPROC {
 	shprocvar();
 }
@@ -798,7 +866,7 @@
  */
 
 int
-unsetvar(char *s)
+unsetvar(const char *s)
 {
 	struct var **vpp;
 	struct var *vp;
@@ -838,7 +906,7 @@
  */
 
 STATIC struct var **
-hashvar(char *p)
+hashvar(const char *p)
 {
 	unsigned int hashval;
 
@@ -857,7 +925,7 @@
  */
 
 STATIC int
-varequal(char *p, char *q)
+varequal(const char *p, const char *q)
 {
 	while (*p == *q++) {
 		if (*p++ == '=')
Index: bin/sh/memalloc.c
===================================================================
--- bin/sh/memalloc.c	(revision 209229)
+++ bin/sh/memalloc.c	(working copy)
@@ -95,7 +95,7 @@
  */
 
 char *
-savestr(char *s)
+savestr(const char *s)
 {
 	char *p;
 
Index: bin/sh/main.h
===================================================================
--- bin/sh/main.h	(revision 209229)
+++ bin/sh/main.h	(working copy)
@@ -35,8 +35,9 @@
 
 extern int rootpid;	/* pid of main shell */
 extern int rootshell;	/* true if we aren't a child of the main shell */
+extern struct jmploc main_handler;	/* top level exception handler */
 
-void readcmdfile(char *);
+void readcmdfile(const char *);
 void cmdloop(int);
 int dotcmd(int, char **);
 int exitcmd(int, char **);
Index: bin/sh/exec.h
===================================================================
--- bin/sh/exec.h	(revision 209229)
+++ bin/sh/exec.h	(working copy)
@@ -57,20 +57,24 @@
 };
 
 
-extern char *pathopt;		/* set by padvance */
+/* action to find_command() */
+#define DO_ERR		0x01	/* prints errors */
+#define DO_NOFUNC	0x02	/* don't return shell functions, for command */
+
+extern const char *pathopt;	/* set by padvance */
 extern int exerrno;		/* last exec error */
 
-void shellexec(char **, char **, char *, int);
-char *padvance(char **, char *);
+void shellexec(char **, char **, const char *, int) __dead2;
+char *padvance(const char **, const char *);
 int hashcmd(int, char **);
-void find_command(char *, struct cmdentry *, int, char *);
-int find_builtin(char *, int *);
+void find_command(const char *, struct cmdentry *, int, const char *);
+int find_builtin(const char *, int *);
 void hashcd(void);
 void changepath(const char *);
 void deletefuncs(void);
-void addcmdentry(char *, struct cmdentry *);
-void defun(char *, union node *);
-int unsetfunc(char *);
-int typecmd_impl(int, char **, int);
+void addcmdentry(const char *, struct cmdentry *);
+void defun(const char *, union node *);
+int unsetfunc(const char *);
+int typecmd_impl(int, char **, int, const char *);
 int typecmd(int, char **);
 void clearcmdentry(int);
Index: bin/sh/alias.c
===================================================================
--- bin/sh/alias.c	(revision 209229)
+++ bin/sh/alias.c	(working copy)
@@ -52,13 +52,13 @@
 STATIC struct alias *atab[ATABSIZE];
 STATIC int aliases;
 
-STATIC void setalias(char *, char *);
+STATIC void setalias(const char *, const char *);
 STATIC int unalias(const char *);
 STATIC struct alias **hashalias(const char *);
 
 STATIC
 void
-setalias(char *name, char *val)
+setalias(const char *name, const char *val)
 {
 	struct alias *ap, **app;
 
@@ -176,7 +176,7 @@
 }
 
 struct alias *
-lookupalias(char *name, int check)
+lookupalias(const char *name, int check)
 {
 	struct alias *ap = *hashalias(name);
 
Index: bin/sh/cd.h
===================================================================
--- bin/sh/cd.h	(revision 209229)
+++ bin/sh/cd.h	(working copy)
@@ -29,6 +29,6 @@
  * $FreeBSD$
  */
 
-char	*getpwd(void);
+void	 pwd_init(int);
 int	 cdcmd (int, char **);
 int	 pwdcmd(int, char **);
Index: bin/sh/mkinit.c
===================================================================
--- bin/sh/mkinit.c	(revision 209229)
+++ bin/sh/mkinit.c	(working copy)
@@ -102,9 +102,9 @@
  */
 
 struct event {
-	char *name;		/* name of event (e.g. INIT) */
-	char *routine;		/* name of routine called on event */
-	char *comment;		/* comment describing routine */
+	const char *name;	/* name of event (e.g. INIT) */
+	const char *routine;	/* name of routine called on event */
+	const char *comment;	/* comment describing routine */
 	struct text code;	/* code for handling event */
 };
 
@@ -140,7 +140,7 @@
 };
 
 
-char *curfile;				/* current file */
+const char *curfile;			/* current file */
 int linno;				/* current line */
 char *header_files[200];		/* list of header files */
 struct text defines;			/* #define statements */
@@ -148,20 +148,20 @@
 int amiddecls;				/* for formatting */
 
 
-void readfile(char *);
-int match(char *, char *);
-int gooddefine(char *);
-void doevent(struct event *, FILE *, char *);
+void readfile(const char *);
+int match(const char *, const char *);
+int gooddefine(const char *);
+void doevent(struct event *, FILE *, const char *);
 void doinclude(char *);
 void dodecl(char *, FILE *);
 void output(void);
-void addstr(char *, struct text *);
+void addstr(const char *, struct text *);
 void addchar(int, struct text *);
 void writetext(struct text *, FILE *);
-FILE *ckfopen(char *, char *);
+FILE *ckfopen(const char *, const char *);
 void *ckmalloc(size_t);
-char *savestr(char *);
-void error(char *);
+char *savestr(const char *);
+void error(const char *);
 
 #define equal(s1, s2)	(strcmp(s1, s2) == 0)
 
@@ -170,9 +170,9 @@
 {
 	char **ap;
 
-	header_files[0] = "\"shell.h\"";
-	header_files[1] = "\"mystring.h\"";
-	header_files[2] = "\"init.h\"";
+	header_files[0] = savestr("\"shell.h\"");
+	header_files[1] = savestr("\"mystring.h\"");
+	header_files[2] = savestr("\"init.h\"");
 	for (ap = argv + 1 ; *ap ; ap++)
 		readfile(*ap);
 	output();
@@ -186,7 +186,7 @@
  */
 
 void
-readfile(char *fname)
+readfile(const char *fname)
 {
 	FILE *fp;
 	char line[1024];
@@ -230,9 +230,9 @@
 
 
 int
-match(char *name, char *line)
+match(const char *name, const char *line)
 {
-	char *p, *q;
+	const char *p, *q;
 
 	p = name, q = line;
 	while (*p) {
@@ -246,9 +246,9 @@
 
 
 int
-gooddefine(char *line)
+gooddefine(const char *line)
 {
-	char *p;
+	const char *p;
 
 	if (! match("#define", line))
 		return 0;			/* not a define */
@@ -269,11 +269,11 @@
 
 
 void
-doevent(struct event *ep, FILE *fp, char *fname)
+doevent(struct event *ep, FILE *fp, const char *fname)
 {
 	char line[1024];
 	int indent;
-	char *p;
+	const char *p;
 
 	sprintf(line, "\n      /* from %s: */\n", fname);
 	addstr(line, &ep->code);
@@ -407,7 +407,7 @@
  */
 
 void
-addstr(char *s, struct text *text)
+addstr(const char *s, struct text *text)
 {
 	while (*s) {
 		if (--text->nleft < 0)
@@ -452,7 +452,7 @@
 }
 
 FILE *
-ckfopen(char *file, char *mode)
+ckfopen(const char *file, const char *mode)
 {
 	FILE *fp;
 
@@ -474,7 +474,7 @@
 }
 
 char *
-savestr(char *s)
+savestr(const char *s)
 {
 	char *p;
 
@@ -484,7 +484,7 @@
 }
 
 void
-error(char *msg)
+error(const char *msg)
 {
 	if (curfile != NULL)
 		fprintf(stderr, "%s:%d: ", curfile, linno);
Index: bin/sh/options.c
===================================================================
--- bin/sh/options.c	(revision 209229)
+++ bin/sh/options.c	(working copy)
@@ -64,7 +64,7 @@
 struct shparam shellparam;	/* current positional parameters */
 char **argptr;			/* argument list for builtin commands */
 char *shoptarg;			/* set by nextopt (like getopt) */
-char *optptr;			/* used by nextopt */
+char *nextopt_optptr;		/* used by nextopt */
 
 char *minusc;			/* argument to -c option */
 
@@ -93,8 +93,11 @@
 	options(1);
 	if (*argptr == NULL && minusc == NULL)
 		sflag = 1;
-	if (iflag == 2 && sflag == 1 && isatty(0) && isatty(1))
+	if (iflag != 0 && sflag == 1 && isatty(0) && isatty(1)) {
 		iflag = 1;
+		if (Eflag == 2)
+			Eflag = 1;
+	}
 	if (mflag == 2)
 		mflag = iflag;
 	for (i = 0; i < NOPTS; i++)
@@ -554,12 +557,13 @@
  */
 
 int
-nextopt(char *optstring)
+nextopt(const char *optstring)
 {
-	char *p, *q;
+	char *p;
+	const char *q;
 	char c;
 
-	if ((p = optptr) == NULL || *p == '\0') {
+	if ((p = nextopt_optptr) == NULL || *p == '\0') {
 		p = *argptr;
 		if (p == NULL || *p != '-' || *++p == '\0')
 			return '\0';
@@ -580,6 +584,6 @@
 		shoptarg = p;
 		p = NULL;
 	}
-	optptr = p;
+	nextopt_optptr = p;
 	return c;
 }
Index: bin/sh/eval.h
===================================================================
--- bin/sh/eval.h	(revision 209229)
+++ bin/sh/eval.h	(working copy)
@@ -35,6 +35,7 @@
 
 extern char *commandname;	/* currently executing command */
 extern int exitstatus;		/* exit status of last command */
+extern int oexitstatus;		/* saved exit status */
 extern struct strlist *cmdenviron;  /* environment for builtin command */
 
 
Index: bin/sh/parser.c
===================================================================
--- bin/sh/parser.c	(revision 209229)
+++ bin/sh/parser.c	(working copy)
@@ -79,10 +79,13 @@
 	int striptabs;		/* if set, strip leading tabs */
 };
 
+struct parser_temp {
+	struct parser_temp *next;
+	void *data;
+};
 
 
 STATIC struct heredoc *heredoclist;	/* list of here documents to read */
-STATIC int parsebackquote;	/* nonzero if we are inside backquotes */
 STATIC int doprompt;		/* if set, prompt the user */
 STATIC int needprompt;		/* true if interactive and at start of line */
 STATIC int lasttoken;		/* last token read */
@@ -95,6 +98,7 @@
 STATIC int quoteflag;		/* set if (part of) last token was quoted */
 STATIC int startlinno;		/* line # where last token started */
 STATIC int funclinno;		/* line # where the current function started */
+STATIC struct parser_temp *parser_temp;
 
 /* XXX When 'noaliases' is set to one, no alias expansion takes place. */
 static int noaliases = 0;
@@ -114,10 +118,77 @@
 STATIC int readtoken1(int, char const *, char *, int);
 STATIC int noexpand(char *);
 STATIC void synexpect(int);
-STATIC void synerror(char *);
+STATIC void synerror(const char *);
 STATIC void setprompt(int);
 
 
+STATIC void *
+parser_temp_alloc(size_t len)
+{
+	struct parser_temp *t;
+
+	INTOFF;
+	t = ckmalloc(sizeof(*t));
+	t->data = NULL;
+	t->next = parser_temp;
+	parser_temp = t;
+	t->data = ckmalloc(len);
+	INTON;
+	return t->data;
+}
+
+
+STATIC void *
+parser_temp_realloc(void *ptr, size_t len)
+{
+	struct parser_temp *t;
+
+	INTOFF;
+	t = parser_temp;
+	if (ptr != t->data)
+		error("bug: parser_temp_realloc misused");
+	t->data = ckrealloc(t->data, len);
+	INTON;
+	return t->data;
+}
+
+
+STATIC void
+parser_temp_free_upto(void *ptr)
+{
+	struct parser_temp *t;
+	int done = 0;
+
+	INTOFF;
+	while (parser_temp != NULL && !done) {
+		t = parser_temp;
+		parser_temp = t->next;
+		done = t->data == ptr;
+		ckfree(t->data);
+		ckfree(t);
+	}
+	INTON;
+	if (!done)
+		error("bug: parser_temp_free_upto misused");
+}
+
+
+STATIC void
+parser_temp_free_all(void)
+{
+	struct parser_temp *t;
+
+	INTOFF;
+	while (parser_temp != NULL) {
+		t = parser_temp;
+		parser_temp = t->next;
+		ckfree(t->data);
+		ckfree(t);
+	}
+	INTON;
+}
+
+
 /*
  * Read and parse a command.  Returns NEOF on end of file.  (NULL is a
  * valid parse tree indicating a blank line.)
@@ -128,6 +199,12 @@
 {
 	int t;
 
+	/* This assumes the parser is not re-entered,
+	 * which could happen if we add command substitution on PS1/PS2.
+	 */
+	parser_temp_free_all();
+	heredoclist = NULL;
+
 	tokpushback = 0;
 	doprompt = interact;
 	if (doprompt)
@@ -864,8 +941,190 @@
 }
 
 
+#define MAXNEST_STATIC 8
+struct tokenstate
+{
+	const char *syntax; /* *SYNTAX */
+	int parenlevel; /* levels of parentheses in arithmetic */
+	enum tokenstate_category
+	{
+		TSTATE_TOP,
+		TSTATE_VAR_OLD, /* ${var+-=?}, inherits dquotes */
+		TSTATE_VAR_NEW, /* other ${var...}, own dquote state */
+		TSTATE_ARITH
+	} category;
+};
 
+
 /*
+ * Called to parse command substitutions.
+ */
+
+STATIC char *
+parsebackq(char *out, struct nodelist **pbqlist,
+		int oldstyle, int dblquote, int quoted)
+{
+	struct nodelist **nlpp;
+	union node *n;
+	char *volatile str;
+	struct jmploc jmploc;
+	struct jmploc *const savehandler = handler;
+	int savelen;
+	int saveprompt;
+	const int bq_startlinno = plinno;
+	char *volatile ostr = NULL;
+	struct parsefile *const savetopfile = getcurrentfile();
+	struct heredoc *const saveheredoclist = heredoclist;
+	struct heredoc *here;
+
+	str = NULL;
+	if (setjmp(jmploc.loc)) {
+		popfilesupto(savetopfile);
+		if (str)
+			ckfree(str);
+		if (ostr)
+			ckfree(ostr);
+		heredoclist = saveheredoclist;
+		handler = savehandler;
+		if (exception == EXERROR) {
+			startlinno = bq_startlinno;
+			synerror("Error in command substitution");
+		}
+		longjmp(handler->loc, 1);
+	}
+	INTOFF;
+	savelen = out - stackblock();
+	if (savelen > 0) {
+		str = ckmalloc(savelen);
+		memcpy(str, stackblock(), savelen);
+	}
+	handler = &jmploc;
+	heredoclist = NULL;
+	INTON;
+        if (oldstyle) {
+                /* We must read until the closing backquote, giving special
+                   treatment to some slashes, and then push the string and
+                   reread it as input, interpreting it normally.  */
+                char *oout;
+                int c;
+                int olen;
+
+
+                STARTSTACKSTR(oout);
+		for (;;) {
+			if (needprompt) {
+				setprompt(2);
+				needprompt = 0;
+			}
+			switch (c = pgetc()) {
+			case '`':
+				goto done;
+
+			case '\\':
+                                if ((c = pgetc()) == '\n') {
+					plinno++;
+					if (doprompt)
+						setprompt(2);
+					else
+						setprompt(0);
+					/*
+					 * If eating a newline, avoid putting
+					 * the newline into the new character
+					 * stream (via the STPUTC after the
+					 * switch).
+					 */
+					continue;
+				}
+                                if (c != '\\' && c != '`' && c != '$'
+                                    && (!dblquote || c != '"'))
+                                        STPUTC('\\', oout);
+				break;
+
+			case '\n':
+				plinno++;
+				needprompt = doprompt;
+				break;
+
+			case PEOF:
+			        startlinno = plinno;
+				synerror("EOF in backquote substitution");
+ 				break;
+
+			default:
+				break;
+			}
+			STPUTC(c, oout);
+                }
+done:
+                STPUTC('\0', oout);
+                olen = oout - stackblock();
+		INTOFF;
+		ostr = ckmalloc(olen);
+		memcpy(ostr, stackblock(), olen);
+		setinputstring(ostr, 1);
+		INTON;
+        }
+	nlpp = pbqlist;
+	while (*nlpp)
+		nlpp = &(*nlpp)->next;
+	*nlpp = (struct nodelist *)stalloc(sizeof (struct nodelist));
+	(*nlpp)->next = NULL;
+
+	if (oldstyle) {
+		saveprompt = doprompt;
+		doprompt = 0;
+	}
+
+	n = list(0);
+
+	if (oldstyle)
+		doprompt = saveprompt;
+	else {
+		if (readtoken() != TRP)
+			synexpect(TRP);
+	}
+
+	(*nlpp)->n = n;
+        if (oldstyle) {
+		/*
+		 * Start reading from old file again, ignoring any pushed back
+		 * tokens left from the backquote parsing
+		 */
+                popfile();
+		tokpushback = 0;
+	}
+	while (stackblocksize() <= savelen)
+		growstackblock();
+	STARTSTACKSTR(out);
+	INTOFF;
+	if (str) {
+		memcpy(out, str, savelen);
+		STADJUST(savelen, out);
+		ckfree(str);
+		str = NULL;
+	}
+	if (ostr) {
+		ckfree(ostr);
+		ostr = NULL;
+	}
+	here = saveheredoclist;
+	if (here != NULL) {
+		while (here->next != NULL)
+			here = here->next;
+		here->next = heredoclist;
+		heredoclist = saveheredoclist;
+	}
+	handler = savehandler;
+	INTON;
+	if (quoted)
+		USTPUTC(CTLBACKQ | CTLQUOTE, out);
+	else
+		USTPUTC(CTLBACKQ, out);
+	return out;
+}
+
+
+/*
  * If eofmark is NULL, read a word or a redirection symbol.  If eofmark
  * is not NULL, read a here document.  In the latter case, eofmark is the
  * word which marks the end of the document and striptabs is true if
@@ -880,12 +1139,10 @@
 #define CHECKEND()	{goto checkend; checkend_return:;}
 #define PARSEREDIR()	{goto parseredir; parseredir_return:;}
 #define PARSESUB()	{goto parsesub; parsesub_return:;}
-#define PARSEBACKQOLD()	{oldstyle = 1; goto parsebackq; parsebackq_oldreturn:;}
-#define PARSEBACKQNEW()	{oldstyle = 0; goto parsebackq; parsebackq_newreturn:;}
 #define	PARSEARITH()	{goto parsearith; parsearith_return:;}
 
 STATIC int
-readtoken1(int firstc, char const *syntax, char *eofmark, int striptabs)
+readtoken1(int firstc, char const *initialsyntax, char *eofmark, int striptabs)
 {
 	int c = firstc;
 	char *out;
@@ -893,23 +1150,21 @@
 	char line[EOFMARKLEN + 1];
 	struct nodelist *bqlist;
 	int quotef;
-	int dblquote;
-	int varnest;	/* levels of variables expansion */
-	int arinest;	/* levels of arithmetic expansion */
-	int parenlevel;	/* levels of parens in arithmetic */
-	int oldstyle;
-	char const *prevsyntax;	/* syntax before arithmetic */
+	int newvarnest;
+	int level;
 	int synentry;
+	struct tokenstate state_static[MAXNEST_STATIC];
+	int maxnest = MAXNEST_STATIC;
+	struct tokenstate *state = state_static;
 
 	startlinno = plinno;
-	dblquote = 0;
-	if (syntax == DQSYNTAX)
-		dblquote = 1;
 	quotef = 0;
 	bqlist = NULL;
-	varnest = 0;
-	arinest = 0;
-	parenlevel = 0;
+	newvarnest = 0;
+	level = 0;
+	state[level].syntax = initialsyntax;
+	state[level].parenlevel = 0;
+	state[level].category = TSTATE_TOP;
 
 	STARTSTACKSTR(out);
 	loop: {	/* for each line, until end of word */
@@ -917,11 +1172,11 @@
 		for (;;) {	/* until end of line or end of word */
 			CHECKSTRSPACE(3, out);	/* permit 3 calls to USTPUTC */
 
-			synentry = syntax[c];
+			synentry = state[level].syntax[c];
 
 			switch(synentry) {
 			case CNL:	/* '\n' */
-				if (syntax == BASESYNTAX)
+				if (state[level].syntax == BASESYNTAX)
 					goto endword;	/* exit outer loop */
 				USTPUTC(c, out);
 				plinno++;
@@ -935,7 +1190,7 @@
 				USTPUTC(c, out);
 				break;
 			case CCTL:
-				if (eofmark == NULL || dblquote)
+				if (eofmark == NULL || initialsyntax != SQSYNTAX)
 					USTPUTC(CTLESC, out);
 				USTPUTC(c, out);
 				break;
@@ -951,41 +1206,37 @@
 					else
 						setprompt(0);
 				} else {
-					if (dblquote && c != '\\' &&
-					    c != '`' && c != '$' &&
-					    (c != '"' || eofmark != NULL))
+					if (state[level].syntax == DQSYNTAX &&
+					    c != '\\' && c != '`' && c != '$' &&
+					    (c != '"' || (eofmark != NULL &&
+						newvarnest == 0)) &&
+					    (c != '}' || state[level].category != TSTATE_VAR_OLD))
 						USTPUTC('\\', out);
 					if (SQSYNTAX[c] == CCTL)
 						USTPUTC(CTLESC, out);
-					else if (eofmark == NULL)
+					else if (eofmark == NULL ||
+					    newvarnest > 0)
 						USTPUTC(CTLQUOTEMARK, out);
 					USTPUTC(c, out);
 					quotef++;
 				}
 				break;
 			case CSQUOTE:
-				if (eofmark == NULL)
-					USTPUTC(CTLQUOTEMARK, out);
-				syntax = SQSYNTAX;
+				USTPUTC(CTLQUOTEMARK, out);
+				state[level].syntax = SQSYNTAX;
 				break;
 			case CDQUOTE:
-				if (eofmark == NULL)
-					USTPUTC(CTLQUOTEMARK, out);
-				syntax = DQSYNTAX;
-				dblquote = 1;
+				USTPUTC(CTLQUOTEMARK, out);
+				state[level].syntax = DQSYNTAX;
 				break;
 			case CENDQUOTE:
-				if (eofmark != NULL && arinest == 0 &&
-				    varnest == 0) {
+				if (eofmark != NULL && newvarnest == 0)
 					USTPUTC(c, out);
-				} else {
-					if (arinest) {
-						syntax = ARISYNTAX;
-						dblquote = 0;
-					} else if (eofmark == NULL) {
-						syntax = BASESYNTAX;
-						dblquote = 0;
-					}
+				else {
+					if (state[level].category == TSTATE_ARITH)
+						state[level].syntax = ARISYNTAX;
+					else
+						state[level].syntax = BASESYNTAX;
 					quotef++;
 				}
 				break;
@@ -993,30 +1244,33 @@
 				PARSESUB();		/* parse substitution */
 				break;
 			case CENDVAR:	/* '}' */
-				if (varnest > 0) {
-					varnest--;
+				if (level > 0 &&
+				    (state[level].category == TSTATE_VAR_OLD ||
+				    state[level].category == TSTATE_VAR_NEW)) {
+					if (state[level].category == TSTATE_VAR_OLD)
+						state[level - 1].syntax = state[level].syntax;
+					else
+						newvarnest--;
+					level--;
 					USTPUTC(CTLENDVAR, out);
 				} else {
 					USTPUTC(c, out);
 				}
 				break;
 			case CLP:	/* '(' in arithmetic */
-				parenlevel++;
+				state[level].parenlevel++;
 				USTPUTC(c, out);
 				break;
 			case CRP:	/* ')' in arithmetic */
-				if (parenlevel > 0) {
+				if (state[level].parenlevel > 0) {
 					USTPUTC(c, out);
-					--parenlevel;
+					--state[level].parenlevel;
 				} else {
 					if (pgetc() == ')') {
-						if (--arinest == 0) {
+						if (level > 0 &&
+						    state[level].category == TSTATE_ARITH) {
+							level--;
 							USTPUTC(CTLENDARI, out);
-							syntax = prevsyntax;
-							if (syntax == DQSYNTAX)
-								dblquote = 1;
-							else
-								dblquote = 0;
 						} else
 							USTPUTC(')', out);
 					} else {
@@ -1030,12 +1284,15 @@
 				}
 				break;
 			case CBQUOTE:	/* '`' */
-				PARSEBACKQOLD();
+				out = parsebackq(out, &bqlist, 1,
+				    state[level].syntax == DQSYNTAX &&
+				    (eofmark == NULL || newvarnest > 0),
+				    state[level].syntax == DQSYNTAX || state[level].syntax == ARISYNTAX);
 				break;
 			case CEOF:
 				goto endword;		/* exit outer loop */
 			default:
-				if (varnest == 0)
+				if (level == 0)
 					goto endword;	/* exit outer loop */
 				USTPUTC(c, out);
 			}
@@ -1043,14 +1300,17 @@
 		}
 	}
 endword:
-	if (syntax == ARISYNTAX)
+	if (state[level].syntax == ARISYNTAX)
 		synerror("Missing '))'");
-	if (syntax != BASESYNTAX && ! parsebackquote && eofmark == NULL)
+	if (state[level].syntax != BASESYNTAX && eofmark == NULL)
 		synerror("Unterminated quoted string");
-	if (varnest != 0) {
+	if (state[level].category == TSTATE_VAR_OLD ||
+	    state[level].category == TSTATE_VAR_NEW) {
 		startlinno = plinno;
 		synerror("Missing '}'");
 	}
+	if (state != state_static)
+		parser_temp_free_upto(state);
 	USTPUTC('\0', out);
 	len = out - stackblock();
 	out = stackblock();
@@ -1073,7 +1333,6 @@
 /* end of readtoken routine */
 
 
-
 /*
  * Check to see whether we are at the end of the here document.  When this
  * is called, c is set to the first character of the next input line.  If
@@ -1190,7 +1449,11 @@
 			PARSEARITH();
 		} else {
 			pungetc();
-			PARSEBACKQNEW();
+			out = parsebackq(out, &bqlist, 0,
+			    state[level].syntax == DQSYNTAX &&
+			    (eofmark == NULL || newvarnest > 0),
+			    state[level].syntax == DQSYNTAX ||
+			    state[level].syntax == ARISYNTAX);
 		}
 	} else {
 		USTPUTC(CTLVAR, out);
@@ -1245,6 +1508,8 @@
 				subtype = VSERROR;
 				if (c == '}')
 					pungetc();
+				else if (c == '\n' || c == PEOF)
+					synerror("Unexpected end of line in substitution");
 				else
 					USTPUTC(c, out);
 			} else {
@@ -1261,6 +1526,8 @@
 			default:
 				p = strchr(types, c);
 				if (p == NULL) {
+					if (c == '\n' || c == PEOF)
+						synerror("Unexpected end of line in substitution");
 					if (flags == VSNUL)
 						STPUTC(':', out);
 					STPUTC(c, out);
@@ -1286,192 +1553,74 @@
 			pungetc();
 		}
 		STPUTC('=', out);
-		if (subtype != VSLENGTH && (dblquote || arinest))
+		if (subtype != VSLENGTH && (state[level].syntax == DQSYNTAX ||
+		    state[level].syntax == ARISYNTAX))
 			flags |= VSQUOTE;
 		*(stackblock() + typeloc) = subtype | flags;
-		if (subtype != VSNORMAL)
-			varnest++;
+		if (subtype != VSNORMAL) {
+			if (level + 1 >= maxnest) {
+				maxnest *= 2;
+				if (state == state_static) {
+					state = parser_temp_alloc(
+					    maxnest * sizeof(*state));
+					memcpy(state, state_static,
+					    MAXNEST_STATIC * sizeof(*state));
+				} else
+					state = parser_temp_realloc(state,
+					    maxnest * sizeof(*state));
+			}
+			level++;
+			state[level].parenlevel = 0;
+			if (subtype == VSMINUS || subtype == VSPLUS ||
+			    subtype == VSQUESTION || subtype == VSASSIGN) {
+				/*
+				 * For operators that were in the Bourne shell,
+				 * inherit the double-quote state.
+				 */
+				state[level].syntax = state[level - 1].syntax;
+				state[level].category = TSTATE_VAR_OLD;
+			} else {
+				/*
+				 * The other operators take a pattern,
+				 * so go to BASESYNTAX.
+				 * Also, ' and " are now special, even
+				 * in here documents.
+				 */
+				state[level].syntax = BASESYNTAX;
+				state[level].category = TSTATE_VAR_NEW;
+				newvarnest++;
+			}
+		}
 	}
 	goto parsesub_return;
 }
 
 
 /*
- * Called to parse command substitutions.  Newstyle is set if the command
- * is enclosed inside $(...); nlpp is a pointer to the head of the linked
- * list of commands (passed by reference), and savelen is the number of
- * characters on the top of the stack which must be preserved.
- */
-
-parsebackq: {
-	struct nodelist **nlpp;
-	int savepbq;
-	union node *n;
-	char *volatile str;
-	struct jmploc jmploc;
-	struct jmploc *const savehandler = handler;
-	int savelen;
-	int saveprompt;
-	const int bq_startlinno = plinno;
-
-	savepbq = parsebackquote;
-	if (setjmp(jmploc.loc)) {
-		if (str)
-			ckfree(str);
-		parsebackquote = 0;
-		handler = savehandler;
-		if (exception == EXERROR) {
-			startlinno = bq_startlinno;
-			synerror("Error in command substitution");
-		}
-		longjmp(handler->loc, 1);
-	}
-	INTOFF;
-	str = NULL;
-	savelen = out - stackblock();
-	if (savelen > 0) {
-		str = ckmalloc(savelen);
-		memcpy(str, stackblock(), savelen);
-	}
-	handler = &jmploc;
-	INTON;
-        if (oldstyle) {
-                /* We must read until the closing backquote, giving special
-                   treatment to some slashes, and then push the string and
-                   reread it as input, interpreting it normally.  */
-                char *out;
-                int c;
-                int savelen;
-                char *str;
-
-
-                STARTSTACKSTR(out);
-		for (;;) {
-			if (needprompt) {
-				setprompt(2);
-				needprompt = 0;
-			}
-			switch (c = pgetc()) {
-			case '`':
-				goto done;
-
-			case '\\':
-                                if ((c = pgetc()) == '\n') {
-					plinno++;
-					if (doprompt)
-						setprompt(2);
-					else
-						setprompt(0);
-					/*
-					 * If eating a newline, avoid putting
-					 * the newline into the new character
-					 * stream (via the STPUTC after the
-					 * switch).
-					 */
-					continue;
-				}
-                                if (c != '\\' && c != '`' && c != '$'
-                                    && (!dblquote || c != '"'))
-                                        STPUTC('\\', out);
-				break;
-
-			case '\n':
-				plinno++;
-				needprompt = doprompt;
-				break;
-
-			case PEOF:
-			        startlinno = plinno;
-				synerror("EOF in backquote substitution");
- 				break;
-
-			default:
-				break;
-			}
-			STPUTC(c, out);
-                }
-done:
-                STPUTC('\0', out);
-                savelen = out - stackblock();
-                if (savelen > 0) {
-                        str = ckmalloc(savelen);
-                        memcpy(str, stackblock(), savelen);
-			setinputstring(str, 1);
-                }
-        }
-	nlpp = &bqlist;
-	while (*nlpp)
-		nlpp = &(*nlpp)->next;
-	*nlpp = (struct nodelist *)stalloc(sizeof (struct nodelist));
-	(*nlpp)->next = NULL;
-	parsebackquote = oldstyle;
-
-	if (oldstyle) {
-		saveprompt = doprompt;
-		doprompt = 0;
-	}
-
-	n = list(0);
-
-	if (oldstyle)
-		doprompt = saveprompt;
-	else {
-		if (readtoken() != TRP)
-			synexpect(TRP);
-	}
-
-	(*nlpp)->n = n;
-        if (oldstyle) {
-		/*
-		 * Start reading from old file again, ignoring any pushed back
-		 * tokens left from the backquote parsing
-		 */
-                popfile();
-		tokpushback = 0;
-	}
-	while (stackblocksize() <= savelen)
-		growstackblock();
-	STARTSTACKSTR(out);
-	if (str) {
-		memcpy(out, str, savelen);
-		STADJUST(savelen, out);
-		INTOFF;
-		ckfree(str);
-		str = NULL;
-		INTON;
-	}
-	parsebackquote = savepbq;
-	handler = savehandler;
-	if (arinest || dblquote)
-		USTPUTC(CTLBACKQ | CTLQUOTE, out);
-	else
-		USTPUTC(CTLBACKQ, out);
-	if (oldstyle)
-		goto parsebackq_oldreturn;
-	else
-		goto parsebackq_newreturn;
-}
-
-/*
  * Parse an arithmetic expansion (indicate start of one and set state)
  */
 parsearith: {
 
-	if (++arinest == 1) {
-		prevsyntax = syntax;
-		syntax = ARISYNTAX;
-		USTPUTC(CTLARI, out);
-		if (dblquote)
-			USTPUTC('"',out);
-		else
-			USTPUTC(' ',out);
-	} else {
-		/*
-		 * we collapse embedded arithmetic expansion to
-		 * parenthesis, which should be equivalent
-		 */
-		USTPUTC('(', out);
+	if (level + 1 >= maxnest) {
+		maxnest *= 2;
+		if (state == state_static) {
+			state = parser_temp_alloc(
+			    maxnest * sizeof(*state));
+			memcpy(state, state_static,
+			    MAXNEST_STATIC * sizeof(*state));
+		} else
+			state = parser_temp_realloc(state,
+			    maxnest * sizeof(*state));
 	}
+	level++;
+	state[level].syntax = ARISYNTAX;
+	state[level].parenlevel = 0;
+	state[level].category = TSTATE_ARITH;
+	USTPUTC(CTLARI, out);
+	if (state[level - 1].syntax == DQSYNTAX)
+		USTPUTC('"',out);
+	else
+		USTPUTC(' ',out);
 	goto parsearith_return;
 }
 
@@ -1516,9 +1665,9 @@
  */
 
 int
-goodname(char *name)
+goodname(const char *name)
 {
-	char *p;
+	const char *p;
 
 	p = name;
 	if (! is_name(*p))
@@ -1553,11 +1702,11 @@
 
 
 STATIC void
-synerror(char *msg)
+synerror(const char *msg)
 {
 	if (commandname)
-		outfmt(&errout, "%s: %d: ", commandname, startlinno);
-	outfmt(&errout, "Syntax error: %s\n", msg);
+		outfmt(out2, "%s: %d: ", commandname, startlinno);
+	outfmt(out2, "Syntax error: %s\n", msg);
 	error((char *)NULL);
 }
 
@@ -1569,7 +1718,10 @@
 #ifndef NO_HISTORY
 	if (!el)
 #endif
+	{
 		out2str(getprompt(NULL));
+		flushout(out2);
+	}
 }
 
 /*
@@ -1582,13 +1734,14 @@
 	static char ps[PROMPTLEN];
 	char *fmt;
 	int i, j, trim;
+	static char internal_error[] = "<internal prompt error>";
 
 	/*
 	 * Select prompt format.
 	 */
 	switch (whichprompt) {
 	case 0:
-		fmt = "";
+		fmt = nullstr;
 		break;
 	case 1:
 		fmt = ps1val();
@@ -1597,7 +1750,7 @@
 		fmt = ps2val();
 		break;
 	default:
-		return "<internal prompt error>";
+		return internal_error;
 	}
 
 	/*
Index: bin/sh/jobs.c
===================================================================
--- bin/sh/jobs.c	(revision 209229)
+++ bin/sh/jobs.c	(working copy)
@@ -91,8 +91,9 @@
 STATIC struct job *getjob(char *);
 STATIC pid_t dowait(int, struct job *);
 STATIC pid_t waitproc(int, int *);
+STATIC void checkzombies(void);
 STATIC void cmdtxt(union node *);
-STATIC void cmdputs(char *);
+STATIC void cmdputs(const char *);
 #if JOBS
 STATIC void setcurjob(struct job *);
 STATIC void deljob(struct job *);
@@ -146,7 +147,7 @@
 		do { /* while we are in the background */
 			initialpgrp = tcgetpgrp(ttyfd);
 			if (initialpgrp < 0) {
-out:				out2str("sh: can't access tty; job control turned off\n");
+out:				out2fmt_flush("sh: can't access tty; job control turned off\n");
 				mflag = 0;
 				return;
 			}
@@ -400,7 +401,7 @@
 	struct job *jp;
 
 	TRACE(("showjobs(%d) called\n", change));
-	while (dowait(0, (struct job *)NULL) > 0);
+	checkzombies();
 	for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) {
 		if (! jp->used)
 			continue;
@@ -742,6 +743,8 @@
 	TRACE(("forkshell(%%%d, %p, %d) called\n", jp - jobtab, (void *)n,
 	    mode));
 	INTOFF;
+	if (mode == FORK_BG)
+		checkzombies();
 	flushall();
 	pid = fork();
 	if (pid == -1) {
@@ -757,6 +760,7 @@
 		TRACE(("Child shell %d\n", (int)getpid()));
 		wasroot = rootshell;
 		rootshell = 0;
+		handler = &main_handler;
 		closescript();
 		INTON;
 		clear_traps();
@@ -862,6 +866,7 @@
 {
 #if JOBS
 	pid_t mypgrp = getpgrp();
+	int propagate_int = jp->jobctl && jp->foreground;
 #endif
 	int status;
 	int st;
@@ -899,6 +904,11 @@
 		else
 			CLEAR_PENDING_INT;
 	}
+#if JOBS
+	else if (rootshell && iflag && propagate_int &&
+			WIFSIGNALED(status) && WTERMSIG(status) == SIGINT)
+		kill(getpid(), SIGINT);
+#endif
 	INTON;
 	return st;
 }
@@ -1046,7 +1056,7 @@
 		if (jp->used == 0)
 			continue;
 		if (jp->state == JOBSTOPPED) {
-			out2str("You have stopped jobs.\n");
+			out2fmt_flush("You have stopped jobs.\n");
 			job_warning = 2;
 			return (1);
 		}
@@ -1055,6 +1065,15 @@
 	return (0);
 }
 
+
+STATIC void
+checkzombies(void)
+{
+	while (njobs > 0 && dowait(0, NULL) > 0)
+		;
+}
+
+
 /*
  * Return a string identifying a command (to be printed by the
  * jobs command.
@@ -1082,7 +1101,7 @@
 {
 	union node *np;
 	struct nodelist *lp;
-	char *p;
+	const char *p;
 	int i;
 	char s[2];
 
@@ -1211,9 +1230,10 @@
 
 
 STATIC void
-cmdputs(char *s)
+cmdputs(const char *s)
 {
-	char *p, *q;
+	const char *p;
+	char *q;
 	char c;
 	int subtype = 0;
 
Index: bin/sh/arith.y
===================================================================
--- bin/sh/arith.y	(revision 209229)
+++ bin/sh/arith.y	(working copy)
@@ -85,9 +85,9 @@
 	ARITH_LPAREN expr ARITH_RPAREN
 		{ $$ = $2; } |
 	expr ARITH_OR expr
-		{ $$ = $1 ? $1 : $3 ? $3 : 0; } |
+		{ $$ = $1 || $3; } |
 	expr ARITH_AND expr
-		{ $$ = $1 ? ( $3 ? $3 : 0 ) : 0; } |
+		{ $$ = $1 && $3; } |
 	expr ARITH_BOR expr
 		{ $$ = $1 | $3; } |
 	expr ARITH_BXOR expr
@@ -265,7 +265,7 @@
 #define YYPARSE_PARAM_TYPE arith_t *
 #define YYPARSE_PARAM result
 
-char *arith_buf, *arith_startbuf;
+const char *arith_buf, *arith_startbuf;
 
 int yylex(void);
 int yyparse(YYPARSE_PARAM_TYPE);
@@ -284,10 +284,12 @@
 }
 
 arith_t
-arith(char *s)
+arith(const char *s)
 {
 	arith_t result;
+	struct stackmark smark;
 
+	setstackmark(&smark);
 	arith_buf = arith_startbuf = s;
 
 	INTOFF;
@@ -295,11 +297,13 @@
 	arith_lex_reset();	/* Reprime lex. */
 	INTON;
 
+	popstackmark(&smark);
+
 	return result;
 }
 
 static void
-yyerror(char *s)
+yyerror(const char *s)
 {
 
 	yyerrok;
@@ -314,7 +318,7 @@
 int
 expcmd(int argc, char **argv)
 {
-	char *p;
+	const char *p;
 	char *concat;
 	char **ap;
 	arith_t i;
@@ -354,7 +358,7 @@
 	printf("%d\n", exp(argv[1]));
 }
 
-error(char *s)
+error(const char *s)
 {
 	fprintf(stderr, "exp: %s\n", s);
 	exit(1);
Index: bin/sh/var.h
===================================================================
--- bin/sh/var.h	(revision 209229)
+++ bin/sh/var.h	(working copy)
@@ -77,6 +77,7 @@
 extern struct var vps4;
 #ifndef NO_HISTORY
 extern struct var vhistsize;
+extern struct var vterm;
 #endif
 
 /*
@@ -96,19 +97,21 @@
 #define optindval()	(voptind.text + 7)
 #ifndef NO_HISTORY
 #define histsizeval()	(vhistsize.text + 9)
+#define termval()	(vterm.text + 5)
 #endif
 
 #define mpathset()	((vmpath.flags & VUNSET) == 0)
 
 void initvar(void);
-void setvar(char *, char *, int);
+void setvar(const char *, const char *, int);
 void setvareq(char *, int);
 struct strlist;
 void listsetvar(struct strlist *);
-char *lookupvar(char *);
-char *bltinlookup(char *, int);
+char *lookupvar(const char *);
+char *bltinlookup(const char *, int);
+void bltinsetlocale(void);
+void bltinunsetlocale(void);
 char **environment(void);
-void shprocvar(void);
 int showvarscmd(int, char **);
 int exportcmd(int, char **);
 int localcmd(int, char **);
@@ -116,5 +119,5 @@
 void poplocalvars(void);
 int setvarcmd(int, char **);
 int unsetcmd(int, char **);
-int unsetvar(char *);
-int setvarsafe(char *, char *, int);
+int unsetvar(const char *);
+int setvarsafe(const char *, const char *, int);

--=-=-=--



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?86pqzkfs19.fsf>