Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Branch: master
6736 lines (6241 sloc) 175.955 kB
/*
* builtin.c - builtin commands
*
* This file is part of zsh, the Z shell.
*
* Copyright (c) 1992-1997 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 to distribute modified versions of this software 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.
*
*/
/* this is defined so we get the prototype for open_memstream */
#define _GNU_SOURCE 1
#include "zsh.mdh"
#include "builtin.pro"
/* Builtins in the main executable */
static struct builtin builtins[] =
{
BIN_PREFIX("-", BINF_DASH),
BIN_PREFIX("builtin", BINF_BUILTIN),
BIN_PREFIX("command", BINF_COMMAND),
BIN_PREFIX("exec", BINF_EXEC),
BIN_PREFIX("noglob", BINF_NOGLOB),
BUILTIN("[", BINF_HANDLES_OPTS, bin_test, 0, -1, BIN_BRACKET, NULL, NULL),
BUILTIN(".", BINF_PSPECIAL, bin_dot, 1, -1, 0, NULL, NULL),
BUILTIN(":", BINF_PSPECIAL, bin_true, 0, -1, 0, NULL, NULL),
BUILTIN("alias", BINF_MAGICEQUALS | BINF_PLUSOPTS, bin_alias, 0, -1, 0, "Lgmrs", NULL),
BUILTIN("autoload", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "mktTUwXz", "u"),
BUILTIN("bg", 0, bin_fg, 0, -1, BIN_BG, NULL, NULL),
BUILTIN("break", BINF_PSPECIAL, bin_break, 0, 1, BIN_BREAK, NULL, NULL),
BUILTIN("bye", 0, bin_break, 0, 1, BIN_EXIT, NULL, NULL),
BUILTIN("cd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 2, BIN_CD, "qsPL", NULL),
BUILTIN("chdir", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 2, BIN_CD, "qsPL", NULL),
BUILTIN("continue", BINF_PSPECIAL, bin_break, 0, 1, BIN_CONTINUE, NULL, NULL),
BUILTIN("declare", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%klmprtuxz", NULL),
BUILTIN("dirs", 0, bin_dirs, 0, -1, 0, "clpv", NULL),
BUILTIN("disable", 0, bin_enable, 0, -1, BIN_DISABLE, "afmprs", NULL),
BUILTIN("disown", 0, bin_fg, 0, -1, BIN_DISOWN, NULL, NULL),
BUILTIN("echo", BINF_SKIPINVALID, bin_print, 0, -1, BIN_ECHO, "neE", "-"),
BUILTIN("emulate", 0, bin_emulate, 0, -1, 0, "LR", NULL),
BUILTIN("enable", 0, bin_enable, 0, -1, BIN_ENABLE, "afmprs", NULL),
BUILTIN("eval", BINF_PSPECIAL, bin_eval, 0, -1, BIN_EVAL, NULL, NULL),
BUILTIN("exit", BINF_PSPECIAL, bin_break, 0, 1, BIN_EXIT, NULL, NULL),
BUILTIN("export", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, BIN_EXPORT, "E:%F:%HL:%R:%TUZ:%afhi:%lprtu", "xg"),
BUILTIN("false", 0, bin_false, 0, -1, 0, NULL, NULL),
/*
* We used to behave as if the argument to -e was optional.
* But that's actually not useful, so it's more consistent to
* cause an error.
*/
BUILTIN("fc", 0, bin_fc, 0, -1, BIN_FC, "aAdDe:EfiIlLmnpPrRt:W", NULL),
BUILTIN("fg", 0, bin_fg, 0, -1, BIN_FG, NULL, NULL),
BUILTIN("float", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "E:%F:%HL:%R:%Z:%ghlprtux", "E"),
BUILTIN("functions", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "kmMtTuUx:z", NULL),
BUILTIN("getln", 0, bin_read, 0, -1, 0, "ecnAlE", "zr"),
BUILTIN("getopts", 0, bin_getopts, 2, -1, 0, NULL, NULL),
BUILTIN("hash", BINF_MAGICEQUALS, bin_hash, 0, -1, 0, "Ldfmrv", NULL),
#ifdef ZSH_HASH_DEBUG
BUILTIN("hashinfo", 0, bin_hashinfo, 0, 0, 0, NULL, NULL),
#endif
BUILTIN("history", 0, bin_fc, 0, -1, BIN_FC, "adDEfiLmnpPrt:", "l"),
BUILTIN("integer", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "HL:%R:%Z:%ghi:%lprtux", "i"),
BUILTIN("jobs", 0, bin_fg, 0, -1, BIN_JOBS, "dlpZrs", NULL),
BUILTIN("kill", BINF_HANDLES_OPTS, bin_kill, 0, -1, 0, NULL, NULL),
BUILTIN("let", 0, bin_let, 1, -1, 0, NULL, NULL),
BUILTIN("local", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%ahi:%lprtux", NULL),
BUILTIN("log", 0, bin_log, 0, 0, 0, NULL, NULL),
BUILTIN("logout", 0, bin_break, 0, 1, BIN_LOGOUT, NULL, NULL),
#if defined(ZSH_MEM) & defined(ZSH_MEM_DEBUG)
BUILTIN("mem", 0, bin_mem, 0, 0, 0, "v", NULL),
#endif
#if defined(ZSH_PAT_DEBUG)
BUILTIN("patdebug", 0, bin_patdebug, 1, -1, 0, "p", NULL),
#endif
BUILTIN("popd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 1, BIN_POPD, "q", NULL),
BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "abcC:Df:ilmnNoOpPrRsSu:x:X:z-", NULL),
BUILTIN("printf", 0, bin_print, 1, -1, BIN_PRINTF, NULL, NULL),
BUILTIN("pushd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 2, BIN_PUSHD, "qsPL", NULL),
BUILTIN("pushln", 0, bin_print, 0, -1, BIN_PRINT, NULL, "-nz"),
BUILTIN("pwd", 0, bin_pwd, 0, 0, 0, "rLP", NULL),
BUILTIN("r", 0, bin_fc, 0, -1, BIN_R, "IlLnr", NULL),
BUILTIN("read", 0, bin_read, 0, -1, 0, "cd:ek:%lnpqrst:%zu:AE", NULL),
BUILTIN("readonly", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%lptux", "r"),
BUILTIN("rehash", 0, bin_hash, 0, 0, 0, "df", "r"),
BUILTIN("return", BINF_PSPECIAL, bin_break, 0, 1, BIN_RETURN, NULL, NULL),
BUILTIN("set", BINF_PSPECIAL | BINF_HANDLES_OPTS, bin_set, 0, -1, 0, NULL, NULL),
BUILTIN("setopt", 0, bin_setopt, 0, -1, BIN_SETOPT, NULL, NULL),
BUILTIN("shift", BINF_PSPECIAL, bin_shift, 0, -1, 0, "p", NULL),
BUILTIN("source", BINF_PSPECIAL, bin_dot, 1, -1, 0, NULL, NULL),
BUILTIN("suspend", 0, bin_suspend, 0, 0, 0, "f", NULL),
BUILTIN("test", BINF_HANDLES_OPTS, bin_test, 0, -1, BIN_TEST, NULL, NULL),
BUILTIN("ttyctl", 0, bin_ttyctl, 0, 0, 0, "fu", NULL),
BUILTIN("times", BINF_PSPECIAL, bin_times, 0, 0, 0, NULL, NULL),
BUILTIN("trap", BINF_PSPECIAL | BINF_HANDLES_OPTS, bin_trap, 0, -1, 0, NULL, NULL),
BUILTIN("true", 0, bin_true, 0, -1, 0, NULL, NULL),
BUILTIN("type", 0, bin_whence, 0, -1, 0, "ampfsSw", "v"),
BUILTIN("typeset", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%klprtuxmz", NULL),
BUILTIN("umask", 0, bin_umask, 0, 1, 0, "S", NULL),
BUILTIN("unalias", 0, bin_unhash, 0, -1, BIN_UNALIAS, "ams", NULL),
BUILTIN("unfunction", 0, bin_unhash, 1, -1, BIN_UNFUNCTION, "m", "f"),
BUILTIN("unhash", 0, bin_unhash, 1, -1, BIN_UNHASH, "adfms", NULL),
BUILTIN("unset", BINF_PSPECIAL, bin_unset, 1, -1, 0, "fmv", NULL),
BUILTIN("unsetopt", 0, bin_setopt, 0, -1, BIN_UNSETOPT, NULL, NULL),
BUILTIN("wait", 0, bin_fg, 0, -1, BIN_WAIT, NULL, NULL),
BUILTIN("whence", 0, bin_whence, 0, -1, 0, "acmpvfsSwx:", NULL),
BUILTIN("where", 0, bin_whence, 0, -1, 0, "pmsSwx:", "ca"),
BUILTIN("which", 0, bin_whence, 0, -1, 0, "ampsSwx:", "c"),
BUILTIN("zmodload", 0, bin_zmodload, 0, -1, 0, "AFRILP:abcfdilmpue", NULL),
BUILTIN("zcompile", 0, bin_zcompile, 0, -1, 0, "tUMRcmzka", NULL),
};
/****************************************/
/* Builtin Command Hash Table Functions */
/****************************************/
/* hash table containing builtin commands */
/**/
mod_export HashTable builtintab;
/**/
void
createbuiltintable(void)
{
builtintab = newhashtable(85, "builtintab", NULL);
builtintab->hash = hasher;
builtintab->emptytable = NULL;
builtintab->filltable = NULL;
builtintab->cmpnodes = strcmp;
builtintab->addnode = addhashnode;
builtintab->getnode = gethashnode;
builtintab->getnode2 = gethashnode2;
builtintab->removenode = removehashnode;
builtintab->disablenode = disablehashnode;
builtintab->enablenode = enablehashnode;
builtintab->freenode = freebuiltinnode;
builtintab->printnode = printbuiltinnode;
(void)addbuiltins("zsh", builtins, sizeof(builtins)/sizeof(*builtins));
}
/* Print a builtin */
/**/
static void
printbuiltinnode(HashNode hn, int printflags)
{
Builtin bn = (Builtin) hn;
if (printflags & PRINT_WHENCE_WORD) {
printf("%s: builtin\n", bn->node.nam);
return;
}
if (printflags & PRINT_WHENCE_CSH) {
printf("%s: shell built-in command\n", bn->node.nam);
return;
}
if (printflags & PRINT_WHENCE_VERBOSE) {
printf("%s is a shell builtin\n", bn->node.nam);
return;
}
/* default is name only */
printf("%s\n", bn->node.nam);
}
/**/
static void
freebuiltinnode(HashNode hn)
{
Builtin bn = (Builtin) hn;
if(!(bn->node.flags & BINF_ADDED)) {
zsfree(bn->node.nam);
zsfree(bn->optstr);
zfree(bn, sizeof(struct builtin));
}
}
/**/
void
init_builtins(void)
{
if (!EMULATION(EMULATE_ZSH)) {
HashNode hn = reswdtab->getnode2(reswdtab, "repeat");
if (hn)
reswdtab->disablenode(hn, 0);
}
}
/* Make sure we have space for a new option and increment. */
#define OPT_ALLOC_CHUNK 16
/**/
static int
new_optarg(Options ops)
{
/* Argument index must be a non-zero 6-bit number. */
if (ops->argscount == 63)
return 1;
if (ops->argsalloc == ops->argscount) {
char **newptr =
(char **)zhalloc((ops->argsalloc + OPT_ALLOC_CHUNK) *
sizeof(char *));
if (ops->argsalloc)
memcpy(newptr, ops->args, ops->argsalloc * sizeof(char *));
ops->args = newptr;
ops->argsalloc += OPT_ALLOC_CHUNK;
}
ops->argscount++;
return 0;
}
/* execute a builtin handler function after parsing the arguments */
/**/
int
execbuiltin(LinkList args, LinkList assigns, Builtin bn)
{
char *pp, *name, *optstr;
int flags, sense, argc, execop, xtr = isset(XTRACE);
struct options ops;
/* initialise options structure */
memset(ops.ind, 0, MAX_OPS*sizeof(unsigned char));
ops.args = NULL;
ops.argscount = ops.argsalloc = 0;
/* initialize some local variables */
name = (char *) ugetnode(args);
if (!bn->handlerfunc) {
DPUTS(1, "Missing builtin detected too late");
deletebuiltin(bn->node.nam);
return 1;
}
/* get some information about the command */
flags = bn->node.flags;
optstr = bn->optstr;
/* Set up the argument list. */
/* count the arguments */
argc = countlinknodes(args);
{
/*
* Keep all arguments, including options, in an array.
* We don't actually need the option part of the argument
* after option processing, but it makes XTRACE output
* much simpler.
*/
VARARR(char *, argarr, argc + 1);
char **argv;
/*
* Get the actual arguments, into argv. Remember argarr
* may be an array declaration, depending on the compiler.
*/
argv = argarr;
while ((*argv++ = (char *)ugetnode(args)));
argv = argarr;
/* Sort out the options. */
if (optstr) {
char *arg = *argv;
/* while arguments look like options ... */
while (arg &&
/* Must begin with - or maybe + */
((sense = (*arg == '-')) ||
((flags & BINF_PLUSOPTS) && *arg == '+'))) {
/* Digits aren't arguments unless the command says they are. */
if (!(flags & BINF_KEEPNUM) && idigit(arg[1]))
break;
/* For cd and friends, a single dash is not an option. */
if ((flags & BINF_SKIPDASH) && !arg[1])
break;
if ((flags & BINF_DASHDASHVALID) && !strcmp(arg, "--")) {
/*
* Need to skip this before checking whether this is
* really an option.
*/
argv++;
break;
}
/*
* Unrecognised options to echo etc. are not really
* options.
*
* Note this flag is not smart enough to handle option
* arguments. In fact, ideally it shouldn't be added
* to any new builtins, to preserve standard option
* handling as much as possible.
*/
if (flags & BINF_SKIPINVALID) {
char *p = arg;
while (*++p && strchr(optstr, (int) *p));
if (*p)
break;
}
/* handle -- or - (ops.ind['-']), and +
* (ops.ind['-'] and ops.ind['+']) */
if (arg[1] == '-')
arg++;
if (!arg[1]) {
ops.ind['-'] = 1;
if (!sense)
ops.ind['+'] = 1;
}
/* save options in ops, as long as they are in bn->optstr */
while (*++arg) {
char *optptr;
if ((optptr = strchr(optstr, execop = (int)*arg))) {
ops.ind[(int)*arg] = (sense) ? 1 : 2;
if (optptr[1] == ':') {
char *argptr = NULL;
if (optptr[2] == ':') {
if (arg[1])
argptr = arg+1;
/* Optional argument in same word*/
} else if (optptr[2] == '%') {
/* Optional numeric argument in same
* or next word. */
if (arg[1] && idigit(arg[1]))
argptr = arg+1;
else if (argv[1] && idigit(*argv[1]))
argptr = arg = *++argv;
} else {
/* Mandatory argument */
if (arg[1])
argptr = arg+1;
else if ((arg = *++argv))
argptr = arg;
else {
zwarnnam(name, "argument expected: -%c",
execop);
return 1;
}
}
if (argptr) {
if (new_optarg(&ops)) {
zwarnnam(name,
"too many option arguments");
return 1;
}
ops.ind[execop] |= ops.argscount << 2;
ops.args[ops.argscount-1] = argptr;
while (arg[1])
arg++;
}
}
} else
break;
}
/* The above loop may have exited on an invalid option. (We *
* assume that any option requiring metafication is invalid.) */
if (*arg) {
if(*arg == Meta)
*++arg ^= 32;
zwarn("bad option: -%c", *arg);
return 1;
}
arg = *++argv;
/* for the "print" builtin, the options after -R are treated as
options to "echo" */
if ((flags & BINF_PRINTOPTS) && ops.ind['R'] &&
!ops.ind['f']) {
optstr = "ne";
flags |= BINF_SKIPINVALID;
}
/* the option -- indicates the end of the options */
if (ops.ind['-'])
break;
}
} else if (!(flags & BINF_HANDLES_OPTS) && *argv &&
!strcmp(*argv, "--")) {
ops.ind['-'] = 1;
argv++;
}
/* handle built-in options, for overloaded handler functions */
if ((pp = bn->defopts)) {
while (*pp) {
/* only if not already set */
if (!ops.ind[(int)*pp])
ops.ind[(int)*pp] = 1;
pp++;
}
}
/* Fix the argument count by subtracting option arguments */
argc -= argv - argarr;
if (errflag) {
errflag &= ~ERRFLAG_ERROR;
return 1;
}
/* check that the argument count lies within the specified bounds */
if (argc < bn->minargs || (argc > bn->maxargs && bn->maxargs != -1)) {
zwarnnam(name, (argc < bn->minargs)
? "not enough arguments" : "too many arguments");
return 1;
}
/* display execution trace information, if required */
if (xtr) {
/* Use full argument list including options for trace output */
char **fullargv = argarr;
printprompt4();
fprintf(xtrerr, "%s", name);
while (*fullargv) {
fputc(' ', xtrerr);
quotedzputs(*fullargv++, xtrerr);
}
if (assigns) {
LinkNode node;
for (node = firstnode(assigns); node; incnode(node)) {
Asgment asg = (Asgment)node;
fputc(' ', xtrerr);
quotedzputs(asg->name, xtrerr);
if (asg->is_array) {
LinkNode arrnode;
fprintf(xtrerr, "=(");
if (asg->value.array) {
for (arrnode = firstnode(asg->value.array);
arrnode;
incnode(arrnode)) {
fputc(' ', xtrerr);
quotedzputs((char *)getdata(arrnode), xtrerr);
}
}
fprintf(xtrerr, " )");
} else if (asg->value.scalar) {
fputc('=', xtrerr);
quotedzputs(asg->value.scalar, xtrerr);
}
}
}
fputc('\n', xtrerr);
fflush(xtrerr);
}
/* call the handler function, and return its return value */
if (flags & BINF_ASSIGN)
{
/*
* Takes two sets of arguments.
*/
HandlerFuncAssign assignfunc = (HandlerFuncAssign)bn->handlerfunc;
return (*(assignfunc)) (name, argv, assigns, &ops, bn->funcid);
}
else
{
return (*(bn->handlerfunc)) (name, argv, &ops, bn->funcid);
}
}
}
/* Enable/disable an element in one of the internal hash tables. *
* With no arguments, it lists all the currently enabled/disabled *
* elements in that particular hash table. */
/**/
int
bin_enable(char *name, char **argv, Options ops, int func)
{
HashTable ht;
HashNode hn;
ScanFunc scanfunc;
Patprog pprog;
int flags1 = 0, flags2 = 0;
int match = 0, returnval = 0;
/* Find out which hash table we are working with. */
if (OPT_ISSET(ops,'p')) {
return pat_enables(name, argv, func == BIN_ENABLE);
} else if (OPT_ISSET(ops,'f'))
ht = shfunctab;
else if (OPT_ISSET(ops,'r'))
ht = reswdtab;
else if (OPT_ISSET(ops,'s'))
ht = sufaliastab;
else if (OPT_ISSET(ops,'a'))
ht = aliastab;
else
ht = builtintab;
/* Do we want to enable or disable? */
if (func == BIN_ENABLE) {
flags2 = DISABLED;
scanfunc = ht->enablenode;
} else {
flags1 = DISABLED;
scanfunc = ht->disablenode;
}
/* Given no arguments, print the names of the enabled/disabled elements *
* in this hash table. If func == BIN_ENABLE, then scanhashtable will *
* print nodes NOT containing the DISABLED flag, else scanhashtable will *
* print nodes containing the DISABLED flag. */
if (!*argv) {
queue_signals();
scanhashtable(ht, 1, flags1, flags2, ht->printnode, 0);
unqueue_signals();
return 0;
}
/* With -m option, treat arguments as glob patterns. */
if (OPT_ISSET(ops,'m')) {
for (; *argv; argv++) {
/* parse pattern */
tokenize(*argv);
if ((pprog = patcompile(*argv, PAT_STATIC, 0))) {
queue_signals();
match += scanmatchtable(ht, pprog, 0, 0, 0, scanfunc, 0);
unqueue_signals();
}
else {
untokenize(*argv);
zwarnnam(name, "bad pattern : %s", *argv);
returnval = 1;
}
}
/* If we didn't match anything, we return 1. */
if (!match)
returnval = 1;
return returnval;
}
/* Take arguments literally -- do not glob */
queue_signals();
for (; *argv; argv++) {
if ((hn = ht->getnode2(ht, *argv))) {
scanfunc(hn, 0);
} else {
zwarnnam(name, "no such hash table element: %s", *argv);
returnval = 1;
}
}
unqueue_signals();
return returnval;
}
/* set: either set the shell options, or set the shell arguments, *
* or declare an array, or show various things */
/**/
int
bin_set(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
{
int action, optno, array = 0, hadopt = 0,
hadplus = 0, hadend = 0, sort = 0;
char **x, *arrayname = NULL;
/* Obsolescent sh compatibility: set - is the same as set +xv *
* and set - args is the same as set +xv -- args */
if (!EMULATION(EMULATE_ZSH) && *args && **args == '-' && !args[0][1]) {
dosetopt(VERBOSE, 0, 0, opts);
dosetopt(XTRACE, 0, 0, opts);
if (!args[1])
return 0;
}
/* loop through command line options (begins with "-" or "+") */
while (*args && (**args == '-' || **args == '+')) {
action = (**args == '-');
hadplus |= !action;
if(!args[0][1])
*args = "--";
while (*++*args) {
if(**args == Meta)
*++*args ^= 32;
if(**args != '-' || action)
hadopt = 1;
/* The pseudo-option `--' signifies the end of options. */
if (**args == '-') {
hadend = 1;
args++;
goto doneoptions;
} else if (**args == 'o') {
if (!*++*args)
args++;
if (!*args) {
printoptionstates(hadplus);
inittyptab();
return 0;
}
if(!(optno = optlookup(*args)))
zerrnam(nam, "no such option: %s", *args);
else if(dosetopt(optno, action, 0, opts))
zerrnam(nam, "can't change option: %s", *args);
break;
} else if(**args == 'A') {
if(!*++*args)
args++;
array = action ? 1 : -1;
arrayname = *args;
if (!arrayname)
goto doneoptions;
else if (!isset(KSHARRAYS))
{
args++;
goto doneoptions;
}
break;
} else if (**args == 's')
sort = action ? 1 : -1;
else {
if (!(optno = optlookupc(**args)))
zerrnam(nam, "bad option: -%c", **args);
else if(dosetopt(optno, action, 0, opts))
zerrnam(nam, "can't change option: -%c", **args);
}
}
args++;
}
if (errflag)
return 1;
doneoptions:
inittyptab();
/* Show the parameters, possibly with values */
queue_signals();
if (!arrayname)
{
if (!hadopt && !*args)
scanhashtable(paramtab, 1, 0, 0, paramtab->printnode,
hadplus ? PRINT_NAMEONLY : 0);
if (array) {
/* display arrays */
scanhashtable(paramtab, 1, PM_ARRAY, 0, paramtab->printnode,
hadplus ? PRINT_NAMEONLY : 0);
}
if (!*args && !hadend) {
unqueue_signals();
return 0;
}
}
if (sort)
strmetasort(args, sort < 0 ? SORTIT_BACKWARDS : 0, NULL);
if (array) {
/* create an array with the specified elements */
char **a = NULL, **y;
int len = arrlen(args);
if (array < 0 && (a = getaparam(arrayname))) {
int al = arrlen(a);
if (al > len)
len = al;
}
for (x = y = zalloc((len + 1) * sizeof(char *)); len--; a++) {
if (!*args)
args = a;
*y++ = ztrdup(*args++);
}
*y++ = NULL;
setaparam(arrayname, x);
} else {
/* set shell arguments */
freearray(pparams);
pparams = zarrdup(args);
}
unqueue_signals();
return 0;
}
/**** directory-handling builtins ****/
/**/
int doprintdir = 0; /* set in exec.c (for autocd) */
/* pwd: display the name of the current directory */
/**/
int
bin_pwd(UNUSED(char *name), UNUSED(char **argv), Options ops, UNUSED(int func))
{
if (OPT_ISSET(ops,'r') || OPT_ISSET(ops,'P') ||
(isset(CHASELINKS) && !OPT_ISSET(ops,'L')))
printf("%s\n", zgetcwd());
else {
zputs(pwd, stdout);
putchar('\n');
}
return 0;
}
/* the directory stack */
/**/
mod_export LinkList dirstack;
/* dirs: list the directory stack, or replace it with a provided list */
/**/
int
bin_dirs(UNUSED(char *name), char **argv, Options ops, UNUSED(int func))
{
LinkList l;
queue_signals();
/* with -v, -p or no arguments display the directory stack */
if (!(*argv || OPT_ISSET(ops,'c')) || OPT_ISSET(ops,'v') ||
OPT_ISSET(ops,'p')) {
LinkNode node;
char *fmt;
int pos = 1;
/* with the -v option, display a numbered list, starting at zero */
if (OPT_ISSET(ops,'v')) {
printf("0\t");
fmt = "\n%d\t";
/* with the -p option, display entries one per line */
} else if (OPT_ISSET(ops,'p'))
fmt = "\n";
else
fmt = " ";
if (OPT_ISSET(ops,'l'))
zputs(pwd, stdout);
else
fprintdir(pwd, stdout);
for (node = firstnode(dirstack); node; incnode(node)) {
printf(fmt, pos++);
if (OPT_ISSET(ops,'l'))
zputs(getdata(node), stdout);
else
fprintdir(getdata(node), stdout);
}
unqueue_signals();
putchar('\n');
return 0;
}
/* replace the stack with the specified directories */
l = znewlinklist();
while (*argv)
zaddlinknode(l, ztrdup(*argv++));
freelinklist(dirstack, freestr);
dirstack = l;
unqueue_signals();
return 0;
}
/* cd, chdir, pushd, popd */
/**/
void
set_pwd_env(void)
{
Param pm;
/* update the PWD and OLDPWD shell parameters */
pm = (Param) paramtab->getnode(paramtab, "PWD");
if (pm && PM_TYPE(pm->node.flags) != PM_SCALAR) {
pm->node.flags &= ~PM_READONLY;
unsetparam_pm(pm, 0, 1);
}
pm = (Param) paramtab->getnode(paramtab, "OLDPWD");
if (pm && PM_TYPE(pm->node.flags) != PM_SCALAR) {
pm->node.flags &= ~PM_READONLY;
unsetparam_pm(pm, 0, 1);
}
setsparam("PWD", ztrdup(pwd));
setsparam("OLDPWD", ztrdup(oldpwd));
pm = (Param) paramtab->getnode(paramtab, "PWD");
if (!(pm->node.flags & PM_EXPORTED))
addenv(pm, pwd);
pm = (Param) paramtab->getnode(paramtab, "OLDPWD");
if (!(pm->node.flags & PM_EXPORTED))
addenv(pm, oldpwd);
}
/* set if we are resolving links to their true paths */
static int chasinglinks;
/* 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 ancillary processing associated with actually changing *
* directory. */
/**/
int
bin_cd(char *nam, char **argv, Options ops, int func)
{
LinkNode dir;
struct stat st1, st2;
if (isset(RESTRICTED)) {
zwarnnam(nam, "restricted");
return 1;
}
doprintdir = (doprintdir == -1);
chasinglinks = OPT_ISSET(ops,'P') ||
(isset(CHASELINKS) && !OPT_ISSET(ops,'L'));
queue_signals();
zpushnode(dirstack, ztrdup(pwd));
if (!(dir = cd_get_dest(nam, argv, OPT_ISSET(ops,'s'), func))) {
zsfree(getlinknode(dirstack));
unqueue_signals();
return 1;
}
cd_new_pwd(func, dir, OPT_ISSET(ops, 'q'));
if (stat(unmeta(pwd), &st1) < 0) {
setjobpwd();
zsfree(pwd);
pwd = NULL;
pwd = metafy(zgetcwd(), -1, META_DUP);
} else if (stat(".", &st2) < 0) {
if (chdir(unmeta(pwd)) < 0)
zwarn("unable to chdir(%s): %e", pwd, errno);
} else if (st1.st_ino != st2.st_ino || st1.st_dev != st2.st_dev) {
if (chasinglinks) {
setjobpwd();
zsfree(pwd);
pwd = NULL;
pwd = metafy(zgetcwd(), -1, META_DUP);
} else if (chdir(unmeta(pwd)) < 0)
zwarn("unable to chdir(%s): %e", pwd, errno);
}
unqueue_signals();
return 0;
}
/* Get directory to chdir to */
/**/
static LinkNode
cd_get_dest(char *nam, char **argv, int hard, int func)
{
LinkNode dir = NULL;
LinkNode target;
char *dest;
if (!argv[0]) {
if (func == BIN_POPD && !nextnode(firstnode(dirstack))) {
zwarnnam(nam, "directory stack empty");
return NULL;
}
if (func == BIN_PUSHD && unset(PUSHDTOHOME))
dir = nextnode(firstnode(dirstack));
if (dir)
zinsertlinknode(dirstack, dir, getlinknode(dirstack));
else if (func != BIN_POPD)
zpushnode(dirstack, ztrdup(home));
} else if (!argv[1]) {
int dd;
char *end;
doprintdir++;
if (argv[0][1] && (argv[0][0] == '+' || argv[0][0] == '-')
&& strspn(argv[0]+1, "0123456789") == strlen(argv[0]+1)) {
dd = zstrtol(argv[0] + 1, &end, 10);
if (*end == '\0') {
if ((argv[0][0] == '+') ^ isset(PUSHDMINUS))
for (dir = firstnode(dirstack); dir && dd; dd--, incnode(dir));
else
for (dir = lastnode(dirstack); dir != (LinkNode) dirstack && dd;
dd--, dir = prevnode(dir));
if (!dir || dir == (LinkNode) dirstack) {
zwarnnam(nam, "no such entry in dir stack");
return NULL;
}
}
}
if (!dir)
zpushnode(dirstack, ztrdup(strcmp(argv[0], "-")
? (doprintdir--, argv[0]) : oldpwd));
} else {
char *u, *d;
int len1, len2, len3;
if (!(u = strstr(pwd, argv[0]))) {
zwarnnam(nam, "string not in pwd: %s", argv[0]);
return NULL;
}
len1 = strlen(argv[0]);
len2 = strlen(argv[1]);
len3 = u - pwd;
d = (char *)zalloc(len3 + len2 + strlen(u + len1) + 1);
strncpy(d, pwd, len3);
strcpy(d + len3, argv[1]);
strcat(d, u + len1);
zpushnode(dirstack, d);
doprintdir++;
}
target = dir;
if (func == BIN_POPD) {
if (!dir) {
target = dir = firstnode(dirstack);
} else if (dir != firstnode(dirstack)) {
return dir;
}
dir = nextnode(dir);
}
if (!dir) {
dir = firstnode(dirstack);
}
if (!(dest = cd_do_chdir(nam, getdata(dir), hard))) {
if (!target)
zsfree(getlinknode(dirstack));
if (func == BIN_POPD)
zsfree(remnode(dirstack, dir));
return NULL;
}
if (dest != (char *)getdata(dir)) {
zsfree(getdata(dir));
setdata(dir, dest);
}
return target ? target : dir;
}
/* 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. */
/**/
static char *
cd_do_chdir(char *cnam, char *dest, int hard)
{
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;
#ifdef __CYGWIN__
/*
* Normalize path under Cygwin to avoid messing with
* DOS style names with drives in them
*/
static char buf[PATH_MAX];
#ifdef HAVE_CYGWIN_CONV_PATH
cygwin_conv_path(CCP_WIN_A_TO_POSIX | CCP_RELATIVE, dest, buf,
PATH_MAX);
#else
#ifndef _SYS_CYGWIN_H
void cygwin_conv_to_posix_path(const char *, char *);
#endif
cygwin_conv_to_posix_path(dest, buf);
#endif
dest = buf;
#endif
nocdpath = dest[0] == '.' &&
(dest[1] == '/' || !dest[1] || (dest[1] == '.' &&
(dest[2] == '/' || !dest[2])));
/*
* If we have an absolute path, use it as-is only
*/
if (*dest == '/') {
if ((ret = cd_try_chdir(NULL, dest, hard)))
return ret;
zwarnnam(cnam, "%e: %s", errno, dest);
return NULL;
}
/*
* If cdpath is being used, check it for ".".
* Don't bother doing this if POSIXCD is set, we don't
* need to know (though it doesn't actually matter).
*/
if (!nocdpath && !isset(POSIXCD))
for (pp = cdpath; *pp; pp++)
if (!(*pp)[0] || ((*pp)[0] == '.' && (*pp)[1] == '\0'))
hasdot = 1;
/*
* If
* (- there is no . in cdpath
* - or cdpath is not being used)
* - and the POSIXCD option is not set
* try the directory as-is (i.e. from .)
*/
if (!hasdot && !isset(POSIXCD)) {
if ((ret = cd_try_chdir(NULL, dest, hard)))
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, hard))) {
if (isset(POSIXCD)) {
/*
* For POSIX we need to print the directory
* any time CDPATH was used, except in the
* special case of an empty segment being
* treated as a ".".
*/
if (**pp)
doprintdir++;
} else {
if (strcmp(*pp, ".")) {
doprintdir++;
}
}
return ret;
}
if (errno != ENOENT)
eno = errno;
}
/*
* POSIX requires us to check "." after CDPATH rather than before.
*/
if (isset(POSIXCD)) {
if ((ret = cd_try_chdir(NULL, dest, hard)))
return ret;
if (errno != ENOENT)
eno = errno;
}
/* handle the CDABLEVARS option */
if ((ret = cd_able_vars(dest))) {
if ((ret = cd_try_chdir(NULL, ret,hard))) {
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", eno, dest);
return NULL;
}
/* If the CDABLEVARS option is set, return the new *
* interpretation of the given path. */
/**/
char *
cd_able_vars(char *s)
{
char *rest, save;
if (isset(CDABLEVARS)) {
for (rest = s; *rest && *rest != '/'; rest++);
save = *rest;
*rest = 0;
s = getnameddir(s);
*rest = save;
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. */
/**/
static char *
cd_try_chdir(char *pfix, char *dest, int hard)
{
char *buf;
int dlen, dochaselinks = 0;
/* handle directory prefix */
if (pfix && *pfix) {
if (*pfix == '/') {
#ifdef __CYGWIN__
/* NB: Don't turn "/"+"bin" into "//"+"bin" by mistake! "//bin" may *
* not be what user really wants (probably wants "/bin"), but *
* "//bin" could be valid too (see fixdir())! This is primarily for *
* handling CDPATH correctly. Likewise for "//"+"bin" not becoming *
* "///bin" (aka "/bin"). */
int root = pfix[1] == '\0' || (pfix[1] == '/' && pfix[2] == '\0');
buf = tricat(pfix, ( root ? "" : "/" ), dest);
#else
buf = tricat(pfix, "/", dest);
#endif
} else {
int pfl = strlen(pfix);
dlen = strlen(pwd);
if (dlen == 1 && *pwd == '/')
dlen = 0;
buf = zalloc(dlen + pfl + strlen(dest) + 3);
if (dlen)
strcpy(buf, pwd);
buf[dlen] = '/';
strcpy(buf + dlen + 1, pfix);
buf[dlen + 1 + pfl] = '/';
strcpy(buf + dlen + pfl + 2, dest);
}
} else if (*dest == '/')
buf = ztrdup(dest);
else {
dlen = strlen(pwd);
if (pwd[dlen-1] == '/')
--dlen;
buf = zalloc(dlen + strlen(dest) + 2);
strcpy(buf, pwd);
buf[dlen] = '/';
strcpy(buf + dlen + 1, dest);
}
/* Normalise path. See the definition of fixdir() for what this means.
* We do not do this if we are chasing links.
*/
if (!chasinglinks)
dochaselinks = fixdir(buf);
else
unmetafy(buf, &dlen);
/* We try the full path first. If that fails, try the
* argument to cd relatively. This is useful if the cwd
* or a parent directory is renamed in the interim.
*/
if (lchdir(buf, NULL, hard) &&
(pfix || *dest == '/' || lchdir(dest, NULL, hard))) {
free(buf);
return NULL;
}
/* the chdir succeeded, so decide if we should force links to be chased */
if (dochaselinks)
chasinglinks = 1;
return metafy(buf, -1, META_NOALLOC);
}
/* do the extra processing associated with changing directory */
/**/
static void
cd_new_pwd(int func, LinkNode dir, int quiet)
{
char *new_pwd, *s;
int dirstacksize;
if (func == BIN_PUSHD)
rolllist(dirstack, dir);
new_pwd = remnode(dirstack, dir);
if (func == BIN_POPD && firstnode(dirstack)) {
zsfree(new_pwd);
new_pwd = getlinknode(dirstack);
} else if (func == BIN_CD && unset(AUTOPUSHD))
zsfree(getlinknode(dirstack));
if (chasinglinks) {
s = findpwd(new_pwd);
if (s) {
zsfree(new_pwd);
new_pwd = s;
}
}
if (isset(PUSHDIGNOREDUPS)) {
LinkNode n;
for (n = firstnode(dirstack); n; incnode(n)) {
if (!strcmp(new_pwd, getdata(n))) {
zsfree(remnode(dirstack, n));
break;
}
}
}
/* shift around the pwd variables, to make oldpwd and pwd relate to the
current (i.e. new) pwd */
zsfree(oldpwd);
oldpwd = pwd;
setjobpwd();
pwd = new_pwd;
set_pwd_env();
if (isset(INTERACTIVE) || isset(POSIXCD)) {
if (func != BIN_CD && isset(INTERACTIVE)) {
if (unset(PUSHDSILENT) && !quiet)
printdirstack();
} else if (doprintdir) {
fprintdir(pwd, stdout);
putchar('\n');
}
}
/* execute the chpwd function */
fflush(stdout);
fflush(stderr);
if (!quiet)
callhookfunc("chpwd", NULL, 1, NULL);
dirstacksize = getiparam("DIRSTACKSIZE");
/* handle directory stack sizes out of range */
if (dirstacksize > 0) {
int remove = countlinknodes(dirstack) -
(dirstacksize < 2 ? 2 : dirstacksize);
while (remove-- >= 0)
zsfree(remnode(dirstack, lastnode(dirstack)));
}
}
/* Print the directory stack */
/**/
static void
printdirstack(void)
{
LinkNode node;
fprintdir(pwd, stdout);
for (node = firstnode(dirstack); node; incnode(node)) {
putchar(' ');
fprintdir(getdata(node), stdout);
}
putchar('\n');
}
/* Normalise a path. Segments consisting of ., and foo/.. *
* combinations, are removed and the path is unmetafied.
* Returns 1 if we found a ../ path which should force links to
* be chased, 0 otherwise.
*/
/**/
int
fixdir(char *src)
{
char *dest = src, *d0 = dest;
#ifdef __CYGWIN__
char *s0 = src;
#endif
int ret = 0;
/*** 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 == '/') {
#ifdef __CYGWIN__
/* allow leading // under cygwin, but /// still becomes / */
if (src == s0 && src[1] == '/' && src[2] != '/')
*dest++ = *src++;
#endif
*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 ret;
}
if (src[0] == '.' && src[1] == '.' &&
(src[2] == '\0' || src[2] == '/')) {
if (isset(CHASEDOTS)) {
ret = 1;
/* and treat as normal path segment */
} else {
if (dest > d0 + 1) {
/*
* remove a foo/.. combination:
* first check foo exists, else return.
*/
struct stat st;
*dest = '\0';
if (stat(d0, &st) < 0 || !S_ISDIR(st.st_mode)) {
char *ptrd, *ptrs;
if (dest == src)
*dest = '.';
for (ptrs = src, ptrd = dest; *ptrs; ptrs++, ptrd++)
*ptrd = (*ptrs == Meta) ? (*++ptrs ^ 32) : *ptrs;
*ptrd = '\0';
return 1;
}
for (dest--; dest > d0 + 1 && dest[-1] != '/'; dest--);
if (dest[-1] != '/')
dest--;
}
src++;
while (*++src == '/');
continue;
}
}
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')
if ((*dest++ = *src++) == Meta)
dest[-1] = *src++ ^ 32;
}
}
}
/**/
mod_export void
printqt(char *str)
{
/* Print str, but turn any single quote into '\'' or ''. */
for (; *str; str++)
if (*str == '\'')
printf(isset(RCQUOTES) ? "''" : "'\\''");
else
putchar(*str);
}
/**/
mod_export void
printif(char *str, int c)
{
/* If flag c has an argument, print that */
if (str) {
printf(" -%c ", c);
quotedzputs(str, stdout);
}
}
/**** history list functions ****/
/* fc, history, r */
/**/
int
bin_fc(char *nam, char **argv, Options ops, int func)
{
zlong first = -1, last = -1;
int retval;
char *s;
struct asgment *asgf = NULL, *asgl = NULL;
Patprog pprog = NULL;
/* fc is only permitted in interactive shells */
#ifdef FACIST_INTERACTIVE
if (!interact) {
zwarnnam(nam, "not interactive shell");
return 1;
}
#endif
if (OPT_ISSET(ops,'p')) {
char *hf = "";
zlong hs = DEFAULT_HISTSIZE;
zlong shs = 0;
int level = OPT_ISSET(ops,'a') ? locallevel : -1;
if (*argv) {
hf = *argv++;
if (*argv) {
char *check;
hs = zstrtol(*argv++, &check, 10);
if (*check) {
zwarnnam("fc", "HISTSIZE must be an integer");
return 1;
}
if (*argv) {
shs = zstrtol(*argv++, &check, 10);
if (*check) {
zwarnnam("fc", "SAVEHIST must be an integer");
return 1;
}
} else
shs = hs;
if (*argv) {
zwarnnam("fc", "too many arguments");
return 1;
}
} else {
hs = histsiz;
shs = savehistsiz;
}
}
if (!pushhiststack(hf, hs, shs, level))
return 1;
if (*hf) {
struct stat st;
if (stat(hf, &st) >= 0 || errno != ENOENT)
readhistfile(hf, 1, HFILE_USE_OPTIONS);
}
return 0;
}
if (OPT_ISSET(ops,'P')) {
if (*argv) {
zwarnnam("fc", "too many arguments");
return 1;
}
return !saveandpophiststack(-1, HFILE_USE_OPTIONS);
}
/* with the -m option, the first argument is taken *
* as a pattern that history lines have to match */
if (*argv && OPT_ISSET(ops,'m')) {
tokenize(*argv);
if (!(pprog = patcompile(*argv++, 0, NULL))) {
zwarnnam(nam, "invalid match pattern");
return 1;
}
}
queue_signals();
if (OPT_ISSET(ops,'R')) {
/* read history from a file */
readhistfile(*argv, 1, OPT_ISSET(ops,'I') ? HFILE_SKIPOLD : 0);
unqueue_signals();
return 0;
}
if (OPT_ISSET(ops,'W')) {
/* write history to a file */
savehistfile(*argv, 1, OPT_ISSET(ops,'I') ? HFILE_SKIPOLD : 0);
unqueue_signals();
return 0;
}
if (OPT_ISSET(ops,'A')) {
/* append history to a file */
savehistfile(*argv, 1, HFILE_APPEND |
(OPT_ISSET(ops,'I') ? HFILE_SKIPOLD : 0));
unqueue_signals();
return 0;
}
if (zleactive) {
zwarnnam(nam, "no interactive history within ZLE");
return 1;
}
/* put foo=bar type arguments into the substitution list */
while (*argv && equalsplit(*argv, &s)) {
Asgment a = (Asgment) zhalloc(sizeof *a);
if (!**argv) {
zwarnnam(nam, "invalid replacement pattern: =%s", s);
return 1;
}
if (!asgf)
asgf = asgl = a;
else {
asgl->node.next = &a->node;
asgl = a;
}
a->name = *argv;
a->is_array = 0;
a->value.scalar = s;
a->node.next = a->node.prev = NULL;
argv++;
}
/* interpret and check first history line specifier */
if (*argv) {
first = fcgetcomm(*argv);
if (first == -1) {
unqueue_signals();
return 1;
}
argv++;
}
/* interpret and check second history line specifier */
if (*argv) {
last = fcgetcomm(*argv);
if (last == -1) {
unqueue_signals();
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) {
unqueue_signals();
zwarnnam("fc", "too many arguments");
return 1;
}
/* default values of first and last, and range checking */
if (last == -1) {
if (OPT_ISSET(ops,'l') && first < curhist) {
/*
* When listing base our calculations on curhist,
* to show anything added since the edited history line.
* Also, in that case curhist will have been modified
* past the current history line; then we want to
* show everything, because the user expects to
* see the result of "print -s". Otherwise, we subtract
* -1 from the line, because the user doesn't usually expect
* to see the command line that caused history to be
* listed.
*/
last = (curline.histnum == curhist) ? addhistnum(curhist,-1,0)
: curhist;
if (last < firsthist())
last = firsthist();
}
else
last = first;
}
if (first == -1) {
/*
* When listing, we want to see everything that's been
* added to the history, including by print -s, so use
* curhist.
* When reexecuting, we want to restrict to the last edited
* command line to avoid giving the user a nasty turn
* if some helpful soul ran "print -s 'rm -rf /'".
*/
first = OPT_ISSET(ops,'l')? addhistnum(curhist,-16,0)
: addhistnum(curline.histnum,-1,0);
if (first < 1)
first = 1;
if (last < first)
last = first;
}
if (OPT_ISSET(ops,'l')) {
/* list the required part of the history */
retval = fclist(stdout, ops, first, last, asgf, pprog, 0);
unqueue_signals();
}
else {
/* edit history file, and (if successful) use the result as a new command */
int tempfd;
FILE *out;
char *fil;
retval = 1;
if ((tempfd = gettempfile(NULL, 1, &fil)) < 0
|| ((out = fdopen(tempfd, "w")) == NULL)) {
unqueue_signals();
zwarnnam("fc", "can't open temp file: %e", errno);
} else {
/*
* Nasty behaviour results if we use the current history
* line here. Treat it as if it doesn't exist, unless
* that gives us an empty range.
*/
if (last >= curhist) {
last = curhist - 1;
if (first > last) {
unqueue_signals();
zwarnnam("fc",
"current history line would recurse endlessly, aborted");
fclose(out);
unlink(fil);
return 1;
}
}
ops->ind['n'] = 1; /* No line numbers here. */
if (!fclist(out, ops, first, last, asgf, pprog, 1)) {
char *editor;
if (func == BIN_R)
editor = "-";
else if (OPT_HASARG(ops, 'e'))
editor = OPT_ARG(ops, 'e');
else
editor = getsparam("FCEDIT");
if (!editor)
editor = getsparam("EDITOR");
if (!editor)
editor = DEFAULT_FCEDIT;
unqueue_signals();
if (fcedit(editor, fil)) {
if (stuff(fil))
zwarnnam("fc", "%e: %s", errno, s);
else {
loop(0,1);
retval = lastval;
}
}
} else
unqueue_signals();
}
unlink(fil);
}
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 */
/**/
static zlong
fcgetcomm(char *s)
{
zlong cmd;
/* First try to match a history number. Negative *
* numbers indicate reversed numbering. */
if ((cmd = atoi(s)) != 0 || *s == '0') {
if (cmd < 0)
cmd = addhistnum(curline.histnum,cmd,HIST_FOREIGN);
if (cmd < 0)
cmd = 0;
return cmd;
}
/* not a number, so search by string */
cmd = hcomsearch(s);
if (cmd == -1)
zwarnnam("fc", "event not found: %s", s);
return cmd;
}
/* Perform old=new substitutions. Uses the asgment structure from zsh.h, *
* which is essentially a linked list of string,replacement pairs. */
/**/
static 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.scalar;
sub = (Asgment)sub->node.next;
oldpos = s;
/* loop over occurences of oldstr in s, replacing them with newstr */
while ((newpos = (char *)strstr(oldpos, oldstr))) {
newmem = (char *) zhalloc(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. */
/**/
static int
fclist(FILE *f, Options ops, zlong first, zlong last,
struct asgment *subs, Patprog pprog, int is_command)
{
int fclistdone = 0, xflags = 0;
zlong tmp;
char *s, *tdfmt, *timebuf;
Histent ent;
/* reverse range if required */
if (OPT_ISSET(ops,'r')) {
tmp = last;
last = first;
first = tmp;
}
if (is_command && first > last) {
zwarnnam("fc", "history events can't be executed backwards, aborted");
if (f != stdout)
fclose(f);
return 1;
}
ent = gethistent(first, first < last? GETHIST_DOWNWARD : GETHIST_UPWARD);
if (!ent || (first < last? ent->histnum > last : ent->histnum < last)) {
if (first == last) {
char buf[DIGBUFSIZE];
convbase(buf, first, 10);
zwarnnam("fc", "no such event: %s", buf);
} else
zwarnnam("fc", "no events in that range");
if (f != stdout)
fclose(f);
return 1;
}
if (OPT_ISSET(ops,'d') || OPT_ISSET(ops,'f') ||
OPT_ISSET(ops,'E') || OPT_ISSET(ops,'i') ||
OPT_ISSET(ops,'t')) {
if (OPT_ISSET(ops,'t')) {
tdfmt = OPT_ARG(ops,'t');
} else if (OPT_ISSET(ops,'i')) {
tdfmt = "%Y-%m-%d %H:%M";
} else if (OPT_ISSET(ops,'E')) {
tdfmt = "%f.%-m.%Y %H:%M";
} else if (OPT_ISSET(ops,'f')) {
tdfmt = "%-m/%f/%Y %H:%M";
} else {
tdfmt = "%H:%M";
}
timebuf = zhalloc(256);
} else {
tdfmt = timebuf = NULL;
}
/* xflags exclude events */
if (OPT_ISSET(ops,'L')) {
xflags |= HIST_FOREIGN;
}
if (OPT_ISSET(ops,'I')) {
xflags |= HIST_READ;
}
for (;;) {
if (ent->node.flags & xflags)
s = NULL;
else
s = dupstring(ent->node.nam);
/* this if does the pattern matching, if required */
if (s && (!pprog || pattry(pprog, s))) {
/* perform substitution */
fclistdone |= (subs ? fcsubs(&s, subs) : 1);
/* do numbering */
if (!OPT_ISSET(ops,'n')) {
char buf[DIGBUFSIZE];
convbase(buf, ent->histnum, 10);
fprintf(f, "%5s%c ", buf,
ent->node.flags & HIST_FOREIGN ? '*' : ' ');
}
/* output actual time (and possibly date) of execution of the
command, if required */
if (tdfmt != NULL) {
struct tm *ltm;
ltm = localtime(&ent->stim);
if (ztrftime(timebuf, 256, tdfmt, ltm, 0L))
fprintf(f, "%s ", timebuf);
}
/* display the time taken by the command, if required */
if (OPT_ISSET(ops,'D')) {
long diff;
diff = (ent->ftim) ? ent->ftim - ent->stim : 0;
fprintf(f, "%ld:%02ld ", diff / 60, diff % 60);
}
/* output the command */
if (f == stdout) {
nicezputs(s, f);
putc('\n', f);
} else {
int len;
unmetafy(s, &len);
fwrite(s, 1, len, f);
putc('\n', f);
}
}
/* move on to the next history line, or quit the loop */
if (first < last) {
if (!(ent = down_histent(ent)) || ent->histnum > last)
break;
}
else {
if (!(ent = up_histent(ent)) || ent->histnum < last)
break;
}
}
/* final processing */
if (f != stdout)
fclose(f);
if (!fclistdone) {
if (subs)
zwarnnam("fc", "no substitutions performed");
else if (xflags || pprog)
zwarnnam("fc", "no matching events found");
return 1;
}
return 0;
}
/* edit a history file */
/**/
static int
fcedit(char *ename, char *fn)
{
char *s;
if (!strcmp(ename, "-"))
return 1;
s = tricat(ename, " ", fn);
execstring(s, 1, 0, "fc");
zsfree(s);
return !lastval;
}
/**** parameter builtins ****/
/* Separate an argument into name=value parts, returning them in an *
* asgment structure. Because the asgment structure used is global, *
* only one of these can be active at a time. The string s gets placed *
* in this global structure, so it needs to be in permanent memory. */
/**/
static Asgment
getasg(char ***argvp, LinkList assigns)
{
char *s = **argvp;
static struct asgment asg;
/* sanity check for valid argument */
if (!s) {
if (assigns) {
Asgment asgp = (Asgment)firstnode(assigns);
if (!asgp)
return NULL;
(void)uremnode(assigns, &asgp->node);
return asgp;
}
return NULL;
}
/* check if name is empty */
if (*s == '=') {
zerr("bad assignment");
return NULL;
}
asg.name = s;
asg.is_array = 0;
/* search for `=' */
for (; *s && *s != '='; s++);
/* found `=', so return with a value */
if (*s) {
*s = '\0';
asg.value.scalar = s + 1;
} else {
/* didn't find `=', so we only have a name */
asg.value.scalar = NULL;
}
(*argvp)++;
return &asg;
}
/* for new special parameters */
enum {
NS_NONE,
NS_NORMAL,
NS_SECONDS
};
static const struct gsu_scalar tiedarr_gsu =
{ tiedarrgetfn, tiedarrsetfn, tiedarrunsetfn };
/* Install a base if we are turning on a numeric option with an argument */
static int
typeset_setbase(const char *name, Param pm, Options ops, int on, int always)
{
char *arg = NULL;
if ((on & PM_INTEGER) && OPT_HASARG(ops,'i'))
arg = OPT_ARG(ops,'i');
else if ((on & PM_EFLOAT) && OPT_HASARG(ops,'E'))
arg = OPT_ARG(ops,'E');
else if ((on & PM_FFLOAT) && OPT_HASARG(ops,'F'))
arg = OPT_ARG(ops,'F');
if (arg) {
char *eptr;
int base = (int)zstrtol(arg, &eptr, 10);
if (*eptr) {
if (on & PM_INTEGER)
zwarnnam(name, "bad base value: %s", arg);
else
zwarnnam(name, "bad precision value: %s", arg);
return 1;
}
if ((on & PM_INTEGER) && (base < 2 || base > 36)) {
zwarnnam(name, "invalid base (must be 2 to 36 inclusive): %d",
base);
return 1;
}
pm->base = base;
} else if (always)
pm->base = 0;
return 0;
}
/* Install a width if we are turning on a padding option with an argument */
static int
typeset_setwidth(const char * name, Param pm, Options ops, int on, int always)
{
char *arg = NULL;
if ((on & PM_LEFT) && OPT_HASARG(ops,'L'))
arg = OPT_ARG(ops,'L');
else if ((on & PM_RIGHT_B) && OPT_HASARG(ops,'R'))
arg = OPT_ARG(ops,'R');
else if ((on & PM_RIGHT_Z) && OPT_HASARG(ops,'Z'))
arg = OPT_ARG(ops,'Z');
if (arg) {
char *eptr;
pm->width = (int)zstrtol(arg, &eptr, 10);
if (*eptr) {
zwarnnam(name, "bad width value: %s", arg);
return 1;
}
} else if (always)
pm->width = 0;
return 0;
}
/* function to set a single parameter */
/**/
static Param
typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
int on, int off, int roff, Asgment asg, Param altpm,
Options ops, int joinchar)
{
int usepm, tc, keeplocal = 0, newspecial = NS_NONE, readonly, dont_set = 0;
char *subscript;
/*
* Do we use the existing pm? Note that this isn't the end of the
* story, because if we try and create a new pm at the same
* locallevel as an unset one we use the pm struct anyway: that's
* handled in createparam(). Here we just avoid using it for the
* present tests if it's unset.
*
* POSIXBUILTINS horror: we need to retain the 'readonly' flag
* of an unset parameter.
*/
usepm = pm && (!(pm->node.flags & PM_UNSET) ||
(isset(POSIXBUILTINS) && (pm->node.flags & PM_READONLY)));
/*
* We need to compare types with an existing pm if special,
* even if that's unset
*/
if (!usepm && pm && (pm->node.flags & PM_SPECIAL))
usepm = 2; /* indicate that we preserve the PM_UNSET flag */
/*
* Don't use an existing param if
* - the local level has changed, and
* - we are really locallizing the parameter
*/
if (usepm && locallevel != pm->level && (on & PM_LOCAL)) {
/*
* If the original parameter was special and we're creating
* a new one, we need to keep it special.
*
* The -h (hide) flag prevents an existing special being made
* local. It can be applied either to the special or in the
* typeset/local statement for the local variable.
*/
if ((pm->node.flags & PM_SPECIAL)
&& !(on & PM_HIDE) && !(pm->node.flags & PM_HIDE & ~off))
newspecial = NS_NORMAL;
usepm = 0;
}
/* attempting a type conversion, or making a tied colonarray? */
tc = 0;
if (ASG_ARRAYP(asg) && PM_TYPE(on) == PM_SCALAR &&
!(usepm && (PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED))))
on |= PM_ARRAY;
if (usepm && ASG_ARRAYP(asg) && newspecial == NS_NONE &&
PM_TYPE(pm->node.flags) != PM_ARRAY &&
PM_TYPE(pm->node.flags) != PM_HASHED) {
if (on & (PM_EFLOAT|PM_FFLOAT|PM_INTEGER)) {
zerrnam(cname, "%s: can't assign array value to non-array", pname);
return NULL;
}
if (pm->node.flags & PM_SPECIAL) {
zerrnam(cname, "%s: can't assign array value to non-array special", pname);
return NULL;
}
tc = 1;
usepm = 0;
}
else if (usepm || newspecial != NS_NONE) {
int chflags = ((off & pm->node.flags) | (on & ~pm->node.flags)) &
(PM_INTEGER|PM_EFLOAT|PM_FFLOAT|PM_HASHED|
PM_ARRAY|PM_TIED|PM_AUTOLOAD);
/* keep the parameter if just switching between floating types */
if ((tc = chflags && chflags != (PM_EFLOAT|PM_FFLOAT)))
usepm = 0;
}
/*
* Extra checks if converting the type of a parameter, or if
* trying to remove readonlyness. It's dangerous doing either
* with a special or a parameter which isn't loaded yet (which
* may be special when it is loaded; we can't tell yet).
*/
if ((readonly =
((usepm || newspecial != NS_NONE) &&
(off & pm->node.flags & PM_READONLY))) ||
tc) {
if (pm->node.flags & PM_SPECIAL) {
int err = 1;
if (!readonly && !strcmp(pname, "SECONDS"))
{
/*
* We allow SECONDS to change type between integer
* and floating point. If we are creating a new
* local copy we check the type here and allow
* a new special to be created with that type.
* We then need to make sure the correct type
* for the special is restored at the end of the scope.
* If we are changing the type of an existing
* parameter, we do the whole thing here.
*/
if (newspecial != NS_NONE)
{
/*
* The first test allows `typeset' to copy the
* existing type. This is the usual behaviour
* for making special parameters local.
*/
if (PM_TYPE(on) == 0 || PM_TYPE(on) == PM_INTEGER ||
PM_TYPE(on) == PM_FFLOAT || PM_TYPE(on) == PM_EFLOAT)
{
newspecial = NS_SECONDS;
err = 0; /* and continue */
tc = 0; /* but don't do a normal conversion */
}
} else if (!setsecondstype(pm, on, off)) {
if (asg->value.scalar && !(pm = setsparam(pname, ztrdup(asg->value.scalar))))
return NULL;
usepm = 1;
err = 0;
}
}
if (err)
{
zerrnam(cname, "%s: can't change type of a special parameter",
pname);
return NULL;
}
} else if (pm->node.flags & PM_AUTOLOAD) {
zerrnam(cname, "%s: can't change type of autoloaded parameter",
pname);
return NULL;
}
}
else if (newspecial != NS_NONE && strcmp(pname, "SECONDS") == 0)
newspecial = NS_SECONDS;
if (isset(POSIXBUILTINS)) {
/*
* Stricter rules about retaining readonly attribute in this case.
*/
if ((on & PM_READONLY) && (!usepm || (pm->node.flags & PM_UNSET)) &&
!ASG_VALUEP(asg))
on |= PM_UNSET;
else if (usepm && (pm->node.flags & PM_READONLY) &&
!(on & PM_READONLY)) {
zerr("read-only variable: %s", pm->node.nam);
return NULL;
}
}
/*
* A parameter will be local if
* 1. we are re-using an existing local parameter
* or
* 2. we are not using an existing parameter, but
* i. there is already a parameter, which will be hidden
* or
* ii. we are creating a new local parameter
*/
if (usepm) {
if (asg->is_array ?
!(PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)) :
(asg->value.scalar && (PM_TYPE(pm->node.flags &
(PM_ARRAY|PM_HASHED))))) {
zerrnam(cname, "%s: inconsistent type for assignment", pname);
return NULL;
}
on &= ~PM_LOCAL;
if (!on && !roff && !ASG_VALUEP(asg)) {
if (OPT_ISSET(ops,'p'))
paramtab->printnode(&pm->node, PRINT_TYPESET);
else if (!OPT_ISSET(ops,'g') &&
(unset(TYPESETSILENT) || OPT_ISSET(ops,'m')))
paramtab->printnode(&pm->node, PRINT_INCLUDEVALUE);
return pm;
}
if ((pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) {
zerrnam(cname, "%s: restricted", pname);
return pm;
}
if ((on & PM_UNIQUE) && !(pm->node.flags & PM_READONLY & ~off)) {
Param apm;
char **x;
if (PM_TYPE(pm->node.flags) == PM_ARRAY) {
x = (*pm->gsu.a->getfn)(pm);
uniqarray(x);
if (pm->node.flags & PM_SPECIAL) {
if (zheapptr(x))
x = zarrdup(x);
(*pm->gsu.a->setfn)(pm, x);
} else if (pm->ename && x)
arrfixenv(pm->ename, x);
} else if (PM_TYPE(pm->node.flags) == PM_SCALAR && pm->ename &&
(apm =
(Param) paramtab->getnode(paramtab, pm->ename))) {
x = (*apm->gsu.a->getfn)(apm);
uniqarray(x);
if (x)
arrfixenv(pm->node.nam, x);
}
}
if (usepm == 2) /* do not change the PM_UNSET flag */
pm->node.flags = (pm->node.flags | (on & ~PM_READONLY)) & ~off;
else {
/*
* Keep unset if using readonly in POSIX mode.
*/
if (!(on & PM_READONLY) || !isset(POSIXBUILTINS))
off |= PM_UNSET;
pm->node.flags = (pm->node.flags |
(on & ~PM_READONLY)) & ~off;
}
if (on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) {
if (typeset_setwidth(cname, pm, ops, on, 0))
return NULL;
}
if (on & (PM_INTEGER | PM_EFLOAT | PM_FFLOAT)) {
if (typeset_setbase(cname, pm, ops, on, 0))
return NULL;
}
if (!(pm->node.flags & (PM_ARRAY|PM_HASHED))) {
if (pm->node.flags & PM_EXPORTED) {
if (!(pm->node.flags & PM_UNSET) && !pm->env && !ASG_VALUEP(asg))
addenv(pm, getsparam(pname));
} else if (pm->env && !(pm->node.flags & PM_HASHELEM))
delenv(pm);
DPUTS(ASG_ARRAYP(asg), "BUG: typeset got array value where scalar expected");
if (asg->value.scalar && !(pm = setsparam(pname, ztrdup(asg->value.scalar))))
return NULL;
} else if (asg->is_array) {
if (!(pm = setaparam(pname, asg->value.array ?
zlinklist2array(asg->value.array) :
mkarray(NULL))))
return NULL;
}
pm->node.flags |= (on & PM_READONLY);
if (OPT_ISSET(ops,'p'))
paramtab->printnode(&pm->node, PRINT_TYPESET);
return pm;
}
if (asg->is_array ?
!(on & (PM_ARRAY|PM_HASHED)) :
(asg->value.scalar && (on & (PM_ARRAY|PM_HASHED)))) {
zerrnam(cname, "%s: inconsistent type for assignment", pname);
return NULL;
}
/*
* We're here either because we're creating a new parameter,
* or we're adding a parameter at a different local level,
* or we're converting the type of a parameter. In the
* last case only, we need to delete the old parameter.
*/
if (tc) {
/* Maintain existing readonly/exported status... */
on |= ~off & (PM_READONLY|PM_EXPORTED) & pm->node.flags;
/* ...but turn off existing readonly so we can delete it */
pm->node.flags &= ~PM_READONLY;
/*
* If we're just changing the type, we should keep the
* variable at the current level of localness.
*/
keeplocal = pm->level;
/*
* Try to carry over a value, but not when changing from,
* to, or between non-scalar types.
*
* (We can do better now, but it does have user-visible
* implications.)
*/
if (!ASG_VALUEP(asg) && !((pm->node.flags|on) & (PM_ARRAY|PM_HASHED))) {
asg->value.scalar = dupstring(getsparam(pname));
asg->is_array = 0;
}
/* pname may point to pm->nam which is about to disappear */
pname = dupstring(pname);
unsetparam_pm(pm, 0, 1);
}
if (newspecial != NS_NONE) {
Param tpm, pm2;
if ((pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) {
zerrnam(cname, "%s: restricted", pname);
return pm;
}
/*
* For specials, we keep the same struct but zero everything.
* Maybe it would be easier to create a new struct but copy
* the get/set methods.
*/
tpm = (Param) zshcalloc(sizeof *tpm);
tpm->node.nam = pm->node.nam;
if (pm->ename &&
(pm2 = (Param) paramtab->getnode(paramtab, pm->ename)) &&
pm2->level == locallevel) {
/* This is getting silly, but anyway: if one of a path/PATH
* pair has already been made local at the current level, we
* have to make sure that the other one does not have its value
* saved: since that comes from an internal variable it will
* already reflect the local value, so restoring it on exit
* would be wrong.
*
* This problem is also why we make sure we have a copy
* of the environment entry in tpm->env, rather than relying
* on the restored value to provide it.
*/
tpm->node.flags = pm->node.flags | PM_NORESTORE;
} else {
copyparam(tpm, pm, 1);
}
tpm->old = pm->old;
tpm->level = pm->level;
tpm->base = pm->base;
tpm->width = pm->width;
if (pm->env)
delenv(pm);
tpm->env = NULL;
pm->old = tpm;
/*
* The remaining on/off flags should be harmless to use,
* because we've checked for unpleasant surprises above.
*/
pm->node.flags = (PM_TYPE(pm->node.flags) | on | PM_SPECIAL) & ~off;
/*
* Readonlyness of special parameters must be preserved.
*/
pm->node.flags |= tpm->node.flags & PM_READONLY;
if (newspecial == NS_SECONDS) {
/* We save off the raw internal value of the SECONDS var */
tpm->u.dval = getrawseconds();
setsecondstype(pm, on, off);
}
/*
* Final tweak: if we've turned on one of the flags with
* numbers, we should use the appropriate integer.
*/
if (on & (PM_LEFT|PM_RIGHT_B|PM_RIGHT_Z)) {
if (typeset_setwidth(cname, pm, ops, on, 1))
return NULL;
}
if (on & (PM_INTEGER|PM_EFLOAT|PM_FFLOAT)) {
if (typeset_setbase(cname, pm, ops, on, 1))
return NULL;
}
} else if ((subscript = strchr(pname, '['))) {
if (on & PM_READONLY) {
zerrnam(cname,
"%s: can't create readonly array elements", pname);
return NULL;
} else if ((on & PM_LOCAL) && locallevel) {
*subscript = 0;
pm = (Param) (paramtab == realparamtab ?
gethashnode2(paramtab, pname) :
paramtab->getnode(paramtab, pname));
*subscript = '[';
if (!pm || pm->level != locallevel) {
zerrnam(cname,
"%s: can't create local array elements", pname);
return NULL;
}
}
if (PM_TYPE(on) == PM_SCALAR && !ASG_ARRAYP(asg)) {
/*
* This will either complain about bad identifiers, or will set
* a hash element or array slice. This once worked by accident,
* creating a stray parameter along the way via createparam(),
* now called below in the isident() branch.
*/
if (!(pm = setsparam(pname, ztrdup(asg->value.scalar ? asg->value.scalar : ""))))
return NULL;
dont_set = 1;
asg->is_array = 0;
keeplocal = 0;
on = pm->node.flags;
} else if (PM_TYPE(on) == PM_ARRAY && ASG_ARRAYP(asg)) {
if (!(pm = setaparam(pname, asg->value.array ?
zlinklist2array(asg->value.array) :
mkarray(NULL))))
return NULL;
dont_set = 1;
keeplocal = 0;
on = pm->node.flags;
} else {
zerrnam(cname,
"%s: i1;nconsistent array element or slice assignment", pname);
return NULL;
}
}
/*
* As we can hide existing parameters, we allow a name if
* it's not a normal identifier but is one of the special
* set found in the parameter table. The second test is
* because we can set individual positional parameters;
* however "0" is not a positional parameter and is OK.
*
* It would be neater to extend isident() and be clearer
* about where we allow various parameter types. It's
* not entirely clear to me isident() should reject
* specially named parameters given that it accepts digits.
*/
else if ((isident(pname) || paramtab->getnode(paramtab, pname))
&& (!idigit(*pname) || !strcmp(pname, "0"))) {
/*
* Create a new node for a parameter with the flags in `on' minus the
* readonly flag
*/
pm = createparam(pname, on & ~PM_READONLY);
if (!pm) {
if (on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z |
PM_INTEGER | PM_EFLOAT | PM_FFLOAT))
zerrnam(cname, "can't change variable attribute: %s", pname);
return NULL;
}
if (on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) {
if (typeset_setwidth(cname, pm, ops, on, 0))
return NULL;
}
if (on & (PM_INTEGER | PM_EFLOAT | PM_FFLOAT)) {
if (typeset_setbase(cname, pm, ops, on, 0))
return NULL;
}
} else {
if (idigit(*pname))
zerrnam(cname, "not an identifier: %s", pname);
else
zerrnam(cname, "not valid in this context: %s", pname);
return NULL;
}
if (altpm && PM_TYPE(pm->node.flags) == PM_SCALAR) {
/*
* It seems safer to set this here than in createparam(),
* to make sure we only ever use the colonarr functions
* when u.data is correctly set.
*/
struct tieddata *tdp = (struct tieddata *)
zalloc(sizeof(struct tieddata));
if (!tdp)
return NULL;
tdp->joinchar = joinchar;
tdp->arrptr = &altpm->u.arr;
pm->gsu.s = &tiedarr_gsu;
pm->u.data = tdp;
}
if (keeplocal)
pm->level = keeplocal;
else if (on & PM_LOCAL)
pm->level = locallevel;
if (ASG_VALUEP(asg) && !dont_set) {
Param ipm = pm;
if (pm->node.flags & (PM_ARRAY|PM_HASHED)) {
DPUTS(!ASG_ARRAYP(asg), "BUG: inconsistent scalar value for array");
if (!(pm=setaparam(pname, asg->value.array ?
zlinklist2array(asg->value.array) :
mkarray(NULL))))
return NULL;
} else {
DPUTS(ASG_ARRAYP(asg), "BUG: inconsistent array value for scalar");
if (!(pm = setsparam(pname, ztrdup(asg->value.scalar))))
return NULL;
}
if (pm != ipm) {
DPUTS(ipm->node.flags != pm->node.flags,
"BUG: parameter recreated with wrong flags");
unsetparam_pm(ipm, 0, 1);
}
} else if (newspecial != NS_NONE &&
!(pm->old->node.flags & (PM_NORESTORE|PM_READONLY))) {
/*
* We need to use the special setting function to re-initialise
* the special parameter to empty.
*/
switch (PM_TYPE(pm->node.flags)) {
case PM_SCALAR:
pm->gsu.s->setfn(pm, ztrdup(""));
break;
case PM_INTEGER:
/*
* Restricted integers are dangerous to initialize to 0,
* so don't do that.
*/
if (!(pm->old->node.flags & PM_RESTRICTED))
pm->gsu.i->setfn(pm, 0);
break;
case PM_EFLOAT:
case PM_FFLOAT:
pm->gsu.f->setfn(pm, 0.0);
break;
case PM_ARRAY:
pm->gsu.a->setfn(pm, mkarray(NULL));
break;
case PM_HASHED:
pm->gsu.h->setfn(pm, newparamtable(17, pm->node.nam));
break;
}
}
pm->node.flags |= (on & PM_READONLY);
if (OPT_ISSET(ops,'p'))
paramtab->printnode(&pm->node, PRINT_TYPESET);
return pm;
}
/*
* declare, export, float, integer, local, readonly, typeset
*
* Note the difference in interface from most builtins, covered by the
* BINF_ASSIGN builtin flag. This is only made use of by builtins
* called by reserved word, which only covers declare, local, readonly
* and typeset. Otherwise assigns is NULL.
*/
/**/
int
bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func)
{
Param pm;
Asgment asg;
Patprog pprog;
char *optstr = TYPESET_OPTSTR;
int on = 0, off = 0, roff, bit = PM_ARRAY;
int i;
int returnval = 0, printflags = 0;
int hasargs;
/* hash -f is really the builtin `functions' */
if (OPT_ISSET(ops,'f'))
return bin_functions(name, argv, ops, func);
/* Translate the options into PM_* flags. *
* Unfortunately, this depends on the order *
* these flags are defined in zsh.h */
for (; *optstr; optstr++, bit <<= 1)
{
int optval = STOUC(*optstr);
if (OPT_MINUS(ops,optval))
on |= bit;
else if (OPT_PLUS(ops,optval))
off |= bit;
}
roff = off;
/* Sanity checks on the options. Remove conflicting options. */
if (on & PM_FFLOAT) {
off |= PM_UPPER | PM_ARRAY | PM_HASHED | PM_INTEGER | PM_EFLOAT;
/* Allow `float -F' to work even though float sets -E by default */
on &= ~PM_EFLOAT;
}
if (on & PM_EFLOAT)
off |= PM_UPPER | PM_ARRAY | PM_HASHED | PM_INTEGER | PM_FFLOAT;
if (on & PM_INTEGER)
off |= PM_UPPER | PM_ARRAY | PM_HASHED | PM_EFLOAT | PM_FFLOAT;
/*
* Allowing -Z with -L is a feature: left justify, suppressing
* leading zeroes.
*/
if (on & (PM_LEFT|PM_RIGHT_Z))
off |= PM_RIGHT_B;
if (on & PM_RIGHT_B)
off |= PM_LEFT | PM_RIGHT_Z;
if (on & PM_UPPER)
off |= PM_LOWER;
if (on & PM_LOWER)
off |= PM_UPPER;
if (on & PM_HASHED)
off |= PM_ARRAY;
if (on & PM_TIED)
off |= PM_INTEGER | PM_EFLOAT | PM_FFLOAT | PM_ARRAY | PM_HASHED;
on &= ~off;
queue_signals();
/* Given no arguments, list whatever the options specify. */
if (OPT_ISSET(ops,'p'))
printflags |= PRINT_TYPESET;
hasargs = *argv != NULL || (assigns && firstnode(assigns));
if (!hasargs) {
if (!OPT_ISSET(ops,'p')) {
if (!(on|roff))
printflags |= PRINT_TYPE;
if (roff || OPT_ISSET(ops,'+'))
printflags |= PRINT_NAMEONLY;
}
scanhashtable(paramtab, 1, on|roff, 0, paramtab->printnode, printflags);
unqueue_signals();
return 0;
}
if (!(OPT_ISSET(ops,'g') || OPT_ISSET(ops,'x') || OPT_ISSET(ops,'m')) ||
OPT_PLUS(ops,'g') || *name == 'l' ||
(!isset(GLOBALEXPORT) && !OPT_ISSET(ops,'g')))
on |= PM_LOCAL;
if (on & PM_TIED) {
Param apm;
struct asgment asg0, asg2;
char *oldval = NULL, *joinstr;
int joinchar, nargs;
if (OPT_ISSET(ops,'m')) {
zwarnnam(name, "incompatible options for -T");
unqueue_signals();
return 1;
}
on &= ~off;
nargs = arrlen(argv) + (assigns ? countlinknodes(assigns) : 0);
if (nargs < 2) {
zwarnnam(name, "-T requires names of scalar and array");
unqueue_signals();
return 1;
}
if (nargs > 3) {
zwarnnam(name, "too many arguments for -T");
unqueue_signals();
return 1;
}
if (!(asg = getasg(&argv, assigns))) {
unqueue_signals();
return 1;
}
asg0 = *asg;
if (ASG_ARRAYP(&asg0)) {
unqueue_signals();
zwarnnam(name, "first argument of tie must be scalar: %s",
asg0.name);
return 1;
}
if (!(asg = getasg(&argv, assigns))) {
unqueue_signals();
return 1;
}
if (!ASG_ARRAYP(asg) && asg->value.scalar) {
unqueue_signals();
zwarnnam(name, "second argument of tie must be array: %s",
asg->name);
return 1;
}
if (!strcmp(asg0.name, asg->name)) {
unqueue_signals();
zerrnam(name, "can't tie a variable to itself: %s", asg0.name);
return 1;
}
if (strchr(asg0.name, '[') || strchr(asg->name, '[')) {
unqueue_signals();
zerrnam(name, "can't tie array elements: %s", asg0.name);
return 1;
}
if (ASG_VALUEP(asg) && ASG_VALUEP(&asg0)) {
unqueue_signals();
zerrnam(name, "only one tied parameter can have value: %s", asg0.name);
return 1;
}
/*
* Third argument, if given, is character used to join
* the elements of the array in the scalar.
*/
if (*argv)
joinstr = *argv;
else if (assigns && firstnode(assigns)) {
Asgment nextasg = (Asgment)firstnode(assigns);
if (ASG_ARRAYP(nextasg) || ASG_VALUEP(nextasg)) {
zwarnnam(name, "third argument of tie must be join character");
unqueue_signals();
return 1;
}
joinstr = nextasg->name;
} else
joinstr = NULL;
if (!joinstr)
joinchar = ':';
else if (!*joinstr)
joinchar = 0;
else if (*joinstr == Meta)
joinchar = joinstr[1] ^ 32;
else
joinchar = *joinstr;
/*
* Keep the old value of the scalar. We need to do this
* here as if it is already tied to the same array it
* will be unset when we retie the array. This is all
* so that typeset -T is idempotent.
*
* We also need to remember here whether the damn thing is
* exported and pass that along. Isn't the world complicated?
*/
if ((pm = (Param) paramtab->getnode(paramtab, asg0.name))
&& !(pm->node.flags & PM_UNSET)
&& (locallevel == pm->level || !(on & PM_LOCAL))) {
if (pm->node.flags & PM_TIED) {
unqueue_signals();
if (!strcmp(asg->name, pm->ename)) {
/*
* Already tied in the fashion requested.
*/
struct tieddata *tdp = (struct tieddata*)pm->u.data;
/* Update join character */
tdp->joinchar = joinchar;
if (asg0.value.scalar)
setsparam(asg0.name, ztrdup(asg0.value.scalar));
return 0;
} else {
zwarnnam(name, "can't tie already tied scalar: %s",
asg0.name);
}
return 1;
}
if (!asg0.value.scalar && !asg->value.array &&
!(PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)))
oldval = ztrdup(getsparam(asg0.name));
on |= (pm->node.flags & PM_EXPORTED);
}
/*
* Create the tied array; this is normal except that
* it has the PM_TIED flag set. Do it first because
* we need the address.
*
* Don't attempt to set it yet, it's too early
* to be exported properly.
*/
asg2.name = asg->name;
asg2.is_array = 0;
asg2.value.array = (LinkList)0;
if (!(apm=typeset_single(name, asg->name,
(Param)paramtab->getnode(paramtab,
asg->name),
func, (on | PM_ARRAY) & ~PM_EXPORTED,
off, roff, &asg2, NULL, ops, 0))) {
if (oldval)
zsfree(oldval);
unqueue_signals();
return 1;
}
/*
* Create the tied colonarray. We make it as a normal scalar
* and fix up the oddities later.
*/
if (!(pm=typeset_single(name, asg0.name,
(Param)paramtab->getnode(paramtab,
asg0.name),
func, on, off, roff, &asg0, apm,
ops, joinchar))) {
if (oldval)
zsfree(oldval);
unsetparam_pm(apm, 1, 1);
unqueue_signals();
return 1;
}
/*
* pm->ename is only deleted when the struct is, so
* we need to free it here if it already exists.
*/
if (pm->ename)
zsfree(pm->ename);
pm->ename = ztrdup(asg->name);
if (apm->ename)
zsfree(apm->ename);
apm->ename = ztrdup(asg0.name);
if (asg->value.array)
setaparam(asg->name, zlinklist2array(asg->value.array));
else if (oldval)
setsparam(asg0.name, oldval);
unqueue_signals();
return 0;
}
if (off & PM_TIED) {
zerrnam(name, "use unset to remove tied variables");
return 1;
}
/* With the -m option, treat arguments as glob patterns */
if (OPT_ISSET(ops,'m')) {
if (!OPT_ISSET(ops,'p')) {
if (!(on|roff))
printflags |= PRINT_TYPE;
if (!on)
printflags |= PRINT_NAMEONLY;
}
while ((asg = getasg(&argv, assigns))) {
LinkList pmlist = newlinklist();
LinkNode pmnode;
tokenize(asg->name); /* expand argument */
if (!(pprog = patcompile(asg->name, 0, NULL))) {
untokenize(asg->name);
zwarnnam(name, "bad pattern : %s", asg->name);
returnval = 1;
continue;
}
if (OPT_PLUS(ops,'m') && !ASG_VALUEP(asg)) {
scanmatchtable(paramtab, pprog, 1, on|roff, 0,
paramtab->printnode, printflags);
continue;
}
/*
* Search through the parameter table and change all parameters
* matching the glob pattern to have these flags and/or value.
* Bad news: if the parameter gets altered, e.g. by
* a type conversion, then paramtab can be shifted around,
* so we need to store the parameters to alter on a separate
* list for later use.
*/
for (i = 0; i < paramtab->hsize; i++) {
for (pm = (Param) paramtab->nodes[i]; pm;
pm = (Param) pm->node.next) {
if (((pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) ||
(pm->node.flags & PM_UNSET))
continue;
if (pattry(pprog, pm->node.nam))
addlinknode(pmlist, pm);
}
}
for (pmnode = firstnode(pmlist); pmnode; incnode(pmnode)) {
pm = (Param) getdata(pmnode);
if (!typeset_single(name, pm->node.nam, pm, func, on, off, roff,
asg, NULL, ops, 0))
returnval = 1;
}
}
unqueue_signals();
return returnval;
}
/* Take arguments literally. Don't glob */
while ((asg = getasg(&argv, assigns))) {
HashNode hn = (paramtab == realparamtab ?
gethashnode2(paramtab, asg->name) :
paramtab->getnode(paramtab, asg->name));
if (OPT_ISSET(ops,'p')) {
if (hn)
printparamnode(hn, printflags);
else {
zwarnnam(name, "no such variable: %s", asg->name);
returnval = 1;
}
continue;
}
if (!typeset_single(name, asg->name, (Param)hn,
func, on, off, roff, asg, NULL,
ops, 0))
returnval = 1;
}
unqueue_signals();
return returnval;
}
/* Helper for bin_functions() when run as "autoload -X" */
/**/
int
eval_autoload(Shfunc shf, char *name, Options ops, int func)
{
if (!(shf->node.flags & PM_UNDEFINED))
return 1;
if (shf->funcdef) {
freeeprog(shf->funcdef);
shf->funcdef = &dummy_eprog;
}
if (OPT_MINUS(ops,'X')) {
char *fargv[3];
fargv[0] = name;
fargv[1] = "\"$@\"";
fargv[2] = 0;
shf->funcdef = mkautofn(shf);
return bin_eval(name, fargv, ops, func);
}
return !loadautofn(shf, (OPT_ISSET(ops,'k') ? 2 :
(OPT_ISSET(ops,'z') ? 0 : 1)), 1);
}
/* List a user-defined math function. */
static void
listusermathfunc(MathFunc p)
{
int showargs;
if (p->module)
showargs = 3;
else if (p->maxargs != (p->minargs ? p->minargs : -1))
showargs = 2;
else if (p->minargs)
showargs = 1;
else
showargs = 0;
printf("functions -M %s", p->name);
if (showargs) {
printf(" %d", p->minargs);
showargs--;
}
if (showargs) {
printf(" %d", p->maxargs);
showargs--;
}
if (showargs) {
/*
* function names are not required to consist of ident characters
*/
putchar(' ');
quotedzputs(p->module, stdout);
showargs--;
}
putchar('\n');
}
/* Display or change the attributes of shell functions. *
* If called as autoload, it will define a new autoloaded *
* (undefined) shell function. */
/**/
int
bin_functions(char *name, char **argv, Options ops, int func)
{
Patprog pprog;
Shfunc shf;
int i, returnval = 0;
int on = 0, off = 0, pflags = 0, roff, expand = 0;
/* Do we have any flags defined? */
if (OPT_PLUS(ops,'u'))
off |= PM_UNDEFINED;
else if (OPT_MINUS(ops,'u') || OPT_ISSET(ops,'X'))
on |= PM_UNDEFINED;
if (OPT_MINUS(ops,'U'))
on |= PM_UNALIASED|PM_UNDEFINED;
else if (OPT_PLUS(ops,'U'))
off |= PM_UNALIASED;
if (OPT_MINUS(ops,'t'))
on |= PM_TAGGED;
else if (OPT_PLUS(ops,'t'))
off |= PM_TAGGED;
if (OPT_MINUS(ops,'T'))
on |= PM_TAGGED_LOCAL;
else if (OPT_PLUS(ops,'T'))
off |= PM_TAGGED_LOCAL;
roff = off;
if (OPT_MINUS(ops,'z')) {
on |= PM_ZSHSTORED;
off |= PM_KSHSTORED;
} else if (OPT_PLUS(ops,'z')) {
off |= PM_ZSHSTORED;
roff |= PM_ZSHSTORED;
}
if (OPT_MINUS(ops,'k')) {
on |= PM_KSHSTORED;
off |= PM_ZSHSTORED;
} else if (OPT_PLUS(ops,'k')) {
off |= PM_KSHSTORED;
roff |= PM_KSHSTORED;
}
if ((off & PM_UNDEFINED) || (OPT_ISSET(ops,'k') && OPT_ISSET(ops,'z')) ||
(OPT_ISSET(ops,'x') && !OPT_HASARG(ops,'x')) ||
(OPT_MINUS(ops,'X') && (OPT_ISSET(ops,'m') || *argv || !scriptname))) {
zwarnnam(name, "invalid option(s)");
return 1;
}
if (OPT_ISSET(ops,'x')) {
char *eptr;
expand = (int)zstrtol(OPT_ARG(ops,'x'), &eptr, 10);
if (*eptr) {
zwarnnam(name, "number expected after -x");
return 1;
}
if (expand == 0) /* no indentation at all */
expand = -1;
}
if (OPT_PLUS(ops,'f') || roff || OPT_ISSET(ops,'+'))
pflags |= PRINT_NAMEONLY;
if (OPT_MINUS(ops,'M') || OPT_PLUS(ops,'M')) {
MathFunc p, q;
/*
* Add/remove/list function as mathematical.
*/
if (on || off || pflags || OPT_ISSET(ops,'X') || OPT_ISSET(ops,'u')
|| OPT_ISSET(ops,'U') || OPT_ISSET(ops,'w')) {
zwarnnam(name, "invalid option(s)");
return 1;
}
if (!*argv) {
/* List functions. */
queue_signals();
for (p = mathfuncs; p; p = p->next)
if (p->flags & MFF_USERFUNC)
listusermathfunc(p);
unqueue_signals();
} else if (OPT_ISSET(ops,'m')) {
/* List matching functions. */
for (; *argv; argv++) {
tokenize(*argv);
if ((pprog = patcompile(*argv, PAT_STATIC, 0))) {
queue_signals();
for (p = mathfuncs, q = NULL; p; q = p) {
MathFunc next;
do {
next = NULL;
if ((p->flags & MFF_USERFUNC) &&
pattry(pprog, p->name)) {
if (OPT_PLUS(ops,'M')) {
next = p->next;
removemathfunc(q, p);
p = next;
} else
listusermathfunc(p);
}
/* if we deleted one, retry with the new p */
} while (next);
if (p)
p = p->next;
}
unqueue_signals();
} else {
untokenize(*argv);
zwarnnam(name, "bad pattern : %s", *argv);
returnval = 1;
}
}
} else if (OPT_PLUS(ops,'M')) {
/* Delete functions. -m is allowed but is handled above. */
for (; *argv; argv++) {
queue_signals();
for (p = mathfuncs, q = NULL; p; q = p, p = p->next) {
if (!strcmp(p->name, *argv)) {
if (!(p->flags & MFF_USERFUNC)) {
zwarnnam(name, "+M %s: is a library function",
*argv);
returnval = 1;
break;
}
removemathfunc(q, p);
break;
}
}
unqueue_signals();
}
} else {
/* Add a function */
int minargs = 0, maxargs = -1;
char *funcname = *argv++;
char *modname = NULL;
char *ptr;
ptr = itype_end(funcname, IIDENT, 0);
if (idigit(*funcname) || funcname == ptr || *ptr) {
zwarnnam(name, "-M %s: bad math function name", funcname);
return 1;
}
if (*argv) {
minargs = (int)zstrtol(*argv, &ptr, 0);
if (minargs < 0 || *ptr) {
zwarnnam(name, "-M: invalid min number of arguments: %s",
*argv);
return 1;
}
maxargs = minargs;
argv++;
}
if (*argv) {
maxargs = (int)zstrtol(*argv, &ptr, 0);
if (maxargs < -1 ||
(maxargs != -1 && maxargs < minargs) ||
*ptr) {
zwarnnam(name,
"-M: invalid max number of arguments: %s",
*argv);
return 1;
}
argv++;
}
if (*argv)
modname = *argv++;
if (*argv) {
zwarnnam(name, "-M: too many arguments");
return 1;
}
p = (MathFunc)zshcalloc(sizeof(struct mathfunc));
p->name = ztrdup(funcname);
p->flags = MFF_USERFUNC;
p->module = modname ? ztrdup(modname) : NULL;
p->minargs = minargs;
p->maxargs = maxargs;
queue_signals();
for (q = mathfuncs; q; q = q->next) {
if (!strcmp(q->name, funcname)) {
zwarnnam(name, "-M %s: function already exists",
funcname);
zsfree(p->name);
zsfree(p->module);
zfree(p, sizeof(struct mathfunc));
return 1;
}
}
p->next = mathfuncs;
mathfuncs = p;
unqueue_signals();
}
return returnval;
}
/* If no arguments given, we will print functions. If flags *
* are given, we will print only functions containing these *
* flags, else we'll print them all. */
if (!*argv) {
int ret = 0;
queue_signals();
if (OPT_MINUS(ops,'X')) {
if ((shf = (Shfunc) shfunctab->getnode(shfunctab, scriptname))) {
DPUTS(!shf->funcdef,
"BUG: Calling autoload from empty function");
} else {
shf = (Shfunc) zshcalloc(sizeof *shf);
shfunctab->addnode(shfunctab, ztrdup(scriptname), shf);
}
shf->node.flags = on;
ret = eval_autoload(shf, scriptname, ops, func);
} else {
if (OPT_ISSET(ops,'U') && !OPT_ISSET(ops,'u'))
on &= ~PM_UNDEFINED;
scanshfunc(1, on|off, DISABLED, shfunctab->printnode,
pflags, expand);
}
unqueue_signals();
return ret;
}
/* With the -m option, treat arguments as glob patterns */
if (OPT_ISSET(ops,'m')) {
on &= ~PM_UNDEFINED;
for (; *argv; argv++) {
/* expand argument */
tokenize(*argv);
if ((pprog = patcompile(*argv, PAT_STATIC, 0))) {
/* with no options, just print all functions matching the glob pattern */
queue_signals();
if (!(on|off) && !OPT_ISSET(ops,'X')) {
scanmatchshfunc(pprog, 1, 0, DISABLED,
shfunctab->printnode, pflags, expand);
} else {
/* apply the options to all functions matching the glob pattern */
for (i = 0; i < shfunctab->hsize; i++) {
for (shf = (Shfunc) shfunctab->nodes[i]; shf;
shf = (Shfunc) shf->node.next)
if (pattry(pprog, shf->node.nam) &&
!(shf->node.flags & DISABLED)) {
shf->node.flags = (shf->node.flags |
(on & ~PM_UNDEFINED)) & ~off;
if (OPT_ISSET(ops,'X') &&
eval_autoload(shf, shf->node.nam, ops, func)) {
returnval = 1;
}
}
}
}
unqueue_signals();
} else {
untokenize(*argv);
zwarnnam(name, "bad pattern : %s", *argv);
returnval = 1;
}
}
return returnval;
}
/* Take the arguments literally -- do not glob */
queue_signals();
for (; *argv; argv++) {
if (OPT_ISSET(ops,'w'))
returnval = dump_autoload(name, *argv, on, ops, func);
else if ((shf = (Shfunc) shfunctab->getnode(shfunctab, *argv))) {
/* if any flag was given */
if (on|off) {
/* turn on/off the given flags */
shf->node.flags = (shf->node.flags | (on & ~PM_UNDEFINED)) & ~off;
if (OPT_ISSET(ops,'X') &&
eval_autoload(shf, shf->node.nam, ops, func))
returnval = 1;
} else
/* no flags, so just print */
printshfuncexpand(&shf->node, pflags, expand);
} else if (on & PM_UNDEFINED) {
int signum = -1, ok = 1;
if (!strncmp(*argv, "TRAP", 4) &&
(signum = getsignum(*argv + 4)) != -1) {
/*
* Because of the possibility of alternative names,
* we must remove the trap explicitly.
*/
removetrapnode(signum);
}
/* Add a new undefined (autoloaded) function to the *
* hash table with the corresponding flags set. */
shf = (Shfunc) zshcalloc(sizeof *shf);
shf->node.flags = on;
shf->funcdef = mkautofn(shf);
shfunc_set_sticky(shf);
shfunctab->addnode(shfunctab, ztrdup(*argv), shf);
if (signum != -1) {
if (settrap(signum, NULL, ZSIG_FUNC)) {
shfunctab->removenode(shfunctab, *argv);
shfunctab->freenode(&shf->node);
returnval = 1;
ok = 0;
}
}
if (ok && OPT_ISSET(ops,'X') &&
eval_autoload(shf, shf->node.nam, ops, func))
returnval = 1;
} else
returnval = 1;
}
unqueue_signals();
return returnval;
}
/**/
Eprog
mkautofn(Shfunc shf)
{
Eprog p;
p = (Eprog) zalloc(sizeof(*p));
p->len = 5 * sizeof(wordcode);
p->prog = (Wordcode) zalloc(p->len);
p->strs = NULL;
p->shf = shf;
p->npats = 0;
p->nref = 1; /* allocated from permanent storage */
p->pats = (Patprog *) p->prog;
p->flags = EF_REAL;
p->dump = NULL;
p->prog[0] = WCB_LIST((Z_SYNC | Z_END), 0);
p->prog[1] = WCB_SUBLIST(WC_SUBLIST_END, 0, 3);
p->prog[2] = WCB_PIPE(WC_PIPE_END, 0);
p->prog[3] = WCB_AUTOFN();
p->prog[4] = WCB_END();
return p;
}
/* unset: unset parameters */
/**/
int
bin_unset(char *name, char **argv, Options ops, int func)
{
Param pm, next;
Patprog pprog;
char *s;
int match = 0, returnval = 0;
int i;
/* unset -f is the same as unfunction */
if (OPT_ISSET(ops,'f'))
return bin_unhash(name, argv, ops, func);
/* with -m option, treat arguments as glob patterns */
if (OPT_ISSET(ops,'m')) {
while ((s = *argv++)) {
/* expand */
tokenize(s);
if ((pprog = patcompile(s, PAT_STATIC, NULL))) {
/* Go through the parameter table, and unset any matches */
queue_signals();
for (i = 0; i < paramtab->hsize; i++) {
for (pm = (Param) paramtab->nodes[i]; pm; pm = next) {
/* record pointer to next, since we may free this one */
next = (Param) pm->node.next;
if ((!(pm->node.flags & PM_RESTRICTED) ||
unset(RESTRICTED)) &&
pattry(pprog, pm->node.nam)) {
unsetparam_pm(pm, 0, 1);
match++;
}
}
}
unqueue_signals();
} else {
untokenize(s);
zwarnnam(name, "bad pattern : %s", s);
returnval = 1;
}
}
/* If we didn't match anything, we return 1. */
if (!match)
returnval = 1;
return returnval;
}
/* do not glob -- unset the given parameter */
queue_signals();
while ((s = *argv++)) {
char *ss = strchr(s, '[');
char *sse = ss;
if (ss) {
if (skipparens('[', ']', &sse) || *sse) {
zerrnam(name, "%s: invalid parameter name", s);
returnval = 1;
continue;
}
*ss = 0;
}
pm = (Param) (paramtab == realparamtab ?
gethashnode2(paramtab, s) :
paramtab->getnode(paramtab, s));
/*
* Unsetting an unset variable is not an error.
* This appears to be reasonably standard behaviour.
*/
if (!pm)
continue;
else if ((pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) {
zerrnam(name, "%s: restricted", pm->node.nam);
returnval = 1;
} else if (ss) {
if (PM_TYPE(pm->node.flags) == PM_HASHED) {
HashTable tht = paramtab;
if ((paramtab = pm->gsu.h->getfn(pm))) {
*--sse = 0;
unsetparam(ss+1);
*sse = ']';
}
paramtab = tht;
} else if (PM_TYPE(pm->node.flags) == PM_SCALAR ||
PM_TYPE(pm->node.flags) == PM_ARRAY) {
struct value vbuf;
vbuf.isarr = (PM_TYPE(pm->node.flags) == PM_ARRAY ?
SCANPM_ARRONLY : 0);
vbuf.pm = pm;
vbuf.flags = 0;
vbuf.start = 0;
vbuf.end = -1;
vbuf.arr = 0;
*ss = '[';
if (getindex(&ss, &vbuf, SCANPM_ASSIGNING) == 0 &&
vbuf.pm && !(vbuf.pm->node.flags & PM_UNSET)) {
if (PM_TYPE(pm->node.flags) == PM_SCALAR) {
setstrvalue(&vbuf, ztrdup(""));
} else {
/* start is after the element for reverse index */
int start = vbuf.start - !!(vbuf.flags & VALFLAG_INV);
if (start < arrlen(vbuf.pm->u.arr)) {
char *arr[2];
arr[0] = "";
arr[1] = 0;
setarrvalue(&vbuf, zarrdup(arr));
}
}
}
returnval = errflag;
errflag &= ~ERRFLAG_ERROR;
} else {
zerrnam(name, "%s: invalid element for unset", s);
returnval = 1;
}
} else {
if (unsetparam_pm(pm, 0, 1))
returnval = 1;
}
if (ss)
*ss = '[';
}
unqueue_signals();
return returnval;
}
/* type, whence, which, command */
static LinkList matchednodes;
static void
fetchcmdnamnode(HashNode hn, UNUSED(int printflags))
{
Cmdnam cn = (Cmdnam) hn;
addlinknode(matchednodes, cn->node.nam);
}
/**/
int
bin_whence(char *nam, char **argv, Options ops, int func)
{
HashNode hn;
Patprog pprog;
int returnval = 0;
int printflags = 0;
int aliasflags;
int csh, all, v, wd;
int informed = 0;
int expand = 0;
char *cnam, **allmatched = 0;
/* Check some option information */
csh = OPT_ISSET(ops,'c');
v = OPT_ISSET(ops,'v');
all = OPT_ISSET(ops,'a');
wd = OPT_ISSET(ops,'w');
if (OPT_ISSET(ops,'x')) {
char *eptr;
expand = (int)zstrtol(OPT_ARG(ops,'x'), &eptr, 10);
if (*eptr) {
zwarnnam(nam, "number expected after -x");
return 1;
}
if (expand == 0) /* no indentation at all */
expand = -1;
}
if (OPT_ISSET(ops,'w'))
printflags |= PRINT_WHENCE_WORD;
else if (OPT_ISSET(ops,'c'))
printflags |= PRINT_WHENCE_CSH;
else if (OPT_ISSET(ops,'v'))
printflags |= PRINT_WHENCE_VERBOSE;
else
printflags |= PRINT_WHENCE_SIMPLE;
if (OPT_ISSET(ops,'f'))
printflags |= PRINT_WHENCE_FUNCDEF;
if (func == BIN_COMMAND)
if (OPT_ISSET(ops,'V')) {
printflags = aliasflags = PRINT_WHENCE_VERBOSE;
v = 1;
} else {
aliasflags = PRINT_LIST;
printflags = PRINT_WHENCE_SIMPLE;
v = 0;
}
else
aliasflags = printflags;
/* With -m option -- treat arguments as a glob patterns */
if (OPT_ISSET(ops,'m')) {
cmdnamtab->filltable(cmdnamtab);
if (all) {
pushheap();
matchednodes = newlinklist();
}
for (; *argv; argv++) {
/* parse the pattern */
tokenize(*argv);
if (!(pprog = patcompile(*argv, PAT_STATIC, NULL))) {
untokenize(*argv);
zwarnnam(nam, "bad pattern : %s", *argv);
returnval = 1;
continue;
}
queue_signals();
if (!OPT_ISSET(ops,'p')) {
/* -p option is for path search only. *
* We're not using it, so search for ... */
/* aliases ... */
informed +=
scanmatchtable(aliastab, pprog, 1, 0, DISABLED,
aliastab->printnode, printflags);
/* and reserved words ... */
informed +=
scanmatchtable(reswdtab, pprog, 1, 0, DISABLED,
reswdtab->printnode, printflags);
/* and shell functions... */
informed +=
scanmatchshfunc(pprog, 1, 0, DISABLED,
shfunctab->printnode, printflags, expand);
/* and builtins. */
informed +=
scanmatchtable(builtintab, pprog, 1, 0, DISABLED,
builtintab->printnode, printflags);
}
/* Done search for `internal' commands, if the -p option *
* was not used. Now search the path. */
informed +=
scanmatchtable(cmdnamtab, pprog, 1, 0, 0,
(all ? fetchcmdnamnode : cmdnamtab->printnode),
printflags);
unqueue_signals();
}
if (all) {
allmatched = argv = zlinklist2array(matchednodes);
matchednodes = NULL;
popheap();
} else
return returnval || !informed;
}
/* Take arguments literally -- do not glob */
queue_signals();
for (; *argv; argv++) {
if (!OPT_ISSET(ops,'p') && !allmatched) {
char *suf;
/* Look for alias */
if ((hn = aliastab->getnode(aliastab, *argv))) {
aliastab->printnode(hn, aliasflags);
informed = 1;
if (!all)
continue;
}
/* Look for suffix alias */
if ((suf = strrchr(*argv, '.')) && suf[1] &&
suf > *argv && suf[-1] != Meta &&
(hn = sufaliastab->getnode(sufaliastab, suf+1))) {
sufaliastab->printnode(hn, printflags);
informed = 1;
if (!all)
continue;
}
/* Look for reserved word */
if ((hn = reswdtab->getnode(reswdtab, *argv))) {
reswdtab->printnode(hn, printflags);
informed = 1;
if (!all)
continue;
}
/* Look for shell function */
if ((hn = shfunctab->getnode(shfunctab, *argv))) {
printshfuncexpand(hn, printflags, expand);
informed = 1;
if (!all)
continue;
}
/* Look for builtin command */
if ((hn = builtintab->getnode(builtintab, *argv))) {
builtintab->printnode(hn, printflags);
informed = 1;
if (!all)
continue;
}
/* Look for commands that have been added to the *
* cmdnamtab with the builtin `hash foo=bar'. */
if ((hn = cmdnamtab->getnode(cmdnamtab, *argv)) && (hn->flags & HASHED)) {
cmdnamtab->printnode(hn, printflags);
informed = 1;
if (!all)
continue;
}
}
/* Option -a is to search the entire path, *
* rather than just looking for one match. */
if (all && **argv != '/') {
char **pp, *buf;
pushheap();
for (pp = path; *pp; pp++) {
if (**pp) {
buf = zhtricat(*pp, "/", *argv);
} else buf = dupstring(*argv);
if (iscom(buf)) {
if (wd) {
printf("%s: command\n", *argv);
} else {
if (v && !csh)
zputs(*argv, stdout), fputs(" is ", stdout);
zputs(buf, stdout);
if (OPT_ISSET(ops,'s') || OPT_ISSET(ops, 'S'))
print_if_link(buf, OPT_ISSET(ops, 'S'));
fputc('\n', stdout);
}
informed = 1;
}
}
if (!informed && (wd || v || csh)) {
zputs(*argv, stdout);
puts(wd ? ": none" : " not found");
returnval = 1;
}
popheap();
} else if ((cnam = findcmd(*argv, 1))) {
/* Found external command. */
if (wd) {
printf("%s: command\n", *argv);
} else {
if (v && !csh)
zputs(*argv, stdout), fputs(" is ", stdout);
zputs(cnam, stdout);
if (OPT_ISSET(ops,'s') || OPT_ISSET(ops,'S'))
print_if_link(cnam, OPT_ISSET(ops,'S'));
fputc('\n', stdout);
}
informed = 1;
} else {
/* Not found at all. */
if (v || csh || wd)
zputs(*argv, stdout), puts(wd ? ": none" : " not found");
returnval = 1;
}
}
if (allmatched)
freearray(allmatched);
unqueue_signals();
return returnval || !informed;
}
/**** command & named directory hash table builtins ****/
/*****************************************************************
* hash -- explicitly hash a command. *
* 1) Given no arguments, list the hash table. *
* 2) The -m option prints out commands in the hash table that *
* match a given glob pattern. *
* 3) The -f option causes the entire path to be added to the *
* hash table (cannot be combined with any arguments). *
* 4) The -r option causes the entire hash table to be discarded *
* (cannot be combined with any arguments). *
* 5) Given argument of the form foo=bar, add element to command *
* hash table, so that when `foo' is entered, then `bar' is *
* executed. *
* 6) Given arguments not of the previous form, add it to the *
* command hash table as if it were being executed. *
* 7) The -d option causes analogous things to be done using *
* the named directory hash table. *
*****************************************************************/
/**/
int
bin_hash(char *name, char **argv, Options ops, UNUSED(int func))
{
HashTable ht;
Patprog pprog;
Asgment asg;
int returnval = 0;
int printflags = 0;
if (OPT_ISSET(ops,'d'))
ht = nameddirtab;
else
ht = cmdnamtab;
if (OPT_ISSET(ops,'r') || OPT_ISSET(ops,'f')) {
/* -f and -r can't be used with any arguments */
if (*argv) {
zwarnnam("hash", "too many arguments");
return 1;
}
/* empty the hash table */
if (OPT_ISSET(ops,'r'))
ht->emptytable(ht);
/* fill the hash table in a standard way */
if (OPT_ISSET(ops,'f'))
ht->filltable(ht);
return 0;
}
if (OPT_ISSET(ops,'L')) printflags |= PRINT_LIST;
/* Given no arguments, display current hash table. */
if (!*argv) {
queue_signals();
scanhashtable(ht, 1, 0, 0, ht->printnode, printflags);
unqueue_signals();
return 0;
}
queue_signals();
while (*argv) {
void *hn;
if (OPT_ISSET(ops,'m')) {
/* with the -m option, treat the argument as a glob pattern */
tokenize(*argv); /* expand */
if ((pprog = patcompile(*argv, PAT_STATIC, NULL))) {
/* display matching hash table elements */
scanmatchtable(ht, pprog, 1, 0, 0, ht->printnode, printflags);
} else {
untokenize(*argv);
zwarnnam(name, "bad pattern : %s", *argv);
returnval = 1;
}
continue;
}
if (!(asg = getasg(&argv, NULL))) {
zwarnnam(name, "bad assignment");
returnval = 1;
} else if (ASG_VALUEP(asg)) {
if(isset(RESTRICTED)) {
zwarnnam(name, "restricted: %s", asg->value.scalar);
returnval = 1;
} else {
/* The argument is of the form foo=bar, *
* so define an entry for the table. */
if(OPT_ISSET(ops,'d')) {
/* shouldn't return NULL if asg->name is not NULL */
if (*itype_end(asg->name, IUSER, 0)) {
zwarnnam(name,
"invalid character in directory name: %s",
asg->name);
returnval = 1;
continue;
} else {
Nameddir nd = hn = zshcalloc(sizeof *nd);
nd->node.flags = 0;
nd->dir = ztrdup(asg->value.scalar);
}
} else {
Cmdnam cn = hn = zshcalloc(sizeof *cn);
cn->node.flags = HASHED;
cn->u.cmd = ztrdup(asg->value.scalar);
}
ht->addnode(ht, ztrdup(asg->name), hn);
if(OPT_ISSET(ops,'v'))
ht->printnode(hn, 0);
}
} else if (!(hn = ht->getnode2(ht, asg->name))) {
/* With no `=value' part to the argument, *
* work out what it ought to be. */
if(OPT_ISSET(ops,'d')) {
if(!getnameddir(asg->name)) {
zwarnnam(name, "no such directory name: %s", asg->name);
returnval = 1;
}
} else {
if (!hashcmd(asg->name, path)) {
zwarnnam(name, "no such command: %s", asg->name);
returnval = 1;
}
}
if(OPT_ISSET(ops,'v') && (hn = ht->getnode2(ht, asg->name)))
ht->printnode(hn, 0);
} else if(OPT_ISSET(ops,'v'))
ht->printnode(hn, 0);
}
unqueue_signals();
return returnval;
}
/* unhash: remove specified elements from a hash table */
/**/
int
bin_unhash(char *name, char **argv, Options ops, int func)
{
HashTable ht;
HashNode hn, nhn;
Patprog pprog;