diff options
Diffstat (limited to 'Src/builtin.c')
-rw-r--r-- | Src/builtin.c | 3599 |
1 files changed, 3599 insertions, 0 deletions
diff --git a/Src/builtin.c b/Src/builtin.c new file mode 100644 index 000000000..31f396d93 --- /dev/null +++ b/Src/builtin.c @@ -0,0 +1,3599 @@ +/* + * 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. + * + */ + +#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("[", 0, 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, bin_alias, 0, -1, 0, "Lgmr", NULL), + BUILTIN("autoload", BINF_TYPEOPTS, bin_functions, 0, -1, 0, "t", "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", 0, bin_cd, 0, 2, BIN_CD, NULL, NULL), + BUILTIN("chdir", 0, bin_cd, 0, 2, BIN_CD, NULL, NULL), + BUILTIN("continue", BINF_PSPECIAL, bin_break, 0, 1, BIN_CONTINUE, NULL, NULL), + BUILTIN("declare", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "LRUZfilrtux", NULL), + BUILTIN("dirs", 0, bin_dirs, 0, -1, 0, "v", NULL), + BUILTIN("disable", 0, bin_enable, 0, -1, BIN_DISABLE, "afmr", NULL), + BUILTIN("disown", 0, bin_fg, 0, -1, BIN_DISOWN, NULL, NULL), + BUILTIN("echo", BINF_PRINTOPTS | BINF_ECHOPTS, bin_print, 0, -1, BIN_ECHO, "neE", "-"), + BUILTIN("echotc", 0, bin_echotc, 1, -1, 0, NULL, NULL), + BUILTIN("emulate", 0, bin_emulate, 1, 1, 0, "R", NULL), + BUILTIN("enable", 0, bin_enable, 0, -1, BIN_ENABLE, "afmr", 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_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, BIN_EXPORT, "LRUZfilrtu", "x"), + BUILTIN("false", 0, bin_false, 0, -1, 0, NULL, NULL), + BUILTIN("fc", BINF_FCOPTS, bin_fc, 0, -1, BIN_FC, "nlreIRWAdDfEim", NULL), + BUILTIN("fg", 0, bin_fg, 0, -1, BIN_FG, NULL, NULL), + BUILTIN("functions", BINF_TYPEOPTS, bin_functions, 0, -1, 0, "mtu", 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, "dfmrv", 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, "nrdDfEim", "l"), + BUILTIN("integer", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "lrtux", "i"), + BUILTIN("jobs", 0, bin_fg, 0, -1, BIN_JOBS, "dlpZrs", NULL), + BUILTIN("kill", 0, bin_kill, 0, -1, 0, NULL, NULL), + BUILTIN("let", 0, bin_let, 1, -1, 0, NULL, NULL), + BUILTIN("local", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "LRUZilrtu", 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 + + BUILTIN("popd", 0, bin_cd, 0, 2, BIN_POPD, NULL, NULL), + BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "RDPnrslzNu0123456789pioOcm-", NULL), + BUILTIN("pushd", 0, bin_cd, 0, 2, BIN_PUSHD, NULL, NULL), + BUILTIN("pushln", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, NULL, "-nz"), + BUILTIN("pwd", 0, bin_pwd, 0, 0, 0, "rLP", NULL), + BUILTIN("r", BINF_R, bin_fc, 0, -1, BIN_FC, "nrl", NULL), + BUILTIN("read", 0, bin_read, 0, -1, 0, "rzu0123456789pkqecnAlE", NULL), + BUILTIN("readonly", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "LRUZfiltux", "r"), + BUILTIN("rehash", 0, bin_hash, 0, 0, 0, "dfv", "r"), + BUILTIN("return", BINF_PSPECIAL, bin_break, 0, 1, BIN_RETURN, NULL, NULL), + BUILTIN("set", BINF_PSPECIAL, 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, NULL, NULL), + BUILTIN("source", BINF_PSPECIAL, bin_dot, 1, -1, 0, NULL, NULL), + BUILTIN("suspend", 0, bin_suspend, 0, 0, 0, "f", NULL), + BUILTIN("test", 0, 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, 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, "ampfsw", "v"), + BUILTIN("typeset", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "LRUZfilrtuxm", NULL), + BUILTIN("umask", 0, bin_umask, 0, 1, 0, "S", NULL), + BUILTIN("unalias", 0, bin_unhash, 1, -1, 0, "m", "a"), + BUILTIN("unfunction", 0, bin_unhash, 1, -1, 0, "m", "f"), + BUILTIN("unhash", 0, bin_unhash, 1, -1, 0, "adfm", NULL), + BUILTIN("unset", BINF_PSPECIAL, bin_unset, 1, -1, 0, "fm", 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, "acmpvfsw", NULL), + BUILTIN("where", 0, bin_whence, 0, -1, 0, "pmsw", "ca"), + BUILTIN("which", 0, bin_whence, 0, -1, 0, "ampsw", "c"), + +#ifdef DYNAMIC + BUILTIN("zmodload", 0, bin_zmodload, 0, -1, 0, "Laudi", NULL), +#endif +}; + +/****************************************/ +/* Builtin Command Hash Table Functions */ +/****************************************/ + +/* hash table containing builtin commands */ + +/**/ +HashTable builtintab; + +/**/ +void +createbuiltintable(void) +{ + builtintab = newhashtable(85, "builtintab", NULL); + + builtintab->hash = hasher; + builtintab->emptytable = NULL; + builtintab->filltable = NULL; + builtintab->addnode = addhashnode; + builtintab->getnode = gethashnode; + builtintab->getnode2 = gethashnode2; + builtintab->removenode = removehashnode; + builtintab->disablenode = disablehashnode; + builtintab->enablenode = enablehashnode; + builtintab->freenode = freebuiltinnode; + builtintab->printnode = printbuiltinnode; + + 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->nam); + return; + } + + if (printflags & PRINT_WHENCE_CSH) { + printf("%s: shell built-in command\n", bn->nam); + return; + } + + if (printflags & PRINT_WHENCE_VERBOSE) { + printf("%s is a shell builtin\n", bn->nam); + return; + } + + /* default is name only */ + printf("%s\n", bn->nam); +} + +/**/ +static void +freebuiltinnode(HashNode hn) +{ + Builtin bn = (Builtin) hn; + + if(!(bn->flags & BINF_ADDED)) { + zsfree(bn->nam); + zsfree(bn->optstr); + zfree(bn, sizeof(struct builtin)); + } +} + +static char *auxdata; +static int auxlen; + +/* execute a builtin handler function after parsing the arguments */ + +#define MAX_OPS 128 + +/**/ +int +execbuiltin(LinkList args, Builtin bn) +{ + LinkNode n; + char ops[MAX_OPS], *arg, *pp, *name, **argv, **oargv, *optstr; + char *oxarg, *xarg = NULL; + int flags, sense, argc = 0, execop; + + /* initialise some static variables */ + auxdata = NULL; + auxlen = 0; + + /* initialize some local variables */ + memset(ops, 0, MAX_OPS); + name = (char *) ugetnode(args); + + arg = (char *) ugetnode(args); + +#ifdef DYNAMIC + if (!bn->handlerfunc) { + zwarnnam(name, "autoload failed", NULL, 0); + deletebuiltin(bn->nam); + return 1; + } +#endif + + /* get some information about the command */ + flags = bn->flags; + optstr = bn->optstr; + + /* Sort out the options. */ + if ((flags & BINF_ECHOPTS) && isset(BSDECHO)) + ops['E'] = 1; + if (optstr) + /* while arguments look like options ... */ + while (arg && + ((sense = (*arg == '-')) || + ((flags & BINF_PLUSOPTS) && *arg == '+')) && + ((flags & BINF_PLUSOPTS) || !atoi(arg))) { + /* unrecognised options to echo etc. are not really options */ + if (flags & BINF_ECHOPTS) { + char *p = arg; + while (*++p && strchr(optstr, (int) *p)); + if (*p) + break; + } + /* save the options in xarg, for execution tracing */ + if (xarg) { + oxarg = tricat(xarg, " ", arg); + zsfree(xarg); + xarg = oxarg; + } else + xarg = ztrdup(arg); + /* handle -- or - (ops['-']), and + (ops['-'] and ops['+']) */ + if (arg[1] == '-') + arg++; + if (!arg[1]) { + ops['-'] = 1; + if (!sense) + ops['+'] = 1; + } + /* save options in ops, as long as they are in bn->optstr */ + execop = -1; + while (*++arg) + if (strchr(optstr, execop = (int)*arg)) + ops[(int)*arg] = (sense) ? 1 : 2; + else + break; + /* "typeset" may take a numeric argument * + * at the tail of the options */ + if (idigit(*arg) && (flags & BINF_TYPEOPT) && + (arg[-1] == 'L' || arg[-1] == 'R' || + arg[-1] == 'Z' || arg[-1] == 'i')) + auxlen = (int)zstrtol(arg, &arg, 10); + /* 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; + zerr("bad option: -%c", NULL, *arg); + zsfree(xarg); + return 1; + } + arg = (char *) ugetnode(args); + /* for the "print" builtin, the options after -R are treated as + options to "echo" */ + if ((flags & BINF_PRINTOPTS) && ops['R']) { + optstr = "ne"; + flags |= BINF_ECHOPTS; + } + /* the option -- indicates the end of the options */ + if (ops['-']) + break; + /* for "fc", -e takes an extra argument */ + if ((flags & BINF_FCOPTS) && execop == 'e') { + auxdata = arg; + arg = (char *) ugetnode(args); + } + /* for "typeset", -L, -R, -Z and -i take a numeric extra argument */ + if ((flags & BINF_TYPEOPT) && (execop == 'L' || execop == 'R' || + execop == 'Z' || execop == 'i') && arg && idigit(*arg)) { + auxlen = atoi(arg); + arg = (char *) ugetnode(args); + } + } + if (flags & BINF_R) + auxdata = "-"; + /* handle built-in options, for overloaded handler functions */ + if ((pp = bn->defopts)) + while (*pp) + ops[(int)*pp++] = 1; + + /* Set up the argument list. */ + if (arg) { + /* count the arguments */ + argc = 1; + n = firstnode(args); + while (n) + argc++, incnode(n); + } + /* Get the actual arguments, into argv. Oargv saves the * + * beginning of the array for later reference. */ + oargv = argv = (char **)ncalloc(sizeof(char **) * (argc + 1)); + if ((*argv++ = arg)) + while ((*argv++ = (char *)ugetnode(args))); + argv = oargv; + if (errflag) { + zsfree(xarg); + errflag = 0; + 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", NULL, 0); + zsfree(xarg); + return 1; + } + + /* display execution trace information, if required */ + if (isset(XTRACE)) { + fprintf(stderr, "%s%s", (prompt4) ? prompt4 : "", name); + if (xarg) + fprintf(stderr, " %s", xarg); + while (*oargv) + fprintf(stderr, " %s", *oargv++); + fputc('\n', stderr); + fflush(stderr); + } + zsfree(xarg); + /* call the handler function, and return its return value */ + return (*(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, char *ops, int func) +{ + HashTable ht; + HashNode hn; + ScanFunc scanfunc; + Comp com; + int flags1 = 0, flags2 = 0; + int match = 0, returnval = 0; + + /* Find out which hash table we are working with. */ + if (ops['f']) + ht = shfunctab; + else if (ops['r']) + ht = reswdtab; + else if (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) { + scanhashtable(ht, 1, flags1, flags2, ht->printnode, 0); + return 0; + } + + /* With -m option, treat arguments as glob patterns. */ + if (ops['m']) { + for (; *argv; argv++) { + /* parse pattern */ + tokenize(*argv); + if ((com = parsereg(*argv))) + match += scanmatchtable(ht, com, 0, 0, scanfunc, 0); + else { + untokenize(*argv); + zwarnnam(name, "bad pattern : %s", *argv, 0); + returnval = 1; + } + } + /* If we didn't match anything, we return 1. */ + if (!match) + returnval = 1; + return returnval; + } + + /* Take arguments literally -- do not glob */ + for (; *argv; argv++) { + if ((hn = ht->getnode2(ht, *argv))) { + scanfunc(hn, 0); + } else { + zwarnnam(name, "no such hash table element: %s", *argv, 0); + returnval = 1; + } + } + 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, char *ops, int func) +{ + int action, optno, array = 0, hadopt = 0, + hadplus = 0, hadend = 0, sort = 0; + char **x; + + /* Obsolecent sh compatibility: set - is the same as set +xv * + * and set - args is the same as set +xv -- args */ + if (*args && **args == '-' && !args[0][1]) { + dosetopt(VERBOSE, 0, 0); + dosetopt(XTRACE, 0, 0); + 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) { + zwarnnam(nam, "string expected after -o", NULL, 0); + inittyptab(); + return 1; + } + if(!(optno = optlookup(*args))) + zwarnnam(nam, "no such option: %s", *args, 0); + else if(dosetopt(optno, action, 0)) + zwarnnam(nam, "can't change option: %s", *args, 0); + break; + } else if(**args == 'A') { + if(!*++*args) + args++; + array = action ? 1 : -1; + goto doneoptions; + } else if (**args == 's') + sort = action ? 1 : -1; + else { + if (!(optno = optlookupc(**args))) + zwarnnam(nam, "bad option: -%c", NULL, **args); + else if(dosetopt(optno, action, 0)) + zwarnnam(nam, "can't change option: -%c", NULL, **args); + } + } + args++; + } + doneoptions: + inittyptab(); + + /* Show the parameters, possibly with values */ + if (!hadopt && !*args) + scanhashtable(paramtab, 1, 0, 0, paramtab->printnode, + hadplus ? PRINT_NAMEONLY : 0); + + if (array && !*args) { + /* display arrays */ + scanhashtable(paramtab, 1, PM_ARRAY, 0, paramtab->printnode, + hadplus ? PRINT_NAMEONLY : 0); + } + if (!*args && !hadend) + return 0; + if (array) + args++; + if (sort) + qsort(args, arrlen(args), sizeof(char *), + sort > 0 ? strpcmp : invstrpcmp); + if (array) { + /* create an array with the specified elements */ + char **a = NULL, **y, *name = args[-1]; + int len = arrlen(args); + + if (array < 0 && (a = getaparam(name))) { + int al = arrlen(a); + + if (al > len) + len = al; + } + for (x = y = zalloc((len + 1) * sizeof(char *)); len--; a++) { + if (!*args) + args = a; + *y++ = ztrdup(*args++); + } + *y++ = NULL; + setaparam(name, x); + } else { + /* set shell arguments */ + freearray(pparams); + PERMALLOC { + pparams = arrdup(args); + } LASTALLOC; + } + 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(char *name, char **argv, char *ops, int func) +{ + if (ops['r'] || ops['P'] || (isset(CHASELINKS) && !ops['L'])) + printf("%s\n", zgetcwd()); + else { + zputs(pwd, stdout); + putchar('\n'); + } + return 0; +} + +/* the directory stack */ + +/**/ +LinkList dirstack; + +/* dirs: list the directory stack, or replace it with a provided list */ + +/**/ +int +bin_dirs(char *name, char **argv, char *ops, int func) +{ + LinkList l; + + /* with the -v option, provide a numbered list of directories, starting at + zero */ + if (ops['v']) { + LinkNode node; + int pos = 1; + + printf("0\t"); + fprintdir(pwd, stdout); + for (node = firstnode(dirstack); node; incnode(node)) { + printf("\n%d\t", pos++); + fprintdir(getdata(node), stdout); + } + putchar('\n'); + return 0; + } + /* given no arguments, list the stack normally */ + if (!*argv) { + printdirstack(); + return 0; + } + /* replace the stack with the specified directories */ + PERMALLOC { + l = newlinklist(); + if (*argv) { + while (*argv) + addlinknode(l, ztrdup(*argv++)); + freelinklist(dirstack, freestr); + dirstack = l; + } + } LASTALLOC; + return 0; +} + +/* cd, chdir, pushd, popd */ + +/**/ +void +set_pwd_env(void) +{ + Param pm; + + pm = (Param) paramtab->getnode(paramtab, "PWD"); + if (pm && PM_TYPE(pm->flags) != PM_SCALAR) { + pm->flags &= ~PM_READONLY; + unsetparam_pm(pm, 0, 1); + } + + pm = (Param) paramtab->getnode(paramtab, "OLDPWD"); + if (pm && PM_TYPE(pm->flags) != PM_SCALAR) { + pm->flags &= ~PM_READONLY; + unsetparam_pm(pm, 0, 1); + } + + setsparam("PWD", ztrdup(pwd)); + setsparam("OLDPWD", ztrdup(oldpwd)); + + pm = (Param) paramtab->getnode(paramtab, "PWD"); + if (!(pm->flags & PM_EXPORTED)) { + pm->flags |= PM_EXPORTED; + pm->env = addenv("PWD", pwd); + } + pm = (Param) paramtab->getnode(paramtab, "OLDPWD"); + if (!(pm->flags & PM_EXPORTED)) { + pm->flags |= PM_EXPORTED; + pm->env = addenv("PWD", pwd); + } +} + +/* The main pwd changing function. The real work is done by other * + * functions. cd_get_dest() does the initial argument processing; * + * cd_do_chdir() actually changes directory, if possible; cd_new_pwd() * + * does the ancilliary processing associated with actually changing * + * directory. */ + +/**/ +int +bin_cd(char *nam, char **argv, char *ops, int func) +{ + LinkNode dir; + struct stat st1, st2; + int chaselinks; + + if (isset(RESTRICTED)) { + zwarnnam(nam, "restricted", NULL, 0); + return 1; + } + doprintdir = (doprintdir == -1); + + for (; *argv && **argv == '-'; argv++) { + char *s = *argv + 1; + + do { + switch (*s) { + case 's': + case 'P': + case 'L': + break; + default: + goto brk; + } + } while (*++s); + for (s = *argv; *++s; ops[*s] = 1); + } + brk: + chaselinks = ops['P'] || (isset(CHASELINKS) && !ops['L']); + PERMALLOC { + pushnode(dirstack, ztrdup(pwd)); + if (!(dir = cd_get_dest(nam, argv, ops, func))) { + zsfree(getlinknode(dirstack)); + LASTALLOC_RETURN 1; + } + } LASTALLOC; + cd_new_pwd(func, dir, chaselinks); + + if (stat(unmeta(pwd), &st1) < 0) { + zsfree(pwd); + pwd = metafy(zgetcwd(), -1, META_DUP); + } else if (stat(".", &st2) < 0) + chdir(unmeta(pwd)); + else if (st1.st_ino != st2.st_ino || st1.st_dev != st2.st_dev) { + if (chaselinks) { + zsfree(pwd); + pwd = metafy(zgetcwd(), -1, META_DUP); + } else { + chdir(unmeta(pwd)); + } + } + set_pwd_env(); + return 0; +} + +/* Get directory to chdir to */ + +/**/ +static LinkNode +cd_get_dest(char *nam, char **argv, char *ops, int func) +{ + LinkNode dir = NULL; + LinkNode target; + char *dest; + + if (!argv[0]) { + if (func == BIN_POPD && !nextnode(firstnode(dirstack))) { + zwarnnam(nam, "directory stack empty", NULL, 0); + return NULL; + } + if (func == BIN_PUSHD && unset(PUSHDTOHOME)) + dir = nextnode(firstnode(dirstack)); + if (dir) + insertlinknode(dirstack, dir, getlinknode(dirstack)); + else if (func != BIN_POPD) + pushnode(dirstack, ztrdup(home)); + } else if (!argv[1]) { + int dd; + char *end; + + doprintdir++; + if (argv[0][1] && (argv[0][0] == '+' || argv[0][0] == '-')) { + 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", NULL, 0); + return NULL; + } + } + } + if (!dir) + pushnode(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], 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); + pushnode(dirstack, d); + doprintdir++; + } + + target = dir; + if (func == BIN_POPD) { + if (!dir) { + target = dir = firstnode(dirstack); + } else if (dir != firstnode(dirstack)) { + return dir; + } + dir = nextnode(dir); + } + if (!dir) { + dir = firstnode(dirstack); + } + if (!(dest = cd_do_chdir(nam, getdata(dir), ops['s']))) { + if (!target) + zsfree(getlinknode(dirstack)); + if (func == BIN_POPD) + zsfree(remnode(dirstack, dir)); + return NULL; + } + if (dest != 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 = 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", dest, errno); + return NULL; + } + + /* if cdpath is being used, check it for . */ + if (!nocdpath) + for (pp = cdpath; *pp; pp++) + if (!(*pp)[0] || ((*pp)[0] == '.' && (*pp)[1] == '\0')) + hasdot = 1; + /* if there is no . in cdpath (or it is not being used), try the directory + as-is (i.e. from .) */ + if (!hasdot) { + if ((ret = cd_try_chdir(NULL, dest, 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 (strcmp(*pp, ".")) { + doprintdir++; + } + return ret; + } + if (errno != ENOENT) + eno = errno; + } + + /* handle the CDABLEVARS option */ + if ((ret = cd_able_vars(dest))) { + if ((ret = cd_try_chdir(NULL, ret,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", dest, eno); + return NULL; +} + +/* If the CDABLEVARS option is set, return the new * + * interpretation of the given path. */ + +/**/ +char * +cd_able_vars(char *s) +{ + char *rest, 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; + + /* handle directory prefix */ + if (pfix && *pfix) { + if (*pfix == '/') + buf = tricat(pfix, "/", dest); + else { + int pwl = strlen(pwd); + int pfl = strlen(pfix); + + buf = zalloc(pwl + pfl + strlen(dest) + 3); + strcpy(buf, pwd); + buf[pwl] = '/'; + strcpy(buf + pwl + 1, pfix); + buf[pwl + 1 + pfl] = '/'; + strcpy(buf + pwl + pfl + 2, dest); + } + } else if (*dest == '/') + buf = ztrdup(dest); + else { + int pwl = strlen(pwd); + + buf = zalloc(pwl + strlen(dest) + 2); + strcpy(buf, pwd); + buf[pwl] = '/'; + strcpy(buf + pwl + 1, dest); + } + + /* Normalise path. See the definition of fixdir() for what this means. */ + fixdir(buf); + + if (lchdir(buf, NULL, hard)) { + zsfree(buf); + return NULL; + } + return metafy(buf, -1, META_NOALLOC); +} + +/* do the extra processing associated with changing directory */ + +/**/ +static void +cd_new_pwd(int func, LinkNode dir, int chaselinks) +{ + Param pm; + List l; + 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 (chaselinks) { + s = new_pwd; + new_pwd = findpwd(s); + zsfree(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; + pwd = new_pwd; + /* update the PWD and OLDPWD shell parameters */ + if ((pm = (Param) paramtab->getnode(paramtab, "PWD")) && + (pm->flags & PM_EXPORTED) && pm->env) + pm->env = replenv(pm->env, pwd); + if ((pm = (Param) paramtab->getnode(paramtab, "OLDPWD")) && + (pm->flags & PM_EXPORTED) && pm->env) + pm->env = replenv(pm->env, oldpwd); + if (unset(PUSHDSILENT) && func != BIN_CD && isset(INTERACTIVE)) + printdirstack(); + else if (doprintdir) { + fprintdir(pwd, stdout); + putchar('\n'); + } + + /* execute the chpwd function */ + if ((l = getshfunc("chpwd")) != &dummy_list) { + fflush(stdout); + fflush(stderr); + doshfunc(l, NULL, 0, 1); + } + + 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. */ + +/**/ +static void +fixdir(char *src) +{ + char *dest = src; + char *d0 = dest; + +/*** if have RFS superroot directory ***/ +#ifdef HAVE_SUPERROOT + /* allow /.. segments to remain */ + while (*src == '/' && src[1] == '.' && src[2] == '.' && + (!src[3] || src[3] == '/')) { + *dest++ = '/'; + *dest++ = '.'; + *dest++ = '.'; + src += 3; + } +#endif + + for (;;) { + /* compress multiple /es into single */ + if (*src == '/') { + *dest++ = *src++; + while (*src == '/') + src++; + } + /* if we are at the end of the input path, remove a trailing / (if it + exists), and return ct */ + if (!*src) { + while (dest > d0 + 1 && dest[-1] == '/') + dest--; + *dest = '\0'; + return; + } + if (dest > d0 + 1 && src[0] == '.' && src[1] == '.' && + (src[2] == '\0' || src[2] == '/')) { + /* remove a foo/.. combination */ + for (dest--; dest > d0 + 1 && dest[-1] != '/'; dest--); + if (dest[-1] != '/') + dest--; + src++; + while (*++src == '/'); + } else if (src[0] == '.' && (src[1] == '/' || src[1] == '\0')) { + /* skip a . section */ + while (*++src == '/'); + } else { + /* copy a normal segment into the output */ + while (*src != '/' && *src != '\0') + if ((*dest++ = *src++) == Meta) + dest[-1] = *src++ ^ 32; + } + } +} + +/**/ +void +printqt(char *str) +{ + /* Print str, but turn any single quote into '\'' or ''. */ + for (; *str; str++) + if (*str == '\'') + printf(isset(RCQUOTES) ? "''" : "'\\''"); + else + putchar(*str); +} + +/**/ +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, char *ops, int func) +{ + int first = -1, last = -1, retval, minflag = 0; + char *s; + struct asgment *asgf = NULL, *asgl = NULL; + Comp com = NULL; + + /* fc is only permitted in interactive shells */ + if (!interact) { + zwarnnam(nam, "not interactive shell", NULL, 0); + return 1; + } + /* with the -m option, the first argument is taken * + * as a pattern that history lines have to match */ + if (*argv && ops['m']) { + tokenize(*argv); + if (!(com = parsereg(*argv++))) { + zwarnnam(nam, "invalid match pattern", NULL, 0); + return 1; + } + } + if (ops['R']) { + /* read history from a file */ + readhistfile(*argv ? *argv : getsparam("HISTFILE"), 1); + return 0; + } + if (ops['W']) { + /* write history to a file */ + savehistfile(*argv ? *argv : getsparam("HISTFILE"), 1, + (ops['I'] ? 2 : 0)); + return 0; + } + if (ops['A']) { + /* append history to a file */ + savehistfile(*argv ? *argv : getsparam("HISTFILE"), 1, + (ops['I'] ? 3 : 1)); + return 0; + } + if (!(ops['l'] && unset(HISTNOSTORE))) + remhist(); + /* put foo=bar type arguments into the substitution list */ + while (*argv && equalsplit(*argv, &s)) { + Asgment a = (Asgment) alloc(sizeof *a); + + if (!asgf) + asgf = asgl = a; + else { + asgl->next = a; + asgl = a; + } + a->name = *argv; + a->value = s; + argv++; + } + /* interpret and check first history line specifier */ + if (*argv) { + minflag = **argv == '-'; + first = fcgetcomm(*argv); + if (first == -1) + return 1; + argv++; + } + /* interpret and check second history line specifier */ + if (*argv) { + last = fcgetcomm(*argv); + if (last == -1) + return 1; + argv++; + } + /* There is a maximum of two history specifiers. At least, there * + * will be as long as the history list is one-dimensional. */ + if (*argv) { + zwarnnam("fc", "too many arguments", NULL, 0); + return 1; + } + /* default values of first and last, and range checking */ + if (first == -1) + first = (ops['l']) ? curhist - 16 : curhist - 1; + if (last == -1) + last = (ops['l']) ? curhist - 1 : first; + if (first < firsthist()) + first = firsthist(); + if (last == -1) + last = (minflag) ? curhist : first; + else if (last < first) + last = first; + if (ops['l']) + /* list the required part of the history */ + retval = fclist(stdout, !ops['n'], ops['r'], ops['D'], + ops['d'] + ops['f'] * 2 + ops['E'] * 4 + ops['i'] * 8, + first, last, asgf, com); + else { + /* edit history file, and (if successful) use the result as a new command */ + int tempfd; + FILE *out; + char *fil; + + retval = 1; + fil = gettempname(); + if (((tempfd = open(fil, O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0600)) + == -1) || + ((out = fdopen(tempfd, "w")) == NULL)) { + zwarnnam("fc", "can't open temp file: %e", NULL, errno); + } else { + if (!fclist(out, 0, ops['r'], 0, 0, first, last, asgf, com)) { + char *editor; + + editor = auxdata ? auxdata : getsparam("FCEDIT"); + if (!editor) + editor = DEFAULT_FCEDIT; + + if (fcedit(editor, fil)) + if (stuff(fil)) + zwarnnam("fc", "%e: %s", s, errno); + else { + loop(0,1); + retval = lastval; + } + } + } + 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 int +fcgetcomm(char *s) +{ + int cmd; + + /* First try to match a history number. Negative * + * numbers indicate reversed numbering. */ + if ((cmd = atoi(s))) { + if (cmd < 0) + cmd = curhist + cmd; + if (cmd >= curhist) { + zwarnnam("fc", "bad history number: %d", 0, cmd); + return -1; + } + return cmd; + } + /* not a number, so search by string */ + cmd = hcomsearch(s); + if (cmd == -1) + zwarnnam("fc", "event not found: %s", s, 0); + return cmd; +} + +/* Perform old=new substituions. Uses the asgment structure from zsh.h, * + * which is essentially a linked list of string,replacement pairs. */ + +/**/ +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; + sub = sub->next; + oldpos = s; + /* loop over occurences of oldstr in s, replacing them with newstr */ + while ((newpos = (char *)strstr(oldpos, oldstr))) { + newmem = (char *) alloc(1 + (newpos - s) + + strlen(newstr) + strlen(newpos + strlen(oldstr))); + ztrncpy(newmem, s, newpos - s); + strcat(newmem, newstr); + oldpos = newmem + strlen(newmem); + strcat(newmem, newpos + strlen(oldstr)); + s = newmem; + subbed = 1; + } + } + *sp = s; + return subbed; +} + +/* Print a series of history events to a file. The file pointer is * + * given by f, and the required range of events by first and last. * + * subs is an optional list of foo=bar substitutions to perform on the * + * history lines before output. com is an optional comp structure * + * that the history lines are required to match. n, r, D and d are * + * options: n indicates that each line should be numbered. r indicates * + * that the lines should be output in reverse order (newest first). * + * D indicates that the real time taken by each command should be * + * output. d indicates that the time of execution of each command * + * should be output; d>1 means that the date should be output too; d>3 * + * means that mm/dd/yyyy form should be used for the dates, as opposed * + * to dd.mm.yyyy form; d>7 means that yyyy-mm-dd form should be used. */ + +/**/ +static int +fclist(FILE *f, int n, int r, int D, int d, int first, int last, struct asgment *subs, Comp com) +{ + int fclistdone = 0; + char *s, *hs; + Histent ent; + + /* reverse range if required */ + if (r) { + r = last; + last = first; + first = r; + } + /* suppress "no substitution" warning if no substitution is requested */ + if (!subs) + fclistdone = 1; + + for (;;) { + hs = quietgetevent(first); + if (!hs) { + zwarnnam("fc", "no such event: %d", NULL, first); + return 1; + } + s = dupstring(hs); + /* this if does the pattern matching, if required */ + if (!com || domatch(s, com, 0)) { + /* perform substitution */ + fclistdone |= fcsubs(&s, subs); + + /* do numbering */ + if (n) + fprintf(f, "%5d ", first); + ent = NULL; + /* output actual time (and possibly date) of execution of the + command, if required */ + if (d) { + struct tm *ltm; + if (!ent) + ent = gethistent(first); + ltm = localtime(&ent->stim); + if (d >= 2) { + if (d >= 8) { + fprintf(f, "%d-%02d-%02d ", + ltm->tm_year + 1900, + ltm->tm_mon + 1, ltm->tm_mday); + } else if (d >= 4) { + fprintf(f, "%d.%d.%d ", + ltm->tm_mday, ltm->tm_mon + 1, + ltm->tm_year + 1900); + } else { + fprintf(f, "%d/%d/%d ", + ltm->tm_mon + 1, ltm->tm_mday, + ltm->tm_year + 1900); + } + } + fprintf(f, "%02d:%02d ", ltm->tm_hour, ltm->tm_min); + } + /* display the time taken by the command, if required */ + if (D) { + long diff; + if (!ent) + ent = gethistent(first); + diff = (ent->ftim) ? ent->ftim - ent->stim : 0; + fprintf(f, "%ld:%02ld ", diff / 60, diff % 60); + } + + /* output the command */ + if (f == stdout) { + nicezputs(s, f); + putc('\n', f); + } else + fprintf(f, "%s\n", s); + } + /* move on to the next history line, or quit the loop */ + if (first == last) + break; + else if (first > last) + first--; + else + first++; + } + + /* final processing */ + if (f != stdout) + fclose(f); + if (!fclistdone) { + zwarnnam("fc", "no substitutions performed", NULL, 0); + return 1; + } + return 0; +} + +/* edit a history file */ + +/**/ +static int +fcedit(char *ename, char *fn) +{ + char *s; + + if (!strcmp(ename, "-")) + return 1; + + s = tricat(ename, " ", fn); + execstring(s, 1, 0); + 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 *s) +{ + static struct asgment asg; + + /* sanity check for valid argument */ + if (!s) + return NULL; + + /* check if name is empty */ + if (*s == '=') { + zerr("bad assignment", NULL, 0); + return NULL; + } + asg.name = s; + + /* search for `=' */ + for (; *s && *s != '='; s++); + + /* found `=', so return with a value */ + if (*s) { + *s = '\0'; + asg.value = s + 1; + } else { + /* didn't find `=', so we only have a name */ + asg.value = NULL; + } + return &asg; +} + +/* declare, export, integer, local, readonly, typeset */ + +/**/ +int +bin_typeset(char *name, char **argv, char *ops, int func) +{ + Param pm; + Asgment asg; + Comp com; + char *optstr = "iLRZlurtxU"; + int on = 0, off = 0, roff, bit = PM_INTEGER; + int initon, initoff, of, i; + int returnval = 0, printflags = 0; + + /* hash -f is really the builtin `functions' */ + if (ops['f']) + return bin_functions(name, argv, ops, func); + + /* Translate the options into PM_* flags. * + * Unfortunately, this depends on the order * + * these flags are defined in zsh.h */ + for (; *optstr; optstr++, bit <<= 1) + if (ops[*optstr] == 1) + on |= bit; + else if (ops[*optstr] == 2) + off |= bit; + roff = off; + + /* Sanity checks on the options. Remove conficting options. */ + if (on & PM_INTEGER) + off |= PM_RIGHT_B | PM_LEFT | PM_RIGHT_Z | PM_UPPER | PM_ARRAY; + if (on & PM_LEFT) + off |= PM_RIGHT_B | PM_INTEGER; + if (on & PM_RIGHT_B) + off |= PM_LEFT | PM_INTEGER; + if (on & PM_RIGHT_Z) + off |= PM_INTEGER; + if (on & PM_UPPER) + off |= PM_LOWER; + if (on & PM_LOWER) + off |= PM_UPPER; + on &= ~off; + + /* Given no arguments, list whatever the options specify. */ + if (!*argv) { + if (!(on|roff)) + printflags |= PRINT_TYPE; + if (roff || ops['+']) + printflags |= PRINT_NAMEONLY; + scanhashtable(paramtab, 1, on|roff, 0, paramtab->printnode, printflags); + return 0; + } + + /* With the -m option, treat arguments as glob patterns */ + if (ops['m']) { + while ((asg = getasg(*argv++))) { + tokenize(asg->name); /* expand argument */ + if (!(com = parsereg(asg->name))) { + untokenize(asg->name); + zwarnnam(name, "bad pattern : %s", argv[-1], 0); + returnval = 1; + continue; + } + /* If no options or values are given, display all * + * parameters matching the glob pattern. */ + if (!(on || roff || asg->value)) { + scanmatchtable(paramtab, com, 0, 0, paramtab->printnode, 0); + continue; + } + /* Since either options or values are given, we search * + * through the parameter table and change all parameters * + * matching the glob pattern to have these flags and/or * + * value. */ + for (i = 0; i < paramtab->hsize; i++) { + for (pm = (Param) paramtab->nodes[i]; pm; pm = (Param) pm->next) { + if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) + continue; + if (domatch(pm->nam, com, 0)) { + /* set up flags if we have any */ + if (on || roff) { + if (PM_TYPE(pm->flags) == PM_ARRAY && (on & PM_UNIQUE) && + !(pm->flags & PM_READONLY & ~off)) + uniqarray((*pm->gets.afn) (pm)); + pm->flags = (pm->flags | on) & ~off; + if (PM_TYPE(pm->flags) != PM_ARRAY) { + if ((on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z | PM_INTEGER)) && auxlen) + pm->ct = auxlen; + /* did we just export this? */ + if ((pm->flags & PM_EXPORTED) && !pm->env) { + pm->env = addenv(pm->nam, (asg->value) ? asg->value : getsparam(pm->nam)); + } else if (!(pm->flags & PM_EXPORTED) && pm->env) { + /* did we just unexport this? */ + delenv(pm->env); + zsfree(pm->env); + pm->env = NULL; + } + } + } + /* set up a new value if given */ + if (asg->value) { + setsparam(pm->nam, ztrdup(asg->value)); + } + } + } + } + } + return returnval; + } + + /* Save the values of on, off, and func */ + initon = on; + initoff = off; + of = func; + + /* Take arguments literally. Don't glob */ + while ((asg = getasg(*argv++))) { + /* restore the original values of on, off, and func */ + on = initon; + off = initoff; + func = of; + on &= ~PM_ARRAY; + + /* check if argument is a valid identifier */ + if (!isident(asg->name)) { + zerr("not an identifier: %s", asg->name, 0); + returnval = 1; + continue; + } + bit = 0; /* flag for switching int<->not-int */ + if ((pm = (Param)paramtab->getnode(paramtab, asg->name)) && + (((pm->flags & PM_SPECIAL) && pm->level == locallevel) || + (!(pm->flags & PM_UNSET) && + ((locallevel == pm->level) || func == BIN_EXPORT) && + !(bit = ((off & pm->flags) | (on & ~pm->flags)) & PM_INTEGER)))) { + /* if no flags or values are given, just print this parameter */ + if (!on && !roff && !asg->value) { + paramtab->printnode((HashNode) pm, 0); + continue; + } + if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) { + zerrnam(name, "%s: restricted", pm->nam, 0); + returnval = 1; + continue; + } + if((pm->flags & PM_SPECIAL) && + PM_TYPE((pm->flags | on) & ~off) != PM_TYPE(pm->flags)) { + zerrnam(name, "%s: cannot change type of a special parameter", + pm->nam, 0); + returnval = 1; + continue; + } + if (PM_TYPE(pm->flags) == PM_ARRAY && (on & PM_UNIQUE) && + !(pm->flags & PM_READONLY & ~off)) + uniqarray((*pm->gets.afn) (pm)); + pm->flags = (pm->flags | on) & ~off; + if ((on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z | PM_INTEGER)) && + auxlen) + pm->ct = auxlen; + if (PM_TYPE(pm->flags) != PM_ARRAY) { + if (pm->flags & PM_EXPORTED) { + if (!(pm->flags & PM_UNSET) && !pm->env && !asg->value) + pm->env = addenv(asg->name, getsparam(asg->name)); + } else if (pm->env) { + delenv(pm->env); + zsfree(pm->env); + pm->env = NULL; + } + if (asg->value) + setsparam(asg->name, ztrdup(asg->value)); + } + } else { + if (bit) { + if (pm->flags & PM_READONLY) { + on |= ~off & PM_READONLY; + pm->flags &= ~PM_READONLY; + } + if (!asg->value) + asg->value = dupstring(getsparam(asg->name)); + unsetparam(asg->name); + } + /* create a new node for a parameter with the * + * flags in `on' minus the readonly flag */ + pm = createparam(ztrdup(asg->name), on & ~PM_READONLY); + DPUTS(!pm, "BUG: parameter not created"); + pm->ct = auxlen; + if (func != BIN_EXPORT) + pm->level = locallevel; + if (asg->value) + setsparam(asg->name, ztrdup(asg->value)); + pm->flags |= (on & PM_READONLY); + } + } + return returnval; +} + +/* 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, char *ops, int func) +{ + Comp com; + Shfunc shf; + int i, returnval = 0; + int on = 0, off = 0; + + /* Do we have any flags defined? */ + if (ops['u'] || ops['t']) { + if (ops['u'] == 1) + on |= PM_UNDEFINED; + else if (ops['u'] == 2) + off |= PM_UNDEFINED; + + if (ops['t'] == 1) + on |= PM_TAGGED; + else if (ops['t'] == 2) + off |= PM_TAGGED; + } + + if (off & PM_UNDEFINED) { + zwarnnam(name, "invalid option(s)", NULL, 0); + return 1; + } + + /* If no arguments given, we will print functions. If flags * + * are given, we will print only functions containing these * + * flags, else we'll print them all. */ + if (!*argv) { + scanhashtable(shfunctab, 1, on|off, DISABLED, shfunctab->printnode, 0); + return 0; + } + + /* With the -m option, treat arguments as glob patterns */ + if (ops['m']) { + on &= ~PM_UNDEFINED; + for (; *argv; argv++) { + /* expand argument */ + tokenize(*argv); + if ((com = parsereg(*argv))) { + /* with no options, just print all functions matching the glob pattern */ + if (!(on|off)) { + scanmatchtable(shfunctab, com, 0, DISABLED, shfunctab->printnode, 0); + } 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->next) + if (domatch(shf->nam, com, 0) && !(shf->flags & DISABLED)) + shf->flags = (shf->flags | on) & (~off); + } + } + } else { + untokenize(*argv); + zwarnnam(name, "bad pattern : %s", *argv, 0); + returnval = 1; + } + } + return returnval; + } + + /* Take the arguments literally -- do not glob */ + for (; *argv; argv++) { + if ((shf = (Shfunc) shfunctab->getnode(shfunctab, *argv))) { + /* if any flag was given */ + if (on|off) + /* turn on/off the given flags */ + shf->flags = (shf->flags | (on & ~PM_UNDEFINED)) & ~off; + else + /* no flags, so just print */ + shfunctab->printnode((HashNode) shf, 0); + } else if (on & PM_UNDEFINED) { + /* Add a new undefined (autoloaded) function to the * + * hash table with the corresponding flags set. */ + shf = (Shfunc) zcalloc(sizeof *shf); + shf->flags = on; + shf->funcdef = mkautofn(shf); + shfunctab->addnode(shfunctab, ztrdup(*argv), shf); + } else + returnval = 1; + } + return returnval; +} + +/**/ +static List +mkautofn(Shfunc shf) +{ + List l; + Sublist s; + Pline p; + Cmd c; + AutoFn a; + PERMALLOC { + a = (AutoFn)allocnode(N_AUTOFN); + a->shf = shf; + c = (Cmd)allocnode(N_CMD); + c->type = AUTOFN; + c->u.autofn = a; + p = (Pline)allocnode(N_PLINE); + p->left = c; + p->type = END; + s = (Sublist)allocnode(N_SUBLIST); + s->left = p; + l = (List)allocnode(N_LIST); + l->left = s; + l->type = Z_SYNC; + } LASTALLOC; + return l; +} + +/* unset: unset parameters */ + +/**/ +int +bin_unset(char *name, char **argv, char *ops, int func) +{ + Param pm, next; + Comp com; + char *s; + int match = 0, returnval = 0; + int i; + + /* unset -f is the same as unfunction */ + if (ops['f']) + return bin_unhash(name, argv, ops, func); + + /* with -m option, treat arguments as glob patterns */ + if (ops['m']) { + while ((s = *argv++)) { + /* expand */ + tokenize(s); + if ((com = parsereg(s))) { + /* 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->next; + if ((!(pm->flags & PM_RESTRICTED) || + unset(RESTRICTED)) && domatch(pm->nam, com, 0)) { + unsetparam(pm->nam); + match++; + } + } + } + } else { + untokenize(s); + zwarnnam(name, "bad pattern : %s", s, 0); + returnval = 1; + } + } + /* If we didn't match anything, we return 1. */ + if (!match) + returnval = 1; + return returnval; + } + + /* do not glob -- unset the given parameter */ + while ((s = *argv++)) { + pm = (Param) paramtab->getnode(paramtab, s); + if (!pm) + returnval = 1; + else if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) { + zerrnam(name, "%s: restricted", pm->nam, 0); + returnval = 1; + } else + unsetparam(s); + } + return returnval; +} + +/* type, whence, which */ + +/**/ +int +bin_whence(char *nam, char **argv, char *ops, int func) +{ + HashNode hn; + Comp com; + int returnval = 0; + int printflags = 0; + int csh, all, v, wd; + int informed; + char *cnam; + + /* Check some option information */ + csh = ops['c']; + v = ops['v']; + all = ops['a']; + wd = ops['w']; + + if (ops['w']) + printflags |= PRINT_WHENCE_WORD; + else if (ops['c']) + printflags |= PRINT_WHENCE_CSH; + else if (ops['v']) + printflags |= PRINT_WHENCE_VERBOSE; + else + printflags |= PRINT_WHENCE_SIMPLE; + if (ops['f']) + printflags |= PRINT_WHENCE_FUNCDEF; + + /* With -m option -- treat arguments as a glob patterns */ + if (ops['m']) { + for (; *argv; argv++) { + /* parse the pattern */ + tokenize(*argv); + if (!(com = parsereg(*argv))) { + untokenize(*argv); + zwarnnam(nam, "bad pattern : %s", *argv, 0); + returnval = 1; + continue; + } + if (!ops['p']) { + /* -p option is for path search only. * + * We're not using it, so search for ... */ + + /* aliases ... */ + scanmatchtable(aliastab, com, 0, DISABLED, aliastab->printnode, printflags); + + /* and reserved words ... */ + scanmatchtable(reswdtab, com, 0, DISABLED, reswdtab->printnode, printflags); + + /* and shell functions... */ + scanmatchtable(shfunctab, com, 0, DISABLED, shfunctab->printnode, printflags); + + /* and builtins. */ + scanmatchtable(builtintab, com, 0, DISABLED, builtintab->printnode, printflags); + } + /* Done search for `internal' commands, if the -p option * + * was not used. Now search the path. */ + cmdnamtab->filltable(cmdnamtab); + scanmatchtable(cmdnamtab, com, 0, 0, cmdnamtab->printnode, printflags); + + } + return returnval; + } + + /* Take arguments literally -- do not glob */ + for (; *argv; argv++) { + informed = 0; + + if (!ops['p']) { + /* Look for alias */ + if ((hn = aliastab->getnode(aliastab, *argv))) { + aliastab->printnode(hn, printflags); + if (!all) + continue; + informed = 1; + } + /* Look for reserved word */ + if ((hn = reswdtab->getnode(reswdtab, *argv))) { + reswdtab->printnode(hn, printflags); + if (!all) + continue; + informed = 1; + } + /* Look for shell function */ + if ((hn = shfunctab->getnode(shfunctab, *argv))) { + shfunctab->printnode(hn, printflags); + if (!all) + continue; + informed = 1; + } + /* Look for builtin command */ + if ((hn = builtintab->getnode(builtintab, *argv))) { + builtintab->printnode(hn, printflags); + if (!all) + continue; + informed = 1; + } + /* 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); + if (!all) + continue; + informed = 1; + } + } + + /* Option -a is to search the entire path, * + * rather than just looking for one match. */ + if (all) { + char **pp, buf[PATH_MAX], *z; + + for (pp = path; *pp; pp++) { + z = buf; + if (**pp) { + strucpy(&z, *pp); + *z++ = '/'; + } + if ((z - buf) + strlen(*argv) >= PATH_MAX) + continue; + strcpy(z, *argv); + if (iscom(buf)) { + if (wd) { + printf("%s: command\n", *argv); + } else { + if (v && !csh) + zputs(*argv, stdout), fputs(" is ", stdout); + zputs(buf, stdout); + if (ops['s']) + print_if_link(buf); + fputc('\n', stdout); + } + informed = 1; + } + } + if (!informed && (wd || v || csh)) { + zputs(*argv, stdout); + puts(wd ? ": none" : " not found"); + returnval = 1; + } + } else if ((cnam = findcmd(*argv))) { + /* Found external command. */ + if (wd) { + printf("%s: command\n", *argv); + } else { + if (v && !csh) + zputs(*argv, stdout), fputs(" is ", stdout); + zputs(cnam, stdout); + if (ops['s']) + print_if_link(cnam); + fputc('\n', stdout); + } + zsfree(cnam); + } else { + /* Not found at all. */ + if (v || csh || wd) + zputs(*argv, stdout), puts(wd ? ": none" : " not found"); + returnval = 1; + } + } + return returnval; +} + +/**** 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, char *ops, int func) +{ + HashTable ht; + Comp com; + Asgment asg; + int returnval = 0; + + if (ops['d']) + ht = nameddirtab; + else + ht = cmdnamtab; + + if (ops['r'] || ops['f']) { + /* -f and -r can't be used with any arguments */ + if (*argv) { + zwarnnam("hash", "too many arguments", NULL, 0); + return 1; + } + + /* empty the hash table */ + if (ops['r']) + ht->emptytable(ht); + + /* fill the hash table in a standard way */ + if (ops['f']) + ht->filltable(ht); + + return 0; + } + + /* Given no arguments, display current hash table. */ + if (!*argv) { + scanhashtable(ht, 1, 0, 0, ht->printnode, 0); + return 0; + } + + while (*argv) { + void *hn; + if (ops['m']) { + /* with the -m option, treat the argument as a glob pattern */ + tokenize(*argv); /* expand */ + if ((com = parsereg(*argv))) { + /* display matching hash table elements */ + scanmatchtable(ht, com, 0, 0, ht->printnode, 0); + } else { + untokenize(*argv); + zwarnnam(name, "bad pattern : %s", *argv, 0); + returnval = 1; + } + } else if((asg = getasg(*argv))->value) { + if(isset(RESTRICTED)) { + zwarnnam(name, "restricted: %s", asg->value, 0); + returnval = 1; + } else { + /* The argument is of the form foo=bar, * + * so define an entry for the table. */ + if(ops['d']) { + Nameddir nd = hn = zcalloc(sizeof *nd); + nd->flags = 0; + nd->dir = ztrdup(asg->value); + } else { + Cmdnam cn = hn = zcalloc(sizeof *cn); + cn->flags = HASHED; + cn->u.cmd = ztrdup(asg->value); + } + ht->addnode(ht, ztrdup(asg->name), hn); + if(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(ops['d']) { + if(!getnameddir(asg->name)) { + zwarnnam(name, "no such directory name: %s", asg->name, 0); + returnval = 1; + } + } else { + if (!hashcmd(asg->name, path)) { + zwarnnam(name, "no such command: %s", asg->name, 0); + returnval = 1; + } + } + if(ops['v'] && (hn = ht->getnode2(ht, asg->name))) + ht->printnode(hn, 0); + } else if(ops['v']) + ht->printnode(hn, 0); + argv++; + } + return returnval; +} + +/* unhash: remove specified elements from a hash table */ + +/**/ +int +bin_unhash(char *name, char **argv, char *ops, int func) +{ + HashTable ht; + HashNode hn, nhn; + Comp com; + int match = 0, returnval = 0; + int i; + + /* Check which hash table we are working with. */ + if (ops['d']) + ht = nameddirtab; /* named directories */ + else if (ops['f']) + ht = shfunctab; /* shell functions */ + else if (ops['a']) + ht = aliastab; /* aliases */ + else + ht = cmdnamtab; /* external commands */ + + /* With -m option, treat arguments as glob patterns. * + * "unhash -m '*'" is legal, but not recommended. */ + if (ops['m']) { + for (; *argv; argv++) { + /* expand argument */ + tokenize(*argv); + if ((com = parsereg(*argv))) { + /* 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 (domatch(hn->nam, com, 0)) { + ht->freenode(ht->removenode(ht, hn->nam)); + match++; + } + } + } + } else { + untokenize(*argv); + zwarnnam(name, "bad pattern : %s", *argv, 0); + returnval = 1; + } + } + /* If we didn't match anything, we return 1. */ + if (!match) + returnval = 1; + return returnval; + } + + /* Take arguments literally -- do not glob */ + for (; *argv; argv++) { + if ((hn = ht->removenode(ht, *argv))) { + ht->freenode(hn); + } else { + zwarnnam(name, "no such hash table element: %s", *argv, 0); + returnval = 1; + } + } + return returnval; +} + +/**** alias builtins ****/ + +/* alias: display or create aliases. */ + +/**/ +int +bin_alias(char *name, char **argv, char *ops, int func) +{ + Alias a; + Comp com; + Asgment asg; + int haveflags = 0, returnval = 0; + int flags1 = 0, flags2 = DISABLED; + int printflags = 0; + + /* Did we specify the type of alias? */ + if (ops['r'] || ops['g']) { + if (ops['r'] && ops['g']) { + zwarnnam(name, "illegal combination of options", NULL, 0); + return 1; + } + haveflags = 1; + if (ops['g']) + flags1 |= ALIAS_GLOBAL; + else + flags2 |= ALIAS_GLOBAL; + } + + if (ops['L']) + printflags |= PRINT_LIST; + + /* In the absence of arguments, list all aliases. If a command * + * line flag is specified, list only those of that type. */ + if (!*argv) { + scanhashtable(aliastab, 1, flags1, flags2, aliastab->printnode, printflags); + return 0; + } + + /* With the -m option, treat the arguments as * + * glob patterns of aliases to display. */ + if (ops['m']) { + for (; *argv; argv++) { + tokenize(*argv); /* expand argument */ + if ((com = parsereg(*argv))) { + /* display the matching aliases */ + scanmatchtable(aliastab, com, flags1, flags2, aliastab->printnode, printflags); + } else { + untokenize(*argv); + zwarnnam(name, "bad pattern : %s", *argv, 0); + returnval = 1; + } + } + return returnval; + } + + /* Take arguments literally. Don't glob */ + while ((asg = getasg(*argv++))) { + if (asg->value && !ops['L']) { + /* The argument is of the form foo=bar and we are not * + * forcing a listing with -L, so define an alias */ + aliastab->addnode(aliastab, ztrdup(asg->name), + createaliasnode(ztrdup(asg->value), flags1)); + } else if ((a = (Alias) aliastab->getnode(aliastab, asg->name))) { + /* display alias if appropriate */ + if (!haveflags || + (ops['r'] && !(a->flags & ALIAS_GLOBAL)) || + (ops['g'] && (a->flags & ALIAS_GLOBAL))) + aliastab->printnode((HashNode) a, printflags); + } else + returnval = 1; + } + return returnval; +} + + +/**** miscellaneous builtins ****/ + +/* true, : (colon) */ + +/**/ +int +bin_true(char *name, char **argv, char *ops, int func) +{ + return 0; +} + +/* false builtin */ + +/**/ +int +bin_false(char *name, char **argv, char *ops, int func) +{ + return 1; +} + +/* the zle buffer stack */ + +/**/ +LinkList bufstack; + +/* echo, print, pushln */ + +/**/ +int +bin_print(char *name, char **args, char *ops, int func) +{ + int nnl = 0, fd, argc, n; + int *len; + Histent ent; + FILE *fout = stdout; + + /* -m option -- treat the first argument as a pattern and remove + * arguments not matching */ + if (ops['m']) { + Comp com; + char **t, **p; + + tokenize(*args); + if (!(com = parsereg(*args))) { + untokenize(*args); + zwarnnam(name, "bad pattern : %s", *args, 0); + return 1; + } + for (p = ++args; *p; p++) + if (!domatch(*p, com, 0)) + for (t = p--; (*t = t[1]); t++); + } + /* compute lengths, and interpret according to -P, -D, -e, etc. */ + argc = arrlen(args); + len = (int *)ncalloc(argc * sizeof(int)); + for(n = 0; n < argc; n++) { + /* first \ sequences */ + if (!ops['e'] && (ops['R'] || ops['r'] || ops['E'])) + unmetafy(args[n], &len[n]); + else + args[n] = getkeystring(args[n], &len[n], + func != BIN_ECHO && !ops['e'], &nnl); + /* -P option -- interpret as a prompt sequence */ + if(ops['P']) + args[n] = unmetafy(promptexpand(metafy(args[n], len[n], + META_NOALLOC), 0, NULL, NULL), &len[n]); + /* -D option -- interpret as a directory, and use ~ */ + if(ops['D']) { + Nameddir d = finddir(args[n]); + if(d) { + char *arg = alloc(strlen(args[n]) + 1); + sprintf(arg, "~%s%s", d->nam, + args[n] + strlen(d->dir)); + args[n] = arg; + len[n] = strlen(args[n]); + } + } + } + + /* -z option -- push the arguments onto the editing buffer stack */ + if (ops['z']) { + PERMALLOC { + pushnode(bufstack, sepjoin(args, NULL)); + } LASTALLOC; + return 0; + } + /* -s option -- add the arguments to the history list */ + if (ops['s']) { + int nwords = 0, nlen, iwords; + char **pargs = args; + + PERMALLOC { + ent = gethistent(++curhist); + zsfree(ent->text); + if (ent->nwords) + zfree(ent->words, ent->nwords*2*sizeof(short)); + while (*pargs++) + nwords++; + if ((ent->nwords = nwords)) { + ent->words = (short *)zalloc(nwords*2*sizeof(short)); + nlen = iwords = 0; + for (pargs = args; *pargs; pargs++) { + ent->words[iwords++] = nlen; + nlen += strlen(*pargs); + ent->words[iwords++] = nlen; + nlen++; + } + } else + ent->words = (short *)NULL; + ent->text = zjoin(args, ' '); + ent->stim = ent->ftim = time(NULL); + ent->flags = 0; + } LASTALLOC; + return 0; + } + /* -u and -p -- output to other than standard output */ + if (ops['u'] || ops['p']) { + if (ops['u']) { + for (fd = 0; fd < 10; fd++) + if (ops[fd + '0']) + break; + if (fd == 10) + fd = 0; + } else + fd = coprocout; + if ((fd = dup(fd)) < 0) { + zwarnnam(name, "bad file number", NULL, 0); + return 1; + } + if ((fout = fdopen(fd, "w")) == 0) { + zwarnnam(name, "bad mode on fd", NULL, 0); + return 1; + } + } + + /* -o and -O -- sort the arguments */ + if (ops['o']) { + if (ops['i']) + qsort(args, arrlen(args), sizeof(char *), cstrpcmp); + + else + qsort(args, arrlen(args), sizeof(char *), strpcmp); + } else if (ops['O']) { + if (ops['i']) + qsort(args, arrlen(args), sizeof(char *), invcstrpcmp); + + else + qsort(args, arrlen(args), sizeof(char *), invstrpcmp); + } + /* after sorting arguments, recalculate lengths */ + if(ops['o'] || ops['O']) + for(n = 0; n < argc; n++) + len[n] = strlen(args[n]); + + /* -c -- output in columns */ + if (ops['c']) { + int l, nc, nr, sc, n, t, i; + char **ap; + + for (n = l = 0, ap = args; *ap; ap++, n++) + if (l < (t = strlen(*ap))) + l = t; + + sc = l + 2; + nc = (columns + 1) / sc; + if (!nc) + nc = 1; + nr = (n + nc - 1) / nc; + + for (i = 0; i < nr; i++) { + ap = args + i; + do { + l = strlen(*ap); + fprintf(fout, "%s", *ap); + for (t = nr; t && *ap; t--, ap++); + if(*ap) + for (; l < sc; l++) + fputc(' ', fout); + } while (*ap); + fputc(ops['N'] ? '\0' : '\n', fout); + } + if (fout != stdout) + fclose(fout); + return 0; + } + /* normal output */ + for (; *args; args++, len++) { + fwrite(*args, *len, 1, fout); + if (args[1]) + fputc(ops['l'] ? '\n' : ops['N'] ? '\0' : ' ', fout); + } + if (!(ops['n'] || nnl)) + fputc(ops['N'] ? '\0' : '\n', fout); + if (fout != stdout) + fclose(fout); + return 0; +} + +/* echotc: output a termcap */ + +/**/ +int +bin_echotc(char *name, char **argv, char *ops, int func) +{ + char *s, buf[2048], *t, *u; + int num, argct; + + s = *argv++; + if (termflags & TERM_BAD) + return 1; + if ((termflags & TERM_UNKNOWN) && (isset(INTERACTIVE) || !init_term())) + return 1; + /* if the specified termcap has a numeric value, display it */ + if ((num = tgetnum(s)) != -1) { + printf("%d\n", num); + return 0; + } + /* if the specified termcap is boolean, and set, say so * + * ncurses can tell if an existing boolean capability is * + * off so in this case we print "no". */ +#if !defined(NCURSES_VERSION) || !defined(COLOR_PAIR) + if (tgetflag(s) > 0) { + puts("yes"); + return (0); + } +#else /* NCURSES_VERSION && COLOR_PAIR */ + switch (tgetflag(s)) { + case -1: + break; + case 0: + puts("no"); + return 0; + default: + puts("yes"); + return 0; + } +#endif /* NCURSES_VERSION && COLOR_PAIR */ + /* get a string-type capability */ + u = buf; + t = tgetstr(s, &u); + if (!t || !*t) { + /* capability doesn't exist, or (if boolean) is off */ + zwarnnam(name, "no such capability: %s", s, 0); + return 1; + } + /* count the number of arguments required */ + for (argct = 0, u = t; *u; u++) + if (*u == '%') { + if (u++, (*u == 'd' || *u == '2' || *u == '3' || *u == '.' || + *u == '+')) + argct++; + } + /* check that the number of arguments provided is correct */ + if (arrlen(argv) != argct) { + zwarnnam(name, (arrlen(argv) < argct) ? "not enough arguments" : + "too many arguments", NULL, 0); + return 1; + } + /* output string, through the proper termcap functions */ + if (!argct) + tputs(t, 1, putraw); + else { + num = (argv[1]) ? atoi(argv[1]) : atoi(*argv); + tputs(tgoto(t, atoi(*argv), num), num, putraw); + } + return 0; +} + +/* shift builtin */ + +/**/ +int +bin_shift(char *name, char **argv, char *ops, int func) +{ + int num = 1, l, ret = 0; + char **s; + + /* optional argument can be either numeric or an array */ + if (*argv && !getaparam(*argv)) + num = matheval(*argv++); + + if (num < 0) { + zwarnnam(name, "argument to shift must be non-negative", NULL, 0); + return 1; + } + + if (*argv) { + for (; *argv; argv++) + if ((s = getaparam(*argv))) { + if (num > arrlen(s)) { + zwarnnam(name, "shift count must be <= $#", NULL, 0); + ret++; + continue; + } + PERMALLOC { + s = arrdup(s + num); + } LASTALLOC; + setaparam(*argv, s); + } + } else { + if (num > (l = arrlen(pparams))) { + zwarnnam(name, "shift count must be <= $#", NULL, 0); + ret = 1; + } else { + s = zalloc((l - num + 1) * sizeof(char *)); + memcpy(s, pparams + num, (l - num + 1) * sizeof(char *)); + while (num--) + zsfree(pparams[num]); + zfree(pparams, (l + 1) * sizeof(char *)); + pparams = s; + } + } + return ret; +} + +/* getopts: automagical option handling for shell scripts */ + +/**/ +int +bin_getopts(char *name, char **argv, char *ops, int func) +{ + int lenstr, lenoptstr, quiet, lenoptbuf; + char *optstr = unmetafy(*argv++, &lenoptstr), *var = *argv++; + char **args = (*argv) ? argv : pparams; + static int optcind = 0; + 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(zoptind > arrlen(args)) + /* 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(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); + if(quiet) { + setsparam(var, ztrdup(p)); + zoptarg = metafy(optbuf, lenoptbuf, META_DUP); + } else { + zerr(*p == '?' ? "bad option: -%c" : + "argument expected after -%c option", NULL, opch); + zoptarg=ztrdup(""); + errflag = 0; + } + 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); + optcind = ztrlen(args[zoptind - 1]); + zsfree(zoptarg); + zoptarg = p; + } else { + zsfree(zoptarg); + zoptarg = ztrdup(""); + } + + setsparam(var, metafy(optbuf, lenoptbuf, META_DUP)); + return 0; +} + +/* break, bye, continue, exit, logout, return -- most of these take * + * one numeric argument, and the other (logout) is related to return. * + * (return is treated as a logout when in a login shell.) */ + +/**/ +int +bin_break(char *name, char **argv, char *ops, int func) +{ + int num = lastval, nump = 0; + + /* handle one optional numeric argument */ + if (*argv) { + num = matheval(*argv++); + nump = 1; + } + + switch (func) { + case BIN_CONTINUE: + if (!loops) { /* continue is only permitted in loops */ + zerrnam(name, "not in while, until, select, or repeat loop", NULL, 0); + return 1; + } + contflag = 1; /* ARE WE SUPPOSED TO FALL THROUGH HERE? */ + case BIN_BREAK: + if (!loops) { /* break is only permitted in loops */ + zerrnam(name, "not in while, until, select, or repeat loop", NULL, 0); + return 1; + } + breaks = nump ? minimum(num,loops) : 1; + break; + case BIN_RETURN: + if (isset(INTERACTIVE) || locallevel || sourcelevel) { + retflag = 1; + breaks = loops; + lastval = num; + if (trapreturn == -2) + trapreturn = lastval; + return lastval; + } + zexit(num, 0); /* else treat return as logout/exit */ + break; + case BIN_LOGOUT: + if (unset(LOGINSHELL)) { + zerrnam(name, "not login shell", NULL, 0); + return 1; + } + zexit(num, 0); + break; + case BIN_EXIT: + zexit(num, 0); + break; + } + return 0; +} + +/* we have printed a 'you have stopped (running) jobs.' message */ + +/**/ +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)) + break; + if (i < MAXJOB) { + if (jobtab[i].stat & STAT_STOPPED) { + +#ifdef USE_SUSPENDED + zerr("you have suspended jobs.", NULL, 0); +#else + zerr("you have stopped jobs.", NULL, 0); +#endif + + } else + zerr("you have running jobs.", NULL, 0); + stopmsg = 1; + } +} + +/* exit the shell. val is the return value of the shell. * + * from_signal should be non-zero if zexit is being called * + * because of a signal. */ + +/**/ +void +zexit(int val, int from_signal) +{ + static int in_exit; + + HEAPALLOC { + if (isset(MONITOR) && !stopmsg && !from_signal) { + scanjobs(); /* check if jobs need printing */ + checkjobs(); /* check if any jobs are running/stopped */ + if (stopmsg) { + stopmsg = 2; + LASTALLOC_RETURN; + } + } + if (in_exit++ && from_signal) + LASTALLOC_RETURN; + if (isset(MONITOR)) + /* send SIGHUP to any jobs left running */ + killrunjobs(from_signal); + if (isset(RCS) && interact) { + if (!nohistsave) + savehistfile(getsparam("HISTFILE"), 1, isset(APPENDHISTORY) ? 3 : 0); + if (islogin && !subsh) { + sourcehome(".zlogout"); +#ifdef GLOBAL_ZLOGOUT + source(GLOBAL_ZLOGOUT); +#endif + } + } + if (sigtrapped[SIGEXIT]) + dotrap(SIGEXIT); + if (mypid != getpid()) + _exit(val); + else + exit(val); + } LASTALLOC; +} + +/* . (dot), source */ + +/**/ +int +bin_dot(char *name, char **argv, char *ops, int func) +{ + char **old, *old0 = NULL; + int ret, diddot = 0, dotdot = 0; + char buf[PATH_MAX]; + char *s, **t, *enam, *arg0; + struct stat st; + + if (!*argv || strlen(*argv) >= PATH_MAX) + return 0; + old = pparams; + /* get arguments for the script */ + if (argv[1]) { + PERMALLOC { + pparams = arrdup(argv + 1); + } LASTALLOC; + } + enam = arg0 = ztrdup(*argv); + if (isset(FUNCTIONARGZERO)) { + old0 = argzero; + argzero = arg0; + } + s = unmeta(enam); + errno = ENOENT; + ret = 1; + /* 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) { + /* 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 && isset(PATHDIRS) && diddot < 2 && dotdot == 0)) { + /* search path for script */ + for (t = path; *t; t++) { + if (!(*t)[0] || ((*t)[0] == '.' && !(*t)[1])) { + if (diddot) + continue; + diddot = 1; + strcpy(buf, arg0); + } else { + if (strlen(*t) + strlen(arg0) + 1 >= PATH_MAX) + continue; + sprintf(buf, "%s/%s", *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; + } + } + } + } + /* clean up and return */ + if (argv[1]) { + freearray(pparams); + pparams = old; + } + if (ret) + zwarnnam(name, "%e: %s", enam, errno); + zsfree(arg0); + if (old0) + argzero = old0; + return ret ? ret : lastval; +} + +/**/ +int +bin_emulate(char *nam, char **argv, char *ops, int func) +{ + emulate(*argv, ops['R']); + return 0; +} + +/* eval: simple evaluation */ + +/**/ +int +bin_eval(char *nam, char **argv, char *ops, int func) +{ + List list; + + inpush(zjoin(argv, ' '), 0, NULL); + strinbeg(); + stophist = 2; + list = parse_list(); + strinend(); + inpop(); + if (!list) { + errflag = 0; + return 1; + } + execlist(list, 1, 0); + if (errflag) { + lastval = errflag; + errflag = 0; + } + return lastval; +} + +static char *zbuf; +static int readfd; + +/* Read a character from readfd, or from the buffer zbuf. Return EOF on end of +file/buffer. */ + +/* read: get a line of input, or (for compctl functions) return some * + * useful data about the state of the editing line. The -E and -e * + * options mean that the result should be sent to stdout. -e means, * + * in addition, that the result should not actually be assigned to * + * the specified parameters. */ + +/**/ +int +bin_read(char *name, char **args, char *ops, int func) +{ + char *reply, *readpmpt; + int bsiz, c = 0, gotnl = 0, al = 0, first, nchars = 1, bslash; + int haso = 0; /* true if /dev/tty has been opened specially */ + int isem = !strcmp(term, "emacs"); + char *buf, *bptr, *firstarg, *zbuforig; + LinkList readll = newlinklist(); + + if ((ops['k'] || ops['b']) && *args && idigit(**args)) { + if (!(nchars = atoi(*args))) + nchars = 1; + args++; + } + + firstarg = *args; + if (*args && **args == '?') + args++; + /* default result parameter */ + reply = *args ? *args++ : ops['A'] ? "reply" : "REPLY"; + if (ops['A'] && *args) { + zwarnnam(name, "only one array argument allowed", NULL, 0); + return 1; + } + + /* handle compctl case */ + if(ops['l'] || ops['c']) + return compctlread(name, args, ops, reply); + + if ((ops['k'] && !ops['u'] && !ops['p']) || ops['q']) { + if (SHTTY == -1) { + /* need to open /dev/tty specially */ + SHTTY = open("/dev/tty", O_RDWR|O_NOCTTY); + haso = 1; + } + /* We should have a SHTTY opened by now. */ + if (SHTTY == -1) { + /* Unfortunately, we didn't. */ + fprintf(stderr, "not interactive and can't open terminal\n"); + fflush(stderr); + return 1; + } + if (unset(INTERACTIVE)) + gettyinfo(&shttyinfo); + /* attach to the tty */ + attachtty(mypgrp); + if (!isem && ops['k']) + setcbreak(); + readfd = SHTTY; + } else if (ops['u'] && !ops['p']) { + /* -u means take input from the specified file descriptor. * + * -up means take input from the coprocess. */ + for (readfd = 9; readfd && !ops[readfd + '0']; --readfd); + } else if (ops['p']) + readfd = coprocin; + else + readfd = 0; + + /* handle prompt */ + if (firstarg) { + for (readpmpt = firstarg; + *readpmpt && *readpmpt != '?'; readpmpt++); + if (*readpmpt++) { + if (isatty(0)) { + zputs(readpmpt, stderr); + fflush(stderr); + } + readpmpt[-1] = '\0'; + } + } + + /* option -k means read only a given number of characters (default 1) */ + if (ops['k']) { + int val; + char d; + + /* allocate buffer space for result */ + bptr = buf = (char *)zalloc(nchars+1); + + do { + /* If read returns 0, is end of file */ + if ((val = read(readfd, bptr, nchars)) <= 0) + break; + + /* decrement number of characters read from number required */ + nchars -= val; + + /* increment pointer past read characters */ + bptr += val; + } while (nchars > 0); + + if (!ops['u'] && !ops['p']) { + /* dispose of result appropriately, etc. */ + if (isem) + while (val > 0 && read(SHTTY, &d, 1) == 1 && d != '\n'); + else + settyinfo(&shttyinfo); + if (haso) { + close(SHTTY); + SHTTY = -1; + } + } + + if (ops['e'] || ops['E']) + fwrite(buf, bptr - buf, 1, stdout); + if (!ops['e']) + setsparam(reply, metafy(buf, bptr - buf, META_REALLOC)); + else + zfree(buf, bptr - buf + 1); + return val <= 0; + } + + /* option -q means get one character, and interpret it as a Y or N */ + if (ops['q']) { + char readbuf[2]; + + /* set up the buffer */ + readbuf[1] = '\0'; + + /* get, and store, reply */ + readbuf[0] = ((char)getquery(NULL, 0)) == 'y' ? 'y' : 'n'; + + /* dispose of result appropriately, etc. */ + if (haso) { + close(SHTTY); + SHTTY = -1; + } + + if (ops['e'] || ops['E']) + printf("%s\n", readbuf); + if (!ops['e']) + setsparam(reply, ztrdup(readbuf)); + + return readbuf[0] == 'n'; + } + + /* All possible special types of input have been exhausted. Take one line, + and assign words to the parameters until they run out. Leftover words go + onto the last parameter. If an array is specified, all the words become + separate elements of the array. */ + + zbuforig = zbuf = (!ops['z']) ? NULL : + (nonempty(bufstack)) ? (char *) getlinknode(bufstack) : ztrdup(""); + first = 1; + bslash = 0; + while (*args || (ops['A'] && !gotnl)) { + buf = bptr = (char *)zalloc(bsiz = 64); + /* get input, a character at a time */ + while (!gotnl) { + c = zread(); + /* \ at the end of a line indicates a continuation * + * line, except in raw mode (-r option) */ + if (bslash && c == '\n') { + bslash = 0; + continue; + } + if (c == EOF || c == '\n') + break; + if (!bslash && isep(c)) { + if (bptr != buf || (!iwsep(c) && first)) { + first |= !iwsep(c); + break; + } + first |= !iwsep(c); + continue; + } + bslash = c == '\\' && !bslash && !ops['r']; + if (bslash) + continue; + first = 0; + if (imeta(c)) { + *bptr++ = Meta; + *bptr++ = c ^ 32; + } else + *bptr++ = c; + /* increase the buffer size, if necessary */ + if (bptr >= buf + bsiz - 1) { + int blen = bptr - buf; + + buf = realloc(buf, bsiz *= 2); + bptr = buf + blen; + } + } + if (c == '\n' || c == EOF) + gotnl = 1; + *bptr = '\0'; + /* dispose of word appropriately */ + if (ops['e'] || ops['E']) { + zputs(buf, stdout); + putchar('\n'); + } + if (!ops['e']) { + if (ops['A']) { + addlinknode(readll, buf); + al++; + } else + setsparam(reply, buf); + } else + free(buf); + if (!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 (ops['A']) { + char **pp, **p = NULL; + LinkNode n; + + p = (ops['e'] ? (char **)NULL + : (char **)zalloc((al + 1) * sizeof(char *))); + + for (pp = p, n = firstnode(readll); n; incnode(n)) { + if (ops['e'] || 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); + } + return c == EOF; + } + buf = bptr = (char *)zalloc(bsiz = 64); + /* any remaining part of the line goes into one parameter */ + bslash = 0; + if (!gotnl) + for (;;) { + c = zread(); + /* \ at the end of a line introduces a continuation line, except in + raw mode (-r option) */ + if (bslash && c == '\n') { + bslash = 0; + continue; + } + if (c == EOF || (c == '\n' && !zbuf)) + break; + if (!bslash && isep(c) && bptr == buf) + if (iwsep(c)) + continue; + else if (!first) { + first = 1; + continue; + } + bslash = c == '\\' && !bslash && !ops['r']; + if (bslash) + continue; + if (imeta(c)) { + *bptr++ = Meta; + *bptr++ = c ^ 32; + } else + *bptr++ = c; + /* increase the buffer size, if necessary */ + if (bptr >= buf + bsiz - 1) { + int blen = bptr - buf; + + buf = realloc(buf, bsiz *= 2); + bptr = buf + blen; + } + } + while (bptr > buf && iwsep(bptr[-1])) + bptr--; + *bptr = '\0'; + /* final assignment of reply, etc. */ + if (ops['e'] || ops['E']) { + zputs(buf, stdout); + putchar('\n'); + } + if (!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; + } + return 0; +} + +/**/ +static int +zread(void) +{ + char cc, retry = 0; + + /* 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; + for (;;) { + /* read a character from readfd */ + switch (read(readfd, &cc, 1)) { + case 1: + /* return the character read */ + return STOUC(cc); +#if defined(EAGAIN) || defined(EWOULDBLOCK) + case -1: + 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; + } + break; +#endif /* EAGAIN || EWOULDBLOCK */ + } + return EOF; + } +} + +/* holds arguments for testlex() */ +/**/ +char **testargs; + +/* test, [: the old-style general purpose logical expression builtin */ + +/**/ +void +testlex(void) +{ + if (tok == LEXERR) + return; + + tokstr = *testargs; + if (!*testargs) { + /* if tok is already zero, reading past the end: error */ + tok = tok ? 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, char *ops, int func) +{ + char **s; + Cond c; + + /* if "test" was invoked as "[", it needs a matching "]" * + * which is subsequently ignored */ + if (func == BIN_BRACKET) { + for (s = argv; *s; s++); + if (s == argv || strcmp(s[-1], "]")) { + zwarnnam(name, "']' expected", NULL, 0); + return 1; + } + s[-1] = NULL; + } + /* an empty argument list evaluates to false (1) */ + if (!*argv) + return 1; + + testargs = argv; + tok = NULLTOK; + condlex = testlex; + testlex(); + c = par_cond(); + condlex = yylex; + + if (errflag) { + errflag = 0; + return 1; + } + + if (!c || tok == LEXERR) { + zwarnnam(name, tokstr ? "parse error" : "argument expected", NULL, 0); + return 1; + } + + /* syntax is OK, so evaluate */ + return !evalcond(c); +} + +/* display a time, provided in units of 1/60s, as minutes and seconds */ +#define pttime(X) printf("%ldm%ld.%02lds",((long) (X))/3600,\ + ((long) (X))/60%60,((long) (X))*100/60%100) + +/* times: display, in a two-line format, the times provided by times(3) */ + +/**/ +int +bin_times(char *name, char **argv, char *ops, int func) +{ + struct tms buf; + + /* get time accounting information */ + if (times(&buf) == -1) + return 1; + pttime(buf.tms_utime); /* user time */ + putchar(' '); + pttime(buf.tms_stime); /* system time */ + putchar('\n'); + pttime(buf.tms_cutime); /* user time, children */ + putchar(' '); + pttime(buf.tms_cstime); /* system time, children */ + putchar('\n'); + return 0; +} + +/* trap: set/unset signal traps */ + +/**/ +int +bin_trap(char *name, char **argv, char *ops, int func) +{ + List l; + char *arg, *s; + int sig; + + if (*argv && !strcmp(*argv, "--")) + argv++; + + /* If given no arguments, list all currently-set traps */ + if (!*argv) { + for (sig = 0; sig < VSIGCOUNT; sig++) { + if (sigtrapped[sig] & ZSIG_FUNC) { + char fname[20]; + HashNode hn; + + sprintf(fname, "TRAP%s", sigs[sig]); + if ((hn = shfunctab->getnode(shfunctab, fname))) + shfunctab->printnode(hn, 0); + DPUTS(!hn, "BUG: I did not find any trap functions!"); + } else if (sigtrapped[sig]) { + if (!sigfuncs[sig]) + printf("trap -- '' %s\n", sigs[sig]); + else { + s = getpermtext((void *) dupstruct((void *) sigfuncs[sig])); + printf("trap -- "); + quotedzputs(s, stdout); + printf(" %s\n", sigs[sig]); + zsfree(s); + } + } + } + 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 + while (*argv) + unsettrap(getsignum(*argv++)); + return 0; + } + + /* Sort out the command to execute on trap */ + arg = *argv++; + if (!*arg) + l = NULL; + else if (!(l = parse_string(arg))) { + zwarnnam(name, "couldn't parse trap command", NULL, 0); + return 1; + } + + /* set traps */ + for (; *argv; argv++) { + List t; + + sig = getsignum(*argv); + if (sig == -1) { + zwarnnam(name, "undefined signal: %s", *argv, 0); + break; + } + PERMALLOC { + t = (List) dupstruct(l); + } LASTALLOC; + if (settrap(sig, t)) + freestruct(t); + } + return *argv != NULL; +} + +/**/ +int +bin_ttyctl(char *name, char **argv, char *ops, int func) +{ + if (ops['f']) + ttyfrozen = 1; + else if (ops['u']) + ttyfrozen = 0; + else + printf("tty is %sfrozen\n", ttyfrozen ? "" : "not "); + return 0; +} + +/* let -- mathematical evaluation */ + +/**/ +int +bin_let(char *name, char **argv, char *ops, int func) +{ + long val = 0; + + while (*argv) + val = matheval(*argv++); + /* Errors in math evaluation in let are non-fatal. */ + errflag = 0; + return !val; +} + +/* umask command. umask may be specified as octal digits, or in the * + * symbolic form that chmod(1) uses. Well, a subset of it. Remember * + * that only the bottom nine bits of umask are used, so there's no * + * point allowing the set{u,g}id and sticky bits to be specified. */ + +/**/ +int +bin_umask(char *nam, char **args, char *ops, int func) +{ + mode_t um; + char *s = *args; + + /* Get the current umask. */ + um = umask(0); + umask(um); + /* No arguments means to display the current setting. */ + if (!s) { + if (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", NULL, 0); + return 1; + } + } else { + /* Symbolic notation -- slightly complicated. */ + int whomask, umaskop, mask; + + /* More than one symbolic argument may be used at once, each separated + by commas. */ + for (;;) { + /* First part of the argument -- who does this apply to? + u=owner, g=group, o=other. */ + whomask = 0; + while (*s == 'u' || *s == 'g' || *s == 'o' || *s == 'a') + if (*s == 'u') + s++, whomask |= 0700; + else if (*s == 'g') + s++, whomask |= 0070; + else if (*s == 'o') + s++, whomask |= 0007; + else if (*s == 'a') + s++, whomask |= 0777; + /* Default whomask is everyone. */ + if (!whomask) + whomask = 0777; + /* Operation may be +, - or =. */ + umaskop = (int)*s; + if (!(umaskop == '+' || umaskop == '-' || umaskop == '=')) { + if (umaskop) + zwarnnam(nam, "bad symbolic mode operator: %c", NULL, umaskop); + else + zwarnnam(nam, "bad umask", NULL, 0); + return 1; + } + /* Permissions mask -- r=read, w=write, x=execute. */ + mask = 0; + while (*++s && *s != ',') + if (*s == 'r') + mask |= 0444 & whomask; + else if (*s == 'w') + mask |= 0222 & whomask; + else if (*s == 'x') + mask |= 0111 & whomask; + else { + zwarnnam(nam, "bad symbolic mode permission: %c", + NULL, *s); + return 1; + } + /* Apply parsed argument to um. */ + if (umaskop == '+') + um &= ~mask; + else if (umaskop == '-') + um |= mask; + else /* umaskop == '=' */ + um = (um | (whomask)) & ~mask; + if (*s == ',') + s++; + else + break; + } + if (*s) { + zwarnnam(nam, "bad character in symbolic mode: %c", NULL, *s); + return 1; + } + } + + /* Finally, set the new umask. */ + umask(um); + return 0; +} + +/* Generic builtin for facilities not available on this OS */ + +/**/ +int +bin_notavail(char *nam, char **argv, char *ops, int func) +{ + zwarnnam(nam, "not available on this system", NULL, 0); + return 1; +} |