/*
 * $Id: builtin.c,v 1.68 1995/05/30 02:47:41 coleman Exp coleman $
 *
 * builtin.c - builtin commands
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 1992-1995 Paul Falstad
 * All rights reserved.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software.
 *
 * In no event shall Paul Falstad or the Zsh Development Group be liable
 * to any party for direct, indirect, special, incidental, or consequential
 * damages arising out of the use of this software and its documentation,
 * even if Paul Falstad and the Zsh Development Group have been advised of
 * the possibility of such damage.
 *
 * Paul Falstad and the Zsh Development Group specifically disclaim any
 * warranties, including, but not limited to, the implied warranties of
 * merchantability and fitness for a particular purpose.  The software
 * provided hereunder is on an "as is" basis, and Paul Falstad and the
 * Zsh Development Group have no obligation to provide maintenance,
 * support, updates, enhancements, or modifications.
 *
 */

#include "zsh.h"

/**** "header" part of the file -- would need to be #included by any file
defining a bin_ handler function ****/

extern int sigtrapped[VSIGCOUNT];
extern List sigfuncs[VSIGCOUNT];

#define makecond() allocnode(N_COND)

/* builtin flags */

#define BINF_PLUSOPTS	1	/* +xyz legal */
#define BINF_R		2	/* this is r (fc -e -) */
#define BINF_PRINTOPTS	4
#define BINF_SETOPTS	8
#define BINF_FCOPTS	16
#define BINF_TYPEOPT	32
#define BINF_TYPEOPTS	(BINF_TYPEOPT|BINF_PLUSOPTS)
#define BINF_ECHOPTS	64

/* Builtin function numbers; used by handler functions that handle more *
 * than one builtin.  Note that builtins such as compctl, that are not  *
 * overloaded, don't get a number.                                      */

#define BIN_TYPESET   0
#define BIN_BG        1
#define BIN_FG        2
#define BIN_JOBS      3
#define BIN_WAIT      4
#define BIN_DISOWN    5
#define BIN_BREAK     6
#define BIN_CONTINUE  7
#define BIN_EXIT      8
#define BIN_RETURN    9
#define BIN_CD       10
#define BIN_POPD     11
#define BIN_PUSHD    12
#define BIN_PRINT    13
#define BIN_EVAL     14
#define BIN_SCHED    15
#define BIN_FC       16
#define BIN_PUSHLINE 17
#define BIN_LOGOUT   18
#define BIN_BUILTIN  19
#define BIN_TEST     20
#define BIN_BRACKET  21
#define BIN_EXPORT   22
#define BIN_ECHO     23

/**** the core of the builtin command system ****/

static void prtopt _((int set));

/* This structure is required for the following array of builtins. */

struct bincmd {
    char *name;			/* go on ... guess!                                   */
    int (*handlerfunc) _((char *, char **, char *, int));
				/* address of the function that executes the command  */
    int minargs;		/* min # of args                                      */
    int maxargs;		/* max # of args, or -1 for no limit                  */
    int flags;			/* BINF_flags (see above)                             */
    int funcid;			/* xbins (see above) for overloaded handlerfuncs      */
    char *optstr;		/* string of legal options                            */
    char *defopts;		/* options set by default for overloaded handlerfuncs */
};

static char *auxdata;
static int auxlen;
static int showflag = 0, showflag2 = 0;

/* For the REAL builtins -- the ones that don't need a handler function. */

#define NULLBINCMD ((int (*) _((char *,char **,char *,int))) 0)

/* This is the main list of builtin commands.  See the structure above for the
meaning of the fields. */

struct bincmd builtins[] =
{
    {"[", bin_test, 0, -1, 0, BIN_BRACKET, NULL, NULL},
    {".", bin_dot, 1, -1, 0, 0, NULL, NULL},
    {":", bin_true, 0, -1, 0, 0, NULL, NULL},
    {"alias", bin_alias, 0, -1, 0, 0, "gamr", NULL},
    {"autoload", bin_typeset, 0, -1, BINF_TYPEOPTS, 0, "txv", "fu"},
    {"bg", bin_fg, 0, -1, 0, BIN_BG, NULL, NULL},
    {"bindkey", bin_bindkey, 0, -1, 0, 0, "asvemdr", NULL},
    {"break", bin_break, 0, 1, 0, BIN_BREAK, NULL, NULL},
    {"builtin", NULLBINCMD, 0, 0, 0, BIN_BUILTIN, NULL, NULL},
    {"bye", bin_break, 0, 1, 0, BIN_EXIT, NULL, NULL},
    {"cd", bin_cd, 0, 2, 0, BIN_CD, NULL, NULL},
    {"chdir", bin_cd, 0, 2, 0, BIN_CD, NULL, NULL},
    {"compctl", bin_compctl, 0, -1, 0, 0, NULL, NULL},
    {"continue", bin_break, 0, 1, 0, BIN_CONTINUE, NULL, NULL},
    {"declare", bin_typeset, 0, -1, BINF_TYPEOPTS, 0, "LRZfilrtux", NULL},
    {"dirs", bin_dirs, 0, -1, 0, 0, "v", NULL},
    {"disable", bin_disable, 0, -1, 0, 0, "-m", NULL},
    {"disown", bin_fg, 0, -1, 0, BIN_DISOWN, NULL, NULL},
    {"echo", bin_print, 0, -1, BINF_PRINTOPTS | BINF_ECHOPTS, BIN_ECHO, "neE", "-"},
    {"echotc", bin_echotc, 1, -1, 0, 0, NULL, NULL},
    {"enable", bin_enable, 0, -1, 0, 0, "m", NULL},
    {"eval", bin_eval, 0, -1, 0, BIN_EVAL, NULL, NULL},
    {"exit", bin_break, 0, 1, 0, BIN_EXIT, NULL, NULL},
    {"export", bin_typeset, 0, -1, BINF_TYPEOPTS, BIN_EXPORT, "LRZfilrtu", "x"},
    {"false", bin_false, 0, -1, 0, 0, NULL, NULL},
    {"fc", bin_fc, 0, -1, BINF_FCOPTS, BIN_FC, "nlreIRWAdDfEim", NULL},
    {"fg", bin_fg, 0, -1, 0, BIN_FG, NULL, NULL},
    {"functions", bin_typeset, 0, -1, BINF_TYPEOPTS, 0, "tum", "f"},
    {"getln", bin_read, 0, -1, 0, 0, "ecnAlE", "zr"},
    {"getopts", bin_getopts, 2, -1, 0, 0, NULL, NULL},
    {"hash", bin_hash, 0, 2, 0, 0, "dfr", NULL},
    {"history", bin_fc, 0, -1, 0, BIN_FC, "nrdDfEim", "l"},
    {"integer", bin_typeset, 0, -1, BINF_TYPEOPTS, 0, "lrtux", "i"},
    {"jobs", bin_fg, 0, -1, 0, BIN_JOBS, "lpZrs", NULL},
    {"kill", bin_kill, 0, -1, 0, 0, NULL, NULL},
    {"let", bin_let, 1, -1, 0, 0, NULL, NULL},
    {"limit", bin_limit, 0, -1, 0, 0, "sh", NULL},
    {"local", bin_typeset, 0, -1, BINF_TYPEOPTS, 0, "LRZilrtu", NULL},
    {"log", bin_log, 0, 0, 0, 0, NULL, NULL},
    {"logout", bin_break, 0, 1, 0, BIN_LOGOUT, NULL, NULL},

#if defined(ZSH_MEM) & defined(ZSH_MEM_DEBUG)
    {"mem", bin_mem, 0, 0, 0, 0, "v", NULL},
#endif

    {"popd", bin_cd, 0, 2, 0, BIN_POPD, NULL, NULL},
    {"print", bin_print, 0, -1, BINF_PRINTOPTS, BIN_PRINT, "RDPnrslzNu0123456789pioOc-", NULL},
    {"pushd", bin_cd, 0, 2, 0, BIN_PUSHD, NULL, NULL},
    {"pushln", bin_print, 0, -1, BINF_PRINTOPTS, BIN_PRINT, NULL, "-nz"},
    {"pwd", bin_pwd, 0, 0, 0, 0, NULL, NULL},
    {"r", bin_fc, 0, -1, BINF_R, BIN_FC, "nrl", NULL},
    {"read", bin_read, 0, -1, 0, 0, "rzu0123456789pkqecnAlE", NULL},
    {"readonly", bin_typeset, 0, -1, BINF_TYPEOPTS, 0, "LRZfiltux", "r"},
    {"rehash", bin_rehash, 0, 0, 0, 0, "df", NULL},
    {"return", bin_break, 0, 1, 0, BIN_RETURN, NULL, NULL},
    {"sched", bin_sched, 0, -1, 0, 0, NULL, NULL},
    {"set", bin_set, 0, -1, BINF_SETOPTS | BINF_PLUSOPTS, 0, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZaefghjklmnopsuvwxy", NULL},
    {"setopt", bin_setopt, 0, -1, BINF_PLUSOPTS, 0, "0123456789BCDEFGHIJKLMNOPQRSTUVWXYZaefghjklmnopsuvwxy", NULL},
    {"shift", bin_shift, 0, -1, 0, 0, NULL, NULL},
    {"source", bin_dot, 1, -1, 0, 0, NULL, NULL},
    {"suspend", bin_suspend, 0, 0, 0, 0, "f", NULL},
    {"test", bin_test, 0, -1, 0, BIN_TEST, NULL, NULL},
    {"ttyctl", bin_ttyctl, 0, 0, 0, 0, "fu", NULL},
    {"times", bin_times, 0, 0, 0, 0, NULL, NULL},
    {"trap", bin_trap, 0, -1, 0, 0, NULL, NULL},
    {"true", bin_true, 0, -1, 0, 0, NULL, NULL},
    {"type", bin_whence, 0, -1, 0, 0, "pfam", "v"},
    {"typeset", bin_typeset, 0, -1, BINF_TYPEOPTS, 0, "LRZfilrtuxm", NULL},
    {"ulimit", bin_ulimit, 0, 1, 0, 0, "HSacdflmnopstv", NULL},
    {"umask", bin_umask, 0, 1, 0, 0, NULL, NULL},
    {"unalias", bin_unalias, 1, -1, 0, 0, "m", NULL},
    {"unfunction", bin_unhash, 1, -1, 0, 0, "m", NULL},
    {"unhash", bin_unhash, 1, -1, 0, 0, "m", NULL},
    {"unlimit", bin_unlimit, 0, -1, 0, 0, "h", NULL},
    {"unset", bin_unset, 1, -1, 0, 0, "m", NULL},
    {"unsetopt", bin_setopt, 0, -1, BINF_PLUSOPTS, 1, "0123456789BCDEFGHIJKLMNOPQRSTUWXYZabefghjklmnopsuvwxy", NULL},
    {"vared", bin_vared, 1, 6, 0, 0, NULL, NULL},
    {"wait", bin_fg, 0, -1, 0, BIN_WAIT, NULL, NULL},
    {"whence", bin_whence, 0, -1, 0, 0, "pvcfam", NULL},
    {"which", bin_whence, 0, -1, 0, 0, "pam", "c"},
    {NULL, NULLBINCMD, 0, 0, 0, 0, NULL, NULL}
};

/* return a specific set of the BINF_* flags */
#define fset(X) (flags & X)

/* execute a builtin handler function after parsing the arguments */

/**/
int
execbin(Lklist args, Cmdnam cnode)
{
    struct bincmd *b;
    char ops[128], *arg, *pp, *name, **argv, **oargv, *optstr;
    int opchar, flags, sense, argc = 0, execop;
    Lknode n;
    char *oxarg, *xarg = NULL;

    /* initialise local variables */
    auxdata = NULL;
    auxlen = 0;
    for (opchar = 0; opchar != 128; opchar++)
	ops[opchar] = 0;
    name = (char *)ugetnode(args);
    b = builtins + cnode->u.binnum;

    /* the "builtin" builtin is handled specially */
    while (b->funcid == BIN_BUILTIN) {
	Cmdnam cname;

	/* need at least one argument -- the builtin command name */
	if (!(name = (char *)ugetnode(args))) {
	    zerrnam("builtin", "command name expected", NULL, 0);
	    return 1;
	}
	/* search for the specified builtin */
	if ((cname = (Cmdnam) gethnode(name, cmdnamtab)) &&
	    (cname->flags & BUILTIN) &&
	    !(cname->flags & EXCMD))
	    b = builtins + cname->u.binnum;
	else
	    for (b = builtins; b->name; b++)
		if (!strcmp(name, b->name))
		    break;
	/* handle case of a non-existent builtin */
	if (!b->name) {
	    zerrnam("builtin", "no such builtin: %s", name, 0);
	    return 1;
	}
    }
    /* get some information about the command */
    flags = b->flags;
    arg = (char *)ugetnode(args);
    optstr = b->optstr;

    /* Sort out the options. */
    if (fset(BINF_ECHOPTS) && isset(BSDECHO))
	ops['E'] = 1;
    if (optstr)
	/* while arguments look like options ... */
	while (arg &&
	       ((sense = (*arg == '-')) ||
		 (fset(BINF_PLUSOPTS) && *arg == '+')) &&
	       (fset(BINF_PLUSOPTS) || !atoi(arg))) {
	    /* unrecognised options to echo etc. are not really options */
	    if (fset(BINF_ECHOPTS)) {
		char *p = arg;
		while (*++p && strchr(optstr, (int)*p));
		if (*p)
		    break;
	    }
	    /* save the options in xarg, for execution tracing */
	    if (xarg) {
		oxarg = tricat(xarg, " ", arg);
		zsfree(xarg);
		xarg = oxarg;
	    } else
		xarg = ztrdup(arg);
	    /* handle -- or - (ops['-']), and + (ops['-'] and ops['+']) */
	    if (arg[1] == '-')
		arg++;
	    if (!arg[1]) {
		ops['-'] = 1;
		if (!sense)
		    ops['+'] = 1;
	    } else
		ops['@'] = 1;
	    /* save options in ops, as long as they are in b->optstr */
	    execop = -1;
	    while (*++arg)
		if (strchr(optstr, execop = (int)*arg))
		    ops[(int)*arg] = (sense) ? 1 : 2;
		else
		    break;
	    /* the above loop may have exited on an invalid option */
	    if (*arg) {
		zerr("bad option: %c", NULL, *arg);
		zsfree(xarg);
		return 1;
	    }
	    /* handle the special "set" options */
	    arg = (char *)ugetnode(args);
	    if (fset(BINF_SETOPTS) && execop == 'o') {
		int c;

		if (!arg)
		    prtopt(sense);
		else {
		    c = optlookup(arg);
		    if (c == -1) {
			zerr("bad option: %s", arg, 0);
			zsfree(xarg);
			return 1;
		    } else {
			if (c == INTERACTIVE)
			    zerr("can't change option: %s", arg, 0);
			else
			    ops[c] = ops['o'];
			arg = (char *)ugetnode(args);
		    }
		}
	    }
	    /* for the "print" builtin, the options after -R are treated as
	    options to "echo" */
	    if (fset(BINF_PRINTOPTS) && ops['R']) {
		optstr = "ne";
		flags |= BINF_ECHOPTS;
	    }
	    /* the option -- indicates the end of the options */
	    if (ops['-'])
		break;
	    /* for "set", -A takes an extra argument and is the last option */
	    if (fset(BINF_SETOPTS) && ops['A']) {
		auxdata = arg;
		arg = (char *)ugetnode(args);
		break;
	    }
	    /* for "fc", -e takes an extra argument */
	    if (fset(BINF_FCOPTS) && execop == 'e') {
		auxdata = arg;
		arg = (char *)ugetnode(args);
	    }
	    /* for "typeset", -L, -R, -Z and -i take a numeric extra argument */
	    if (fset(BINF_TYPEOPT) && (execop == 'L' || execop == 'R' ||
				       execop == 'Z' || execop == 'i') &&
		                      arg && idigit(*arg)) {
		auxlen = atoi(arg);
		arg = (char *)ugetnode(args);
	    }
	}
    if (fset(BINF_R))
	auxdata = "-";
    /* handle built-in options, for overloaded handler functions */
    if ((pp = b->defopts))
	while (*pp)
	    ops[(int)*pp++] = 1;

    /* Set up the argument list. */
    if (arg) {
	/* count the arguments */
	argc = 1;
	n = firstnode(args);
	while (n)
	    argc++, incnode(n);
    }
    /* Get the actual arguments, into argv.  oargv saves the beginning of the
    array for later reference. */
    oargv = argv = (char **)ncalloc(sizeof(char **) * (argc + 1));
    if ((*argv++ = arg))
	while ((*argv++ = (char *)ugetnode(args)));
    argv = oargv;
    if (errflag) {
	zsfree(xarg);
	return 1;
    }
    /* check that the argument count lies within the specified bounds */
    if (argc < b->minargs || (argc > b->maxargs && b->maxargs != -1)) {
	zerrnam(name, (argc < b->minargs)
		? "not enough arguments" : "too many arguments", NULL, 0);
	zsfree(xarg);
	return 1;
    }

    /* display execution trace information, if required */
    if (isset(XTRACE)) {
	fprintf(stderr, "%s%s", (prompt4) ? prompt4 : "", name);
	if (xarg)
	    fprintf(stderr, " %s", xarg);
	while (*oargv)
	    fprintf(stderr, " %s", *oargv++);
	fputc('\n', stderr);
	fflush(stderr);
    }
    zsfree(xarg);
    /* call the handler function, and return its return value */
    return (*(b->handlerfunc)) (name, argv, ops, b->funcid);
}

/* add builtins to the command hash table */

/**/
void
addbuiltins(void)
{
    struct cmdnam *c;
    struct bincmd *b;
    int bn;

    for (bn = 0, b = builtins; b->name; b++, bn++) {
	c = (Cmdnam) zcalloc(sizeof *c);
	c->flags = BUILTIN;
	c->u.binnum = bn;
	addhnode(ztrdup(b->name), c, cmdnamtab, freecmdnam);
    }
}

/* disable: disable a builtin command (so that it can only be executed via the
"builtin" builtin (unless that's been disabled too!)) */

/**/
int
bin_disable(char *name, char **argv, char *ops, int func)
{
    Cmdnam chn, chn2, nchn;
    Comp com;

    /* Given no arguments, list the disabled commands.  Useful for compctl on
    enable. */
    if (!*argv) {
	listhtable(cmdnamtab, (HFunc) pdisabledcmd);
	return 0;
    }
    if (ops['m']) {
	/* With -m option, treat arguments as glob patterns.  "disable -m '*'"
	is not a recommended procedure. */
	for (; *argv; argv++) {
	    /* expand argument */
	    tokenize(*argv);
	    if (!(com = parsereg(*argv))) {
		untokenize(*argv);
		zwarnnam(name, "bad pattern : %s", *argv, 0);
		continue;
	    }
	    if (!strncmp(*argv, "TRAP", 4)) {
		/* arguments are signal traps -- unset the specified traps */
		char trapname[20];
		int t;

		strncpy(trapname, "TRAP", sizeof(trapname));
		for (t = 0; t < VSIGCOUNT; t++) {
		    strncpy(trapname + 4, sigs[t], sizeof(trapname) - 5);
		    if (domatch(trapname, com, 0)) {
			unsettrap(t);
			chn = (Cmdnam) zcalloc(sizeof *chn);
			chn->flags |= DISABLED;
			addhnode(ztrdup(trapname), chn, cmdnamtab, freecmdnam);
		    }
		}
	    } else {
		/* Arguments are normal builtins.  "disable '*'" is perfectly
		safe, because '*' isn't a builtin (yet!). */
		int t, n;

		n = cmdnamtab->hsize;
		for (t = 0; t < n; t++)
		    for (chn = (Cmdnam) cmdnamtab->nodes[t]; chn; chn = nchn) {
			nchn = (Cmdnam) chn->next;
			if (domatch(chn->nam, com, 0)) {
			    chn2 = (Cmdnam) zcalloc(sizeof *chn2);
			    chn2->flags |= DISABLED;
			    addhnode(ztrdup(chn->nam), chn2, cmdnamtab,
				     freecmdnam);
			}
		    }
	    }
	}
    } else {
	/* take arguments literally -- do not glob */
	char **p, buf[PATH_MAX];

	while (*argv) {
	    if (!strncmp(*argv, "TRAP", 4))
		unsettrap(getsignum(*argv + 4));
	    chn = (Cmdnam) zcalloc(sizeof *chn);
	    for (p = path; *p; p++) {
		strcpy(buf, *path);
		strcat(buf, "/");
		strcat(buf, *argv);
		if (iscom(buf)) {
		    chn->u.name = path;
		    break;
		}
	    }
	    chn->flags |= *p ? EXCMD : DISABLED;
	    addhnode(ztrdup(*argv++), chn, cmdnamtab, freecmdnam);
	}
    }
    return 0;
}

/* Print command name, if command is a disabled builtin. */

/**/
void
pdisabledcmd(char *s, Cmdnam cc)
{
    if (cc->flags & DISABLED)
	printf("%s\n", s);
}

/* Enable builtin command.  With no arguments, list all currently enabled
commands. */

/**/
int
bin_enable(char *name, char **argv, char *ops, int whocares)
{
    struct cmdnam *c;
    struct bincmd *b;
    int bn, ret = 0;
    Comp com;

    if (!*argv) {
	/* List enabled commands and return */
	listhtable(cmdnamtab, (HFunc) penabledcmd);
	return 0;
    }
    for (; *argv; argv++) {
	if (ops['m']) {
	    /* -m option -- treat args as patterns, rather than command names.
	    This bit expands the current argument into a list of matching
	    names.  The same thing appears all over this file. */
	    tokenize(*argv);
	    if (!(com = parsereg(*argv))) {
		ret = 1;
		untokenize(*argv);
		zwarnnam(name, "bad pattern : %s", *argv, 0);
		continue;
	    }
	    /* Now loop over the results of the expansion. */
	    for (bn = 0, b = builtins; b->name; b++, bn++)
		if (domatch(b->name, com, 0)) {
		    c = (Cmdnam) zcalloc(sizeof *c);
		    c->flags = BUILTIN;
		    c->u.binnum = bn;
		    addhnode(ztrdup(b->name), c, cmdnamtab, freecmdnam);
		}
	} else {
	    /* Args are simply command names.  Search for the proper command,
	    and enable it. */
	    for (bn = 0, b = builtins; b->name; b++, bn++)
		if (!strcmp(*argv, b->name))
		    break;
	    if (!b->name) {
		zerrnam(name, "no such builtin: %s", *argv, 0);
		ret = 1;
	    } else {
		c = (Cmdnam) zcalloc(sizeof *c);
		c->flags = BUILTIN;
		c->u.binnum = bn;
		addhnode(ztrdup(b->name), c, cmdnamtab, freecmdnam);
	    }
	}
    }
    return ret;
}

/* Print command name, if command is an enabled builtin. */

/**/
void
penabledcmd(char *s, Cmdnam cc)
{
    if (cc->flags & BUILTIN)
	printf("%s\n", s);
}

/* set: either set the shell options, or set the shell arguments, or declare an
array, or show various things */

/**/
int
bin_set(char *name, char **argv, char *ops, int func)
{
    struct option *opp;
    char **x;

    if (((ops['+'] && ops['-']) || !ops['-']) && !ops['@'] && !*argv) {
	/* show the parameters, possibly with values */
	showflag = ~0;
	showflag2 = ops[(int)'+'];
	listhtable(paramtab, (HFunc) printparam);
    }
    /* set/unset shell options */
    for (opp = optns; opp->name; opp++)
	if (ops[(int)opp->id] == 1)
	    opts[(int)opp->id] = OPT_SET;
	else if (ops[(int)opp->id] == 2) {
	    opts[(int)opp->id] = OPT_UNSET;
#ifdef HAVE_SETUID
	    if (opp->id == PRIVILEGED)
		setuid(getuid()), setgid(getgid());
#endif
	}
    if (ops['A'] && !auxdata) {
	/* display arrays */
	showflag = PMFLAG_A;
	showflag2 = ops[(int)'+'];
	listhtable(paramtab, (HFunc) printparam);
    }
    if (!*argv && !ops['-'])
	return 0;
    permalloc();
    x = arrdup(argv);
    heapalloc();
    if (ops['A'])
	/* create an array with the specified elements */
	setaparam(auxdata, x);
    else {
	/* set shell arguments */
	freearray(pparams);
	pparams = x;
    }
    return 0;
}

/* setopt, unsetopt */

/**/
int
bin_setopt(char *nam, char **args, char *ops, int isun)
{
    struct option *opp;
    int c, match;

    match = ops[(int)'m'];
    ops['m'] = 0;
    /* With no arguments, display options.  The output format is determined by
    the prtopt function (above). */
    if (!ops['@'] && !*args) {
	prtopt(!isun);
	return 0;
    }

    /* set/unset options specified by letter codes, rather than by name.
    The options appear to the function as options, rather than arguments. */
    for (opp = optns; opp->name; opp++)
	if (ops[opp->id] == 1 + isun)
	    opts[(int)opp->id] = OPT_SET;
	else if (ops[(int)opp->id] == 2 - isun) {
	    opts[(int)opp->id] = OPT_UNSET;
#ifdef HAVE_SETUID
	    if (opp->id == PRIVILEGED)
		setuid(getuid()), setgid(getgid());
#endif
	}
    if (!match)
	/* Not globbing the arguments -- arguments are simply option names. */
	while (*args) {
	    c = optlookup(*args++);
	    if (c != -1) {
#ifdef HAVE_SETUID
		if (c == PRIVILEGED && isun)
		    setuid(getuid()), setgid(getgid());
#endif
		if (c == INTERACTIVE)
		    zwarnnam(nam, "can't change option: %s", args[-1], 0);
		else
		    opts[c] = (isun) ? OPT_UNSET : OPT_SET;
	    } else {
		zwarnnam(nam, "no such option: %s", args[-1], 0);
		return 1;
	    }
    } else {
	/* Globbing option (-m) set. */
	Comp com;
	struct option *o;

	while (*args) {
	    /* Expand the current arg. */
	    tokenize(*args);
	    if (!(com = parsereg(*args))) {
		untokenize(*args);
		zwarnnam(nam, "bad pattern : %s", *args, 0);
		continue;
	    }
	    /* Loop over expansions. */
	    for (o = optns; o->name; o++)
		if (o->id != INTERACTIVE && o->id != MONITOR &&
		    o->id != PRIVILEGED && domatch(o->name, com, 0))
		    opts[(int)o->id] = (isun) ? OPT_UNSET : OPT_SET;
	    args++;
	}
    }
    return 0;
}

/* print options */

static void
prtopt(int set)
{
    struct option *opp;

    if (isset(KSHOPTIONPRINT)) {
	/* ksh-style option display -- list all options, with an indication
	of current status */
	printf("Current option settings\n");
	for (opp = optns; opp->name; opp++)
	    printf("%-20s%s\n", opp->name, isset(opp->id) ? "on" : "off");
    } else
	/* list all options that are on, or all that are off */
	for (opp = optns; opp->name; opp++)
	    if ((!set) == (!isset(opp->id)))
		puts(opp->name);
}

/* identify an option name */

/**/
int
optlookup(char *s)
{
    char *t;
    struct option *o;

    t = s = dupstring(s);
    /* exorcise underscores, and change to lowercase */
    while (*t)
	if (*t == '_')
	    chuck(t);
	else {
	    *t = tulower(*t);
	    t++;
	}
    /* search for name in table */
    for (o = optns; o->name; o++)
	if (!strcmp(o->name, s))
	    return (int)o->id;
    return -1;
}

/**** job control builtins ****/

/* bg, disown, fg, jobs, wait: most of the job control commands are     *
 * here.  They all take the same type of argument.  Exception: wait can *
 * take a pid or a job specifier, whereas the others only work on jobs. */

/**/
int
bin_fg(char *name, char **argv, char *ops, int func)
{
    int job, lng, firstjob = -1, retval = 0;

    if (ops['Z']) {
	if (*argv)
	    strcpy(hackzero, *argv);
	return 0;
    }

    lng = (ops['l']) ? 1 : (ops['p']) ? 2 : 0;
    if ((func == BIN_FG || func == BIN_BG) && !jobbing) {
	/* oops... maybe bg and fg should have been disabled? */
	zwarnnam(name, "no job control in this shell.", NULL, 0);
	return 1;
    }

    /* If necessary, update job table. */
    if (unset(NOTIFY))
	scanjobs();

    /* Make sure we have a suitable previous job set. */
    if (curjob != -1 && !(jobtab[curjob].stat & STAT_INUSE)) {
	curjob = prevjob;
	setprevjob();
	if (curjob != -1 && !(jobtab[curjob].stat & STAT_INUSE)) {
	    curjob = prevjob;
	    setprevjob();
	}
    }

    if (func == BIN_JOBS)
        /* If you immediately type "exit" after "jobs", this      *
         * will prevent zexit from complaining about stopped jobs */
	stopmsg = 2;
    if (!*argv)
	/* This block handles all of the default cases (no arguments).  bg,
	fg and disown act on the current job, and jobs and wait act on all the
	jobs. */
 	if (func == BIN_FG || func == BIN_BG || func == BIN_DISOWN) {
	    /* W.r.t. the above comment, we'd better have a current job at this
	    point or else. */
	    if (curjob == -1 || curjob == thisjob ||
		(jobtab[curjob].stat & STAT_NOPRINT)) {
		zwarnnam(name, "no current job", NULL, 0);
		return 1;
	    }
	    firstjob = curjob;
	} else if (func == BIN_JOBS) {
	    /* List jobs. */
	    for (job = 0; job != MAXJOB; job++)
		if (job != thisjob && jobtab[job].stat) {
		    if ((!ops['r'] && !ops['s']) ||
			(ops['r'] && ops['s']) ||
			(ops['r'] && !(jobtab[job].stat & STAT_STOPPED)) ||
			(ops['s'] && jobtab[job].stat & STAT_STOPPED))
			printjob(job + jobtab, lng);
		}
	    return 0;
	} else {   /* Must be BIN_WAIT, so wait for all jobs */
	    for (job = 0; job != MAXJOB; job++)
		if (job != thisjob && jobtab[job].stat)
		    waitjob(job, SIGINT);
	    return 0;
	}

    /* Defaults have been handled.  We now have an argument or two, or three...
    In the default case for bg, fg and disown, the argument will be provided by
    the above routine.  We now loop over the arguments. */
    for (; (firstjob != -1) || *argv; (void)(*argv && argv++)) {
	int stopped, ocj = thisjob;

	if (func == BIN_WAIT && isanum(*argv)) {
	    /* wait can take a pid; the others can't. */
	    waitforpid((long)atoi(*argv));
	    retval = lastval2;
	    thisjob = ocj;
	    continue;
	}
	/* The only type of argument allowed now is a job spec.  Check it. */
	job = (*argv) ? getjob(*argv, name) : firstjob;
	firstjob = -1;
	if (job == -1)
	    break;
	if (!(jobtab[job].stat & STAT_INUSE) ||
	    (jobtab[job].stat & STAT_NOPRINT)) {
	    zwarnnam(name, "no such job: %d", 0, job);
	    return 1;
	}
	/* We have a job number.  Now decide what to do with it. */
	switch (func) {
	case BIN_FG:
	case BIN_BG:
	case BIN_WAIT:
	    if ((stopped = (jobtab[job].stat & STAT_STOPPED)))
		makerunning(jobtab + job);
	    else if (func == BIN_BG) {
		/* Silly to bg a job already running. */
		zwarnnam(name, "job already in background", NULL, 0);
		thisjob = ocj;
		return 1;
	    }
	    /* It's time to shuffle the jobs around!  Reset the current job,
	    and pick a sensible secondary job. */
	    if (curjob == job) {
		curjob = prevjob;
		prevjob = (func == BIN_BG) ? -1 : job;
	    }
	    if (prevjob == job || prevjob == -1)
		setprevjob();
	    if (curjob == -1) {
		curjob = prevjob;
		setprevjob();
	    }
	    if (func != BIN_WAIT)
		/* for bg and fg -- show the job we are operating on */
		printjob(jobtab + job, (stopped) ? -1 : 0);
	    if (func != BIN_BG) {		/* fg or wait */
		if (strcmp(jobtab[job].pwd, pwd)) {
		    printf("(pwd : ");
		    printdir(jobtab[job].pwd);
		    printf(")\n");
		}
		fflush(stdout);
		if (func != BIN_WAIT) {		/* fg */
		    thisjob = job;
		    attachtty(jobtab[job].gleader);
		}
	    }
	    if (stopped) {
		if (func != BIN_BG && jobtab[job].ty)
		    settyinfo(jobtab[job].ty);
		killjb(jobtab + job, SIGCONT);
	    }
	    if (func == BIN_WAIT)
	        waitjob(job, SIGINT);
	    if (func != BIN_BG) {
		waitjobs();
		retval = lastval2;
	    }
	    break;
	case BIN_JOBS:
	    printjob(job + jobtab, lng);
	    break;
	case BIN_DISOWN:
	    {
		static struct job zero;

		jobtab[job] = zero;
		break;
	    }
	}
	thisjob = ocj;
    }
    return retval;
}

/* kill: send a signal to a process.  The process(es) may be specified *
 * by job specifier (see above) or pid.  A signal, defaulting to       *
 * SIGTERM, may be specified by name or number, preceded by a dash.    */

/**/
int
bin_kill(char *nam, char **argv, char *ops, int func)
{
    int sig = SIGTERM;
    int retval = 0;

    /* check for, and interpret, a signal specifier */
    if (*argv && **argv == '-') {
	if (idigit((*argv)[1]))
	    /* signal specified by number */
	    sig = atoi(*argv + 1);
	else {
	    /* with argument "-l", or "-", display the list of signal names */
	    if ((*argv)[1] == 'l' && (*argv)[2] == '\0') {
		printf("%s", sigs[1]);
		for (sig = 2; sig <= SIGCOUNT; sig++)
		    printf(" %s", sigs[sig]);
		putchar('\n');
		return 0;
	    }
	    /* check for signal matching specified name */
	    for (sig = 1; sig <= SIGCOUNT; sig++)
		if (!strcmp(sigs[sig], *argv + 1))
		    break;
	    if (sig > SIGCOUNT) {
		zwarnnam(nam, "unknown signal: SIG%s", *argv + 1, 0);
		zwarnnam(nam, "type kill -l for a List of signals", NULL, 0);
		return 1;
	    }
	}
	argv++;
    }

    /* Remaining arguments specify processes.  Loop over them, and send the
    signal (number sig) to each process. */
    for (; *argv; argv++) {
	if (**argv == '%') {
	    /* job specifier introduced by '%' */
	    int p = getjob(*argv, "kill");

	    if (p == -1) {
		retval = 1;
		continue;
	    }
	    if (killjb(jobtab + p, sig) == -1) {
		zwarnnam("kill", "kill failed: %e", NULL, errno);
		retval = 1;
		continue;
	    }
	    /* automatically update the job table if sending a SIGCONT to a
	    job, and send the job a SIGCONT if sending it a non-stopping
	    signal. */
	    if (jobtab[p].stat & STAT_STOPPED) {
		if (sig == SIGCONT)
		    jobtab[p].stat &= ~STAT_STOPPED;
		if (sig != SIGKILL && sig != SIGCONT && sig != SIGTSTP
		    && sig != SIGTTOU && sig != SIGTTIN && sig != SIGSTOP)
		    killjb(jobtab + p, SIGCONT);
	    }
	} else if (!isanum(*argv)) {
	    zwarnnam("kill", "illegal pid: %s", *argv, 0);
	    retval = 1;
	} else if (kill(atoi(*argv), sig) == -1) {
	    zwarnnam("kill", "kill failed: %e", NULL, errno);
	    retval = 1;
	}
    }
    return retval;
}

/* suspend: suspend this shell */

/**/
int
bin_suspend(char *name, char **argv, char *ops, int func)
{
    /* won't suspend a login shell, unless forced */
    if (islogin && !ops['f']) {
	zerrnam(name, "can't suspend login shell", NULL, 0);
	return 1;
    }
    if (jobbing) {
	/* stop ignoring signals */
	signal_default(SIGPIPE);
	signal_default(SIGTTIN);
	signal_default(SIGTSTP);
	signal_default(SIGTTOU);
    }
    /* suspend ourselves with a SIGTSTP */
    kill(0, SIGTSTP);
    if (jobbing) {
	/* stay suspended */
	while (gettygrp() != mypgrp) {
	    sleep(1);
	    if (gettygrp() != mypgrp)
		kill(0, SIGTTIN);
	}
	/* restore signal handling */
	signal_ignore(SIGTTOU);
	signal_ignore(SIGTSTP);
	signal_ignore(SIGTTIN);
	signal_ignore(SIGPIPE);
    }
    return 0;
}

/* find a job named s */

/**/
int
findjobnam(char *s)
{
    int jobnum;

    for (jobnum = MAXJOB - 1; jobnum >= 0; jobnum--)
	if (!(jobtab[jobnum].stat & (STAT_SUBJOB | STAT_NOPRINT)) &&
	    jobtab[jobnum].stat && jobtab[jobnum].procs && jobnum != thisjob &&
	    jobtab[jobnum].procs->text && strpfx(s, jobtab[jobnum].procs->text))
	    return jobnum;
    return -1;
}

/* convert a job specifier ("%%", "%1", "%foo", "%?bar?", etc.) to a job
number */

/**/
int
getjob(char *s, char *prog)
{
    int jobnum, retval;

    /* if there is no %, treat as a name */
    if (*s != '%')
	goto jump;
    s++;
    /* "%%", "%+" and "%" all represent the current job */
    if (*s == '%' || *s == '+' || !*s) {
	if (curjob == -1) {
	    zwarnnam(prog, "no current job", NULL, 0);
	    retval = -1;
	    goto done;
	}
	retval = curjob;
	goto done;
    }
    /* "%-" represents the previous job */
    if (*s == '-') {
	if (prevjob == -1) {
	    zwarnnam(prog, "no previous job", NULL, 0);
	    retval = -1;
	    goto done;
	}
	retval = prevjob;
	goto done;
    }
    /* a digit here means we have a job number */
    if (idigit(*s)) {
	jobnum = atoi(s);
	if (jobnum && jobnum < MAXJOB && jobtab[jobnum].stat &&
	    !(jobtab[jobnum].stat & STAT_SUBJOB) && jobnum != thisjob) {
	    retval = jobnum;
	    goto done;
	}
	zwarnnam(prog, "no such job", NULL, 0);
	retval = -1;
	goto done;
    }
    /* "%?" introduces a search string */
    if (*s == '?') {
	struct process *pn;

	for (jobnum = MAXJOB - 1; jobnum >= 0; jobnum--)
	    if (jobtab[jobnum].stat && !(jobtab[jobnum].stat & STAT_SUBJOB) &&
		jobnum != thisjob)
		for (pn = jobtab[jobnum].procs; pn; pn = pn->next)
		    if (strstr(pn->text, s + 1)) {
			retval = jobnum;
			goto done;
		    }
	zwarnnam(prog, "job not found: %s", s, 0);
	retval = -1;
	goto done;
    }
  jump:
    /* anything else is a job name, specified as a string that begins the
    job's command */
    if ((jobnum = findjobnam(s)) != -1) {
	retval = jobnum;
	goto done;
    }
    /* if we get here, it is because none of the above succeeded and went
    to done */
    zwarnnam(prog, "job not found: %s", s, 0);
    retval = -1;
  done:
    return retval;
}

/* This simple function indicates whether or not s may represent      *
 * a number.  It returns true iff s consists purely of digits and     *
 * minuses.  Note that minus may appear more than once, and the empty *
 * string will produce a `true' response.                             */

/**/
int
isanum(char *s)
{
    while (*s == '-' || idigit(*s))
	s++;
    return *s == '\0';
}

/**** directory-handling builtins ****/

int doprintdir = 0;		/* set in exec.c (for autocd) */

static void pdstack _((void));

/* pwd: display the name of the current directory */

/**/
int
bin_pwd(char *name, char **argv, char *ops, int func)
{
    printf("%s\n", pwd);
    return 0;
}

/* dirs: list the directory stack, or replace it with a provided list */

/**/
int
bin_dirs(char *name, char **argv, char *ops, int func)
{
    Lklist l;

    /* with the -v option, provide a numbered list of directories, starting at
    zero */
    if (ops['v']) {
	Lknode node;
	int pos = 1;

	printf("0\t");
	printdir(pwd);
	for (node = firstnode(dirstack); node; incnode(node)) {
	    printf("\n%d\t", pos++);
	    printdir(getdata(node));
	}
	putchar('\n');
	return 0;
    }
    /* given no arguments, list the stack normally */
    if (!*argv) {
	pdstack();
	return 0;
    }
    /* replace the stack with the specified directories */
    permalloc();
    l = newlist();
    if (!*argv) {
	heapalloc();
	return 0;
    }
    while (*argv)
	addnode(l, ztrdup(*argv++));
    freetable(dirstack, freestr);
    dirstack = l;
    heapalloc();
    return 0;
}

/* cd, chdir, pushd, popd */

/* The main pwd changing function.  The real work is done by other     *
 * functions.  cd_get_dest() does the initial argument processing;     *
 * cd_do_chdir() actually changes directory, if possible; cd_new_pwd() *
 * does the ancilliary processing associated with actually changing    *
 * directory.                                                          */

/**/
int
bin_cd(char *nam, char **argv, char *ops, int func)
{
    char *dest, *dir;
    struct stat st1, st2;

    doprintdir = (doprintdir == -1);

    /* handle the AUTOPUSHD option */
    if (func == BIN_CD && isset(AUTOPUSHD))
	func = BIN_PUSHD;
    /* interpret argument */
    dir = dest = cd_get_dest(nam, argv, ops, func);
    if (!dest)
	return 1;

    /* change directory and return if it failed */
    dest = cd_do_chdir(nam, dest);
    if (dest != dir)
	zsfree(dir);
    if (!dest)
	return 1;
    /* do processing for changed pwd */
    cd_new_pwd(func, dest);
    zsfree(dest);

    /* more messy processing */
    if (stat(pwd, &st1) < 0) {
	zsfree(pwd);
	pwd = zgetwd();
    } else if (stat(".", &st2) < 0)
	chdir(pwd);
    else if (st1.st_ino != st2.st_ino || st1.st_dev != st2.st_dev)
	if (isset(CHASELINKS)) {
	    zsfree(pwd);
	    pwd = zgetwd();
	} else {
	    chdir(pwd);
	}
    return 0;
}

/* get directory to chdir to */

/**/
char *
cd_get_dest(char *nam, char **argv, char *ops, int func)
{
    char *dest = NULL;

    if (!argv[0]) {
	/* Default to cd to home directory, or pop last directory off the
	directory stack. */
	if (func == BIN_CD || ((func == BIN_PUSHD && isset(PUSHDTOHOME))
			       || empty(dirstack)))
	    dest = ztrdup(home);
	else
	    dest = ztrdup(getnode(dirstack));
    } else if (!argv[1]) {
	/* One argument only -- the simple case. */
	Lknode n;
	int dd;
	char *end;

	doprintdir++;
	if (argv[0][1] && argv[0][0] == (isset(PUSHDMINUS) ? '-' : '+')) {
	    /* popping directory, specified with + */
	    dd = strtol(argv[0] + 1, &end, 10) - 1;
	    if (dd >= 0 && *end == '\0') {
		for (n = firstnode(dirstack); n && dd; dd--, incnode(n));
		if (!n) {
		    zwarnnam(nam, "no such entry in dir stack", NULL, 0);
		    return NULL;
		}
		dest = (char *)remnode(dirstack, n);
	    }
	} else if (argv[0][1] && argv[0][0] == (isset(PUSHDMINUS) ? '+' : '-')) {
	    /* popping directory, specified with - */
	    dd = strtol(argv[0] + 1, &end, 10);
	    if (*end == '\0') {
		for (n = lastnode(dirstack); n != (Lknode) dirstack && dd;
		     dd--, n = prevnode(n));
		if (n == (Lknode) dirstack) {
		    zwarnnam(nam, "no such entry in dir stack", NULL, 0);
		    return NULL;
		}
		dest = (char *)remnode(dirstack, n);
	    }
	}
	/* If all else fails, maybe the argument specifies the directory! */
	if (!dest)
	    dest = ztrdup(strcmp(argv[0], "-") ? (doprintdir--, argv[0]) :
			  oldpwd);
    } else {
	/* pushd with two arguments.  The first is a string in the current
	directory name, which is replaced by the second string.  A normal
	pushd to this new directory is performed. */
	char *u;
	int len1, len2, len3;

	if (!(u = strstr(pwd, argv[0]))) {
	    zwarnnam(nam, "string not in pwd: %s", argv[0], 0);
	    return NULL;
	}
	len1 = strlen(argv[0]);
	len2 = strlen(argv[1]);
	len3 = u - pwd;
	dest = (char *)zalloc(len3 + len2 + strlen(u + len1) + 1);
	strncpy(dest, pwd, len3);
	strcpy(dest + len3, argv[1]);
	strcat(dest, u + len1);
	doprintdir++;
    }
    return dest;
}

/* Change to given directory, if possible.  This function works out  *
 * exactly how the directory should be interpreted, including cdpath *
 * and CDABLEVARS.  For each possible interpretation of the given    *
 * path, this calls cd_try_chdir(), which attempts to chdir to that  *
 * particular path.                                                  */

/**/
char *
cd_do_chdir(char *cnam, char *dest)
{
    char **pp, *ret;
    int hasdot = 0, eno = ENOENT;
    /* nocdpath indicates that cdpath should not be used.  This is the case iff
    dest is a relative path whose first segment is . or .., but if the path is
    absolute then cdpath won't be used anyway. */
    int nocdpath = dest[0] == '.' &&
    (dest[1] == '/' || !dest[1] || (dest[1] == '.' &&
				    (dest[2] == '/' || !dest[1])));

    /* if we have an absolute path, use it as-is only */
    if (*dest == '/') {
	if ((ret = cd_try_chdir(NULL, dest)))
	    return ret;
	zwarnnam(cnam, "%e: %s", dest, errno);
	return NULL;
    }

    /* if cdpath is being used, check it for . */
    if (!nocdpath)
	for (pp = cdpath; *pp; pp++)
	    if ((*pp)[0] == '.' && (*pp)[1] == '\0')
		hasdot = 1;
    /* if there is no . in cdpath (or it is not being used), try the directory
    as-is (i.e. from .) */
    if (!hasdot) {
	if ((ret = cd_try_chdir(NULL, dest)))
	    return ret;
	if (errno != ENOENT)
	    eno = errno;
    }
    /* if cdpath is being used, try given directory relative to each element in
    cdpath in turn */
    if (!nocdpath)
	for (pp = cdpath; *pp; pp++) {
	    if ((ret = cd_try_chdir(*pp, dest))) {
		if (strcmp(*pp, ".")) {
		    doprintdir++;
		}
		return ret;
	    }
	    if (errno != ENOENT)
		eno = errno;
	}

    /* handle the CDABLEVARS option */
    if ((ret = cd_able_vars(dest))) {
	if ((ret = cd_try_chdir(NULL, ret))) {
	    doprintdir++;
	    return ret;
	}
	if (errno != ENOENT)
	    eno = errno;
    }

    /* If we got here, it means that we couldn't chdir to any of the
    multitudinous possible paths allowed by zsh.  We've run out of options!
    Add more here! */
    zwarnnam(cnam, "%e: %s", dest, eno);
    return NULL;
}

/* If the CDABLEVARS option is set, return the new *
 * interpretation of the given path.               */

/**/
char *
cd_able_vars(char *s)
{
    char *rest;

    if (isset(CDABLEVARS)) {
	for (rest = s; *rest && *rest != '/'; rest++);
	s = getnamedir(s, rest - s);

	if (s && *rest)
	    s = dyncat(s, rest);

	return s;
    }
    return NULL;
}

/* Attempt to change to a single given directory.  The directory,    *
 * for the convenience of the calling function, may be provided in   *
 * two parts, which must be concatenated before attempting to chdir. *
 * Returns NULL if the chdir fails.  If the directory change is      *
 * possible, it is performed, and a pointer to the new full pathname *
 * is returned.                                                      */

/**/
char *
cd_try_chdir(char *pfix, char *dest)
{
    char buf[PATH_MAX], buf2[PATH_MAX];
    char *s;
    int dotsct;

    /* handle directory prefix */
    if (pfix)
	sprintf(buf, "%s/%s", (!strcmp("/", pfix)) ? "" : pfix, dest);
    else
	strcpy(buf, dest);
    /* Normalise path.  See the definition of fixdir() for what this means. */
    dotsct = fixdir(buf2, buf);

    /* if the path is absolute, the test and return value are (relatively)
    simple */
    if (buf2[0] == '/')
	return (chdir(buf) == -1) ? NULL : ztrdup(buf2);
    /* If the path is a simple `downward' relative path, the test is again
    fairly simple.  The relative path must be added to the end of the current
    directory. */
    if (!dotsct) {
	if (chdir(buf) == -1)
	    return NULL;
	if (*buf2)
	    sprintf(buf, "%s/%s", (!strcmp("/", pwd)) ? "" : pwd, buf2);
	else
	    strcpy(buf, pwd);
	return ztrdup(buf);
    }
    /* There are one or more .. segments at the beginning of the relative path.
    A corresponding number of segments must be removed from the end of the
    current directory before the downward relative path is appended. */
    strcpy(buf, pwd);
    s = buf + strlen(buf) - 1;
    while (dotsct--)
	while (s != buf)
	    if (*--s == '/')
		break;
    if (s == buf || *buf2)
	s++;
    strcpy(s, buf2);
    /* For some reason, this chdir must be attempted with both the newly
    created path and the original non-normalised version. */
    if (chdir(buf) != -1 || chdir(dest) != -1)
	return ztrdup(buf);
    return NULL;
}

/* do the extra processing associated with changing directory */

/**/
void
cd_new_pwd(int func, char *s)
{
    Param pm;
    List l;
    char *new_pwd;

    /* get a copy of the real pwd */
    if (isset(CHASELINKS))
	new_pwd = findpwd(s);
    else
	new_pwd = ztrdup(s);
    /* handle duplicated pwd (new_pwd is the same as pwd) */
    if (!strcmp(new_pwd, pwd) &&
	(func != BIN_PUSHD || isset(PUSHDIGNOREDUPS))) {
	zsfree(new_pwd);
#ifdef ALWAYS_DO_CD_PROCESSING
	if (unset(PUSHDSILENT) && func != BIN_CD && isset(INTERACTIVE))
	    pdstack();
	if (l = getshfunc("chpwd")) {
	    fflush(stdout);
	    fflush(stderr);
	    doshfunc(dupstruct(l), NULL, 0, 1);
	}
#endif
	return;
    }

    /* shift around the pwd variables, to make oldpwd and pwd relate to the
    current (i.e. new) pwd */
    zsfree(oldpwd);
    oldpwd = pwd;
    pwd = new_pwd;
    /* update the PWD and OLDPWD shell parameters */
    if ((pm = (Param) gethnode("PWD", paramtab)) &&
	(pm->flags & PMFLAG_x) && pm->env)
	pm->env = replenv(pm->env, pwd);
    if ((pm = (Param) gethnode("OLDPWD", paramtab)) &&
	(pm->flags & PMFLAG_x) && pm->env)
	pm->env = replenv(pm->env, oldpwd);

    /* do the directory stack thing */
    if (func == BIN_PUSHD) {
	permalloc();
	if (isset(PUSHDIGNOREDUPS)) {
	    Lknode n;
	    char *nodedata;

	    for (n = firstnode(dirstack); n; incnode(n)) {
		nodedata = (char *)getdata(n);
		if (!strcmp(oldpwd, nodedata) || !strcmp(pwd, nodedata)) {
		    free(remnode(dirstack, n));
		    break;
		}
	    }
	}
	pushnode(dirstack, ztrdup(oldpwd));
	heapalloc();
    }
    /* directory display */
    if (unset(PUSHDSILENT) && func != BIN_CD && isset(INTERACTIVE))
	pdstack();
    else if (doprintdir) {
	printdir(pwd);
        putchar('\n');
    }

    /* execute the chpwd function */
    if ((l = getshfunc("chpwd"))) {
	fflush(stdout);
	fflush(stderr);
	doshfunc(dupstruct(l), NULL, 0, 1);
    }

    /* handle directory stack sizes out of range */
    if (dirstacksize != -1 && countnodes(dirstack) >= dirstacksize) {
	if (dirstacksize < 2)
	    dirstacksize = 2;
	else
	    free(remnode(dirstack, lastnode(dirstack)));
    }
}

/* print the directory stack */

static void
pdstack(void)
{
    Lknode node;

    printdir(pwd);
    for (node = firstnode(dirstack); node; incnode(node)) {
	putchar(' ');
	printdir(getdata(node));
    }
    putchar('\n');
}

/* Normalise a path.  Segments consisting of ., and foo/.. combinations, are
removed.  The number of .. segments at the beginning of the path is returned.
The normalised path, minus leading ..s, is copied to dest. */

/**/
int
fixdir(char *dest, char *src)
{
    int ct = 0;
    char *d0 = dest;

/*** if have RFS superroot directory ***/
#ifdef HAVE_SUPERROOT
    /* allow /.. segments to remain */
    while (*src == '/' && src[1] == '.' && src[2] == '.' &&
      (!src[3] || src[3] == '/')) {
	*dest++ = '/';
	*dest++ = '.';
	*dest++ = '.';
	src += 3;
    }
#endif

    for (;;) {
	/* compress multiple /es into single */
	if (*src == '/') {
	    *dest++ = *src++;
	    while (*src == '/')
		src++;
	}
	/* if we are at the end of the input path, remove a trailing / (if it
	exists), and return ct */
	if (!*src) {
	    while (dest > d0 + 1 && dest[-1] == '/')
		dest--;
	    *dest = '\0';
	    return ct;
	}
	if (src[0] == '.' && src[1] == '.' &&
	  (src[2] == '\0' || src[2] == '/')) {
	    /* remove a foo/.. combination, or increment ct, as appropriate */
	    if (dest > d0 + 1) {
		for (dest--; dest > d0 + 1 && dest[-1] != '/'; dest--);
		if (dest[-1] != '/')
		    dest--;
	    } else
		ct++;
	    src++;
	    while (*++src == '/');
	} else if (src[0] == '.' && (src[1] == '/' || src[1] == '\0')) {
	    /* skip a . section */
	    while (*++src == '/');
	} else {
	    /* copy a normal segment into the output */
	    while (*src != '/' && *src != '\0')
		*dest++ = *src++;
	}
    }
}

/**** compctl builtin ****/

  /* Flag for listing and command or default completion:
   * bit 0 is set to list; bit 1, command; bit 2, default; bit 3, first
   * bit 4, flag to remove arguments from hash table.
   */
static int cclist;

/**/
int
bin_compctl(char *name, char **argv, char *ops, int func)
{
    Compctl cc = NULL;
    int ret = 0;
    unsigned long flags = 0;

    cclist = 0;

    if (*argv) {
	cc = (Compctl) zcalloc(sizeof(*cc));

	/* parse all the arguments */
	if (get_compctl(name, &argv, cc, 1, 0)) {
	    freecompctl(cc);
	    return 1;
	}
 	if ((cclist & 1) && (*argv || (cclist & 14))) {
 	    /* we're listing and there are arguments or a -D, -T, or -C,
	     * so display only those.
	     */
	    char **ptr = argv;
	    showflag = 0;
	    for (; *ptr; ptr++) {
		Compctlp cp = (Compctlp)gethnode(*ptr, compctltab);
		if (!cp) {
		    zerrnam(name, "no compctl defined for %s", *ptr, 0);
		    ret = 1;
		} else
		    printcompctl(*ptr, cp->cc);
	    }
	    if (cclist & 2)
		printcompctl("", &cc_compos);
	    if (cclist & 4)
		printcompctl("", &cc_default);
 	    if (cclist & 8)
 		printcompctl("", &cc_first);
	} else if (*argv)
	    /* assign the compctl to the commands given */
	    compctl_process_cc(argv, cc);

	/* remember flags for printing */
	flags = cc->mask;

	/* if no command arguments or just listing, we don't want cc */
	if (!*argv || (cclist & 1))
	    freecompctl(cc);
    }
     if (!*argv && !(cclist & 14)) {
 	/* no commands and no -C, -T, or -D, so just list */
	showflag = (cclist & 1) ? 0 : flags;
	listhtable(compctltab, (HFunc) printcompctlp);
	printcompctl((cclist & 1) ? "" : "COMMAND", &cc_compos);
	printcompctl((cclist & 1) ? "" : "DEFAULT", &cc_default);
 	printcompctl((cclist & 1) ? "" : "FIRST", &cc_first);
    }
    return ret;
}

/**/
int
get_compctl(char *name, char ***av, Compctl cc, int first, int isdef)
{
    /* Parse the basic flags for completion:
     * first is a flag that we are not in extended completion,
     * while hx indicates or (+) completion (need to know for
     * default and command completion as the initial compctl is special). 
     * cct is a temporary just to hold flags; it never needs freeing.
     */
    struct compctl cct;
    char **argv = *av;
    int ready = 0, hx = 0;

    /* Handle `compctl + foo ...' specially:  turn it into
     * a default compctl by removing it from the hash table.
     */
    if (first && **argv == '+' && !(*argv)[1] &&
	*argv[1] != '-') {
	freecompctl(cc);
 	cclist = 16;
	return 0;
    }

    memset((void *)&cct, 0, sizeof(cct));

    for (; !ready && *argv && **argv == '-';) {
	/* Loop through the flags until we have no more:
	 * those with arguments are not properly allocated yet,
	 * we just hang on to the argument that was passed.
	 */
	if (**argv == '-' && !(*argv)[1])
	    *argv = "-+";
	while (!ready && *++(*argv))
	    switch (**argv) {
	    case 'f':
		cct.mask |= CC_FILES;
		break;
	    case 'c':
		cct.mask |= CC_COMMPATH;
		break;
	    case 'o':
		cct.mask |= CC_OPTIONS;
		break;
	    case 'v':
		cct.mask |= CC_VARS;
		break;
	    case 'b':
		cct.mask |= CC_BINDINGS;
		break;
	    case 'A':
		cct.mask |= CC_ARRAYS;
		break;
	    case 'I':
		cct.mask |= CC_INTVARS;
		break;
	    case 'F':
		cct.mask |= CC_FUNCS;
		break;
	    case 'p':
		cct.mask |= CC_PARAMS;
		break;
	    case 'E':
		cct.mask |= CC_ENVVARS;
		break;
	    case 'j':
		cct.mask |= CC_JOBS;
		break;
	    case 'r':
		cct.mask |= CC_RUNNING;
		break;
	    case 'z':
		cct.mask |= CC_STOPPED;
		break;
	    case 'B':
		cct.mask |= CC_BUILTINS;
		break;
	    case 'a':
		cct.mask |= CC_ALREG | CC_ALGLOB;
		break;
	    case 'R':
		cct.mask |= CC_ALREG;
		break;
	    case 'G':
		cct.mask |= CC_ALGLOB;
		break;
	    case 'u':
		cct.mask |= CC_USERS;
		break;
	    case 'd':
		cct.mask |= CC_DISCMDS;
		break;
	    case 'e':
		cct.mask |= CC_EXCMDS;
		break;
	    case 'N':
		cct.mask |= CC_SCALARS;
		break;
	    case 'O':
		cct.mask |= CC_READONLYS;
		break;
	    case 'Z':
		cct.mask |= CC_SPECIALS;
		break;
	    case 'q':
		cct.mask |= CC_REMOVE;
		break;
	    case 'U':
		cct.mask |= CC_DELETE;
		break;
	    case 'n':
		cct.mask |= CC_NAMED;
		break;
	    case 'k':
		if ((*argv)[1]) {
		    cct.keyvar = (*argv) + 1;
		    *argv = "" - 1;
		} else if (!argv[1]) {
		    zerrnam(name, "variable name expected after -%c", NULL,
			    **argv);
		    return 1;
		} else {
		    cct.keyvar = *++argv;
		    *argv = "" - 1;
		}
		break;
	    case 'K':
		if ((*argv)[1]) {
		    cct.func = (*argv) + 1;
		    *argv = "" - 1;
		} else if (!argv[1]) {
		    zerrnam(name, "function name expected after -%c", NULL,
			    **argv);
		    return 1;
		} else {
		    cct.func = *++argv;
		    *argv = "" - 1;
		}
		break;
	    case 'X':
		if ((*argv)[1]) {
		    cct.explain = (*argv) + 1;
		    *argv = "" - 1;
		} else if (!argv[1]) {
		    zerrnam(name, "string expected after -%c", NULL, **argv);
		    return 1;
		} else {
		    cct.explain = *++argv;
		    *argv = "" - 1;
		}
		break;
	    case 'P':
		if (hx) {
		    zerrnam(name, "no prefix allowed in or'd (+) completion",
			    NULL, 0);
		    return 1;
		}
		if ((*argv)[1]) {
		    cct.prefix = (*argv) + 1;
		    *argv = "" - 1;
		} else if (!argv[1]) {
		    zerrnam(name, "string expected after -%c", NULL, **argv);
		    return 1;
		} else {
		    cct.prefix = *++argv;
		    *argv = "" - 1;
		}
		break;
	    case 'S':
		if (hx) {
		    zerrnam(name, "no suffix allowed in or'd (+) completion",
			    NULL, 0);
		    return 1;
		}
		if ((*argv)[1]) {
		    cct.suffix = (*argv) + 1;
		    *argv = "" - 1;
		} else if (!argv[1]) {
		    zerrnam(name, "string expected after -%c", NULL, **argv);
		    return 1;
		} else {
		    cct.suffix = *++argv;
		    *argv = "" - 1;
		}
		break;
	    case 'g':
		if ((*argv)[1]) {
		    cct.glob = (*argv) + 1;
		    *argv = "" - 1;
		} else if (!argv[1]) {
		    zerrnam(name, "glob pattern expected after -%c", NULL,
			    **argv);
		    return 1;
		} else {
		    cct.glob = *++argv;
		    *argv = "" - 1;
		}
		break;
	    case 's':
		if ((*argv)[1]) {
		    cct.str = (*argv) + 1;
		    *argv = "" - 1;
		} else if (!argv[1]) {
		    zerrnam(name, "command string expected after -%c", NULL,
			    **argv);
		    return 1;
		} else {
		    cct.str = *++argv;
		    *argv = "" - 1;
		}
		break;
	    case 'l':
		if ((*argv)[1]) {
		    cct.subcmd = (*argv) + 1;
		    *argv = "" - 1;
		} else if (!argv[1]) {
		    zerrnam(name, "command name expected after -%c", NULL,
			    **argv);
		    return 1;
		} else {
		    cct.subcmd = *++argv;
		    *argv = "" - 1;
		}
		break;
	    case 'H':
		if ((*argv)[1])
		    cct.hnum = atoi((*argv) + 1);
		else if (argv[1])
		    cct.hnum = atoi(*++argv);
		else {
		    zerrnam(name, "number expected after -%c", NULL,
			    **argv);
		    return 1;
		}
		if (!argv[1]) {
		    zerrnam(name, "missing pattern after -%c", NULL,
			    **argv);
		    return 1;
		}
		cct.hpat = *++argv;
		if (cct.hnum < 1)
		    cct.hnum = 0;
		if (*cct.hpat == '*' && !cct.hpat[1])
		    cct.hpat = "";
		*argv = "" - 1;
		break;
	    case 'C':
		if (first && !hx) {
		    cclist |= 2;
		} else {
		    zerrnam(name, "misplaced command completion (-C) flag",
			    NULL, 0);
		    return 1;
		}
		break;
	    case 'D':
		if (first && !hx) {
		    isdef = 1;
		    cclist |= 4;
		} else {
		    zerrnam(name, "misplaced default completion (-D) flag",
			    NULL, 0);
		    return 1;
		}
		break;
 	    case 'T':
              if (first && !hx) {
 		    cclist |= 8;
 		} else {
 		    zerrnam(name, "misplaced first completion (-T) flag",
 			    NULL, 0);
 		    return 1;
 		}
 		break;
	    case 'L':
		if (!first || hx) {
		    zerrnam(name, "illegal use of -L flag", NULL, 0);
		    return 1;
		}
		cclist |= 1;
		break;
	    case 'x':
		if (!argv[1]) {
		    zerrnam(name, "condition expected after -%c", NULL,
			    **argv);
		    return 1;
		}
		if (first) {
		    argv++;
		    if (get_xcompctl(name, &argv, &cct, isdef)) {
			if (cct.ext)
			    freecompctl(cct.ext);
			return 1;
		    }
		    ready = 2;
		} else {
		    zerrnam(name, "recursive extended completion not allowed",
			    NULL, 0);
		    return 1;
		}
		break;
	    default:
		if (!first && (**argv == '-' || **argv == '+'))
		    (*argv)--, argv--, ready = 1;
		else {
		    zerrnam(name, "bad option: %c", NULL, **argv);
		    return 1;
		}
	    }

	if (*++argv && (!ready || ready == 2) &&
	    **argv == '+' && !argv[0][1]) {
	    /* There's an alternative (+) completion:  assign
	     * what we have so far before moving on to that.
	     */
	    if (cc_assign(name, &cc, &cct, first && !hx))
		return 1;

	    hx = 1;
	    ready = 0;

	    if (!*++argv || **argv != '-' ||
		(**argv == '-' && (!argv[0][1] ||
				   (argv[0][1] == '-' && !argv[0][2])))) {
		/* No argument to +, which means do default completion */
		if (isdef) {
		    zerrnam(name,
			    "recursive or'd default completions not allowed",
			    NULL, 0);
		    return 1;
		}
		cc->xor = &cc_default;
		if (!*argv || **argv == '-') {
		    /* Nothing left after final + */
		    if (*argv)
			(*argv)--;
		    argv--;
		    ready = 1;
		}
	    } else {
		/* more flags follow:  prepare to loop again */
		cc->xor = (Compctl) zcalloc(sizeof(*cc));
		cc = cc->xor;
		memset((void *)&cct, 0, sizeof(cct));
	    }
	}
    }

    /* assign the last set of flags we parsed */
    if (cc_assign(name, &cc, &cct, first && !hx))
	return 1;

    *av = argv;

    return 0;
}

/**/
int
get_xcompctl(char *name, char ***av, Compctl cc, int isdef)
{
    /* Handle the -x ... -- part of compctl. */
    char **argv = *av, *t, *tt, sav;
    int n, l = 0, ready = 0;
    Compcond m, c, o;
    Compctl *next = &(cc->ext);

    while (!ready) {
	/* o keeps track of or's, m remembers the starting condition,
	 * c is the current condition being parsed
	 */
	o = m = c = (Compcond) zcalloc(sizeof(*c));
	/* Loop over each condition:  something like 's[...][...], p[...]' */
	for (t = *argv; *t;) {
	    while (*t == ' ')
		t++;
	    /* First get the condition code */
	    switch (*t) {
	    case 's':
		c->type = CCT_CURSUF;
		break;
	    case 'S':
		c->type = CCT_CURPRE;
		break;
	    case 'p':
		c->type = CCT_POS;
		break;
	    case 'c':
		c->type = CCT_CURSTR;
		break;
	    case 'C':
		c->type = CCT_CURPAT;
		break;
	    case 'w':
		c->type = CCT_WORDSTR;
		break;
	    case 'W':
		c->type = CCT_WORDPAT;
		break;
	    case 'n':
		c->type = CCT_CURSUB;
		break;
	    case 'N':
		c->type = CCT_CURSUBC;
		break;
	    case 'm':
		c->type = CCT_NUMWORDS;
		break;
	    case 'r':
		c->type = CCT_RANGESTR;
		break;
	    case 'R':
		c->type = CCT_RANGEPAT;
		break;
	    default:
		t[1] = '\0';
		zerrnam(name, "unknown condition code: %s", t, 0);
		zfree(m, sizeof(struct compcond));

		return 1;
	    }
	    /* Now get the arguments in square brackets */
	    if (t[1] != '[') {
		t[1] = '\0';
		zerrnam(name, "expected condition after condition code: %s", t, 0);
		zfree(m, sizeof(struct compcond));

		return 1;
	    }
	    t++;
	    /* First count how many or'd arguments there are,
	     * marking the active ]'s and ,'s with unprintable characters.
	     */
	    for (n = 0, tt = t; *tt == '['; n++) {
		for (l = 1, tt++; *tt && l; tt++)
		    if (*tt == '\\' && tt[1])
			tt++;
		    else if (*tt == '[')
			l++;
		    else if (*tt == ']')
			l--;
		    else if (l == 1 && *tt == ',')
			*tt = '\201';
		if (tt[-1] == ']')
		    tt[-1] = '\200';
	    }

	    if (l) {
		t[1] = '\0';
		zerrnam(name, "error after condition code: %s", t, 0);
		zfree(m, sizeof(struct compcond));

		return 1;
	    }
	    c->n = n;

	    /* Allocate space for all the arguments of the conditions */
	    if (c->type == CCT_POS ||
		c->type == CCT_NUMWORDS) {
		c->u.r.a = (int *)zcalloc(n * sizeof(int));
		c->u.r.b = (int *)zcalloc(n * sizeof(int));
	    } else if (c->type == CCT_CURSUF ||
		       c->type == CCT_CURPRE)
		c->u.s.s = (char **)zcalloc(n * sizeof(char *));

	    else if (c->type == CCT_RANGESTR ||
		     c->type == CCT_RANGEPAT) {
		c->u.l.a = (char **)zcalloc(n * sizeof(char *));
		c->u.l.b = (char **)zcalloc(n * sizeof(char *));
	    } else {
		c->u.s.p = (int *)zcalloc(n * sizeof(int));
		c->u.s.s = (char **)zcalloc(n * sizeof(char *));
	    }
	    /* Now loop over the actual arguments */
	    for (l = 0; *t == '['; l++, t++) {
		for (t++; *t && *t == ' '; t++);
		tt = t;
		if (c->type == CCT_POS ||
		    c->type == CCT_NUMWORDS) {
		    /* p[...] or m[...]:  one or two numbers expected */
		    for (; *t && *t != '\201' && *t != '\200'; t++);
		    if (!(sav = *t)) {
			zerrnam(name, "error in condition", NULL, 0);
			freecompcond(m);
			return 1;
		    }
		    *t = '\0';
		    c->u.r.a[l] = atoi(tt);
		    /* Second argument is optional:  see if it's there */
		    if (sav == '\200')
			/* no:  copy first argument */
			c->u.r.b[l] = c->u.r.a[l];
		    else {
			tt = ++t;
			for (; *t && *t != '\200'; t++);
			if (!*t) {
			    zerrnam(name, "error in condition", NULL, 0);
			    freecompcond(m);
			    return 1;
			}
			*t = '\0';
			c->u.r.b[l] = atoi(tt);
		    }
		} else if (c->type == CCT_CURSUF ||
			   c->type == CCT_CURPRE) {
		    /* -s[..] or -S[..]:  single string expected */
		    for (; *t && *t != '\200'; t++)
			if (*t == '\201')
			    *t = ',';
		    if (!*t) {
			zerrnam(name, "error in condition", NULL, 0);
			freecompcond(m);
			return 1;
		    }
		    *t = '\0';
		    c->u.s.s[l] = ztrdup(tt);
		} else if (c->type == CCT_RANGESTR ||
			   c->type == CCT_RANGEPAT) {
		    /* -r[..,..] or -R[..,..]:  two strings expected */
		    for (; *t && *t != '\201'; t++);
		    if (!*t) {
			zerrnam(name, "error in condition", NULL, 0);
			freecompcond(m);
			return 1;
		    }
		    *t = '\0';
		    c->u.l.a[l] = ztrdup(tt);
		    tt = ++t;
		    /* any more commas are text, not active */
		    for (; *t && *t != '\200'; t++)
			if (*t == '\201')
			    *t = ',';
		    if (!*t) {
			zerrnam(name, "error in condition", NULL, 0);
			freecompcond(m);
			return 1;
		    }
		    *t = '\0';
		    c->u.l.b[l] = ztrdup(tt);
		} else {
		    /* remaining patterns are number followed by string */
		    for (; *t && *t != '\201'; t++);
		    if (!*t) {
			zerrnam(name, "error in condition", NULL, 0);
			freecompcond(m);
			return 1;
		    }
		    *t = '\0';
		    c->u.s.p[l] = atoi(tt);
		    tt = ++t;
		    for (; *t && *t != '\200'; t++)
			if (*t == '\201')
			    *t = ',';
		    if (!*t) {
			zerrnam(name, "error in condition", NULL, 0);
			freecompcond(m);
			return 1;
		    }
		    *t = '\0';
		    c->u.s.s[l] = ztrdup(tt);
		}
	    }
	    while (*t == ' ')
		t++;
	    if (*t == ',') {
		/* Another condition to `or' */
		o->or = c = (Compcond) zcalloc(sizeof(*c));
		o = c;
		t++;
	    } else if (*t) {
		/* Another condition to `and' */
		c->and = (Compcond) zcalloc(sizeof(*c));
		c = c->and;
	    }
	}
	/* Assign condition to current compctl */
	*next = (Compctl) zcalloc(sizeof(*cc));
	(*next)->cond = m;
	argv++;
	/* End of the condition; get the flags that go with it. */
	if (get_compctl(name, &argv, *next, 0, isdef))
	    return 1;
 	if ((!argv || !*argv) && (cclist & 14))
 	    /* default, first, or command completion finished */
	    ready = 1;
	else {
	    /* see if we are looking for more conditions or are
	     * ready to return (ready = 1)
	     */
	    if (!argv || !*argv || **argv != '-' ||
		((!argv[0][1] || argv[0][1] == '+') && !argv[1])) {
		zerrnam(name, "missing command names", NULL, 0);
		return 1;
	    }
	    if (!strcmp(*argv, "--"))
		ready = 1;
	    else if (!strcmp(*argv, "-+") && argv[1] &&
		     !strcmp(argv[1], "--")) {
		ready = 1;
		argv++;
	    }
	    argv++;
	    /* prepare to put the next lot of conditions on the end */
	    next = &((*next)->next);
	}
    }
    /* save position at end of parsing */
    *av = argv - 1;
    return 0;
}

/**/
int
cc_assign(char *name, Compctl *ccptr, Compctl cct, int reass)
{
    /* Copy over the details from the values in cct to those in *ccptr */
    Compctl cc;

    if (cct->subcmd && (cct->keyvar || cct->glob || cct->str ||
			cct->func || cct->explain || cct->prefix)) {
	zerrnam(name, "illegal combination of options", NULL, 0);
	return 1;
    }

    /* Handle assignment of new default or command completion */
    if (reass && !(cclist & 1)) {
	/* if not listing */
 	if (cclist == 6 || cclist >= 10) {
 	    zerrnam(name, "can't set -D, -T, and -C simultaneously", NULL, 0);
	    /* ... because the following code wouldn't work. */
	    return 1;
	}
	if (cclist & 2) {
	    /* command */
	    *ccptr = &cc_compos;
	    cc_reassign(*ccptr);
	} else if (cclist & 4) {
	    /* default */
	    *ccptr = &cc_default;
	    cc_reassign(*ccptr);
 	} else if (cclist & 8) {
 	    /* first */
 	    *ccptr = &cc_first;
 	    cc_reassign(*ccptr);
	}
    }

    /* Free the old compctl */
    cc = *ccptr;
    zsfree(cc->keyvar);
    zsfree(cc->glob);
    zsfree(cc->str);
    zsfree(cc->func);
    zsfree(cc->explain);
    zsfree(cc->prefix);
    zsfree(cc->suffix);
    zsfree(cc->subcmd);
    zsfree(cc->hpat);
    
    /* and copy over the new stuff, (permanently) allocating
     * space for strings.
     */
    cc->mask = cct->mask;
    cc->keyvar = ztrdup(cct->keyvar);
    cc->glob = ztrdup(cct->glob);
    cc->str = ztrdup(cct->str);
    cc->func = ztrdup(cct->func);
    cc->explain = ztrdup(cct->explain);
    cc->prefix = ztrdup(cct->prefix);
    cc->suffix = ztrdup(cct->suffix);
    cc->subcmd = ztrdup(cct->subcmd);
    cc->hpat = ztrdup(cct->hpat);
    cc->hnum = cct->hnum;

    /* careful with extended completion:  it's already allocated */
    cc->ext = cct->ext;

    return 0;
}

/**/
void
cc_reassign(Compctl cc)
{
    /* Free up a new default or command completion:
     * this is a hack to free up the parts which should be deleted,
     * without removing the basic variable which is statically allocated
     */
    Compctl c2;

    c2 = (Compctl) zcalloc(sizeof *cc);
    c2->xor = cc->xor;
    c2->ext = cc->ext;
    c2->refc = 1;

    freecompctl(c2);

    cc->ext = cc->xor = NULL;
}

/**/
void
compctl_process(char **s, int mask, char *uk, char *gl, char *st, char *fu, char *ex, char *pr, char *su, char *sc, char *hp, int hn)
{
    /* Command called internally to initialise some basic compctls */
    Compctl cc;

    cc = (Compctl) zcalloc(sizeof *cc);
    cc->mask = mask;
    cc->keyvar = ztrdup(uk);
    cc->glob = ztrdup(gl);
    cc->str = ztrdup(st);
    cc->func = ztrdup(fu);
    cc->explain = ztrdup(ex);
    cc->prefix = ztrdup(pr);
    cc->suffix = ztrdup(su);
    cc->subcmd = ztrdup(sc);
    cc->hpat = ztrdup(hp);
    cc->hnum = hn;

    cclist = 0;
    compctl_process_cc(s, cc);
}

/**/
void
compctl_process_cc(char **s, Compctl cc)
{
    Compctlp ccp;

    if (cclist & 16) {
	/* Delete entries for the commands listed */
	for (; *s; s++) {
	    if ((ccp = (Compctlp) remhnode(*s, compctltab))) {
		freecompctlp(ccp);
	    }
	}
    } else {
	/* Add the compctl just read to the hash table */

	cc->refc = 0;
	for (; *s; s++) {
	    cc->refc++;
	    ccp = (Compctlp) zalloc(sizeof *ccp);
	    ccp->cc = cc;
	    addhnode(ztrdup(*s), ccp, compctltab, freecompctlp);
	}
    }
}

/**/
void
printcompctl(char *s, Compctl cc)
{
    Compctl cc2;
    char *css = "fcqovbAIFpEjrzBRGudeNOZUn";
    char *mss = " pcCwWsSnNmrR";
    unsigned long t = 0x7fffffff;

    if (cc->mask & showflag) {
	/* showflag means only list commands with that flag set */
	if (s)
	    puts(s);
    } else if (!showflag) {
	unsigned long flags = cc->mask;

	/* print either command name or start of compctl command itself */
	if (s)
	    if (cclist & 1) {
		printf("compctl ");
		if (cc == &cc_compos)
		    printf("-C ");
		if (cc == &cc_default)
		    printf("-D ");
 		if (cc == &cc_first)
 		    printf("-T");
	    } else 
		printf("%s ", s);
	/* loop through flags w/o args that are set, printing them if so */
	if (flags & t) {
	    putchar('-');
	    if ((flags & (CC_ALREG | CC_ALGLOB)) == (CC_ALREG | CC_ALGLOB))
		putchar('a'), flags &= ~(CC_ALREG | CC_ALGLOB);
	    while (*css) {
		if ((flags & 1) && (t & 1))
		    putchar(*css);
		css++;
		flags >>= 1;
		t >>= 1;
	    }
	    putchar(' ');
	}
	/* now flags with arguments */
	flags = cc->mask;
	if (cc->keyvar)
	    if (*cc->keyvar == '(') {
		printf("-k \'");
		printqt(cc->keyvar);
		printf("\' ");
	    } else
		printf("-k %s ", cc->keyvar);
	printif(cc->func, 'K');
	printif(cc->explain, 'X');
	printif(cc->prefix, 'P');
	printif(cc->suffix, 'S');
	printif(cc->glob, 'g');
	printif(cc->str, 's');
	printif(cc->subcmd, 'l');
	if (cc->hpat) {
	    printf("-H %d \'", cc->hnum);
	    printqt(cc->hpat);
	    printf("\' ");
	}
	/* now the -x ... -- extended completion part */
	if (cc->ext) {
	    Compcond c, o;
	    int i;

	    cc2 = cc->ext;
	    printf("-x ");

	    while (cc2) {
		/* loop over conditions */
		c = cc2->cond;

		putchar('\'');
		for (c = cc2->cond; c;) {
		    /* loop over or's */
		    o = c->or;
		    while (c) {
			/* loop over and's */
			putchar(mss[c->type]);

			for (i = 0; i < c->n; i++) {
			    /* for all [...]'s of a given condition */
			    putchar('[');
			    switch (c->type) {
			    case CCT_POS:
			    case CCT_NUMWORDS:
				printf("%d,%d", c->u.r.a[i], c->u.r.b[i]);
				break;
			    case CCT_CURSUF:
			    case CCT_CURPRE:
				printqt(c->u.s.s[i]);
				break;
			    case CCT_RANGESTR:
			    case CCT_RANGEPAT:
				printqt(c->u.l.a[i]);
				putchar(',');
				printqt(c->u.l.b[i]);
				break;
			    default:
				printf("%d,", c->u.s.p[i]);
				printqt(c->u.s.s[i]);
			    }
			    putchar(']');
			}
			if ((c = c->and))
			    putchar(' ');
		    }
		    if ((c = o))
			printf(" , ");
		}
		printf("\' ");
		c = cc2->cond;
		cc2->cond = NULL;
		/* now print the flags for the current condition */
		printcompctl((char *) NULL, cc2);
		cc2->cond = c;
		if ((cc2 = (Compctl) (cc2->next)))
		    printf("- ");
	    }
	    if (cclist & 1)
		printf("-- ");
	}
	if (cc && cc->xor) {
	    /* print or'd (+) completions */
	    printf("+ ");
	    if (cc->xor != &cc_default)
		printcompctl((char *) NULL, cc->xor);
	}
	if (s) {
	    if (cclist & 1)
		printf(s);
	    putchar('\n');
	}
    }
}

/**/
void
printcompctlp(char *s, Compctlp ccp)
{
    /* Function needed for use by listhtable() */
    printcompctl(s, ccp->cc);
}

/**/
void
printqt(char *str)
{
    /* Print str, but turn any single quote into '\''. */
    for (; *str; str++)
	if (*str == '\'')
	    printf("\'\\\'\'");
	else
	    putchar(*str);
}

/**/
void
printif(char *str, int c)
{
    /* If flag c has an argument, print that */
    if (str) {
	printf("-%c \'", c);
	printqt(str);
	printf("\' ");
    }
}

/**** history list functions ****/

/* fc, history, r */

/**/
int
bin_fc(char *nam, char **argv, char *ops, int func)
{
    int first = -1, last = -1, retval, delayrem, minflag = 0;
    char *s;
    struct asgment *asgf = NULL, *asgl = NULL;
    Comp com = NULL;

    /* fc is only permitted in interactive shells */
    if (!interact) {
	zwarnnam(nam, "not interactive shell", NULL, 0);
	return 1;
    }
    /* with the -m option, the first argument is taken as a pattern that
    history lines have to match */
    if (*argv && ops['m']) {
	tokenize(*argv);
	if (!(com = parsereg(*argv++))) {
	    zwarnnam(nam, "invalid match pattern", NULL, 0);
	    return 1;
	}
    }
    /* delayrem is set if the command is r */
    delayrem = !strcmp(nam, "r");
    if (!delayrem && !(ops['l'] && unset(HISTNOSTORE)) &&
	(ops['R'] || ops['W'] || ops['A']))
	remhist();
    if (ops['R']) {
	/* read history from a file */
	readhistfile(*argv ? *argv : getsparam("HISTFILE"), 1);
	return 0;
    }
    if (ops['W']) {
	/* write history to a file */
	savehistfile(*argv ? *argv : getsparam("HISTFILE"), 1,
		     (ops['I'] ? 2 : 0));
	return 0;
    }
    if (ops['A']) {
	/* append history to a file */
	savehistfile(*argv ? *argv : getsparam("HISTFILE"), 1,
		     (ops['I'] ? 3 : 1));
	return 0;
    }
    /* put foo=bar type arguments into the substitution list */
    while (*argv && equalsplit(*argv, &s)) {
	struct asgment *a = (struct asgment *)alloc(sizeof *a);

	if (!asgf)
	    asgf = asgl = a;
	else {
	    asgl->next = a;
	    asgl = a;
	}
	a->name = *argv;
	a->value = s;
	argv++;
    }
    /* interpret and check first history line specifier */
    if (*argv) {
	minflag = **argv == '-';
	first = fcgetcomm(*argv);
	if (first == -1)
	    return 1;
	argv++;
    }
    /* interpret and check second history line specifier */
    if (*argv) {
	last = fcgetcomm(*argv);
	if (last == -1)
	    return 1;
	argv++;
    }
    /* There is a maximum of two history specifiers.  At least, there will be
    as long as the history list is one-dimensional. */
    if (*argv) {
	zwarnnam("fc", "too many arguments", NULL, 0);
	return 1;
    }
    /* default values of first and last, and range checking */
    if (first == -1)
	first = (ops['l']) ? curhist - 16 : curhist - 1;
    if (last == -1)
	last = (ops['l']) ? curhist : first;
    if (first < firsthist())
	first = firsthist();
    if (last == -1)
	last = (minflag) ? curhist : first;
    if (ops['l'])
	/* list the required part of the history */
	retval = fclist(stdout, !ops['n'], ops['r'], ops['D'],
			ops['d'] + ops['f'] * 2 + ops['E'] * 4 + ops['i'] * 8,
			first, last, asgf, com);
    else {
	/* edit history file, and (if successful) use the result as a new command */
	int tempfd;
	FILE *out;
	char *fil;

	retval = 1;
	fil = gettemp();
	if (((tempfd = creat(fil, 0600)) == -1) ||
		((out = fdopen(tempfd, "w")) == NULL)) {
	    zwarnnam("fc", "can't open temp file: %e", NULL, errno);
	} else {
	    if (!fclist(out, 0, ops['r'], 0, 0, first, last, asgf, com))
		if (fcedit(auxdata ? auxdata :
			   (fceditparam ? fceditparam : DEFAULT_FCEDIT), fil))
		    if (stuff(fil))
			zwarnnam("fc", "%e: %s", s, errno);
		    else
			retval = 0;
	}
	unlink(fil);
    }
    if (delayrem)
	remhist();
    return retval;
}

/* History handling functions: these are called by ZLE, as well as  *
 * the actual builtins.  fcgetcomm() gets a history line, specified *
 * either by number or leading string.  fcsubs() performs a given   *
 * set of simple old=new substitutions on a given command line.     *
 * fclist() outputs a given range of history lines to a text file.  */

/* get the history event associated with s */

/**/
int
fcgetcomm(char *s)
{
    int cmd;

    /* First try to match a history number.  Negative numbers indicate reversed
    numbering. */
    if ((cmd = atoi(s))) {
	if (cmd < 0)
	    cmd = curhist + cmd;
	if (cmd >= curhist) {
	    zwarnnam("fc", "bad history number: %d", 0, cmd);
	    return -1;
	}
	return cmd;
    }
    /* not a number, so search by string */
    cmd = hcomsearch(s);
    if (cmd == -1)
	zwarnnam("fc", "event not found: %s", s, 0);
    return cmd;
}

/* Perform old=new substituions.  Uses the asgment structure from zsh.h, *
 * which is essentially a linked list of string,replacement pairs.       */

/**/
int
fcsubs(char **sp, struct asgment *sub)
{
    char *oldstr, *newstr, *oldpos, *newpos, *newmem, *s = *sp;
    int subbed = 0;

    /* loop through the linked list */
    while (sub) {
	oldstr = sub->name;
	newstr = sub->value;
	sub = sub->next;
	oldpos = s;
	/* loop over occurences of oldstr in s, replacing them with newstr */
	while ((newpos = (char *)strstr(oldpos, oldstr))) {
	    newmem = (char *) alloc(1 + (newpos - s)
			+ strlen(newstr) + strlen(newpos + strlen(oldstr)));
	    ztrncpy(newmem, s, newpos - s);
	    strcat(newmem, newstr);
	    oldpos = newmem + strlen(newmem);
	    strcat(newmem, newpos + strlen(oldstr));
	    s = newmem;
	    subbed = 1;
	}
    }
    *sp = s;
    return subbed;
}

/* Print a series of history events to a file.  The file pointer is     *
 * given by f, and the required range of events by first and last.      *
 * subs is an optional list of foo=bar substitutions to perform on the  *
 * history lines before output.  com is an optional comp structure      *
 * that the history lines are required to match.  n, r, D and d are     *
 * options: n indicates that each line should be numbered.  r indicates *
 * that the lines should be output in reverse order (newest first).     *
 * D indicates that the real time taken by each command should be       *
 * output.  d indicates that the time of execution of each command      *
 * should be output; d>1 means that the date should be output too; d>3  *
 * means that mm/dd/yyyy form should be used for the dates, as opposed  *
 * to dd.mm.yyyy form; d>7 means that yyyy-mm-dd form should be used.   */

/**/
int
fclist(FILE *f, int n, int r, int D, int d, int first, int last, struct asgment *subs, Comp com)
{
    int fclistdone = 0;
    char *s, *hs;
    Histent ent;

    /* reverse range if required */
    if (r) {
	r = last;
	last = first;
	first = r;
    }
    /* suppress "no substitution" warning if no substitution is requested */
    if (!subs)
	fclistdone = 1;

    for (;;) {
	hs = quietgetevent(first);
	if (!hs) {
	    zwarnnam("fc", "no such event: %d", NULL, first);
	    return 1;
	}
	s = makehstr(hs);
	/* this if does the pattern matching, if required */
	if (!com || domatch(s, com, 0)) {
	    /* perform substitution */
	    fclistdone |= fcsubs(&s, subs);

	    /* do numbering */
	    if (n)
		fprintf(f, "%5d  ", first);
	    ent = NULL;
	    /* output actual time (and possibly date) of execution of the
	    command, if required */
	    if (d) {
		struct tm *ltm;
		if (!ent)
		    ent = gethistent(first);
		ltm = localtime(&ent->stim);
		if (d >= 2) {
		    if (d >= 8) {
			fprintf(f, "%d-%02d-%02d ",
				ltm->tm_year + 1900,
				ltm->tm_mon + 1, ltm->tm_mday);
		    } else if (d >= 4) {
			fprintf(f, "%d.%d.%d ",
				ltm->tm_mday, ltm->tm_mon + 1,
				ltm->tm_year + 1900);
		    } else {
			fprintf(f, "%d/%d/%d ",
				ltm->tm_mon + 1, ltm->tm_mday,
				ltm->tm_year + 1900);
		    }
		}
		fprintf(f, "%02d:%02d  ", ltm->tm_hour, ltm->tm_min);
	    }
	    /* display the time taken by the command, if required */
	    if (D) {
		long diff;
		if (!ent)
		    ent = gethistent(first);
		diff = (ent->ftim) ? ent->ftim - ent->stim : 0;
		fprintf(f, "%ld:%02ld  ", diff / 60, diff % 60);
	    }

	    /* output the command */
	    if (f == stdout) {
		niceprintf(s, f);
		putc('\n', f);
	    } else
		fprintf(f, "%s\n", s);
	}
	/* move on to the next history line, or quit the loop */
	if (first == last)
	    break;
	else if (first > last)
	    first--;
	else
	    first++;
    }

    /* final processing */
    if (f != stdout)
	fclose(f);
    if (!fclistdone) {
	zwarnnam("fc", "no substitutions performed", NULL, 0);
	return 1;
    }
    return 0;
}

/* edit a history file */

/**/
int
fcedit(char *ename, char *fn)
{
    if (!strcmp(ename, "-"))
	return 1;
    return !zyztem(ename, fn);
}

/* fprintf, with conversion of control codes to a printable form.  This is used
in displaying history events. */

/**/
void
niceprintf(char *s, FILE *f)
{
    for (; *s; s++) {
	if (isprint(*s))
	    fputc(*s, f);
	else if (*s == '\n') {
	    putc('\\', f);
	    putc('n', f);
	} else {
	    putc('^', f);
	    fputc(*s | 0x40, f);
	}
    }
}

/**** parameter builtins ****/

/* autoload, declare, export, functions, integer, local, readonly, typeset */

/**/
int
bin_typeset(char *name, char **argv, char *ops, int func)
{
    int on = 0, off = 0, roff, bit = 1, retcode = 0, initon, initoff, of;
    char *optstr = "LRZilurtx";
    struct param *pm;
    struct asgment *asg;

    /* translate the options into PMFLAGs */
    for (; *optstr; optstr++, bit <<= 1)
	if (ops[*optstr] == 1)
	    on |= bit;
	else if (ops[*optstr] == 2)
	    off |= bit;
    roff = off;

    /* function operations (-f flag) */
    if (ops['f']) {
	/* Set up on and off.  Only the -t and -u options are meaningful. */
	on &= PMFLAG_t | PMFLAG_u;
	off &= PMFLAG_t | PMFLAG_u;
	showflag = (ops['f'] == 1);
	if (ops['@'] && (off & PMFLAG_u)) {
	    zerrnam(name, "invalid option(s)", NULL, 0);
	    return 1;
	}
	showflag2 = 0;
	if (!*argv) {
	    /* given no arguments, list the functions */
	    showflag2 = off | on;
	    listhtable(cmdnamtab, (HFunc) pshfunc);
	} else if (ops['m']) {
	    /* with the -m option, treat arguments as glob patterns */
	    Comp com;
	    int i, n;
	    struct cmdnam *chn;

	    on &= ~PMFLAG_A;
	    for (; *argv; argv++) {
		/* expand argument */
		tokenize(*argv);
		if (!(com = parsereg(*argv))) {
		    retcode = 1;
		    untokenize(*argv);
		    zerrnam(name, "bad pattern : %s", *argv, 0);
		    continue;
		}
		n = cmdnamtab->hsize;
		/* go through the command table, and either show, or apply the
		options to, each matching function */
		for (i = 0; i < n; i++)
		    for (chn = (struct cmdnam *)cmdnamtab->nodes[i]; chn;
			 chn = (struct cmdnam *)chn->next)
			if ((chn->flags & SHFUNC) &&
			    domatch(chn->nam, com, 0)) {
			    if (on | off)
				chn->flags = (chn->flags | on) & (~off);
			    else
				pshfunc(chn->nam, chn);
			}
	    }
	} else
	    /* take the arguments literally */
	    for (; *argv; argv++) {
		Cmdnam cc;

		if ((cc = (Cmdnam) gethnode(*argv, cmdnamtab)) &&
		    (cc->flags & SHFUNC))
		    /* show, or apply the options to, the specified function */
		    if (on | off)
			cc->flags = (cc->flags | on) & (~off);
		    else
			pshfunc(*argv, cc);
		else if (on & PMFLAG_u) {
		    cc = (Cmdnam) zcalloc(sizeof *cc);
		    cc->flags = SHFUNC | on;
		    addhnode(ztrdup(*argv), cc, cmdnamtab, freecmdnam);
		} else
		    retcode = 1;
	    }
	return retcode;
    }

    /* sanity checks on the options -- remove conficting options, mostly */
    if ((on | off) & PMFLAG_x)
	func = BIN_EXPORT;
    if (on & PMFLAG_i)
	off |= PMFLAG_R | PMFLAG_L | PMFLAG_Z | PMFLAG_u | PMFLAG_A;
    if (on & PMFLAG_L)
	off |= PMFLAG_R | PMFLAG_i;
    if (on & PMFLAG_R)
	off |= PMFLAG_L | PMFLAG_i;
    if (on & PMFLAG_Z)
	off |= PMFLAG_i;
    if (on & PMFLAG_u)
	off |= PMFLAG_l;
    if (on & PMFLAG_l)
	off |= PMFLAG_u;
    on &= ~off;
    showflag = showflag2 = 0;
    initon = on;
    initoff = off;
    of = func;
    /* given no arguments, list whatever the options specify */
    if (!*argv) {
	showflag = on | roff;
	showflag2 = roff || ops['+'];
	listhtable(paramtab, (HFunc) printparam);
	return 0;
    }
    while ((asg = getasg(*argv++))) {
	func = of;
	on = initon;
	off = initoff;
	if (ops['m']) {
	    /* with the -m option, treat arguments as glob patterns */
	    Comp com;
	    int i, n;

	    on &= ~PMFLAG_A;
	    /* expand argument */
	    tokenize(asg->name);
	    if (!(com = parsereg(asg->name))) {
		untokenize(asg->name);
		zerrnam(name, "bad pattern : %s", argv[-1], 0);
		continue;
	    }
	    n = paramtab->hsize;
	    /* search through the parameter table, and act on any matches */
	    for (i = 0; i < n; i++)
		for (pm = (struct param *)paramtab->nodes[i]; pm;
		     pm = (struct param *)pm->next)
		    if (domatch(pm->nam, com, 0)) {
			/* match found -- if no value and no options are
			provided, display the current value */
			if (!on && !roff && !asg->value) {
			    printparam(pm->nam, pm);
			    continue;
			}
			/* set up the flags and, if required, the value */
			pm->flags = (pm->flags | on) & ~off;
			if ((on & (PMFLAG_L | PMFLAG_R | PMFLAG_Z | PMFLAG_i))
			    && (pmtype(pm) != PMFLAG_A))
			    pm->ct = auxlen;
			if (pmtype(pm) != PMFLAG_A) {
			    if (pm->flags & PMFLAG_x) {
				if (!pm->env)
				    pm->env = addenv(pm->nam,
						     (asg->value) ? asg->value : getsparam(pm->nam));
			    } else if (pm->env) {
				delenv(pm->env);
				zsfree(pm->env);
				pm->env = NULL;
			    }
			    if (asg->value)
				setsparam(pm->nam, ztrdup(asg->value));
			}
		    }
	} else {
	    /* take arguments literally */
	    if (!isident(asg->name)) {
		zerr("not an identifier: %s", asg->name, 0);
		continue;
	    }
	    pm = (Param) gethnode(asg->name, paramtab);
	    if (pm && (pm->flags & PMFLAG_SPECIAL)) {
		func = 0;
		on = (pmtype(pm) == PMFLAG_i) ?
		    (on &= ~(PMFLAG_L | PMFLAG_R | PMFLAG_Z | PMFLAG_u)) :
		    (on & ~PMFLAG_i);
		off &= ~PMFLAG_i;
	    }
	    if (pm && pm->level)
		if ((on & PMFLAG_x) &&!(on &= ~PMFLAG_x) && !off)
		    return 1;
	    bit = 0;	/* flag for switching int<->not-int */
	    if (pm && !(pm->flags & PMFLAG_UNSET) &&
		((((locallevel == pm->level) || func == BIN_EXPORT)
		  && !(bit = ((off & pm->flags) | (on & ~pm->flags)) & PMFLAG_i)) ||
		 (pm->flags & PMFLAG_SPECIAL))) {
		if (!on && !roff && !asg->value) {
		    printparam(asg->name, pm);
		    continue;
		}
		pm->flags = (pm->flags | on) & ~off;
		if (on & (PMFLAG_L | PMFLAG_R | PMFLAG_Z | PMFLAG_i))
		    pm->ct = auxlen;
		if (pmtype(pm) != PMFLAG_A) {
		    if (pm->flags & PMFLAG_x) {
			if (!pm->env)
			    pm->env = addenv(asg->name,
					     (asg->value) ? asg->value : getsparam(asg->name));
		    } else if (pm->env) {
			delenv(pm->env);
			zsfree(pm->env);
			pm->env = NULL;
		    }
		    if (asg->value)
			setsparam(asg->name, ztrdup(asg->value));
		}
	    } else {
		int readonly = on & PMFLAG_r;

		if (bit) {
		    if (!asg->value)
			asg->value = dupstring(getsparam(asg->name));
		    unsetparam(asg->name);
		} else if (locallist && func != BIN_EXPORT) {
		    permalloc();
		    addnode(locallist, ztrdup(asg->name));
		    heapalloc();
		}
		pm = createparam(ztrdup(asg->name), on & ~PMFLAG_r);
		pm->ct = auxlen;
		if (func != BIN_EXPORT)
		    pm->level = locallevel;
		if (asg->value)
		    setsparam(asg->name, ztrdup(asg->value));
		pm->flags |= readonly;
	    }
	}
    }
    return 0;
}

/* unset: unset parameters */

/**/
int
bin_unset(char *name, char **argv, char *ops, int func)
{
    int retval = 0;
    char *s;

    while ((s = *argv++))
	if (ops['m']) {
	    /* with -m option, treat arguments as glob patterns */
	    int i, n;
	    Comp com;
	    struct param *par, *next;

	    /* expand */
	    tokenize(s);
	    if (!(com = parsereg(s))) {
		retval = 1;
		untokenize(s);
		zwarnnam(name, "bad pattern : %s", s, 0);
		continue;
	    }
	    /* go through the parameter table, and unset any matches */
	    n = paramtab->hsize;
	    for (i = 0; i < n; i++)
		for (par = (struct param *)paramtab->nodes[i]; par;
		     par = next) {
		    next = (struct param *)par->next;
		    if (domatch(par->nam, com, 0))
			unsetparam(par->nam);
		}
	} else {
	    /* simply unset the given parameter */
	    if (gethnode(s, paramtab))
		unsetparam(s);
	    else
		retval = 1;
	}
    return retval;
}

/* vared: edit (literally) a parameter value */

/**/
int
bin_vared(char *name, char **args, char *ops, int func)
{
    char *s;
    char *t;
    struct param *pm;
    int create = 0;
    char *p1 = NULL, *p2 = NULL;

    /* all options are handled as arguments */
    while (*args && **args == '-') {
	while (*++(*args))
	    switch (**args) {
	    case 'c':
		/* -c option -- allow creation of the parameter if it doesn't
		yet exist */
		create = 1;
		break;
	    case 'p':
		/* -p option -- set main prompt string */
		if ((*args)[1])
		    p1 = *args + 1, *args = "" - 1;
		else if (args[1])
		    p1 = *(++args), *args = "" - 1;
		else {
		    zwarnnam(name, "prompt string expected after -%c", NULL,
			     **args);
		    return 1;
		}
		break;
	    case 'r':
		/* -r option -- set right prompt string */
		if ((*args)[1])
		    p2 = *args + 1, *args = "" - 1;
		else if (args[1])
		    p2 = *(++args), *args = "" - 1;
		else {
		    zwarnnam(name, "prompt string expected after -%c", NULL,
			     **args);
		    return 1;
		}
		break;
	    default:
		/* unrecognised option character */
		zwarnnam(name, "unknown option: %s", *args, 0);
		return 1;
	    }
	args++;
    }

    /* check we have a parameter name */
    if (!*args) {
	zwarnnam(name, "missing variable", NULL, 0);
	return 1;
    }
    /* handle non-existent parameter */
    if (!(s = getsparam(args[0]))) {
	if (create)
	    createparam(args[0], PMFLAG_s);
	else {
	    zwarnnam(name, "no such variable: %s", args[0], 0);
	    return 1;
	}
    }
    /* edit the parameter value */
    permalloc();
    pushnode(bufstack, ztrdup(s));
    heapalloc();
    t = (char *)zleread(p1, p2);
    if (!t || errflag)
	/* error in editing */
	return 1;
    /* strip off trailing newline, if any */
    if (t[strlen(t) - 1] == '\n')
	t[strlen(t) - 1] = '\0';
    /* final assignment of parameter value */
    pm = (struct param *)gethnode(args[0], paramtab);
    if (pmtype(pm) == PMFLAG_A) {
	permalloc();
	setaparam(args[0], spacesplit(t));
	lastalloc();
    } else
	setsparam(args[0], t);
    return 0;
}

/* type, whence, which */

/**/
int
bin_whence(char *nam, char **argv, char *ops, int func)
{
    struct cmdnam *chn;
    struct alias *a;
    struct reswd *rw;
    int retval = 0;
    int csh = ops[(int)'c'], all = ops[(int)'a'];
    int v = ops['v'] || csh;
    char *cnam;
    int informed;

    /* Main loop over arguments */
    for (; *argv; argv++) {
	if (ops['m']) {
	    /* -m option -- treat argument as a glob pattern */
	    int i, n;
	    Comp com;

	    /* the usual globbing routine */
	    tokenize(*argv);
	    if (!(com = parsereg(*argv))) {
		retval = 1;
		untokenize(*argv);
		zwarnnam(nam, "bad pattern : %s", *argv, 0);
		continue;
	    }
	    if (!ops['p']) {
		/* -p option is for path search only.  We're not using it, so
		search for aliases ... */
		n = aliastab->hsize;
		for (i = 0; i < n; i++) {
		    for (a = (struct alias *)aliastab->nodes[i]; a;
			 a = (struct alias *)a->next) {
			/* Going through the alias table.  Display a match if
			there is one. */
			if (a->nam && domatch(a->nam, com, 0)) {
			    if (!v)
				puts(a->text);
			    else if (a->cmd)
				printf((csh) ? "%s: aliased to %s\n" :
				       "%s is an alias for %s\n", a->nam, a->text);
			    else
				printf((csh) ? "%s: globally aliased to %s\n" :
				       "%s is a global alias for %s\n", a->nam, a->text);
			}
		    }
		}
		/* ... reserved words ... */
		n = reswdtab->hsize;
		for (i = 0; i < n; i++) {
		    for (rw = (struct reswd *)reswdtab->nodes[i]; rw;
			 rw = (struct reswd *)rw->next) {
			if (rw->nam && domatch(rw->nam, com, 0))
			    printf((csh) ? "%s: shell reserved word\n" :
				   (v) ? "%s is a reserved word\n" : "%s\n",
				   rw->nam);
		    }
		}
		/* ... and builtins and shell functions. */
		n = cmdnamtab->hsize;
		for (i = 0; i < n; i++) {
		    for (chn = (struct cmdnam *)cmdnamtab->nodes[i]; chn;
			 chn = (struct cmdnam *)chn->next) {
			/* Going through the hash table, looking for matching
			builtins and functions.  Display a match if there is
			one. */
			if (chn->nam &&
			    (chn->flags & (SHFUNC | BUILTIN)) &&
			    !(chn->flags & EXCMD) &&
			    domatch(chn->nam, com, 0)) {
			    if (chn->flags & SHFUNC) {
				if (csh || ops['f']) {
				    showflag = 1;
				    showflag2 = 0;
				    pshfunc(chn->nam, chn);
				} else {
				    printf((v) ? "%s is a function\n" : "%s\n", chn->nam);
				}
			    } else
				printf((csh) ? "%s: shell built-in command\n" :
				       (v) ? "%s is a shell builtin\n" : "%s\n", chn->nam);
			}
		    }
		}
	    }
	    /* Done search for `internal' commands, if the -p option was not
	    used.  Now search the path. */
	    fullhash();
	    n = cmdnamtab->hsize;
	    for (i = 0; i < n; i++) {
		for (chn = (struct cmdnam *)cmdnamtab->nodes[i]; chn;
		     chn = (struct cmdnam *)chn->next) {
		    if (chn->nam && (chn->flags & EXCMD) &&
			domatch(chn->nam, com, 0)) {
			if (chn->flags & BUILTIN)
			    printf("%s is hashed to %s\n", chn->nam,
				   chn->u.cmd);
			else if (v && !csh)
			    printf("%s is %s/%s\n", chn->nam,
				   chn->u.name ? *(chn->u.name) : "",
				   chn->nam);
			else
			    printf("%s/%s\n", chn->u.name ? *(chn->u.name) : "",
				   chn->nam);
		    }
		}
	    }

	} else {
	    /* Not treating args as glob patterns. */
	    informed = 0;
	    /* Look for alias, unless -p was used. */
	    if (!ops['p'] && (a = (Alias) gethnode(*argv, aliastab))) {
		if (!v)
		    puts(a->text);
		else if (a->cmd)
		    printf((csh) ? "%s: aliased to %s\n" :
			   "%s is an alias for %s\n", *argv, a->text);
		else
		    printf((csh) ? "%s: globally aliased to %s\n" :
			   "%s is a global alias for %s\n", *argv, a->text);
		if (!all)
		    continue;
		informed = 1;
	    }
	    /* Look for reserved word unless -p was used. */
	    if (!ops['p'] && gethnode(*argv, reswdtab)) {
		printf((csh) ? "%s: shell reserved word\n" :
		       (v) ? "%s is a reserved word\n" : "%s\n", *argv);
		if (!all)
		    continue;
		informed = 1;
	    }
	    /* Look for builtin or function, if -p was not used. */
	    if (!ops['p'] && (chn = (Cmdnam) gethnode(*argv, cmdnamtab)) &&
		(chn->flags & (SHFUNC | BUILTIN))) {
		if (chn->flags & EXCMD)
		    printf("%s is hashed to %s\n", chn->nam, chn->u.cmd);
		else if (chn->flags & SHFUNC) {
		    if (csh || ops['f']) {
			showflag = 1;
			showflag2 = 0;
			pshfunc(*argv, chn);
		    } else {
			printf((v) ? "%s is a function\n" : "%s\n", *argv);
		    }
		} else
		    printf((csh) ? "%s: shell built-in command\n" :
			   (v) ? "%s is a shell builtin\n" : "%s\n", *argv);
		if (!all)
		    continue;
		informed = 1;
	    }
	    /* Option -a is to search the entire path, rather than just looking
	    for one match.  This block implements this option. */
	    if (all) {
		char **pp, buf[PATH_MAX], *z;

		for (pp = path; *pp; pp++) {
		    z = buf;
		    strucpy(&z, *pp);
		    *z++ = '/';
		    strcpy(z, *argv);
		    if (iscom(buf)) {
			if (v && !csh)
			    printf("%s is %s\n", *argv, buf);
			else
			    puts(buf);
			informed = 1;
		    }
		}
		if (!informed && v) {
		    printf("%s not found\n", *argv);
		    retval = 1;
		}
	    } else if (!(cnam = findcmd(*argv))) {
	        /* Not found at all. */
		if (v)
		    printf("%s not found\n", *argv);
		retval = 1;
	    } else {
		/* Found external command. */
		if (v && !csh)
		    printf("%s is %s\n", *argv, cnam);
		else
		    puts(cnam);
		zsfree(cnam);
	    }
	}
    }
    return retval;
}

/* print a param; used with listhtable */

/**/
void
printparam(char *s, Param p)
{
    if ((showflag > 0 && !(p->flags & showflag)) || (p->flags & PMFLAG_UNSET))
	return;
    if (!showflag) {
	int fgs = p->flags;

	/* describe flags */
	if (fgs & PMFLAG_i)
	    printf("integer ");
	if (fgs & PMFLAG_A)
	    printf("array ");
	if (fgs & PMFLAG_L)
	    printf("left justified %d ", p->ct);
	if (fgs & PMFLAG_R)
	    printf("right justified %d ", p->ct);
	if (fgs & PMFLAG_Z)
	    printf("zero filled %d ", p->ct);
	if (fgs & PMFLAG_l)
	    printf("lowercase ");
	if (fgs & PMFLAG_u)
	    printf("uppercase ");
	if (fgs & PMFLAG_r)
	    printf("readonly ");
	if (fgs & PMFLAG_t)
	    printf("tagged ");
	if (fgs & PMFLAG_x)
	    printf("exported ");
    }
    /* just the name? */
    if (showflag2)
	printf("%s\n", s);
    else {
	char *t, **u;

	printf("%s=", s);
	/* how the value is displayed depends on the type of the parameter */
	switch (p->flags & PMTYPE) {
	case PMFLAG_s:		/* string: simple output */
	    if (p->gets.cfn && (t = p->gets.cfn(p)))
		puts(t);
	    else
		putchar('\n');
	    break;
	case PMFLAG_i:		/* integer */
	    printf("%ld\n", p->gets.ifn(p));
	    break;
	case PMFLAG_A:		/* array: more interesting */
	    putchar('(');
	    u = p->gets.afn(p);
	    if (!*u)
		printf(")\n");
	    else {
		while (u[1])
		    printf("%s ", *u++);
		printf("%s)\n", *u);
	    }
	    break;
	}
    }
}

/* Check whether a command is a bin_typeset. */

/**/
int
istypeset(Cmdnam c, char *nam)
{
    struct bincmd *b;

    if (c)
	/* if the command is known to be a builtin, return immediately */
	if ((c->flags & BUILTIN) && !(c->flags & EXCMD))
	    return (builtins[c->u.binnum].handlerfunc == bin_typeset);
	else
	    nam = c->nam;
    /* search builtins for a matching command */
    if (nam)
	for (b = builtins; b->name; b++)
	    if (!strcmp(nam, b->name)) return b->handlerfunc == bin_typeset;
    return 0;
}

/* print a shell function (used with listhtable) */

/**/
void
pshfunc(char *s, Cmdnam cc)
{
    char *t;

    if (!(cc->flags & SHFUNC))
	return;
    if (showflag && (cc->flags & showflag2) != showflag2)
	return;
    if (cc->flags & PMFLAG_u)
	printf("undefined ");
    if (cc->flags & PMFLAG_t)
	printf("traced ");
    if (!cc->u.list || !showflag) {
	printf("%s ()\n", s);
	return;
    }
    t = getpermtext((void *) dupstruct((void *) cc->u.list));
    printf("%s () {\n\t%s\n}\n", s, t);
    zsfree(t);
}

/**** command & named directory hash table builtins ****/

/* hash: explicitly hash a command.  argv[0] is the command name, and *
 * argv[1] is the filename to execute when the command is entered.    *
 * Given exactly one argument, add it to the hash table as it it      *
 * were being executed.  Given no arguments, list the hash table.     *
 * The -f option causes the entire path to be added to the hash table *
 * (cannot be combined with any arguments).  The -r option causes     *
 * the entire hash table to be discarded (cannot be combined with     *
 * any arguments).  The -d option causes analogous things to be done  *
 * to the named directory table.                                      */

/**/
int
bin_hash(char *name, char **argv, char *ops, int func)
{
    struct cmdnam *chn;

    /* -f and -r can't be used with any arguments */
    if (argv[0] && (ops['f'] || ops['r'])) {
	zwarnnam("hash", "too many arguments", NULL, 0);
	return 1;
    }

    /* "hash -r" acts like rehash */
    if (ops['r'])
	    return bin_rehash(name, argv, ops, func);

    /* named directory table */
    if (ops['d']) {
	char buf[PATH_MAX], buf2[PATH_MAX], *ptr;
	int dotsct, curent;

	/* if given the -f option, add all usernames */
	if (ops['f']) {
	    addusernames();
	    return 0;
	}

	/* default (no arguments and -d is only flag) *
         * is to display the table                    */
	if (!argv[0]) {
	    for (curent=0; curent < userdirct; curent++)
		printf("~%s=%s\n", namdirs[curent].name, namdirs[curent].dir);
	    return 0;
	}

	/* with one argument, explicitly add it to the table if necessary */
	if (!argv[1]) {
	    ptr = getnamedir(argv[0], strlen(argv[0]));
	    if (ptr) {
		zsfree(ptr);
		return 0;
	    }
	    zerrnam(name, "argument is not a user or a parameter", NULL, 0);
	    return 1;
	}

	/* two arguments -- canonicalise the given path, *
	 * and add it to the table                       */
	dotsct = fixdir(buf, argv[1]);
	if (*buf == '/') {
	    adduserdir(argv[0], buf, 0, 1);
	    return 0;
	}
	if (!dotsct) {
	    if (*buf)
		sprintf(buf2, "%s/%s", (!strcmp("/", pwd)) ? "" : pwd, buf);
	    else
		strcpy(buf2, pwd);
	} else {
	    strcpy(buf2, pwd);
	    ptr = buf2 + strlen(buf2) - 1;
	    while (dotsct--)
		while (ptr != buf2)
		    if (*--ptr == '/')
			break;
	    if (ptr == buf2 || *buf)
		ptr++;
	    strcpy(ptr, buf);
	}
	adduserdir(argv[0], buf2, 0, 1);
	return 0;
    }

    /* command hash table */

    /* if given -f option, add the entire path to hash table */
    if (ops['f']) {
	fullhash();
	return 0;
    }

    /* default (no flags or arguments) to display hash table */
    if (!argv[0]) {
	Hashnode hn;
	Cmdnam cn;
	int hval;

	for (hval = cmdnamtab->hsize - 1; hval; hval--)
	    for (hn = cmdnamtab->nodes[hval]; hn; hn = hn->next)
		if (ISEXCMD((cn=(Cmdnam)hn)->flags)) {
		    printf("%s=", cn->nam);
		    if (cn->flags & BUILTIN)
			printf("%s\n", cn->u.cmd);
		    else
			printf("%s/%s\n", cn->u.name ? *(cn->u.name) : "",
			    cn->nam);
		}
	return 0;
    }

    /* with one argument, hash the command specified, if required */
    if (!argv[1]) {
	if (gethnode(argv[0], cmdnamtab) || hashcmd(argv[0], path))
	    return 0;
	zerrnam(name, "argument is not a valid command", NULL, 0);
	return 1;
    }

    /* two arguments -- set up specified hash table entry */
    chn = (Cmdnam) zcalloc(sizeof *chn);
    chn->flags = HASHCMD;
    chn->u.cmd = ztrdup(argv[1]);
    addhnode(ztrdup(argv[0]), chn, cmdnamtab, freecmdnam);
    return 0;
}

/* rehash: scrap the hash table.  With the -f option, re-scan the path *
 * immediately.  The -d option applies this to the named directories.  */

/**/
int
bin_rehash(char *name, char **argv, char *ops, int func)
{
    /* named directory hash table */
    if (ops['d']) {
	/* remove the old table */
	for (; userdirct; userdirct--) {
	    zsfree(namdirs[userdirct-1].name);
	    zsfree(namdirs[userdirct-1].dir);
	}
#ifdef CACHE_USERNAMES
	usernamescached = 0;
#endif /* CACHE_USERNAMES */
#if 0
	/* re-initialise the named directory table completely */
	zsfree(namdirs);
	namdirs = (Nameddirs) zcalloc(sizeof(*namdirs) * 2);
	userdirsz = 2;
#endif
	/* add the home directory (looks a bit silly if its not available) */
	adduserdir("", home, 0, 1);

	/* create a new table, if -f was given */
        if (ops['f'])
	    addusernames();

    } else {
	/* command hash table */
	newcmdnamtab();

	/* create a new table, if -f was given */
	if (ops['f'])
	    fullhash();
    }
    return 0;
}

/* unhash: remove specified commands from the hash table */

/**/
int
bin_unhash(char *name, char **argv, char *ops, int func)
{
    void *dat;
    Comp com;

    if (ops['m']) {
	/* with -m option, treat arguments as glob patterns */
	for (; *argv; argv++) {
	    /* expand argument */
	    tokenize(*argv);
	    if (!(com = parsereg(*argv))) {
		untokenize(*argv);
		zwarnnam(name, "bad pattern : %s", *argv, 0);
		continue;
	    }
	    if (!strncmp(*argv, "TRAP", 4)) {
		/* arguments are signal traps -- unset the specified traps */
		char trapname[20];
		int t;

		strncpy(trapname, "TRAP", sizeof(trapname));
		for (t = 0; t < VSIGCOUNT; t++) {
		    strncpy(trapname + 4, sigs[t], sizeof(trapname) - 5);
		    if (domatch(trapname, com, 0))
			unsettrap(t);
		}
	    } else {
		/* argument expands to normal commands */
		Cmdnam chn, nchn;
		int t, n;

		n = cmdnamtab->hsize;
		for (t = 0; t < n; t++)
		    for (chn = (Cmdnam) cmdnamtab->nodes[t]; chn; chn = nchn) {
			nchn = (Cmdnam) chn->next;
			if (domatch(chn->nam, com, 0) && !(chn->flags & BUILTIN))
			    freecmdnam(remhnode(chn->nam, cmdnamtab));
		    }
	    }
	}
    } else {
	/* take arguments literlly */
	while (*argv) {
	    if (!strncmp(*argv, "TRAP", 4))
		unsettrap(getsignum(*argv + 4));
	    if ((dat = remhnode(*argv++, cmdnamtab)))
		freecmdnam(dat);
	}
    }
    return 0;
}

/**** alias builtins ****/

/* alias: display or create aliases.  Options -a, -g and -r request *
 * listing of all, global and regular aliases respectively.         */

/**/
int
bin_alias(char *name, char **argv, char *ops, int func)
{
    struct alias *an;
    struct asgment *asg;
    /* incm is set if global aliases should not be shown/created */
    int incm = !(ops['a'] || ops['g']), ret = 0;

    /* showflag is 2 if only regular aliases should be listed, or 1 if global
    aliases should be listed */
    if (ops['r'])
	showflag = 2;
    else
	showflag = !incm;
    /* in the absence of arguments, list the aliases */
    if (!*argv)
	listhtable(aliastab, (HFunc) printalias);
    else
	while ((asg = getasg(*argv++))) {
	    if (asg->value)
		/* the argument being of the form foo=bar, define an alias */
		addhnode(ztrdup(asg->name), mkanode(ztrdup(asg->value), incm),
			 aliastab, freeanode);
	    else if (ops['m']) {
		/* with the -m option, treat the argument as a glob pattern */
		int n, i;
		struct alias *a;
		Comp com;

		/* expand */
		tokenize(argv[-1]);
		if (!(com = parsereg(argv[-1]))) {
		    ret = 1;
		    untokenize(argv[-1]);
		    zerrnam(name, "bad pattern : %s", argv[-1], 0);
		    continue;
		}
		/* display the matching aliases */
		n = aliastab->hsize;
		for (i = 0; i < n; i++) {
		    for (a = (struct alias *)aliastab->nodes[i]; a;
			 a = (struct alias *)a->next) {
			if (a->nam && domatch(a->nam, com, 0))
			    printalias(a->nam, a), ret = 0;
		    }
		}
	    } else if ((an = (Alias) gethnode(asg->name, aliastab))) {
		/* display alias if appropriate */
		if ((!ops['r'] || an->cmd == 1) &&
		    (!ops['g'] || !an->cmd))
		    printalias(asg->name, an);
	    } else
		ret = 1;
	}
    return ret;
}

/* unalias: remove alias definitions from the table */

/**/
int
bin_unalias(char *name, char **argv, char *ops, int func)
{
    int ret = 0;
    Alias dat;

    while (*argv)
	/* the -m option indicates that the arguments are glob patterns */
	if (ops['m']) {
	    int n, i, match = 0;
	    Alias a;
	    Comp com;

	    /* expand */
	    tokenize(*argv++);
	    if (!(com = parsereg(argv[-1]))) {
		ret = 1;
		untokenize(argv[-1]);
		zwarnnam(name, "bad pattern : %s", argv[-1], 0);
		continue;
	    }
	    /* remove all the expanded aliases */
	    n = aliastab->hsize;
	    for (i = 0; i < n; i++) {
		for (a = (Alias) aliastab->nodes[i]; a; a = dat) {
		    dat = (Alias) a->next;
		    if (a->nam && domatch(a->nam, com, 0))
			freeanode(remhnode(a->nam, aliastab)), match++;
		}
	    }
	    if (!match)
		ret = 1;
	} else {
	    /* remove the specified alias */
	    if ((dat = (Alias) gethnode(*argv++, aliastab)))
		freeanode(remhnode(dat->nam, aliastab));
	    else
		ret = 1;
	}
    return ret;
}

/* Print an alias; used with listhtable.  showflag determines, in *
 * part, whether the alias will actually be printed.              */

/**/
void
printalias(char *s, struct alias *a)
{
    char *ptr;
    int special = 0, inquote = 0;

    if (!showflag ||			/* single requested alias */
	(showflag == 1 && !a->cmd) ||   /* global alias           */
	(showflag == 2 && a->cmd)) {	/* regular alias          */
	/* check for special characters in the expansion text     */
	for (ptr = a->text; *ptr; ptr++)
	    if (ispecial(*ptr))
		special++;
	if (special) {
	    /* quote the replacement text, because of special characters */
	    if (isset(RCQUOTES)) {
		/* use rc-style quotes-within-quotes for the whole string */
		printf("%s=\'", s);
		for (ptr = a->text; *ptr; ptr++) {
		    if (*ptr == '\'')
			printf("\'\'");
		    else
			putchar(*ptr);
		}
		printf("\'\n");
	    } else {
		/* use Bourne-style quoting, avoiding empty quoted strings */
		printf("%s=", s);
		for (ptr = a->text; *ptr; ptr++) {
		    if (*ptr == '\'' && inquote) {
			printf("\'\\\'");
			inquote = 0;
		    } else if (*ptr == '\'')
			printf("\\\'");
		    else if (!inquote) {
			printf("\'%c",*ptr);
			inquote = 1;
		    } else
			putchar(*ptr);
		}
		if (inquote)
		    putchar('\'');
		putchar('\n');
	    }
	} else
	    /* no special characters */
	    printf("%s=%s\n", s, a->text);
	}
}

/**** resource limit builtins ****/

/* Array of resource names, required for the limit builtin.  They must *
 * appear in this array in numerical order of the RLIMIT_* macros.     */

#ifdef HAVE_GETRLIMIT
static char *recs[] =
{
    "cputime", "filesize", "datasize", "stacksize", "coredumpsize",
# ifdef RLIMIT_RSS
#  ifdef RLIMIT_MEMLOCK
    "memoryuse",
#  else
    "resident",
#  endif /* RLIMIT_MEMLOCK */
# endif /* RLIMIT_RSS */
# ifdef RLIMIT_MEMLOCK
    "memorylocked",
# endif /* RLIMIT_MEMLOCK */
# ifdef RLIMIT_NPROC
    "maxproc",
# endif /* RLIMIT_NPROC */
# ifdef RLIMIT_NOFILE
    "descriptors",
# endif /* RLIMIT_NOFILE */
# ifdef RLIMIT_VMEM
    "vmemorysize"
# endif /* RLIMIT_VMEM */
};
#endif /* HAVE_GETRLIMIT */

/* limit: set or show resource limits.  The variable hard indicates *
 * whether `hard' or `soft' resource limits are being set/shown.    */

/**/
int
bin_limit(char *nam, char **argv, char *ops, int func)
{
#ifndef HAVE_GETRLIMIT
    /* limit builtin not appropriate to this system */
    zwarnnam(nam, "not available on this system", NULL, 0);
    return 1;
#else
    char *s;
    int hard = ops['h'], limnum, lim;
    long val;

    if (ops['s']) {
	if (*argv)
	    zwarnnam(nam, "arguments after -s ignored", NULL, 0);
	for (limnum = 0; limnum != RLIM_NLIMITS; limnum++)
	    if (setrlimit(limnum, limits + limnum) < 0)
		zwarnnam(nam, "setrlimit failed: %e", NULL, errno);
	return 0;
    }
    /* without arguments, display limits */
    if (!*argv) {
	showlimits(hard, -1);
	return 0;
    }
    while ((s = *argv++)) {
	/* Search for the appropriate resource name.  When a name matches (i.e.
	starts with) the argument, the lim variable changes from -1 to the
	number of the resource.  If another match is found, lim goes to -2. */
	for (lim = -1, limnum = 0; limnum < RLIM_NLIMITS; limnum++)
	    if (!strncmp(recs[limnum], s, strlen(s))) {
		if (lim != -1)
		    lim = -2;
		else
		    lim = limnum;
	    }
	/* lim==-1 indicates that no matches were found.  lim==-2 indicates
	that multiple matches were found. */
	if (lim < 0) {
	    zwarnnam("limit",
		     (lim == -2) ? "ambiguous resource specification: %s"
		     : "no such resource: %s", s, 0);
	    return 1;
	}
	/* without value for limit, display the current limit */
	if (!(s = *argv++)) {
	    showlimits(hard, lim);
	    return 0;
	}
	if (lim==RLIMIT_CPU) {
	    /* time-type resource -- may be specified as seconds, or minutes or
	    hours with the `m' and `h' modifiers, and `:' may be used to add
	    together more than one of these.  It's easier to understand from
	    the code: */
	    val = strtol(s, &s, 10);
	    if (*s)
		if ((*s == 'h' || *s == 'H') && !s[1])
		    val *= 3600L;
		else if ((*s == 'm' || *s == 'M') && !s[1])
		    val *= 60L;
		else if (*s == ':')
		    val = val * 60 + strtol(s + 1, &s, 10);
		else {
		    zwarnnam("limit", "unknown scaling factor: %s", s, 0);
		    return 1;
		}
	}
# ifdef RLIMIT_NPROC
	else if (lim == RLIMIT_NPROC)
	    /* pure numeric resource -- only a straight decimal number is
	    permitted. */
	    val = strtol(s, &s, 10);
# endif /* RLIMIT_NPROC */
# ifdef RLIMIT_NOFILE
	else if (lim == RLIMIT_NOFILE)
	    /* pure numeric resource -- only a straight decimal number is
	    permitted. */
	    val = strtol(s, &s, 10);
# endif /* RLIMIT_NOFILE */
	else {
	    /* memory-type resource -- `k' and `M' modifiers are permitted,
	    meaning (respectively) 2^10 and 2^20. */
	    val = strtol(s, &s, 10);
	    if (!*s || ((*s == 'k' || *s == 'K') && !s[1]))
		val *= 1024L;
	    else if ((*s == 'M' || *s == 'm') && !s[1])
		val *= 1024L * 1024;
	    else {
		zwarnnam("limit", "unknown scaling factor: %s", s, 0);
		return 1;
	    }
	}
	/* new limit is valid and has been interpreted; apply it to the
	specified resource */
	if (hard) {
	    /* can only raise hard limits if running as root */
	    if (val > limits[lim].rlim_max && geteuid()) {
		zwarnnam("limit", "can't raise hard limits", NULL, 0);
		return 1;
	    } else {
		limits[lim].rlim_max = val;
		if (limits[lim].rlim_max < limits[lim].rlim_cur)
		    limits[lim].rlim_cur = limits[lim].rlim_max;
	    }
	} else if (val > limits[lim].rlim_max) {
	    zwarnnam("limit", "limit exceeds hard limit", NULL, 0);
	    return 1;
	} else
	    limits[lim].rlim_cur = val;
    }
    return 0;
#endif /* HAVE_GETRLIMIT */
}

/* unlimit: remove resource limits.  Much of this code is the same as *
 * that in bin_limit().                                               */

/**/
int
bin_unlimit(char *nam, char **argv, char *ops, int func)
{
#ifndef HAVE_GETRLIMIT
    /* unlimit builtin not appropriate to this system */
    zwarnnam(nam, "not available on this system", NULL, 0);
    return 1;
#else
    int hard = ops['h'], limnum, lim;

    /* can only remove hard limits if running as root */
    if (hard && geteuid()) {
	zwarnnam(nam, "can't remove hard limits", NULL, 0);
	return 1;
    }
    /* Without arguments, remove all limits.  Are you _sure_ you wanted to
    execute this bit of the code? */
    if (!*argv) {
	for (limnum = 0; limnum != RLIM_NLIMITS; limnum++) {
	    if (hard)
		limits[limnum].rlim_max = RLIM_INFINITY;
	    else
		limits[limnum].rlim_cur = limits[limnum].rlim_max;
	}
	return 0;
    }
    for (; *argv; argv++) {
	/* Search for the appropriate resource name.  When a name matches (i.e.
	starts with) the argument, the lim variable changes from -1 to the
	number of the resource.  If another match is found, lim goes to -2. */
	for (lim = -1, limnum = 0; limnum < RLIM_NLIMITS; limnum++)
	    if (!strncmp(recs[limnum], *argv, strlen(*argv))) {
		if (lim != -1)
		    lim = -2;
		else
		    lim = limnum;
	    }
	/* lim==-1 indicates that no matches were found.  lim==-2 indicates
	that multiple matches were found. */
	if (lim < 0) {
	    zwarnnam(nam,
		     (lim == -2) ? "ambiguous resource specification: %s"
		     : "no such resource: %s", *argv, 0);
	    return 1;
	}
	/* remove specified limit */
	if (hard)
	    limits[lim].rlim_max = RLIM_INFINITY;
	else
	    limits[lim].rlim_cur = limits[lim].rlim_max;
    }
    return 0;
#endif /* HAVE_GETRLIMIT */
}

/* ulimit: set or display resource limits */

/**/
int
bin_ulimit(char *name, char **argv, char *ops, int func)
{
#ifndef HAVE_GETRLIMIT
    /* builtin not appropriate */
    zwarnnam(name, "not available on this system", NULL, 0);
    return 1;
#else
    int res, hard;

    hard = ops['H'];
    /* select the specified limit */
    if (ops['a'] || !ops['@'])
	/* -a option -- display all the limits */
	if (*argv) {
	    zwarnnam(name, "no arguments required after -a", NULL, 0);
	    return 1;
	} else {
	    for (res = 0; res < RLIM_NLIMITS; res++)
		printulimit(res, hard);
	    return 0;
	}
    else if (ops['t'])
	res = RLIMIT_CPU;
    else if (ops['f'])
	res = RLIMIT_FSIZE;
    else if (ops['d'])
	res = RLIMIT_DATA;
    else if (ops['s'])
	res = RLIMIT_STACK;
    else if (ops['c'])
	res = RLIMIT_CORE;
# ifdef RLIMIT_RSS
    else if (ops['m'])
	res = RLIMIT_RSS;
# endif /* RLIMIT_RSS */
# ifdef RLIMIT_MEMLOCK
    else if (ops['l'])
	res = RLIMIT_MEMLOCK;
# endif /* RLIMIT_MEMLOCK */
# ifdef RLIMIT_NOFILE
    else if (ops['n'])
	res = RLIMIT_NOFILE;
# endif /* RLIMIT_NOFILE */
# ifdef RLIMIT_NPROC
    else if (ops['p'])
	res = RLIMIT_NPROC;
# endif /* RLIMIT_NPROC */
# ifdef RLIMIT_VMEM
    else if (ops['v'])
	res = RLIMIT_VMEM;
# endif /* RLIMIT_VMEM */
    else {
	/* unrecognised limit */
	zwarnnam(name, "no such limit", NULL, 0);
	return 1;
    }
    if (!*argv)
	/* if no new limit is specified, display the old limit */
	printulimit(res, hard);
    else if (strcmp(*argv, "unlimited")) {
	/* set limit to specified value */
	RLIM_T limit;

	limit = (RLIM_T) atol(*argv);
	/* scale appropriately */
	switch (res) {
	case RLIMIT_FSIZE:
	case RLIMIT_CORE:
	    limit *= 512;
	    break;
	case RLIMIT_DATA:
	case RLIMIT_STACK:
# ifdef RLIMIT_RSS
	case RLIMIT_RSS:
# endif /* RLIMIT_RSS */
# ifdef RLIMIT_MEMLOCK
	case RLIMIT_MEMLOCK:
# endif /* RLIMIT_MEMLOCK */
# ifdef RLIMIT_VMEM
	case RLIMIT_VMEM:
# endif /* RLIMIT_VMEM */
	    limit *= 1024;
	    break;
	}
	if (hard) {
	    /* can't raise hard limit unless running as root */
	    if (limit > limits[res].rlim_max && geteuid()) {
		zwarnnam(name, "can't raise hard limits", NULL, 0);
		return 1;
	    }
	    limits[res].rlim_max = limit;
	} else {
	    /* can't raise soft limit above hard limit */
	    if (limit > limits[res].rlim_max) {
		if (geteuid()) {
		    zwarnnam(name, "value exceeds hard limit", NULL, 0);
		    return 1;
		}
		limits[res].rlim_max = limits[res].rlim_cur = limit;
	    } else
		limits[res].rlim_cur = limit;
	}
    } else {
	/* remove specified limit */
	if (hard) {
	    /* can't remove hard limit unless running as root */
	    if (geteuid()) {
		zwarnnam(name, "can't remove hard limits", NULL, 0);
		return 1;
	    }
	    limits[res].rlim_max = RLIM_INFINITY;
	} else
	    /* `removal' of soft limit means setting it equal to the
	    corresponding hard limit */
	    limits[res].rlim_cur = limits[res].rlim_max;
    }
    return 0;
#endif /* HAVE_GETRLIMIT */
}

/* Display resource limits.  hard indicates whether `hard' or `soft'  *
 * limits should be displayed.  lim specifies the limit, or may be -1 *
 * to show all.                                                       */

#ifdef HAVE_GETRLIMIT
/**/
void
showlimits(int hard, int lim)
{
    int rt;
    RLIM_T val;

    /* main loop over resource types */
    for (rt = 0; rt != RLIM_NLIMITS; rt++)
	if (rt == lim || lim == -1) {
	    /* display limit for resource number rt */
	    printf("%-16s", recs[rt]);
	    val = (hard) ? limits[rt].rlim_max : limits[rt].rlim_cur;
	    if (val == RLIM_INFINITY)
		printf("unlimited\n");
	    else if (rt==RLIMIT_CPU)
		/* time-type resource -- display as hours, minutes and
		seconds. */
		printf("%d:%02d:%02d\n", (int)(val / 3600),
		       (int)(val / 60) % 60, (int)(val % 60));
# ifdef RLIMIT_NPROC
	    else if (rt == RLIMIT_NPROC)
		/* pure numeric resource */
		printf("%d\n", (int)val);
# endif /* RLIMIT_NPROC */
# ifdef RLIMIT_NOFILE
	    else if (rt == RLIMIT_NOFILE)
		/* pure numeric resource */
		printf("%d\n", (int)val);
# endif /* RLIMIT_NOFILE */
	    else if (val >= 1024L * 1024L)
		/* memory resource -- display with `K' or `M' modifier */
# ifdef RLIM_T_IS_QUAD_T
		printf("%qdMB\n", val / (1024L * 1024L));
	    else
		printf("%qdkB\n", val / 1024L);
# else
		printf("%ldMB\n", val / (1024L * 1024L));
            else
		printf("%ldkB\n", val / 1024L);
# endif /* RLIM_T_IS_QUAD_T */
	}
}
#endif  /* HAVE_GETRLIMIT */

/* Display a resource limit, in ulimit style.  lim specifies which   *
 * limit should be displayed, and hard indicates whether the hard or *
 * soft limit should be displayed.                                   */

#ifdef HAVE_GETRLIMIT
/**/
void
printulimit(int lim, int hard)
{
    RLIM_T limit;

    /* get the limit in question */
    limit = (hard) ? limits[lim].rlim_max : limits[lim].rlim_cur;
    /* display the appropriate heading */
    switch (lim) {
    case RLIMIT_CPU:
	printf("cpu time (seconds)         ");
	break;
    case RLIMIT_FSIZE:
	printf("file size (blocks)         ");
	limit /= 512;
	break;
    case RLIMIT_DATA:
	printf("data seg size (kbytes)     ");
	limit /= 1024;
	break;
    case RLIMIT_STACK:
	printf("stack size (kbytes)        ");
	limit /= 1024;
	break;
    case RLIMIT_CORE:
	printf("core file size (blocks)    ");
	limit /= 512;
	break;
# ifdef RLIMIT_RSS
    case RLIMIT_RSS:
	printf("resident set size (kbytes) ");
	limit /= 1024;
	break;
# endif /* RLIMIT_RSS */
# ifdef RLIMIT_MEMLOCK
    case RLIMIT_MEMLOCK:
	printf("locked-in-memory size (kb) ");
	limit /= 1024;
	break;
# endif /* RLIMIT_MEMLOCK */
# ifdef RLIMIT_NPROC
    case RLIMIT_NPROC:
	printf("processes                  ");
	break;
# endif /* RLIMIT_NPROC */
# ifdef RLIMIT_NOFILE
    case RLIMIT_NOFILE:
	printf("file descriptors           ");
	break;
# endif /* RLIMIT_NOFILE */
# ifdef RLIMIT_VMEM
    case RLIMIT_VMEM:
	printf("virtual memory size (kb)   ");
	limit /= 1024;
	break;
# endif /* RLIMIT_VMEM */
    }
    /* display the limit */
    if (limit == RLIM_INFINITY)
	printf("unlimited\n");
    else
	printf("%ld\n", (long)limit);
}
#endif /* HAVE_GETRLIMIT */

/**** miscellaneous builtins ****/

/* true, : (colon) */

/**/
int
bin_true(char *name, char **argv, char *ops, int func)
{
    return (0);
}

/* false builtin */

/**/
int
bin_false(char *name, char **argv, char *ops, int func)
{
    return (1);
}

/* echo, print, pushln */

/**/
int
bin_print(char *name, char **args, char *ops, int func)
{
    int nnl = 0, fd;
    Histent ent;
    FILE *fout = stdout;

    /* -z option -- push the arguments onto the editing buffer stack */
    if (ops['z']) {
	permalloc();
	pushnode(bufstack, ztrdup(spacejoin(args)));
	heapalloc();
	return 0;
    }
    /* -s option -- add the arguments to the history list */
    if (ops['s']) {
	permalloc();
	ent = gethistent(++curhist);
	zsfree(ent->lex);
	zsfree(ent->lit);
	ent->lex = ztrdup(join(args, HISTSPACE));
	ent->lit = ztrdup(join(args, ' '));
	ent->stim = ent->ftim = time(NULL);
	ent->flags = 0;
	heapalloc();
	return 0;
    }
    if (ops['R'])
	ops['r'] = 1;	/* -R implies -r */
    /* -u and -p -- output to other than standard output */
    if (ops['u'] || ops['p']) {
	if (ops['u']) {
	    for (fd = 0; fd < 10; fd++)
		if (ops[fd + '0'])
		    break;
	    if (fd == 10)
		fd = 0;
	} else
	    fd = coprocout;
	if ((fd = dup(fd)) < 0) {
	    zwarnnam(name, "bad file number", NULL, 0);
	    return 1;
	}
	if ((fout = fdopen(fd, "w")) == 0) {
	    zwarnnam(name, "bad mode on fd", NULL, 0);
	    return 1;
	}
    }
    /* -o and -O -- sort the arguments */
    if (ops['o']) {
	if (ops['i'])
	    qsort(args, arrlen(args), sizeof(char *), cstrpcmp);

	else
	    qsort(args, arrlen(args), sizeof(char *), strpcmp);
    } else if (ops['O']) {
	if (ops['i'])
	    qsort(args, arrlen(args), sizeof(char *), invcstrpcmp);

	else
	    qsort(args, arrlen(args), sizeof(char *), invstrpcmp);
    }
    /* -c -- output in columns */
    if (ops['c']) {
	int l, nc, nr, sc, n, t, i;
	char **ap;

	for (n = l = 0, ap = args; *ap; ap++, n++)
	    if (l < (t = strlen(*ap)))
		l = t;

	nc = (columns - 1) / (l + 2);
	sc = 0;
	if (nc)
	    sc = (columns - 1) / nc;
	else
	    nc = 1;
	nr = (n + nc - 1) / nc;

	for (i = 0; i < nr; i++) {
	    ap = args + i;
	    do {
		l = strlen(*ap);
		fprintf(fout, "%s", *ap);
		for (; l < sc; l++)
		    fputc(' ', fout);
		for (t = nr; t && *ap; t--, ap++);
	    }
	    while (*ap);
	    fputc(ops['N'] ? '\0' : '\n', fout);
	}
	if (fout != stdout)
	    fclose(fout);
	return 0;
    }
    /* normal output */
    for (; *args; args++) {
	char *arg = *args;
	int len;

	if (!ops['e'] && (ops['r'] || ops['E']))
            len = strlen(arg);
        else
	    arg = getkeystring(arg, &len, func != BIN_ECHO && !ops['e'], &nnl);
	if (ops['D'])
	    fprintdir(arg, fout);
	else {
	    if (ops['P'])
		arg = putprompt(arg, &len, NULL, 0);
	    fwrite(arg, len, 1, fout);
	}

	if (args[1])
	    fputc(ops['l'] ? '\n' : ops['0'] ? '\0' : ' ', fout);
    }
    if (!(ops['n'] || nnl))
	fputc(ops['N'] ? '\0' : '\n', fout);
    if (fout != stdout)
	fclose(fout);
    return 0;
}

/* echotc: output a termcap */

/**/
int
bin_echotc(char *name, char **argv, char *ops, int func)
{
    char *s, buf[2048], *t, *u;
    int num, argct;

    s = *argv++;
    if (!termok)
	return 1;
    /* if the specified termcap has a numeric value, display it */
    if ((num = tgetnum(s)) != -1) {
	printf("%d\n", num);
	return 0;
    }
    /* if the specified termcap is boolean, and set, say so */
    if (tgetflag(s)) {
	puts("yes");
	return (0);
    }
    /* get a string-type capability */
    u = buf;
    t = tgetstr(s, &u);
    if (!t || !*t) {
	/* capability doesn't exist, or (if boolean) is off */
	zwarnnam(name, "no such capability: %s", s, 0);
	return 1;
    }
    /* count the number of arguments required */
    for (argct = 0, u = t; *u; u++)
	if (*u == '%') {
	    if (u++, (*u == 'd' || *u == '2' || *u == '3' || *u == '.' ||
		      *u == '+'))
		argct++;
	}
    /* check that the number of arguments provided is correct */
    if (arrlen(argv) != argct) {
	zwarnnam(name, (arrlen(argv) < argct) ? "not enough arguments" :
		 "too many arguments", NULL, 0);
	return 1;
    }
    /* output string, through the proper termcap functions */
    if (!argct)
	tputs(t, 1, putraw);
    else {
	num = (argv[1]) ? atoi(argv[1]) : atoi(*argv);
	tputs(tgoto(t, atoi(*argv), num), num, putraw);
    }
    return 0;
}

/* shift builtin */

/**/
int
bin_shift(char *name, char **argv, char *ops, int func)
{
    int num = 1, l;
    char **s;
 
    /* optional argument can be either numeric or an array */
    if (*argv && !getaparam(*argv))
        num = matheval(*argv++);
 
    if (num < 0) {
        zerrnam(name, "argument to shift must be non-negative", NULL, 0);
        return 1;
    }

    if (*argv) {
        for (; *argv; argv++)
            if ((s = getaparam(*argv))) {
                if (num < (l = arrlen(s)))
                    l = num;
                permalloc();
                s = arrdup(s + l);
                heapalloc();
                setaparam(*argv, s);
            }
    } else {
        if (num > arrlen(pparams))
            num = arrlen(pparams);
        permalloc();
        s = arrdup(pparams + num);
        heapalloc();
        freearray(pparams);
        pparams = s;
    }
    return 0;
}

/* getopts: automagical option handling for shell scripts */

/**/
int
bin_getopts(char *name, char **argv, char *ops, int func)
{
    char *optstr = *argv++, *var = *argv++;
    char **args = (*argv) ? argv : pparams;
    static int optcind = 1, quiet;
    char *str, optbuf[3], *opch = optbuf + 1;

    /* zoptind keeps count of the current argument number */
    if (zoptind < 1)
	/* first call */
	zoptind = 1;
    if (zoptind == 1)
	quiet = 0;
    optbuf[0] = '+';
    optbuf[1] = optbuf[2] = '\0';
    zsfree(zoptarg);
    zoptarg = ztrdup("");
    setsparam(var, ztrdup(""));
    if (*optstr == ':') {
	quiet = 1;
	optstr++;
    }
    if (zoptind > arrlen(args))
	return 1;
    str = args[zoptind - 1];
    if ((*str != '+' && *str != '-') || optcind >= (int)strlen(str) ||
	!strcmp("--", str)) {
	/* current argument doesn't contain options, or optcind is impossibly
	large */
	if (*str == '+' || *str == '-')
	    zoptind++;
	optcind = 0;
	return 1;
    }
    /* Get the option character.  optcind records the current position within
    the argument. */
    if (!optcind)
	optcind = 1;
    *opch = str[optcind++];
    if (!args[zoptind - 1][optcind]) {
	zoptind++;
	optcind = 0;
    }
    /* look for option in the provided optstr */
    for (; *optstr; optstr++)
	if (*opch == *optstr)
	    break;
    if (!*optstr) {
	/* not a valid option */
	setsparam(var, ztrdup("?"));
	if (quiet) {
	    zsfree(zoptarg);
	    zoptarg = ztrdup(opch);
	    return 0;
	}
	zerr("bad option: %c", NULL, *opch);
	errflag = 0;
	return 0;
    }
    /* copy option into specified parameter, with + if required */
    setsparam(var, ztrdup(opch - (*str == '+')));
    /* handle case of an expected extra argument */
    if (optstr[1] == ':') {
	if (!args[zoptind - 1]) {
	    /* no extra argument was provided */
	    if (quiet) {
		zsfree(zoptarg);
		zoptarg = ztrdup(opch);
		setsparam(var, ztrdup(":"));
		return 0;
	    }
	    setsparam(var, ztrdup("?"));
	    zerr("argument expected after %c option", NULL, *opch);
	    errflag = 0;
	    return 0;
	}
	/* skip over the extra argument */
	zsfree(zoptarg);
	zoptarg = ztrdup(args[zoptind - 1] + optcind);
	zoptind++;
	optcind = 0;
    }
    return 0;
}

#define minimum(a,b) ((a) < (b) ? (a) : (b))

/* break, bye, continue, exit, logout, return -- most of these take   *
 * one numeric argument, and the other (logout) is related to return. *
 * (return is treated as a logout when in a login shell.)             */

/**/
int
bin_break(char *name, char **argv, char *ops, int func)
{
    int num = lastval, nump = 0;

    /* handle one optional numeric argument */
    if (*argv) {
	num = matheval(*argv++);
	nump = 1;
    }

    switch (func) {
    case BIN_CONTINUE:
	if (!loops) {   /* continue is only permitted in loops */
	    zerrnam(name, "not in while, until, select, or repeat loop", NULL, 0);
	    return 1;
	}
	contflag = 1;   /* ARE WE SUPPOSED TO FALL THROUGH HERE? */
    case BIN_BREAK:
	if (!loops) {   /* break is only permitted in loops */
	    zerrnam(name, "not in while, until, select, or repeat loop", NULL, 0);
	    return 1;
	}
	breaks = nump ? minimum(num,loops) : 1;
	break;
    case BIN_RETURN:
	if (isset(INTERACTIVE) || locallevel || sourcelevel) {
	    retflag = 1;
	    breaks = loops;
	    lastval = num;
	    if (trapreturn)
		trapreturn = lastval;
	    return lastval;
	}
	zexit(num);     /* else treat return as logout/exit */
	break;
    case BIN_LOGOUT:
	if (unset(LOGINSHELL)) {
	    zerrnam(name, "not login shell", NULL, 0);
	    return 1;
	}
	zexit(num);
	break;
    case BIN_EXIT:
	zexit(num);
	break;
    }
    return 0;
}

/* exit the shell */

/**/
void
zexit(int val)
{
    if (isset(MONITOR))    /* if using job control                  */
	if (!stopmsg) {
	    scanjobs();    /* check if jobs need printing           */
	    checkjobs();   /* check if any jobs are running/stopped */
	    if (stopmsg) {
		stopmsg = 2;
		return;
	    }
	} else
	    killrunjobs(); /* send SIGHUP to any jobs left running  */
    if (unset(NORCS) && interact) {
	if (isset(APPENDHISTORY))
	    savehistfile(getsparam("HISTFILE"), 1, 3);
	else
	    savehistfile(getsparam("HISTFILE"), 1, 0);
	if (islogin && !subsh) {
	    sourcehome(".zlogout");
#ifdef GLOBAL_ZLOGOUT
            source(GLOBAL_ZLOGOUT);
#endif
        }
    }
    if (sigtrapped[SIGEXIT])
	dotrap(SIGEXIT);
    exit(val);
}

/* ., source */

/**/
int
bin_dot(char *name, char **argv, char *ops, int func)
{
    char **old, *old0;
    int ret, diddot = 0, dotdot = 0;
    char buf[PATH_MAX];
    char *s, **t, *enam;

    if (!*argv)
	return 0;
    old = pparams;
    old0 = argzero;
    /* get arguments for the script */
    if (argv[1]) {
	permalloc();
	pparams = arrdup(argv + 1);
	heapalloc();
    }
    enam = argzero = ztrdup(*argv);
    errno = ENOENT;
    ret = 1;
    /* for source only, check in current directory first */
    if (*name != '.' && access(argzero, F_OK) == 0) {
	diddot = 1;
	ret = source(enam = argzero);
    }
    if (ret) {
	/* use a path with / in it */
	for (s = argzero; *s; s++)
	    if (*s == '/') {
		if (*argzero == '.') {
		    if (argzero + 1 == s)
			++diddot;
		    else if (argzero[1] == '.' && argzero + 2 == s)
			++dotdot;
		}
		ret = source(argzero);
		break;
	    }
	if (!*s || (ret && isset(PATHDIRS) && diddot < 2 && dotdot == 0)) {
	    /* search path for script */
	    for (t = path; *t; t++) {
		if ((*t)[0] == '.' && !(*t)[1]) {
		    if (diddot)
			continue;
		    diddot = 1;
		    strcpy(buf, argzero);
		} else
		    sprintf(buf, "%s/%s", *t, argzero);
		if (access(buf, F_OK) == 0) {
		    ret = source(enam = buf);
		    break;
		}
	    }
	}
    }
    /* clean up and return */
    if (argv[1]) {
	freearray(pparams);
	pparams = old;
    }
    if (ret)
	zerrnam(name, "%e: %s", enam, errno);
    zsfree(argzero);
    argzero = old0;
    return ret ? ret : lastval;
}

/* eval: simple mathematical evaluation */

/**/
int
bin_eval(char *nam, char **argv, char *ops, int func)
{
    char *s = ztrdup(spacejoin(argv));
    List list;

    hungets(s);
    zsfree(s);
    strinbeg();
    if (!(list = parse_list())) {
	hflush();
	strinend();
	return 1;
    }
    strinend();
    runlist(list, 0);
    return lastval;
}

static char *zbuf;
static int readfd;

/* Read a character from readfd, or from the buffer zbuf.  Return EOF on end of
file/buffer. */

extern int cs;

/* read: get a line of input, or (for compctl functions) return some *
 * useful data about the state of the editing line.  The -E and -e   *
 * options mean that the result should be sent to stdout.  -e means, *
 * in addition, that the result should not actually be assigned to   *
 * the specified parameters.                                         */

/**/
int
bin_read(char *name, char **args, char *ops, int func)
{
    char *reply = "REPLY", *readpmpt;
    int bsiz, c = 0, gotnl = 0, al = 0;
    char *buf, *bptr, *firstarg = *args, *zbuforig;
    Lklist readll = newlist();

    /* option -k means read only a given number of characters (default 1) */
    if (ops['k']) {
	int nchars, val;
	char d;
	/* haso indicates that /dev/tty has been opened specially, *
	 * and needs to be closed again before returning.  isem    *
	 * indicates that the terminal type is emacs.              */
	int haso = 0, isem = !strcmp(term, "emacs");

	if (SHTTY == -1) {
	    /* need to open /dev/tty specially */
	    SHTTY = open("/dev/tty", O_RDWR);
	    haso = 1;
	}
	/* We should have a SHTTY opened by now. */
	if (SHTTY == -1) {
	    /* Unfortunately, we didn't. */
	    fprintf(stderr, "not interactive and can't open terminal\n");
	    fflush(stderr);
	    return 1;
	}
	/* sort out the number of characters to read */
	if (*args && idigit(**args)) {
	    if (!(nchars = atoi(*args)))
		nchars = 1;
	    args++;
	} else
	    nchars = 1;

	/* handle the prompt string, if provided */
	firstarg=*args;
	if (*args && **args == '?')
	    args++;
	if (firstarg) {
	    for (readpmpt = firstarg;
		 *readpmpt && *readpmpt != '?'; readpmpt++);
	    if (*readpmpt++) {
		if (isatty(0))
		    write(2, readpmpt, strlen(readpmpt));
		readpmpt[-1] = '\0';
	    }
	}

	/* default result parameter */
	reply = (*args) ? *args++ : "REPLY";
	/* allocate buffer space for result */
	bptr = buf = (char *)zalloc(nchars + 1);

	/* attach to the tty */
	attachtty(mypgrp);
	if (!isem)
	    setcbreak();

	while (nchars>0) {
	    /* If read returns 0, is end of file */
	    if ((val=read(SHTTY, bptr, nchars))<=0) {
	        /* If end of file, or an error occurred */
		*bptr='\0';
		if (!isem)
		    settyinfo(&shttyinfo);
		if (ops['e'] || ops['E']) {
		    printf("%s\n", buf);
		    if (ops['e'])
			zsfree(buf);
		}
		if (!ops['e'])
		    setsparam(reply, buf);
 
		if (haso) {
		    close(SHTTY);
		    SHTTY = -1;
		}
		return 1;
	    }
	    
	    /* decrement number of characters read from number required */
	    nchars-=val;
	    
	    /* increment pointer past read characters */
	    bptr+=val;
	}
	
	/* Terminate string */
	*bptr='\0';

	/* dispose of result appropriately, etc. */
	if (isem)
	    while (read(SHTTY, &d, 1) == 1 && d != '\n');
	else
	    settyinfo(&shttyinfo);
	if (haso) {
	    close(SHTTY);
	    SHTTY = -1;
	}
	if (ops['e'] || ops['E']) {
	    printf("%s\n", buf);
	    if (ops['e'])
		zsfree(buf);
	}
	if (!ops['e'])
	    setsparam(reply, buf);
	return 0;
    }

    /* option -l is used in compctl functions */
    if (ops['l']) {
	/* only allowed to be called by ZLE */
	if (!inzlefunc) {
	    zwarnnam(name, "option valid only in functions called from zle",
		     NULL, 0);
	    errflag = 0;
	    return 1;
	}
	/* -ln gives the index of the word the cursor is currently on, which is
	available in cs (but remember that Zsh counts from one, not zero!) */
	if (ops['n']) {
	    char nbuf[14];

	    if (ops['e'] || ops['E'])
		printf("%d\n", cs + 1);
	    if (!ops['e']) {
		sprintf(nbuf, "%d", cs + 1);
		setsparam(*args ? *args : "REPLY", ztrdup(nbuf));
	    }
	    return 0;
	}
	/* without -n, the current line is assigned to the given parameter as a
	scalar */
	if (ops['e'] || ops['E'])
	    printf("%s\n", (char *)line);
	if (!ops['e'])
	    setsparam(*args ? *args : "REPLY", ztrdup((char *)line));
	return 0;
    }

    /* option -c is used in compctl functions */
    if (ops['c']) {
	/* only allowed to be called by ZLE */
	if (!inzlefunc) {
	    zwarnnam(name, "option valid only in functions called from zle",
		     NULL, 0);
	    errflag = 0;
	    return 1;
	}
	/* -cn gives the current cursor position within the current word, which
	is available in clwpos (but remember that Zsh counts from one, not
	zero!) */
	if (ops['n']) {
	    char nbuf[14];

	    if (ops['e'] || ops['E'])
		printf("%d\n", clwpos + 1);
	    if (!ops['e']) {
		sprintf(nbuf, "%d", clwpos + 1);
		setsparam(*args ? *args : "REPLY", ztrdup(nbuf));
	    }
	    return 0;
	}
	/* without -n, the words of the current line are assigned to the given
	parameters separately */
	if (ops['A'] && !ops['e']) {
	    /* the -A option means that one array is specified, instead of
	    many parameters */
	    char **p, **b = (char **)zcalloc((clwnum + 1) * sizeof(char *));
	    int i;

	    for (i = 0, p = b; i < clwnum; p++, i++)
		*p = ztrdup(clwords[i]);

	    setaparam(*args ? *args : "reply", b);
	    return 0;
	}
	if (ops['e'] || ops['E']) {
	    int i;

	    for (i = 0; i < clwnum; i++)
		printf("%s\n", clwords[i]);

	    if (ops['e'])
		return 0;
	}
	if (*args) {
	    int i = 0;

	    for (; i < clwnum && *args; args++, i++)
		setsparam(*args, ztrdup(clwords[i]));
	} else
	    setsparam("REPLY", ztrdup(clwords[clwpos]));

	return 0;
    }

    /* option -q means get one character, and interpret it as a Y or N */
    if (ops['q']) {
	char *readbuf;
	/* haso indicates that /dev/tty has been opened specially, and needs to
	be closed again before returning */
	int haso = 0;

	if (SHTTY == -1) {
	    /* need to open /dev/tty specially */
	    SHTTY = open("/dev/tty", O_RDWR);
	    haso = 1;
	}
	/* we should have a SHTTY open by now */
	if (SHTTY == -1) {
	    fprintf(stderr, "not interactive and can't open terminal\n");
	    fflush(stderr);
	    return 1;
	}

	/* set up the buffer */
	readbuf = (char *)zalloc(2);
	readbuf[1] = '\0';
	/* handle prompt */
	if (*args && **args == '?')
	    args++;
	if (firstarg) {
	    for (readpmpt = firstarg;
		 *readpmpt && *readpmpt != '?'; readpmpt++);
	    if (*readpmpt++) {
		if (isatty(0))
		    write(2, readpmpt, strlen(readpmpt));
		readpmpt[-1] = '\0';
	    }
	}
	/* default result parameter */
	reply = (*args) ? *args++ : "REPLY";
	/* get, and store, reply */
	readbuf[0] = ((char)getquery()) == 'y' ? 'y' : 'n';

	/* dispose of result appropriately, etc. */
	if (haso) {
	    close(SHTTY);
	    SHTTY = -1;
	}
	if (ops['e'] || ops['E']) {
	    printf("%s\n", readbuf);
	    if (ops['e'])
		free(readbuf);
	}
	if (!ops['e'])
	    setsparam(reply, readbuf);

	return readbuf[0] == 'n';
    }

    /* All possible special types of input have been exhausted.  Take one line,
    and assign words to the parameters until they run out.  Leftover words go
    onto the last parameter.  If an array is specified, all the words become
    separate elements of the array. */
    if (*args && **args == '?')
	args++;
    /* default result parameter */
    reply = *args ? *args++ : ops['A'] ? "reply" : "REPLY";
    if (ops['A'] && *args) {
	zwarnnam(name, "only one array argument allowed", NULL, 0);
	return 1;
    }
    /* -u means take input from the specified file descriptor.  -up means take
    input from the coprocess. */
    if (ops['u'] && !ops['p']) {
	for (readfd = 0; readfd < 10; ++readfd)
	    if (ops[readfd + '0'])
		break;
	if (readfd == 10)
	    readfd = 0;
    } else if (ops['p'])
	readfd = coprocin;
    else {
	/* last resort: take input from plain old stdin */
	attachtty((jobtab[thisjob].gleader) ? jobtab[thisjob].gleader : mypgrp);
	readfd = 0;
	/* handle prompt */
	if (firstarg) {
	    for (readpmpt = firstarg;
		 *readpmpt && *readpmpt != '?'; readpmpt++);
	    if (*readpmpt++) {
		if (isatty(0))
		    write(2, readpmpt, strlen(readpmpt));
		readpmpt[-1] = '\0';
	    }
	}
#if 0
	  else if (isset(SHINSTDIN) && unset(INTERACTIVE)) {
	    /* use stdout or stderr, if either is a tty */
	    if (isatty(1))
		readfd = 1;
	    else if (isatty(2))
		readfd = 2;
	}
#endif
    }

    zbuforig = zbuf = (!ops['z']) ? NULL :
	(full(bufstack)) ? (char *)getnode(bufstack) : ztrdup("");
    while (*args || (ops['A'] && !gotnl)) {
	buf = bptr = (char *)zalloc(bsiz = 64);
	/* get input, a character at a time */
	for (;;) {
	    if (gotnl)
		break;
	    c = zread();
	    /* \ at the end of a line indicates a continuation line, except in
	    raw mode (-r option) */
	    if (!ops['r'] && c == '\n' && bptr != buf && bptr[-1] == '\\') {
		bptr--;
		continue;
	    }
	    if (c == EOF || (isep(c) && bptr != buf) || c == '\n')
		break;
	    if (isep(c))
		continue;
	    *bptr++ = c;
	    /* increase the buffer size, if necessary */
	    if (bptr == buf + bsiz) {
		buf = realloc(buf, bsiz *= 2);
		bptr = buf + (bsiz / 2);
	    }
	}
	/* handle EOF */
	if (c == EOF) {
	    if (readfd == coprocin) {
		close(coprocin);
		close(coprocout);
		coprocin = coprocout = -1;
	    }
	    return 1;
	}
	if (c == '\n')
	    gotnl = 1;
	*bptr = '\0';
	/* dispose of word appropriately */
	if (ops['e'] || ops['E']) {
	    printf("%s\n", buf);
	    if (ops['e'])
		free(buf);
	}
	if (!ops['e']) {
	    if (ops['A']) {
		addnode(readll, buf);
		al++;
	    } else
		setsparam(reply, buf);
	}
	if (!ops['A'])
	    reply = *args++;
    }
    /* final assignment (and display) of array parameter */
    if (ops['A']) {
	char **pp, **p = NULL;
	Lknode n;

	p = (ops['e'] ? (char **)NULL
	     : (char **)zcalloc((al + 1) * sizeof(char *)));

	for (pp = p, n = firstnode(readll); n; incnode(n)) {
	    if (ops['e'] || ops['E']) {
		printf("%s\n", (char *)getdata(n));
		if (p)
		    zsfree(getdata(n));
	    } else
		*pp++ = (char *)getdata(n);
	}
	if (p)
	    setaparam(reply, p);
	return 0;
    }
    buf = bptr = (char *)zalloc(bsiz = 64);
    /* any remaining part of the line goes into one parameter */
    if (!gotnl)
	for (;;) {
	    c = zread();
	    /* \ at the end of a line introduces a continuation line, except in
	    raw mode (-r option) */
	    if (!ops['r'] && c == '\n' && bptr != buf && bptr[-1] == '\\') {
		bptr--;
		continue;
	    }
	    if (c == EOF || (c == '\n' && !zbuf))
		break;
	    if (isep(c) && bptr == buf)
		continue;
	    *bptr++ = c;
	    /* increase the buffer size, if necessary */
	    if (bptr == buf + bsiz) {
		buf = realloc(buf, bsiz *= 2);
		bptr = buf + (bsiz / 2);
	    }
	}
    while (bptr > buf && isep(bptr[-1]))
	bptr--;
    *bptr = '\0';
    /* final assignment of reply, etc. */
    if (ops['e'] || ops['E']) {
	printf("%s\n", buf);
	if (ops['e'])
	    zsfree(buf);
    }
    if (!ops['e'])
	setsparam(reply, buf);
    if (zbuforig) {
	char first = *zbuforig;

	zsfree(zbuforig);
	if (!first)
	    return 1;
    } else if (c == EOF) {
	if (readfd == coprocin) {
	    close(coprocin);
	    close(coprocout);
	    coprocin = coprocout = -1;
	}
	return 1;
    }
    return 0;
}

/**/
int
zread(void)
{
    char cc;

    /* use zbuf if possible */
    if (zbuf)
	/* If zbuf points to anything, it points to the next character in the
	buffer.  This may be a null byte to indicate EOF.  If reading from the
	buffer, move on the buffer pointer. */
	return (*zbuf) ? *zbuf++ : EOF;
    /* read a character from readfd */
    if (read(readfd, &cc, 1) != 1)
	/* on EOF, return EOF */
	return EOF;
    /* return the character read */
    return (int)cc;
}

/* sched: execute commands at scheduled times */

/**/
int
bin_sched(char *nam, char **argv, char *ops, int func)
{
    char *s = *argv++;
    time_t t;
    long h, m;
    struct tm *tm;
    struct schedcmd *sch, *sch2, *schl;
    int sn;

    /* If the argument begins with a -, remove the specified item from the
    schedule. */
    if (s && *s == '-') {
	sn = atoi(s + 1);

	if (!sn) {
	    zwarnnam("sched", "usage for delete: sched -<item#>.", NULL, 0);
	    return 1;
	}
	for (schl = (struct schedcmd *)&schedcmds, sch = schedcmds, sn--;
	     sch && sn; sch = (schl = sch)->next, sn--);
	if (!sch) {
	    zwarnnam("sched", "not that many entries", NULL, 0);
	    return 1;
	}
	schl->next = sch->next;
	zsfree(sch->cmd);
	zfree(sch, sizeof(struct schedcmd));

	return 0;
    }

    /* given no arguments, display the schedule list */
    if (!s) {
	char tbuf[40];

	for (sn = 1, sch = schedcmds; sch; sch = sch->next, sn++) {
	    t = sch->time;
	    tm = localtime(&t);
	    ztrftime(tbuf, 20, "%a %b %e %k:%M:%S", tm);
	    printf("%3d %s %s\n", sn, tbuf, sch->cmd);
	}
	return 0;
    } else if (!*argv) {
	/* other than the two cases above, sched requires at least two
	arguments */
	zwarnnam("sched", "not enough arguments", NULL, 0);
	return 1;
    }

    /* The first argument specifies the time to schedule the command for.  The
    remaining arguments form the command. */
    if (*s == '+') {
	/* + introduces a relative time.  The rest of the argument is an
	hour:minute offset from the current time.  Once the hour and minute
	numbers have been extracted, and the format verified, the resulting
	offset is simply added to the current time. */
	h = strtol(s + 1, &s, 10);
	if (*s != ':') {
	    zwarnnam("sched", "bad time specifier", NULL, 0);
	    return 1;
	}
	m = strtol(s + 1, &s, 10);
	if (*s) {
	    zwarnnam("sched", "bad time specifier", NULL, 0);
	    return 1;
	}
	t = time(NULL) + h * 3600 + m * 60;
    } else {
	/* If there is no +, an absolute time of day must have been given.
	This is in hour:minute format, optionally followed by a string starting
	with `a' or `p' (for a.m. or p.m.).  Characters after the `a' or `p'
	are ignored. */
	h = strtol(s, &s, 10);
	if (*s != ':') {
	    zwarnnam("sched", "bad time specifier", NULL, 0);
	    return 1;
	}
	m = strtol(s + 1, &s, 10);
	if (*s && *s != 'a' && *s != 'A' && *s != 'p' && *s != 'P') {
	    zwarnnam("sched", "bad time specifier", NULL, 0);
	    return 1;
	}
	t = time(NULL);
	tm = localtime(&t);
	t -= tm->tm_sec + tm->tm_min * 60 + tm->tm_hour * 3600;
	if (*s == 'p' || *s == 'P')
	    h += 12;
	t += h * 3600 + m * 60;
	/* If the specified time is before the current time, it must refer to
	tomorrow. */
	if (t < time(NULL))
	    t += 3600 * 24;
    }
    /* The time has been calculated; now add the new entry to the linked list
    of scheduled commands. */
    sch = (struct schedcmd *)zcalloc(sizeof *sch);
    sch->time = t;
    sch->cmd = ztrdup(spacejoin(argv));
    sch->next = NULL;
    for (sch2 = (struct schedcmd *)&schedcmds; sch2->next; sch2 = sch2->next);
    sch2->next = sch;
    return 0;
}


/* holds lexer for par_cond():  normally yylex(), testlex() for bin_test() */
extern void (*condlex) _((void));

/* holds arguments for testlex() */
static char **testargs;

/* test, [: the old-style general purpose logical expression builtin */

/**/
void
testlex(void)
{
    if (tok == LEXERR)
	return;

    tokstr = *testargs;
    if (!*testargs) {
	/* if tok is already zero, reading past the end:  error */
	tok = tok ? 0 : LEXERR;
	return;
    } else if (!strcmp(*testargs, "-o"))
	tok = DBAR;
    else if (!strcmp(*testargs, "-a"))
	tok = DAMPER;
    else if (!strcmp(*testargs, "!"))
	tok = BANG;
    else if (!strcmp(*testargs, "("))
	tok = INPAR;
    else if (!strcmp(*testargs, ")"))
	tok = OUTPAR;
    else
	tok = STRING;
    testargs++;
}

/**/
int
bin_test(char *name, char **argv, char *ops, int func)
{
    char **s;
    Cond c;

    /* if "test" was invoked as "[", it needs a matching "]" *
     * which is subsequently ignored                         */
    if (func == BIN_BRACKET) {
	for (s = argv; *s; s++);
	if (s == argv || strcmp(s[-1], "]")) {
	    zerrnam(name, "']' expected", NULL, 0);
	    return 1;
	}
	s[-1] = NULL;
    }
    /* an empty argument list evaluates to false (1) */
    if (!*argv)
	return 1;

    testargs = argv;
    tok = 0;
    condlex = testlex;
    testlex();
    c = par_cond();
    condlex = yylex;

    if (errflag)
	return 1;

    if (!c || tok == LEXERR) {
	zerrnam(name, tokstr ? "parse error" : "argument expected", NULL, 0);
	return 1;
    }

    /* syntax is OK, so evaluate */
    return !evalcond(c);
}

/* display a time, provided in units of 1/60s, as minutes and seconds */
#define pttime(X) printf("%ldm%lds",((long) (X))/3600,((long) (X))/60%60)

/* times: display, in a two-line format, the times provided by times(3) */

/**/
int
bin_times(char *name, char **argv, char *ops, int func)
{
    struct tms buf;

    /* get time accounting information */
    if (times(&buf) == -1)
	return 1;
    pttime(buf.tms_utime);	/* user time */
    putchar(' ');
    pttime(buf.tms_stime);	/* system time */
    putchar('\n');
    pttime(buf.tms_cutime);	/* user time, children */
    putchar(' ');
    pttime(buf.tms_cstime);	/* system time, children */
    putchar('\n');
    return 0;
}

/* trap: set/unset signal traps */

/**/
int
bin_trap(char *name, char **argv, char *ops, int func)
{
    List l;
    char *arg;
    int sig;

    /* if given no arguments, list all currently-set traps */
    if (!*argv) {
	for (sig = 0; sig < VSIGCOUNT; sig++)
	    if (sigtrapped[sig])
		if (!sigfuncs[sig])
		    printf("TRAP%s () {}\n", sigs[sig]);
		else {
		    char *s =
		    getpermtext((void *) dupstruct((void *) sigfuncs[sig]));

		    printf("TRAP%s () {\n\t%s\n}\n", sigs[sig], s);
		    zsfree(s);
		}
	return 0;
    }
    /* If we have a signal number, unset the specified signals.  With only a -,
    remove all traps. */
    if ((getsignum(*argv) != -1) || (!strcmp(*argv, "-") && argv++)) {
	if (!*argv)
	    for (sig = 0; sig != VSIGCOUNT; sig++)
		unsettrap(sig);
	else
	    while (*argv)
		unsettrap(getsignum(*argv++));
	return 0;
    }
    /* sort out the command to execute on trap */
    arg = *argv++;
    if (!*arg)
	l = NULL;
    else if (!(l = parselstring(arg))) {
	zerrnam(name, "couldn't parse trap command", NULL, 0);
	return 1;
    }
    /* set traps */
    for (; *argv; argv++) {
	sig = getsignum(*argv);
	if (sig == -1) {
	    zerrnam(name, "undefined signal: %s", *argv, 0);
	    break;
	}
	settrap(sig, l);
    }
    if (l)
	popheap();
    return errflag;
}

/**/
int
bin_ttyctl(char *name, char **argv, char *ops, int func)
{
    if (!ops['@'])
	printf("tty is %sfrozen\n", ttyfrozen ? "" : "not ");
    else if (ops['f'])
	ttyfrozen = 1;
    else
	ttyfrozen = 0;
    return 0;
}

/* let -- mathematical evaluation */

/**/
int
bin_let(char *name, char **argv, char *ops, int func)
{
    long val = 0;

    while (*argv)
	val = matheval(*argv++);
    return !val;
}

/* umask command.  umask may be specified as octal digits, or in the  *
 * symbolic form that chmod(1) uses.  Well, a subset of it.  Remember *
 * that only the bottom nine bits of umask are used, so there's no    *
 * point allowing the set{u,g}id and sticky bits to be specified.     */

/**/
int
bin_umask(char *nam, char **args, char *ops, int func)
{
    mode_t um;
    char *s = *args;

    /* Get the current umask. */
    um = umask(0);
    umask(um);
    /* No arguments means to display the current setting. */
    if (!s) {
	printf("%03o\n", (unsigned)um);
	return 0;
    }

    if (idigit(*s)) {
	/* Simple digital umask. */
	um = strtol(s, &s, 8);
	if (*s) {
	    zwarnnam(nam, "bad umask", NULL, 0);
	    return 1;
	}
    } else {
	/* Symbolic notation -- slightly complicated. */
	int whomask, umaskop, mask;

	/* More than one symbolic argument may be used at once, each separated
	by commas. */
	for (;;) {
	    /* First part of the argument -- who does this apply to?
	    u=owner, g=group, o=other. */
	    whomask = 0;
	    while (*s == 'u' || *s == 'g' || *s == 'o' || *s == 'a')
		if (*s == 'u')
		    s++, whomask |= 0700;
		else if (*s == 'g')
		    s++, whomask |= 0070;
		else if (*s == 'o')
		    s++, whomask |= 0007;
		else if (*s == 'a')
		    s++, whomask |= 0777;
	    /* Default whomask is everyone. */
	    if (!whomask)
		whomask = 0777;
	    /* Operation may be +, - or =. */
	    umaskop = (int)*s;
	    if (!(umaskop == '+' || umaskop == '-' || umaskop == '=')) {
		zwarnnam(nam, "bad symbolic mode operator: %c", NULL, umaskop);
		return 1;
	    }
	    /* Permissions mask -- r=read, w=write, x=execute. */
	    mask = 0;
	    while (*++s && *s != ',')
		if (*s == 'r')
		    mask |= 0444 & whomask;
		else if (*s == 'w')
		    mask |= 0222 & whomask;
		else if (*s == 'x')
		    mask |= 0111 & whomask;
		else {
		    zwarnnam(nam, "bad symbolic mode permission: %c",
			     NULL, *s);
		    return 1;
		}
	    /* Apply parsed argument to um. */
	    if (umaskop == '+')
		um &= ~mask;
	    else if (umaskop == '-')
		um |= mask;
	    else		/* umaskop == '=' */
		um = (um | (whomask)) & ~mask;
	    if (*s == ',')
		s++;
	    else
		break;
	}
	if (*s) {
	    zwarnnam(nam, "bad character in symbolic mode: %c", NULL, *s);
	    return 1;
	}
    }

    /* Finally, set the new umask. */
    umask(um);
    return 0;
}

/**** utility functions -- should go in utils.c ****/

/* execute func on each member of the hash table ht */

static hnamcmp _((struct hashnode ** a, struct hashnode ** b));

static int
hnamcmp(struct hashnode **a, struct hashnode **b)
{
    return forstrcmp(&((*a)->nam), &((*b)->nam));
}

/**/
void
listhtable(Hashtab ht, HFunc func)
{
    int count;
    struct hashnode *hn;

#ifndef HASHORDER

    int nhash;
    struct hashnode **hnsorttab, **htp;

    hnsorttab = (struct hashnode **)zalloc(ht->ct * sizeof(struct hashnode *));

    for (htp = hnsorttab, count = ht->hsize - 1; count >= 0; count--)
	for (hn = ht->nodes[count]; hn; hn = hn->next)
	    *htp++ = hn;

    qsort((void *) & hnsorttab[0], ht->ct, sizeof(struct hashnode *),
	           (int (*) _((const void *, const void *)))hnamcmp);

    for (htp = hnsorttab, nhash = 0; nhash < ht->ct; nhash++, htp++)
	func((*htp)->nam, (char *)(*htp));

    free(hnsorttab);

#else

    for (count = ht->hsize - 1; count >= 0; count--)
	for (hn = ht->nodes[count]; hn; hn = hn->next)
	    func(hn->nam, (char *)hn);

#endif
}

/* separate an argument into name=value parts, returning them in an *
 * asgment structure                                                */

/**/
struct asgment *
getasg(char *s)
{
    static struct asgment asg;

    if (!s)
	/* argument is non-existent */
	return NULL;
    if (*s == '=') {
	/* name is empty */
	zerr("bad assignment", NULL, 0);
	return NULL;
    }
    asg.name = s;
    /* search for = */
    for (; *s && *s != '='; s++);
    if (*s) {
	/* found =, so return with a value */
	*s = '\0';
	asg.value = s + 1;
    } else
	/* no =, only name */
	asg.value = NULL;
    return &asg;
}

/* get a signal number from a string */

/**/
int
getsignum(char *s)
{
    int x = atoi(s), sig;

    /* check for a signal specified by number */
    if (idigit(*s) && x >= 0 && x < VSIGCOUNT)
	return x;
    /* search for signal by name */
    for (sig = 0; sig != VSIGCOUNT; sig++)
	if (!strcmp(s, sigs[sig]))
	    return sig;
    /* no matching signal */
    return -1;
}

/* Output a single character, for the termcap routines.  This is needed *
 * if putchar is a macro.                                               */

/**/
int
putraw(int c)
{
    putchar(c);
    return 0;
}
