diff options
| author | Craig Jennings <c@cjennings.net> | 2025-05-08 18:49:34 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-05-08 18:51:59 -0500 |
| commit | 000e00871830cd15de032c80e2b62946cf19445c (patch) | |
| tree | 794a7922750472bbe0e024042d6ba84f411fc3e0 /dotfiles/system/.zsh/modules/Src/builtin.c | |
| parent | fe302606931e4bad91c4ed6df81a4403523ba780 (diff) | |
adding missing dotfiles and folders
- profile.d/
- bashrc
- authinfo.gpg
- .zsh/
Diffstat (limited to 'dotfiles/system/.zsh/modules/Src/builtin.c')
| -rw-r--r-- | dotfiles/system/.zsh/modules/Src/builtin.c | 7236 |
1 files changed, 7236 insertions, 0 deletions
diff --git a/dotfiles/system/.zsh/modules/Src/builtin.c b/dotfiles/system/.zsh/modules/Src/builtin.c new file mode 100644 index 0000000..93fa911 --- /dev/null +++ b/dotfiles/system/.zsh/modules/Src/builtin.c @@ -0,0 +1,7236 @@ +/* + * 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, "dmktrRTUwWXz", "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:%klmp:%rtuxz", 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, "lLR", 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, 0, "E:%F:%HL:%R:%TUZ:%afhi:%lp:%rtu", "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:%ghlp:%rtux", "E"), + BUILTIN("functions", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "kmMstTuUWx: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:%lp:%rtux", "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:%lp:%rtux", 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:v:x:X:z-", NULL), + BUILTIN("printf", BINF_SKIPINVALID | BINF_SKIPDASH, bin_print, 1, -1, BIN_PRINTF, "v:", 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, BIN_READONLY, "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:%klp:%rtuxmz", 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, BIN_UNSET, "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:abcfdilmpsue", 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, 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; + int sense; /* 1 for -x, 0 for +x */ + /* 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; + zwarnnam(name, "bad option: %c%c", "+-"[sense], *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->flags & ASG_ARRAY) { + fprintf(xtrerr, "=("); + if (asg->value.array) { + if (asg->flags & ASG_KEY_VALUE) { + LinkNode keynode, valnode; + keynode = firstnode(asg->value.array); + for (;;) { + if (!keynode) + break; + valnode = nextnode(keynode); + if (!valnode) + break; + fputc('[', xtrerr); + quotedzputs((char *)getdata(keynode), + xtrerr); + fprintf(stderr, "]="); + quotedzputs((char *)getdata(valnode), + xtrerr); + keynode = nextnode(valnode); + } + } else { + LinkNode arrnode; + 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++) { + queue_signals(); + + /* parse pattern */ + tokenize(*argv); + if ((pprog = patcompile(*argv, PAT_STATIC, 0))) + match += scanmatchtable(ht, pprog, 0, 0, 0, scanfunc, 0); + else { + untokenize(*argv); + zwarnnam(name, "bad pattern : %s", *argv); + returnval = 1; + } + unqueue_signals(); + } + /* 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)) && arrlen_gt(a, len)) { + a += len; + len += arrlen(a); + } + for (x = y = zalloc((len + 1) * sizeof(char *)); len--;) { + 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); + } + + assignsparam("PWD", ztrdup(pwd), 0); + assignsparam("OLDPWD", ztrdup(oldpwd), 0); + + 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) { + if (!home) { + zwarnnam(nam, "HOME not set"); + return NULL; + } + 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 (!dir || !getdata(dir)) { + DPUTS(1, "Directory not set, not detected early enough"); + return NULL; + } + 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+1]; +#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(unmeta(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 + /* This function is always called with n path containing at + * least one slash, either because one was input by the user or + * because the caller has prepended either pwd or a cdpath dir. + * If asked to make a relative change and pwd is set to ".", + * the current directory has been removed out from under us, + * so force links to be chased. + * + * Ordinarily we can't get here with "../" as the first component + * but handle the silly special case of ".." in cdpath. + * + * Order of comparisons here looks funny, but it short-circuits + * most rapidly in the event of a false condition. Set to 2 + * here so we still obey the (lack of) CHASEDOTS option after + * the first "../" is preserved (test chasedots > 1 below). + */ + int chasedots = (src[0] == '.' && pwd[0] == '.' && pwd[1] == '\0' && + (src[1] == '/' || (src[1] == '.' && src[2] == '/'))) * 2; + +/*** 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 chasedots; + } + if (src[0] == '.' && src[1] == '.' && + (src[2] == '\0' || src[2] == '/')) { + if (isset(CHASEDOTS) || chasedots > 1) { + chasedots = 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; + } + } + /* unreached */ +} + +/**/ +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) { + unqueue_signals(); + 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->flags = 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, fil); + 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; + int len; + ltm = localtime(&ent->stim); + if ((len = ztrftime(timebuf, 256, tdfmt, ltm, 0L)) >= 0) { + fwrite(timebuf, 1, len, f); + fprintf(f, " "); + } + } + /* 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.flags = 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' or 'export' + * flags of an unset parameter. + */ + usepm = pm && (!(pm->node.flags & PM_UNSET) || + (isset(POSIXBUILTINS) && + (pm->node.flags & (PM_READONLY|PM_EXPORTED)))); + + /* + * 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 = assignsparam( + pname, ztrdup(asg->value.scalar), 0))) + 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|PM_EXPORTED)) && + (!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; + } + /* This is handled by createparam(): + if (usepm && (pm->node.flags & PM_EXPORTED) && !(off & PM_EXPORTED)) + on |= PM_EXPORTED; + */ + } + + /* + * 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->flags & ASG_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 = assignsparam(pname, ztrdup(asg->value.scalar), 0))) + return NULL; + } else if (asg->flags & ASG_ARRAY) { + int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0; + if (!(pm = assignaparam(pname, asg->value.array ? + zlinklist2array(asg->value.array) : + mkarray(NULL), flags))) + return NULL; + } + if (errflag) + return NULL; + pm->node.flags |= (on & PM_READONLY); + if (OPT_ISSET(ops,'p')) + paramtab->printnode(&pm->node, PRINT_TYPESET); + return pm; + } + + if ((asg->flags & ASG_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->flags = 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; + } + if (pm->node.flags & PM_SINGLE) { + zerrnam(cname, "%s: can only have a single instance", 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 ? + /* getnode2() to avoid autoloading */ + paramtab->getnode2(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 = assignsparam( + pname, + ztrdup(asg->value.scalar ? asg->value.scalar : ""), 0))) + return NULL; + dont_set = 1; + asg->flags = 0; + keeplocal = 0; + on = pm->node.flags; + } else if (PM_TYPE(on) == PM_ARRAY && ASG_ARRAYP(asg)) { + int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0; + if (!(pm = assignaparam(pname, asg->value.array ? + zlinklist2array(asg->value.array) : + mkarray(NULL), flags))) + return NULL; + dont_set = 1; + keeplocal = 0; + on = pm->node.flags; + } else { + zerrnam(cname, + "%s: inconsistent 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)) { + char **arrayval; + int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0; + if (!ASG_ARRAYP(asg)) { + /* + * Attempt to assign a scalar value to an array. + * This can happen if the array is special. + * We'll be lenient and guess what the user meant. + * This is how normal assigment works. + */ + if (*asg->value.scalar) { + /* Array with one value */ + arrayval = mkarray(ztrdup(asg->value.scalar)); + } else { + /* Empty array */ + arrayval = mkarray(NULL); + } + } else if (asg->value.array) + arrayval = zlinklist2array(asg->value.array); + else + arrayval = mkarray(NULL); + if (!(pm=assignaparam(pname, arrayval, flags))) + return NULL; + } else { + DPUTS(ASG_ARRAYP(asg), "BUG: inconsistent array value for scalar"); + if (!(pm = assignsparam(pname, ztrdup(asg->value.scalar), 0))) + 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); + + /* POSIX handles "readonly" specially */ + if (func == BIN_READONLY && isset(POSIXBUILTINS) && !OPT_PLUS(ops, 'g')) + ops->ind['g'] = 1; + + /* 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; + if (OPT_HASARG(ops,'p')) { + char *eptr; + int pflag = (int)zstrtol(OPT_ARG(ops,'p'), &eptr, 10); + if (pflag == 1 && !*eptr) + printflags |= PRINT_LINE; + else if (pflag || *eptr) { + zwarnnam(name, "bad argument to -p: %s", OPT_ARG(ops,'p')); + unqueue_signals(); + return 1; + } + /* -p0 treated as -p for consistency */ + } + } + 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 (PM_TYPE(pm->node.flags) != PM_SCALAR) { + zwarnnam(name, "already tied as non-scalar: %s", asg0.name); + } else if (!strcmp(asg->name, pm->ename)) { + /* + * Already tied in the fashion requested. + */ + struct tieddata *tdp = (struct tieddata*)pm->u.data; + int flags = (asg->flags & ASG_KEY_VALUE) ? + ASSPM_KEY_VALUE : 0; + /* Update join character */ + tdp->joinchar = joinchar; + if (asg0.value.scalar) + assignsparam(asg0.name, ztrdup(asg0.value.scalar), 0); + else if (asg->value.array) + assignaparam( + asg->name, zlinklist2array(asg->value.array),flags); + 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.flags = 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) { + int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0; + assignaparam(asg->name, zlinklist2array(asg->value.array), flags); + } else if (oldval) + assignsparam(asg0.name, oldval, 0); + unqueue_signals(); + + return 0; + } + if (off & PM_TIED) { + unqueue_signals(); + 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 ? + /* getnode2() to avoid autoloading */ + paramtab->getnode2(paramtab, asg->name) : + paramtab->getnode(paramtab, asg->name)); + if (OPT_ISSET(ops,'p')) { + if (hn) + paramtab->printnode(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, + OPT_ISSET(ops,'d')); +} + +/* Helper for bin_functions() for -X and -r options */ + +/**/ +static int +check_autoload(Shfunc shf, char *name, Options ops, int func) +{ + if (OPT_ISSET(ops,'X')) + { + return eval_autoload(shf, name, ops, func); + } + if ((OPT_ISSET(ops,'r') || OPT_ISSET(ops,'R')) && + (shf->node.flags & PM_UNDEFINED)) + { + char *dir_path; + if (shf->filename && (shf->node.flags & PM_LOADDIR)) { + char *spec_path[2]; + spec_path[0] = shf->filename; + spec_path[1] = NULL; + if (getfpfunc(shf->node.nam, NULL, &dir_path, spec_path, 1)) { + /* shf->filename is already correct. */ + return 0; + } + if (!OPT_ISSET(ops,'d')) { + if (OPT_ISSET(ops,'R')) { + zerr("%s: function definition file not found", + shf->node.nam); + return 1; + } + return 0; + } + } + if (getfpfunc(shf->node.nam, NULL, &dir_path, NULL, 1)) { + dircache_set(&shf->filename, NULL); + if (*dir_path != '/') { + dir_path = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP), + "/", dir_path); + dir_path = xsymlink(dir_path, 1); + } + dircache_set(&shf->filename, dir_path); + shf->node.flags |= PM_LOADDIR; + return 0; + } + if (OPT_ISSET(ops,'R')) { + zerr("%s: function definition file not found", + shf->node.nam); + return 1; + } + /* with -r, we don't flag an error, just let it be found later. */ + } + return 0; +} + +/* 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 %s", (p->flags & MFF_STR) ? "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'); +} + + +static void +add_autoload_function(Shfunc shf, char *funcname) +{ + char *nam; + if (*funcname == '/' && funcname[1] && + (nam = strrchr(funcname, '/')) && nam[1] && + (shf->node.flags & PM_UNDEFINED)) { + char *dir; + nam = strrchr(funcname, '/'); + if (nam == funcname) { + dir = "/"; + } else { + *nam++ = '\0'; + dir = funcname; + } + dircache_set(&shf->filename, NULL); + dircache_set(&shf->filename, dir); + shf->node.flags |= PM_LOADDIR; + shf->node.flags |= PM_ABSPATH_USED; + shfunctab->addnode(shfunctab, ztrdup(nam), shf); + } else { + Shfunc shf2; + Funcstack fs; + const char *calling_f = NULL; + char buf[PATH_MAX+1]; + + /* Find calling function */ + for (fs = funcstack; fs; fs = fs->prev) { + if (fs->tp == FS_FUNC && fs->name && (!shf->node.nam || 0 != strcmp(fs->name,shf->node.nam))) { + calling_f = fs->name; + break; + } + } + + /* Get its directory */ + if (calling_f) { + /* Should contain load directory, and be loaded via absolute path */ + if ((shf2 = (Shfunc) shfunctab->getnode2(shfunctab, calling_f)) + && (shf2->node.flags & PM_LOADDIR) && (shf2->node.flags & PM_ABSPATH_USED) + && shf2->filename) + { + if (strlen(shf2->filename) + strlen(funcname) + 1 < PATH_MAX) + { + sprintf(buf, "%s/%s", shf2->filename, funcname); + /* Set containing directory if the function file + * exists (do normal FPATH processing otherwise) */ + if (!access(buf, R_OK)) { + dircache_set(&shf->filename, NULL); + dircache_set(&shf->filename, shf2->filename); + shf->node.flags |= PM_LOADDIR; + shf->node.flags |= PM_ABSPATH_USED; + } + } + } + } + + shfunctab->addnode(shfunctab, ztrdup(funcname), shf); + } +} + +/* 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; + if (OPT_MINUS(ops,'W')) + on |= PM_WARNNESTED; + else if (OPT_PLUS(ops,'W')) + off |= PM_WARNNESTED; + 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 (OPT_MINUS(ops,'d')) { + on |= PM_CUR_FPATH; + off |= PM_CUR_FPATH; + } else if (OPT_PLUS(ops,'d')) { + off |= PM_CUR_FPATH; + roff |= PM_CUR_FPATH; + } + + 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') || !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, prev; + /* + * 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++) { + queue_signals(); + tokenize(*argv); + if ((pprog = patcompile(*argv, PAT_STATIC, 0))) { + 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; + } + } else { + untokenize(*argv); + zwarnnam(name, "bad pattern : %s", *argv); + returnval = 1; + } + unqueue_signals(); + } + } 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, maxargs; + char *funcname = *argv++; + char *modname = NULL; + char *ptr; + + if (OPT_ISSET(ops,'s')) { + minargs = maxargs = 1; + } else { + minargs = 0; + maxargs = -1; + } + + 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; + } + if (OPT_ISSET(ops,'s') && minargs != 1) { + zwarnnam(name, "-Ms: must take a single string argument"); + 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; + } + if (OPT_ISSET(ops,'s') && maxargs != 1) { + zwarnnam(name, "-Ms: must take a single string argument"); + 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; + if (OPT_ISSET(ops,'s')) + p->flags |= MFF_STR; + p->module = modname ? ztrdup(modname) : NULL; + p->minargs = minargs; + p->maxargs = maxargs; + + queue_signals(); + for (q = mathfuncs, prev = NULL; q; prev = q, q = q->next) { + if (!strcmp(q->name, funcname)) { + removemathfunc(prev, q); + break; + } + } + + p->next = mathfuncs; + mathfuncs = p; + unqueue_signals(); + } + + return returnval; + } + + if (OPT_MINUS(ops,'X')) { + Funcstack fs; + char *funcname = NULL; + int ret; + if (*argv && argv[1]) { + zwarnnam(name, "-X: too many arguments"); + return 1; + } + queue_signals(); + for (fs = funcstack; fs; fs = fs->prev) { + if (fs->tp == FS_FUNC) { + /* + * dupstring here is paranoia but unlikely to be + * problematic + */ + funcname = dupstring(fs->name); + break; + } + } + if (!funcname) + { + zerrnam(name, "bad autoload"); + ret = 1; + } else { + if ((shf = (Shfunc) shfunctab->getnode(shfunctab, funcname))) { + DPUTS(!shf->funcdef, + "BUG: Calling autoload from empty function"); + } else { + shf = (Shfunc) zshcalloc(sizeof *shf); + shfunctab->addnode(shfunctab, ztrdup(funcname), shf); + } + if (*argv) { + dircache_set(&shf->filename, NULL); + dircache_set(&shf->filename, *argv); + on |= PM_LOADDIR; + } + shf->node.flags = on; + ret = eval_autoload(shf, funcname, ops, func); + } + unqueue_signals(); + return ret; + } else if (!*argv) { + /* 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. */ + int ret = 0; + + queue_signals(); + 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++) { + queue_signals(); + /* expand argument */ + tokenize(*argv); + if ((pprog = patcompile(*argv, PAT_STATIC, 0))) { + /* with no options, just print all functions matching the glob pattern */ + 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 (check_autoload(shf, shf->node.nam, + ops, func)) { + returnval = 1; + } + } + } + } + } else { + untokenize(*argv); + zwarnnam(name, "bad pattern : %s", *argv); + returnval = 1; + } + unqueue_signals(); + } + 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 (check_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); + } + + if (**argv == '/') { + char *base = strrchr(*argv, '/') + 1; + if (*base && + (shf = (Shfunc) shfunctab->getnode(shfunctab, base))) { + char *dir; + /* turn on/off the given flags */ + shf->node.flags = + (shf->node.flags | (on & ~PM_UNDEFINED)) & ~off; + if (shf->node.flags & PM_UNDEFINED) { + /* update path if not yet loaded */ + if (base == *argv + 1) + dir = "/"; + else { + dir = *argv; + base[-1] = '\0'; + } + dircache_set(&shf->filename, NULL); + dircache_set(&shf->filename, dir); + } + if (check_autoload(shf, shf->node.nam, ops, func)) + returnval = 1; + continue; + } + } + + /* 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); + add_autoload_function(shf, *argv); + + if (signum != -1) { + if (settrap(signum, NULL, ZSIG_FUNC)) { + shfunctab->removenode(shfunctab, *argv); + shfunctab->freenode(&shf->node); + returnval = 1; + ok = 0; + } + } + + if (ok && check_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++)) { + queue_signals(); + /* expand */ + tokenize(s); + if ((pprog = patcompile(s, PAT_STATIC, NULL))) { + /* Go through the parameter table, and unset any matches */ + 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++; + } + } + } + } else { + untokenize(s); + zwarnnam(name, "bad pattern : %s", s); + returnval = 1; + } + unqueue_signals(); + } + /* 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, '['), *subscript = 0; + if (ss) { + char *sse; + *ss = 0; + if ((sse = parse_subscript(ss+1, 1, ']'))) { + *sse = 0; + subscript = dupstring(ss+1); + *sse = ']'; + remnulargs(subscript); + untokenize(subscript); + } + } + if ((ss && !subscript) || !isident(s)) { + if (ss) + *ss = '['; + zerrnam(name, "%s: invalid parameter name", s); + returnval = 1; + continue; + } + pm = (Param) (paramtab == realparamtab ? + /* getnode2() to avoid autoloading */ + paramtab->getnode2(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))) + unsetparam(subscript); + 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 (arrlen_gt(vbuf.pm->u.arr, start)) { + 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(); + } + queue_signals(); + 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; + } + 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); + run_queued_signals(); + } + 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); + quotedzputs(buf, stdout); + } else + 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)) { + /* this is information and not an error so, as in csh, use stdout */ + zputs(*argv, stdout); + puts(wd ? ": none" : " not found"); + returnval = 1; + } + popheap(); + } else if (func == BIN_COMMAND && OPT_ISSET(ops,'p') && + (hn = builtintab->getnode(builtintab, *argv))) { + /* + * Special case for "command -p[vV]" which needs to + * show a builtin in preference to an external command. + */ + builtintab->printnode(hn, printflags); + informed = 1; + } else if ((cnam = findcmd(*argv, 1, + func == BIN_COMMAND && + OPT_ISSET(ops,'p')))) { + /* Found external command. */ + if (wd) { + printf("%s: command\n", *argv); + } else { + if (v && !csh) { + zputs(*argv, stdout), fputs(" is ", stdout); + quotedzputs(cnam, stdout); + } else + 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. That's not an error as such so this goes to stdout */ + 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; + } + argv++; + continue; + } + if (!(asg = getasg(&argv, NULL))) { + zwarnnam(name, "bad assignment"); + returnval = 1; + break; + } 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; + int match = 0, returnval = 0, all = 0; + int i; + + /* Check which hash table we are working with. */ + if (func == BIN_UNALIAS) { + if (OPT_ISSET(ops,'s')) + ht = sufaliastab; /* suffix aliases */ + else + ht = aliastab; /* aliases */ + if (OPT_ISSET(ops, 'a')) { + if (*argv) { + zwarnnam(name, "-a: too many arguments"); + return 1; + } + all = 1; + } else if (!*argv) { + zwarnnam(name, "not enough arguments"); + return 1; + } + } else if (OPT_ISSET(ops,'d')) + ht = nameddirtab; /* named directories */ + else if (OPT_ISSET(ops,'f')) + ht = shfunctab; /* shell functions */ + else if (OPT_ISSET(ops,'s')) + ht = sufaliastab; /* suffix aliases, must precede aliases */ + else if (func == BIN_UNHASH && (OPT_ISSET(ops,'a'))) + ht = aliastab; /* aliases */ + else + ht = cmdnamtab; /* external commands */ + + if (all) { + queue_signals(); + for (i = 0; i < ht->hsize; i++) { + for (hn = ht->nodes[i]; hn; hn = nhn) { + /* record pointer to next, since we may free this one */ + nhn = hn->next; + ht->freenode(ht->removenode(ht, hn->nam)); + } + } + unqueue_signals(); + return 0; + } + + /* With -m option, treat arguments as glob patterns. * + * "unhash -m '*'" is legal, but not recommended. */ + if (OPT_ISSET(ops,'m')) { + for (; *argv; argv++) { + queue_signals(); + /* expand argument */ + tokenize(*argv); + if ((pprog = patcompile(*argv, PAT_STATIC, NULL))) { + /* remove all nodes matching glob pattern */ + for (i = 0; i < ht->hsize; i++) { + for (hn = ht->nodes[i]; hn; hn = nhn) { + /* record pointer to next, since we may free this one */ + nhn = hn->next; + if (pattry(pprog, hn->nam)) { + ht->freenode(ht->removenode(ht, hn->nam)); + match++; + } + } + } + } else { + untokenize(*argv); + zwarnnam(name, "bad pattern : %s", *argv); + returnval = 1; + } + unqueue_signals(); + } + /* 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->removenode(ht, *argv))) { + ht->freenode(hn); + } else if (func == BIN_UNSET && isset(POSIXBUILTINS)) { + /* POSIX: unset: "Unsetting a variable or function that was * + * not previously set shall not be considered an error." */ + returnval = 0; + } else { + zwarnnam(name, "no such hash table element: %s", *argv); + returnval = 1; + } + } + unqueue_signals(); + return returnval; +} + +/**** alias builtins ****/ + +/* alias: display or create aliases. */ + +/**/ +int +bin_alias(char *name, char **argv, Options ops, UNUSED(int func)) +{ + Alias a; + Patprog pprog; + Asgment asg; + int returnval = 0; + int flags1 = 0, flags2 = DISABLED; + int printflags = 0; + int type_opts; + HashTable ht = aliastab; + + /* Did we specify the type of alias? */ + type_opts = OPT_ISSET(ops, 'r') + OPT_ISSET(ops, 'g') + + OPT_ISSET(ops, 's'); + if (type_opts) { + if (type_opts > 1) { + zwarnnam(name, "illegal combination of options"); + return 1; + } + if (OPT_ISSET(ops,'g')) + flags1 |= ALIAS_GLOBAL; + else + flags2 |= ALIAS_GLOBAL; + if (OPT_ISSET(ops, 's')) { + /* + * Although we keep suffix aliases in a different table, + * it is useful to be able to distinguish Alias structures + * without reference to the table, so we have a separate + * flag, too. + */ + flags1 |= ALIAS_SUFFIX; + ht = sufaliastab; + } else + flags2 |= ALIAS_SUFFIX; + } + + if (OPT_ISSET(ops,'L')) + printflags |= PRINT_LIST; + else if (OPT_PLUS(ops,'g') || OPT_PLUS(ops,'r') || OPT_PLUS(ops,'s') || + OPT_PLUS(ops,'m') || OPT_ISSET(ops,'+')) + printflags |= PRINT_NAMEONLY; + + /* In the absence of arguments, list all aliases. If a command * + * line flag is specified, list only those of that type. */ + if (!*argv) { + queue_signals(); + scanhashtable(ht, 1, flags1, flags2, ht->printnode, printflags); + unqueue_signals(); + return 0; + } + + /* With the -m option, treat the arguments as * + * glob patterns of aliases to display. */ + if (OPT_ISSET(ops,'m')) { + for (; *argv; argv++) { + queue_signals(); + tokenize(*argv); /* expand argument */ + if ((pprog = patcompile(*argv, PAT_STATIC, NULL))) { + /* display the matching aliases */ + scanmatchtable(ht, pprog, 1, flags1, flags2, + ht->printnode, printflags); + } else { + untokenize(*argv); + zwarnnam(name, "bad pattern : %s", *argv); + returnval = 1; + } + unqueue_signals(); + } + return returnval; + } + + /* Take arguments literally. Don't glob */ + queue_signals(); + while ((asg = getasg(&argv, NULL))) { + if (asg->value.scalar && !OPT_ISSET(ops,'L')) { + /* The argument is of the form foo=bar and we are not * + * forcing a listing with -L, so define an alias */ + ht->addnode(ht, ztrdup(asg->name), + createaliasnode(ztrdup(asg->value.scalar), flags1)); + } else if ((a = (Alias) ht->getnode(ht, asg->name))) { + /* display alias if appropriate */ + if (!type_opts || ht == sufaliastab || + (OPT_ISSET(ops,'r') && + !(a->node.flags & (ALIAS_GLOBAL|ALIAS_SUFFIX))) || + (OPT_ISSET(ops,'g') && (a->node.flags & ALIAS_GLOBAL))) + ht->printnode(&a->node, printflags); + } else + returnval = 1; + } + unqueue_signals(); + return returnval; +} + + +/**** miscellaneous builtins ****/ + +/* true, : (colon) */ + +/**/ +int +bin_true(UNUSED(char *name), UNUSED(char **argv), UNUSED(Options ops), UNUSED(int func)) +{ + return 0; +} + +/* false builtin */ + +/**/ +int +bin_false(UNUSED(char *name), UNUSED(char **argv), UNUSED(Options ops), UNUSED(int func)) +{ + return 1; +} + +/* the zle buffer stack */ + +/**/ +mod_export LinkList bufstack; + +/* echo, print, printf, pushln */ + +#define print_val(VAL) \ + if (prec >= 0) \ + count += fprintf(fout, spec, width, prec, VAL); \ + else \ + count += fprintf(fout, spec, width, VAL); + +/* + * Because of the use of getkeystring() to interpret the arguments, + * the elements of args spend a large part of the function unmetafied + * with the lengths in len. This may have seemed a good idea once. + * As we are stuck with this for now, we need to be very careful + * deciding what state args is in. + */ + +/**/ +int +bin_print(char *name, char **args, Options ops, int func) +{ + int flen, width, prec, type, argc, n, narg, curlen = 0; + int nnl = 0, fmttrunc = 0, ret = 0, maxarg = 0, nc = 0; + int flags[6], *len, visarr = 0; + char *start, *endptr, *c, *d, *flag, *buf = NULL, spec[14], *fmt = NULL; + char **first, **argp, *curarg, *flagch = "'0+- #", save = '\0', nullstr = '\0'; + size_t rcount = 0, count = 0; + size_t *cursplit = 0, *splits = 0; + FILE *fout = stdout; +#ifdef HAVE_OPEN_MEMSTREAM + size_t mcount; +#define ASSIGN_MSTREAM(BUF,FOUT) \ + do { \ + if ((FOUT = open_memstream(&BUF, &mcount)) == NULL) { \ + zwarnnam(name, "open_memstream failed"); \ + return 1; \ + } \ + } while (0) + /* + * Some implementations of open_memstream() have a bug such that, + * if fflush() is followed by fclose(), another NUL byte is written + * to the buffer at the wrong position. Therefore we must fclose() + * before reading. + */ +#define READ_MSTREAM(BUF,FOUT) \ + ((fclose(FOUT) == 0) ? mcount : (size_t)-1) +#define CLOSE_MSTREAM(FOUT) 0 + +#else /* simulate HAVE_OPEN_MEMSTREAM */ + +#define ASSIGN_MSTREAM(BUF,FOUT) \ + do { \ + int tempfd; \ + char *tmpf; \ + if ((tempfd = gettempfile(NULL, 1, &tmpf)) < 0) { \ + zwarnnam(name, "can't open temp file: %e", errno); \ + return 1; \ + } \ + unlink(tmpf); \ + if ((FOUT = fdopen(tempfd, "w+")) == NULL) { \ + close(tempfd); \ + zwarnnam(name, "can't open temp file: %e", errno); \ + return 1; \ + } \ + } while (0) +#define READ_MSTREAM(BUF,FOUT) \ + ((((count = ftell(FOUT)), (BUF = (char *)zalloc(count + 1))) && \ + ((fseek(FOUT, 0L, SEEK_SET) == 0) && !(BUF[count] = '\0')) && \ + (fread(BUF, 1, count, FOUT) == count)) ? count : (size_t)-1) +#define CLOSE_MSTREAM(FOUT) fclose(FOUT) + +#endif + +#define IS_MSTREAM(FOUT) \ + (FOUT != stdout && \ + (OPT_ISSET(ops,'z') || OPT_ISSET(ops,'s') || OPT_ISSET(ops,'v'))) + + /* Testing EBADF special-cases >&- redirections */ +#define CLOSE_CLEANLY(FOUT) \ + (IS_MSTREAM(FOUT) ? CLOSE_MSTREAM(FOUT) == 0 : \ + ((FOUT == stdout) ? (fflush(FOUT) == 0 || errno == EBADF) : \ + (fclose(FOUT) == 0))) /* implies error for -u on a closed fd */ + + Histent ent; + mnumber mnumval; + double doubleval; + int intval; + zlong zlongval; + zulong zulongval; + char *stringval; + + /* Error check option combinations and option arguments */ + + if (OPT_ISSET(ops, 'z') + + OPT_ISSET(ops, 's') + OPT_ISSET(ops, 'S') + + OPT_ISSET(ops, 'v') > 1) { + zwarnnam(name, "only one of -s, -S, -v, or -z allowed"); + return 1; + } + if ((OPT_ISSET(ops, 'z') | OPT_ISSET(ops, 's') | OPT_ISSET(ops, 'S')) + + (OPT_ISSET(ops, 'c') | OPT_ISSET(ops, 'C')) > 1) { + zwarnnam(name, "-c or -C not allowed with -s, -S, or -z"); + return 1; + } + if ((OPT_ISSET(ops, 'z') | OPT_ISSET(ops, 'v') | + OPT_ISSET(ops, 's') | OPT_ISSET(ops, 'S')) + + (OPT_ISSET(ops, 'p') | OPT_ISSET(ops, 'u')) > 1) { + zwarnnam(name, "-p or -u not allowed with -s, -S, -v, or -z"); + return 1; + } + /* + if (OPT_ISSET(ops, 'f') && + (OPT_ISSET(ops, 'S') || OPT_ISSET(ops, 'c') || OPT_ISSET(ops, 'C'))) { + zwarnnam(name, "-f not allowed with -c, -C, or -S"); + return 1; + } + */ + + /* -C -- number of columns */ + if (!fmt && OPT_ISSET(ops,'C')) { + char *eptr, *argptr = OPT_ARG(ops,'C'); + nc = (int)zstrtol(argptr, &eptr, 10); + if (*eptr) { + zwarnnam(name, "number expected after -%c: %s", 'C', argptr); + return 1; + } + if (nc <= 0) { + zwarnnam(name, "invalid number of columns: %s", argptr); + return 1; + } + } + + if (func == BIN_PRINTF) { + if (!strcmp(*args, "--") && !*++args) { + zwarnnam(name, "not enough arguments"); + return 1; + } + fmt = *args++; + } else if (func == BIN_ECHO && isset(BSDECHO)) + ops->ind['E'] = 1; + else if (OPT_HASARG(ops,'f')) + fmt = OPT_ARG(ops,'f'); + if (fmt) + fmt = getkeystring(fmt, &flen, OPT_ISSET(ops,'b') ? GETKEYS_BINDKEY : + GETKEYS_PRINTF_FMT, &fmttrunc); + + first = args; + + /* -m option -- treat the first argument as a pattern and remove + * arguments not matching */ + if (OPT_ISSET(ops,'m')) { + Patprog pprog; + char **t, **p; + + if (!*args) { + zwarnnam(name, "no pattern specified"); + return 1; + } + queue_signals(); + tokenize(*args); + if (!(pprog = patcompile(*args, PAT_STATIC, NULL))) { + untokenize(*args); + zwarnnam(name, "bad pattern: %s", *args); + unqueue_signals(); + return 1; + } + for (t = p = ++args; *p; p++) + if (pattry(pprog, *p)) + *t++ = *p; + *t = NULL; + first = args; + unqueue_signals(); + if (fmt && !*args) return 0; + } + /* compute lengths, and interpret according to -P, -D, -e, etc. */ + argc = arrlen(args); + len = (int *) hcalloc(argc * sizeof(int)); + for (n = 0; n < argc; n++) { + /* first \ sequences */ + if (fmt || + (!OPT_ISSET(ops,'e') && + (OPT_ISSET(ops,'R') || OPT_ISSET(ops,'r') || OPT_ISSET(ops,'E')))) + unmetafy(args[n], &len[n]); + else { + int escape_how; + if (OPT_ISSET(ops,'b')) + escape_how = GETKEYS_BINDKEY; + else if (func != BIN_ECHO && !OPT_ISSET(ops,'e')) + escape_how = GETKEYS_PRINT; + else + escape_how = GETKEYS_ECHO; + args[n] = getkeystring(args[n], &len[n], escape_how, &nnl); + if (nnl) { + /* If there was a \c escape, make this the last arg. */ + argc = n + 1; + args[argc] = NULL; + } + } + /* -P option -- interpret as a prompt sequence */ + if (OPT_ISSET(ops,'P')) { + /* + * promptexpand uses permanent storage: to avoid + * messy memory management, stick it on the heap + * instead. + */ + char *str = unmetafy( + promptexpand(metafy(args[n], len[n], META_NOALLOC), + 0, NULL, NULL, NULL), + &len[n]); + args[n] = dupstrpfx(str, len[n]); + free(str); + } + /* -D option -- interpret as a directory, and use ~ */ + if (OPT_ISSET(ops,'D')) { + Nameddir d; + + queue_signals(); + /* TODO: finddir takes a metafied file */ + d = finddir(args[n]); + if (d) { + int dirlen = strlen(d->dir); + char *arg = zhalloc(len[n] - dirlen + strlen(d->node.nam) + 2); + sprintf(arg, "~%s%s", d->node.nam, args[n] + dirlen); + args[n] = arg; + len[n] = strlen(args[n]); + } + unqueue_signals(); + } + } + + /* -o and -O -- sort the arguments */ + if (OPT_ISSET(ops,'o') || OPT_ISSET(ops,'O')) { + int flags; + + if (fmt && !*args) + return 0; + flags = OPT_ISSET(ops,'i') ? SORTIT_IGNORING_CASE : 0; + if (OPT_ISSET(ops,'O')) + flags |= SORTIT_BACKWARDS; + strmetasort(args, flags, len); + } + + /* -u and -p -- output to other than standard output */ + if ((OPT_HASARG(ops,'u') || OPT_ISSET(ops,'p')) && + /* rule out conflicting options -- historical precedence */ + ((!fmt && (OPT_ISSET(ops,'c') || OPT_ISSET(ops,'C'))) || + !(OPT_ISSET(ops, 'z') || OPT_ISSET(ops, 'v') || + OPT_ISSET(ops, 's') || OPT_ISSET(ops, 'S')))) { + int fdarg, fd; + + if (OPT_ISSET(ops, 'p')) { + fdarg = coprocout; + if (fdarg < 0) { + zwarnnam(name, "-p: no coprocess"); + return 1; + } + } else { + char *argptr = OPT_ARG(ops,'u'), *eptr; + /* Handle undocumented feature that -up worked */ + if (!strcmp(argptr, "p")) { + fdarg = coprocout; + if (fdarg < 0) { + zwarnnam(name, "-p: no coprocess"); + return 1; + } + } else { + fdarg = (int)zstrtol(argptr, &eptr, 10); + if (*eptr) { + zwarnnam(name, "number expected after -u: %s", argptr); + return 1; + } + } + } + + if ((fd = dup(fdarg)) < 0) { + zwarnnam(name, "bad file number: %d", fdarg); + return 1; + } + if ((fout = fdopen(fd, "w")) == 0) { + close(fd); + zwarnnam(name, "bad mode on fd %d", fd); + return 1; + } + } + + if (OPT_ISSET(ops, 'v') || + (fmt && (OPT_ISSET(ops,'z') || OPT_ISSET(ops,'s')))) + ASSIGN_MSTREAM(buf,fout); + + /* -c -- output in columns */ + if (!fmt && (OPT_ISSET(ops,'c') || OPT_ISSET(ops,'C'))) { + int l, nr, sc, n, t, i; +#ifdef MULTIBYTE_SUPPORT + int *widths; + + if (isset(MULTIBYTE)) { + int *wptr; + + /* + * We need the character widths to align output in + * columns. + */ + wptr = widths = (int *) zhalloc(argc * sizeof(int)); + for (i = 0; i < argc && args[i]; i++, wptr++) { + int l = len[i], width = 0; + char *aptr = args[i]; + mbstate_t mbs; + + memset(&mbs, 0, sizeof(mbstate_t)); + while (l > 0) { + wchar_t wc; + size_t cnt; + int wcw; + + /* + * Prevent misaligned columns due to escape sequences by + * skipping over them. Octals \033 and \233 are the + * possible escape characters recognized by ANSI. + * + * It ought to be possible to do this in the case + * of prompt expansion by propagating the information + * about escape sequences (currently we strip this + * out). + */ + if (*aptr == '\033' || *aptr == '\233') { + for (aptr++, l--; + l && !isalpha(STOUC(*aptr)); + aptr++, l--) + ; + aptr++; + l--; + continue; + } + + cnt = mbrtowc(&wc, aptr, l, &mbs); + + if (cnt == MB_INCOMPLETE || cnt == MB_INVALID) + { + /* treat as ordinary string */ + width += l; + break; + } + wcw = WCWIDTH(wc); + /* treat unprintable as 0 */ + if (wcw > 0) + width += wcw; + /* skip over NUL normally */ + if (cnt == 0) + cnt = 1; + aptr += cnt; + l -= cnt; + } + widths[i] = width; + } + } + else + widths = len; +#else + int *widths = len; +#endif + + if (OPT_ISSET(ops,'C')) { + /* + * n: number of elements + * nc: number of columns (above) + * nr: number of rows + */ + n = arrlen(args); + nr = (n + nc - 1) / nc; + + /* + * i: loop counter + * l: maximum length seen + * + * Ignore lengths in last column since they don't affect + * the separation. + */ + for (i = l = 0; i < argc; i++) { + if (OPT_ISSET(ops, 'a')) { + if ((i % nc) == nc - 1) + continue; + } else { + if (i >= nr * (nc - 1)) + break; + } + if (l < widths[i]) + l = widths[i]; + } + sc = l + 2; + } + else + { + /* + * n: loop counter + * l: maximum length seen + */ + for (n = l = 0; n < argc; n++) + if (l < widths[n]) + l = widths[n]; + + /* + * sc: column width + * nc: number of columns (at least one) + */ + sc = l + 2; + nc = (zterm_columns + 1) / sc; + if (!nc) + nc = 1; + nr = (n + nc - 1) / nc; + } + + if (OPT_ISSET(ops,'a')) /* print across, i.e. columns first */ + n = 0; + for (i = 0; i < nr; i++) { + if (OPT_ISSET(ops,'a')) + { + int ic; + for (ic = 0; ic < nc && n < argc; ic++, n++) + { + fwrite(args[n], len[n], 1, fout); + l = widths[n]; + if (n < argc) + for (; l < sc; l++) + fputc(' ', fout); + } + } + else + { + n = i; + do { + fwrite(args[n], len[n], 1, fout); + l = widths[n]; + for (t = nr; t && n < argc; t--, n++); + if (n < argc) + for (; l < sc; l++) + fputc(' ', fout); + } while (n < argc); + } + fputc(OPT_ISSET(ops,'N') ? '\0' : '\n', fout); + } + if (IS_MSTREAM(fout) && (rcount = READ_MSTREAM(buf,fout)) == -1) + ret = 1; + if (!CLOSE_CLEANLY(fout) || ret) { + zwarnnam(name, "write error: %e", errno); + ret = 1; + } + if (buf) { + /* assert: we must be doing -v at this point */ + queue_signals(); + if (ret) + free(buf); + else + setsparam(OPT_ARG(ops, 'v'), + metafy(buf, rcount, META_REALLOC)); + unqueue_signals(); + } + return ret; + } + + /* normal output */ + if (!fmt) { + if (OPT_ISSET(ops, 'z') || OPT_ISSET(ops, 'v') || + OPT_ISSET(ops, 's') || OPT_ISSET(ops, 'S')) { + /* + * We don't want the arguments unmetafied after all. + */ + for (n = 0; n < argc; n++) + metafy(args[n], len[n], META_NOALLOC); + } + + /* -z option -- push the arguments onto the editing buffer stack */ + if (OPT_ISSET(ops,'z')) { + queue_signals(); + zpushnode(bufstack, sepjoin(args, NULL, 0)); + unqueue_signals(); + return 0; + } + /* -s option -- add the arguments to the history list */ + if (OPT_ISSET(ops,'s') || OPT_ISSET(ops,'S')) { + int nwords = 0, nlen, iwords; + char **pargs = args; + + queue_signals(); + while (*pargs++) + nwords++; + if (nwords) { + if (OPT_ISSET(ops,'S')) { + int wordsize; + short *words; + if (nwords > 1) { + zwarnnam(name, "option -S takes a single argument"); + unqueue_signals(); + return 1; + } + words = NULL; + wordsize = 0; + histsplitwords(*args, &words, &wordsize, &nwords, 1); + ent = prepnexthistent(); + ent->words = (short *)zalloc(nwords*sizeof(short)); + memcpy(ent->words, words, nwords*sizeof(short)); + free(words); + ent->nwords = nwords/2; + } else { + ent = prepnexthistent(); + ent->words = (short *)zalloc(nwords*2*sizeof(short)); + ent->nwords = nwords; + nlen = iwords = 0; + for (pargs = args; *pargs; pargs++) { + ent->words[iwords++] = nlen; + nlen += strlen(*pargs); + ent->words[iwords++] = nlen; + nlen++; + } + } + } else { + ent = prepnexthistent(); + ent->words = (short *)NULL; + } + ent->node.nam = zjoin(args, ' ', 0); + ent->stim = ent->ftim = time(NULL); + ent->node.flags = 0; + addhistnode(histtab, ent->node.nam, ent); + unqueue_signals(); + return 0; + } + + if (OPT_HASARG(ops, 'x') || OPT_HASARG(ops, 'X')) { + char *eptr; + int expand, startpos = 0; + int all = OPT_HASARG(ops, 'X'); + char *xarg = all ? OPT_ARG(ops, 'X') : OPT_ARG(ops, 'x'); + + expand = (int)zstrtol(xarg, &eptr, 10); + if (*eptr || expand <= 0) { + zwarnnam(name, "positive integer expected after -%c: %s", 'x', + xarg); + return 1; + } + for (; *args; args++, len++) { + startpos = zexpandtabs(*args, *len, expand, startpos, fout, + all); + if (args[1]) { + if (OPT_ISSET(ops, 'l')) { + fputc('\n', fout); + startpos = 0; + } else if (OPT_ISSET(ops,'N')) { + fputc('\0', fout); + } else { + fputc(' ', fout); + startpos++; + } + } + } + } else { + for (; *args; args++, len++) { + fwrite(*args, *len, 1, fout); + if (args[1]) + fputc(OPT_ISSET(ops,'l') ? '\n' : + OPT_ISSET(ops,'N') ? '\0' : ' ', fout); + } + } + if (!(OPT_ISSET(ops,'n') || nnl || + (OPT_ISSET(ops, 'v') && !OPT_ISSET(ops, 'l')))) + fputc(OPT_ISSET(ops,'N') ? '\0' : '\n', fout); + if (IS_MSTREAM(fout) && (rcount = READ_MSTREAM(buf,fout)) == -1) + ret = 1; + if (!CLOSE_CLEANLY(fout) || ret) { + zwarnnam(name, "write error: %e", errno); + ret = 1; + } + if (buf) { + /* assert: we must be doing -v at this point */ + queue_signals(); + if (ret) + free(buf); + else + setsparam(OPT_ARG(ops, 'v'), + metafy(buf, rcount, META_REALLOC)); + unqueue_signals(); + } + return ret; + } + + /* + * All the remaining code in this function is for printf-style + * output (printf itself, or print -f). We still have to handle + * special cases of printing to a ZLE buffer or the history, however. + */ + + if (OPT_ISSET(ops,'v')) { + struct value vbuf; + char* s = OPT_ARG(ops,'v'); + Value v = getvalue(&vbuf, &s, 0); + visarr = v && PM_TYPE(v->pm->node.flags) == PM_ARRAY; + } + /* printf style output */ + *spec = '%'; + argp = args; + do { + rcount = count; + if (argp > args && visarr) { /* reusing format string */ + if (!splits) + cursplit = splits = (size_t *)zhalloc(sizeof(size_t) * + (arrlen(args) / (argp - args) + 1)); + *cursplit++ = count; + } + if (maxarg) { + first += maxarg; + argc -= maxarg; + maxarg = 0; + } + for (c = fmt; c-fmt < flen; c++) { + if (*c != '%') { + putc(*c, fout); + ++count; + continue; + } + + start = c++; + if (*c == '%') { + putc('%', fout); + ++count; + continue; + } + + type = prec = -1; + width = 0; + curarg = NULL; + d = spec + 1; + + if (*c >= '1' && *c <= '9') { + narg = strtoul(c, &endptr, 0); + if (*endptr == '$') { + c = endptr + 1; + DPUTS(narg <= 0, "specified zero or negative arg"); + if (narg > argc) { + zwarnnam(name, "%d: argument specifier out of range", + narg); + if (fout != stdout) + fclose(fout); +#ifdef HAVE_OPEN_MEMSTREAM + if (buf) + free(buf); +#endif + return 1; + } else { + if (narg > maxarg) maxarg = narg; + curarg = *(first + narg - 1); + curlen = len[first - args + narg - 1]; + } + } + } + + /* copy only one of each flag as spec has finite size */ + memset(flags, 0, sizeof(flags)); + while (*c && (flag = strchr(flagch, *c))) { + if (!flags[flag - flagch]) { + flags[flag - flagch] = 1; + *d++ = *c; + } + c++; + } + + if (idigit(*c)) { + width = strtoul(c, &endptr, 0); + c = endptr; + } else if (*c == '*') { + if (idigit(*++c)) { + narg = strtoul(c, &endptr, 0); + if (*endptr == '$') { + c = endptr + 1; + if (narg > argc || narg <= 0) { + zwarnnam(name, + "%d: argument specifier out of range", + narg); + if (fout != stdout) + fclose(fout); +#ifdef HAVE_OPEN_MEMSTREAM + if (buf) + free(buf); +#endif + return 1; + } else { + if (narg > maxarg) maxarg = narg; + argp = first + narg - 1; + } + } + } + if (*argp) { + width = (int)mathevali(*argp++); + if (errflag) { + errflag &= ~ERRFLAG_ERROR; + ret = 1; + } + } + } + *d++ = '*'; + + if (*c == '.') { + if (*++c == '*') { + if (idigit(*++c)) { + narg = strtoul(c, &endptr, 0); + if (*endptr == '$') { + c = endptr + 1; + if (narg > argc || narg <= 0) { + zwarnnam(name, + "%d: argument specifier out of range", + narg); + if (fout != stdout) + fclose(fout); +#ifdef HAVE_OPEN_MEMSTREAM + if (buf) + free(buf); +#endif + return 1; + } else { + if (narg > maxarg) maxarg = narg; + argp = first + narg - 1; + } + } + } + + if (*argp) { + prec = (int)mathevali(*argp++); + if (errflag) { + errflag &= ~ERRFLAG_ERROR; + ret = 1; + } + } + } else if (idigit(*c)) { + prec = strtoul(c, &endptr, 0); + c = endptr; + } else + prec = 0; + if (prec >= 0) *d++ = '.', *d++ = '*'; + } + + /* ignore any size modifier */ + if (*c == 'l' || *c == 'L' || *c == 'h') c++; + + if (!curarg && *argp) { + curarg = *argp; + curlen = len[argp++ - args]; + } + d[1] = '\0'; + switch (*d = *c) { + case 'c': + if (curarg) + intval = *curarg; + else + intval = 0; + print_val(intval); + break; + case 's': + case 'b': + if (curarg) { + char *b, *ptr; + int lbytes, lchars, lleft; +#ifdef MULTIBYTE_SUPPORT + mbstate_t mbs; +#endif + + if (*c == 'b') { + b = getkeystring(metafy(curarg, curlen, META_USEHEAP), + &lbytes, + OPT_ISSET(ops,'b') ? GETKEYS_BINDKEY : + GETKEYS_PRINTF_ARG, &nnl); + } else { + b = curarg; + lbytes = curlen; + } + /* + * Handle width/precision here and use fwrite so that + * nul characters can be output. + * + * First, examine width of string given that it + * may contain multibyte characters. The output + * widths are for characters, so we need to count + * (in lchars). However, if we need to truncate + * the string we need the width in bytes (in lbytes). + */ + ptr = b; +#ifdef MULTIBYTE_SUPPORT + memset(&mbs, 0, sizeof(mbs)); +#endif + + for (lchars = 0, lleft = lbytes; lleft > 0; lchars++) { + int chars; + + if (lchars == prec) { + /* Truncate at this point. */ + lbytes = ptr - b; + break; + } +#ifdef MULTIBYTE_SUPPORT + if (isset(MULTIBYTE)) { + chars = mbrlen(ptr, lleft, &mbs); + if (chars < 0) { + /* + * Invalid/incomplete character at this + * point. Assume all the rest are a + * single byte. That's about the best we + * can do. + */ + lchars += lleft; + lbytes = (ptr - b) + lleft; + break; + } else if (chars == 0) { + /* NUL, handle as real character */ + chars = 1; + } + } + else /* use the non-multibyte code below */ +#endif + chars = 1; /* compiler can optimise this...*/ + lleft -= chars; + ptr += chars; + } + if (width > 0 && flags[3]) width = -width; + if (width > 0 && lchars < width) + count += fprintf(fout, "%*c", width - lchars, ' '); + count += fwrite(b, 1, lbytes, fout); + if (width < 0 && lchars < -width) + count += fprintf(fout, "%*c", -width - lchars, ' '); + if (nnl) { + /* If the %b arg had a \c escape, truncate the fmt. */ + flen = c - fmt + 1; + fmttrunc = 1; + } + } else if (width) + count += fprintf(fout, "%*c", width, ' '); + break; + case 'q': + stringval = curarg ? + quotestring(metafy(curarg, curlen, META_USEHEAP), + QT_BACKSLASH_SHOWNULL) : &nullstr; + *d = 's'; + print_val(unmetafy(stringval, &curlen)); + break; + case 'd': + case 'i': + type=1; + break; + case 'e': + case 'E': + case 'f': + case 'g': + case 'G': + type=2; + break; + case 'o': + case 'u': + case 'x': + case 'X': + type=3; + break; + case 'n': + if (curarg) setiparam(curarg, count - rcount); + break; + default: + if (*c) { + save = c[1]; + c[1] = '\0'; + } + zwarnnam(name, "%s: invalid directive", start); + if (*c) c[1] = save; + /* Why do we care about a clean close here? */ + if (!CLOSE_CLEANLY(fout)) + zwarnnam(name, "write error: %e", errno); +#ifdef HAVE_OPEN_MEMSTREAM + if (buf) + free(buf); +#endif + return 1; + } + + if (type > 0) { + if (curarg && (*curarg == '\'' || *curarg == '"' )) { + convchar_t cc; +#ifdef MULTIBYTE_SUPPORT + if (isset(MULTIBYTE)) { + mb_charinit(); + (void)mb_metacharlenconv(metafy(curarg+1, curlen-1, + META_USEHEAP), &cc); + } + else + cc = WEOF; + if (cc == WEOF) + cc = (curlen > 1) ? STOUC(curarg[1]) : 0; +#else + cc = (curlen > 1) ? STOUC(curarg[1]) : 0; +#endif + if (type == 2) { + doubleval = cc; + print_val(doubleval); + } else { + intval = cc; + print_val(intval); + } + } else { + switch (type) { + case 1: +#ifdef ZSH_64_BIT_TYPE + *d++ = 'l'; +#endif + *d++ = 'l', *d++ = *c, *d = '\0'; + zlongval = (curarg) ? mathevali(curarg) : 0; + if (errflag) { + zlongval = 0; + errflag &= ~ERRFLAG_ERROR; + ret = 1; + } + print_val(zlongval) + break; + case 2: + if (curarg) { + char *eptr; + /* + * First attempt to parse as a floating + * point constant. If we go through + * a math evaluation, we can lose + * mostly unimportant information + * that people in standards organizations + * worry about. + */ + doubleval = strtod(curarg, &eptr); + /* + * If it didn't parse as a constant, + * parse it as an expression. + */ + if (*eptr != '\0') { + mnumval = matheval(curarg); + doubleval = (mnumval.type & MN_FLOAT) ? + mnumval.u.d : (double)mnumval.u.l; + } + } else doubleval = 0; + if (errflag) { + doubleval = 0; + errflag &= ~ERRFLAG_ERROR; + ret = 1; + } + /* force consistent form for Inf/NaN output */ + if (isnan(doubleval)) + count += fputs("nan", fout); + else if (isinf(doubleval)) + count += fputs((doubleval < 0.0) ? "-inf" : "inf", fout); + else + print_val(doubleval) + break; + case 3: +#ifdef ZSH_64_BIT_UTYPE + *d++ = 'l'; +#endif + *d++ = 'l', *d++ = *c, *d = '\0'; + if (!curarg) + zulongval = (zulong)0; + else if (!zstrtoul_underscore(curarg, &zulongval)) + zulongval = mathevali(curarg); + if (errflag) { + zulongval = 0; + errflag &= ~ERRFLAG_ERROR; + ret = 1; + } + print_val(zulongval) + } + } + } + if (maxarg && (argp - first > maxarg)) + maxarg = argp - first; + } + + if (maxarg) argp = first + maxarg; + /* if there are remaining args, reuse format string */ + } while (*argp && argp != first && !fmttrunc && !OPT_ISSET(ops,'r')); + + if (IS_MSTREAM(fout)) { + queue_signals(); + if ((rcount = READ_MSTREAM(buf,fout)) == -1) { + zwarnnam(name, "i/o error: %e", errno); + if (buf) + free(buf); + } else { + if (visarr && splits) { + char **arrayval = zshcalloc((cursplit - splits + 2) * sizeof(char *)); + for (;cursplit >= splits; cursplit--) { + int start = cursplit == splits ? 0 : cursplit[-1]; + arrayval[cursplit - splits] = + metafy(buf + start, count - start, META_DUP); + count = start; + } + setaparam(OPT_ARG(ops, 'v'), arrayval); + free(buf); + } else { + stringval = metafy(buf, rcount, META_REALLOC); + if (OPT_ISSET(ops,'z')) { + zpushnode(bufstack, stringval); + } else if (OPT_ISSET(ops,'v')) { + setsparam(OPT_ARG(ops, 'v'), stringval); + } else { + ent = prepnexthistent(); + ent->node.nam = stringval; + ent->stim = ent->ftim = time(NULL); + ent->node.flags = 0; + ent->words = (short *)NULL; + addhistnode(histtab, ent->node.nam, ent); + } + } + } + unqueue_signals(); + } + + if (!CLOSE_CLEANLY(fout)) + { + zwarnnam(name, "write error: %e", errno); + ret = 1; + } + return ret; +} + +/* shift builtin */ + +/**/ +int +bin_shift(char *name, char **argv, Options ops, UNUSED(int func)) +{ + int num = 1, l, ret = 0; + char **s; + + /* optional argument can be either numeric or an array */ + queue_signals(); + if (*argv && !getaparam(*argv)) { + num = mathevali(*argv++); + if (errflag) { + unqueue_signals(); + return 1; + } + } + + if (num < 0) { + unqueue_signals(); + zwarnnam(name, "argument to shift must be non-negative"); + return 1; + } + + if (*argv) { + for (; *argv; argv++) + if ((s = getaparam(*argv))) { + if (arrlen_lt(s, num)) { + zwarnnam(name, "shift count must be <= $#"); + ret++; + continue; + } + if (OPT_ISSET(ops,'p')) { + char **s2, **src, **dst; + int count; + l = arrlen(s); + src = s; + dst = s2 = (char **)zalloc((l - num + 1) * sizeof(char *)); + for (count = l - num; count; count--) + *dst++ = ztrdup(*src++); + *dst = NULL; + s = s2; + } else { + s = zarrdup(s + num); + } + setaparam(*argv, s); + } + } else { + if (num > (l = arrlen(pparams))) { + zwarnnam(name, "shift count must be <= $#"); + ret = 1; + } else { + s = zalloc((l - num + 1) * sizeof(char *)); + if (OPT_ISSET(ops,'p')) { + memcpy(s, pparams, (l - num) * sizeof(char *)); + s[l-num] = NULL; + while (num--) + zsfree(pparams[l-1-num]); + } else { + memcpy(s, pparams + num, (l - num + 1) * sizeof(char *)); + while (num--) + zsfree(pparams[num]); + } + zfree(pparams, (l + 1) * sizeof(char *)); + pparams = s; + } + } + unqueue_signals(); + return ret; +} + +/* + * Position of getopts option within OPTIND argument with multiple options. + */ + +/**/ +int optcind; + +/* getopts: automagical option handling for shell scripts */ + +/**/ +int +bin_getopts(UNUSED(char *name), char **argv, UNUSED(Options ops), UNUSED(int func)) +{ + int lenstr, lenoptstr, quiet, lenoptbuf; + char *optstr = unmetafy(*argv++, &lenoptstr), *var = *argv++; + char **args = (*argv) ? argv : pparams; + char *str, optbuf[2] = " ", *p, opch; + + /* zoptind keeps count of the current argument number. The * + * user can set it to zero to start a new option parse. */ + if (zoptind < 1) { + /* first call */ + zoptind = 1; + optcind = 0; + } + if (arrlen_lt(args, zoptind)) + /* no more options */ + return 1; + + /* leading ':' in optstr means don't print an error message */ + quiet = *optstr == ':'; + optstr += quiet; + lenoptstr -= quiet; + + /* find place in relevant argument */ + str = unmetafy(dupstring(args[zoptind - 1]), &lenstr); + if (!lenstr) /* Definitely not an option. */ + return 1; + if(optcind >= lenstr) { + optcind = 0; + if(!args[zoptind++]) + return 1; + str = unmetafy(dupstring(args[zoptind - 1]), &lenstr); + } + if(!optcind) { + if(lenstr < 2 || (*str != '-' && *str != '+')) + return 1; + if(lenstr == 2 && str[0] == '-' && str[1] == '-') { + zoptind++; + return 1; + } + optcind++; + } + opch = str[optcind++]; + if(str[0] == '+') { + optbuf[0] = '+'; + lenoptbuf = 2; + } else + lenoptbuf = 1; + optbuf[lenoptbuf - 1] = opch; + + /* check for legality */ + if(opch == ':' || !(p = memchr(optstr, opch, lenoptstr))) { + p = "?"; + err: + zsfree(zoptarg); + setsparam(var, ztrdup(p)); + if(quiet) { + zoptarg = metafy(optbuf, lenoptbuf, META_DUP); + } else { + zwarn(*p == '?' ? "bad option: %c%c" : + "argument expected after %c%c option", + "?-+"[lenoptbuf], opch); + zoptarg=ztrdup(""); + } + return 0; + } + + /* check for required argument */ + if(p[1] == ':') { + if(optcind == lenstr) { + if(!args[zoptind]) { + p = ":"; + goto err; + } + p = ztrdup(args[zoptind++]); + } else + p = metafy(str+optcind, lenstr-optcind, META_DUP); + /* + * Careful: I've just changed the following two lines from + * optcind = ztrlen(args[zoptind - 1]); + * and it's a rigorous theorem that every change in getopts breaks + * something. See zsh-workers/9095 for the bug fixed here. + * PWS 2000/05/02 + */ + optcind = 0; + zoptind++; + zsfree(zoptarg); + zoptarg = p; + } else { + zsfree(zoptarg); + zoptarg = ztrdup(""); + } + + setsparam(var, metafy(optbuf, lenoptbuf, META_DUP)); + return 0; +} + +/* Flag that we should exit the shell as soon as all functions return. */ +/**/ +mod_export int +exit_pending; + +/* Shell level at which we exit if exit_pending */ +/**/ +mod_export int +exit_level; + +/* break, bye, continue, exit, logout, return -- most of these take * + * one numeric argument, and the other (logout) is related to return. * + * (return is treated as a logout when in a login shell.) */ + +/**/ +int +bin_break(char *name, char **argv, UNUSED(Options ops), int func) +{ + int num = lastval, nump = 0, implicit; + + /* handle one optional numeric argument */ + implicit = !*argv; + if (*argv) { + num = mathevali(*argv++); + nump = 1; + } + + if (nump > 0 && (func == BIN_CONTINUE || func == BIN_BREAK) && num <= 0) { + zerrnam(name, "argument is not positive: %d", num); + return 1; + } + + switch (func) { + case BIN_CONTINUE: + if (!loops) { /* continue is only permitted in loops */ + zerrnam(name, "not in while, until, select, or repeat loop"); + return 1; + } + contflag = 1; /* FALLTHROUGH */ + case BIN_BREAK: + if (!loops) { /* break is only permitted in loops */ + zerrnam(name, "not in while, until, select, or repeat loop"); + return 1; + } + breaks = nump ? minimum(num,loops) : 1; + break; + case BIN_RETURN: + if ((isset(INTERACTIVE) && isset(SHINSTDIN)) + || locallevel || sourcelevel) { + retflag = 1; + breaks = loops; + lastval = num; + if (trap_state == TRAP_STATE_PRIMED && trap_return == -2 + /* + * With POSIX, "return" on its own in a trap doesn't + * update $? --- we keep the status from before the + * trap. + */ + && !(isset(POSIXTRAPS) && implicit)) { + trap_state = TRAP_STATE_FORCE_RETURN; + trap_return = lastval; + } + return lastval; + } + zexit(num, 0); /* else treat return as logout/exit */ + break; + case BIN_LOGOUT: + if (unset(LOGINSHELL)) { + zerrnam(name, "not login shell"); + return 1; + } + /*FALLTHROUGH*/ + case BIN_EXIT: + if (locallevel > forklevel && shell_exiting != -1) { + /* + * We don't exit directly from functions to allow tidying + * up, in particular EXIT traps. We still need to perform + * the usual interactive tests to see if we can exit at + * all, however. + * + * If we are forked, we exit the shell at the function depth + * at which we became a subshell, hence the comparison. + * + * If we are already exiting... give this all up as + * a bad job. + */ + if (stopmsg || (zexit(0,2), !stopmsg)) { + retflag = 1; + breaks = loops; + exit_pending = (num << 1) | 1; + exit_level = locallevel; + } + } else + zexit(num, 0); + break; + } + return 0; +} + +/* we have printed a 'you have stopped (running) jobs.' message */ + +/**/ +mod_export int stopmsg; + +/* check to see if user has jobs running/stopped */ + +/**/ +static void +checkjobs(void) +{ + int i; + + for (i = 1; i <= maxjob; i++) + if (i != thisjob && (jobtab[i].stat & STAT_LOCKED) && + !(jobtab[i].stat & STAT_NOPRINT) && + (isset(CHECKRUNNINGJOBS) || jobtab[i].stat & STAT_STOPPED)) + break; + if (i <= maxjob) { + if (jobtab[i].stat & STAT_STOPPED) { + +#ifdef USE_SUSPENDED + zerr("you have suspended jobs."); +#else + zerr("you have stopped jobs."); +#endif + + } else + zerr("you have running jobs."); + stopmsg = 1; + } +} + +/* + * -1 if the shell is already committed to exit. + * positive if zexit() was already called. + */ + +/**/ +int shell_exiting; + +/* exit the shell. val is the return value of the shell. * + * from_where is + * 1 if zexit is called because of a signal + * 2 if we can't actually exit yet (e.g. functions need + * terminating) but should perform the usual interactive tests. + */ + +/**/ +mod_export void +zexit(int val, int from_where) +{ + /* Don't do anything recursively: see below */ + if (shell_exiting == -1) + return; + + if (isset(MONITOR) && !stopmsg && from_where != 1) { + scanjobs(); /* check if jobs need printing */ + if (isset(CHECKJOBS)) + checkjobs(); /* check if any jobs are running/stopped */ + if (stopmsg) { + stopmsg = 2; + return; + } + } + /* Positive in_exit means we have been here before */ + if (from_where == 2 || (shell_exiting++ && from_where)) + return; + + /* + * We're now committed to exiting. Set shell_exiting to -1 to + * indicate we shouldn't do any recursive processing. + */ + shell_exiting = -1; + /* + * We want to do all remaining processing regardless of preceding + * errors, even user interrupts. + */ + errflag = 0; + + if (isset(MONITOR)) { + /* send SIGHUP to any jobs left running */ + killrunjobs(from_where == 1); + } + if (isset(RCS) && interact) { + if (!nohistsave) { + int writeflags = HFILE_USE_OPTIONS; + if (from_where == 1) + writeflags |= HFILE_NO_REWRITE; + saveandpophiststack(1, writeflags); + savehistfile(NULL, 1, writeflags); + } + if (islogin && !subsh) { + sourcehome(".zlogout"); +#ifdef GLOBAL_ZLOGOUT + if (isset(RCS) && isset(GLOBALRCS)) + source(GLOBAL_ZLOGOUT); +#endif + } + } + lastval = val; + /* + * Now we are committed to exiting any previous state + * is irrelevant. Ensure trap can run. + */ + errflag = intrap = 0; + if (sigtrapped[SIGEXIT]) + dotrap(SIGEXIT); + callhookfunc("zshexit", NULL, 1, NULL); + runhookdef(EXITHOOK, NULL); + if (opts[MONITOR] && interact && (SHTTY != -1)) { + release_pgrp(); + } + if (mypid != getpid()) + _exit(val); + else + exit(val); +} + +/* . (dot), source */ + +/**/ +int +bin_dot(char *name, char **argv, UNUSED(Options ops), UNUSED(int func)) +{ + char **old, *old0 = NULL; + int diddot = 0, dotdot = 0; + char *s, **t, *enam, *arg0, *buf; + struct stat st; + enum source_return ret; + + if (!*argv) + return 0; + old = pparams; + /* get arguments for the script */ + if (argv[1]) + pparams = zarrdup(argv + 1); + + enam = arg0 = ztrdup(*argv); + if (isset(FUNCTIONARGZERO)) { + old0 = argzero; + argzero = ztrdup(arg0); + } + s = unmeta(enam); + errno = ENOENT; + ret = SOURCE_NOT_FOUND; + /* for source only, check in current directory first */ + if (*name != '.' && access(s, F_OK) == 0 + && stat(s, &st) >= 0 && !S_ISDIR(st.st_mode)) { + diddot = 1; + ret = source(enam); + } + if (ret == SOURCE_NOT_FOUND) { + /* use a path with / in it */ + for (s = arg0; *s; s++) + if (*s == '/') { + if (*arg0 == '.') { + if (arg0 + 1 == s) + ++diddot; + else if (arg0[1] == '.' && arg0 + 2 == s) + ++dotdot; + } + ret = source(arg0); + break; + } + if (!*s || (ret == SOURCE_NOT_FOUND && + isset(PATHDIRS) && diddot < 2 && dotdot == 0)) { + pushheap(); + /* search path for script */ + for (t = path; *t; t++) { + if (!(*t)[0] || ((*t)[0] == '.' && !(*t)[1])) { + if (diddot) + continue; + diddot = 1; + buf = dupstring(arg0); + } else + buf = zhtricat(*t, "/", arg0); + + s = unmeta(buf); + if (access(s, F_OK) == 0 && stat(s, &st) >= 0 + && !S_ISDIR(st.st_mode)) { + ret = source(enam = buf); + break; + } + } + popheap(); + } + } + /* clean up and return */ + if (argv[1]) { + freearray(pparams); + pparams = old; + } + if (ret == SOURCE_NOT_FOUND) { + if (isset(POSIXBUILTINS)) { + /* hard error in POSIX (we'll exit later) */ + zerrnam(name, "%e: %s", errno, enam); + } else { + zwarnnam(name, "%e: %s", errno, enam); + } + } + zsfree(arg0); + if (old0) { + zsfree(argzero); + argzero = old0; + } + return ret == SOURCE_OK ? lastval : 128 - ret; +} + +/* + * common for bin_emulate and bin_eval + */ + +static int +eval(char **argv) +{ + Eprog prog; + char *oscriptname = scriptname; + int oineval = ineval, fpushed; + struct funcstack fstack; + + /* + * If EVALLINENO is not set, we use the line number of the + * environment and must flag this up to exec.c. Otherwise, + * we use a special script name to indicate the special line number. + */ + ineval = !isset(EVALLINENO); + if (!ineval) { + scriptname = "(eval)"; + fstack.prev = funcstack; + fstack.name = scriptname; + fstack.caller = funcstack ? funcstack->name : dupstring(argzero); + fstack.lineno = lineno; + fstack.tp = FS_EVAL; + + /* + * To get file line numbers, we need to know if parent is + * the original script/shell or a sourced file, in which + * case we use the line number raw, or a function or eval, + * in which case we need to deduce where that came from. + * + * This replicates the logic for working out the information + * for $funcfiletrace---eval is similar to an inlined function + * call from a tracing perspective. + */ + if (!funcstack || funcstack->tp == FS_SOURCE) { + fstack.flineno = fstack.lineno; + fstack.filename = fstack.caller; + } else { + fstack.flineno = funcstack->flineno + lineno; + /* + * Line numbers in eval start from 1, not zero, + * so offset by one to get line in file. + */ + if (funcstack->tp == FS_EVAL) + fstack.flineno--; + fstack.filename = funcstack->filename; + if (!fstack.filename) + fstack.filename = ""; + } + funcstack = &fstack; + + fpushed = 1; + } else + fpushed = 0; + + prog = parse_string(zjoin(argv, ' ', 1), 1); + if (prog) { + if (wc_code(*prog->prog) != WC_LIST) { + /* No code to execute */ + lastval = 0; + } else { + execode(prog, 1, 0, "eval"); + + if (errflag && !lastval) + lastval = errflag; + } + } else { + lastval = 1; + } + + if (fpushed) + funcstack = funcstack->prev; + + errflag &= ~ERRFLAG_ERROR; + scriptname = oscriptname; + ineval = oineval; + + return lastval; +} + +/* emulate: set emulation mode and optionally evaluate shell code */ + +/**/ +int +bin_emulate(char *nam, char **argv, Options ops, UNUSED(int func)) +{ + int opt_L = OPT_ISSET(ops, 'L'); + int opt_R = OPT_ISSET(ops, 'R'); + int opt_l = OPT_ISSET(ops, 'l'); + int saveemulation, savehackchar; + int ret = 1, new_emulation; + unsigned int savepatterns; + char saveopts[OPT_SIZE], new_opts[OPT_SIZE]; + char *cmd = 0; + const char *shname = *argv; + LinkList optlist; + LinkNode optnode; + Emulation_options save_sticky; + OptIndex *on_ptr, *off_ptr; + + /* without arguments just print current emulation */ + if (!shname) { + if (opt_L || opt_R) { + zwarnnam(nam, "not enough arguments"); + return 1; + } + + switch(SHELL_EMULATION()) { + case EMULATE_CSH: + shname = "csh"; + break; + + case EMULATE_KSH: + shname = "ksh"; + break; + + case EMULATE_SH: + shname = "sh"; + break; + + default: + shname = "zsh"; + break; + } + + printf("%s\n", shname); + return 0; + } + + /* with single argument set current emulation */ + if (!argv[1]) { + char *cmdopts; + if (opt_l) { + cmdopts = (char *)zhalloc(OPT_SIZE); + memcpy(cmdopts, opts, OPT_SIZE); + } else + cmdopts = opts; + emulate(shname, opt_R, &emulation, cmdopts); + if (opt_L) + cmdopts[LOCALOPTIONS] = cmdopts[LOCALTRAPS] = + cmdopts[LOCALPATTERNS] = 1; + if (opt_l) { + list_emulate_options(cmdopts, opt_R); + return 0; + } + clearpatterndisables(); + return 0; + } + + if (opt_l) { + zwarnnam(nam, "too many arguments for -l"); + return 1; + } + + argv++; + memcpy(saveopts, opts, sizeof(opts)); + memcpy(new_opts, opts, sizeof(opts)); + savehackchar = keyboardhackchar; + emulate(shname, opt_R, &new_emulation, new_opts); + optlist = newlinklist(); + if (parseopts(nam, &argv, new_opts, &cmd, optlist, 0)) { + ret = 1; + goto restore; + } + + /* parseopts() has consumed anything that looks like an option */ + if (*argv) { + zwarnnam(nam, "unknown argument %s", *argv); + goto restore; + } + + savepatterns = savepatterndisables(); + /* + * All emulations start with an empty set of pattern disables, + * hence no special "sticky" behaviour is required. + */ + clearpatterndisables(); + + saveemulation = emulation; + emulation = new_emulation; + memcpy(opts, new_opts, sizeof(opts)); + /* If "-c command" is given, evaluate command using specified + * emulation mode. + */ + if (cmd) { + if (opt_L) { + zwarnnam(nam, "option -L incompatible with -c"); + goto restore2; + } + *--argv = cmd; /* on stack, never free()d, see execbuiltin() */ + } else { + if (opt_L) + opts[LOCALOPTIONS] = opts[LOCALTRAPS] = opts[LOCALPATTERNS] = 1; + return 0; + } + + save_sticky = sticky; + sticky = hcalloc(sizeof(*sticky)); + sticky->emulation = emulation; + for (optnode = firstnode(optlist); optnode; incnode(optnode)) { + /* Data is index into new_opts */ + char *optptr = (char *)getdata(optnode); + if (*optptr) + sticky->n_on_opts++; + else + sticky->n_off_opts++; + } + if (sticky->n_on_opts) + on_ptr = sticky->on_opts = + zhalloc(sticky->n_on_opts * sizeof(*sticky->on_opts)); + else + on_ptr = NULL; + if (sticky->n_off_opts) + off_ptr = sticky->off_opts = zhalloc(sticky->n_off_opts * + sizeof(*sticky->off_opts)); + else + off_ptr = NULL; + for (optnode = firstnode(optlist); optnode; incnode(optnode)) { + /* Data is index into new_opts */ + char *optptr = (char *)getdata(optnode); + int optno = optptr - new_opts; + if (*optptr) + *on_ptr++ = optno; + else + *off_ptr++ = optno; + } + ret = eval(argv); + sticky = save_sticky; +restore2: + emulation = saveemulation; + memcpy(opts, saveopts, sizeof(opts)); + restorepatterndisables(savepatterns); +restore: + keyboardhackchar = savehackchar; + inittyptab(); /* restore banghist */ + return ret; +} + +/* eval: simple evaluation */ + +/**/ +mod_export int ineval; + +/**/ +int +bin_eval(UNUSED(char *nam), char **argv, UNUSED(Options ops), UNUSED(int func)) +{ + return eval(argv); +} + +static char *zbuf; +static int readfd; + +/* Read a character from readfd, or from the buffer zbuf. Return EOF on end of +file/buffer. */ + +/* read: get a line of input, or (for compctl functions) return some * + * useful data about the state of the editing line. The -E and -e * + * options mean that the result should be sent to stdout. -e means, * + * in addition, that the result should not actually be assigned to * + * the specified parameters. */ + +/**/ +int +bin_read(char *name, char **args, Options ops, UNUSED(int func)) +{ + char *reply, *readpmpt; + int bsiz, c = 0, gotnl = 0, al = 0, first, nchars = 1, bslash, keys = 0; + int haso = 0; /* true if /dev/tty has been opened specially */ + int isem = !strcmp(term, "emacs"), izle = zleactive; + char *buf, *bptr, *firstarg, *zbuforig; + LinkList readll = newlinklist(); + FILE *oshout = NULL; + int readchar = -1, val, resettty = 0; + struct ttyinfo saveti; + char d; + long izle_timeout = 0; +#ifdef MULTIBYTE_SUPPORT + wchar_t delim = L'\n', wc; + mbstate_t mbs; + char *laststart; + size_t ret; +#else + char delim = '\n'; +#endif + + if (OPT_HASARG(ops,c='k')) { + char *eptr, *optarg = OPT_ARG(ops,c); + nchars = (int)zstrtol(optarg, &eptr, 10); + if (*eptr) { + zwarnnam(name, "number expected after -%c: %s", c, optarg); + return 1; + } + } + /* This `*args++ : *args' looks a bit weird, but it works around a bug + * in gcc-2.8.1 under DU 4.0. */ + firstarg = (*args && **args == '?' ? *args++ : *args); + reply = *args ? *args++ : OPT_ISSET(ops,'A') ? "reply" : "REPLY"; + + if (OPT_ISSET(ops,'A') && *args) { + zwarnnam(name, "only one array argument allowed"); + return 1; + } + + /* handle compctl case */ + if(OPT_ISSET(ops,'l') || OPT_ISSET(ops,'c')) + return compctlreadptr(name, args, ops, reply); + + if ((OPT_ISSET(ops,'k') || OPT_ISSET(ops,'q')) && + !OPT_ISSET(ops,'u') && !OPT_ISSET(ops,'p')) { + if (!zleactive) { + if (SHTTY == -1) { + /* need to open /dev/tty specially */ + if ((SHTTY = open("/dev/tty", O_RDWR|O_NOCTTY)) != -1) { + haso = 1; + oshout = shout; + init_shout(); + } + } else if (!shout) { + /* We need an output FILE* on the tty */ + init_shout(); + } + /* We should have a SHTTY opened by now. */ + if (SHTTY == -1) { + /* Unfortunately, we didn't. */ + fprintf(stderr, "not interactive and can't open terminal\n"); + fflush(stderr); + return 1; + } + if (unset(INTERACTIVE)) + gettyinfo(&shttyinfo); + /* attach to the tty */ + attachtty(mypgrp); + if (!isem) + setcbreak(); + readfd = SHTTY; + } + keys = 1; + } else if (OPT_HASARG(ops,'u') && !OPT_ISSET(ops,'p')) { + /* -u means take input from the specified file descriptor. */ + char *eptr, *argptr = OPT_ARG(ops,'u'); + /* The old code handled -up, but that was never documented. Still...*/ + if (!strcmp(argptr, "p")) { + readfd = coprocin; + if (readfd < 0) { + zwarnnam(name, "-p: no coprocess"); + return 1; + } + } else { + readfd = (int)zstrtol(argptr, &eptr, 10); + if (*eptr) { + zwarnnam(name, "number expected after -%c: %s", 'u', argptr); + return 1; + } + } +#if 0 + /* This code is left as a warning to future generations --- pws. */ + for (readfd = 9; readfd && !OPT_ISSET(ops,readfd + '0'); --readfd); +#endif + izle = 0; + } else if (OPT_ISSET(ops,'p')) { + readfd = coprocin; + if (readfd < 0) { + zwarnnam(name, "-p: no coprocess"); + return 1; + } + izle = 0; + } else + readfd = izle = 0; + + if (OPT_ISSET(ops,'s') && SHTTY != -1) { + struct ttyinfo ti; + gettyinfo(&ti); + saveti = ti; + resettty = 1; +#ifdef HAS_TIO + ti.tio.c_lflag &= ~ECHO; +#else + ti.sgttyb.sg_flags &= ~ECHO; +#endif + settyinfo(&ti); + } + + /* handle prompt */ + if (firstarg) { + for (readpmpt = firstarg; + *readpmpt && *readpmpt != '?'; readpmpt++); + if (*readpmpt++) { + if (keys || isatty(0)) { + zputs(readpmpt, (shout ? shout : stderr)); + fflush(shout ? shout : stderr); + } + readpmpt[-1] = '\0'; + } + } + + if (OPT_ISSET(ops,'d')) { + char *delimstr = OPT_ARG(ops,'d'); +#ifdef MULTIBYTE_SUPPORT + wint_t wi; + + if (isset(MULTIBYTE)) { + mb_charinit(); + (void)mb_metacharlenconv(delimstr, &wi); + } + else + wi = WEOF; + if (wi != WEOF) + delim = (wchar_t)wi; + else + delim = (wchar_t)((delimstr[0] == Meta) ? + delimstr[1] ^ 32 : delimstr[0]); +#else + delim = (delimstr[0] == Meta) ? delimstr[1] ^ 32 : delimstr[0]; +#endif + if (SHTTY != -1) { + struct ttyinfo ti; + gettyinfo(&ti); + if (! resettty) { + saveti = ti; + resettty = 1; + } +#ifdef HAS_TIO + ti.tio.c_lflag &= ~ICANON; + ti.tio.c_cc[VMIN] = 1; + ti.tio.c_cc[VTIME] = 0; +#else + ti.sgttyb.sg_flags |= CBREAK; +#endif + settyinfo(&ti); + } + } + if (OPT_ISSET(ops,'t')) { + zlong timeout = 0; + if (OPT_HASARG(ops,'t')) { + mnumber mn = zero_mnumber; + mn = matheval(OPT_ARG(ops,'t')); + if (errflag) + return 1; + if (mn.type == MN_FLOAT) { + mn.u.d *= 1e6; + timeout = (zlong)mn.u.d; + } else { + timeout = (zlong)mn.u.l * (zlong)1000000; + } + } + if (izle) { + /* + * Timeout is in 100ths of a second rather than us. + * See calc_timeout() in zle_main for format of this. + */ + timeout = -(timeout/(zlong)10000 + 1L); + izle_timeout = (long)timeout; +#ifdef LONG_MAX + /* saturate if range exceeded */ + if ((zlong)izle_timeout != timeout) + izle_timeout = LONG_MAX; +#endif + } else { + if (readfd == -1 || + !read_poll(readfd, &readchar, keys && !zleactive, + timeout)) { + if (keys && !zleactive && !isem) + settyinfo(&shttyinfo); + else if (resettty && SHTTY != -1) + settyinfo(&saveti); + if (haso) { + fclose(shout); + shout = oshout; + SHTTY = -1; + } + return OPT_ISSET(ops,'q') ? 2 : 1; + } + } + } + +#ifdef MULTIBYTE_SUPPORT + memset(&mbs, 0, sizeof(mbs)); +#endif + + /* + * option -k means read only a given number of characters (default 1) + * option -q means get one character, and interpret it as a Y or N + */ + if (OPT_ISSET(ops,'k') || OPT_ISSET(ops,'q')) { + int eof = 0; + /* allocate buffer space for result */ +#ifdef MULTIBYTE_SUPPORT + bptr = buf = (char *)zalloc(nchars*MB_CUR_MAX+1); +#else + bptr = buf = (char *)zalloc(nchars+1); +#endif + + do { + if (izle) { + zleentry(ZLE_CMD_GET_KEY, izle_timeout, NULL, &val); + if (val < 0) { + eof = 1; + break; + } + *bptr = (char) val; +#ifdef MULTIBYTE_SUPPORT + if (isset(MULTIBYTE)) { + ret = mbrlen(bptr++, 1, &mbs); + if (ret == MB_INVALID) + memset(&mbs, 0, sizeof(mbs)); + /* treat invalid as single character */ + if (ret != MB_INCOMPLETE) + nchars--; + continue; + } else { + bptr++; + nchars--; + } +#else + bptr++; + nchars--; +#endif + } else { + /* If read returns 0, is end of file */ + if (readchar >= 0) { + *bptr = readchar; + val = 1; + readchar = -1; + } else { + while ((val = read(readfd, bptr, nchars)) < 0) { + if (errno != EINTR || + errflag || retflag || breaks || contflag) + break; + } + if (val <= 0) { + eof = 1; + break; + } + } + +#ifdef MULTIBYTE_SUPPORT + if (isset(MULTIBYTE)) { + while (val > 0) { + ret = mbrlen(bptr, val, &mbs); + if (ret == MB_INCOMPLETE) { + bptr += val; + break; + } else { + if (ret == MB_INVALID) { + memset(&mbs, 0, sizeof(mbs)); + /* treat as single byte */ + ret = 1; + } + else if (ret == 0) /* handle null as normal char */ + ret = 1; + else if (ret > (size_t)val) { + /* Some mbrlen()s return the full char len */ + ret = val; + } + nchars--; + val -= ret; + bptr += ret; + } + } + continue; + } +#endif + /* decrement number of characters read from number required */ + nchars -= val; + + /* increment pointer past read characters */ + bptr += val; + } + } while (nchars > 0); + + if (!izle && !OPT_ISSET(ops,'u') && !OPT_ISSET(ops,'p')) { + /* dispose of result appropriately, etc. */ + if (isem) + while (val > 0 && read(SHTTY, &d, 1) == 1 && d != '\n'); + else { + settyinfo(&shttyinfo); + resettty = 0; + } + if (haso) { + fclose(shout); /* close(SHTTY) */ + shout = oshout; + SHTTY = -1; + } + } + + if (OPT_ISSET(ops,'q')) + { + /* + * Keep eof as status but status is now whether we read + * 'y' or 'Y'. If we timed out, status is 2. + */ + if (eof) + eof = 2; + else + eof = (bptr - buf != 1 || (buf[0] != 'y' && buf[0] != 'Y')); + buf[0] = eof ? 'n' : 'y'; + bptr = buf + 1; + } + if (OPT_ISSET(ops,'e') || OPT_ISSET(ops,'E')) + fwrite(buf, bptr - buf, 1, stdout); + if (!OPT_ISSET(ops,'e')) + setsparam(reply, metafy(buf, bptr - buf, META_REALLOC)); + else + zfree(buf, bptr - buf + 1); + if (resettty && SHTTY != -1) + settyinfo(&saveti); + return eof; + } + + /* All possible special types of input have been exhausted. Take one line, + and assign words to the parameters until they run out. Leftover words go + onto the last parameter. If an array is specified, all the words become + separate elements of the array. */ + + zbuforig = zbuf = (!OPT_ISSET(ops,'z')) ? NULL : + (nonempty(bufstack)) ? (char *) getlinknode(bufstack) : ztrdup(""); + first = 1; + bslash = 0; + while (*args || (OPT_ISSET(ops,'A') && !gotnl)) { + sigset_t s = child_unblock(); + buf = bptr = (char *)zalloc(bsiz = 64); +#ifdef MULTIBYTE_SUPPORT + laststart = buf; + ret = MB_INCOMPLETE; +#endif + /* get input, a character at a time */ + while (!gotnl) { + c = zread(izle, &readchar, izle_timeout); + /* \ at the end of a line indicates a continuation * + * line, except in raw mode (-r option) */ +#ifdef MULTIBYTE_SUPPORT + if (c == EOF) { + /* not waiting to be completed any more */ + ret = 0; + break; + } + *bptr = (char)c; + if (isset(MULTIBYTE)) { + ret = mbrtowc(&wc, bptr, 1, &mbs); + if (!ret) /* NULL */ + ret = 1; + } else { + ret = 1; + wc = (wchar_t)c; + } + if (ret != MB_INCOMPLETE) { + if (ret == MB_INVALID) { + memset(&mbs, 0, sizeof(mbs)); + /* Treat this as a single character */ + wc = (wchar_t)c; + laststart = bptr; + } + if (bslash && wc == delim) { + bslash = 0; + continue; + } + if (wc == delim) + break; + /* + * `first' is non-zero if any separator we encounter is a + * non-whitespace separator, which means that anything + * (even an empty string) between, before or after separators + * is significant. If it is zero, we have a whitespace + * separator, which shouldn't cause extra empty strings to + * be emitted. Hence the test for (*buf || first) when + * we assign the result of reading a word. + */ + if (!bslash && wcsitype(wc, ISEP)) { + if (bptr != buf || + (!(c < 128 && iwsep(c)) && first)) { + first |= !(c < 128 && iwsep(c)); + break; + } + first |= !(c < 128 && iwsep(c)); + continue; + } + bslash = (wc == L'\\' && !bslash && !OPT_ISSET(ops,'r')); + if (bslash) + continue; + first = 0; + } + if (imeta(STOUC(*bptr))) { + bptr[1] = bptr[0] ^ 32; + bptr[0] = Meta; + bptr += 2; + } + else + bptr++; + if (ret != MB_INCOMPLETE) + laststart = bptr; +#else + if (c == EOF) + break; + if (bslash && c == delim) { + bslash = 0; + continue; + } + if (c == delim) + break; + /* + * `first' is non-zero if any separator we encounter is a + * non-whitespace separator, which means that anything + * (even an empty string) between, before or after separators + * is significant. If it is zero, we have a whitespace + * separator, which shouldn't cause extra empty strings to + * be emitted. Hence the test for (*buf || first) when + * we assign the result of reading a word. + */ + if (!bslash && isep(c)) { + if (bptr != buf || (!iwsep(c) && first)) { + first |= !iwsep(c); + break; + } + first |= !iwsep(c); + continue; + } + bslash = c == '\\' && !bslash && !OPT_ISSET(ops,'r'); + if (bslash) + continue; + first = 0; + if (imeta(c)) { + *bptr++ = Meta; + *bptr++ = c ^ 32; + } else + *bptr++ = c; +#endif + /* increase the buffer size, if necessary */ + if (bptr >= buf + bsiz - 1) { + int blen = bptr - buf; +#ifdef MULTIBYTE_SUPPORT + int llen = laststart - buf; +#endif + + buf = realloc(buf, bsiz *= 2); + bptr = buf + blen; +#ifdef MULTIBYTE_SUPPORT + laststart = buf + llen; +#endif + } + } + signal_setmask(s); +#ifdef MULTIBYTE_SUPPORT + if (c == EOF) { + gotnl = 1; + *bptr = '\0'; /* see below */ + } else if (ret == MB_INCOMPLETE) { + /* + * We can only get here if there is an EOF in the + * middle of a character... safest to keep the debris, + * I suppose. + */ + *bptr = '\0'; + } else { + if (wc == delim) + gotnl = 1; + *laststart = '\0'; + } +#else + if (c == delim || c == EOF) + gotnl = 1; + *bptr = '\0'; +#endif + /* dispose of word appropriately */ + if (OPT_ISSET(ops,'e') || + /* + * When we're doing an array assignment, we'll + * handle echoing at that point. In all other + * cases (including -A with no assignment) + * we'll do it here. + */ + (OPT_ISSET(ops,'E') && !OPT_ISSET(ops,'A'))) { + zputs(buf, stdout); + putchar('\n'); + } + if (!OPT_ISSET(ops,'e') && (*buf || first || gotnl)) { + if (OPT_ISSET(ops,'A')) { + addlinknode(readll, buf); + al++; + } else + setsparam(reply, buf); + } else + free(buf); + if (!OPT_ISSET(ops,'A')) + reply = *args++; + } + /* handle EOF */ + if (c == EOF) { + if (readfd == coprocin) { + close(coprocin); + close(coprocout); + coprocin = coprocout = -1; + } + } + /* final assignment (and display) of array parameter */ + if (OPT_ISSET(ops,'A')) { + char **pp, **p = NULL; + LinkNode n; + + p = (OPT_ISSET(ops,'e') ? (char **)NULL + : (char **)zalloc((al + 1) * sizeof(char *))); + + for (pp = p, n = firstnode(readll); n; incnode(n)) { + if (OPT_ISSET(ops,'E')) { + zputs((char *) getdata(n), stdout); + putchar('\n'); + } + if (p) + *pp++ = (char *)getdata(n); + else + zsfree(getdata(n)); + } + if (p) { + *pp++ = NULL; + setaparam(reply, p); + } + if (resettty && SHTTY != -1) + settyinfo(&saveti); + return c == EOF; + } + buf = bptr = (char *)zalloc(bsiz = 64); +#ifdef MULTIBYTE_SUPPORT + laststart = buf; + ret = MB_INCOMPLETE; +#endif + /* any remaining part of the line goes into one parameter */ + bslash = 0; + if (!gotnl) { + sigset_t s = child_unblock(); + for (;;) { + c = zread(izle, &readchar, izle_timeout); +#ifdef MULTIBYTE_SUPPORT + if (c == EOF) { + /* not waiting to be completed any more */ + ret = 0; + break; + } + *bptr = (char)c; + if (isset(MULTIBYTE)) { + ret = mbrtowc(&wc, bptr, 1, &mbs); + if (!ret) /* NULL */ + ret = 1; + } else { + ret = 1; + wc = (wchar_t)c; + } + if (ret != MB_INCOMPLETE) { + if (ret == MB_INVALID) { + memset(&mbs, 0, sizeof(mbs)); + /* Treat this as a single character */ + wc = (wchar_t)c; + laststart = bptr; + } + /* + * \ at the end of a line introduces a continuation line, + * except in raw mode (-r option) + */ + if (bslash && wc == delim) { + bslash = 0; + continue; + } + if (wc == delim && !zbuf) + break; + if (!bslash && bptr == buf && wcsitype(wc, ISEP)) { + if (c < 128 && iwsep(c)) + continue; + else if (!first) { + first = 1; + continue; + } + } + bslash = (wc == L'\\' && !bslash && !OPT_ISSET(ops,'r')); + if (bslash) + continue; + } + if (imeta(STOUC(*bptr))) { + bptr[1] = bptr[0] ^ 32; + bptr[0] = Meta; + bptr += 2; + } + else + bptr++; + if (ret != MB_INCOMPLETE) + laststart = bptr; +#else + /* \ at the end of a line introduces a continuation line, except in + raw mode (-r option) */ + if (bslash && c == delim) { + bslash = 0; + continue; + } + if (c == EOF || (c == delim && !zbuf)) + break; + if (!bslash && isep(c) && bptr == buf) { + if (iwsep(c)) + continue; + else if (!first) { + first = 1; + continue; + } + } + bslash = c == '\\' && !bslash && !OPT_ISSET(ops,'r'); + if (bslash) + continue; + if (imeta(c)) { + *bptr++ = Meta; + *bptr++ = c ^ 32; + } else + *bptr++ = c; +#endif + /* increase the buffer size, if necessary */ + if (bptr >= buf + bsiz - 1) { + int blen = bptr - buf; +#ifdef MULTIBYTE_SUPPORT + int llen = laststart - buf; +#endif + + buf = realloc(buf, bsiz *= 2); + bptr = buf + blen; +#ifdef MULTIBYTE_SUPPORT + laststart = buf + llen; +#endif + } + } + signal_setmask(s); + } +#ifdef MULTIBYTE_SUPPORT + if (ret != MB_INCOMPLETE) + bptr = laststart; +#endif + /* + * Strip trailing IFS whitespace. + * iwsep can only be certain single-byte ASCII bytes, but we + * must check the byte isn't metafied. + */ + while (bptr > buf) { + if (bptr > buf + 1 && bptr[-2] == Meta) { + /* non-ASCII, can't be IWSEP */ + break; + } else if (iwsep(bptr[-1])) + bptr--; + else + break; + } + *bptr = '\0'; + if (resettty && SHTTY != -1) + settyinfo(&saveti); + /* final assignment of reply, etc. */ + if (OPT_ISSET(ops,'e') || OPT_ISSET(ops,'E')) { + zputs(buf, stdout); + putchar('\n'); + } + if (!OPT_ISSET(ops,'e')) + setsparam(reply, buf); + else + zsfree(buf); + if (zbuforig) { + char first = *zbuforig; + + zsfree(zbuforig); + if (!first) + return 1; + } else if (c == EOF) { + if (readfd == coprocin) { + close(coprocin); + close(coprocout); + coprocin = coprocout = -1; + } + return 1; + } + /* + * The following is to ensure a failure to set the parameter + * causes a non-zero status return. There are arguments for + * turning a non-zero status into errflag more widely. + */ + return errflag; +} + +/**/ +static int +zread(int izle, int *readchar, long izle_timeout) +{ + char cc, retry = 0; + int ret; + + if (izle) { + int c; + zleentry(ZLE_CMD_GET_KEY, izle_timeout, NULL, &c); + + return (c < 0 ? EOF : c); + } + /* use zbuf if possible */ + if (zbuf) { + /* If zbuf points to anything, it points to the next character in the + buffer. This may be a null byte to indicate EOF. If reading from the + buffer, move on the buffer pointer. */ + if (*zbuf == Meta) + return zbuf++, STOUC(*zbuf++ ^ 32); + else + return (*zbuf) ? STOUC(*zbuf++) : EOF; + } + if (*readchar >= 0) { + cc = *readchar; + *readchar = -1; + return STOUC(cc); + } + for (;;) { + /* read a character from readfd */ + ret = read(readfd, &cc, 1); + switch (ret) { + case 1: + /* return the character read */ + return STOUC(cc); + case -1: +#if defined(EAGAIN) || defined(EWOULDBLOCK) + if (!retry && readfd == 0 && ( +# ifdef EAGAIN + errno == EAGAIN +# ifdef EWOULDBLOCK + || +# endif /* EWOULDBLOCK */ +# endif /* EAGAIN */ +# ifdef EWOULDBLOCK + errno == EWOULDBLOCK +# endif /* EWOULDBLOCK */ + ) && setblock_stdin()) { + retry = 1; + continue; + } else +#endif /* EAGAIN || EWOULDBLOCK */ + if (errno == EINTR && !(errflag || retflag || breaks || contflag)) + continue; + break; + } + return EOF; + } +} + +/* holds arguments for testlex() */ +/**/ +char **testargs, **curtestarg; + +/* test, [: the old-style general purpose logical expression builtin */ + +/**/ +void +testlex(void) +{ + if (tok == LEXERR) + return; + + tokstr = *(curtestarg = testargs); + if (!*testargs) { + /* if tok is already zero, reading past the end: error */ + tok = tok ? NULLTOK : LEXERR; + return; + } else if (!strcmp(*testargs, "-o")) + tok = DBAR; + else if (!strcmp(*testargs, "-a")) + tok = DAMPER; + else if (!strcmp(*testargs, "!")) + tok = BANG; + else if (!strcmp(*testargs, "(")) + tok = INPAR; + else if (!strcmp(*testargs, ")")) + tok = OUTPAR; + else + tok = STRING; + testargs++; +} + +/**/ +int +bin_test(char *name, char **argv, UNUSED(Options ops), int func) +{ + char **s; + Eprog prog; + struct estate state; + int nargs, sense = 0, ret; + + /* if "test" was invoked as "[", it needs a matching "]" * + * which is subsequently ignored */ + if (func == BIN_BRACKET) { + for (s = argv; *s; s++); + if (s == argv || strcmp(s[-1], "]")) { + zwarnnam(name, "']' expected"); + return 2; + } + s[-1] = NULL; + } + /* an empty argument list evaluates to false (1) */ + if (!*argv) + return 1; + + /* + * Implement some XSI extensions to POSIX here. + * See + * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html + */ + nargs = arrlen(argv); + if (nargs == 3 || nargs == 4) + { + /* + * As parentheses are an extension, we need to be careful --- + * if this is a three-argument expression that could + * be a binary operator, prefer that. + */ + if (!strcmp(argv[0], "(") && !strcmp(argv[nargs-1],")") && + (nargs != 3 || !is_cond_binary_op(argv[1]))) { + argv[nargs-1] = NULL; + argv++; + } + if (nargs == 4 && !strcmp("!", argv[0])) { + sense = 1; + argv++; + } + } + + zcontext_save(); + testargs = argv; + tok = NULLTOK; + condlex = testlex; + testlex(); + prog = parse_cond(); + condlex = zshlex; + + if (errflag) { + errflag &= ~ERRFLAG_ERROR; + zcontext_restore(); + return 2; + } + + if (!prog || tok == LEXERR) { + zwarnnam(name, tokstr ? "parse error" : "argument expected"); + zcontext_restore(); + return 2; + } + zcontext_restore(); + + if (*curtestarg) { + zwarnnam(name, "too many arguments"); + return 2; + } + + /* syntax is OK, so evaluate */ + + state.prog = prog; + state.pc = prog->prog; + state.strs = prog->strs; + + ret = evalcond(&state, name); + if (ret < 2 && sense) + ret = ! ret; + + return ret; +} + +/* display a time, provided in units of 1/60s, as minutes and seconds */ +#define pttime(X) printf("%ldm%ld.%02lds",((long) (X))/(60 * clktck),\ + ((long) (X))/clktck%clktck,\ + ((long) (X))*100/clktck%100) + +/* times: display, in a two-line format, the times provided by times(3) */ + +/**/ +int +bin_times(UNUSED(char *name), UNUSED(char **argv), UNUSED(Options ops), UNUSED(int func)) +{ + struct tms buf; + long clktck = get_clktck(); + + /* get time accounting information */ + if (times(&buf) == -1) + return 1; + pttime(buf.tms_utime); /* user time */ + putchar(' '); + pttime(buf.tms_stime); /* system time */ + putchar('\n'); + pttime(buf.tms_cutime); /* user time, children */ + putchar(' '); + pttime(buf.tms_cstime); /* system time, children */ + putchar('\n'); + return 0; +} + +/* trap: set/unset signal traps */ + +/**/ +int +bin_trap(char *name, char **argv, UNUSED(Options ops), UNUSED(int func)) +{ + Eprog prog; + char *arg, *s; + int sig; + + if (*argv && !strcmp(*argv, "--")) + argv++; + + /* If given no arguments, list all currently-set traps */ + if (!*argv) { + queue_signals(); + for (sig = 0; sig < VSIGCOUNT; sig++) { + if (sigtrapped[sig] & ZSIG_FUNC) { + HashNode hn; + + if ((hn = gettrapnode(sig, 0))) + shfunctab->printnode(hn, 0); + DPUTS(!hn, "BUG: I did not find any trap functions!"); + } else if (sigtrapped[sig]) { + const char *name = getsigname(sig); + if (!siglists[sig]) + printf("trap -- '' %s\n", name); + else { + s = getpermtext(siglists[sig], NULL, 0); + printf("trap -- "); + quotedzputs(s, stdout); + printf(" %s\n", name); + zsfree(s); + } + } + } + unqueue_signals(); + return 0; + } + + /* If we have a signal number, unset the specified * + * signals. With only -, remove all traps. */ + if ((getsignum(*argv) != -1) || (!strcmp(*argv, "-") && argv++)) { + if (!*argv) { + for (sig = 0; sig < VSIGCOUNT; sig++) + unsettrap(sig); + } else { + for (; *argv; argv++) { + sig = getsignum(*argv); + if (sig == -1) { + zwarnnam(name, "undefined signal: %s", *argv); + break; + } + unsettrap(sig); + } + } + return *argv != NULL; + } + + /* Sort out the command to execute on trap */ + arg = *argv++; + if (!*arg) + prog = &dummy_eprog; + else if (!(prog = parse_string(arg, 1))) { + zwarnnam(name, "couldn't parse trap command"); + return 1; + } + + /* set traps */ + for (; *argv; argv++) { + Eprog t; + int flags; + + sig = getsignum(*argv); + if (sig == -1) { + zwarnnam(name, "undefined signal: %s", *argv); + break; + } + if (idigit(**argv) || + !strcmp(sigs[sig], *argv) || + (!strncmp("SIG", *argv, 3) && !strcmp(sigs[sig], *argv+3))) { + /* The signal was specified by number or by canonical name (with + * or without SIG prefix). + */ + flags = 0; + } + else { + /* + * Record that the signal is used under an assumed name. + * If we ever have more than one alias per signal this + * will need improving. + */ + flags = ZSIG_ALIAS; + } + t = dupeprog(prog, 0); + if (settrap(sig, t, flags)) + freeeprog(t); + } + return *argv != NULL; +} + +/**/ +int +bin_ttyctl(UNUSED(char *name), UNUSED(char **argv), Options ops, UNUSED(int func)) +{ + if (OPT_ISSET(ops,'f')) + ttyfrozen = 1; + else if (OPT_ISSET(ops,'u')) + ttyfrozen = 0; + else + printf("tty is %sfrozen\n", ttyfrozen ? "" : "not "); + return 0; +} + +/* let -- mathematical evaluation */ + +/**/ +int +bin_let(UNUSED(char *name), char **argv, UNUSED(Options ops), UNUSED(int func)) +{ + mnumber val = zero_mnumber; + + while (*argv) + val = matheval(*argv++); + /* Errors in math evaluation in let are non-fatal. */ + errflag &= ~ERRFLAG_ERROR; + /* should test for fabs(val.u.d) < epsilon? */ + return (val.type == MN_INTEGER) ? val.u.l == 0 : val.u.d == 0.0; +} + +/* umask command. umask may be specified as octal digits, or in the * + * symbolic form that chmod(1) uses. Well, a subset of it. Remember * + * that only the bottom nine bits of umask are used, so there's no * + * point allowing the set{u,g}id and sticky bits to be specified. */ + +/**/ +int +bin_umask(char *nam, char **args, Options ops, UNUSED(int func)) +{ + mode_t um; + char *s = *args; + + /* Get the current umask. */ + um = umask(0); + umask(um); + /* No arguments means to display the current setting. */ + if (!s) { + if (OPT_ISSET(ops,'S')) { + char *who = "ugo"; + + while (*who) { + char *what = "rwx"; + printf("%c=", *who++); + while (*what) { + if (!(um & 0400)) + putchar(*what); + um <<= 1; + what++; + } + putchar(*who ? ',' : '\n'); + } + } else { + if (um & 0700) + putchar('0'); + printf("%03o\n", (unsigned)um); + } + return 0; + } + + if (idigit(*s)) { + /* Simple digital umask. */ + um = zstrtol(s, &s, 8); + if (*s) { + zwarnnam(nam, "bad umask"); + return 1; + } + } else { + /* Symbolic notation -- slightly complicated. */ + int whomask, umaskop, mask; + + /* More than one symbolic argument may be used at once, each separated + by commas. */ + for (;;) { + /* First part of the argument -- who does this apply to? + u=owner, g=group, o=other. */ + whomask = 0; + while (*s == 'u' || *s == 'g' || *s == 'o' || *s == 'a') + if (*s == 'u') + s++, whomask |= 0700; + else if (*s == 'g') + s++, whomask |= 0070; + else if (*s == 'o') + s++, whomask |= 0007; + else if (*s == 'a') + s++, whomask |= 0777; + /* Default whomask is everyone. */ + if (!whomask) + whomask = 0777; + /* Operation may be +, - or =. */ + umaskop = (int)*s; + if (!(umaskop == '+' || umaskop == '-' || umaskop == '=')) { + if (umaskop) + zwarnnam(nam, "bad symbolic mode operator: %c", umaskop); + else + zwarnnam(nam, "bad umask"); + return 1; + } + /* Permissions mask -- r=read, w=write, x=execute. */ + mask = 0; + while (*++s && *s != ',') + if (*s == 'r') + mask |= 0444 & whomask; + else if (*s == 'w') + mask |= 0222 & whomask; + else if (*s == 'x') + mask |= 0111 & whomask; + else { + zwarnnam(nam, "bad symbolic mode permission: %c", *s); + return 1; + } + /* Apply parsed argument to um. */ + if (umaskop == '+') + um &= ~mask; + else if (umaskop == '-') + um |= mask; + else /* umaskop == '=' */ + um = (um | (whomask)) & ~mask; + if (*s == ',') + s++; + else + break; + } + if (*s) { + zwarnnam(nam, "bad character in symbolic mode: %c", *s); + return 1; + } + } + + /* Finally, set the new umask. */ + umask(um); + return 0; +} + +/* Generic builtin for facilities not available on this OS */ + +/**/ +mod_export int +bin_notavail(char *nam, UNUSED(char **argv), UNUSED(Options ops), UNUSED(int func)) +{ + zwarnnam(nam, "not available on this system"); + return 1; +} |
