mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-01-29 02:32:11 +01:00
3599 lines
91 KiB
C
3599 lines
91 KiB
C
/*
|
|
* 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;
|
|
}
|