diff options
author | Tanaka Akira <akr@users.sourceforge.net> | 1999-04-15 18:05:38 +0000 |
---|---|---|
committer | Tanaka Akira <akr@users.sourceforge.net> | 1999-04-15 18:05:38 +0000 |
commit | e74702b467171dbdafb56dfe354794a212e020d9 (patch) | |
tree | c295b3e9b2e93e2de10331877442615b0f37e779 /Src | |
parent | c175751b501a3a4cb40ad4787340a597ea769be4 (diff) | |
download | zsh-e74702b467171dbdafb56dfe354794a212e020d9.tar.gz zsh-e74702b467171dbdafb56dfe354794a212e020d9.tar.xz zsh-e74702b467171dbdafb56dfe354794a212e020d9.zip |
Initial revision
Diffstat (limited to 'Src')
104 files changed, 57115 insertions, 0 deletions
diff --git a/Src/.cvsignore b/Src/.cvsignore new file mode 100644 index 000000000..edec5401b --- /dev/null +++ b/Src/.cvsignore @@ -0,0 +1,25 @@ +Makefile +Makemod.in Makemod +*.pro +*.o +*.o.c +*.so +*.mdh +*.mdhi +*.mdhs +*.mdh.tmp +modules.index +modules.index.tmp +modules.stamp +modules-bltin +stamp-modobjs +stamp-modobjs.tmp +ansi2knr +zsh +libzsh.so* +sigcount.h +signames.c +zshpaths.h +zshxmods.h +bltinmods.list +tags TAGS diff --git a/Src/.distfiles b/Src/.distfiles new file mode 100644 index 000000000..727c855cc --- /dev/null +++ b/Src/.distfiles @@ -0,0 +1,11 @@ +DISTFILES_SRC=' + .cvsignore .distfiles .exrc .indent.pro + Makefile.in Makemod.in.in + ansi2knr.c + builtin.c compat.c cond.c exec.c glob.c hashtable.c hashtable.h + hist.c init.c input.c jobs.c lex.c linklist.c loop.c main.c makepro.awk + math.c mem.c mkbltnmlst.sh mkmakemod.sh mkmodindex.sh + module.c options.c params.c parse.c prompt.c prototypes.h + signals.c signals.h signames.awk subst.c system.h text.c utils.c + watch.c xmods.conf zsh.h zsh.mdd ztype.h +' diff --git a/Src/.exrc b/Src/.exrc new file mode 100644 index 000000000..91d0b39ef --- /dev/null +++ b/Src/.exrc @@ -0,0 +1,2 @@ +set ai +set sw=4 diff --git a/Src/.lastloc b/Src/.lastloc new file mode 100644 index 000000000..b3c6cf0f3 --- /dev/null +++ b/Src/.lastloc @@ -0,0 +1,5 @@ +(("/home/user2/pws/src/zsh-3.1.5/Src/glob.c" . 15475) + ("/home/user2/pws/src/zsh-3.1.5/Src/zsh.export" . 794) + ("/home/user2/pws/src/zsh-3.1.5/Src/utils.c" . 10946) + ("/home/user2/pws/src/zsh-3.1.5/Src/Makefile" . 4282) + ("/home/user2/pws/src/zsh-3.1.5/Src/mkmakemod.sh" . 6832)) diff --git a/Src/Builtins/.cvsignore b/Src/Builtins/.cvsignore new file mode 100644 index 000000000..ff73d86c4 --- /dev/null +++ b/Src/Builtins/.cvsignore @@ -0,0 +1,11 @@ +Makefile +Makefile.in +*.pro +*.o +*.o.c +*.so +*.mdh +*.mdhi +*.mdhs +*.mdh.tmp +rlimits.h diff --git a/Src/Builtins/.distfiles b/Src/Builtins/.distfiles new file mode 100644 index 000000000..cd36388ef --- /dev/null +++ b/Src/Builtins/.distfiles @@ -0,0 +1,5 @@ +DISTFILES_SRC=' + .cvsignore .distfiles .exrc + rlimits.mdd rlimits.c rlimits.awk + sched.mdd sched.c +' diff --git a/Src/Builtins/.exrc b/Src/Builtins/.exrc new file mode 100644 index 000000000..91d0b39ef --- /dev/null +++ b/Src/Builtins/.exrc @@ -0,0 +1,2 @@ +set ai +set sw=4 diff --git a/Src/Builtins/rlimits.awk b/Src/Builtins/rlimits.awk new file mode 100644 index 000000000..e2500582a --- /dev/null +++ b/Src/Builtins/rlimits.awk @@ -0,0 +1,72 @@ +# +# rlimits.awk: {g,n}awk script to generate rlimits.h +# +# NB: On SunOS 4.1.3 - user-functions don't work properly, also \" problems +# Without 0 + hacks some nawks compare numbers as strings +# +BEGIN {limidx = 0} + +/^[\t ]*(#[\t ]*define[\t _]*RLIMIT_[A-Z]*[\t ]*[0-9][0-9]*|RLIMIT_[A-Z]*,[\t ]*)/ { + limindex = index($0, "RLIMIT_") + limtail = substr($0, limindex, 80) + split(limtail, tmp) + limnam = substr(tmp[1], 8, 20) + limnum = tmp[2] + # in this case I assume GNU libc resourcebits.h + if (limnum == "") { + limnum = limidx++ + limindex = index($0, ",") + limnam = substr(limnam, 1, limindex-1) + } + limrev[limnam] = limnum + if (lim[limnum] == "") { + lim[limnum] = limnam + if (limnum ~ /^[0-9]*$/) { + if (limnam == "MEMLOCK") { msg[limnum] = "memorylocked" } + if (limnam == "RSS") { msg[limnum] = "resident" } + if (limnam == "VMEM") { msg[limnum] = "vmemorysize" } + if (limnam == "NOFILE") { msg[limnum] = "descriptors" } + if (limnam == "OFILE") { msg[limnum] = "descriptors" } + if (limnam == "CORE") { msg[limnum] = "coredumpsize" } + if (limnam == "STACK") { msg[limnum] = "stacksize" } + if (limnam == "DATA") { msg[limnum] = "datasize" } + if (limnam == "FSIZE") { msg[limnum] = "filesize" } + if (limnam == "CPU") { msg[limnum] = "cputime" } + if (limnam == "NPROC") { msg[limnum] = "maxproc" } + if (limnam == "AS") { msg[limnum] = "addressspace" } + if (limnam == "TCACHE") { msg[limnum] = "cachedthreads" } + } + } +} +/^[\t ]*#[\t ]*define[\t _]*RLIM_NLIMITS[\t ]*[0-9][0-9]*/ { + limindex = index($0, "RLIM_") + limtail = substr($0, limindex, 80) + split(limtail, tmp) + nlimits = tmp[2] +} +# in case of GNU libc +/^[\t ]*RLIM_NLIMITS[\t ]*=[\t ]*RLIMIT_NLIMITS/ { + nlimits = limidx +} + +END { + if (limrev["MEMLOCK"] != "") { + irss = limrev["RSS"] + msg[irss] = "memoryuse" + } + ps = "%s" + + printf("%s\n%s\n\n", "/** rlimits.h **/", "/** architecture-customized limits for zsh **/") + printf("#define ZSH_NLIMITS %d\n\nstatic char *recs[ZSH_NLIMITS+1] = {\n", 0 + nlimits) + + for (i = 0; i < 0 + nlimits; i++) + if (msg[i] == "") { + badlimit++ + printf("\t%c%s%c,\n", 34, lim[i], 34) + } else + printf("\t%c%s%c,\n", 34, msg[i], 34) + print "\tNULL" + print "};" + print "" + exit(badlimit) +} diff --git a/Src/Builtins/rlimits.c b/Src/Builtins/rlimits.c new file mode 100644 index 000000000..20b8d663d --- /dev/null +++ b/Src/Builtins/rlimits.c @@ -0,0 +1,593 @@ +/* + * rlimits.c - resource limit builtins + * + * 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 "rlimits.mdh" +#include "rlimits.pro" + +#if defined(HAVE_GETRLIMIT) && defined(RLIM_INFINITY) + +/* Generated rec array containing limits required for the limit builtin. * + * They must appear in this array in numerical order of the RLIMIT_* macros. */ + +# include "rlimits.h" + +# if defined(RLIM_T_IS_QUAD_T) || defined(RLIM_T_IS_UNSIGNED) +static rlim_t +zstrtorlimt(const char *s, char **t, int base) +{ + rlim_t ret = 0; + + if (!base) + if (*s != '0') + base = 10; + else if (*++s == 'x' || *s == 'X') + base = 16, s++; + else + base = 8; + + if (base <= 10) + for (; *s >= '0' && *s < ('0' + base); s++) + ret = ret * base + *s - '0'; + else + for (; idigit(*s) || (*s >= 'a' && *s < ('a' + base - 10)) + || (*s >= 'A' && *s < ('A' + base - 10)); s++) + ret = ret * base + (idigit(*s) ? (*s - '0') : (*s & 0x1f) + 9); + if (t) + *t = (char *)s; + return ret; +} +# else /* !RLIM_T_IS_QUAD_T && !RLIM_T_IS_UNSIGNED */ +# define zstrtorlimt(a, b, c) zstrtol((a), (b), (c)) +# endif /* !RLIM_T_IS_QUAD_T && !RLIM_T_IS_UNSIGNED */ + +/* Display resource limits. hard indicates whether `hard' or `soft' * + * limits should be displayed. lim specifies the limit, or may be -1 * + * to show all. */ + +/**/ +static void +showlimits(int hard, int lim) +{ + int rt; + rlim_t val; + + /* main loop over resource types */ + for (rt = 0; rt != ZSH_NLIMITS; rt++) + if (rt == lim || lim == -1) { + /* display limit for resource number rt */ + printf("%-16s", recs[rt]); + val = (hard) ? limits[rt].rlim_max : limits[rt].rlim_cur; + if (val == RLIM_INFINITY) + printf("unlimited\n"); + else if (rt==RLIMIT_CPU) + /* time-type resource -- display as hours, minutes and + seconds. */ + printf("%d:%02d:%02d\n", (int)(val / 3600), + (int)(val / 60) % 60, (int)(val % 60)); +# ifdef RLIMIT_NPROC + else if (rt == RLIMIT_NPROC) + /* pure numeric resource */ + printf("%d\n", (int)val); +# endif /* RLIMIT_NPROC */ +# ifdef RLIMIT_NOFILE + else if (rt == RLIMIT_NOFILE) + /* pure numeric resource */ + printf("%d\n", (int)val); +# endif /* RLIMIT_NOFILE */ + else if (val >= 1024L * 1024L) + /* memory resource -- display with `K' or `M' modifier */ +# ifdef RLIM_T_IS_QUAD_T + printf("%qdMB\n", val / (1024L * 1024L)); + else + printf("%qdkB\n", val / 1024L); +# else + printf("%ldMB\n", val / (1024L * 1024L)); + else + printf("%ldkB\n", val / 1024L); +# endif /* RLIM_T_IS_QUAD_T */ + } +} + +/* Display a resource limit, in ulimit style. lim specifies which * + * limit should be displayed, and hard indicates whether the hard or * + * soft limit should be displayed. */ + +/**/ +static void +printulimit(int lim, int hard, int head) +{ + rlim_t limit; + + /* get the limit in question */ + limit = (hard) ? limits[lim].rlim_max : limits[lim].rlim_cur; + /* display the appropriate heading */ + switch (lim) { + case RLIMIT_CPU: + if (head) + printf("cpu time (seconds) "); + break; + case RLIMIT_FSIZE: + if (head) + printf("file size (blocks) "); + if (limit != RLIM_INFINITY) + limit /= 512; + break; + case RLIMIT_DATA: + if (head) + printf("data seg size (kbytes) "); + if (limit != RLIM_INFINITY) + limit /= 1024; + break; + case RLIMIT_STACK: + if (head) + printf("stack size (kbytes) "); + if (limit != RLIM_INFINITY) + limit /= 1024; + break; + case RLIMIT_CORE: + if (head) + printf("core file size (blocks) "); + if (limit != RLIM_INFINITY) + limit /= 512; + break; +# ifdef RLIMIT_RSS + case RLIMIT_RSS: + if (head) + printf("resident set size (kbytes) "); + if (limit != RLIM_INFINITY) + limit /= 1024; + break; +# endif /* RLIMIT_RSS */ +# ifdef RLIMIT_MEMLOCK + case RLIMIT_MEMLOCK: + if (head) + printf("locked-in-memory size (kb) "); + if (limit != RLIM_INFINITY) + limit /= 1024; + break; +# endif /* RLIMIT_MEMLOCK */ +# ifdef RLIMIT_NPROC + case RLIMIT_NPROC: + if (head) + printf("processes "); + break; +# endif /* RLIMIT_NPROC */ +# ifdef RLIMIT_NOFILE + case RLIMIT_NOFILE: + if (head) + printf("file descriptors "); + break; +# endif /* RLIMIT_NOFILE */ +# ifdef RLIMIT_VMEM + case RLIMIT_VMEM: + if (head) + printf("virtual memory size (kb) "); + if (limit != RLIM_INFINITY) + limit /= 1024; + break; +# endif /* RLIMIT_VMEM */ +# if defined RLIMIT_AS && RLIMIT_AS != RLIMIT_VMEM + case RLIMIT_AS: + if (head) + printf("address space (kb) "); + if (limit != RLIM_INFINITY) + limit /= 1024; + break; +# endif /* RLIMIT_AS */ +# ifdef RLIMIT_TCACHE + case RLIMIT_TCACHE: + if (head) + printf("cached threads "); + break; +# endif /* RLIMIT_TCACHE */ + } + /* display the limit */ + if (limit == RLIM_INFINITY) + printf("unlimited\n"); + else + printf("%ld\n", (long)limit); +} + +/* limit: set or show resource limits. The variable hard indicates * + * whether `hard' or `soft' resource limits are being set/shown. */ + +/**/ +static int +bin_limit(char *nam, char **argv, char *ops, int func) +{ + char *s; + int hard, limnum, lim; + rlim_t val; + int ret = 0; + + hard = ops['h']; + if (ops['s'] && !*argv) + return setlimits(NULL); + /* without arguments, display limits */ + if (!*argv) { + showlimits(hard, -1); + return 0; + } + while ((s = *argv++)) { + /* Search for the appropriate resource name. When a name matches (i.e. * + * starts with) the argument, the lim variable changes from -1 to the * + * number of the resource. If another match is found, lim goes to -2. */ + for (lim = -1, limnum = 0; limnum < ZSH_NLIMITS; limnum++) + if (!strncmp(recs[limnum], s, strlen(s))) { + if (lim != -1) + lim = -2; + else + lim = limnum; + } + /* lim==-1 indicates that no matches were found. * + * lim==-2 indicates that multiple matches were found. */ + if (lim < 0) { + zwarnnam("limit", + (lim == -2) ? "ambiguous resource specification: %s" + : "no such resource: %s", s, 0); + return 1; + } + /* without value for limit, display the current limit */ + if (!(s = *argv++)) { + showlimits(hard, lim); + return 0; + } + if (lim==RLIMIT_CPU) { + /* time-type resource -- may be specified as seconds, or minutes or * + * hours with the `m' and `h' modifiers, and `:' may be used to add * + * together more than one of these. It's easier to understand from * + * the code: */ + val = zstrtorlimt(s, &s, 10); + if (*s) + if ((*s == 'h' || *s == 'H') && !s[1]) + val *= 3600L; + else if ((*s == 'm' || *s == 'M') && !s[1]) + val *= 60L; + else if (*s == ':') + val = val * 60 + zstrtorlimt(s + 1, &s, 10); + else { + zwarnnam("limit", "unknown scaling factor: %s", s, 0); + return 1; + } + } +# ifdef RLIMIT_NPROC + else if (lim == RLIMIT_NPROC) + /* pure numeric resource -- only a straight decimal number is + permitted. */ + val = zstrtorlimt(s, &s, 10); +# endif /* RLIMIT_NPROC */ +# ifdef RLIMIT_NOFILE + else if (lim == RLIMIT_NOFILE) + /* pure numeric resource -- only a straight decimal number is + permitted. */ + val = zstrtorlimt(s, &s, 10); +# endif /* RLIMIT_NOFILE */ + else { + /* memory-type resource -- `k' and `M' modifiers are permitted, + meaning (respectively) 2^10 and 2^20. */ + val = zstrtorlimt(s, &s, 10); + if (!*s || ((*s == 'k' || *s == 'K') && !s[1])) + val *= 1024L; + else if ((*s == 'M' || *s == 'm') && !s[1]) + val *= 1024L * 1024; + else { + zwarnnam("limit", "unknown scaling factor: %s", s, 0); + return 1; + } + } + /* new limit is valid and has been interpreted; apply it to the + specified resource */ + if (hard) { + /* can only raise hard limits if running as root */ + if (val > current_limits[lim].rlim_max && geteuid()) { + zwarnnam("limit", "can't raise hard limits", NULL, 0); + return 1; + } else { + limits[lim].rlim_max = val; + if (val < limits[lim].rlim_cur) + limits[lim].rlim_cur = val; + } + } else if (val > limits[lim].rlim_max) { + zwarnnam("limit", "limit exceeds hard limit", NULL, 0); + return 1; + } else + limits[lim].rlim_cur = val; + if (ops['s'] && zsetlimit(lim, "limit")) + ret++; + } + return ret; +} + +/* unlimit: remove resource limits. Much of this code is the same as * + * that in bin_limit(). */ + +/**/ +static int +bin_unlimit(char *nam, char **argv, char *ops, int func) +{ + int hard, limnum, lim; + int ret = 0; + uid_t euid = geteuid(); + + hard = ops['h']; + /* Without arguments, remove all limits. */ + if (!*argv) { + for (limnum = 0; limnum != RLIM_NLIMITS; limnum++) { + if (hard) + if (euid && current_limits[limnum].rlim_max != RLIM_INFINITY) + ret++; + else + limits[limnum].rlim_max = RLIM_INFINITY; + else + limits[limnum].rlim_cur = limits[limnum].rlim_max; + } + if (ops['s']) + ret += setlimits(nam); + if (ret) + zwarnnam(nam, "can't remove hard limits", NULL, 0); + } else { + for (; *argv; argv++) { + /* Search for the appropriate resource name. When a name * + * matches (i.e. starts with) the argument, the lim variable * + * changes from -1 to the number of the resource. If another * + * match is found, lim goes to -2. */ + for (lim = -1, limnum = 0; limnum < ZSH_NLIMITS; limnum++) + if (!strncmp(recs[limnum], *argv, strlen(*argv))) { + if (lim != -1) + lim = -2; + else + lim = limnum; + } + /* lim==-1 indicates that no matches were found. * + * lim==-2 indicates that multiple matches were found. */ + if (lim < 0) { + zwarnnam(nam, + (lim == -2) ? "ambiguous resource specification: %s" + : "no such resource: %s", *argv, 0); + return 1; + } + /* remove specified limit */ + if (hard) + if (euid && current_limits[lim].rlim_max != RLIM_INFINITY) { + zwarnnam(nam, "can't remove hard limits", NULL, 0); + ret++; + } else + limits[lim].rlim_max = RLIM_INFINITY; + else + limits[lim].rlim_cur = limits[lim].rlim_max; + if (ops['s'] && zsetlimit(lim, nam)) + ret++; + } + } + return ret; +} + +/* ulimit: set or display resource limits */ + +/**/ +static int +bin_ulimit(char *name, char **argv, char *ops, int func) +{ + int res, resmask = 0, hard = 0, soft = 0, nres = 0; + char *options; + + do { + options = *argv; + if (options && *options == '-' && !options[1]) { + zwarnnam(name, "missing option letter", NULL, 0); + return 1; + } + res = -1; + if (options && *options == '-') { + argv++; + while (*++options) { + if(*options == Meta) + *++options ^= 32; + res = -1; + switch (*options) { + case 'H': + hard = 1; + continue; + case 'S': + soft = 1; + continue; + case 'a': + if (*argv || options[1] || resmask) { + zwarnnam(name, "no arguments required after -a", + NULL, 0); + return 1; + } + resmask = (1 << RLIM_NLIMITS) - 1; + nres = RLIM_NLIMITS; + continue; + case 't': + res = RLIMIT_CPU; + break; + case 'f': + res = RLIMIT_FSIZE; + break; + case 'd': + res = RLIMIT_DATA; + break; + case 's': + res = RLIMIT_STACK; + break; + case 'c': + res = RLIMIT_CORE; + break; +# ifdef RLIMIT_RSS + case 'm': + res = RLIMIT_RSS; + break; +# endif /* RLIMIT_RSS */ +# ifdef RLIMIT_MEMLOCK + case 'l': + res = RLIMIT_MEMLOCK; + break; +# endif /* RLIMIT_MEMLOCK */ +# ifdef RLIMIT_NOFILE + case 'n': + res = RLIMIT_NOFILE; + break; +# endif /* RLIMIT_NOFILE */ +# ifdef RLIMIT_NPROC + case 'u': + res = RLIMIT_NPROC; + break; +# endif /* RLIMIT_NPROC */ +# ifdef RLIMIT_VMEM + case 'v': + res = RLIMIT_VMEM; + break; +# endif /* RLIMIT_VMEM */ + default: + /* unrecognised limit */ + zwarnnam(name, "bad option: -%c", NULL, *options); + return 1; + } + if (options[1]) { + resmask |= 1 << res; + nres++; + } + } + } + if (!*argv || **argv == '-') { + if (res < 0) + if (*argv || nres) + continue; + else + res = RLIMIT_FSIZE; + resmask |= 1 << res; + nres++; + continue; + } + if (res < 0) + res = RLIMIT_FSIZE; + if (strcmp(*argv, "unlimited")) { + /* set limit to specified value */ + rlim_t limit; + + limit = zstrtorlimt(*argv, NULL, 10); + /* scale appropriately */ + switch (res) { + case RLIMIT_FSIZE: + case RLIMIT_CORE: + limit *= 512; + break; + case RLIMIT_DATA: + case RLIMIT_STACK: +# ifdef RLIMIT_RSS + case RLIMIT_RSS: +# endif /* RLIMIT_RSS */ +# ifdef RLIMIT_MEMLOCK + case RLIMIT_MEMLOCK: +# endif /* RLIMIT_MEMLOCK */ +# ifdef RLIMIT_VMEM + case RLIMIT_VMEM: +# endif /* RLIMIT_VMEM */ + limit *= 1024; + break; + } + if (hard) { + /* can't raise hard limit unless running as root */ + if (limit > current_limits[res].rlim_max && geteuid()) { + zwarnnam(name, "can't raise hard limits", NULL, 0); + return 1; + } + limits[res].rlim_max = limit; + if (limit < limits[res].rlim_cur) + limits[res].rlim_cur = limit; + } + if (!hard || soft) { + /* can't raise soft limit above hard limit */ + if (limit > limits[res].rlim_max) { + if (limit > current_limits[res].rlim_max && geteuid()) { + zwarnnam(name, "value exceeds hard limit", NULL, 0); + return 1; + } + limits[res].rlim_max = limits[res].rlim_cur = limit; + } else + limits[res].rlim_cur = limit; + } + } else { + /* remove specified limit */ + if (hard) { + /* can't remove hard limit unless running as root */ + if (current_limits[res].rlim_max != RLIM_INFINITY && geteuid()) { + zwarnnam(name, "can't remove hard limits", NULL, 0); + return 1; + } + limits[res].rlim_max = RLIM_INFINITY; + } + if (!hard || soft) + /* `removal' of soft limit means setting it equal to the + corresponding hard limit */ + limits[res].rlim_cur = limits[res].rlim_max; + } + if (zsetlimit(res, name)) + return 1; + argv++; + } while (*argv); + for (res = 0; res < RLIM_NLIMITS; res++, resmask >>= 1) + if (resmask & 1) + printulimit(res, hard, nres > 1); + return 0; +} + +#else /* !HAVE_GETRLIMIT || !RLIM_INFINITY */ + +# define bin_limit bin_notavail +# define bin_ulimit bin_notavail +# define bin_unlimit bin_notavail + +#endif /* !HAVE_GETRLIMIT || !RLIM_INFINITY */ + +static struct builtin bintab[] = { + BUILTIN("limit", 0, bin_limit, 0, -1, 0, "sh", NULL), + BUILTIN("ulimit", 0, bin_ulimit, 0, -1, 0, NULL, NULL), + BUILTIN("unlimit", 0, bin_unlimit, 0, -1, 0, "hs", NULL), +}; + +/**/ +int +boot_rlimits(Module m) +{ + return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); +} + +#ifdef MODULE + +/**/ +int +cleanup_rlimits(Module m) +{ + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} +#endif diff --git a/Src/Builtins/rlimits.mdd b/Src/Builtins/rlimits.mdd new file mode 100644 index 000000000..f0e41b73e --- /dev/null +++ b/Src/Builtins/rlimits.mdd @@ -0,0 +1,18 @@ +autobins="limit ulimit unlimit" + +objects="rlimits.o" + +:<<\Make +rlimits.o rlimits..o: rlimits.h + +# this file will not be made if limits are unavailable: +# silent so the warning doesn't appear unless necessary +rlimits.h: rlimits.awk @RLIMITS_INC_H@ + @echo '$(AWK) -f $(sdir)/rlimits.awk @RLIMITS_INC_H@ > rlimits.h'; \ + $(AWK) -f $(sdir)/rlimits.awk @RLIMITS_INC_H@ > rlimits.h || \ + echo WARNING: unknown limits: mail rlimits.h to developers + +clean-here: clean.rlimits +clean.rlimits: + rm -f rlimits.h +Make diff --git a/Src/Builtins/sched.c b/Src/Builtins/sched.c new file mode 100644 index 000000000..b4914899e --- /dev/null +++ b/Src/Builtins/sched.c @@ -0,0 +1,214 @@ +/* + * sched.c - execute commands at scheduled times + * + * 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 "sched.mdh" +#include "sched.pro" + +/* node in sched list */ + +typedef struct schedcmd *Schedcmd; + +struct schedcmd { + struct schedcmd *next; + char *cmd; /* command to run */ + time_t time; /* when to run it */ +}; + +/* the list of sched jobs pending */ + +static struct schedcmd *schedcmds; + +/**/ +static int +bin_sched(char *nam, char **argv, char *ops, int func) +{ + char *s = *argv++; + time_t t; + long h, m; + struct tm *tm; + struct schedcmd *sch, *sch2, *schl; + int sn; + + /* If the argument begins with a -, remove the specified item from the + schedule. */ + if (s && *s == '-') { + sn = atoi(s + 1); + + if (!sn) { + zwarnnam("sched", "usage for delete: sched -<item#>.", NULL, 0); + return 1; + } + for (schl = (struct schedcmd *)&schedcmds, sch = schedcmds, sn--; + sch && sn; sch = (schl = sch)->next, sn--); + if (!sch) { + zwarnnam("sched", "not that many entries", NULL, 0); + return 1; + } + schl->next = sch->next; + zsfree(sch->cmd); + zfree(sch, sizeof(struct schedcmd)); + + return 0; + } + + /* given no arguments, display the schedule list */ + if (!s) { + char tbuf[40]; + + for (sn = 1, sch = schedcmds; sch; sch = sch->next, sn++) { + t = sch->time; + tm = localtime(&t); + ztrftime(tbuf, 20, "%a %b %e %k:%M:%S", tm); + printf("%3d %s %s\n", sn, tbuf, sch->cmd); + } + return 0; + } else if (!*argv) { + /* other than the two cases above, sched * + *requires at least two arguments */ + zwarnnam("sched", "not enough arguments", NULL, 0); + return 1; + } + + /* The first argument specifies the time to schedule the command for. The + remaining arguments form the command. */ + if (*s == '+') { + /* + introduces a relative time. The rest of the argument is an + hour:minute offset from the current time. Once the hour and minute + numbers have been extracted, and the format verified, the resulting + offset is simply added to the current time. */ + h = zstrtol(s + 1, &s, 10); + if (*s != ':') { + zwarnnam("sched", "bad time specifier", NULL, 0); + return 1; + } + m = zstrtol(s + 1, &s, 10); + if (*s) { + zwarnnam("sched", "bad time specifier", NULL, 0); + return 1; + } + t = time(NULL) + h * 3600 + m * 60; + } else { + /* If there is no +, an absolute time of day must have been given. + This is in hour:minute format, optionally followed by a string starting + with `a' or `p' (for a.m. or p.m.). Characters after the `a' or `p' + are ignored. */ + h = zstrtol(s, &s, 10); + if (*s != ':') { + zwarnnam("sched", "bad time specifier", NULL, 0); + return 1; + } + m = zstrtol(s + 1, &s, 10); + if (*s && *s != 'a' && *s != 'A' && *s != 'p' && *s != 'P') { + zwarnnam("sched", "bad time specifier", NULL, 0); + return 1; + } + t = time(NULL); + tm = localtime(&t); + t -= tm->tm_sec + tm->tm_min * 60 + tm->tm_hour * 3600; + if (*s == 'p' || *s == 'P') + h += 12; + t += h * 3600 + m * 60; + /* If the specified time is before the current time, it must refer to + tomorrow. */ + if (t < time(NULL)) + t += 3600 * 24; + } + /* The time has been calculated; now add the new entry to the linked list + of scheduled commands. */ + sch = (struct schedcmd *) zcalloc(sizeof *sch); + sch->time = t; + PERMALLOC { + sch->cmd = zjoin(argv, ' '); + } LASTALLOC; + sch->next = NULL; + for (sch2 = (struct schedcmd *)&schedcmds; sch2->next; sch2 = sch2->next); + sch2->next = sch; + return 0; +} + +/* Check scheduled commands; call this function from time to time. */ + +/**/ +static void +checksched(void) +{ + time_t t; + struct schedcmd *sch, *schl; + + if(!schedcmds) + return; + t = time(NULL); + for (schl = (struct schedcmd *)&schedcmds, sch = schedcmds; sch; + sch = (schl = sch)->next) { + if (sch->time <= t) { + execstring(sch->cmd, 0, 0); + schl->next = sch->next; + zsfree(sch->cmd); + zfree(sch, sizeof(struct schedcmd)); + sch = schl; + } + } +} + +static void (*p_checksched) _((void)) = checksched; +static struct linknode n_checksched = { NULL, NULL, &p_checksched }; + +static struct builtin bintab[] = { + BUILTIN("sched", 0, bin_sched, 0, -1, 0, NULL, NULL), +}; + +/**/ +int +boot_sched(Module m) +{ + if(!addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab))) + return 1; + uaddlinknode(prepromptfns, &n_checksched); + return 0; +} + +#ifdef MODULE + +/**/ +int +cleanup_sched(Module m) +{ + struct schedcmd *sch, *schn; + + for (sch = schedcmds; sch; sch = schn) { + schn = sch->next; + zsfree(sch->cmd); + zfree(sch, sizeof(*sch)); + } + uremnode(prepromptfns, &n_checksched); + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} + +#endif diff --git a/Src/Builtins/sched.mdd b/Src/Builtins/sched.mdd new file mode 100644 index 000000000..6ed749f32 --- /dev/null +++ b/Src/Builtins/sched.mdd @@ -0,0 +1,3 @@ +autobins="sched" + +objects="sched.o" diff --git a/Src/Makefile.in b/Src/Makefile.in new file mode 100644 index 000000000..453159e17 --- /dev/null +++ b/Src/Makefile.in @@ -0,0 +1,195 @@ +# +# Makefile for Src subdirectory +# +# Copyright (c) 1995-1997 Richard Coleman +# 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 Richard Coleman 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 Richard Coleman and the Zsh Development Group have been advised of +# the possibility of such damage. +# +# Richard Coleman 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 Richard Coleman and the +# Zsh Development Group have no obligation to provide maintenance, +# support, updates, enhancements, or modifications. +# + +subdir = Src +dir_top = .. +SUBDIRS = + +@@version.mk@@ +@@defs.mk@@ + +sdir_src = $(sdir) +dir_src = . + +# ========= DEPENDENCIES FOR BUILDING ========== + +LINK = $(CC) $(LDFLAGS) $(EXELDFLAGS) $(EXTRA_LDFLAGS) -o $@ +DLLINK = $(DLLD) $(LDFLAGS) $(LIBLDFLAGS) $(DLLDFLAGS) -o $@ + +all: bin modules + +bin: zsh + +modules: headers + +MAIN_OBJS = main.o + +LSTMP = +LLIST = +NSTMP = stamp-modobjs +NLIST = `cat stamp-modobjs` + +LIBZSH = libzsh-$(VERSION).$(DL_EXT) +NIBZSH = + +LDRUNPATH = LD_RUN_PATH=$(libdir)/zsh +NDRUNPATH = + +zsh: $(@L@IBZSH) $(@L@STMP) $(MAIN_OBJS) + rm -f $@ + $(@L@DRUNPATH) $(LINK) $(MAIN_OBJS) $(@L@LIST) $(@L@IBZSH) $(LIBS) + +$(LIBZSH): $(LIBOBJS) $(NSTMP) + rm -f $@ + $(DLLINK) $(LIBOBJS) $(NLIST) + +stamp-modobjs: modobjs + @if cmp -s stamp-modobjs.tmp stamp-modobjs; then \ + rm -f stamp-modobjs.tmp; \ + echo "\`stamp-modobjs' is up to date."; \ + else \ + mv -f stamp-modobjs.tmp stamp-modobjs; \ + echo "Updated \`stamp-modobjs'."; \ + fi + +modobjs: headers rm-modobjs-tmp + +rm-modobjs-tmp: + rm -f stamp-modobjs.tmp + +Makemod modules.index prep: modules-bltin + ( cd $(sdir_top) && $(SHELL) $(subdir)/mkmodindex.sh $(subdir) ) \ + > modules.index.tmp + @if cmp -s modules.index.tmp modules.index; then \ + rm -f modules.index.tmp; \ + echo "\`modules.index' is up to date."; \ + else \ + mv -f modules.index.tmp modules.index; \ + echo "Updated \`modules.index'."; \ + fi + @case $(sdir_top) in \ + /*) top_srcdir=$(sdir_top) ;; \ + *) top_srcdir=$(subdir)/$(sdir_top) ;; \ + esac; \ + export top_srcdir; \ + echo 'cd $(dir_top) && $(SHELL)' \ + '$$top_srcdir/$(subdir)/mkmakemod.sh $(subdir) Makemod'; \ + cd $(dir_top) && \ + $(SHELL) $$top_srcdir/$(subdir)/mkmakemod.sh $(subdir) Makemod + @$(MAKE) -f Makemod $(MAKEDEFS) prep || rm -f Makemod + +FORCE: + +# ========== LINKING IN MODULES ========== + +modules-bltin: + if test @D@ = N; then \ + cat $(sdir)/xmods.conf > $@; \ + elif test @RTLD_GLOBAL_OK@ != yes; then \ + echo comp1 > $@; \ + else \ + echo > $@; \ + fi + +# ========== ANSI TO K&R CONVERSION ========== + +ANSI_KNR = ansi2knr +ANSIKNR = + +Makemod: $(ANSI@U@KNR) + +ansi2knr.o: ansi2knr.c + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(sdir)/ansi2knr.c + +ansi2knr: ansi2knr.o + rm -f $@ + $(CC) $(LDFLAGS) $(EXELDFLAGS) -o $@ ansi2knr.o + +# ========== DEPENDENCIES FOR INSTALLING ========== + +install: install.bin install.modules +uninstall: uninstall.bin uninstall.modules + +install.bin: install.bin-here +uninstall.bin: uninstall.bin-here + +# install binary, creating install directory if necessary +install.bin-here: zsh install.bin-@L@ + $(sdir_top)/mkinstalldirs $(bindir) + $(INSTALL_PROGRAM) zsh $(bindir)/zsh-$(VERSION) + if test -f $(bindir)/zsh; then \ + rm -f $(bindir)/zsh.old; \ + ln $(bindir)/zsh $(bindir)/zsh.old; \ + else :; fi + rm -f $(bindir)/zsh.new + ln $(bindir)/zsh-$(VERSION) $(bindir)/zsh.new + mv $(bindir)/zsh.new $(bindir)/zsh + +install.bin-N: +install.bin-L: $(LIBZSH) + $(sdir_top)/mkinstalldirs $(libdir)/zsh + $(INSTALL_PROGRAM) $(LIBZSH) $(libdir)/zsh/$(LIBZSH) + +# uninstall binary +uninstall.bin-here: uninstall.bin-@L@ + rm -f $(bindir)/zsh-$(VERSION) $(bindir)/zsh + +uninstall.bin-N: +uninstall.bin-L: + rm -f $(libdir)/zsh/$(LIBZSH) + +# ========== DEPENDENCIES FOR CLEANUP ========== + +@@clean.mk@@ + +mostlyclean-here: + rm -f stamp-modobjs stamp-modobjs.tmp + +clean-here: + rm -f modules.index.tmp modules.stamp zsh ansi2knr.o ansi2knr + rm -f libzsh-*.$(DL_EXT) + +distclean-here: + rm -f TAGS tags + rm -f modules.index modules-bltin Makefile + +mostlyclean: mostlyclean-modules +clean: clean-modules +distclean: distclean-modules +realclean: realclean-modules + +mostlyclean-modules clean-modules distclean-modules realclean-modules: Makemod + @$(MAKE) -f Makemod $(MAKEDEFS) `echo $@ | sed 's/-modules//'` + +# ========== RECURSIVE MAKES ========== + +install.modules uninstall.modules \ +modobjs modules headers proto $(MAIN_OBJS): Makemod + @$(MAKE) -f Makemod $(MAKEDEFS) $@ + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +@@config.mk@@ diff --git a/Src/Makemod.in.in b/Src/Makemod.in.in new file mode 100644 index 000000000..f27a6bd57 --- /dev/null +++ b/Src/Makemod.in.in @@ -0,0 +1,180 @@ +# +# Makemod.in.in +# +# Copyright (c) 1995-1997 Richard Coleman +# 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 Richard Coleman 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 Richard Coleman and the Zsh Development Group have been advised of +# the possibility of such damage. +# +# Richard Coleman 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 Richard Coleman and the +# Zsh Development Group have no obligation to provide maintenance, +# support, updates, enhancements, or modifications. +# + +# ========== OVERRIDABLE VARIABLES ========== + +# subdir is done by mkmakemod.sh +# dir_top is done by mkmakemod.sh +# SUBDIRS is done by mkmakemod.sh + +@@version.mk@@ +@@defs.mk@@ + +sdir_src = $(sdir_top)/Src +dir_src = $(dir_top)/Src + +# ========== COMPILATION RULES ========== + +DNCFLAGS = + +COMPILE = $(CC) -c -I. $(CPPFLAGS) $(DEFS) $(CFLAGS) $(D@L@CFLAGS) +DLCOMPILE = $(CC) -c -I. $(CPPFLAGS) $(DEFS) -DMODULE $(CFLAGS) $(DLCFLAGS) +LINK = $(CC) $(LDFLAGS) $(EXELDFLAGS) $(EXTRA_LDFLAGS) -o $@ +DLLINK = $(DLLD) $(LDFLAGS) $(LIBLDFLAGS) $(DLLDFLAGS) -o $@ + +KNR_OBJ=.o +KNROBJ=._foo_ + +ANSIOBJ=.o +ANSI_OBJ=._foo_ + +.SUFFIXES: .c .$(DL_EXT) ..o .._foo_ .o ._foo_ .pro + +.c$(ANSI@U@OBJ): + $(COMPILE) -o $@ $< + @rm -f $(dir_src)/stamp-modobjs + +.c$(KNR@U@OBJ): + $(dir_src)/ansi2knr $< > $@.c + $(COMPILE) -o $@ $@.c + rm -f $@.c + @rm -f $(dir_src)/stamp-modobjs + +.c.$(ANSI@U@OBJ): + $(DLCOMPILE) -o $@ $< + +.c.$(KNR@U@OBJ): + $(dir_src)/ansi2knr $< > $@.c + $(DLCOMPILE) -o $@ $@.c + rm -f $@.c + +.c.pro: + $(AWK) -f $(sdir_src)/makepro.awk $< $(subdir) > $@ + +PROTODEPS = $(sdir_src)/makepro.awk + +# ========== DEPENDENCIES FOR BUILDING ========== + +all: modobjs modules + +modobjs: $(MODOBJS) +modules: $(MODULES) +headers: $(MDHS) +proto: $(PROTOS) + +prep: + @case $(sdir_top) in \ + /*) top_srcdir=$(sdir_top) ;; \ + *) top_srcdir=$(subdir)/$(sdir_top) ;; \ + esac; \ + export top_srcdir; \ + cd $(dir_top) || exit 1; \ + subdirs='$(SUBDIRS)'; \ + for subdir in $$subdirs; do \ + dir=$(subdir)/$$subdir; \ + test -d $$dir || mkdir $$dir; \ + $(SHELL) $$top_srcdir/Src/mkmakemod.sh $$dir Makefile || exit 1; \ + ( cd $$dir && $(MAKE) $(MAKEDEFS) $@ ) || exit 1; \ + done + +headers prep: $(dir_src)/modules.stamp +$(dir_src)/modules.stamp: $(MDDS) + echo 'timestamp for *.mdd files' > $@ + +FORCE: + +# ========== DEPENDENCIES FOR INSTALLING ========== + +install: install.bin install.modules +uninstall: uninstall.bin uninstall.modules + +install.bin: install.bin-here +uninstall.bin: uninstall.bin-here +install.modules: install.modules-here +uninstall.modules: uninstall.modules-here + +install.bin-here uninstall.bin-here: + +install.modules-here: + $(sdir_top)/mkinstalldirs $(MODDIR) + modules='$(MODULES)'; for mod in $$modules; do \ + $(INSTALL_PROGRAM) $$mod $(MODDIR)/$$mod; \ + done + +uninstall.modules-here: + modules='$(MODULES)'; for mod in $$modules; do \ + if test -f $(MODDIR)/$$mod; then \ + rm -f $(MODDIR)/$$mod; \ + else :; fi; \ + done + +# ========== DEPENDENCIES FOR CLEANUP ========== + +@@clean.mk@@ + +mostlyclean-here: + rm -f *.o *.$(DL_EXT) + +clean-here: + rm -f *.o.c *.pro *.mdh *.mdhi *.mdhs *.mdh.tmp + +distclean-here: + rm -f $(makefile) $(makefile).in + +# ========== RECURSIVE MAKES ========== + +install.bin uninstall.bin install.modules uninstall.modules \ +modobjs modules headers proto: + @subdirs='$(SUBDIRS)'; for subdir in $$subdirs; do \ + ( cd $$subdir && $(MAKE) $(MAKEDEFS) $@ ) || exit 1; \ + done + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +$(makefile): $(makefile).in $(dir_top)/config.status + @case $(sdir_top) in \ + /*) top_srcdir=$(sdir_top) ;; \ + *) top_srcdir=$(subdir)/$(sdir_top) ;; \ + esac; \ + export top_srcdir; \ + echo 'cd $(dir_top) && $(SHELL)' \ + '$$top_srcdir/Src/mkmakemod.sh -m $(subdir) $(makefile)'; \ + cd $(dir_top) && \ + $(SHELL) $$top_srcdir/Src/mkmakemod.sh -m $(subdir) $(makefile) + +$(makefile).in: $(sdir_src)/mkmakemod.sh $(sdir_src)/Makemod.in.in $(MDDS) $(dir_src)/modules-bltin + @case $(sdir_top) in \ + /*) top_srcdir=$(sdir_top) ;; \ + *) top_srcdir=$(subdir)/$(sdir_top) ;; \ + esac; \ + export top_srcdir; \ + echo 'cd $(dir_top) && $(SHELL)' \ + '$$top_srcdir/Src/mkmakemod.sh -i $(subdir) $(makefile)'; \ + cd $(dir_top) && \ + $(SHELL) $$top_srcdir/Src/mkmakemod.sh -i $(subdir) $(makefile) + +$(dir_src)/modules-bltin: + @cd $(dir_src) && $(MAKE) $(MAKEDEFS) modules-bltin diff --git a/Src/Modules/.cvsignore b/Src/Modules/.cvsignore new file mode 100644 index 000000000..169be5ef9 --- /dev/null +++ b/Src/Modules/.cvsignore @@ -0,0 +1,10 @@ +Makefile +Makefile.in +*.pro +*.o +*.o.c +*.so +*.mdh +*.mdhi +*.mdhs +*.mdh.tmp diff --git a/Src/Modules/.distfiles b/Src/Modules/.distfiles new file mode 100644 index 000000000..4c98f97ea --- /dev/null +++ b/Src/Modules/.distfiles @@ -0,0 +1,8 @@ +DISTFILES_SRC=' + .cvsignore .distfiles .exrc + cap.mdd cap.c + clone.mdd clone.c + example.mdd example.c + files.mdd files.c + stat.mdd stat.c +' diff --git a/Src/Modules/.exrc b/Src/Modules/.exrc new file mode 100644 index 000000000..91d0b39ef --- /dev/null +++ b/Src/Modules/.exrc @@ -0,0 +1,2 @@ +set ai +set sw=4 diff --git a/Src/Modules/cap.c b/Src/Modules/cap.c new file mode 100644 index 000000000..008b6932d --- /dev/null +++ b/Src/Modules/cap.c @@ -0,0 +1,141 @@ +/* + * cap.c - POSIX.1e (POSIX.6) capability set manipulation + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1997 Andrew Main + * 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 Andrew Main 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 Andrew Main and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Andrew Main 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 Andrew Main and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "cap.mdh" +#include "cap.pro" + +#ifdef HAVE_CAP_GET_PROC + +static int +bin_cap(char *nam, char **argv, char *ops, int func) +{ + int ret = 0; + cap_t caps; + if(*argv) { + caps = cap_from_text(*argv); + if(!caps) { + zwarnnam(nam, "invalid capability string", NULL, 0); + return 1; + } + if(cap_set_proc(caps)) { + zwarnnam(nam, "can't change capabilites: %e", NULL, errno); + ret = 1; + } + } else { + char *result; + ssize_t length; + caps = cap_get_proc(); + if(caps) + result = cap_to_text(caps, &length); + if(!caps || !result) { + zwarnnam(nam, "can't get capabilites: %e", NULL, errno); + ret = 1; + } else + puts(result); + } + cap_free(&caps); + return ret; +} + +static int +bin_getcap(char *nam, char **argv, char *ops, int func) +{ + int ret = 0; + + do { + char *result; + ssize_t length; + cap_t caps = cap_get_file(*argv); + if(caps) + result = cap_to_text(caps, &length); + if (!caps || !result) { + zwarnnam(nam, "%s: %e", *argv, errno); + ret = 1; + } else + printf("%s %s\n", *argv, result); + cap_free(&caps); + } while(*++argv); + return ret; +} + +static int +bin_setcap(char *nam, char **argv, char *ops, int func) +{ + cap_t caps; + int ret = 0; + + caps = cap_from_text(*argv++); + if(!caps) { + zwarnnam(nam, "invalid capability string", NULL, 0); + return 1; + } + + do { + if(cap_set_file(*argv, caps)) { + zwarnnam(nam, "%s: %e", *argv, errno); + ret = 1; + } + } while(*++argv); + cap_free(&caps); + return ret; +} + +#else /* !HAVE_CAP_GET_PROC */ + +# define bin_cap bin_notavail +# define bin_getcap bin_notavail +# define bin_setcap bin_notavail + +#endif /* !HAVE_CAP_GET_PROC */ + +/* module paraphernalia */ + +static struct builtin bintab[] = { + BUILTIN("cap", 0, bin_cap, 0, 1, 0, NULL, NULL), + BUILTIN("getcap", 0, bin_getcap, 1, -1, 0, NULL, NULL), + BUILTIN("setcap", 0, bin_setcap, 2, -1, 0, NULL, NULL), +}; + +/**/ +int +boot_cap(Module m) +{ + return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); +} + +#ifdef MODULE + +/**/ +int +cleanup_cap(Module m) +{ + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} +#endif diff --git a/Src/Modules/cap.mdd b/Src/Modules/cap.mdd new file mode 100644 index 000000000..97f377e9d --- /dev/null +++ b/Src/Modules/cap.mdd @@ -0,0 +1,3 @@ +autobins="cap getcap setcap" + +objects="cap.o" diff --git a/Src/Modules/clone.c b/Src/Modules/clone.c new file mode 100644 index 000000000..11387fc90 --- /dev/null +++ b/Src/Modules/clone.c @@ -0,0 +1,115 @@ +/* + * clone.c - start a forked instance of the current shell on a new terminal + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1997 Zoltán Hidvégi + * 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 Zoltán Hidvégi 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 Zoltán Hidvégi and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Zoltán Hidvégi 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 Zoltán Hidvégi and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +/* + * The clone builtin can be used to start a forked instance of the current + * shell on a new terminal. The only argument to the builtin is the name + * of the new terminal. In the new shell the PID, PPID and TTY parameters + * are changed appropriately. $! is set to zero in the new instance of the + * shell and to the pid of the new instance in the original shell. + * + */ + +#include "clone.mdh" +#include "clone.pro" + +/**/ +static int +bin_clone(char *nam, char **args, char *ops, int func) +{ + int ttyfd, pid; + + unmetafy(*args, NULL); + ttyfd = open(*args, O_RDWR|O_NOCTTY); + if (ttyfd < 0) { + zwarnnam(nam, "%s: %e", *args, errno); + return 1; + } + pid = fork(); + if (!pid) { + clearjobtab(); + ppid = getppid(); + mypid = getpid(); +#ifdef HAVE_SETSID + if (setsid() != mypid) { + zwarnnam(nam, "failed to create new session: %e", NULL, errno); +#endif +#ifdef TIOCNOTTY + if (ioctl(SHTTY, TIOCNOTTY)) + zwarnnam(nam, "%e", NULL, errno); + setpgrp(0L, mypid); +#endif +#ifdef HAVE_SETSID + } +#endif + if (ttyfd) { + close(0); + dup(ttyfd); + } else + ttyfd = -1; + close(1); + close(2); + dup(0); + dup(0); + closem(0); + close(coprocin); + close(coprocout); + init_io(); + setsparam("TTY", ztrdup(ttystrname)); + } + close(ttyfd); + if (pid < 0) { + zerrnam(nam, "fork failed: %e", NULL, errno); + return 1; + } + lastpid = pid; + return 0; +} + +static struct builtin bintab[] = { + BUILTIN("clone", 0, bin_clone, 1, 1, 0, NULL, NULL), +}; + +/**/ +int +boot_clone(Module m) +{ + return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); +} + +#ifdef MODULE + +/**/ +int +cleanup_clone(Module m) +{ + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} +#endif diff --git a/Src/Modules/clone.mdd b/Src/Modules/clone.mdd new file mode 100644 index 000000000..5277d3151 --- /dev/null +++ b/Src/Modules/clone.mdd @@ -0,0 +1,3 @@ +autobins="clone" + +objects="clone.o" diff --git a/Src/Modules/example.c b/Src/Modules/example.c new file mode 100644 index 000000000..45ef3c28f --- /dev/null +++ b/Src/Modules/example.c @@ -0,0 +1,76 @@ +/* + * example.c - an example module for zsh + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1996-1997 Zoltán Hidvégi + * 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 Zoltán Hidvégi 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 Zoltán Hidvégi and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Zoltán Hidvégi 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 Zoltán Hidvégi and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "example.mdh" +#include "example.pro" + +/**/ +static int +bin_example(char *nam, char **args, char *ops, int func) +{ + unsigned char c; + + printf("Options: "); + for (c = 32; ++c < 128;) + if (ops[c]) + putchar(c); + printf("\nArguments:"); + for (; *args; args++) { + putchar(' '); + fputs(*args, stdout); + } + printf("\nName: %s\n", nam); + return 0; +} + +/* + * boot_example is executed when the module is loaded. + */ + +static struct builtin bintab[] = { + BUILTIN("example", 0, bin_example, 0, -1, 0, "flags", NULL), +}; + +/**/ +int +boot_example(Module m) +{ + return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); +} + +#ifdef MODULE + +/**/ +int +cleanup_example(Module m) +{ + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} +#endif diff --git a/Src/Modules/example.mdd b/Src/Modules/example.mdd new file mode 100644 index 000000000..89f12097c --- /dev/null +++ b/Src/Modules/example.mdd @@ -0,0 +1,3 @@ +autobins="example" + +objects="example.o" diff --git a/Src/Modules/files.c b/Src/Modules/files.c new file mode 100644 index 000000000..6127c5524 --- /dev/null +++ b/Src/Modules/files.c @@ -0,0 +1,528 @@ +/* + * files.c - file operation builtins + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1996-1997 Andrew Main + * 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 Andrew Main 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 Andrew Main and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Andrew Main 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 Andrew Main and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "files.mdh" + +typedef int (*MoveFunc) _((char const *, char const *)); + +#ifndef STDC_HEADERS +extern int link _((const char *, const char *)); +extern int symlink _((const char *, const char *)); +extern int rename _((const char *, const char *)); +#endif + +#include "files.pro" + +/**/ +static int +ask(void) +{ + int a = getchar(), c; + for(c = a; c != EOF && c != '\n'; ) + c = getchar(); + return a == 'y' || a == 'Y'; +} + +/* sync builtin */ + +/**/ +static int +bin_sync(char *nam, char **args, char *ops, int func) +{ + sync(); + return 0; +} + +/* mkdir builtin */ + +/**/ +static int +bin_mkdir(char *nam, char **args, char *ops, int func) +{ + mode_t oumask = umask(0); + mode_t mode = 0777 & ~oumask; + int err = 0; + + umask(oumask); + if(ops['m']) { + char *str = *args++, *ptr; + + if(!*args) { + zwarnnam(nam, "not enough arguments", NULL, 0); + return 1; + } + mode = zstrtol(str, &ptr, 8); + if(!*str || *ptr) { + zwarnnam(nam, "invalid mode `%s'", str, 0); + return 1; + } + } + for(; *args; args++) { + char *ptr = strchr(*args, 0); + + while(ptr > *args + (**args == '/') && *--ptr == '/') + *ptr = 0; + if(ztrlen(*args) > PATH_MAX - 1) { + zwarnnam(nam, "%s: %e", *args, ENAMETOOLONG); + err = 1; + continue; + } + if(ops['p']) { + char *ptr = *args; + + for(;;) { + while(*ptr == '/') + ptr++; + while(*ptr && *ptr != '/') + ptr++; + if(!*ptr) { + err |= domkdir(nam, *args, mode, 1); + break; + } else { + int e; + + *ptr = 0; + e = domkdir(nam, *args, mode | 0300, 1); + if(e) { + err = 1; + break; + } + *ptr = '/'; + } + } + } else + err |= domkdir(nam, *args, mode, 0); + } + return err; +} + +/**/ +static int +domkdir(char *nam, char *path, mode_t mode, int p) +{ + int err; + mode_t oumask; + char const *rpath = unmeta(path); + + if(p) { + struct stat st; + + if(!lstat(rpath, &st) && S_ISDIR(st.st_mode)) + return 0; + } + oumask = umask(0); + err = mkdir(path, mode) ? errno : 0; + umask(oumask); + if(!err) + return 0; + zwarnnam(nam, "cannot make directory `%s': %e", path, err); + return 1; +} + +/* rmdir builtin */ + +/**/ +static int +bin_rmdir(char *nam, char **args, char *ops, int func) +{ + int err = 0; + + for(; *args; args++) { + char *rpath = unmeta(*args); + + if(!rpath) { + zwarnnam(nam, "%s: %e", *args, ENAMETOOLONG); + err = 1; + } else if(rmdir(rpath)) { + zwarnnam(nam, "cannot remove directory `%s': %e", *args, errno); + err = 1; + } + } + return err; +} + +/* ln and mv builtins */ + +#define BIN_LN 0 +#define BIN_MV 1 + +#define MV_NODIRS (1<<0) +#define MV_FORCE (1<<1) +#define MV_INTER (1<<2) +#define MV_ASKNW (1<<3) +#define MV_ATOMIC (1<<4) + +/* bin_ln actually does three related jobs: hard linking, symbolic * + * linking, and renaming. If called as mv it renames, otherwise * + * it looks at the -s option. If hard linking, it will refuse to * + * attempt linking to a directory unless the -d option is given. */ + +/**/ +static int +bin_ln(char *nam, char **args, char *ops, int func) +{ + MoveFunc move; + int flags, space, err = 0; + char **a, *ptr, *rp; + struct stat st; + char buf[PATH_MAX * 2 + 1]; + + + if(func == BIN_MV) { + move = rename; + flags = ops['f'] ? 0 : MV_ASKNW; + flags |= MV_ATOMIC; + } else { + flags = ops['f'] ? MV_FORCE : 0; +#ifdef HAVE_LSTAT + if(ops['s']) + move = symlink; + else +#endif + { + move = link; + if(!ops['d']) + flags |= MV_NODIRS; + } + } + if(ops['i'] && !ops['f']) + flags |= MV_INTER; + for(a = args; a[1]; a++) ; + if(a != args) { + rp = unmeta(*a); + if(rp && !stat(rp, &st) && S_ISDIR(st.st_mode)) + goto havedir; + } + if(a > args+1) { + zwarnnam(nam, "last of many arguments must be a directory", NULL, 0); + return 1; + } + if(!args[1]) { + ptr = strrchr(args[0], '/'); + if(ptr) + args[1] = ptr+1; + else + args[1] = args[0]; + } + return domove(nam, move, args[0], args[1], flags); + havedir: + strcpy(buf, *a); + *a = NULL; + space = PATH_MAX - 1 - ztrlen(buf); + rp = strchr(buf, 0); + *rp++ = '/'; + for(; *args; args++) { + if(ztrlen(*args) > PATH_MAX) { + zwarnnam(nam, "%s: %e", *args, ENAMETOOLONG); + err = 1; + continue; + } + ptr = strrchr(*args, '/'); + if(ptr) + ptr++; + else + ptr = *args; + if(ztrlen(ptr) > space) { + zwarnnam(nam, "%s: %e", ptr, ENAMETOOLONG); + err = 1; + continue; + } + strcpy(rp, ptr); + err |= domove(nam, move, *args, buf, flags); + } + return err; +} + +/**/ +static int +domove(char *nam, MoveFunc move, char *p, char *q, int flags) +{ + struct stat st; + char *qbuf; + char pbuf[PATH_MAX + 1]; + strcpy(pbuf, unmeta(p)); + qbuf = unmeta(q); + if(flags & MV_NODIRS) { + errno = EISDIR; + if(lstat(pbuf, &st) || S_ISDIR(st.st_mode)) { + zwarnnam(nam, "%s: %e", p, errno); + return 1; + } + } + if(!lstat(qbuf, &st)) { + int doit = flags & MV_FORCE; + if(S_ISDIR(st.st_mode)) { + zwarnnam(nam, "%s: cannot overwrite directory", q, 0); + return 1; + } else if(flags & MV_INTER) { + nicezputs(nam, stderr); + fputs(": replace `", stderr); + nicezputs(q, stderr); + fputs("'? ", stderr); + fflush(stderr); + if(!ask()) + return 0; + doit = 1; + } else if((flags & MV_ASKNW) && + !S_ISLNK(st.st_mode) && + access(qbuf, W_OK)) { + nicezputs(nam, stderr); + fputs(": replace `", stderr); + nicezputs(q, stderr); + fprintf(stderr, "', overriding mode %04o? ", + mode_to_octal(st.st_mode)); + fflush(stderr); + if(!ask()) + return 0; + doit = 1; + } + if(doit && !(flags & MV_ATOMIC)) + unlink(qbuf); + } + if(move(pbuf, qbuf)) { + zwarnnam(nam, "%s: %e", p, errno); + return 1; + } + return 0; +} + +/* rm builtin */ + +/**/ +static int +bin_rm(char *nam, char **args, char *ops, int func) +{ + int err = 0, len; + char *rp, *s; + struct dirsav ds; + + ds.ino = ds.dev = 0; + ds.dirname = NULL; + ds.dirfd = ds.level = -1; + if (ops['r'] || ops['s']) { + if ((ds.dirfd = open(".", O_RDONLY|O_NOCTTY)) < 0 && + zgetdir(&ds) && *ds.dirname != '/') + ds.dirfd = open("..", O_RDONLY|O_NOCTTY); + } + for(; !errflag && !(err & 2) && *args; args++) { + rp = ztrdup(*args); + unmetafy(rp, &len); + if (ops['s']) { + s = strrchr(rp, '/'); + if (s && !s[1]) { + while (*s == '/' && s > rp) + *s-- = '\0'; + while (*s != '/' && s > rp) + s--; + } + if (s && s[1]) { + int e; + + *s = '\0'; + e = lchdir(s > rp ? rp : "/", &ds, 1); + err |= -e; + if (!e) { + struct dirsav d; + + d.ino = d.dev = 0; + d.dirname = NULL; + d.dirfd = d.level = -1; + err |= dorm(nam, *args, s + 1, ops, &d, 0); + zsfree(d.dirname); + if (restoredir(&ds)) + err |= 2; + } else + zwarnnam(nam, "%s: %e", *args, errno); + } else + err |= dorm(nam, *args, rp, ops, &ds, 0); + } else + err |= dorm(nam, *args, rp, ops, &ds, 1); + zfree(rp, len + 1); + } + if ((err & 2) && ds.dirfd >= 0 && restoredir(&ds) && zchdir(pwd)) { + zsfree(pwd); + pwd = ztrdup("/"); + chdir(pwd); + } + if (ds.dirfd >= 0) + close(ds.dirfd); + zsfree(ds.dirname); + return ops['f'] ? 0 : !!err; +} + +/**/ +static int +dorm(char *nam, char *arg, char *rp, char *ops, struct dirsav *ds, int first) +{ + struct stat st; + + if((!ops['d'] || !ops['f']) && !lstat(rp, &st)) { + if(!ops['d'] && S_ISDIR(st.st_mode)) { + if(ops['r']) + return dormr(nam, arg, rp, ops, ds, first); + if(!ops['f']) + zwarnnam(nam, "%s: %e", arg, EISDIR); + return 1; + } + if(!ops['f'] && ops['i']) { + nicezputs(nam, stderr); + fputs(": remove `", stderr); + nicezputs(arg, stderr); + fputs("'? ", stderr); + fflush(stderr); + if(!ask()) + return 0; + } else if(!ops['f'] && + !S_ISLNK(st.st_mode) && + access(rp, W_OK)) { + nicezputs(nam, stderr); + fputs(": remove `", stderr); + nicezputs(arg, stderr); + fprintf(stderr, "', overriding mode %04o? ", + mode_to_octal(st.st_mode)); + fflush(stderr); + if(!ask()) + return 0; + } + } + if(!unlink(rp)) + return 0; + if(!ops['f']) + zwarnnam(nam, "%s: %e", arg, errno); + return 1; +} + +/**/ +static int +dormr(char *nam, char *arg, char *rp, char *ops, struct dirsav *ds, int first) +{ + char *fn; + DIR *d; + int err; + struct dirsav dsav; + char *files = NULL; + int fileslen = 0; + + err = -lchdir(rp, ds, !first); + if (err) { + if (!ops['f']) + zwarnnam(nam, "%s: %e", arg, errno); + return err; + } + + dsav.ino = dsav.dev = 0; + dsav.dirname = NULL; + dsav.dirfd = dsav.level = -1; + d = opendir("."); + if(!d) { + if(!ops['f']) + zwarnnam(nam, "%s: %e", arg, errno); + err = 1; + } else { + int arglen = strlen(arg) + 1; + + while (!errflag && (fn = zreaddir(d, 1))) { + int l = strlen(fn) + 1; + files = hrealloc(files, fileslen, fileslen + l); + strcpy(files + fileslen, fn); + fileslen += l; + } + closedir(d); + for (fn = files; !errflag && !(err & 2) && fn < files + fileslen;) { + int l = strlen(fn) + 1; + VARARR(char, narg, arglen + l); + + strcpy(narg,arg); + narg[arglen-1] = '/'; + strcpy(narg + arglen, fn); + unmetafy(fn, NULL); + err |= dorm(nam, narg, fn, ops, &dsav, 0); + fn += l; + } + hrealloc(files, fileslen, 0); + } + zsfree(dsav.dirname); + if (err & 2) + return 2; + if (restoredir(ds)) { + if(!ops['f']) + zwarnnam(nam, "failed to return to previous directory: %e", + NULL, errno); + return 2; + } + if(!ops['f'] && ops['i']) { + nicezputs(nam, stderr); + fputs(": remove `", stderr); + nicezputs(arg, stderr); + fputs("'? ", stderr); + fflush(stderr); + if(!ask()) + return err; + } + if(!rmdir(rp)) + return err; + if(!ops['f']) + zwarnnam(nam, "%s: %e", arg, errno); + return 1; +} + +/* module paraphernalia */ + +#ifdef HAVE_LSTAT +# define LN_OPTS "dfis" +#else +# define LN_OPTS "dfi" +#endif + +static struct builtin bintab[] = { + BUILTIN("ln", 0, bin_ln, 1, -1, BIN_LN, LN_OPTS, NULL), + BUILTIN("mkdir", 0, bin_mkdir, 1, -1, 0, "pm", NULL), + BUILTIN("mv", 0, bin_ln, 2, -1, BIN_MV, "fi", NULL), + BUILTIN("rm", 0, bin_rm, 1, -1, 0, "dfirs", NULL), + BUILTIN("rmdir", 0, bin_rmdir, 1, -1, 0, NULL, NULL), + BUILTIN("sync", 0, bin_sync, 0, 0, 0, NULL, NULL), +}; + +/**/ +int +boot_files(Module m) +{ + return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); +} + +#ifdef MODULE + +/**/ +int +cleanup_files(Module m) +{ + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} +#endif diff --git a/Src/Modules/files.mdd b/Src/Modules/files.mdd new file mode 100644 index 000000000..236ca2d5a --- /dev/null +++ b/Src/Modules/files.mdd @@ -0,0 +1,3 @@ +autobins="ln mkdir mv rm rmdir sync" + +objects="files.o" diff --git a/Src/Modules/stat.c b/Src/Modules/stat.c new file mode 100644 index 000000000..09245b52f --- /dev/null +++ b/Src/Modules/stat.c @@ -0,0 +1,535 @@ +/* + * stat.c - stat builtin interface to system call + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1996-1997 Peter Stephenson + * 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 Peter Stephenson 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 Peter Stephenson and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Peter Stephenson 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 Peter Stephenson and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "stat.mdh" +#include "stat.pro" + +enum statnum { ST_DEV, ST_INO, ST_MODE, ST_NLINK, ST_UID, ST_GID, + ST_RDEV, ST_SIZE, ST_ATIM, ST_MTIM, ST_CTIM, + ST_BLKSIZE, ST_BLOCKS, ST_READLINK, ST_COUNT }; +enum statflags { STF_NAME = 1, STF_FILE = 2, STF_STRING = 4, STF_RAW = 8, + STF_PICK = 16, STF_ARRAY = 32, STF_GMT = 64 }; +static char *statelts[] = { "device", "inode", "mode", "nlink", + "uid", "gid", "rdev", "size", "atime", + "mtime", "ctime", "blksize", "blocks", + "link", NULL }; + +/**/ +static void +statmodeprint(mode_t mode, char *outbuf, int flags) +{ + if (flags & STF_RAW) { + sprintf(outbuf, "%lu", (unsigned long)mode); + if (flags & STF_STRING) + strcat(outbuf, " ("); + } + if (flags & STF_STRING) { + static const char *modes = "?rwxrwxrwx"; + static const mode_t mflags[] = { S_IRUSR, S_IWUSR, S_IXUSR, + S_IRGRP, S_IWGRP, S_IXGRP, + S_IROTH, S_IWOTH, S_IXOTH }; + const mode_t *mfp = mflags; + char pm[11]; + int i; + + if (S_ISBLK(mode)) + *pm = 'b'; + else if (S_ISCHR(mode)) + *pm = 'c'; + else if (S_ISDIR(mode)) + *pm = 'd'; + else if (S_ISFIFO(mode)) + *pm = 'p'; + else if (S_ISLNK(mode)) + *pm = 'l'; + else if (S_ISMPC(mode)) + *pm = 'm'; + else if (S_ISNWK(mode)) + *pm = 'n'; + else if (S_ISOFD(mode)) + *pm = 'M'; + else if (S_ISOFL(mode)) + *pm = 'M'; + else if (S_ISREG(mode)) + *pm = '-'; + else if (S_ISSOCK(mode)) + *pm = 's'; + else + *pm = '?'; + + for (i = 1; i <= 9; i++) + pm[i] = (mode & *mfp++) ? modes[i] : '-'; + + if (mode & S_ISUID) + pm[3] = (mode & S_IXUSR) ? 's' : 'S'; + if (mode & S_ISGID) + pm[6] = (mode & S_IXGRP) ? 's' : 'S'; + if (mode & S_ISVTX) + pm[9] = (mode & S_IXOTH) ? 't' : 'T'; + + pm[10] = 0; + strcat(outbuf, pm); + if (flags & STF_RAW) + strcat(outbuf, ")"); + } +} + + +/**/ +static void +statuidprint(uid_t uid, char *outbuf, int flags) +{ + if (flags & STF_RAW) { + sprintf(outbuf, "%lu", (unsigned long)uid); + if (flags & STF_STRING) + strcat(outbuf, " ("); + } + if (flags & STF_STRING) { +#ifdef HAVE_GETPWUID + struct passwd *pwd; + pwd = getpwuid(uid); + strcat(outbuf, pwd ? pwd->pw_name : "???"); +#else /* !HAVE_GETPWUID */ + strcat(outbuf, "???"); +#endif /* !HAVE_GETPWUID */ + if (flags & STF_RAW) + strcat(outbuf, ")"); + } +} + + +/**/ +static void +statgidprint(gid_t gid, char *outbuf, int flags) +{ + if (flags & STF_RAW) { + sprintf(outbuf, "%lu", (unsigned long)gid); + if (flags & STF_STRING) + strcat(outbuf, " ("); + } + if (flags & STF_STRING) { +#ifdef HAVE_GETGRGID + struct group *gr; + gr = getgrgid(gid); + strcat(outbuf, gr ? gr->gr_name : "???"); +#else /* !HAVE_GETGRGID */ + strcat(outbuf, "???"); +#endif /* !HAVE_GETGRGID */ + if (flags & STF_RAW) + strcat(outbuf, ")"); + } +} + +static char *timefmt; + +/**/ +static void +stattimeprint(time_t tim, char *outbuf, int flags) +{ + if (flags & STF_RAW) { + sprintf(outbuf, "%ld", (unsigned long)tim); + if (flags & STF_STRING) + strcat(outbuf, " ("); + } + if (flags & STF_STRING) { + char *oend = outbuf + strlen(outbuf); + ztrftime(oend, 40, timefmt, (flags & STF_GMT) ? gmtime(&tim) : + localtime(&tim)); + if (flags & STF_RAW) + strcat(outbuf, ")"); + } +} + + +/**/ +static void +statulprint(unsigned long num, char *outbuf) +{ + sprintf(outbuf, "%lu", num); +} + + +/**/ +static void +statlinkprint(struct stat *sbuf, char *outbuf, char *fname) +{ + int num; + + /* fname is NULL if we are looking at an fd */ + if (fname && S_ISLNK(sbuf->st_mode) && + (num = readlink(fname, outbuf, PATH_MAX)) > 0) { + /* readlink doesn't terminate the buffer itself */ + outbuf[num] = '\0'; + } +} + + +/**/ +static void +statprint(struct stat *sbuf, char *outbuf, char *fname, int iwhich, int flags) +{ + char *optr = outbuf; + + if (flags & STF_NAME) { + sprintf(outbuf, (flags & (STF_PICK|STF_ARRAY)) ? + "%s " : "%-8s", statelts[iwhich]); + optr += strlen(outbuf); + } + *optr = '\0'; + + /* cast values to unsigned long as safest bet */ + switch (iwhich) { + case ST_DEV: + statulprint((unsigned long)sbuf->st_dev, optr); + break; + + case ST_INO: + statulprint((unsigned long)sbuf->st_ino, optr); + break; + + case ST_MODE: + statmodeprint(sbuf->st_mode, optr, flags); + break; + + case ST_NLINK: + statulprint((unsigned long)sbuf->st_nlink, optr); + break; + + case ST_UID: + statuidprint(sbuf->st_uid, optr, flags); + break; + + case ST_GID: + statgidprint(sbuf->st_gid, optr, flags); + break; + + case ST_RDEV: + statulprint((unsigned long)sbuf->st_rdev, optr); + break; + + case ST_SIZE: + statulprint((unsigned long)sbuf->st_size, optr); + break; + + case ST_ATIM: + stattimeprint(sbuf->st_atime, optr, flags); + break; + + case ST_MTIM: + stattimeprint(sbuf->st_mtime, optr, flags); + break; + + case ST_CTIM: + stattimeprint(sbuf->st_ctime, optr, flags); + break; + + case ST_BLKSIZE: + statulprint((unsigned long)sbuf->st_blksize, optr); + break; + + case ST_BLOCKS: + statulprint((unsigned long)sbuf->st_blocks, optr); + break; + + case ST_READLINK: + statlinkprint(sbuf, optr, fname); + break; + + case ST_COUNT: /* keep some compilers happy */ + break; + } +} + + +/* + * + * Options: + * -f fd: stat fd instead of file + * -g: use GMT rather than local time for time strings (forces -s on). + * -n: always print file name of file being statted + * -N: never print file name + * -l: list stat types + * -L: do lstat (else links are implicitly dereferenced by stat) + * -t: always print name of stat type + * -T: never print name of stat type + * -r: print raw alongside string data + * -s: string, print mode, times, uid, gid as appropriate strings: + * harmless but unnecessary when combined with -r. + * -A array: assign results to given array, one stat result per element. + * File names and type names are only added if explicitly requested: + * file names are returned as a separate array element, type names as + * prefix to element. Note the formatting deliberately contains + * fewer frills when -A is used. + * -F fmt: specify a $TIME-like format for printing times; the default + * is the (CTIME-like) "%a %b %e %k:%M:%S". This option implies + * -s as it is not useful for numerical times. + * + * +type selects just element type of stat buffer (-l gives list): + * type can be shortened to unambiguous string. only one type is + * allowed. The extra type, +link, reads the target of a symbolic + * link; it is empty if the stat was not an lstat or if + * a file descriptor was stat'd, if the stat'd file is + * not a symbolic link, or if symbolic links are not + * supported. If +link is explicitly requested, the -L (lstat) + * option is set automatically. + */ +/**/ +static int +bin_stat(char *name, char **args, char *ops, int func) +{ + char **aptr, *arrnam = NULL, **array = NULL, **arrptr = NULL; + int len, iwhich = -1, ret = 0, flags = 0, arrsize = 0, fd = 0; + struct stat statbuf; + int found = 0, nargs; + + timefmt = "%a %b %e %k:%M:%S"; + + for (; *args && (**args == '+' || **args == '-'); args++) { + char *arg = *args+1; + if (!*arg || *arg == '-' || *arg == '+') { + args++; + break; + } + + if (**args == '+') { + if (found) + break; + len = strlen(arg); + for (aptr = statelts; *aptr; aptr++) + if (!strncmp(*aptr, arg, len)) { + found++; + iwhich = aptr - statelts; + } + if (found > 1) { + zerrnam(name, "%s: ambiguous stat element", arg, 0); + return 1; + } else if (found == 0) { + zerrnam(name, "%s: no such stat element", arg, 0); + return 1; + } + /* if name of link requested, turn on lstat */ + if (iwhich == ST_READLINK) + ops['L'] = 1; + flags |= STF_PICK; + } else { + for (; *arg; arg++) { + if (strchr("glLnNrstT", *arg)) + ops[*arg] = 1; + else if (*arg == 'A') { + if (arg[1]) { + arrnam = arg+1; + } else if (!(arrnam = *++args)) { + zerrnam(name, "missing parameter name\n", + NULL, 0); + return 1; + } + flags |= STF_ARRAY; + break; + } else if (*arg == 'f') { + char *sfd; + ops['f'] = 1; + if (arg[1]) { + sfd = arg+1; + } else if (!(sfd = *++args)) { + zerrnam(name, "missing file descriptor\n", NULL, 0); + return 1; + } + fd = zstrtol(sfd, &sfd, 10); + if (*sfd) { + zerrnam(name, "bad file descriptor\n", NULL, 0); + return 1; + } + break; + } else if (*arg == 'F') { + if (arg[1]) { + timefmt = arg+1; + } else if (!(timefmt = *++args)) { + zerrnam(name, "missing time format\n", NULL, 0); + return 1; + } + /* force string format in order to use time format */ + ops['s'] = 1; + break; + } else { + zerrnam(name, "bad option: -%c", NULL, *arg); + return 1; + } + } + } + } + + if (ops['l']) { + /* list types and return: can also list to array */ + if (arrnam) { + arrptr = array = (char **)zalloc((ST_COUNT+1)*sizeof(char *)); + array[ST_COUNT] = NULL; + } + for (aptr = statelts; *aptr; aptr++) { + if (arrnam) { + *arrptr++ = ztrdup(*aptr); + } else { + printf("%s", *aptr); + if (aptr[1]) + putchar(' '); + } + } + if (arrnam) { + setaparam(arrnam, array); + if (errflag) + return 1; + } else + putchar('\n'); + return 0; + } + + if (!*args && !ops['f']) { + zwarnnam(name, "no files given", NULL, 0); + return 1; + } else if (*args && ops['f']) { + zwarnnam(name, "no files allowed with -f", NULL, 0); + return 1; + } + + nargs = 0; + if (ops['f']) + nargs = 1; + else + for (aptr = args; *aptr; aptr++) + nargs++; + + if (ops['s'] || ops['r']) + flags |= STF_STRING; + if (ops['r'] || !ops['s']) + flags |= STF_RAW; + if (ops['n']) + flags |= STF_FILE; + if (ops['t']) + flags |= STF_NAME; + if (ops['g']) + flags |= STF_GMT; + + if (!arrnam) { + if (nargs > 1) + flags |= STF_FILE; + if (!(flags & STF_PICK)) + flags |= STF_NAME; + } + + if (ops['N'] || ops['f']) + flags &= ~STF_FILE; + if (ops['T']) + flags &= ~STF_NAME; + + if (arrnam) { + arrsize = (flags & STF_PICK) ? 1 : ST_COUNT; + if (flags & STF_FILE) + arrsize++; + arrsize *= nargs; + arrptr = array = (char **)zcalloc((arrsize+1)*sizeof(char *)); + } + + for (; ops['f'] || *args; args++) { + char outbuf[PATH_MAX + 9]; /* "link " + link name + NULL */ + int rval = ops['f'] ? fstat(fd, &statbuf) : + ops['L'] ? lstat(*args, &statbuf) : stat(*args, &statbuf); + if (rval) { + if (ops['f']) + sprintf(outbuf, "%d", fd); + zwarnnam(name, "%s: %e", ops['f'] ? outbuf : *args, errno); + ret = 1; + if (ops['f'] || arrnam) + break; + else + continue; + } + + if (flags & STF_FILE) + if (arrnam) + *arrptr++ = ztrdup(*args); + else + printf("%s%s", *args, (flags & STF_PICK) ? " " : ":\n"); + if (iwhich > -1) { + statprint(&statbuf, outbuf, *args, iwhich, flags); + if (arrnam) + *arrptr++ = ztrdup(outbuf); + else + printf("%s\n", outbuf); + } else { + int i; + for (i = 0; i < ST_COUNT; i++) { + statprint(&statbuf, outbuf, *args, i, flags); + if (arrnam) + *arrptr++= ztrdup(outbuf); + else + printf("%s\n", outbuf); + } + } + if (ops['f']) + break; + + if (!arrnam && args[1] && !(flags & STF_PICK)) + putchar('\n'); + } + + if (arrnam) + if (ret) { + for (aptr = array; *aptr; aptr++) + zsfree(*aptr); + zfree(array, arrsize+1); + } else { + setaparam(arrnam, array); + if (errflag) + return 1; + } + + return ret; +} + +static struct builtin bintab[] = { + BUILTIN("stat", 0, bin_stat, 0, -1, 0, NULL, NULL), +}; + +/**/ +int +boot_stat(Module m) +{ + return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); +} + +#ifdef MODULE + +/**/ +int +cleanup_stat(Module m) +{ + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} + +#endif diff --git a/Src/Modules/stat.mdd b/Src/Modules/stat.mdd new file mode 100644 index 000000000..b775fda09 --- /dev/null +++ b/Src/Modules/stat.mdd @@ -0,0 +1,3 @@ +autobins="stat" + +objects="stat.o" diff --git a/Src/Modules/zftp.c b/Src/Modules/zftp.c new file mode 100644 index 000000000..ca0843419 --- /dev/null +++ b/Src/Modules/zftp.c @@ -0,0 +1,2596 @@ +/* + * zftp.c - builtin FTP client + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1998 Peter Stephenson + * 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 Peter Stephenson 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 Peter Stephenson and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Peter Stephenson 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 Peter Stephenson and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +/* + * TODO: + * can signal handling be improved? + * error messages may need tidying up. + * maybe we should block CTRL-c on some more operations, + * otherwise you can get the connection closed prematurely. + * some way of turning off progress reports when backgrounded + * would be nice, but the shell doesn't make it easy to find that out. + * the progress reports 100% a bit prematurely: the data may still + * be in transit, and we are stuck waiting for a message from the + * server. but there's really nothing else to do. it's worst + * with small files. + * proxy/gateway connections if i knew what to do + * options to specify e.g. a non-standard port + * optimizing things out is hard in general when you don't know what + * the shell's going to want, but they may be places to second guess + * the user. Some of the variables could be made special and so + * only retrieve things like the current directory when necessary. + * But it's much neater to have ordinary variables, which the shell + * can manage without our interference, and getting the directory + * just every time it changes isn't so bad. The user can always + * toggle the `Dumb' preference if it's feeling clever. + */ +#include "zftp.mdh" +#include "zftp.pro" + +#include <sys/socket.h> +#include <netdb.h> +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <arpa/inet.h> +/* it's a TELNET based protocol, but don't think I like doing this */ +#include <arpa/telnet.h> + +/* bet there are machines which have neither INADDR_NONE nor in_addr_t. */ +#ifndef INADDR_NONE +#define INADDR_NONE (in_addr_t)-1 +#endif + +/* + * For FTP block mode + * + * The server on our AIX machine here happily accepts block mode, takes the + * first connection, then at the second complains that it's got nowhere + * to send data. The same problem happens with ncftp, it's not just + * me. And a lot of servers don't even support block mode. So I'm not sure + * how widespread the supposed ability to leave open the data fd between + * transfers. Therefore, I've closed all connections after the transfer. + * But then what's the point in block mode? I only implemented it because + * it says in RFC959 that you need it to be able to restart transfers + * later in the file. However, it turns out that's not true for + * most servers --- but our AIX machine happily accepts the REST + * command and then dumps the whole file onto you. Sigh. + * + * Note on block sizes: + * Not quite sure how to optimize this: in principle + * we should handle blocks up to 65535 bytes, which + * is pretty big, and should presumably send blocks + * which are smaller to be on the safe side. + * Currently we send 32768 and use that also as + * the maximum to receive. No-one's complained yet. Of course, + * no-one's *used* it yet apart from me, but even so. + */ + +struct zfheader { + char flags; + unsigned char bytes[2]; +}; + +enum { + ZFHD_MARK = 16, /* restart marker */ + ZFHD_ERRS = 32, /* suspected errors in block */ + ZFHD_EOFB = 64, /* block is end of record */ + ZFHD_EORB = 128 /* block is end of file */ +}; + +typedef int (*readwrite_t)(int, char *, size_t, int); + +struct zftpcmd { + const char *nam; + int (*fun) _((char *, char **, int)); + int min, max, flags; +}; + +enum { + ZFTP_CONN = 0x0001, /* must be connected */ + ZFTP_LOGI = 0x0002, /* must be logged in */ + ZFTP_TBIN = 0x0004, /* set transfer type image */ + ZFTP_TASC = 0x0008, /* set transfer type ASCII */ + ZFTP_NLST = 0x0010, /* use NLST rather than LIST */ + ZFTP_DELE = 0x0020, /* a delete rather than a make */ + ZFTP_SITE = 0x0040, /* a site rather than a quote */ + ZFTP_APPE = 0x0080, /* append rather than overwrite */ + ZFTP_HERE = 0x0100, /* here rather than over there */ + ZFTP_CDUP = 0x0200, /* CDUP rather than CWD */ + ZFTP_REST = 0x0400, /* restart: set point in remote file */ + ZFTP_RECV = 0x0800 /* receive rather than send */ +}; + +typedef struct zftpcmd *Zftpcmd; + +static struct zftpcmd zftpcmdtab[] = { + { "open", zftp_open, 0, 4, 0 }, + { "params", zftp_params, 0, 4, 0 }, + { "login", zftp_login, 0, 3, ZFTP_CONN }, + { "user", zftp_login, 0, 3, ZFTP_CONN }, + { "cd", zftp_cd, 1, 1, ZFTP_CONN|ZFTP_LOGI }, + { "cdup", zftp_cd, 0, 0, ZFTP_CONN|ZFTP_LOGI|ZFTP_CDUP }, + { "dir", zftp_dir, 0, -1, ZFTP_CONN|ZFTP_LOGI }, + { "ls", zftp_dir, 0, -1, ZFTP_CONN|ZFTP_LOGI|ZFTP_NLST }, + { "type", zftp_type, 0, 1, ZFTP_CONN|ZFTP_LOGI }, + { "ascii", zftp_type, 0, 0, ZFTP_CONN|ZFTP_LOGI|ZFTP_TASC }, + { "binary", zftp_type, 0, 0, ZFTP_CONN|ZFTP_LOGI|ZFTP_TBIN }, + { "mode", zftp_mode, 0, 1, ZFTP_CONN|ZFTP_LOGI }, + { "local", zftp_local, 0, -1, ZFTP_HERE }, + { "remote", zftp_local, 1, -1, ZFTP_CONN|ZFTP_LOGI }, + { "get", zftp_getput, 1, -1, ZFTP_CONN|ZFTP_LOGI|ZFTP_RECV }, + { "getat", zftp_getput, 2, 2, ZFTP_CONN|ZFTP_LOGI|ZFTP_RECV|ZFTP_REST }, + { "put", zftp_getput, 1, -1, ZFTP_CONN|ZFTP_LOGI }, + { "putat", zftp_getput, 2, 2, ZFTP_CONN|ZFTP_LOGI|ZFTP_REST }, + { "append", zftp_getput, 1, -1, ZFTP_CONN|ZFTP_LOGI|ZFTP_APPE }, + { "appendat", zftp_getput, 2, 2, ZFTP_CONN|ZFTP_LOGI|ZFTP_APPE|ZFTP_REST }, + { "delete", zftp_delete, 1, -1, ZFTP_CONN|ZFTP_LOGI }, + { "mkdir", zftp_mkdir, 1, 1, ZFTP_CONN|ZFTP_LOGI }, + { "rmdir", zftp_mkdir, 1, 1, ZFTP_CONN|ZFTP_LOGI|ZFTP_DELE }, + { "rename", zftp_rename, 2, 2, ZFTP_CONN|ZFTP_LOGI }, + { "quote", zftp_quote, 1, -1, ZFTP_CONN }, + { "site", zftp_quote, 1, -1, ZFTP_CONN|ZFTP_SITE }, + { "close", zftp_close, 0, 0, ZFTP_CONN }, + { "quit", zftp_close, 0, 0, ZFTP_CONN }, + { 0, 0, 0, 0} +}; + +static struct builtin bintab[] = { + BUILTIN("zftp", 0, bin_zftp, 1, -1, 0, NULL, NULL), +}; + +/* + * these are the non-special params to unset when a connection + * closes. any special params are handled, well, specially. + * currently there aren't any, which is the way I like it. + */ +static char *zfparams[] = { + "ZFTP_HOST", "ZFTP_IP", "ZFTP_SYSTEM", "ZFTP_USER", + "ZFTP_ACCOUNT", "ZFTP_PWD", "ZFTP_TYPE", "ZFTP_MODE", NULL +}; + +/* flags for zfsetparam */ + +enum { + ZFPM_READONLY = 0x01, /* make parameter readonly */ + ZFPM_IFUNSET = 0x02, /* only set if not already set */ + ZFPM_INTEGER = 0x04 /* passed pointer to long */ +}; + +/* + * Basic I/O variables for control connection: + * zcfd != -1 is a flag that there is a connection open. + */ +static int zcfd = -1; +static FILE *zcin; +static struct sockaddr_in zsock; + +/* + * zcfinish = 0 keep going + * 1 line finished, alles klar + * 2 EOF + */ +static int zcfinish; +/* zfclosing is set if zftp_close() is active */ +static int zfclosing; + +/* + * Now stuff for data connection + */ +static int zdfd = -1; +static struct sockaddr_in zdsock; + +/* + * Stuff about last message: last line of message and status code. + * The reply is also stored in $ZFTP_REPLY; we keep these separate + * for convenience. + */ +static char *lastmsg, lastcodestr[4]; +static int lastcode; + +/* flag for remote system is UNIX --- useful to know as things are simpler */ +static int zfis_unix, zfpassive_conn; + +/* remote system has size, mdtm commands */ +enum { + ZFCP_UNKN = 0, /* dunno if it works on this server */ + ZFCP_YUPP = 1, /* it does */ + ZFCP_NOPE = 2 /* it doesn't */ +}; + +static int zfhas_size, zfhas_mdtm; + +/* + * We keep an fd open for communication between the main shell + * and forked off bits and pieces. This allows us to know + * if something happend in a subshell: mode changed, type changed, + * connection was closed. If something too substantial happened + * in a subshell --- connection opened, ZFTP_USER and ZFTP_PWD changed + * --- we don't try to track it because it's too complicated. + */ +enum { + ZFST_ASCI = 0x00, /* type for next transfer is ASCII */ + ZFST_IMAG = 0x01, /* type for next transfer is image */ + + ZFST_TMSK = 0x01, /* mask for type flags */ + ZFST_TBIT = 0x01, /* number of bits in type flags */ + + ZFST_CASC = 0x00, /* current type is ASCII - default */ + ZFST_CIMA = 0x02, /* current type is image */ + + ZFST_STRE = 0x00, /* stream mode - default */ + ZFST_BLOC = 0x04, /* block mode */ + + ZFST_MMSK = 0x04, /* mask for mode flags */ + + ZFST_LOGI = 0x08, /* user logged in */ + ZFST_NOPS = 0x10, /* server doesn't understand PASV */ + ZFST_NOSZ = 0x20, /* server doesn't send `(XXXX bytes)' reply */ + ZFST_TRSZ = 0x40, /* tried getting 'size' from reply */ + ZFST_CLOS = 0x80 /* connection closed */ +}; +#define ZFST_TYPE(x) (x & ZFST_TMSK) +/* + * shift current type flags to match type flags: should be by + * the number of bits in the type flags + */ +#define ZFST_CTYP(x) ((x >> ZFST_TBIT) & ZFST_TMSK) +#define ZFST_MODE(x) (x & ZFST_MMSK) + +static int zfstatfd = -1, zfstatus; + +/* Preferences, read in from the `zftp_prefs' array variable */ +enum { + ZFPF_SNDP = 0x01, /* Use send port mode */ + ZFPF_PASV = 0x02, /* Try using passive mode */ + ZFPF_DUMB = 0x04 /* Don't do clever things with variables */ +}; + +/* The flags as stored internally. */ +int zfprefs; + + +/* zfuserparams is the storage area for zftp_params() */ +char **zfuserparams; + +/* + * Bits and pieces for dealing with SIGALRM (and SIGPIPE, but that's + * easier). The complication is that SIGALRM may already be handled + * by the user setting TMOUT and possibly setting their own trap --- in + * fact, it's always handled by the shell when it's interactive. It's + * too difficult to use zsh's own signal handler --- either it would + * need rewriting to use a C function as a trap, or we would need a + * hack to make it callback via a hidden builtin from a function --- so + * just install our own, and use settrap() to restore the behaviour + * afterwards if necessary. However, the more that could be done by + * the main shell code, the better I would like it. + * + * Since we don't want to go through the palaver of changing between + * the main zsh signal handler and ours every time we start or stop the + * alarm, we keep the flag zfalarmed set to 1 while zftp is rigged to + * handle alarms. This is tested at the end of bin_zftp(), which is + * the entry point for all functions, and that restores the original + * handler for SIGALRM. To turn off the alarm temporarily in the zftp + * code we then just call alarm(0). + * + * If we could rely on having select() or some replacement, we would + * only need the alarm during zftp_open(). + */ + +/* flags for alarm set, alarm gone off */ +int zfalarmed, zfdrrrring; +/* remember old alarm status */ +time_t oaltime; +unsigned int oalremain; + +/* + * Where to jump to when the alarm goes off. This is much + * easier than fiddling with error flags at every turn. + * Since we don't expect too many alarm's, the simple setjmp() + * mechanism should be good enough. + * + * gcc -O gives apparently spurious `may be clobbered by longjmp' warnings. + */ +jmp_buf zfalrmbuf; + +/* The signal handler itself */ + +/**/ +static RETSIGTYPE +zfhandler(int sig) +{ + if (sig == SIGALRM) { + zfdrrrring = 1; +#ifdef ETIMEDOUT /* just in case */ + errno = ETIMEDOUT; +#else + errno = EIO; +#endif + longjmp(zfalrmbuf, 1); + } + DPUTS(1, "zfhandler caught incorrect signal"); +} + +/* Set up for an alarm call */ + +/**/ +static void +zfalarm(int tmout) +{ + zfdrrrring = 0; + /* + * We need to do this even if tmout is zero, since there may + * be a non-zero timeout set in the main shell which we don't + * want to go off. This could be argued the other way, since + * if we don't get that it's probably harmless. But this looks safer. + */ + if (zfalarmed) { + alarm(tmout); + return; + } + signal(SIGALRM, zfhandler); + oalremain = alarm(tmout); + if (oalremain) + oaltime = time(NULL); + /* + * We'll leave sigtrapped, sigfuncs and TRAPXXX as they are; since the + * shell's handler doesn't get the signal, they don't matter. + */ + zfalarmed = 1; +} + +/* Set up for a broken pipe */ + +/**/ +static void +zfpipe() +{ + /* Just ignore SIGPIPE and rely on getting EPIPE from the write. */ + signal(SIGPIPE, SIG_IGN); +} + +/* Unset the alarm, see above */ + +/**/ +static void +zfunalarm() +{ + if (oalremain) { + /* + * The alarm was previously set, so set it back, adjusting + * for the time used. Mostly the alarm was set to zero + * beforehand, so it would probably be best to reinstall + * the proper signal handler before resetting the alarm. + * + * I love the way alarm() uses unsigned int while time_t + * is probably something completely different. + */ + time_t tdiff = time(NULL) - oaltime; + alarm(oalremain < tdiff ? 1 : oalremain - tdiff); + } else + alarm(0); + if (sigtrapped[SIGALRM] || interact) { + if (sigfuncs[SIGALRM] || !sigtrapped[SIGALRM]) + install_handler(SIGALRM); + else + signal_ignore(SIGALRM); + } else + signal_default(SIGALRM); + zfalarmed = 0; +} + +/* Restore SIGPIPE handling to its usual status */ + +/**/ +static void +zfunpipe() +{ + if (sigtrapped[SIGPIPE]) { + if (sigfuncs[SIGPIPE]) + install_handler(SIGPIPE); + else + signal_ignore(SIGPIPE); + } else + signal_default(SIGPIPE); +} + +/* + * Same as movefd(), but don't mark the fd in the zsh tables, + * because we only want it closed by zftp. However, we still + * need to shift the fd's out of the way of the user-visible 0-9. + */ + +/**/ +static int +zfmovefd(int fd) +{ + if (fd != -1 && fd < 10) { +#ifdef F_DUPFD + int fe = fcntl(fd, F_DUPFD, 10); +#else + int fe = zfmovefd(dup(fd)); +#endif + close(fd); + fd = fe; + } + return fd; +} + +/* + * set a non-special parameter. + * if ZFPM_IFUNSET, don't set if it already exists. + * if ZFPM_READONLY, make it readonly, but only when creating it. + * if ZFPM_INTEGER, val pointer is to long (NB not int), don't free. + */ +/**/ +static void +zfsetparam(char *name, void *val, int flags) +{ + Param pm = NULL; + int type = (flags & ZFPM_INTEGER) ? PM_INTEGER : PM_SCALAR; + + if (!(pm = (Param) paramtab->getnode(paramtab, name)) + || (pm->flags & PM_UNSET)) { + /* + * just make it readonly when creating, in case user + * *really* knows what they're doing + */ + if ((pm = createparam(name, type)) && (flags & ZFPM_READONLY)) + pm->flags |= PM_READONLY; + } else if (flags & ZFPM_IFUNSET) { + pm = NULL; + } + if (!pm || PM_TYPE(pm->flags) != type) { + /* parameters are funny, you just never know */ + if (type == PM_SCALAR) + zsfree((char *)val); + return; + } + if (type == PM_INTEGER) + pm->sets.ifn(pm, *(long *)val); + else + pm->sets.cfn(pm, (char *)val); +} + +/* + * Unset a ZFTP parameter when the connection is closed. + * We only do this with connection-specific parameters. + */ + +/**/ +static void +zfunsetparam(char *name) +{ + Param pm; + + if ((pm = (Param) paramtab->getnode(paramtab, name))) { + pm->flags &= ~PM_READONLY; + unsetparam_pm(pm, 0, 1); + } +} + +/* + * Join command and arguments to make a proper TELNET command line. + * New line is in permanent storage. + */ + +/**/ +static char * +zfargstring(char *cmd, char **args) +{ + int clen = strlen(cmd) + 3; + char *line, **aptr; + + for (aptr = args; *aptr; aptr++) + clen += strlen(*aptr) + 1; + line = zalloc(clen); + strcpy(line, cmd); + for (aptr = args; *aptr; aptr++) { + strcat(line, " "); + strcat(line, *aptr); + } + strcat(line, "\r\n"); + + return line; +} + +/* + * get a line on the control connection according to TELNET rules + * Return status is first digit of FTP reply code + */ + +/**/ +static int +zfgetline(char *ln, int lnsize, int tmout) +{ + int ch, added = 0; + /* current line point */ + char *pcur = ln, cmdbuf[3]; + + zcfinish = 0; + /* leave room for null byte */ + lnsize--; + /* in case we return unexpectedly before getting anything */ + ln[0] = '\0'; + + if (setjmp(zfalrmbuf)) { + alarm(0); + zwarnnam("zftp", "timeout getting response", NULL, 0); + return 5; + } + zfalarm(tmout); + + /* + * We need to be more careful about errors here; we + * should do the stuff with errflag and so forth. + * We should probably holdintr() here, since if we don't + * get the message, the connection is going to be messed up. + * But then we get `frustrated user syndrome'. + */ + for (;;) { + ch = fgetc(zcin); + + switch(ch) { + case EOF: + if (ferror(zcin) && errno == EINTR) { + clearerr(zcin); + continue; + } + zcfinish = 2; + break; + + case '\r': + /* always precedes something else */ + ch = fgetc(zcin); + if (ch == EOF) { + zcfinish = 2; + break; + } + if (ch == '\n') { + zcfinish = 1; + break; + } + if (ch == '\0') { + ch = '\r'; + break; + } + /* not supposed to get here */ + ch = '\r'; + break; + + case '\n': + /* not supposed to get here */ + zcfinish = 1; + break; + + case IAC: + /* + * oh great, now it's sending TELNET commands. try + * to persuade it not to. + */ + ch = fgetc(zcin); + switch (ch) { + case WILL: + case WONT: + ch = fgetc(zcin); + /* whatever it wants to do, stop it. */ + cmdbuf[0] = (char)IAC; + cmdbuf[1] = (char)DONT; + cmdbuf[2] = ch; + write(zcfd, cmdbuf, 3); + continue; + + case DO: + case DONT: + ch = fgetc(zcin); + /* well, tough, we're not going to. */ + cmdbuf[0] = (char)IAC; + cmdbuf[1] = (char)WONT; + cmdbuf[2] = ch; + write(zcfd, cmdbuf, 3); + continue; + + case EOF: + /* strange machine. */ + zcfinish = 2; + break; + + default: + break; + } + break; + } + + if (zcfinish) + break; + if (added < lnsize) { + *pcur++ = ch; + added++; + } + /* junk it if we don't have room, but go on reading */ + } + + alarm(0); + + *pcur = '\0'; + /* if zcfinish == 2, at EOF, return that, else 0 */ + return (zcfinish & 2); +} + +/* + * Get a whole message from the server. A dash after + * the first line code means keep reading until we get + * a line with the same code followed by a space. + * + * Note that this returns an FTP status code, the first + * digit of the reply. There is also a pseudocode, 6, which + * means `there's no point trying anything, just yet'. + * We return it either if the connection is closed, or if + * we got a 530 (user not logged in), in which case whatever + * you're trying to do isn't going to work. + */ + +/**/ +static int +zfgetmsg() +{ + char line[256], *ptr, *verbose; + int stopit, printing = 0, tmout; + + if (zcfd == -1) + return 5; + if (!(verbose = getsparam("ZFTP_VERBOSE"))) + verbose = ""; + zsfree(lastmsg); + lastmsg = NULL; + + tmout = getiparam("ZFTP_TMOUT"); + + zfgetline(line, 256, tmout); + ptr = line; + if (zfdrrrring || !isdigit((int)*ptr) || !isdigit((int)ptr[1]) || + !isdigit((int)ptr[2])) { + /* timeout, or not talking FTP. not really interested. */ + zcfinish = 2; + if (!zfclosing) + zfclose(); + lastmsg = ztrdup(""); + strcpy(lastcodestr, "000"); + zfsetparam("ZFTP_REPLY", ztrdup(lastmsg), ZFPM_READONLY); + return 6; + } + strncpy(lastcodestr, ptr, 3); + ptr += 3; + lastcodestr[3] = '\0'; + lastcode = atoi(lastcodestr); + zfsetparam("ZFTP_CODE", ztrdup(lastcodestr), ZFPM_READONLY); + stopit = (*ptr++ != '-'); + + if (strchr(verbose, lastcodestr[0])) { + /* print the whole thing verbatim */ + printing = 1; + fputs(line, stderr); + } else if (strchr(verbose, '0') && !stopit) { + /* print multiline parts with the code stripped */ + printing = 2; + fputs(ptr, stderr); + } + if (printing) + fputc('\n', stderr); + + while (zcfinish != 2 && !stopit) { + zfgetline(line, 256, tmout); + ptr = line; + if (zfdrrrring) { + line[0] = '\0'; + break; + } + + if (!strncmp(lastcodestr, line, 3)) { + if (line[3] == ' ') { + stopit = 1; + ptr += 4; + } else if (line[3] == '-') + ptr += 4; + } else if (!strncmp(" ", line, 4)) + ptr += 4; + + if (printing == 2) { + if (!stopit) { + fputs(ptr, stderr); + fputc('\n', stderr); + } + } else if (printing) { + fputs(line, stderr); + fputc('\n', stderr); + } + } + + if (printing) + fflush(stderr); + + /* The internal message is just the text. */ + lastmsg = ztrdup(ptr); + /* + * The parameter is the whole thing, including the code. + */ + zfsetparam("ZFTP_REPLY", ztrdup(line), ZFPM_READONLY); + /* + * close the connection here if zcfinish == 2, i.e. EOF, + * or if we get a 421 (service not available, closing connection), + * but don't do it if it's expected (zfclosing set). + */ + if ((zcfinish == 2 || lastcode == 421) && !zfclosing) { + zcfinish = 2; /* don't need to tell server */ + zfclose(); + /* unexpected, so tell user */ + zwarnnam("zftp", "remote server has closed connection", NULL, 0); + return 6; /* pretend it failed, because it did */ + } + if (lastcode == 530) { + /* user not logged in */ + return 6; + } + /* + * May as well handle this here, though it's pretty uncommon. + * A 120 is something like "service ready in nnn minutes". + * It means we just hang around waiting for another reply. + */ + if (lastcode == 120) { + zwarnnam("zftp", "delay expected, waiting: %s", lastmsg, 0); + return zfgetmsg(); + } + + /* first digit of code determines success, failure, not in the mood... */ + return lastcodestr[0] - '0'; +} + + +/* + * Send a command and get the reply. + * The command is expected to have the \r\n already tacked on. + * Returns the status code for the reply. + */ + +/**/ +static int +zfsendcmd(char *cmd) +{ + /* + * We use the fd directly; there's no point even using + * stdio with line buffering, since we always send the + * complete line in one string anyway. + */ + int ret, tmout; + + if (zcfd == -1) + return 5; + tmout = getiparam("ZFTP_TMOUT"); + if (setjmp(zfalrmbuf)) { + alarm(0); + zwarnnam("zftp", "timeout sending message", NULL, 0); + return 5; + } + zfalarm(tmout); + ret = write(zcfd, cmd, strlen(cmd)); + alarm(0); + + if (ret <= 0) { + zwarnnam("zftp send", "failed sending control message", NULL, 0); + return 5; /* FTP status code */ + } + + return zfgetmsg(); +} + + +/* Set up a data connection, return 1 for failure, 0 for success */ + +/**/ +static int +zfopendata(char *name) +{ + if (!(zfprefs & (ZFPF_SNDP|ZFPF_PASV))) { + zwarnnam(name, "Must set preference S or P to transfer data", NULL, 0); + return 1; + } + zdfd = zfmovefd(socket(AF_INET, SOCK_STREAM, 0)); + if (zdfd < 0) { + zwarnnam(name, "can't get data socket: %e", NULL, errno); + return 1; + } + + zdsock = zsock; + zdsock.sin_family = AF_INET; + + if (!(zfstatus & ZFST_NOPS) && (zfprefs & ZFPF_PASV)) { + char *ptr; + int i, nums[6], err; + unsigned char iaddr[4], iport[2]; + + if (zfsendcmd("PASV\r\n") == 6) + return 1; + else if (lastcode >= 500 && lastcode <= 504) { + /* + * Fall back to send port mode. That will + * test the preferences for whether that's OK. + */ + zfstatus |= ZFST_NOPS; + zfclosedata(); + return zfopendata(name); + } + /* + * OK, now we need to know what port we're looking at, + * which is cunningly concealed in the reply. + * lastmsg already has the reply code expunged. + */ + for (ptr = lastmsg; *ptr; ptr++) + if (isdigit(*ptr)) + break; + if (sscanf(ptr, "%d,%d,%d,%d,%d,%d", + nums, nums+1, nums+2, nums+3, nums+4, nums+5) != 6) { + zwarnnam(name, "bad response to PASV: %s", lastmsg, 0); + zfclosedata(); + return 1; + } + for (i = 0; i < 4; i++) + iaddr[i] = STOUC(nums[i]); + iport[0] = STOUC(nums[4]); + iport[1] = STOUC(nums[5]); + + memcpy(&zdsock.sin_addr, iaddr, sizeof(iaddr)); + memcpy(&zdsock.sin_port, iport, sizeof(iport)); + + /* we should timeout this connect */ + do { + err = connect(zdfd, (struct sockaddr *)&zdsock, sizeof(zdsock)); + } while (err && errno == EINTR && !errflag); + + if (err) { + zwarnnam(name, "connect failed: %e", NULL, errno); + zfclosedata(); + return 1; + } + + zfpassive_conn = 1; + } else { + char portcmd[40]; + unsigned char *addr, *port; + int ret, len; + + if (!(zfprefs & ZFPF_SNDP)) { + zwarnnam(name, "only sendport mode available for data", NULL, 0); + return 1; + } + + zdsock.sin_port = 0; /* to be set by bind() */ + len = sizeof(zdsock); + /* need to do timeout stuff and probably handle EINTR here */ + if (bind(zdfd, (struct sockaddr *)&zdsock, sizeof(zdsock)) < 0) + ret = 1; + else if (getsockname(zdfd, (struct sockaddr *)&zdsock, &len) < 0) + ret = 2; + else if (listen(zdfd, 1) < 0) + ret = 3; + else + ret = 0; + + if (ret) { + zwarnnam(name, "failure on data socket: %s: %e", + ret == 3 ? "listen" : ret == 2 ? "getsockname" : "bind", + errno); + zfclosedata(); + return 1; + } + + addr = (unsigned char *) &zdsock.sin_addr; + port = (unsigned char *) &zdsock.sin_port; + sprintf(portcmd, "PORT %d,%d,%d,%d,%d,%d\r\n", + addr[0],addr[1],addr[2],addr[3],port[0],port[1]); + if (zfsendcmd(portcmd) >= 5) { + zwarnnam(name, "port command failed", NULL, 0); + zfclosedata(); + return 1; + } + zfpassive_conn = 0; + } + + return 0; +} + +/* Close the data connection. */ + +/**/ +static void +zfclosedata(void) +{ + if (zdfd == -1) + return; + close(zdfd); + zdfd = -1; +} + +/* + * Set up a data connection and use cmd to initiate a transfer. + * The actual data fd will be zdfd; the calling routine + * must handle the data itself. + * rest is a REST command to specify starting somewhere other + * then the start of the remote file. + * getsize is non-zero if we want to try to find the number + * of bytes in the reply to a RETR command. + * + * Return 0 on success, 1 on failure. + */ + +/**/ +static int +zfgetdata(char *name, char *rest, char *cmd, int getsize) +{ + int len, newfd; + + if (zfopendata(name)) + return 1; + + /* + * Set position in remote file for get/put. + * According to RFC959, the restart command needs something + * called a marker which has previously been put into the data. + * Luckily for the real world, UNIX machines just interpret this + * as an offset into the byte stream. + * + * This has to be sent immediately before the data transfer, i.e. + * after all mucking around with types and sizes and so on. + */ + if (rest && zfsendcmd(rest) > 3) { + zfclosedata(); + return 1; + } + + if (zfsendcmd(cmd) > 2) { + zfclosedata(); + return 1; + } + if (getsize || (!(zfstatus & ZFST_TRSZ) && !strncmp(cmd, "RETR", 4))) { + /* + * See if we got something like: + * Opening data connection for nortypix.gif (1234567 bytes). + * On the first RETR, always see if this works, Then we + * can avoid sending a special SIZE command beforehand. + */ + char *ptr = strstr(lastmsg, "bytes"); + zfstatus |= ZFST_NOSZ|ZFST_TRSZ; + if (ptr) { + while (ptr > lastmsg && !isdigit(*ptr)) + ptr--; + while (ptr > lastmsg && isdigit(ptr[-1])) + ptr--; + if (isdigit(*ptr)) { + zfstatus &= ~ZFST_NOSZ; + if (getsize) { + long sz = zstrtol(ptr, NULL, 10); + zfsetparam("ZFTP_SIZE", &sz, ZFPM_READONLY|ZFPM_INTEGER); + } + } + } + } + + if (!zfpassive_conn) { + /* + * the current zdfd is the socket we opened, but we need + * to let the server set up a different fd for reading/writing. + * then we can close the fd we were listening for a connection on. + * don't expect me to understand this, i'm only the programmer. + */ + + /* accept the connection */ + len = sizeof(zdsock); + newfd = zfmovefd(accept(zdfd, (struct sockaddr *)&zdsock, &len)); + zfclosedata(); + if (newfd < 0) { + zwarnnam(name, "unable to accept data.", NULL, 0); + return 1; + } + zdfd = newfd; /* this is now the actual data fd */ + } + + + /* more options, just to look professional */ +#ifdef SO_LINGER + /* + * Since data can take arbitrary amounts of time to arrive, + * the socket can be made to hang around until it doesn't think + * anything is arriving. + * + * In BSD 4.3, you could only linger for infinity. Don't + * know if this has changed. + */ + { + struct linger li; + + li.l_onoff = 1; + li.l_linger = 120; + setsockopt(zdfd, SOL_SOCKET, SO_LINGER, (char *)&li, sizeof(li)); + } +#endif +#if defined(IP_TOS) && defined(IPTOS_THROUGHPUT) + /* try to get high throughput, snigger */ + { + int arg = IPTOS_THROUGHPUT; + setsockopt(zdfd, IPPROTO_IP, IP_TOS, (char *)&arg, sizeof(arg)); + } +#endif +#if defined(F_SETFD) && defined(FD_CLOEXEC) + /* If the shell execs a program, we don't want this fd left open. */ + len = FD_CLOEXEC; + fcntl(zdfd, F_SETFD, &len); +#endif + + return 0; +} + +/* + * Find out about a local or remote file and pass back the information. + * + * We could jigger this to use ls like ncftp does as a backup. + * But if the server is non-standard enough not to have SIZE and MDTM, + * there's a very good chance ls -l isn't going to do great things. + * + * if fd is >= 0, it is used for an fstat when remote is zero: + * this is because on a put we are taking input from fd 0. + */ + +/**/ +static int +zfstats(char *fnam, int remote, long *retsize, char **retmdtm, int fd) +{ + long sz = -1; + char *mt = NULL; + int ret; + + if (retsize) + *retsize = -1; + if (retmdtm) + *retmdtm = NULL; + if (remote) { + char *cmd; + if ((zfhas_size == ZFCP_NOPE && retsize) || + (zfhas_mdtm == ZFCP_NOPE && retmdtm)) + return 2; + + /* + * File is coming from over there. + * Make sure we get the type right. + */ + zfsettype(ZFST_TYPE(zfstatus)); + if (retsize) { + cmd = tricat("SIZE ", fnam, "\r\n"); + ret = zfsendcmd(cmd); + zsfree(cmd); + if (ret == 6) + return 1; + else if (lastcode < 300) { + sz = zstrtol(lastmsg, 0, 10); + zfhas_size = ZFCP_YUPP; + } else if (lastcode >= 500 && lastcode <= 504) { + zfhas_size = ZFCP_NOPE; + return 2; + } else if (lastcode == 550) + return 1; + /* if we got a 550 from SIZE, the file doesn't exist */ + } + + if (retmdtm) { + cmd = tricat("MDTM ", fnam, "\r\n"); + ret = zfsendcmd(cmd); + zsfree(cmd); + if (ret == 6) + return 1; + else if (lastcode < 300) { + mt = ztrdup(lastmsg); + zfhas_mdtm = ZFCP_YUPP; + } else if (lastcode >= 500 && lastcode <= 504) { + zfhas_mdtm = ZFCP_NOPE; + return 2; + } else if (lastcode == 550) + return 1; + } + } else { + /* File is over here */ + struct stat statbuf; + struct tm *tm; + char tmbuf[20]; + + if ((fd == -1 ? stat(fnam, &statbuf) : fstat(fd, &statbuf)) < 0) + return 1; + /* make sure it's long, since this has to be a pointer */ + sz = statbuf.st_size; + + if (retmdtm) { + /* use gmtime() rather than localtime() for consistency */ + tm = gmtime(&statbuf.st_mtime); + /* + * FTP format for data is YYYYMMDDHHMMSS + * Using tm directly is easier than worrying about + * incompatible strftime()'s. + */ + sprintf(tmbuf, "%04d%02d%02d%02d%02d%02d", + tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + mt = ztrdup(tmbuf); + } + } + if (retsize) + *retsize = sz; + if (retmdtm) + *retmdtm = mt; + return 0; +} + +/* Set parameters to say what's coming */ + +/**/ +static void +zfstarttrans(char *nam, int recv, long sz) +{ + long cnt = 0; + /* + * sz = -1 signifies error getting size. don't set ZFTP_SIZE if sz is + * zero, either: it probably came from an fstat() on a pipe, so it + * means we don't know and shouldn't tell the user porkies. + */ + if (sz > 0) + zfsetparam("ZFTP_SIZE", &sz, ZFPM_READONLY|ZFPM_INTEGER); + zfsetparam("ZFTP_FILE", ztrdup(nam), ZFPM_READONLY); + zfsetparam("ZFTP_TRANSFER", ztrdup(recv ? "G" : "P"), ZFPM_READONLY); + zfsetparam("ZFTP_COUNT", &cnt, ZFPM_READONLY|ZFPM_INTEGER); +} + +/* Tidy up afterwards */ + +/**/ +static void +zfendtrans() +{ + zfunsetparam("ZFTP_SIZE"); + zfunsetparam("ZFTP_FILE"); + zfunsetparam("ZFTP_TRANSFER"); + zfunsetparam("ZFTP_COUNT"); +} + +/* Read with timeout if recv is set. */ + +/**/ +static int +zfread(int fd, char *bf, size_t sz, int tmout) +{ + int ret; + + if (!tmout) + return read(fd, bf, sz); + + if (setjmp(zfalrmbuf)) { + alarm(0); + zwarnnam("zftp", "timeout on network read", NULL, 0); + return -1; + } + zfalarm(tmout); + + ret = read(fd, bf, sz); + + /* we don't bother turning off the whole alarm mechanism here */ + alarm(0); + return ret; +} + +/* Write with timeout if recv is not set. */ + +/**/ +static int +zfwrite(int fd, char *bf, size_t sz, int tmout) +{ + int ret; + + if (!tmout) + return write(fd, bf, sz); + + if (setjmp(zfalrmbuf)) { + alarm(0); + zwarnnam("zftp", "timeout on network write", NULL, 0); + return -1; + } + zfalarm(tmout); + + ret = write(fd, bf, sz); + + /* we don't bother turning off the whole alarm mechanism here */ + alarm(0); + return ret; +} + +static int zfread_eof; + +/* Version of zfread when we need to read in block mode. */ + +/**/ +static int +zfread_block(int fd, char *bf, size_t sz, int tmout) +{ + int n; + struct zfheader hdr; + size_t blksz, cnt; + char *bfptr; + do { + /* we need the header */ + do { + n = zfread(fd, (char *)&hdr, sizeof(hdr), tmout); + } while (n < 0 && errno == EINTR); + if (n != 3 && !zfdrrrring) { + zwarnnam("zftp", "failed to read FTP block header", NULL, 0); + return n; + } + /* size is stored in network byte order */ + if (hdr.flags & ZFHD_EOFB) + zfread_eof = 1; + blksz = (hdr.bytes[0] << 8) | hdr.bytes[1]; + if (blksz > sz) { + /* + * See comments in file headers + */ + zwarnnam("zftp", "block too large to handle", NULL, 0); + errno = EIO; + return -1; + } + bfptr = bf; + cnt = blksz; + while (cnt) { + n = zfread(fd, bfptr, cnt, tmout); + if (n > 0) { + bfptr += n; + cnt -= n; + } else if (n < 0 && (errflag || zfdrrrring || errno != EINTR)) + return n; + else + break; + } + if (cnt) { + zwarnnam("zftp", "short data block", NULL, 0); + errno = EIO; + return -1; + } + } while ((hdr.flags & ZFHD_MARK) && !zfread_eof); + return (hdr.flags & ZFHD_MARK) ? 0 : blksz; +} + +/* Version of zfwrite when we need to write in block mode. */ + +/**/ +static int +zfwrite_block(int fd, char *bf, size_t sz, int tmout) +{ + int n; + struct zfheader hdr; + size_t cnt; + char *bfptr; + /* we need the header */ + do { + hdr.bytes[0] = (sz & 0xff00) >> 8; + hdr.bytes[1] = sz & 0xff; + hdr.flags = sz ? 0 : ZFHD_EOFB; + n = zfwrite(fd, (char *)&hdr, sizeof(hdr), tmout); + } while (n < 0 && errno == EINTR); + if (n != 3 && !zfdrrrring) { + zwarnnam("zftp", "failed to write FTP block header", NULL, 0); + return n; + } + bfptr = bf; + cnt = sz; + while (cnt) { + n = zfwrite(fd, bfptr, cnt, tmout); + if (n > 0) { + bfptr += n; + cnt -= n; + } else if (n < 0 && (errflag || zfdrrrring || errno != EINTR)) + return n; + } + + return sz; +} + +/* + * Move stuff from fdin to fdout, tidying up the data connection + * when finished. The data connection could be either input or output: + * recv is 1 for receiving a file, 0 for sending. + * + * progress is 1 to use a progress meter. + * startat says how far in we're starting with a REST command. + * + * Since we're doing some buffering here anyway, we don't bother + * with a stdio layer. + */ + +/**/ +static int +zfsenddata(char *name, int recv, int progress, long startat) +{ +#define ZF_BUFSIZE 32768 +#define ZF_ASCSIZE (ZF_BUFSIZE/2) + /* ret = 2 signals the local read/write failed, so send abort */ + int n, ret = 0, gotack = 0, fdin, fdout, fromasc = 0, toasc = 0; + int rtmout = 0, wtmout = 0; + char lsbuf[ZF_BUFSIZE], *ascbuf = NULL, *optr; + long sofar = 0, last_sofar = 0; + readwrite_t read_ptr = zfread, write_ptr = zfwrite; + List l; + + if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) { + /* + * progress to set up: ZFTP_COUNT is zero. + * We do this here in case we needed to wait for a RETR + * command to tell us how many bytes are coming. + */ + doshfunc("zftp_progress", l, NULL, 0, 1); + /* Now add in the bit of the file we've got/sent already */ + sofar = last_sofar = startat; + } + if (recv) { + fdin = zdfd; + fdout = 1; + rtmout = getiparam("ZFTP_TMOUT"); + if (ZFST_CTYP(zfstatus) == ZFST_ASCI) + fromasc = 1; + if (ZFST_MODE(zfstatus) == ZFST_BLOC) + read_ptr = zfread_block; + } else { + fdin = 0; + fdout = zdfd; + wtmout = getiparam("ZFTP_TMOUT"); + if (ZFST_CTYP(zfstatus) == ZFST_ASCI) + toasc = 1; + if (ZFST_MODE(zfstatus) == ZFST_BLOC) + write_ptr = zfwrite_block; + } + + if (toasc) + ascbuf = zalloc(ZF_ASCSIZE); + zfpipe(); + zfread_eof = 0; + while (!ret && !zfread_eof) { + n = (toasc) ? read_ptr(fdin, ascbuf, ZF_ASCSIZE, rtmout) + : read_ptr(fdin, lsbuf, ZF_BUFSIZE, rtmout); + if (n > 0) { + char *iptr; + if (toasc) { + /* \n -> \r\n it shouldn't happen to a dog. */ + char *iptr = ascbuf, *optr = lsbuf; + int cnt = n; + while (cnt--) { + if (*iptr == '\n') { + *optr++ = '\r'; + n++; + } + *optr++ = *iptr++; + } + } + if (fromasc && (iptr = memchr(lsbuf, '\r', n))) { + /* \r\n -> \n */ + char *optr = iptr; + int cnt = n - (iptr - lsbuf); + while (cnt--) { + if (*iptr != '\r' || iptr[1] != '\n') { + *optr++ = *iptr; + } else + n--; + iptr++; + } + } + optr = lsbuf; + + sofar += n; + + for (;;) { + /* + * in principle, write can be interrupted after + * safely writing some bytes, and will return the + * number already written, which may not be the + * complete buffer. so make this robust. they call me + * `robustness stephenson'. in my dreams. + */ + int newn = write_ptr(fdout, optr, n, wtmout); + if (newn == n) + break; + if (newn < 0) { + /* + * The somewhat contorted test here (for write) + * and below (for read) means: + * real error if + * - errno is set and it's not just an interrupt, or + * - errflag is set, probably due to CTRL-c, or + * - zfdrrrring is set, due to the alarm going off. + * print an error message if + * - not a timeout, since that was reported, and + * either + * - a non-interactive shell, where we don't + * know what happened otherwise + * - or both of + * - not errflag, i.e. CTRL-c or what have you, + * since the user probably knows about that, and + * - not a SIGPIPE, since usually people are + * silent about those when going to pagers + * (if you quit less or more in the middle + * and see an error message you think `I + * shouldn't have done that'). + * + * If we didn't print an error message here, + * and were going to do an abort (ret == 2) + * because the error happened on the local end + * of the connection, set ret to 3 and don't print + * the 'aborting...' either. + * + * There must be a better way of doing this. + */ + if (errno != EINTR || errflag || zfdrrrring) { + if (!zfdrrrring && + (!interact || (!errflag && errno != EPIPE))) { + ret = recv ? 2 : 1; + zwarnnam(name, "write failed: %e", NULL, errno); + } else + ret = recv ? 3 : 1; + break; + } + continue; + } + optr += newn; + n -= newn; + } + } else if (n < 0) { + if (errno != EINTR || errflag || zfdrrrring) { + if (!zfdrrrring && + (!interact || (!errflag && errno != EPIPE))) { + ret = recv ? 1 : 2; + zwarnnam(name, "read failed: %e", NULL, errno); + } else + ret = recv ? 1 : 3; + break; + } + } else + break; + if (!ret && sofar != last_sofar && progress && + (l = getshfunc("zftp_progress")) != &dummy_list) { + zfsetparam("ZFTP_COUNT", &sofar, ZFPM_READONLY|ZFPM_INTEGER); + doshfunc("zftp_progress", l, NULL, 0, 1); + last_sofar = sofar; + } + } + zfunpipe(); + /* + * At this point any timeout was on the data connection, + * so we don't need to force the control connection to close. + */ + zfdrrrring = 0; + if (!errflag && !ret && !recv && ZFST_MODE(zfstatus) == ZFST_BLOC) { + /* send an end-of-file marker block */ + ret = (zfwrite_block(fdout, lsbuf, 0, wtmout) < 0); + } + if (errflag || ret > 1) { + /* + * some error occurred, maybe a keyboard interrupt, or + * a local file/pipe handling problem. + * send an abort. + * + * safest to block all signals here? can get frustrating if + * we're waiting for an abort. don't I know. let's start + * off just by blocking SIGINT's. + * + * maybe the timeout for the abort should be shorter than + * for normal commands. and what about aborting after + * we had a timeout on the data connection, is that + * really a good idea? + */ + /* RFC 959 says this is what to send */ + unsigned char msg[4] = { IAC, IP, IAC, SYNCH }; + + if (ret == 2) + zwarnnam(name, "aborting data transfer...", NULL, 0); + + holdintr(); + + /* the following is black magic, as far as I'm concerned. */ + /* what are we going to do if it fails? not a lot, actually. */ + send(zcfd, (char *)msg, 3, 0); + send(zcfd, (char *)msg+3, 1, MSG_OOB); + + zfsendcmd("ABOR\r\n"); + if (lastcode == 226) { + /* + * 226 is supposed to mean the transfer got sent OK after + * all, and the abort got ignored, at least that's what + * rfc959 seems to be saying. but in fact what can happen + * is the transfer finishes (at least as far as the + * server's concerned) and it's response is waiting, then + * the abort gets sent, and we need to mop up a response to + * that. so actually in most cases we get two replies + * anyway. we could test if we had select() on all hosts. + */ + /* gotack = 1; */ + /* + * we'd better leave errflag, since we don't know + * where it came from. maybe the user wants to abort + * a whole script or function. + */ + } else + ret = 1; + + noholdintr(); + } + + if (toasc) + zfree(ascbuf, ZF_ASCSIZE); + zfclosedata(); + if (!gotack && zfgetmsg() > 2) + ret = 1; + return ret != 0; +} + +/* Open a new control connection, i.e. start a new FTP session */ + +/**/ +static int +zftp_open(char *name, char **args, int flags) +{ + struct in_addr ipaddr; + struct protoent *zprotop; + struct servent *zservp; + struct hostent *zhostp = NULL; + char **addrp, tbuf[2] = "X", *fname; + int err, len, tmout; + + if (!*args) { + if (zfuserparams) + args = zfuserparams; + else { + zwarnnam(name, "no host specified", NULL, 0); + return 1; + } + } + + /* + * Close the existing connection if any. + * Probably this is the safest thing to do. It's possible + * a `QUIT' will hang, though. + */ + if (zcfd != -1) + zfclose(); + + /* this is going to give 0. why bother? */ + zprotop = getprotobyname("tcp"); + zservp = getservbyname("ftp", "tcp"); + + if (!zprotop || !zservp) { + zwarnnam(name, "Somebody stole FTP!", NULL, 0); + return 1; + } + + /* don't try talking to server yet */ + zcfinish = 2; + + /* + * This sets an alarm for the whole process, getting the host name + * as well as connecting. Arguably you could time them out separately. + */ + tmout = getiparam("ZFTP_TMOUT"); + if (setjmp(zfalrmbuf)) { + char *hname; + alarm(0); + if ((hname = getsparam("ZFTP_HOST")) && *hname) + zwarnnam(name, "timeout connecting to %s", hname, 0); + else + zwarnnam(name, "timeout on host name lookup", NULL, 0); + zfclose(); + return 1; + } + zfalarm(tmout); + + /* + * Now this is what I like. A library which provides decent higher + * level functions to do things like converting address types. It saves + * so much trouble. Pity about the rest of the network interface, though. + */ + ipaddr.s_addr = inet_addr(args[0]); + if (ipaddr.s_addr != INADDR_NONE) { + /* + * hmmm, we don't necessarily want to do this... maybe the + * user is actively trying to avoid a bad nameserver. + * perhaps better just to set ZFTP_HOST to the dot address, too. + * that way shell functions know how it was opened. + * + * zhostp = gethostbyaddr(&ipaddr, sizeof(ipaddr), AF_INET); + * + * or, we could have a `no_lookup' flag. + */ + zfsetparam("ZFTP_HOST", ztrdup(args[0]), ZFPM_READONLY); + zsock.sin_family = AF_INET; + } else { + zhostp = gethostbyname(args[0]); + if (!zhostp || errflag) { + /* should use herror() here if available, but maybe + * needs configure test. on AIX it's present but not + * in headers. + */ + zwarnnam(name, "host not found: %s", args[0], 0); + alarm(0); + return 1; + } + zsock.sin_family = zhostp->h_addrtype; + zfsetparam("ZFTP_HOST", ztrdup(zhostp->h_name), ZFPM_READONLY); + } + + zsock.sin_port = ntohs(zservp->s_port); + zcfd = zfmovefd(socket(zsock.sin_family, SOCK_STREAM, 0)); + if (zcfd < 0) { + zwarnnam(name, "socket failed: %e", NULL, errno); + zfunsetparam("ZFTP_HOST"); + alarm(0); + return 1; + } + +#if defined(F_SETFD) && defined(FD_CLOEXEC) + /* If the shell execs a program, we don't want this fd left open. */ + len = FD_CLOEXEC; + fcntl(zcfd, F_SETFD, &len); +#endif + + /* + * now connect the socket. manual pages all say things like `this is all + * explained oh-so-wonderfully in some other manual page'. not. + */ + + err = 1; + + if (ipaddr.s_addr != INADDR_NONE) { + /* dot address */ + memcpy(&zsock.sin_addr, &ipaddr, sizeof(ipaddr)); + do { + err = connect(zcfd, (struct sockaddr *)&zsock, sizeof(zsock)); + } while (err && errno == EINTR && !errflag); + } else { + /* host name: try all possible IP's */ + for (addrp = zhostp->h_addr_list; *addrp; addrp++) { + memcpy(&zsock.sin_addr, *addrp, zhostp->h_length); + do { + err = connect(zcfd, (struct sockaddr *)&zsock, sizeof(zsock)); + } while (err && errno == EINTR && !errflag); + /* you can check whether it's worth retrying here */ + } + } + + alarm(0); + + if (err < 0) { + zwarnnam(name, "connect failed: %e", NULL, errno); + zfclose(); + return 1; + } + zfsetparam("ZFTP_IP", ztrdup(inet_ntoa(zsock.sin_addr)), ZFPM_READONLY); + /* now we can talk to the control connection */ + zcfinish = 0; + + len = sizeof(zsock); + if (getsockname(zcfd, (struct sockaddr *)&zsock, &len) < 0) { + zwarnnam(name, "getsockname failed: %e", NULL, errno); + zfclose(); + return 1; + } + /* nice to get some options right, ignore if they don't work */ +#ifdef SO_OOBINLINE + /* + * this says we want messages in line. maybe sophisticated people + * do clever things with SIGURG. + */ + len = 1; + setsockopt(zcfd, SOL_SOCKET, SO_OOBINLINE, (char *)&len, sizeof(len)); +#endif +#if defined(IP_TOS) && defined(IPTOS_LOWDELAY) + /* for control connection we want low delay. please don't laugh. */ + len = IPTOS_LOWDELAY; + setsockopt(zcfd, IPPROTO_IP, IP_TOS, (char *)&len, sizeof(len)); +#endif + + /* + * We use stdio with line buffering for convenience on input. + * On output, we can just dump a complete message to the fd via write(). + */ + zcin = fdopen(zcfd, "r"); + + if (!zcin) { + zwarnnam(name, "file handling error", NULL, 0); + zfclose(); + return 1; + } + +#ifdef _IONBF + setvbuf(zcin, NULL, _IONBF, 0); +#else + setlinebuf(zcin); +#endif + + /* + * now see what the remote server has to say about that. + */ + if (zfgetmsg() >= 4) { + zfclose(); + return 1; + } + + zfis_unix = 0; + zfhas_size = zfhas_mdtm = ZFCP_UNKN; + zdfd = -1; + /* initial status: open, ASCII data, stream mode 'n' stuff */ + zfstatus = 0; + + /* open file for saving the current status */ + fname = gettempname(); + zfstatfd = open(fname, O_RDWR|O_CREAT, 0600); + DPUTS(zfstatfd == -1, "zfstatfd not created"); +#if defined(F_SETFD) && defined(FD_CLOEXEC) + /* If the shell execs a program, we don't want this fd left open. */ + len = FD_CLOEXEC; + fcntl(zfstatfd, F_SETFD, &len); +#endif + unlink(fname); + + /* now find out what system we're connected to */ + if (!(zfprefs & ZFPF_DUMB) && zfsendcmd("SYST\r\n") == 2) { + char *ptr = lastmsg, *eptr, *systype; + for (eptr = ptr; *eptr; eptr++) + ; + systype = ztrduppfx(ptr, eptr-ptr); + if (!strncmp(systype, "UNIX Type: L8", 13)) { + /* + * Use binary for transfers. This simple test saves much + * hassle for all concerned, particularly me. + */ + zfstatus |= ZFST_IMAG; + zfis_unix = 1; + } + /* + * we could set zfis_unix based just on the UNIX part, + * but I don't really know the consequences of that. + */ + zfsetparam("ZFTP_SYSTEM", systype, ZFPM_READONLY); + } else if (zcfd == -1) { + /* final paranoid check */ + return 1; + } + + tbuf[0] = (ZFST_TYPE(zfstatus) == ZFST_ASCI) ? 'A' : 'I'; + zfsetparam("ZFTP_TYPE", ztrdup(tbuf), ZFPM_READONLY); + zfsetparam("ZFTP_MODE", ztrdup("S"), ZFPM_READONLY); + /* if remaining arguments, use them to log in. */ + if (zcfd > -1 && *++args) + return zftp_login(name, args, flags); + /* if something wayward happened, connection was already closed */ + return zcfd == -1; +} + +/* + * Read a parameter string, with a prompt if reading from stdin. + * The returned string is on the heap. + * If noecho, turn off ECHO mode while reading. + */ + +/**/ +static char * +zfgetinfo(char *prompt, int noecho) +{ + int resettty = 0; + /* 256 characters should be enough, hardly worth allocating + * a password string byte by byte + */ + char instr[256], *strret; + int len; + + /* + * Only print the prompt if getting info from a tty. Of + * course, we don't know if stderr has been redirected, but + * that seems a minor point. + */ + if (isatty(0)) { + if (noecho) { + /* hmmm... all this great big shell and we have to read + * something with no echo by ourselves. + * bin_read() is far to complicated for our needs. + * we could use zread(), but that relies on static + * variables, so someone doesn't want that to happen. + * + * this is modified from setcbreak() in utils.c, + * except I don't see any point in using cbreak mode + */ + struct ttyinfo ti; + + ti = shttyinfo; +#ifdef HAS_TIO + ti.tio.c_lflag &= ~ECHO; +#else + ti.sgttyb.sg_flags &= ~ECHO; +#endif + settyinfo(&ti); + resettty = 1; + } + fflush(stdin); + fputs(prompt, stderr); + fflush(stderr); + } + + fgets(instr, 256, stdin); + if (instr[len = strlen(instr)-1] == '\n') + instr[len] = '\0'; + + strret = dupstring(instr); + + if (resettty) { + /* '\n' didn't get echoed */ + fputc('\n', stdout); + fflush(stdout); + settyinfo(&shttyinfo); + } + + return strret; +} + +/* + * set params for an open with no arguments. + * this allows easy re-opens. + */ + +/**/ +static int +zftp_params(char *name, char **args, int flags) +{ + char *prompts[] = { "Host: ", "User: ", "Password: ", "Account: " }; + char **aptr, **newarr; + int i, j, len; + + if (!*args) { + if (zfuserparams) { + for (aptr = zfuserparams, i = 0; *aptr; aptr++, i++) { + if (i == 2) { + len = strlen(*aptr); + for (j = 0; j < len; j++) + fputc('*', stdout); + fputc('\n', stdout); + } else + fprintf(stdout, "%s\n", *aptr); + } + } + return 0; + } + if (!strcmp(*args, "-")) { + if (zfuserparams) + freearray(zfuserparams); + zfuserparams = 0; + return 0; + } + len = arrlen(args); + newarr = (char **)zcalloc((len+1)*sizeof(char *)); + for (aptr = args, i = 0; *aptr && !errflag; aptr++, i++) { + char *str; + if (!strcmp(*aptr, "?")) + str = zfgetinfo(prompts[i], i == 2); + else + str = *aptr; + newarr[i] = ztrdup(str); + } + if (errflag) { + /* maybe user CTRL-c'd in the middle somewhere */ + for (aptr = newarr; *aptr; aptr++) + zsfree(*aptr); + zfree(newarr, len+1); + return 1; + } + if (zfuserparams) + freearray(zfuserparams); + zfuserparams = newarr; + return 0; +} + +/* login a user: often called as part of the open sequence */ + +/**/ +static int +zftp_login(char *name, char **args, int flags) +{ + char *ucmd, *passwd = NULL, *acct = NULL; + char *user; + int stopit; + + if ((zfstatus & ZFST_LOGI) && zfsendcmd("REIN\r\n") >= 4) + return 1; + + zfstatus &= ~ZFST_LOGI; + if (*args) { + user = *args++; + } else { + user = zfgetinfo("User: ", 0); + } + + ucmd = tricat("USER ", user, "\r\n"); + stopit = 0; + + if (zfsendcmd(ucmd) == 6) + stopit = 2; + + while (!stopit && !errflag) { + switch (lastcode) { + case 230: /* user logged in */ + case 202: /* command not implemented, don't care */ + stopit = 1; + break; + + case 331: /* need password */ + if (*args) + passwd = *args++; + else + passwd = zfgetinfo("Password: ", 1); + zsfree(ucmd); + ucmd = tricat("PASS ", passwd, "\r\n"); + if (zfsendcmd(ucmd) == 6) + stopit = 2; + break; + + case 332: /* need account */ + case 532: + if (*args) + acct = *args++; + else + acct = zfgetinfo("Account: ", 0); + zsfree(ucmd); + ucmd = tricat("ACCT ", passwd, "\r\n"); + if (zfsendcmd(ucmd) == 6) + stopit = 2; + break; + + case 421: /* service not available, so closed anyway */ + case 501: /* syntax error */ + case 503: /* bad commands */ + case 530: /* not logged in */ + case 550: /* random can't-do-that */ + default: /* whatever, should flag this as bad karma */ + /* need more diagnostics here */ + stopit = 2; + break; + } + } + + zsfree(ucmd); + if (zcfd == -1) + return 1; + if (stopit == 2 || (lastcode != 230 && lastcode != 202)) { + zwarnnam(name, "login failed", NULL, 0); + return 1; + } + + if (*args) { + int cnt; + for (cnt = 0; *args; args++) + cnt++; + zwarnnam(name, "warning: %d comand arguments not used\n", NULL, cnt); + } + zfstatus |= ZFST_LOGI; + zfsetparam("ZFTP_USER", ztrdup(user), ZFPM_READONLY); + if (acct) + zfsetparam("ZFTP_ACCOUNT", ztrdup(acct), ZFPM_READONLY); + + /* + * Get the directory. This is possibly an unnecessary overhead, of + * course, but when you're being driven by shell functions there's + * just no way of telling. + */ + return zfgetcwd(); +} + +/* do ls or dir on the remote directory */ + +/**/ +static int +zftp_dir(char *name, char **args, int flags) +{ + /* maybe should be cleverer about handling arguments */ + char *cmd; + int ret; + + /* + * RFC959 says this must be ASCII or EBCDIC, not image format. + * I rather suspect on a UNIX server we get away handsomely + * with doing everything, including this, as image. + */ + zfsettype(ZFST_ASCI); + + cmd = zfargstring((flags & ZFTP_NLST) ? "NLST" : "LIST", args); + ret = zfgetdata(name, NULL, cmd, 0); + zsfree(cmd); + if (ret) + return 1; + + fflush(stdout); /* since we're now using fd 1 */ + return zfsenddata(name, 1, 0, 0); +} + +/* change the remote directory */ + +/**/ +static int +zftp_cd(char *name, char **args, int flags) +{ + /* change directory --- enhance to allow 'zftp cdup' */ + int ret; + + if ((flags & ZFTP_CDUP) || !strcmp(*args, "..") || + !strcmp(*args, "../")) { + ret = zfsendcmd("CDUP\r\n"); + } else { + char *cmd = tricat("CWD ", *args, "\r\n"); + ret = zfsendcmd(cmd); + zsfree(cmd); + } + if (ret > 2) + return 1; + /* sometimes the directory may be in the response. usually not. */ + if (zfgetcwd()) + return 1; + + return 0; +} + +/* get the remote directory */ + +/**/ +static int +zfgetcwd(void) +{ + char *ptr, *eptr; + int endc; + List l; + + if (zfprefs & ZFPF_DUMB) + return 1; + if (zfsendcmd("PWD\r\n") > 2) { + zfunsetparam("ZFTP_PWD"); + return 1; + } + ptr = lastmsg; + while (*ptr == ' ') + ptr++; + if (!*ptr) /* ultra safe */ + return 1; + if (*ptr == '"') { + ptr++; + endc = '"'; + } else + endc = ' '; + for (eptr = ptr; *eptr && *eptr != endc; eptr++) + ; + zfsetparam("ZFTP_PWD", ztrduppfx(ptr, eptr-ptr), ZFPM_READONLY); + + /* + * This isn't so necessary if we're going to have a shell function + * front end. By putting it here, and in close when ZFTP_PWD is unset, + * we at least cover the bases. + */ + if ((l = getshfunc("zftp_chpwd")) != &dummy_list) + doshfunc("zftp_chpwd", l, NULL, 0, 1); + + return 0; +} + +/* + * Set the type for the next transfer, usually image (binary) or ASCII. + */ + +/**/ +static int +zfsettype(int type) +{ + char buf[] = "TYPE X\r\n"; + if (ZFST_TYPE(type) == ZFST_CTYP(zfstatus)) + return 0; + buf[5] = (ZFST_TYPE(type) == ZFST_ASCI) ? 'A' : 'I'; + if (zfsendcmd(buf) > 2) + return 1; + zfstatus &= ~(ZFST_TMSK << ZFST_TBIT); + /* shift the type left to set the current type bits */; + zfstatus |= type << ZFST_TBIT; + return 0; +} + +/* + * Print or get a new type for the transfer. + * We don't actually set the type at this point. + */ + +/**/ +static int +zftp_type(char *name, char **args, int flags) +{ + char *str, nt, tbuf[2] = "A"; + if (flags & (ZFTP_TBIN|ZFTP_TASC)) { + nt = (flags & ZFTP_TBIN) ? 'I' : 'A'; + } else if (!(str = *args)) { + /* + * Since this is supposed to be a low-level basis for + * an FTP system, just print the single code letter. + */ + printf("%c\n", (ZFST_TYPE(zfstatus) == ZFST_ASCI) ? 'A' : 'I'); + fflush(stdout); + return 0; + } else { + nt = toupper(*str); + /* + * RFC959 specifies other types, but these are the only + * ones we know what to do with. + */ + if (str[1] || (nt != 'A' && nt != 'B' && nt != 'I')) { + zwarnnam(name, "transfer type %s not recognised", str, 0); + return 1; + } + + if (nt == 'B') /* binary = image */ + nt = 'I'; + } + + zfstatus &= ~ZFST_TMSK; + zfstatus |= (nt == 'I') ? ZFST_IMAG : ZFST_ASCI; + tbuf[0] = nt; + zfsetparam("ZFTP_TYPE", ztrdup(tbuf), ZFPM_READONLY); + return 0; +} + +/**/ +static int +zftp_mode(char *name, char **args, int flags) +{ + char *str, cmd[] = "MODE X\r\n"; + int nt; + + if (!(str = *args)) { + printf("%c\n", (ZFST_MODE(zfstatus) == ZFST_STRE) ? 'S' : 'B'); + fflush(stdout); + return 0; + } + nt = str[0] = toupper(*str); + if (str[1] || (nt != 'S' && nt != 'B')) { + zwarnnam(name, "transfer mode %s not recognised", str, 0); + return 1; + } + cmd[5] = (char) nt; + if (zfsendcmd(cmd) > 2) + return 1; + zfstatus &= ZFST_MMSK; + zfstatus |= (nt == 'S') ? ZFST_STRE : ZFST_BLOC; + zfsetparam("ZFTP_MODE", ztrdup(str), ZFPM_READONLY); + return 0; +} + +/**/ +static int +zftp_local(char *name, char **args, int flags) +{ + int more = !!args[1], ret = 0, dofd = !*args; + while (*args || dofd) { + long sz; + char *mt; + int newret = zfstats(*args, !(flags & ZFTP_HERE), &sz, &mt, + dofd ? 0 : -1); + if (newret == 2) /* at least one is not implemented */ + return 2; + else if (newret) { + ret = 1; + if (mt) + zsfree(mt); + args++; + continue; + } + if (more) { + fputs(*args, stdout); + fputc(' ', stdout); + } + printf("%ld %s\n", sz, mt); + zsfree(mt); + if (dofd) + break; + args++; + } + fflush(stdout); + + return ret; +} + +/* + * Generic transfer for get, put and append. + * + * Get sends all files to stdout, i.e. this is basically cat. It's up to a + * shell function driver to turn this into standard FTP-like commands. + * + * Put/append sends everything from stdin down the drai^H^H^Hata connection. + * Slightly weird with multiple files in that it tries to read + * a separate complete file from stdin each time, which is + * only even potentially useful interactively. But the only + * real alternative is just to allow one file at a time. + */ + +/**/ +static int +zftp_getput(char *name, char **args, int flags) +{ + int ret = 0, recv = (flags & ZFTP_RECV), getsize = 0, progress = 1; + char *cmd = recv ? "RETR " : (flags & ZFTP_APPE) ? "APPE " : "STOR "; + List l; + + /* + * At this point I'd like to set progress to 0 if we're + * backgrounded, since it's hard for the user to find out. + * It turns out it's hard enough for us to find out. + * The problem is that zsh clears it's job table, so we + * just don't know if we're some forked shell in a pipeline + * somewhere or in the background. This seems to me a problem. + */ + + zfsettype(ZFST_TYPE(zfstatus)); + + if (recv) + fflush(stdout); /* since we may be using fd 1 */ + for (; *args; args++) { + char *ln, *rest = NULL; + long startat = 0; + if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) { + long sz; + /* + * This calls the SIZE command to get the size for remote + * files. Some servers send the size with the reply to + * the transfer command (i.e. RETR), in which + * case we note the fact and don't call this + * next time. For that reason, the first call + * of zftp_progress is delayed until zfsenddata(). + */ + if ((!(zfprefs & ZFPF_DUMB) && + (zfstatus & (ZFST_NOSZ|ZFST_TRSZ)) != ZFST_TRSZ) + || !recv) { + /* the final 0 is a local fd to fstat if recv is zero */ + zfstats(*args, recv, &sz, NULL, 0); + /* even if it doesn't support SIZE, it may tell us */ + if (recv && sz == -1) + getsize = 1; + } else + getsize = 1; + zfstarttrans(*args, recv, sz); + } + + if (flags & ZFTP_REST) { + startat = zstrtol(args[1], NULL, 10); + rest = tricat("REST ", args[1], "\r\n"); + } + + ln = tricat(cmd, *args, "\r\n"); + /* note zdfd doesn't exist till zfgetdata() creates it */ + if (zfgetdata(name, rest, ln, getsize) || + zfsenddata(name, recv, progress, startat)) + ret = 1; + zsfree(ln); + if (progress && (l = getshfunc("zftp_progress")) != &dummy_list) { + /* progress to finish: ZFTP_TRANSFER set to GF or PF */ + zfsetparam("ZFTP_TRANSFER", ztrdup(recv ? "GF" : "PF"), + ZFPM_READONLY); + doshfunc("zftp_progress", l, NULL, 0, 1); + } + if (rest) { + zsfree(rest); + args++; + } + if (errflag) + break; + } + zfendtrans(); + return ret; +} + +/* + * Delete a list of files on the server. We allow a list by analogy with + * `rm'. + */ + +/**/ +static int +zftp_delete(char *name, char **args, int flags) +{ + int ret = 0; + char *cmd, **aptr; + for (aptr = args; *aptr; aptr++) { + cmd = tricat("DELE ", *aptr, "\r\n"); + if (zfsendcmd(cmd) > 2) + ret = 1; + zsfree(cmd); + } + return ret; +} + +/* Create or remove a directory on the server */ + +/**/ +static int +zftp_mkdir(char *name, char **args, int flags) +{ + int ret; + char *cmd = tricat((flags & ZFTP_DELE) ? "RMD " : "MKD ", + *args, "\r\n"); + ret = (zfsendcmd(cmd) > 2); + zsfree(cmd); + return ret; +} + +/* Rename a file on the server */ + +/**/ +static int +zftp_rename(char *name, char **args, int flags) +{ + int ret; + char *cmd; + + cmd = tricat("RNFR ", args[0], "\r\n"); + ret = 1; + if (zfsendcmd(cmd) == 3) { + zsfree(cmd); + cmd = tricat("RNTO ", args[1], "\r\n"); + if (zfsendcmd(cmd) == 2) + ret = 0; + } + zsfree(cmd); + return ret; +} + +/* + * Send random commands, either with SITE or literal. + * In the second case, the user better know what they're doing. + */ + +/**/ +static int +zftp_quote(char *name, char **args, int flags) +{ + int ret = 0; + char *cmd; + + cmd = (flags & ZFTP_SITE) ? zfargstring("SITE", args) + : zfargstring(args[0], args+1); + ret = (zfsendcmd(cmd) > 2); + zsfree(cmd); + + return ret; +} + +/* Close the connection, ending the session */ + +/**/ +static int +zftp_close(char *name, char **args, int flags) +{ + char **aptr; + List l; + zfclosing = 1; + if (zcfinish != 2) { + /* + * haven't had EOF from server, so send a QUIT and get the response. + * maybe we should set a shorter timeout for this to avoid + * CTRL-c rage. + */ + zfsendcmd("QUIT\r\n"); + } + if (zcin) + fclose(zcin); + zcin = NULL; + close(zcfd); + zcfd = -1; + + /* Write the final status in case this is a subshell */ + zfstatus |= ZFST_CLOS; + lseek(zfstatfd, 0, 0); + write(zfstatfd, &zfstatus, sizeof(zfstatus)); + close(zfstatfd); + zfstatfd = -1; + + /* Unset the non-special parameters */ + for (aptr = zfparams; *aptr; aptr++) + zfunsetparam(*aptr); + + /* Now ZFTP_PWD is unset. It's up to zftp_chpwd to notice. */ + if ((l = getshfunc("zftp_chpwd")) != &dummy_list) + doshfunc("zftp_chpwd", l, NULL, 0, 1); + + /* tidy up status variables, because mess is bad */ + zfclosing = zfdrrrring = 0; + + return 0; +} + +/* Safe front end to zftp_close() from within the package */ + +/**/ +static void +zfclose(void) +{ + if (zcfd != -1) + zftp_close("zftp close", NULL, 0); +} + +/* The builtin command frontend to the rest of the package */ + +/**/ +static int +bin_zftp(char *name, char **args, char *ops, int func) +{ + char fullname[11] = "zftp "; + char *cnam = *args++, *prefs, *ptr; + Zftpcmd zptr; + int n, ret; + + for (zptr = zftpcmdtab; zptr->nam; zptr++) + if (!strcmp(zptr->nam, cnam)) + break; + + if (!zptr->nam) { + zwarnnam(name, "no such subcommand: %s", cnam, 0); + return 1; + } + + /* check number of arguments */ + for (n = 0; args[n]; n++) + ; + if (n < zptr->min || (zptr->max != -1 && n > zptr->max)) { + zwarnnam(name, "wrong no. of arguments for %s", cnam, 0); + return 1; + } + + strcat(fullname, cnam); + if (zfstatfd != -1) { + /* Get the status in case it was set by a forked process */ + int oldstatus = zfstatus; + lseek(zfstatfd, 0, 0); + read(zfstatfd, &zfstatus, sizeof(zfstatus)); + if (zcfd != -1 && (zfstatus & ZFST_CLOS)) { + /* got closed in subshell without us knowing */ + zcfinish = 2; + zfclose(); + } else { + /* + * fix up status types: unfortunately they may already + * have been looked at between being changed in the subshell + * and now, but we can't help that. + */ + if (ZFST_TYPE(oldstatus) != ZFST_TYPE(zfstatus)) + zfsetparam("ZFTP_TYPE", + ztrdup(ZFST_TYPE(zfstatus) == ZFST_ASCI ? + "A" : "I"), ZFPM_READONLY); + if (ZFST_MODE(oldstatus) != ZFST_MODE(zfstatus)) + zfsetparam("ZFTP_MODE", + ztrdup(ZFST_MODE(zfstatus) == ZFST_BLOC ? + "B" : "S"), ZFPM_READONLY); + } + } + if ((zptr->flags & ZFTP_CONN) && zcfd == -1) { + zwarnnam(fullname, "not connected.", NULL, 0); + return 1; + } + + if ((prefs = getsparam("ZFTP_PREFS"))) { + zfprefs = 0; + for (ptr = prefs; *ptr; ptr++) { + switch (toupper(*ptr)) { + case 'S': + /* sendport */ + zfprefs |= ZFPF_SNDP; + break; + + case 'P': + /* + * passive + * If we have already been told to use sendport mode, + * we're never going to use passive mode. + */ + if (!(zfprefs & ZFPF_SNDP)) + zfprefs |= ZFPF_PASV; + break; + + case 'D': + /* dumb */ + zfprefs |= ZFPF_DUMB; + break; + + default: + zwarnnam(name, "preference %c not recognized", NULL, *ptr); + break; + } + } + } + + ret = (*zptr->fun)(fullname, args, zptr->flags); + + if (zfalarmed) + zfunalarm(); + if (zfdrrrring) { + /* had a timeout, close the connection */ + zcfinish = 2; /* don't try sending QUIT */ + zfclose(); + } + if (zfstatfd != -1) { + /* Set the status in case another process needs to know */ + lseek(zfstatfd, 0, 0); + write(zfstatfd, &zfstatus, sizeof(zfstatus)); + } + return ret; +} + +/* The load/unload routines required by the zsh library interface */ + +/**/ +int +boot_zftp(Module m) +{ + int ret; + if ((ret = addbuiltins(m->nam, bintab, + sizeof(bintab)/sizeof(*bintab))) == 1) { + /* if successful, set some default parameters */ + long tmout_def = 60; + zfsetparam("ZFTP_VERBOSE", ztrdup("450"), ZFPM_IFUNSET); + zfsetparam("ZFTP_TMOUT", &tmout_def, ZFPM_IFUNSET|ZFPM_INTEGER); + zfsetparam("ZFTP_PREFS", ztrdup("PS"), ZFPM_IFUNSET); + /* default preferences if user deletes variable */ + zfprefs = ZFPF_SNDP|ZFPF_PASV; + } + return !ret; +} + +#ifdef MODULE + +/**/ +int +cleanup_zftp(Module m) +{ + /* + * There are various parameters hanging around, but they're + * all non-special so are entirely non-life-threatening. + */ + zfclosedata(); + zfclose(); + zsfree(lastmsg); + if (zfuserparams) + freearray(zfuserparams); + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} + +#endif diff --git a/Src/Modules/zftp.mdd b/Src/Modules/zftp.mdd new file mode 100644 index 000000000..83051ae54 --- /dev/null +++ b/Src/Modules/zftp.mdd @@ -0,0 +1,3 @@ +autobins="zftp" + +objects="zftp.o" diff --git a/Src/Zle/.cvsignore b/Src/Zle/.cvsignore new file mode 100644 index 000000000..b10adcc3b --- /dev/null +++ b/Src/Zle/.cvsignore @@ -0,0 +1,14 @@ +Makefile +Makefile.in +*.pro +*.o +*.o.c +*.so +*.mdh +*.mdhi +*.mdhs +*.mdh.tmp +thingies.list +widgets.list +zle_things.h +zle_widget.h diff --git a/Src/Zle/.distfiles b/Src/Zle/.distfiles new file mode 100644 index 000000000..42c62efe9 --- /dev/null +++ b/Src/Zle/.distfiles @@ -0,0 +1,10 @@ +DISTFILES_SRC=' + .cvsignore .distfiles .exrc + comp1.mdd comp.h comp1.c + compctl.mdd compctl.c + deltochar.mdd deltochar.c + zle.mdd iwidgets.list zle.h zle_bindings.c zle_hist.c + zle_keymap.c zle_main.c zle_misc.c zle_move.c zle_params.c + zle_refresh.c zle_things.sed zle_thingy.c zle_tricky.c + zle_utils.c zle_vi.c zle_widget.sed zle_word.c +' diff --git a/Src/Zle/.exrc b/Src/Zle/.exrc new file mode 100644 index 000000000..91d0b39ef --- /dev/null +++ b/Src/Zle/.exrc @@ -0,0 +1,2 @@ +set ai +set sw=4 diff --git a/Src/Zle/.lastloc b/Src/Zle/.lastloc new file mode 100644 index 000000000..a060de394 --- /dev/null +++ b/Src/Zle/.lastloc @@ -0,0 +1,10 @@ +(("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle.h" . 2619) + ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle.export" . 35) + ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle_main.c" . 13806) + ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/iwidgets.list" . 7831) + ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/comp.h" . 8512) + ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle_tricky.c" . 72920) + ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/comp1.c" . 9640) + ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle_vi.c" . 16035) + ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/zle_utils.c" . 8406) + ("/home/user2/pws/src/zsh-3.1.5/Src/Zle/comp1.export" . 81)) diff --git a/Src/Zle/comp.h b/Src/Zle/comp.h new file mode 100644 index 000000000..5d7ef74e2 --- /dev/null +++ b/Src/Zle/comp.h @@ -0,0 +1,137 @@ +/* + * comp.h - header file for completion + * + * 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. + * + */ + +#undef compctlread + +typedef struct compctlp *Compctlp; +typedef struct compctl *Compctl; +typedef struct compcond *Compcond; + +/* node for compctl hash table (compctltab) */ + +struct compctlp { + HashNode next; /* next in hash chain */ + char *nam; /* command name */ + int flags; /* CURRENTLY UNUSED */ + Compctl cc; /* pointer to the compctl desc. */ +}; + +/* compctl -x condition */ + +struct compcond { + Compcond and, or; /* the next or'ed/and'ed conditions */ + int type; /* the type (CCT_*) */ + int n; /* the array length */ + union { /* these structs hold the data used to */ + struct { /* test this condition */ + int *a, *b; /* CCT_POS, CCT_NUMWORDS */ + } + r; + struct { /* CCT_CURSTR, CCT_CURPAT,... */ + int *p; + char **s; + } + s; + struct { /* CCT_RANGESTR,... */ + char **a, **b; + } + l; + } + u; +}; + +#define CCT_UNUSED 0 +#define CCT_POS 1 +#define CCT_CURSTR 2 +#define CCT_CURPAT 3 +#define CCT_WORDSTR 4 +#define CCT_WORDPAT 5 +#define CCT_CURSUF 6 +#define CCT_CURPRE 7 +#define CCT_CURSUB 8 +#define CCT_CURSUBC 9 +#define CCT_NUMWORDS 10 +#define CCT_RANGESTR 11 +#define CCT_RANGEPAT 12 + +/* Contains the real description for compctls */ + +struct compctl { + int refc; /* reference count */ + Compctl next; /* next compctl for -x */ + unsigned long mask; /* mask of things to complete (CC_*) */ + char *keyvar; /* for -k (variable) */ + char *glob; /* for -g (globbing) */ + char *str; /* for -s (expansion) */ + char *func; /* for -K (function) */ + char *explain; /* for -X (explanation) */ + char *ylist; /* for -y (user-defined desc. for listing) */ + char *prefix, *suffix; /* for -P and -S (prefix, suffix) */ + char *subcmd; /* for -l (command name to use) */ + char *withd; /* for -w (with directory */ + char *hpat; /* for -H (history pattern) */ + int hnum; /* for -H (number of events to search) */ + Compctl ext; /* for -x (first of the compctls after -x) */ + Compcond cond; /* for -x (condition for this compctl) */ + Compctl xor; /* for + (next of the xor'ed compctls) */ +}; + +/* objects to complete */ +#define CC_FILES (1<<0) +#define CC_COMMPATH (1<<1) +#define CC_REMOVE (1<<2) +#define CC_OPTIONS (1<<3) +#define CC_VARS (1<<4) +#define CC_BINDINGS (1<<5) +#define CC_ARRAYS (1<<6) +#define CC_INTVARS (1<<7) +#define CC_SHFUNCS (1<<8) +#define CC_PARAMS (1<<9) +#define CC_ENVVARS (1<<10) +#define CC_JOBS (1<<11) +#define CC_RUNNING (1<<12) +#define CC_STOPPED (1<<13) +#define CC_BUILTINS (1<<14) +#define CC_ALREG (1<<15) +#define CC_ALGLOB (1<<16) +#define CC_USERS (1<<17) +#define CC_DISCMDS (1<<18) +#define CC_EXCMDS (1<<19) +#define CC_SCALARS (1<<20) +#define CC_READONLYS (1<<21) +#define CC_SPECIALS (1<<22) +#define CC_DELETE (1<<23) +#define CC_NAMED (1<<24) +#define CC_QUOTEFLAG (1<<25) +#define CC_EXTCMDS (1<<26) +#define CC_RESWDS (1<<27) +#define CC_DIRS (1<<28) + +#define CC_EXPANDEXPL (1<<30) +#define CC_RESERVED (1<<31) diff --git a/Src/Zle/comp1.c b/Src/Zle/comp1.c new file mode 100644 index 000000000..acd1288a6 --- /dev/null +++ b/Src/Zle/comp1.c @@ -0,0 +1,291 @@ +/* + * comp1.c - base of the completion system + * + * 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 "comp1.mdh" + +#include "comp1.pro" + +/* default completion infos */ + +/**/ +struct compctl cc_compos, cc_default, cc_first, cc_dummy; + +/* hash table for completion info for commands */ + +/**/ +HashTable compctltab; + +/* Words on the command line, for use in completion */ + +/**/ +int clwsize, clwnum, clwpos; +/**/ +char **clwords; + +/* != 0 if in a shell function called from completion, such that read -[cl] * + * will work (i.e., the line is metafied, and the above word arrays are OK). */ + +/**/ +int incompctlfunc; + +/**/ +static void +createcompctltable(void) +{ + compctltab = newhashtable(23, "compctltab", NULL); + + compctltab->hash = hasher; + compctltab->emptytable = emptyhashtable; + compctltab->filltable = NULL; + compctltab->addnode = addhashnode; + compctltab->getnode = gethashnode2; + compctltab->getnode2 = gethashnode2; + compctltab->removenode = removehashnode; + compctltab->disablenode = NULL; + compctltab->enablenode = NULL; + compctltab->freenode = freecompctlp; + compctltab->printnode = NULL; +} + +/**/ +static void +freecompctlp(HashNode hn) +{ + Compctlp ccp = (Compctlp) hn; + + zsfree(ccp->nam); + freecompctl(ccp->cc); + zfree(ccp, sizeof(struct compctlp)); +} + +/**/ +void +freecompctl(Compctl cc) +{ + if (cc == &cc_default || + cc == &cc_first || + cc == &cc_compos || + --cc->refc > 0) + return; + + zsfree(cc->keyvar); + zsfree(cc->glob); + zsfree(cc->str); + zsfree(cc->func); + zsfree(cc->explain); + zsfree(cc->ylist); + zsfree(cc->prefix); + zsfree(cc->suffix); + zsfree(cc->hpat); + zsfree(cc->subcmd); + if (cc->cond) + freecompcond(cc->cond); + if (cc->ext) { + Compctl n, m; + + n = cc->ext; + do { + m = (Compctl) (n->next); + freecompctl(n); + n = m; + } + while (n); + } + if (cc->xor && cc->xor != &cc_default) + freecompctl(cc->xor); + zfree(cc, sizeof(struct compctl)); +} + +/**/ +void +freecompcond(void *a) +{ + Compcond cc = (Compcond) a; + Compcond and, or, c; + int n; + + for (c = cc; c; c = or) { + or = c->or; + for (; c; c = and) { + and = c->and; + if (c->type == CCT_POS || + c->type == CCT_NUMWORDS) { + free(c->u.r.a); + free(c->u.r.b); + } else if (c->type == CCT_CURSUF || + c->type == CCT_CURPRE) { + for (n = 0; n < c->n; n++) + if (c->u.s.s[n]) + zsfree(c->u.s.s[n]); + free(c->u.s.s); + } else if (c->type == CCT_RANGESTR || + c->type == CCT_RANGEPAT) { + for (n = 0; n < c->n; n++) + if (c->u.l.a[n]) + zsfree(c->u.l.a[n]); + free(c->u.l.a); + for (n = 0; n < c->n; n++) + if (c->u.l.b[n]) + zsfree(c->u.l.b[n]); + free(c->u.l.b); + } else { + for (n = 0; n < c->n; n++) + if (c->u.s.s[n]) + zsfree(c->u.s.s[n]); + free(c->u.s.p); + free(c->u.s.s); + } + zfree(c, sizeof(struct compcond)); + } + } +} + +/**/ +int +compctlread(char *name, char **args, char *ops, char *reply) +{ + char *buf, *bptr; + + /* only allowed to be called for completion */ + if (!incompctlfunc) { + zwarnnam(name, "option valid only in functions called for completion", + NULL, 0); + return 1; + } + + if (ops['l']) { + /* -ln gives the index of the word the cursor is currently on, which is + available in cs (but remember that Zsh counts from one, not zero!) */ + if (ops['n']) { + char nbuf[14]; + + if (ops['e'] || ops['E']) + printf("%d\n", cs + 1); + if (!ops['e']) { + sprintf(nbuf, "%d", cs + 1); + setsparam(reply, ztrdup(nbuf)); + } + return 0; + } + /* without -n, the current line is assigned to the given parameter as a + scalar */ + if (ops['e'] || ops['E']) { + zputs((char *) line, stdout); + putchar('\n'); + } + if (!ops['e']) + setsparam(reply, ztrdup((char *) line)); + } else { + int i; + + /* -cn gives the current cursor position within the current word, which + is available in clwpos (but remember that Zsh counts from one, not + zero!) */ + if (ops['n']) { + char nbuf[14]; + + if (ops['e'] || ops['E']) + printf("%d\n", clwpos + 1); + if (!ops['e']) { + sprintf(nbuf, "%d", clwpos + 1); + setsparam(reply, ztrdup(nbuf)); + } + return 0; + } + /* without -n, the words of the current line are assigned to the given + parameters separately */ + if (ops['A'] && !ops['e']) { + /* the -A option means that one array is specified, instead of + many parameters */ + char **p, **b = (char **)zcalloc((clwnum + 1) * sizeof(char *)); + + for (i = 0, p = b; i < clwnum; p++, i++) + *p = ztrdup(clwords[i]); + + setaparam(reply, b); + return 0; + } + if (ops['e'] || ops['E']) { + for (i = 0; i < clwnum; i++) { + zputs(clwords[i], stdout); + putchar('\n'); + } + + if (ops['e']) + return 0; + } + + for (i = 0; i < clwnum && *args; reply = *args++, i++) + setsparam(reply, ztrdup(clwords[i])); + + if (i < clwnum) { + int j, len; + + for (j = i, len = 0; j < clwnum; len += strlen(clwords[j++])); + bptr = buf = zalloc(len + j - i); + while (i < clwnum) { + strucpy(&bptr, clwords[i++]); + *bptr++ = ' '; + } + bptr[-1] = '\0'; + } else + buf = ztrdup(""); + setsparam(reply, buf); + } + return 0; +} + +/**/ +int +boot_comp1(Module m) +{ + compctlreadptr = compctlread; + clwords = (char **) zcalloc((clwsize = 16) * sizeof(char *)); + createcompctltable(); + cc_compos.mask = CC_COMMPATH; + cc_default.refc = 10000; + cc_default.mask = CC_FILES; + cc_first.refc = 10000; + cc_first.mask = 0; + return 0; +} + +#ifdef MODULE + +/**/ +int +cleanup_comp1(Module m) +{ + deletehashtable(compctltab); + zfree(clwords, clwsize * sizeof(char *)); + compctlreadptr = fallback_compctlread; + return 0; +} + +#endif /* MODULE */ diff --git a/Src/Zle/comp1.export b/Src/Zle/comp1.export new file mode 100644 index 000000000..1ac9195df --- /dev/null +++ b/Src/Zle/comp1.export @@ -0,0 +1,22 @@ +#! +cc_compos +cc_default +cc_dummy +cc_first +clwnum +clwords +clwpos +clwsize +cmatcher +compctl_widgetptr +compctltab +freecmatcher +freecmlist +freecompcond +freecompctl +incompctlfunc +instring +patcomps +printcompctlptr +quotename +rembslash diff --git a/Src/Zle/comp1.mdd b/Src/Zle/comp1.mdd new file mode 100644 index 000000000..9037e568a --- /dev/null +++ b/Src/Zle/comp1.mdd @@ -0,0 +1,3 @@ +objects="comp1.o" + +headers="comp.h" diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c new file mode 100644 index 000000000..658cf4161 --- /dev/null +++ b/Src/Zle/compctl.c @@ -0,0 +1,1085 @@ +/* + * compctl.c - the compctl builtin + * + * 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 "compctl.mdh" +#include "compctl.pro" + +#define COMP_LIST (1<<0) /* -L */ +#define COMP_COMMAND (1<<1) /* -C */ +#define COMP_DEFAULT (1<<2) /* -D */ +#define COMP_FIRST (1<<3) /* -T */ +#define COMP_REMOVE (1<<4) + +#define COMP_SPECIAL (COMP_COMMAND|COMP_DEFAULT|COMP_FIRST) + +/* Flag for listing, command, default, or first completion */ +static int cclist; + +/* Mask for determining what to print */ +static unsigned long showmask = 0; + +/* Parse the basic flags for `compctl' */ + +/**/ +static int +get_compctl(char *name, char ***av, Compctl cc, int first, int isdef) +{ + /* Parse the basic flags for completion: + * first is a flag that we are not in extended completion, + * while hx indicates or (+) completion (need to know for + * default and command completion as the initial compctl is special). + * cct is a temporary just to hold flags; it never needs freeing. + */ + struct compctl cct; + char **argv = *av; + int ready = 0, hx = 0; + + /* Handle `compctl + foo ...' specially: turn it into + * a default compctl by removing it from the hash table. + */ + if (first && argv[0][0] == '+' && !argv[0][1] && + !(argv[1] && argv[1][0] == '-' && argv[1][1])) { + argv++; + if(argv[0] && argv[0][0] == '-') + argv++; + *av = argv; + freecompctl(cc); + cclist = COMP_REMOVE; + return 0; + } + + memset((void *)&cct, 0, sizeof(cct)); + + /* Loop through the flags until we have no more: * + * those with arguments are not properly allocated yet, * + * we just hang on to the argument that was passed. */ + for (; !ready && argv[0] && argv[0][0] == '-' && (argv[0][1] || !first);) { + if (!argv[0][1]) + *argv = "-+"; + while (!ready && *++(*argv)) { + if(**argv == Meta) + *++*argv ^= 32; + switch (**argv) { + case 'f': + cct.mask |= CC_FILES; + break; + case 'c': + cct.mask |= CC_COMMPATH; + break; + case 'm': + cct.mask |= CC_EXTCMDS; + break; + case 'w': + cct.mask |= CC_RESWDS; + break; + case 'o': + cct.mask |= CC_OPTIONS; + break; + case 'v': + cct.mask |= CC_VARS; + break; + case 'b': + cct.mask |= CC_BINDINGS; + break; + case 'A': + cct.mask |= CC_ARRAYS; + break; + case 'I': + cct.mask |= CC_INTVARS; + break; + case 'F': + cct.mask |= CC_SHFUNCS; + break; + case 'p': + cct.mask |= CC_PARAMS; + break; + case 'E': + cct.mask |= CC_ENVVARS; + break; + case 'j': + cct.mask |= CC_JOBS; + break; + case 'r': + cct.mask |= CC_RUNNING; + break; + case 'z': + cct.mask |= CC_STOPPED; + break; + case 'B': + cct.mask |= CC_BUILTINS; + break; + case 'a': + cct.mask |= CC_ALREG | CC_ALGLOB; + break; + case 'R': + cct.mask |= CC_ALREG; + break; + case 'G': + cct.mask |= CC_ALGLOB; + break; + case 'u': + cct.mask |= CC_USERS; + break; + case 'd': + cct.mask |= CC_DISCMDS; + break; + case 'e': + cct.mask |= CC_EXCMDS; + break; + case 'N': + cct.mask |= CC_SCALARS; + break; + case 'O': + cct.mask |= CC_READONLYS; + break; + case 'Z': + cct.mask |= CC_SPECIALS; + break; + case 'q': + cct.mask |= CC_REMOVE; + break; + case 'U': + cct.mask |= CC_DELETE; + break; + case 'n': + cct.mask |= CC_NAMED; + break; + case 'Q': + cct.mask |= CC_QUOTEFLAG; + break; + case '/': + cct.mask |= CC_DIRS; + break; + case 'k': + if ((*argv)[1]) { + cct.keyvar = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "variable name expected after -%c", NULL, + **argv); + return 1; + } else { + cct.keyvar = *++argv; + *argv = "" - 1; + } + break; + case 'K': + if ((*argv)[1]) { + cct.func = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "function name expected after -%c", NULL, + **argv); + return 1; + } else { + cct.func = *++argv; + *argv = "" - 1; + } + break; + case 'Y': + cct.mask |= CC_EXPANDEXPL; + goto expl; + case 'X': + cct.mask &= ~CC_EXPANDEXPL; + expl: + if ((*argv)[1]) { + cct.explain = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "string expected after -%c", NULL, **argv); + return 1; + } else { + cct.explain = *++argv; + *argv = "" - 1; + } + break; + case 'y': + if ((*argv)[1]) { + cct.ylist = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "function/variable expected after -%c", + NULL, **argv); + } else { + cct.ylist = *++argv; + *argv = "" - 1; + } + break; + case 'P': + if ((*argv)[1]) { + cct.prefix = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "string expected after -%c", NULL, **argv); + return 1; + } else { + cct.prefix = *++argv; + *argv = "" - 1; + } + break; + case 'S': + if ((*argv)[1]) { + cct.suffix = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "string expected after -%c", NULL, **argv); + return 1; + } else { + cct.suffix = *++argv; + *argv = "" - 1; + } + break; + case 'g': + if ((*argv)[1]) { + cct.glob = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "glob pattern expected after -%c", NULL, + **argv); + return 1; + } else { + cct.glob = *++argv; + *argv = "" - 1; + } + break; + case 's': + if ((*argv)[1]) { + cct.str = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "command string expected after -%c", NULL, + **argv); + return 1; + } else { + cct.str = *++argv; + *argv = "" - 1; + } + break; + case 'l': + if ((*argv)[1]) { + cct.subcmd = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "command name expected after -%c", NULL, + **argv); + return 1; + } else { + cct.subcmd = *++argv; + *argv = "" - 1; + } + break; + case 'W': + if ((*argv)[1]) { + cct.withd = (*argv) + 1; + *argv = "" - 1; + } else if (!argv[1]) { + zwarnnam(name, "path expected after -%c", NULL, + **argv); + return 1; + } else { + cct.withd = *++argv; + *argv = "" - 1; + } + break; + case 'H': + if ((*argv)[1]) + cct.hnum = atoi((*argv) + 1); + else if (argv[1]) + cct.hnum = atoi(*++argv); + else { + zwarnnam(name, "number expected after -%c", NULL, + **argv); + return 1; + } + if (!argv[1]) { + zwarnnam(name, "missing pattern after -%c", NULL, + **argv); + return 1; + } + cct.hpat = *++argv; + if (cct.hnum < 1) + cct.hnum = 0; + if (*cct.hpat == '*' && !cct.hpat[1]) + cct.hpat = ""; + *argv = "" - 1; + break; + case 'C': + if (first && !hx) { + cclist |= COMP_COMMAND; + } else { + zwarnnam(name, "misplaced command completion (-C) flag", + NULL, 0); + return 1; + } + break; + case 'D': + if (first && !hx) { + isdef = 1; + cclist |= COMP_DEFAULT; + } else { + zwarnnam(name, "misplaced default completion (-D) flag", + NULL, 0); + return 1; + } + break; + case 'T': + if (first && !hx) { + cclist |= COMP_FIRST; + } else { + zwarnnam(name, "misplaced first completion (-T) flag", + NULL, 0); + return 1; + } + break; + case 'L': + if (!first || hx) { + zwarnnam(name, "illegal use of -L flag", NULL, 0); + return 1; + } + cclist |= COMP_LIST; + break; + case 'x': + if (!argv[1]) { + zwarnnam(name, "condition expected after -%c", NULL, + **argv); + return 1; + } + if (first) { + argv++; + if (get_xcompctl(name, &argv, &cct, isdef)) { + if (cct.ext) + freecompctl(cct.ext); + return 1; + } + ready = 2; + } else { + zwarnnam(name, "recursive extended completion not allowed", + NULL, 0); + return 1; + } + break; + default: + if (!first && (**argv == '-' || **argv == '+')) + (*argv)--, argv--, ready = 1; + else { + zwarnnam(name, "bad option: -%c", NULL, **argv); + return 1; + } + } + } + + if (*++argv && (!ready || ready == 2) && + **argv == '+' && !argv[0][1]) { + /* There's an alternative (+) completion: assign + * what we have so far before moving on to that. + */ + if (cc_assign(name, &cc, &cct, first && !hx)) + return 1; + + hx = 1; + ready = 0; + + if (!*++argv || **argv != '-' || + (**argv == '-' && (!argv[0][1] || + (argv[0][1] == '-' && !argv[0][2])))) { + /* No argument to +, which means do default completion */ + if (isdef) + zwarnnam(name, + "recursive xor'd default completions not allowed", + NULL, 0); + else + cc->xor = &cc_default; + } else { + /* more flags follow: prepare to loop again */ + cc->xor = (Compctl) zcalloc(sizeof(*cc)); + cc = cc->xor; + memset((void *)&cct, 0, sizeof(cct)); + } + } + } + if (!ready && *argv && **argv == '-') + argv++; + + if (! (cct.mask & (CC_EXCMDS | CC_DISCMDS))) + cct.mask |= CC_EXCMDS; + + /* assign the last set of flags we parsed */ + if (cc_assign(name, &cc, &cct, first && !hx)) + return 1; + + *av = argv; + + return 0; +} + +/* Handle the -x ... -- part of compctl. */ + +/**/ +static int +get_xcompctl(char *name, char ***av, Compctl cc, int isdef) +{ + char **argv = *av, *t, *tt, sav; + int n, l = 0, ready = 0; + Compcond m, c, o; + Compctl *next = &(cc->ext); + + while (!ready) { + /* o keeps track of or's, m remembers the starting condition, + * c is the current condition being parsed + */ + o = m = c = (Compcond) zcalloc(sizeof(*c)); + /* Loop over each condition: something like 's[...][...], p[...]' */ + for (t = *argv; *t;) { + while (*t == ' ') + t++; + /* First get the condition code */ + switch (*t) { + case 's': + c->type = CCT_CURSUF; + break; + case 'S': + c->type = CCT_CURPRE; + break; + case 'p': + c->type = CCT_POS; + break; + case 'c': + c->type = CCT_CURSTR; + break; + case 'C': + c->type = CCT_CURPAT; + break; + case 'w': + c->type = CCT_WORDSTR; + break; + case 'W': + c->type = CCT_WORDPAT; + break; + case 'n': + c->type = CCT_CURSUB; + break; + case 'N': + c->type = CCT_CURSUBC; + break; + case 'm': + c->type = CCT_NUMWORDS; + break; + case 'r': + c->type = CCT_RANGESTR; + break; + case 'R': + c->type = CCT_RANGEPAT; + break; + default: + t[1] = '\0'; + zwarnnam(name, "unknown condition code: %s", t, 0); + zfree(m, sizeof(struct compcond)); + + return 1; + } + /* Now get the arguments in square brackets */ + if (t[1] != '[') { + t[1] = '\0'; + zwarnnam(name, "expected condition after condition code: %s", t, 0); + zfree(m, sizeof(struct compcond)); + + return 1; + } + t++; + /* First count how many or'd arguments there are, + * marking the active ]'s and ,'s with unprintable characters. + */ + for (n = 0, tt = t; *tt == '['; n++) { + for (l = 1, tt++; *tt && l; tt++) + if (*tt == '\\' && tt[1]) + tt++; + else if (*tt == '[') + l++; + else if (*tt == ']') + l--; + else if (l == 1 && *tt == ',') + *tt = '\201'; + if (tt[-1] == ']') + tt[-1] = '\200'; + } + + if (l) { + t[1] = '\0'; + zwarnnam(name, "error after condition code: %s", t, 0); + zfree(m, sizeof(struct compcond)); + + return 1; + } + c->n = n; + + /* Allocate space for all the arguments of the conditions */ + if (c->type == CCT_POS || + c->type == CCT_NUMWORDS) { + c->u.r.a = (int *)zcalloc(n * sizeof(int)); + c->u.r.b = (int *)zcalloc(n * sizeof(int)); + } else if (c->type == CCT_CURSUF || + c->type == CCT_CURPRE) + c->u.s.s = (char **)zcalloc(n * sizeof(char *)); + + else if (c->type == CCT_RANGESTR || + c->type == CCT_RANGEPAT) { + c->u.l.a = (char **)zcalloc(n * sizeof(char *)); + c->u.l.b = (char **)zcalloc(n * sizeof(char *)); + } else { + c->u.s.p = (int *)zcalloc(n * sizeof(int)); + c->u.s.s = (char **)zcalloc(n * sizeof(char *)); + } + /* Now loop over the actual arguments */ + for (l = 0; *t == '['; l++, t++) { + for (t++; *t && *t == ' '; t++); + tt = t; + if (c->type == CCT_POS || + c->type == CCT_NUMWORDS) { + /* p[...] or m[...]: one or two numbers expected */ + for (; *t && *t != '\201' && *t != '\200'; t++); + if (!(sav = *t)) { + zwarnnam(name, "error in condition", NULL, 0); + freecompcond(m); + return 1; + } + *t = '\0'; + c->u.r.a[l] = atoi(tt); + /* Second argument is optional: see if it's there */ + if (sav == '\200') + /* no: copy first argument */ + c->u.r.b[l] = c->u.r.a[l]; + else { + tt = ++t; + for (; *t && *t != '\200'; t++); + if (!*t) { + zwarnnam(name, "error in condition", NULL, 0); + freecompcond(m); + return 1; + } + *t = '\0'; + c->u.r.b[l] = atoi(tt); + } + } else if (c->type == CCT_CURSUF || + c->type == CCT_CURPRE) { + /* -s[..] or -S[..]: single string expected */ + for (; *t && *t != '\200'; t++) + if (*t == '\201') + *t = ','; + if (!*t) { + zwarnnam(name, "error in condition", NULL, 0); + freecompcond(m); + return 1; + } + *t = '\0'; + c->u.s.s[l] = ztrdup(tt); + } else if (c->type == CCT_RANGESTR || + c->type == CCT_RANGEPAT) { + /* -r[..,..] or -R[..,..]: two strings expected */ + for (; *t && *t != '\201'; t++); + if (!*t) { + zwarnnam(name, "error in condition", NULL, 0); + freecompcond(m); + return 1; + } + *t = '\0'; + c->u.l.a[l] = ztrdup(tt); + tt = ++t; + /* any more commas are text, not active */ + for (; *t && *t != '\200'; t++) + if (*t == '\201') + *t = ','; + if (!*t) { + zwarnnam(name, "error in condition", NULL, 0); + freecompcond(m); + return 1; + } + *t = '\0'; + c->u.l.b[l] = ztrdup(tt); + } else { + /* remaining patterns are number followed by string */ + for (; *t && *t != '\200' && *t != '\201'; t++); + if (!*t || *t == '\200') { + zwarnnam(name, "error in condition", NULL, 0); + freecompcond(m); + return 1; + } + *t = '\0'; + c->u.s.p[l] = atoi(tt); + tt = ++t; + for (; *t && *t != '\200'; t++) + if (*t == '\201') + *t = ','; + if (!*t) { + zwarnnam(name, "error in condition", NULL, 0); + freecompcond(m); + return 1; + } + *t = '\0'; + c->u.s.s[l] = ztrdup(tt); + } + } + while (*t == ' ') + t++; + if (*t == ',') { + /* Another condition to `or' */ + o->or = c = (Compcond) zcalloc(sizeof(*c)); + o = c; + t++; + } else if (*t) { + /* Another condition to `and' */ + c->and = (Compcond) zcalloc(sizeof(*c)); + c = c->and; + } + } + /* Assign condition to current compctl */ + *next = (Compctl) zcalloc(sizeof(*cc)); + (*next)->cond = m; + argv++; + /* End of the condition; get the flags that go with it. */ + if (get_compctl(name, &argv, *next, 0, isdef)) + return 1; + if ((!argv || !*argv) && (cclist & COMP_SPECIAL)) + /* default, first, or command completion finished */ + ready = 1; + else { + /* see if we are looking for more conditions or are + * ready to return (ready = 1) + */ + if (!argv || !*argv || **argv != '-' || + ((!argv[0][1] || argv[0][1] == '+') && !argv[1])) { + zwarnnam(name, "missing command names", NULL, 0); + return 1; + } + if (!strcmp(*argv, "--")) + ready = 1; + else if (!strcmp(*argv, "-+") && argv[1] && + !strcmp(argv[1], "--")) { + ready = 1; + argv++; + } + argv++; + /* prepare to put the next lot of conditions on the end */ + next = &((*next)->next); + } + } + /* save position at end of parsing */ + *av = argv - 1; + return 0; +} + +/**/ +static int +cc_assign(char *name, Compctl *ccptr, Compctl cct, int reass) +{ + /* Copy over the details from the values in cct to those in *ccptr */ + Compctl cc; + + if (cct->subcmd && (cct->keyvar || cct->glob || cct->str || + cct->func || cct->explain || cct->ylist || + cct->prefix)) { + zwarnnam(name, "illegal combination of options", NULL, 0); + return 1; + } + + /* Handle assignment of new default or command completion */ + if (reass && !(cclist & COMP_LIST)) { + /* if not listing */ + if (cclist == (COMP_COMMAND|COMP_DEFAULT) + || cclist == (COMP_COMMAND|COMP_FIRST) + || cclist == (COMP_DEFAULT|COMP_FIRST) + || cclist == COMP_SPECIAL) { + zwarnnam(name, "can't set -D, -T, and -C simultaneously", NULL, 0); + /* ... because the following code wouldn't work. */ + return 1; + } + if (cclist & COMP_COMMAND) { + /* command */ + *ccptr = &cc_compos; + cc_reassign(*ccptr); + } else if (cclist & COMP_DEFAULT) { + /* default */ + *ccptr = &cc_default; + cc_reassign(*ccptr); + } else if (cclist & COMP_FIRST) { + /* first */ + *ccptr = &cc_first; + cc_reassign(*ccptr); + } + } + + /* Free the old compctl */ + cc = *ccptr; + zsfree(cc->keyvar); + zsfree(cc->glob); + zsfree(cc->str); + zsfree(cc->func); + zsfree(cc->explain); + zsfree(cc->ylist); + zsfree(cc->prefix); + zsfree(cc->suffix); + zsfree(cc->subcmd); + zsfree(cc->withd); + zsfree(cc->hpat); + + /* and copy over the new stuff, (permanently) allocating + * space for strings. + */ + cc->mask = cct->mask; + cc->keyvar = ztrdup(cct->keyvar); + cc->glob = ztrdup(cct->glob); + cc->str = ztrdup(cct->str); + cc->func = ztrdup(cct->func); + cc->explain = ztrdup(cct->explain); + cc->ylist = ztrdup(cct->ylist); + cc->prefix = ztrdup(cct->prefix); + cc->suffix = ztrdup(cct->suffix); + cc->subcmd = ztrdup(cct->subcmd); + cc->withd = ztrdup(cct->withd); + cc->hpat = ztrdup(cct->hpat); + cc->hnum = cct->hnum; + + /* careful with extended completion: it's already allocated */ + cc->ext = cct->ext; + + return 0; +} + +/**/ +static void +cc_reassign(Compctl cc) +{ + /* Free up a new default or command completion: + * this is a hack to free up the parts which should be deleted, + * without removing the basic variable which is statically allocated + */ + Compctl c2; + + c2 = (Compctl) zcalloc(sizeof *cc); + c2->xor = cc->xor; + c2->ext = cc->ext; + c2->refc = 1; + + freecompctl(c2); + + cc->ext = cc->xor = NULL; +} + +/**/ +static void +compctl_process_cc(char **s, Compctl cc) +{ + Compctlp ccp; + + if (cclist & COMP_REMOVE) { + /* Delete entries for the commands listed */ + for (; *s; s++) { + if ((ccp = (Compctlp) compctltab->removenode(compctltab, *s))) + compctltab->freenode((HashNode) ccp); + } + } else { + /* Add the compctl just read to the hash table */ + + cc->refc = 0; + for (; *s; s++) { + cc->refc++; + ccp = (Compctlp) zalloc(sizeof *ccp); + ccp->cc = cc; + compctltab->addnode(compctltab, ztrdup(*s), ccp); + } + } +} + +/* Print a `compctl' */ + +/**/ +static void +printcompctl(char *s, Compctl cc, int printflags) +{ + Compctl cc2; + char *css = "fcqovbAIFpEjrzBRGudeNOZUnQmw/"; + char *mss = " pcCwWsSnNmrR"; + unsigned long t = 0x7fffffff; + unsigned long flags = cc->mask; + unsigned long oldshowmask; + + if ((flags & CC_EXCMDS) && !(flags & CC_DISCMDS)) + flags &= ~CC_EXCMDS; + + /* If showmask is non-zero, then print only those * + * commands with that flag set. */ + if (showmask && !(flags & showmask)) + return; + + /* Temporarily clear showmask in case we make * + * recursive calls to printcompctl. */ + oldshowmask = showmask; + showmask = 0; + + /* print either command name or start of compctl command itself */ + if (s) { + if (cclist & COMP_LIST) { + printf("compctl"); + if (cc == &cc_compos) + printf(" -C"); + if (cc == &cc_default) + printf(" -D"); + if (cc == &cc_first) + printf(" -T"); + } else + quotedzputs(s, stdout); + } + + /* loop through flags w/o args that are set, printing them if so */ + if (flags & t) { + printf(" -"); + if ((flags & (CC_ALREG | CC_ALGLOB)) == (CC_ALREG | CC_ALGLOB)) + putchar('a'), flags &= ~(CC_ALREG | CC_ALGLOB); + while (*css) { + if (flags & t & 1) + putchar(*css); + css++; + flags >>= 1; + t >>= 1; + } + } + + /* now flags with arguments */ + flags = cc->mask; + printif(cc->keyvar, 'k'); + printif(cc->func, 'K'); + printif(cc->explain, (cc->mask & CC_EXPANDEXPL) ? 'Y' : 'X'); + printif(cc->ylist, 'y'); + printif(cc->prefix, 'P'); + printif(cc->suffix, 'S'); + printif(cc->glob, 'g'); + printif(cc->str, 's'); + printif(cc->subcmd, 'l'); + printif(cc->withd, 'W'); + if (cc->hpat) { + printf(" -H %d ", cc->hnum); + quotedzputs(cc->hpat, stdout); + } + + /* now the -x ... -- extended completion part */ + if (cc->ext) { + Compcond c, o; + int i; + + cc2 = cc->ext; + printf(" -x"); + + while (cc2) { + /* loop over conditions */ + c = cc2->cond; + + printf(" '"); + for (c = cc2->cond; c;) { + /* loop over or's */ + o = c->or; + while (c) { + /* loop over and's */ + putchar(mss[c->type]); + + for (i = 0; i < c->n; i++) { + /* for all [...]'s of a given condition */ + putchar('['); + switch (c->type) { + case CCT_POS: + case CCT_NUMWORDS: + printf("%d,%d", c->u.r.a[i], c->u.r.b[i]); + break; + case CCT_CURSUF: + case CCT_CURPRE: + printqt(c->u.s.s[i]); + break; + case CCT_RANGESTR: + case CCT_RANGEPAT: + printqt(c->u.l.a[i]); + putchar(','); + printqt(c->u.l.b[i]); + break; + default: + printf("%d,", c->u.s.p[i]); + printqt(c->u.s.s[i]); + } + putchar(']'); + } + if ((c = c->and)) + putchar(' '); + } + if ((c = o)) + printf(" , "); + } + putchar('\''); + c = cc2->cond; + cc2->cond = NULL; + /* now print the flags for the current condition */ + printcompctl(NULL, cc2, 0); + cc2->cond = c; + if ((cc2 = (Compctl) (cc2->next))) + printf(" -"); + } + if (cclist & COMP_LIST) + printf(" --"); + } + if (cc && cc->xor) { + /* print xor'd (+) completions */ + printf(" +"); + if (cc->xor != &cc_default) + printcompctl(NULL, cc->xor, 0); + } + if (s) { + if ((cclist & COMP_LIST) && (cc != &cc_compos) + && (cc != &cc_default) && (cc != &cc_first)) { + if(s[0] == '-' || s[0] == '+') + printf(" -"); + putchar(' '); + quotedzputs(s, stdout); + } + putchar('\n'); + } + + showmask = oldshowmask; +} + +/**/ +static void +printcompctlp(HashNode hn, int printflags) +{ + Compctlp ccp = (Compctlp) hn; + + /* Function needed for use by scanhashtable() */ + printcompctl(ccp->nam, ccp->cc, printflags); +} + +/* Main entry point for the `compctl' builtin */ + +/**/ +static int +bin_compctl(char *name, char **argv, char *ops, int func) +{ + Compctl cc = NULL; + int ret = 0; + + /* clear static flags */ + cclist = 0; + showmask = 0; + + /* Parse all the arguments */ + if (*argv) { + cc = (Compctl) zcalloc(sizeof(*cc)); + if (get_compctl(name, &argv, cc, 1, 0)) { + freecompctl(cc); + return 1; + } + + /* remember flags for printing */ + showmask = cc->mask; + if ((showmask & CC_EXCMDS) && !(showmask & CC_DISCMDS)) + showmask &= ~CC_EXCMDS; + + /* if no command arguments or just listing, we don't want cc */ + if (!*argv || (cclist & COMP_LIST)) + freecompctl(cc); + } + + /* If no commands and no -C, -T, or -D, print all the compctl's * + * If some flags (other than -C, -T, or -D) were given, then * + * only print compctl containing those flags. */ + if (!*argv && !(cclist & COMP_SPECIAL)) { + scanhashtable(compctltab, 1, 0, 0, compctltab->printnode, 0); + printcompctl((cclist & COMP_LIST) ? "" : "COMMAND", &cc_compos, 0); + printcompctl((cclist & COMP_LIST) ? "" : "DEFAULT", &cc_default, 0); + printcompctl((cclist & COMP_LIST) ? "" : "FIRST", &cc_first, 0); + return ret; + } + + /* If we're listing and we've made it to here, then there are arguments * + * or a COMP_SPECIAL flag (-D, -C, -T), so print only those. */ + if (cclist & COMP_LIST) { + HashNode hn; + char **ptr; + + showmask = 0; + for (ptr = argv; *ptr; ptr++) { + if ((hn = compctltab->getnode(compctltab, *ptr))) { + compctltab->printnode(hn, 0); + } else { + zwarnnam(name, "no compctl defined for %s", *ptr, 0); + ret = 1; + } + } + if (cclist & COMP_COMMAND) + printcompctl("", &cc_compos, 0); + if (cclist & COMP_DEFAULT) + printcompctl("", &cc_default, 0); + if (cclist & COMP_FIRST) + printcompctl("", &cc_first, 0); + return ret; + } + + /* Assign the compctl to the commands given */ + if (*argv) { + if(cclist & COMP_SPECIAL) + /* Ideally we'd handle this properly, setting both the * + * special and normal completions. For the moment, * + * this is better than silently failing. */ + zwarnnam(name, "extraneous commands ignored", NULL, 0); + else + compctl_process_cc(argv, cc); + } + + return ret; +} + +static struct builtin bintab[] = { + BUILTIN("compctl", 0, bin_compctl, 0, -1, 0, NULL, NULL), +}; + +/**/ +int +boot_compctl(Module m) +{ + if(!addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab))) + return 1; + compctltab->printnode = printcompctlp; + return 0; +} + +#ifdef MODULE + +/**/ +int +cleanup_compctl(Module m) +{ + compctltab->printnode = NULL; + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} +#endif diff --git a/Src/Zle/compctl.mdd b/Src/Zle/compctl.mdd new file mode 100644 index 000000000..c83ecda29 --- /dev/null +++ b/Src/Zle/compctl.mdd @@ -0,0 +1,5 @@ +moddeps="comp1" + +autobins="compctl" + +objects="compctl.o" diff --git a/Src/Zle/deltochar.c b/Src/Zle/deltochar.c new file mode 100644 index 000000000..87f8593b8 --- /dev/null +++ b/Src/Zle/deltochar.c @@ -0,0 +1,91 @@ +/* + * deltochar.c - ZLE module implementing Emacs' zap-to-char + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1996-1997 Peter Stephenson + * 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 Peter Stephenson 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 Peter Stephenson and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Peter Stephenson 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 Peter Stephenson and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "deltochar.mdh" +#include "deltochar.pro" + +static Widget w_deletetochar; + +/**/ +static void +deltochar(void) +{ + int c = getkey(0), dest = cs, ok = 0, n = zmult; + + if (n > 0) { + while (n-- && dest != ll) { + while (dest != ll && line[dest] != c) + dest++; + if (dest != ll) { + dest++; + if (!n) { + foredel(dest - cs); + ok++; + } + } + } + } else { + /* ignore character cursor is on when scanning backwards */ + if (dest) + dest--; + while (n++ && dest != 0) { + while (dest != 0 && line[dest] != c) + dest--; + if (line[dest] == c && !n) { + backdel(cs - dest); + ok++; + } + } + } + if (!ok) + feep(); +} + +/**/ +int +boot_deltochar(Module m) +{ + w_deletetochar = addzlefunction("delete-to-char", deltochar, ZLE_KEEPSUFFIX); + if (w_deletetochar) + return 0; + zwarnnam(m->nam, "name clash when adding ZLE function `delete-to-char'", + NULL, 0); + return -1; +} + +#ifdef MODULE + +/**/ +int +cleanup_deltochar(Module m) +{ + deletezlefunction(w_deletetochar); + return 0; +} +#endif diff --git a/Src/Zle/deltochar.mdd b/Src/Zle/deltochar.mdd new file mode 100644 index 000000000..4d1f89d1c --- /dev/null +++ b/Src/Zle/deltochar.mdd @@ -0,0 +1,3 @@ +moddeps="zle" + +objects="deltochar.o" diff --git a/Src/Zle/iwidgets.list b/Src/Zle/iwidgets.list new file mode 100644 index 000000000..01862160e --- /dev/null +++ b/Src/Zle/iwidgets.list @@ -0,0 +1,172 @@ +# +# intwidgets.list - list of internally implemented ZLE widgets +# +# Each line has the form: +# +# "canonical-name" , functionname , ZLE_FLAGS +# +# `#' starts a comment. Blank lines are ignored. +# + +"accept-and-hold", acceptandhold, 0 +"accept-and-infer-next-history", acceptandinfernexthistory, 0 +"accept-and-menu-complete", acceptandmenucomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"accept-line", acceptline, 0 +"accept-line-and-down-history", acceptlineanddownhistory, 0 +"backward-char", backwardchar, 0 +"backward-delete-char", backwarddeletechar, ZLE_KEEPSUFFIX +"backward-delete-word", backwarddeleteword, ZLE_KEEPSUFFIX +"backward-kill-line", backwardkillline, ZLE_KILL | ZLE_KEEPSUFFIX +"backward-kill-word", backwardkillword, ZLE_KILL | ZLE_KEEPSUFFIX +"backward-word", backwardword, 0 +"beginning-of-buffer-or-history", beginningofbufferorhistory, 0 +"beginning-of-history", beginningofhistory, 0 +"beginning-of-line", beginningofline, 0 +"beginning-of-line-hist", beginningoflinehist, 0 +"capitalize-word", capitalizeword, 0 +"clear-screen", clearscreen, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"complete-word", completeword, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"copy-prev-word", copyprevword, 0 +"copy-region-as-kill", copyregionaskill, ZLE_KEEPSUFFIX +"delete-char", deletechar, ZLE_KEEPSUFFIX +"delete-char-or-list", deletecharorlist, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"delete-word", deleteword, ZLE_KEEPSUFFIX +"describe-key-briefly", describekeybriefly, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"digit-argument", digitargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"down-case-word", downcaseword, 0 +"down-history", downhistory, 0 +"down-line-or-history", downlineorhistory, ZLE_LINEMOVE | ZLE_LASTCOL +"down-line-or-search", downlineorsearch, ZLE_LINEMOVE | ZLE_LASTCOL +"emacs-backward-word", emacsbackwardword, 0 +"emacs-forward-word", emacsforwardword, 0 +"end-of-buffer-or-history", endofbufferorhistory, 0 +"end-of-history", endofhistory, 0 +"end-of-line", endofline, 0 +"end-of-line-hist", endoflinehist, 0 +"exchange-point-and-mark", exchangepointandmark, 0 +"execute-last-named-cmd", NULL, 0 +"execute-named-cmd", NULL, 0 +"expand-cmd-path", expandcmdpath, 0 +"expand-history", expandhistory, 0 +"expand-or-complete", expandorcomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"expand-or-complete-prefix", expandorcompleteprefix, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"expand-word", expandword, 0 +"forward-char", forwardchar, 0 +"forward-word", forwardword, 0 +"get-line", getline, 0 +"gosmacs-transpose-chars", gosmacstransposechars, 0 +"history-beginning-search-backward", historybeginningsearchbackward, 0 +"history-beginning-search-forward", historybeginningsearchforward, 0 +"history-incremental-search-backward", historyincrementalsearchbackward, 0 +"history-incremental-search-forward", historyincrementalsearchforward, 0 +"history-search-backward", historysearchbackward, 0 +"history-search-forward", historysearchforward, 0 +"infer-next-history", infernexthistory, 0 +"insert-last-word", insertlastword, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"kill-buffer", killbuffer, ZLE_KILL | ZLE_KEEPSUFFIX +"kill-line", killline, ZLE_KILL | ZLE_KEEPSUFFIX +"kill-region", killregion, ZLE_KILL | ZLE_KEEPSUFFIX +"kill-whole-line", killwholeline, ZLE_KILL | ZLE_KEEPSUFFIX +"kill-word", killword, ZLE_KILL | ZLE_KEEPSUFFIX +"list-choices", listchoices, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"list-expand", listexpand, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"magic-space", magicspace, 0 +"menu-complete", menucomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"menu-expand-or-complete", menuexpandorcomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"neg-argument", negargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"overwrite-mode", overwritemode, 0 +"pound-insert", poundinsert, 0 +"push-input", pushinput, 0 +"push-line", pushline, 0 +"push-line-or-edit", pushlineoredit, 0 +"quoted-insert", quotedinsert, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"quote-line", quoteline, 0 +"quote-region", quoteregion, 0 +"redisplay", redisplay, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"redo", redo, 0 +"reverse-menu-complete", reversemenucomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"run-help", processcmd, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"self-insert", selfinsert, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"self-insert-unmeta", selfinsertunmeta, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"send-break", sendbreak, 0 +"set-mark-command", setmarkcommand, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"spell-word", spellword, 0 +"transpose-chars", transposechars, 0 +"transpose-words", transposewords, 0 +"undefined-key", undefinedkey, 0 +"undo", undo, 0 +"universal-argument", universalargument, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"up-case-word", upcaseword, 0 +"up-history", uphistory, 0 +"up-line-or-history", uplineorhistory, ZLE_LINEMOVE | ZLE_LASTCOL +"up-line-or-search", uplineorsearch, ZLE_LINEMOVE | ZLE_LASTCOL +"vi-add-eol", viaddeol, 0 +"vi-add-next", viaddnext, 0 +"vi-backward-blank-word", vibackwardblankword, 0 +"vi-backward-char", vibackwardchar, 0 +"vi-backward-delete-char", vibackwarddeletechar, ZLE_KEEPSUFFIX +"vi-backward-kill-word", vibackwardkillword, ZLE_KILL | ZLE_KEEPSUFFIX +"vi-backward-word", vibackwardword, 0 +"vi-beginning-of-line", vibeginningofline, 0 +"vi-caps-lock-panic", vicapslockpanic, 0 +"vi-change", vichange, 0 +"vi-change-eol", vichangeeol, 0 +"vi-change-whole-line", vichangewholeline, 0 +"vi-cmd-mode", vicmdmode, 0 +"vi-delete", videlete, ZLE_KILL | ZLE_KEEPSUFFIX +"vi-delete-char", videletechar, ZLE_KEEPSUFFIX +"vi-digit-or-beginning-of-line", vidigitorbeginningofline, 0 +"vi-down-line-or-history", vidownlineorhistory, ZLE_LINEMOVE +"vi-end-of-line", viendofline, ZLE_LASTCOL +"vi-fetch-history", vifetchhistory, 0 +"vi-find-next-char", vifindnextchar, 0 +"vi-find-next-char-skip", vifindnextcharskip, 0 +"vi-find-prev-char", vifindprevchar, 0 +"vi-find-prev-char-skip", vifindprevcharskip, 0 +"vi-first-non-blank", vifirstnonblank, 0 +"vi-forward-blank-word", viforwardblankword, 0 +"vi-forward-blank-word-end", viforwardblankwordend, 0 +"vi-forward-char", viforwardchar, 0 +"vi-forward-word", viforwardword, 0 +"vi-forward-word-end", viforwardwordend, 0 +"vi-goto-column", vigotocolumn, 0 +"vi-goto-mark", vigotomark, 0 +"vi-goto-mark-line", vigotomarkline, 0 +"vi-history-search-backward", vihistorysearchbackward, 0 +"vi-history-search-forward", vihistorysearchforward, 0 +"vi-indent", viindent, 0 +"vi-insert", viinsert, 0 +"vi-insert-bol", viinsertbol, 0 +"vi-join", vijoin, 0 +"vi-kill-eol", vikilleol, ZLE_KILL | ZLE_KEEPSUFFIX +"vi-kill-line", vikillline, ZLE_KILL | ZLE_KEEPSUFFIX +"vi-match-bracket", vimatchbracket, 0 +"vi-open-line-above", viopenlineabove, 0 +"vi-open-line-below", viopenlinebelow, 0 +"vi-oper-swap-case", vioperswapcase, 0 +"vi-pound-insert", vipoundinsert, 0 +"vi-put-after", viputafter, ZLE_YANK +"vi-put-before", viputbefore, ZLE_YANK +"vi-quoted-insert", viquotedinsert, ZLE_MENUCMP | ZLE_KEEPSUFFIX +"vi-repeat-change", virepeatchange, 0 +"vi-repeat-find", virepeatfind, 0 +"vi-repeat-search", virepeatsearch, 0 +"vi-replace", vireplace, 0 +"vi-replace-chars", vireplacechars, 0 +"vi-rev-repeat-find", virevrepeatfind, 0 +"vi-rev-repeat-search", virevrepeatsearch, 0 +"vi-set-buffer", visetbuffer, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"vi-set-mark", visetmark, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"vi-substitute", visubstitute, 0 +"vi-swap-case", viswapcase, 0 +"vi-undo-change", viundochange, 0 +"vi-unindent", viunindent, 0 +"vi-up-line-or-history", viuplineorhistory, ZLE_LINEMOVE +"vi-yank", viyank, 0 +"vi-yank-eol", viyankeol, 0 +"vi-yank-whole-line", viyankwholeline, 0 +"what-cursor-position", whatcursorposition, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"where-is", whereis, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"which-command", processcmd, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL +"yank", yank, ZLE_YANK +"yank-pop", yankpop, ZLE_YANK diff --git a/Src/Zle/zle.export b/Src/Zle/zle.export new file mode 100644 index 000000000..ccd5df98a --- /dev/null +++ b/Src/Zle/zle.export @@ -0,0 +1,10 @@ +#! +addzlefunction +backdel +backkill +deletezlefunction +feep +foredel +forekill +getkey +zmod diff --git a/Src/Zle/zle.h b/Src/Zle/zle.h new file mode 100644 index 000000000..faf6cf878 --- /dev/null +++ b/Src/Zle/zle.h @@ -0,0 +1,137 @@ +/* + * zle.h - header file for line editor + * + * 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. + * + */ + +#undef trashzle +#undef zleread +#undef spaceinline +#undef gotword +#undef refresh + +typedef struct widget *Widget; +typedef struct thingy *Thingy; + +/* widgets (ZLE functions) */ + +typedef void (*ZleIntFunc) _((void)); + +struct widget { + int flags; /* flags (see below) */ + Thingy first; /* `first' thingy that names this widget */ + union { + ZleIntFunc fn; /* pointer to internally implemented widget */ + char *fnnam; /* name of the shell function for user-defined widget */ + } u; +}; + +#define WIDGET_INT (1<<0) /* widget is internally implemented */ +#define ZLE_MENUCMP (1<<1) /* DON'T invalidate completion list */ +#define ZLE_YANK (1<<3) +#define ZLE_LINEMOVE (1<<4) /* command is a line-oriented movement */ +#define ZLE_LASTCOL (1<<5) /* command maintains lastcol correctly */ +#define ZLE_KILL (1<<6) +#define ZLE_KEEPSUFFIX (1<<9) /* DON'T remove added suffix */ + +/* thingies */ + +struct thingy { + HashNode next; /* next node in the hash chain */ + char *nam; /* name of the thingy */ + int flags; /* TH_* flags (see below) */ + int rc; /* reference count */ + Widget widget; /* widget named by this thingy */ + Thingy samew; /* `next' thingy (circularly) naming the same widget */ +}; + +/* DISABLED is (1<<0) */ +#define TH_IMMORTAL (1<<1) /* can't refer to a different widget */ + +/* command modifier prefixes */ + +struct modifier { + int flags; /* MOD_* flags (see below) */ + int mult; /* repeat count */ + int tmult; /* repeat count actually being edited */ + int vibuf; /* vi cut buffer */ +}; + +#define MOD_MULT (1<<0) /* a repeat count has been selected */ +#define MOD_TMULT (1<<1) /* a repeat count is being entered */ +#define MOD_VIBUF (1<<2) /* a vi cut buffer has been selected */ +#define MOD_VIAPP (1<<3) /* appending to the vi cut buffer */ +#define MOD_NEG (1<<4) /* last command was negate argument */ + +/* current modifier status */ + +#define zmult (zmod.mult) + +/* undo system */ + +struct change { + struct change *prev, *next; /* adjacent changes */ + int flags; /* see below */ + int hist; /* history line being changed */ + int off; /* offset of the text changes */ + char *del; /* characters to delete (metafied) */ + char *ins; /* characters to insert (metafied) */ +}; + +#define CH_NEXT (1<<0) /* next structure is also part of this change */ +#define CH_PREV (1<<1) /* previous structure is also part of this change */ + +/* known thingies */ + +#define Th(X) (&thingies[X]) + +/* opaque keymap type */ + +typedef struct keymap *Keymap; + +typedef void (*KeyScanFunc) _((char *, Thingy, char *, void *)); + +#define invicmdmode() (!strcmp(curkeymapname, "vicmd")) + +/* Standard type of suffix removal. */ + +#define removesuffix() iremovesuffix(256) + +/* Cut/kill buffer type. The buffer itself is purely binary data, * + * not NUL-terminated. len is a length count. flags uses the * + * CUTBUFFER_* constants defined below. */ + +struct cutbuffer { + char *buf; + size_t len; + char flags; +}; + +typedef struct cutbuffer *Cutbuffer; + +#define CUTBUFFER_LINE 1 /* for vi: buffer contains whole lines of data */ + +#define KRINGCT 8 /* number of buffers in the kill ring */ diff --git a/Src/Zle/zle.mdd b/Src/Zle/zle.mdd new file mode 100644 index 000000000..29f39d363 --- /dev/null +++ b/Src/Zle/zle.mdd @@ -0,0 +1,70 @@ +moddeps="comp1" + +autobins="bindkey vared zle" + +objects="zle_bindings.o zle_hist.o zle_keymap.o zle_main.o \ +zle_misc.o zle_move.o zle_params.o zle_refresh.o \ +zle_thingy.o zle_tricky.o zle_utils.o zle_vi.o zle_word.o" + +headers="zle.h zle_things.h" + +:<<\Make +zle_things.h: thingies.list zle_things.sed + ( \ + echo '/** zle_things.h **/'; \ + echo '/** indices of and pointers to known thingies **/'; \ + echo; \ + echo 'enum {'; \ + sed -n -f $(sdir)/zle_things.sed < thingies.list; \ + echo ' ZLE_BUILTIN_THINGY_COUNT'; \ + echo '};'; \ + ) > $@ + +zle_widget.h: widgets.list zle_widget.sed + ( \ + echo '/** zle_widget.h **/'; \ + echo '/** indices of and pointers to internal widgets **/'; \ + echo; \ + echo 'enum {'; \ + sed -n -f $(sdir)/zle_widget.sed < widgets.list; \ + echo ' ZLE_BUILTIN_WIDGET_COUNT'; \ + echo '};'; \ + ) > $@ + +thingies.list: iwidgets.list + ( \ + echo '/** thingies.list **/'; \ + echo '/** thingy structures for the known thingies **/'; \ + echo; \ + echo '/* format: T("name", TH_FLAGS, w_widget, t_nextthingy) */'; \ + echo; \ + sed -e 's/#.*//; /^$$/d; s/" *,.*/"/' \ + -e 's/^"/T("/; s/$$/, 0,/; h' \ + -e 's/-//g; s/^.*"\(.*\)".*/w_\1, t_D\1)/' \ + -e 'H; g; s/\n/ /' \ + < $(sdir)/iwidgets.list; \ + sed -e 's/#.*//; /^$$/d; s/" *,.*/"/' \ + -e 's/^"/T("./; s/$$/, TH_IMMORTAL,/; h' \ + -e 's/-//g; s/^.*"\.\(.*\)".*/w_\1, t_\1)/' \ + -e 'H; g; s/\n/ /' \ + < $(sdir)/iwidgets.list; \ + ) > $@ + +widgets.list: iwidgets.list + ( \ + echo '/** widgets.list **/'; \ + echo '/** widget structures for the internal widgets **/'; \ + echo; \ + echo '/* format: W(ZLE_FLAGS, t_firstname, functionname) */'; \ + echo; \ + sed -e 's/#.*//; /^$$/d; s/-//g' \ + -e 's/^"\(.*\)" *, *\([^ ]*\) *, *\(.*\)/W(\3, t_\1, \2)/' \ + < $(sdir)/iwidgets.list; \ + ) > $@ + +zle_bindings.o zle_bindings..o: zle_widget.h widgets.list thingies.list + +clean-here: clean.zle +clean.zle: + rm -f zle_things.h zle_widget.h widgets.list thingies.list +Make diff --git a/Src/Zle/zle_bindings.c b/Src/Zle/zle_bindings.c new file mode 100644 index 000000000..40e555ad1 --- /dev/null +++ b/Src/Zle/zle_bindings.c @@ -0,0 +1,421 @@ +/* + * zle_bindings.c - commands and keymaps + * + * 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 "zle.mdh" +#include "zle_widget.h" + +#include "zle_bindings.pro" + +/* + * widgets is the table of internally implemented widgets. This + * table is not used directly, but each widget in it is referenced + * by address from within the table of thingies (below). The only + * complication here is that not all systems support union + * initialisation. + */ + +static +#ifdef HAVE_UNION_INIT +# define BR(X) {X} +struct widget +#else /* !HAVE_UNION_INIT */ +# define BR(X) X +struct intwidget { + int flags; + Thingy first; + ZleIntFunc fn; +} +#endif /* !HAVE_UNION_INIT */ +widgets[] = { +#define W(zle_flags, t_firstname, functionname) \ + { WIDGET_INT | zle_flags, t_firstname, BR(functionname) }, +#include "widgets.list" +#undef W +}; + +/* + * thingies is the table of `known thingies', that exist on startup. + * Some bits of ZLE rely on some of these thingies always being the + * ones in this table, rather than doing a name lookup and accepting + * any semantically identical thingy. The initial reference count of + * these thingies is 2: 1 for the widget they name, and 1 extra to + * make sure they never get deleted. + */ + +/**/ +struct thingy thingies[] = { +#define T(name, th_flags, w_idget, t_next) \ + { NULL, name, th_flags, 2, w_idget, t_next }, +#include "thingies.list" +#undef T + { NULL, NULL, 0, 0, NULL, NULL } +}; + +/* + * Default key binding tables: + * + * In these tables, each element is bound to a single thingy, the index + * of which in the above table is stored here. + */ + +/**/ +int emacsbind[32] = { + /* ^@ */ z_setmarkcommand, + /* ^A */ z_beginningofline, + /* ^B */ z_backwardchar, + /* ^C */ z_undefinedkey, + /* ^D */ z_deletecharorlist, + /* ^E */ z_endofline, + /* ^F */ z_forwardchar, + /* ^G */ z_sendbreak, + /* ^H */ z_backwarddeletechar, + /* ^I */ z_expandorcomplete, + /* ^J */ z_acceptline, + /* ^K */ z_killline, + /* ^L */ z_clearscreen, + /* ^M */ z_acceptline, + /* ^N */ z_downlineorhistory, + /* ^O */ z_acceptlineanddownhistory, + /* ^P */ z_uplineorhistory, + /* ^Q */ z_pushline, + /* ^R */ z_historyincrementalsearchbackward, + /* ^S */ z_historyincrementalsearchforward, + /* ^T */ z_transposechars, + /* ^U */ z_killwholeline, + /* ^V */ z_quotedinsert, + /* ^W */ z_backwardkillword, + /* ^X */ z_undefinedkey, + /* ^Y */ z_yank, + /* ^Z */ z_undefinedkey, + /* ^[ */ z_undefinedkey, + /* ^\ */ z_undefinedkey, + /* ^] */ z_undefinedkey, + /* ^^ */ z_undefinedkey, + /* ^_ */ z_undo, +}; + +/**/ +int metabind[128] = { + /* M-^@ */ z_undefinedkey, + /* M-^A */ z_undefinedkey, + /* M-^B */ z_undefinedkey, + /* M-^C */ z_undefinedkey, + /* M-^D */ z_listchoices, + /* M-^E */ z_undefinedkey, + /* M-^F */ z_undefinedkey, + /* M-^G */ z_sendbreak, + /* M-^H */ z_backwardkillword, + /* M-^I */ z_selfinsertunmeta, + /* M-^J */ z_selfinsertunmeta, + /* M-^K */ z_undefinedkey, + /* M-^L */ z_clearscreen, + /* M-^M */ z_selfinsertunmeta, + /* M-^N */ z_undefinedkey, + /* M-^O */ z_undefinedkey, + /* M-^P */ z_undefinedkey, + /* M-^Q */ z_undefinedkey, + /* M-^R */ z_undefinedkey, + /* M-^S */ z_undefinedkey, + /* M-^T */ z_undefinedkey, + /* M-^U */ z_undefinedkey, + /* M-^V */ z_undefinedkey, + /* M-^W */ z_undefinedkey, + /* M-^X */ z_undefinedkey, + /* M-^Y */ z_undefinedkey, + /* M-^Z */ z_undefinedkey, + /* M-^[ */ z_undefinedkey, + /* M-^\ */ z_undefinedkey, + /* M-^] */ z_undefinedkey, + /* M-^^ */ z_undefinedkey, + /* M-^_ */ z_copyprevword, + /* M- */ z_expandhistory, + /* M-! */ z_expandhistory, + /* M-" */ z_quoteregion, + /* M-# */ z_undefinedkey, + /* M-$ */ z_spellword, + /* M-% */ z_undefinedkey, + /* M-& */ z_undefinedkey, + /* M-' */ z_quoteline, + /* M-( */ z_undefinedkey, + /* M-) */ z_undefinedkey, + /* M-* */ z_undefinedkey, + /* M-+ */ z_undefinedkey, + /* M-, */ z_undefinedkey, + /* M-- */ z_negargument, + /* M-. */ z_insertlastword, + /* M-/ */ z_undefinedkey, + /* M-0 */ z_digitargument, + /* M-1 */ z_digitargument, + /* M-2 */ z_digitargument, + /* M-3 */ z_digitargument, + /* M-4 */ z_digitargument, + /* M-5 */ z_digitargument, + /* M-6 */ z_digitargument, + /* M-7 */ z_digitargument, + /* M-8 */ z_digitargument, + /* M-9 */ z_digitargument, + /* M-: */ z_undefinedkey, + /* M-; */ z_undefinedkey, + /* M-< */ z_beginningofbufferorhistory, + /* M-= */ z_undefinedkey, + /* M-> */ z_endofbufferorhistory, + /* M-? */ z_whichcommand, + /* M-@ */ z_undefinedkey, + /* M-A */ z_acceptandhold, + /* M-B */ z_backwardword, + /* M-C */ z_capitalizeword, + /* M-D */ z_killword, + /* M-E */ z_undefinedkey, + /* M-F */ z_forwardword, + /* M-G */ z_getline, + /* M-H */ z_runhelp, + /* M-I */ z_undefinedkey, + /* M-J */ z_undefinedkey, + /* M-K */ z_undefinedkey, + /* M-L */ z_downcaseword, + /* M-M */ z_undefinedkey, + /* M-N */ z_historybeginningsearchforward, + /* M-O */ z_undefinedkey, + /* M-P */ z_historybeginningsearchbackward, + /* M-Q */ z_pushline, + /* M-R */ z_undefinedkey, + /* M-S */ z_spellword, + /* M-T */ z_transposewords, + /* M-U */ z_upcaseword, + /* M-V */ z_undefinedkey, + /* M-W */ z_copyregionaskill, + /* M-X */ z_undefinedkey, + /* M-Y */ z_undefinedkey, + /* M-Z */ z_undefinedkey, + /* M-[ */ z_undefinedkey, + /* M-\ */ z_undefinedkey, + /* M-] */ z_undefinedkey, + /* M-^ */ z_undefinedkey, + /* M-_ */ z_insertlastword, + /* M-` */ z_undefinedkey, + /* M-a */ z_acceptandhold, + /* M-b */ z_backwardword, + /* M-c */ z_capitalizeword, + /* M-d */ z_killword, + /* M-e */ z_undefinedkey, + /* M-f */ z_forwardword, + /* M-g */ z_getline, + /* M-h */ z_runhelp, + /* M-i */ z_undefinedkey, + /* M-j */ z_undefinedkey, + /* M-k */ z_undefinedkey, + /* M-l */ z_downcaseword, + /* M-m */ z_undefinedkey, + /* M-n */ z_historybeginningsearchforward, + /* M-o */ z_undefinedkey, + /* M-p */ z_historybeginningsearchbackward, + /* M-q */ z_pushline, + /* M-r */ z_undefinedkey, + /* M-s */ z_spellword, + /* M-t */ z_transposewords, + /* M-u */ z_upcaseword, + /* M-v */ z_undefinedkey, + /* M-w */ z_copyregionaskill, + /* M-x */ z_executenamedcmd, + /* M-y */ z_yankpop, + /* M-z */ z_executelastnamedcmd, + /* M-{ */ z_undefinedkey, + /* M-| */ z_vigotocolumn, + /* M-} */ z_undefinedkey, + /* M-~ */ z_undefinedkey, + /* M-^? */ z_backwardkillword, +}; + +/**/ +int viinsbind[32] = { + /* ^@ */ z_undefinedkey, + /* ^A */ z_selfinsert, + /* ^B */ z_selfinsert, + /* ^C */ z_selfinsert, + /* ^D */ z_listchoices, + /* ^E */ z_selfinsert, + /* ^F */ z_selfinsert, + /* ^G */ z_listexpand, + /* ^H */ z_vibackwarddeletechar, + /* ^I */ z_expandorcomplete, + /* ^J */ z_acceptline, + /* ^K */ z_selfinsert, + /* ^L */ z_clearscreen, + /* ^M */ z_acceptline, + /* ^N */ z_selfinsert, + /* ^O */ z_selfinsert, + /* ^P */ z_selfinsert, + /* ^Q */ z_viquotedinsert, + /* ^R */ z_redisplay, + /* ^S */ z_selfinsert, + /* ^T */ z_selfinsert, + /* ^U */ z_vikillline, + /* ^V */ z_viquotedinsert, + /* ^W */ z_vibackwardkillword, + /* ^X */ z_selfinsert, + /* ^Y */ z_selfinsert, + /* ^Z */ z_selfinsert, + /* ^[ */ z_vicmdmode, + /* ^\ */ z_selfinsert, + /* ^] */ z_selfinsert, + /* ^^ */ z_selfinsert, + /* ^_ */ z_selfinsert, +}; + +/**/ +int vicmdbind[128] = { + /* ^@ */ z_undefinedkey, + /* ^A */ z_undefinedkey, + /* ^B */ z_undefinedkey, + /* ^C */ z_undefinedkey, + /* ^D */ z_listchoices, + /* ^E */ z_undefinedkey, + /* ^F */ z_undefinedkey, + /* ^G */ z_listexpand, + /* ^H */ z_vibackwardchar, + /* ^I */ z_undefinedkey, + /* ^J */ z_acceptline, + /* ^K */ z_undefinedkey, + /* ^L */ z_clearscreen, + /* ^M */ z_acceptline, + /* ^N */ z_downhistory, + /* ^O */ z_undefinedkey, + /* ^P */ z_uphistory, + /* ^Q */ z_undefinedkey, + /* ^R */ z_redisplay, + /* ^S */ z_undefinedkey, + /* ^T */ z_undefinedkey, + /* ^U */ z_undefinedkey, + /* ^V */ z_undefinedkey, + /* ^W */ z_undefinedkey, + /* ^X */ z_undefinedkey, + /* ^Y */ z_undefinedkey, + /* ^Z */ z_undefinedkey, + /* ^[ */ z_undefinedkey, + /* ^\ */ z_undefinedkey, + /* ^] */ z_undefinedkey, + /* ^^ */ z_undefinedkey, + /* ^_ */ z_undefinedkey, + /* */ z_viforwardchar, + /* ! */ z_undefinedkey, + /* " */ z_visetbuffer, + /* # */ z_poundinsert, + /* $ */ z_viendofline, + /* % */ z_vimatchbracket, + /* & */ z_undefinedkey, + /* ' */ z_vigotomarkline, + /* ( */ z_undefinedkey, + /* ) */ z_undefinedkey, + /* * */ z_undefinedkey, + /* + */ z_vidownlineorhistory, + /* , */ z_virevrepeatfind, + /* - */ z_viuplineorhistory, + /* . */ z_virepeatchange, + /* / */ z_vihistorysearchbackward, + /* 0 */ z_vidigitorbeginningofline, + /* 1 */ z_digitargument, + /* 2 */ z_digitargument, + /* 3 */ z_digitargument, + /* 4 */ z_digitargument, + /* 5 */ z_digitargument, + /* 6 */ z_digitargument, + /* 7 */ z_digitargument, + /* 8 */ z_digitargument, + /* 9 */ z_digitargument, + /* : */ z_undefinedkey, + /* ; */ z_virepeatfind, + /* < */ z_viunindent, + /* = */ z_listchoices, + /* > */ z_viindent, + /* ? */ z_vihistorysearchforward, + /* @ */ z_undefinedkey, + /* A */ z_viaddeol, + /* B */ z_vibackwardblankword, + /* C */ z_vichangeeol, + /* D */ z_vikilleol, + /* E */ z_viforwardblankwordend, + /* F */ z_vifindprevchar, + /* G */ z_vifetchhistory, + /* H */ z_undefinedkey, + /* I */ z_viinsertbol, + /* J */ z_vijoin, + /* K */ z_undefinedkey, + /* L */ z_undefinedkey, + /* M */ z_undefinedkey, + /* N */ z_virevrepeatsearch, + /* O */ z_viopenlineabove, + /* P */ z_viputbefore, + /* Q */ z_undefinedkey, + /* R */ z_vireplace, + /* S */ z_vichangewholeline, + /* T */ z_vifindprevcharskip, + /* U */ z_undefinedkey, + /* V */ z_undefinedkey, + /* W */ z_viforwardblankword, + /* X */ z_vibackwarddeletechar, + /* Y */ z_viyankwholeline, + /* Z */ z_undefinedkey, + /* [ */ z_undefinedkey, + /* \ */ z_undefinedkey, + /* ] */ z_undefinedkey, + /* ^ */ z_vifirstnonblank, + /* _ */ z_undefinedkey, + /* ` */ z_vigotomark, + /* a */ z_viaddnext, + /* b */ z_vibackwardword, + /* c */ z_vichange, + /* d */ z_videlete, + /* e */ z_viforwardwordend, + /* f */ z_vifindnextchar, + /* g */ z_undefinedkey, + /* h */ z_vibackwardchar, + /* i */ z_viinsert, + /* j */ z_downlineorhistory, + /* k */ z_uplineorhistory, + /* l */ z_viforwardchar, + /* m */ z_visetmark, + /* n */ z_virepeatsearch, + /* o */ z_viopenlinebelow, + /* p */ z_viputafter, + /* q */ z_undefinedkey, + /* r */ z_vireplacechars, + /* s */ z_visubstitute, + /* t */ z_vifindnextcharskip, + /* u */ z_viundochange, + /* v */ z_undefinedkey, + /* w */ z_viforwardword, + /* x */ z_videletechar, + /* y */ z_viyank, + /* z */ z_undefinedkey, + /* { */ z_undefinedkey, + /* | */ z_vigotocolumn, + /* } */ z_undefinedkey, + /* ~ */ z_viswapcase, + /* ^? */ z_vibackwardchar, +}; diff --git a/Src/Zle/zle_hist.c b/Src/Zle/zle_hist.c new file mode 100644 index 000000000..76e421c1c --- /dev/null +++ b/Src/Zle/zle_hist.c @@ -0,0 +1,1139 @@ +/* + * zle_hist.c - history editing + * + * 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 "zle.mdh" +#include "zle_hist.pro" + +/* Are references to earlier history lines permitted? == 0 if * + * editing or reading a standalone line, such as in vared or select. */ + +/**/ +int histallowed; + +/* Column position of vi ideal cursor. -1 if it is unknown -- most * + * movements and changes do this. */ + +/**/ +int lastcol; + +/* current history line number */ + +/**/ +int histline; + +/* the last line in the history (the current one), metafied */ + +/**/ +char *curhistline; + +/**/ +void +remember_edits(void) +{ + if (histline == curhist) { + zsfree(curhistline); + curhistline = metafy((char *) line, ll, META_DUP); + } + else { + Histent ent = gethistent(histline); + + if (metadiffer(ent->zle_text ? ent->zle_text : ent->text, + (char *) line, ll)) { + zsfree(ent->zle_text); + ent->zle_text = metafy((char *) line, ll, META_DUP); + } + } +} + +/**/ +void +forget_edits(void) +{ + int i; + + for (i = 0; i < histentct; i++) { + zsfree(histentarr[i].zle_text); + histentarr[i].zle_text = NULL; + } +} + +/**/ +void +uphistory(void) +{ + if (zmult < 0) { + zmult = -zmult; + downhistory(); + zmult = -zmult; + } else if(!zle_goto_hist(histline - zmult) && isset(HISTBEEP)) + feep(); +} + +/**/ +int +upline(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -zmult; + n = downline(); + zmult = -zmult; + return n; + } + if (lastcol == -1) + lastcol = cs - findbol(); + cs = findbol(); + while (n) { + if (!cs) + break; + cs--; + cs = findbol(); + n--; + } + if (!n) { + int x = findeol(); + + if ((cs += lastcol) >= x) { + cs = x; + if (cs > findbol() && invicmdmode()) + cs--; + } + } + return n; +} + +/**/ +void +uplineorhistory(void) +{ + int ocs = cs; + int n = upline(); + if (n) { + int m = zmult; + + cs = ocs; + if (virangeflag || !histallowed) { + feep(); + return; + } + zmult = n; + uphistory(); + zmult = m; + } +} + +/**/ +void +viuplineorhistory(void) +{ + int col = lastcol; + uplineorhistory(); + lastcol = col; + vifirstnonblank(); +} + + +/**/ +void +uplineorsearch(void) +{ + int ocs = cs; + int n = upline(); + if (n) { + int m = zmult; + + cs = ocs; + if (virangeflag || !histallowed) { + feep(); + return; + } + zmult = n; + historysearchbackward(); + zmult = m; + } +} + +/**/ +int +downline(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -zmult; + n = upline(); + zmult = -zmult; + return n; + } + if (lastcol == -1) + lastcol = cs - findbol(); + while (n) { + int x = findeol(); + + if (x == ll) + break; + cs = x + 1; + n--; + } + if (!n) { + int x = findeol(); + + if ((cs += lastcol) >= x) { + cs = x; + if (cs > findbol() && invicmdmode()) + cs--; + } + } + return n; +} + +/**/ +void +downlineorhistory(void) +{ + int ocs = cs; + int n = downline(); + if (n) { + int m = zmult; + + cs = ocs; + if (virangeflag || !histallowed) { + feep(); + return; + } + zmult = n; + downhistory(); + zmult = m; + } +} + +/**/ +void +vidownlineorhistory(void) +{ + int col = lastcol; + downlineorhistory(); + lastcol = col; + vifirstnonblank(); +} + +/**/ +void +downlineorsearch(void) +{ + int ocs = cs; + int n = downline(); + if (n) { + int m = zmult; + + cs = ocs; + if (virangeflag || !histallowed) { + feep(); + return; + } + zmult = n; + historysearchforward(); + zmult = m; + } +} + +/**/ +void +acceptlineanddownhistory(void) +{ + char *s; + + if (!(s = zle_get_event(histline + 1))) { + feep(); + return; + } + pushnode(bufstack, ztrdup(s)); + done = 1; + stackhist = histline + 1; +} + +/**/ +void +downhistory(void) +{ + if (zmult < 0) { + zmult = -zmult; + uphistory(); + zmult = -zmult; + } else if(!zle_goto_hist(histline + zmult) && isset(HISTBEEP)) + feep(); +} + +/**/ +void +historysearchbackward(void) +{ + int histpos, histmpos, hl = histline; + int n = zmult; + char *s; + + if (!n) + return; + if (n < 0) { + zmult = -n; + historysearchforward(); + zmult = n; + return; + } + for (histpos = histmpos = 0; histpos < ll && !iblank(line[histpos]); + histpos++, histmpos++) + if(imeta(line[histpos])) + histmpos++; + for (;;) { + hl--; + if (!(s = zle_get_event(hl))) { + feep(); + return; + } + if (metadiffer(s, (char *) line, histpos) < 0 && + iblank(s[histmpos] == Meta ? s[histmpos+1]^32 : s[histmpos]) && + metadiffer(s, (char *) line, ll) && !--n) + break; + } + zle_goto_hist(hl); +} + +/**/ +void +historysearchforward(void) +{ + int histpos, histmpos, hl = histline; + int n = zmult; + char *s; + + if (!n) + return; + if (n < 0) { + zmult = -n; + historysearchbackward(); + zmult = n; + return; + } + for (histpos = histmpos = 0; histpos < ll && !iblank(line[histpos]); + histpos++, histmpos++) + if(imeta(line[histpos])) + histmpos++; + for (;;) { + hl++; + if (!(s = zle_get_event(hl))) { + feep(); + return; + } + if (metadiffer(s, (char *) line, histpos) < (histline == curhist) && + (!s[histmpos] || + iblank(s[histmpos] == Meta ? s[histmpos+1]^32 : s[histmpos])) && + metadiffer(s, (char *) line, ll) && !--n) + break; + } + zle_goto_hist(hl); +} + +/**/ +void +beginningofbufferorhistory(void) +{ + if (findbol()) + cs = 0; + else + beginningofhistory(); +} + +/**/ +void +beginningofhistory(void) +{ + if (!zle_goto_hist(firsthist()) && isset(HISTBEEP)) + feep(); +} + +/**/ +void +endofbufferorhistory(void) +{ + if (findeol() != ll) + cs = ll; + else + endofhistory(); +} + +/**/ +void +endofhistory(void) +{ + zle_goto_hist(curhist); +} + +/**/ +void +insertlastword(void) +{ + int n; + char *s, *t; + Histent he; + +/* multiple calls will now search back through the history, pem */ + static char *lastinsert; + static int lasthist, lastpos; + int evhist = curhist - 1, save; + + if (lastinsert) { + int lastlen = ztrlen(lastinsert); + int pos = cs; + + if (lastpos <= pos && + lastlen == pos - lastpos && + memcmp(lastinsert, (char *)&line[lastpos], lastlen) == 0) { + evhist = --lasthist; + cs = lastpos; + foredel(pos - cs); + } + zsfree(lastinsert); + lastinsert = NULL; + } + if (!(he = quietgethist(evhist)) || !he->nwords) { + feep(); + return; + } + if (zmult > 0) { + n = he->nwords - (zmult - 1); + } else { + n = 1 - zmult; + } + if (n < 1 || n > he->nwords) { + feep(); + return; + } + s = he->text + he->words[2*n-2]; + t = he->text + he->words[2*n-1]; + save = *t; + *t = '\0'; /* ignore trailing whitespace */ + + lasthist = evhist; + lastpos = cs; + lastinsert = ztrdup(s); + n = zmult; + zmult = 1; + doinsert(s); + zmult = n; + *t = save; +} + +/**/ +char * +qgetevent(int ev) +{ + return ((ev == curhist) ? curhistline : quietgetevent(ev)); +} + +/**/ +char * +zle_get_event(int ev) +{ + Histent ent; + + if (ev == curhist) + return curhistline; + if (! (ent = quietgethist(ev))) + return NULL; + if (ent->zle_text) + return ent->zle_text; + return ent->text; +} + +/**/ +static int +zle_goto_hist(int ev) +{ + char *t; + + remember_edits(); + if(!(t = zle_get_event(ev))) + return 0; + mkundoent(); + histline = ev; + setline(t); + setlastline(); + return 1; +} + +/**/ +void +pushline(void) +{ + int n = zmult; + + if (n < 0) + return; + pushnode(bufstack, metafy((char *) line, ll, META_DUP)); + while (--n) + pushnode(bufstack, ztrdup("")); + stackcs = cs; + *line = '\0'; + ll = cs = 0; +} + +/**/ +void +pushlineoredit(void) +{ + int ics; + unsigned char *s; + char *hline = hgetline(); + + if (zmult < 0) + return; + if (hline && *hline) { + ics = ztrlen(hline); + sizeline(ics + ll + 1); + for (s = line + ll; --s >= line; *(s + ics) = *s); + for (s = line; *hline; hline++) + *s++ = *hline == Meta ? *++hline ^ 32 : *hline; + ll += ics; + cs += ics; + } + pushline(); + if (!isfirstln) { + errflag = done = 1; + } +} + +/**/ +void +pushinput(void) +{ + int i; + + if (zmult < 0) + return; + zmult += i = !isfirstln; + pushlineoredit(); + zmult -= i; +} + +/**/ +void +getline(void) +{ + char *s = (char *)getlinknode(bufstack); + + if (!s) + feep(); + else { + int cc; + + unmetafy(s, &cc); + spaceinline(cc); + memcpy((char *)line + cs, s, cc); + cs += cc; + free(s); + } +} + +/**/ +void +historyincrementalsearchbackward(void) +{ + doisearch(-1); +} + +/**/ +void +historyincrementalsearchforward(void) +{ + doisearch(1); +} + +static struct isrch_spot { + int hl; /* This spot's histline */ + unsigned short pos; /* The search position in our metafied str */ + unsigned short cs; /* The visible search position to the user */ + unsigned short len; /* The search string's length */ + unsigned short flags; /* This spot's flags */ +#define ISS_FAILING 1 +#define ISS_FORWARD 2 +} *isrch_spots; + +static int max_spot = 0; + +#ifdef MODULE + +/**/ +void +free_isrch_spots(void) +{ + zfree(isrch_spots, max_spot * sizeof(*isrch_spots)); +} + +#endif /* MODULE */ + +/**/ +static void +set_isrch_spot(int num, int hl, int pos, int cs, int len, int dir, int nomatch) +{ + if (num >= max_spot) { + if (!isrch_spots) { + isrch_spots = (struct isrch_spot*) + zalloc((max_spot = 64) * sizeof *isrch_spots); + } else { + isrch_spots = (struct isrch_spot*)realloc((char*)isrch_spots, + (max_spot += 64) * sizeof *isrch_spots); + } + } + + isrch_spots[num].hl = hl; + isrch_spots[num].pos = (unsigned short)pos; + isrch_spots[num].cs = (unsigned short)cs; + isrch_spots[num].len = (unsigned short)len; + isrch_spots[num].flags = (dir > 0? ISS_FORWARD : 0) + + (nomatch? ISS_FAILING : 0); +} + +/**/ +static void +get_isrch_spot(int num, int *hlp, int *posp, int *csp, int *lenp, int *dirp, int *nomatch) +{ + *hlp = isrch_spots[num].hl; + *posp = (int)isrch_spots[num].pos; + *csp = (int)isrch_spots[num].cs; + *lenp = (int)isrch_spots[num].len; + *dirp = (isrch_spots[num].flags & ISS_FORWARD)? 1 : -1; + *nomatch = (isrch_spots[num].flags & ISS_FAILING); +} + +#define ISEARCH_PROMPT "failing XXX-i-search: " +#define NORM_PROMPT_POS 8 +#define FIRST_SEARCH_CHAR (NORM_PROMPT_POS + 14) + +/**/ +static void +doisearch(int dir) +{ + char *s, *ibuf = halloc(80), *sbuf = ibuf + FIRST_SEARCH_CHAR; + int sbptr = 0, top_spot = 0, pos, sibuf = 80; + int nomatch = 0, skip_line = 0, skip_pos = 0; + int odir = dir, sens = zmult == 1 ? 3 : 1; + int hl = histline; + Thingy cmd; + char *okeymap = curkeymapname; + static char *previous_search = NULL; + static int previous_search_len = 0; + + strcpy(ibuf, ISEARCH_PROMPT); + memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? "fwd" : "bck", 3); + remember_edits(); + s = zle_get_event(hl); + selectkeymap("main", 1); + pos = metalen(s, cs); + for (;;) { + /* Remember the current values in case search fails (doesn't push). */ + set_isrch_spot(top_spot, hl, pos, cs, sbptr, dir, nomatch); + if (sbptr == 1 && sbuf[0] == '^') { + cs = 0; + nomatch = 0; + statusline = ibuf + NORM_PROMPT_POS; + } else if (sbptr > 0) { + char *last_line = s; + + for (;;) { + char *t; + + if (skip_pos) { + if (dir < 0) { + if (pos == 0) + skip_line = 1; + else + pos -= 1 + (pos != 1 && s[pos-2] == Meta); + } else if (sbuf[0] != '^') { + if (pos >= strlen(s+1)) + skip_line = 1; + else + pos += 1 + (s[pos] == Meta); + } else + skip_line = 1; + skip_pos = 0; + } + if (!skip_line && ((sbuf[0] == '^') ? + (t = metadiffer(s, sbuf + 1, sbptr - 1) < sens ? s : NULL) : + (t = hstrnstr(s, pos, sbuf, sbptr, dir, sens)))) { + zle_goto_hist(hl); + pos = t - s; + cs = ztrsub(t, s) + (dir == 1? sbptr - (sbuf[0]=='^') : 0); + nomatch = 0; + statusline = ibuf + NORM_PROMPT_POS; + break; + } + hl += dir; + if (!(s = zle_get_event(hl))) { + if (sbptr == (int)isrch_spots[top_spot-1].len + && (isrch_spots[top_spot-1].flags & ISS_FAILING)) + top_spot--; + get_isrch_spot(top_spot, &hl, &pos, &cs, &sbptr, + &dir, &nomatch); + if (!nomatch) { + feep(); + nomatch = 1; + } + s = last_line; + skip_line = 0; + statusline = ibuf; + break; + } + pos = dir == 1? 0 : strlen(s); + skip_line = !strcmp(last_line, s); + } + } else { + top_spot = 0; + nomatch = 0; + statusline = ibuf + NORM_PROMPT_POS; + } + sbuf[sbptr] = '_'; + statusll = sbuf - statusline + sbptr + 1; + ref: + refresh(); + if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) { + int i; + get_isrch_spot(0, &hl, &pos, &i, &sbptr, &dir, &nomatch); + s = zle_get_event(hl); + zle_goto_hist(hl); + cs = i; + break; + } + if(cmd == Th(z_clearscreen)) { + clearscreen(); + goto ref; + } else if(cmd == Th(z_redisplay)) { + redisplay(); + goto ref; + } else if(cmd == Th(z_vicmdmode)) { + if(selectkeymap(invicmdmode() ? "main" : "vicmd", 0)) + feep(); + goto ref; + } else if(cmd == Th(z_vibackwarddeletechar) || + cmd == Th(z_backwarddeletechar)) { + if (top_spot) + get_isrch_spot(--top_spot, &hl, &pos, &cs, &sbptr, + &dir, &nomatch); + else + feep(); + if (nomatch) { + statusline = ibuf; + skip_pos = 1; + } + s = zle_get_event(hl); + if (nomatch || !sbptr || (sbptr == 1 && sbuf[0] == '^')) { + int i = cs; + zle_goto_hist(hl); + cs = i; + } + memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? "fwd" : "bck", 3); + continue; + } else if(cmd == Th(z_acceptandhold)) { + acceptandhold(); + break; + } else if(cmd == Th(z_acceptandinfernexthistory)) { + acceptandinfernexthistory(); + break; + } else if(cmd == Th(z_acceptlineanddownhistory)) { + acceptlineanddownhistory(); + break; + } else if(cmd == Th(z_acceptline)) { + acceptline(); + break; + } else if(cmd == Th(z_historyincrementalsearchbackward)) { + set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch); + if (dir != -1) + dir = -1; + else + skip_pos = 1; + goto rpt; + } else if(cmd == Th(z_historyincrementalsearchforward)) { + set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch); + if (dir != 1) + dir = 1; + else + skip_pos = 1; + goto rpt; + } else if(cmd == Th(z_virevrepeatsearch)) { + set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch); + dir = -odir; + skip_pos = 1; + goto rpt; + } else if(cmd == Th(z_virepeatsearch)) { + set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch); + dir = odir; + skip_pos = 1; + rpt: + if (!sbptr && previous_search_len) { + if (previous_search_len > sibuf - FIRST_SEARCH_CHAR - 2) { + ibuf = hrealloc(ibuf, sibuf, sibuf + previous_search_len); + sbuf = ibuf + FIRST_SEARCH_CHAR; + sibuf += previous_search_len; + } + memcpy(sbuf, previous_search, sbptr = previous_search_len); + } + memcpy(ibuf + NORM_PROMPT_POS, (dir == 1) ? "fwd" : "bck", 3); + continue; + } else if(cmd == Th(z_viquotedinsert) || + cmd == Th(z_quotedinsert)) { + if(cmd == Th(z_viquotedinsert)) { + sbuf[sbptr] = '^'; + refresh(); + } + if ((c = getkey(0)) == EOF) + feep(); + else + goto ins; + } else { + if(cmd == Th(z_selfinsertunmeta)) { + c &= 0x7f; + if(c == '\r') + c = '\n'; + } else if (cmd == Th(z_magicspace)) + c = ' '; + else if (cmd != Th(z_selfinsert)) { + ungetkeycmd(); + if (cmd == Th(z_sendbreak)) + sbptr = 0; + break; + } + ins: + if (sbptr == PATH_MAX) { + feep(); + continue; + } + set_isrch_spot(top_spot++, hl, pos, cs, sbptr, dir, nomatch); + if (sbptr == sibuf - FIRST_SEARCH_CHAR - 2) { + ibuf = hrealloc(ibuf, sibuf, sibuf * 2); + sbuf = ibuf + FIRST_SEARCH_CHAR; + sibuf *= 2; + } + sbuf[sbptr++] = c; + } + handlefeep(); + } + if (sbptr) { + zfree(previous_search, previous_search_len); + previous_search = zalloc(sbptr); + memcpy(previous_search, sbuf, previous_search_len = sbptr); + } + statusline = NULL; + selectkeymap(okeymap, 1); +} + +/**/ +void +acceptandinfernexthistory(void) +{ + int t0; + char *s; + + done = 1; + for (t0 = histline - 2;; t0--) { + if (!(s = qgetevent(t0))) + return; + if (!metadiffer(s, (char *) line, ll)) + break; + } + if (!(s = qgetevent(t0 + 1))) + return; + pushnode(bufstack, ztrdup(s)); + stackhist = t0 + 1; +} + +/**/ +void +infernexthistory(void) +{ + int t0; + char *s; + + for (t0 = histline - 2;; t0--) { + if (!(s = qgetevent(t0))) { + feep(); + return; + } + if (! metadiffer(s, (char *) line, ll)) + break; + } + if (!(s = qgetevent(t0 + 1))) { + feep(); + return; + } + zle_goto_hist(t0 + 1); +} + +/**/ +void +vifetchhistory(void) +{ + if (zmult < 0) + return; + if (histline == curhist) { + if (!(zmod.flags & MOD_MULT)) { + cs = ll; + cs = findbol(); + return; + } + } + if (!zle_goto_hist((zmod.flags & MOD_MULT) ? zmult : curhist) && + isset(HISTBEEP)) + feep(); +} + +/* the last vi search */ + +static char *visrchstr; +static int visrchsense; + +/**/ +static int +getvisrchstr(void) +{ + char *sbuf = halloc(80); + int sptr = 1, ret = 0, ssbuf = 80; + Thingy cmd; + char *okeymap = curkeymapname; + + if (visrchstr) { + zsfree(visrchstr); + visrchstr = NULL; + } + statusline = sbuf; + sbuf[0] = (visrchsense == -1) ? '?' : '/'; + selectkeymap("main", 1); + while (sptr) { + sbuf[sptr] = '_'; + statusll = sptr + 1; + refresh(); + if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) { + ret = 0; + break; + } + if(cmd == Th(z_magicspace)) { + c = ' '; + cmd = Th(z_selfinsert); + } + if(cmd == Th(z_redisplay)) { + redisplay(); + } else if(cmd == Th(z_clearscreen)) { + clearscreen(); + } else if(cmd == Th(z_acceptline) || + cmd == Th(z_vicmdmode)) { + sbuf[sptr] = 0; + visrchstr = metafy(sbuf + 1, sptr - 1, META_DUP); + ret = 1; + sptr = 0; + } else if(cmd == Th(z_backwarddeletechar) || + cmd == Th(z_vibackwarddeletechar)) { + sptr--; + } else if(cmd == Th(z_backwardkillword) || + cmd == Th(z_vibackwardkillword)) { + while(sptr != 1 && iblank(sbuf[sptr - 1])) + sptr--; + if(iident(sbuf[sptr - 1])) + while(sptr != 1 && iident(sbuf[sptr - 1])) + sptr--; + else + while(sptr != 1 && !iident(sbuf[sptr - 1]) && !iblank(sbuf[sptr - 1])) + sptr--; + } else if(cmd == Th(z_viquotedinsert) || cmd == Th(z_quotedinsert)) { + if(cmd == Th(z_viquotedinsert)) { + sbuf[sptr] = '^'; + refresh(); + } + if ((c = getkey(0)) == EOF) + feep(); + else + goto ins; + } else if(cmd == Th(z_selfinsertunmeta) || cmd == Th(z_selfinsert)) { + if(cmd == Th(z_selfinsertunmeta)) { + c &= 0x7f; + if(c == '\r') + c = '\n'; + } + ins: + if(sptr == ssbuf - 1) { + char *newbuf = halloc(ssbuf *= 2); + strcpy(newbuf, sbuf); + statusline = sbuf = newbuf; + } + sbuf[sptr++] = c; + } else { + feep(); + } + handlefeep(); + } + statusline = NULL; + selectkeymap(okeymap, 1); + return ret; +} + +/**/ +void +vihistorysearchforward(void) +{ + visrchsense = 1; + if (getvisrchstr()) + virepeatsearch(); +} + +/**/ +void +vihistorysearchbackward(void) +{ + visrchsense = -1; + if (getvisrchstr()) + virepeatsearch(); +} + +/**/ +void +virepeatsearch(void) +{ + int hl = histline, t0; + int n = zmult; + char *s; + + if (!visrchstr) { + feep(); + return; + } + if (!n) + return; + if (n < 0) { + n = -n; + visrchsense = -visrchsense; + } + t0 = strlen(visrchstr); + for (;;) { + hl += visrchsense; + if (!(s = zle_get_event(hl))) { + feep(); + return; + } + if (!metadiffer(s, (char *) line, ll)) + continue; + if (*visrchstr == '^') { + if (strncmp(s, visrchstr + 1, t0 - 1) != 0) + continue; + } else if (!hstrnstr(s, 0, visrchstr, t0, 1, 1)) + continue; + if (--n <= 0) + break; + } + zle_goto_hist(hl); +} + +/**/ +void +virevrepeatsearch(void) +{ + visrchsense = -visrchsense; + virepeatsearch(); + visrchsense = -visrchsense; +} + +/* Extra function added by A.R. Iano-Fletcher. */ +/*The extern variable "cs" is the position of the cursor. */ +/* history-beginning-search-backward */ + +/**/ +void +historybeginningsearchbackward(void) +{ + int cpos = cs; /* save cursor position */ + int hl = histline; + int n = zmult; + char *s; + + if (!n) + return; + if (n < 0) { + zmult = -n; + historybeginningsearchforward(); + zmult = n; + return; + } + for (;;) { + hl--; + if (!(s = zle_get_event(hl))) { + feep(); + return; + } + if (metadiffer(s, (char *)line, cs) < 0 && + metadiffer(s, (char *)line, ll)) + if (--n <= 0) + break; + } + + zle_goto_hist(hl); + cs = cpos; +} + +/* Extra function added by A.R. Iano-Fletcher. */ + +/* history-beginning-search-forward */ +/**/ +void +historybeginningsearchforward(void) +{ + int cpos = cs; /* save cursor position */ + int hl = histline; + int n = zmult; + char *s; + + if (!n) + return; + if (n < 0) { + zmult = -n; + historybeginningsearchbackward(); + zmult = n; + return; + } + for (;;) { + hl++; + if (!(s = zle_get_event(hl))) { + feep(); + return; + } + if (metadiffer(s, (char *)line, cs) < (hl == curhist) && + metadiffer(s, (char *)line, ll)) + if (--n <= 0) + break; + } + + zle_goto_hist(hl); + cs = cpos; +} diff --git a/Src/Zle/zle_keymap.c b/Src/Zle/zle_keymap.c new file mode 100644 index 000000000..7de96bd03 --- /dev/null +++ b/Src/Zle/zle_keymap.c @@ -0,0 +1,1238 @@ +/* + * zle_keymap.c - keymaps and key bindings + * + * 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 "zle.mdh" + +/* + * Keymap structures: + * + * There is a hash table of keymap names. Each name just points to a keymap. + * More than one name may point to the same keymap. + * + * Each keymap consists of a table of bindings for each character, and a + * hash table of multi-character key bindings. The keymap has no individual + * name, but maintains a reference count. + * + * In a keymap's table of initial bindings, each character is either bound to + * a thingy, or is a prefix (in which case NULL is stored). Those prefix + * entries are matched by more complex entries in the multi-character + * binding hash table. Each entry in this hash table (which is indexed by + * metafied key sequence) either has a normal thingy binding or a string to + * send (in which case the NULL thingy is used). Each entry also has a count + * of other entries for which it is a prefix. + */ + +typedef struct keymapname *KeymapName; +typedef struct key *Key; + +struct keymapname { + HashNode next; /* next in the hash chain */ + char *nam; /* name of the keymap */ + int flags; /* various flags (see below) */ + Keymap keymap; /* the keymap itsef */ +}; + +#define KMN_IMMORTAL (1<<1) + +struct keymap { + Thingy first[256]; /* base binding of each character */ + HashTable multi; /* multi-character bindings */ + int flags; /* various flags (see below) */ + int rc; /* reference count */ +}; + +#define KM_IMMUTABLE (1<<1) + +struct key { + HashNode next; /* next in hash chain */ + char *nam; /* key sequence (metafied) */ + Thingy bind; /* binding of this key sequence */ + char *str; /* string for send-string (metafied) */ + int prefixct; /* number of sequences for which this is a prefix */ +}; + +/* This structure is used when listing keymaps. */ + +struct bindstate { + int flags; + char *kmname; + char *firstseq; + char *lastseq; + Thingy bind; + char *str; +}; + +#define BS_LIST (1<<0) +#define BS_ALL (1<<1) + +/* local functions */ + +#include "zle_keymap.pro" + +/* currently selected keymap, and its name */ + +/**/ +Keymap curkeymap; +/**/ +char *curkeymapname; + +/* the hash table of keymap names */ + +static HashTable keymapnamtab; + +/* key sequence reading data */ + +static char *keybuf; +static int keybuflen, keybufsz = 20; + +/* last command executed with execute-named-command */ + +static Thingy lastnamed; + +/**********************************/ +/* hashtable management functions */ +/**********************************/ + +/**/ +static void +createkeymapnamtab(void) +{ + keymapnamtab = newhashtable(7, "keymapnamtab", NULL); + + keymapnamtab->hash = hasher; + keymapnamtab->emptytable = emptyhashtable; + keymapnamtab->filltable = NULL; + keymapnamtab->addnode = addhashnode; + keymapnamtab->getnode = gethashnode2; + keymapnamtab->getnode2 = gethashnode2; + keymapnamtab->removenode = removehashnode; + keymapnamtab->disablenode = NULL; + keymapnamtab->enablenode = NULL; + keymapnamtab->freenode = freekeymapnamnode; + keymapnamtab->printnode = NULL; +} + +/**/ +static KeymapName +makekeymapnamnode(Keymap keymap) +{ + KeymapName kmn = (KeymapName) zcalloc(sizeof(*kmn)); + + kmn->keymap = keymap; + return kmn; +} + +/**/ +static void +freekeymapnamnode(HashNode hn) +{ + KeymapName kmn = (KeymapName) hn; + + zsfree(kmn->nam); + if(!--kmn->keymap->rc) + deletekeymap(kmn->keymap); + zfree(kmn, sizeof(*kmn)); +} + +/**/ +static HashTable +newkeytab(char *kmname) +{ + HashTable ht = newhashtable(19, + kmname ? dyncat("keytab:", kmname) : "keytab:", NULL); + + ht->hash = hasher; + ht->emptytable = emptyhashtable; + ht->filltable = NULL; + ht->addnode = addhashnode; + ht->getnode = gethashnode2; + ht->getnode2 = gethashnode2; + ht->removenode = removehashnode; + ht->disablenode = NULL; + ht->enablenode = NULL; + ht->freenode = freekeynode; + ht->printnode = NULL; + + return ht; +} + +/**/ +static Key +makekeynode(Thingy t, char *str) +{ + Key k = (Key) zcalloc(sizeof(*k)); + + k->bind = t; + k->str = str; + return k; +} + +/**/ +static void +freekeynode(HashNode hn) +{ + Key k = (Key) hn; + + zsfree(k->nam); + unrefthingy(k->bind); + zsfree(k->str); + zfree(k, sizeof(*k)); +} + +/**************************/ +/* main keymap operations */ +/**************************/ + +static HashTable copyto; + +/**/ +static Keymap +newkeymap(Keymap tocopy, char *kmname) +{ + Keymap km = zcalloc(sizeof(*km)); + int i; + + km->rc = 0; + km->multi = newkeytab(kmname); + if(tocopy) { + for(i = 256; i--; ) + km->first[i] = refthingy(tocopy->first[i]); + copyto = km->multi; + scanhashtable(tocopy->multi, 0, 0, 0, scancopykeys, 0); + } else { + for(i = 256; i--; ) + km->first[i] = refthingy(t_undefinedkey); + } + return km; +} + +/**/ +static void +scancopykeys(HashNode hn, int flags) +{ + Key k = (Key) hn; + Key kn = zalloc(sizeof(*k)); + + memcpy(kn, k, sizeof(*k)); + refthingy(kn->bind); + kn->str = ztrdup(k->str); + copyto->addnode(copyto, ztrdup(k->nam), kn); +} + +/**/ +static void +deletekeymap(Keymap km) +{ + int i; + + deletehashtable(km->multi); + for(i = 256; i--; ) + unrefthingy(km->first[i]); + zfree(km, sizeof(*km)); +} + +static Keymap skm_km; +static int skm_last; +static KeyScanFunc skm_func; +static void *skm_magic; + +/**/ +void +scankeymap(Keymap km, int sort, KeyScanFunc func, void *magic) +{ + char m[3]; + + skm_km = km; + skm_last = sort ? -1 : 255; + skm_func = func; + skm_magic = magic; + scanhashtable(km->multi, sort, 0, 0, scankeys, 0); + if(!sort) + skm_last = -1; + while(skm_last < 255) { + skm_last++; + if(km->first[skm_last] && km->first[skm_last] != t_undefinedkey) { + m[0] = skm_last; + metafy(m, 1, META_NOALLOC); + func(m, km->first[skm_last], NULL, magic); + } + } +} + +/**/ +static void +scankeys(HashNode hn, int flags) +{ + Key k = (Key) hn; + int f = k->nam[0] == Meta ? STOUC(k->nam[1])^32 : STOUC(k->nam[0]); + char m[3]; + + while(skm_last < f) { + skm_last++; + if(skm_km->first[skm_last] && + skm_km->first[skm_last] != t_undefinedkey) { + m[0] = skm_last; + metafy(m, 1, META_NOALLOC); + skm_func(m, skm_km->first[skm_last], NULL, skm_magic); + } + } + skm_func(k->nam, k->bind, k->str, skm_magic); +} + +/**************************/ +/* keymap name operations */ +/**************************/ + +/**/ +Keymap +openkeymap(char *name) +{ + KeymapName n = (KeymapName) keymapnamtab->getnode(keymapnamtab, name); + return n ? n->keymap : NULL; +} + +/**/ +static int +unlinkkeymap(char *name) +{ + KeymapName n = (KeymapName) keymapnamtab->getnode(keymapnamtab, name); + if(!n) + return 2; + if(n->flags & KMN_IMMORTAL) + return 1; + keymapnamtab->freenode(keymapnamtab->removenode(keymapnamtab, name)); + return 0; +} + +/**/ +static int +linkkeymap(Keymap km, char *name) +{ + KeymapName n = (KeymapName) keymapnamtab->getnode(keymapnamtab, name); + if(n) { + if(n->flags & KMN_IMMORTAL) + return 1; + if(n->keymap == km) + return 0; + if(!--n->keymap->rc) + deletekeymap(n->keymap); + n->keymap = km; + } else + keymapnamtab->addnode(keymapnamtab, ztrdup(name), + makekeymapnamnode(km)); + km->rc++; + return 0; +} + +/* Select a keymap as the current ZLE keymap. Can optionally fall back * + * on the guaranteed safe keymap if it fails. */ + +/**/ +int +selectkeymap(char *name, int fb) +{ + Keymap km = openkeymap(name); + + if(!km) { + char *nm = niceztrdup(name); + char *msg = tricat("No such keymap `", nm, "'"); + + zsfree(nm); + showmsg(msg); + zsfree(msg); + if(!fb) + return 1; + km = openkeymap(name = ".safe"); + } + curkeymapname = name; + curkeymap = km; + return 0; +} + +/* Reopen the currently selected keymap, in case it got deleted. This * + * should be called after doing anything that might have run an * + * arbitrary user-specified command. */ + +/**/ +void +reselectkeymap(void) +{ + selectkeymap(curkeymapname, 1); +} + +/******************************/ +/* operations on key bindings */ +/******************************/ + +/* Add/delete/change a keybinding in some keymap. km is the keymap to be * + * altered. seq is the metafied key sequence whose binding is to change. * + * bind is the thingy to which the key sequence is to be bound. For * + * send-string, bind is NULL and str is the metafied key sequence to push * + * back onto the input. */ + +/**/ +int +bindkey(Keymap km, char *seq, Thingy bind, char *str) +{ + Key k; + int f = seq[0] == Meta ? STOUC(seq[1])^32 : STOUC(seq[0]); + char *buf, *ptr; + + if(km->flags & KM_IMMUTABLE) + return 1; + if(!*seq) + return 2; + if(!bind || ztrlen(seq) > 1) { + /* key needs to become a prefix if isn't one already */ + if(km->first[f]) { + char fs[3]; + fs[0] = f; + metafy(fs, 1, META_NOALLOC); + km->multi->addnode(km->multi, ztrdup(fs), + makekeynode(km->first[f], NULL)); + km->first[f] = NULL; + } + k = (Key) km->multi->getnode(km->multi, seq); + } else { + /* If the sequence is a prefix entry only due to being * + * a send-string binding, we can remove that entry. */ + if(!km->first[f]) { + k = (Key) km->multi->getnode(km->multi, seq); + if(!k->prefixct) + km->multi->freenode(km->multi->removenode(km->multi, seq)); + else + goto domulti; + } else + unrefthingy(km->first[f]); + /* Just replace the single-character binding. */ + km->first[f] = bind; + return 0; + } + domulti: + buf = ztrdup(seq); + ptr = strchr(buf, 0); + if(bind == t_undefinedkey) { + if(k) { + zsfree(k->str); + unrefthingy(k->bind); + k->bind = t_undefinedkey; + k->str = NULL; + while(!k->prefixct && k->bind == t_undefinedkey) { + km->multi->freenode(km->multi->removenode(km->multi, buf)); + *--ptr = 0; + if(ptr[-1] == Meta) + *--ptr = 0; + k = (Key) km->multi->getnode(km->multi, buf); + k->prefixct--; + if(!k->prefixct && k->bind && + (!buf[1] || (buf[0] == Meta && !buf[2]))) { + km->first[f] = refthingy(k->bind); + km->multi->freenode(km->multi->removenode(km->multi, buf)); + break; + } + } + } + } else { + if(!k) { + int added; + + km->multi->addnode(km->multi, ztrdup(buf), makekeynode(bind, ztrdup(str))); + do { + *--ptr = 0; + if(ptr > buf && ptr[-1] == Meta) + *--ptr = 0; + k = (Key) km->multi->getnode(km->multi, buf); + if((added = !k)) + km->multi->addnode(km->multi, ztrdup(buf), + k = makekeynode(refthingy(t_undefinedkey), NULL)); + k->prefixct++; + } while(added); + } else { + unrefthingy(k->bind); + zsfree(k->str); + k->bind = bind; + k->str = bind ? NULL : ztrdup(str); + } + } + free(buf); + return 0; +} + +/* Look up a key binding. The binding is returned. In the case of a * + * send-string, NULL is returned and *strp is modified to point to the * + * metafied string of characters to be pushed back. */ + +/**/ +Thingy +keybind(Keymap km, char *seq, char **strp) +{ + Key k; + + if(ztrlen(seq) == 1) { + int f = seq[0] == Meta ? STOUC(seq[1])^32 : STOUC(seq[0]); + Thingy bind = km->first[f]; + + if(bind) + return bind; + } + k = (Key) km->multi->getnode(km->multi, seq); + if(!k) + return t_undefinedkey; + *strp = k->str; + return k->bind; +} + +/* Check whether a key sequence is a prefix of a longer bound sequence. * + * One oddity: if *nothing* in the keymap is bound, this returns true * + * for the empty sequence, even though this is not strictly accurate. */ + +/**/ +static int +keyisprefix(Keymap km, char *seq) +{ + Key k; + + if(!*seq) + return 1; + if(ztrlen(seq) == 1) { + int f = seq[0] == Meta ? STOUC(seq[1])^32 : STOUC(seq[0]); + + if(km->first[f]) + return 0; + } + k = (Key) km->multi->getnode(km->multi, seq); + return k && k->prefixct; +} + +/*******************/ +/* bindkey builtin */ +/*******************/ + +/* + * THE BINDKEY BUILTIN + * + * Keymaps can be specified to bindkey in the following ways: + * + * -e select "emacs", also link it to "main" + * -v select "viins", also link it to "main" + * -a select "vicmd" + * -M first argument gives map name + * defaults to "main" + * + * These operations cannot have a keymap selected in the normal way: + * + * -l list all the keymap names + * -d delete all keymaps and reset to the default state (no arguments) + * -D delete named keymaps + * -A link the two named keymaps (2 arguments) + * -N create new empty keymap (1 argument) + * -N create new keymap, copying the second named keymap (2 arguments) + * + * Other operations: + * + * -m add the meta bindings to the selected keymap (no arguments) + * -r unbind each named string in the selected keymap + * -s bind send-strings in the selected keymap (2+ arguments) + * bind commands in the selected keymap (2+ arguments) + * display one binding in the selected keymap (1 argument) + * display the entire selected keymap (no arguments) + * + * There is an exception that the entire keymap display will not be performed + * if the -e or -v options were used. + * + * Other options: + * + * -L do listings in the form of bindkey commands + * -R for the binding operations, accept ranges instead of sequences + */ + +/**/ +int +bin_bindkey(char *name, char **argv, char *ops, int func) +{ + static struct opn { + char o; + char selp; + int (*func) _((char *, char *, Keymap, char **, char *, char)); + int min, max; + } const opns[] = { + { 'l', 0, bin_bindkey_lsmaps, 0, 0 }, + { 'd', 0, bin_bindkey_delall, 0, 0 }, + { 'D', 0, bin_bindkey_del, 1, -1 }, + { 'A', 0, bin_bindkey_link, 2, 2 }, + { 'N', 0, bin_bindkey_new, 1, 2 }, + { 'm', 1, bin_bindkey_meta, 0, 0 }, + { 'r', 1, bin_bindkey_bind, 1, -1 }, + { 's', 1, bin_bindkey_bind, 2, -1 }, + { 0, 1, bin_bindkey_bind, 0, -1 }, + }; + struct opn const *op, *opp; + char *kmname; + Keymap km; + int n; + + /* select operation and ensure no clashing arguments */ + for(op = opns; op->o && !ops[op->o]; op++) ; + if(op->o) + for(opp = op; (++opp)->o; ) + if(ops[opp->o]) { + zwarnnam(name, "incompatible operation selection options", + NULL, 0); + return 1; + } + n = ops['e'] + ops['v'] + ops['a'] + ops['M']; + if(!op->selp && n) { + zwarnnam(name, "keymap cannot be selected with -%c", NULL, op->o); + return 1; + } + if(n > 1) { + zwarnnam(name, "incompatible keymap selection options", NULL, 0); + return 1; + } + + /* keymap selection */ + if(op->selp) { + if(ops['e']) + kmname = "emacs"; + else if(ops['v']) + kmname = "viins"; + else if(ops['a']) + kmname = "vicmd"; + else if(ops['M']) { + kmname = *argv++; + if(!kmname) { + zwarnnam(name, "-M option requires a keymap argument", NULL, 0); + return 1; + } + } else + kmname = "main"; + km = openkeymap(kmname); + if(!km) { + zwarnnam(name, "no such keymap `%s'", kmname, 0); + return 1; + } + if(ops['e'] || ops['v']) + linkkeymap(km, "main"); + } else { + kmname = NULL; + km = NULL; + } + + /* listing is a special case */ + if(!op->o && (!argv[0] || !argv[1])) { + if(ops['e'] || ops['v']) + return 0; + return bin_bindkey_list(name, kmname, km, argv, ops, op->o); + } + + /* check number of arguments */ + for(n = 0; argv[n]; n++) ; + if(n < op->min) { + zwarnnam(name, "not enough arguments for -%c", NULL, op->o); + return 1; + } else if(op->max != -1 && n > op->max) { + zwarnnam(name, "too many arguments for -%c", NULL, op->o); + return 1; + } + + /* pass on the work to the operation function */ + return op->func(name, kmname, km, argv, ops, op->o); +} + +/* list the available keymaps */ + +/**/ +static int +bin_bindkey_lsmaps(char *name, char *kmname, Keymap km, char **argv, char *ops, char func) +{ + scanhashtable(keymapnamtab, 1, 0, 0, scanlistmaps, ops['L']); + return 0; +} + +/**/ +static void +scanlistmaps(HashNode hn, int list) +{ + KeymapName n = (KeymapName) hn; + + if(list) { + fputs("bindkey -N ", stdout); + if(n->nam[0] == '-') + fputs("-- ", stdout); + quotedzputs(n->nam, stdout); + } else + nicezputs(n->nam, stdout); + putchar('\n'); +} + +/* reset all keymaps to the default state */ + +/**/ +static int +bin_bindkey_delall(char *name, char *kmname, Keymap km, char **argv, char *ops, char func) +{ + keymapnamtab->emptytable(keymapnamtab); + default_bindings(); + return 0; +} + +/* delete named keymaps */ + +/**/ +static int +bin_bindkey_del(char *name, char *kmname, Keymap km, char **argv, char *ops, char func) +{ + int ret = 0; + + do { + int r = unlinkkeymap(*argv); + if(r == 1) + zwarnnam(name, "keymap name `%s' is protected", *argv, 0); + else if(r == 2) + zwarnnam(name, "no such keymap `%s'", *argv, 0); + ret |= !!r; + } while(*++argv); + return ret; +} + +/* link named keymaps */ + +/**/ +static int +bin_bindkey_link(char *name, char *kmname, Keymap km, char **argv, char *ops, char func) +{ + km = openkeymap(argv[0]); + if(!km) { + zwarnnam(name, "no such keymap `%s'", argv[0], 0); + return 1; + } else if(linkkeymap(km, argv[1])) { + zwarnnam(name, "keymap name `%s' is protected", argv[1], 0); + return 1; + } + return 0; +} + +/* create a new keymap */ + +/**/ +static int +bin_bindkey_new(char *name, char *kmname, Keymap km, char **argv, char *ops, char func) +{ + KeymapName kmn = (KeymapName) keymapnamtab->getnode(keymapnamtab, argv[0]); + + if(kmn && (kmn -> flags & KMN_IMMORTAL)) { + zwarnnam(name, "keymap name `%s' is protected", argv[0], 0); + return 1; + } + if(argv[1]) { + km = openkeymap(argv[1]); + if(!km) { + zwarnnam(name, "no such keymap `%s'", argv[0], 0); + return 1; + } + } else + km = NULL; + linkkeymap(newkeymap(km, argv[0]), argv[0]); + return 0; +} + +/* Add standard meta bindings to a keymap. Only sequences currently either * + * unbound or bound to self-insert are affected. Note that the use of * + * bindkey() is quite necessary: if this function were to go through the * + * km->first table itself, it would miss any prefix sequences that should * + * be rebound. */ + +/**/ +static int +bin_bindkey_meta(char *name, char *kmname, Keymap km, char **argv, char *ops, char func) +{ + char m[3], *str; + int i; + Thingy fn; + + if(km->flags & KM_IMMUTABLE) { + zwarnnam(name, "keymap `%s' is protected", kmname, 0); + return 1; + } + for(i = 128; i < 256; i++) + if(metabind[i - 128] != z_undefinedkey) { + m[0] = i; + metafy(m, 1, META_NOALLOC); + fn = keybind(km, m, &str); + if(fn == t_selfinsert || fn == t_undefinedkey) + bindkey(km, m, refthingy(Th(metabind[i - 128])), NULL); + } + return 0; +} + +/* Change key bindings. func can be: * + * 'r' bind sequences to undefined-key * + * 's' bind sequneces to specified send-strings * + * 0 bind sequences to specified functions * + * If the -R option is used, bind to key ranges * + * instead of single key sequences. */ + +/**/ +static int +bin_bindkey_bind(char *name, char *kmname, Keymap km, char **argv, char *ops, char func) +{ + int ret = 0; + + if(!func || func == 's') { + char **a; + + for(a = argv+2; *a; a++) + if(!*++a) { + zwarnnam(name, "even number of arguments required", NULL, 0); + return 1; + } + } + if(km->flags & KM_IMMUTABLE) { + zwarnnam(name, "keymap `%s' is protected", kmname, 0); + return 1; + } + do { + char *useq = *argv, *bseq, *seq, *str; + int len; + Thingy fn; + + if(func == 'r') { + fn = refthingy(t_undefinedkey); + str = NULL; + } else if(func == 's') { + str = getkeystring(*++argv, &len, 2, NULL); + fn = NULL; + str = metafy(str, len, META_HREALLOC); + } else { + fn = rthingy(*++argv); + str = NULL; + } + bseq = getkeystring(useq, &len, 2, NULL); + seq = metafy(bseq, len, META_USEHEAP); + if(ops['R']) { + int first, last; + char m[3]; + + if(len < 2 || len > 2 + (bseq[1] == '-') || + (first = STOUC(bseq[0])) > (last = STOUC(bseq[len - 1]))) { + zwarnnam(name, "malformed key range `%s'", useq, 0); + ret = 1; + } else { + for(; first <= last; first++) { + m[0] = first; + metafy(m, 1, META_NOALLOC); + bindkey(km, m, refthingy(fn), str); + } + unrefthingy(fn); + } + } else { + if(bindkey(km, seq, fn, str)) { + zwarnnam(name, "cannot bind to an empty key sequence", NULL, 0); + ret = 1; + } + } + } while(*++argv); + return ret; +} + +/* List key bindings. If an argument is given, list just that one * + * binding, otherwise list the entire keymap. If the -L option is * + * given, list in the form of bindkey commands. */ + +/**/ +static int +bin_bindkey_list(char *name, char *kmname, Keymap km, char **argv, char *ops, char func) +{ + struct bindstate bs; + + bs.flags = ops['L'] ? BS_LIST : 0; + bs.kmname = kmname; + if(argv[0]) { + int len; + char *seq; + + seq = getkeystring(argv[0], &len, 2, NULL); + seq = metafy(seq, len, META_HREALLOC); + bs.flags |= BS_ALL; + bs.firstseq = bs.lastseq = seq; + bs.bind = keybind(km, seq, &bs.str); + bindlistout(&bs); + } else { + bs.firstseq = ztrdup(""); + bs.lastseq = ztrdup(""); + bs.bind = t_undefinedkey; + bs.str = NULL; + scankeymap(km, 1, scanbindlist, &bs); + bindlistout(&bs); + zsfree(bs.firstseq); + zsfree(bs.lastseq); + } + return 0; +} + +/**/ +static void +scanbindlist(char *seq, Thingy bind, char *str, void *magic) +{ + struct bindstate *bs = magic; + + if(bind == bs->bind && (bind || !strcmp(str, bs->str)) && + ztrlen(seq) == 1 && ztrlen(bs->lastseq) == 1) { + int l = bs->lastseq[1] ? + STOUC(bs->lastseq[1]) ^ 32 : STOUC(bs->lastseq[0]); + int t = seq[1] ? STOUC(seq[1]) ^ 32 : STOUC(seq[0]); + + if(t == l + 1) { + zsfree(bs->lastseq); + bs->lastseq = ztrdup(seq); + return; + } + } + bindlistout(bs); + zsfree(bs->firstseq); + bs->firstseq = ztrdup(seq); + zsfree(bs->lastseq); + bs->lastseq = ztrdup(seq); + bs->bind = bind; + bs->str = str; +} + +/**/ +static void +bindlistout(struct bindstate *bs) +{ + int range; + + if(bs->bind == t_undefinedkey && !(bs->flags & BS_ALL)) + return; + range = strcmp(bs->firstseq, bs->lastseq); + if(bs->flags & BS_LIST) { + int nodash = 1; + + fputs("bindkey ", stdout); + if(range) + fputs("-R ", stdout); + if(!bs->bind) + fputs("-s ", stdout); + if(!strcmp(bs->kmname, "main")) + ; + else if(!strcmp(bs->kmname, "vicmd")) + fputs("-a ", stdout); + else { + fputs("-M ", stdout); + quotedzputs(bs->kmname, stdout); + putchar(' '); + nodash = 0; + } + if(nodash && bs->firstseq[0] == '-') + fputs("-- ", stdout); + } + printbind(bs->firstseq, stdout); + if(range) { + putchar('-'); + printbind(bs->lastseq, stdout); + } + putchar(' '); + if(bs->bind) { + ((bs->flags & BS_LIST) ? quotedzputs : nicezputs) + (bs->bind->nam, stdout); + } else + printbind(bs->str, stdout); + putchar('\n'); +} + +/****************************/ +/* initialisation functions */ +/****************************/ + +/* main initialisation entry point */ + +/**/ +void +init_keymaps(void) +{ + createkeymapnamtab(); + default_bindings(); + keybuf = (char *)zalloc(keybufsz); + lastnamed = refthingy(t_undefinedkey); +} + +#ifdef MODULE + +/* cleanup entry point (for unloading the zle module) */ + +/**/ +void +cleanup_keymaps(void) +{ + unrefthingy(lastnamed); + deletehashtable(keymapnamtab); + zfree(keybuf, keybufsz); +} + +#endif /* MODULE */ + +/* Create the default keymaps. For efficiency reasons, this function * + * assigns directly to the km->first array. It knows that there are no * + * prefix bindings in the way, and that it is using a simple keymap. */ + +/**/ +static void +default_bindings(void) +{ + Keymap vmap = newkeymap(NULL, "viins"); + Keymap emap = newkeymap(NULL, "emacs"); + Keymap amap = newkeymap(NULL, "vicmd"); + Keymap smap = newkeymap(NULL, ".safe"); + char buf[3], *ed; + int i; + + /* vi insert mode and emacs mode: * + * 0-31 taken from the tables * + * 32-126 self-insert * + * 127 same as entry[8] * + * 128-255 self-insert */ + for (i = 0; i < 32; i++) { + vmap->first[i] = refthingy(Th(viinsbind[i])); + emap->first[i] = refthingy(Th(emacsbind[i])); + } + for (i = 32; i < 256; i++) { + vmap->first[i] = refthingy(t_selfinsert); + emap->first[i] = refthingy(t_selfinsert); + } + unrefthingy(t_selfinsert); + unrefthingy(t_selfinsert); + vmap->first[127] = refthingy(vmap->first[8]); + emap->first[127] = refthingy(emap->first[8]); + + /* vi command mode: * + * 0-127 taken from the table * + * 128-255 undefined-key */ + for (i = 0; i < 128; i++) + amap->first[i] = refthingy(Th(vicmdbind[i])); + for (i = 128; i < 256; i++) + amap->first[i] = refthingy(t_undefinedkey); + + /* safe fallback keymap: + * 0-255 self-insert, except: * + * '\n' accept-line * + * '\r' accept-line */ + for (i = 0; i < 256; i++) + smap->first[i] = refthingy(t_selfinsert); + unrefthingy(t_selfinsert); + unrefthingy(t_selfinsert); + smap->first['\n'] = refthingy(t_acceptline); + smap->first['\r'] = refthingy(t_acceptline); + + /* vt100 arrow keys are bound by default, for historical reasons. * + * Both standard and keypad modes are supported. */ + + /* vi command mode: arrow keys */ + bindkey(amap, "\33[A", refthingy(t_uplineorhistory), NULL); + bindkey(amap, "\33[B", refthingy(t_downlineorhistory), NULL); + bindkey(amap, "\33[C", refthingy(t_viforwardchar), NULL); + bindkey(amap, "\33[D", refthingy(t_vibackwardchar), NULL); + bindkey(amap, "\33OA", refthingy(t_uplineorhistory), NULL); + bindkey(amap, "\33OB", refthingy(t_downlineorhistory), NULL); + bindkey(amap, "\33OC", refthingy(t_viforwardchar), NULL); + bindkey(amap, "\33OD", refthingy(t_vibackwardchar), NULL); + + /* emacs mode: arrow keys */ + bindkey(emap, "\33[A", refthingy(t_uplineorhistory), NULL); + bindkey(emap, "\33[B", refthingy(t_downlineorhistory), NULL); + bindkey(emap, "\33[C", refthingy(t_forwardchar), NULL); + bindkey(emap, "\33[D", refthingy(t_backwardchar), NULL); + bindkey(emap, "\33OA", refthingy(t_uplineorhistory), NULL); + bindkey(emap, "\33OB", refthingy(t_downlineorhistory), NULL); + bindkey(emap, "\33OC", refthingy(t_forwardchar), NULL); + bindkey(emap, "\33OD", refthingy(t_backwardchar), NULL); + + /* emacs mode: ^X sequences */ + bindkey(emap, "\30*", refthingy(t_expandword), NULL); + bindkey(emap, "\30g", refthingy(t_listexpand), NULL); + bindkey(emap, "\30G", refthingy(t_listexpand), NULL); + bindkey(emap, "\30\16", refthingy(t_infernexthistory), NULL); + bindkey(emap, "\30\13", refthingy(t_killbuffer), NULL); + bindkey(emap, "\30\6", refthingy(t_vifindnextchar), NULL); + bindkey(emap, "\30\17", refthingy(t_overwritemode), NULL); + bindkey(emap, "\30\25", refthingy(t_undo), NULL); + bindkey(emap, "\30\26", refthingy(t_vicmdmode), NULL); + bindkey(emap, "\30\12", refthingy(t_vijoin), NULL); + bindkey(emap, "\30\2", refthingy(t_vimatchbracket), NULL); + bindkey(emap, "\30s", refthingy(t_historyincrementalsearchforward), NULL); + bindkey(emap, "\30r", refthingy(t_historyincrementalsearchbackward), NULL); + bindkey(emap, "\30u", refthingy(t_undo), NULL); + bindkey(emap, "\30\30", refthingy(t_exchangepointandmark), NULL); + bindkey(emap, "\30=", refthingy(t_whatcursorposition), NULL); + + /* emacs mode: ESC sequences, all taken from the meta binding table */ + buf[0] = '\33'; + buf[2] = 0; + for (i = 0; i < 128; i++) + if (metabind[i] != z_undefinedkey) { + buf[1] = i; + bindkey(emap, buf, refthingy(Th(metabind[i])), NULL); + } + + /* Put the keymaps in the right namespace. The "main" keymap * + * will be linked to the "emacs" keymap, except that if VISUAL * + * or EDITOR contain the string "vi" then it will be linked to * + * the "viins" keymap. */ + linkkeymap(vmap, "viins"); + linkkeymap(emap, "emacs"); + linkkeymap(amap, "vicmd"); + linkkeymap(smap, ".safe"); + if (((ed = zgetenv("VISUAL")) && strstr(ed, "vi")) || + ((ed = zgetenv("EDITOR")) && strstr(ed, "vi"))) + linkkeymap(vmap, "main"); + else + linkkeymap(emap, "main"); + + /* the .safe map cannot be modified or deleted */ + smap->flags |= KM_IMMUTABLE; + ((KeymapName) keymapnamtab->getnode(keymapnamtab, ".safe"))->flags + |= KMN_IMMORTAL; +} + +/*************************/ +/* reading key sequences */ +/*************************/ + +/* read a sequence of keys that is bound to some command in a keymap */ + +/**/ +char * +getkeymapcmd(Keymap km, Thingy *funcp, char **strp) +{ + Thingy func = t_undefinedkey; + char *str = NULL; + int lastlen = 0, lastc = c; + + keybuflen = 0; + keybuf[0] = 0; + while((c = getkeybuf(!!lastlen)) != EOF) { + char *s; + Thingy f = keybind(km, keybuf, &s); + + if(f != t_undefinedkey) { + lastlen = keybuflen; + func = f; + str = s; + lastc = c; + } + if(!keyisprefix(km, keybuf)) + break; + } + if(!lastlen && keybuflen) + lastlen = keybuflen; + else + c = lastc; + if(lastlen != keybuflen) { + unmetafy(keybuf + lastlen, &keybuflen); + ungetkeys(keybuf+lastlen, keybuflen); + if(vichgflag) + vichgbufptr -= keybuflen; + keybuf[lastlen] = 0; + } + *funcp = func; + *strp = str; + return keybuf; +} + +/**/ +static int +getkeybuf(int w) +{ + int c = getkey(w); + + if(c < 0) + return EOF; + if(keybuflen + 3 > keybufsz) + keybuf = realloc(keybuf, keybufsz *= 2); + if(imeta(c)) { + keybuf[keybuflen++] = Meta; + keybuf[keybuflen++] = c ^ 32; + } else + keybuf[keybuflen++] = c; + keybuf[keybuflen] = 0; + return c; +} + +/* Push back the last command sequence read by getkeymapcmd(). * + * Must be executed at most once after each getkeymapcmd(). */ + +/**/ +void +ungetkeycmd(void) +{ + ungetkeys(keybuf, keybuflen); +} + +/* read a command from the current keymap, with widgets */ + +/**/ +Thingy +getkeycmd(void) +{ + Thingy func; + int hops = 0; + char *seq, *str; + + sentstring: + seq = getkeymapcmd(curkeymap, &func, &str); + if(!*seq) + return NULL; + if(!func) { + int len; + char *pb; + + if (++hops == 20) { + zerr("string inserting another one too many times", NULL, 0); + hops = 0; + return NULL; + } + pb = unmetafy(ztrdup(str), &len); + ungetkeys(pb, len); + zfree(pb, strlen(str) + 1); + goto sentstring; + } + if (func == Th(z_executenamedcmd) && !statusline) { + while(func == Th(z_executenamedcmd)) + func = executenamedcommand("execute: "); + if(!func) + func = t_undefinedkey; + else if(func != Th(z_executelastnamedcmd)) { + unrefthingy(lastnamed); + lastnamed = refthingy(func); + } + } + if (func == Th(z_executelastnamedcmd)) + func = lastnamed; + return func; +} diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c new file mode 100644 index 000000000..338cdef41 --- /dev/null +++ b/Src/Zle/zle_main.c @@ -0,0 +1,907 @@ +/* + * zle_main.c - main routines for line editor + * + * 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 "zle.mdh" +#include "zle_main.pro" + +/* != 0 if we're done editing */ + +/**/ +int done; + +/* location of mark */ + +/**/ +int mark; + +/* last character pressed */ + +/**/ +int c; + +/* the binding for this key */ + +/**/ +Thingy bindk; + +/* insert mode/overwrite mode flag */ + +/**/ +int insmode; + +static int eofchar, eofsent; +static long keytimeout; + +#ifdef HAVE_SELECT +/* Terminal baud rate */ + +static int baud; +#endif + +/* flags associated with last command */ + +/**/ +int lastcmd; + +/* the status line, and its length */ + +/**/ +char *statusline; +/**/ +int statusll; + +/* The current history line and cursor position for the top line * + * on the buffer stack. */ + +/**/ +int stackhist, stackcs; + +/* != 0 if we are making undo records */ + +/**/ +int undoing; + +/* current modifier status */ + +/**/ +struct modifier zmod; + +/* Current command prefix status. This is normally 0. Prefixes set * + * this to 1. Each time round the main loop, this is checked: if it * + * is 0, the modifier status is reset; if it is 1, the modifier * + * status is left unchanged, and this flag is reset to 0. The * + * effect is that several prefix commands can be executed, and have * + * cumulative effect, but any other command execution will clear the * + * modifiers. */ + +/**/ +int prefixflag; + +/* != 0 if there is a pending beep (usually indicating an error) */ + +/**/ +int feepflag; + +/* set up terminal */ + +/**/ +void +setterm(void) +{ + struct ttyinfo ti; + +#if defined(CLOBBERS_TYPEAHEAD) && defined(FIONREAD) + int val; + + ioctl(SHTTY, FIONREAD, (char *)&val); + if (val) + return; +#endif + +/* sanitize the tty */ +#ifdef HAS_TIO + shttyinfo.tio.c_lflag |= ICANON | ECHO; +# ifdef FLUSHO + shttyinfo.tio.c_lflag &= ~FLUSHO; +# endif +#else /* not HAS_TIO */ + shttyinfo.sgttyb.sg_flags = (shttyinfo.sgttyb.sg_flags & ~CBREAK) | ECHO; + shttyinfo.lmodes &= ~LFLUSHO; +#endif + + attachtty(mypgrp); + ti = shttyinfo; +#ifdef HAS_TIO + if (unset(FLOWCONTROL)) + ti.tio.c_iflag &= ~IXON; + ti.tio.c_lflag &= ~(ICANON | ECHO +# ifdef FLUSHO + | FLUSHO +# endif + ); +# ifdef TAB3 + ti.tio.c_oflag &= ~TAB3; +# else +# ifdef OXTABS + ti.tio.c_oflag &= ~OXTABS; +# else + ti.tio.c_oflag &= ~XTABS; +# endif +# endif + ti.tio.c_oflag |= ONLCR; + ti.tio.c_cc[VQUIT] = +# ifdef VDISCARD + ti.tio.c_cc[VDISCARD] = +# endif +# ifdef VSUSP + ti.tio.c_cc[VSUSP] = +# endif +# ifdef VDSUSP + ti.tio.c_cc[VDSUSP] = +# endif +# ifdef VSWTCH + ti.tio.c_cc[VSWTCH] = +# endif +# ifdef VLNEXT + ti.tio.c_cc[VLNEXT] = +# endif + VDISABLEVAL; +# if defined(VSTART) && defined(VSTOP) + if (unset(FLOWCONTROL)) + ti.tio.c_cc[VSTART] = ti.tio.c_cc[VSTOP] = VDISABLEVAL; +# endif + eofchar = ti.tio.c_cc[VEOF]; + ti.tio.c_cc[VMIN] = 1; + ti.tio.c_cc[VTIME] = 0; + ti.tio.c_iflag |= (INLCR | ICRNL); + /* this line exchanges \n and \r; it's changed back in getkey + so that the net effect is no change at all inside the shell. + This double swap is to allow typeahead in common cases, eg. + + % bindkey -s '^J' 'echo foo^M' + % sleep 10 + echo foo<return> <--- typed before sleep returns + + The shell sees \n instead of \r, since it was changed by the kernel + while zsh wasn't looking. Then in getkey() \n is changed back to \r, + and it sees "echo foo<accept line>", as expected. Without the double + swap the shell would see "echo foo\n", which is translated to + "echo fooecho foo<accept line>" because of the binding. + Note that if you type <line-feed> during the sleep the shell just sees + \n, which is translated to \r in getkey(), and you just get another + prompt. For type-ahead to work in ALL cases you have to use + stty inlcr. + + Unfortunately it's IMPOSSIBLE to have a general solution if both + <return> and <line-feed> are mapped to the same character. The shell + could check if there is input and read it before setting it's own + terminal modes but if we get a \n we don't know whether to keep it or + change to \r :-( + */ + +#else /* not HAS_TIO */ + ti.sgttyb.sg_flags = (ti.sgttyb.sg_flags | CBREAK) & ~ECHO & ~XTABS; + ti.lmodes &= ~LFLUSHO; + eofchar = ti.tchars.t_eofc; + ti.tchars.t_quitc = + ti.ltchars.t_suspc = + ti.ltchars.t_flushc = + ti.ltchars.t_dsuspc = ti.ltchars.t_lnextc = -1; +#endif + +#if defined(TTY_NEEDS_DRAINING) && defined(TIOCOUTQ) && defined(HAVE_SELECT) + if (baud) { /**/ + int n = 0; + + while ((ioctl(SHTTY, TIOCOUTQ, (char *)&n) >= 0) && n) { + struct timeval tv; + + tv.tv_sec = n / baud; + tv.tv_usec = ((n % baud) * 1000000) / baud; + select(0, NULL, NULL, NULL, &tv); + } + } +#endif + + settyinfo(&ti); +} + +static char *kungetbuf; +static int kungetct, kungetsz; + +/**/ +void +ungetkey(int ch) +{ + if (kungetct == kungetsz) + kungetbuf = realloc(kungetbuf, kungetsz *= 2); + kungetbuf[kungetct++] = ch; +} + +/**/ +void +ungetkeys(char *s, int len) +{ + s += len; + while (len--) + ungetkey(*--s); +} + +#if defined(pyr) && defined(HAVE_SELECT) +static int +breakread(int fd, char *buf, int n) +{ + fd_set f; + + FD_ZERO(&f); + FD_SET(fd, &f); + return (select(fd + 1, (SELECT_ARG_2_T) & f, NULL, NULL, NULL) == -1 ? + EOF : read(fd, buf, n)); +} + +# define read breakread +#endif + +/**/ +int +getkey(int keytmout) +{ + char cc; + unsigned int ret; + long exp100ths; + int die = 0, r, icnt = 0; + int old_errno = errno; + +#ifdef HAVE_SELECT + fd_set foofd; + +#else +# ifdef HAS_TIO + struct ttyinfo ti; + +# endif +#endif + + if (kungetct) + ret = STOUC(kungetbuf[--kungetct]); + else { + if (keytmout) { + if (keytimeout > 500) + exp100ths = 500; + else if (keytimeout > 0) + exp100ths = keytimeout; + else + exp100ths = 0; +#ifdef HAVE_SELECT + if (exp100ths) { + struct timeval expire_tv; + + expire_tv.tv_sec = exp100ths / 100; + expire_tv.tv_usec = (exp100ths % 100) * 10000L; + FD_ZERO(&foofd); + FD_SET(SHTTY, &foofd); + if (select(SHTTY+1, (SELECT_ARG_2_T) & foofd, + NULL, NULL, &expire_tv) <= 0) + return EOF; + } +#else +# ifdef HAS_TIO + ti = shttyinfo; + ti.tio.c_lflag &= ~ICANON; + ti.tio.c_cc[VMIN] = 0; + ti.tio.c_cc[VTIME] = exp100ths / 10; +# ifdef HAVE_TERMIOS_H + tcsetattr(SHTTY, TCSANOW, &ti.tio); +# else + ioctl(SHTTY, TCSETA, &ti.tio); +# endif + r = read(SHTTY, &cc, 1); +# ifdef HAVE_TERMIOS_H + tcsetattr(SHTTY, TCSANOW, &shttyinfo.tio); +# else + ioctl(SHTTY, TCSETA, &shttyinfo.tio); +# endif + return (r <= 0) ? EOF : cc; +# endif +#endif + } + while ((r = read(SHTTY, &cc, 1)) != 1) { + if (r == 0) { + /* The test for IGNOREEOF was added to make zsh ignore ^Ds + that were typed while commands are running. Unfortuantely + this caused trouble under at least one system (SunOS 4.1). + Here shells that lost their xterm (e.g. if it was killed + with -9) didn't fail to read from the terminal but instead + happily continued to read EOFs, so that the above read + returned with 0, and, with IGNOREEOF set, this caused + an infinite loop. The simple way around this was to add + the counter (icnt) so that this happens 20 times and than + the shell gives up (yes, this is a bit dirty...). */ + if (isset(IGNOREEOF) && icnt++ < 20) + continue; + stopmsg = 1; + zexit(1, 0); + } + icnt = 0; + if (errno == EINTR) { + die = 0; + if (!errflag && !retflag && !breaks) + continue; + errflag = 0; + errno = old_errno; + return EOF; + } else if (errno == EWOULDBLOCK) { + fcntl(0, F_SETFL, 0); + } else if (errno == EIO && !die) { + ret = opts[MONITOR]; + opts[MONITOR] = 1; + attachtty(mypgrp); + refresh(); /* kludge! */ + opts[MONITOR] = ret; + die = 1; + } else if (errno != 0) { + zerr("error on TTY read: %e", NULL, errno); + stopmsg = 1; + zexit(1, 0); + } + } + if (cc == '\r') /* undo the exchange of \n and \r determined by */ + cc = '\n'; /* setterm() */ + else if (cc == '\n') + cc = '\r'; + + ret = STOUC(cc); + } + if (vichgflag) { + if (vichgbufptr == vichgbufsz) + vichgbuf = realloc(vichgbuf, vichgbufsz *= 2); + vichgbuf[vichgbufptr++] = ret; + } + errno = old_errno; + return ret; +} + +/* Read a line. It is returned metafied. */ + +/**/ +unsigned char * +zleread(char *lp, char *rp, int ha) +{ + unsigned char *s; + int old_errno = errno; + int tmout = getiparam("TMOUT"); + +#ifdef HAVE_SELECT + long costmult; + struct timeval tv; + fd_set foofd; + + baud = getiparam("BAUD"); + costmult = (baud) ? 3840000L / baud : 0; + tv.tv_sec = 0; +#endif + + /* ZLE doesn't currently work recursively. This is needed in case a * + * select loop is used in a function called from ZLE. vared handles * + * this differently itself. */ + if(zleactive) { + char *pptbuf; + int pptlen; + + pptbuf = unmetafy(promptexpand(lp, 0, NULL, NULL), &pptlen); + write(2, (WRITE_ARG_2_T)pptbuf, pptlen); + free(pptbuf); + return (unsigned char *)shingetline(); + } + + keytimeout = getiparam("KEYTIMEOUT"); + if (!shout) { + if (SHTTY != -1) + init_shout(); + + if (!shout) + return NULL; + /* We could be smarter and default to a system read. */ + + /* If we just got a new shout, make sure the terminal is set up. */ + if (termflags & TERM_UNKNOWN) + init_term(); + } + + fflush(shout); + fflush(stderr); + intr(); + insmode = unset(OVERSTRIKE); + eofsent = 0; + resetneeded = 0; + lpptbuf = promptexpand(lp, 1, NULL, NULL); + pmpt_attr = txtchange; + rpptbuf = promptexpand(rp, 1, NULL, NULL); + rpmpt_attr = txtchange; + histallowed = ha; + PERMALLOC { + histline = curhist; +#ifdef HAVE_SELECT + FD_ZERO(&foofd); +#endif + undoing = 1; + line = (unsigned char *)zalloc((linesz = 256) + 2); + virangeflag = lastcmd = done = cs = ll = mark = 0; + curhistline = NULL; + vichgflag = 0; + viinsbegin = 0; + statusline = NULL; + selectkeymap("main", 1); + fixsuffix(); + if ((s = (unsigned char *)getlinknode(bufstack))) { + setline((char *)s); + zsfree((char *)s); + if (stackcs != -1) { + cs = stackcs; + stackcs = -1; + if (cs > ll) + cs = ll; + } + if (stackhist != -1) { + histline = stackhist; + stackhist = -1; + } + } + initundo(); + if (isset(PROMPTCR)) + putc('\r', shout); + if (tmout) + alarm(tmout); + zleactive = 1; + resetneeded = 1; + errflag = retflag = 0; + lastcol = -1; + initmodifier(&zmod); + prefixflag = 0; + feepflag = 0; + refresh(); + while (!done && !errflag) { + + statusline = NULL; + vilinerange = 0; + reselectkeymap(); + bindk = getkeycmd(); + if (!ll && isfirstln && c == eofchar) { + eofsent = 1; + break; + } + if (bindk) { + execzlefunc(bindk); + handleprefixes(); + /* for vi mode, make sure the cursor isn't somewhere illegal */ + if (invicmdmode() && cs > findbol() && + (cs == ll || line[cs] == '\n')) + cs--; + if (undoing) + handleundo(); + } else { + errflag = 1; + break; + } +#ifdef HAVE_SELECT + if (baud && !(lastcmd & ZLE_MENUCMP)) { + FD_SET(SHTTY, &foofd); + if ((tv.tv_usec = cost * costmult) > 500000) + tv.tv_usec = 500000; + if (!kungetct && select(SHTTY+1, (SELECT_ARG_2_T) & foofd, + NULL, NULL, &tv) <= 0) + refresh(); + } else +#endif + if (!kungetct) + refresh(); + handlefeep(); + } + statusline = NULL; + invalidatelist(); + trashzle(); + free(lpptbuf); + free(rpptbuf); + zleactive = 0; + alarm(0); + } LASTALLOC; + zsfree(curhistline); + freeundo(); + if (eofsent) { + free(line); + line = NULL; + } else { + line[ll++] = '\n'; + line = (unsigned char *) metafy((char *) line, ll, META_REALLOC); + } + forget_edits(); + errno = old_errno; + return line; +} + +/* execute a widget */ + +/**/ +void +execzlefunc(Thingy func) +{ + Widget w; + + if(func->flags & DISABLED) { + /* this thingy is not the name of a widget */ + char *nm = niceztrdup(func->nam); + char *msg = tricat("No such widget `", nm, "'"); + + zsfree(nm); + showmsg(msg); + zsfree(msg); + feep(); + } else if((w = func->widget)->flags & WIDGET_INT) { + int wflags = w->flags; + + if(!(wflags & ZLE_KEEPSUFFIX)) + removesuffix(); + if(!(wflags & ZLE_MENUCMP)) { + fixsuffix(); + invalidatelist(); + } + if (wflags & ZLE_LINEMOVE) + vilinerange = 1; + if(!(wflags & ZLE_LASTCOL)) + lastcol = -1; + w->u.fn(); + lastcmd = wflags; + } else { + List l = getshfunc(w->u.fnnam); + + if(l == &dummy_list) { + /* the shell function doesn't exist */ + char *nm = niceztrdup(w->u.fnnam); + char *msg = tricat("No such shell function `", nm, "'"); + + zsfree(nm); + showmsg(msg); + zsfree(msg); + feep(); + } else { + startparamscope(); + makezleparams(); + doshfunc(l, NULL, 0, 1); + endparamscope(); + lastcmd = 0; + } + } +} + +/* initialise command modifiers */ + +/**/ +static void +initmodifier(struct modifier *mp) +{ + mp->flags = 0; + mp->mult = 1; + mp->tmult = 1; + mp->vibuf = 0; +} + +/* Reset command modifiers, unless the command just executed was a prefix. * + * Also set zmult, if the multiplier has been amended. */ + +/**/ +static void +handleprefixes(void) +{ + if (prefixflag) { + prefixflag = 0; + if(zmod.flags & MOD_TMULT) { + zmod.flags |= MOD_MULT; + zmod.mult = zmod.tmult; + } + } else + initmodifier(&zmod); +} + +/* vared: edit (literally) a parameter value */ + +/**/ +static int +bin_vared(char *name, char **args, char *ops, int func) +{ + char *s; + char *t; + Param pm; + int create = 0; + char *p1 = NULL, *p2 = NULL; + + /* all options are handled as arguments */ + while (*args && **args == '-') { + while (*++(*args)) + switch (**args) { + case 'c': + /* -c option -- allow creation of the parameter if it doesn't + yet exist */ + create = 1; + break; + case 'p': + /* -p option -- set main prompt string */ + if ((*args)[1]) + p1 = *args + 1, *args = "" - 1; + else if (args[1]) + p1 = *(++args), *args = "" - 1; + else { + zwarnnam(name, "prompt string expected after -%c", NULL, + **args); + return 1; + } + break; + case 'r': + /* -r option -- set right prompt string */ + if ((*args)[1]) + p2 = *args + 1, *args = "" - 1; + else if (args[1]) + p2 = *(++args), *args = "" - 1; + else { + zwarnnam(name, "prompt string expected after -%c", NULL, + **args); + return 1; + } + break; + case 'h': + /* -h option -- enable history */ + ops['h'] = 1; + break; + default: + /* unrecognised option character */ + zwarnnam(name, "unknown option: %s", *args, 0); + return 1; + } + args++; + } + + /* check we have a parameter name */ + if (!*args) { + zwarnnam(name, "missing variable", NULL, 0); + return 1; + } + /* handle non-existent parameter */ + if (!(s = getsparam(args[0]))) { + if (create) + createparam(args[0], PM_SCALAR); + else { + zwarnnam(name, "no such variable: %s", args[0], 0); + return 1; + } + } + + if(zleactive) { + zwarnnam(name, "ZLE cannot be used recursively (yet)", NULL, 0); + return 1; + } + + /* edit the parameter value */ + PERMALLOC { + pushnode(bufstack, ztrdup(s)); + } LASTALLOC; + t = (char *) zleread(p1, p2, ops['h']); + if (!t || errflag) { + /* error in editing */ + errflag = 0; + return 1; + } + /* strip off trailing newline, if any */ + if (t[strlen(t) - 1] == '\n') + t[strlen(t) - 1] = '\0'; + /* final assignment of parameter value */ + pm = (Param) paramtab->getnode(paramtab, args[0]); + if (pm && PM_TYPE(pm->flags) == PM_ARRAY) { + char **a; + + PERMALLOC { + a = spacesplit(t, 1); + } LASTALLOC; + setaparam(args[0], a); + } else + setsparam(args[0], t); + return 0; +} + +/**/ +void +describekeybriefly(void) +{ + char *seq, *str, *msg, *is; + Thingy func; + + if (statusline) + return; + statusline = "Describe key briefly: _"; + statusll = strlen(statusline); + refresh(); + seq = getkeymapcmd(curkeymap, &func, &str); + statusline = NULL; + if(!*seq) + return; + msg = bindztrdup(seq); + msg = appstr(msg, " is "); + if (!func) + is = bindztrdup(str); + else + is = niceztrdup(func->nam); + msg = appstr(msg, is); + zsfree(is); + showmsg(msg); + zsfree(msg); +} + +#define MAXFOUND 4 + +struct findfunc { + Thingy func; + int found; + char *msg; +}; + +/**/ +static void +scanfindfunc(char *seq, Thingy func, char *str, void *magic) +{ + struct findfunc *ff = magic; + + if(func != ff->func) + return; + if (!ff->found++) + ff->msg = appstr(ff->msg, " is on"); + if(ff->found <= MAXFOUND) { + char *b = bindztrdup(seq); + + ff->msg = appstr(ff->msg, " "); + ff->msg = appstr(ff->msg, b); + zsfree(b); + } +} + +/**/ +void +whereis(void) +{ + struct findfunc ff; + + if (!(ff.func = executenamedcommand("Where is: "))) + return; + ff.found = 0; + ff.msg = niceztrdup(ff.func->nam); + scankeymap(curkeymap, 1, scanfindfunc, &ff); + if (!ff.found) + ff.msg = appstr(ff.msg, " is not bound to any key"); + else if(ff.found > MAXFOUND) + ff.msg = appstr(ff.msg, " et al"); + showmsg(ff.msg); + zsfree(ff.msg); +} + +/**/ +void +trashzle(void) +{ + if (zleactive) { + /* This refresh() is just to get the main editor display right and * + * get the cursor in the right place. For that reason, we disable * + * list display (which would otherwise result in infinite * + * recursion [at least, it would if refresh() didn't have its * + * extra `inlist' check]). */ + int sl = showinglist; + showinglist = 0; + refresh(); + showinglist = sl; + moveto(nlnct, 0); + if (clearflag && tccan(TCCLEAREOD)) { + tcout(TCCLEAREOD); + clearflag = 0; + } + if (postedit) + fprintf(shout, "%s", postedit); + fflush(shout); + resetneeded = 1; + settyinfo(&shttyinfo); + } + if (errflag) + kungetct = 0; +} + +static struct builtin bintab[] = { + BUILTIN("bindkey", 0, bin_bindkey, 0, -1, 0, "evaMldDANmrsLR", NULL), + BUILTIN("vared", 0, bin_vared, 1, 7, 0, NULL, NULL), + BUILTIN("zle", 0, bin_zle, 0, -1, 0, "lDANL", NULL), +}; + +/**/ +int +boot_zle(Module m) +{ + /* Set up editor entry points */ + trashzleptr = trashzle; + gotwordptr = gotword; + refreshptr = refresh; + spaceinlineptr = spaceinline; + zlereadptr = zleread; + + /* initialise the thingies */ + init_thingies(); + + /* miscellaneous initialisations */ + stackhist = stackcs = -1; + kungetbuf = (char *) zalloc(kungetsz = 32); + + /* initialise the keymap system */ + init_keymaps(); + + addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + return 0; +} + +#ifdef MODULE + +/**/ +int +cleanup_zle(Module m) +{ + int i; + + if(zleactive) { + zerrnam(m->nam, "can't unload the zle module while zle is active", + NULL, 0); + return 1; + } + + deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); + cleanup_keymaps(); + deletehashtable(thingytab); + + zfree(vichgbuf, vichgbufsz); + zfree(kungetbuf, kungetsz); + free_isrch_spots(); + + zfree(cutbuf.buf, cutbuf.len); + for(i = KRINGCT; i--; ) + zfree(kring[i].buf, kring[i].len); + for(i = 35; i--; ) + zfree(vibuf[i].buf, vibuf[i].len); + + /* editor entry points */ + trashzleptr = noop_function; + gotwordptr = noop_function; + refreshptr = noop_function; + spaceinlineptr = noop_function_int; + zlereadptr = fallback_zleread; + + return 0; +} + +#endif /* MODULE */ diff --git a/Src/Zle/zle_misc.c b/Src/Zle/zle_misc.c new file mode 100644 index 000000000..42953852f --- /dev/null +++ b/Src/Zle/zle_misc.c @@ -0,0 +1,816 @@ +/* + * zle_misc.c - miscellaneous editor routines + * + * 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 "zle.mdh" +#include "zle_misc.pro" + +/* insert a metafied string, with repetition and suffix removal */ + +/**/ +void +doinsert(char *str) +{ + char *s; + int len = ztrlen(str); + int c1 = *str == Meta ? STOUC(str[1])^32 : STOUC(*str);/* first character */ + int neg = zmult < 0; /* insert *after* the cursor? */ + int m = neg ? -zmult : zmult; /* number of copies to insert */ + + iremovesuffix(c1); + invalidatelist(); + + if(insmode) + spaceinline(m * len); + else if(cs + m * len > ll) + spaceinline(cs + m * len - ll); + while(m--) + for(s = str; *s; s++) + line[cs++] = *s == Meta ? *++s ^ 32 : *s; + if(neg) + cs += zmult * len; +} + +/**/ +void +selfinsert(void) +{ + char s[3], *p = s; + + if(imeta(c)) { + *p++ = Meta; + c ^= 32; + } + *p++ = c; + *p = 0; + doinsert(s); +} + +/**/ +void +selfinsertunmeta(void) +{ + c &= 0x7f; + if (c == '\r') + c = '\n'; + selfinsert(); +} + +/**/ +void +deletechar(void) +{ + if (zmult < 0) { + zmult = -zmult; + backwarddeletechar(); + zmult = -zmult; + return; + } + if (cs + zmult <= ll) { + cs += zmult; + backdel(zmult); + } else + feep(); +} + +/**/ +void +backwarddeletechar(void) +{ + if (zmult < 0) { + zmult = -zmult; + deletechar(); + zmult = -zmult; + return; + } + backdel(zmult > cs ? cs : zmult); +} + +/**/ +void +killwholeline(void) +{ + int i, fg, n = zmult; + + if (n < 0) + return; + while (n--) { + if ((fg = (cs && cs == ll))) + cs--; + while (cs && line[cs - 1] != '\n') + cs--; + for (i = cs; i != ll && line[i] != '\n'; i++); + forekill(i - cs + (i != ll), fg); + } +} + +/**/ +void +killbuffer(void) +{ + cs = 0; + forekill(ll, 0); +} + +/**/ +void +backwardkillline(void) +{ + int i = 0, n = zmult; + + if (n < 0) { + zmult = -n; + killline(); + zmult = n; + return; + } + while (n--) { + if (cs && line[cs - 1] == '\n') + cs--, i++; + else + while (cs && line[cs - 1] != '\n') + cs--, i++; + } + forekill(i, 1); +} + +/**/ +void +gosmacstransposechars(void) +{ + int cc; + + if (cs < 2 || line[cs - 1] == '\n' || line[cs - 2] == '\n') { + if (cs == ll || line[cs] == '\n' || + ((cs + 1 == ll || line[cs + 1] == '\n') && + (!cs || line[cs - 1] == '\n'))) { + feep(); + return; + } + cs += (cs == 0 || line[cs - 1] == '\n') ? 2 : 1; + } + cc = line[cs - 2]; + line[cs - 2] = line[cs - 1]; + line[cs - 1] = cc; +} + +/**/ +void +transposechars(void) +{ + int cc, ct; + int n = zmult; + int neg = n < 0; + + if (neg) + n = -n; + while (n--) { + if (!(ct = cs) || line[cs - 1] == '\n') { + if (ll == cs || line[cs] == '\n') { + feep(); + return; + } + if (!neg) + cs++; + ct++; + } + if (neg) { + if (cs && line[cs - 1] != '\n') { + cs--; + if (ct > 1 && line[ct - 2] != '\n') + ct--; + } + } else { + if (cs != ll && line[cs] != '\n') + cs++; + } + if (ct == ll || line[ct] == '\n') + ct--; + if (ct < 1 || line[ct - 1] == '\n') { + feep(); + return; + } + cc = line[ct - 1]; + line[ct - 1] = line[ct]; + line[ct] = cc; + } +} + +/**/ +void +poundinsert(void) +{ + cs = 0; + vifirstnonblank(); + if (line[cs] != '#') { + spaceinline(1); + line[cs] = '#'; + cs = findeol(); + while(cs != ll) { + cs++; + vifirstnonblank(); + spaceinline(1); + line[cs] = '#'; + cs = findeol(); + } + } else { + foredel(1); + cs = findeol(); + while(cs != ll) { + cs++; + vifirstnonblank(); + if(line[cs] == '#') + foredel(1); + cs = findeol(); + } + } + done = 1; +} + +/**/ +void +acceptline(void) +{ + done = 1; +} + +/**/ +void +acceptandhold(void) +{ + pushnode(bufstack, metafy((char *)line, ll, META_DUP)); + stackcs = cs; + done = 1; +} + +/**/ +void +killline(void) +{ + int i = 0, n = zmult; + + if (n < 0) { + zmult = -n; + backwardkillline(); + zmult = n; + return; + } + while (n--) { + if (line[cs] == '\n') + cs++, i++; + else + while (cs != ll && line[cs] != '\n') + cs++, i++; + } + backkill(i, 0); +} + +/**/ +void +killregion(void) +{ + if (mark > ll) + mark = ll; + if (mark > cs) + forekill(mark - cs, 0); + else + backkill(cs - mark, 1); +} + +/**/ +void +copyregionaskill(void) +{ + if (mark > ll) + mark = ll; + if (mark > cs) + cut(cs, mark - cs, 0); + else + cut(mark, cs - mark, 1); +} + +static int kct, yankb, yanke; + +/**/ +void +yank(void) +{ + Cutbuffer buf = &cutbuf; + int n = zmult; + + if (n < 0) + return; + if (zmod.flags & MOD_VIBUF) + buf = &vibuf[zmod.vibuf]; + if (!buf->buf) { + feep(); + return; + } + mark = cs; + yankb = cs; + while (n--) { + kct = kringnum; + spaceinline(buf->len); + memcpy((char *)line + cs, buf->buf, buf->len); + cs += buf->len; + yanke = cs; + } +} + +/**/ +void +yankpop(void) +{ + int cc; + + if (!(lastcmd & ZLE_YANK) || !kring[kct].buf) { + feep(); + return; + } + cs = yankb; + foredel(yanke - yankb); + cc = kring[kct].len; + spaceinline(cc); + memcpy((char *)line + cs, kring[kct].buf, cc); + cs += cc; + yanke = cs; + kct = (kct + KRINGCT - 1) % KRINGCT; +} + +/**/ +void +overwritemode(void) +{ + insmode ^= 1; +} +/**/ +void +whatcursorposition(void) +{ + char msg[100]; + char *s = msg; + int bol = findbol(); + int c = STOUC(line[cs]); + + if (cs == ll) + strucpy(&s, "EOF"); + else { + strucpy(&s, "Char: "); + switch (c) { + case ' ': + strucpy(&s, "SPC"); + break; + case '\t': + strucpy(&s, "TAB"); + break; + case '\n': + strucpy(&s, "LFD"); + break; + default: + if (imeta(c)) { + *s++ = Meta; + *s++ = c ^ 32; + } else + *s++ = c; + } + sprintf(s, " (0%o, %d, 0x%x)", c, c, c); + s += strlen(s); + } + sprintf(s, " point %d of %d(%d%%) column %d", cs+1, ll+1, + ll ? 100 * cs / ll : 0, cs - bol); + showmsg(msg); +} + +/**/ +void +undefinedkey(void) +{ + feep(); +} + +/**/ +void +quotedinsert(void) +{ +#ifndef HAS_TIO + struct sgttyb sob; + + sob = shttyinfo.sgttyb; + sob.sg_flags = (sob.sg_flags | RAW) & ~ECHO; + ioctl(SHTTY, TIOCSETN, &sob); +#endif + c = getkey(0); +#ifndef HAS_TIO + setterm(); +#endif + if (c < 0) + feep(); + else + selfinsert(); +} + +/**/ +void +digitargument(void) +{ + int sign = (zmult < 0) ? -1 : 1; + + if (!(zmod.flags & MOD_TMULT)) + zmod.tmult = 0; + if (zmod.flags & MOD_NEG) { + /* If we just had a negative argument, this is the digit, * + * rather than the -1 assumed by negargument() */ + zmod.tmult = sign * (c & 0xf); + zmod.flags &= ~MOD_NEG; + } else + zmod.tmult = zmod.tmult * 10 + sign * (c & 0xf); + zmod.flags |= MOD_TMULT; + prefixflag = 1; +} + +/**/ +void +negargument(void) +{ + if(zmod.flags & MOD_TMULT) { + feep(); + return; + } + zmod.tmult = -1; + zmod.flags |= MOD_TMULT|MOD_NEG; + prefixflag = 1; +} + +/**/ +void +universalargument(void) +{ + int digcnt = 0, pref = 0, minus = 1, gotk; + while ((gotk = getkey(0)) != EOF) { + if (gotk == '-' && !digcnt) { + minus = -1; + digcnt++; + } else if (gotk >= '0' && gotk <= '9') { + pref = pref * 10 + (gotk & 0xf); + digcnt++; + } else { + ungetkey(gotk); + break; + } + } + if (digcnt) + zmod.tmult = minus * (pref ? pref : 1); + else + zmod.tmult *= 4; + zmod.flags |= MOD_TMULT; + prefixflag = 1; +} + +/**/ +void +copyprevword(void) +{ + int len, t0; + + for (t0 = cs - 1; t0 >= 0; t0--) + if (iword(line[t0])) + break; + for (; t0 >= 0; t0--) + if (!iword(line[t0])) + break; + if (t0) + t0++; + len = cs - t0; + spaceinline(len); + memcpy((char *)&line[cs], (char *)&line[t0], len); + cs += len; +} + +/**/ +void +sendbreak(void) +{ + errflag = 1; +} + +/**/ +void +quoteregion(void) +{ + char *str; + size_t len; + + if (mark > ll) + mark = ll; + if (mark < cs) { + int tmp = mark; + mark = cs; + cs = tmp; + } + str = (char *)hcalloc(len = mark - cs); + memcpy(str, (char *)&line[cs], len); + foredel(len); + str = makequote(str, &len); + spaceinline(len); + memcpy((char *)&line[cs], str, len); + mark = cs; + cs += len; +} + +/**/ +void +quoteline(void) +{ + char *str; + size_t len = ll; + + str = makequote((char *)line, &len); + sizeline(len); + memcpy(line, str, len); + cs = ll = len; +} + +/**/ +static char * +makequote(char *str, size_t *len) +{ + int qtct = 0; + char *l, *ol; + char *end = str + *len; + + for (l = str; l < end; l++) + if (*l == '\'') + qtct++; + *len += 2 + qtct*3; + l = ol = (char *)halloc(*len); + *l++ = '\''; + for (; str < end; str++) + if (*str == '\'') { + *l++ = '\''; + *l++ = '\\'; + *l++ = '\''; + *l++ = '\''; + } else + *l++ = *str; + *l++ = '\''; + return ol; +} + +static char *cmdbuf; +static LinkList cmdll; +static int cmdambig; + +/**/ +static void +scancompcmd(HashNode hn, int flags) +{ + int l; + Thingy t = (Thingy) hn; + + if(strpfx(cmdbuf, t->nam)) { + addlinknode(cmdll, t->nam); + l = pfxlen(peekfirst(cmdll), t->nam); + if (l < cmdambig) + cmdambig = l; + } + +} + +#define NAMLEN 60 + +/**/ +Thingy +executenamedcommand(char *prmt) +{ + Thingy cmd; + int len, l = strlen(prmt); + char *ptr; + char *okeymap = curkeymapname; + + cmdbuf = halloc(l + NAMLEN + 2); + strcpy(cmdbuf, prmt); + statusline = cmdbuf; + selectkeymap("main", 1); + ptr = cmdbuf += l; + len = 0; + for (;;) { + *ptr = '_'; + statusll = l + len + 1; + refresh(); + if (!(cmd = getkeycmd()) || cmd == Th(z_sendbreak)) { + statusline = NULL; + selectkeymap(okeymap, 1); + return NULL; + } + if(cmd == Th(z_clearscreen)) { + clearscreen(); + } else if(cmd == Th(z_redisplay)) { + redisplay(); + } else if(cmd == Th(z_viquotedinsert)) { + *ptr = '^'; + refresh(); + c = getkey(0); + if(c == EOF || !c || len == NAMLEN) + feep(); + else + *ptr++ = c, len++; + } else if(cmd == Th(z_quotedinsert)) { + if((c = getkey(0)) == EOF || !c || len == NAMLEN) + feep(); + else + *ptr++ = c, len++; + } else if(cmd == Th(z_backwarddeletechar) || + cmd == Th(z_vibackwarddeletechar)) { + if (len) + len--, ptr--; + } else if(cmd == Th(z_killregion) || cmd == Th(z_backwardkillword) || + cmd == Th(z_vibackwardkillword)) { + while (len && (len--, *--ptr != '-')); + } else if(cmd == Th(z_killwholeline) || cmd == Th(z_vikillline) || + cmd == Th(z_backwardkillline)) { + len = 0; + ptr = cmdbuf; + } else { + if(cmd == Th(z_acceptline) || cmd == Th(z_vicmdmode)) { + Thingy r; + unambiguous: + *ptr = 0; + r = rthingy(cmdbuf); + if (!(r->flags & DISABLED)) { + unrefthingy(r); + statusline = NULL; + selectkeymap(okeymap, 1); + return r; + } + unrefthingy(r); + } + if(cmd == Th(z_selfinsertunmeta)) { + c &= 0x7f; + if(c == '\r') + c = '\n'; + cmd = Th(z_selfinsert); + } + if (cmd == Th(z_listchoices) || cmd == Th(z_deletecharorlist) || + cmd == Th(z_expandorcomplete) || cmd == Th(z_completeword) || + cmd == Th(z_expandorcompleteprefix) || cmd == Th(z_vicmdmode) || + cmd == Th(z_acceptline) || c == ' ' || c == '\t') { + cmdambig = 100; + + HEAPALLOC { + cmdll = newlinklist(); + *ptr = 0; + + scanhashtable(thingytab, 1, 0, DISABLED, scancompcmd, 0); + } LASTALLOC; + if (empty(cmdll)) + feep(); + else if (cmd == Th(z_listchoices) || + cmd == Th(z_deletecharorlist)) { + int zmultsav = zmult; + *ptr = '_'; + statusll = l + len + 1; + zmult = 1; + listlist(cmdll); + zmult = zmultsav; + } else if (!nextnode(firstnode(cmdll))) { + strcpy(ptr = cmdbuf, peekfirst(cmdll)); + ptr += (len = strlen(ptr)); + if(cmd == Th(z_acceptline) || cmd == Th(z_vicmdmode)) + goto unambiguous; + } else { + strcpy(cmdbuf, peekfirst(cmdll)); + ptr = cmdbuf + cmdambig; + *ptr = '_'; + if (isset(AUTOLIST) && + !(isset(LISTAMBIGUOUS) && cmdambig > len)) { + int zmultsav = zmult; + if (isset(LISTBEEP)) + feep(); + statusll = l + cmdambig + 1; + zmult = 1; + listlist(cmdll); + zmult = zmultsav; + } + len = cmdambig; + } + } else { + if (len == NAMLEN || icntrl(c) || cmd != Th(z_selfinsert)) + feep(); + else + *ptr++ = c, len++; + } + } + handlefeep(); + } +} + +/*****************/ +/* Suffix system */ +/*****************/ + +/* + * The completion system sometimes tentatively adds a suffix to a word, + * which can be removed depending on what is inserted next. These + * functions provide the capability to handle a removable suffix. + * + * Any removable suffix consists of characters immediately before the + * cursor. Whether it is removed depends on the next editing action. + * There can be more than one suffix simultaneously present, with + * different actions deleting different numbers of characters. + * + * If the next editing action changes the buffer other than by inserting + * characters, normally the suffix should be removed so as to leave a + * meaningful complete word. The behaviour should be the same if the + * next character inserted is a word separator. If the next character + * reasonably belongs where it is typed, or if the next editing action + * is a deletion, the suffix should not be removed. Other reasons for + * suffix removal may have other behaviour. + * + * In order to maintain a consistent state, after a suffix has been added + * the table *must* be zeroed, one way or another, before the buffer is + * changed. If the suffix is not being removed, call fixsuffix() to + * indicate that it is being permanently fixed. + */ + +/* Length of suffix to remove when inserting each possible character value. * + * suffixlen[256] is the length to remove for non-insertion editing actions. */ + +/**/ +int suffixlen[257]; + +/* Set up suffix: the last n characters are a suffix that should be * + * removed in the usual word end conditions. */ + +/**/ +void +makesuffix(int n) +{ + suffixlen[256] = suffixlen[' '] = suffixlen['\t'] = suffixlen['\n'] = n; +} + +/* Set up suffix for parameter names: the last n characters are a suffix * + * that should be removed if the next character is one of the ones that * + * needs to go immediately after the parameter name. br indicates that * + * the name is in braces (${PATH} instead of $PATH), so the extra * + * characters that can only be used in braces are included. */ + +/**/ +void +makeparamsuffix(int br, int n) +{ + if(br || unset(KSHARRAYS)) + suffixlen[':'] = suffixlen['['] = n; + if(br) { + suffixlen['#'] = suffixlen['%'] = suffixlen['?'] = n; + suffixlen['-'] = suffixlen['+'] = suffixlen['='] = n; + /*{*/ suffixlen['}'] = n; + } +} + +/* Remove suffix, if there is one, when inserting character c. */ + +/**/ +void +iremovesuffix(int c) +{ + int sl = suffixlen[c]; + if(sl) { + backdel(sl); + invalidatelist(); + } + fixsuffix(); +} + +/* Fix the suffix in place, if there is one, making it non-removable. */ + +/**/ +void +fixsuffix(void) +{ + memset(suffixlen, 0, sizeof(suffixlen)); +} diff --git a/Src/Zle/zle_move.c b/Src/Zle/zle_move.c new file mode 100644 index 000000000..8ed4c657a --- /dev/null +++ b/Src/Zle/zle_move.c @@ -0,0 +1,502 @@ +/* + * zle_move.c - editor movement + * + * 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 "zle.mdh" +#include "zle_move.pro" + +static vimarkcs[27], vimarkline[27]; + +/**/ +void +beginningofline(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + endofline(); + zmult = n; + return; + } + while (n--) { + if (cs == 0) + return; + if (line[cs - 1] == '\n') + if (!--cs) + return; + while (cs && line[cs - 1] != '\n') + cs--; + } +} + +/**/ +void +endofline(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + beginningofline(); + zmult = n; + return; + } + while (n--) { + if (cs >= ll) { + cs = ll; + return; + } + if (line[cs] == '\n') + if (++cs == ll) + return; + while (cs != ll && line[cs] != '\n') + cs++; + } +} + +/**/ +void +beginningoflinehist(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + endoflinehist(); + zmult = n; + return; + } + while (n) { + if (cs == 0) + break; + if (line[cs - 1] == '\n') + if (!--cs) + break; + while (cs && line[cs - 1] != '\n') + cs--; + n--; + } + if (n) { + int m = zmult; + + zmult = n; + uphistory(); + zmult = m; + cs = 0; + } +} + +/**/ +void +endoflinehist(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + beginningoflinehist(); + zmult = n; + return; + } + while (n) { + if (cs >= ll) { + cs = ll; + break; + } + if (line[cs] == '\n') + if (++cs == ll) + break; + while (cs != ll && line[cs] != '\n') + cs++; + n--; + } + if (n) { + int m = zmult; + + zmult = n; + downhistory(); + zmult = m; + } +} + +/**/ +void +forwardchar(void) +{ + cs += zmult; + if (cs > ll) + cs = ll; + if (cs < 0) + cs = 0; +} + +/**/ +void +backwardchar(void) +{ + cs -= zmult; + if (cs > ll) + cs = ll; + if (cs < 0) + cs = 0; +} + +/**/ +void +setmarkcommand(void) +{ + mark = cs; +} + +/**/ +void +exchangepointandmark(void) +{ + int x; + + x = mark; + mark = cs; + cs = x; + if (cs > ll) + cs = ll; +} + +/**/ +void +vigotocolumn(void) +{ + int x, y; + + findline(&x, &y); + if (zmult >= 0) + cs = x + zmult - (zmult > 0); + else + cs = y + zmult; + if (cs > y) + cs = y; + if (cs < x) + cs = x; +} + +/**/ +void +vimatchbracket(void) +{ + int ocs = cs, dir, ct; + unsigned char oth, me; + + otog: + if (cs == ll || line[cs] == '\n') { + feep(); + cs = ocs; + return; + } + switch (me = line[cs]) { + case '{': + dir = 1; + oth = '}'; + break; + case /*{*/ '}': + virangeflag = -virangeflag; + dir = -1; + oth = '{'; /*}*/ + break; + case '(': + dir = 1; + oth = ')'; + break; + case ')': + virangeflag = -virangeflag; + dir = -1; + oth = '('; + break; + case '[': + dir = 1; + oth = ']'; + break; + case ']': + virangeflag = -virangeflag; + dir = -1; + oth = '['; + break; + default: + cs++; + goto otog; + } + ct = 1; + while (cs >= 0 && cs < ll && ct) { + cs += dir; + if (line[cs] == oth) + ct--; + else if (line[cs] == me) + ct++; + } + if (cs < 0 || cs >= ll) { + feep(); + cs = ocs; + } else if(dir > 0 && virangeflag) + cs++; +} + +/**/ +void +viforwardchar(void) +{ + int lim = findeol() - invicmdmode(); + int n = zmult; + + if (n < 0) { + zmult = -n; + vibackwardchar(); + zmult = n; + return; + } + if (cs >= lim) { + feep(); + return; + } + while (n-- && cs < lim) + cs++; +} + +/**/ +void +vibackwardchar(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + viforwardchar(); + zmult = n; + return; + } + if (cs == findbol()) { + feep(); + return; + } + while (n--) { + cs--; + if (cs < 0 || line[cs] == '\n') { + cs++; + break; + } + } +} + +/**/ +void +viendofline(void) +{ + int oldcs = cs, n = zmult; + + if (n < 1) { + feep(); + return; + } + while(n--) { + if (cs > ll) { + cs = oldcs; + feep(); + return; + } + cs = findeol() + 1; + } + cs--; + lastcol = 1<<30; +} + +/**/ +void +vibeginningofline(void) +{ + cs = findbol(); +} + +static int vfindchar, vfinddir, tailadd; + +/**/ +void +vifindnextchar(void) +{ + if ((vfindchar = vigetkey()) != -1) { + vfinddir = 1; + tailadd = 0; + virepeatfind(); + } +} + +/**/ +void +vifindprevchar(void) +{ + if ((vfindchar = vigetkey()) != -1) { + vfinddir = -1; + tailadd = 0; + virepeatfind(); + } +} + +/**/ +void +vifindnextcharskip(void) +{ + if ((vfindchar = vigetkey()) != -1) { + vfinddir = 1; + tailadd = -1; + virepeatfind(); + } +} + +/**/ +void +vifindprevcharskip(void) +{ + if ((vfindchar = vigetkey()) != -1) { + vfinddir = -1; + tailadd = 1; + virepeatfind(); + } +} + +/**/ +void +virepeatfind(void) +{ + int ocs = cs, n = zmult; + + if (!vfinddir) { + feep(); + return; + } + if (n < 0) { + zmult = -n; + virevrepeatfind(); + zmult = n; + return; + } + while (n--) { + do + cs += vfinddir; + while (cs >= 0 && cs < ll && line[cs] != vfindchar && line[cs] != '\n'); + if (cs < 0 || cs >= ll || line[cs] == '\n') { + feep(); + cs = ocs; + return; + } + } + cs += tailadd; + if (vfinddir == 1 && virangeflag) + cs++; +} + +/**/ +void +virevrepeatfind(void) +{ + if (zmult < 0) { + zmult = -zmult; + virepeatfind(); + zmult = -zmult; + return; + } + vfinddir = -vfinddir; + virepeatfind(); + vfinddir = -vfinddir; +} + +/**/ +void +vifirstnonblank(void) +{ + cs = findbol(); + while (cs != ll && iblank(line[cs])) + cs++; +} + +/**/ +void +visetmark(void) +{ + int ch; + + ch = getkey(0); + if (ch < 'a' || ch > 'z') { + feep(); + return; + } + ch -= 'a'; + vimarkcs[ch] = cs; + vimarkline[ch] = histline; +} + +/**/ +void +vigotomark(void) +{ + int ch; + + ch = getkey(0); + if (ch == c) + ch = 26; + else { + if (ch < 'a' || ch > 'z') { + feep(); + return; + } + ch -= 'a'; + } + if (!vimarkline[ch]) { + feep(); + return; + } + if (curhist != vimarkline[ch]) { + char *s; + + remember_edits(); + if (!(s = qgetevent(vimarkline[ch]))) { + vimarkline[ch] = 0; + feep(); + return; + } + histline = vimarkline[ch]; + setline(s); + } + cs = vimarkcs[ch]; + if (cs > ll) + cs = ll; +} + +/**/ +void +vigotomarkline(void) +{ + vigotomark(); + vifirstnonblank(); +} diff --git a/Src/Zle/zle_params.c b/Src/Zle/zle_params.c new file mode 100644 index 000000000..ed1420829 --- /dev/null +++ b/Src/Zle/zle_params.c @@ -0,0 +1,196 @@ +/* + * zle_params.c - ZLE special parameters + * + * 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 "zle.mdh" + +#include "zle_params.pro" + +/* + * ZLE SPECIAL PARAMETERS: + * + * These special parameters are created, with a local scope, when + * running user-defined widget functions. Reading and writing them + * reads and writes bits of ZLE state. The parameters are: + * + * BUFFER (scalar) entire buffer contents + * CURSOR (integer) cursor position; 0 <= $CURSOR <= $#BUFFER + * LBUFFER (scalar) portion of buffer to the left of the cursor + * RBUFFER (scalar) portion of buffer to the right of the cursor + */ + +#define FN(X) ( (void (*) _((void))) (X) ) +static struct zleparam { + char *name; + int type; + void (*setfn) _((void)); + void (*getfn) _((void)); + void (*unsetfn) _((Param, int)); + void *data; +} zleparams[] = { + { "BUFFER", PM_SCALAR, FN(set_buffer), FN(get_buffer), + zleunsetfn, NULL }, + { "CURSOR", PM_INTEGER, FN(set_cursor), FN(get_cursor), + zleunsetfn, NULL }, + { "LBUFFER", PM_SCALAR, FN(set_lbuffer), FN(get_lbuffer), + zleunsetfn, NULL }, + { "RBUFFER", PM_SCALAR, FN(set_rbuffer), FN(get_rbuffer), + zleunsetfn, NULL }, + { NULL, 0, NULL, NULL, NULL, NULL } +}; + +/**/ +void +makezleparams(void) +{ + struct zleparam *zp; + + for(zp = zleparams; zp->name; zp++) { + Param pm = createparam(zp->name, zp->type | PM_SPECIAL); + + pm->level = locallevel; + pm->u.data = zp->data; + switch(PM_TYPE(zp->type)) { + case PM_SCALAR: + pm->sets.cfn = (void (*) _((Param, char *))) zp->setfn; + pm->gets.cfn = (char *(*) _((Param))) zp->getfn; + break; + case PM_ARRAY: + pm->sets.afn = (void (*) _((Param, char **))) zp->setfn; + pm->gets.afn = (char **(*) _((Param))) zp->getfn; + break; + case PM_INTEGER: + pm->sets.ifn = (void (*) _((Param, long))) zp->setfn; + pm->gets.ifn = (long (*) _((Param))) zp->getfn; + break; + } + pm->unsetfn = zp->unsetfn; + } +} + +/* Special unset function for ZLE special parameters: act like the standard * + * unset function if this is a user-initiated unset, but nothing is done if * + * the parameter is merely going out of scope (which it will do). */ + +/**/ +static void +zleunsetfn(Param pm, int exp) +{ + if(exp) + stdunsetfn(pm, exp); +} + +/**/ +static void +set_buffer(Param pm, char *x) +{ + if(x) { + unmetafy(x, &ll); + sizeline(ll); + strcpy((char *)line, x); + zsfree(x); + if(cs > ll) + cs = ll; + } else + cs = ll = 0; +} + +/**/ +static char * +get_buffer(Param pm) +{ + return metafy((char *)line, ll, META_HEAPDUP); +} + +/**/ +static void +set_cursor(Param pm, long x) +{ + if(x < 0) + cs = 0; + else if(x > ll) + cs = ll; + else + cs = x; +} + +/**/ +static long +get_cursor(Param pm) +{ + return cs; +} + +/**/ +static void +set_lbuffer(Param pm, char *x) +{ + char *y; + int len; + + if(x) + unmetafy(y = x, &len); + else + y = "", len = 0; + sizeline(ll - cs + len); + memmove(line + len, line + cs, ll - cs); + memcpy(line, y, len); + ll = ll - cs + len; + cs = len; + zsfree(x); +} + +/**/ +static char * +get_lbuffer(Param pm) +{ + return metafy((char *)line, cs, META_HEAPDUP); +} + +/**/ +static void +set_rbuffer(Param pm, char *x) +{ + char *y; + int len; + + if(x) + unmetafy(y = x, &len); + else + y = "", len = 0; + sizeline(ll = cs + len); + memcpy(line + cs, y, len); + zsfree(x); +} + +/**/ +static char * +get_rbuffer(Param pm) +{ + return metafy((char *)line + cs, ll - cs, META_HEAPDUP); +} diff --git a/Src/Zle/zle_refresh.c b/Src/Zle/zle_refresh.c new file mode 100644 index 000000000..4621b5124 --- /dev/null +++ b/Src/Zle/zle_refresh.c @@ -0,0 +1,1116 @@ +/* + * zle_refresh.c - screen update + * + * 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 "zle.mdh" +#include "zle_refresh.pro" + +/* Expanded prompts */ + +/**/ +char *lpptbuf, *rpptbuf; + +/* Text attributes after displaying prompts */ + +/**/ +unsigned pmpt_attr, rpmpt_attr; + +/* number of lines displayed */ + +/**/ +int nlnct; + +/* Most lines of the buffer we've shown at once with the current list * + * showing. == 0 if there is no list. == -1 if a new list has just * + * been put on the screen. == -2 if refresh() needs to put up a new * + * list. */ + +/**/ +int showinglist; + +/* Non-zero if ALWAYS_LAST_PROMPT has been used, meaning that the * + * screen below the buffer display should not be cleared by * + * refresh(), but should be by trashzle(). */ + +/**/ +int clearflag; + +#ifdef HAVE_SELECT +/* cost of last update */ +/**/ +int cost; + +# define SELECT_ADD_COST(X) cost += X +# define zputc(a, b) putc(a, b), cost++ +# define zwrite(a, b, c, d) fwrite(a, b, c, d), cost += (b * c) +#else +# define SELECT_ADD_COST(X) +# define zputc(a, b) putc(a, b) +# define zwrite(a, b, c, d) fwrite(a, b, c, d) +#endif + +/* Oct/Nov 94: <mason> some code savagely redesigned to fix several bugs - + refreshline() & tc_rightcurs() majorly rewritten; refresh() fixed - + I've put my fingers into just about every routine in here - + any queries about updates to mason@werple.net.au */ + +static char **nbuf = NULL, /* new video buffer line-by-line char array */ + **obuf = NULL; /* old video buffer line-by-line char array */ +static int more_start, /* more text before start of screen? */ + more_end, /* more stuff after end of screen? */ + lppth, /* lines taken up by the prompt */ + olnct, /* previous number of lines */ + ovln, /* previous video cursor position line */ + pptw, rpw, /* prompt widths on screen */ + rppth, /* right prompt height */ + vcs, vln, /* video cursor position column & line */ + vmaxln, /* video maximum number of lines */ + winw, winh, rwinh, /* window width & height */ + winpos; /* singlelinezle: line's position in window */ + +/**/ +void +resetvideo(void) +{ + int ln; + static int lwinw = -1, lwinh = -1; /* last window width & height */ + + genprompts(); + winw = columns; /* terminal width */ + if (termflags & TERM_SHORT) + winh = 1; + else + winh = (lines < 2) ? 24 : lines; + rwinh = lines; /* keep the real number of lines */ + winpos = vln = vmaxln = 0; + if (lwinw != winw || lwinh != winh) { + if (nbuf) { + for (ln = 0; ln != lwinh; ln++) { + zfree(nbuf[ln], lwinw + 2); + zfree(obuf[ln], lwinw + 2); + } + free(nbuf); + free(obuf); + } + nbuf = (char **)zcalloc((winh + 1) * sizeof(char *)); + obuf = (char **)zcalloc((winh + 1) * sizeof(char *)); + nbuf[0] = (char *)zalloc(winw + 2); + obuf[0] = (char *)zalloc(winw + 2); + + lwinw = winw; + lwinh = winh; + } + for (ln = 0; ln != winh + 1; ln++) { + if (nbuf[ln]) + *nbuf[ln] = '\0'; + if (obuf[ln]) + *obuf[ln] = '\0'; + } + + if (pptw) { + memset(nbuf[0], ' ', pptw); + memset(obuf[0], ' ', pptw); + nbuf[0][pptw] = obuf[0][pptw] = '\0'; + } + + vcs = pptw; + olnct = nlnct = 0; + if (showinglist > 0) + showinglist = -2; +} + +/* + * Nov 96: <mason> changed to single line scroll + */ + +/**/ +static void +scrollwindow(int tline) +{ + int t0; + char *s; + + s = nbuf[tline]; + for (t0 = tline; t0 < winh - 1; t0++) + nbuf[t0] = nbuf[t0 + 1]; + nbuf[winh - 1] = s; + if (!tline) + more_start = 1; + return; +} + +/* this is the messy part. */ +/* this define belongs where it's used!!! */ + +#define nextline \ +{ \ + *s = '\0'; \ + if (ln != winh - 1) \ + ln++; \ + else { \ + if (!canscroll) { \ + if (nvln != -1 && nvln != winh - 1 \ + && (numscrolls != onumscrolls - 1 \ + || nvln <= winh / 2)) \ + break; \ + numscrolls++; \ + canscroll = winh / 2; \ + } \ + canscroll--; \ + scrollwindow(0); \ + if (nvln != -1) \ + nvln--; \ + } \ + if (!nbuf[ln]) \ + nbuf[ln] = (char *)zalloc(winw + 2); \ + s = (unsigned char *)nbuf[ln]; \ + sen = s + winw; \ +} + +#define snextline \ +{ \ + *s = '\0'; \ + if (ln != winh - 1) \ + ln++; \ + else \ + if (tosln < 3) { \ + more_status = 1; \ + scrollwindow(tosln + 1); \ + } else if (tosln - 1 <= nvln) { \ + scrollwindow(0); \ + if (nvln) \ + nvln--, tosln--; \ + } else { \ + tosln--; \ + scrollwindow(tosln); \ + } \ + if (!nbuf[ln]) \ + nbuf[ln] = (char *)zalloc(winw + 2); \ + s = (unsigned char *)nbuf[ln]; \ + sen = s + winw; \ +} + +static int cleareol, /* clear to end-of-line (if can't cleareod) */ + clearf, /* alwayslastprompt used immediately before */ + put_rpmpt, /* whether we should display right-prompt */ + oput_rpmpt, /* whether displayed right-prompt last time */ + oxtabs, /* oxtabs - tabs expand to spaces if set */ + numscrolls, onumscrolls; + +/**/ +void +refresh(void) +{ + static int inlist; /* avoiding recursion */ + int canscroll = 0, /* number of lines we are allowed to scroll */ + ln = 0, /* current line we're working on */ + more_status = 0, /* more stuff in status line */ + nvcs = 0, nvln = -1, /* video cursor column and line */ + t0 = -1, /* tmp */ + tosln = 0; /* tmp in statusline stuff */ + unsigned char *s, /* pointer into the video buffer */ + *t, /* pointer into the real buffer */ + *sen, /* pointer to end of the video buffer (eol) */ + *scs; /* pointer to cursor position in real buffer */ + char **qbuf; /* tmp */ + + /* If this is called from listmatches() (indirectly via trashzle()), and * + * that was called from the end of refresh(), then we don't need to do * + * anything. All this `inlist' code is actually unnecessary, but it * + * improves speed a little in a common case. */ + if (inlist) + return; + +#ifdef HAVE_SELECT + cost = 0; /* reset */ +#endif + +/* Nov 96: <mason> I haven't checked how complete this is. sgtty stuff may + or may not work */ + oxtabs = ((SGTTYFLAG & SGTABTYPE) == SGTABTYPE); + + cleareol = 0; /* unset */ + more_start = more_end = 0; /* unset */ + if (isset(SINGLELINEZLE) || lines < 3 + || (termflags & (TERM_NOUP | TERM_BAD | TERM_UNKNOWN))) + termflags |= TERM_SHORT; + else + termflags &= ~TERM_SHORT; + if (resetneeded) { + onumscrolls = 0; + setterm(); +#ifdef TIOCGWINSZ + if (winchanged) { + moveto(0, 0); + t0 = olnct; /* this is to clear extra lines even when */ + winchanged = 0; /* the terminal cannot TCCLEAREOD */ + } +#endif + resetvideo(); + resetneeded = 0; /* unset */ + oput_rpmpt = 0; /* no right-prompt currently on screen */ + + /* we probably should only have explicitly set attributes */ + tsetcap(TCALLATTRSOFF, 0); + tsetcap(TCSTANDOUTEND, 0); + tsetcap(TCUNDERLINEEND, 0); + + if (!clearflag) + if (tccan(TCCLEAREOD)) + tcout(TCCLEAREOD); + else + cleareol = 1; /* request: clear to end of line */ + if (t0 > -1) + olnct = t0; + if (termflags & TERM_SHORT) + vcs = 0; + else if (!clearflag && lpptbuf[0]) + zputs(lpptbuf, shout); + if (clearflag) { + zputc('\r', shout); + vcs = 0; + moveto(0, pptw); + } + fflush(shout); + clearf = clearflag; + } else if (winw != columns || rwinh != lines) + resetvideo(); + +/* now winw equals columns and winh equals lines + width comparisons can be made with winw, height comparisons with winh */ + + if (termflags & TERM_SHORT) { + singlerefresh(); + return; + } + + if (cs < 0) { +#ifdef DEBUG + fprintf(stderr, "BUG: negative cursor position\n"); + fflush(stderr); +#endif + cs = 0; + } + scs = line + cs; + numscrolls = 0; + +/* first, we generate the video line buffers so we know what to put on + the screen - also determine final cursor position (nvln, nvcs) */ + + /* Deemed necessary by PWS 1995/05/15 due to kill-line problems */ + if (!*nbuf) + *nbuf = (char *)zalloc(winw + 2); + + s = (unsigned char *)(nbuf[ln = 0] + pptw); + t = line; + sen = (unsigned char *)(*nbuf + winw); + for (; t < line+ll; t++) { + if (t == scs) /* if cursor is here, remember it */ + nvcs = s - (unsigned char *)(nbuf[nvln = ln]); + + if (*t == '\n') { /* newline */ + nbuf[ln][winw + 1] = '\0'; /* text not wrapped */ + nextline + } else if (*t == '\t') { /* tab */ + t0 = (char *)s - nbuf[ln]; + if ((t0 | 7) + 1 >= winw) { + nbuf[ln][winw + 1] = '\n'; /* text wrapped */ + nextline + } else + do + *s++ = ' '; + while ((++t0) & 7); + } else if (icntrl(*t)) { /* other control character */ + *s++ = '^'; + if (s == sen) { + nbuf[ln][winw + 1] = '\n'; /* text wrapped */ + nextline + } + *s++ = (*t == 127) ? '?' : (*t | '@'); + } else /* normal character */ + *s++ = *t; + if (s == sen) { + nbuf[ln][winw + 1] = '\n'; /* text wrapped */ + nextline + } + } + +/* if we're really on the next line, don't fake it; do everything properly */ + if (t == scs && (nvcs = s - (unsigned char *)(nbuf[nvln = ln])) == winw) { + nbuf[ln][winw + 1] = '\n'; /* text wrapped */ + switch ('\0') { /* a sad hack to make the break */ + case '\0': /* in nextline work */ + nextline + } + *s = '\0'; + nvcs = 0; + nvln++; + } + + if (t != line + ll) + more_end = 1; + + if (statusline) { + tosln = ln + 1; + if (ln == winh - 1) { + if (nvln > 0) { + scrollwindow(0); + nvln--; + } + tosln--; + } + nbuf[ln][winw + 1] = '\0'; /* text not wrapped */ + snextline + t = (unsigned char *)statusline; + for (; t < (unsigned char *)statusline + statusll; t++) { + if (icntrl(*t)) { /* simplified processing in the status line */ + *s++ = '^'; + if (s == sen) { + nbuf[ln][winw + 1] = '\n'; /* text wrapped */ + snextline + } + *s++ = (*t == 127) ? '?' : (*t | '@'); + } else + *s++ = *t; + if (s == sen) { + nbuf[ln][winw + 1] = '\n'; /* text wrapped */ + snextline + } + } + } + +/* insert <.... at end of last line if there is more text past end of screen */ + if (more_end) { + if (!statusline) + tosln = winh; + strncpy(nbuf[tosln - 1] + winw - 7, " <.... ", 7); + nbuf[tosln - 1][winw] = nbuf[tosln - 1][winw + 1] = '\0'; + } + +/* insert <....> at end of first status line if status is too big */ + if (more_status) { + strncpy(nbuf[tosln] + winw - 8, " <....> ", 8); + nbuf[tosln][winw] = nbuf[tosln][winw + 1] = '\0'; + } + + *s = '\0'; + nlnct = ln + 1; + for (ln = nlnct; ln < winh; ln++) + zfree(nbuf[ln], winw + 2), nbuf[ln] = NULL; + +/* determine whether the right-prompt exists and can fit on the screen */ + if (!more_start) + put_rpmpt = rppth == 1 && rpptbuf[0] && !strchr(rpptbuf, '\t') && + (int)strlen(nbuf[0]) + rpw < winw - 1; + else { +/* insert >.... on first line if there is more text before start of screen */ + memset(nbuf[0], ' ', pptw); + t0 = winw - pptw; + t0 = t0 > 5 ? 5 : t0; + strncpy(nbuf[0] + pptw, ">....", t0); + memset(nbuf[0] + pptw + t0, ' ', winw - t0 - pptw); + nbuf[0][winw] = nbuf[0][winw + 1] = '\0'; + } + + for (ln = 0; !clearf && (ln < nlnct); ln++) { + /* if we have more lines than last time, clear the newly-used lines */ + if (ln >= olnct) + cleareol = 1; + + /* if old line and new line are different, + see if we can insert/delete a line to speed up update */ + + if (ln < olnct - 1 && !(hasam && vcs == winw) && + nbuf[ln] && obuf[ln] && + strncmp(nbuf[ln], obuf[ln], 16)) { + if (tccan(TCDELLINE) && obuf[ln + 1] && obuf[ln + 1][0] && + nbuf[ln] && !strncmp(nbuf[ln], obuf[ln + 1], 16)) { + moveto(ln, 0); + tcout(TCDELLINE); + zfree(obuf[ln], winw + 2); + for (t0 = ln; t0 != olnct; t0++) + obuf[t0] = obuf[t0 + 1]; + obuf[--olnct] = NULL; + } + /* don't try to insert a line if olnct = vmaxln (vmaxln is the number + of lines that have been displayed by this routine) so that we don't + go off the end of the screen. */ + + else if (tccan(TCINSLINE) && olnct < vmaxln && nbuf[ln + 1] && + obuf[ln] && !strncmp(nbuf[ln + 1], obuf[ln], 16)) { + moveto(ln, 0); + tcout(TCINSLINE); + for (t0 = olnct; t0 != ln; t0--) + obuf[t0] = obuf[t0 - 1]; + obuf[ln] = NULL; + olnct++; + } + } + + /* update the single line */ + refreshline(ln); + + /* output the right-prompt if appropriate */ + if (put_rpmpt && !ln && !oput_rpmpt) { + moveto(0, winw - 1 - rpw); + zputs(rpptbuf, shout); + vcs = winw - 1; + /* reset character attributes to that set by the main prompt */ + txtchange = pmpt_attr; + if (txtchangeisset(TXTNOBOLDFACE) && (rpmpt_attr & TXTBOLDFACE)) + tsetcap(TCALLATTRSOFF, 0); + if (txtchangeisset(TXTNOSTANDOUT) && (rpmpt_attr & TXTSTANDOUT)) + tsetcap(TCSTANDOUTEND, 0); + if (txtchangeisset(TXTNOUNDERLINE) && (rpmpt_attr & TXTUNDERLINE)) + tsetcap(TCUNDERLINEEND, 0); + if (txtchangeisset(TXTBOLDFACE) && (rpmpt_attr & TXTNOBOLDFACE)) + tsetcap(TCBOLDFACEBEG, 0); + if (txtchangeisset(TXTSTANDOUT) && (rpmpt_attr & TXTNOSTANDOUT)) + tsetcap(TCSTANDOUTBEG, 0); + if (txtchangeisset(TXTUNDERLINE) && (rpmpt_attr & TXTNOUNDERLINE)) + tsetcap(TCUNDERLINEBEG, 0); + } + } + +/* if old buffer had extra lines, set them to be cleared and refresh them +individually */ + + if (olnct > nlnct) { + cleareol = 1; + for (ln = nlnct; ln < olnct; ln++) + refreshline(ln); + } + +/* reset character attributes */ + if (clearf && postedit) { + if ((txtchange = pmpt_attr ? pmpt_attr : rpmpt_attr)) { + if (txtchangeisset(TXTNOBOLDFACE)) + tsetcap(TCALLATTRSOFF, 0); + if (txtchangeisset(TXTNOSTANDOUT)) + tsetcap(TCSTANDOUTEND, 0); + if (txtchangeisset(TXTNOUNDERLINE)) + tsetcap(TCUNDERLINEEND, 0); + if (txtchangeisset(TXTBOLDFACE)) + tsetcap(TCBOLDFACEBEG, 0); + if (txtchangeisset(TXTSTANDOUT)) + tsetcap(TCSTANDOUTBEG, 0); + if (txtchangeisset(TXTUNDERLINE)) + tsetcap(TCUNDERLINEBEG, 0); + } + } + clearf = 0; + +/* move to the new cursor position */ + moveto(nvln, nvcs); + +/* swap old and new buffers - better than freeing/allocating every time */ + qbuf = nbuf; + nbuf = obuf; + obuf = qbuf; +/* store current values so we can use them next time */ + ovln = nvln; + olnct = nlnct; + oput_rpmpt = put_rpmpt; + onumscrolls = numscrolls; + if (nlnct > vmaxln) + vmaxln = nlnct; + fflush(shout); /* make sure everything is written out */ + + /* if we have a new list showing, note it; if part of the list has been + overwritten, redisplay it. */ + if (showinglist == -2 || (showinglist > 0 && showinglist < nlnct)) { + inlist = 1; + listmatches(); + inlist = 0; + refresh(); + } + if (showinglist == -1) + showinglist = nlnct; +} + +#define tcinscost(X) (tccan(TCMULTINS) ? tclen[TCMULTINS] : (X)*tclen[TCINS]) +#define tcdelcost(X) (tccan(TCMULTDEL) ? tclen[TCMULTDEL] : (X)*tclen[TCDEL]) +#define tc_delchars(X) (void) tcmultout(TCDEL, TCMULTDEL, (X)) +#define tc_inschars(X) (void) tcmultout(TCINS, TCMULTINS, (X)) +#define tc_upcurs(X) (void) tcmultout(TCUP, TCMULTUP, (X)) +#define tc_leftcurs(X) (void) tcmultout(TCLEFT, TCMULTLEFT, (X)) + +/* refresh one line, using whatever speed-up tricks are provided by the tty */ + +/**/ +static void +refreshline(int ln) +{ + char *nl, *ol, *p1; /* line buffer pointers */ + int ccs = 0, /* temporary count for cursor position */ + char_ins = 0, /* number of characters inserted/deleted */ + col_cleareol, /* clear to end-of-line from this column */ + i, j, /* tmp */ + ins_last, /* insert pushed last character off line */ + nllen, ollen, /* new and old line buffer lengths */ + rnllen; /* real new line buffer length */ + +/* 0: setup */ + nl = nbuf[ln]; + rnllen = nllen = nl ? strlen(nl) : 0; + ol = obuf[ln] ? obuf[ln] : ""; + ollen = strlen(ol); + +/* optimisation: can easily happen for clearing old lines. If the terminal has + the capability, then this is the easiest way to skip unnecessary stuff */ + if (cleareol && !nllen && !(hasam && ln < nlnct - 1) + && tccan(TCCLEAREOL)) { + moveto(ln, 0); + tcout(TCCLEAREOL); + return; + } + +/* 1: pad out the new buffer with spaces to contain _all_ of the characters + which need to be written. do this now to allow some pre-processing */ + + if (cleareol /* request to clear to end of line */ + || !nllen /* no line buffer given */ + || (ln == 0 && (put_rpmpt != oput_rpmpt))) { /* prompt changed */ + p1 = halloc(winw + 2); + if (nllen) + strncpy(p1, nl, nllen); + memset(p1 + nllen, ' ', winw - nllen); + p1[winw] = '\0'; + p1[winw + 1] = (nllen < winw) ? '\0' : nl[winw + 1]; + if (ln && nbuf[ln]) + memcpy(nl, p1, winw + 2); /* next time obuf will be up-to-date */ + else + nl = p1; /* don't keep the padding for prompt line */ + nllen = winw; + } else if (ollen > nllen) { /* make new line at least as long as old */ + p1 = halloc(ollen + 1); + strncpy(p1, nl, nllen); + memset(p1 + nllen, ' ', ollen - nllen); + p1[ollen] = '\0'; + nl = p1; + nllen = ollen; + } + +/* 2: see if we can clear to end-of-line, and if it's faster, work out where + to do it from - we can normally only do so if there's no right-prompt. + With automatic margins, we shouldn't do it if there is another line, in + case it messes up cut and paste. */ + + if (hasam && ln < nlnct - 1 && rnllen == winw) + col_cleareol = -2; /* clearing eol would be evil so don't */ + else { + col_cleareol = -1; + if (tccan(TCCLEAREOL) && (nllen == winw || put_rpmpt != oput_rpmpt)) { + for (i = nllen; i && nl[i - 1] == ' '; i--); + for (j = ollen; j && ol[j - 1] == ' '; j--); + if ((j > i + tclen[TCCLEAREOL]) /* new buf has enough spaces */ + || (nllen == winw && nl[winw - 1] == ' ')) + col_cleareol = i; + } + } + +/* 2b: first a new trick for automargin niceness - good for cut and paste */ + + if (hasam && vcs == winw) { + if (nbuf[vln] && nbuf[vln][vcs + 1] == '\n') { + vln++, vcs = 1; + if (nbuf[vln] && *nbuf[vln]) + zputc(*nbuf[vln], shout); + else + zputc(' ', shout); /* I don't think this should happen */ + if (ln == vln) { /* better safe than sorry */ + nl++; + if (*ol) + ol++; + ccs = 1; + } /* else hmmm... I wonder what happened */ + } else { + vln++, vcs = 0; + zputc('\n', shout); + } + } + ins_last = 0; + +/* 2c: if we're on the first line, start checking at the end of the prompt; + we shouldn't be doing anything within the prompt */ + + if (ln == 0 && pptw) { + i = pptw - ccs; + j = strlen(ol); + nl += i; + ol += (i > j ? j : i); /* if ol is too short, point it to '\0' */ + ccs = pptw; + } + +/* 3: main display loop - write out the buffer using whatever tricks we can */ + + for (;;) { + if (*nl && *ol && nl[1] == ol[1]) /* skip only if second chars match */ + /* skip past all matching characters */ + for (; *nl && (*nl == *ol); nl++, ol++, ccs++) ; + + if (!*nl) { + if (ccs == winw && hasam && char_ins > 0 && ins_last + && vcs != winw) { + nl--; /* we can assume we can go back here */ + moveto(ln, winw - 1); + zputc(*nl, shout); + vcs++; + return; /* write last character in line */ + } + if ((char_ins <= 0) || (ccs >= winw)) /* written everything */ + return; + if (tccan(TCCLEAREOL) && (char_ins >= tclen[TCCLEAREOL]) + && col_cleareol != -2) + /* we've got junk on the right yet to clear */ + col_cleareol = 0; /* force a clear to end of line */ + } + + moveto(ln, ccs); /* move to where we do all output from */ + + /* if we can finish quickly, do so */ + if ((col_cleareol >= 0) && (ccs >= col_cleareol)) { + tcout(TCCLEAREOL); + return; + } + + /* we've written out the new but yet to clear rubbish due to inserts */ + if (!*nl) { + i = (winw - ccs < char_ins) ? (winw - ccs) : char_ins; + if (tccan(TCDEL) && (tcdelcost(i) <= i + 1)) + tc_delchars(i); + else { + vcs += i; + while (i-- > 0) + zputc(' ', shout); + } + return; + } + + /* if we've reached the end of the old buffer, then there are few tricks + we can do, so we just dump out what we must and clear if we can */ + if (!*ol) { + i = (col_cleareol >= 0) ? col_cleareol : nllen; + i -= vcs; + zwrite(nl, i, 1, shout); + vcs += i; + if (col_cleareol >= 0) + tcout(TCCLEAREOL); + return; + } + + /* inserting & deleting chars: we can if there's no right-prompt */ + if ((ln || !put_rpmpt || !oput_rpmpt) + && (nl[1] && ol[1] && nl[1] != ol[1])) { + + /* deleting characters - see if we can find a match series that + makes it cheaper to delete intermediate characters + eg. oldline: hifoobar \ hopefully cheaper here to delete two + newline: foobar / characters, then we have six matches */ + if (tccan(TCDEL)) { + for (i = 1; *(ol + i); i++) + if (tcdelcost(i) < pfxlen(ol + i, nl)) { + tc_delchars(i); + ol += i; + char_ins -= i; + i = 0; + break; + } + if (!i) + continue; + } + /* inserting characters - characters pushed off the right should be + annihilated, but we don't do this if we're on the last line lest + undesired scrolling occurs due to `illegal' characters on screen */ + + if (tccan(TCINS) && (vln != lines - 1)) { /* not on last line */ + for (i = 1; *(nl + i); i++) + if (tcinscost(i) < pfxlen(nl + i, ol)) { + tc_inschars(i); + zwrite(nl, i, 1, shout); + nl += i; + char_ins += i; + ccs = (vcs += i); + /* if we've pushed off the right, truncate oldline */ + for (i = 0; *(ol + i) && i < winw - ccs; i++); + if (i == winw - ccs) { + *(ol + i) = '\0'; + ins_last = 1; + } + i = 0; + break; + } + if (!i) + continue; + } + } + /* we can't do any fancy tricks, so just dump the single character + and keep on trying */ + zputc(*nl, shout); + nl++, ol++; + ccs++, vcs++; + } +} + +/* move the cursor to line ln (relative to the prompt line), + absolute column cl; update vln, vcs - video line and column */ + +/**/ +void +moveto(int ln, int cl) +{ + int c; + + if (vcs == winw) { + vln++, vcs = 0; + if (!hasam) { + zputc('\r', shout); + zputc('\n', shout); + } else { + if ((vln < nlnct) && nbuf[vln] && *nbuf[vln]) + c = *nbuf[vln]; + else + c = ' '; + zputc(c, shout); + zputc('\r', shout); + if ((vln < olnct) && obuf[vln] && *obuf[vln]) + *obuf[vln] = c; + } + } + + if (ln == vln && cl == vcs) + return; + +/* move up */ + if (ln < vln) { + tc_upcurs(vln - ln); + vln = ln; + } +/* move down; if we might go off the end of the screen, use newlines + instead of TCDOWN */ + + while (ln > vln) { + if (vln < vmaxln - 1) + if (ln > vmaxln - 1) { + if (tc_downcurs(vmaxln - 1 - vln)) + vcs = 0; + vln = vmaxln - 1; + } else { + if (tc_downcurs(ln - vln)) + vcs = 0; + vln = ln; + continue; + } + zputc('\r', shout), vcs = 0; /* safety precaution */ + while (ln > vln) { + zputc('\n', shout); + vln++; + } + } + + if (cl == vcs) + return; + +/* choose cheapest movements for ttys without multiple movement capabilities - + do this now because it's easier (to code) */ + if (cl <= vcs / 2) { + zputc('\r', shout); + vcs = 0; + } + if (vcs < cl) + tc_rightcurs(cl); + else if (vcs > cl) + tc_leftcurs(vcs - cl); + vcs = cl; +} + +/**/ +int +tcmultout(int cap, int multcap, int ct) +{ + if (tccan(multcap) && (!tccan(cap) || tclen[multcap] <= tclen[cap] * ct)) { + tcoutarg(multcap, ct); + return 1; + } else if (tccan(cap)) { + while (ct--) + tcout(cap); + return 1; + } + return 0; +} + +/**/ +static void +tc_rightcurs(int cl) +{ + int ct, /* number of characters to move across */ + i = vcs, /* cursor position after initial movements */ + j; + char *t; + + ct = cl - vcs; + +/* do a multright if we can - it's the most reliable */ + if (tccan(TCMULTRIGHT)) { + tcoutarg(TCMULTRIGHT, ct); + return; + } + +/* try tabs if tabs are non destructive and multright is not possible */ + if (!oxtabs && tccan(TCNEXTTAB) && ((vcs | 7) < cl)) { + i = (vcs | 7) + 1; + tcout(TCNEXTTAB); + for ( ; i + 8 <= cl; i += 8) + tcout(TCNEXTTAB); + if ((ct = cl - i) == 0) /* number of chars still to move across */ + return; + } + +/* otherwise _carefully_ write the contents of the video buffer. + if we're anywhere in the prompt, goto the left column and write the whole + prompt out unless ztrlen(lpptbuf) == pptw : we can cheat then */ + if (vln == 0 && i < pptw) { + if (strlen(lpptbuf) == pptw) + fputs(lpptbuf + i, shout); + else if (tccan(TCRIGHT) && (tclen[TCRIGHT] * ct <= ztrlen(lpptbuf))) + /* it is cheaper to send TCRIGHT than reprint the whole prompt */ + for (ct = pptw - i; ct--; ) + tcout(TCRIGHT); + else { + if (i != 0) + zputc('\r', shout); + tc_upcurs(lppth - 1); + zputs(lpptbuf, shout); + } + i = pptw; + ct = cl - i; + } + + if (nbuf[vln]) { + for (j = 0, t = nbuf[vln]; *t && (j < i); j++, t++); + if (j == i) + for ( ; *t && ct; ct--, t++) + zputc(*t, shout); + } + while (ct--) + zputc(' ', shout); /* not my fault your terminal can't go right */ +} + +/**/ +static int +tc_downcurs(int ct) +{ + int ret = 0; + + if (ct && !tcmultout(TCDOWN, TCMULTDOWN, ct)) { + while (ct--) + zputc('\n', shout); + zputc('\r', shout), ret = -1; + } + return ret; +} + +/**/ +void +tcout(int cap) +{ + tputs(tcstr[cap], 1, putshout); + SELECT_ADD_COST(tclen[cap]); +} + +/**/ +static void +tcoutarg(int cap, int arg) +{ + char *result; + + result = tgoto(tcstr[cap], arg, arg); + tputs(result, 1, putshout); + SELECT_ADD_COST(strlen(result)); +} + +/**/ +void +clearscreen(void) +{ + tcout(TCCLEARSCREEN); + resetneeded = 1; + clearflag = 0; +} + +/**/ +void +redisplay(void) +{ + moveto(0, 0); + zputc('\r', shout); /* extra care */ + tc_upcurs(lppth - 1); + resetneeded = 1; + clearflag = 0; +} + +/**/ +static void +singlerefresh(void) +{ + char *vbuf, *vp, /* video buffer and pointer */ + **qbuf, /* tmp */ + *refreshop = *obuf; /* pointer to old video buffer */ + int t0, /* tmp */ + vsiz, /* size of new video buffer */ + nvcs = 0; /* new video cursor column */ + + nlnct = 1; +/* generate the new line buffer completely */ + for (vsiz = 1 + pptw, t0 = 0; t0 != ll; t0++, vsiz++) + if (line[t0] == '\t') + vsiz = (vsiz | 7) + 1; + else if (icntrl(line[t0])) + vsiz++; + vbuf = (char *)zalloc(vsiz); + + if (cs < 0) { +#ifdef DEBUG + fprintf(stderr, "BUG: negative cursor position\n"); + fflush(stderr); +#endif + cs = 0; + } + + memcpy(vbuf, strchr(lpptbuf, 0) - pptw, pptw); /* only use last part of prompt */ + vbuf[pptw] = '\0'; + vp = vbuf + pptw; + + for (t0 = 0; t0 != ll; t0++) { + if (line[t0] == '\t') + for (*vp++ = ' '; (vp - vbuf) & 7; ) + *vp++ = ' '; + else if (line[t0] == '\n') { + *vp++ = '\\'; + *vp++ = 'n'; + } else if (line[t0] == 0x7f) { + *vp++ = '^'; + *vp++ = '?'; + } else if (icntrl(line[t0])) { + *vp++ = '^'; + *vp++ = line[t0] | '@'; + } else + *vp++ = line[t0]; + if (t0 == cs) + nvcs = vp - vbuf - 1; + } + if (t0 == cs) + nvcs = vp - vbuf; + *vp = '\0'; + +/* determine which part of the new line buffer we want for the display */ + if ((winpos && nvcs < winpos + 1) || (nvcs > winpos + winw - 2)) { + if ((winpos = nvcs - ((winw - hasam) / 2)) < 0) + winpos = 0; + } + if (winpos) + vbuf[winpos] = '<'; /* line continues to the left */ + if ((int)strlen(vbuf + winpos) > (winw - hasam)) { + vbuf[winpos + winw - hasam - 1] = '>'; /* line continues to right */ + vbuf[winpos + winw - hasam] = '\0'; + } + strcpy(nbuf[0], vbuf + winpos); + zfree(vbuf, vsiz); + nvcs -= winpos; + +/* display the `visable' portion of the line buffer */ + for (t0 = 0, vp = *nbuf;;) { + /* skip past all matching characters */ + for (; *vp && *vp == *refreshop; t0++, vp++, refreshop++) ; + + if (!*vp && !*refreshop) + break; + + singmoveto(t0); /* move to where we do all output from */ + + if (!*refreshop) { + if ((t0 = strlen(vp))) + zwrite(vp, t0, 1, shout); + vcs += t0; + break; + } + if (!*vp) { + if (tccan(TCCLEAREOL)) + tcout(TCCLEAREOL); + else + for (; *refreshop++; vcs++) + zputc(' ', shout); + break; + } + zputc(*vp, shout); + vcs++, t0++; + vp++, refreshop++; + } +/* move to the new cursor position */ + singmoveto(nvcs); + + qbuf = nbuf; + nbuf = obuf; + obuf = qbuf; + fflush(shout); /* make sure everything is written out */ +} + +/**/ +static void +singmoveto(int pos) +{ + if (pos == vcs) + return; + if (pos <= vcs / 2) { + zputc('\r', shout); + vcs = 0; + } + if (pos < vcs) { + tc_leftcurs(vcs - pos); + vcs = pos; + } + if (pos > vcs) { + if (tcmultout(TCRIGHT, TCMULTRIGHT, pos - vcs)) + vcs = pos; + else + while (pos > vcs) { + zputc(nbuf[0][vcs], shout); + vcs++; + } + } +} + +/* recheck size of prompts */ + +/**/ +static void +genprompts(void) +{ + countprompt(lpptbuf, &pptw, &lppth); + countprompt(rpptbuf, &rpw, &rppth); +} diff --git a/Src/Zle/zle_things.sed b/Src/Zle/zle_things.sed new file mode 100644 index 000000000..781d23704 --- /dev/null +++ b/Src/Zle/zle_things.sed @@ -0,0 +1,9 @@ +/^ *T("/{ + s/^[^"]*"/ z_/ + s/".*$/,/ + s/-//g + s/\./D/g + P + s/ z_\(.*\),/#define t_\1 (\&thingies[z_\1])/ + P +} diff --git a/Src/Zle/zle_thingy.c b/Src/Zle/zle_thingy.c new file mode 100644 index 000000000..c4f2e25e1 --- /dev/null +++ b/Src/Zle/zle_thingy.c @@ -0,0 +1,491 @@ +/* + * zle_thingy.c - thingies + * + * 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 "zle.mdh" +#include "zle_thingy.pro" + +/* + * Thingies: + * + * From the user's point of view, a thingy is just a string. Internally, + * the thingy is a struct thingy; these structures are in a hash table + * indexed by the string the user sees. This hash table contains all + * thingies currently referenced anywhere; each has a reference count, + * and is deleted when it becomes unused. Being the name of a function + * counts as a reference. + * + * The DISABLED flag on a thingy indicates that it is not the name of a + * widget. This makes it easy to generate completion lists; + * looking only at the `enabled' nodes makes the thingy table look like + * a table of widgets. + */ + +/* Hashtable of thingies. Enabled nodes are those that refer to widgets. */ + +/**/ +HashTable thingytab; + +/**********************************/ +/* hashtable management functions */ +/**********************************/ + +/**/ +static void +createthingytab(void) +{ + thingytab = newhashtable(199, "thingytab", NULL); + + thingytab->hash = hasher; + thingytab->emptytable = emptythingytab; + thingytab->filltable = NULL; + thingytab->addnode = addhashnode; + thingytab->getnode = gethashnode; + thingytab->getnode2 = gethashnode2; + thingytab->removenode = removehashnode; + thingytab->disablenode = NULL; + thingytab->enablenode = NULL; + thingytab->freenode = freethingynode; + thingytab->printnode = NULL; +} + +/**/ +static void +emptythingytab(HashTable ht) +{ + /* This will only be called when deleting the thingy table, which * + * is only done to unload the zle module. A normal emptytable() * + * function would free all the thingies, but we don't want to do * + * that because some of them are the known thingies in the fixed * + * `thingies' table. As the module cleanup code deletes all the * + * keymaps and so on before deleting the thingy table, we can * + * just remove the user-defined widgets and then be sure that * + * *all* the thingies left are the fixed ones. This has the side * + * effect of freeing all resources used by user-defined widgets. */ + scanhashtable(thingytab, 0, 0, DISABLED, scanemptythingies, 0); +} + +/**/ +static void +scanemptythingies(HashNode hn, int flags) +{ + Thingy t = (Thingy) hn; + + /* Mustn't unbind internal widgets -- we wouldn't want to free the * + * memory they use. */ + if(!(t->widget->flags & WIDGET_INT)) + unbindwidget(t, 1); +} + +/**/ +static Thingy +makethingynode(void) +{ + Thingy t = (Thingy) zcalloc(sizeof(*t)); + + t->flags = DISABLED; + return t; +} + +/**/ +static void +freethingynode(HashNode hn) +{ + Thingy th = (Thingy) hn; + + zsfree(th->nam); + zfree(th, sizeof(*th)); +} + +/************************/ +/* referencing thingies */ +/************************/ + +/* It is important to maintain the reference counts on thingies. When * + * copying a reference to a thingy, wrap the copy in refthingy(), to * + * increase its reference count. When removing a reference, * + * unrefthingy() it. Both of these functions handle NULL arguments * + * correctly. */ + +/**/ +Thingy +refthingy(Thingy th) +{ + if(th) + th->rc++; + return th; +} + +/**/ +void +unrefthingy(Thingy th) +{ + if(th && !--th->rc) + thingytab->freenode(thingytab->removenode(thingytab, th->nam)); +} + +/* Use rthingy() to turn a string into a thingy. It increases the reference * + * count, after creating the thingy structure if necessary. */ + +/**/ +Thingy +rthingy(char *nam) +{ + Thingy t = (Thingy) thingytab->getnode2(thingytab, nam); + + if(!t) + thingytab->addnode(thingytab, ztrdup(nam), t = makethingynode()); + return refthingy(t); +} + +/***********/ +/* widgets */ +/***********/ + +/* + * Each widget is attached to one or more thingies. Each thingy + * names either zero or one widgets. Thingies that name a widget + * are treated as being referenced. The widget type, flags and pointer + * are stored in a separate structure pointed to by the thingies. Each + * thingy also has a pointer to the `next' thingy (in a circular list) + * that references the same widget. The DISABLED flag is unset in these + * thingies. + */ + +/* Bind a widget to a thingy. The thingy's reference count must already * + * have been incremented. The widget may already be bound to other * + * thingies; if it is not, then its `first' member must be NULL. Return * + * is 0 on success, or -1 if the thingy has the TH_IMMORTAL flag set. */ + +/**/ +static int +bindwidget(Widget w, Thingy t) +{ + if(t->flags & TH_IMMORTAL) { + unrefthingy(t); + return -1; + } + if(!(t->flags & DISABLED)) { + if(t->widget == w) + return 0; + unbindwidget(t, 1); + } + if(w->first) { + t->samew = w->first->samew; + w->first->samew = t; + } else { + w->first = t; + t->samew = t; + } + t->widget = w; + t->flags &= ~DISABLED; + return 0; +} + +/* Unbind a widget from a thingy. This decrements the thingy's reference * + * count. The widget will be destroyed if this is its last name. * + * TH_IMMORTAL thingies won't be touched, unless override is non-zero. * + * Returns 0 on success, or -1 if the thingy is protected. If the thingy * + * doesn't actually reference a widget, this is considered successful. */ + +/**/ +static int +unbindwidget(Thingy t, int override) +{ + Widget w; + + if(t->flags & DISABLED) + return 0; + if(!override && (t->flags & TH_IMMORTAL)) + return -1; + w = t->widget; + if(t->samew == t) + freewidget(w); + else { + Thingy p; + for(p = w->first; p->samew != t; p = p->samew) ; + w->first = p; /* optimised for deletezlefunction() */ + p->samew = t->samew; + } + t->flags &= ~TH_IMMORTAL; + t->flags |= DISABLED; + unrefthingy(t); + return 0; +} + +/* Free a widget. */ + +/**/ +static void +freewidget(Widget w) +{ + if(!(w->flags & WIDGET_INT)) + zsfree(w->u.fnnam); + zfree(w, sizeof(*w)); +} + +/* Add am internal widget provided by a module. The name given is the * + * canonical one, which must not begin with a dot. The widget is first * + * bound to the dotted canonical name; if that name is already taken by * + * an internal widget, failure is indicated. The same widget is then * + * bound to the canonical name, and a pointer to the widget structure * + * returned. */ + +/**/ +Widget +addzlefunction(char *name, ZleIntFunc ifunc, int flags) +{ + VARARR(char, dotn, strlen(name) + 2); + Widget w; + Thingy t; + + if(name[0] == '.') + return NULL; + dotn[0] = '.'; + strcpy(dotn + 1, name); + t = (Thingy) thingytab->getnode(thingytab, dotn); + if(t && (t->flags & TH_IMMORTAL)) + return NULL; + w = zalloc(sizeof(*w)); + w->flags = WIDGET_INT | flags; + w->first = NULL; + w->u.fn = ifunc; + t = rthingy(dotn); + bindwidget(w, t); + t->flags |= TH_IMMORTAL; + bindwidget(w, rthingy(name)); + return w; +} + +#ifdef DYNAMIC + +/* Delete an internal widget provided by a module. Don't try to delete * + * a widget from the fixed table -- it would be bad. (Thanks, Egon.) */ + +/**/ +void +deletezlefunction(Widget w) +{ + Thingy p, n; + + p = w->first; + while(1) { + n = p->samew; + if(n == p) { + unbindwidget(p, 1); + return; + } + unbindwidget(p, 1); + p = n; + } +} + +#endif /* DYNAMIC */ + +/***************/ +/* zle builtin */ +/***************/ + +/* + * The available operations are: + * + * -l list user-defined widgets (no arguments) + * -D delete widget names + * -A link the two named widgets (2 arguments) + * -N create new user-defined widget (1 or 2 arguments) + * invoke a widget (1 argument) + */ + +/**/ +int +bin_zle(char *name, char **args, char *ops, int func) +{ + static struct opn { + char o; + int (*func) _((char *, char **, char *, char)); + int min, max; + } const opns[] = { + { 'l', bin_zle_list, 0, 0 }, + { 'D', bin_zle_del, 1, -1 }, + { 'A', bin_zle_link, 2, 2 }, + { 'N', bin_zle_new, 1, 2 }, + { 0, bin_zle_call, 0, -1 }, + }; + struct opn const *op, *opp; + int n; + + /* select operation and ensure no clashing arguments */ + for(op = opns; op->o && !ops[op->o]; op++) ; + if(op->o) + for(opp = op; (++opp)->o; ) + if(ops[opp->o]) { + zerrnam(name, "incompatible operation selection options", + NULL, 0); + return 1; + } + + /* check number of arguments */ + for(n = 0; args[n]; n++) ; + if(!op->o && n != 1) { + zerrnam(name, "wrong number of arguments", NULL, 0); + return 1; + } + if(n < op->min) { + zerrnam(name, "not enough arguments for -%c", NULL, op->o); + return 1; + } else if(op->max != -1 && n > op->max) { + zerrnam(name, "too many arguments for -%c", NULL, op->o); + return 1; + } + + /* pass on the work to the operation function */ + return op->func(name, args, ops, op->o); +} + +/**/ +static int +bin_zle_list(char *name, char **args, char *ops, char func) +{ + scanhashtable(thingytab, 1, 0, DISABLED, scanlistwidgets, ops['L']); + return 0; +} + +/**/ +static void +scanlistwidgets(HashNode hn, int list) +{ + Thingy t = (Thingy) hn; + Widget w = t->widget; + + if(w->flags & WIDGET_INT) + return; + if(list) { + fputs("zle -N ", stdout); + if(t->nam[0] == '-') + fputs("-- ", stdout); + quotedzputs(t->nam, stdout); + if(strcmp(t->nam, w->u.fnnam)) { + fputc(' ', stdout); + quotedzputs(w->u.fnnam, stdout); + } + } else { + nicezputs(t->nam, stdout); + if(strcmp(t->nam, w->u.fnnam)) { + fputs(" (", stdout); + nicezputs(w->u.fnnam, stdout); + fputc(')', stdout); + } + } + putchar('\n'); +} + +/**/ +static int +bin_zle_del(char *name, char **args, char *ops, char func) +{ + int ret = 0; + + do { + Thingy t = (Thingy) thingytab->getnode(thingytab, *args); + if(!t) { + zwarnnam(name, "no such widget `%s'", *args, 0); + ret = 1; + } else if(unbindwidget(t, 0)) { + zwarnnam(name, "widget name `%s' is protected", *args, 0); + ret = 1; + } + } while(*++args); + return ret; +} + +/**/ +static int +bin_zle_link(char *name, char **args, char *ops, char func) +{ + Thingy t = (Thingy) thingytab->getnode(thingytab, args[0]); + + if(!t) { + zerrnam(name, "no such widget `%s'", args[0], 0); + return 1; + } else if(bindwidget(t->widget, rthingy(args[1]))) { + zerrnam(name, "widget name `%s' is protected", args[1], 0); + return 1; + } + return 0; + +} + +/**/ +static int +bin_zle_new(char *name, char **args, char *ops, char func) +{ + Widget w = zalloc(sizeof(*w)); + + w->flags = 0; + w->first = NULL; + w->u.fnnam = ztrdup(args[1] ? args[1] : args[0]); + if(!bindwidget(w, rthingy(args[0]))) + return 0; + freewidget(w); + zerrnam(name, "widget name `%s' is protected", args[0], 0); + return 1; +} + +/**/ +static int +bin_zle_call(char *name, char **args, char *ops, char func) +{ + Thingy t; + + if(!zleactive || incompctlfunc) { + zerrnam(name, "widgets can only be called when ZLE is active", + NULL, 0); + return 1; + } + t = rthingy(args[0]); + PERMALLOC { + execzlefunc(t); + } LASTALLOC; + unrefthingy(t); + return 0; +} + +/*******************/ +/* initialiasation */ +/*******************/ + +/**/ +void +init_thingies(void) +{ + Thingy t; + + createthingytab(); + for(t = thingies; t->nam; t++) + thingytab->addnode(thingytab, t->nam, t); +} diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c new file mode 100644 index 000000000..1aa1a008c --- /dev/null +++ b/Src/Zle/zle_tricky.c @@ -0,0 +1,4015 @@ +/* + * zle_tricky.c - expansion and completion + * + * 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 "zle.mdh" +#include "zle_tricky.pro" + +/* The main part of ZLE maintains the line being edited as binary data, * + * but here, where we interface with the lexer and other bits of zsh, * + * we need the line metafied. The technique used is quite simple: on * + * entry to the expansion/completion system, we metafy the line in * + * place, adjusting ll and cs to match. All completion and expansion * + * is done on the metafied line. Immediately before returning, the * + * line is unmetafied again, changing ll and cs back. (ll and cs might * + * have changed during completion, so they can't be merely saved and * + * restored.) The various indexes into the line that are used in this * + * file only are not translated: they remain indexes into the metafied * + * line. */ + +#ifdef HAVE_NIS_PLUS +# include <rpcsvc/nis.h> +#else +# ifdef HAVE_NIS +# include <rpc/types.h> +# include <rpc/rpc.h> +# include <rpcsvc/ypclnt.h> +# include <rpcsvc/yp_prot.h> + +/* This is used when getting usernames from the NIS. */ +typedef struct { + int len; + char *s; +} +dopestring; +# endif +#endif + +#define inststr(X) inststrlen((X),1,-1) + +/* wb and we hold the beginning/end position of the word we are completing. */ + +static int wb, we; + +/* offs is the cursor position within the tokenized * + * current word after removing nulargs. */ + +static int offs; + +/* These control the type of completion that will be done. They are * + * affected by the choice of ZLE command and by relevant shell options. */ + +static int usemenu, useglob; + +/* != 0 if we are in the middle of a menu completion */ + +static int menucmp; + +/* A pointer to the current position in the menu-completion array (the one * + * that was put in the command line last). */ + +static char **menucur; + +/* menupos is the point (in the command line) where the menu-completion * + * strings are inserted. menulen is the length of the string that was * + * inserted last. menuend is the end position of this string in the * + * command line. menuwe is non-zero if the cursor was at the end of the * + * word (meaning that suffixes should go before the cursor). menuinsc is * + * the length of any suffix that has been temporarily added. */ + +static int menupos, menulen, menuend, menuwe, menuinsc; + +/* This is used as a flag from get_comp_string() that we are doing * + * completion inside a brace expansion. */ + +static int complinbrace; + +/* The list of matches. fmatches contains the matches we first ignore * + * because of fignore. */ + +static LinkList matches, fmatches; + +/* The list of matches turned into an array. This is used to sort this * + * list and when menu-completion is used (directly or via automenu). */ + +static char **amatches; + +/* The number of matches. */ + +static int nmatches; + +/* A list of user-defined explanations for the completions to be shown * + * instead of amatches when listing completions. */ + +static char **aylist; + +/* !=0 if we have a valid completion list. */ + +static int validlist; + +/* This flag is non-zero if we are completing a pattern (with globcomplete) */ + +static int ispattern; + +/* Two patterns used when doing glob-completion. The first one is built * + * from the whole word we are completing and the second one from that * + * part of the word that was identified as a possible filename. */ + +static Comp patcomp, filecomp; + +/* We store the following prefixes/suffixes: * + * lpre/lsuf -- what's on the line * + * rpre/rsuf -- same as lpre/lsuf, but expanded * + * * + * ... and if we are completing files, too: * + * ppre/psuf -- the path prefix/suffix * + * fpre/fsuf -- prefix/suffix of the pathname component the cursor is in * + * prpre -- ppre in expanded form usable for opendir * + * * + * The integer variables hold the lengths of lpre, lsuf, rpre, rsuf, * + * fpre, and fsuf. noreal is non-zero if we have rpre/rsuf. */ + +static char *lpre, *lsuf; +static char *rpre, *rsuf; +static char *ppre, *psuf, *prpre; +static char *fpre, *fsuf; +static int lpl, lsl, rpl, rsl, fpl, fsl; +static int noreal; + +/* This is used when completing after `$' and holds the whole prefix, * + * used in do_single() to check whether the word expands to a directory * + * name (in that case and if autoparamslash is set, we add a `/'). * + * qparampre is the same but quoted. The length of it is in qparprelen. * + * parambr is != 0 if the parameter name is in braces. */ + +static char *parampre = NULL, *qparampre = NULL; +static int qparprelen, parambr; + +/* This is either zero or equal to the special character the word we are * + * trying to complete starts with (e.g. Tilde or Equals). */ + +static char ic; + +/* These hold the minimum common prefix/suffix lengths (normal and for * + * fignore ignored). */ + +static int ab, ae, fab, fae; + +/* This variable says what we are currently adding to the list of matches. */ + +static int addwhat; + +/* firstm hold the first match we found, shortest contains the shortest * + * one (normal and for fignore ignored). */ + +static char *firstm, *shortest, *ffirstm, *fshortest; + +/* This holds the word we are completing in quoted from. */ + +static char *qword; + +/* This is the length of the shortest match we found (normal and for * + * fignore ignored). */ + +static int shortl, fshortl; + +/* This is non-zero if we are doing a menu-completion and this is not the * + * first call (e.g. when automenu is set and menu-completion was entered * + * due to this). */ + +static int amenu; + +/* Find out if we have to insert a tab (instead of trying to complete). */ + +/**/ +static int +usetab(void) +{ + unsigned char *s = line + cs - 1; + + for (; s >= line && *s != '\n'; s--) + if (*s != '\t' && *s != ' ') + return 0; + return 1; +} + +#define COMP_COMPLETE 0 +#define COMP_LIST_COMPLETE 1 +#define COMP_SPELL 2 +#define COMP_EXPAND 3 +#define COMP_EXPAND_COMPLETE 4 +#define COMP_LIST_EXPAND 5 +#define COMP_ISEXPAND(X) ((X) >= COMP_EXPAND) + +/**/ +void +completeword(void) +{ + usemenu = isset(MENUCOMPLETE); + useglob = isset(GLOBCOMPLETE); + if (c == '\t' && usetab()) + selfinsert(); + else + docomplete(COMP_COMPLETE); +} + +/**/ +void +menucomplete(void) +{ + usemenu = 1; + useglob = isset(GLOBCOMPLETE); + if (c == '\t' && usetab()) + selfinsert(); + else + docomplete(COMP_COMPLETE); +} + +/**/ +void +listchoices(void) +{ + usemenu = isset(MENUCOMPLETE); + useglob = isset(GLOBCOMPLETE); + docomplete(COMP_LIST_COMPLETE); +} + +/**/ +void +spellword(void) +{ + usemenu = useglob = 0; + docomplete(COMP_SPELL); +} + +/**/ +void +deletecharorlist(void) +{ + char **mc = menucur; + + usemenu = isset(MENUCOMPLETE); + useglob = isset(GLOBCOMPLETE); + if (cs != ll) + deletechar(); + else + docomplete(COMP_LIST_COMPLETE); + + menucur = mc; +} + +/**/ +void +expandword(void) +{ + usemenu = useglob = 0; + if (c == '\t' && usetab()) + selfinsert(); + else + docomplete(COMP_EXPAND); +} + +/**/ +void +expandorcomplete(void) +{ + usemenu = isset(MENUCOMPLETE); + useglob = isset(GLOBCOMPLETE); + if (c == '\t' && usetab()) + selfinsert(); + else + docomplete(COMP_EXPAND_COMPLETE); +} + +/**/ +void +menuexpandorcomplete(void) +{ + usemenu = 1; + useglob = isset(GLOBCOMPLETE); + if (c == '\t' && usetab()) + selfinsert(); + else + docomplete(COMP_EXPAND_COMPLETE); +} + +/**/ +void +listexpand(void) +{ + usemenu = isset(MENUCOMPLETE); + useglob = isset(GLOBCOMPLETE); + docomplete(COMP_LIST_EXPAND); +} + +/**/ +void +reversemenucomplete(void) +{ + if (!menucmp) { + menucomplete(); + return; + } + HEAPALLOC { + if (menucur == amatches) + menucur = amatches + nmatches - 1; + else + menucur--; + metafy_line(); + do_single(*menucur); + unmetafy_line(); + } LASTALLOC; +} + +/* Accepts the current completion and starts a new arg, * + * with the next completions. This gives you a way to * + * accept several selections from the list of matches. */ + +/**/ +void +acceptandmenucomplete(void) +{ + if (!menucmp) { + feep(); + return; + } + cs = menuend + menuinsc; + inststrlen(" ", 1, 1); + if (qparampre) + inststrlen(qparampre, 1, qparprelen); + if (lpre && !ispattern) + inststrlen(lpre, 1, -1); + if (lsuf && !ispattern) + inststrlen(lsuf, 0, -1); + menupos = cs; + menuend = cs + (lsuf ? strlen(lsuf) : 0); + menulen = 0; + menuinsc = 0; + menuwe = 1; + menucomplete(); +} + +/* These are flags saying if we are completing in the command * + * position or in a redirection. */ + +static int lincmd, linredir; + +/* Non-zero if the last completion done was ambiguous (used to find * + * out if AUTOMENU should start). More precisely, it's nonzero after * + * successfully doing any completion, unless the completion was * + * unambiguous and did not cause the display of a completion list. * + * From the other point of view, it's nonzero iff AUTOMENU (if set) * + * should kick in on another completion. */ + +static int lastambig; + +/* This describes some important things collected during the last * + * completion. Its value is zero or the inclusive OR of some of * + * the HAS_* things below. */ + +static int haswhat; + +/* We have a suffix to add (given with compctl -S). */ + +#define HAS_SUFFIX 1 + +/* We have filenames in the completion list. */ + +#define HAS_FILES 2 + +/* We have other things than files in the completion list. If this is * + * not set but HAS_FILES is, we probably put the file type characters * + * in the completion list (if listtypes is set) and we attempt to add * + * a slash to completed directories. */ + +#define HAS_MISC 4 + +/* This is set if we have filenames in the completion list that were * + * generated by a globcompletion pattern. */ + +#define HAS_PATHPAT 8 + + +/* This holds the naem of the current command (used to find the right * + * compctl). */ + +static char *cmdstr; + + +/* Check if the given string is the name of a parameter and if this * + * parameter is one worth expanding. */ + +/**/ +static int +checkparams(char *p) +{ + int t0, n, l = strlen(p), e = 0; + struct hashnode *hn; + + for (t0 = paramtab->hsize - 1, n = 0; n < 2 && t0 >= 0; t0--) + for (hn = paramtab->nodes[t0]; n < 2 && hn; hn = hn->next) + if (pfxlen(p, hn->nam) == l) { + n++; + if (strlen(hn->nam) == l) + e = 1; + } + return (n == 1) ? (getsparam(p) != NULL) : + (!menucmp && e && isset(RECEXACT)); +} + +/* Check if the given string has wildcards. The difficulty is that we * + * have to treat things like job specifications (%...) and parameter * + * expressions correctly. */ + +/**/ +static int +cmphaswilds(char *str) +{ + if ((*str == Inbrack || *str == Outbrack) && !str[1]) + return 0; + + /* If a leading % is immediately followed by ?, then don't * + * treat that ? as a wildcard. This is so you don't have * + * to escape job references such as %?foo. */ + if (str[0] == '%' && str[1] ==Quest) + str += 2; + + for (; *str;) { + if (*str == String || *str == Qstring) { + /* A parameter expression. */ + + if (*++str == Inbrace) + skipparens(Inbrace, Outbrace, &str); + else if (*str == String || *str == Qstring) + str++; + else { + /* Skip all the things a parameter expression might start * + * with (before we come to the parameter name). */ + for (; *str; str++) + if (*str != '^' && *str != Hat && + *str != '=' && *str != Equals && + *str != '~' && *str != Tilde) + break; + if (*str == '#' || *str == Pound) + str++; + /* Star and Quest are parameter names here, not wildcards */ + if (*str == Star || *str == Quest) + str++; + } + } else { + /* Not a parameter expression so we check for wildcards */ + if (((*str == Pound || *str == Hat) && isset(EXTENDEDGLOB)) || + *str == Star || *str == Bar || *str == Quest || + !skipparens(Inbrack, Outbrack, &str) || + !skipparens(Inang, Outang, &str) || + (unset(IGNOREBRACES) && + !skipparens(Inbrace, Outbrace, &str)) || + (*str == Inpar && str[1] == ':' && + !skipparens(Inpar, Outpar, &str))) + return 1; + if (*str) + str++; + } + } + return 0; +} + +/* The main entry point for completion. */ + +/**/ +static void +docomplete(int lst) +{ + char *s, *ol; + int olst = lst, chl = 0, ne = noerrs, ocs; + + /* If we are doing a menu-completion... */ + + if (menucmp && lst != COMP_LIST_EXPAND) { + do_menucmp(lst); + return; + } + + /* Check if we have to start a menu-completion (via automenu). */ + + if ((amenu = (isset(AUTOMENU) && lastambig))) + usemenu = 1; + + /* Expand history references before starting completion. If anything * + * changed, do no more. */ + + if (doexpandhist()) + return; + + metafy_line(); + + ocs = cs; + if (!isfirstln && chline != NULL) { + /* If we are completing in a multi-line buffer (which was not * + * taken from the history), we have to prepend the stuff saved * + * in chline to the contents of line. */ + + ol = dupstring((char *)line); + /* Make sure that chline is zero-terminated. */ + *hptr = '\0'; + cs = 0; + inststr(chline); + chl = cs; + cs += ocs; + } else + ol = NULL; + inwhat = IN_NOTHING; + qword = NULL; + /* Get the word to complete. */ + noerrs = 1; + s = get_comp_string(); + DPUTS(wb < 0 || cs < wb || cs > we, + "BUG: 0 <= wb <= cs <= we is not true!"); + noerrs = ne; + /* For vi mode, reset the start-of-insertion pointer to the beginning * + * of the word being completed, if it is currently later. Vi itself * + * would never change the pointer in the middle of an insertion, but * + * then vi doesn't have completion. More to the point, this is only * + * an emulation. */ + if (viinsbegin > ztrsub((char *) line + wb, (char *) line)) + viinsbegin = ztrsub((char *) line + wb, (char *) line); + /* If we added chline to the line buffer, reset the original contents. */ + if (ol) { + cs -= chl; + wb -= chl; + we -= chl; + if (wb < 0) { + strcpy((char *) line, ol); + ll = strlen((char *) line); + cs = ocs; + unmetafy_line(); + feep(); + return; + } + ocs = cs; + cs = 0; + foredel(chl); + cs = ocs; + } + freeheap(); + /* Save the lexer state, in case the completion code uses the lexer * + * somewhere (e.g. when processing a compctl -s flag). */ + lexsave(); + if (inwhat == IN_ENV) + lincmd = 0; + if (s) { + if (lst == COMP_EXPAND_COMPLETE) { + /* Check if we have to do expansion or completion. */ + char *q = s; + + if (*q == Equals) { + /* The word starts with `=', see if we can expand it. */ + q = s + 1; + if (cmdnamtab->getnode(cmdnamtab, q) || hashcmd(q, pathchecked)) + if (isset(RECEXACT)) + lst = COMP_EXPAND; + else { + int t0, n = 0; + char *fc; + struct hashnode *hn; + + for (t0 = cmdnamtab->hsize - 1; t0 >= 0; t0--) + for (hn = cmdnamtab->nodes[t0]; hn; + hn = hn->next) { + if (strpfx(q, hn->nam) && (fc = findcmd(hn->nam))) { + zsfree(fc); + n++; + } + if (n == 2) + break; + } + + if (n == 1) + lst = COMP_EXPAND; + } + } + if (lst == COMP_EXPAND_COMPLETE) + do { + /* check if there is a parameter expresiion. */ + for (; *q && *q != String; q++); + if (*q == String && q[1] != Inpar && q[1] != Inbrack) { + if (*++q == Inbrace) { + if (! skipparens(Inbrace, Outbrace, &q) && + q == s + cs - wb) + lst = COMP_EXPAND; + } else { + char *t, sav, sav2; + + /* Skip the things parameter expressions might * + * start with (the things before the parameter * + * name). */ + for (; *q; q++) + if (*q != '^' && *q != Hat && + *q != '=' && *q != Equals && + *q != '~' && *q != Tilde) + break; + if ((*q == '#' || *q == Pound || *q == '+') && + q[1] != String) + q++; + + sav2 = *(t = q); + if (*q == Quest || *q == Star || *q == String || + *q == Qstring) + *q = ztokens[*q - Pound], ++q; + else if (*q == '?' || *q == '*' || *q == '$' || + *q == '-' || *q == '!' || *q == '@') + q++; + else if (idigit(*q)) + do q++; while (idigit(*q)); + else + while (iident(*q)) + q++; + sav = *q; + *q = '\0'; + if (cs - wb == q - s && + (idigit(sav2) || checkparams(t))) + lst = COMP_EXPAND; + *q = sav; + *t = sav2; + } + if (lst != COMP_EXPAND) + lst = COMP_COMPLETE; + } else + break; + } while (q < s + cs - wb); + if (lst == COMP_EXPAND_COMPLETE) { + /* If it is still not clear if we should use expansion or * + * completion and there is a `$' or a backtick in the word, * + * than do expansion. */ + for (q = s; *q; q++) + if (*q == Tick || *q == Qtick || + *q == String || *q == Qstring) + break; + lst = *q ? COMP_EXPAND : COMP_COMPLETE; + } + /* And do expansion if there are wildcards and globcomplete is * + * not used. */ + if (unset(GLOBCOMPLETE) && cmphaswilds(s)) + lst = COMP_EXPAND; + } + if (lincmd && (inwhat == IN_NOTHING)) + inwhat = IN_CMD; + + if (lst == COMP_SPELL) { + char *x, *q; + + for (q = s; *q; q++) + if (INULL(*q)) + *q = Nularg; + cs = wb; + foredel(we - wb); + HEAPALLOC { + untokenize(x = dupstring(s)); + if (*s == Tilde || *s == Equals || *s == String) + *x = *s; + spckword(&x, 0, lincmd, 0); + } LASTALLOC; + untokenize(x); + inststr(x); + } else if (COMP_ISEXPAND(lst)) { + /* Do expansion. */ + char *ol = (olst == COMP_EXPAND_COMPLETE) ? + dupstring((char *)line) : (char *)line; + int ocs = cs, ne = noerrs; + + noerrs = 1; + doexpansion(s, lst, olst, lincmd); + lastambig = 0; + noerrs = ne; + + /* If expandorcomplete was invoked and the expansion didn't * + * change the command line, do completion. */ + if (olst == COMP_EXPAND_COMPLETE && + !strcmp(ol, (char *)line)) { + char *p; + + cs = ocs; + errflag = 0; + + p = s; + if (*p == Tilde || *p == Equals) + p++; + for (; *p; p++) + if (itok(*p)) + if (*p != String && *p != Qstring) + *p = ztokens[*p - Pound]; + else if (p[1] == Inbrace) + p++, skipparens(Inbrace, Outbrace, &p); + docompletion(s, lst, lincmd, 1); + } + } else + /* Just do completion. */ + docompletion(s, lst, lincmd, 0); + zsfree(s); + } + /* Reset the lexer state, pop the heap. */ + lexrestore(); + popheap(); + zsfree(qword); + unmetafy_line(); +} + +/* Do completion, given that we are in the middle of a menu completion. We * + * don't need to generate a list of matches, because that's already been * + * done by previous commands. We will either list the completions, or * + * insert the next completion. */ + +/**/ +static void +do_menucmp(int lst) +{ + /* Just list the matches if the list was requested. */ + if (lst == COMP_LIST_COMPLETE) { + showinglist = -2; + return; + } + /* Otherwise go to the next match in the array... */ + HEAPALLOC { + if (!*++menucur) + menucur = amatches; + /* ... and insert it into the command line. */ + metafy_line(); + do_single(*menucur); + unmetafy_line(); + } LASTALLOC; +} + +/* 1 if we are completing in a string */ +static int instring; + +/* 1 if we are completing the prefix */ +static int comppref; + +/* This function inserts an `x' in the command line at the cursor position. * + * * + * Oh, you want to know why? Well, if completion is tried somewhere on an * + * empty part of the command line, the lexer code would normally not be * + * able to give us the `word' we want to complete, since there is no word. * + * But we need to call the lexer to find out where we are (and for which * + * command we are completing and such things). So we temporarily add a `x' * + * (any character without special meaning would do the job) at the cursor * + * position, than the lexer gives us the word `x' and its beginning and end * + * positions and we can remove the `x'. * + * * + * If we are just completing the prefix (comppref set), we also insert a * + * space after the x to end the word. We never need to remove the space: * + * anywhere we are able to retrieve a word for completion it will be * + * discarded as whitespace. It has the effect of making any suffix * + * referrable to as the next word on the command line when indexing * + * from a completion function. */ + +/**/ +static void +addx(char **ptmp) +{ + int addspace = 0; + + if (!line[cs] || line[cs] == '\n' || + (iblank(line[cs]) && (!cs || line[cs-1] != '\\')) || + line[cs] == ')' || line[cs] == '`' || + (instring && (line[cs] == '"' || line[cs] == '\'')) || + (addspace = (comppref && !iblank(line[cs])))) { + *ptmp = (char *)line; + line = (unsigned char *)halloc(strlen((char *)line) + 3 + addspace); + memcpy(line, *ptmp, cs); + line[cs] = 'x'; + if (addspace) + line[cs+1] = ' '; + strcpy((char *)line + cs + 1 + addspace, (*ptmp) + cs); + addedx = 1 + addspace; + } else { + addedx = 0; + *ptmp = NULL; + } +} + +/* Like dupstring, but add an extra space at the end of the string. */ + +/**/ +static char * +dupstrspace(const char *str) +{ + int len = strlen((char *)str); + char *t = (char *)ncalloc(len + 2); + strcpy(t, str); + strcpy(t+len, " "); + return t; +} + +/* These functions metafy and unmetafy the ZLE buffer, as described at the * + * top of this file. Note that ll and cs are translated. They *must* be * + * called in matching pairs, around all the expansion/completion code. * + * Currently, there are four pairs: in history expansion, in the main * + * completion function, and one in each of the middle-of-menu-completion * + * functions (there's one for each direction). */ + +/**/ +static void +metafy_line(void) +{ + int len = ll; + char *s; + + for (s = (char *) line; s < (char *) line + ll;) + if (imeta(*s++)) + len++; + sizeline(len); + (void) metafy((char *) line, ll, META_NOALLOC); + ll = len; + cs = metalen((char *) line, cs); +} + +/**/ +static void +unmetafy_line(void) +{ + cs = ztrsub((char *) line + cs, (char *) line); + (void) unmetafy((char *) line, &ll); +} + +/* Lasciate ogni speranza. * + * This function is a nightmare. It works, but I'm sure that nobody really * + * understands why. The problem is: to make it cleaner we would need * + * changes in the lexer code (and then in the parser, and then...). */ + +/**/ +static char * +get_comp_string(void) +{ + int t0, tt0, i, j, k, cp, rd, sl, ocs; + char *s = NULL, *linptr, *tmp, *p, *tt = NULL; + + complinbrace = 0; + /* This global flag is used to signal the lexer code if it should * + * expand aliases or not. */ + noaliases = isset(COMPLETEALIASES); + + /* Find out if we are somewhere in a `string', i.e. inside '...', * + * "...", `...`, or ((...)). */ + + for (i = j = k = 0, p = (char *)line; p < (char *)line + cs; p++) + if (*p == '`' && !(k & 1)) + i++; + else if (*p == '\"' && !(k & 1) && !(i & 1)) + j++; + else if (*p == '\'' && !(j & 1)) + k++; + else if (*p == '\\' && p[1] && !(k & 1)) + p++; + instring = (j & 1) ? 2 : (k & 1); + addx(&tmp); + if (instring) { + /* Yes, we are in a string. */ + if (!tmp) { + tmp = (char *)line; + line = (unsigned char *) dupstring((char *) line); + } + /* Now remove the quotes. * + * What?? Why that?? Well, we want to be able to complete * + * inside strings. The lexer code gives us no help here, * + * so we have to cheat. We remove the quotes, the lexer * + * will than treat the words in the strings normally and we * + * can complete them. * + * This is completely the wrong thing to do, but it's * + * occasionally useful, and we can't handle quotes properly * + * yet anyway. */ + for (p = (char *)line; *p; p++) + if (*p == '"' || *p == '\'') + *p = ' '; + } + linptr = (char *)line; + pushheap(); + HEAPALLOC { + start: + inwhat = IN_NOTHING; + /* Now set up the lexer and start it. */ + parbegin = parend = -1; + lincmd = incmdpos; + linredir = inredir; + zsfree(cmdstr); + cmdstr = NULL; + zleparse = 1; + clwpos = -1; + lexsave(); + inpush(dupstrspace((char *) linptr), 0, NULL); + strinbeg(); + stophist = 2; + i = tt0 = cp = rd = 0; + + /* This loop is possibly the wrong way to do this. It goes through * + * the previously massaged command line using the lexer. It stores * + * each token in each command (commands being regarded, roughly, as * + * being separated by tokens | & &! |& || &&). The loop stops when * + * the end of the command containing the cursor is reached. It's a * + * simple way to do things, but suffers from an inability to * + * distinguish actual command arguments from, for example, * + * filenames in redirections. (But note that code elsewhere checks * + * if we are completing *in* a redirection.) The only way to fix * + * this would be to pass the command line through the parser too, * + * and get the arguments that way. Maybe in 3.1... */ + do { + lincmd = incmdpos; + linredir = inredir; + /* Get the next token. */ + ctxtlex(); + if (tok == DINPAR) + tokstr = NULL; + + /* We reached the end. */ + if (tok == ENDINPUT) + break; + if (tok == BAR || tok == AMPER || + tok == BARAMP || tok == AMPERBANG || + ((tok == DBAR || tok == DAMPER) && !incond)) { + /* This is one of the things that separate commands. If we * + * already have the things we need (e.g. the token strings), * + * leave the loop. */ + if (tt) + break; + /* Otherwise reset the variables we are collecting data in. */ + i = tt0 = cp = rd = 0; + } + if (lincmd && tok == STRING) { + /* The lexer says, this token is in command position, so * + * store the token string (to find the right compctl). */ + zsfree(cmdstr); + cmdstr = ztrdup(tokstr); + i = 0; + } + if (!zleparse && !tt0) { + /* This is done when the lexer reached the word the cursor is on. */ + tt = tokstr ? dupstring(tokstr) : NULL; + /* If we added a `x', remove it. */ + if (addedx && tt) + chuck(tt + cs - wb); + tt0 = tok; + /* Store the number of this word. */ + clwpos = i; + cp = lincmd; + rd = linredir; + if (inwhat == IN_NOTHING && incond) + inwhat = IN_COND; + } + if (!tokstr) + continue; + /* We need to store the token strings of all words (for some of * + * the more complicated compctl -x things). They are stored in * + * the clwords array. Make this array big enough. */ + if (i + 1 == clwsize) { + int n; + clwords = (char **)realloc(clwords, + (clwsize *= 2) * sizeof(char *)); + for(n = clwsize; --n > i; ) + clwords[n] = NULL; + } + zsfree(clwords[i]); + /* And store the current token string. */ + clwords[i] = ztrdup(tokstr); + sl = strlen(tokstr); + /* Sometimes the lexer gives us token strings ending with * + * spaces we delete the spaces. */ + while (sl && clwords[i][sl - 1] == ' ' && + (sl < 2 || (clwords[i][sl - 2] != Bnull && + clwords[i][sl - 2] != Meta))) + clwords[i][--sl] = '\0'; + /* If this is the word the cursor is in and we added a `x', * + * remove it. */ + if (clwpos == i++ && addedx) + chuck(&clwords[i - 1][((cs - wb) >= sl) ? + (sl - 1) : (cs - wb)]); + } while (tok != LEXERR && tok != ENDINPUT && + (tok != SEPER || (zleparse && !tt0))); + /* Calculate the number of words stored in the clwords array. */ + clwnum = (tt || !i) ? i : i - 1; + zsfree(clwords[clwnum]); + clwords[clwnum] = NULL; + t0 = tt0; + lincmd = cp; + linredir = rd; + strinend(); + inpop(); + errflag = zleparse = 0; + if (parbegin != -1) { + /* We are in command or process substitution */ + if (parend >= 0 && !tmp) + line = (unsigned char *) dupstring(tmp = (char *)line); + linptr = (char *) line + ll + addedx - parbegin + 1; + if (parend >= 0) { + ll -= parend; + line[ll + addedx] = '\0'; + } + lexrestore(); + goto start; + } + + if (inwhat == IN_MATH) + s = NULL; + else if (!t0 || t0 == ENDINPUT) { + /* There was no word (empty line). */ + s = ztrdup(""); + we = wb = cs; + clwpos = clwnum; + t0 = STRING; + } else if (t0 == STRING) { + /* We found a simple string. */ + s = ztrdup(clwords[clwpos]); + } else if (t0 == ENVSTRING) { + /* The cursor was inside a parameter assignment. */ + for (s = tt; iident(*s); s++); + if (skipparens(Inbrack, Outbrack, &s) > 0 || s > tt + cs - wb) + s = NULL, inwhat = IN_MATH; + else if (*s == '=') { + s++; + wb += s - tt; + t0 = STRING; + s = ztrdup(s); + inwhat = IN_ENV; + } + lincmd = 1; + } + if (we > ll) + we = ll; + tt = (char *)line; + if (tmp) { + line = (unsigned char *)tmp; + ll = strlen((char *)line); + } + if (t0 != STRING && inwhat != IN_MATH) { + if (tmp) { + tmp = NULL; + linptr = (char *)line; + lexrestore(); + goto start; + } + feep(); + noaliases = 0; + lexrestore(); + LASTALLOC_RETURN NULL; + } + + noaliases = 0; + + /* Check if we are in an array subscript. We simply assume that * + * we are in a subscript if we are in brackets. Correct solution * + * is very difficult. This is quite close, but gets things like * + * foo[_ wrong (note no $). If we are in a subscript, treat it * + * as being in math. */ + if (inwhat != IN_MATH) { + int i = 0; + for (tt = s; ++tt < s + cs - wb;) + if (*tt == Inbrack) + i++; + else if (i && *tt == Outbrack) + i--; + if (i) + inwhat = IN_MATH; + } + if (inwhat == IN_MATH) { + /* In mathematical expression, we complete parameter names (even * + * if they don't have a `$' in front of them). So we have to * + * find that name. */ + for (we = cs; iident(line[we]); we++); + for (wb = cs; --wb >= 0 && iident(line[wb]);); + wb++; + zsfree(s); + s = zalloc(we - wb + 1); + strncpy(s, (char *) line + wb, we - wb); + s[we - wb] = '\0'; + } + /* This variable will hold the current word in quoted form. */ + qword = ztrdup(s); + /* While building the quoted form, we also clean up the command line. */ + offs = cs - wb; + for (p = s, tt = qword, i = wb; *p; p++, tt++, i++) + if (INULL(*p)) { + if (i < cs) + offs--; + if (p[1] || *p != Bnull) { + if (*p == Bnull) { + *tt = '\\'; + if (cs == i + 1) + cs++, offs++; + } else { + ocs = cs; + cs = i; + foredel(1); + chuck(tt--); + if ((cs = ocs) > i--) + cs--; + we--; + } + } else { + ocs = cs; + *tt = '\0'; + cs = we; + backdel(1); + if (ocs == we) + cs = we - 1; + else + cs = ocs; + we--; + } + chuck(p--); + } + + if (!isset(IGNOREBRACES)) { + /* Try and deal with foo{xxx etc.; only simple cases + * (only one inbrace, completion after inbrace and before outbrace + * if present). + */ + int myoffs = isset(COMPLETEINWORD) ? offs : strlen(s); + tt = NULL; + /* First check the conditions mentioned above + * and locate opening brace + */ + for (i = 0, p = s; *p; p++, i++) { + /* careful, ${... is not a brace expansion... + * in fact, if it's got a substitution in it's too + * hard for us anyway. sorry. + */ + if (*p == String || *p == Qstring) { + tt = NULL; + break; + } else if (*p == Inbrace) { + if (tt) { + /* too many inbraces */ + tt = NULL; + break; + } + tt = p; + } else if (*p == Outbrace && i < myoffs) { + /* outbrace is before cursor pos, so nothing to complete */ + tt = NULL; + break; + } + } + + if (tt && tt < s + myoffs) { + /* Braces are go: delete opening brace */ + char *com = NULL; + chuck(tt); + offs--; + myoffs--; + + /* Look for text up to comma before cursor and delete it */ + for (i = tt - s, p = tt; *p && i < myoffs; p++, i++) + if (*p == Comma) + com = p; + if (com) { + i = com - tt + 1; + while (i--) + chuck(tt), offs--, myoffs--; + } + + /* Look for text between subsequent comma + * and closing brace or end of string and delete it + */ + for (p = s + myoffs; *p && *p != Outbrace; p++) + if (*p == Comma) { + while (*p && *p != Outbrace) + chuck(p); + break; + } + if (*p == Outbrace) + chuck(p); + else { + /* we are still waiting for an outbrace and maybe commas */ + complinbrace = 1; + } + } + } + + } LASTALLOC; + lexrestore(); + + return (char *)s; +} + +/* Expand the current word. */ + +/**/ +static void +doexpansion(char *s, int lst, int olst, int explincmd) +{ + LinkList vl; + char *ss; + + DPUTS(useheap, "BUG: useheap in doexpansion()"); + HEAPALLOC { + pushheap(); + vl = newlinklist(); + ss = dupstring(s); + addlinknode(vl, ss); + prefork(vl, 0); + if (errflag) + goto end; + if ((lst == COMP_LIST_EXPAND) || (lst == COMP_EXPAND)) { + int ng = opts[NULLGLOB]; + + opts[NULLGLOB] = 1; + globlist(vl); + opts[NULLGLOB] = ng; + } + if (errflag) + goto end; + if (empty(vl) || !*(char *)peekfirst(vl)) { + if (!noerrs) + feep(); + goto end; + } + if (peekfirst(vl) == (void *) ss || + (olst == COMP_EXPAND_COMPLETE && + !nextnode(firstnode(vl)) && *s == Tilde && + (ss = dupstring(s), filesubstr(&ss, 0)) && + !strcmp(ss, (char *)peekfirst(vl)))) { + /* If expansion didn't change the word, try completion if * + * expandorcomplete was called, otherwise, just beep. */ + if (lst == COMP_EXPAND_COMPLETE) + docompletion(s, COMP_COMPLETE, explincmd, 0); + else + feep(); + goto end; + } + if (lst == COMP_LIST_EXPAND) { + /* Only the list of expansions was requested. */ + listlist(vl); + goto end; + } + /* Remove the current word and put the expansions there. */ + cs = wb; + foredel(we - wb); + while ((ss = (char *)ugetnode(vl))) { + untokenize(ss); + ss = quotename(ss, NULL, NULL, NULL); + inststr(ss); +#if 0 + if (nonempty(vl)) { + spaceinline(1); + line[cs++] = ' '; + } +#endif + if (olst != COMP_EXPAND_COMPLETE || nonempty(vl) || + (cs && line[cs-1] != '/')) { + spaceinline(1); + line[cs++] = ' '; + } + } + end: + popheap(); + } LASTALLOC; +} + +/* This is called from the lexer to give us word positions. */ + +/**/ +void +gotword(void) +{ + we = ll + 1 - inbufct + (addedx == 2 ? 1 : 0); + if (cs <= we) { + wb = ll - wordbeg + addedx; + zleparse = 0; + } +} + +/* Insert the given string into the command line. If move is non-zero, * + * the cursor position is changed and len is the length of the string * + * to insert (if it is -1, the length is calculated here). */ + +/**/ +static void +inststrlen(char *str, int move, int len) +{ + if (!len) + return; + if (len == -1) + len = strlen(str); + spaceinline(len); + strncpy((char *)(line + cs), str, len); + if (move) + cs += len; +} + +/* Quote the string s and return the result. If e is non-zero, it the * + * pointer it points to may point to aposition in s and in e the position * + * of the corresponding character in the quoted string is returned. Like * + * e, te may point to a position in the string and pl is used to return * + * the position of the character pointed to by te in the quoted string. * + * The string is metafied and may contain tokens. */ + +/**/ +static char * +quotename(const char *s, char **e, char *te, int *pl) +{ + const char *u, *tt; + char *v, buf[PATH_MAX * 2]; + int sf = 0; + + tt = v = buf; + u = s; + for (; *u; u++) { + if (e && *e == u) + *e = v, sf |= 1; + if (te == u) + *pl = v - tt, sf |= 2; + if (ispecial(*u) && + (!instring || (isset(BANGHIST) && + *u == (char)bangchar) || + (instring == 2 && + (*u == '$' || *u == '`' || *u == '\"')) || + (instring == 1 && *u == '\''))) + if (*u == '\n' || (instring == 1 && *u == '\'')) { + if (unset(RCQUOTES)) { + *v++ = '\''; + if (*u == '\'') + *v++ = '\\'; + *v++ = *u; + *v++ = '\''; + } else if (*u == '\n') + *v++ = '"', *v++ = '\n', *v++ = '"'; + else + *v++ = '\'', *v++ = '\''; + continue; + } else + *v++ = '\\'; + if(*u == Meta) + *v++ = *u++; + *v++ = *u; + } + *v = '\0'; + if (strcmp(buf, s)) + tt = dupstring(buf); + else + tt = s; + v += tt - buf; + if (e && (sf & 1)) + *e += tt - buf; + + if (e && *e == u) + *e = v; + if (te == u) + *pl = v - tt; + + return (char *) tt; +} + +/* This adds a match to the list of matches. The string to add is given * + * in s, the type of match is given in the global variable addwhat and * + * the parameter t (if not NULL) is a pointer to a hash node node which * + * may be used to give other information to this function. * + * * + * addwhat contains either one of the special values (negative, see below) * + * or the inclusive OR of some of the CC_* flags used for compctls. */ + +/**/ +static void +addmatch(char *s, char *t) +{ + int test = 0, sl = strlen(s), pl = rpl, cc = 0, *bp, *ep, *sp; + char *e = NULL, *tt, *te, *fc, **fm; + Comp cp = patcomp; + HashNode hn; + Param pm; + LinkList l = matches; + +/* + * addwhat: -5 is for files, + * -6 is for glob expansions, + * -8 is for executable files (e.g. command paths), + * -9 is for parameters + * -7 is for command names (from cmdnamtab) + * -4 is for a cdable parameter + * -3 is for executable command names. + * -2 is for anything unquoted + * -1 is for other file specifications + * (things with `~' of `=' at the beginning, ...). + */ + + /* Just to make the code cleaner */ + hn = (HashNode) t; + pm = (Param) t; + + if (!addwhat) { + test = 1; + } else if (addwhat == -1 || addwhat == -5 || addwhat == -6 || + addwhat == CC_FILES || addwhat == -7 || addwhat == -8) { + if (sl < fpl + fsl) + return; + + if ((addwhat == CC_FILES || + addwhat == -5) && !*psuf && !*fsuf) { + /* If this is a filename, do the fignore check. */ + char **pt = fignore; + int filell; + + for (test = 1; test && *pt; pt++) + if ((filell = strlen(*pt)) < sl + && !strcmp(*pt, s + sl - filell)) + test = 0; + + if (!test) + l = fmatches; + } + pl = fpl; + if (addwhat == -5 || addwhat == -8) { + test = 1; + cp = filecomp; + cc = cp || ispattern; + e = s + sl - fsl; + } else { + if ((cp = filecomp)) { + if ((test = domatch(s, filecomp, 0))) + cc = 1; + } else { + e = s + sl - fsl; + if ((test = !strncmp(s, fpre, fpl))) + test = !strcmp(e, fsuf); + if (ispattern) + cc = 1; + } + } + if (test) { + fc = NULL; + if (addwhat == -7 && !(fc = findcmd(s))) + return; + if (fc) + zsfree(fc); + haswhat |= HAS_FILES; + + if (addwhat == CC_FILES || addwhat == -6 || + addwhat == -5 || addwhat == -8) { + te = s + pl; + s = quotename(s, &e, te, &pl); + sl = strlen(s); + } else if (!cc) { + s = dupstring(t = s); + e += s - t; + } + if (cc) { + tt = (char *)halloc(strlen(ppre) + strlen(psuf) + sl + 1); + strcpy(tt, ppre); + strcat(tt, s); + strcat(tt, psuf); + untokenize(s = tt); + } + } + } else if (addwhat == CC_QUOTEFLAG || addwhat == -2 || + (addwhat == -3 && !(hn->flags & DISABLED)) || + (addwhat == -4 && (PM_TYPE(pm->flags) == PM_SCALAR) && + (tt = pm->gets.cfn(pm)) && *tt == '/') || + (addwhat == -9 && !(hn->flags & PM_UNSET)) || + (addwhat > 0 && + ((!(hn->flags & PM_UNSET) && + (((addwhat & CC_ARRAYS) && (hn->flags & PM_ARRAY)) || + ((addwhat & CC_INTVARS) && (hn->flags & PM_INTEGER)) || + ((addwhat & CC_ENVVARS) && (hn->flags & PM_EXPORTED)) || + ((addwhat & CC_SCALARS) && (hn->flags & PM_SCALAR)) || + ((addwhat & CC_READONLYS) && (hn->flags & PM_READONLY)) || + ((addwhat & CC_SPECIALS) && (hn->flags & PM_SPECIAL)) || + ((addwhat & CC_PARAMS) && !(hn->flags & PM_EXPORTED)))) || + ((( addwhat & CC_SHFUNCS) || + ( addwhat & CC_BUILTINS) || + ( addwhat & CC_EXTCMDS) || + ( addwhat & CC_RESWDS) || + ((addwhat & CC_ALREG) && !(hn->flags & ALIAS_GLOBAL)) || + ((addwhat & CC_ALGLOB) && (hn->flags & ALIAS_GLOBAL))) && + (((addwhat & CC_DISCMDS) && (hn->flags & DISABLED)) || + ((addwhat & CC_EXCMDS) && !(hn->flags & DISABLED)))) || + ((addwhat & CC_BINDINGS) && !(hn->flags & DISABLED))))) { + if (sl >= rpl + rsl) { + if (cp) + test = domatch(s, patcomp, 0); + else { + e = s + sl - rsl; + if ((test = !strncmp(s, rpre, rpl))) + test = !strcmp(e, rsuf); + } + } + if (!test && sl < lpl + lsl) + return; + if (!test && lpre && lsuf && sl >= lpl + lsl) { + e = s + sl - lsl; + if ((test = !strncmp(s, lpre, lpl))) + test = !strcmp(e, lsuf); + pl = lpl; + } + if (addwhat == CC_QUOTEFLAG) { + te = s + pl; + s = quotename(s, &e, te, &pl); + sl = strlen(s); + } + if (test) + haswhat |= HAS_MISC; + } + if (!test) + return; + + if (ispattern) { + t = s; + } else { + t = s += pl; + if (*e) + t = s = dupstrpfx(t, e - t); + } + + if (l == fmatches) { + bp = &fab; + ep = &fae; + sp = &fshortl; + fm = &ffirstm; + } else { + bp = &ab; + ep = &ae; + sp = &shortl; + fm = &firstm; + } + + if (!ispattern && *fm) { + if ((test = pfxlen(*fm, s)) < *bp) + *bp = test; + if ((test = sfxlen(*fm, s)) < *ep) + *ep = test; + if (*ep > *sp - *bp) + *ep = *sp - *bp; + } + + /* If we are doing a glob completion we store the whole string in * + * the list. Otherwise only the part that fits between the prefix * + * and the suffix is stored. */ + addlinknode(l, t); + if (!*fm) { + *bp = *ep = 10000; + *fm = t; + *sp = 100000; + } + if (!ispattern && (sl = strlen(t)) < *sp) { + *sp = sl; + if (l == fmatches) + fshortest = t; + else + shortest = t; + } +} + +#ifdef HAVE_NIS_PLUS +static int +match_username(nis_name table, nis_object *object, void *userdata) +{ + if (errflag) + return 1; + else { + static char buf[40]; + register entry_col *ec = + object->zo_data.objdata_u.en_data.en_cols.en_cols_val; + register int l = minimum(ec->ec_value.ec_value_len, 39); + + memcpy(buf, ec->ec_value.ec_value_val, l); + buf[l] = '\0'; + + addmatch(dupstring(buf), NULL); + } + return 0; +} +#else +# ifdef HAVE_NIS +static int +match_username(int status, char *key, int keylen, char *val, int vallen, dopestring *data) +{ + if (errflag || status != YP_TRUE) + return 1; + + if (vallen > keylen && val[keylen] == ':') { + val[keylen] = '\0'; + addmatch(dupstring(val), NULL); + } + return 0; +} +# endif /* HAVE_NIS */ +#endif /* HAVE_NIS_PLUS */ + +/**/ +static void +maketildelist(void) +{ +#if defined(HAVE_NIS) || defined(HAVE_NIS_PLUS) + FILE *pwf; + char buf[BUFSIZ], *p; + int skipping; + +# ifndef HAVE_NIS_PLUS + char domain[YPMAXDOMAIN]; + struct ypall_callback cb; + dopestring data; + + data.s = fpre; + data.len = fpl; + /* Get potential matches from NIS and cull those without local accounts */ + if (getdomainname(domain, YPMAXDOMAIN) == 0) { + cb.foreach = (int (*)()) match_username; + cb.data = (char *)&data; + yp_all(domain, PASSWD_MAP, &cb); + } +# else /* HAVE_NIS_PLUS */ + /* Maybe we should turn this string into a #define'd constant...? */ + + nis_list("passwd.org_dir", EXPAND_NAME|ALL_RESULTS|FOLLOW_LINKS|FOLLOW_PATH, + match_username, 0); +# endif + /* Don't forget the non-NIS matches from the flat passwd file */ + if ((pwf = fopen(PASSWD_FILE, "r")) != NULL) { + skipping = 0; + while (fgets(buf, BUFSIZ, pwf) != NULL) { + if (strchr(buf, '\n') != NULL) { + if (!skipping) { + if ((p = strchr(buf, ':')) != NULL) { + *p = '\0'; + addmatch(dupstring(buf), NULL); + } + } else + skipping = 0; + } else + skipping = 1; + } + fclose(pwf); + } +#else /* no NIS or NIS_PLUS */ + /* add all the usernames to the named directory table */ + nameddirtab->filltable(nameddirtab); +#endif + + scanhashtable(nameddirtab, 0, (addwhat==-1) ? 0 : ND_USERNAME, 0, + addhnmatch, 0); +} + +/* Copy the given string and remove backslashes from the copy and return it. */ + +/**/ +static char * +rembslash(char *s) +{ + char *t = s = dupstring(s); + + while (*s) + if (*s == '\\') { + chuck(s); + if (*s) + s++; + } else + s++; + + return t; +} + +/* This does the check for compctl -x `n' and `N' patterns. */ + +/**/ +static int +getcpat(char *wrd, int cpatindex, char *cpat, int class) +{ + char *str, *s, *t, *p; + int d = 0; + + if (!wrd || !*wrd) + return -1; + + cpat = rembslash(cpat); + + str = ztrdup(wrd); + untokenize(str); + if (!cpatindex) + cpatindex++, d = 0; + else if ((d = (cpatindex < 0))) + cpatindex = -cpatindex; + + for (s = d ? str + strlen(str) - 1 : str; + d ? (s >= str) : *s; + d ? s-- : s++) { + for (t = s, p = cpat; *t && *p; p++) { + if (class) { + if (*p == *s && !--cpatindex) { + zsfree(str); + return (int)(s - str + 1); + } + } else if (*t++ != *p) + break; + } + if (!class && !*p && !--cpatindex) { + zsfree(str); + t += wrd - str; + for (d = 0; --t >= wrd;) + if (! INULL(*t)) + d++; + return d; + } + } + zsfree(str); + return -1; +} + +/* This holds a pointer to the compctl we are using. */ + +static Compctl ccmain; + + +/* Find the compctl to use and return it. The first argument gives a * + * compctl to start searching with (if it is zero, the hash table is * + * searched). compadd is used to return a number of characters that * + * should be ignored at the beginning of the word and incmd is * + * non-zero if we are in command position. */ + +/**/ +static Compctl +get_ccompctl(Compctl occ, int *compadd, int incmd) +{ + Compctl compc, ret; + Compctlp ccp; + int t, i, a, b, tt, ra, rb, j, isf = 1; + Compcond or, cc; + char *s, *ss, *sc, *cmd = dupstring(cmdstr); + Comp comp; + + first_rec: + *compadd = 0; + ra = 0; + rb = clwnum - 1; + sc = NULL; + + if (!(ret = compc = occ)) { + if (isf) { + isf = 0; + ret = &cc_first; + } + else if (inwhat == IN_ENV) + /* Default completion for parameter values. */ + ret = &cc_default; + else if (inwhat == IN_MATH) { + /* Parameter names inside mathematical expression. */ + cc_dummy.mask = CC_PARAMS; + ret = &cc_dummy; + cc_dummy.refc = 10000; + } else if (inwhat == IN_COND) { + /* We try to be clever here: in conditions we complete option * + * names after a `-o', file names after `-nt', `-ot', and `-ef' * + * and file names and parameter names elsewhere. */ + s = clwpos ? clwords[clwpos - 1] : ""; + cc_dummy.mask = !strcmp("-o", s) ? CC_OPTIONS : + ((*s == '-' && s[1] && !s[2]) || + !strcmp("-nt", s) || + !strcmp("-ot", s) || + !strcmp("-ef", s)) ? CC_FILES : + (CC_FILES | CC_PARAMS); + ret = &cc_dummy; + cc_dummy.refc = 10000; + } else if (incmd) + ret = &cc_compos; + /* And in redirections or if there is no command name (and we are * + * not in command position) or if no special compctl was given * + * for the command: use default completion. Note that we first * + * search the complete command name and than the trailing * + * pathname component. */ + else if (linredir || + !(cmd && + (((ccp = (Compctlp) compctltab->getnode(compctltab, cmd)) && + (compc = ret = ccp->cc)) || + ((s = dupstring(cmd)) && remlpaths(&s) && + (ccp = (Compctlp) compctltab->getnode(compctltab, s)) && + (compc = ret = ccp->cc))))) + ret = &cc_default; + + ccmain = compc = ret; + ccmain->refc++; + } + /* The compctl we found has extended completion patterns, check them. */ + if (compc && compc->ext) { + compc = compc->ext; + /* This loops over the patterns separated by `--'. */ + for (t = 0; compc && !t; compc = compc->next) { + /* This loops over OR'ed patterns. */ + for (cc = compc->cond; cc && !t; cc = or) { + or = cc->or; + /* This loops over AND'ed patterns. */ + for (t = 1; cc && t; cc = cc->and) { + /* And this loops of [...] pairs. */ + for (t = i = 0; i < cc->n && !t; i++) { + s = NULL; + ra = 0; + rb = clwnum - 1; + switch (cc->type) { + case CCT_POS: + tt = clwpos; + goto cct_num; + case CCT_NUMWORDS: + tt = clwnum; + cct_num: + if ((a = cc->u.r.a[i]) < 0) + a += clwnum; + if ((b = cc->u.r.b[i]) < 0) + b += clwnum; + if (cc->type == CCT_POS) + ra = a, rb = b; + t = (tt >= a && tt <= b); + break; + case CCT_CURSUF: + case CCT_CURPRE: + s = ztrdup(clwpos < clwnum ? clwords[clwpos] : ""); + untokenize(s); + sc = rembslash(cc->u.s.s[i]); + a = strlen(sc); + if (!strncmp(s, sc, a)) { + *compadd = (cc->type == CCT_CURSUF ? a : 0); + t = 1; + } + break; + case CCT_CURSUB: + case CCT_CURSUBC: + if (clwpos < 0 || clwpos > clwnum) + t = 0; + else { + a = getcpat(clwords[clwpos], + cc->u.s.p[i], + cc->u.s.s[i], + cc->type == CCT_CURSUBC); + if (a != -1) + *compadd = a, t = 1; + } + break; + + case CCT_CURPAT: + case CCT_CURSTR: + tt = clwpos; + goto cct_str; + case CCT_WORDPAT: + case CCT_WORDSTR: + tt = 0; + cct_str: + if ((a = tt + cc->u.s.p[i]) < 0) + a += clwnum; + s = ztrdup((a < 0 || a >= clwnum) ? "" : + clwords[a]); + untokenize(s); + + if (cc->type == CCT_CURPAT || + cc->type == CCT_WORDPAT) { + tokenize(ss = dupstring(cc->u.s.s[i])); + t = ((comp = parsereg(ss)) && + domatch(s, comp, 0)); + } else + t = (!strcmp(s, rembslash(cc->u.s.s[i]))); + break; + case CCT_RANGESTR: + case CCT_RANGEPAT: + if (cc->type == CCT_RANGEPAT) + tokenize(sc = dupstring(cc->u.l.a[i])); + for (j = clwpos; j; j--) { + untokenize(s = ztrdup(clwords[j])); + if (cc->type == CCT_RANGESTR) + sc = rembslash(cc->u.l.a[i]); + if (cc->type == CCT_RANGESTR ? + !strncmp(s, sc, strlen(sc)) : + ((comp = parsereg(sc)) && + domatch(s, comp, 0))) { + zsfree(s); + ra = j + 1; + t = 1; + break; + } + zsfree(s); + } + if (t) { + if (cc->type == CCT_RANGEPAT) + tokenize(sc = dupstring(cc->u.l.b[i])); + for (j++; j < clwnum; j++) { + untokenize(s = ztrdup(clwords[j])); + if (cc->type == CCT_RANGESTR) + sc = rembslash(cc->u.l.b[i]); + if (cc->type == CCT_RANGESTR ? + !strncmp(s, sc, strlen(sc)) : + ((comp = parsereg(sc)) && + domatch(s, comp, 0))) { + zsfree(s); + rb = j - 1; + t = clwpos <= rb; + break; + } + zsfree(s); + } + } + s = NULL; + } + zsfree(s); + } + } + } + if (t) + break; + } + if (compc) + /* We found a matching pattern, we may return it. */ + ret = compc; + } + if (ret->subcmd) { + /* The thing we want to return has a subcmd flag (-l). */ + char **ow = clwords, *os = cmdstr, *ops = NULL; + int oldn = clwnum, oldp = clwpos; + + /* So we restrict the words-array. */ + if (ra >= clwnum) + ra = clwnum - 1; + if (ra < 1) + ra = 1; + if (rb >= clwnum) + rb = clwnum - 1; + if (rb < 1) + rb = 1; + clwnum = rb - ra + 1; + clwpos = clwpos - ra; + + if (ret->subcmd[0]) { + /* And probably put the command name given to the flag * + * in the array. */ + clwpos++; + clwnum++; + incmd = 0; + ops = clwords[ra - 1]; + clwords[ra - 1] = cmdstr = ret->subcmd; + clwords += ra - 1; + } else { + cmdstr = clwords[ra]; + incmd = !clwpos; + clwords += ra; + } + *compadd = 0; + if (ccmain != &cc_dummy) + freecompctl(ccmain); + /* Then we call this function recursively. */ + + ret = get_ccompctl(NULL, compadd, incmd); + /* And restore the things we changed. */ + clwords = ow; + cmdstr = os; + clwnum = oldn; + clwpos = oldp; + if (ops) + clwords[ra - 1] = ops; + } + if (ret == &cc_first) + goto first_rec; + return ret; +} + +/* Dump a hash table (without sorting). For each element the addmatch * + * function is called and at the beginning the addwhat variable is set. * + * This could be done using scanhashtable(), but this is easy and much * + * more efficient. */ + +/**/ +static void +dumphashtable(HashTable ht, int what) +{ + HashNode hn; + int i; + + addwhat = what; + + for (i = 0; i < ht->hsize; i++) + for (hn = ht->nodes[i]; hn; hn = hn->next) + addmatch(hn->nam, (char *) hn); + +} + +/* ScanFunc used by maketildelist() et al. */ + +/**/ +static void +addhnmatch(HashNode hn, int flags) +{ + addmatch(hn->nam, NULL); +} + +/* Perform expansion on the given string and return the result. * + * During this errors are not reported. */ + +/**/ +static char * +getreal(char *str) +{ + LinkList l = newlinklist(); + int ne = noerrs; + + noerrs = 1; + addlinknode(l, dupstring(str)); + prefork(l, 0); + noerrs = ne; + if (!errflag && nonempty(l)) + return ztrdup(peekfirst(l)); + errflag = 0; + + return ztrdup(str); +} + +/* This reads a directory and adds the files to the list of * + * matches. The parameters say which files should be added. */ + +/**/ +static void +gen_matches_files(int dirs, int execs, int all) +{ + DIR *d; + struct stat buf; + char *n, p[PATH_MAX], *q = NULL, *e; + LinkList l = NULL; + int ns = 0, ng = opts[NULLGLOB], test, aw = addwhat; + + addwhat = execs ? -8 : -5; + opts[NULLGLOB] = 1; + + if (*psuf) { + /* If there is a path suffix, check if it doesn't have a `*' or * + * `)' at the end (this is used to determine if we should use * + * globbing). */ + q = psuf + strlen(psuf) - 1; + ns = !(*q == Star || *q == Outpar); + l = newlinklist(); + /* And generate only directory names. */ + dirs = 1; + all = execs = 0; + } + /* Open directory. */ + if ((d = opendir((prpre && *prpre) ? prpre : "."))) { + /* If we search only special files, prepare a path buffer for stat. */ + if (!all && prpre) { + strcpy(p, prpre); + q = p + strlen(prpre); + } + /* Fine, now read the directory. */ + while ((n = zreaddir(d, 1)) && !errflag) { + /* Ignore files beginning with `.' unless the thing we found on * + * the command line also starts with a dot or GLOBDOTS is set. */ + if (*n != '.' || *fpre == '.' || isset(GLOBDOTS)) { + if (filecomp) + /* If we have a pattern for the filename check, use it. */ + test = domatch(n, filecomp, 0); + else { + /* Otherwise use the prefix and suffix strings directly. */ + e = n + strlen(n) - fsl; + if ((test = !strncmp(n, fpre, fpl))) + test = !strcmp(e, fsuf); + } + /* Filename didn't match? */ + if (!test) + continue; + if (!all) { + /* We still have to check the file type, so prepare * + * the path buffer by appending the filename. */ + strcpy(q, n); + /* And do the stat. */ + if (stat(p, &buf) < 0) + continue; + } + if (all || + (dirs && S_ISDIR(buf.st_mode)) || + (execs && S_ISREG(buf.st_mode) && (buf.st_mode&S_IXUGO))) { + /* If we want all files or the file has the right type... */ + if (*psuf) { + /* We have to test for a path suffix. */ + int o = strlen(p), tt; + + /* Append it to the path buffer. */ + strcpy(p + o, psuf); + + /* Do we have to use globbing? */ + if (ispattern || (ns && isset(GLOBCOMPLETE))) { + /* Yes, so append a `*' if needed. */ + if (ns) { + int tl = strlen(p); + + p[tl] = Star; + p[tl + 1] = '\0'; + } + /* Do the globbing... */ + remnulargs(p); + addlinknode(l, p); + globlist(l); + /* And see if that produced a filename. */ + tt = nonempty(l); + while (ugetnode(l)); + } else + /* Otherwise just check, if we have access * + * to the file. */ + tt = !access(p, F_OK); + + p[o] = '\0'; + if (tt) + /* Ok, we can add the filename to the * + * list of matches. */ + addmatch(dupstring(n), NULL); + } else + /* We want all files, so just add the name * + * to the matches. */ + addmatch(dupstring(n), NULL); + } + } + } + closedir(d); + } + opts[NULLGLOB] = ng; + addwhat = aw; +} + +/* This holds the explanation string we have to print. */ + +static char *expl; + +/* This holds the suffix to add (given with compctl -S). */ + +static char *ccsuffix; + +/* This s non-zero if the compctl -q flag was given (the suffix should * + * be removed when a space or something like that is typed next). */ + +static int remsuffix; + +/**/ +static void +quotepresuf(char **ps) +{ + if (*ps) { + char *p = quotename(*ps, NULL, NULL, NULL); + + if (p != *ps) { + zsfree(*ps); + *ps = ztrdup(p); + } + } +} + +/**/ +static void +docompletion(char *s, int lst, int incmd, int untokenized) +{ + static int delit, compadd; + + fixsuffix(); + HEAPALLOC { + pushheap(); + + /* Make sure we have the completion list and compctl. */ + if(makecomplist(s, incmd, &delit, &compadd, untokenized)) { + /* Error condition: feeeeeeeeeeeeep(). */ + feep(); + goto compend; + } + + if (lst == COMP_LIST_COMPLETE) + /* All this and the guy only wants to see the list, sigh. */ + showinglist = -2; + else { + /* We have matches. */ + if (delit) { + /* If we have to delete the word from the command line, * + * do it now. */ + wb -= compadd; + strcpy((char *)line + wb, (char *)line + we); + we = cs = wb; + } + if (nmatches > 1) + /* There are more than one match. */ + do_ambiguous(); + else if (nmatches == 1) { + /* Only one match. */ + do_single(amatches[0]); + invalidatelist(); + } + } + + /* Print the explanation string if needed. */ + if (!showinglist && expl && nmatches != 1) { + int up; + + if (!nmatches) + feep(); + trashzle(); + + clearflag = (isset(USEZLE) && !termflags && + (isset(ALWAYSLASTPROMPT) && zmult == 1)) || + (unset(ALWAYSLASTPROMPT) && zmult != 1); + + up = printfmt(expl, nmatches, 1); + + if (clearflag) + tcmultout(TCUP, TCMULTUP, up + nlnct); + else + putc('\n', shout); + fflush(shout); + } + compend: + ll = strlen((char *)line); + if (cs > ll) + cs = ll; + popheap(); + } LASTALLOC; +} + +/* Create the completion list. This is called whenever some bit of * + * completion code needs the list. If the list is already available * + * (validlist!=0), this function doesn't do anything. Along with * + * the list is maintained the prefixes/suffixes etc. When any of * + * this becomes invalid -- e.g. if some text is changed on the * + * command line -- invalidatelist() should be called, to set * + * validlist to zero and free up the memory used. This function * + * returns non-zero on error. delit and compadd return information * + * about bits of the command line that need to be deleted. */ + +/**/ +static int +makecomplist(char *s, int incmd, int *delit, int *compadd, int untokenized) +{ + Compctl cc = NULL; + int oloffs = offs, owe = we, owb = wb, ocs = cs, oll = ll, isf = 1; + int t, sf1, sf2, ooffs; + char *p, *sd = NULL, *tt, *s1, *s2, *os = NULL; + unsigned char *ol = NULL; + + /* If we already have a list from a previous execution of this * + * function, skip the list building code. */ + if (validlist) + return !nmatches; + + os = dupstring(s); + ol = (unsigned char *)dupstring((char *)line); + + xorrec: + + DPUTS(ll != strlen((char *) line), "BUG: xorrec: ll != strlen(line)"); + + /* Go to the end of the word if complete_in_word is not set. */ + if (unset(COMPLETEINWORD) && cs != we) + cs = we, offs = strlen(s); + + ispattern = haswhat = lastambig = 0; + patcomp = filecomp = NULL; + menucur = NULL; + shortest = NULL; + fshortest = NULL; + rpre = rsuf = lpre = lsuf = ppre = psuf = prpre = + fpre = fsuf = firstm = ffirstm = parampre = qparampre = NULL; + + /* Blank out the lists. */ + matches = newlinklist(); + fmatches = newlinklist(); + + /* If we don't have a compctl definition yet or we have a compctl * + * with extended completion, get it (or the next one, resp.). */ + if (!cc || cc->ext) + cc = get_ccompctl(cc, compadd, incmd); + + /* *compadd is the number of characters we have to ignore at the * + * beginning of the word. */ + wb += *compadd; + s += *compadd; + if ((offs -= *compadd) < 0) + /* It's bigger than our word prefix, so we can't help here... */ + return 1; + + /* Insert the prefix (compctl -P), if any. */ + if (cc->prefix) { + int pl = 0, sl = strlen(cc->prefix); + + if (*s) { + /* First find out how much of the prefix is already on the line. */ + sd = dupstring(s); + untokenize(sd); + pl = pfxlen(cc->prefix, sd); + s += pl; + } + if (pl < sl) { + int savecs = cs; + + /* Then insert the prefix. */ + cs = wb + pl; + inststrlen(cc->prefix + pl, 0, sl - pl); + cs = savecs + sl - pl; + } + /* And adjust the word beginning/end variables. */ + wb += sl; + we += sl - pl; + offs -= pl; + } + /* Does this compctl have a suffix (compctl -S)? */ + if ((ccsuffix = cc->suffix) && *ccsuffix) { + char *sdup = dupstring(ccsuffix); + int sl = strlen(sdup), suffixll; + + /* Ignore trailing spaces. */ + for (p = sdup + sl - 1; p >= sdup && *p == ' '; p--, sl--); + p[1] = '\0'; + + if (!sd) { + sd = dupstring(s); + untokenize(sd); + } + /* If the suffix is already there, ignore it (and don't add * + * it again). */ + if (*sd && (suffixll = strlen(sd)) >= sl && + offs <= suffixll - sl && !strcmp(sdup, sd + suffixll - sl)) { + ccsuffix = NULL; + haswhat |= HAS_SUFFIX; + s[suffixll - sl] = '\0'; + } + } + /* Do we have one of the special characters `~' and `=' at the beginning? */ + if ((ic = *s) != Tilde && ic != Equals) + ic = 0; + + /* Check if we have to complete a parameter name... */ + + /* Try to find a `$'. */ + for (p = s + offs; p > s && *p != String; p--); + if (*p == String) { + /* Handle $$'s */ + while (p > s && p[-1] == String) + p--; + while (p[1] == String && p[2] == String) + p += 2; + } + if (*p == String && p[1] != Inpar && p[1] != Inbrack) { + /* This is really a parameter expression (not $(...) or $[...]). */ + char *b = p + 1, *e = b; + int n = 0, br = 1; + + if (*b == Inbrace) { + /* If this is a ${...}, ignore the possible (...) flags. */ + b++, br++; + n = skipparens(Inpar, Outpar, &b); + } + + /* Ignore the stuff before the parameter name. */ + for (; *b; b++) + if (*b != '^' && *b != Hat && + *b != '=' && *b != Equals && + *b != '~' && *b != Tilde) + break; + if (*b == '#' || *b == Pound || *b == '+') + b++; + + e = b; + /* Find the end of the name. */ + if (*e == Quest || *e == Star || *e == String || *e == Qstring || + *e == '?' || *e == '*' || *e == '$' || + *e == '-' || *e == '!' || *e == '@') + e++; + else if (idigit(*e)) + while (idigit(*e)) + e++; + else if (iident(*e)) + while (iident(*e) || + (useglob && (*e == Star || *e == Quest))) + e++; + + /* Now make sure that the cursor is inside the name. */ + if (offs <= e - s && offs >= b - s && n <= 0) { + /* It is. */ + parambr = br - 1; + /* Get the prefix (anything up to the character before the name). */ + *e = '\0'; + parampre = ztrduppfx(s, b - s); + qparampre = ztrdup(quotename(parampre, NULL, NULL, NULL)); + untokenize(qparampre); + qparprelen = strlen(qparampre); + /* And adjust wb, we, and offs again. */ + offs -= b - s; + wb = cs - offs; + we = wb + e - b; + s = b; + /* And now make sure that we complete parameter names. */ + cc = ccmain = &cc_dummy; + cc_dummy.refc = 10000; + cc_dummy.mask = CC_PARAMS | CC_ENVVARS; + } + } + ooffs = offs; + /* If we have to ignore the word, do that. */ + if (cc->mask & CC_DELETE) { + *delit = 1; + *s = '\0'; + offs = 0; + } else + *delit = 0; + + /* Compute line prefix/suffix. */ + + lpl = offs; + lpre = zalloc(lpl + 1); + memcpy(lpre, s, lpl); + lpre[lpl] = '\0'; + p = quotename(lpre, NULL, NULL, NULL); + if (strcmp(p, lpre) && !strpfx(p, qword)) { + int l1, l2; + + backdel(l1 = cs - wb); + untokenize(p); + inststrlen(p, 1, l2 = strlen(p)); + we += l2 - l1; + } + lsuf = ztrdup(s + offs); + lsl = strlen(lsuf); + if (lsl && (p = quotename(lsuf, NULL, NULL, NULL)) && + (strcmp(p, lsuf) && !strsfx(p, qword))) { + int l1, l2; + + foredel(l1 = strlen(s + offs)); + untokenize(p); + inststrlen(p, 0, l2 = strlen(p)); + we += l2 - l1; + } + + /* First check for ~.../... */ + if (ic == Tilde) { + for (p = lpre + lpl; p > lpre; p--) + if (*p == '/') + break; + + if (*p == '/') + ic = 0; + } + /* Compute real prefix/suffix. */ + + noreal = !*delit; + for (p = lpre; *p && *p != String && *p != Tick; p++); + tt = ic && !parampre ? lpre + 1 : lpre; + rpre = (*p || *lpre == Tilde || *lpre == Equals) ? + (noreal = 0, getreal(tt)) : + ztrdup(tt); + + for (p = lsuf; *p && *p != String && *p != Tick; p++); + rsuf = *p ? (noreal = 0, getreal(lsuf)) : ztrdup(lsuf); + + /* Check if word is a pattern. */ + + for (s1 = NULL, sf1 = 0, p = rpre + (rpl = strlen(rpre)) - 1; + p >= rpre && (ispattern != 3 || !sf1); + p--) + if (itok(*p) && (p > rpre || (*p != Equals && *p != Tilde))) + ispattern |= sf1 ? 1 : 2; + else if (*p == '/') { + sf1++; + if (!s1) + s1 = p; + } + for (s2 = NULL, sf2 = t = 0, p = rsuf; *p && (!t || !sf2); p++) + if (itok(*p)) + t |= sf2 ? 4 : 2; + else if (*p == '/') { + sf2++; + if (!s2) + s2 = p; + } + ispattern = ispattern | t; + + /* But if we were asked not to do glob completion, we never treat the * + * thing as a pattern. */ + if (!useglob) + ispattern = 0; + + if (ispattern) { + /* The word should be treated as a pattern, so compute the matcher. */ + p = (char *)ncalloc(rpl + rsl + 2); + strcpy(p, rpre); + if (rpl && p[rpl - 1] != Star) { + p[rpl] = Star; + strcpy(p + rpl + 1, rsuf); + } else + strcpy(p + rpl, rsuf); + patcomp = parsereg(p); + } + if (!patcomp) { + untokenize(rpre); + untokenize(rsuf); + + rpl = strlen(rpre); + rsl = strlen(rsuf); + } + untokenize(lpre); + untokenize(lsuf); + + /* Handle completion of files specially (of course). */ + + if ((cc->mask & (CC_FILES | CC_DIRS | CC_COMMPATH)) || cc->glob) { + /* s1 and s2 point to the last/first slash in the prefix/suffix. */ + if (!s1) + s1 = rpre; + if (!s2) + s2 = rsuf + rsl; + + /* Compute the path prefix/suffix. */ + if (*s1 != '/') + ppre = ztrdup(""); + else + ppre = ztrduppfx(rpre, s1 - rpre + 1); + psuf = ztrdup(s2); + + /* And get the file prefix. */ + fpre = ztrdup(((s1 == s || s1 == rpre || ic) && + (*s != '/' || cs == wb)) ? s1 : s1 + 1); + /* And the suffix. */ + fsuf = ztrduppfx(rsuf, s2 - rsuf); + + if (useglob && (ispattern & 2)) { + int t2; + + /* We have to use globbing, so compute the pattern from * + * the file prefix and suffix with a `*' between them. */ + p = (char *)ncalloc((t2 = strlen(fpre)) + strlen(fsuf) + 2); + strcpy(p, fpre); + if ((!t2 || p[t2 - 1] != Star) && *fsuf != Star) + p[t2++] = Star; + strcpy(p + t2, fsuf); + filecomp = parsereg(p); + } + if (!filecomp) { + untokenize(fpre); + untokenize(fsuf); + + fpl = strlen(fpre); + fsl = strlen(fsuf); + } + addwhat = -1; + + /* Completion after `~', maketildelist adds the usernames * + * and named directories. */ + if (ic == Tilde) + maketildelist(); + else if (ic == Equals) { + /* Completion after `=', get the command names from * + * the cmdnamtab and aliases from aliastab. */ + if (isset(HASHLISTALL)) + cmdnamtab->filltable(cmdnamtab); + dumphashtable(cmdnamtab, -7); + dumphashtable(aliastab, -2); + } else { + /* Normal file completion... */ + if (ispattern & 1) { + /* But with pattern matching. */ + LinkList l = newlinklist(); + LinkNode n; + int ng = opts[NULLGLOB]; + + opts[NULLGLOB] = 1; + + addwhat = 0; + p = (char *)ncalloc(lpl + lsl + 3); + strcpy(p, lpre); + if (*lsuf != '*' && *lpre && lpre[lpl - 1] != '*') + strcat(p, "*"); + strcat(p, lsuf); + if (*lsuf && lsuf[lsl - 1] != '*' && lsuf[lsl - 1] != ')') + strcat(p, "*"); + + /* Do the globbing. */ + tokenize(p); + remnulargs(p); + addlinknode(l, p); + globlist(l); + + if (nonempty(l)) { + /* And add the resulting words. */ + haswhat |= HAS_PATHPAT; + for (n = firstnode(l); n; incnode(n)) + addmatch(getdata(n), NULL); + } + opts[NULLGLOB] = ng; + } else { + /* No pattern matching. */ + addwhat = CC_FILES; + if (cc->withd) { + prpre = tricat(cc->withd, "/", ppre); + } else + prpre = ztrdup(ppre); + + if (sf2) + /* We are in the path, so add only directories. */ + gen_matches_files(1, 0, 0); + else { + if (cc->mask & CC_FILES) + /* Add all files. */ + gen_matches_files(0, 0, 1); + else if (cc->mask & CC_COMMPATH) { + /* Completion of command paths. */ + if (sf1 || cc->withd) + /* There is a path prefix, so add * + * directories and executables. */ + gen_matches_files(1, 1, 0); + else { + /* No path prefix, so add the things * + * reachable via the PATH variable. */ + char **pc = path, *pp = prpre; + + for (; *pc; pc++) + if (!**pc || (pc[0][0] == '.' && !pc[0][1])) + break; + if (*pc) { + prpre = "./"; + gen_matches_files(1, 1, 0); + prpre = pp; + } + } + } else if (cc->mask & CC_DIRS) + gen_matches_files(1, 0, 0); + /* The compctl has a glob pattern (compctl -g). */ + if (cc->glob) { + int ns, pl = strlen(prpre), o; + char *g = dupstring(cc->glob), pa[PATH_MAX]; + char *p2, *p3; + int ne = noerrs, md = opts[MARKDIRS]; + + /* These are used in the globbing code to make * + * things a bit faster. */ + glob_pre = fpre; + glob_suf = fsuf; + + noerrs = 1; + addwhat = -6; + strcpy(pa, prpre); + o = strlen(pa); + opts[MARKDIRS] = 0; + + /* The compctl -g string may contain more than * + * one pattern, so we need a loop. */ + while (*g) { + LinkList l = newlinklist(); + int ng; + + /* Find the blank terminating the pattern. */ + while (*g && inblank(*g)) + g++; + /* Oops, we already reached the end of the + string. */ + if (!*g) + break; + for (p = g + 1; *p && !inblank(*p); p++) + if (*p == '\\' && p[1]) + p++; + /* Get the pattern string. */ + tokenize(g = dupstrpfx(g, p - g)); + if (*g == '=') + *g = Equals; + if (*g == '~') + *g = Tilde; + remnulargs(g); + if ((*g == Equals || *g == Tilde) && !cc->withd) { + /* The pattern has a `~' or `=' at the * + * beginning, so we expand this and use * + * the result. */ + filesub(&g, 0); + addlinknode(l, dupstring(g)); + } else if (*g == '/' && !cc->withd) + /* The pattern is a full path (starting * + * with '/'), so add it unchanged. */ + addlinknode(l, dupstring(g)); + else { + /* It's a simple pattern, so append it to * + * the path we have on the command line. */ + strcpy(pa + o, g); + addlinknode(l, dupstring(pa)); + } + /* Do the globbing. */ + ng = opts[NULLGLOB]; + opts[NULLGLOB] = 1; + globlist(l); + opts[NULLGLOB] = ng; + /* Get the results. */ + if (nonempty(l) && peekfirst(l)) { + for (p2 = (char *)peekfirst(l); *p2; p2++) + if (itok(*p2)) + break; + if (!*p2) { + if ((*g == Equals || *g == Tilde || + *g == '/') || cc->withd) { + /* IF the pattern started with `~', * + * `=', or `/', add the result only, * + * if it really matches what we have * + * on the line. * + * Do this if an initial directory * + * was specified, too. */ + while ((p2 = (char *)ugetnode(l))) + if (strpfx(prpre, p2)) + addmatch(p2 + pl, NULL); + } else { + /* Otherwise ignore the path we * + * prepended to the pattern. */ + while ((p2 = p3 = + (char *)ugetnode(l))) { + for (ns = sf1; *p3 && ns; p3++) + if (*p3 == '/') + ns--; + + addmatch(p3, NULL); + } + } + } + } + pa[o] = '\0'; + g = p; + } + glob_pre = glob_suf = NULL; + noerrs = ne; + opts[MARKDIRS] = md; + } + } + } + } + } + /* Use tricat() instead of dyncat() to get zalloc()'d memory. */ + if (ic) { + /* Now change the `~' and `=' tokens to the real characters so * + * that things starting with these characters will be added. */ + char *orpre = rpre; + + rpre = tricat("", (ic == Tilde) ? "~" : "=", rpre); + rpl++; + zsfree(orpre); + } + if (!ic && (cc->mask & CC_COMMPATH) && !*ppre && !*psuf) { + /* If we have to complete commands, add alias names, * + * shell functions and builtins too. */ + dumphashtable(aliastab, -3); + dumphashtable(reswdtab, -3); + dumphashtable(shfunctab, -3); + dumphashtable(builtintab, -3); + if (isset(HASHLISTALL)) + cmdnamtab->filltable(cmdnamtab); + dumphashtable(cmdnamtab, -3); + /* And parameter names if autocd and cdablevars are set. */ + if (isset(AUTOCD) && isset(CDABLEVARS)) + dumphashtable(paramtab, -4); + } + addwhat = (cc->mask & CC_QUOTEFLAG) ? -2 : CC_QUOTEFLAG; + + if (cc->mask & CC_NAMED) + /* Add named directories. */ + dumphashtable(nameddirtab, addwhat); + if (cc->mask & CC_OPTIONS) + /* Add option names. */ + dumphashtable(optiontab, addwhat); + if (cc->mask & CC_VARS) + /* And parameter names. */ + dumphashtable(paramtab, -9); + if (cc->mask & CC_BINDINGS) + /* And zle function names... */ + dumphashtable(thingytab, CC_BINDINGS); + if (cc->keyvar) { + /* This adds things given to the compctl -k flag * + * (from a parameter or a list of words). */ + char **usr = get_user_var(cc->keyvar); + + if (usr) + while (*usr) + addmatch(*usr++, NULL); + } + if (cc->mask & CC_USERS) + /* Add user names. */ + maketildelist(); + if (cc->func) { + /* This handles the compctl -K flag. */ + List list; + char **r; + int lv = lastval; + + /* Get the function. */ + if ((list = getshfunc(cc->func)) != &dummy_list) { + /* We have it, so build a argument list. */ + LinkList args = newlinklist(); + + addlinknode(args, cc->func); + + if (*delit) { + p = dupstrpfx(os, ooffs); + untokenize(p); + addlinknode(args, p); + p = dupstring(os + ooffs); + untokenize(p); + addlinknode(args, p); + } else { + addlinknode(args, lpre); + addlinknode(args, lsuf); + } + + /* This flag allows us to use read -l and -c. */ + incompctlfunc = 1; + /* Call the function. */ + doshfunc(list, args, 0, 1); + incompctlfunc = 0; + /* And get the result from the reply parameter. */ + if ((r = get_user_var("reply"))) + while (*r) + addmatch(*r++, NULL); + } + lastval = lv; + } + if (cc->mask & (CC_JOBS | CC_RUNNING | CC_STOPPED)) { + /* Get job names. */ + int i; + char *j, *jj; + + for (i = 0; i < MAXJOB; i++) + if (jobtab[i].stat & STAT_INUSE) { + int stopped = jobtab[i].stat & STAT_STOPPED; + + j = jj = dupstring(jobtab[i].procs->text); + /* Find the first word. */ + for (; *jj; jj++) + if (*jj == ' ') { + *jj = '\0'; + break; + } + if ((cc->mask & CC_JOBS) || + (stopped && (cc->mask & CC_STOPPED)) || + (!stopped && (cc->mask & CC_RUNNING))) + addmatch(j, NULL); + } + } + if (cc->str) { + /* Get the stuff from a compctl -s. */ + LinkList foo = newlinklist(); + LinkNode n; + int first = 1, ng = opts[NULLGLOB], oowe = we, oowb = wb; + char *tmpbuf; + + opts[NULLGLOB] = 1; + + /* Put the strin in the lexer buffer and call the lexer to * + * get the words we have to expand. */ + zleparse = 1; + lexsave(); + tmpbuf = (char *)halloc(strlen(cc->str) + 5); + sprintf(tmpbuf, "foo %s", cc->str); /* KLUDGE! */ + inpush(tmpbuf, 0, NULL); + strinbeg(); + noaliases = 1; + do { + ctxtlex(); + if (tok == ENDINPUT || tok == LEXERR) + break; + if (!first && tokstr && *tokstr) + addlinknode(foo, ztrdup(tokstr)); + first = 0; + } while (tok != ENDINPUT && tok != LEXERR); + noaliases = 0; + strinend(); + inpop(); + errflag = zleparse = 0; + lexrestore(); + /* Fine, now do full expansion. */ + prefork(foo, 0); + if (!errflag) { + globlist(foo); + if (!errflag) + /* And add the resulting words as matches. */ + for (n = firstnode(foo); n; incnode(n)) + addmatch((char *)n->dat, NULL); + } + opts[NULLGLOB] = ng; + we = oowe; + wb = oowb; + } + if (cc->hpat) { + /* We have a pattern to take things from the history. */ + Comp compc = NULL; + char *e, *h, hpatsav; + Histent he; + int i = curhist - 1, n = cc->hnum; + + /* Parse the pattern, if it isn't the null string. */ + if (*(cc->hpat)) { + char *thpat = dupstring(cc->hpat); + + tokenize(thpat); + compc = parsereg(thpat); + } + /* n holds the number of history line we have to search. */ + if (!n) + n = -1; + + /* Now search the history. */ + while (n-- && (he = quietgethist(i--))) { + int iwords; + for (iwords = 0; iwords < he->nwords; iwords++) { + h = he->text + he->words[iwords*2]; + e = he->text + he->words[iwords*2+1]; + hpatsav = *e; + *e = '\0'; + /* We now have a word from the history, ignore it * + * if it begins with a quote or `$'. */ + if (*h != '\'' && *h != '"' && *h != '`' && *h != '$' && + (!compc || domatch(h, compc, 0))) + /* Otherwise add it if it was matched. */ + addmatch(dupstring(h), NULL); + if (hpatsav) + *e = hpatsav; + } + } + } + if ((t = cc->mask & (CC_ARRAYS | CC_INTVARS | CC_ENVVARS | CC_SCALARS | + CC_READONLYS | CC_SPECIALS | CC_PARAMS))) + /* Add various flavours of parameters. */ + dumphashtable(paramtab, t); + if ((t = cc->mask & CC_SHFUNCS)) + /* Add shell functions. */ + dumphashtable(shfunctab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); + if ((t = cc->mask & CC_BUILTINS)) + /* Add builtins. */ + dumphashtable(builtintab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); + if ((t = cc->mask & CC_EXTCMDS)) + /* Add external commands */ + dumphashtable(cmdnamtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); + if ((t = cc->mask & CC_RESWDS)) + /* Add reserved words */ + dumphashtable(reswdtab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); + if ((t = cc->mask & (CC_ALREG | CC_ALGLOB))) + /* Add the two types of aliases. */ + dumphashtable(aliastab, t | (cc->mask & (CC_DISCMDS|CC_EXCMDS))); + + /* If we have no matches, ignore fignore. */ + if (empty(matches)) { + matches = fmatches; + firstm = ffirstm; + shortest = fshortest; + ab = fab; + ae = fae; + shortl = fshortl; + } + + /* Make an array from the list of matches. */ + makearray(matches); + PERMALLOC { + amatches = arrdup(amatches); + if (firstm) + firstm = ztrdup(firstm); + /* And quote the prefixes/suffixes. */ + if (hasspecial(s)) { + zfree(lpre, lpl); + zfree(lsuf, lsl); + lpre = zalloc(lpl + 1); + memcpy(lpre, s, lpl); + lpre[lpl] = '\0'; + lsuf = ztrdup(s + offs); + quotepresuf(&lpre); + quotepresuf(&lsuf); + untokenize(lpre); + untokenize(lsuf); + } + quotepresuf(&fpre); + quotepresuf(&fsuf); + quotepresuf(&ppre); + quotepresuf(&psuf); + } LASTALLOC; + + if (!errflag && cc->ylist) { + /* generate the user-defined display list: if anything fails, * + * we silently allow the normal completion list to be used. */ + char **yaptr, *uv = NULL; + List list; + + if (cc->ylist[0] == '$' || cc->ylist[0] == '(') { + /* from variable */ + uv = cc->ylist + (cc->ylist[0] == '$'); + } else if ((list = getshfunc(cc->ylist)) != &dummy_list) { + /* from function: pass completions as arg list */ + LinkList args = newlinklist(); + int addlen = strlen(rpre) + strlen(rsuf) + 1; + + addlinknode(args, cc->ylist); + for (yaptr = amatches; *yaptr; yaptr++) { + /* can't use tricat(). rats. */ + char *ptr = (char *)halloc(addlen + strlen(*yaptr)); + sprintf(ptr, "%s%s%s", rpre, *yaptr, rsuf); + addlinknode(args, ptr); + } + + /* No harm in allowing read -l and -c here, too */ + incompctlfunc = 1; + doshfunc(list, args, 0, 1); + incompctlfunc = 0; + uv = "reply"; + } + if (uv && (yaptr = get_user_var(uv))) { + PERMALLOC { + aylist = arrdup(yaptr); + } LASTALLOC; + } + } + + /* Get the explanation string we will have to print: * + * do this here in case a -y function alters the messge */ + if ((expl = cc->explain)) { + if (cc->mask & CC_EXPANDEXPL && !parsestr(expl = dupstring(expl))) { + singsub(&expl); + untokenize(expl); + } + expl = ztrdup(expl); + } + + remsuffix = (cc->mask & CC_REMOVE); + ccsuffix = cc->suffix; + + validlist = 1; + if (nmatches && !errflag) + return 0; + + if ((isf || cc->xor) && !parampre) { + /* We found no matches, but there is a xor'ed completion: * + * fine, so go back and continue with that compctl. */ + errflag = 0; + cc = cc->xor; + isf = 0; + wb = owb; + we = owe; + cs = ocs; + ll = oll; + strcpy((char *)line, (char *)ol); + offs = oloffs; + s = dupstring(os); + free(amatches); + zsfree(rpre); + zsfree(rsuf); + zsfree(lpre); + zsfree(lsuf); + zsfree(ppre); + zsfree(psuf); + zsfree(fpre); + zsfree(fsuf); + zsfree(prpre); + zsfree(parampre); + zsfree(qparampre); + zsfree(firstm); + if (expl) + zsfree(expl); + expl = NULL; + if (aylist) + freearray(aylist); + aylist = NULL; + goto xorrec; + } + + /* No matches and xor'ed completion: restore the command line if * + * it was alredy quoted, which is the case when s is untokenized. */ + if (untokenized) + strcpy((char *)line, (char *)ol); + return 1; +} + +/* Invalidate the completion list. */ + +/**/ +void +invalidatelist(void) +{ + if(showinglist == -2) + listmatches(); + if(validlist) { + freearray(amatches); + if (aylist) + freearray(aylist); + aylist = NULL; + if (expl) + zsfree(expl); + expl = 0; + zsfree(rpre); + zsfree(rsuf); + zsfree(lpre); + zsfree(lsuf); + zsfree(ppre); + zsfree(psuf); + zsfree(fpre); + zsfree(fsuf); + zsfree(prpre); + zsfree(parampre); + zsfree(qparampre); + zsfree(firstm); + if (ccmain != &cc_dummy) + freecompctl(ccmain); + } + lastambig = menucmp = showinglist = validlist = 0; + menucur = NULL; +} + +/* Get the words from a variable or a compctl -k list. */ + +/**/ +static char ** +get_user_var(char *nam) +{ + if (!nam) + return NULL; + else if (*nam == '(') { + /* It's a (...) list, not a parameter name. */ + char *ptr, *s, **uarr, **aptr; + int count = 0, notempty = 0, brk = 0; + LinkList arrlist = newlinklist(); + + ptr = dupstring(nam); + s = ptr + 1; + while (*++ptr) { + if (*ptr == '\\' && ptr[1]) + chuck(ptr), notempty = 1; + else if (*ptr == ',' || inblank(*ptr) || *ptr == ')') { + if (*ptr == ')') + brk++; + if (notempty) { + *ptr = '\0'; + count++; + if (*s == '\n') + s++; + addlinknode(arrlist, s); + } + s = ptr + 1; + notempty = 0; + } else { + notempty = 1; + if(*ptr == Meta) + ptr++; + } + if (brk) + break; + } + if (!brk || !count) + return NULL; + *ptr = '\0'; + aptr = uarr = (char **)ncalloc(sizeof(char *) * (count + 1)); + + while ((*aptr++ = (char *)ugetnode(arrlist))); + uarr[count] = NULL; + return uarr; + } else { + /* Otherwise it should be a parameter name. */ + char **arr = NULL, *val; + if (!(arr = getaparam(nam)) && (val = getsparam(nam))) { + arr = (char **)ncalloc(2*sizeof(char *)); + arr[0] = val; + arr[1] = NULL; + } + return arr; + } + +} + +/* This is strcmp with ignoring backslashes. */ + +/**/ +static int +strbpcmp(const void *a, const void *b) +{ + char *aa = *((char **)a), *bb = *((char **)b); + + while (*aa && *bb) { + if (*aa == '\\') + aa++; + if (*bb == '\\') + bb++; + if (*aa != *bb) + return (int)(*aa - *bb); + if (*aa) + aa++; + if (*bb) + bb++; + } + return (int)(*aa - *bb); +} + +/* Make an array from a linked list */ + +/**/ +static void +makearray(LinkList l) +{ + char **ap, **bp, **cp; + LinkNode nod; + + /* Build an array for the matches. */ + ap = amatches = (char **)ncalloc(((nmatches = countlinknodes(l)) + 1) * + sizeof(char *)); + + /* And copy them into it. */ + for (nod = firstnode(l); nod; incnode(nod)) + *ap++ = (char *)getdata(nod); + *ap = NULL; + + /* Now sort the array. */ + qsort((void *) amatches, nmatches, sizeof(char *), + (int (*) _((const void *, const void *)))strbpcmp); + + /* And delete the ones that occur more than once. */ + for (ap = cp = amatches; *ap; ap++) { + *cp++ = *ap; + for (bp = ap; bp[1] && !strcmp(*ap, bp[1]); bp++); + ap = bp; + } + *cp = NULL; + nmatches = arrlen(amatches); +} + +/* Handle the case were we found more than one match. */ + +/**/ +static void +do_ambiguous(void) +{ + int p = (usemenu || ispattern), atend = (cs == we); + int inv = 0; + + menucmp = 0; + + /* If we have to insert the first match, call do_single(). This is * + * how REC_EXACT takes effect. We effectively turn the ambiguous * + * completion into an unambiguous one. */ + if (shortest && shortl == 0 && isset(RECEXACT) && + (usemenu == 0 || unset(AUTOMENU))) { + do_single(shortest); + invalidatelist(); + return; + } + /* Setting lastambig here means that the completion is ambiguous and * + * AUTO_MENU might want to start a menu completion next time round, * + * but this might be overridden below if we can complete an * + * unambiguous prefix. */ + lastambig = 1; + if(p) { + /* p is set if we are in a position to start using menu completion * + * due to one of the menu completion options, or due to the * + * menu-complete-word command, or due to using GLOB_COMPLETE which * + * does menu-style completion regardless of the setting of the * + * normal menu completion options. */ + do_ambig_menu(); + } else { + /* Sort-of general case: we have an ambiguous completion, and aren't * + * starting menu completion or doing anything really weird. We need * + * to insert any unambiguous prefix and suffix, if possible. */ + if(ab) + inststrlen(firstm, 1, ab); + if(ae && !atend) + inststrlen(firstm + strlen(firstm) - ae, 0, ae); + if(ab || (ae && !atend)) + inv = 1; + /* If the LIST_AMBIGUOUS option (meaning roughly `show a list only * + * if the completion is completely ambiguous') is set, and some * + * prefix was inserted, return now, bypassing the list-displaying * + * code. On the way, invalidate the list and note that we don't * + * want to enter an AUTO_MENU imediately. */ + if(isset(LISTAMBIGUOUS) && inv) { + invalidatelist(); + lastambig = 0; + return; + } + } + /* At this point, we might want a completion listing. Show the listing * + * if it is needed. */ + if (isset(LISTBEEP)) + feep(); + if (isset(AUTOLIST) && !amenu && !showinglist) + showinglist = -2; + if(inv) + invalidatelist(); +} + +/* This is a stat that ignores backslashes in the filename. The `ls' * + * parameter says if we have to do lstat() or stat(). I think this * + * should instead be done by use of a general function to expand a * + * filename (stripping backslashes), combined with the actual * + * (l)stat(). */ + +/**/ +static int +ztat(char *nam, struct stat *buf, int ls) +{ + char b[PATH_MAX], *p; + + for (p = b; p < b + sizeof(b) - 1 && *nam; nam++) + if (*nam == '\\' && nam[1]) + *p++ = *++nam; + else + *p++ = *nam; + *p = '\0'; + + return ls ? lstat(b, buf) : stat(b, buf); +} + +/* Insert a single match in the command line. */ + +/**/ +static void +do_single(char *str) +{ + int l; + int havesuff = 0; + + fixsuffix(); + + if (!menucur) { + /* We are currently not in a menu-completion, * + * so set the position variables. */ + if (ispattern) { + cs = we; + menupos = wb; + } else + menupos = cs; + menuwe = (cs == we) || isset(ALWAYSTOEND); + menuend = we; + } + /* If we are already in a menu-completion or if we have done a * + * glob completion, we have to delete some of the stuff on the * + * command line. */ + if (menucur) { + if (menuinsc) { + cs = menuend + lsl; + foredel(menuinsc); + } + l = menulen; + } else if (ispattern) + l = we - wb; + else + l = 0; + + menuinsc = 0; + cs = menupos; + foredel(l); + + /* And than we insert the new string. */ + inststrlen(str, 1, menulen = strlen(str)); + menuend = cs; + + cs += lsl; + + if (ccsuffix) { + /* There is a compctl -S suffix. Add it. */ + if (!(haswhat & HAS_SUFFIX) && *ccsuffix) { + havesuff = 1; + inststr(ccsuffix); + menuinsc = ztrlen(ccsuffix); + if (remsuffix && menuwe) + makesuffix(menuinsc); + } + havesuff = 1; + } else { + /* There is no user-specified suffix, * + * so generate one automagically. */ + if(parampre && parambr) { + /*{{*/ + /* Completing a parameter in braces. Add a removable `}' suffix. */ + inststrlen("}", 1, 1); + menuinsc++; + } + if(!(haswhat & HAS_MISC) || + (parampre && isset(AUTOPARAMSLASH))) { + /* If we have only filenames or we completed a parameter name * + * and AUTO_PARAM_SLASH is set, lets see if it is a directory. * + * If it is, we append a slash. */ + char *p; + struct stat buf; + + /* Build the path name. */ + if (ispattern || ic || parampre) { + int ne = noerrs; + + noerrs = 1; + + if (parampre) { + int pl = strlen(parampre); + p = (char *) ncalloc(pl + strlen(lpre) + strlen(str) + + strlen(lsuf) + 1); + sprintf(p, "%s%s%s%s", parampre, lpre, str, lsuf); + if (pl && p[pl-1] == Inbrace) + strcpy(p+pl-1, p+pl); + } + else if (ic) { + p = (char *) ncalloc(strlen(ppre) + strlen(fpre) + strlen(str) + + strlen(fsuf) + strlen(psuf) + 2); + sprintf(p, "%c%s%s%s%s%s", ic, + ppre, fpre, str, fsuf, psuf); + } + else + p = dupstring(str); + parsestr(p); + if (ic) + *p = ic; + singsub(&p); + + noerrs = ne; + } else { + p = (char *) ncalloc((prpre ? strlen(prpre) : 0) + strlen(fpre) + + strlen(str) + strlen(fsuf) + strlen(psuf) + 3); + sprintf(p, "%s%s%s%s%s", + (prpre && *prpre) ? prpre : "./", fpre, str, + fsuf, psuf); + } + /* And do the stat. */ + if (!ztat(p, &buf, 0) && S_ISDIR(buf.st_mode)) { + /* It is a directory, so add the slash. */ + havesuff = 1; + inststrlen("/", 1, 1); + menuinsc++; + if(menuwe && isset(AUTOREMOVESLASH)) { + makesuffix(1); + suffixlen['/'] = 1; + } + } + } + } + /* If completing in a brace expansion... */ + if(complinbrace) { + if(havesuff) { + /*{{*/ + /* If a suffix was added, and is removable, let * + * `,' and `}' remove it. */ + if(isset(AUTOPARAMKEYS)) + suffixlen[','] = suffixlen['}'] = suffixlen[256]; + } else { + /*{{*/ + /* Otherwise, add a `,' suffix, and let `}' remove it. */ + havesuff = 1; + inststrlen(",", 1, 1); + menuinsc++; + if(menuwe && isset(AUTOPARAMKEYS)) + suffixlen[','] = suffixlen['}'] = 1; + } + } else if(!menucmp && !havesuff) { + /* If we didn't add a suffix, add a space, unless we are * + * doing menu completion. */ + inststrlen(" ", 1, 1); + menuinsc++; + if(menuwe) + makesuffix(1); + } + if(menuwe && parampre && isset(AUTOPARAMKEYS)) + makeparamsuffix(parambr, menuinsc); + + if (!menuwe) + cs = menuend; +} + +/* This handles the beginning of menu-completion. */ + +/**/ +static void +do_ambig_menu(void) +{ + menucmp = 1; + menucur = NULL; + do_single(amatches[0]); + menucur = amatches; +} + +/* Return the length of the common prefix of s and t. */ + +/**/ +int +pfxlen(char *s, char *t) +{ + int i = 0; + + while (*s && *s == *t) + s++, t++, i++; + return i; +} + +/* Return the length of the common suffix of s and t. */ + +/**/ +static int +sfxlen(char *s, char *t) +{ + if (*s && *t) { + int i = 0; + char *s2 = s + strlen(s) - 1, *t2 = t + strlen(t) - 1; + + while (s2 >= s && t2 >= t && *s2 == *t2) + s2--, t2--, i++; + + return i; + } else + return 0; +} + +/* This is used to print the explanation string. * + * It returns the number of lines printed. */ + +/**/ +static int +printfmt(char *fmt, int n, int dopr) +{ + char *p = fmt, nc[DIGBUFSIZE]; + int l = 0, cc = 0; + + for (; *p; p++) { + /* Handle the `%' stuff (%% == %, %n == <number of matches>). */ + if (*p == '%') { + if (*++p) { + switch (*p) { + case '%': + if (dopr) + putc('%', shout); + cc++; + break; + case 'n': + sprintf(nc, "%d", n); + if (dopr) + fprintf(shout, nc); + cc += strlen(nc); + break; + } + } else + break; + } else { + cc++; + if (*p == '\n') { + l += 1 + (cc / columns); + cc = 0; + } + if (dopr) + putc(*p, shout); + } + } + + return l + (cc / columns); +} + +/* List the matches. Note that the list entries are metafied. */ + +/**/ +void +listmatches(void) +{ + int longest = 1, fct, fw, colsz, t0, t1, ct, up, cl, xup = 0; + int off = 0, boff = 0, nboff = 0; + int of = (!aylist && isset(LISTTYPES) && !(haswhat & HAS_MISC)); + char **arr, **ap, sav; + int nfpl, nfsl, nlpl, nlsl; + int listmax = getiparam("LISTMAX"), litnl = 0; + size_t (*strlenfn) _((char const *)); + +#ifdef DEBUG + /* Sanity check */ + if(!validlist) { + showmsg("BUG: listmatches called with bogus list"); + return; + } +#endif + + /* Calculate lengths of prefixes/suffixes to be added */ + nfpl = fpre ? niceztrlen(fpre) : 0; + nfsl = fsuf ? niceztrlen(fsuf) : 0; + nlpl = lpre ? niceztrlen(lpre) : 0; + nlsl = lsuf ? niceztrlen(lsuf) : 0; + + /* Calculate the lengths of the prefixes/suffixes we have to ignore + during printing. */ + if (ispattern && !aylist && !(haswhat & (HAS_MISC | HAS_PATHPAT))) { + if (ppre && *ppre) + off = strlen(ppre); + if (psuf && *psuf) { + boff = strlen(psuf); + nboff = niceztrlen(psuf); + } + } + + /* Set the cursor below the prompt. */ + trashzle(); + showinglist = 0; + + clearflag = (isset(USEZLE) && !termflags && + (isset(ALWAYSLASTPROMPT) && zmult == 1)) || + (unset(ALWAYSLASTPROMPT) && zmult != 1); + + /* just to keep gcc happy */ + fw = colsz = up = 0; + if (aylist) { + arr = aylist; + /* If no literal newlines, the remaining code should use strlen() */ + strlenfn = (size_t (*) _((char const *)))strlen; + + /* The hard bit here is that we are handling newlines literally. * + * In fact, we are in principle handling all characters literally, * + * but it's quite enough work with just newlines. * + * If there are such, we give up trying to print the list as * + * columns and print as rows, counting the extra newlines. */ + ct = 0; + for (ap = arr; *ap; ap++) { + ct++; + if (strchr(*ap, '\n')) + litnl++; + } + if (litnl) { + colsz = ct; + up = colsz + nlnct - clearflag; + /* Count real newlines, as well as overflowing lines. */ + for (ap = arr; *ap; ap++) { + char *nlptr, *sptr = *ap; + while (sptr && *sptr) { + up += (nlptr = strchr(sptr, '\n')) + ? 1 + (nlptr-sptr)/columns + : strlen(sptr)/columns; + sptr = nlptr ? nlptr+1 : NULL; + } + } + } + } else { + arr = amatches; + ct = nmatches; + strlenfn = niceztrlen; + } + + + if (!litnl) { + /* Calculate the column width, the number of columns and the + number of lines. */ + for (ap = arr; *ap; ap++) + if ((cl = strlenfn(*ap + off) - nboff + + ((ispattern || aylist) ? 0 : + (!(haswhat & HAS_MISC) ? + nfpl + nfsl : nlpl + nlsl))) > longest) + longest = cl; + if (of) + longest++; + + fw = longest + 2; + fct = (columns + 1) / fw; + if (fct == 0) { + fct = 1; + colsz = ct; + up = colsz + nlnct - clearflag; + for (ap = arr; *ap; ap++) + up += (strlenfn(*ap + off) - nboff + of + + ((ispattern || aylist) ? 0 : + (!(haswhat & HAS_MISC) ? + nfpl + nfsl : nlpl + nlsl))) / columns; + } else { + colsz = (ct + fct - 1) / fct; + up = colsz + nlnct - clearflag + (ct == 0); + } + } + + /* Print the explanation string, if any. */ + if (expl) { + xup = printfmt(expl, ct, 1) + 1; + putc('\n', shout); + up += xup; + } + + /* Maybe we have to ask if the user wants to see the list. */ + if ((listmax && ct > listmax) || (!listmax && up >= lines)) { + int qup; + setterm(); + qup = printfmt("zsh: do you wish to see all %n possibilities? ", ct, 1); + fflush(shout); + if (getzlequery() != 'y') { + if (clearflag) { + putc('\r', shout); + tcmultout(TCUP, TCMULTUP, qup); + if (tccan(TCCLEAREOD)) + tcout(TCCLEAREOD); + tcmultout(TCUP, TCMULTUP, nlnct + xup); + } else + putc('\n', shout); + return; + } + if (clearflag) { + putc('\r', shout); + tcmultout(TCUP, TCMULTUP, qup); + if (tccan(TCCLEAREOD)) + tcout(TCCLEAREOD); + } else + putc('\n', shout); + settyinfo(&shttyinfo); + } + + /* Now print the matches. */ + for (t1 = 0; t1 != colsz; t1++) { + ap = arr + t1; + if (of) { + /* We have to print the file types. */ + while (*ap) { + int t2; + char *pb; + struct stat buf; + + /* Build the path name for the stat. */ + if (ispattern) { + int cut = strlen(*ap) - boff; + + sav = ap[0][cut]; + ap[0][cut] = '\0'; + nicezputs(*ap + off, shout); + t2 = niceztrlen(*ap + off); + ap[0][cut] = sav; + pb = *ap; + } else { + nicezputs(fpre, shout); + nicezputs(*ap, shout); + nicezputs(fsuf, shout); + t2 = nfpl + niceztrlen(*ap) + nfsl; + pb = (char *) halloc((prpre ? strlen(prpre) : 0) + 3 + + strlen(fpre) + strlen(*ap) + strlen(fsuf)); + sprintf(pb, "%s%s%s%s", + (prpre && *prpre) ? prpre : "./", fpre, *ap, fsuf); + } + if (ztat(pb, &buf, 1)) + putc(' ', shout); + else + /* Print the file type character. */ + putc(file_type(buf.st_mode), shout); + for (t0 = colsz; t0 && *ap; t0--, ap++); + if (*ap) + /* And add spaces to make the columns aligned. */ + for (++t2; t2 < fw; t2++) + putc(' ', shout); + } + } else + while (*ap) { + int t2; + + if (aylist) { + zputs(*ap, shout); + t2 = strlen(*ap); + } else if (ispattern) { + int cut = strlen(*ap) - boff; + + sav = ap[0][cut]; + ap[0][cut] = '\0'; + nicezputs(*ap + off, shout); + t2 = niceztrlen(*ap + off); + ap[0][cut] = sav; + } else if (!(haswhat & HAS_MISC)) { + nicezputs(fpre, shout); + nicezputs(*ap, shout); + nicezputs(fsuf, shout); + t2 = nfpl + niceztrlen(*ap) + nfsl; + } else { + nicezputs(lpre, shout); + nicezputs(*ap, shout); + nicezputs(lsuf, shout); + t2 = nlpl + niceztrlen(*ap) + nlsl; + } + for (t0 = colsz; t0 && *ap; t0--, ap++); + if (*ap) + for (; t2 < fw; t2++) + putc(' ', shout); + } + if (t1 != colsz - 1 || !clearflag) + putc('\n', shout); + } + if (clearflag) + /* Move the cursor up to the prompt, if always_last_prompt * + * is set and all that... */ + if (up < lines) { + tcmultout(TCUP, TCMULTUP, up); + showinglist = -1; + } else + clearflag = 0, putc('\n', shout); +} + +/* This is used to print expansions. */ + +/**/ +void +listlist(LinkList l) +{ + int hw = haswhat, ip = ispattern; + char *lp = lpre, *ls = lsuf; + int nm = nmatches, vl = validlist; + char **am = amatches, **ay = aylist; + char *ex = expl; + + haswhat = HAS_MISC; + ispattern = 0; + validlist = 1; + lpre = lsuf = ""; + aylist = NULL; + expl = NULL; + + makearray(l); + listmatches(); + showinglist = 0; + + expl = ex; + amatches = am; + aylist = ay; + nmatches = nm; + validlist = vl; + lpre = lp; + lsuf = ls; + ispattern = ip; + haswhat = hw; +} + +/* Expand the history references. */ + +/**/ +int +doexpandhist(void) +{ + unsigned char *ol; + int oll, ocs, ne = noerrs, err; + + DPUTS(useheap, "BUG: useheap in doexpandhist()"); + HEAPALLOC { + pushheap(); + metafy_line(); + oll = ll; + ocs = cs; + ol = (unsigned char *)dupstring((char *)line); + expanding = 1; + excs = cs; + ll = cs = 0; + lexsave(); + /* We push ol as it will remain unchanged */ + inpush((char *) ol, 0, NULL); + strinbeg(); + noaliases = 1; + noerrs = 1; + exlast = inbufct; + do { + ctxtlex(); + } while (tok != ENDINPUT && tok != LEXERR); + stophist = 2; + while (!lexstop) + hgetc(); + /* We have to save errflags because it's reset in lexrestore. Since * + * noerrs was set to 1 errflag is true if there was a habort() which * + * means that the expanded string is unusable. */ + err = errflag; + noerrs = ne; + noaliases = 0; + strinend(); + inpop(); + zleparse = 0; + lexrestore(); + expanding = 0; + + if (!err) { + cs = excs; + if (strcmp((char *)line, (char *)ol)) { + unmetafy_line(); + /* For vi mode -- reset the beginning-of-insertion pointer * + * to the beginning of the line. This seems a little silly, * + * if we are, for example, expanding "exec !!". */ + if (viinsbegin > findbol()) + viinsbegin = findbol(); + popheap(); + LASTALLOC_RETURN 1; + } + } + + strcpy((char *)line, (char *)ol); + ll = oll; + cs = ocs; + unmetafy_line(); + + popheap(); + } LASTALLOC; + return 0; +} + +/**/ +void +magicspace(void) +{ + c = ' '; + selfinsert(); + doexpandhist(); +} + +/**/ +void +expandhistory(void) +{ + if (!doexpandhist()) + feep(); +} + +static int cmdwb, cmdwe; + +/**/ +static char * +getcurcmd(void) +{ + int curlincmd; + char *s = NULL; + + DPUTS(useheap, "BUG: useheap in getcurcmd()"); + HEAPALLOC { + zleparse = 2; + lexsave(); + metafy_line(); + inpush(dupstrspace((char *) line), 0, NULL); + unmetafy_line(); + strinbeg(); + pushheap(); + do { + curlincmd = incmdpos; + ctxtlex(); + if (tok == ENDINPUT || tok == LEXERR) + break; + if (tok == STRING && curlincmd) { + zsfree(s); + s = ztrdup(tokstr); + cmdwb = ll - wordbeg; + cmdwe = ll + 1 - inbufct; + } + } + while (tok != ENDINPUT && tok != LEXERR && zleparse); + popheap(); + strinend(); + inpop(); + errflag = zleparse = 0; + lexrestore(); + } LASTALLOC; + return s; +} + +/**/ +void +processcmd(void) +{ + char *s; + int m = zmult; + + s = getcurcmd(); + if (!s) { + feep(); + return; + } + zmult = 1; + pushline(); + zmult = m; + inststr(bindk->nam); + inststr(" "); + untokenize(s); + HEAPALLOC { + inststr(quotename(s, NULL, NULL, NULL)); + } LASTALLOC; + zsfree(s); + done = 1; +} + +/**/ +void +expandcmdpath(void) +{ + int oldcs = cs, na = noaliases; + char *s, *str; + + noaliases = 1; + s = getcurcmd(); + noaliases = na; + if (!s || cmdwb < 0 || cmdwe < cmdwb) { + feep(); + return; + } + str = findcmd(s); + zsfree(s); + if (!str) { + feep(); + return; + } + cs = cmdwb; + foredel(cmdwe - cmdwb); + spaceinline(strlen(str)); + strncpy((char *)line + cs, str, strlen(str)); + cs = oldcs; + if (cs >= cmdwe - 1) + cs += cmdwe - cmdwb + strlen(str); + if (cs > ll) + cs = ll; + zsfree(str); +} + +/* Extra function added by AR Iano-Fletcher. */ +/* This is a expand/complete in the vein of wash. */ + +/**/ +void +expandorcompleteprefix(void) +{ + comppref = 1; + expandorcomplete(); + comppref = 0; +} diff --git a/Src/Zle/zle_utils.c b/Src/Zle/zle_utils.c new file mode 100644 index 000000000..8fe3e7f0b --- /dev/null +++ b/Src/Zle/zle_utils.c @@ -0,0 +1,650 @@ +/* + * zle_utils.c - miscellaneous line editor utilities + * + * 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 "zle.mdh" +#include "zle_utils.pro" + +/* Primary cut buffer */ + +/**/ +struct cutbuffer cutbuf; + +/* Emacs-style kill buffer ring */ + +/**/ +struct cutbuffer kring[KRINGCT]; +/**/ +int kringnum; + +/* Vi named cut buffers. 0-25 are the named buffers "a to "z, and * + * 26-34 are the numbered buffer stack "1 to "9. */ + +/**/ +struct cutbuffer vibuf[35]; + +/* the line before last mod (for undo purposes) */ + +/**/ +char *lastline; +/**/ +int lastlinesz, lastll; + +/* size of line buffer */ + +/**/ +int linesz; + +/* make sure that the line buffer has at least sz chars */ + +/**/ +void +sizeline(int sz) +{ + while (sz > linesz) + line = (unsigned char *)realloc(line, (linesz *= 4) + 2); +} + +/* insert space for ct chars at cursor position */ + +/**/ +void +spaceinline(int ct) +{ + int i; + + sizeline(ct + ll); + for (i = ll; --i >= cs;) + line[i + ct] = line[i]; + ll += ct; + line[ll] = '\0'; + + if (mark > cs) + mark += ct; +} + +/**/ +static void +shiftchars(int to, int cnt) +{ + if (mark >= to + cnt) + mark -= cnt; + else if (mark > to) + mark = to; + + while (to + cnt < ll) { + line[to] = line[to + cnt]; + to++; + } + line[ll = to] = '\0'; +} + +/**/ +void +backkill(int ct, int dir) +{ + int i = (cs -= ct); + + cut(i, ct, dir); + shiftchars(i, ct); +} + +/**/ +void +forekill(int ct, int dir) +{ + int i = cs; + + cut(i, ct, dir); + shiftchars(i, ct); +} + +/**/ +void +cut(int i, int ct, int dir) +{ + if (zmod.flags & MOD_VIBUF) { + struct cutbuffer *b = &vibuf[zmod.vibuf]; + + if (!(zmod.flags & MOD_VIAPP) || !b->buf) { + zfree(b->buf, b->len); + b->buf = (char *)zalloc(ct); + memcpy(b->buf, (char *) line + i, ct); + b->len = ct; + b->flags = vilinerange ? CUTBUFFER_LINE : 0; + } else { + int len = b->len; + + if(vilinerange) + b->flags |= CUTBUFFER_LINE; + b->buf = realloc(b->buf, ct + len + !!(b->flags & CUTBUFFER_LINE)); + if (b->flags & CUTBUFFER_LINE) + b->buf[len++] = '\n'; + memcpy(b->buf + len, (char *) line + i, ct); + b->len = len + ct; + } + return; + } else { + /* Save in "1, shifting "1-"8 along to "2-"9 */ + int n; + zfree(vibuf[34].buf, vibuf[34].len); + for(n=34; n>26; n--) + vibuf[n] = vibuf[n-1]; + vibuf[26].buf = (char *)zalloc(ct); + memcpy(vibuf[26].buf, (char *) line + i, ct); + vibuf[26].len = ct; + vibuf[26].flags = vilinerange ? CUTBUFFER_LINE : 0; + } + if (!cutbuf.buf) { + cutbuf.buf = ztrdup(""); + cutbuf.len = cutbuf.flags = 0; + } else if (!(lastcmd & ZLE_KILL)) { + kringnum = (kringnum + 1) % KRINGCT; + if (kring[kringnum].buf) + free(kring[kringnum].buf); + kring[kringnum] = cutbuf; + cutbuf.buf = ztrdup(""); + cutbuf.len = cutbuf.flags = 0; + } + if (dir) { + char *s = (char *)zalloc(cutbuf.len + ct); + + memcpy(s, (char *) line + i, ct); + memcpy(s + ct, cutbuf.buf, cutbuf.len); + free(cutbuf.buf); + cutbuf.buf = s; + cutbuf.len += ct; + } else { + cutbuf.buf = realloc(cutbuf.buf, cutbuf.len + ct); + memcpy(cutbuf.buf + cutbuf.len, (char *) line + i, ct); + cutbuf.len += ct; + } + if(vilinerange) + cutbuf.flags |= CUTBUFFER_LINE; + else + cutbuf.flags &= ~CUTBUFFER_LINE; +} + +/**/ +void +backdel(int ct) +{ + shiftchars(cs -= ct, ct); +} + +/**/ +void +foredel(int ct) +{ + shiftchars(cs, ct); +} + +/**/ +void +setline(char const *s) +{ + sizeline(strlen(s)); + strcpy((char *) line, s); + unmetafy((char *) line, &ll); + if ((cs = ll) && invicmdmode()) + cs--; +} + +/**/ +int +findbol(void) +{ + int x = cs; + + while (x > 0 && line[x - 1] != '\n') + x--; + return x; +} + +/**/ +int +findeol(void) +{ + int x = cs; + + while (x != ll && line[x] != '\n') + x++; + return x; +} + +/**/ +void +findline(int *a, int *b) +{ + *a = findbol(); + *b = findeol(); +} + +/* Search for needle in haystack. Haystack is a metafied string while * + * needle is unmetafied and len-long. Start the search at position * + * pos. Search forward if dir > 0 otherwise search backward. */ + +/**/ +char * +hstrnstr(char *haystack, int pos, char *needle, int len, int dir, int sens) +{ + char *s = haystack + pos; + + if (dir > 0) { + while (*s) { + if (metadiffer(s, needle, len) < sens) + return s; + s += 1 + (*s == Meta); + } + } else { + for (;;) { + if (metadiffer(s, needle, len) < sens) + return s; + if (s == haystack) + break; + s -= 1 + (s != haystack+1 && s[-2] == Meta); + } + } + return NULL; +} + +/* Query the user, and return a single character response. The * + * question is assumed to have been printed already, and the * + * cursor is left immediately after the response echoed. * + * (Might cause a problem if this takes it onto the next line.) * + * <Tab> is interpreted as 'y'; any other control character is * + * interpreted as 'n'. If there are any characters in the * + * buffer, this is taken as a negative response, and no * + * characters are read. Case is folded. */ + +/**/ +int +getzlequery(void) +{ + int c; +#ifdef FIONREAD + int val; + + /* check for typeahead, which is treated as a negative response */ + ioctl(SHTTY, FIONREAD, (char *)&val); + if (val) { + putc('n', shout); + return 'n'; + } +#endif + + /* get a character from the tty and interpret it */ + c = getkey(0); + if (c == '\t') + c = 'y'; + else if (icntrl(c) || c == EOF) + c = 'n'; + else + c = tulower(c); + + /* echo response and return */ + putc(c, shout); + return c; +} + +/* Format a string, keybinding style. */ + +/**/ +char * +bindztrdup(char *str) +{ + int c, len = 1; + char *buf, *ptr, *ret; + + for(ptr = str; *ptr; ptr++) { + c = *ptr == Meta ? STOUC(*++ptr) ^ 32 : STOUC(*ptr); + if(c & 0x80) { + len += 3; + c &= 0x7f; + } + if(c < 32 || c == 0x7f) { + len++; + c ^= 64; + } + len += c == '\\' || c == '^'; + len++; + } + ptr = buf = zalloc(len); + for(; *str; str++) { + c = *str == Meta ? STOUC(*++str) ^ 32 : STOUC(*str); + if(c & 0x80) { + *ptr++ = '\\'; + *ptr++ = 'M'; + *ptr++ = '-'; + c &= 0x7f; + } + if(c < 32 || c == 0x7f) { + *ptr++ = '^'; + c ^= 64; + } + if(c == '\\' || c == '^') + *ptr++ = '\\'; + *ptr++ = c; + } + *ptr = 0; + ret = dquotedztrdup(buf); + zsfree(buf); + return ret; +} + +/* Display a metafied string, keybinding-style. */ + +/**/ +int +printbind(char *str, FILE *stream) +{ + char *b = bindztrdup(str); + int ret = zputs(b, stream); + + zsfree(b); + return ret; +} + +/* Display a message where the completion list normally goes. * + * The message must be metafied. */ + +/**/ +void +showmsg(char const *msg) +{ + char const *p; + int up = 0, cc = 0, c; + + trashzle(); + clearflag = isset(USEZLE) && !termflags && isset(ALWAYSLASTPROMPT); + + for(p = msg; (c = *p); p++) { + if(c == Meta) + c = *++p ^ 32; + if(c == '\n') { + putc('\n', shout); + up += 1 + cc / columns; + cc = 0; + } else { + char const *n = nicechar(c); + fputs(n, shout); + cc += strlen(n); + } + } + up += cc / columns; + + if (clearflag) { + putc('\r', shout); + tcmultout(TCUP, TCMULTUP, up + nlnct); + } else + putc('\n', shout); + showinglist = 0; +} + +/* handle the error flag */ + +/**/ +void +feep(void) +{ + feepflag = 1; +} + +/**/ +void +handlefeep(void) +{ + if(feepflag) + beep(); + feepflag = 0; +} + +/***************/ +/* undo system */ +/***************/ + +/* head of the undo list, and the current position */ + +static struct change *changes, *curchange; + +/* list of pending changes, not yet in the undo system */ + +static struct change *nextchanges, *endnextchanges; + +/**/ +void +initundo(void) +{ + nextchanges = NULL; + changes = curchange = zalloc(sizeof(*curchange)); + curchange->prev = curchange->next = NULL; + curchange->del = curchange->ins = NULL; + lastline = zalloc(lastlinesz = linesz); + memcpy(lastline, line, lastll = ll); +} + +/**/ +void +freeundo(void) +{ + freechanges(changes); + freechanges(nextchanges); + zfree(lastline, lastlinesz); +} + +/**/ +static void +freechanges(struct change *p) +{ + struct change *n; + + for(; p; p = n) { + n = p->next; + zsfree(p->del); + zsfree(p->ins); + zfree(p, sizeof(*p)); + } +} + +/* register pending changes in the undo system */ + +/**/ +void +handleundo(void) +{ + mkundoent(); + if(!nextchanges) + return; + setlastline(); + if(curchange->next) { + freechanges(curchange->next); + curchange->next = NULL; + zsfree(curchange->del); + zsfree(curchange->ins); + curchange->del = curchange->ins = NULL; + } + nextchanges->prev = curchange->prev; + if(curchange->prev) + curchange->prev->next = nextchanges; + else + changes = nextchanges; + curchange->prev = endnextchanges; + endnextchanges->next = curchange; + nextchanges = endnextchanges = NULL; +} + +/* add an entry to the undo system, if anything has changed */ + +/**/ +void +mkundoent(void) +{ + int pre, suf; + int sh = ll < lastll ? ll : lastll; + struct change *ch; + + if(lastll == ll && !memcmp(lastline, line, ll)) + return; + for(pre = 0; pre < sh && line[pre] == lastline[pre]; ) + pre++; + for(suf = 0; suf < sh - pre && + line[ll - 1 - suf] == lastline[lastll - 1 - suf]; ) + suf++; + ch = zalloc(sizeof(*ch)); + ch->next = NULL; + ch->hist = histline; + ch->off = pre; + if(suf + pre == lastll) + ch->del = NULL; + else + ch->del = metafy(lastline + pre, lastll - pre - suf, META_DUP); + if(suf + pre == ll) + ch->ins = NULL; + else + ch->ins = metafy((char *)line + pre, ll - pre - suf, META_DUP); + if(nextchanges) { + ch->flags = CH_PREV; + ch->prev = endnextchanges; + endnextchanges->flags |= CH_NEXT; + endnextchanges->next = ch; + } else { + nextchanges = ch; + ch->flags = 0; + ch->prev = NULL; + } + endnextchanges = ch; +} + +/* set lastline to match line */ + +/**/ +void +setlastline(void) +{ + if(lastlinesz != linesz) + lastline = realloc(lastline, lastlinesz = linesz); + memcpy(lastline, line, lastll = ll); +} + +/* move backwards through the change list */ + +/**/ +void +undo(void) +{ + handleundo(); + do { + if(!curchange->prev) { + feep(); + return; + } + unapplychange(curchange = curchange->prev); + } while(curchange->flags & CH_PREV); + setlastline(); +} + +/**/ +static void +unapplychange(struct change *ch) +{ + if(ch->hist != histline) { + remember_edits(); + setline(zle_get_event(histline = ch->hist)); + } + cs = ch->off; + if(ch->ins) + foredel(ztrlen(ch->ins)); + if(ch->del) { + char *c = ch->del; + + spaceinline(ztrlen(c)); + for(; *c; c++) + if(*c == Meta) + line[cs++] = STOUC(*++c) ^ 32; + else + line[cs++] = STOUC(*c); + } +} + +/* move forwards through the change list */ + +/**/ +void +redo(void) +{ + handleundo(); + do { + if(!curchange->next) { + feep(); + return; + } + applychange(curchange); + curchange = curchange->next; + } while(curchange->prev->flags & CH_NEXT); + setlastline(); +} + +/**/ +static void +applychange(struct change *ch) +{ + if(ch->hist != histline) { + remember_edits(); + setline(zle_get_event(histline = ch->hist)); + } + cs = ch->off; + if(ch->del) + foredel(ztrlen(ch->del)); + if(ch->ins) { + char *c = ch->ins; + + spaceinline(ztrlen(c)); + for(; *c; c++) + if(*c == Meta) + line[cs++] = STOUC(*++c) ^ 32; + else + line[cs++] = STOUC(*c); + } +} + +/* vi undo: toggle between the end of the undo list and the preceding point */ + +/**/ +void +viundochange(void) +{ + handleundo(); + if(curchange->next) { + do { + applychange(curchange); + curchange = curchange->next; + } while(curchange->next); + setlastline(); + } else + undo(); +} diff --git a/Src/Zle/zle_vi.c b/Src/Zle/zle_vi.c new file mode 100644 index 000000000..a599d8091 --- /dev/null +++ b/Src/Zle/zle_vi.c @@ -0,0 +1,925 @@ +/* + * zle_vi.c - vi-specific functions + * + * 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 "zle.mdh" +#include "zle_vi.pro" + +/* != 0 if we're getting a vi range */ + +/**/ +int virangeflag; + +/* kludge to get cw and dw to work right */ + +/**/ +int wordflag; + +/* != 0 if we're killing lines into a buffer, vi-style */ + +/**/ +int vilinerange; + +/* last vi change buffer, for vi change repetition */ + +/**/ +int vichgbufsz, vichgbufptr, vichgflag; + +/**/ +char *vichgbuf; + +/* point where vi insert mode was last entered */ + +/**/ +int viinsbegin; + +static struct modifier lastmod; +static int inrepeat, vichgrepeat; + +/**/ +static void +startvichange(int im) +{ + if (im != -1) { + insmode = im; + vichgflag = 1; + } + if (inrepeat) { + zmod = lastmod; + inrepeat = vichgflag = 0; + vichgrepeat = 1; + } else { + lastmod = zmod; + if (vichgbuf) + free(vichgbuf); + vichgbuf = (char *)zalloc(vichgbufsz = 16); + vichgbuf[0] = c; + vichgbufptr = 1; + vichgrepeat = 0; + } +} + +/**/ +static void +startvitext(int im) +{ + startvichange(im); + selectkeymap("main", 1); + undoing = 0; + viinsbegin = cs; +} + +/**/ +int +vigetkey(void) +{ + Keymap mn = openkeymap("main"); + char m[3], *str; + Thingy cmd; + + if((c = getkey(0)) == EOF) { + feep(); + return -1; + } + + m[0] = c; + metafy(m, 1, META_NOALLOC); + if(mn) + cmd = keybind(mn, m, &str); + else + cmd = t_undefinedkey; + + if (!cmd || cmd == Th(z_sendbreak)) { + feep(); + return -1; + } else if (cmd == Th(z_quotedinsert)) { + if ((c = getkey(0)) == EOF) { + feep(); + return -1; + } + } else if(cmd == Th(z_viquotedinsert)) { + char sav = line[cs]; + + line[cs] = '^'; + refresh(); + c = getkey(0); + line[cs] = sav; + if(c == EOF) { + feep(); + return -1; + } + } else if (cmd == Th(z_vicmdmode)) + return -1; + return c; +} + +/**/ +static int +getvirange(int wf) +{ + int pos = cs; + int mult1 = zmult, hist1 = histline; + Thingy k2; + + virangeflag = 1; + wordflag = wf; + /* Now we need to execute the movement command, to see where it * + * actually goes. virangeflag here indicates to the movement * + * function that it should place the cursor at the end of the * + * range, rather than where the cursor would actually go if it * + * were executed normally. This makes a difference to some * + * commands, but not all. For example, if searching forward * + * for a character, under normal circumstances the cursor lands * + * on the character. For a range, the range must include the * + * character, so the cursor gets placed after the character if * + * virangeflag is set. vi-match-bracket needs to change the * + * value of virangeflag under some circumstances, meaning that * + * we need to change the *starting* position. */ + zmod.flags &= ~MOD_TMULT; + do { + vilinerange = 0; + prefixflag = 0; + if (!(k2 = getkeycmd()) || (k2->flags & DISABLED) || + k2 == Th(z_sendbreak)) { + wordflag = 0; + virangeflag = 0; + feep(); + return -1; + } + if(k2 == bindk) + /* The command key is repeated: a number of lines is used. */ + dovilinerange(); + else + execzlefunc(k2); + if(vichgrepeat) + zmult = mult1; + else + zmult = mult1 * zmod.tmult; + } while(prefixflag); + wordflag = 0; + virangeflag = 0; + + /* It is an error to use a non-movement command to delimit the * + * range. We here reject the case where the command modified * + * the line, or selected a different history line. */ + if(histline != hist1 || ll != lastll || memcmp(line, lastline, ll)) { + histline = hist1; + memcpy(line, lastline, ll = lastll); + cs = pos; + feep(); + return -1; + } + + /* Can't handle an empty file. Also, if the movement command * + * failed, or didn't move, it is an error. */ + if (!ll || (cs == pos && virangeflag != 2)) { + feep(); + return -1; + } + + /* vi-match-bracket changes the value of virangeflag when * + * moving to the opening bracket, meaning that we need to * + * change the *starting* position. */ + if(virangeflag == -1) + pos++; + + /* Get the range the right way round. cs is placed at the * + * start of the range, and pos (the return value of this * + * function) is the end. */ + if (cs > pos) { + int tmp = cs; + cs = pos; + pos = tmp; + } + + /* Was it a line-oriented move? If so, the command will have set * + * the vilinerange flag. In this case, entire lines are taken, * + * rather than just the sequence of characters delimited by pos * + * and cs. The terminating newline is left out of the range, * + * which the real command must deal with appropriately. At this * + * point we just need to make the range encompass entire lines. */ + if(vilinerange) { + int newcs = findbol(); + cs = pos; + pos = findeol(); + cs = newcs; + } + return pos; +} + +/**/ +static void +dovilinerange(void) +{ + int pos = cs, n = zmult; + + /* A number of lines is taken as the range. The current line * + * is included. If the repeat count is positive the lines go * + * downward, otherwise upward. The repeat count gives the * + * number of lines. */ + vilinerange = 1; + if (!n) { + feep(); + return; + } + if (n > 0) { + while(n-- && cs <= ll) + cs = findeol() + 1; + if (n != -1) { + cs = pos; + feep(); + return; + } + cs--; + } else { + while(n++ && cs >= 0) + cs = findbol() - 1; + if (n != 1) { + cs = pos; + feep(); + return; + } + cs++; + } + virangeflag = 2; +} + +/**/ +void +viaddnext(void) +{ + if (cs != findeol()) + cs++; + startvitext(1); +} + +/**/ +void +viaddeol(void) +{ + cs = findeol(); + startvitext(1); +} + +/**/ +void +viinsert(void) +{ + startvitext(1); +} + +/**/ +void +viinsertbol(void) +{ + vifirstnonblank(); + startvitext(1); +} + +/**/ +void +videlete(void) +{ + int c2; + + startvichange(1); + if ((c2 = getvirange(0)) != -1) { + forekill(c2 - cs, 0); + if (vilinerange && ll) { + if (cs == ll) + cs--; + foredel(1); + vifirstnonblank(); + } + } + vichgflag = 0; +} + +/**/ +void +videletechar(void) +{ + int n = zmult; + + startvichange(-1); + /* handle negative argument */ + if (n < 0) { + zmult = -n; + vibackwarddeletechar(); + zmult = n; + return; + } + /* it is an error to be on the end of line */ + if (cs == ll || line[cs] == '\n') { + feep(); + return; + } + /* Put argument into the acceptable range -- it is not an error to * + * specify a greater count than the number of available characters. */ + if (n > findeol() - cs) + n = findeol() - cs; + /* do the deletion */ + forekill(n, 0); +} + +/**/ +void +vichange(void) +{ + int c2; + + startvichange(1); + if ((c2 = getvirange(1)) != -1) { + forekill(c2 - cs, 0); + selectkeymap("main", 1); + viinsbegin = cs; + undoing = 0; + } +} + +/**/ +void +visubstitute(void) +{ + int n = zmult; + + startvichange(1); + if (n < 0) { + feep(); + return; + } + /* it is an error to be on the end of line */ + if (cs == ll || line[cs] == '\n') { + feep(); + return; + } + /* Put argument into the acceptable range -- it is not an error to * + * specify a greater count than the number of available characters. */ + if (n > findeol() - cs) + n = findeol() - cs; + /* do the substitution */ + forekill(n, 0); + startvitext(1); +} + +/**/ +void +vichangeeol(void) +{ + forekill(findeol() - cs, 0); + startvitext(1); +} + +/**/ +void +vichangewholeline(void) +{ + vifirstnonblank(); + vichangeeol(); +} + +/**/ +void +viyank(void) +{ + int oldcs = cs, c2; + + startvichange(1); + if ((c2 = getvirange(0)) != -1) + cut(cs, c2 - cs, 0); + vichgflag = 0; + cs = oldcs; +} + +/**/ +void +viyankeol(void) +{ + int x = findeol(); + + startvichange(-1); + if (x == cs) { + feep(); + return; + } + cut(cs, x - cs, 0); +} + +/**/ +void +viyankwholeline(void) +{ + int bol = findbol(), oldcs = cs; + int n = zmult; + + startvichange(-1); + if (n < 1) + return; + while(n--) { + if (cs > ll) { + feep(); + cs = oldcs; + return; + } + cs = findeol() + 1; + } + vilinerange = 1; + cut(bol, cs - bol - 1, 0); + cs = oldcs; +} + +/**/ +void +vireplace(void) +{ + startvitext(0); +} + +/* vi-replace-chars has some oddities relating to vi-repeat-change. In * + * the real vi, if one does 3r at the end of a line, it feeps without * + * reading the argument, and won't repeat the action. A successful rx * + * followed by 3. at the end of a line (or 3rx followed by . at the end * + * of a line) will obviously feep after the ., even though it has the * + * argument available. Here repeating is tied very closely to argument * + * reading, so some trickery is needed to emulate this. When repeating * + * a change, we always read the argument normally, even if the count * + * was bad. When recording a change for repeating, and a bad count is * + * given, we squash the repeat buffer to avoid repeating the partial * + * command; we've lost the previous change, but that can't be avoided * + * without a rewrite of the repeat code. */ + +/**/ +void +vireplacechars(void) +{ + int ch, n = zmult; + + startvichange(1); + /* check argument range */ + if (n < 1 || n + cs > findeol()) { + if(vichgrepeat) { + int ofeep = feepflag; + vigetkey(); + feepflag = ofeep; + } + if(vichgflag) { + free(vichgbuf); + vichgbuf = NULL; + vichgflag = 0; + } + feep(); + return; + } + /* get key */ + if((ch = vigetkey()) == -1) { + vichgflag = 0; + feep(); + return; + } + /* do change */ + if (ch == '\r' || ch == '\n') { + /* <return> handled specially */ + cs += n - 1; + backkill(n - 1, 0); + line[cs++] = '\n'; + } else { + while (n--) + line[cs++] = ch; + cs--; + } + vichgflag = 0; +} + +/**/ +void +vicmdmode(void) +{ + if (invicmdmode() || selectkeymap("vicmd", 0)) + feep(); + undoing = 1; + vichgflag = 0; + if (cs != findbol()) + cs--; +} + +/**/ +void +viopenlinebelow(void) +{ + cs = findeol(); + spaceinline(1); + line[cs++] = '\n'; + startvitext(1); +} + +/**/ +void +viopenlineabove(void) +{ + cs = findbol(); + spaceinline(1); + line[cs] = '\n'; + startvitext(1); +} + +/**/ +void +vioperswapcase(void) +{ + int oldcs, c2; + + /* get the range */ + startvichange(1); + if ((c2 = getvirange(0)) != -1) { + oldcs = cs; + /* swap the case of all letters within range */ + while (cs < c2) { + if (islower(line[cs])) + line[cs] = tuupper(line[cs]); + else if (isupper(line[cs])) + line[cs] = tulower(line[cs]); + cs++; + } + /* go back to the first line of the range */ + cs = oldcs; + vifirstnonblank(); + } + vichgflag = 0; +} + +/**/ +void +virepeatchange(void) +{ + /* make sure we have a change to repeat */ + if (!vichgbuf || vichgflag) { + feep(); + return; + } + /* restore or update the saved count and buffer */ + if (zmod.flags & MOD_MULT) { + lastmod.mult = zmod.mult; + lastmod.flags |= MOD_MULT; + } + if (zmod.flags & MOD_VIBUF) { + lastmod.vibuf = zmod.vibuf; + lastmod.flags = (lastmod.flags & ~MOD_VIAPP) | + MOD_VIBUF | (zmod.flags & MOD_VIAPP); + } + /* repeat the command */ + inrepeat = 1; + ungetkeys(vichgbuf, vichgbufptr); +} + +/**/ +void +viindent(void) +{ + int oldcs = cs, c2; + + /* get the range */ + startvichange(1); + if ((c2 = getvirange(0)) == -1) { + vichgflag = 0; + return; + } + vichgflag = 0; + /* must be a line range */ + if (!vilinerange) { + feep(); + cs = oldcs; + return; + } + oldcs = cs; + /* add a tab to the beginning of each line within range */ + while (cs < c2) { + spaceinline(1); + line[cs] = '\t'; + cs = findeol() + 1; + } + /* go back to the first line of the range */ + cs = oldcs; + vifirstnonblank(); +} + +/**/ +void +viunindent(void) +{ + int oldcs = cs, c2; + + /* get the range */ + startvichange(1); + if ((c2 = getvirange(0)) == -1) { + vichgflag = 0; + return; + } + vichgflag = 0; + /* must be a line range */ + if (!vilinerange) { + feep(); + cs = oldcs; + return; + } + oldcs = cs; + /* remove a tab from the beginning of each line within range */ + while (cs < c2) { + if (line[cs] == '\t') + foredel(1); + cs = findeol() + 1; + } + /* go back to the first line of the range */ + cs = oldcs; + vifirstnonblank(); +} + +/**/ +void +vibackwarddeletechar(void) +{ + int n = zmult; + + if (invicmdmode()) + startvichange(-1); + /* handle negative argument */ + if (n < 0) { + zmult = -n; + videletechar(); + zmult = n; + return; + } + /* It is an error to be at the beginning of the line, or (in * + * insert mode) to delete past the beginning of insertion. */ + if ((!invicmdmode() && cs - n < viinsbegin) || cs == findbol()) { + feep(); + return; + } + /* Put argument into the acceptable range -- it is not an error to * + * specify a greater count than the number of available characters. */ + if (n > cs - findbol()) + n = cs - findbol(); + /* do the deletion */ + backkill(n, 1); +} + +/**/ +void +vikillline(void) +{ + if (viinsbegin > cs) { + feep(); + return; + } + backdel(cs - viinsbegin); +} + +/**/ +void +viputbefore(void) +{ + Cutbuffer buf = &cutbuf; + int n = zmult; + + startvichange(-1); + if (n < 0) + return; + if (zmod.flags & MOD_VIBUF) + buf = &vibuf[zmod.vibuf]; + if (!buf->buf) { + feep(); + return; + } + if(buf->flags & CUTBUFFER_LINE) { + cs = findbol(); + spaceinline(buf->len + 1); + memcpy((char *)line + cs, buf->buf, buf->len); + line[cs + buf->len] = '\n'; + vifirstnonblank(); + } else { + while (n--) { + spaceinline(buf->len); + memcpy((char *)line + cs, buf->buf, buf->len); + cs += buf->len; + } + if (cs) + cs--; + } +} + +/**/ +void +viputafter(void) +{ + Cutbuffer buf = &cutbuf; + int n = zmult; + + startvichange(-1); + if (n < 0) + return; + if (zmod.flags & MOD_VIBUF) + buf = &vibuf[zmod.vibuf]; + if (!buf->buf) { + feep(); + return; + } + if(buf->flags & CUTBUFFER_LINE) { + cs = findeol(); + spaceinline(buf->len + 1); + line[cs++] = '\n'; + memcpy((char *)line + cs, buf->buf, buf->len); + vifirstnonblank(); + } else { + if (cs != findeol()) + cs++; + while (n--) { + spaceinline(buf->len); + memcpy((char *)line + cs, buf->buf, buf->len); + cs += buf->len; + } + if (cs) + cs--; + } + +} + +/**/ +void +vijoin(void) +{ + int x; + + startvichange(-1); + if ((x = findeol()) == ll) { + feep(); + return; + } + cs = x + 1; + for (x = 1; cs != ll && iblank(line[cs]); cs++, x++); + backdel(x); + if (cs && iblank(line[cs-1])) + cs--; + else { + spaceinline(1); + line[cs] = ' '; + } +} + +/**/ +void +viswapcase(void) +{ + int eol, n = zmult; + + startvichange(-1); + if (n < 1) + return; + eol = findeol(); + while (cs < eol && n--) { + if (islower(line[cs])) + line[cs] = tuupper(line[cs]); + else if (isupper(line[cs])) + line[cs] = tulower(line[cs]); + cs++; + } + if (cs && cs == eol) + cs--; +} + +/**/ +void +vicapslockpanic(void) +{ + beep(); + statusline = "press a lowercase key to continue"; + statusll = strlen(statusline); + refresh(); + while (!islower(getkey(0))); + statusline = NULL; +} + +/**/ +void +visetbuffer(void) +{ + int ch; + + if ((zmod.flags & MOD_VIBUF) || + (((ch = getkey(0)) < '1' || ch > '9') && + (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z'))) { + feep(); + return; + } + if (ch >= 'A' && ch <= 'Z') /* needed in cut() */ + zmod.flags |= MOD_VIAPP; + else + zmod.flags &= ~MOD_VIAPP; + zmod.vibuf = tulower(ch) + (idigit(ch) ? -'1' + 26 : -'a'); + zmod.flags |= MOD_VIBUF; + prefixflag = 1; +} + +/**/ +void +vikilleol(void) +{ + int n = findeol() - cs; + + startvichange(-1); + if (!n) { + /* error -- line already empty */ + feep(); + return; + } + /* delete to end of line */ + forekill(findeol() - cs, 0); +} + +/**/ +void +vipoundinsert(void) +{ + int oldcs = cs; + + startvichange(-1); + vifirstnonblank(); + if(line[cs] != '#') { + spaceinline(1); + line[cs] = '#'; + if(cs <= viinsbegin) + viinsbegin++; + cs = oldcs + (cs <= oldcs); + } else { + foredel(1); + if (cs < viinsbegin) + viinsbegin--; + cs = oldcs - (cs < oldcs); + } +} + +/**/ +void +viquotedinsert(void) +{ +#ifndef HAS_TIO + struct sgttyb sob; +#endif + + spaceinline(1); + line[cs] = '^'; + refresh(); +#ifndef HAS_TIO + sob = shttyinfo.sgttyb; + sob.sg_flags = (sob.sg_flags | RAW) & ~ECHO; + ioctl(SHTTY, TIOCSETN, &sob); +#endif + c = getkey(0); +#ifndef HAS_TIO + setterm(); +#endif + foredel(1); + if(c < 0) + feep(); + else + selfinsert(); +} + +/* the 0 key in vi: continue a repeat count in the manner of * + * digit-argument if possible, otherwise do vi-beginning-of-line. */ + +/**/ +void +vidigitorbeginningofline(void) +{ + if(zmod.flags & MOD_TMULT) + digitargument(); + else { + removesuffix(); + invalidatelist(); + vibeginningofline(); + } +} diff --git a/Src/Zle/zle_widget.sed b/Src/Zle/zle_widget.sed new file mode 100644 index 000000000..635322b42 --- /dev/null +++ b/Src/Zle/zle_widget.sed @@ -0,0 +1,7 @@ +/^ *W(/{ + s/[^,]*, *t_/ wi_/ + s/ *,.*/,/ + P + s/ wi_\(.*\),/#define w_\1 (\&widgets[wi_\1])/ + P +} diff --git a/Src/Zle/zle_word.c b/Src/Zle/zle_word.c new file mode 100644 index 000000000..923216ef8 --- /dev/null +++ b/Src/Zle/zle_word.c @@ -0,0 +1,477 @@ +/* + * zle_word.c - word-related editor functions + * + * 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 "zle.mdh" +#include "zle_word.pro" + +/**/ +void +forwardword(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + backwardword(); + zmult = n; + return; + } + while (n--) { + while (cs != ll && iword(line[cs])) + cs++; + if (wordflag && !n) + return; + while (cs != ll && !iword(line[cs])) + cs++; + } +} + +/**/ +void +viforwardword(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + backwardword(); + zmult = n; + return; + } + while (n--) { + if (iident(line[cs])) + while (cs != ll && iident(line[cs])) + cs++; + else + while (cs != ll && !iident(line[cs]) && !iblank(line[cs])) + cs++; + if (wordflag && !n) + return; + while (cs != ll && iblank(line[cs])) + cs++; + } +} + +/**/ +void +viforwardblankword(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + vibackwardblankword(); + zmult = n; + return; + } + while (n--) { + while (cs != ll && !iblank(line[cs])) + cs++; + if (wordflag && !n) + return; + while (cs != ll && iblank(line[cs])) + cs++; + } +} + +/**/ +void +emacsforwardword(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + emacsbackwardword(); + zmult = n; + return; + } + while (n--) { + while (cs != ll && !iword(line[cs])) + cs++; + if (wordflag && !n) + return; + while (cs != ll && iword(line[cs])) + cs++; + } +} + +/**/ +void +viforwardblankwordend(void) +{ + int n = zmult; + + if (n < 0) + return; + while (n--) { + while (cs != ll && iblank(line[cs + 1])) + cs++; + while (cs != ll && !iblank(line[cs + 1])) + cs++; + } + if (cs != ll && virangeflag) + cs++; +} + +/**/ +void +viforwardwordend(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + backwardword(); + zmult = n; + return; + } + while (n--) { + if (iblank(line[cs + 1])) + while (cs != ll && iblank(line[cs + 1])) + cs++; + if (iident(line[cs + 1])) + while (cs != ll && iident(line[cs + 1])) + cs++; + else + while (cs != ll && !iident(line[cs + 1]) && !iblank(line[cs + 1])) + cs++; + } + if (cs != ll && virangeflag) + cs++; +} + +/**/ +void +backwardword(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + forwardword(); + zmult = n; + return; + } + while (n--) { + while (cs && !iword(line[cs - 1])) + cs--; + while (cs && iword(line[cs - 1])) + cs--; + } +} + +/**/ +void +vibackwardword(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + backwardword(); + zmult = n; + return; + } + while (n--) { + while (cs && iblank(line[cs - 1])) + cs--; + if (iident(line[cs - 1])) + while (cs && iident(line[cs - 1])) + cs--; + else + while (cs && !iident(line[cs - 1]) && !iblank(line[cs - 1])) + cs--; + } +} + +/**/ +void +vibackwardblankword(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + viforwardblankword(); + zmult = n; + return; + } + while (n--) { + while (cs && iblank(line[cs - 1])) + cs--; + while (cs && !iblank(line[cs - 1])) + cs--; + } +} + +/**/ +void +emacsbackwardword(void) +{ + int n = zmult; + + if (n < 0) { + zmult = -n; + emacsforwardword(); + zmult = n; + return; + } + while (n--) { + while (cs && !iword(line[cs - 1])) + cs--; + while (cs && iword(line[cs - 1])) + cs--; + } +} + +/**/ +void +backwarddeleteword(void) +{ + int x = cs, n = zmult; + + if (n < 0) { + zmult = -n; + deleteword(); + zmult = n; + return; + } + while (n--) { + while (x && !iword(line[x - 1])) + x--; + while (x && iword(line[x - 1])) + x--; + } + backdel(cs - x); +} + +/**/ +void +vibackwardkillword(void) +{ + int x = cs, lim = (viinsbegin > findbol()) ? viinsbegin : findbol(); + int n = zmult; + + if (n < 0) { + feep(); + return; + } +/* this taken from "vibackwardword" */ + while (n--) { + while ((x > lim) && iblank(line[x - 1])) + x--; + if (iident(line[x - 1])) + while ((x > lim) && iident(line[x - 1])) + x--; + else + while ((x > lim) && !iident(line[x - 1]) && !iblank(line[x - 1])) + x--; + } + backkill(cs - x, 1); +} + +/**/ +void +backwardkillword(void) +{ + int x = cs; + int n = zmult; + + if (n < 0) { + zmult = -n; + killword(); + zmult = n; + return; + } + while (n--) { + while (x && !iword(line[x - 1])) + x--; + while (x && iword(line[x - 1])) + x--; + } + backkill(cs - x, 1); +} + +/**/ +void +upcaseword(void) +{ + int n = zmult; + int neg = n < 0, ocs = cs; + + if (neg) + n = -n; + while (n--) { + while (cs != ll && !iword(line[cs])) + cs++; + while (cs != ll && iword(line[cs])) { + line[cs] = tuupper(line[cs]); + cs++; + } + } + if (neg) + cs = ocs; +} + +/**/ +void +downcaseword(void) +{ + int n = zmult; + int neg = n < 0, ocs = cs; + + if (neg) + n = -n; + while (n--) { + while (cs != ll && !iword(line[cs])) + cs++; + while (cs != ll && iword(line[cs])) { + line[cs] = tulower(line[cs]); + cs++; + } + } + if (neg) + cs = ocs; +} + +/**/ +void +capitalizeword(void) +{ + int first, n = zmult; + int neg = n < 0, ocs = cs; + + if (neg) + n = -n; + while (n--) { + first = 1; + while (cs != ll && !iword(line[cs])) + cs++; + while (cs != ll && iword(line[cs]) && !isalpha(line[cs])) + cs++; + while (cs != ll && iword(line[cs])) { + line[cs] = (first) ? tuupper(line[cs]) : tulower(line[cs]); + first = 0; + cs++; + } + } + if (neg) + cs = ocs; +} + +/**/ +void +deleteword(void) +{ + int x = cs; + int n = zmult; + + if (n < 0) { + zmult = -n; + backwarddeleteword(); + zmult = n; + return; + } + while (n--) { + while (x != ll && !iword(line[x])) + x++; + while (x != ll && iword(line[x])) + x++; + } + foredel(x - cs); +} + +/**/ +void +killword(void) +{ + int x = cs; + int n = zmult; + + if (n < 0) { + zmult = -n; + backwardkillword(); + zmult = n; + return; + } + while (n--) { + while (x != ll && !iword(line[x])) + x++; + while (x != ll && iword(line[x])) + x++; + } + forekill(x - cs, 0); +} + +/**/ +void +transposewords(void) +{ + int p1, p2, p3, p4, x = cs; + char *temp, *pp; + int n = zmult; + int neg = n < 0, ocs = cs; + + if (neg) + n = -n; + while (n--) { + while (x != ll && line[x] != '\n' && !iword(line[x])) + x++; + if (x == ll || line[x] == '\n') { + x = cs; + while (x && line[x - 1] != '\n' && !iword(line[x])) + x--; + if (!x || line[x - 1] == '\n') { + feep(); + return; + } + } + for (p4 = x; p4 != ll && iword(line[p4]); p4++); + for (p3 = p4; p3 && iword(line[p3 - 1]); p3--); + if (!p3) { + feep(); + return; + } + for (p2 = p3; p2 && !iword(line[p2 - 1]); p2--); + if (!p2) { + feep(); + return; + } + for (p1 = p2; p1 && iword(line[p1 - 1]); p1--); + pp = temp = (char *)halloc(p4 - p1 + 1); + struncpy(&pp, (char *) line + p3, p4 - p3); + struncpy(&pp, (char *) line + p2, p3 - p2); + struncpy(&pp, (char *) line + p1, p2 - p1); + strncpy((char *)line + p1, temp, p4 - p1); + cs = p4; + } + if (neg) + cs = ocs; +} diff --git a/Src/ansi2knr.c b/Src/ansi2knr.c new file mode 100644 index 000000000..e5502a7e6 --- /dev/null +++ b/Src/ansi2knr.c @@ -0,0 +1,413 @@ +/* Copyright (C) 1989, 1991, 1993, 1994 Aladdin Enterprises. All rights reserved. */ + +/* ansi2knr.c */ +/* Convert ANSI function declarations to K&R syntax */ + +/* +ansi2knr is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY. No author or distributor accepts responsibility +to anyone for the consequences of using it or for whether it serves any +particular purpose or works at all, unless he says so in writing. Refer +to the GNU General Public License for full details. + +Everyone is granted permission to copy, modify and redistribute +ansi2knr, but only under the conditions described in the GNU +General Public License. A copy of this license is supposed to have been +given to you along with ansi2knr so you can know your rights and +responsibilities. It should be in a file named COPYING. Among other +things, the copyright notice and this notice must be preserved on all +copies. +*/ + +/* + * Usage: + ansi2knr input_file [output_file] + * If no output_file is supplied, output goes to stdout. + * There are no error messages. + * + * ansi2knr recognizes function definitions by seeing a non-keyword + * identifier at the left margin, followed by a left parenthesis, + * with a right parenthesis as the last character on the line. + * It will recognize a multi-line header provided that the last character + * of the last line of the header is a right parenthesis, + * and no intervening line ends with a left brace or a semicolon. + * These algorithms ignore whitespace and comments, except that + * the function name must be the first thing on the line. + * The following constructs will confuse it: + * - Any other construct that starts at the left margin and + * follows the above syntax (such as a macro or function call). + * - Macros that tinker with the syntax of the function header. + */ + +/* + * Change history: + lpd 89-xx-xx original version + lpd 94-07-16 added some conditionals to help GNU `configure', + suggested by Francois Pinard <pinard@iro.umontreal.ca>; + properly erase prototype args in function parameters, + contributed by Jim Avera <jima@netcom.com>; + correct error in writeblanks (it shouldn't erase EOLs) + */ + +/* Most of the conditionals here are to make ansi2knr work with */ +/* the GNU configure machinery. */ + +#ifdef HAVE_CONFIG_H +# ifdef CONFIG_BROKETS +/* + We use <config.h> instead of "config.h" so that a compilation + using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h + (which it would do because it found this file in $srcdir). + */ +# include <config.h> +# else +# include "config.h" +# endif +#endif + +#include <stdio.h> +#include <ctype.h> + +#ifdef HAVE_CONFIG_H + +/* + For properly autoconfiguring ansi2knr, use AC_CONFIG_HEADER(config.h). + This will define HAVE_CONFIG_H and so, activate the following lines. + */ + +# if STDC_HEADERS || HAVE_STRING_H +# include <string.h> +# else +# include <strings.h> +# endif + +#else /* not HAVE_CONFIG_H */ + +/* + Without AC_CONFIG_HEADER, merely use <string.h> as in the original + Ghostscript distribution. This loses on older BSD systems. + */ + +# include <string.h> + +#endif /* not HAVE_CONFIG_H */ + +#ifdef STDC_HEADERS +# include <stdlib.h> +#else +/* + malloc and free should be declared in stdlib.h, + but if you've got a K&R compiler, they probably aren't. + */ +char *malloc(); +void free(); +#endif + +/* Scanning macros */ +#define isidchar(ch) (isalnum(ch) || (ch) == '_') +#define isidfirstchar(ch) (isalpha(ch) || (ch) == '_') + +/* Forward references */ +char *skipspace(); +void writeblanks(); +int test1(); +int convert1(); + +/* The main program */ +int +main(argc, argv) + int argc; + char *argv[]; +{ FILE *in, *out; +#define bufsize 5000 /* arbitrary size */ + char *buf; + char *line; + switch ( argc ) + { + default: + printf("Usage: ansi2knr input_file [output_file]\n"); + exit(0); + case 2: + out = stdout; break; + case 3: + out = fopen(argv[2], "w"); + if ( out == NULL ) + { fprintf(stderr, "Cannot open %s\n", argv[2]); + exit(1); + } + } + in = fopen(argv[1], "r"); + if ( in == NULL ) + { fprintf(stderr, "Cannot open %s\n", argv[1]); + exit(1); + } + fprintf(out, "#line 1 \"%s\"\n", argv[1]); + buf = malloc(bufsize); + line = buf; + while ( fgets(line, (unsigned)(buf + bufsize - line), in) != NULL ) + { switch ( test1(buf) ) + { + case 2: /* a function header */ + convert1(buf, out, 1); + break; + case 1: /* a function */ + convert1(buf, out, 0); + break; + case -1: /* maybe the start of a function */ + line = buf + strlen(buf); + if ( line != buf + (bufsize - 1) ) /* overflow check */ + continue; + /* falls through */ + default: /* not a function */ + fputs(buf, out); + break; + } + line = buf; + } + if ( line != buf ) fputs(buf, out); + free(buf); + fclose(out); + fclose(in); + return 0; +} + +/* Skip over space and comments, in either direction. */ +char * +skipspace(p, dir) + register char *p; + register int dir; /* 1 for forward, -1 for backward */ +{ for ( ; ; ) + { while ( isspace(*p) ) p += dir; + if ( !(*p == '/' && p[dir] == '*') ) break; + p += dir; p += dir; + while ( !(*p == '*' && p[dir] == '/') ) + { if ( *p == 0 ) return p; /* multi-line comment?? */ + p += dir; + } + p += dir; p += dir; + } + return p; +} + +/* + * Write blanks over part of a string. + * Don't overwrite end-of-line characters. + */ +void +writeblanks(start, end) + char *start; + char *end; +{ char *p; + for ( p = start; p < end; p++ ) + if ( *p != '\r' && *p != '\n' ) *p = ' '; +} + +/* + * Test whether the string in buf is a function definition. + * The string may contain and/or end with a newline. + * Return as follows: + * 0 - definitely not a function definition; + * 1 - definitely a function definition; + * 2 - definitely a function prototype (NOT USED); + * -1 - may be the beginning of a function definition, + * append another line and look again. + * The reason we don't attempt to convert function prototypes is that + * Ghostscript's declaration-generating macros look too much like + * prototypes, and confuse the algorithms. + */ +int +test1(buf) + char *buf; +{ register char *p = buf; + char *bend; + char *endfn; + int contin; + if ( !isidfirstchar(*p) ) + return 0; /* no name at left margin */ + bend = skipspace(buf + strlen(buf) - 1, -1); + switch ( *bend ) + { + case ';': contin = 0 /*2*/; break; + case ')': contin = 1; break; + case '{': return 0; /* not a function */ + default: contin = -1; + } + while ( isidchar(*p) ) p++; + endfn = p; + p = skipspace(p, 1); + if ( *p++ != '(' ) + return 0; /* not a function */ + p = skipspace(p, 1); + if ( *p == ')' ) + return 0; /* no parameters */ + /* Check that the apparent function name isn't a keyword. */ + /* We only need to check for keywords that could be followed */ + /* by a left parenthesis (which, unfortunately, is most of them). */ + { static char *words[] = + { "asm", "auto", "case", "char", "const", "double", + "extern", "float", "for", "if", "int", "long", + "register", "return", "short", "signed", "sizeof", + "static", "switch", "typedef", "unsigned", + "void", "volatile", "while", 0 + }; + char **key = words; + char *kp; + int len = endfn - buf; + while ( (kp = *key) != 0 ) + { if ( strlen(kp) == len && !strncmp(kp, buf, len) ) + return 0; /* name is a keyword */ + key++; + } + } + return contin; +} + +/* Convert a recognized function definition or header to K&R syntax. */ +int +convert1(buf, out, header) + char *buf; + FILE *out; + int header; /* Boolean */ +{ char *endfn; + register char *p; + char **breaks; + unsigned num_breaks = 2; /* for testing */ + char **btop; + char **bp; + char **ap; + /* Pre-ANSI implementations don't agree on whether strchr */ + /* is called strchr or index, so we open-code it here. */ + for ( endfn = buf; *(endfn++) != '('; ) ; +top: p = endfn; + breaks = (char **)malloc(sizeof(char *) * num_breaks * 2); + if ( breaks == 0 ) + { /* Couldn't allocate break table, give up */ + fprintf(stderr, "Unable to allocate break table!\n"); + fputs(buf, out); + return -1; + } + btop = breaks + num_breaks * 2 - 2; + bp = breaks; + /* Parse the argument list */ + do + { int level = 0; + char *lp = NULL; + char *rp; + char *end = NULL; + if ( bp >= btop ) + { /* Filled up break table. */ + /* Allocate a bigger one and start over. */ + free((char *)breaks); + num_breaks <<= 1; + goto top; + } + *bp++ = p; + /* Find the end of the argument */ + for ( ; end == NULL; p++ ) + { switch(*p) + { + case ',': + if ( !level ) end = p; + break; + case '(': + if ( !level ) lp = p; + level++; + break; + case ')': + if ( --level < 0 ) end = p; + else rp = p; + break; + case '/': + p = skipspace(p, 1) - 1; + break; + default: + ; + } + } + /* Erase any embedded prototype parameters. */ + if ( lp ) + writeblanks(lp + 1, rp); + p--; /* back up over terminator */ + /* Find the name being declared. */ + /* This is complicated because of procedure and */ + /* array modifiers. */ + for ( ; ; ) + { p = skipspace(p - 1, -1); + switch ( *p ) + { + case ']': /* skip array dimension(s) */ + case ')': /* skip procedure args OR name */ + { int level = 1; + while ( level ) + switch ( *--p ) + { + case ']': case ')': level++; break; + case '[': case '(': level--; break; + case '/': p = skipspace(p, -1) + 1; break; + default: ; + } + } + if ( *p == '(' && *skipspace(p + 1, 1) == '*' ) + { /* We found the name being declared */ + while ( !isidfirstchar(*p) ) + p = skipspace(p, 1) + 1; + goto found; + } + break; + default: goto found; + } + } +found: if ( *p == '.' && p[-1] == '.' && p[-2] == '.' ) + { p++; + if ( bp == breaks + 1 ) /* sole argument */ + writeblanks(breaks[0], p); + else + writeblanks(bp[-1] - 1, p); + bp--; + } + else + { while ( isidchar(*p) ) p--; + *bp++ = p+1; + } + p = end; + } + while ( *p++ == ',' ); + *bp = p; + /* Make a special check for 'void' arglist */ + if ( bp == breaks+2 ) + { p = skipspace(breaks[0], 1); + if ( !strncmp(p, "void", 4) ) + { p = skipspace(p+4, 1); + if ( p == breaks[2] - 1 ) + { bp = breaks; /* yup, pretend arglist is empty */ + writeblanks(breaks[0], p + 1); + } + } + } + /* Put out the function name */ + p = buf; + while ( p != endfn ) putc(*p, out), p++; + /* Put out the declaration */ + if ( header ) + { fputs(");", out); + for ( p = breaks[0]; *p; p++ ) + if ( *p == '\n' ) + putc('\n', out); + } + else + { for ( ap = breaks+1; ap < bp; ap += 2 ) + { p = *ap; + while ( isidchar(*p) ) + putc(*p, out), p++; + if ( ap < bp - 1 ) + fputs(", ", out); + } + fputs(") ", out); + /* Put out the argument declarations */ + for ( ap = breaks+2; ap <= bp; ap += 2 ) + (*ap)[-1] = ';'; + fputs(breaks[0], out); + } + free((char *)breaks); + return 0; +} diff --git a/Src/builtin.c b/Src/builtin.c new file mode 100644 index 000000000..31f396d93 --- /dev/null +++ b/Src/builtin.c @@ -0,0 +1,3599 @@ +/* + * builtin.c - builtin commands + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zsh.mdh" +#include "builtin.pro" + +/* Builtins in the main executable */ + +static struct builtin builtins[] = +{ + BIN_PREFIX("-", BINF_DASH), + BIN_PREFIX("builtin", BINF_BUILTIN), + BIN_PREFIX("command", BINF_COMMAND), + BIN_PREFIX("exec", BINF_EXEC), + BIN_PREFIX("noglob", BINF_NOGLOB), + BUILTIN("[", 0, bin_test, 0, -1, BIN_BRACKET, NULL, NULL), + BUILTIN(".", BINF_PSPECIAL, bin_dot, 1, -1, 0, NULL, NULL), + BUILTIN(":", BINF_PSPECIAL, bin_true, 0, -1, 0, NULL, NULL), + BUILTIN("alias", BINF_MAGICEQUALS, bin_alias, 0, -1, 0, "Lgmr", NULL), + BUILTIN("autoload", BINF_TYPEOPTS, bin_functions, 0, -1, 0, "t", "u"), + BUILTIN("bg", 0, bin_fg, 0, -1, BIN_BG, NULL, NULL), + BUILTIN("break", BINF_PSPECIAL, bin_break, 0, 1, BIN_BREAK, NULL, NULL), + BUILTIN("bye", 0, bin_break, 0, 1, BIN_EXIT, NULL, NULL), + BUILTIN("cd", 0, bin_cd, 0, 2, BIN_CD, NULL, NULL), + BUILTIN("chdir", 0, bin_cd, 0, 2, BIN_CD, NULL, NULL), + BUILTIN("continue", BINF_PSPECIAL, bin_break, 0, 1, BIN_CONTINUE, NULL, NULL), + BUILTIN("declare", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "LRUZfilrtux", NULL), + BUILTIN("dirs", 0, bin_dirs, 0, -1, 0, "v", NULL), + BUILTIN("disable", 0, bin_enable, 0, -1, BIN_DISABLE, "afmr", NULL), + BUILTIN("disown", 0, bin_fg, 0, -1, BIN_DISOWN, NULL, NULL), + BUILTIN("echo", BINF_PRINTOPTS | BINF_ECHOPTS, bin_print, 0, -1, BIN_ECHO, "neE", "-"), + BUILTIN("echotc", 0, bin_echotc, 1, -1, 0, NULL, NULL), + BUILTIN("emulate", 0, bin_emulate, 1, 1, 0, "R", NULL), + BUILTIN("enable", 0, bin_enable, 0, -1, BIN_ENABLE, "afmr", NULL), + BUILTIN("eval", BINF_PSPECIAL, bin_eval, 0, -1, BIN_EVAL, NULL, NULL), + BUILTIN("exit", BINF_PSPECIAL, bin_break, 0, 1, BIN_EXIT, NULL, NULL), + BUILTIN("export", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, BIN_EXPORT, "LRUZfilrtu", "x"), + BUILTIN("false", 0, bin_false, 0, -1, 0, NULL, NULL), + BUILTIN("fc", BINF_FCOPTS, bin_fc, 0, -1, BIN_FC, "nlreIRWAdDfEim", NULL), + BUILTIN("fg", 0, bin_fg, 0, -1, BIN_FG, NULL, NULL), + BUILTIN("functions", BINF_TYPEOPTS, bin_functions, 0, -1, 0, "mtu", NULL), + BUILTIN("getln", 0, bin_read, 0, -1, 0, "ecnAlE", "zr"), + BUILTIN("getopts", 0, bin_getopts, 2, -1, 0, NULL, NULL), + BUILTIN("hash", BINF_MAGICEQUALS, bin_hash, 0, -1, 0, "dfmrv", NULL), + +#ifdef ZSH_HASH_DEBUG + BUILTIN("hashinfo", 0, bin_hashinfo, 0, 0, 0, NULL, NULL), +#endif + + BUILTIN("history", 0, bin_fc, 0, -1, BIN_FC, "nrdDfEim", "l"), + BUILTIN("integer", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "lrtux", "i"), + BUILTIN("jobs", 0, bin_fg, 0, -1, BIN_JOBS, "dlpZrs", NULL), + BUILTIN("kill", 0, bin_kill, 0, -1, 0, NULL, NULL), + BUILTIN("let", 0, bin_let, 1, -1, 0, NULL, NULL), + BUILTIN("local", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "LRUZilrtu", NULL), + BUILTIN("log", 0, bin_log, 0, 0, 0, NULL, NULL), + BUILTIN("logout", 0, bin_break, 0, 1, BIN_LOGOUT, NULL, NULL), + +#if defined(ZSH_MEM) & defined(ZSH_MEM_DEBUG) + BUILTIN("mem", 0, bin_mem, 0, 0, 0, "v", NULL), +#endif + + BUILTIN("popd", 0, bin_cd, 0, 2, BIN_POPD, NULL, NULL), + BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "RDPnrslzNu0123456789pioOcm-", NULL), + BUILTIN("pushd", 0, bin_cd, 0, 2, BIN_PUSHD, NULL, NULL), + BUILTIN("pushln", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, NULL, "-nz"), + BUILTIN("pwd", 0, bin_pwd, 0, 0, 0, "rLP", NULL), + BUILTIN("r", BINF_R, bin_fc, 0, -1, BIN_FC, "nrl", NULL), + BUILTIN("read", 0, bin_read, 0, -1, 0, "rzu0123456789pkqecnAlE", NULL), + BUILTIN("readonly", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "LRUZfiltux", "r"), + BUILTIN("rehash", 0, bin_hash, 0, 0, 0, "dfv", "r"), + BUILTIN("return", BINF_PSPECIAL, bin_break, 0, 1, BIN_RETURN, NULL, NULL), + BUILTIN("set", BINF_PSPECIAL, bin_set, 0, -1, 0, NULL, NULL), + BUILTIN("setopt", 0, bin_setopt, 0, -1, BIN_SETOPT, NULL, NULL), + BUILTIN("shift", BINF_PSPECIAL, bin_shift, 0, -1, 0, NULL, NULL), + BUILTIN("source", BINF_PSPECIAL, bin_dot, 1, -1, 0, NULL, NULL), + BUILTIN("suspend", 0, bin_suspend, 0, 0, 0, "f", NULL), + BUILTIN("test", 0, bin_test, 0, -1, BIN_TEST, NULL, NULL), + BUILTIN("ttyctl", 0, bin_ttyctl, 0, 0, 0, "fu", NULL), + BUILTIN("times", BINF_PSPECIAL, bin_times, 0, 0, 0, NULL, NULL), + BUILTIN("trap", BINF_PSPECIAL, bin_trap, 0, -1, 0, NULL, NULL), + BUILTIN("true", 0, bin_true, 0, -1, 0, NULL, NULL), + BUILTIN("type", 0, bin_whence, 0, -1, 0, "ampfsw", "v"), + BUILTIN("typeset", BINF_TYPEOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "LRUZfilrtuxm", NULL), + BUILTIN("umask", 0, bin_umask, 0, 1, 0, "S", NULL), + BUILTIN("unalias", 0, bin_unhash, 1, -1, 0, "m", "a"), + BUILTIN("unfunction", 0, bin_unhash, 1, -1, 0, "m", "f"), + BUILTIN("unhash", 0, bin_unhash, 1, -1, 0, "adfm", NULL), + BUILTIN("unset", BINF_PSPECIAL, bin_unset, 1, -1, 0, "fm", NULL), + BUILTIN("unsetopt", 0, bin_setopt, 0, -1, BIN_UNSETOPT, NULL, NULL), + BUILTIN("wait", 0, bin_fg, 0, -1, BIN_WAIT, NULL, NULL), + BUILTIN("whence", 0, bin_whence, 0, -1, 0, "acmpvfsw", NULL), + BUILTIN("where", 0, bin_whence, 0, -1, 0, "pmsw", "ca"), + BUILTIN("which", 0, bin_whence, 0, -1, 0, "ampsw", "c"), + +#ifdef DYNAMIC + BUILTIN("zmodload", 0, bin_zmodload, 0, -1, 0, "Laudi", NULL), +#endif +}; + +/****************************************/ +/* Builtin Command Hash Table Functions */ +/****************************************/ + +/* hash table containing builtin commands */ + +/**/ +HashTable builtintab; + +/**/ +void +createbuiltintable(void) +{ + builtintab = newhashtable(85, "builtintab", NULL); + + builtintab->hash = hasher; + builtintab->emptytable = NULL; + builtintab->filltable = NULL; + builtintab->addnode = addhashnode; + builtintab->getnode = gethashnode; + builtintab->getnode2 = gethashnode2; + builtintab->removenode = removehashnode; + builtintab->disablenode = disablehashnode; + builtintab->enablenode = enablehashnode; + builtintab->freenode = freebuiltinnode; + builtintab->printnode = printbuiltinnode; + + addbuiltins("zsh", builtins, sizeof(builtins)/sizeof(*builtins)); +} + +/* Print a builtin */ + +/**/ +static void +printbuiltinnode(HashNode hn, int printflags) +{ + Builtin bn = (Builtin) hn; + + if (printflags & PRINT_WHENCE_WORD) { + printf("%s: builtin\n", bn->nam); + return; + } + + if (printflags & PRINT_WHENCE_CSH) { + printf("%s: shell built-in command\n", bn->nam); + return; + } + + if (printflags & PRINT_WHENCE_VERBOSE) { + printf("%s is a shell builtin\n", bn->nam); + return; + } + + /* default is name only */ + printf("%s\n", bn->nam); +} + +/**/ +static void +freebuiltinnode(HashNode hn) +{ + Builtin bn = (Builtin) hn; + + if(!(bn->flags & BINF_ADDED)) { + zsfree(bn->nam); + zsfree(bn->optstr); + zfree(bn, sizeof(struct builtin)); + } +} + +static char *auxdata; +static int auxlen; + +/* execute a builtin handler function after parsing the arguments */ + +#define MAX_OPS 128 + +/**/ +int +execbuiltin(LinkList args, Builtin bn) +{ + LinkNode n; + char ops[MAX_OPS], *arg, *pp, *name, **argv, **oargv, *optstr; + char *oxarg, *xarg = NULL; + int flags, sense, argc = 0, execop; + + /* initialise some static variables */ + auxdata = NULL; + auxlen = 0; + + /* initialize some local variables */ + memset(ops, 0, MAX_OPS); + name = (char *) ugetnode(args); + + arg = (char *) ugetnode(args); + +#ifdef DYNAMIC + if (!bn->handlerfunc) { + zwarnnam(name, "autoload failed", NULL, 0); + deletebuiltin(bn->nam); + return 1; + } +#endif + + /* get some information about the command */ + flags = bn->flags; + optstr = bn->optstr; + + /* Sort out the options. */ + if ((flags & BINF_ECHOPTS) && isset(BSDECHO)) + ops['E'] = 1; + if (optstr) + /* while arguments look like options ... */ + while (arg && + ((sense = (*arg == '-')) || + ((flags & BINF_PLUSOPTS) && *arg == '+')) && + ((flags & BINF_PLUSOPTS) || !atoi(arg))) { + /* unrecognised options to echo etc. are not really options */ + if (flags & BINF_ECHOPTS) { + char *p = arg; + while (*++p && strchr(optstr, (int) *p)); + if (*p) + break; + } + /* save the options in xarg, for execution tracing */ + if (xarg) { + oxarg = tricat(xarg, " ", arg); + zsfree(xarg); + xarg = oxarg; + } else + xarg = ztrdup(arg); + /* handle -- or - (ops['-']), and + (ops['-'] and ops['+']) */ + if (arg[1] == '-') + arg++; + if (!arg[1]) { + ops['-'] = 1; + if (!sense) + ops['+'] = 1; + } + /* save options in ops, as long as they are in bn->optstr */ + execop = -1; + while (*++arg) + if (strchr(optstr, execop = (int)*arg)) + ops[(int)*arg] = (sense) ? 1 : 2; + else + break; + /* "typeset" may take a numeric argument * + * at the tail of the options */ + if (idigit(*arg) && (flags & BINF_TYPEOPT) && + (arg[-1] == 'L' || arg[-1] == 'R' || + arg[-1] == 'Z' || arg[-1] == 'i')) + auxlen = (int)zstrtol(arg, &arg, 10); + /* The above loop may have exited on an invalid option. (We * + * assume that any option requiring metafication is invalid.) */ + if (*arg) { + if(*arg == Meta) + *++arg ^= 32; + zerr("bad option: -%c", NULL, *arg); + zsfree(xarg); + return 1; + } + arg = (char *) ugetnode(args); + /* for the "print" builtin, the options after -R are treated as + options to "echo" */ + if ((flags & BINF_PRINTOPTS) && ops['R']) { + optstr = "ne"; + flags |= BINF_ECHOPTS; + } + /* the option -- indicates the end of the options */ + if (ops['-']) + break; + /* for "fc", -e takes an extra argument */ + if ((flags & BINF_FCOPTS) && execop == 'e') { + auxdata = arg; + arg = (char *) ugetnode(args); + } + /* for "typeset", -L, -R, -Z and -i take a numeric extra argument */ + if ((flags & BINF_TYPEOPT) && (execop == 'L' || execop == 'R' || + execop == 'Z' || execop == 'i') && arg && idigit(*arg)) { + auxlen = atoi(arg); + arg = (char *) ugetnode(args); + } + } + if (flags & BINF_R) + auxdata = "-"; + /* handle built-in options, for overloaded handler functions */ + if ((pp = bn->defopts)) + while (*pp) + ops[(int)*pp++] = 1; + + /* Set up the argument list. */ + if (arg) { + /* count the arguments */ + argc = 1; + n = firstnode(args); + while (n) + argc++, incnode(n); + } + /* Get the actual arguments, into argv. Oargv saves the * + * beginning of the array for later reference. */ + oargv = argv = (char **)ncalloc(sizeof(char **) * (argc + 1)); + if ((*argv++ = arg)) + while ((*argv++ = (char *)ugetnode(args))); + argv = oargv; + if (errflag) { + zsfree(xarg); + errflag = 0; + return 1; + } + + /* check that the argument count lies within the specified bounds */ + if (argc < bn->minargs || (argc > bn->maxargs && bn->maxargs != -1)) { + zwarnnam(name, (argc < bn->minargs) + ? "not enough arguments" : "too many arguments", NULL, 0); + zsfree(xarg); + return 1; + } + + /* display execution trace information, if required */ + if (isset(XTRACE)) { + fprintf(stderr, "%s%s", (prompt4) ? prompt4 : "", name); + if (xarg) + fprintf(stderr, " %s", xarg); + while (*oargv) + fprintf(stderr, " %s", *oargv++); + fputc('\n', stderr); + fflush(stderr); + } + zsfree(xarg); + /* call the handler function, and return its return value */ + return (*(bn->handlerfunc)) (name, argv, ops, bn->funcid); +} + +/* Enable/disable an element in one of the internal hash tables. * + * With no arguments, it lists all the currently enabled/disabled * + * elements in that particular hash table. */ + +/**/ +int +bin_enable(char *name, char **argv, char *ops, int func) +{ + HashTable ht; + HashNode hn; + ScanFunc scanfunc; + Comp com; + int flags1 = 0, flags2 = 0; + int match = 0, returnval = 0; + + /* Find out which hash table we are working with. */ + if (ops['f']) + ht = shfunctab; + else if (ops['r']) + ht = reswdtab; + else if (ops['a']) + ht = aliastab; + else + ht = builtintab; + + /* Do we want to enable or disable? */ + if (func == BIN_ENABLE) { + flags2 = DISABLED; + scanfunc = ht->enablenode; + } else { + flags1 = DISABLED; + scanfunc = ht->disablenode; + } + + /* Given no arguments, print the names of the enabled/disabled elements * + * in this hash table. If func == BIN_ENABLE, then scanhashtable will * + * print nodes NOT containing the DISABLED flag, else scanhashtable will * + * print nodes containing the DISABLED flag. */ + if (!*argv) { + scanhashtable(ht, 1, flags1, flags2, ht->printnode, 0); + return 0; + } + + /* With -m option, treat arguments as glob patterns. */ + if (ops['m']) { + for (; *argv; argv++) { + /* parse pattern */ + tokenize(*argv); + if ((com = parsereg(*argv))) + match += scanmatchtable(ht, com, 0, 0, scanfunc, 0); + else { + untokenize(*argv); + zwarnnam(name, "bad pattern : %s", *argv, 0); + returnval = 1; + } + } + /* If we didn't match anything, we return 1. */ + if (!match) + returnval = 1; + return returnval; + } + + /* Take arguments literally -- do not glob */ + for (; *argv; argv++) { + if ((hn = ht->getnode2(ht, *argv))) { + scanfunc(hn, 0); + } else { + zwarnnam(name, "no such hash table element: %s", *argv, 0); + returnval = 1; + } + } + return returnval; +} + +/* set: either set the shell options, or set the shell arguments, * + * or declare an array, or show various things */ + +/**/ +int +bin_set(char *nam, char **args, char *ops, int func) +{ + int action, optno, array = 0, hadopt = 0, + hadplus = 0, hadend = 0, sort = 0; + char **x; + + /* Obsolecent sh compatibility: set - is the same as set +xv * + * and set - args is the same as set +xv -- args */ + if (*args && **args == '-' && !args[0][1]) { + dosetopt(VERBOSE, 0, 0); + dosetopt(XTRACE, 0, 0); + if (!args[1]) + return 0; + } + + /* loop through command line options (begins with "-" or "+") */ + while (*args && (**args == '-' || **args == '+')) { + action = (**args == '-'); + hadplus |= !action; + if(!args[0][1]) + *args = "--"; + while (*++*args) { + if(**args == Meta) + *++*args ^= 32; + if(**args != '-' || action) + hadopt = 1; + /* The pseudo-option `--' signifies the end of options. */ + if (**args == '-') { + hadend = 1; + args++; + goto doneoptions; + } else if (**args == 'o') { + if (!*++*args) + args++; + if (!*args) { + zwarnnam(nam, "string expected after -o", NULL, 0); + inittyptab(); + return 1; + } + if(!(optno = optlookup(*args))) + zwarnnam(nam, "no such option: %s", *args, 0); + else if(dosetopt(optno, action, 0)) + zwarnnam(nam, "can't change option: %s", *args, 0); + break; + } else if(**args == 'A') { + if(!*++*args) + args++; + array = action ? 1 : -1; + goto doneoptions; + } else if (**args == 's') + sort = action ? 1 : -1; + else { + if (!(optno = optlookupc(**args))) + zwarnnam(nam, "bad option: -%c", NULL, **args); + else if(dosetopt(optno, action, 0)) + zwarnnam(nam, "can't change option: -%c", NULL, **args); + } + } + args++; + } + doneoptions: + inittyptab(); + + /* Show the parameters, possibly with values */ + if (!hadopt && !*args) + scanhashtable(paramtab, 1, 0, 0, paramtab->printnode, + hadplus ? PRINT_NAMEONLY : 0); + + if (array && !*args) { + /* display arrays */ + scanhashtable(paramtab, 1, PM_ARRAY, 0, paramtab->printnode, + hadplus ? PRINT_NAMEONLY : 0); + } + if (!*args && !hadend) + return 0; + if (array) + args++; + if (sort) + qsort(args, arrlen(args), sizeof(char *), + sort > 0 ? strpcmp : invstrpcmp); + if (array) { + /* create an array with the specified elements */ + char **a = NULL, **y, *name = args[-1]; + int len = arrlen(args); + + if (array < 0 && (a = getaparam(name))) { + int al = arrlen(a); + + if (al > len) + len = al; + } + for (x = y = zalloc((len + 1) * sizeof(char *)); len--; a++) { + if (!*args) + args = a; + *y++ = ztrdup(*args++); + } + *y++ = NULL; + setaparam(name, x); + } else { + /* set shell arguments */ + freearray(pparams); + PERMALLOC { + pparams = arrdup(args); + } LASTALLOC; + } + return 0; +} + +/**** directory-handling builtins ****/ + +/**/ +int doprintdir = 0; /* set in exec.c (for autocd) */ + +/* pwd: display the name of the current directory */ + +/**/ +int +bin_pwd(char *name, char **argv, char *ops, int func) +{ + if (ops['r'] || ops['P'] || (isset(CHASELINKS) && !ops['L'])) + printf("%s\n", zgetcwd()); + else { + zputs(pwd, stdout); + putchar('\n'); + } + return 0; +} + +/* the directory stack */ + +/**/ +LinkList dirstack; + +/* dirs: list the directory stack, or replace it with a provided list */ + +/**/ +int +bin_dirs(char *name, char **argv, char *ops, int func) +{ + LinkList l; + + /* with the -v option, provide a numbered list of directories, starting at + zero */ + if (ops['v']) { + LinkNode node; + int pos = 1; + + printf("0\t"); + fprintdir(pwd, stdout); + for (node = firstnode(dirstack); node; incnode(node)) { + printf("\n%d\t", pos++); + fprintdir(getdata(node), stdout); + } + putchar('\n'); + return 0; + } + /* given no arguments, list the stack normally */ + if (!*argv) { + printdirstack(); + return 0; + } + /* replace the stack with the specified directories */ + PERMALLOC { + l = newlinklist(); + if (*argv) { + while (*argv) + addlinknode(l, ztrdup(*argv++)); + freelinklist(dirstack, freestr); + dirstack = l; + } + } LASTALLOC; + return 0; +} + +/* cd, chdir, pushd, popd */ + +/**/ +void +set_pwd_env(void) +{ + Param pm; + + pm = (Param) paramtab->getnode(paramtab, "PWD"); + if (pm && PM_TYPE(pm->flags) != PM_SCALAR) { + pm->flags &= ~PM_READONLY; + unsetparam_pm(pm, 0, 1); + } + + pm = (Param) paramtab->getnode(paramtab, "OLDPWD"); + if (pm && PM_TYPE(pm->flags) != PM_SCALAR) { + pm->flags &= ~PM_READONLY; + unsetparam_pm(pm, 0, 1); + } + + setsparam("PWD", ztrdup(pwd)); + setsparam("OLDPWD", ztrdup(oldpwd)); + + pm = (Param) paramtab->getnode(paramtab, "PWD"); + if (!(pm->flags & PM_EXPORTED)) { + pm->flags |= PM_EXPORTED; + pm->env = addenv("PWD", pwd); + } + pm = (Param) paramtab->getnode(paramtab, "OLDPWD"); + if (!(pm->flags & PM_EXPORTED)) { + pm->flags |= PM_EXPORTED; + pm->env = addenv("PWD", pwd); + } +} + +/* The main pwd changing function. The real work is done by other * + * functions. cd_get_dest() does the initial argument processing; * + * cd_do_chdir() actually changes directory, if possible; cd_new_pwd() * + * does the ancilliary processing associated with actually changing * + * directory. */ + +/**/ +int +bin_cd(char *nam, char **argv, char *ops, int func) +{ + LinkNode dir; + struct stat st1, st2; + int chaselinks; + + if (isset(RESTRICTED)) { + zwarnnam(nam, "restricted", NULL, 0); + return 1; + } + doprintdir = (doprintdir == -1); + + for (; *argv && **argv == '-'; argv++) { + char *s = *argv + 1; + + do { + switch (*s) { + case 's': + case 'P': + case 'L': + break; + default: + goto brk; + } + } while (*++s); + for (s = *argv; *++s; ops[*s] = 1); + } + brk: + chaselinks = ops['P'] || (isset(CHASELINKS) && !ops['L']); + PERMALLOC { + pushnode(dirstack, ztrdup(pwd)); + if (!(dir = cd_get_dest(nam, argv, ops, func))) { + zsfree(getlinknode(dirstack)); + LASTALLOC_RETURN 1; + } + } LASTALLOC; + cd_new_pwd(func, dir, chaselinks); + + if (stat(unmeta(pwd), &st1) < 0) { + zsfree(pwd); + pwd = metafy(zgetcwd(), -1, META_DUP); + } else if (stat(".", &st2) < 0) + chdir(unmeta(pwd)); + else if (st1.st_ino != st2.st_ino || st1.st_dev != st2.st_dev) { + if (chaselinks) { + zsfree(pwd); + pwd = metafy(zgetcwd(), -1, META_DUP); + } else { + chdir(unmeta(pwd)); + } + } + set_pwd_env(); + return 0; +} + +/* Get directory to chdir to */ + +/**/ +static LinkNode +cd_get_dest(char *nam, char **argv, char *ops, int func) +{ + LinkNode dir = NULL; + LinkNode target; + char *dest; + + if (!argv[0]) { + if (func == BIN_POPD && !nextnode(firstnode(dirstack))) { + zwarnnam(nam, "directory stack empty", NULL, 0); + return NULL; + } + if (func == BIN_PUSHD && unset(PUSHDTOHOME)) + dir = nextnode(firstnode(dirstack)); + if (dir) + insertlinknode(dirstack, dir, getlinknode(dirstack)); + else if (func != BIN_POPD) + pushnode(dirstack, ztrdup(home)); + } else if (!argv[1]) { + int dd; + char *end; + + doprintdir++; + if (argv[0][1] && (argv[0][0] == '+' || argv[0][0] == '-')) { + dd = zstrtol(argv[0] + 1, &end, 10); + if (*end == '\0') { + if ((argv[0][0] == '+') ^ isset(PUSHDMINUS)) + for (dir = firstnode(dirstack); dir && dd; dd--, incnode(dir)); + else + for (dir = lastnode(dirstack); dir != (LinkNode) dirstack && dd; + dd--, dir = prevnode(dir)); + if (!dir || dir == (LinkNode) dirstack) { + zwarnnam(nam, "no such entry in dir stack", NULL, 0); + return NULL; + } + } + } + if (!dir) + pushnode(dirstack, ztrdup(strcmp(argv[0], "-") + ? (doprintdir--, argv[0]) : oldpwd)); + } else { + char *u, *d; + int len1, len2, len3; + + if (!(u = strstr(pwd, argv[0]))) { + zwarnnam(nam, "string not in pwd: %s", argv[0], 0); + return NULL; + } + len1 = strlen(argv[0]); + len2 = strlen(argv[1]); + len3 = u - pwd; + d = (char *)zalloc(len3 + len2 + strlen(u + len1) + 1); + strncpy(d, pwd, len3); + strcpy(d + len3, argv[1]); + strcat(d, u + len1); + pushnode(dirstack, d); + doprintdir++; + } + + target = dir; + if (func == BIN_POPD) { + if (!dir) { + target = dir = firstnode(dirstack); + } else if (dir != firstnode(dirstack)) { + return dir; + } + dir = nextnode(dir); + } + if (!dir) { + dir = firstnode(dirstack); + } + if (!(dest = cd_do_chdir(nam, getdata(dir), ops['s']))) { + if (!target) + zsfree(getlinknode(dirstack)); + if (func == BIN_POPD) + zsfree(remnode(dirstack, dir)); + return NULL; + } + if (dest != getdata(dir)) { + zsfree(getdata(dir)); + setdata(dir, dest); + } + return target ? target : dir; +} + +/* Change to given directory, if possible. This function works out * + * exactly how the directory should be interpreted, including cdpath * + * and CDABLEVARS. For each possible interpretation of the given * + * path, this calls cd_try_chdir(), which attempts to chdir to that * + * particular path. */ + +/**/ +static char * +cd_do_chdir(char *cnam, char *dest, int hard) +{ + char **pp, *ret; + int hasdot = 0, eno = ENOENT; + /* nocdpath indicates that cdpath should not be used. This is the case iff + dest is a relative path whose first segment is . or .., but if the path is + absolute then cdpath won't be used anyway. */ + int nocdpath = dest[0] == '.' && + (dest[1] == '/' || !dest[1] || (dest[1] == '.' && + (dest[2] == '/' || !dest[2]))); + + /* if we have an absolute path, use it as-is only */ + if (*dest == '/') { + if ((ret = cd_try_chdir(NULL, dest, hard))) + return ret; + zwarnnam(cnam, "%e: %s", dest, errno); + return NULL; + } + + /* if cdpath is being used, check it for . */ + if (!nocdpath) + for (pp = cdpath; *pp; pp++) + if (!(*pp)[0] || ((*pp)[0] == '.' && (*pp)[1] == '\0')) + hasdot = 1; + /* if there is no . in cdpath (or it is not being used), try the directory + as-is (i.e. from .) */ + if (!hasdot) { + if ((ret = cd_try_chdir(NULL, dest, hard))) + return ret; + if (errno != ENOENT) + eno = errno; + } + /* if cdpath is being used, try given directory relative to each element in + cdpath in turn */ + if (!nocdpath) + for (pp = cdpath; *pp; pp++) { + if ((ret = cd_try_chdir(*pp, dest, hard))) { + if (strcmp(*pp, ".")) { + doprintdir++; + } + return ret; + } + if (errno != ENOENT) + eno = errno; + } + + /* handle the CDABLEVARS option */ + if ((ret = cd_able_vars(dest))) { + if ((ret = cd_try_chdir(NULL, ret,hard))) { + doprintdir++; + return ret; + } + if (errno != ENOENT) + eno = errno; + } + + /* If we got here, it means that we couldn't chdir to any of the + multitudinous possible paths allowed by zsh. We've run out of options! + Add more here! */ + zwarnnam(cnam, "%e: %s", dest, eno); + return NULL; +} + +/* If the CDABLEVARS option is set, return the new * + * interpretation of the given path. */ + +/**/ +char * +cd_able_vars(char *s) +{ + char *rest, save; + + if (isset(CDABLEVARS)) { + for (rest = s; *rest && *rest != '/'; rest++); + save = *rest; + *rest = 0; + s = getnameddir(s); + *rest = save; + + if (s && *rest) + s = dyncat(s, rest); + + return s; + } + return NULL; +} + +/* Attempt to change to a single given directory. The directory, * + * for the convenience of the calling function, may be provided in * + * two parts, which must be concatenated before attempting to chdir. * + * Returns NULL if the chdir fails. If the directory change is * + * possible, it is performed, and a pointer to the new full pathname * + * is returned. */ + +/**/ +static char * +cd_try_chdir(char *pfix, char *dest, int hard) +{ + char *buf; + + /* handle directory prefix */ + if (pfix && *pfix) { + if (*pfix == '/') + buf = tricat(pfix, "/", dest); + else { + int pwl = strlen(pwd); + int pfl = strlen(pfix); + + buf = zalloc(pwl + pfl + strlen(dest) + 3); + strcpy(buf, pwd); + buf[pwl] = '/'; + strcpy(buf + pwl + 1, pfix); + buf[pwl + 1 + pfl] = '/'; + strcpy(buf + pwl + pfl + 2, dest); + } + } else if (*dest == '/') + buf = ztrdup(dest); + else { + int pwl = strlen(pwd); + + buf = zalloc(pwl + strlen(dest) + 2); + strcpy(buf, pwd); + buf[pwl] = '/'; + strcpy(buf + pwl + 1, dest); + } + + /* Normalise path. See the definition of fixdir() for what this means. */ + fixdir(buf); + + if (lchdir(buf, NULL, hard)) { + zsfree(buf); + return NULL; + } + return metafy(buf, -1, META_NOALLOC); +} + +/* do the extra processing associated with changing directory */ + +/**/ +static void +cd_new_pwd(int func, LinkNode dir, int chaselinks) +{ + Param pm; + List l; + char *new_pwd, *s; + int dirstacksize; + + if (func == BIN_PUSHD) + rolllist(dirstack, dir); + new_pwd = remnode(dirstack, dir); + + if (func == BIN_POPD && firstnode(dirstack)) { + zsfree(new_pwd); + new_pwd = getlinknode(dirstack); + } else if (func == BIN_CD && unset(AUTOPUSHD)) + zsfree(getlinknode(dirstack)); + + if (chaselinks) { + s = new_pwd; + new_pwd = findpwd(s); + zsfree(s); + } + if (isset(PUSHDIGNOREDUPS)) { + LinkNode n; + for (n = firstnode(dirstack); n; incnode(n)) { + if (!strcmp(new_pwd, getdata(n))) { + zsfree(remnode(dirstack, n)); + break; + } + } + } + + /* shift around the pwd variables, to make oldpwd and pwd relate to the + current (i.e. new) pwd */ + zsfree(oldpwd); + oldpwd = pwd; + pwd = new_pwd; + /* update the PWD and OLDPWD shell parameters */ + if ((pm = (Param) paramtab->getnode(paramtab, "PWD")) && + (pm->flags & PM_EXPORTED) && pm->env) + pm->env = replenv(pm->env, pwd); + if ((pm = (Param) paramtab->getnode(paramtab, "OLDPWD")) && + (pm->flags & PM_EXPORTED) && pm->env) + pm->env = replenv(pm->env, oldpwd); + if (unset(PUSHDSILENT) && func != BIN_CD && isset(INTERACTIVE)) + printdirstack(); + else if (doprintdir) { + fprintdir(pwd, stdout); + putchar('\n'); + } + + /* execute the chpwd function */ + if ((l = getshfunc("chpwd")) != &dummy_list) { + fflush(stdout); + fflush(stderr); + doshfunc(l, NULL, 0, 1); + } + + dirstacksize = getiparam("DIRSTACKSIZE"); + /* handle directory stack sizes out of range */ + if (dirstacksize > 0) { + int remove = countlinknodes(dirstack) - + (dirstacksize < 2 ? 2 : dirstacksize); + while (remove-- >= 0) + zsfree(remnode(dirstack, lastnode(dirstack))); + } +} + +/* Print the directory stack */ + +/**/ +static void +printdirstack(void) +{ + LinkNode node; + + fprintdir(pwd, stdout); + for (node = firstnode(dirstack); node; incnode(node)) { + putchar(' '); + fprintdir(getdata(node), stdout); + } + putchar('\n'); +} + +/* Normalise a path. Segments consisting of ., and foo/.. * + * combinations, are removed and the path is unmetafied. */ + +/**/ +static void +fixdir(char *src) +{ + char *dest = src; + char *d0 = dest; + +/*** if have RFS superroot directory ***/ +#ifdef HAVE_SUPERROOT + /* allow /.. segments to remain */ + while (*src == '/' && src[1] == '.' && src[2] == '.' && + (!src[3] || src[3] == '/')) { + *dest++ = '/'; + *dest++ = '.'; + *dest++ = '.'; + src += 3; + } +#endif + + for (;;) { + /* compress multiple /es into single */ + if (*src == '/') { + *dest++ = *src++; + while (*src == '/') + src++; + } + /* if we are at the end of the input path, remove a trailing / (if it + exists), and return ct */ + if (!*src) { + while (dest > d0 + 1 && dest[-1] == '/') + dest--; + *dest = '\0'; + return; + } + if (dest > d0 + 1 && src[0] == '.' && src[1] == '.' && + (src[2] == '\0' || src[2] == '/')) { + /* remove a foo/.. combination */ + for (dest--; dest > d0 + 1 && dest[-1] != '/'; dest--); + if (dest[-1] != '/') + dest--; + src++; + while (*++src == '/'); + } else if (src[0] == '.' && (src[1] == '/' || src[1] == '\0')) { + /* skip a . section */ + while (*++src == '/'); + } else { + /* copy a normal segment into the output */ + while (*src != '/' && *src != '\0') + if ((*dest++ = *src++) == Meta) + dest[-1] = *src++ ^ 32; + } + } +} + +/**/ +void +printqt(char *str) +{ + /* Print str, but turn any single quote into '\'' or ''. */ + for (; *str; str++) + if (*str == '\'') + printf(isset(RCQUOTES) ? "''" : "'\\''"); + else + putchar(*str); +} + +/**/ +void +printif(char *str, int c) +{ + /* If flag c has an argument, print that */ + if (str) { + printf(" -%c ", c); + quotedzputs(str, stdout); + } +} + +/**** history list functions ****/ + +/* fc, history, r */ + +/**/ +int +bin_fc(char *nam, char **argv, char *ops, int func) +{ + int first = -1, last = -1, retval, minflag = 0; + char *s; + struct asgment *asgf = NULL, *asgl = NULL; + Comp com = NULL; + + /* fc is only permitted in interactive shells */ + if (!interact) { + zwarnnam(nam, "not interactive shell", NULL, 0); + return 1; + } + /* with the -m option, the first argument is taken * + * as a pattern that history lines have to match */ + if (*argv && ops['m']) { + tokenize(*argv); + if (!(com = parsereg(*argv++))) { + zwarnnam(nam, "invalid match pattern", NULL, 0); + return 1; + } + } + if (ops['R']) { + /* read history from a file */ + readhistfile(*argv ? *argv : getsparam("HISTFILE"), 1); + return 0; + } + if (ops['W']) { + /* write history to a file */ + savehistfile(*argv ? *argv : getsparam("HISTFILE"), 1, + (ops['I'] ? 2 : 0)); + return 0; + } + if (ops['A']) { + /* append history to a file */ + savehistfile(*argv ? *argv : getsparam("HISTFILE"), 1, + (ops['I'] ? 3 : 1)); + return 0; + } + if (!(ops['l'] && unset(HISTNOSTORE))) + remhist(); + /* put foo=bar type arguments into the substitution list */ + while (*argv && equalsplit(*argv, &s)) { + Asgment a = (Asgment) alloc(sizeof *a); + + if (!asgf) + asgf = asgl = a; + else { + asgl->next = a; + asgl = a; + } + a->name = *argv; + a->value = s; + argv++; + } + /* interpret and check first history line specifier */ + if (*argv) { + minflag = **argv == '-'; + first = fcgetcomm(*argv); + if (first == -1) + return 1; + argv++; + } + /* interpret and check second history line specifier */ + if (*argv) { + last = fcgetcomm(*argv); + if (last == -1) + return 1; + argv++; + } + /* There is a maximum of two history specifiers. At least, there * + * will be as long as the history list is one-dimensional. */ + if (*argv) { + zwarnnam("fc", "too many arguments", NULL, 0); + return 1; + } + /* default values of first and last, and range checking */ + if (first == -1) + first = (ops['l']) ? curhist - 16 : curhist - 1; + if (last == -1) + last = (ops['l']) ? curhist - 1 : first; + if (first < firsthist()) + first = firsthist(); + if (last == -1) + last = (minflag) ? curhist : first; + else if (last < first) + last = first; + if (ops['l']) + /* list the required part of the history */ + retval = fclist(stdout, !ops['n'], ops['r'], ops['D'], + ops['d'] + ops['f'] * 2 + ops['E'] * 4 + ops['i'] * 8, + first, last, asgf, com); + else { + /* edit history file, and (if successful) use the result as a new command */ + int tempfd; + FILE *out; + char *fil; + + retval = 1; + fil = gettempname(); + if (((tempfd = open(fil, O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0600)) + == -1) || + ((out = fdopen(tempfd, "w")) == NULL)) { + zwarnnam("fc", "can't open temp file: %e", NULL, errno); + } else { + if (!fclist(out, 0, ops['r'], 0, 0, first, last, asgf, com)) { + char *editor; + + editor = auxdata ? auxdata : getsparam("FCEDIT"); + if (!editor) + editor = DEFAULT_FCEDIT; + + if (fcedit(editor, fil)) + if (stuff(fil)) + zwarnnam("fc", "%e: %s", s, errno); + else { + loop(0,1); + retval = lastval; + } + } + } + unlink(fil); + } + return retval; +} + +/* History handling functions: these are called by ZLE, as well as * + * the actual builtins. fcgetcomm() gets a history line, specified * + * either by number or leading string. fcsubs() performs a given * + * set of simple old=new substitutions on a given command line. * + * fclist() outputs a given range of history lines to a text file. */ + +/* get the history event associated with s */ + +/**/ +static int +fcgetcomm(char *s) +{ + int cmd; + + /* First try to match a history number. Negative * + * numbers indicate reversed numbering. */ + if ((cmd = atoi(s))) { + if (cmd < 0) + cmd = curhist + cmd; + if (cmd >= curhist) { + zwarnnam("fc", "bad history number: %d", 0, cmd); + return -1; + } + return cmd; + } + /* not a number, so search by string */ + cmd = hcomsearch(s); + if (cmd == -1) + zwarnnam("fc", "event not found: %s", s, 0); + return cmd; +} + +/* Perform old=new substituions. Uses the asgment structure from zsh.h, * + * which is essentially a linked list of string,replacement pairs. */ + +/**/ +static int +fcsubs(char **sp, struct asgment *sub) +{ + char *oldstr, *newstr, *oldpos, *newpos, *newmem, *s = *sp; + int subbed = 0; + + /* loop through the linked list */ + while (sub) { + oldstr = sub->name; + newstr = sub->value; + sub = sub->next; + oldpos = s; + /* loop over occurences of oldstr in s, replacing them with newstr */ + while ((newpos = (char *)strstr(oldpos, oldstr))) { + newmem = (char *) alloc(1 + (newpos - s) + + strlen(newstr) + strlen(newpos + strlen(oldstr))); + ztrncpy(newmem, s, newpos - s); + strcat(newmem, newstr); + oldpos = newmem + strlen(newmem); + strcat(newmem, newpos + strlen(oldstr)); + s = newmem; + subbed = 1; + } + } + *sp = s; + return subbed; +} + +/* Print a series of history events to a file. The file pointer is * + * given by f, and the required range of events by first and last. * + * subs is an optional list of foo=bar substitutions to perform on the * + * history lines before output. com is an optional comp structure * + * that the history lines are required to match. n, r, D and d are * + * options: n indicates that each line should be numbered. r indicates * + * that the lines should be output in reverse order (newest first). * + * D indicates that the real time taken by each command should be * + * output. d indicates that the time of execution of each command * + * should be output; d>1 means that the date should be output too; d>3 * + * means that mm/dd/yyyy form should be used for the dates, as opposed * + * to dd.mm.yyyy form; d>7 means that yyyy-mm-dd form should be used. */ + +/**/ +static int +fclist(FILE *f, int n, int r, int D, int d, int first, int last, struct asgment *subs, Comp com) +{ + int fclistdone = 0; + char *s, *hs; + Histent ent; + + /* reverse range if required */ + if (r) { + r = last; + last = first; + first = r; + } + /* suppress "no substitution" warning if no substitution is requested */ + if (!subs) + fclistdone = 1; + + for (;;) { + hs = quietgetevent(first); + if (!hs) { + zwarnnam("fc", "no such event: %d", NULL, first); + return 1; + } + s = dupstring(hs); + /* this if does the pattern matching, if required */ + if (!com || domatch(s, com, 0)) { + /* perform substitution */ + fclistdone |= fcsubs(&s, subs); + + /* do numbering */ + if (n) + fprintf(f, "%5d ", first); + ent = NULL; + /* output actual time (and possibly date) of execution of the + command, if required */ + if (d) { + struct tm *ltm; + if (!ent) + ent = gethistent(first); + ltm = localtime(&ent->stim); + if (d >= 2) { + if (d >= 8) { + fprintf(f, "%d-%02d-%02d ", + ltm->tm_year + 1900, + ltm->tm_mon + 1, ltm->tm_mday); + } else if (d >= 4) { + fprintf(f, "%d.%d.%d ", + ltm->tm_mday, ltm->tm_mon + 1, + ltm->tm_year + 1900); + } else { + fprintf(f, "%d/%d/%d ", + ltm->tm_mon + 1, ltm->tm_mday, + ltm->tm_year + 1900); + } + } + fprintf(f, "%02d:%02d ", ltm->tm_hour, ltm->tm_min); + } + /* display the time taken by the command, if required */ + if (D) { + long diff; + if (!ent) + ent = gethistent(first); + diff = (ent->ftim) ? ent->ftim - ent->stim : 0; + fprintf(f, "%ld:%02ld ", diff / 60, diff % 60); + } + + /* output the command */ + if (f == stdout) { + nicezputs(s, f); + putc('\n', f); + } else + fprintf(f, "%s\n", s); + } + /* move on to the next history line, or quit the loop */ + if (first == last) + break; + else if (first > last) + first--; + else + first++; + } + + /* final processing */ + if (f != stdout) + fclose(f); + if (!fclistdone) { + zwarnnam("fc", "no substitutions performed", NULL, 0); + return 1; + } + return 0; +} + +/* edit a history file */ + +/**/ +static int +fcedit(char *ename, char *fn) +{ + char *s; + + if (!strcmp(ename, "-")) + return 1; + + s = tricat(ename, " ", fn); + execstring(s, 1, 0); + zsfree(s); + + return !lastval; +} + +/**** parameter builtins ****/ + +/* Separate an argument into name=value parts, returning them in an * + * asgment structure. Because the asgment structure used is global, * + * only one of these can be active at a time. The string s gets placed * + * in this global structure, so it needs to be in permanent memory. */ + +/**/ +static Asgment +getasg(char *s) +{ + static struct asgment asg; + + /* sanity check for valid argument */ + if (!s) + return NULL; + + /* check if name is empty */ + if (*s == '=') { + zerr("bad assignment", NULL, 0); + return NULL; + } + asg.name = s; + + /* search for `=' */ + for (; *s && *s != '='; s++); + + /* found `=', so return with a value */ + if (*s) { + *s = '\0'; + asg.value = s + 1; + } else { + /* didn't find `=', so we only have a name */ + asg.value = NULL; + } + return &asg; +} + +/* declare, export, integer, local, readonly, typeset */ + +/**/ +int +bin_typeset(char *name, char **argv, char *ops, int func) +{ + Param pm; + Asgment asg; + Comp com; + char *optstr = "iLRZlurtxU"; + int on = 0, off = 0, roff, bit = PM_INTEGER; + int initon, initoff, of, i; + int returnval = 0, printflags = 0; + + /* hash -f is really the builtin `functions' */ + if (ops['f']) + return bin_functions(name, argv, ops, func); + + /* Translate the options into PM_* flags. * + * Unfortunately, this depends on the order * + * these flags are defined in zsh.h */ + for (; *optstr; optstr++, bit <<= 1) + if (ops[*optstr] == 1) + on |= bit; + else if (ops[*optstr] == 2) + off |= bit; + roff = off; + + /* Sanity checks on the options. Remove conficting options. */ + if (on & PM_INTEGER) + off |= PM_RIGHT_B | PM_LEFT | PM_RIGHT_Z | PM_UPPER | PM_ARRAY; + if (on & PM_LEFT) + off |= PM_RIGHT_B | PM_INTEGER; + if (on & PM_RIGHT_B) + off |= PM_LEFT | PM_INTEGER; + if (on & PM_RIGHT_Z) + off |= PM_INTEGER; + if (on & PM_UPPER) + off |= PM_LOWER; + if (on & PM_LOWER) + off |= PM_UPPER; + on &= ~off; + + /* Given no arguments, list whatever the options specify. */ + if (!*argv) { + if (!(on|roff)) + printflags |= PRINT_TYPE; + if (roff || ops['+']) + printflags |= PRINT_NAMEONLY; + scanhashtable(paramtab, 1, on|roff, 0, paramtab->printnode, printflags); + return 0; + } + + /* With the -m option, treat arguments as glob patterns */ + if (ops['m']) { + while ((asg = getasg(*argv++))) { + tokenize(asg->name); /* expand argument */ + if (!(com = parsereg(asg->name))) { + untokenize(asg->name); + zwarnnam(name, "bad pattern : %s", argv[-1], 0); + returnval = 1; + continue; + } + /* If no options or values are given, display all * + * parameters matching the glob pattern. */ + if (!(on || roff || asg->value)) { + scanmatchtable(paramtab, com, 0, 0, paramtab->printnode, 0); + continue; + } + /* Since either options or values are given, we search * + * through the parameter table and change all parameters * + * matching the glob pattern to have these flags and/or * + * value. */ + for (i = 0; i < paramtab->hsize; i++) { + for (pm = (Param) paramtab->nodes[i]; pm; pm = (Param) pm->next) { + if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) + continue; + if (domatch(pm->nam, com, 0)) { + /* set up flags if we have any */ + if (on || roff) { + if (PM_TYPE(pm->flags) == PM_ARRAY && (on & PM_UNIQUE) && + !(pm->flags & PM_READONLY & ~off)) + uniqarray((*pm->gets.afn) (pm)); + pm->flags = (pm->flags | on) & ~off; + if (PM_TYPE(pm->flags) != PM_ARRAY) { + if ((on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z | PM_INTEGER)) && auxlen) + pm->ct = auxlen; + /* did we just export this? */ + if ((pm->flags & PM_EXPORTED) && !pm->env) { + pm->env = addenv(pm->nam, (asg->value) ? asg->value : getsparam(pm->nam)); + } else if (!(pm->flags & PM_EXPORTED) && pm->env) { + /* did we just unexport this? */ + delenv(pm->env); + zsfree(pm->env); + pm->env = NULL; + } + } + } + /* set up a new value if given */ + if (asg->value) { + setsparam(pm->nam, ztrdup(asg->value)); + } + } + } + } + } + return returnval; + } + + /* Save the values of on, off, and func */ + initon = on; + initoff = off; + of = func; + + /* Take arguments literally. Don't glob */ + while ((asg = getasg(*argv++))) { + /* restore the original values of on, off, and func */ + on = initon; + off = initoff; + func = of; + on &= ~PM_ARRAY; + + /* check if argument is a valid identifier */ + if (!isident(asg->name)) { + zerr("not an identifier: %s", asg->name, 0); + returnval = 1; + continue; + } + bit = 0; /* flag for switching int<->not-int */ + if ((pm = (Param)paramtab->getnode(paramtab, asg->name)) && + (((pm->flags & PM_SPECIAL) && pm->level == locallevel) || + (!(pm->flags & PM_UNSET) && + ((locallevel == pm->level) || func == BIN_EXPORT) && + !(bit = ((off & pm->flags) | (on & ~pm->flags)) & PM_INTEGER)))) { + /* if no flags or values are given, just print this parameter */ + if (!on && !roff && !asg->value) { + paramtab->printnode((HashNode) pm, 0); + continue; + } + if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) { + zerrnam(name, "%s: restricted", pm->nam, 0); + returnval = 1; + continue; + } + if((pm->flags & PM_SPECIAL) && + PM_TYPE((pm->flags | on) & ~off) != PM_TYPE(pm->flags)) { + zerrnam(name, "%s: cannot change type of a special parameter", + pm->nam, 0); + returnval = 1; + continue; + } + if (PM_TYPE(pm->flags) == PM_ARRAY && (on & PM_UNIQUE) && + !(pm->flags & PM_READONLY & ~off)) + uniqarray((*pm->gets.afn) (pm)); + pm->flags = (pm->flags | on) & ~off; + if ((on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z | PM_INTEGER)) && + auxlen) + pm->ct = auxlen; + if (PM_TYPE(pm->flags) != PM_ARRAY) { + if (pm->flags & PM_EXPORTED) { + if (!(pm->flags & PM_UNSET) && !pm->env && !asg->value) + pm->env = addenv(asg->name, getsparam(asg->name)); + } else if (pm->env) { + delenv(pm->env); + zsfree(pm->env); + pm->env = NULL; + } + if (asg->value) + setsparam(asg->name, ztrdup(asg->value)); + } + } else { + if (bit) { + if (pm->flags & PM_READONLY) { + on |= ~off & PM_READONLY; + pm->flags &= ~PM_READONLY; + } + if (!asg->value) + asg->value = dupstring(getsparam(asg->name)); + unsetparam(asg->name); + } + /* create a new node for a parameter with the * + * flags in `on' minus the readonly flag */ + pm = createparam(ztrdup(asg->name), on & ~PM_READONLY); + DPUTS(!pm, "BUG: parameter not created"); + pm->ct = auxlen; + if (func != BIN_EXPORT) + pm->level = locallevel; + if (asg->value) + setsparam(asg->name, ztrdup(asg->value)); + pm->flags |= (on & PM_READONLY); + } + } + return returnval; +} + +/* Display or change the attributes of shell functions. * + * If called as autoload, it will define a new autoloaded * + * (undefined) shell function. */ + +/**/ +int +bin_functions(char *name, char **argv, char *ops, int func) +{ + Comp com; + Shfunc shf; + int i, returnval = 0; + int on = 0, off = 0; + + /* Do we have any flags defined? */ + if (ops['u'] || ops['t']) { + if (ops['u'] == 1) + on |= PM_UNDEFINED; + else if (ops['u'] == 2) + off |= PM_UNDEFINED; + + if (ops['t'] == 1) + on |= PM_TAGGED; + else if (ops['t'] == 2) + off |= PM_TAGGED; + } + + if (off & PM_UNDEFINED) { + zwarnnam(name, "invalid option(s)", NULL, 0); + return 1; + } + + /* If no arguments given, we will print functions. If flags * + * are given, we will print only functions containing these * + * flags, else we'll print them all. */ + if (!*argv) { + scanhashtable(shfunctab, 1, on|off, DISABLED, shfunctab->printnode, 0); + return 0; + } + + /* With the -m option, treat arguments as glob patterns */ + if (ops['m']) { + on &= ~PM_UNDEFINED; + for (; *argv; argv++) { + /* expand argument */ + tokenize(*argv); + if ((com = parsereg(*argv))) { + /* with no options, just print all functions matching the glob pattern */ + if (!(on|off)) { + scanmatchtable(shfunctab, com, 0, DISABLED, shfunctab->printnode, 0); + } else { + /* apply the options to all functions matching the glob pattern */ + for (i = 0; i < shfunctab->hsize; i++) { + for (shf = (Shfunc) shfunctab->nodes[i]; shf; shf = (Shfunc) shf->next) + if (domatch(shf->nam, com, 0) && !(shf->flags & DISABLED)) + shf->flags = (shf->flags | on) & (~off); + } + } + } else { + untokenize(*argv); + zwarnnam(name, "bad pattern : %s", *argv, 0); + returnval = 1; + } + } + return returnval; + } + + /* Take the arguments literally -- do not glob */ + for (; *argv; argv++) { + if ((shf = (Shfunc) shfunctab->getnode(shfunctab, *argv))) { + /* if any flag was given */ + if (on|off) + /* turn on/off the given flags */ + shf->flags = (shf->flags | (on & ~PM_UNDEFINED)) & ~off; + else + /* no flags, so just print */ + shfunctab->printnode((HashNode) shf, 0); + } else if (on & PM_UNDEFINED) { + /* Add a new undefined (autoloaded) function to the * + * hash table with the corresponding flags set. */ + shf = (Shfunc) zcalloc(sizeof *shf); + shf->flags = on; + shf->funcdef = mkautofn(shf); + shfunctab->addnode(shfunctab, ztrdup(*argv), shf); + } else + returnval = 1; + } + return returnval; +} + +/**/ +static List +mkautofn(Shfunc shf) +{ + List l; + Sublist s; + Pline p; + Cmd c; + AutoFn a; + PERMALLOC { + a = (AutoFn)allocnode(N_AUTOFN); + a->shf = shf; + c = (Cmd)allocnode(N_CMD); + c->type = AUTOFN; + c->u.autofn = a; + p = (Pline)allocnode(N_PLINE); + p->left = c; + p->type = END; + s = (Sublist)allocnode(N_SUBLIST); + s->left = p; + l = (List)allocnode(N_LIST); + l->left = s; + l->type = Z_SYNC; + } LASTALLOC; + return l; +} + +/* unset: unset parameters */ + +/**/ +int +bin_unset(char *name, char **argv, char *ops, int func) +{ + Param pm, next; + Comp com; + char *s; + int match = 0, returnval = 0; + int i; + + /* unset -f is the same as unfunction */ + if (ops['f']) + return bin_unhash(name, argv, ops, func); + + /* with -m option, treat arguments as glob patterns */ + if (ops['m']) { + while ((s = *argv++)) { + /* expand */ + tokenize(s); + if ((com = parsereg(s))) { + /* Go through the parameter table, and unset any matches */ + for (i = 0; i < paramtab->hsize; i++) { + for (pm = (Param) paramtab->nodes[i]; pm; pm = next) { + /* record pointer to next, since we may free this one */ + next = (Param) pm->next; + if ((!(pm->flags & PM_RESTRICTED) || + unset(RESTRICTED)) && domatch(pm->nam, com, 0)) { + unsetparam(pm->nam); + match++; + } + } + } + } else { + untokenize(s); + zwarnnam(name, "bad pattern : %s", s, 0); + returnval = 1; + } + } + /* If we didn't match anything, we return 1. */ + if (!match) + returnval = 1; + return returnval; + } + + /* do not glob -- unset the given parameter */ + while ((s = *argv++)) { + pm = (Param) paramtab->getnode(paramtab, s); + if (!pm) + returnval = 1; + else if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) { + zerrnam(name, "%s: restricted", pm->nam, 0); + returnval = 1; + } else + unsetparam(s); + } + return returnval; +} + +/* type, whence, which */ + +/**/ +int +bin_whence(char *nam, char **argv, char *ops, int func) +{ + HashNode hn; + Comp com; + int returnval = 0; + int printflags = 0; + int csh, all, v, wd; + int informed; + char *cnam; + + /* Check some option information */ + csh = ops['c']; + v = ops['v']; + all = ops['a']; + wd = ops['w']; + + if (ops['w']) + printflags |= PRINT_WHENCE_WORD; + else if (ops['c']) + printflags |= PRINT_WHENCE_CSH; + else if (ops['v']) + printflags |= PRINT_WHENCE_VERBOSE; + else + printflags |= PRINT_WHENCE_SIMPLE; + if (ops['f']) + printflags |= PRINT_WHENCE_FUNCDEF; + + /* With -m option -- treat arguments as a glob patterns */ + if (ops['m']) { + for (; *argv; argv++) { + /* parse the pattern */ + tokenize(*argv); + if (!(com = parsereg(*argv))) { + untokenize(*argv); + zwarnnam(nam, "bad pattern : %s", *argv, 0); + returnval = 1; + continue; + } + if (!ops['p']) { + /* -p option is for path search only. * + * We're not using it, so search for ... */ + + /* aliases ... */ + scanmatchtable(aliastab, com, 0, DISABLED, aliastab->printnode, printflags); + + /* and reserved words ... */ + scanmatchtable(reswdtab, com, 0, DISABLED, reswdtab->printnode, printflags); + + /* and shell functions... */ + scanmatchtable(shfunctab, com, 0, DISABLED, shfunctab->printnode, printflags); + + /* and builtins. */ + scanmatchtable(builtintab, com, 0, DISABLED, builtintab->printnode, printflags); + } + /* Done search for `internal' commands, if the -p option * + * was not used. Now search the path. */ + cmdnamtab->filltable(cmdnamtab); + scanmatchtable(cmdnamtab, com, 0, 0, cmdnamtab->printnode, printflags); + + } + return returnval; + } + + /* Take arguments literally -- do not glob */ + for (; *argv; argv++) { + informed = 0; + + if (!ops['p']) { + /* Look for alias */ + if ((hn = aliastab->getnode(aliastab, *argv))) { + aliastab->printnode(hn, printflags); + if (!all) + continue; + informed = 1; + } + /* Look for reserved word */ + if ((hn = reswdtab->getnode(reswdtab, *argv))) { + reswdtab->printnode(hn, printflags); + if (!all) + continue; + informed = 1; + } + /* Look for shell function */ + if ((hn = shfunctab->getnode(shfunctab, *argv))) { + shfunctab->printnode(hn, printflags); + if (!all) + continue; + informed = 1; + } + /* Look for builtin command */ + if ((hn = builtintab->getnode(builtintab, *argv))) { + builtintab->printnode(hn, printflags); + if (!all) + continue; + informed = 1; + } + /* Look for commands that have been added to the * + * cmdnamtab with the builtin `hash foo=bar'. */ + if ((hn = cmdnamtab->getnode(cmdnamtab, *argv)) && (hn->flags & HASHED)) { + cmdnamtab->printnode(hn, printflags); + if (!all) + continue; + informed = 1; + } + } + + /* Option -a is to search the entire path, * + * rather than just looking for one match. */ + if (all) { + char **pp, buf[PATH_MAX], *z; + + for (pp = path; *pp; pp++) { + z = buf; + if (**pp) { + strucpy(&z, *pp); + *z++ = '/'; + } + if ((z - buf) + strlen(*argv) >= PATH_MAX) + continue; + strcpy(z, *argv); + if (iscom(buf)) { + if (wd) { + printf("%s: command\n", *argv); + } else { + if (v && !csh) + zputs(*argv, stdout), fputs(" is ", stdout); + zputs(buf, stdout); + if (ops['s']) + print_if_link(buf); + fputc('\n', stdout); + } + informed = 1; + } + } + if (!informed && (wd || v || csh)) { + zputs(*argv, stdout); + puts(wd ? ": none" : " not found"); + returnval = 1; + } + } else if ((cnam = findcmd(*argv))) { + /* Found external command. */ + if (wd) { + printf("%s: command\n", *argv); + } else { + if (v && !csh) + zputs(*argv, stdout), fputs(" is ", stdout); + zputs(cnam, stdout); + if (ops['s']) + print_if_link(cnam); + fputc('\n', stdout); + } + zsfree(cnam); + } else { + /* Not found at all. */ + if (v || csh || wd) + zputs(*argv, stdout), puts(wd ? ": none" : " not found"); + returnval = 1; + } + } + return returnval; +} + +/**** command & named directory hash table builtins ****/ + +/***************************************************************** + * hash -- explicitly hash a command. * + * 1) Given no arguments, list the hash table. * + * 2) The -m option prints out commands in the hash table that * + * match a given glob pattern. * + * 3) The -f option causes the entire path to be added to the * + * hash table (cannot be combined with any arguments). * + * 4) The -r option causes the entire hash table to be discarded * + * (cannot be combined with any arguments). * + * 5) Given argument of the form foo=bar, add element to command * + * hash table, so that when `foo' is entered, then `bar' is * + * executed. * + * 6) Given arguments not of the previous form, add it to the * + * command hash table as if it were being executed. * + * 7) The -d option causes analogous things to be done using * + * the named directory hash table. * + *****************************************************************/ + +/**/ +int +bin_hash(char *name, char **argv, char *ops, int func) +{ + HashTable ht; + Comp com; + Asgment asg; + int returnval = 0; + + if (ops['d']) + ht = nameddirtab; + else + ht = cmdnamtab; + + if (ops['r'] || ops['f']) { + /* -f and -r can't be used with any arguments */ + if (*argv) { + zwarnnam("hash", "too many arguments", NULL, 0); + return 1; + } + + /* empty the hash table */ + if (ops['r']) + ht->emptytable(ht); + + /* fill the hash table in a standard way */ + if (ops['f']) + ht->filltable(ht); + + return 0; + } + + /* Given no arguments, display current hash table. */ + if (!*argv) { + scanhashtable(ht, 1, 0, 0, ht->printnode, 0); + return 0; + } + + while (*argv) { + void *hn; + if (ops['m']) { + /* with the -m option, treat the argument as a glob pattern */ + tokenize(*argv); /* expand */ + if ((com = parsereg(*argv))) { + /* display matching hash table elements */ + scanmatchtable(ht, com, 0, 0, ht->printnode, 0); + } else { + untokenize(*argv); + zwarnnam(name, "bad pattern : %s", *argv, 0); + returnval = 1; + } + } else if((asg = getasg(*argv))->value) { + if(isset(RESTRICTED)) { + zwarnnam(name, "restricted: %s", asg->value, 0); + returnval = 1; + } else { + /* The argument is of the form foo=bar, * + * so define an entry for the table. */ + if(ops['d']) { + Nameddir nd = hn = zcalloc(sizeof *nd); + nd->flags = 0; + nd->dir = ztrdup(asg->value); + } else { + Cmdnam cn = hn = zcalloc(sizeof *cn); + cn->flags = HASHED; + cn->u.cmd = ztrdup(asg->value); + } + ht->addnode(ht, ztrdup(asg->name), hn); + if(ops['v']) + ht->printnode(hn, 0); + } + } else if (!(hn = ht->getnode2(ht, asg->name))) { + /* With no `=value' part to the argument, * + * work out what it ought to be. */ + if(ops['d']) { + if(!getnameddir(asg->name)) { + zwarnnam(name, "no such directory name: %s", asg->name, 0); + returnval = 1; + } + } else { + if (!hashcmd(asg->name, path)) { + zwarnnam(name, "no such command: %s", asg->name, 0); + returnval = 1; + } + } + if(ops['v'] && (hn = ht->getnode2(ht, asg->name))) + ht->printnode(hn, 0); + } else if(ops['v']) + ht->printnode(hn, 0); + argv++; + } + return returnval; +} + +/* unhash: remove specified elements from a hash table */ + +/**/ +int +bin_unhash(char *name, char **argv, char *ops, int func) +{ + HashTable ht; + HashNode hn, nhn; + Comp com; + int match = 0, returnval = 0; + int i; + + /* Check which hash table we are working with. */ + if (ops['d']) + ht = nameddirtab; /* named directories */ + else if (ops['f']) + ht = shfunctab; /* shell functions */ + else if (ops['a']) + ht = aliastab; /* aliases */ + else + ht = cmdnamtab; /* external commands */ + + /* With -m option, treat arguments as glob patterns. * + * "unhash -m '*'" is legal, but not recommended. */ + if (ops['m']) { + for (; *argv; argv++) { + /* expand argument */ + tokenize(*argv); + if ((com = parsereg(*argv))) { + /* remove all nodes matching glob pattern */ + for (i = 0; i < ht->hsize; i++) { + for (hn = ht->nodes[i]; hn; hn = nhn) { + /* record pointer to next, since we may free this one */ + nhn = hn->next; + if (domatch(hn->nam, com, 0)) { + ht->freenode(ht->removenode(ht, hn->nam)); + match++; + } + } + } + } else { + untokenize(*argv); + zwarnnam(name, "bad pattern : %s", *argv, 0); + returnval = 1; + } + } + /* If we didn't match anything, we return 1. */ + if (!match) + returnval = 1; + return returnval; + } + + /* Take arguments literally -- do not glob */ + for (; *argv; argv++) { + if ((hn = ht->removenode(ht, *argv))) { + ht->freenode(hn); + } else { + zwarnnam(name, "no such hash table element: %s", *argv, 0); + returnval = 1; + } + } + return returnval; +} + +/**** alias builtins ****/ + +/* alias: display or create aliases. */ + +/**/ +int +bin_alias(char *name, char **argv, char *ops, int func) +{ + Alias a; + Comp com; + Asgment asg; + int haveflags = 0, returnval = 0; + int flags1 = 0, flags2 = DISABLED; + int printflags = 0; + + /* Did we specify the type of alias? */ + if (ops['r'] || ops['g']) { + if (ops['r'] && ops['g']) { + zwarnnam(name, "illegal combination of options", NULL, 0); + return 1; + } + haveflags = 1; + if (ops['g']) + flags1 |= ALIAS_GLOBAL; + else + flags2 |= ALIAS_GLOBAL; + } + + if (ops['L']) + printflags |= PRINT_LIST; + + /* In the absence of arguments, list all aliases. If a command * + * line flag is specified, list only those of that type. */ + if (!*argv) { + scanhashtable(aliastab, 1, flags1, flags2, aliastab->printnode, printflags); + return 0; + } + + /* With the -m option, treat the arguments as * + * glob patterns of aliases to display. */ + if (ops['m']) { + for (; *argv; argv++) { + tokenize(*argv); /* expand argument */ + if ((com = parsereg(*argv))) { + /* display the matching aliases */ + scanmatchtable(aliastab, com, flags1, flags2, aliastab->printnode, printflags); + } else { + untokenize(*argv); + zwarnnam(name, "bad pattern : %s", *argv, 0); + returnval = 1; + } + } + return returnval; + } + + /* Take arguments literally. Don't glob */ + while ((asg = getasg(*argv++))) { + if (asg->value && !ops['L']) { + /* The argument is of the form foo=bar and we are not * + * forcing a listing with -L, so define an alias */ + aliastab->addnode(aliastab, ztrdup(asg->name), + createaliasnode(ztrdup(asg->value), flags1)); + } else if ((a = (Alias) aliastab->getnode(aliastab, asg->name))) { + /* display alias if appropriate */ + if (!haveflags || + (ops['r'] && !(a->flags & ALIAS_GLOBAL)) || + (ops['g'] && (a->flags & ALIAS_GLOBAL))) + aliastab->printnode((HashNode) a, printflags); + } else + returnval = 1; + } + return returnval; +} + + +/**** miscellaneous builtins ****/ + +/* true, : (colon) */ + +/**/ +int +bin_true(char *name, char **argv, char *ops, int func) +{ + return 0; +} + +/* false builtin */ + +/**/ +int +bin_false(char *name, char **argv, char *ops, int func) +{ + return 1; +} + +/* the zle buffer stack */ + +/**/ +LinkList bufstack; + +/* echo, print, pushln */ + +/**/ +int +bin_print(char *name, char **args, char *ops, int func) +{ + int nnl = 0, fd, argc, n; + int *len; + Histent ent; + FILE *fout = stdout; + + /* -m option -- treat the first argument as a pattern and remove + * arguments not matching */ + if (ops['m']) { + Comp com; + char **t, **p; + + tokenize(*args); + if (!(com = parsereg(*args))) { + untokenize(*args); + zwarnnam(name, "bad pattern : %s", *args, 0); + return 1; + } + for (p = ++args; *p; p++) + if (!domatch(*p, com, 0)) + for (t = p--; (*t = t[1]); t++); + } + /* compute lengths, and interpret according to -P, -D, -e, etc. */ + argc = arrlen(args); + len = (int *)ncalloc(argc * sizeof(int)); + for(n = 0; n < argc; n++) { + /* first \ sequences */ + if (!ops['e'] && (ops['R'] || ops['r'] || ops['E'])) + unmetafy(args[n], &len[n]); + else + args[n] = getkeystring(args[n], &len[n], + func != BIN_ECHO && !ops['e'], &nnl); + /* -P option -- interpret as a prompt sequence */ + if(ops['P']) + args[n] = unmetafy(promptexpand(metafy(args[n], len[n], + META_NOALLOC), 0, NULL, NULL), &len[n]); + /* -D option -- interpret as a directory, and use ~ */ + if(ops['D']) { + Nameddir d = finddir(args[n]); + if(d) { + char *arg = alloc(strlen(args[n]) + 1); + sprintf(arg, "~%s%s", d->nam, + args[n] + strlen(d->dir)); + args[n] = arg; + len[n] = strlen(args[n]); + } + } + } + + /* -z option -- push the arguments onto the editing buffer stack */ + if (ops['z']) { + PERMALLOC { + pushnode(bufstack, sepjoin(args, NULL)); + } LASTALLOC; + return 0; + } + /* -s option -- add the arguments to the history list */ + if (ops['s']) { + int nwords = 0, nlen, iwords; + char **pargs = args; + + PERMALLOC { + ent = gethistent(++curhist); + zsfree(ent->text); + if (ent->nwords) + zfree(ent->words, ent->nwords*2*sizeof(short)); + while (*pargs++) + nwords++; + if ((ent->nwords = nwords)) { + ent->words = (short *)zalloc(nwords*2*sizeof(short)); + nlen = iwords = 0; + for (pargs = args; *pargs; pargs++) { + ent->words[iwords++] = nlen; + nlen += strlen(*pargs); + ent->words[iwords++] = nlen; + nlen++; + } + } else + ent->words = (short *)NULL; + ent->text = zjoin(args, ' '); + ent->stim = ent->ftim = time(NULL); + ent->flags = 0; + } LASTALLOC; + return 0; + } + /* -u and -p -- output to other than standard output */ + if (ops['u'] || ops['p']) { + if (ops['u']) { + for (fd = 0; fd < 10; fd++) + if (ops[fd + '0']) + break; + if (fd == 10) + fd = 0; + } else + fd = coprocout; + if ((fd = dup(fd)) < 0) { + zwarnnam(name, "bad file number", NULL, 0); + return 1; + } + if ((fout = fdopen(fd, "w")) == 0) { + zwarnnam(name, "bad mode on fd", NULL, 0); + return 1; + } + } + + /* -o and -O -- sort the arguments */ + if (ops['o']) { + if (ops['i']) + qsort(args, arrlen(args), sizeof(char *), cstrpcmp); + + else + qsort(args, arrlen(args), sizeof(char *), strpcmp); + } else if (ops['O']) { + if (ops['i']) + qsort(args, arrlen(args), sizeof(char *), invcstrpcmp); + + else + qsort(args, arrlen(args), sizeof(char *), invstrpcmp); + } + /* after sorting arguments, recalculate lengths */ + if(ops['o'] || ops['O']) + for(n = 0; n < argc; n++) + len[n] = strlen(args[n]); + + /* -c -- output in columns */ + if (ops['c']) { + int l, nc, nr, sc, n, t, i; + char **ap; + + for (n = l = 0, ap = args; *ap; ap++, n++) + if (l < (t = strlen(*ap))) + l = t; + + sc = l + 2; + nc = (columns + 1) / sc; + if (!nc) + nc = 1; + nr = (n + nc - 1) / nc; + + for (i = 0; i < nr; i++) { + ap = args + i; + do { + l = strlen(*ap); + fprintf(fout, "%s", *ap); + for (t = nr; t && *ap; t--, ap++); + if(*ap) + for (; l < sc; l++) + fputc(' ', fout); + } while (*ap); + fputc(ops['N'] ? '\0' : '\n', fout); + } + if (fout != stdout) + fclose(fout); + return 0; + } + /* normal output */ + for (; *args; args++, len++) { + fwrite(*args, *len, 1, fout); + if (args[1]) + fputc(ops['l'] ? '\n' : ops['N'] ? '\0' : ' ', fout); + } + if (!(ops['n'] || nnl)) + fputc(ops['N'] ? '\0' : '\n', fout); + if (fout != stdout) + fclose(fout); + return 0; +} + +/* echotc: output a termcap */ + +/**/ +int +bin_echotc(char *name, char **argv, char *ops, int func) +{ + char *s, buf[2048], *t, *u; + int num, argct; + + s = *argv++; + if (termflags & TERM_BAD) + return 1; + if ((termflags & TERM_UNKNOWN) && (isset(INTERACTIVE) || !init_term())) + return 1; + /* if the specified termcap has a numeric value, display it */ + if ((num = tgetnum(s)) != -1) { + printf("%d\n", num); + return 0; + } + /* if the specified termcap is boolean, and set, say so * + * ncurses can tell if an existing boolean capability is * + * off so in this case we print "no". */ +#if !defined(NCURSES_VERSION) || !defined(COLOR_PAIR) + if (tgetflag(s) > 0) { + puts("yes"); + return (0); + } +#else /* NCURSES_VERSION && COLOR_PAIR */ + switch (tgetflag(s)) { + case -1: + break; + case 0: + puts("no"); + return 0; + default: + puts("yes"); + return 0; + } +#endif /* NCURSES_VERSION && COLOR_PAIR */ + /* get a string-type capability */ + u = buf; + t = tgetstr(s, &u); + if (!t || !*t) { + /* capability doesn't exist, or (if boolean) is off */ + zwarnnam(name, "no such capability: %s", s, 0); + return 1; + } + /* count the number of arguments required */ + for (argct = 0, u = t; *u; u++) + if (*u == '%') { + if (u++, (*u == 'd' || *u == '2' || *u == '3' || *u == '.' || + *u == '+')) + argct++; + } + /* check that the number of arguments provided is correct */ + if (arrlen(argv) != argct) { + zwarnnam(name, (arrlen(argv) < argct) ? "not enough arguments" : + "too many arguments", NULL, 0); + return 1; + } + /* output string, through the proper termcap functions */ + if (!argct) + tputs(t, 1, putraw); + else { + num = (argv[1]) ? atoi(argv[1]) : atoi(*argv); + tputs(tgoto(t, atoi(*argv), num), num, putraw); + } + return 0; +} + +/* shift builtin */ + +/**/ +int +bin_shift(char *name, char **argv, char *ops, int func) +{ + int num = 1, l, ret = 0; + char **s; + + /* optional argument can be either numeric or an array */ + if (*argv && !getaparam(*argv)) + num = matheval(*argv++); + + if (num < 0) { + zwarnnam(name, "argument to shift must be non-negative", NULL, 0); + return 1; + } + + if (*argv) { + for (; *argv; argv++) + if ((s = getaparam(*argv))) { + if (num > arrlen(s)) { + zwarnnam(name, "shift count must be <= $#", NULL, 0); + ret++; + continue; + } + PERMALLOC { + s = arrdup(s + num); + } LASTALLOC; + setaparam(*argv, s); + } + } else { + if (num > (l = arrlen(pparams))) { + zwarnnam(name, "shift count must be <= $#", NULL, 0); + ret = 1; + } else { + s = zalloc((l - num + 1) * sizeof(char *)); + memcpy(s, pparams + num, (l - num + 1) * sizeof(char *)); + while (num--) + zsfree(pparams[num]); + zfree(pparams, (l + 1) * sizeof(char *)); + pparams = s; + } + } + return ret; +} + +/* getopts: automagical option handling for shell scripts */ + +/**/ +int +bin_getopts(char *name, char **argv, char *ops, int func) +{ + int lenstr, lenoptstr, quiet, lenoptbuf; + char *optstr = unmetafy(*argv++, &lenoptstr), *var = *argv++; + char **args = (*argv) ? argv : pparams; + static int optcind = 0; + char *str, optbuf[2] = " ", *p, opch; + + /* zoptind keeps count of the current argument number. The * + * user can set it to zero to start a new option parse. */ + if (zoptind < 1) { + /* first call */ + zoptind = 1; + optcind = 0; + } + if(zoptind > arrlen(args)) + /* no more options */ + return 1; + + /* leading ':' in optstr means don't print an error message */ + quiet = *optstr == ':'; + optstr += quiet; + lenoptstr -= quiet; + + /* find place in relevant argument */ + str = unmetafy(dupstring(args[zoptind - 1]), &lenstr); + if(optcind >= lenstr) { + optcind = 0; + if(!args[zoptind++]) + return 1; + str = unmetafy(dupstring(args[zoptind - 1]), &lenstr); + } + if(!optcind) { + if(lenstr < 2 || (*str != '-' && *str != '+')) + return 1; + if(lenstr == 2 && str[0] == '-' && str[1] == '-') { + zoptind++; + return 1; + } + optcind++; + } + opch = str[optcind++]; + if(str[0] == '+') { + optbuf[0] = '+'; + lenoptbuf = 2; + } else + lenoptbuf = 1; + optbuf[lenoptbuf - 1] = opch; + + /* check for legality */ + if(opch == ':' || !(p = memchr(optstr, opch, lenoptstr))) { + p = "?"; +err: + zsfree(zoptarg); + if(quiet) { + setsparam(var, ztrdup(p)); + zoptarg = metafy(optbuf, lenoptbuf, META_DUP); + } else { + zerr(*p == '?' ? "bad option: -%c" : + "argument expected after -%c option", NULL, opch); + zoptarg=ztrdup(""); + errflag = 0; + } + return 0; + } + + /* check for required argument */ + if(p[1] == ':') { + if(optcind == lenstr) { + if(!args[zoptind]) { + p = ":"; + goto err; + } + p = ztrdup(args[zoptind++]); + } else + p = metafy(str+optcind, lenstr-optcind, META_DUP); + optcind = ztrlen(args[zoptind - 1]); + zsfree(zoptarg); + zoptarg = p; + } else { + zsfree(zoptarg); + zoptarg = ztrdup(""); + } + + setsparam(var, metafy(optbuf, lenoptbuf, META_DUP)); + return 0; +} + +/* break, bye, continue, exit, logout, return -- most of these take * + * one numeric argument, and the other (logout) is related to return. * + * (return is treated as a logout when in a login shell.) */ + +/**/ +int +bin_break(char *name, char **argv, char *ops, int func) +{ + int num = lastval, nump = 0; + + /* handle one optional numeric argument */ + if (*argv) { + num = matheval(*argv++); + nump = 1; + } + + switch (func) { + case BIN_CONTINUE: + if (!loops) { /* continue is only permitted in loops */ + zerrnam(name, "not in while, until, select, or repeat loop", NULL, 0); + return 1; + } + contflag = 1; /* ARE WE SUPPOSED TO FALL THROUGH HERE? */ + case BIN_BREAK: + if (!loops) { /* break is only permitted in loops */ + zerrnam(name, "not in while, until, select, or repeat loop", NULL, 0); + return 1; + } + breaks = nump ? minimum(num,loops) : 1; + break; + case BIN_RETURN: + if (isset(INTERACTIVE) || locallevel || sourcelevel) { + retflag = 1; + breaks = loops; + lastval = num; + if (trapreturn == -2) + trapreturn = lastval; + return lastval; + } + zexit(num, 0); /* else treat return as logout/exit */ + break; + case BIN_LOGOUT: + if (unset(LOGINSHELL)) { + zerrnam(name, "not login shell", NULL, 0); + return 1; + } + zexit(num, 0); + break; + case BIN_EXIT: + zexit(num, 0); + break; + } + return 0; +} + +/* we have printed a 'you have stopped (running) jobs.' message */ + +/**/ +int stopmsg; + +/* check to see if user has jobs running/stopped */ + +/**/ +static void +checkjobs(void) +{ + int i; + + for (i = 1; i < MAXJOB; i++) + if (i != thisjob && (jobtab[i].stat & STAT_LOCKED) && + !(jobtab[i].stat & STAT_NOPRINT)) + break; + if (i < MAXJOB) { + if (jobtab[i].stat & STAT_STOPPED) { + +#ifdef USE_SUSPENDED + zerr("you have suspended jobs.", NULL, 0); +#else + zerr("you have stopped jobs.", NULL, 0); +#endif + + } else + zerr("you have running jobs.", NULL, 0); + stopmsg = 1; + } +} + +/* exit the shell. val is the return value of the shell. * + * from_signal should be non-zero if zexit is being called * + * because of a signal. */ + +/**/ +void +zexit(int val, int from_signal) +{ + static int in_exit; + + HEAPALLOC { + if (isset(MONITOR) && !stopmsg && !from_signal) { + scanjobs(); /* check if jobs need printing */ + checkjobs(); /* check if any jobs are running/stopped */ + if (stopmsg) { + stopmsg = 2; + LASTALLOC_RETURN; + } + } + if (in_exit++ && from_signal) + LASTALLOC_RETURN; + if (isset(MONITOR)) + /* send SIGHUP to any jobs left running */ + killrunjobs(from_signal); + if (isset(RCS) && interact) { + if (!nohistsave) + savehistfile(getsparam("HISTFILE"), 1, isset(APPENDHISTORY) ? 3 : 0); + if (islogin && !subsh) { + sourcehome(".zlogout"); +#ifdef GLOBAL_ZLOGOUT + source(GLOBAL_ZLOGOUT); +#endif + } + } + if (sigtrapped[SIGEXIT]) + dotrap(SIGEXIT); + if (mypid != getpid()) + _exit(val); + else + exit(val); + } LASTALLOC; +} + +/* . (dot), source */ + +/**/ +int +bin_dot(char *name, char **argv, char *ops, int func) +{ + char **old, *old0 = NULL; + int ret, diddot = 0, dotdot = 0; + char buf[PATH_MAX]; + char *s, **t, *enam, *arg0; + struct stat st; + + if (!*argv || strlen(*argv) >= PATH_MAX) + return 0; + old = pparams; + /* get arguments for the script */ + if (argv[1]) { + PERMALLOC { + pparams = arrdup(argv + 1); + } LASTALLOC; + } + enam = arg0 = ztrdup(*argv); + if (isset(FUNCTIONARGZERO)) { + old0 = argzero; + argzero = arg0; + } + s = unmeta(enam); + errno = ENOENT; + ret = 1; + /* for source only, check in current directory first */ + if (*name != '.' && access(s, F_OK) == 0 + && stat(s, &st) >= 0 && !S_ISDIR(st.st_mode)) { + diddot = 1; + ret = source(enam); + } + if (ret) { + /* use a path with / in it */ + for (s = arg0; *s; s++) + if (*s == '/') { + if (*arg0 == '.') { + if (arg0 + 1 == s) + ++diddot; + else if (arg0[1] == '.' && arg0 + 2 == s) + ++dotdot; + } + ret = source(arg0); + break; + } + if (!*s || (ret && isset(PATHDIRS) && diddot < 2 && dotdot == 0)) { + /* search path for script */ + for (t = path; *t; t++) { + if (!(*t)[0] || ((*t)[0] == '.' && !(*t)[1])) { + if (diddot) + continue; + diddot = 1; + strcpy(buf, arg0); + } else { + if (strlen(*t) + strlen(arg0) + 1 >= PATH_MAX) + continue; + sprintf(buf, "%s/%s", *t, arg0); + } + s = unmeta(buf); + if (access(s, F_OK) == 0 && stat(s, &st) >= 0 + && !S_ISDIR(st.st_mode)) { + ret = source(enam = buf); + break; + } + } + } + } + /* clean up and return */ + if (argv[1]) { + freearray(pparams); + pparams = old; + } + if (ret) + zwarnnam(name, "%e: %s", enam, errno); + zsfree(arg0); + if (old0) + argzero = old0; + return ret ? ret : lastval; +} + +/**/ +int +bin_emulate(char *nam, char **argv, char *ops, int func) +{ + emulate(*argv, ops['R']); + return 0; +} + +/* eval: simple evaluation */ + +/**/ +int +bin_eval(char *nam, char **argv, char *ops, int func) +{ + List list; + + inpush(zjoin(argv, ' '), 0, NULL); + strinbeg(); + stophist = 2; + list = parse_list(); + strinend(); + inpop(); + if (!list) { + errflag = 0; + return 1; + } + execlist(list, 1, 0); + if (errflag) { + lastval = errflag; + errflag = 0; + } + return lastval; +} + +static char *zbuf; +static int readfd; + +/* Read a character from readfd, or from the buffer zbuf. Return EOF on end of +file/buffer. */ + +/* read: get a line of input, or (for compctl functions) return some * + * useful data about the state of the editing line. The -E and -e * + * options mean that the result should be sent to stdout. -e means, * + * in addition, that the result should not actually be assigned to * + * the specified parameters. */ + +/**/ +int +bin_read(char *name, char **args, char *ops, int func) +{ + char *reply, *readpmpt; + int bsiz, c = 0, gotnl = 0, al = 0, first, nchars = 1, bslash; + int haso = 0; /* true if /dev/tty has been opened specially */ + int isem = !strcmp(term, "emacs"); + char *buf, *bptr, *firstarg, *zbuforig; + LinkList readll = newlinklist(); + + if ((ops['k'] || ops['b']) && *args && idigit(**args)) { + if (!(nchars = atoi(*args))) + nchars = 1; + args++; + } + + firstarg = *args; + if (*args && **args == '?') + args++; + /* default result parameter */ + reply = *args ? *args++ : ops['A'] ? "reply" : "REPLY"; + if (ops['A'] && *args) { + zwarnnam(name, "only one array argument allowed", NULL, 0); + return 1; + } + + /* handle compctl case */ + if(ops['l'] || ops['c']) + return compctlread(name, args, ops, reply); + + if ((ops['k'] && !ops['u'] && !ops['p']) || ops['q']) { + if (SHTTY == -1) { + /* need to open /dev/tty specially */ + SHTTY = open("/dev/tty", O_RDWR|O_NOCTTY); + haso = 1; + } + /* We should have a SHTTY opened by now. */ + if (SHTTY == -1) { + /* Unfortunately, we didn't. */ + fprintf(stderr, "not interactive and can't open terminal\n"); + fflush(stderr); + return 1; + } + if (unset(INTERACTIVE)) + gettyinfo(&shttyinfo); + /* attach to the tty */ + attachtty(mypgrp); + if (!isem && ops['k']) + setcbreak(); + readfd = SHTTY; + } else if (ops['u'] && !ops['p']) { + /* -u means take input from the specified file descriptor. * + * -up means take input from the coprocess. */ + for (readfd = 9; readfd && !ops[readfd + '0']; --readfd); + } else if (ops['p']) + readfd = coprocin; + else + readfd = 0; + + /* handle prompt */ + if (firstarg) { + for (readpmpt = firstarg; + *readpmpt && *readpmpt != '?'; readpmpt++); + if (*readpmpt++) { + if (isatty(0)) { + zputs(readpmpt, stderr); + fflush(stderr); + } + readpmpt[-1] = '\0'; + } + } + + /* option -k means read only a given number of characters (default 1) */ + if (ops['k']) { + int val; + char d; + + /* allocate buffer space for result */ + bptr = buf = (char *)zalloc(nchars+1); + + do { + /* If read returns 0, is end of file */ + if ((val = read(readfd, bptr, nchars)) <= 0) + break; + + /* decrement number of characters read from number required */ + nchars -= val; + + /* increment pointer past read characters */ + bptr += val; + } while (nchars > 0); + + if (!ops['u'] && !ops['p']) { + /* dispose of result appropriately, etc. */ + if (isem) + while (val > 0 && read(SHTTY, &d, 1) == 1 && d != '\n'); + else + settyinfo(&shttyinfo); + if (haso) { + close(SHTTY); + SHTTY = -1; + } + } + + if (ops['e'] || ops['E']) + fwrite(buf, bptr - buf, 1, stdout); + if (!ops['e']) + setsparam(reply, metafy(buf, bptr - buf, META_REALLOC)); + else + zfree(buf, bptr - buf + 1); + return val <= 0; + } + + /* option -q means get one character, and interpret it as a Y or N */ + if (ops['q']) { + char readbuf[2]; + + /* set up the buffer */ + readbuf[1] = '\0'; + + /* get, and store, reply */ + readbuf[0] = ((char)getquery(NULL, 0)) == 'y' ? 'y' : 'n'; + + /* dispose of result appropriately, etc. */ + if (haso) { + close(SHTTY); + SHTTY = -1; + } + + if (ops['e'] || ops['E']) + printf("%s\n", readbuf); + if (!ops['e']) + setsparam(reply, ztrdup(readbuf)); + + return readbuf[0] == 'n'; + } + + /* All possible special types of input have been exhausted. Take one line, + and assign words to the parameters until they run out. Leftover words go + onto the last parameter. If an array is specified, all the words become + separate elements of the array. */ + + zbuforig = zbuf = (!ops['z']) ? NULL : + (nonempty(bufstack)) ? (char *) getlinknode(bufstack) : ztrdup(""); + first = 1; + bslash = 0; + while (*args || (ops['A'] && !gotnl)) { + buf = bptr = (char *)zalloc(bsiz = 64); + /* get input, a character at a time */ + while (!gotnl) { + c = zread(); + /* \ at the end of a line indicates a continuation * + * line, except in raw mode (-r option) */ + if (bslash && c == '\n') { + bslash = 0; + continue; + } + if (c == EOF || c == '\n') + break; + if (!bslash && isep(c)) { + if (bptr != buf || (!iwsep(c) && first)) { + first |= !iwsep(c); + break; + } + first |= !iwsep(c); + continue; + } + bslash = c == '\\' && !bslash && !ops['r']; + if (bslash) + continue; + first = 0; + if (imeta(c)) { + *bptr++ = Meta; + *bptr++ = c ^ 32; + } else + *bptr++ = c; + /* increase the buffer size, if necessary */ + if (bptr >= buf + bsiz - 1) { + int blen = bptr - buf; + + buf = realloc(buf, bsiz *= 2); + bptr = buf + blen; + } + } + if (c == '\n' || c == EOF) + gotnl = 1; + *bptr = '\0'; + /* dispose of word appropriately */ + if (ops['e'] || ops['E']) { + zputs(buf, stdout); + putchar('\n'); + } + if (!ops['e']) { + if (ops['A']) { + addlinknode(readll, buf); + al++; + } else + setsparam(reply, buf); + } else + free(buf); + if (!ops['A']) + reply = *args++; + } + /* handle EOF */ + if (c == EOF) { + if (readfd == coprocin) { + close(coprocin); + close(coprocout); + coprocin = coprocout = -1; + } + } + /* final assignment (and display) of array parameter */ + if (ops['A']) { + char **pp, **p = NULL; + LinkNode n; + + p = (ops['e'] ? (char **)NULL + : (char **)zalloc((al + 1) * sizeof(char *))); + + for (pp = p, n = firstnode(readll); n; incnode(n)) { + if (ops['e'] || ops['E']) { + zputs((char *) getdata(n), stdout); + putchar('\n'); + } + if (p) + *pp++ = (char *)getdata(n); + else + zsfree(getdata(n)); + } + if (p) { + *pp++ = NULL; + setaparam(reply, p); + } + return c == EOF; + } + buf = bptr = (char *)zalloc(bsiz = 64); + /* any remaining part of the line goes into one parameter */ + bslash = 0; + if (!gotnl) + for (;;) { + c = zread(); + /* \ at the end of a line introduces a continuation line, except in + raw mode (-r option) */ + if (bslash && c == '\n') { + bslash = 0; + continue; + } + if (c == EOF || (c == '\n' && !zbuf)) + break; + if (!bslash && isep(c) && bptr == buf) + if (iwsep(c)) + continue; + else if (!first) { + first = 1; + continue; + } + bslash = c == '\\' && !bslash && !ops['r']; + if (bslash) + continue; + if (imeta(c)) { + *bptr++ = Meta; + *bptr++ = c ^ 32; + } else + *bptr++ = c; + /* increase the buffer size, if necessary */ + if (bptr >= buf + bsiz - 1) { + int blen = bptr - buf; + + buf = realloc(buf, bsiz *= 2); + bptr = buf + blen; + } + } + while (bptr > buf && iwsep(bptr[-1])) + bptr--; + *bptr = '\0'; + /* final assignment of reply, etc. */ + if (ops['e'] || ops['E']) { + zputs(buf, stdout); + putchar('\n'); + } + if (!ops['e']) + setsparam(reply, buf); + else + zsfree(buf); + if (zbuforig) { + char first = *zbuforig; + + zsfree(zbuforig); + if (!first) + return 1; + } else if (c == EOF) { + if (readfd == coprocin) { + close(coprocin); + close(coprocout); + coprocin = coprocout = -1; + } + return 1; + } + return 0; +} + +/**/ +static int +zread(void) +{ + char cc, retry = 0; + + /* use zbuf if possible */ + if (zbuf) + /* If zbuf points to anything, it points to the next character in the + buffer. This may be a null byte to indicate EOF. If reading from the + buffer, move on the buffer pointer. */ + if (*zbuf == Meta) + return zbuf++, STOUC(*zbuf++ ^ 32); + else + return (*zbuf) ? STOUC(*zbuf++) : EOF; + for (;;) { + /* read a character from readfd */ + switch (read(readfd, &cc, 1)) { + case 1: + /* return the character read */ + return STOUC(cc); +#if defined(EAGAIN) || defined(EWOULDBLOCK) + case -1: + if (!retry && readfd == 0 && ( +# ifdef EAGAIN + errno == EAGAIN +# ifdef EWOULDBLOCK + || +# endif /* EWOULDBLOCK */ +# endif /* EAGAIN */ +# ifdef EWOULDBLOCK + errno == EWOULDBLOCK +# endif /* EWOULDBLOCK */ + ) && setblock_stdin()) { + retry = 1; + continue; + } + break; +#endif /* EAGAIN || EWOULDBLOCK */ + } + return EOF; + } +} + +/* holds arguments for testlex() */ +/**/ +char **testargs; + +/* test, [: the old-style general purpose logical expression builtin */ + +/**/ +void +testlex(void) +{ + if (tok == LEXERR) + return; + + tokstr = *testargs; + if (!*testargs) { + /* if tok is already zero, reading past the end: error */ + tok = tok ? NULLTOK : LEXERR; + return; + } else if (!strcmp(*testargs, "-o")) + tok = DBAR; + else if (!strcmp(*testargs, "-a")) + tok = DAMPER; + else if (!strcmp(*testargs, "!")) + tok = BANG; + else if (!strcmp(*testargs, "(")) + tok = INPAR; + else if (!strcmp(*testargs, ")")) + tok = OUTPAR; + else + tok = STRING; + testargs++; +} + +/**/ +int +bin_test(char *name, char **argv, char *ops, int func) +{ + char **s; + Cond c; + + /* if "test" was invoked as "[", it needs a matching "]" * + * which is subsequently ignored */ + if (func == BIN_BRACKET) { + for (s = argv; *s; s++); + if (s == argv || strcmp(s[-1], "]")) { + zwarnnam(name, "']' expected", NULL, 0); + return 1; + } + s[-1] = NULL; + } + /* an empty argument list evaluates to false (1) */ + if (!*argv) + return 1; + + testargs = argv; + tok = NULLTOK; + condlex = testlex; + testlex(); + c = par_cond(); + condlex = yylex; + + if (errflag) { + errflag = 0; + return 1; + } + + if (!c || tok == LEXERR) { + zwarnnam(name, tokstr ? "parse error" : "argument expected", NULL, 0); + return 1; + } + + /* syntax is OK, so evaluate */ + return !evalcond(c); +} + +/* display a time, provided in units of 1/60s, as minutes and seconds */ +#define pttime(X) printf("%ldm%ld.%02lds",((long) (X))/3600,\ + ((long) (X))/60%60,((long) (X))*100/60%100) + +/* times: display, in a two-line format, the times provided by times(3) */ + +/**/ +int +bin_times(char *name, char **argv, char *ops, int func) +{ + struct tms buf; + + /* get time accounting information */ + if (times(&buf) == -1) + return 1; + pttime(buf.tms_utime); /* user time */ + putchar(' '); + pttime(buf.tms_stime); /* system time */ + putchar('\n'); + pttime(buf.tms_cutime); /* user time, children */ + putchar(' '); + pttime(buf.tms_cstime); /* system time, children */ + putchar('\n'); + return 0; +} + +/* trap: set/unset signal traps */ + +/**/ +int +bin_trap(char *name, char **argv, char *ops, int func) +{ + List l; + char *arg, *s; + int sig; + + if (*argv && !strcmp(*argv, "--")) + argv++; + + /* If given no arguments, list all currently-set traps */ + if (!*argv) { + for (sig = 0; sig < VSIGCOUNT; sig++) { + if (sigtrapped[sig] & ZSIG_FUNC) { + char fname[20]; + HashNode hn; + + sprintf(fname, "TRAP%s", sigs[sig]); + if ((hn = shfunctab->getnode(shfunctab, fname))) + shfunctab->printnode(hn, 0); + DPUTS(!hn, "BUG: I did not find any trap functions!"); + } else if (sigtrapped[sig]) { + if (!sigfuncs[sig]) + printf("trap -- '' %s\n", sigs[sig]); + else { + s = getpermtext((void *) dupstruct((void *) sigfuncs[sig])); + printf("trap -- "); + quotedzputs(s, stdout); + printf(" %s\n", sigs[sig]); + zsfree(s); + } + } + } + return 0; + } + + /* If we have a signal number, unset the specified * + * signals. With only -, remove all traps. */ + if ((getsignum(*argv) != -1) || (!strcmp(*argv, "-") && argv++)) { + if (!*argv) + for (sig = 0; sig < VSIGCOUNT; sig++) + unsettrap(sig); + else + while (*argv) + unsettrap(getsignum(*argv++)); + return 0; + } + + /* Sort out the command to execute on trap */ + arg = *argv++; + if (!*arg) + l = NULL; + else if (!(l = parse_string(arg))) { + zwarnnam(name, "couldn't parse trap command", NULL, 0); + return 1; + } + + /* set traps */ + for (; *argv; argv++) { + List t; + + sig = getsignum(*argv); + if (sig == -1) { + zwarnnam(name, "undefined signal: %s", *argv, 0); + break; + } + PERMALLOC { + t = (List) dupstruct(l); + } LASTALLOC; + if (settrap(sig, t)) + freestruct(t); + } + return *argv != NULL; +} + +/**/ +int +bin_ttyctl(char *name, char **argv, char *ops, int func) +{ + if (ops['f']) + ttyfrozen = 1; + else if (ops['u']) + ttyfrozen = 0; + else + printf("tty is %sfrozen\n", ttyfrozen ? "" : "not "); + return 0; +} + +/* let -- mathematical evaluation */ + +/**/ +int +bin_let(char *name, char **argv, char *ops, int func) +{ + long val = 0; + + while (*argv) + val = matheval(*argv++); + /* Errors in math evaluation in let are non-fatal. */ + errflag = 0; + return !val; +} + +/* umask command. umask may be specified as octal digits, or in the * + * symbolic form that chmod(1) uses. Well, a subset of it. Remember * + * that only the bottom nine bits of umask are used, so there's no * + * point allowing the set{u,g}id and sticky bits to be specified. */ + +/**/ +int +bin_umask(char *nam, char **args, char *ops, int func) +{ + mode_t um; + char *s = *args; + + /* Get the current umask. */ + um = umask(0); + umask(um); + /* No arguments means to display the current setting. */ + if (!s) { + if (ops['S']) { + char *who = "ugo"; + + while (*who) { + char *what = "rwx"; + printf("%c=", *who++); + while (*what) { + if (!(um & 0400)) + putchar(*what); + um <<= 1; + what++; + } + putchar(*who ? ',' : '\n'); + } + } else { + if (um & 0700) + putchar('0'); + printf("%03o\n", (unsigned)um); + } + return 0; + } + + if (idigit(*s)) { + /* Simple digital umask. */ + um = zstrtol(s, &s, 8); + if (*s) { + zwarnnam(nam, "bad umask", NULL, 0); + return 1; + } + } else { + /* Symbolic notation -- slightly complicated. */ + int whomask, umaskop, mask; + + /* More than one symbolic argument may be used at once, each separated + by commas. */ + for (;;) { + /* First part of the argument -- who does this apply to? + u=owner, g=group, o=other. */ + whomask = 0; + while (*s == 'u' || *s == 'g' || *s == 'o' || *s == 'a') + if (*s == 'u') + s++, whomask |= 0700; + else if (*s == 'g') + s++, whomask |= 0070; + else if (*s == 'o') + s++, whomask |= 0007; + else if (*s == 'a') + s++, whomask |= 0777; + /* Default whomask is everyone. */ + if (!whomask) + whomask = 0777; + /* Operation may be +, - or =. */ + umaskop = (int)*s; + if (!(umaskop == '+' || umaskop == '-' || umaskop == '=')) { + if (umaskop) + zwarnnam(nam, "bad symbolic mode operator: %c", NULL, umaskop); + else + zwarnnam(nam, "bad umask", NULL, 0); + return 1; + } + /* Permissions mask -- r=read, w=write, x=execute. */ + mask = 0; + while (*++s && *s != ',') + if (*s == 'r') + mask |= 0444 & whomask; + else if (*s == 'w') + mask |= 0222 & whomask; + else if (*s == 'x') + mask |= 0111 & whomask; + else { + zwarnnam(nam, "bad symbolic mode permission: %c", + NULL, *s); + return 1; + } + /* Apply parsed argument to um. */ + if (umaskop == '+') + um &= ~mask; + else if (umaskop == '-') + um |= mask; + else /* umaskop == '=' */ + um = (um | (whomask)) & ~mask; + if (*s == ',') + s++; + else + break; + } + if (*s) { + zwarnnam(nam, "bad character in symbolic mode: %c", NULL, *s); + return 1; + } + } + + /* Finally, set the new umask. */ + umask(um); + return 0; +} + +/* Generic builtin for facilities not available on this OS */ + +/**/ +int +bin_notavail(char *nam, char **argv, char *ops, int func) +{ + zwarnnam(nam, "not available on this system", NULL, 0); + return 1; +} diff --git a/Src/compat.c b/Src/compat.c new file mode 100644 index 000000000..ca9c57aac --- /dev/null +++ b/Src/compat.c @@ -0,0 +1,285 @@ +/* + * compat.c - compatibiltiy routines for the deprived + * + * 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 "compat.pro" + +/* Return pointer to first occurence of string t * + * in string s. Return NULL if not present. */ + +#ifndef HAVE_STRSTR +char * +strstr(const char *s, const char *t) +{ + char *p1, *p2; + + for (; *s; s++) { + for (p1 = s, p2 = t; *p2; p1++, p2++) + if (*p1 != *p2) + break; + if (!*p2) + return (char *)s; + } + return NULL; +} +#endif + + +#ifndef HAVE_GETHOSTNAME +int +gethostname(char *name, size_t namelen) +{ + struct utsname uts; + + uname(&uts); + if(strlen(uts.nodename) >= namelen) { + errno = EINVAL; + return -1; + } + strcpy(name, uts.nodename); + return 0; +} +#endif + + +#ifndef HAVE_GETTIMEOFDAY +int +gettimeofday(struct timeval *tv, struct timezone *tz) +{ + tv->tv_usec = 0; + tv->tv_sec = (long)time((time_t) 0); + return 0; +} +#endif + + +/* compute the difference between two calendar times */ + +#ifndef HAVE_DIFFTIME +double +difftime(time_t t2, time_t t1) +{ + return ((double)t2 - (double)t1); +} +#endif + + +#ifndef HAVE_STRERROR +extern char *sys_errlist[]; + +/* Get error message string associated with a particular * + * error number, and returns a pointer to that string. * + * This is not a particularly robust version of strerror. */ + +char * +strerror(int errnum) +{ + return (sys_errlist[errnum]); +} +#endif + + +/**/ +char * +zgetdir(struct dirsav *d) +{ + char nbuf[PATH_MAX+3]; + char *buf; + int bufsiz, pos, len; + struct stat sbuf; + struct dirent *de; + DIR *dir; + ino_t ino, pino; + dev_t dev, pdev; + + buf = halloc(bufsiz = PATH_MAX); + pos = bufsiz - 1; + buf[pos] = '\0'; + strcpy(nbuf, "../"); + if (stat(".", &sbuf) < 0) { + if (d) + return NULL; + buf[0] = '.'; + buf[1] = '\0'; + return buf; + } + + pino = sbuf.st_ino; + pdev = sbuf.st_dev; + if (d) + d->ino = pino, d->dev = pdev; +#ifdef HAVE_FCHDIR + else +#endif + holdintr(); + + for (;;) { + if (stat("..", &sbuf) < 0) + break; + + ino = pino; + dev = pdev; + pino = sbuf.st_ino; + pdev = sbuf.st_dev; + + if (ino == pino && dev == pdev) { + if (!buf[pos]) + buf[--pos] = '/'; + if (d) { +#ifndef HAVE_FCHDIR + zchdir(buf + pos); + noholdintr(); +#endif + return d->dirname = ztrdup(buf + pos); + } + zchdir(buf + pos); + noholdintr(); + return buf + pos; + } + + if (!(dir = opendir(".."))) + break; + + while ((de = readdir(dir))) { + char *fn = de->d_name; + /* Ignore `.' and `..'. */ + if (fn[0] == '.' && + (fn[1] == '\0' || + (fn[1] == '.' && fn[2] == '\0'))) + continue; +#ifdef HAVE_STRUCT_DIRENT_D_STAT + if(de->d_stat.st_dev == dev && de->d_stat.st_ino == ino) { + strncpy(nbuf + 3, fn, PATH_MAX); + break; + } +#else /* !HAVE_STRUCT_DIRENT_D_STAT */ +# ifdef HAVE_STRUCT_DIRENT_D_INO + if (dev != pdev || (ino_t) de->d_ino == ino) +# endif /* HAVE_STRUCT_DIRENT_D_INO */ + { + strncpy(nbuf + 3, fn, PATH_MAX); + lstat(nbuf, &sbuf); + if (sbuf.st_dev == dev && sbuf.st_ino == ino) + break; + } +#endif /* !HAVE_STRUCT_DIRENT_D_STAT */ + } + closedir(dir); + if (!de) + break; + len = strlen(nbuf + 2); + pos -= len; + while (pos <= 1) { + char *newbuf = halloc(2*bufsiz); + memcpy(newbuf + bufsiz, buf, bufsiz); + buf = newbuf; + pos += bufsiz; + bufsiz *= 2; + } + memcpy(buf + pos, nbuf + 2, len); +#ifdef HAVE_FCHDIR + if (d) + return d->dirname = ztrdup(buf + pos + 1); +#endif + if (chdir("..")) + break; + } + if (d) { +#ifndef HAVE_FCHDIR + if (*buf) + zchdir(buf + pos + 1); + noholdintr(); +#endif + return NULL; + } + if (*buf) + zchdir(buf + pos + 1); + noholdintr(); + buf[0] = '.'; + buf[1] = '\0'; + return buf; +} + +/**/ +char * +zgetcwd(void) +{ + return zgetdir(NULL); +} + +/* chdir with arbitrary long pathname. Returns 0 on success, 0 on normal * + * faliliure and -2 when chdir failed and the current directory is lost. */ + +/**/ +int +zchdir(char *dir) +{ + char *s; + int currdir = -2; + + for (;;) { + if (!*dir) + return 0; + if (!chdir(dir)) + return 0; + if ((errno != ENAMETOOLONG && errno != ENOMEM) || + strlen(dir) < PATH_MAX) + break; + for (s = dir + PATH_MAX - 1; s > dir && *s != '/'; s--); + if (s == dir) + break; +#ifdef HAVE_FCHDIR + if (currdir == -2) + currdir = open(".", O_RDONLY|O_NOCTTY); +#endif + *s = '\0'; + if (chdir(dir)) { + *s = '/'; + break; + } +#ifndef HAVE_FCHDIR + currdir = -1; +#endif + *s = '/'; + while (*++s == '/'); + dir = s; + } +#ifdef HAVE_FCHDIR + if (currdir == -1 || (currdir >= 0 && fchdir(currdir))) { + if (currdir >= 0) + close(currdir); + return -2; + } + if (currdir >= 0) + close(currdir); + return -1; +#else + return currdir == -2 ? -1 : -2; +#endif +} diff --git a/Src/cond.c b/Src/cond.c new file mode 100644 index 000000000..79886a720 --- /dev/null +++ b/Src/cond.c @@ -0,0 +1,226 @@ +/* + * cond.c - evaluate conditional expressions + * + * 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 "cond.pro" + +/**/ +int +evalcond(Cond c) +{ + struct stat *st; + + switch (c->type) { + case COND_NOT: + return !evalcond(c->left); + case COND_AND: + return evalcond(c->left) && evalcond(c->right); + case COND_OR: + return evalcond(c->left) || evalcond(c->right); + } + singsub((char **)&c->left); + untokenize(c->left); + if (c->right) { + singsub((char **)&c->right); + if (c->type != COND_STREQ && c->type != COND_STRNEQ) + untokenize(c->right); + } + switch (c->type) { + case COND_STREQ: + return matchpat(c->left, c->right); + case COND_STRNEQ: + return !matchpat(c->left, c->right); + case COND_STRLT: + return strcmp(c->left, c->right) < 0; + case COND_STRGTR: + return strcmp(c->left, c->right) > 0; + case 'e': + case 'a': + return (doaccess(c->left, F_OK)); + case 'b': + return (S_ISBLK(dostat(c->left))); + case 'c': + return (S_ISCHR(dostat(c->left))); + case 'd': + return (S_ISDIR(dostat(c->left))); + case 'f': + return (S_ISREG(dostat(c->left))); + case 'g': + return (!!(dostat(c->left) & S_ISGID)); + case 'k': + return (!!(dostat(c->left) & S_ISVTX)); + case 'n': + return (!!strlen(c->left)); + case 'o': + return (optison(c->left)); + case 'p': + return (S_ISFIFO(dostat(c->left))); + case 'r': + return (doaccess(c->left, R_OK)); + case 's': + return ((st = getstat(c->left)) && !!(st->st_size)); + case 'S': + return (S_ISSOCK(dostat(c->left))); + case 'u': + return (!!(dostat(c->left) & S_ISUID)); + case 'w': + return (doaccess(c->left, W_OK)); + case 'x': + if (privasserted()) { + mode_t mode = dostat(c->left); + return (mode & S_IXUGO) || S_ISDIR(mode); + } + return doaccess(c->left, X_OK); + case 'z': + return (!strlen(c->left)); + case 'h': + case 'L': + return (S_ISLNK(dolstat(c->left))); + case 'O': + return ((st = getstat(c->left)) && st->st_uid == geteuid()); + case 'G': + return ((st = getstat(c->left)) && st->st_gid == getegid()); + case 'N': + return ((st = getstat(c->left)) && st->st_atime <= st->st_mtime); + case 't': + return isatty(matheval(c->left)); + case COND_EQ: + return matheval(c->left) == matheval(c->right); + case COND_NE: + return matheval(c->left) != matheval(c->right); + case COND_LT: + return matheval(c->left) < matheval(c->right); + case COND_GT: + return matheval(c->left) > matheval(c->right); + case COND_LE: + return matheval(c->left) <= matheval(c->right); + case COND_GE: + return matheval(c->left) >= matheval(c->right); + case COND_NT: + case COND_OT: + { + time_t a; + + if (!(st = getstat(c->left))) + return 0; + a = st->st_mtime; + if (!(st = getstat(c->right))) + return 0; + return (c->type == COND_NT) ? a > st->st_mtime : a < st->st_mtime; + } + case COND_EF: + { + dev_t d; + ino_t i; + + if (!(st = getstat(c->left))) + return 0; + d = st->st_dev; + i = st->st_ino; + if (!(st = getstat(c->right))) + return 0; + return d == st->st_dev && i == st->st_ino; + } + default: + zerr("bad cond structure", NULL, 0); + } + return 0; +} + + +/**/ +static int +doaccess(char *s, int c) +{ + return !access(unmeta(s), c); +} + + +static struct stat st; + +/**/ +static struct stat * +getstat(char *s) +{ +/* /dev/fd/n refers to the open file descriptor n. We always use fstat * + * in this case since on Solaris /dev/fd/n is a device special file */ + if (!strncmp(s, "/dev/fd/", 8)) { + if (fstat(atoi(s + 8), &st)) + return NULL; + return &st; + } + + if (stat(unmeta(s), &st)) + return NULL; + return &st; +} + + +/**/ +static mode_t +dostat(char *s) +{ + struct stat *statp; + + if (!(statp = getstat(s))) + return 0; + return statp->st_mode; +} + + +/* pem@aaii.oz; needed since dostat now uses "stat" */ + +/**/ +static mode_t +dolstat(char *s) +{ + if (lstat(unmeta(s), &st) < 0) + return 0; + return st.st_mode; +} + + +/**/ +static int +optison(char *s) +{ + int i; + + if (strlen(s) == 1) + i = optlookupc(*s); + else + i = optlookup(s); + if (!i) { + zerr("no such option: %s", s, 0); + return 0; + } else if(i < 0) + return unset(-i); + else + return isset(i); +} diff --git a/Src/exec.c b/Src/exec.c new file mode 100644 index 000000000..1b355d028 --- /dev/null +++ b/Src/exec.c @@ -0,0 +1,2965 @@ +/* + * exec.c - command execution + * + * 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 "exec.pro" + +/* used to suppress ERREXIT and trapping of SIGZERR, SIGEXIT. */ + +/**/ +int noerrexit; + +/* suppress error messages */ + +/**/ +int noerrs; + +/* do not save history on exec and exit */ + +/**/ +int nohistsave; + +/* error/break flag */ + +/**/ +int errflag; + +/* Status of return from a trap */ + +/**/ +int trapreturn; + +/* != 0 if this is a subshell */ + +/**/ +int subsh; + +/* != 0 if we have a return pending */ + +/**/ +int retflag; + +/**/ +long lastval2; + +/* The table of file descriptors. A table element is zero if the * + * corresponding fd is not used by the shell. It is greater than * + * 1 if the fd is used by a <(...) or >(...) substitution and 1 if * + * it is an internal file descriptor which must be closed before * + * executing an external command. The first ten elements of the * + * table is not used. A table element is set by movefd and cleard * + * by zclose. */ + +/**/ +char *fdtable; + +/* The allocated size of fdtable */ + +/**/ +int fdtable_size; + +/* The highest fd that marked with nonzero in fdtable */ + +/**/ +int max_zsh_fd; + +/* input fd from the coprocess */ + +/**/ +int coprocin; + +/* output fd from the coprocess */ + +/**/ +int coprocout; + +/* != 0 if the line editor is active */ + +/**/ +int zleactive; + +/* pid of process undergoing 'process substitution' */ + +/**/ +pid_t cmdoutpid; + +/* exit status of process undergoing 'process substitution' */ + +/**/ +int cmdoutval; + +/* Stack to save some variables before executing a signal handler function */ + +/**/ +struct execstack *exstack; + +#define execerr() if (!forked) { lastval = 1; return; } else _exit(1) + +static LinkList args; +static int doneps4; + +/* parse string into a list */ + +/**/ +List +parse_string(char *s) +{ + List l; + + lexsave(); + inpush(s, 0, NULL); + strinbeg(); + stophist = 2; + l = parse_list(); + strinend(); + inpop(); + lexrestore(); + return l; +} + +#ifdef HAVE_GETRLIMIT + +/* the resource limits for the shell and its children */ + +/**/ +struct rlimit current_limits[RLIM_NLIMITS], limits[RLIM_NLIMITS]; + +/**/ +int +zsetlimit(int limnum, char *nam) +{ + if (limits[limnum].rlim_max != current_limits[limnum].rlim_max || + limits[limnum].rlim_cur != current_limits[limnum].rlim_cur) { + if (setrlimit(limnum, limits + limnum)) { + if (nam) + zwarnnam(nam, "setrlimit failed: %e", NULL, errno); + return -1; + } + current_limits[limnum] = limits[limnum]; + } + return 0; +} + +/**/ +int +setlimits(char *nam) +{ + int limnum; + int ret = 0; + + for (limnum = 0; limnum < RLIM_NLIMITS; limnum++) + if (zsetlimit(limnum, nam)) + ret++; + return ret; +} + +#endif /* HAVE_GETRLIMIT */ + +/* fork and set limits */ + +/**/ +static pid_t +zfork(void) +{ + pid_t pid; + + if (thisjob >= MAXJOB - 1) { + zerr("job table full", NULL, 0); + return -1; + } + pid = fork(); + if (pid == -1) { + zerr("fork failed: %e", NULL, errno); + return -1; + } +#ifdef HAVE_GETRLIMIT + if (!pid) + /* set resource limits for the child process */ + setlimits(NULL); +#endif + return pid; +} + + +/**/ +int list_pipe = 0, simple_pline = 0; + +static pid_t list_pipe_pid; +static int nowait, pline_level = 0; +static int list_pipe_child = 0, list_pipe_job; +static char list_pipe_text[JOBTEXTSIZE]; + +/* execute a current shell command */ + +/**/ +static int +execcursh(Cmd cmd) +{ + if (!list_pipe) + deletejob(jobtab + thisjob); + execlist(cmd->u.list, 1, cmd->flags & CFLAG_EXEC); + cmd->u.list = NULL; + return lastval; +} + +/* execve after handling $_ and #! */ + +#define POUNDBANGLIMIT 64 + +/**/ +static int +zexecve(char *pth, char **argv) +{ + int eno; + static char buf[PATH_MAX * 2]; + char **eep; + + unmetafy(pth, NULL); + for (eep = argv; *eep; eep++) + if (*eep != pth) + unmetafy(*eep, NULL); + for (eep = environ; *eep; eep++) + if (**eep == '_' && (*eep)[1] == '=') + break; + buf[0] = '_'; + buf[1] = '='; + if (*pth == '/') + strcpy(buf + 2, pth); + else + sprintf(buf + 2, "%s/%s", pwd, pth); + if (!*eep) + eep[1] = NULL; + *eep = buf; + execve(pth, argv, environ); + + /* If the execve returns (which in general shouldn't happen), * + * then check for an errno equal to ENOEXEC. This errno is set * + * if the process file has the appropriate access permission, * + * but has an invalid magic number in its header. */ + if ((eno = errno) == ENOEXEC) { + char execvebuf[POUNDBANGLIMIT + 1], *ptr, *ptr2, *argv0; + int fd, ct, t0; + + if ((fd = open(pth, O_RDONLY|O_NOCTTY)) >= 0) { + argv0 = *argv; + *argv = pth; + ct = read(fd, execvebuf, POUNDBANGLIMIT); + close(fd); + if (ct > 0) { + if (execvebuf[0] == '#') { + if (execvebuf[1] == '!') { + for (t0 = 0; t0 != ct; t0++) + if (execvebuf[t0] == '\n') + execvebuf[t0] = '\0'; + execvebuf[POUNDBANGLIMIT] = '\0'; + for (ptr = execvebuf + 2; *ptr && *ptr == ' '; ptr++); + for (ptr2 = ptr; *ptr && *ptr != ' '; ptr++); + if (*ptr) { + *ptr = '\0'; + argv[-2] = ptr2; + argv[-1] = ptr + 1; + execve(ptr2, argv - 2, environ); + } else { + argv[-1] = ptr2; + execve(ptr2, argv - 1, environ); + } + } else { + argv[-1] = "sh"; + execve("/bin/sh", argv - 1, environ); + } + } else { + for (t0 = 0; t0 != ct; t0++) + if (!execvebuf[t0]) + break; + if (t0 == ct) { + argv[-1] = "sh"; + execve("/bin/sh", argv - 1, environ); + } + } + } else + eno = errno; + *argv = argv0; + } else + eno = errno; + } + /* restore the original arguments and path but do not bother with * + * null characters as these cannot be passed to external commands * + * anyway. So the result is truncated at the first null char. */ + pth = metafy(pth, -1, META_NOALLOC); + for (eep = argv; *eep; eep++) + if (*eep != pth) + (void) metafy(*eep, -1, META_NOALLOC); + return eno; +} + +#define MAXCMDLEN (PATH_MAX*4) + +/* test whether we really want to believe the error number */ + +/**/ +static int +isgooderr(int e, char *dir) +{ + /* + * Maybe the directory was unreadable, or maybe it wasn't + * even a directory. + */ + return ((e != EACCES || !access(dir, X_OK)) && + e != ENOENT && e != ENOTDIR); +} + +/* execute an external command */ + +/**/ +void +execute(Cmdnam not_used_yet, int dash) +{ + Cmdnam cn; + static LinkList exargs; + char buf[MAXCMDLEN], buf2[MAXCMDLEN]; + char *s, *z, *arg0; + char **argv, **pp; + int eno = 0, ee; + + arg0 = (char *) peekfirst(args); + if (isset(RESTRICTED) && strchr(arg0, '/')) { + zerr("%s: restricted", arg0, 0); + _exit(1); + } + + /* If the parameter STTY is set in the command's environment, * + * we first run the stty command with the value of this * + * parameter as it arguments. */ + if (!exargs && (s = zgetenv("STTY")) && isatty(0)) { + char *t; + + exargs = args; /* this prevents infinite recursion */ + args = NULL; + t = tricat("stty", " ", s); + execstring(t, 1, 0); + zsfree(t); + args = exargs; + exargs = NULL; + } + + cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0); + + /* If ARGV0 is in the commands environment, we use * + * that as argv[0] for this external command */ + if (unset(RESTRICTED) && (z = zgetenv("ARGV0"))) { + setdata(firstnode(args), (void *) ztrdup(z)); + delenv(z - 6); + } else if (dash) { + /* Else if the pre-command `-' was given, we add `-' * + * to the front of argv[0] for this command. */ + sprintf(buf2, "-%s", arg0); + setdata(firstnode(args), (void *) ztrdup(buf2)); + } + + argv = makecline(args); + child_unblock(); + if ((int) strlen(arg0) >= PATH_MAX) { + zerr("command too long: %s", arg0, 0); + _exit(1); + } + for (s = arg0; *s; s++) + if (*s == '/') { + errno = zexecve(arg0, argv); + if (arg0 == s || unset(PATHDIRS) || + (arg0[0] == '.' && (arg0 + 1 == s || + (arg0[1] == '.' && arg0 + 2 == s)))) { + zerr("%e: %s", arg0, errno); + _exit(1); + } + break; + } + + if (cn) { + char nn[PATH_MAX], *dptr; + + if (cn->flags & HASHED) + strcpy(nn, cn->u.cmd); + else { + for (pp = path; pp < cn->u.name; pp++) + if (!**pp || (**pp == '.' && (*pp)[1] == '\0')) { + ee = zexecve(arg0, argv); + if (isgooderr(ee, *pp)) + eno = ee; + } else if (**pp != '/') { + z = buf; + strucpy(&z, *pp); + *z++ = '/'; + strcpy(z, arg0); + ee = zexecve(buf, argv); + if (isgooderr(ee, *pp)) + eno = ee; + } + strcpy(nn, cn->u.name ? *(cn->u.name) : ""); + strcat(nn, "/"); + strcat(nn, cn->nam); + } + ee = zexecve(nn, argv); + + if ((dptr = strrchr(nn, '/'))) + *dptr = '\0'; + if (isgooderr(ee, *nn ? nn : "/")) + eno = ee; + } + for (pp = path; *pp; pp++) + if (!(*pp)[0] || ((*pp)[0] == '.' && !(*pp)[1])) { + ee = zexecve(arg0, argv); + if (isgooderr(ee, *pp)) + eno = ee; + } else { + z = buf; + strucpy(&z, *pp); + *z++ = '/'; + strcpy(z, arg0); + ee = zexecve(buf, argv); + if (isgooderr(ee, *pp)) + eno = ee; + } + if (eno) + zerr("%e: %s", arg0, eno); + else + zerr("command not found: %s", arg0, 0); + _exit(1); +} + +#define try(X) { if (iscom(X)) return ztrdup(X); } + +/* get the full pathname of an external command */ + +/**/ +char * +findcmd(char *arg0) +{ + char **pp; + char *z, *s, buf[MAXCMDLEN]; + Cmdnam cn; + + cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0); + if (!cn && isset(HASHCMDS)) + cn = hashcmd(arg0, path); + if ((int) strlen(arg0) > PATH_MAX) + return NULL; + for (s = arg0; *s; s++) + if (*s == '/') { + try(arg0); + if (arg0 == s || unset(PATHDIRS)) { + return NULL; + } + break; + } + if (cn) { + char nn[PATH_MAX]; + + if (cn->flags & HASHED) + strcpy(nn, cn->u.cmd); + else { + for (pp = path; pp < cn->u.name; pp++) + if (**pp != '/') { + z = buf; + if (**pp) { + strucpy(&z, *pp); + *z++ = '/'; + } + strcpy(z, arg0); + try(buf); + } + strcpy(nn, cn->u.name ? *(cn->u.name) : ""); + strcat(nn, "/"); + strcat(nn, cn->nam); + } + try(nn); + } + for (pp = path; *pp; pp++) { + z = buf; + if (**pp) { + strucpy(&z, *pp); + *z++ = '/'; + } + strcpy(z, arg0); + try(buf); + } + return NULL; +} + +/**/ +int +iscom(char *s) +{ + struct stat statbuf; + char *us = unmeta(s); + + return (access(us, X_OK) == 0 && stat(us, &statbuf) >= 0 && + S_ISREG(statbuf.st_mode)); +} + +/**/ +int +isreallycom(Cmdnam cn) +{ + char fullnam[MAXCMDLEN]; + + strcpy(fullnam, cn->u.name ? *(cn->u.name) : ""); + strcat(fullnam, "/"); + strcat(fullnam, cn->nam); + return iscom(fullnam); +} + +/**/ +int +isrelative(char *s) +{ + if (*s != '/') + return 1; + for (; *s; s++) + if (*s == '.' && s[-1] == '/' && + (s[1] == '/' || s[1] == '\0' || + (s[1] == '.' && (s[2] == '/' || s[2] == '\0')))) + return 1; + return 0; +} + +/**/ +Cmdnam +hashcmd(char *arg0, char **pp) +{ + Cmdnam cn; + char *s, buf[PATH_MAX]; + char **pq; + + for (; *pp; pp++) + if (**pp == '/') { + s = buf; + strucpy(&s, *pp); + *s++ = '/'; + if ((s - buf) + strlen(arg0) >= PATH_MAX) + continue; + strcpy(s, arg0); + if (iscom(buf)) + break; + } + + if (!*pp) + return NULL; + + cn = (Cmdnam) zcalloc(sizeof *cn); + cn->flags = 0; + cn->u.name = pp; + cmdnamtab->addnode(cmdnamtab, ztrdup(arg0), cn); + + if (isset(HASHDIRS)) { + for (pq = pathchecked; pq <= pp; pq++) + hashdir(pq); + pathchecked = pp + 1; + } + + return cn; +} + +/* execute a string */ + +/**/ +void +execstring(char *s, int dont_change_job, int exiting) +{ + List list; + + pushheap(); + if ((list = parse_string(s))) + execlist(list, dont_change_job, exiting); + popheap(); +} + +/* Main routine for executing a list. * + * exiting means that the (sub)shell we are in is a definite goner * + * after the current list is finished, so we may be able to exec the * + * last command directly instead of forking. If dont_change_job is * + * nonzero, then restore the current job number after executing the * + * list. */ + +/**/ +void +execlist(List list, int dont_change_job, int exiting) +{ + Sublist slist; + static int donetrap; + int ret, cj; + int old_pline_level, old_list_pipe; + + cj = thisjob; + old_pline_level = pline_level; + old_list_pipe = list_pipe; + + if (sourcelevel && unset(SHINSTDIN)) + pline_level = list_pipe = 0; + + /* Loop over all sets of comands separated by newline, * + * semi-colon or ampersand (`sublists'). */ + while (list && list != &dummy_list && !breaks && !retflag) { + /* Reset donetrap: this ensures that a trap is only * + * called once for each sublist that fails. */ + donetrap = 0; + simplifyright(list); + slist = list->left; + + /* Loop through code followed by &&, ||, or end of sublist. */ + while (slist) { + switch (slist->type) { + case END: + /* End of sublist; just execute, ignoring status. */ + execpline(slist, list->type, !list->right && exiting); + goto sublist_done; + break; + case ANDNEXT: + /* If the return code is non-zero, we skip pipelines until * + * we find a sublist followed by ORNEXT. */ + if ((ret = execpline(slist, Z_SYNC, 0))) { + while ((slist = slist->right)) + if (slist->type == ORNEXT) + break; + if (!slist) { + /* We've skipped to the end of the list, not executing * + * the final pipeline, so don't perform error handling * + * for this sublist. */ + donetrap = 1; + goto sublist_done; + } + } + break; + case ORNEXT: + /* If the return code is zero, we skip pipelines until * + * we find a sublist followed by ANDNEXT. */ + if (!(ret = execpline(slist, Z_SYNC, 0))) { + while ((slist = slist->right)) + if (slist->type == ANDNEXT) + break; + if (!slist) { + /* We've skipped to the end of the list, not executing * + * the final pipeline, so don't perform error handling * + * for this sublist. */ + donetrap = 1; + goto sublist_done; + } + } + break; + } + slist = slist->right; + } +sublist_done: + + if (sigtrapped[SIGDEBUG]) + dotrap(SIGDEBUG); + + /* Check whether we are suppressing traps/errexit * + * (typically in init scripts) and if we haven't * + * already performed them for this sublist. */ + if (!noerrexit && !donetrap) { + if (sigtrapped[SIGZERR] && lastval) { + dotrap(SIGZERR); + donetrap = 1; + } + if (lastval && isset(ERREXIT)) { + if (sigtrapped[SIGEXIT]) + dotrap(SIGEXIT); + if (mypid != getpid()) + _exit(lastval); + else + exit(lastval); + } + } + + list = list->right; + } + + pline_level = old_pline_level; + list_pipe = old_list_pipe; + if (dont_change_job) + thisjob = cj; +} + +/* Execute a pipeline. * + * last1 is a flag that this command is the last command in a shell * + * that is about to exit, so we can exec instead of forking. It gets * + * passed all the way down to execcmd() which actually makes the * + * decision. A 0 is always passed if the command is not the last in * + * the pipeline. This function assumes that the sublist is not NULL. * + * If last1 is zero but the command is at the end of a pipeline, we * + * pass 2 down to execcmd(). * + */ + +/**/ +static int +execpline(Sublist l, int how, int last1) +{ + int ipipe[2], opipe[2]; + int pj, newjob; + int old_simple_pline = simple_pline; + static int lastwj; + + if (!l->left) + return lastval = (l->flags & PFLAG_NOT) != 0; + + pj = thisjob; + ipipe[0] = ipipe[1] = opipe[0] = opipe[1] = 0; + child_block(); + + /* get free entry in job table and initialize it */ + if ((thisjob = newjob = initjob()) == -1) + return 1; + if (how & Z_TIMED) + jobtab[thisjob].stat |= STAT_TIMED; + + if (l->flags & PFLAG_COPROC) { + how = Z_ASYNC; + if (coprocin >= 0) { + zclose(coprocin); + zclose(coprocout); + } + mpipe(ipipe); + mpipe(opipe); + coprocin = ipipe[0]; + coprocout = opipe[1]; + fdtable[coprocin] = fdtable[coprocout] = 0; + } + if (!pline_level++) { + list_pipe_job = newjob; + nowait = 0; + } + list_pipe_pid = lastwj = 0; + if (pline_level == 1) + simple_pline = (l->left->type == END); + execpline2(l->left, how, opipe[0], ipipe[1], last1); + pline_level--; + if (how & Z_ASYNC) { + lastwj = newjob; + jobtab[thisjob].stat |= STAT_NOSTTY; + if (l->flags & PFLAG_COPROC) + zclose(ipipe[1]); + if (how & Z_DISOWN) { + deletejob(jobtab + thisjob); + thisjob = -1; + } + else + spawnjob(); + child_unblock(); + return 0; + } else { + if (newjob != lastwj) { + Job jn = jobtab + newjob; + + if (newjob == list_pipe_job && list_pipe_child) + _exit(0); + + lastwj = thisjob = newjob; + + if (list_pipe) + jn->stat |= STAT_NOPRINT; + + if (nowait) { + if(!pline_level) { + struct process *pn, *qn; + + curjob = newjob; + addproc(list_pipe_pid, list_pipe_text); + + for (pn = jobtab[jn->other].procs; pn; pn = pn->next) + if (WIFSTOPPED(pn->status)) + break; + + if (pn) { + for (qn = jn->procs; qn->next; qn = qn->next); + qn->status = pn->status; + } + + jn->stat &= ~(STAT_DONE | STAT_NOPRINT); + jn->stat |= STAT_STOPPED | STAT_CHANGED; + printjob(jn, !!isset(LONGLISTJOBS), 1); + } + else + deletejob(jn); + } + + for (; !nowait;) { + if (list_pipe_child) { + jn->stat |= STAT_NOPRINT; + makerunning(jn); + } + if (!(jn->stat & STAT_LOCKED)) + waitjobs(); + + if (list_pipe_child && + jn->stat & STAT_DONE && + lastval2 & 0200) + killpg(mypgrp, lastval2 & ~0200); + if ((list_pipe || last1) && !list_pipe_child && + jn->stat & STAT_STOPPED) { + pid_t pid; + int synch[2]; + + pipe(synch); + + if ((pid = fork()) == -1) { + trashzle(); + close(synch[0]); + close(synch[1]); + putc('\n', stderr); + fprintf(stderr, "zsh: job can't be suspended\n"); + fflush(stderr); + makerunning(jn); + killjb(jn, SIGCONT); + thisjob = newjob; + } + else if (pid) { + char dummy; + + list_pipe_pid = pid; + nowait = errflag = 1; + breaks = loops; + close(synch[1]); + read(synch[0], &dummy, 1); + close(synch[0]); + jobtab[list_pipe_job].other = newjob; + jobtab[list_pipe_job].stat |= STAT_SUPERJOB; + jn->stat |= STAT_SUBJOB | STAT_NOPRINT; + jn->other = pid; + killpg(jobtab[list_pipe_job].gleader, SIGSTOP); + break; + } + else { + close(synch[0]); + entersubsh(Z_ASYNC, 0, 0); + setpgrp(0L, mypgrp = jobtab[list_pipe_job].gleader); + close(synch[1]); + kill(getpid(), SIGSTOP); + list_pipe = 0; + list_pipe_child = 1; + break; + } + } + else if (subsh && jn->stat & STAT_STOPPED) + thisjob = newjob; + else + break; + } + child_unblock(); + + if (list_pipe && (lastval & 0200) && pj >= 0 && + (!(jn->stat & STAT_INUSE) || (jn->stat & STAT_DONE))) { + jn = jobtab + pj; + jn->stat |= STAT_NOPRINT; + killjb(jobtab + pj, lastval & ~0200); + } + if (list_pipe_child || (list_pipe && (jn->stat & STAT_DONE))) + deletejob(jn); + thisjob = pj; + + } + if (l->flags & PFLAG_NOT) + lastval = !lastval; + } + if (!pline_level) + simple_pline = old_simple_pline; + return lastval; +} + +static int subsh_close = -1; + +/* execute pipeline. This function assumes the `pline' is not NULL. */ + +/**/ +static void +execpline2(Pline pline, int how, int input, int output, int last1) +{ + pid_t pid; + int pipes[2]; + int oldlineno; + + if (breaks || retflag) + return; + + oldlineno = lineno; + lineno = pline->left->lineno; + + if (pline_level == 1) + strcpy(list_pipe_text, getjobtext((void *) pline->left)); + if (pline->type == END) { + execcmd(pline->left, input, output, how, last1 ? 1 : 2); + pline->left = NULL; + } else { + int old_list_pipe = list_pipe; + + mpipe(pipes); + + /* if we are doing "foo | bar" where foo is a current * + * shell command, do foo in a subshell and do the * + * rest of the pipeline in the current shell. */ + if (pline->left->type >= CURSH && (how & Z_SYNC)) { + int synch[2]; + + pipe(synch); + if ((pid = fork()) == -1) { + close(synch[0]); + close(synch[1]); + zerr("fork failed: %e", NULL, errno); + } else if (pid) { + char dummy, *text; + + text = getjobtext((void *) pline->left); + addproc(pid, text); + close(synch[1]); + read(synch[0], &dummy, 1); + close(synch[0]); + } else { + zclose(pipes[0]); + close(synch[0]); + entersubsh(how, 2, 0); + close(synch[1]); + execcmd(pline->left, input, pipes[1], how, 0); + _exit(lastval); + } + } else { + /* otherwise just do the pipeline normally. */ + subsh_close = pipes[0]; + execcmd(pline->left, input, pipes[1], how, 0); + } + pline->left = NULL; + zclose(pipes[1]); + if (pline->right) { + /* if another execpline() is invoked because the command is * + * a list it must know that we're already in a pipeline */ + list_pipe = 1; + execpline2(pline->right, how, pipes[0], output, last1); + list_pipe = old_list_pipe; + zclose(pipes[0]); + subsh_close = -1; + } + } + + lineno = oldlineno; +} + +/* make the argv array */ + +/**/ +static char ** +makecline(LinkList list) +{ + LinkNode node; + char **argv, **ptr; + + /* A bigger argv is necessary for executing scripts */ + ptr = + argv = 2 + (char **) ncalloc((countlinknodes(list) + 4) * sizeof(char *)); + if (isset(XTRACE)) { + if (!doneps4) + fprintf(stderr, "%s", (prompt4) ? prompt4 : ""); + + for (node = firstnode(list); node; incnode(node)) { + *ptr++ = (char *)getdata(node); + zputs(getdata(node), stderr); + if (nextnode(node)) + fputc(' ', stderr); + } + fputc('\n', stderr); + fflush(stderr); + } else { + for (node = firstnode(list); node; incnode(node)) + *ptr++ = (char *)getdata(node); + } + *ptr = NULL; + return (argv); +} + +/**/ +void +untokenize(char *s) +{ + for (; *s; s++) + if (itok(*s)) + if (*s == Nularg) + chuck(s--); + else + *s = ztokens[*s - Pound]; +} + +/* Open a file for writing redicection */ + +/**/ +static int +clobber_open(struct redir *f) +{ + struct stat buf; + int fd, oerrno; + + /* If clobbering, just open. */ + if (isset(CLOBBER) || IS_CLOBBER_REDIR(f->type)) + return open(unmeta(f->name), + O_WRONLY | O_CREAT | O_TRUNC | O_NOCTTY, 0666); + + /* If not clobbering, attempt to create file exclusively. */ + if ((fd = open(unmeta(f->name), + O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0666)) >= 0) + return fd; + + /* If that fails, we are still allowed to open non-regular files. * + * Try opening, and if it's a regular file then close it again * + * because we weren't supposed to open it. */ + oerrno = errno; + if ((fd = open(unmeta(f->name), O_WRONLY | O_NOCTTY)) != -1) { + if(!fstat(fd, &buf) && !S_ISREG(buf.st_mode)) + return fd; + close(fd); + } + errno = oerrno; + return -1; +} + +/* size of buffer for tee and cat processes */ +#define TCBUFSIZE 4092 + +/* close an multio (success) */ + +/**/ +static void +closemn(struct multio **mfds, int fd) +{ + struct multio *mn = mfds[fd]; + char buf[TCBUFSIZE]; + int len, i; + + if (fd < 0 || !mfds[fd] || mfds[fd]->ct < 2) + return; + if (zfork()) { + for (i = 0; i < mn->ct; i++) + zclose(mn->fds[i]); + zclose(mn->pipe); + mn->ct = 1; + mn->fds[0] = fd; + return; + } + /* pid == 0 */ + closeallelse(mn); + if (mn->rflag) { + /* tee process */ + while ((len = read(mn->pipe, buf, TCBUFSIZE)) > 0) + for (i = 0; i < mn->ct; i++) + write(mn->fds[i], buf, len); + } else { + /* cat process */ + for (i = 0; i < mn->ct; i++) + while ((len = read(mn->fds[i], buf, TCBUFSIZE)) > 0) + write(mn->pipe, buf, len); + } + _exit(0); +} + +/* close all the mnodes (failure) */ + +/**/ +static void +closemnodes(struct multio **mfds) +{ + int i, j; + + for (i = 0; i < 10; i++) + if (mfds[i]) { + for (j = 0; j < mfds[i]->ct; j++) + zclose(mfds[i]->fds[j]); + mfds[i] = NULL; + } +} + +/**/ +static void +closeallelse(struct multio *mn) +{ + int i, j; + + for (i = 0; i < OPEN_MAX; i++) + if (mn->pipe != i) { + for (j = 0; j < mn->ct; j++) + if (mn->fds[j] == i) + break; + if (j == mn->ct) + zclose(i); + } +} + +/* A multio is a list of fds associated with a certain fd. * + * Thus if you do "foo >bar >ble", the multio for fd 1 will have * + * two fds, the result of open("bar",...), and the result of * + * open("ble",....). */ + +/* Add a fd to an multio. fd1 must be < 10, and may be in any state. * + * fd2 must be open, and is `consumed' by this function. Note that * + * fd1 == fd2 is possible, and indicates that fd1 was really closed. * + * We effectively do `fd2 = movefd(fd2)' at the beginning of this * + * function, but in most cases we can avoid an extra dup by delaying * + * the movefd: we only >need< to move it if we're actually doing a * + * multiple redirection. */ + +/**/ +static void +addfd(int forked, int *save, struct multio **mfds, int fd1, int fd2, int rflag) +{ + int pipes[2]; + + if (!mfds[fd1] || unset(MULTIOS)) { + if(!mfds[fd1]) { /* starting a new multio */ + mfds[fd1] = (struct multio *) alloc(sizeof(struct multio)); + if (!forked && save[fd1] == -2) + save[fd1] = (fd1 == fd2) ? -1 : movefd(fd1); + } + redup(fd2, fd1); + mfds[fd1]->ct = 1; + mfds[fd1]->fds[0] = fd1; + mfds[fd1]->rflag = rflag; + } else { + if (mfds[fd1]->rflag != rflag) { + zerr("file mode mismatch on fd %d", NULL, fd1); + return; + } + if (mfds[fd1]->ct == 1) { /* split the stream */ + mfds[fd1]->fds[0] = movefd(fd1); + mfds[fd1]->fds[1] = movefd(fd2); + mpipe(pipes); + mfds[fd1]->pipe = pipes[1 - rflag]; + redup(pipes[rflag], fd1); + mfds[fd1]->ct = 2; + } else { /* add another fd to an already split stream */ + if(!(mfds[fd1]->ct % MULTIOUNIT)) { + int new = sizeof(struct multio) + sizeof(int) * mfds[fd1]->ct; + int old = new - sizeof(int) * MULTIOUNIT; + mfds[fd1] = hrealloc((char *)mfds[fd1], old, new); + } + mfds[fd1]->fds[mfds[fd1]->ct++] = movefd(fd2); + } + } + if (subsh_close >= 0 && !fdtable[subsh_close]) + subsh_close = -1; +} + +/**/ +static void +addvars(LinkList l, int export) +{ + Varasg v; + LinkList vl; + int xtr; + char **arr, **ptr; + + xtr = isset(XTRACE); + if (xtr && nonempty(l)) { + fprintf(stderr, "%s", prompt4 ? prompt4 : ""); + doneps4 = 1; + } + + while (nonempty(l)) { + v = (Varasg) ugetnode(l); + singsub(&v->name); + if (errflag) + return; + untokenize(v->name); + if (xtr) + fprintf(stderr, "%s=", v->name); + if (v->type == PM_SCALAR) { + vl = newlinklist(); + addlinknode(vl, v->str); + } else + vl = v->arr; + prefork(vl, v->type == PM_SCALAR ? 7 : 3); + if (errflag) + return; + if (isset(GLOBASSIGN) || v->type != PM_SCALAR) + globlist(vl); + if (errflag) + return; + if (v->type == PM_SCALAR && (empty(vl) || !nextnode(firstnode(vl)))) { + Param pm; + char *val; + int allexp; + + if (empty(vl)) + val = ztrdup(""); + else { + untokenize(peekfirst(vl)); + val = ztrdup(ugetnode(vl)); + } + if (xtr) + fprintf(stderr, "%s ", val); + if (export) { + if (export < 0) { + /* We are going to fork so do not bother freeing this */ + pm = (Param) paramtab->removenode(paramtab, v->name); + if (isset(RESTRICTED) && (pm->flags & PM_RESTRICTED)) { + zerr("%s: restricted", pm->nam, 0); + zsfree(val); + return; + } + } + allexp = opts[ALLEXPORT]; + opts[ALLEXPORT] = 1; + pm = setsparam(v->name, val); + opts[ALLEXPORT] = allexp; + } else + pm = setsparam(v->name, val); + if (errflag) + return; + continue; + } + ptr = arr = (char **) zalloc(sizeof(char **) * (countlinknodes(vl) + 1)); + + while (nonempty(vl)) + *ptr++ = ztrdup((char *) ugetnode(vl)); + + *ptr = NULL; + if (xtr) { + fprintf(stderr, "( "); + for (ptr = arr; *ptr; ptr++) + fprintf(stderr, "%s ", *ptr); + fprintf(stderr, ") "); + } + setaparam(v->name, arr); + if (errflag) + return; + } +} + +/**/ +static void +execcmd(Cmd cmd, int input, int output, int how, int last1) +{ + HashNode hn = NULL; + LinkNode node; + Redir fn; + struct multio *mfds[10]; + char *text; + int save[10]; + int fil, dfil, is_cursh, type, i; + int nullexec = 0, assign = 0, forked = 0; + int is_shfunc = 0, is_builtin = 0, is_exec = 0; + /* Various flags to the command. */ + int cflags = 0, checked = 0; + + doneps4 = 0; + args = cmd->args; + type = cmd->type; + + for (i = 0; i < 10; i++) { + save[i] = -2; + mfds[i] = NULL; + } + + /* If the command begins with `%', then assume it is a * + * reference to a job in the job table. */ + if (type == SIMPLE && nonempty(args) && + *(char *)peekfirst(args) == '%') { + pushnode(args, dupstring((how & Z_DISOWN) + ? "disown" : (how & Z_ASYNC) ? "bg" : "fg")); + how = Z_SYNC; + } + + /* If AUTORESUME is set, the command is SIMPLE, and doesn't have * + * any redirections, then check if it matches as a prefix of a * + * job currently in the job table. If it does, then we treat it * + * as a command to resume this job. */ + if (isset(AUTORESUME) && type == SIMPLE && (how & Z_SYNC) && + nonempty(args) && empty(cmd->redir) && !input && + !nextnode(firstnode(args))) { + if (unset(NOTIFY)) + scanjobs(); + if (findjobnam(peekfirst(args)) != -1) + pushnode(args, dupstring("fg")); + } + + /* Check if it's a builtin needing automatic MAGIC_EQUALS_SUBST * + * handling. Things like typeset need this. We can't detect the * + * command if it contains some tokens (e.g. x=ex; ${x}port), so this * + * only works in simple cases. has_token() is called to make sure * + * this really is a simple case. */ + if (type == SIMPLE) { + while (nonempty(args)) { + char *cmdarg = (char *) peekfirst(args); + checked = !has_token(cmdarg); + if (!checked) + break; + if (!(cflags & (BINF_BUILTIN | BINF_COMMAND)) && + (hn = shfunctab->getnode(shfunctab, cmdarg))) { + is_shfunc = 1; + break; + } + if (!(hn = builtintab->getnode(builtintab, cmdarg))) { + checked = !(cflags & BINF_BUILTIN); + break; + } + if (!(hn->flags & BINF_PREFIX)) { + is_builtin = 1; +#ifdef DYNAMIC + /* autoload the builtin if necessary */ + if (!((Builtin) hn)->handlerfunc) { + load_module(((Builtin) hn)->optstr); + hn = builtintab->getnode(builtintab, cmdarg); + } +#endif + assign = (hn->flags & BINF_MAGICEQUALS); + break; + } + cflags &= ~BINF_BUILTIN & ~BINF_COMMAND; + cflags |= hn->flags; + uremnode(args, firstnode(args)); + hn = NULL; + checked = 0; + if ((cflags & BINF_COMMAND) && unset(POSIXBUILTINS)) + break; + } + } + + /* Do prefork substitutions */ + prefork(args, assign ? 2 : isset(MAGICEQUALSUBST)); + + if (type == SIMPLE) { + int unglobbed = 0; + + for (;;) { + char *cmdarg; + + if (!(cflags & BINF_NOGLOB)) + while (!checked && !errflag && nonempty(args) && + has_token((char *) peekfirst(args))) + glob(args, firstnode(args)); + else if (!unglobbed) { + for (node = firstnode(args); node; incnode(node)) + untokenize((char *) getdata(node)); + unglobbed = 1; + } + + /* Current shell should not fork unless the * + * exec occurs at the end of a pipeline. */ + if ((cflags & BINF_EXEC) && last1 == 2) + cmd->flags |= CFLAG_EXEC; + + /* Empty command */ + if (empty(args)) { + if (nonempty(cmd->redir)) { + if (cmd->flags & CFLAG_EXEC) { + /* Was this "exec < foobar"? */ + nullexec = 1; + break; + } else if (!nullcmd || !*nullcmd || + (cflags & BINF_PREFIX)) { + zerr("redirection with no command", NULL, 0); + errflag = lastval = 1; + return; + } else if (readnullcmd && *readnullcmd && + ((Redir) peekfirst(cmd->redir))->type == READ && + !nextnode(firstnode(cmd->redir))) { + addlinknode(args, dupstring(readnullcmd)); + } else + addlinknode(args, dupstring(nullcmd)); + } else if ((cflags & BINF_PREFIX) && (cflags & BINF_COMMAND)) { + lastval = 0; + return; + } else { + cmdoutval = 0; + addvars(cmd->vars, 0); + if (errflag) + lastval = errflag; + else + lastval = cmdoutval; + if (isset(XTRACE)) { + fputc('\n', stderr); + fflush(stderr); + } + return; + } + } else if (isset(RESTRICTED) && (cflags & BINF_EXEC) && + (cmd->flags & CFLAG_EXEC)) { + zerrnam("exec", "%s: restricted", (char *) getdata(firstnode(args)), 0); + lastval = 1; + return; + } + + if (errflag || checked || + (unset(POSIXBUILTINS) && (cflags & BINF_COMMAND))) + break; + + cmdarg = (char *) peekfirst(args); + if (!(cflags & (BINF_BUILTIN | BINF_COMMAND)) && + (hn = shfunctab->getnode(shfunctab, cmdarg))) { + is_shfunc = 1; + break; + } + if (!(hn = builtintab->getnode(builtintab, cmdarg))) { + if (cflags & BINF_BUILTIN) { + zerr("no such builtin: %s", cmdarg, 0); + errflag = lastval = 1; + return; + } + break; + } + if (!(hn->flags & BINF_PREFIX)) { + is_builtin = 1; +#ifdef DYNAMIC + /* autoload the builtin if necessary */ + if (!((Builtin) hn)->handlerfunc) + load_module(((Builtin) hn)->optstr); +#endif + break; + } + cflags &= ~BINF_BUILTIN & ~BINF_COMMAND; + cflags |= hn->flags; + uremnode(args, firstnode(args)); + hn = NULL; + } + } + + if (errflag) { + lastval = 1; + return; + } + + /* Get the text associated with this command. */ + if (jobbing || (how & Z_TIMED)) + text = getjobtext((void *) cmd); + else + text = NULL; + + /* Set up special parameter $_ */ + zsfree(underscore); + if (nonempty(args) + && (underscore = ztrdup((char *) getdata(lastnode(args))))) + untokenize(underscore); + else + underscore = ztrdup(""); + + /* Warn about "rm *" */ + if (type == SIMPLE && interact && unset(RMSTARSILENT) + && isset(SHINSTDIN) && nonempty(args) && nextnode(firstnode(args)) + && !strcmp(peekfirst(args), "rm")) { + LinkNode node, next; + + for (node = nextnode(firstnode(args)); node && !errflag; node = next) { + char *s = (char *) getdata(node); + int l = strlen(s); + + next = nextnode(node); + if (s[0] == Star && !s[1]) { + if (!checkrmall(pwd)) + uremnode(args, node); + } else if (l > 2 && s[l - 2] == '/' && s[l - 1] == Star) { + char t = s[l - 2]; + + s[l - 2] = 0; + if (!checkrmall(s)) + uremnode(args, node); + s[l - 2] = t; + } + } + if (!nextnode(firstnode(args))) + errflag = 1; + } + + if (errflag) { + lastval = 1; + return; + } + + if (type == SIMPLE && !nullexec) { + char *s; + char trycd = (isset(AUTOCD) && isset(SHINSTDIN) + && empty(cmd->redir) && !empty(args) + && !nextnode(firstnode(args)) + && *(char *)peekfirst(args)); + + DPUTS(empty(args), "BUG: empty(args) in exec.c"); + if (!hn) { + /* Resolve external commands */ + char *cmdarg = (char *) peekfirst(args); + + hn = cmdnamtab->getnode(cmdnamtab, cmdarg); + if (hn && trycd && !isreallycom((Cmdnam)hn)) { + cmdnamtab->removenode(cmdnamtab, cmdarg); + cmdnamtab->freenode(hn); + hn = NULL; + } + if (!hn && isset(HASHCMDS) && strcmp(cmdarg, "..")) { + for (s = cmdarg; *s && *s != '/'; s++); + if (!*s) + hn = (HashNode) hashcmd(cmdarg, pathchecked); + } + } + + /* If no command found yet, see if it * + * is a directory we should AUTOCD to. */ + if (!hn && trycd && (s = cancd(peekfirst(args)))) { + peekfirst(args) = (void *) s; + pushnode(args, dupstring("cd")); + if ((hn = builtintab->getnode(builtintab, "cd"))) + is_builtin = 1; + } + } + + /* This is nonzero if the command is a current shell procedure? */ + is_cursh = (is_builtin || is_shfunc || (type >= CURSH) || nullexec); + + /************************************************************************** + * Do we need to fork? We need to fork if: * + * 1) The command is supposed to run in the background. (or) * + * 2) There is no `exec' flag, and either: * + * a) This is a builtin or shell function with output piped somewhere. * + * b) This is an external command and we can't do a `fake exec'. * + * * + * A `fake exec' is possible if we have all the following conditions: * + * 1) last1 flag is 1. This indicates that the current shell will not * + * be needed after the current command. This is typically the case * + * when when the command is the last stage in a subshell, or is the * + * last command after the option `-c'. * + * 2) We are not trapping EXIT or ZERR. * + * 3) We don't have any files to delete. * + * * + * The condition above for a `fake exec' will also work for a current * + * shell command such as a builtin, but doesn't really buy us anything * + * (doesn't save us a process), since it is already running in the * + * current shell. * + **************************************************************************/ + + if ((how & Z_ASYNC) || (!(cmd->flags & CFLAG_EXEC) && + (((is_builtin || is_shfunc) && output) || + (!is_cursh && (last1 != 1 || sigtrapped[SIGZERR] || + sigtrapped[SIGEXIT] || havefiles()))))) { + + pid_t pid; + int synch[2]; + char dummy; + + child_block(); + pipe(synch); + + if ((pid = zfork()) == -1) { + close(synch[0]); + close(synch[1]); + return; + } if (pid) { + close(synch[1]); + read(synch[0], &dummy, 1); + close(synch[0]); +#ifdef PATH_DEV_FD + closem(2); +#endif + if (how & Z_ASYNC) { + lastpid = (long) pid; + } else if (!jobtab[thisjob].stty_in_env && nonempty(cmd->vars)) { + /* search for STTY=... */ + while (nonempty(cmd->vars)) + if (!strcmp(((Varasg) ugetnode(cmd->vars))->name, "STTY")) { + jobtab[thisjob].stty_in_env = 1; + break; + } + } + addproc(pid, text); + return; + } + /* pid == 0 */ + close(synch[0]); + entersubsh(how, type != SUBSH && !(how & Z_ASYNC) ? 2 : 1, 0); + close(synch[1]); + forked = 1; + if (sigtrapped[SIGINT] & ZSIG_IGNORED) + holdintr(); +#ifdef HAVE_NICE + /* Check if we should run background jobs at a lower priority. */ + if ((how & Z_ASYNC) && isset(BGNICE)) + nice(5); +#endif /* HAVE_NICE */ + + } else if (is_cursh) { + /* This is a current shell procedure that didn't need to fork. * + * This includes current shell procedures that are being exec'ed, * + * as well as null execs. */ + jobtab[thisjob].stat |= STAT_CURSH; + } else { + /* This is an exec (real or fake) for an external command. * + * Note that any form of exec means that the subshell is fake * + * (but we may be in a subshell already). */ + is_exec = 1; + } + + if (!(cflags & BINF_NOGLOB)) + globlist(args); + if (errflag) { + lastval = 1; + goto err; + } + + /* Add pipeline input/output to mnodes */ + if (input) + addfd(forked, save, mfds, 0, input, 0); + if (output) + addfd(forked, save, mfds, 1, output, 1); + + /* Do process substitutions */ + spawnpipes(cmd->redir); + + /* Do io redirections */ + while (nonempty(cmd->redir)) { + fn = (Redir) ugetnode(cmd->redir); + DPUTS(fn->type == HEREDOC || fn->type == HEREDOCDASH, + "BUG: unexpanded here document"); + if (fn->type == INPIPE) { + if (fn->fd2 == -1) { + closemnodes(mfds); + fixfds(save); + execerr(); + } + addfd(forked, save, mfds, fn->fd1, fn->fd2, 0); + } else if (fn->type == OUTPIPE) { + if (fn->fd2 == -1) { + closemnodes(mfds); + fixfds(save); + execerr(); + } + addfd(forked, save, mfds, fn->fd1, fn->fd2, 1); + } else { + if (fn->type != HERESTR && xpandredir(fn, cmd->redir)) + continue; + if (errflag) { + closemnodes(mfds); + fixfds(save); + execerr(); + } + if (isset(RESTRICTED) && IS_WRITE_FILE(fn->type)) { + zerr("writing redirection not allowed in restricted mode", NULL, 0); + execerr(); + } + if (unset(EXECOPT)) + continue; + switch(fn->type) { + case HERESTR: + fil = getherestr(fn); + if (fil == -1) { + closemnodes(mfds); + fixfds(save); + if (errno != EINTR) + zerr("%e", NULL, errno); + execerr(); + } + addfd(forked, save, mfds, fn->fd1, fil, 0); + break; + case READ: + case READWRITE: + if (fn->type == READ) + fil = open(unmeta(fn->name), O_RDONLY | O_NOCTTY); + else + fil = open(unmeta(fn->name), + O_RDWR | O_CREAT | O_NOCTTY, 0666); + if (fil == -1) { + closemnodes(mfds); + fixfds(save); + if (errno != EINTR) + zerr("%e: %s", fn->name, errno); + execerr(); + } + addfd(forked, save, mfds, fn->fd1, fil, 0); + /* If this is 'exec < file', read from stdin, * + * not terminal, unless `file' is a terminal. */ + if (nullexec && fn->fd1 == 0 && isset(SHINSTDIN) && interact) + init_io(); + break; + case CLOSE: + if (!forked && fn->fd1 < 10 && save[fn->fd1] == -2) + save[fn->fd1] = movefd(fn->fd1); + closemn(mfds, fn->fd1); + zclose(fn->fd1); + break; + case MERGEIN: + case MERGEOUT: + if(fn->fd2 < 10) + closemn(mfds, fn->fd2); + fil = dup(fn->fd2); + if (fil == -1) { + char fdstr[4]; + + closemnodes(mfds); + fixfds(save); + sprintf(fdstr, "%d", fn->fd2); + zerr("%s: %e", fdstr, errno); + execerr(); + } + addfd(forked, save, mfds, fn->fd1, fil, fn->type == MERGEOUT); + break; + default: + if (IS_APPEND_REDIR(fn->type)) + fil = open(unmeta(fn->name), + (unset(CLOBBER) && !IS_CLOBBER_REDIR(fn->type)) ? + O_WRONLY | O_APPEND | O_NOCTTY : + O_WRONLY | O_APPEND | O_CREAT | O_NOCTTY, 0666); + else + fil = clobber_open(fn); + if(fil != -1 && IS_ERROR_REDIR(fn->type)) + dfil = dup(fil); + else + dfil = 0; + if (fil == -1 || dfil == -1) { + if(fil != -1) + close(fil); + closemnodes(mfds); + fixfds(save); + if (errno != EINTR) + zerr("%e: %s", fn->name, errno); + execerr(); + } + addfd(forked, save, mfds, fn->fd1, fil, 1); + if(IS_ERROR_REDIR(fn->type)) + addfd(forked, save, mfds, 2, dfil, 1); + break; + } + } + } + + /* We are done with redirection. close the mnodes, * + * spawning tee/cat processes as necessary. */ + for (i = 0; i < 10; i++) + closemn(mfds, i); + + if (nullexec) { + for (i = 0; i < 10; i++) + if (save[i] != -2) + zclose(save[i]); + /* + * Here we specifically *don't* restore the original fd's + * before returning. + */ + return; + } + + if (isset(EXECOPT) && !errflag) { + /* + * We delay the entersubsh() to here when we are exec'ing + * the current shell (including a fake exec to run a builtin then + * exit) in case there is an error return. + */ + if (is_exec) + entersubsh(how, type != SUBSH ? 2 : 1, 1); + if (type >= CURSH) { + static int (*func[]) _((Cmd)) = { + execcursh, exectime, execfuncdef, execfor, execwhile, + execrepeat, execif, execcase, execselect, execcond, + execarith, execautofn + }; + + if (last1 == 1) + cmd->flags |= CFLAG_EXEC; + lastval = (func[type - CURSH]) (cmd); + } else if (is_builtin || is_shfunc) { + LinkList restorelist = 0, removelist = 0; + /* builtin or shell function */ + + if (!forked && ((cflags & BINF_COMMAND) || + (unset(POSIXBUILTINS) && !assign) || + (isset(POSIXBUILTINS) && !is_shfunc && + !(hn->flags & BINF_PSPECIAL)))) + save_params(cmd, &restorelist, &removelist); + + if (cmd->vars) { + /* Export this if the command is a shell function, + * but not if it's a builtin. + */ + addvars(cmd->vars, is_shfunc); + if (errflag) { + restore_params(restorelist, removelist); + lastval = 1; + fixfds(save); + return; + } + } + + if (is_shfunc) { + /* It's a shell function */ +#ifdef PATH_DEV_FD + int i; + + for (i = 10; i <= max_zsh_fd; i++) + if (fdtable[i] > 1) + fdtable[i]++; +#endif + if (subsh_close >= 0) + zclose(subsh_close); + subsh_close = -1; + execshfunc(cmd, (Shfunc) hn); +#ifdef PATH_DEV_FD + for (i = 10; i <= max_zsh_fd; i++) + if (fdtable[i] > 1) + if (--(fdtable[i]) <= 2) + zclose(i); +#endif + } else { + /* It's a builtin */ + if (forked) + closem(1); + lastval = execbuiltin(args, (Builtin) hn); +#ifdef PATH_DEV_FD + closem(2); +#endif + if (isset(PRINTEXITVALUE) && isset(SHINSTDIN) && lastval && !subsh) { + fprintf(stderr, "zsh: exit %ld\n", (long)lastval); + } + fflush(stdout); + if (save[1] == -2) { + if (ferror(stdout)) { + zerr("write error: %e", NULL, errno); + clearerr(stdout); + errflag = 0; + } + } else + clearerr(stdout); + } + + if (cmd->flags & CFLAG_EXEC) { + if (subsh) + _exit(lastval); + + /* If we are exec'ing a command, and we are not in a subshell, * + * then check if we should save the history file. */ + if (isset(RCS) && interact && !nohistsave) + savehistfile(getsparam("HISTFILE"), 1, isset(APPENDHISTORY) ? 3 : 0); + exit(lastval); + } + + restore_params(restorelist, removelist); + + } else { + if (cmd->flags & CFLAG_EXEC) { + setiparam("SHLVL", --shlvl); + /* If we are exec'ing a command, and we are not * + * in a subshell, then save the history file. */ + if (!subsh && isset(RCS) && interact && !nohistsave) + savehistfile(getsparam("HISTFILE"), 1, isset(APPENDHISTORY) ? 3 : 0); + } + if (type == SIMPLE) { + if (cmd->vars) { + addvars(cmd->vars, -1); + if (errflag) + _exit(1); + } + closem(1); + if (coprocin) + zclose(coprocin); + if (coprocout) + zclose(coprocout); +#ifdef HAVE_GETRLIMIT + if (!forked) + setlimits(NULL); +#endif + execute((Cmdnam) hn, cflags & BINF_DASH); + } else { /* ( ... ) */ + DPUTS(cmd->vars && nonempty(cmd->vars), + "BUG: assigment before complex command"); + list_pipe = 0; + if (subsh_close >= 0) + zclose(subsh_close); + subsh_close = -1; + /* If we're forked (and we should be), no need to return */ + DPUTS(last1 != 1 && !forked, "BUG: not exiting?"); + execlist(cmd->u.list, 0, 1); + } + } + } + + err: + if (forked) + _exit(lastval); + fixfds(save); +} + +/* Arrange to have variables restored. */ + +/**/ +static void +save_params(Cmd cmd, LinkList *restore_p, LinkList *remove_p) +{ + Param pm; + LinkNode node; + char *s; + + MUSTUSEHEAP("save_params()"); + + *restore_p = newlinklist(); + *remove_p = newlinklist(); + + for (node = firstnode(cmd->vars); node; incnode(node)) { + s = ((Varasg) getdata(node))->name; + if ((pm = (Param) paramtab->getnode(paramtab, s))) { + if (!(pm->flags & PM_SPECIAL)) { + paramtab->removenode(paramtab, s); + } else if (!(pm->flags & PM_READONLY) && + (unset(RESTRICTED) || !(pm->flags & PM_RESTRICTED))) { + Param tpm = (Param) alloc(sizeof *tpm); + + tpm->nam = s; + tpm->flags = pm->flags; + switch (PM_TYPE(pm->flags)) { + case PM_SCALAR: + tpm->u.str = ztrdup(pm->gets.cfn(pm)); + break; + case PM_INTEGER: + tpm->u.val = pm->gets.ifn(pm); + break; + case PM_ARRAY: + PERMALLOC { + tpm->u.arr = arrdup(pm->gets.afn(pm)); + } LASTALLOC; + break; + } + pm = tpm; + } + addlinknode(*remove_p, s); + addlinknode(*restore_p, pm); + } else { + addlinknode(*remove_p, s); + } + } +} + +/* Restore saved parameters after executing a shfunc or builtin */ + +/**/ +static void +restore_params(LinkList restorelist, LinkList removelist) +{ + Param pm; + char *s; + + if (removelist) { + /* remove temporary parameters */ + while ((s = (char *) ugetnode(removelist))) { + if ((pm = (Param) paramtab->getnode(paramtab, s)) && + !(pm->flags & PM_SPECIAL)) { + pm->flags &= ~PM_READONLY; + unsetparam_pm(pm, 0, 0); + } + } + } + + if (restorelist) { + /* restore saved parameters */ + while ((pm = (Param) ugetnode(restorelist))) { + if (pm->flags & PM_SPECIAL) { + Param tpm = (Param) paramtab->getnode(paramtab, pm->nam); + + DPUTS(!tpm || PM_TYPE(pm->flags) != PM_TYPE(tpm->flags) || + !(pm->flags & PM_SPECIAL), + "BUG: in restoring special parameters"); + tpm->flags = pm->flags; + switch (PM_TYPE(pm->flags)) { + case PM_SCALAR: + tpm->sets.cfn(tpm, pm->u.str); + break; + case PM_INTEGER: + tpm->sets.ifn(tpm, pm->u.val); + break; + case PM_ARRAY: + tpm->sets.afn(tpm, pm->u.arr); + break; + } + } else + paramtab->addnode(paramtab, pm->nam, pm); + if (pm->flags & PM_EXPORTED) + pm->env = addenv(pm->nam, getsparam(pm->nam)); + } + } +} + +/* restore fds after redirecting a builtin */ + +/**/ +static void +fixfds(int *save) +{ + int old_errno = errno; + int i; + + for (i = 0; i != 10; i++) + if (save[i] != -2) + redup(save[i], i); + errno = old_errno; +} + +/**/ +static void +entersubsh(int how, int cl, int fake) +{ + int sig; + + if (cl != 2) + for (sig = 0; sig < VSIGCOUNT; sig++) + if (!(sigtrapped[sig] & ZSIG_FUNC)) + unsettrap(sig); + if (unset(MONITOR)) { + if (how & Z_ASYNC) { + settrap(SIGINT, NULL); + settrap(SIGQUIT, NULL); + if (isatty(0)) { + close(0); + if (open("/dev/null", O_RDWR | O_NOCTTY)) { + zerr("can't open /dev/null: %e", NULL, errno); + _exit(1); + } + } + } + } else if (thisjob != -1 && cl) { + if (jobtab[list_pipe_job].gleader && (list_pipe || list_pipe_child)) { + if (kill(jobtab[list_pipe_job].gleader, 0) == -1 || + setpgrp(0L, jobtab[list_pipe_job].gleader) == -1) { + jobtab[list_pipe_job].gleader = + jobtab[thisjob].gleader = mypgrp; + setpgrp(0L, mypgrp); + + if (how & Z_SYNC) + attachtty(jobtab[thisjob].gleader); + } + } + else if (!jobtab[thisjob].gleader || + (setpgrp(0L, jobtab[thisjob].gleader) == -1)) { + jobtab[thisjob].gleader = getpid(); + if (list_pipe_job != thisjob && + !jobtab[list_pipe_job].gleader) + jobtab[list_pipe_job].gleader = jobtab[thisjob].gleader; + setpgrp(0L, jobtab[thisjob].gleader); + if (how & Z_SYNC) + attachtty(jobtab[thisjob].gleader); + } + } + if (!fake) + subsh = 1; + if (SHTTY != -1) { + zclose(SHTTY); + SHTTY = -1; + } + if (isset(MONITOR)) { + signal_default(SIGTTOU); + signal_default(SIGTTIN); + signal_default(SIGTSTP); + } + if (interact) { + signal_default(SIGTERM); + if (!(sigtrapped[SIGINT] & ZSIG_IGNORED)) + signal_default(SIGINT); + } + if (!(sigtrapped[SIGQUIT] & ZSIG_IGNORED)) + signal_default(SIGQUIT); + opts[MONITOR] = opts[USEZLE] = 0; + zleactive = 0; + if (cl) + clearjobtab(); + times(&shtms); +} + +/* close internal shell fds */ + +/**/ +void +closem(int how) +{ + int i; + + for (i = 10; i <= max_zsh_fd; i++) + if (fdtable[i] && (!how || fdtable[i] == how)) + zclose(i); +} + +/* convert here document into a here string */ + +/**/ +char * +gethere(char *str, int typ) +{ + char *buf; + int bsiz, qt = 0, strip = 0; + char *s, *t, *bptr, c; + + for (s = str; *s; s++) + if (INULL(*s)) { + *s = Nularg; + qt = 1; + } + untokenize(str); + if (typ == HEREDOCDASH) { + strip = 1; + while (*str == '\t') + str++; + } + bptr = buf = zalloc(bsiz = 256); + for (;;) { + t = bptr; + + while ((c = hgetc()) == '\t' && strip) + ; + for (;;) { + if (bptr == buf + bsiz) { + buf = realloc(buf, 2 * bsiz); + t = buf + bsiz - (bptr - t); + bptr = buf + bsiz; + bsiz *= 2; + } + if (lexstop || c == '\n') + break; + *bptr++ = c; + c = hgetc(); + } + *bptr = '\0'; + if (!strcmp(t, str)) + break; + if (lexstop) { + t = bptr; + break; + } + *bptr++ = '\n'; + } + if (t > buf && t[-1] == '\n') + t--; + *t = '\0'; + if (!qt) + parsestr(buf); + s = dupstring(buf); + zfree(buf, bsiz); + return s; +} + +/* open here string fd */ + +/**/ +static int +getherestr(struct redir *fn) +{ + char *s, *t; + int fd, len; + + t = fn->name; + singsub(&t); + untokenize(t); + unmetafy(t, &len); + t[len++] = '\n'; + s = gettempname(); + if (!s || (fd = open(s, O_CREAT|O_WRONLY|O_EXCL|O_NOCTTY, 0600)) == -1) + return -1; + write(fd, t, len); + close(fd); + fd = open(s, O_RDONLY | O_NOCTTY); + unlink(s); + return fd; +} + +/* $(...) */ + +/**/ +LinkList +getoutput(char *cmd, int qt) +{ + List list; + int pipes[2]; + pid_t pid; + Cmd c; + Redir r; + + if (!(list = parse_string(cmd))) + return NULL; + if (list != &dummy_list && !list->right && !list->left->flags && + list->left->type == END && list->left->left->type == END && + (c = list->left->left->left)->type == SIMPLE && empty(c->args) && + empty(c->vars) && nonempty(c->redir) && + !nextnode(firstnode(c->redir)) && + (r = (Redir) getdata(firstnode(c->redir)))->fd1 == 0 && + r->type == READ) { + /* $(< word) */ + int stream; + char *s = r->name; + + singsub(&s); + if (errflag) + return NULL; + untokenize(s); + if ((stream = open(unmeta(s), O_RDONLY | O_NOCTTY)) == -1) { + zerr("%e: %s", s, errno); + return NULL; + } + return readoutput(stream, qt); + } + + mpipe(pipes); + child_block(); + cmdoutval = 0; + if ((cmdoutpid = pid = zfork()) == -1) { + /* fork error */ + zclose(pipes[0]); + zclose(pipes[1]); + errflag = 1; + cmdoutpid = 0; + child_unblock(); + return NULL; + } else if (pid) { + LinkList retval; + + zclose(pipes[1]); + retval = readoutput(pipes[0], qt); + fdtable[pipes[0]] = 0; + child_suspend(0); /* unblocks */ + lastval = cmdoutval; + return retval; + } + + /* pid == 0 */ + child_unblock(); + zclose(pipes[0]); + redup(pipes[1], 1); + opts[MONITOR] = 0; + entersubsh(Z_SYNC, 1, 0); + execlist(list, 0, 1); + close(1); + _exit(lastval); + zerr("exit returned in child!!", NULL, 0); + kill(getpid(), SIGKILL); + return NULL; +} + +/* read output of command substitution */ + +/**/ +static LinkList +readoutput(int in, int qt) +{ + LinkList ret; + char *buf, *ptr; + int bsiz, c, cnt = 0; + FILE *fin; + + fin = fdopen(in, "r"); + ret = newlinklist(); + ptr = buf = (char *) ncalloc(bsiz = 64); + while ((c = fgetc(fin)) != EOF || errno == EINTR) { + if (c == EOF) { + errno = 0; + clearerr(fin); + continue; + } + if (imeta(c)) { + *ptr++ = Meta; + c ^= 32; + cnt++; + } + if (++cnt >= bsiz) { + char *pp = (char *) ncalloc(bsiz *= 2); + + memcpy(pp, buf, cnt - 1); + ptr = (buf = pp) + cnt - 1; + } + *ptr++ = c; + } + fclose(fin); + while (cnt && ptr[-1] == '\n') + ptr--, cnt--; + *ptr = '\0'; + if (qt) { + if (!cnt) { + *ptr++ = Nularg; + *ptr = '\0'; + } + addlinknode(ret, buf); + } else { + char **words = spacesplit(buf, 0); + + while (*words) { + if (isset(GLOBSUBST)) + tokenize(*words); + addlinknode(ret, *words++); + } + } + return ret; +} + +/**/ +static List +parsecmd(char *cmd) +{ + char *str; + List list; + + for (str = cmd + 2; *str && *str != Outpar; str++); + if (!*str || cmd[1] != Inpar) { + zerr("oops.", NULL, 0); + return NULL; + } + *str = '\0'; + if (str[1] || !(list = parse_string(cmd + 2))) { + zerr("parse error in process substitution", NULL, 0); + return NULL; + } + return list; +} + +/* =(...) */ + +/**/ +char * +getoutputfile(char *cmd) +{ + pid_t pid; + char *nam; + List list; + int fd; + + if (thisjob == -1) + return NULL; + if (!(list = parsecmd(cmd))) + return NULL; + if (!(nam = gettempname())) + return NULL; + + nam = ztrdup(nam); + PERMALLOC { + if (!jobtab[thisjob].filelist) + jobtab[thisjob].filelist = newlinklist(); + addlinknode(jobtab[thisjob].filelist, nam); + } LASTALLOC; + child_block(); + fd = open(nam, O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0600); + + if (fd < 0 || (cmdoutpid = pid = zfork()) == -1) { + /* fork or open error */ + child_unblock(); + return nam; + } else if (pid) { + int os; + + close(fd); + os = jobtab[thisjob].stat; + waitforpid(pid); + cmdoutval = 0; + jobtab[thisjob].stat = os; + return nam; + } + + /* pid == 0 */ + redup(fd, 1); + opts[MONITOR] = 0; + entersubsh(Z_SYNC, 1, 0); + execlist(list, 0, 1); + close(1); + _exit(lastval); + zerr("exit returned in child!!", NULL, 0); + kill(getpid(), SIGKILL); + return NULL; +} + +#if !defined(PATH_DEV_FD) && defined(HAVE_FIFOS) +/* get a temporary named pipe */ + +static char * +namedpipe(void) +{ + char *tnam = gettempname(); + +# ifdef HAVE_MKFIFO + if (mkfifo(tnam, 0600) < 0) +# else + if (mknod(tnam, 0010600, 0) < 0) +# endif + return NULL; + return tnam; +} +#endif /* ! PATH_DEV_FD && HAVE_FIFOS */ + +/* <(...) or >(...) */ + +/**/ +char * +getproc(char *cmd) +{ +#if !defined(HAVE_FIFOS) && !defined(PATH_DEV_FD) + zerr("doesn't look like your system supports FIFOs.", NULL, 0); + return NULL; +#else + List list; + int out = *cmd == Inang; + char *pnam; +#ifndef PATH_DEV_FD + int fd; +#else + int pipes[2]; +#endif + + if (thisjob == -1) + return NULL; +#ifndef PATH_DEV_FD + if (!(pnam = namedpipe())) + return NULL; +#else + pnam = ncalloc(strlen(PATH_DEV_FD) + 6); +#endif + if (!(list = parsecmd(cmd))) + return NULL; +#ifndef PATH_DEV_FD + PERMALLOC { + if (!jobtab[thisjob].filelist) + jobtab[thisjob].filelist = newlinklist(); + addlinknode(jobtab[thisjob].filelist, ztrdup(pnam)); + } LASTALLOC; + if (zfork()) { +#else + mpipe(pipes); + if (zfork()) { + sprintf(pnam, "%s/%d", PATH_DEV_FD, pipes[!out]); + zclose(pipes[out]); + fdtable[pipes[!out]] = 2; +#endif + return pnam; + } +#ifndef PATH_DEV_FD + closem(0); + fd = open(pnam, out ? O_WRONLY | O_NOCTTY : O_RDONLY | O_NOCTTY); + if (fd == -1) { + zerr("can't open %s: %e", pnam, errno); + _exit(1); + } + entersubsh(Z_ASYNC, 1, 0); + redup(fd, out); +#else + entersubsh(Z_ASYNC, 1, 0); + redup(pipes[out], out); + closem(0); /* this closes pipes[!out] as well */ +#endif + execlist(list, 0, 1); + zclose(out); + _exit(lastval); + return NULL; +#endif /* HAVE_FIFOS and PATH_DEV_FD not defined */ +} + +/* > >(...) or < <(...) (does not use named pipes) */ + +/**/ +static int +getpipe(char *cmd) +{ + List list; + int pipes[2], out = *cmd == Inang; + + if (!(list = parsecmd(cmd))) + return -1; + mpipe(pipes); + if (zfork()) { + zclose(pipes[out]); + return pipes[!out]; + } + entersubsh(Z_ASYNC, 1, 0); + redup(pipes[out], out); + closem(0); /* this closes pipes[!out] as well */ + execlist(list, 0, 1); + _exit(lastval); + return 0; +} + +/* open pipes with fds >= 10 */ + +/**/ +static void +mpipe(int *pp) +{ + pipe(pp); + pp[0] = movefd(pp[0]); + pp[1] = movefd(pp[1]); +} + +/* Do process substitution with redirection */ + +/**/ +static void +spawnpipes(LinkList l) +{ + LinkNode n; + Redir f; + char *str; + + n = firstnode(l); + for (; n; incnode(n)) { + f = (Redir) getdata(n); + if (f->type == OUTPIPE || f->type == INPIPE) { + str = f->name; + f->fd2 = getpipe(str); + } + } +} + +/* evaluate a [[ ... ]] */ + +/**/ +static int +execcond(Cmd cmd) +{ + return !evalcond(cmd->u.cond); +} + +/* evaluate a ((...)) arithmetic command */ + +/**/ +static int +execarith(Cmd cmd) +{ + char *e; + long val = 0; + + while ((e = (char *) ugetnode(cmd->args))) + val = matheval(e); + errflag = 0; + return !val; +} + +/* perform time ... command */ + +/**/ +static int +exectime(Cmd cmd) +{ + int jb; + + jb = thisjob; + if (!cmd->u.pline) { + shelltime(); + return 0; + } + execpline(cmd->u.pline, Z_TIMED|Z_SYNC, 0); + thisjob = jb; + return lastval; +} + +/* Define a shell function */ + +/**/ +static int +execfuncdef(Cmd cmd) +{ + Shfunc shf; + char *s; + int signum; + + PERMALLOC { + while ((s = (char *) ugetnode(cmd->args))) { + shf = (Shfunc) zalloc(sizeof *shf); + shf->funcdef = (List) dupstruct(cmd->u.list); + shf->flags = 0; + + /* is this shell function a signal trap? */ + if (!strncmp(s, "TRAP", 4) && (signum = getsignum(s + 4)) != -1) { + if (settrap(signum, shf->funcdef)) { + freestruct(shf->funcdef); + zfree(shf, sizeof *shf); + LASTALLOC_RETURN 1; + } + sigtrapped[signum] |= ZSIG_FUNC; + } + shfunctab->addnode(shfunctab, ztrdup(s), shf); + } + } LASTALLOC; + if(isset(HISTNOFUNCTIONS)) + remhist(); + return 0; +} + +/* Main entry point to execute a shell function. */ + +/**/ +static void +execshfunc(Cmd cmd, Shfunc shf) +{ + LinkList last_file_list = NULL; + + if (errflag) + return; + + if (!list_pipe) { + /* Without this deletejob the process table * + * would be filled by a recursive function. */ + last_file_list = jobtab[thisjob].filelist; + jobtab[thisjob].filelist = NULL; + deletejob(jobtab + thisjob); + } + + doshfunc(shf->funcdef, cmd->args, shf->flags, 0); + + if (!list_pipe) + deletefilelist(last_file_list); +} + +/* Function to execute the special type of command that represents an * + * autoloaded shell function. The command structure tells us which * + * function it is. This function is actually called as part of the * + * execution of the autoloaded function itself, so when the function * + * has been autoloaded, its list is just run with no frills. */ + +/**/ +static int +execautofn(Cmd cmd) +{ + Shfunc shf = cmd->u.autofn->shf; + List l = getfpfunc(shf->nam); + if(l == &dummy_list) { + zerr("%s: function definition file not found", shf->nam, 0); + return 1; + } + if(isset(KSHAUTOLOAD)) { + VARARR(char, n, strlen(shf->nam) + 1); + strcpy(n, shf->nam); + execlist(l, 1, 0); + shf = (Shfunc) shfunctab->getnode(shfunctab, n); + if(!shf || (shf->flags & PM_UNDEFINED)) { + zerr("%s: function not defined by file", n, 0); + return 1; + } + } else { + freestruct(shf->funcdef); + PERMALLOC { + shf->funcdef = dupstruct(stripkshdef(l, shf->nam)); + } LASTALLOC; + shf->flags &= ~PM_UNDEFINED; + } + HEAPALLOC { + execlist(dupstruct(shf->funcdef), 1, 0); + } LASTALLOC; + return lastval; +} + +/* execute a shell function */ + +/**/ +void +doshfunc(List list, LinkList doshargs, int flags, int noreturnval) +/* If noreturnval is nonzero, then reset the current return * + * value (lastval) to its value before the shell function * + * was executed. */ +{ + char **tab, **x, *oargv0 = NULL; + int xexittr, newexittr, oldzoptind, oldlastval; + char *ou; + void *xexitfn, *newexitfn; + char saveopts[OPT_SIZE]; + int obreaks = breaks; + + HEAPALLOC { + pushheap(); + if (trapreturn < 0) + trapreturn--; + oldlastval = lastval; + xexittr = sigtrapped[SIGEXIT]; + if (xexittr & ZSIG_FUNC) + xexitfn = shfunctab->removenode(shfunctab, "TRAPEXIT"); + else + xexitfn = sigfuncs[SIGEXIT]; + sigtrapped[SIGEXIT] = 0; + sigfuncs[SIGEXIT] = NULL; + tab = pparams; + oldzoptind = zoptind; + zoptind = 1; + + /* We need to save the current options even if LOCALOPTIONS is * + * not currently set. That's because if it gets set in the * + * function we need to restore the original options on exit. */ + memcpy(saveopts, opts, sizeof(opts)); + + if (flags & PM_TAGGED) + opts[XTRACE] = 1; + opts[PRINTEXITVALUE] = 0; + if (doshargs) { + LinkNode node; + + node = doshargs->first; + pparams = x = (char **) zcalloc(((sizeof *x) * (1 + countlinknodes(doshargs)))); + if (isset(FUNCTIONARGZERO)) { + oargv0 = argzero; + argzero = ztrdup((char *) node->dat); + } + node = node->next; + for (; node; node = node->next, x++) + *x = ztrdup((char *) node->dat); + } else { + pparams = (char **) zcalloc(sizeof *pparams); + if (isset(FUNCTIONARGZERO)) { + oargv0 = argzero; + argzero = ztrdup(argzero); + } + } + startparamscope(); + ou = underscore; + underscore = ztrdup(underscore); + execlist(dupstruct(list), 1, 0); + zsfree(underscore); + underscore = ou; + endparamscope(); + + if (retflag) { + retflag = 0; + breaks = obreaks; + } + freearray(pparams); + if (oargv0) { + zsfree(argzero); + argzero = oargv0; + } + zoptind = oldzoptind; + pparams = tab; + + if (isset(LOCALOPTIONS)) { + /* restore all shell options except PRIVILEGED and RESTRICTED */ + saveopts[PRIVILEGED] = opts[PRIVILEGED]; + saveopts[RESTRICTED] = opts[RESTRICTED]; + memcpy(opts, saveopts, sizeof(opts)); + } else { + /* just restore a couple. */ + opts[XTRACE] = saveopts[XTRACE]; + opts[PRINTEXITVALUE] = saveopts[PRINTEXITVALUE]; + opts[LOCALOPTIONS] = saveopts[LOCALOPTIONS]; + } + + /* + * The trap '...' EXIT runs in the environment of the caller, + * so remember it here but run it after resetting the + * traps for the parent. + */ + newexittr = sigtrapped[SIGEXIT]; + newexitfn = sigfuncs[SIGEXIT]; + if (newexittr & ZSIG_FUNC) + shfunctab->removenode(shfunctab, "TRAPEXIT"); + + sigtrapped[SIGEXIT] = xexittr; + if (xexittr & ZSIG_FUNC) { + shfunctab->addnode(shfunctab, ztrdup("TRAPEXIT"), xexitfn); + sigfuncs[SIGEXIT] = ((Shfunc) xexitfn)->funcdef; + } else + sigfuncs[SIGEXIT] = (List) xexitfn; + + if (newexitfn) { + dotrapargs(SIGEXIT, &newexittr, newexitfn); + freestruct(newexitfn); + } + + if (trapreturn < -1) + trapreturn++; + if (noreturnval) + lastval = oldlastval; + popheap(); + } LASTALLOC; +} + +/* Search fpath for an undefined function. Finds the file, and returns the * + * list of its contents. */ + +/**/ +static List +getfpfunc(char *s) +{ + char **pp, buf[PATH_MAX]; + off_t len; + char *d; + List r; + int fd; + + pp = fpath; + for (; *pp; pp++) { + if (strlen(*pp) + strlen(s) + 1 >= PATH_MAX) + continue; + if (**pp) + sprintf(buf, "%s/%s", *pp, s); + else + strcpy(buf, s); + unmetafy(buf, NULL); + if (!access(buf, R_OK) && (fd = open(buf, O_RDONLY | O_NOCTTY)) != -1) { + if ((len = lseek(fd, 0, 2)) != -1) { + lseek(fd, 0, 0); + d = (char *) zcalloc(len + 1); + if (read(fd, d, len) == len) { + close(fd); + d = metafy(d, len, META_REALLOC); + HEAPALLOC { + r = parse_string(d); + } LASTALLOC; + zfree(d, len + 1); + return r; + } else { + zfree(d, len + 1); + close(fd); + } + } else { + close(fd); + } + } + } + return &dummy_list; +} + +/* Handle the most common type of ksh-style autoloading, when doing a * + * zsh-style autoload. Given the list read from an autoload file, and the * + * name of the function being defined, check to see if the file consists * + * entirely of a single definition for that function. If so, use the * + * contents of that definition. Otherwise, use the entire file. */ + +/**/ +static List +stripkshdef(List l, char *name) +{ + Sublist s; + Pline p; + Cmd c; + if(!l) + return NULL; + if(l->type != Z_SYNC || l->right) + return l; + s = l->left; + if(s->flags || s->right) + return l; + p = s->left; + if(p->right) + return l; + c = p->left; + if(c->type != FUNCDEF || c->flags || + nonempty(c->redir) || nonempty(c->vars) || + empty(c->args) || lastnode(c->args) != firstnode(c->args) || + strcmp(name, peekfirst(c->args))) + return l; + return c->u.list; +} + +/* check to see if AUTOCD applies here */ + +/**/ +static char * +cancd(char *s) +{ + int nocdpath = s[0] == '.' && + (s[1] == '/' || !s[1] || (s[1] == '.' && (s[2] == '/' || !s[1]))); + char *t; + + if (*s != '/') { + char sbuf[PATH_MAX], **cp; + + if (cancd2(s)) + return s; + if (access(unmeta(s), X_OK) == 0) + return NULL; + if (!nocdpath) + for (cp = cdpath; *cp; cp++) { + if (strlen(*cp) + strlen(s) + 1 >= PATH_MAX) + continue; + if (**cp) + sprintf(sbuf, "%s/%s", *cp, s); + else + strcpy(sbuf, s); + if (cancd2(sbuf)) { + doprintdir = -1; + return dupstring(sbuf); + } + } + if ((t = cd_able_vars(s))) { + if (cancd2(t)) { + doprintdir = -1; + return t; + } + } + return NULL; + } + return cancd2(s) ? s : NULL; +} + +/**/ +static int +cancd2(char *s) +{ + struct stat buf; + char *us = unmeta(s); + + return !(access(us, X_OK) || stat(us, &buf) || !S_ISDIR(buf.st_mode)); +} + +/**/ +void +execsave(void) +{ + struct execstack *es; + + es = (struct execstack *) malloc(sizeof(struct execstack)); + es->args = args; + es->list_pipe_pid = list_pipe_pid; + es->nowait = nowait; + es->pline_level = pline_level; + es->list_pipe_child = list_pipe_child; + es->list_pipe_job = list_pipe_job; + strcpy(es->list_pipe_text, list_pipe_text); + es->lastval = lastval; + es->noeval = noeval; + es->badcshglob = badcshglob; + es->cmdoutpid = cmdoutpid; + es->cmdoutval = cmdoutval; + es->trapreturn = trapreturn; + es->noerrs = noerrs; + es->subsh_close = subsh_close; + es->underscore = underscore; + underscore = ztrdup(underscore); + es->next = exstack; + exstack = es; + noerrs = cmdoutpid = 0; +} + +/**/ +void +execrestore(void) +{ + struct execstack *en; + + DPUTS(!exstack, "BUG: execrestore() without execsave()"); + args = exstack->args; + list_pipe_pid = exstack->list_pipe_pid; + nowait = exstack->nowait; + pline_level = exstack->pline_level; + list_pipe_child = exstack->list_pipe_child; + list_pipe_job = exstack->list_pipe_job; + strcpy(list_pipe_text, exstack->list_pipe_text); + lastval = exstack->lastval; + noeval = exstack->noeval; + badcshglob = exstack->badcshglob; + cmdoutpid = exstack->cmdoutpid; + cmdoutval = exstack->cmdoutval; + trapreturn = exstack->trapreturn; + noerrs = exstack->noerrs; + subsh_close = exstack->subsh_close; + zsfree(underscore); + underscore = exstack->underscore; + en = exstack->next; + free(exstack); + exstack = en; +} diff --git a/Src/glob.c b/Src/glob.c new file mode 100644 index 000000000..be7a04515 --- /dev/null +++ b/Src/glob.c @@ -0,0 +1,2800 @@ +/* + * glob.c - filename generation + * + * 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 "glob.pro" + +/* flag for CSHNULLGLOB */ + +/**/ +int badcshglob; + +static int mode; /* != 0 if we are parsing glob patterns */ +static int pathpos; /* position in pathbuf */ +static int matchsz; /* size of matchbuf */ +static int matchct; /* number of matches found */ +static char *pathbuf; /* pathname buffer */ +static int pathbufsz; /* size of pathbuf */ +static int pathbufcwd; /* where did we chdir()'ed */ +static char **matchbuf; /* array of matches */ +static char **matchptr; /* &matchbuf[matchct] */ +static char *colonmod; /* colon modifiers in qualifier list */ + +typedef struct stat *Statptr; /* This makes the Ultrix compiler happy. Go figure. */ + +/* modifier for unit conversions */ + +#define TT_DAYS 0 +#define TT_HOURS 1 +#define TT_MINS 2 +#define TT_WEEKS 3 +#define TT_MONTHS 4 + +#define TT_BYTES 0 +#define TT_POSIX_BLOCKS 1 +#define TT_KILOBYTES 2 +#define TT_MEGABYTES 3 + +typedef int (*TestMatchFunc) _((struct stat *, long)); + +struct qual { + struct qual *next; /* Next qualifier, must match */ + struct qual *or; /* Alternative set of qualifiers to match */ + TestMatchFunc func; /* Function to call to test match */ + long data; /* Argument passed to function */ + int sense; /* Whether asserting or negating */ + int amc; /* Flag for which time to test (a, m, c) */ + int range; /* Whether to test <, > or = (as per signum) */ + int units; /* Multiplier for time or size, respectively */ +}; + +/* Qualifiers pertaining to current pattern */ +static struct qual *quals; + +/* Other state values for current pattern */ +static int qualct, qualorct; +static int range, amc, units; +static int gf_nullglob, gf_markdirs, gf_noglobdots, gf_listtypes, gf_follow; + +/* Prefix, suffix for doing zle trickery */ + +/**/ +char *glob_pre, *glob_suf; + +/* pathname component in filename patterns */ + +struct complist { + Complist next; + Comp comp; + int closure; /* 1 if this is a (foo/)# */ + int follow; /* 1 to go thru symlinks */ +}; +struct comp { + Comp left, right, next, exclude; + char *str; + int stat; +}; + +/* Type of Comp: a closure with one or two #'s, the end of a * + * pattern or path component, a piece of path to be added. */ +#define C_ONEHASH 1 +#define C_TWOHASH 2 +#define C_OPTIONAL 4 +#define C_STAR 8 +#define C_CLOSURE (C_ONEHASH|C_TWOHASH|C_OPTIONAL|C_STAR) +#define C_LAST 16 +#define C_PATHADD 32 + +/* Test macros for the above */ +#define CLOSUREP(c) (c->stat & C_CLOSURE) +#define ONEHASHP(c) (c->stat & (C_ONEHASH|C_STAR)) +#define TWOHASHP(c) (c->stat & C_TWOHASH) +#define OPTIONALP(c) (c->stat & C_OPTIONAL) +#define STARP(c) (c->stat & C_STAR) +#define LASTP(c) (c->stat & C_LAST) +#define PATHADDP(c) (c->stat & C_PATHADD) + +/* Flags passed down to guts when compiling */ +#define GF_PATHADD 1 /* file glob, adding path components */ +#define GF_TOPLEV 2 /* outside (), so ~ ends main match */ + +static char *pptr; /* current place in string being matched */ +static Comp tail; +static int first; /* are leading dots special? */ + +/* Add a component to pathbuf: This keeps track of how * + * far we are into a file name, since each path component * + * must be matched separately. */ + +/**/ +static void +addpath(char *s) +{ + DPUTS(!pathbuf, "BUG: pathbuf not initialised"); + while (pathpos + (int) strlen(s) + 1 >= pathbufsz) + pathbuf = realloc(pathbuf, pathbufsz *= 2); + while ((pathbuf[pathpos++] = *s++)); + pathbuf[pathpos - 1] = '/'; + pathbuf[pathpos] = '\0'; +} + +/* stat the filename s appended to pathbuf. l should be true for lstat, * + * false for stat. If st is NULL, the file is only chechked for existance. * + * s == "" is treated as s == ".". This is necessary since on most systems * + * foo/ can be used to reference a non-directory foo. Returns nonzero if * + * the file does not exists. */ + +/**/ +static int +statfullpath(const char *s, struct stat *st, int l) +{ + char buf[PATH_MAX]; + + DPUTS(strlen(s) + !*s + pathpos - pathbufcwd >= PATH_MAX, + "BUG: statfullpath(): pathname too long"); + strcpy(buf, pathbuf + pathbufcwd); + strcpy(buf + pathpos - pathbufcwd, s); + if (!*s) { + buf[pathpos - pathbufcwd] = '.'; + buf[pathpos - pathbufcwd + 1] = '\0'; + l = 0; + } + unmetafy(buf, NULL); + if (!st) + return access(buf, F_OK) && (!l || readlink(buf, NULL, 0)); + return l ? lstat(buf, st) : stat(buf, st); +} + +/* add a match to the list */ + +/**/ +static void +insert(char *s, int checked) +{ + struct stat buf, buf2, *bp; + char *news = s; + int statted = 0; + + if (gf_listtypes || gf_markdirs) { + /* Add the type marker to the end of the filename */ + mode_t mode; + checked = statted = 1; + if (statfullpath(s, &buf, 1)) + return; + mode = buf.st_mode; + if (gf_follow) { + if (!S_ISLNK(mode) || statfullpath(s, &buf2, 0)) + memcpy(&buf2, &buf, sizeof(buf)); + statted = 2; + mode = buf2.st_mode; + } + if (gf_listtypes || S_ISDIR(mode)) { + int ll = strlen(s); + + news = (char *)ncalloc(ll + 2); + strcpy(news, s); + news[ll] = file_type(mode); + news[ll + 1] = '\0'; + } + } + if (qualct || qualorct) { + /* Go through the qualifiers, rejecting the file if appropriate */ + struct qual *qo, *qn; + + if (!statted && statfullpath(s, &buf, 1)) + return; + qo = quals; + for (qn = qo; qn && qn->func;) { + range = qn->range; + amc = qn->amc; + units = qn->units; + if ((qn->sense & 2) && statted != 2) { + /* If (sense & 2), we're following links */ + if (!S_ISLNK(buf.st_mode) || statfullpath(s, &buf2, 0)) + memcpy(&buf2, &buf, sizeof(buf)); + statted = 2; + } + bp = (qn->sense & 2) ? &buf2 : &buf; + /* Reject the file if the function returned zero * + * and the sense was positive (sense&1 == 0), or * + * vice versa. */ + if ((!((qn->func) (bp, qn->data)) ^ qn->sense) & 1) { + /* Try next alternative, or return if there are no more */ + if (!(qo = qo->or)) + return; + qn = qo; + continue; + } + qn = qn->next; + } + } else if (!checked && statfullpath(s, NULL, 1)) + return; + + news = dyncat(pathbuf, news); + if (colonmod) { + /* Handle the remainder of the qualifer: e.g. (:r:s/foo/bar/). */ + s = colonmod; + modify(&news, &s); + } + *matchptr++ = news; + if (++matchct == matchsz) { + matchbuf = (char **)realloc((char *)matchbuf, + sizeof(char **) * (matchsz *= 2)); + + matchptr = matchbuf + matchct; + } +} + +/* Check to see if str is eligible for filename generation. */ + +/**/ +int +haswilds(char *str) +{ + /* `[' and `]' are legal even if bad patterns are usually not. */ + if ((*str == Inbrack || *str == Outbrack) && !str[1]) + return 0; + + /* If % is immediately followed by ?, then that ? is * + * not treated as a wildcard. This is so you don't have * + * to escape job references such as %?foo. */ + if (str[0] == '%' && str[1] == Quest) + str[1] = '?'; + + for (; *str; str++) { + switch (*str) { + case Inpar: + case Bar: + case Star: + case Inbrack: + case Inang: + case Quest: + return 1; + case Pound: + case Hat: + if (isset(EXTENDEDGLOB)) + return 1; + break; + } + } + return 0; +} + +/* Do the globbing: scanner is called recursively * + * with successive bits of the path until we've * + * tried all of it. */ + +/**/ +static void +scanner(Complist q) +{ + Comp c; + int closure; + int pbcwdsav = pathbufcwd; + struct dirsav ds; + + ds.ino = ds.dev = 0; + ds.dirname = NULL; + ds.dirfd = ds.level = -1; + if (!q) + return; + + if ((closure = q->closure)) /* (foo/)# - match zero or more dirs */ + if (q->closure == 2) /* (foo/)## - match one or more dirs */ + q->closure = 1; + else + scanner(q->next); + c = q->comp; + /* Now the actual matching for the current path section. */ + if (!(c->next || c->left) && !haswilds(c->str)) { + /* It's a straight string to the end of the path section. */ + int l = strlen(c->str); + + if (l + !l + pathpos - pathbufcwd >= PATH_MAX) { + int err; + + if (l >= PATH_MAX) + return; + err = lchdir(pathbuf + pathbufcwd, &ds, 0); + if (err == -1) + return; + if (err) { + zerr("current directory lost during glob", NULL, 0); + return; + } + pathbufcwd = pathpos; + } + if (q->next) { + /* Not the last path section. Just add it to the path. */ + int oppos = pathpos; + + if (!errflag && !(q->closure && !strcmp(c->str, "."))) { + addpath(c->str); + if (!closure || statfullpath("", NULL, 1)) + scanner((q->closure) ? q : q->next); + pathbuf[pathpos = oppos] = '\0'; + } + } else + insert(c->str, 0); + } else { + /* Do pattern matching on current path section. */ + char *fn; + int dirs = !!q->next; + DIR *lock; + char *subdirs = NULL; + int subdirlen = 0; + + fn = pathbuf[pathbufcwd] ? unmeta(pathbuf + pathbufcwd) : "."; + if (dirs) { + struct stat st; + stat(fn, &st); + /* a directory with subdirectories has link count greater than 2 */ + if (!S_ISDIR(st.st_mode) || st.st_nlink == 2) + return; + } + lock = opendir(fn); + if (lock == NULL) + return; + while ((fn = zreaddir(lock, 1)) && !errflag) { + /* prefix and suffix are zle trickery */ + if (!dirs && !colonmod && + ((glob_pre && !strpfx(glob_pre, fn)) + || (glob_suf && !strsfx(glob_suf, fn)))) + continue; + if (domatch(fn, c, gf_noglobdots)) { + /* if this name matchs the pattern... */ + if (pbcwdsav == pathbufcwd && + strlen(fn) + pathpos - pathbufcwd >= PATH_MAX) { + int err; + + DPUTS(pathpos == pathbufcwd, + "BUG: filename longer than PATH_MAX"); + err = lchdir(pathbuf + pathbufcwd, &ds, 0); + if (err == -1) + break; + if (err) { + zerr("current directory lost during glob", NULL, 0); + break; + } + pathbufcwd = pathpos; + } + if (dirs) { + int l; + + /* if not the last component in the path */ + if (closure) { + /* if matching multiple directories */ + struct stat buf; + + if (statfullpath(fn, &buf, !q->follow)) { + if (errno != ENOENT && errno != EINTR && + errno != ENOTDIR && !errflag) { + zerr("%e: %s", fn, errno); + errflag = 0; + } + continue; + } + if (!S_ISDIR(buf.st_mode)) + continue; + } + l = strlen(fn) + 1; + subdirs = hrealloc(subdirs, subdirlen, subdirlen + l); + strcpy(subdirs + subdirlen, fn); + subdirlen += l; + } else + /* if the last filename component, just add it */ + insert(fn, 1); + } + } + closedir(lock); + if (subdirs) { + int oppos = pathpos; + + for (fn = subdirs; fn < subdirs+subdirlen; fn += strlen(fn) + 1) { + addpath(fn); + scanner((q->closure) ? q : q->next); /* scan next level */ + pathbuf[pathpos = oppos] = '\0'; + } + hrealloc(subdirs, subdirlen, 0); + } + } + if (pbcwdsav < pathbufcwd) { + if (restoredir(&ds)) + zerr("current directory lost during glob", NULL, 0); + zsfree(ds.dirname); + if (ds.dirfd >= 0) + close(ds.dirfd); + pathbufcwd = pbcwdsav; + } +} + +/* Parse a series of path components pointed to by pptr */ + +/* enum used with ksh-like patterns, @(...) etc. */ + +enum { KF_NONE, KF_AT, KF_QUEST, KF_STAR, KF_PLUS, KF_NOT }; + +/* parse lowest level pattern */ + +/**/ +static Comp +parsecomp(int gflag) +{ + int kshfunc; + Comp c = (Comp) alloc(sizeof *c), c1, c2; + char *cstr, *ls = NULL; + + /* In case of alternatives, code coming up is stored in tail. */ + c->next = tail; + cstr = pptr; + + while (*pptr && (mode || *pptr != '/') && *pptr != Bar && + (unset(EXTENDEDGLOB) || *pptr != Tilde || + !pptr[1] || pptr[1] == Outpar || pptr[1] == Bar) && + *pptr != Outpar) { + /* Go through code until we find something separating alternatives, + * or path components if relevant. + */ + if (*pptr == Hat && isset(EXTENDEDGLOB)) { + /* negate remaining pattern */ + Comp stail = tail; + tail = NULL; + c->str = dupstrpfx(cstr, pptr - cstr); + pptr++; + + c1 = (Comp) alloc(sizeof *c1); + c1->stat |= C_STAR; + + c2 = (Comp) alloc(sizeof *c2); + if (!(c2->exclude = parsecomp(gflag))) + return NULL; + if (!*pptr || *pptr == '/') + c2->stat |= C_LAST; + c2->left = c1; + c2->next = stail; + c->next = c2; + tail = stail; + return c; + } + + /* Ksh-type globs */ + kshfunc = KF_NONE; + if (isset(KSHGLOB) && *pptr && pptr[1] == Inpar) { + switch (*pptr) { + case '@': /* just do paren as usual */ + kshfunc = KF_AT; + break; + + case Quest: + case '?': /* matched optionally, treat as (...|) */ + kshfunc = KF_QUEST; + break; + + case Star: + case '*': /* treat as (...)# */ + kshfunc = KF_STAR; + break; + + case '+': /* treat as (...)## */ + kshfunc = KF_PLUS; + break; + + case '!': /* treat as (*~...) */ + kshfunc = KF_NOT; + break; + } + if (kshfunc != KF_NONE) + pptr++; + } + + if (*pptr == Inpar) { + /* Found a group (...) */ + char *startp = pptr, *endp; + Comp stail = tail; + int dpnd = 0; + + /* Need matching close parenthesis */ + if (skipparens(Inpar, Outpar, &pptr)) { + errflag = 1; + return NULL; + } + if (kshfunc == KF_STAR) + dpnd = 1; + else if (kshfunc == KF_PLUS) + dpnd = 2; + else if (kshfunc == KF_QUEST) + dpnd = 3; + if (*pptr == Pound && isset(EXTENDEDGLOB)) { + /* Zero (or one) or more repetitions of group */ + pptr++; + if(*pptr == Pound) { + pptr++; + if(dpnd == 0) + dpnd = 2; + else if(dpnd == 3) + dpnd = 1; + } else + dpnd = 1; + } + /* Parse the remaining pattern following the group... */ + if (!(c1 = parsecomp(gflag))) + return NULL; + /* ...remembering what comes after it... */ + tail = (dpnd || kshfunc == KF_NOT) ? NULL : c1; + /* ...before going back and parsing inside the group. */ + endp = pptr; + pptr = startp; + c->str = dupstrpfx(cstr, (pptr - cstr) - (kshfunc != KF_NONE)); + pptr++; + c2 = (Comp) alloc(sizeof *c); + c->next = c2; + c2->next = (dpnd || kshfunc == KF_NOT) ? + c1 : (Comp) alloc(sizeof *c); + if (!(c2->left = parsecompsw(0))) + return NULL; + if (kshfunc == KF_NOT) { + /* we'd actually rather it didn't match. Instead, match * + * a star and put the parsed pattern into exclude. */ + Comp c3 = (Comp) alloc(sizeof *c3); + c3->stat |= C_STAR; + + c2->exclude = c2->left; + c2->left = c3; + } + /* Remember closures for group. */ + if (dpnd) + c2->stat |= (dpnd == 3) ? C_OPTIONAL + : (dpnd == 2) ? C_TWOHASH : C_ONEHASH; + pptr = endp; + tail = stail; + return c; + } + if (*pptr == Star && pptr[1] && + (unset(EXTENDEDGLOB) || !(gflag & GF_TOPLEV) || + pptr[1] != Tilde || !pptr[2] || pptr[2] == Bar || + pptr[2] == Outpar) && (mode || pptr[1] != '/')) { + /* Star followed by other patterns is now treated as a special + * type of closure in doesmatch(). + */ + c->str = dupstrpfx(cstr, pptr - cstr); + pptr++; + c1 = (Comp) alloc(sizeof *c1); + c1->stat |= C_STAR; + if (!(c2 = parsecomp(gflag))) + return NULL; + c1->next = c2; + c->next = c1; + return c; + } + if (*pptr == Pound && isset(EXTENDEDGLOB)) { + /* repeat whatever we've just had (ls) zero or more times */ + if (!ls) + return NULL; + c2 = (Comp) alloc(sizeof *c); + c2->str = dupstrpfx(ls, pptr - ls); + pptr++; + if (*pptr == Pound) { + /* need one or more matches: cheat by copying previous char */ + pptr++; + c->next = c1 = (Comp) alloc(sizeof *c); + c1->str = c2->str; + } else + c1 = c; + c1->next = c2; + c2->stat |= C_ONEHASH; + /* parse the rest of the pattern and return. */ + c2->next = parsecomp(gflag); + if (!c2->next) + return NULL; + c->str = dupstrpfx(cstr, ls - cstr); + return c; + } + ls = pptr; /* whatever we just parsed */ + if (*pptr == Inang) { + /* Numeric glob */ + int dshct; + + dshct = (pptr[1] == Outang); + while (*++pptr && *pptr != Outang) + if (*pptr == '-' && !dshct) + dshct = 1; + else if (!idigit(*pptr)) + break; + if (*pptr != Outang) + return NULL; + } else if (*pptr == Inbrack) { + /* Character set: brackets had better match */ + if (pptr[1] == Outbrack) + *++pptr = ']'; + else if ((pptr[1] == Hat || pptr[1] == '^' || pptr[1] == '!') && + pptr[2] == Outbrack) + *(pptr += 2) = ']'; + while (*++pptr && *pptr != Outbrack) { + if (itok(*pptr)) { + /* POSIX classes: make sure it's a real one, * + * leave the Inbrack tokenised if so. */ + char *nptr; + if (*pptr == Inbrack && pptr[1] == ':' + && (nptr = strchr(pptr+2, ':')) && + *++nptr == Outbrack) + pptr = nptr; + *pptr = ztokens[*pptr - Pound]; + } + } + if (*pptr != Outbrack) + return NULL; + } else if (itok(*pptr) && *pptr != Star && *pptr != Quest) + /* something that can be tokenised which isn't otherwise special */ + *pptr = ztokens[*pptr - Pound]; + pptr++; + } + /* mark if last pattern component in path component or pattern */ + if (*pptr == '/' || !*pptr || + (isset(EXTENDEDGLOB) && *pptr == Tilde && (gflag & GF_TOPLEV))) + c->stat |= C_LAST; + c->str = dupstrpfx(cstr, pptr - cstr); + return c; +} + +/* Parse pattern possibly with different alternatives (|) */ + +/**/ +static Comp +parsecompsw(int gflag) +{ + Comp c1, c2, c3, excl = NULL, stail = tail; + char *sptr; + + /* + * Check for a tilde in the expression. We need to know this in + * advance so as to be able to treat the whole a~b expression by + * backtracking: see exclusion code in doesmatch(). + */ + if (isset(EXTENDEDGLOB)) { + int pct = 0; + for (sptr = pptr; *sptr; sptr++) { + if (*sptr == Inpar) + pct++; + else if (*sptr == Outpar && --pct < 0) + break; + else if (*sptr == Bar && !pct) + break; + else if (*sptr == Tilde && !pct) { + tail = NULL; + break; + } + } + } + + c1 = parsecomp(gflag); + if (!c1) + return NULL; + if (isset(EXTENDEDGLOB) && *pptr == Tilde) { + /* Matching remainder of pattern excludes the pattern from matching */ + int oldmode = mode; + + mode = 1; + pptr++; + excl = parsecomp(gflag); + mode = oldmode; + if (!excl) + return NULL; + } + tail = stail; + if (*pptr == Bar || excl) { + /* found an alternative or something to exclude */ + c2 = (Comp) alloc(sizeof *c2); + if (*pptr == Bar) { + /* get the next alternative after the | */ + pptr++; + c3 = parsecompsw(gflag); + if (!c3) + return NULL; + } else + c3 = NULL; + /* mark if end of pattern or path component */ + if (!*pptr || *pptr == '/') + c1->stat |= c2->stat = C_LAST; + c2->str = dupstring(""); + c2->left = c1; + c2->right = c3; + if ((c2->exclude = excl)) + c2->next = stail; + if (gflag & GF_PATHADD) + c2->stat |= C_PATHADD; + return c2; + } + return c1; +} + +/* This function tokenizes a zsh glob pattern */ + +/**/ +static Complist +parsecomplist(void) +{ + Comp c1; + Complist p1; + char *str; + + if (pptr[0] == Star && pptr[1] == Star && + (pptr[2] == '/' || (pptr[2] == Star && pptr[3] == '/'))) { + /* Match any number of directories. */ + int follow; + + /* with three stars, follow symbolic links */ + follow = (pptr[2] == Star); + pptr += (3 + follow); + + /* Now get the next path component if there is one. */ + p1 = (Complist) alloc(sizeof *p1); + if ((p1->next = parsecomplist()) == NULL) { + errflag = 1; + return NULL; + } + p1->comp = (Comp) alloc(sizeof *p1->comp); + p1->comp->stat |= C_LAST; /* end of path component */ + p1->comp->str = dupstring("*"); + *p1->comp->str = Star; /* match anything... */ + p1->closure = 1; /* ...zero or more times. */ + p1->follow = follow; + return p1; + } + + /* Parse repeated directories such as (dir/)# and (dir/)## */ + if (*(str = pptr) == Inpar && !skipparens(Inpar, Outpar, &str) && + *str == Pound && isset(EXTENDEDGLOB) && str[-2] == '/') { + pptr++; + if (!(c1 = parsecompsw(0))) + return NULL; + if (pptr[0] == '/' && pptr[1] == Outpar && pptr[2] == Pound) { + int pdflag = 0; + + pptr += 3; + if (*pptr == Pound) { + pdflag = 1; + pptr++; + } + p1 = (Complist) alloc(sizeof *p1); + p1->comp = c1; + p1->closure = 1 + pdflag; + p1->follow = 0; + p1->next = parsecomplist(); + return (p1->comp) ? p1 : NULL; + } + } else { + /* parse single path component */ + if (!(c1 = parsecompsw(GF_PATHADD|GF_TOPLEV))) + return NULL; + /* then do the remaining path compoents */ + if (*pptr == '/' || !*pptr) { + int ef = *pptr == '/'; + + p1 = (Complist) alloc(sizeof *p1); + p1->comp = c1; + p1->closure = 0; + p1->next = ef ? (pptr++, parsecomplist()) : NULL; + return (ef && !p1->next) ? NULL : p1; + } + } + errflag = 1; + return NULL; +} + +/* turn a string into a Complist struct: this has path components */ + +/**/ +static Complist +parsepat(char *str) +{ + mode = 0; /* path components present */ + pptr = str; + tail = NULL; + return parsecomplist(); +} + +/* get number after qualifier */ + +/**/ +static long +qgetnum(char **s) +{ + long v = 0; + + if (!idigit(**s)) { + zerr("number expected", NULL, 0); + return 0; + } + while (idigit(**s)) + v = v * 10 + *(*s)++ - '0'; + return v; +} + +/* get octal number after qualifier */ + +/**/ +static long +qgetoctnum(char **s) +{ + long v = 0; + + if (!idigit(**s)) { + zerr("octal number expected", NULL, 0); + return 0; + } + while (**s >= '0' && **s <= '7') + v = v * 010 + *(*s)++ - '0'; + return v; +} + +/* Main entry point to the globbing code for filename globbing. * + * np points to a node in the list list which will be expanded * + * into a series of nodes. */ + +/**/ +void +glob(LinkList list, LinkNode np) +{ + struct qual *qo, *qn, *ql; + LinkNode node = prevnode(np); + char *str; /* the pattern */ + int sl; /* length of the pattern */ + Complist q; /* pattern after parsing */ + char *ostr = (char *)getdata(np); /* the pattern before the parser */ + /* chops it up */ + + MUSTUSEHEAP("glob"); + if (unset(GLOBOPT) || !haswilds(ostr)) { + untokenize(ostr); + return; + } + str = dupstring(ostr); + sl = strlen(str); + uremnode(list, np); + + /* Initialise state variables for current file pattern */ + qo = qn = quals = ql = NULL; + qualct = qualorct = 0; + colonmod = NULL; + gf_nullglob = isset(NULLGLOB); + gf_markdirs = isset(MARKDIRS); + gf_listtypes = gf_follow = 0; + gf_noglobdots = unset(GLOBDOTS); + + /* Check for qualifiers */ + if (isset(BAREGLOBQUAL) && str[sl - 1] == Outpar) { + char *s; + + /* Check these are really qualifiers, not a set of * + * alternatives or exclusions */ + for (s = str + sl - 2; *s != Inpar; s--) + if (*s == Bar || *s == Outpar || + (isset(EXTENDEDGLOB) && *s == Tilde)) + break; + if (*s == Inpar) { + /* Real qualifiers found. */ + int sense = 0; /* bit 0 for match (0)/don't match (1) */ + /* bit 1 for follow links (2), don't (0) */ + long data = 0; /* Any numerical argument required */ + int (*func) _((Statptr, long)); + + str[sl-1] = 0; + *s++ = 0; + while (*s && !colonmod) { + func = (int (*) _((Statptr, long)))0; + if (idigit(*s)) { + /* Store numeric argument for qualifier */ + func = qualflags; + data = 0; + while (idigit(*s)) + data = data * 010 + (*s++ - '0'); + } else if (*s == ',') { + /* A comma separates alternative sets of qualifiers */ + s++; + sense = 0; + if (qualct) { + qn = (struct qual *)hcalloc(sizeof *qn); + qo->or = qn; + qo = qn; + qualorct++; + qualct = 0; + ql = NULL; + } + } else + switch (*s++) { + case ':': + /* Remaining arguments are history-type * + * colon substitutions, handled separately. */ + colonmod = s - 1; + untokenize(colonmod); + break; + case Hat: + case '^': + /* Toggle sense: go from positive to * + * negative match and vice versa. */ + sense ^= 1; + break; + case '-': + /* Toggle matching of symbolic links */ + sense ^= 2; + break; + case '@': + /* Match symbolic links */ + func = qualislnk; + break; + case Equals: + case '=': + /* Match sockets */ + func = qualissock; + break; + case 'p': + /* Match named pipes */ + func = qualisfifo; + break; + case '/': + /* Match directories */ + func = qualisdir; + break; + case '.': + /* Match regular files */ + func = qualisreg; + break; + case '%': + /* Match special files: block, * + * character or any device */ + if (*s == 'b') + s++, func = qualisblk; + else if (*s == 'c') + s++, func = qualischr; + else + func = qualisdev; + break; + case Star: + /* Match executable plain files */ + func = qualiscom; + break; + case 'R': + /* Match world-readable files */ + func = qualflags; + data = 0004; + break; + case 'W': + /* Match world-writeable files */ + func = qualflags; + data = 0002; + break; + case 'X': + /* Match world-executable files */ + func = qualflags; + data = 0001; + break; + case 'A': + func = qualflags; + data = 0040; + break; + case 'I': + func = qualflags; + data = 0020; + break; + case 'E': + func = qualflags; + data = 0010; + break; + case 'r': + /* Match files readable by current process */ + func = qualflags; + data = 0400; + break; + case 'w': + /* Match files writeable by current process */ + func = qualflags; + data = 0200; + break; + case 'x': + /* Match files executable by current process */ + func = qualflags; + data = 0100; + break; + case 's': + /* Match setuid files */ + func = qualflags; + data = 04000; + break; + case 'S': + /* Match setgid files */ + func = qualflags; + data = 02000; + break; + case 't': + func = qualflags; + data = 01000; + break; + case 'd': + /* Match device files by device number * + * (as given by stat's st_dev element). */ + func = qualdev; + data = qgetnum(&s); + break; + case 'l': + /* Match files with the given no. of hard links */ + func = qualnlink; + amc = -1; + goto getrange; + case 'U': + /* Match files owned by effective user ID */ + func = qualuid; + data = geteuid(); + break; + case 'G': + /* Match files owned by effective group ID */ + func = qualgid; + data = getegid(); + break; + case 'u': + /* Match files owned by given user id */ + func = qualuid; + /* either the actual uid... */ + if (idigit(*s)) + data = qgetnum(&s); + else { + /* ... or a user name */ + char sav, *tt; + + /* Find matching delimiters */ + tt = get_strarg(s); + if (!*tt) { + zerr("missing end of name", + NULL, 0); + data = 0; + } else { +#ifdef HAVE_GETPWNAM + struct passwd *pw; + sav = *tt; + *tt = '\0'; + + if ((pw = getpwnam(s + 1))) + data = pw->pw_uid; + else { + zerr("unknown user", NULL, 0); + data = 0; + } + *tt = sav; +#else /* !HAVE_GETPWNAM */ + sav = *tt; + zerr("unknown user", NULL, 0); + data = 0; +#endif /* !HAVE_GETPWNAM */ + if (sav) + s = tt + 1; + else + s = tt; + } + } + break; + case 'g': + /* Given gid or group id... works like `u' */ + func = qualgid; + /* either the actual gid... */ + if (idigit(*s)) + data = qgetnum(&s); + else { + /* ...or a delimited group name. */ + char sav, *tt; + + tt = get_strarg(s); + if (!*tt) { + zerr("missing end of name", + NULL, 0); + data = 0; + } else { +#ifdef HAVE_GETGRNAM + struct group *gr; + sav = *tt; + *tt = '\0'; + + if ((gr = getgrnam(s + 1))) + data = gr->gr_gid; + else { + zerr("unknown group", NULL, 0); + data = 0; + } + *tt = sav; +#else /* !HAVE_GETGRNAM */ + sav = *tt; + zerr("unknown group", NULL, 0); + data = 0; +#endif /* !HAVE_GETGRNAM */ + if (sav) + s = tt + 1; + else + s = tt; + } + } + break; + case 'o': + /* Match octal mode of file exactly. * + * Currently undocumented. */ + func = qualeqflags; + data = qgetoctnum(&s); + break; + case 'M': + /* Mark directories with a / */ + if ((gf_markdirs = !(sense & 1))) + gf_follow = sense & 2; + break; + case 'T': + /* Mark types in a `ls -F' type fashion */ + if ((gf_listtypes = !(sense & 1))) + gf_follow = sense & 2; + break; + case 'N': + /* Nullglob: remove unmatched patterns. */ + gf_nullglob = !(sense & 1); + break; + case 'D': + /* Glob dots: match leading dots implicitly */ + gf_noglobdots = sense & 1; + break; + case 'a': + /* Access time in given range */ + amc = 0; + func = qualtime; + goto getrange; + case 'm': + /* Modification time in given range */ + amc = 1; + func = qualtime; + goto getrange; + case 'c': + /* Inode creation time in given range */ + amc = 2; + func = qualtime; + goto getrange; + case 'L': + /* File size (Length) in given range */ + func = qualsize; + amc = -1; + /* Get size multiplier */ + units = TT_BYTES; + if (*s == 'p' || *s == 'P') + units = TT_POSIX_BLOCKS, ++s; + else if (*s == 'k' || *s == 'K') + units = TT_KILOBYTES, ++s; + else if (*s == 'm' || *s == 'M') + units = TT_MEGABYTES, ++s; + getrange: + /* Get time multiplier */ + if (amc >= 0) { + units = TT_DAYS; + if (*s == 'h') + units = TT_HOURS, ++s; + else if (*s == 'm') + units = TT_MINS, ++s; + else if (*s == 'w') + units = TT_WEEKS, ++s; + else if (*s == 'M') + units = TT_MONTHS, ++s; + } + /* See if it's greater than, equal to, or less than */ + if ((range = *s == '+' ? 1 : *s == '-' ? -1 : 0)) + ++s; + data = qgetnum(&s); + break; + + default: + zerr("unknown file attribute", NULL, 0); + return; + } + if (func) { + /* Requested test is performed by function func */ + if (!qn) + qn = (struct qual *)hcalloc(sizeof *qn); + if (ql) + ql->next = qn; + ql = qn; + if (!quals) + quals = qo = qn; + qn->func = func; + qn->sense = sense; + qn->data = data; + qn->range = range; + qn->units = units; + qn->amc = amc; + qn = NULL; + qualct++; + } + if (errflag) + return; + } + } + } + if (!pathbuf) + pathbuf = zalloc(pathbufsz = PATH_MAX); + DPUTS(pathbufcwd, "BUG: glob changed directory"); + if (*str == '/') { /* pattern has absolute path */ + str++; + pathbuf[0] = '/'; + pathbuf[pathpos = 1] = '\0'; + } else /* pattern is relative to pwd */ + pathbuf[pathpos = 0] = '\0'; + q = parsepat(str); + if (!q || errflag) { /* if parsing failed */ + if (unset(BADPATTERN)) { + untokenize(ostr); + insertlinknode(list, node, ostr); + return; + } + errflag = 0; + zerr("bad pattern: %s", ostr, 0); + return; + } + + /* Initialise receptacle for matched files, * + * expanded by insert() where necessary. */ + matchptr = matchbuf = (char **)zalloc((matchsz = 16) * sizeof(char *)); + matchct = 0; + + /* The actual processing takes place here: matches go into * + * matchbuf. This is the only top-level call to scanner(). */ + scanner(q); + + /* Deal with failures to match depending on options */ + if (matchct) + badcshglob |= 2; /* at least one cmd. line expansion O.K. */ + else if (!gf_nullglob) + if (isset(CSHNULLGLOB)) { + badcshglob |= 1; /* at least one cmd. line expansion failed */ + } else if (isset(NOMATCH)) { + zerr("no matches found: %s", ostr, 0); + free(matchbuf); + return; + } else { + /* treat as an ordinary string */ + untokenize(*matchptr++ = dupstring(ostr)); + matchct = 1; + } + /* Sort arguments in to lexical (and possibly numeric) order. * + * This is reversed to facilitate insertion into the list. */ + qsort((void *) & matchbuf[0], matchct, sizeof(char *), + (int (*) _((const void *, const void *)))notstrcmp); + + matchptr = matchbuf; + while (matchct--) /* insert matches in the arg list */ + insertlinknode(list, node, *matchptr++); + free(matchbuf); +} + +/* Return the order of two strings, taking into account * + * possible numeric order if NUMERICGLOBSORT is set. * + * The comparison here is reversed. */ + +/**/ +static int +notstrcmp(char **a, char **b) +{ + char *c = *b, *d = *a; + int cmp; + +#ifdef HAVE_STRCOLL + cmp = strcoll(c, d); +#endif + for (; *c == *d && *c; c++, d++); +#ifndef HAVE_STRCOLL + cmp = (int)STOUC(*c) - (int)STOUC(*d); +#endif + if (isset(NUMERICGLOBSORT) && (idigit(*c) || idigit(*d))) { + for (; c > *b && idigit(c[-1]); c--, d--); + if (idigit(*c) && idigit(*d)) { + while (*c == '0') + c++; + while (*d == '0') + d++; + for (; idigit(*c) && *c == *d; c++, d++); + if (idigit(*c) || idigit(*d)) { + cmp = (int)STOUC(*c) - (int)STOUC(*d); + while (idigit(*c) && idigit(*d)) + c++, d++; + if (idigit(*c) && !idigit(*d)) + return 1; + if (idigit(*d) && !idigit(*c)) + return -1; + } + } + } + return cmp; +} + +/* Return the trailing character for marking file types */ + +/**/ +char +file_type(mode_t filemode) +{ + if(S_ISBLK(filemode)) + return '#'; + else if(S_ISCHR(filemode)) + return '%'; + else if(S_ISDIR(filemode)) + return '/'; + else if(S_ISFIFO(filemode)) + return '|'; + else if(S_ISLNK(filemode)) + return '@'; + else if(S_ISREG(filemode)) + return (filemode & S_IXUGO) ? '*' : ' '; + else if(S_ISSOCK(filemode)) + return '='; + else + return '?'; +} + +/* check to see if str is eligible for brace expansion */ + +/**/ +int +hasbraces(char *str) +{ + char *lbr, *mbr, *comma; + + if (isset(BRACECCL)) { + /* In this case, any properly formed brace expression * + * will match and expand to the characters in between. */ + int bc; + + for (bc = 0; *str; ++str) + if (*str == Inbrace) { + if (!bc && str[1] == Outbrace) + *str++ = '{', *str = '}'; + else + bc++; + } else if (*str == Outbrace) + if (!bc) + *str = '}'; + else if (!--bc) + return 1; + return 0; + } + /* Otherwise we need to look for... */ + lbr = mbr = comma = NULL; + for (;;) { + switch (*str++) { + case Inbrace: + if (!lbr) { + lbr = str - 1; + while (idigit(*str)) + str++; + if (*str == '.' && str[1] == '.') { + str++; + while (idigit(*++str)); + if (*str == Outbrace && + (idigit(lbr[1]) || idigit(str[-1]))) + return 1; + } + } else { + char *s = --str; + + if (skipparens(Inbrace, Outbrace, &str)) { + *lbr = *s = '{'; + if (comma) + str = comma; + if (mbr && mbr < str) + str = mbr; + lbr = mbr = comma = NULL; + } else if (!mbr) + mbr = s; + } + break; + case Outbrace: + if (!lbr) + str[-1] = '}'; + else if (comma) + return 1; + else { + *lbr = '{'; + str[-1] = '}'; + if (mbr) + str = mbr; + mbr = lbr = NULL; + } + break; + case Comma: + if (!lbr) + str[-1] = ','; + else if (!comma) + comma = str - 1; + break; + case '\0': + if (lbr) + *lbr = '{'; + if (!mbr && !comma) + return 0; + if (comma) + str = comma; + if (mbr && mbr < str) + str = mbr; + lbr = mbr = comma = NULL; + break; + } + } +} + +/* expand stuff like >>*.c */ + +/**/ +int +xpandredir(struct redir *fn, LinkList tab) +{ + LinkList fake; + char *nam; + struct redir *ff; + int ret = 0; + + /* Stick the name in a list... */ + fake = newlinklist(); + addlinknode(fake, fn->name); + /* ...which undergoes all the usual shell expansions */ + prefork(fake, isset(MULTIOS) ? 0 : 4); + /* Globbing is only done for multios. */ + if (!errflag && isset(MULTIOS)) + globlist(fake); + if (errflag) + return 0; + if (nonempty(fake) && !nextnode(firstnode(fake))) { + /* Just one match, the usual case. */ + char *s = peekfirst(fake); + fn->name = s; + untokenize(s); + if (fn->type == MERGEIN || fn->type == MERGEOUT) { + if (s[0] == '-' && !s[1]) + fn->type = CLOSE; + else if (s[0] == 'p' && !s[1]) + fn->fd2 = (fn->type == MERGEOUT) ? coprocout : coprocin; + else { + while (idigit(*s)) + s++; + if (!*s && s > fn->name) + fn->fd2 = zstrtol(fn->name, NULL, 10); + else if (fn->type == MERGEIN) + zerr("file number expected", NULL, 0); + else + fn->type = ERRWRITE; + } + } + } else if (fn->type == MERGEIN) + zerr("file number expected", NULL, 0); + else { + if (fn->type == MERGEOUT) + fn->type = ERRWRITE; + while ((nam = (char *)ugetnode(fake))) { + /* Loop over matches, duplicating the * + * redirection for each file found. */ + ff = (struct redir *)alloc(sizeof *ff); + *ff = *fn; + ff->name = nam; + addlinknode(tab, ff); + ret = 1; + } + } + return ret; +} + +/* concatenate s1 and s2 in dynamically allocated buffer */ + +/**/ +char * +dyncat(char *s1, char *s2) +{ + /* This version always uses space from the current heap. */ + char *ptr; + int l1 = strlen(s1); + + ptr = (char *)ncalloc(l1 + strlen(s2) + 1); + strcpy(ptr, s1); + strcpy(ptr + l1, s2); + return ptr; +} + +/* concatenate s1, s2, and s3 in dynamically allocated buffer */ + +/**/ +char * +tricat(char const *s1, char const *s2, char const *s3) +{ + /* This version always uses permanently-allocated space. */ + char *ptr; + + ptr = (char *)zalloc(strlen(s1) + strlen(s2) + strlen(s3) + 1); + strcpy(ptr, s1); + strcat(ptr, s2); + strcat(ptr, s3); + return ptr; +} + +/* brace expansion */ + +/**/ +void +xpandbraces(LinkList list, LinkNode *np) +{ + LinkNode node = (*np), last = prevnode(node); + char *str = (char *)getdata(node), *str3 = str, *str2; + int prev, bc, comma, dotdot; + + for (; *str != Inbrace; str++); + /* First, match up braces and see what we have. */ + for (str2 = str, bc = comma = dotdot = 0; *str2; ++str2) + if (*str2 == Inbrace) + ++bc; + else if (*str2 == Outbrace) { + if (--bc == 0) + break; + } else if (bc == 1) + if (*str2 == Comma) + ++comma; /* we have {foo,bar} */ + else if (*str2 == '.' && str2[1] == '.') + dotdot++; /* we have {num1..num2} */ + DPUTS(bc, "BUG: unmatched brace in xpandbraces()"); + if (!comma && dotdot) { + /* Expand range like 0..10 numerically: comma or recursive + brace expansion take precedence. */ + char *dots, *p; + LinkNode olast = last; + /* Get the first number of the range */ + int rstart = zstrtol(str+1,&dots,10), rend = 0, err = 0, rev = 0; + int wid1 = (dots - str) - 1, wid2 = (str2 - dots) - 2; + int strp = str - str3; + + if (dots == str + 1 || *dots != '.' || dots[1] != '.') + err++; + else { + /* Get the last number of the range */ + rend = zstrtol(dots+2,&p,10); + if (p == dots+2 || p != str2) + err++; + } + if (!err) { + /* If either no. begins with a zero, pad the output with * + * zeroes. Otherwise, choose a min width to suppress them. */ + int minw = (str[1] == '0') ? wid1 : (dots[2] == '0' ) ? wid2 : + (wid2 > wid1) ? wid1 : wid2; + if (rstart > rend) { + /* Handle decreasing ranges correctly. */ + int rt = rend; + rend = rstart; + rstart = rt; + rev = 1; + } + uremnode(list, node); + for (; rend >= rstart; rend--) { + /* Node added in at end, so do highest first */ + p = dupstring(str3); + sprintf(p + strp, "%0*d", minw, rend); + strcat(p + strp, str2 + 1); + insertlinknode(list, last, p); + if (rev) /* decreasing: add in reverse order. */ + last = nextnode(last); + } + *np = nextnode(olast); + return; + } + } + if (!comma && isset(BRACECCL)) { /* {a-mnop} */ + /* Here we expand each character to a separate node, * + * but also ranges of characters like a-m. ccl is a * + * set of flags saying whether each character is present; * + * the final list is in lexical order. */ + char ccl[256], *p; + unsigned char c1, c2, lastch; + unsigned int len, pl; + + uremnode(list, node); + memset(ccl, 0, sizeof(ccl) / sizeof(ccl[0])); + for (p = str + 1, lastch = 0; p < str2;) { + if (itok(c1 = *p++)) + c1 = ztokens[c1 - STOUC(Pound)]; + if ((char) c1 == Meta) + c1 = 32 ^ *p++; + if (itok(c2 = *p)) + c2 = ztokens[c2 - STOUC(Pound)]; + if ((char) c2 == Meta) + c2 = 32 ^ p[1]; + if (c1 == '-' && lastch && p < str2 && (int)lastch <= (int)c2) { + while ((int)lastch < (int)c2) + ccl[lastch++] = 1; + lastch = 0; + } else + ccl[lastch = c1] = 1; + } + pl = str - str3; + len = pl + strlen(++str2) + 2; + for (p = ccl + 255; p-- > ccl;) + if (*p) { + c1 = p - ccl; + if (imeta(c1)) { + str = ncalloc(len + 1); + str[pl] = Meta; + str[pl+1] = c1 ^ 32; + strcpy(str + pl + 2, str2); + } else { + str = ncalloc(len); + str[pl] = c1; + strcpy(str + pl + 1, str2); + } + memcpy(str, str3, pl); + insertlinknode(list, last, str); + } + *np = nextnode(last); + return; + } + prev = str++ - str3; + str2++; + uremnode(list, node); + node = last; + /* Finally, normal comma expansion * + * str1{foo,bar}str2 -> str1foostr2 str1barstr2. * + * Any number of intervening commas is allowed. */ + for (;;) { + char *zz, *str4; + int cnt; + + for (str4 = str, cnt = 0; cnt || (*str != Comma && *str != + Outbrace); str++) { + if (*str == Inbrace) + cnt++; + else if (*str == Outbrace) + cnt--; + DPUTS(!*str, "BUG: illegal brace expansion"); + } + /* Concatenate the string before the braces (str3), the section * + * just found (str4) and the text after the braces (str2) */ + zz = (char *)ncalloc(prev + (str - str4) + strlen(str2) + 1); + ztrncpy(zz, str3, prev); + strncat(zz, str4, str - str4); + strcat(zz, str2); + /* and add this text to the argument list. */ + insertlinknode(list, node, zz); + incnode(node); + if (*str != Outbrace) + str++; + else + break; + } + *np = nextnode(last); +} + +/* check to see if a matches b (b is not a filename pattern) */ + +/**/ +int +matchpat(char *a, char *b) +{ + Comp c = parsereg(b); + + if (!c) { + zerr("bad pattern: %s", b, 0); + return 0; + } + return domatch(a, c, 0); +} + +/* do the ${foo%%bar}, ${foo#bar} stuff */ +/* please do not laugh at this code. */ + +/* Having found a match in getmatch, decide what part of string + * to return. The matched part starts b characters into string s + * and finishes e characters in: 0 <= b <= e <= strlen(s) + * (yes, empty matches should work). + * Bits 3 and higher in fl are used: the flags are + * 8: Result is matched portion. + * 16: Result is unmatched portion. + * (N.B. this should be set for standard ${foo#bar} etc. matches.) + * 32: Result is numeric position of start of matched portion. + * 64: Result is numeric position of end of matched portion. + * 128: Result is length of matched portion. + */ + +/**/ +static char * +get_match_ret(char *s, int b, int e, int fl) +{ + char buf[80], *r, *p, *rr; + int ll = 0, l = strlen(s), bl = 0, t = 0, i; + + if (fl & 8) /* matched portion */ + ll += 1 + (e - b); + if (fl & 16) /* unmatched portion */ + ll += 1 + (l - (e - b)); + if (fl & 32) { + /* position of start of matched portion */ + sprintf(buf, "%d ", b + 1); + ll += (bl = strlen(buf)); + } + if (fl & 64) { + /* position of end of matched portion */ + sprintf(buf + bl, "%d ", e + 1); + ll += (bl = strlen(buf)); + } + if (fl & 128) { + /* length of matched portion */ + sprintf(buf + bl, "%d ", e - b); + ll += (bl = strlen(buf)); + } + if (bl) + buf[bl - 1] = '\0'; + + rr = r = (char *)ncalloc(ll); + + if (fl & 8) { + /* copy matched portion to new buffer */ + for (i = b, p = s + b; i < e; i++) + *rr++ = *p++; + t = 1; + } + if (fl & 16) { + /* Copy unmatched portion to buffer. If both portions * + * requested, put a space in between (why?) */ + if (t) + *rr++ = ' '; + /* there may be unmatched bits at both beginning and end of string */ + for (i = 0, p = s; i < b; i++) + *rr++ = *p++; + for (i = e, p = s + e; i < l; i++) + *rr++ = *p++; + t = 1; + } + *rr = '\0'; + if (bl) { + /* if there was a buffer (with a numeric result), add it; * + * if there was other stuff too, stick in a space first. */ + if (t) + *rr++ = ' '; + strcpy(rr, buf); + } + return r; +} + +/* It is called from paramsubst to get the match for ${foo#bar} etc. + * Bits of fl determines the required action: + * bit 0: match the end instead of the beginning (% or %%) + * bit 1: % or # was doubled so get the longest match + * bit 2: substring match + * bit 3: include the matched portion + * bit 4: include the unmatched portion + * bit 5: the index of the beginning + * bit 6: the index of the end + * bit 7: the length of the match + * bit 8: match the complete string + * *sp points to the string we have to modify. The n'th match will be + * returned in *sp. ncalloc is used to get memory for the result string. + */ + +/**/ +int +getmatch(char **sp, char *pat, int fl, int n) +{ + Comp c; + char *s = *sp, *t, sav; + int i, j, l = strlen(*sp); + + c = parsereg(pat); + if (!c) { + zerr("bad pattern: %s", pat, 0); + return 1; + } + if (fl & 256) { + i = domatch(s, c, 0); + *sp = get_match_ret(*sp, 0, domatch(s, c, 0) ? l : 0, fl); + if (! **sp && (((fl & 8) && !i) || ((fl & 16) && i))) + return 0; + return 1; + } + switch (fl & 7) { + case 0: + /* Smallest possible match at head of string: * + * start adding characters until we get a match. */ + for (i = 0, t = s; i <= l; i++, t++) { + sav = *t; + *t = '\0'; + if (domatch(s, c, 0) && !--n) { + *t = sav; + *sp = get_match_ret(*sp, 0, i, fl); + return 1; + } + if ((*t = sav) == Meta) + i++, t++; + } + break; + + case 1: + /* Smallest possible match at tail of string: * + * move back down string until we get a match. */ + for (t = s + l; t >= s; t--) { + if (domatch(t, c, 0) && !--n) { + *sp = get_match_ret(*sp, t - s, l, fl); + return 1; + } + if (t > s+1 && t[-2] == Meta) + t--; + } + break; + + case 2: + /* Largest possible match at head of string: * + * delete characters from end until we get a match. */ + for (t = s + l; t > s; t--) { + sav = *t; + *t = '\0'; + if (domatch(s, c, 0) && !--n) { + *t = sav; + *sp = get_match_ret(*sp, 0, t - s, fl); + return 1; + } + *t = sav; + if (t >= s+2 && t[-2] == Meta) + t--; + } + break; + + case 3: + /* Largest possible match at tail of string: * + * move forward along string until we get a match. */ + for (i = 0, t = s; i < l; i++, t++) { + if (domatch(t, c, 0) && !--n) { + *sp = get_match_ret(*sp, i, l, fl); + return 1; + } + if (*t == Meta) + i++, t++; + } + break; + + case 4: + /* Smallest at start, but matching substrings. */ + if (domatch(s + l, c, 0) && !--n) { + *sp = get_match_ret(*sp, 0, 0, fl); + return 1; + } + for (i = 1; i <= l; i++) { + for (t = s, j = i; j <= l; j++, t++) { + sav = s[j]; + s[j] = '\0'; + if (domatch(t, c, 0) && !--n) { + s[j] = sav; + *sp = get_match_ret(*sp, t - s, j, fl); + return 1; + } + if ((s[j] = sav) == Meta) + j++; + if (*t == Meta) + t++; + } + if (s[i] == Meta) + i++; + } + break; + + case 5: + /* Smallest at end, matching substrings */ + if (domatch(s + l, c, 0) && !--n) { + *sp = get_match_ret(*sp, l, l, fl); + return 1; + } + for (i = l; i--;) { + if (i && s[i-1] == Meta) + i--; + for (t = s + l, j = i; j >= 0; j--, t--) { + sav = *t; + *t = '\0'; + if (domatch(s + j, c, 0) && !--n) { + *t = sav; + *sp = get_match_ret(*sp, j, t - s, fl); + return 1; + } + *t = sav; + if (t >= s+2 && t[-2] == Meta) + t--; + if (j >= 2 && s[j-2] == Meta) + j--; + } + } + break; + + case 6: + /* Largest at start, matching substrings. */ + for (i = l; i; i--) { + for (t = s, j = i; j <= l; j++, t++) { + sav = s[j]; + s[j] = '\0'; + if (domatch(t, c, 0) && !--n) { + s[j] = sav; + *sp = get_match_ret(*sp, t - s, j, fl); + return 1; + } + if ((s[j] = sav) == Meta) + j++; + if (*t == Meta) + t++; + } + if (i >= 2 && s[i-2] == Meta) + i--; + } + if (domatch(s + l, c, 0) && !--n) { + *sp = get_match_ret(*sp, 0, 0, fl); + return 1; + } + break; + + case 7: + /* Largest at end, matching substrings. */ + for (i = 0; i < l; i++) { + for (t = s + l, j = i; j >= 0; j--, t--) { + sav = *t; + *t = '\0'; + if (domatch(s + j, c, 0) && !--n) { + *t = sav; + *sp = get_match_ret(*sp, j, t - s, fl); + return 1; + } + *t = sav; + if (t >= s+2 && t[-2] == Meta) + t--; + if (j >= 2 && s[j-2] == Meta) + j--; + } + if (s[i] == Meta) + i++; + } + if (domatch(s + l, c, 0) && !--n) { + *sp = get_match_ret(*sp, l, l, fl); + return 1; + } + break; + } + /* munge the whole string */ + *sp = get_match_ret(*sp, 0, 0, fl); + return 1; +} + +/* The main entry point for matching a string str against * + * a compiled pattern c. `fist' indicates whether leading * + * dots are special. */ + +/**/ +int +domatch(char *str, Comp c, int fist) +{ + int ret; + pptr = str; + first = fist; + if (*pptr == Nularg) + pptr++; + PERMALLOC { + ret = doesmatch(c); + } LASTALLOC; + return ret; +} + +#define untok(C) (itok(C) ? ztokens[(C) - Pound] : (C)) + +/* See if pattern has a matching exclusion (~...) part */ + +/**/ +static int +excluded(Comp c, char *eptr) +{ + char *saves = pptr; + int savei = first, ret; + + first = 0; + if (PATHADDP(c) && pathpos) { + VARARR(char, buf, pathpos + strlen(eptr) + 1); + + strcpy(buf, pathbuf); + strcpy(buf + pathpos, eptr); + pptr = buf; + ret = doesmatch(c->exclude); + } else { + pptr = eptr; + ret = doesmatch(c->exclude); + } + if (*pptr) + ret = 0; + + pptr = saves; + first = savei; + + return ret; +} + +struct gclose { + char *start; + char *end; +}; +typedef struct gclose *Gclose; + +static int inclosure; /* see comment in doesmatch() */ + +/* Add a list of matches that fit the closure. trystring is a string of + * the same length as the target string; a non-zero in that string + * indicates that we have already tried to match the patterns following + * the closure (c->next) at that point and failed. This means that not + * only should we not bother using the corresponding match, we should + * also not bother going any further, since the first time we got to + * that position (when it was marked), we must already have failed on + * and backtracked over any further closure matches beyond that point. + */ + +/**/ +static void +addclosures(Comp c, LinkList closlist, int *pdone, char *trystring) +{ + Gclose gcnode; + char *opptr = pptr; + + while (*pptr) { + if (STARP(c)) { + if (trystring[(pptr+1)-opptr]) + break; + gcnode = (Gclose)zalloc(sizeof(struct gclose)); + gcnode->start = pptr; + gcnode->end = ++pptr; + } else { + char *saves = pptr; + if (OPTIONALP(c) && *pdone >= 1) + return; + if (!matchonce(c) || saves == pptr || + trystring[pptr-opptr]) { + pptr = saves; + break; + } + gcnode = (Gclose)zalloc(sizeof(struct gclose)); + gcnode->start = saves; + gcnode->end = pptr; + } + pushnode(closlist, gcnode); + (*pdone)++; + } +} + +/* see if current string in pptr matches c */ + +/**/ +static int +doesmatch(Comp c) +{ + if (CLOSUREP(c)) { + int done, retflag = 0; + char *saves, *trystring, *opptr; + LinkList closlist; + Gclose gcnode; + + if (first && *pptr == '.') + return 0; + + if (!inclosure && !c->left) { + /* We are not inside another closure, and the current + * pattern is a simple string. We handle this very common + * case specially: otherwise, matches like *foo* are + * extremely slow. Here, instead of backtracking, we track + * forward until we get a match. At top level, we are bound + * to get there eventually, so this is OK. + */ + char looka; + + if (STARP(c) && c->next && + !c->next->left && (looka = *c->next->str) && + !itok(looka)) { + /* Another simple optimisation for a very common case: + * we are processing a * and there is + * an ordinary character match next. We look ahead for + * that character, taking care of Meta bytes. + */ + while (*pptr) { + for (; *pptr; pptr++) { + if (*pptr == Meta) + pptr++; + else if (*pptr == looka) + break; + } + if (!*(saves = pptr)) + break; + if (doesmatch(c->next)) + return 1; + pptr = saves+1; + } + } else { + /* Standard track-forward code */ + for (done = 0; ; done++) { + saves = pptr; + if ((done || ONEHASHP(c) || OPTIONALP(c)) && + ((!c->next && (!LASTP(c) || !*pptr)) || + (c->next && doesmatch(c->next)))) + return 1; + if (done && OPTIONALP(c)) + return 0; + pptr = saves; + first = 0; + if (STARP(c)) { + if (!*pptr) + return 0; + pptr++; + } else if (!matchonce(c) || pptr == saves) + return 0; + } + } + return 0; + } + /* The full, gory backtracking code is now necessary. */ + inclosure++; + closlist = newlinklist(); + trystring = zcalloc(strlen(pptr)+1); + opptr = pptr; + + /* Start by making a list where each match is as long + * as possible. We later have to take account of the + * fact that earlier matches may be too long. + */ + done = 0; + addclosures(c, closlist, &done, trystring); + for (;;) { + if (TWOHASHP(c) && !done) + break; + saves = pptr; + /* do we really want this LASTP here?? */ + if ((!c->next && (!LASTP(c) || !*pptr)) || + (c->next && doesmatch(c->next))) { + retflag = 1; + break; + } + trystring[saves-opptr] = 1; + /* + * If we failed, the first thing to try is whether we can + * shorten the match using the last pattern in the closure. + */ + gcnode = firstnode(closlist) ? peekfirst(closlist) : NULL; + if (gcnode && --gcnode->end > gcnode->start + && (gcnode->end[-1] != Meta || + --gcnode->end > gcnode->start)) { + char savec = *gcnode->end; + *gcnode->end = '\0'; + pptr = gcnode->start; + if (matchonce(c) && pptr != gcnode->start + && !trystring[pptr-opptr]) { + *gcnode->end = savec; + gcnode->end = pptr; + /* Try again to construct a list based on + * this new position + */ + addclosures(c, closlist, &done, trystring+(pptr-opptr)); + continue; + } + *gcnode->end = savec; + } + /* We've now exhausted the possibilities with that match, + * backtrack to the previous. + */ + if ((gcnode = (Gclose)getlinknode(closlist))) { + pptr = gcnode->start; + zfree(gcnode, sizeof(struct gclose)); + done--; + } else + break; + } + freelinklist(closlist, free); + zfree(trystring, strlen(opptr)+1); + inclosure--; + + return retflag; + } else + return matchonce(c); +} + +/**/ +static int +posix_range(char **patptr, int ch) +{ + /* Match POSIX ranges, which correspond to ctype macros, * + * e.g. [:alpha:] -> isalpha. It just doesn't seem worth * + * the palaver of creating a hash table for this. */ + char *start = *patptr; + int len; + + /* we made sure in parsecomp() there was a ':' to search for */ + *patptr = strchr(start, ':'); + len = (*patptr)++ - start; + + if (!strncmp(start, "alpha", len)) + return isalpha(ch); + if (!strncmp(start, "alnum", len)) + return isalnum(ch); + if (!strncmp(start, "blank", len)) + return ch == ' ' || ch == '\t'; + if (!strncmp(start, "cntrl", len)) + return iscntrl(ch); + if (!strncmp(start, "digit", len)) + return isdigit(ch); + if (!strncmp(start, "graph", len)) + return isgraph(ch); + if (!strncmp(start, "lower", len)) + return islower(ch); + if (!strncmp(start, "print", len)) + return isprint(ch); + if (!strncmp(start, "punct", len)) + return ispunct(ch); + if (!strncmp(start, "space", len)) + return isspace(ch); + if (!strncmp(start, "upper", len)) + return isupper(ch); + if (!strncmp(start, "xdigit", len)) + return isxdigit(ch); + return 0; +} + +/**/ +static void +rangematch(char **patptr, int ch, int rchar) +{ + /* Check for a character in a [...] or [^...]. The [ * + * and optional ^ have already been skipped. */ + + char *pat = *patptr; +#ifdef HAVE_STRCOLL + char l_buf[2], r_buf[2], ch_buf[2]; + + ch_buf[0] = ch; + l_buf[1] = r_buf[1] = ch_buf[1] = '\0'; +#endif + +#define PAT(X) (pat[X] == Meta ? pat[(X)+1] ^ 32 : untok(pat[X])) +#define PPAT(X) (pat[(X)-1] == Meta ? pat[X] ^ 32 : untok(pat[X])) + + for (pat++; *pat != Outbrack && *pat; + *pat == Meta ? pat += 2 : pat++) { + if (*pat == Inbrack) { + /* Inbrack can only occur inside a range if we found [:...:]. */ + pat += 2; + if (posix_range(&pat, ch)) + break; + } else if (*pat == '-' && pat[-1] != rchar && + pat[1] != Outbrack) { +#ifdef HAVE_STRCOLL + l_buf[0] = PPAT(-1); + r_buf[0] = PAT(1); + if (strcoll(l_buf, ch_buf) <= 0 && + strcoll(ch_buf, r_buf) <= 0) +#else + if (PPAT(-1) <= ch && PAT(1) >= ch) +#endif + break; + } else if (ch == PAT(0)) + break; + } + + *patptr = pat; +} + +/**/ +static int +matchonce(Comp c) +{ + char *pat = c->str; + for (;;) { + /* loop until success or failure of pattern */ + if (!pat || !*pat) { + /* No current pattern (c->str). */ + char *saves; + int savei; + + if (errflag) + return 0; + /* Remember state in case we need to go back and * + * check for exclusion of pattern or alternatives. */ + saves = pptr; + savei = first; + /* Loop over alternatives with exclusions: (foo~bar|...). * + * Exclusions apply to the pattern in c->left. */ + if (c->left || c->right) { + int ret = 0, ret2 = 0; + if (c->exclude) { + char *exclend = 0; + + /* We may need to back up on things like `(*~foo)' + * if the `*' matched `foo' but needs to match `fo'. + * exclend marks the end of the shortened text. We + * need to restore it to match the tail. + * We set `inclosure' because we need the more + * sophisticated code in doesmatch() for any nested + * closures. + */ + inclosure++; + + while (!exclend || exclend >= pptr) { + char exclsav = 0; + if (exclend) { + exclsav = *exclend; + *exclend = '\0'; + } + if ((ret = doesmatch(c->left))) { + if (exclend) + *exclend = exclsav; + exclsav = *(exclend = pptr); + *exclend = '\0'; + ret2 = !excluded(c, saves); + } + if (exclend) + *exclend = exclsav; + + if (!ret) + break; + if ((ret = ret2 && + ((!c->next && (!LASTP(c) || !*pptr)) + || (c->next && doesmatch(c->next)))) || + (!c->next && LASTP(c))) + break; + /* Back up if necessary: exclend gives the position + * of the end of the match we are excluding, + * so only try to match to there. + */ + exclend--; + pptr = saves; + } + inclosure--; + if (ret) + return 1; + } else + ret = doesmatch(c->left); + ret2 = 0; + if (c->right && (!ret || inclosure)) { + /* If in a closure, we always want the longest match. */ + char *newpptr = pptr; + pptr = saves; + first = savei; + ret2 = doesmatch(c->right); + if (ret && (!ret2 || pptr < newpptr)) + pptr = newpptr; + } + if (!ret && !ret2) + return 0; + } + if (CLOSUREP(c)) + return 1; + if (!c->next) /* no more patterns left */ + return (!LASTP(c) || !*pptr); + /* optimisation when next pattern is not a closure */ + if (!CLOSUREP(c->next)) { + c = c->next; + pat = c->str; + continue; + } + return doesmatch(c->next); + } + /* Don't match leading dot if first is set */ + if (first && *pptr == '.' && *pat != '.') + return 0; + if (*pat == Star) { /* final * is not expanded to ?#; returns success */ + while (*pptr) + pptr++; + return 1; + } + first = 0; /* finished checking start of pattern */ + if (*pat == Quest && *pptr) { + /* match exactly one character */ + if (*pptr == Meta) + pptr++; + pptr++; + pat++; + continue; + } + if (*pat == Inbrack) { + /* Match groups of characters */ + char ch; + + if (!*pptr) + break; + ch = *pptr == Meta ? pptr[1] ^ 32 : *pptr; + if (pat[1] == Hat || pat[1] == '^' || pat[1] == '!') { + /* group is negated */ + *++pat = Hat; + rangematch(&pat, ch, Hat); + DPUTS(!*pat, "BUG: something is very wrong in doesmatch()"); + if (*pat != Outbrack) + break; + pat++; + *pptr == Meta ? pptr += 2 : pptr++; + continue; + } else { + /* pattern is not negated (affirmed? asserted?) */ + rangematch(&pat, ch, Inbrack); + DPUTS(!pat || !*pat, "BUG: something is very wrong in doesmatch()"); + if (*pat == Outbrack) + break; + for (*pptr == Meta ? pptr += 2 : pptr++; + *pat != Outbrack; pat++); + pat++; + continue; + } + } + if (*pat == Inang) { + /* Numeric globbing. */ + unsigned long t1, t2, t3; + char *ptr; + + if (!idigit(*pptr)) + break; + if (*++pat == Outang || + (*pat == '-' && pat[1] == Outang && ++pat)) { + /* <> or <->: any number matches */ + while (idigit(*++pptr)); + pat++; + } else { + /* Flag that there is no upper limit */ + int not3 = 0; + char *opptr = pptr; + /* + * Form is <a-b>, where a or b are numbers or blank. + * t1 = number supplied: must be positive, so use + * unsigned arithmetic. + */ + t1 = (unsigned long)zstrtol(pptr, &ptr, 10); + pptr = ptr; + /* t2 = lower limit */ + if (idigit(*pat)) + t2 = (unsigned long)zstrtol(pat, &ptr, 10); + else + t2 = 0, ptr = pat; + if (*ptr != '-' || (not3 = (ptr[1] == Outang))) + /* exact match or no upper limit */ + t3 = t2, pat = ptr + not3; + else /* t3 = upper limit */ + t3 = (unsigned long)zstrtol(ptr + 1, &pat, 10); + DPUTS(*pat != Outang, "BUG: wrong internal range pattern"); + pat++; + /* + * If the number found is too large for the pattern, + * try matching just the first part. This way + * we always get the longest possible match. + */ + while (!not3 && t1 > t3 && pptr > opptr+1) { + pptr--; + t1 /= 10; + } + if (t1 < t2 || (!not3 && t1 > t3)) + break; + } + continue; + } + if (*pptr == *pat) { + /* just plain old characters */ + pptr++; + pat++; + continue; + } + break; + } + return 0; +} + +/* turn a string into a Comp struct: this doesn't treat / specially */ + +/**/ +Comp +parsereg(char *str) +{ + remnulargs(str); + mode = 1; /* no path components */ + pptr = str; + tail = NULL; + return parsecompsw(GF_TOPLEV); +} + +/* blindly turn a string into a tokenised expression without lexing */ + +/**/ +void +tokenize(char *s) +{ + char *t; + int bslash = 0; + + for (; *s; s++) { + cont: + switch (*s) { + case Bnull: + case '\\': + if (bslash) { + s[-1] = Bnull; + break; + } + bslash = 1; + continue; + case '<': + if (isset(SHGLOB)) + break; + if (bslash) { + s[-1] = Bnull; + break; + } + t = s; + while (idigit(*++s)); + if (*s != '-') + goto cont; + while (idigit(*++s)); + if (*s != '>') + goto cont; + *t = Inang; + *s = Outang; + break; + case '^': + case '#': + case '~': + if (unset(EXTENDEDGLOB)) + break; + case '(': + case '|': + case ')': + if (isset(SHGLOB)) + break; + case '[': + case ']': + case '*': + case '?': + for (t = ztokens; *t; t++) + if (*t == *s) { + if (bslash) + s[-1] = Bnull; + else + *s = (t - ztokens) + Pound; + break; + } + } + bslash = 0; + } +} + +/* remove unnecessary Nulargs */ + +/**/ +void +remnulargs(char *s) +{ + int nl = *s; + char *t = s; + + while (*s) + if (INULL(*s)) + chuck(s); + else + s++; + if (!*t && nl) { + t[0] = Nularg; + t[1] = '\0'; + } +} + +/* qualifier functions: mostly self-explanatory, see glob(). */ + +/* device number */ + +/**/ +static int +qualdev(struct stat *buf, long dv) +{ + return buf->st_dev == dv; +} + +/* number of hard links to file */ + +/**/ +static int +qualnlink(struct stat *buf, long ct) +{ + return (range < 0 ? buf->st_nlink < ct : + range > 0 ? buf->st_nlink > ct : + buf->st_nlink == ct); +} + +/* user ID */ + +/**/ +static int +qualuid(struct stat *buf, long uid) +{ + return buf->st_uid == uid; +} + +/* group ID */ + +/**/ +static int +qualgid(struct stat *buf, long gid) +{ + return buf->st_gid == gid; +} + +/* device special file? */ + +/**/ +static int +qualisdev(struct stat *buf, long junk) +{ + return S_ISBLK(buf->st_mode) || S_ISCHR(buf->st_mode); +} + +/* block special file? */ + +/**/ +static int +qualisblk(struct stat *buf, long junk) +{ + return S_ISBLK(buf->st_mode); +} + +/* character special file? */ + +/**/ +static int +qualischr(struct stat *buf, long junk) +{ + return S_ISCHR(buf->st_mode); +} + +/* directory? */ + +/**/ +static int +qualisdir(struct stat *buf, long junk) +{ + return S_ISDIR(buf->st_mode); +} + +/* FIFO? */ + +/**/ +static int +qualisfifo(struct stat *buf, long junk) +{ + return S_ISFIFO(buf->st_mode); +} + +/* symbolic link? */ + +/**/ +static int +qualislnk(struct stat *buf, long junk) +{ + return S_ISLNK(buf->st_mode); +} + +/* regular file? */ + +/**/ +static int +qualisreg(struct stat *buf, long junk) +{ + return S_ISREG(buf->st_mode); +} + +/* socket? */ + +/**/ +static int +qualissock(struct stat *buf, long junk) +{ + return S_ISSOCK(buf->st_mode); +} + +/* given flag is set in mode */ + +/**/ +static int +qualflags(struct stat *buf, long mod) +{ + return mode_to_octal(buf->st_mode) & mod; +} + +/* mode matches number supplied exactly */ + +/**/ +static int +qualeqflags(struct stat *buf, long mod) +{ + return mode_to_octal(buf->st_mode) == mod; +} + +/* regular executable file? */ + +/**/ +static int +qualiscom(struct stat *buf, long mod) +{ + return S_ISREG(buf->st_mode) && (buf->st_mode & S_IXUGO); +} + +/* size in required range? */ + +/**/ +static int +qualsize(struct stat *buf, long size) +{ + unsigned long scaled = buf->st_size; + + switch (units) { + case TT_POSIX_BLOCKS: + scaled += 511l; + scaled /= 512l; + break; + case TT_KILOBYTES: + scaled += 1023l; + scaled /= 1024l; + break; + case TT_MEGABYTES: + scaled += 1048575l; + scaled /= 1048576l; + break; + } + + return (range < 0 ? scaled < (unsigned long) size : + range > 0 ? scaled > (unsigned long) size : + scaled == (unsigned long) size); +} + +/* time in required range? */ + +/**/ +static int +qualtime(struct stat *buf, long days) +{ + time_t now, diff; + + time(&now); + diff = now - (amc == 0 ? buf->st_atime : amc == 1 ? buf->st_mtime : + buf->st_ctime); + /* handle multipliers indicating units */ + switch (units) { + case TT_DAYS: + diff /= 86400l; + break; + case TT_HOURS: + diff /= 3600l; + break; + case TT_MINS: + diff /= 60l; + break; + case TT_WEEKS: + diff /= 604800l; + break; + case TT_MONTHS: + diff /= 2592000l; + break; + } + + return (range < 0 ? diff < days : + range > 0 ? diff > days : + diff == days); +} diff --git a/Src/hashtable.c b/Src/hashtable.c new file mode 100644 index 000000000..4adf3904d --- /dev/null +++ b/Src/hashtable.c @@ -0,0 +1,1285 @@ +/* + * hashtable.c - hash tables + * + * 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 "../config.h" + +#ifdef ZSH_HASH_DEBUG +# define HASHTABLE_DEBUG_MEMBERS \ + /* Members of struct hashtable used for debugging hash tables */ \ + HashTable next, last; /* linked list of all hash tables */ \ + char *tablename; /* string containing name of the hash table */ \ + PrintTableStats printinfo; /* pointer to function to print table stats */ +#else /* !ZSH_HASH_DEBUG */ +# define HASHTABLE_DEBUG_MEMBERS +#endif /* !ZSH_HASH_DEBUG */ + +#define HASHTABLE_INTERNAL_MEMBERS \ + ScanStatus scan; /* status of a scan over this hashtable */ \ + HASHTABLE_DEBUG_MEMBERS + +typedef struct scanstatus *ScanStatus; + +#include "zsh.mdh" +#include "hashtable.pro" + +/* Structure for recording status of a hashtable scan in progress. When a * + * scan starts, the .scan member of the hashtable structure points to one * + * of these. That member being non-NULL disables resizing of the * + * hashtable (when adding elements). When elements are deleted, the * + * contents of this structure is used to make sure the scan won't stumble * + * into the deleted element. */ + +struct scanstatus { + int sorted; + union { + struct { + HashNode *tab; + int ct; + } s; + HashNode u; + } u; +}; + +/********************************/ +/* Generic Hash Table functions */ +/********************************/ + +#ifdef ZSH_HASH_DEBUG +static HashTable firstht, lastht; +#endif /* ZSH_HASH_DEBUG */ + +/* Generic hash function */ + +/**/ +unsigned +hasher(char *str) +{ + unsigned hashval = 0; + + while (*str) + hashval += (hashval << 5) + ((unsigned) *str++); + + return hashval; +} + +/* Get a new hash table */ + +/**/ +HashTable +newhashtable(int size, char const *name, PrintTableStats printinfo) +{ + HashTable ht; + + ht = (HashTable) zcalloc(sizeof *ht); +#ifdef ZSH_HASH_DEBUG + ht->next = NULL; + if(!firstht) + firstht = ht; + ht->last = lastht; + if(lastht) + lastht->next = ht; + lastht = ht; + ht->printinfo = printinfo ? printinfo : printhashtabinfo; + ht->tablename = ztrdup(name); +#endif /* ZSH_HASH_DEBUG */ + ht->nodes = (HashNode *) zcalloc(size * sizeof(HashNode)); + ht->hsize = size; + ht->ct = 0; + ht->scan = NULL; + return ht; +} + +/* Delete a hash table. After this function has been used, any * + * existing pointers to the hash table are invalid. */ + +/**/ +void +deletehashtable(HashTable ht) +{ + ht->emptytable(ht); +#ifdef ZSH_HASH_DEBUG + if(ht->next) + ht->next->last = ht->last; + else + lastht = ht->last; + if(ht->last) + ht->last->next = ht->next; + else + firstht = ht->next; +#endif /* ZSH_HASH_DEBUG */ + zfree(ht->nodes, ht->hsize * sizeof(HashNode)); + zfree(ht, sizeof(*ht)); +} + +/* Add a node to a hash table. * + * nam is the key to use in hashing. dat is a pointer * + * to the node to add. If there is already a node in * + * the table with the same key, it is first freed, and * + * then the new node is added. If the number of nodes * + * is now greater than twice the number of hash values, * + * the table is then expanded. */ + +/**/ +void +addhashnode(HashTable ht, char *nam, void *nodeptr) +{ + unsigned hashval; + HashNode hn, hp, hq; + + hn = (HashNode) nodeptr; + hn->nam = nam; + + hashval = ht->hash(hn->nam) % ht->hsize; + hp = ht->nodes[hashval]; + + /* check if this is the first node for this hash value */ + if (!hp) { + hn->next = NULL; + ht->nodes[hashval] = hn; + if (++ht->ct >= ht->hsize * 2 && !ht->scan) + expandhashtable(ht); + return; + } + + /* else check if the first node contains the same key */ + if (!strcmp(hp->nam, hn->nam)) { + ht->nodes[hashval] = hn; + replacing: + hn->next = hp->next; + if(ht->scan) + if(ht->scan->sorted) { + HashNode *tab = ht->scan->u.s.tab; + int i; + for(i = ht->scan->u.s.ct; i--; ) + if(tab[i] == hp) + tab[i] = hn; + } else if(ht->scan->u.u == hp) + ht->scan->u.u = hn; + ht->freenode(hp); + return; + } + + /* else run through the list and check all the keys */ + hq = hp; + hp = hp->next; + for (; hp; hq = hp, hp = hp->next) { + if (!strcmp(hp->nam, hn->nam)) { + hq->next = hn; + goto replacing; + } + } + + /* else just add it at the front of the list */ + hn->next = ht->nodes[hashval]; + ht->nodes[hashval] = hn; + if (++ht->ct >= ht->hsize * 2 && !ht->scan) + expandhashtable(ht); +} + +/* Get an enabled entry in a hash table. * + * If successful, it returns a pointer to * + * the hashnode. If the node is DISABLED * + * or isn't found, it returns NULL */ + +/**/ +HashNode +gethashnode(HashTable ht, char *nam) +{ + unsigned hashval; + HashNode hp; + + hashval = ht->hash(nam) % ht->hsize; + for (hp = ht->nodes[hashval]; hp; hp = hp->next) { + if (!strcmp(hp->nam, nam)) { + if (hp->flags & DISABLED) + return NULL; + else + return hp; + } + } + return NULL; +} + +/* Get an entry in a hash table. It will * + * ignore the DISABLED flag and return a * + * pointer to the hashnode if found, else * + * it returns NULL. */ + +/**/ +HashNode +gethashnode2(HashTable ht, char *nam) +{ + unsigned hashval; + HashNode hp; + + hashval = ht->hash(nam) % ht->hsize; + for (hp = ht->nodes[hashval]; hp; hp = hp->next) { + if (!strcmp(hp->nam, nam)) + return hp; + } + return NULL; +} + +/* Remove an entry from a hash table. * + * If successful, it removes the node from the * + * table and returns a pointer to it. If there * + * is no such node, then it returns NULL */ + +/**/ +HashNode +removehashnode(HashTable ht, char *nam) +{ + unsigned hashval; + HashNode hp, hq; + + hashval = ht->hash(nam) % ht->hsize; + hp = ht->nodes[hashval]; + + /* if no nodes at this hash value, return NULL */ + if (!hp) + return NULL; + + /* else check if the key in the first one matches */ + if (!strcmp(hp->nam, nam)) { + ht->nodes[hashval] = hp->next; + gotit: + ht->ct--; + if(ht->scan) + if(ht->scan->sorted) { + HashNode *tab = ht->scan->u.s.tab; + int i; + for(i = ht->scan->u.s.ct; i--; ) + if(tab[i] == hp) + tab[i] = NULL; + } else if(ht->scan->u.u == hp) + ht->scan->u.u = hp->next; + return hp; + } + + /* else run through the list and check the rest of the keys */ + hq = hp; + hp = hp->next; + for (; hp; hq = hp, hp = hp->next) { + if (!strcmp(hp->nam, nam)) { + hq->next = hp->next; + goto gotit; + } + } + + /* else it is not in the list, so return NULL */ + return NULL; +} + +/* Disable a node in a hash table */ + +/**/ +void +disablehashnode(HashNode hn, int flags) +{ + hn->flags |= DISABLED; +} + +/* Enable a node in a hash table */ + +/**/ +void +enablehashnode(HashNode hn, int flags) +{ + hn->flags &= ~DISABLED; +} + +/* Compare two hash table entries */ + +/**/ +static int +hnamcmp(const void *ap, const void *bp) +{ + HashNode a = *(HashNode *)ap; + HashNode b = *(HashNode *)bp; + return ztrcmp((unsigned char *) a->nam, (unsigned char *) b->nam); +} + +/* Scan the nodes in a hash table and execute scanfunc on nodes based on + * the flags that are set/unset. scanflags is passed unchanged to + * scanfunc (if executed). + * + * If sorted != 0, then sort entries of hash table before scanning. + * If flags1 > 0, then execute scanfunc on a node only if at least one of + * these flags is set. + * If flags2 > 0, then execute scanfunc on a node only if all of + * these flags are NOT set. + * The conditions above for flags1/flags2 must both be true. + * + * It is safe to add, remove or replace hash table elements from within + * the scanfunc. Replaced elements will appear in the scan exactly once, + * the new version if it was not scanned before the replacement was made. + * Added elements might or might not appear in the scan. + */ + +/**/ +void +scanhashtable(HashTable ht, int sorted, int flags1, int flags2, ScanFunc scanfunc, int scanflags) +{ + struct scanstatus st; + + if (sorted) { + int i, ct = ht->ct; + VARARR(HashNode, hnsorttab, ct); + HashNode *htp, hn; + + for (htp = hnsorttab, i = 0; i < ht->hsize; i++) + for (hn = ht->nodes[i]; hn; hn = hn->next) + *htp++ = hn; + qsort((void *)hnsorttab, ct, sizeof(HashNode), hnamcmp); + + st.sorted = 1; + st.u.s.tab = hnsorttab; + st.u.s.ct = ct; + ht->scan = &st; + + for (htp = hnsorttab, i = 0; i < ct; i++, htp++) + if (*htp && ((*htp)->flags & flags1) + !flags1 && + !((*htp)->flags & flags2)) + scanfunc(*htp, scanflags); + + ht->scan = NULL; + } else { + int i, hsize = ht->hsize; + HashNode *nodes = ht->nodes; + + st.sorted = 0; + ht->scan = &st; + + for (i = 0; i < hsize; i++) + for (st.u.u = nodes[i]; st.u.u; ) { + HashNode hn = st.u.u; + st.u.u = st.u.u->next; + if ((hn->flags & flags1) + !flags1 && !(hn->flags & flags2)) + scanfunc(hn, scanflags); + } + + ht->scan = NULL; + } +} + +/* Scan all nodes in a hash table and executes scanfunc on the * + * nodes which meet all the following criteria: * + * The hash key must match the glob pattern given by `com'. * + * If (flags1 > 0), then any flag in flags1 must be set. * + * If (flags2 > 0), then all flags in flags2 must NOT be set. * + * * + * scanflags is passed unchanged to scanfunc (if executed). * + * The return value is the number of matches. */ + +/**/ +int +scanmatchtable(HashTable ht, Comp com, int flags1, int flags2, ScanFunc scanfunc, int scanflags) +{ + int i, hsize = ht->hsize; + HashNode *nodes = ht->nodes; + int match = 0; + struct scanstatus st; + + st.sorted = 0; + ht->scan = &st; + + for (i = 0; i < hsize; i++) + for (st.u.u = nodes[i]; st.u.u; ) { + HashNode hn = st.u.u; + st.u.u = st.u.u->next; + if ((hn->flags & flags1) + !flags1 && !(hn->flags & flags2) && + domatch(hn->nam, com, 0)) + scanfunc(hn, scanflags); + match++; + } + + ht->scan = NULL; + + return match; +} + +/* Expand hash tables when they get too many entries. * + * The new size is 4 times the previous size. */ + +/**/ +static void +expandhashtable(HashTable ht) +{ + struct hashnode **onodes, **ha, *hn, *hp; + int i, osize; + + osize = ht->hsize; + onodes = ht->nodes; + + ht->hsize = osize * 4; + ht->nodes = (HashNode *) zcalloc(ht->hsize * sizeof(HashNode)); + ht->ct = 0; + + /* scan through the old list of nodes, and * + * rehash them into the new list of nodes */ + for (i = 0, ha = onodes; i < osize; i++, ha++) { + for (hn = *ha; hn;) { + hp = hn->next; + ht->addnode(ht, hn->nam, hn); + hn = hp; + } + } + zfree(onodes, osize * sizeof(HashNode)); +} + +/* Empty the hash table and resize it if necessary */ + +/**/ +static void +resizehashtable(HashTable ht, int newsize) +{ + struct hashnode **ha, *hn, *hp; + int i; + + /* free all the hash nodes */ + ha = ht->nodes; + for (i = 0; i < ht->hsize; i++, ha++) { + for (hn = *ha; hn;) { + hp = hn->next; + ht->freenode(hn); + hn = hp; + } + } + + /* If new size desired is different from current size, * + * we free it and allocate a new nodes array. */ + if (ht->hsize != newsize) { + zfree(ht->nodes, ht->hsize * sizeof(HashNode)); + ht->nodes = (HashNode *) zcalloc(newsize * sizeof(HashNode)); + ht->hsize = newsize; + } else { + /* else we just re-zero the current nodes array */ + memset(ht->nodes, 0, newsize * sizeof(HashNode)); + } + + ht->ct = 0; +} + +/* Generic method to empty a hash table */ + +/**/ +void +emptyhashtable(HashTable ht) +{ + resizehashtable(ht, ht->hsize); +} + +#ifdef ZSH_HASH_DEBUG + +/* Print info about hash table */ + +#define MAXDEPTH 7 + +/**/ +static void +printhashtabinfo(HashTable ht) +{ + HashNode hn; + int chainlen[MAXDEPTH + 1]; + int i, tmpcount, total; + + printf("name of table : %s\n", ht->tablename); + printf("size of nodes[] : %d\n", ht->hsize); + printf("number of nodes : %d\n\n", ht->ct); + + memset(chainlen, 0, sizeof(chainlen)); + + /* count the number of nodes just to be sure */ + total = 0; + for (i = 0; i < ht->hsize; i++) { + tmpcount = 0; + for (hn = ht->nodes[i]; hn; hn = hn->next) + tmpcount++; + if (tmpcount >= MAXDEPTH) + chainlen[MAXDEPTH]++; + else + chainlen[tmpcount]++; + total += tmpcount; + } + + for (i = 0; i < MAXDEPTH; i++) + printf("number of hash values with chain of length %d : %4d\n", i, chainlen[i]); + printf("number of hash values with chain of length %d+ : %4d\n", MAXDEPTH, chainlen[MAXDEPTH]); + printf("total number of nodes : %4d\n", total); +} + +/**/ +int +bin_hashinfo(char *nam, char **args, char *ops, int func) +{ + HashTable ht; + printf("----------------------------------------------------\n"); + for(ht = firstht; ht; ht = ht->next) { + ht->printinfo(ht); + printf("----------------------------------------------------\n"); + } + return 0; +} + +#endif /* ZSH_HASH_DEBUG */ + +/********************************/ +/* Command Hash Table Functions */ +/********************************/ + +/* hash table containing external commands */ + +/**/ +HashTable cmdnamtab; + +/* how far we've hashed the PATH so far */ + +/**/ +char **pathchecked; + +/* Create a new command hash table */ + +/**/ +void +createcmdnamtable(void) +{ + cmdnamtab = newhashtable(201, "cmdnamtab", NULL); + + cmdnamtab->hash = hasher; + cmdnamtab->emptytable = emptycmdnamtable; + cmdnamtab->filltable = fillcmdnamtable; + cmdnamtab->addnode = addhashnode; + cmdnamtab->getnode = gethashnode2; + cmdnamtab->getnode2 = gethashnode2; + cmdnamtab->removenode = removehashnode; + cmdnamtab->disablenode = NULL; + cmdnamtab->enablenode = NULL; + cmdnamtab->freenode = freecmdnamnode; + cmdnamtab->printnode = printcmdnamnode; + + pathchecked = path; +} + +/**/ +static void +emptycmdnamtable(HashTable ht) +{ + emptyhashtable(ht); + pathchecked = path; +} + +/* Add all commands in a given directory * + * to the command hashtable. */ + +/**/ +void +hashdir(char **dirp) +{ + Cmdnam cn; + DIR *dir; + char *fn; + + if (isrelative(*dirp) || !(dir = opendir(unmeta(*dirp)))) + return; + + while ((fn = zreaddir(dir, 1))) { + if (!cmdnamtab->getnode(cmdnamtab, fn)) { + cn = (Cmdnam) zcalloc(sizeof *cn); + cn->flags = 0; + cn->u.name = dirp; + cmdnamtab->addnode(cmdnamtab, ztrdup(fn), cn); + } + } + closedir(dir); +} + +/* Go through user's PATH and add everything to * + * the command hashtable. */ + +/**/ +static void +fillcmdnamtable(HashTable ht) +{ + char **pq; + + for (pq = pathchecked; *pq; pq++) + hashdir(pq); + + pathchecked = pq; +} + +/**/ +static void +freecmdnamnode(HashNode hn) +{ + Cmdnam cn = (Cmdnam) hn; + + zsfree(cn->nam); + if (cn->flags & HASHED) + zsfree(cn->u.cmd); + + zfree(cn, sizeof(struct cmdnam)); +} + +/* Print an element of the cmdnamtab hash table (external command) */ + +/**/ +static void +printcmdnamnode(HashNode hn, int printflags) +{ + Cmdnam cn = (Cmdnam) hn; + + if (printflags & PRINT_WHENCE_WORD) { + printf("%s: %s\n", cn->nam, (cn->flags & HASHED) ? + "hashed" : "command"); + return; + } + + if ((printflags & PRINT_WHENCE_CSH) || (printflags & PRINT_WHENCE_SIMPLE)) { + if (cn->flags & HASHED) { + zputs(cn->u.cmd, stdout); + putchar('\n'); + } else { + zputs(*(cn->u.name), stdout); + putchar('/'); + zputs(cn->nam, stdout); + putchar('\n'); + } + return; + } + + if (printflags & PRINT_WHENCE_VERBOSE) { + if (cn->flags & HASHED) { + nicezputs(cn->nam, stdout); + printf(" is hashed to "); + nicezputs(cn->u.cmd, stdout); + putchar('\n'); + } else { + nicezputs(cn->nam, stdout); + printf(" is "); + nicezputs(*(cn->u.name), stdout); + putchar('/'); + nicezputs(cn->nam, stdout); + putchar('\n'); + } + return; + } + + if (cn->flags & HASHED) { + quotedzputs(cn->nam, stdout); + putchar('='); + quotedzputs(cn->u.cmd, stdout); + putchar('\n'); + } else { + quotedzputs(cn->nam, stdout); + putchar('='); + quotedzputs(*(cn->u.name), stdout); + putchar('/'); + quotedzputs(cn->nam, stdout); + putchar('\n'); + } +} + +/***************************************/ +/* Shell Function Hash Table Functions */ +/***************************************/ + +/* hash table containing the shell functions */ + +/**/ +HashTable shfunctab; + +/**/ +void +createshfunctable(void) +{ + shfunctab = newhashtable(7, "shfunctab", NULL); + + shfunctab->hash = hasher; + shfunctab->emptytable = NULL; + shfunctab->filltable = NULL; + shfunctab->addnode = addhashnode; + shfunctab->getnode = gethashnode; + shfunctab->getnode2 = gethashnode2; + shfunctab->removenode = removeshfuncnode; + shfunctab->disablenode = disableshfuncnode; + shfunctab->enablenode = enableshfuncnode; + shfunctab->freenode = freeshfuncnode; + shfunctab->printnode = printshfuncnode; +} + +/* Remove an entry from the shell function hash table. * + * It checks if the function is a signal trap and if so, * + * it will disable the trapping of that signal. */ + +/**/ +static HashNode +removeshfuncnode(HashTable ht, char *nam) +{ + HashNode hn; + + if ((hn = removehashnode(shfunctab, nam))) { + if (!strncmp(hn->nam, "TRAP", 4)) + unsettrap(getsignum(hn->nam + 4)); + return hn; + } else + return NULL; +} + +/* Disable an entry in the shell function hash table. * + * It checks if the function is a signal trap and if so, * + * it will disable the trapping of that signal. */ + +/**/ +static void +disableshfuncnode(HashNode hn, int flags) +{ + hn->flags |= DISABLED; + if (!strncmp(hn->nam, "TRAP", 4)) { + int signum = getsignum(hn->nam + 4); + sigtrapped[signum] &= ~ZSIG_FUNC; + sigfuncs[signum] = NULL; + unsettrap(signum); + } +} + +/* Re-enable an entry in the shell function hash table. * + * It checks if the function is a signal trap and if so, * + * it will re-enable the trapping of that signal. */ + +/**/ +static void +enableshfuncnode(HashNode hn, int flags) +{ + Shfunc shf = (Shfunc) hn; + int signum; + + shf->flags &= ~DISABLED; + if (!strncmp(shf->nam, "TRAP", 4)) { + signum = getsignum(shf->nam + 4); + if (signum != -1) { + settrap(signum, shf->funcdef); + sigtrapped[signum] |= ZSIG_FUNC; + } + } +} + +/**/ +static void +freeshfuncnode(HashNode hn) +{ + Shfunc shf = (Shfunc) hn; + + zsfree(shf->nam); + if (shf->funcdef) + freestruct(shf->funcdef); + zfree(shf, sizeof(struct shfunc)); +} + +/* Print a shell function */ + +/**/ +static void +printshfuncnode(HashNode hn, int printflags) +{ + Shfunc f = (Shfunc) hn; + char *t; + + if ((printflags & PRINT_NAMEONLY) || + ((printflags & PRINT_WHENCE_SIMPLE) && + !(printflags & PRINT_WHENCE_FUNCDEF))) { + zputs(f->nam, stdout); + putchar('\n'); + return; + } + + if ((printflags & (PRINT_WHENCE_VERBOSE|PRINT_WHENCE_WORD)) && + !(printflags & PRINT_WHENCE_FUNCDEF)) { + nicezputs(f->nam, stdout); + printf((printflags & PRINT_WHENCE_WORD) ? ": function\n" : + " is a shell function\n"); + return; + } + + if (f->flags & PM_UNDEFINED) + printf("undefined "); + if (f->flags & PM_TAGGED) + printf("traced "); + if ((f->flags & PM_UNDEFINED) || !f->funcdef) { + nicezputs(f->nam, stdout); + printf(" () { }\n"); + return; + } + + t = getpermtext((void *) dupstruct((void *) f->funcdef)); + quotedzputs(f->nam, stdout); + printf(" () {\n\t"); + zputs(t, stdout); + printf("\n}\n"); + zsfree(t); +} + +/**************************************/ +/* Reserved Word Hash Table Functions */ +/**************************************/ + +/* Nodes for reserved word hash table */ + +static struct reswd reswds[] = { + {NULL, "!", 0, BANG}, + {NULL, "[[", 0, DINBRACK}, + {NULL, "{", 0, INBRACE}, + {NULL, "}", 0, OUTBRACE}, + {NULL, "case", 0, CASE}, + {NULL, "coproc", 0, COPROC}, + {NULL, "do", 0, DO}, + {NULL, "done", 0, DONE}, + {NULL, "elif", 0, ELIF}, + {NULL, "else", 0, ELSE}, + {NULL, "end", 0, ZEND}, + {NULL, "esac", 0, ESAC}, + {NULL, "fi", 0, FI}, + {NULL, "for", 0, FOR}, + {NULL, "foreach", 0, FOREACH}, + {NULL, "function", 0, FUNC}, + {NULL, "if", 0, IF}, + {NULL, "nocorrect", 0, NOCORRECT}, + {NULL, "repeat", 0, REPEAT}, + {NULL, "select", 0, SELECT}, + {NULL, "then", 0, THEN}, + {NULL, "time", 0, TIME}, + {NULL, "until", 0, UNTIL}, + {NULL, "while", 0, WHILE}, + {NULL, NULL} +}; + +/* hash table containing the reserved words */ + +/**/ +HashTable reswdtab; + +/* Build the hash table containing zsh's reserved words. */ + +/**/ +void +createreswdtable(void) +{ + Reswd rw; + + reswdtab = newhashtable(23, "reswdtab", NULL); + + reswdtab->hash = hasher; + reswdtab->emptytable = NULL; + reswdtab->filltable = NULL; + reswdtab->addnode = addhashnode; + reswdtab->getnode = gethashnode; + reswdtab->getnode2 = gethashnode2; + reswdtab->removenode = NULL; + reswdtab->disablenode = disablehashnode; + reswdtab->enablenode = enablehashnode; + reswdtab->freenode = NULL; + reswdtab->printnode = printreswdnode; + + for (rw = reswds; rw->nam; rw++) + reswdtab->addnode(reswdtab, rw->nam, rw); +} + +/* Print a reserved word */ + +/**/ +static void +printreswdnode(HashNode hn, int printflags) +{ + Reswd rw = (Reswd) hn; + + if (printflags & PRINT_WHENCE_WORD) { + printf("%s: reserved\n", rw->nam); + return; + } + + if (printflags & PRINT_WHENCE_CSH) { + printf("%s: shell reserved word\n", rw->nam); + return; + } + + if (printflags & PRINT_WHENCE_VERBOSE) { + printf("%s is a reserved word\n", rw->nam); + return; + } + + /* default is name only */ + printf("%s\n", rw->nam); +} + +/********************************/ +/* Aliases Hash Table Functions */ +/********************************/ + +/* hash table containing the aliases */ + +/**/ +HashTable aliastab; + +/* Create new hash table for aliases */ + +/**/ +void +createaliastable(void) +{ + aliastab = newhashtable(23, "aliastab", NULL); + + aliastab->hash = hasher; + aliastab->emptytable = NULL; + aliastab->filltable = NULL; + aliastab->addnode = addhashnode; + aliastab->getnode = gethashnode; + aliastab->getnode2 = gethashnode2; + aliastab->removenode = removehashnode; + aliastab->disablenode = disablehashnode; + aliastab->enablenode = enablehashnode; + aliastab->freenode = freealiasnode; + aliastab->printnode = printaliasnode; + + /* add the default aliases */ + aliastab->addnode(aliastab, ztrdup("run-help"), createaliasnode(ztrdup("man"), 0)); + aliastab->addnode(aliastab, ztrdup("which-command"), createaliasnode(ztrdup("whence"), 0)); +} + +/* Create a new alias node */ + +/**/ +Alias +createaliasnode(char *txt, int flags) +{ + Alias al; + + al = (Alias) zcalloc(sizeof *al); + al->flags = flags; + al->text = txt; + al->inuse = 0; + return al; +} + +/**/ +static void +freealiasnode(HashNode hn) +{ + Alias al = (Alias) hn; + + zsfree(al->nam); + zsfree(al->text); + zfree(al, sizeof(struct alias)); +} + +/* Print an alias */ + +/**/ +static void +printaliasnode(HashNode hn, int printflags) +{ + Alias a = (Alias) hn; + + if (printflags & PRINT_NAMEONLY) { + zputs(a->nam, stdout); + putchar('\n'); + return; + } + + if (printflags & PRINT_WHENCE_WORD) { + printf("%s: alias\n", a->nam); + return; + } + + if (printflags & PRINT_WHENCE_SIMPLE) { + zputs(a->text, stdout); + putchar('\n'); + return; + } + + if (printflags & PRINT_WHENCE_CSH) { + nicezputs(a->nam, stdout); + if (a->flags & ALIAS_GLOBAL) + printf(": globally aliased to "); + else + printf(": aliased to "); + nicezputs(a->text, stdout); + putchar('\n'); + return; + } + + if (printflags & PRINT_WHENCE_VERBOSE) { + nicezputs(a->nam, stdout); + if (a->flags & ALIAS_GLOBAL) + printf(" is a global alias for "); + else + printf(" is an alias for "); + nicezputs(a->text, stdout); + putchar('\n'); + return; + } + + if (printflags & PRINT_LIST) { + printf("alias "); + if (a->flags & ALIAS_GLOBAL) + printf("-g "); + + /* If an alias begins with `-', then we must output `-- ' * + * first, so that it is not interpreted as an option. */ + if(a->nam[0] == '-') + printf("-- "); + } + + quotedzputs(a->nam, stdout); + putchar('='); + quotedzputs(a->text, stdout); + putchar('\n'); +} + +/**********************************/ +/* Parameter Hash Table Functions */ +/**********************************/ + +/**/ +void +freeparamnode(HashNode hn) +{ + Param pm = (Param) hn; + + zsfree(pm->nam); + zfree(pm, sizeof(struct param)); +} + +/* Print a parameter */ + +/**/ +void +printparamnode(HashNode hn, int printflags) +{ + Param p = (Param) hn; + char *t, **u; + + if (p->flags & PM_UNSET) + return; + + /* Print the attributes of the parameter */ + if (printflags & PRINT_TYPE) { + if (p->flags & PM_INTEGER) + printf("integer "); + if (p->flags & PM_ARRAY) + printf("array "); + if (p->flags & PM_LEFT) + printf("left justified %d ", p->ct); + if (p->flags & PM_RIGHT_B) + printf("right justified %d ", p->ct); + if (p->flags & PM_RIGHT_Z) + printf("zero filled %d ", p->ct); + if (p->flags & PM_LOWER) + printf("lowercase "); + if (p->flags & PM_UPPER) + printf("uppercase "); + if (p->flags & PM_READONLY) + printf("readonly "); + if (p->flags & PM_TAGGED) + printf("tagged "); + if (p->flags & PM_EXPORTED) + printf("exported "); + } + + if (printflags & PRINT_NAMEONLY) { + zputs(p->nam, stdout); + putchar('\n'); + return; + } + + /* How the value is displayed depends * + * on the type of the parameter */ + quotedzputs(p->nam, stdout); + putchar('='); + switch (PM_TYPE(p->flags)) { + case PM_SCALAR: + /* string: simple output */ + if (p->gets.cfn && (t = p->gets.cfn(p))) + quotedzputs(t, stdout); + putchar('\n'); + break; + case PM_INTEGER: + /* integer */ + printf("%ld\n", p->gets.ifn(p)); + break; + case PM_ARRAY: + /* array */ + putchar('('); + u = p->gets.afn(p); + if(*u) { + quotedzputs(*u++, stdout); + while (*u) { + putchar(' '); + quotedzputs(*u++, stdout); + } + } + printf(")\n"); + break; + } +} + +/****************************************/ +/* Named Directory Hash Table Functions */ +/****************************************/ + +/* hash table containing named directories */ + +/**/ +HashTable nameddirtab; + +/* != 0 if all the usernames have already been * + * added to the named directory hash table. */ + +static int allusersadded; + +/* Create new hash table for named directories */ + +/**/ +void +createnameddirtable(void) +{ + nameddirtab = newhashtable(201, "nameddirtab", NULL); + + nameddirtab->hash = hasher; + nameddirtab->emptytable = emptynameddirtable; + nameddirtab->filltable = fillnameddirtable; + nameddirtab->addnode = addnameddirnode; + nameddirtab->getnode = gethashnode; + nameddirtab->getnode2 = gethashnode2; + nameddirtab->removenode = removenameddirnode; + nameddirtab->disablenode = NULL; + nameddirtab->enablenode = NULL; + nameddirtab->freenode = freenameddirnode; + nameddirtab->printnode = printnameddirnode; + + allusersadded = 0; + finddir(NULL); /* clear the finddir cache */ +} + +/* Empty the named directories table */ + +/**/ +static void +emptynameddirtable(HashTable ht) +{ + emptyhashtable(ht); + allusersadded = 0; + finddir(NULL); /* clear the finddir cache */ +} + +/* Add all the usernames in the password file/database * + * to the named directories table. */ + +/**/ +static void +fillnameddirtable(HashTable ht) +{ +#ifdef HAVE_GETPWENT + if (!allusersadded) { + struct passwd *pw; + + setpwent(); + + /* loop through the password file/database * + * and add all entries returned. */ + while ((pw = getpwent()) && !errflag) + adduserdir(ztrdup(pw->pw_name), pw->pw_dir, ND_USERNAME, 1); + + endpwent(); + allusersadded = 1; + } + return; +#endif /* HAVE_GETPWENT */ +} + +/* Add an entry to the named directory hash * + * table, clearing the finddir() cache and * + * initialising the `diff' member. */ + +/**/ +static void +addnameddirnode(HashTable ht, char *nam, void *nodeptr) +{ + Nameddir nd = (Nameddir) nodeptr; + + nd->diff = strlen(nd->dir) - strlen(nam); + finddir(NULL); /* clear the finddir cache */ + addhashnode(ht, nam, nodeptr); +} + +/* Remove an entry from the named directory * + * hash table, clearing the finddir() cache. */ + +/**/ +static HashNode +removenameddirnode(HashTable ht, char *nam) +{ + HashNode hn = removehashnode(ht, nam); + + if(hn) + finddir(NULL); /* clear the finddir cache */ + return hn; +} + +/* Free up the memory used by a named directory hash node. */ + +/**/ +static void +freenameddirnode(HashNode hn) +{ + Nameddir nd = (Nameddir) hn; + + zsfree(nd->nam); + zsfree(nd->dir); + zfree(nd, sizeof(struct nameddir)); +} + +/* Print a named directory */ + +/**/ +static void +printnameddirnode(HashNode hn, int printflags) +{ + Nameddir nd = (Nameddir) hn; + + if (printflags & PRINT_NAMEONLY) { + zputs(nd->nam, stdout); + putchar('\n'); + return; + } + + quotedzputs(nd->nam, stdout); + putchar('='); + quotedzputs(nd->dir, stdout); + putchar('\n'); +} diff --git a/Src/hashtable.h b/Src/hashtable.h new file mode 100644 index 000000000..5b78c9c18 --- /dev/null +++ b/Src/hashtable.h @@ -0,0 +1,62 @@ +/* + * hashtable.h - header file for hash table handling code + * + * 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. + * + */ + +/* Builtin function numbers; used by handler functions that handle more * + * than one builtin. Note that builtins such as compctl, that are not * + * overloaded, don't get a number. */ + +#define BIN_TYPESET 0 +#define BIN_BG 1 +#define BIN_FG 2 +#define BIN_JOBS 3 +#define BIN_WAIT 4 +#define BIN_DISOWN 5 +#define BIN_BREAK 6 +#define BIN_CONTINUE 7 +#define BIN_EXIT 8 +#define BIN_RETURN 9 +#define BIN_CD 10 +#define BIN_POPD 11 +#define BIN_PUSHD 12 +#define BIN_PRINT 13 +#define BIN_EVAL 14 +#define BIN_SCHED 15 +#define BIN_FC 16 +#define BIN_PUSHLINE 17 +#define BIN_LOGOUT 18 +#define BIN_TEST 19 +#define BIN_BRACKET 20 +#define BIN_EXPORT 21 +#define BIN_ECHO 22 +#define BIN_DISABLE 23 +#define BIN_ENABLE 24 + +/* These currently depend on being 0 and 1. */ +#define BIN_SETOPT 0 +#define BIN_UNSETOPT 1 diff --git a/Src/hist.c b/Src/hist.c new file mode 100644 index 000000000..a4c5735c1 --- /dev/null +++ b/Src/hist.c @@ -0,0 +1,1670 @@ +/* + * hist.c - history expansion + * + * 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 "hist.pro" + +/* != 0 means history substitution is turned off */ + +/**/ +int stophist; + +/* this line began with a space, so junk it if HISTIGNORESPACE is on */ + +/**/ +int spaceflag; + +/* if != 0, we are expanding the current line */ + +/**/ +int expanding; + +/* these are used to modify the cursor position during expansion */ + +/**/ +int excs, exlast; + +/* + * Current history event number + * + * Note on curhist: with history inactive, this points to the + * last line actually added to the history list. With history active, + * the line does not get added to the list until hend(), if at all. + * However, curhist is incremented to reflect the current line anyway. + * Thus if the line is not added to the list, curhist must be + * decremented in hend(). + */ + +/**/ +int curhist; + +/* number of history entries */ + +/**/ +int histentct; + +/* array of history entries */ + +/**/ +Histent histentarr; + +/* capacity of history lists */ + +/**/ +int histsiz; + +/* if = 1, we have performed history substitution on the current line * + * if = 2, we have used the 'p' modifier */ + +/**/ +int histdone; + +/* state of the history mechanism */ + +/**/ +int histactive; + +/* Bits of histactive variable */ +#define HA_ACTIVE (1<<0) /* History mechanism is active */ +#define HA_NOSTORE (1<<1) /* Don't store the line when finished */ +#define HA_JUNKED (1<<2) /* Last history line was already junked */ +#define HA_NOINC (1<<3) /* Don't store, curhist not incremented */ + +/* Array of word beginnings and endings in current history line. */ + +/**/ +short *chwords; + +/* Max, actual position in chwords. + * nwords = chwordpos/2 because we record beginning and end of words. + */ + +/**/ +int chwordlen, chwordpos; + +/* the last l for s/l/r/ history substitution */ + +/**/ +char *hsubl; + +/* the last r for s/l/r/ history substitution */ + +/**/ +char *hsubr; + +/* pointer into the history line */ + +/**/ +char *hptr; + +/* the current history line */ + +/**/ +char *chline; + +/* true if the last character returned by hgetc was an escaped bangchar * + * if it is set and NOBANGHIST is unset hwaddc escapes bangchars */ + +/**/ +int qbang; + +/* max size of histline */ + +/**/ +int hlinesz; + +/* default event (usually curhist-1, that is, "!!") */ + +static int defev; + +/* add a character to the current history word */ + +/**/ +void +hwaddc(int c) +{ + /* Only if history line exists and lexing has not finished. */ + if (chline && !(errflag || lexstop)) { + /* Quote un-expanded bangs in the history line. */ + if (c == bangchar && stophist < 2 && qbang) + /* If qbang is not set, we do not escape this bangchar as it's * + * not mecessary (e.g. it's a bang in !=, or it is followed * + * by a space). Roughly speaking, qbang is zero only if the * + * history interpreter has already digested this bang and * + * found that it is not necessary to escape it. */ + hwaddc('\\'); + *hptr++ = c; + + /* Resize history line if necessary */ + if (hptr - chline >= hlinesz) { + int oldsiz = hlinesz; + + chline = realloc(chline, hlinesz = oldsiz + 16); + hptr = chline + oldsiz; + } + } +} + +/* This function adds a character to the zle input line. It is used when * + * zsh expands history (see doexpandhist() in zle_tricky.c). It also * + * calculates the new cursor position after the expansion. It is called * + * from hgetc() and from gettok() in lex.c for characters in comments. */ + +/**/ +void +addtoline(int c) +{ + if (! expanding || lexstop) + return; + if (qbang && c == bangchar && stophist < 2) { + exlast--; + spaceinline(1); + line[cs++] = '\\'; + } + if (excs > cs) { + excs += 1 + inbufct - exlast; + if (excs < cs) + /* this case could be handled better but it is * + * so rare that it does not worth it */ + excs = cs; + } + exlast = inbufct; + spaceinline(1); + line[cs++] = itok(c) ? ztokens[c - Pound] : c; +} + +/**/ +int +hgetc(void) +{ + int c = ingetc(); + + qbang = 0; + if (!stophist && !(inbufflags & INP_ALIAS)) { + /* If necessary, expand history characters. */ + c = histsubchar(c); + if (c < 0) { + /* bad expansion */ + errflag = lexstop = 1; + return ' '; + } + } + if ((inbufflags & INP_HIST) && !stophist) { + /* the current character c came from a history expansion * + * (inbufflags && INP_HIST) and history is not disabled * + * (e.g. we are not inside single quotes). In that case, \! * + * should be treated as ! (since this \! came from a previous * + * history line where \ was used to escape the bang). So if * + * c == '\\' we fetch one more character to see if it's a bang, * + * and if it is not, we unget it and reset c back to '\\' */ + qbang = 0; + if (c == '\\' && !(qbang = (c = ingetc()) == bangchar)) + safeinungetc(c), c = '\\'; + } else if (stophist || (inbufflags & INP_ALIAS)) + /* If the result is a bangchar which came from history or alias * + * expansion, we treat it as an escaped bangchar, unless history * + * is disabled. If stophist == 1 it only means that history is * + * temporarily disabled by a !" which won't appear in in the * + * history, so we still have an escaped bang. stophist > 1 if * + * history is disabled with NOBANGHIST or by someone else (e.g. * + * when the lexer scans single quoted text). */ + qbang = c == bangchar && (stophist < 2); + hwaddc(c); + addtoline(c); + + return c; +} + +/**/ +static void +safeinungetc(int c) +{ + if (lexstop) + lexstop = 0; + else + inungetc(c); +} + +/**/ +void +herrflush(void) +{ + while (!lexstop && inbufct) + hwaddc(ingetc()); +} + +/* extract :s/foo/bar/ delimiters and arguments */ + +/**/ +static int +getsubsargs(char *subline) +{ + int del; + char *ptr1, *ptr2; + + del = ingetc(); + ptr1 = hdynread2(del); + if (!ptr1) + return 1; + ptr2 = hdynread2(del); + if (strlen(ptr1)) { + zsfree(hsubl); + hsubl = ptr1; + } + zsfree(hsubr); + hsubr = ptr2; + if (hsubl && !strstr(subline, hsubl)) { + herrflush(); + zerr("substitution failed", NULL, 0); + return 1; + } + return 0; +} + +/* Get the maximum no. of words for a history entry. */ + +/**/ +static int +getargc(Histent ehist) +{ + return ehist->nwords ? ehist->nwords-1 : 0; +} + +/* Perform history substitution, returning the next character afterwards. */ + +/**/ +static int +histsubchar(int c) +{ + int ev, farg, evset = -1, larg, argc, cflag = 0, bflag = 0; + static int mev = -1, marg = -1; + char buf[256], *ptr; + char *sline; + Histent ehist; + + /* look, no goto's */ + if (isfirstch && c == hatchar) { + /* Line begins ^foo^bar */ + isfirstch = 0; + inungetc(hatchar); + if (!(ehist = gethist(defev)) + || !(sline = getargs(ehist, 0, getargc(ehist))) + || getsubsargs(sline) || !hsubl) + return -1; + subst(&sline, hsubl, hsubr, 0); + } else { + /* Line doesn't begin ^foo^bar */ + if (c != ' ') + isfirstch = 0; + if (c == '\\') { + int g = ingetc(); + + if (g != bangchar) + safeinungetc(g); + else { + qbang = 1; + return bangchar; + } + } + if (c != bangchar) + return c; + *hptr = '\0'; + if ((c = ingetc()) == '{') { + bflag = cflag = 1; + c = ingetc(); + } + if (c == '\"') { + stophist = 1; + return ingetc(); + } + if ((!cflag && inblank(c)) || c == '=' || c == '(' || lexstop) { + safeinungetc(c); + return bangchar; + } + cflag = 0; + ptr = buf; + + /* get event number */ + + if (c == '?') { + for (;;) { + c = ingetc(); + if (c == '?' || c == '\n' || lexstop) + break; + else + *ptr++ = c; + } + if (c != '\n' && !lexstop) + c = ingetc(); + *ptr = '\0'; + mev = ev = hconsearch(hsubl = ztrdup(buf), &marg); + evset = 0; + if (ev == -1) { + herrflush(); + zerr("no such event: %s", buf, 0); + return -1; + } + } else { + int t0; + + for (;;) { + if (inblank(c) || c == ';' || c == ':' || c == '^' || + c == '$' || c == '*' || c == '%' || c == '}' || + c == '\'' || c == '"' || c == '`' || lexstop) + break; + if (ptr != buf) { + if (c == '-') + break; + if ((idigit(buf[0]) || buf[0] == '-') && !idigit(c)) + break; + } + *ptr++ = c; + if (c == '#' || c == bangchar) { + c = ingetc(); + break; + } + c = ingetc(); + } + *ptr = 0; + if (!*buf) + if (c != '%') { + if (isset(CSHJUNKIEHISTORY)) + ev = curhist - 1; + else + ev = defev; + if (c == ':' && evset == -1) + evset = 0; + else + evset = 1; + } else { + if (marg != -1) + ev = mev; + else + ev = defev; + evset = 0; + } else if ((t0 = atoi(buf))) { + ev = (t0 < 0) ? curhist + t0 : t0; + evset = 1; + } else if ((unsigned)*buf == bangchar) { + ev = curhist - 1; + evset = 1; + } else if (*buf == '#') { + ev = curhist; + evset = 1; + } else if ((ev = hcomsearch(buf)) == -1) { + herrflush(); + zerr("event not found: %s", buf, 0); + return -1; + } else + evset = 1; + } + + /* get the event */ + + if (!(ehist = gethist(defev = ev))) + return -1; + + /* extract the relevant arguments */ + + argc = getargc(ehist); + if (c == ':') { + cflag = 1; + c = ingetc(); + if (c == '%' && marg != -1) { + if (!evset) { + ehist = gethist(defev = mev); + argc = getargc(ehist); + } else { + herrflush(); + zerr("Ambiguous history reference", NULL, 0); + return -1; + } + + } + } + if (c == '*') { + farg = 1; + larg = argc; + cflag = 0; + } else { + inungetc(c); + larg = farg = getargspec(argc, marg, evset); + if (larg == -2) + return -1; + if (farg != -1) + cflag = 0; + c = ingetc(); + if (c == '*') { + cflag = 0; + larg = argc; + } else if (c == '-') { + cflag = 0; + larg = getargspec(argc, marg, evset); + if (larg == -2) + return -1; + if (larg == -1) + larg = argc - 1; + } else + inungetc(c); + } + if (farg == -1) + farg = 0; + if (larg == -1) + larg = argc; + if (!(sline = getargs(ehist, farg, larg))) + return -1; + } + + /* do the modifiers */ + + for (;;) { + c = (cflag) ? ':' : ingetc(); + cflag = 0; + if (c == ':') { + int gbal = 0; + + if ((c = ingetc()) == 'g') { + gbal = 1; + c = ingetc(); + } + switch (c) { + case 'p': + histdone = HISTFLAG_DONE | HISTFLAG_NOEXEC; + break; + case 'h': + if (!remtpath(&sline)) { + herrflush(); + zerr("modifier failed: h", NULL, 0); + return -1; + } + break; + case 'e': + if (!rembutext(&sline)) { + herrflush(); + zerr("modifier failed: e", NULL, 0); + return -1; + } + break; + case 'r': + if (!remtext(&sline)) { + herrflush(); + zerr("modifier failed: r", NULL, 0); + return -1; + } + break; + case 't': + if (!remlpaths(&sline)) { + herrflush(); + zerr("modifier failed: t", NULL, 0); + return -1; + } + break; + case 's': + if (getsubsargs(sline)) + return -1; /* fall through */ + case '&': + if (hsubl && hsubr) + subst(&sline, hsubl, hsubr, gbal); + else { + herrflush(); + zerr("no previous substitution", NULL, 0); + return -1; + } + break; + case 'q': + quote(&sline); + break; + case 'x': + quotebreak(&sline); + break; + case 'l': + downcase(&sline); + break; + case 'u': + upcase(&sline); + break; + default: + herrflush(); + zerr("illegal modifier: %c", NULL, c); + return -1; + } + } else { + if (c != '}' || !bflag) + inungetc(c); + if (c != '}' && bflag) { + zerr("'}' expected", NULL, 0); + return -1; + } + break; + } + } + + /* + * Push the expanded value onto the input stack, + * marking this as a history word for purposes of the alias stack. + */ + + lexstop = 0; + /* this function is called only called from hgetc and only if * + * !(inbufflags & INP_ALIAS). History expansion should never be * + * done with INP_ALIAS (to prevent recursive history expansion and * + * histoty expansion of aliases). Escapes are not removed here. * + * This is now handled in hgetc. */ + inpush(sline, INP_HIST, NULL); /* sline from heap, don't free */ + histdone |= HISTFLAG_DONE; + if (isset(HISTVERIFY)) + histdone |= HISTFLAG_NOEXEC | HISTFLAG_RECALL; + + /* Don't try and re-expand line. */ + return ingetc(); +} + +/* unget a char and remove it from chline. It can only be used * + * to unget a character returned by hgetc. */ + +/**/ +void +hungetc(int c) +{ + int doit = 1; + + while (!lexstop) { + if (hptr[-1] != (char) c && stophist < 4 && + hptr > chline + 1 && hptr[-1] == '\n' && hptr[-2] == '\\') + hungetc('\n'), hungetc('\\'); + + if (expanding) { + cs--; + ll--; + exlast++; + } + DPUTS(hptr <= chline, "BUG: hungetc attempted at buffer start"); + hptr--; + DPUTS(*hptr != (char) c, "BUG: wrong character in hungetc() "); + qbang = (c == bangchar && stophist < 2 && + hptr > chline && hptr[-1] == '\\'); + if (doit) + inungetc(c); + if (!qbang) + return; + doit = !stophist && ((inbufflags & INP_HIST) || + !(inbufflags & INP_ALIAS)); + c = '\\'; + } +} + +/* begin reading a string */ + +/**/ +void +strinbeg(void) +{ + strin++; + hbegin(); + lexinit(); +} + +/* done reading a string */ + +/**/ +void +strinend(void) +{ + hend(); + DPUTS(!strin, "BUG: strinend() called without strinbeg()"); + strin--; + isfirstch = 1; + histdone = 0; +} + +/* initialize the history mechanism */ + +/**/ +void +hbegin(void) +{ + Histent curhistent; + + isfirstln = isfirstch = 1; + errflag = histdone = spaceflag = 0; + stophist = (!interact || unset(BANGHIST) || unset(SHINSTDIN)) << 1; + chline = hptr = zcalloc(hlinesz = 16); + chwords = zalloc((chwordlen = 16)*sizeof(short)); + chwordpos = 0; + + if (histactive & HA_JUNKED) + curhist--; + curhistent = gethistent(curhist); + if (!curhistent->ftim) + curhistent->ftim = time(NULL); + histactive = HA_ACTIVE; + if (interact && isset(SHINSTDIN) && !strin) { + attachtty(mypgrp); + defev = curhist; + curhist++; + } else + histactive |= HA_NOINC; +} + +/* compare current line with history entry using only text in words */ + +/**/ +static int +histcmp(Histent he) +{ + int kword, lword; + int nwords = chwordpos/2; + + /* If the history entry came from a file, the words were not + * divided by the lexer so we have to resort to strcmp. + */ + if (he->flags & HIST_READ) + return strcmp(he->text, chline); + + if (nwords != he->nwords) + return 1; + + for (kword = 0; kword < 2*nwords; kword += 2) + if ((lword = chwords[kword+1]-chwords[kword]) + != he->words[kword+1]-he->words[kword] || + memcmp(he->text+he->words[kword], chline+chwords[kword], lword)) + return 1; + + return 0; +} + +/**/ +void +histreduceblanks(void) +{ + int i, len, pos, needblank; + + for (i = 0, len = 0; i < chwordpos; i += 2) { + len += chwords[i+1] - chwords[i] + + (i > 0 && chwords[i] > chwords[i-1]); + } + if (chline[len] == '\0') + return; + + for (i = 0, pos = 0; i < chwordpos; i += 2) { + len = chwords[i+1] - chwords[i]; + needblank = (i < chwordpos-2 && chwords[i+2] > chwords[i+1]); + if (pos != chwords[i]) { + memcpy(chline + pos, chline + chwords[i], len + needblank); + chwords[i] = pos; + chwords[i+1] = chwords[i] + len; + } + pos += len + needblank; + } + chline[pos] = '\0'; +} + +/* say we're done using the history mechanism */ + +/**/ +int +hend(void) +{ + int flag, save = 1; + + DPUTS(!chline, "BUG: chline is NULL in hend()"); + if (histactive & (HA_NOSTORE|HA_NOINC)) { + zfree(chline, hlinesz); + zfree(chwords, chwordlen*sizeof(short)); + chline = NULL; + if (!(histactive & HA_NOINC)) + curhist--; + histactive = 0; + return 1; + } + flag = histdone; + histdone = 0; + if (hptr < chline + 1) + save = 0; + else { + *hptr = '\0'; + if (hptr[-1] == '\n') + if (chline[1]) { + *--hptr = '\0'; + } else + save = 0; + if (!*chline || !strcmp(chline, "\n") || + (isset(HISTIGNORESPACE) && spaceflag)) + save = 0; + } + if (flag & (HISTFLAG_DONE | HISTFLAG_RECALL)) { + char *ptr; + + ptr = ztrdup(chline); + if ((flag & (HISTFLAG_DONE | HISTFLAG_RECALL)) == HISTFLAG_DONE) { + zputs(ptr, shout); + fputc('\n', shout); + fflush(shout); + } + if (flag & HISTFLAG_RECALL) { + PERMALLOC { + pushnode(bufstack, ptr); + } LASTALLOC; + save = 0; + } else + zsfree(ptr); + } + if (save) { + Histent he; + int keepflags = 0; + +#ifdef DEBUG + /* debugging only */ + if (chwordpos%2) { + hwend(); + DPUTS(1, "BUG: uncompleted line in history"); + } +#endif + /* get rid of pesky \n which we've already nulled out */ + if (!chline[chwords[chwordpos-2]]) + chwordpos -= 2; + /* strip superfluous blanks, if desired */ + if (isset(HISTREDUCEBLANKS)) + histreduceblanks(); + + if (isset(HISTIGNOREDUPS) && (he = gethistent(curhist - 1)) + && he->text && !histcmp(he)) { + /* This history entry compares the same as the previous. + * In case minor changes were made, we overwrite the + * previous one with the current one. This also gets + * the timestamp right. However, keep the old flags. + */ + keepflags = he->flags; + curhist--; + } + + he = gethistent(curhist); + zsfree(he->text); + he->text = ztrdup(chline); + if (he->nwords) + zfree(he->words, he->nwords*2*sizeof(short)); + he->stim = time(NULL); + he->ftim = 0L; + he->flags = keepflags; + + if ((he->nwords = chwordpos/2)) { + he->words = (short *)zalloc(chwordpos * sizeof(short)); + memcpy(he->words, chwords, chwordpos * sizeof(short)); + } + } else + curhist--; + zfree(chline, hlinesz); + zfree(chwords, chwordlen*sizeof(short)); + chline = NULL; + histactive = 0; + return !(flag & HISTFLAG_NOEXEC || errflag); +} + +/* remove the current line from the history List */ + +/**/ +void +remhist(void) +{ + if (!(histactive & HA_ACTIVE)) { + if (!(histactive & HA_JUNKED)) { + /* make sure this doesn't show up when we do firsthist() */ + Histent he = gethistent(curhist); + zsfree(he->text); + he->text = NULL; + histactive |= HA_JUNKED; + /* curhist-- is delayed until the next hbegin() */ + } + } else + histactive |= HA_NOSTORE; +} + +/* Gives current expansion word if not last word before chwordpos. */ + +/**/ +int hwgetword = -1; + +/* begin a word */ + +/**/ +void +hwbegin(int offset) +{ + if (chwordpos%2) + chwordpos--; /* make sure we're on a word start, not end */ + /* If we're expanding an alias, we should overwrite the expansion + * in the history. + */ + if ((inbufflags & INP_ALIAS) && !(inbufflags & INP_HIST)) + hwgetword = chwordpos; + else + hwgetword = -1; + chwords[chwordpos++] = hptr - chline + offset; +} + +/* add a word to the history List */ + +/**/ +void +hwend(void) +{ + if (chwordpos%2 && chline) { + /* end of word reached and we've already begun a word */ + if (hptr > chline + chwords[chwordpos-1]) { + chwords[chwordpos++] = hptr - chline; + if (chwordpos >= chwordlen) { + chwords = (short *) realloc(chwords, + (chwordlen += 16)*sizeof(short)); + } + if (hwgetword > -1) { + /* We want to reuse the current word position */ + chwordpos = hwgetword; + /* Start from where previous word ended, if possible */ + hptr = chline + chwords[chwordpos ? chwordpos - 1 : 0]; + } + } else { + /* scrub that last word, it doesn't exist */ + chwordpos--; + } + } +} + +/* Go back to immediately after the last word, skipping space. */ + +/**/ +void +histbackword(void) +{ + if (!(chwordpos%2) && chwordpos) + hptr = chline + chwords[chwordpos-1]; +} + +/* Get the start and end point of the current history word */ + +/**/ +static void +hwget(char **startptr) +{ + int pos = hwgetword > -1 ? hwgetword : chwordpos - 2; + +#ifdef DEBUG + /* debugging only */ + if (hwgetword == -1 && !chwordpos) { + /* no words available */ + DPUTS(1, "BUG: hwget() called with no words"); + *startptr = ""; + return; + } + else if (hwgetword == -1 && chwordpos%2) { + DPUTS(1, "BUG: hwget() called in middle of word"); + *startptr = ""; + return; + } +#endif + + *startptr = chline + chwords[pos]; + chline[chwords[++pos]] = '\0'; +} + +/* Replace the current history word with rep, if different */ + +/**/ +void +hwrep(char *rep) +{ + char *start; + hwget(&start); + + if (!strcmp(rep, start)) + return; + + hptr = start; + chwordpos = (hwgetword > -1) ? hwgetword : chwordpos - 2; + hwbegin(0); + qbang = 1; + while (*rep) + hwaddc(*rep++); + hwend(); +} + +/* Get the entire current line, deleting it in the history. */ + +/**/ +char * +hgetline(void) +{ + /* Currently only used by pushlineoredit(). + * It's necessary to prevent that from getting too pally with + * the history code. + */ + char *ret; + + if (!chline || hptr == chline) + return NULL; + *hptr = '\0'; + ret = dupstring(chline); + + /* reset line */ + hptr = chline; + chwordpos = 0; + hwgetword = -1; + + return ret; +} + +/* get an argument specification */ + +/**/ +static int +getargspec(int argc, int marg, int evset) +{ + int c, ret = -1; + + if ((c = ingetc()) == '0') + return 0; + if (idigit(c)) { + ret = 0; + while (idigit(c)) { + ret = ret * 10 + c - '0'; + c = ingetc(); + } + inungetc(c); + } else if (c == '^') + ret = 1; + else if (c == '$') + ret = argc; + else if (c == '%') { + if (evset) { + herrflush(); + zerr("Ambiguous history reference", NULL, 0); + return -2; + } + if (marg == -1) { + herrflush(); + zerr("%% with no previous word matched", NULL, 0); + return -2; + } + ret = marg; + } else + inungetc(c); + return ret; +} + +/* do ?foo? search */ + +/**/ +static int +hconsearch(char *str, int *marg) +{ + int t0, t1 = 0; + char *s; + Histent he; + + for (t0 = curhist - 1; (he = quietgethist(t0)); t0--) + if ((s = strstr(he->text, str))) { + int pos = s - he->text; + while (t1 < he->nwords && he->words[2*t1] <= pos) + t1++; + *marg = t1 - 1; + return t0; + } + return -1; +} + +/* do !foo search */ + +/**/ +int +hcomsearch(char *str) +{ + int t0; + char *hs; + + for (t0 = curhist - 1; (hs = quietgetevent(t0)); t0--) + if (!strncmp(hs, str, strlen(str))) + return t0; + return -1; +} + +/* various utilities for : modifiers */ + +/**/ +int +remtpath(char **junkptr) +{ + char *str = *junkptr, *remcut; + + if ((remcut = strrchr(str, '/'))) { + if (str != remcut) + *remcut = '\0'; + else + str[1] = '\0'; + return 1; + } + return 0; +} + +/**/ +int +remtext(char **junkptr) +{ + char *str = *junkptr, *remcut; + + if ((remcut = strrchr(str, '.')) && remcut != str) { + *remcut = '\0'; + return 1; + } + return 0; +} + +/**/ +int +rembutext(char **junkptr) +{ + char *str = *junkptr, *remcut; + + if ((remcut = strrchr(str, '.')) && remcut != str) { + *junkptr = dupstring(remcut + 1); /* .xx or xx? */ + return 1; + } + return 0; +} + +/**/ +int +remlpaths(char **junkptr) +{ + char *str = *junkptr, *remcut; + + if ((remcut = strrchr(str, '/'))) { + *remcut = '\0'; + *junkptr = dupstring(remcut + 1); + return 1; + } + return 0; +} + +/**/ +int +makeuppercase(char **junkptr) +{ + char *str = *junkptr; + + for (; *str; str++) + *str = tuupper(*str); + return 1; +} + +/**/ +int +makelowercase(char **junkptr) +{ + char *str = *junkptr; + + for (; *str; str++) + *str = tulower(*str); + return 1; +} + +/**/ +int +makecapitals(char **junkptr) +{ + char *str = *junkptr; + + for (; *str;) { + for (; *str && !ialnum(*str); str++); + if (*str) + *str = tuupper(*str), str++; + for (; *str && ialnum(*str); str++) + *str = tulower(*str); + } + return 1; +} + +/**/ +void +subst(char **strptr, char *in, char *out, int gbal) +{ + char *str = *strptr, *instr = *strptr, *substcut, *sptr, *oldstr; + int off, inlen, outlen; + + if (!*in) + in = str, gbal = 0; + if (!(substcut = (char *)strstr(str, in))) + return; + inlen = strlen(in); + sptr = convamps(out, in, inlen); + outlen = strlen(sptr); + + do { + *substcut = '\0'; + off = substcut - *strptr + outlen; + substcut += inlen; + *strptr = tricat(oldstr = *strptr, sptr, substcut); + if (oldstr != instr) + zsfree(oldstr); + str = (char *)*strptr + off; + } while (gbal && (substcut = (char *)strstr(str, in))); +} + +/**/ +static char * +convamps(char *out, char *in, int inlen) +{ + char *ptr, *ret, *pp; + int slen, sdup = 0; + + for (ptr = out, slen = 0; *ptr; ptr++, slen++) + if (*ptr == '\\') + ptr++, sdup = 1; + else if (*ptr == '&') + slen += inlen - 1, sdup = 1; + if (!sdup) + return out; + ret = pp = (char *)alloc(slen + 1); + for (ptr = out; *ptr; ptr++) + if (*ptr == '\\') + *pp++ = *++ptr; + else if (*ptr == '&') { + strcpy(pp, in); + pp += inlen; + } else + *pp++ = *ptr; + *pp = '\0'; + return ret; +} + +/**/ +struct histent * +quietgethist(int ev) +{ + static struct histent storehist; + + if (ev < firsthist() || ev > curhist) + return NULL; + if (ev == curhist && (histactive & HA_ACTIVE)) { + /* The current history line has not been stored. Build it up + * from other variables. + */ + storehist.text = chline; + storehist.nwords = chwordpos/2; + storehist.words = chwords; + + return &storehist; + } else + return gethistent(ev); +} + +/**/ +char * +quietgetevent(int ev) +{ + Histent ent = quietgethist(ev); + + return ent ? ent->text : NULL; +} + +/**/ +static Histent +gethist(int ev) +{ + Histent ret; + + ret = quietgethist(ev); + if (!ret) { + herrflush(); + zerr("no such event: %d", NULL, ev); + } + return ret; +} + +/**/ +static char * +getargs(Histent elist, int arg1, int arg2) +{ + short *words = elist->words; + int pos1, nwords = elist->nwords; + + if (arg2 < arg1 || arg1 >= nwords || arg2 >= nwords) { + /* remember, argN is indexed from 0, nwords is total no. of words */ + herrflush(); + zerr("no such word in event", NULL, 0); + return NULL; + } + + pos1 = words[2*arg1]; + return dupstrpfx(elist->text + pos1, words[2*arg2+1] - pos1); +} + +/**/ +void +upcase(char **x) +{ + char *pp = *(char **)x; + + for (; *pp; pp++) + *pp = tuupper(*pp); +} + +/**/ +void +downcase(char **x) +{ + char *pp = *(char **)x; + + for (; *pp; pp++) + *pp = tulower(*pp); +} + +/**/ +int +quote(char **tr) +{ + char *ptr, *rptr, **str = (char **)tr; + int len = 3; + int inquotes = 0; + + for (ptr = *str; *ptr; ptr++, len++) + if (*ptr == '\'') { + len += 3; + if (!inquotes) + inquotes = 1; + else + inquotes = 0; + } else if (inblank(*ptr) && !inquotes && ptr[-1] != '\\') + len += 2; + ptr = *str; + *str = rptr = (char *)alloc(len); + *rptr++ = '\''; + for (; *ptr; ptr++) + if (*ptr == '\'') { + if (!inquotes) + inquotes = 1; + else + inquotes = 0; + *rptr++ = '\''; + *rptr++ = '\\'; + *rptr++ = '\''; + *rptr++ = '\''; + } else if (inblank(*ptr) && !inquotes && ptr[-1] != '\\') { + *rptr++ = '\''; + *rptr++ = *ptr; + *rptr++ = '\''; + } else + *rptr++ = *ptr; + *rptr++ = '\''; + *rptr++ = 0; + str[1] = NULL; + return 0; +} + +/**/ +static int +quotebreak(char **tr) +{ + char *ptr, *rptr, **str = (char **)tr; + int len = 3; + + for (ptr = *str; *ptr; ptr++, len++) + if (*ptr == '\'') + len += 3; + else if (inblank(*ptr)) + len += 2; + ptr = *str; + *str = rptr = (char *)alloc(len); + *rptr++ = '\''; + for (; *ptr;) + if (*ptr == '\'') { + *rptr++ = '\''; + *rptr++ = '\\'; + *rptr++ = '\''; + *rptr++ = '\''; + ptr++; + } else if (inblank(*ptr)) { + *rptr++ = '\''; + *rptr++ = *ptr++; + *rptr++ = '\''; + } else + *rptr++ = *ptr++; + *rptr++ = '\''; + *rptr++ = '\0'; + return 0; +} + +#if 0 +/* read an arbitrary amount of data into a buffer until stop is found */ + +/**/ +char * +hdynread(int stop) +{ + int bsiz = 256, ct = 0, c; + char *buf = (char *)zalloc(bsiz), *ptr; + + ptr = buf; + while ((c = ingetc()) != stop && c != '\n' && !lexstop) { + if (c == '\\') + c = ingetc(); + *ptr++ = c; + if (++ct == bsiz) { + buf = realloc(buf, bsiz *= 2); + ptr = buf + ct; + } + } + *ptr = 0; + if (c == '\n') { + inungetc('\n'); + zerr("delimiter expected", NULL, 0); + zfree(buf, bsiz); + return NULL; + } + return buf; +} +#endif + +/**/ +static char * +hdynread2(int stop) +{ + int bsiz = 256, ct = 0, c; + char *buf = (char *)zalloc(bsiz), *ptr; + + ptr = buf; + while ((c = ingetc()) != stop && c != '\n' && !lexstop) { + if (c == '\n') { + inungetc(c); + break; + } + if (c == '\\') + c = ingetc(); + *ptr++ = c; + if (++ct == bsiz) { + buf = realloc(buf, bsiz *= 2); + ptr = buf + ct; + } + } + *ptr = 0; + if (c == '\n') + inungetc('\n'); + return buf; +} + +/**/ +void +inithist(void) +{ + histentct = histsiz; + histentarr = (Histent) zcalloc(histentct * sizeof *histentarr); +} + +/**/ +void +resizehistents(void) +{ + int newentct, t0, t1, firstlex; + Histent newarr; + + newentct = histsiz; + newarr = (Histent) zcalloc(newentct * sizeof *newarr); + firstlex = curhist - histsiz + 1; + t0 = firsthist(); + if (t0 < curhist - newentct) + t0 = curhist - newentct; + t1 = t0 % newentct; + for (; t0 <= curhist; t0++) { + newarr[t1] = *gethistent(t0); + if (t0 < firstlex) { + zsfree(newarr[t1].text); + newarr[t1].text = NULL; + } + t1++; + if (t1 == newentct) + t1 = 0; + } + free(histentarr); + histentarr = newarr; + histentct = newentct; +} + +/**/ +void +readhistfile(char *s, int err) +{ + char *buf; + FILE *in; + Histent ent; + time_t tim = time(NULL); + short *wordlist; + int nwordpos, nwordlist, bufsiz; + + if (!s) + return; + if ((in = fopen(unmeta(s), "r"))) { + nwordlist = 16; + wordlist = (short *)zalloc(nwordlist*sizeof(short)); + bufsiz = 1024; + buf = zalloc(bufsiz); + + while (fgets(buf, bufsiz, in)) { + int l = strlen(buf); + char *pt, *start; + + while (l) { + while (buf[l - 1] != '\n') { + buf = zrealloc(buf, 2 * bufsiz); + bufsiz = 2 * bufsiz; + if (!fgets(buf + l, bufsiz - l, in)) { + l++; + break; + } + l = strlen(buf); + } + buf[l - 1] = '\0'; + if (l > 1 && buf[l - 2] == '\\') { + buf[l - 2] = '\n'; + fgets(buf + l - 1, bufsiz - (l - 1), in); + l = strlen(buf); + } else + break; + } + + ent = gethistent(++curhist); + pt = buf; + if (*pt == ':') { + pt++; + ent->stim = zstrtol(pt, NULL, 0); + for (; *pt != ':' && *pt; pt++); + if (*pt) { + pt++; + ent->ftim = zstrtol(pt, NULL, 0); + for (; *pt != ';' && *pt; pt++); + if (*pt) + pt++; + } else { + ent->ftim = tim; + } + if (ent->stim == 0) + ent->stim = tim; + if (ent->ftim == 0) + ent->ftim = tim; + } else { + ent->ftim = ent->stim = tim; + } + + zsfree(ent->text); + ent->text = ztrdup(pt); + ent->flags = HIST_OLD|HIST_READ; + if (ent->nwords) + zfree(ent->words, ent->nwords*2*sizeof(short)); + + /* Divide up the words. We don't know how it lexes, + so just look for spaces. + */ + nwordpos = 0; + start = pt; + do { + while (*pt == ' ') + pt++; + if (*pt) { + if (nwordpos >= nwordlist) + wordlist = (short *) realloc(wordlist, + (nwordlist += 16)*sizeof(short)); + wordlist[nwordpos++] = pt - start; + while (*pt && *pt != ' ') + pt++; + wordlist[nwordpos++] = pt - start; + } + } while (*pt); + + ent->nwords = nwordpos/2; + if (ent->nwords) { + ent->words = (short *)zalloc(nwordpos*sizeof(short)); + memcpy(ent->words, wordlist, nwordpos*sizeof(short)); + } else + ent->words = (short *)NULL; + } + fclose(in); + + zfree(wordlist, nwordlist*sizeof(short)); + zfree(buf, bufsiz); + } else if (err) + zerr("can't read history file", s, 0); +} + +/**/ +void +savehistfile(char *s, int err, int app) +{ + char *t; + FILE *out; + int ev; + Histent ent; + int savehist = getiparam("SAVEHIST"); + + if (!s || !interact || savehist <= 0) + return; + ev = curhist - savehist + 1; + if (ev < firsthist()) + ev = firsthist(); + if (app & 1) + out = fdopen(open(unmeta(s), + O_CREAT | O_WRONLY | O_APPEND | O_NOCTTY, 0600), "a"); + else + out = fdopen(open(unmeta(s), + O_CREAT | O_WRONLY | O_TRUNC | O_NOCTTY, 0600), "w"); + if (out) { + for (; ev <= curhist - !!(histactive & HA_ACTIVE); ev++) { + ent = gethistent(ev); + if (app & 2) { + if (ent->flags & HIST_OLD) + continue; + ent->flags |= HIST_OLD; + } + t = ent->text; + if (isset(EXTENDEDHISTORY)) { + fprintf(out, ": %ld:%ld;", + (long)ent->stim, + (long)ent->ftim); + } else if (*t == ':') + fputc('\\', out); + + for (; *t; t++) { + if (*t == '\n') + fputc('\\', out); + fputc(*t, out); + } + fputc('\n', out); + } + fclose(out); + + if (app & 2 && (out = fopen(unmeta(s), "r"))) { + char **store, buf[1024], **ptr; + int i, l, histnum = 0; + + store = (char **)zcalloc((savehist + 1) * sizeof *store); + while (fgets(buf, sizeof(buf), out)) { + char *t; + + if (store[i = histnum % savehist]) + free(store[i]); + store[i] = ztrdup(buf); + l = strlen(buf); + if (l > 1) { + t = store[i] + l; + while ((t[-1] != '\n' || + (t[-1] == '\n' && t[-2] == '\\')) && + fgets(buf, sizeof(buf), out)) { + l += strlen(buf); + store[i] = zrealloc(store[i], l + 1); + t = store[i] + l; + strcat(store[i], buf); + } + } + histnum++; + } + fclose(out); + if ((out = fdopen(open(unmeta(s), + O_WRONLY | O_TRUNC | O_NOCTTY, 0600), "w"))) { + if (histnum < savehist) + for (i = 0; i < histnum; i++) + fprintf(out, "%s", store[i]); + else + for (i = histnum; i < histnum + savehist; i++) + fprintf(out, "%s", store[i % savehist]); + fclose(out); + } + for (ptr = store; *ptr; ptr++) + zsfree(*ptr); + free(store); + } + } else if (err) + zerr("can't write history file %s", s, 0); +} + +/**/ +int +firsthist(void) +{ + int ev; + Histent ent; + + ev = curhist - histentct + 1; + if (ev < 1) + ev = 1; + do { + ent = gethistent(ev); + if (ent->text) + break; + ev++; + } + while (ev < curhist); + return ev; +} + diff --git a/Src/init.c b/Src/init.c new file mode 100644 index 000000000..33496adc6 --- /dev/null +++ b/Src/init.c @@ -0,0 +1,936 @@ +/* + * init.c - main loop and initialization routines + * + * 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 "init.pro" + +#include "zshpaths.h" +#include "zshxmods.h" + +/**/ +int noexitct = 0; + +/* what level of sourcing we are at */ + +/**/ +int sourcelevel; + +/* the shell tty fd */ + +/**/ +int SHTTY; + +/* the FILE attached to the shell tty */ + +/**/ +FILE *shout; + +/* termcap strings */ + +/**/ +char *tcstr[TC_COUNT]; + +/* lengths of each termcap string */ + +/**/ +int tclen[TC_COUNT]; + +/* Values of the li, co and am entries */ + +/**/ +int tclines, tccolumns, hasam; + +#ifdef DEBUG +/* depth of allocation type stack */ + +/**/ +int alloc_stackp; +#endif + +/* keep executing lists until EOF found */ + +/**/ +void +loop(int toplevel, int justonce) +{ + List list; +#ifdef DEBUG + int oasp = toplevel ? 0 : alloc_stackp; +#endif + + pushheap(); + for (;;) { + freeheap(); + errflag = 0; + if (isset(SHINSTDIN)) { + setblock_stdin(); + if (interact) + preprompt(); + } + hbegin(); /* init history mech */ + intr(); /* interrupts on */ + lexinit(); /* initialize lexical state */ + if (!(list = parse_event())) { /* if we couldn't parse a list */ + hend(); + if ((tok == ENDINPUT && !errflag) || + (tok == LEXERR && (!isset(SHINSTDIN) || !toplevel)) || + justonce) + break; + continue; + } + if (hend()) { + int toksav = tok; + List prelist; + + if (toplevel && (prelist = getshfunc("preexec")) != &dummy_list) { + Histent he = gethistent(curhist); + LinkList args; + PERMALLOC { + args = newlinklist(); + addlinknode(args, "preexec"); + if (he && he->text) + addlinknode(args, he->text); + } LASTALLOC; + doshfunc(prelist, args, 0, 1); + freelinklist(args, (FreeFunc) NULL); + errflag = 0; + } + if (stopmsg) /* unset 'you have stopped jobs' flag */ + stopmsg--; + execlist(list, 0, 0); + tok = toksav; + if (toplevel) + noexitct = 0; + } + DPUTS(alloc_stackp != oasp, "BUG: alloc_stackp changed in loop()"); + if (ferror(stderr)) { + zerr("write error", NULL, 0); + clearerr(stderr); + } + if (subsh) /* how'd we get this far in a subshell? */ + exit(lastval); + if (((!interact || sourcelevel) && errflag) || retflag) + break; + if (trapreturn) { + lastval = trapreturn; + trapreturn = 0; + } + if (isset(SINGLECOMMAND) && toplevel) { + if (sigtrapped[SIGEXIT]) + dotrap(SIGEXIT); + exit(lastval); + } + if (justonce) + break; + } + popheap(); +} + +static char *cmd; +static int restricted; + +/**/ +void +parseargs(char **argv) +{ + char **x; + int action, optno; + LinkList paramlist; + int bourne = (emulation == EMULATE_KSH || emulation == EMULATE_SH); + + argzero = *argv++; + SHIN = 0; + + /* There's a bit of trickery with opts[INTERACTIVE] here. It starts * + * at a value of 2 (instead of 1) or 0. If it is explicitly set on * + * the command line, it goes to 1 or 0. If input is coming from * + * somewhere that normally makes the shell non-interactive, we do * + * "opts[INTERACTIVE] &= 1", so that only a *default* on state will * + * be changed. At the end of the function, a value of 2 gets * + * changed to 1. */ + opts[INTERACTIVE] = isatty(0) ? 2 : 0; + opts[SHINSTDIN] = 0; + opts[SINGLECOMMAND] = 0; + + /* loop through command line options (begins with "-" or "+") */ + while (*argv && (**argv == '-' || **argv == '+')) { + action = (**argv == '-'); + if(!argv[0][1]) + *argv = "--"; + while (*++*argv) { + /* The pseudo-option `--' signifies the end of options. * + * `-b' does too, csh-style, unless we're emulating a * + * Bourne style shell. */ + if (**argv == '-' || (!bourne && **argv == 'b')) { + argv++; + goto doneoptions; + } + + if (**argv == 'c') { /* -c command */ + if (!*++argv) { + zerr("string expected after -c", NULL, 0); + exit(1); + } + cmd = *argv++; + opts[INTERACTIVE] &= 1; + opts[SHINSTDIN] = 0; + goto doneoptions; + } else if (**argv == 'o') { + if (!*++*argv) + argv++; + if (!*argv) { + zerr("string expected after -o", NULL, 0); + exit(1); + } + if(!(optno = optlookup(*argv))) + zerr("no such option: %s", *argv, 0); + else if (optno == RESTRICTED) + restricted = action; + else + dosetopt(optno, action, 1); + break; + } else { + if (!(optno = optlookupc(**argv))) { + zerr("bad option: -%c", NULL, **argv); + exit(1); + } else if (optno == RESTRICTED) + restricted = action; + else + dosetopt(optno, action, 1); + } + } + argv++; + } + doneoptions: + paramlist = newlinklist(); + if (*argv) { + if (unset(SHINSTDIN)) { + argzero = *argv; + if (!cmd) + SHIN = movefd(open(unmeta(argzero), O_RDONLY | O_NOCTTY)); + if (SHIN == -1) { + zerr("can't open input file: %s", argzero, 0); + exit(1); + } + opts[INTERACTIVE] &= 1; + argv++; + } + while (*argv) + addlinknode(paramlist, ztrdup(*argv++)); + } else + opts[SHINSTDIN] = 1; + if(isset(SINGLECOMMAND)) + opts[INTERACTIVE] &= 1; + opts[INTERACTIVE] = !!opts[INTERACTIVE]; + pparams = x = (char **) zcalloc((countlinknodes(paramlist) + 1) * sizeof(char *)); + + while ((*x++ = (char *)getlinknode(paramlist))); + free(paramlist); + argzero = ztrdup(argzero); +} + + +/**/ +void +init_io(void) +{ + long ttpgrp; + static char outbuf[BUFSIZ], errbuf[BUFSIZ]; + +#ifdef RSH_BUG_WORKAROUND + int i; +#endif + +/* stdout, stderr fully buffered */ +#ifdef _IOFBF + setvbuf(stdout, outbuf, _IOFBF, BUFSIZ); + setvbuf(stderr, errbuf, _IOFBF, BUFSIZ); +#else + setbuffer(stdout, outbuf, BUFSIZ); + setbuffer(stderr, errbuf, BUFSIZ); +#endif + +/* This works around a bug in some versions of in.rshd. * + * Currently this is not defined by default. */ +#ifdef RSH_BUG_WORKAROUND + if (cmd) { + for (i = 3; i < 10; i++) + close(i); + } +#endif + + if (shout) { + fclose(shout); + shout = 0; + } + if (SHTTY != -1) { + zclose(SHTTY); + SHTTY = -1; + } + + /* Make sure the tty is opened read/write. */ + if (isatty(0)) { + zsfree(ttystrname); + if ((ttystrname = ztrdup(ttyname(0)))) + SHTTY = movefd(open(ttystrname, O_RDWR | O_NOCTTY)); + } + if (SHTTY == -1 && + (SHTTY = movefd(open("/dev/tty", O_RDWR | O_NOCTTY))) != -1) { + zsfree(ttystrname); + ttystrname = ztrdup("/dev/tty"); + } + if (SHTTY == -1) { + zsfree(ttystrname); + ttystrname = ztrdup(""); + } + + /* We will only use zle if shell is interactive, * + * SHTTY != -1, and shout != 0 */ + if (interact && SHTTY != -1) { + init_shout(); + if(!shout) + opts[USEZLE] = 0; + } else + opts[USEZLE] = 0; + +#ifdef JOB_CONTROL + /* If interactive, make the shell the foreground process */ + if (opts[MONITOR] && interact && (SHTTY != -1)) { + attachtty(GETPGRP()); + if ((mypgrp = GETPGRP()) > 0) { + while ((ttpgrp = gettygrp()) != -1 && ttpgrp != mypgrp) { + sleep(1); + mypgrp = GETPGRP(); + if (mypgrp == gettygrp()) + break; + killpg(mypgrp, SIGTTIN); + mypgrp = GETPGRP(); + } + } else + opts[MONITOR] = 0; + } else + opts[MONITOR] = 0; +#else + opts[MONITOR] = 0; +#endif +} + +/**/ +void +init_shout(void) +{ + static char shoutbuf[BUFSIZ]; +#if defined(JOB_CONTROL) && defined(TIOCSETD) && defined(NTTYDISC) + int ldisc = NTTYDISC; + + ioctl(SHTTY, TIOCSETD, (char *)&ldisc); +#endif + + /* Associate terminal file descriptor with a FILE pointer */ + shout = fdopen(SHTTY, "w"); +#ifdef _IOFBF + setvbuf(shout, shoutbuf, _IOFBF, BUFSIZ); +#endif + + gettyinfo(&shttyinfo); /* get tty state */ +#if defined(__sgi) + if (shttyinfo.tio.c_cc[VSWTCH] <= 0) /* hack for irises */ + shttyinfo.tio.c_cc[VSWTCH] = CSWTCH; +#endif +} + +/* names of the termcap strings we want */ + +static char *tccapnams[TC_COUNT] = { + "cl", "le", "LE", "nd", "RI", "up", "UP", "do", + "DO", "dc", "DC", "ic", "IC", "cd", "ce", "al", "dl", "ta", + "md", "so", "us", "me", "se", "ue" +}; + +/* Initialise termcap */ + +/**/ +int +init_term(void) +{ +#ifndef TGETENT_ACCEPTS_NULL + static char termbuf[2048]; /* the termcap buffer */ +#endif + + if (!*term) { + termflags |= TERM_UNKNOWN; + return 0; + } + + /* unset zle if using zsh under emacs */ + if (!strcmp(term, "emacs")) + opts[USEZLE] = 0; + +#ifdef TGETENT_ACCEPTS_NULL + /* If possible, we let tgetent allocate its own termcap buffer */ + if (tgetent(NULL, term) != 1) { +#else + if (tgetent(termbuf, term) != 1) { +#endif + + if (isset(INTERACTIVE)) + zerr("can't find termcap info for %s", term, 0); + errflag = 0; + termflags |= TERM_BAD; + return 0; + } else { + char tbuf[1024], *pp; + int t0; + + termflags &= ~TERM_BAD; + termflags &= ~TERM_UNKNOWN; + for (t0 = 0; t0 != TC_COUNT; t0++) { + pp = tbuf; + zsfree(tcstr[t0]); + /* AIX tgetstr() ignores second argument */ + if (!(pp = tgetstr(tccapnams[t0], &pp))) + tcstr[t0] = NULL, tclen[t0] = 0; + else { + tclen[t0] = strlen(pp); + tcstr[t0] = (char *) zalloc(tclen[t0] + 1); + memcpy(tcstr[t0], pp, tclen[t0] + 1); + } + } + + /* check whether terminal has automargin (wraparound) capability */ + hasam = tgetflag("am"); + + tclines = tgetnum("li"); + tccolumns = tgetnum("co"); + + /* if there's no termcap entry for cursor up, use single line mode: * + * this is flagged by termflags which is examined in zle_refresh.c * + */ + if (tccan(TCUP)) + termflags &= ~TERM_NOUP; + else { + tcstr[TCUP] = NULL; + termflags |= TERM_NOUP; + } + + /* if there's no termcap entry for cursor left, use \b. */ + if (!tccan(TCLEFT)) { + tcstr[TCLEFT] = ztrdup("\b"); + tclen[TCLEFT] = 1; + } + + /* if the termcap entry for down is \n, don't use it. */ + if (tccan(TCDOWN) && tcstr[TCDOWN][0] == '\n') { + tclen[TCDOWN] = 0; + zsfree(tcstr[TCDOWN]); + tcstr[TCDOWN] = NULL; + } + + /* if there's no termcap entry for clear, use ^L. */ + if (!tccan(TCCLEARSCREEN)) { + tcstr[TCCLEARSCREEN] = ztrdup("\14"); + tclen[TCCLEARSCREEN] = 1; + } + } + return 1; +} + +/* Initialize lots of global variables and hash tables */ + +/**/ +void +setupvals(void) +{ +#ifdef HAVE_GETPWUID + struct passwd *pswd; +#endif + struct timezone dummy_tz; + char *ptr; +#ifdef HAVE_GETRLIMIT + int i; +#endif + + noeval = 0; + curhist = 0; + histsiz = DEFAULT_HISTSIZE; + inithist(); + + cmdstack = (unsigned char *) zalloc(256); + cmdsp = 0; + + bangchar = '!'; + hashchar = '#'; + hatchar = '^'; + termflags = TERM_UNKNOWN; + curjob = prevjob = coprocin = coprocout = -1; + gettimeofday(&shtimer, &dummy_tz); /* init $SECONDS */ + srand((unsigned int)(shtimer.tv_sec + shtimer.tv_usec)); /* seed $RANDOM */ + + hostnam = (char *) zalloc(256); + gethostname(hostnam, 256); + + /* Set default path */ + path = (char **) zalloc(sizeof(*path) * 5); + path[0] = ztrdup("/bin"); + path[1] = ztrdup("/usr/bin"); + path[2] = ztrdup("/usr/ucb"); + path[3] = ztrdup("/usr/local/bin"); + path[4] = NULL; + + cdpath = mkarray(NULL); + manpath = mkarray(NULL); + fignore = mkarray(NULL); + fpath = mkarray(NULL); + mailpath = mkarray(NULL); + watch = mkarray(NULL); + psvar = mkarray(NULL); +#ifdef DYNAMIC + module_path = mkarray(ztrdup(MODULE_DIR)); + modules = newlinklist(); +#endif + + /* Set default prompts */ + if(unset(INTERACTIVE)) { + prompt = ztrdup(""); + prompt2 = ztrdup(""); + } else if (emulation == EMULATE_KSH || emulation == EMULATE_SH) { + prompt = ztrdup(privasserted() ? "# " : "$ "); + prompt2 = ztrdup("> "); + } else { + prompt = ztrdup("%m%# "); + prompt2 = ztrdup("%_> "); + } + prompt3 = ztrdup("?# "); + prompt4 = ztrdup("+ "); + sprompt = ztrdup("zsh: correct '%R' to '%r' [nyae]? "); + + ifs = ztrdup(DEFAULT_IFS); + wordchars = ztrdup(DEFAULT_WORDCHARS); + postedit = ztrdup(""); + underscore = ztrdup(""); + + zoptarg = ztrdup(""); + zoptind = 1; + + ppid = (long) getppid(); + mypid = (long) getpid(); + term = ztrdup(""); + + /* The following variable assignments cause zsh to behave more * + * like Bourne and Korn shells when invoked as "sh" or "ksh". * + * NULLCMD=":" and READNULLCMD=":" */ + + if (emulation == EMULATE_KSH || emulation == EMULATE_SH) { + nullcmd = ztrdup(":"); + readnullcmd = ztrdup(":"); + } else { + nullcmd = ztrdup("cat"); + readnullcmd = ztrdup("more"); + } + + /* We cache the uid so we know when to * + * recheck the info for `USERNAME' */ + cached_uid = getuid(); + + /* Get password entry and set info for `HOME' and `USERNAME' */ +#ifdef HAVE_GETPWUID + if ((pswd = getpwuid(cached_uid))) { + home = metafy(pswd->pw_dir, -1, META_DUP); + cached_username = ztrdup(pswd->pw_name); + } else +#endif /* HAVE_GETPWUID */ + { + home = ztrdup("/"); + cached_username = ztrdup(""); + } + + /* Try a cheap test to see if we can * + * initialize `PWD' from `HOME' */ + if (ispwd(home)) + pwd = ztrdup(home); + else if ((ptr = zgetenv("PWD")) && ispwd(ptr)) + pwd = ztrdup(ptr); + else + pwd = metafy(zgetcwd(), -1, META_DUP); + + oldpwd = ztrdup(pwd); /* initialize `OLDPWD' = `PWD' */ + + inittyptab(); /* initialize the ztypes table */ + initlextabs(); /* initialize lexing tables */ + + createreswdtable(); /* create hash table for reserved words */ + createaliastable(); /* create hash table for aliases */ + createcmdnamtable(); /* create hash table for external commands */ + createshfunctable(); /* create hash table for shell functions */ + createbuiltintable(); /* create hash table for builtin commands */ + createnameddirtable(); /* create hash table for named directories */ + createparamtable(); /* create paramater hash table */ + +#ifdef TIOCGWINSZ + adjustwinsize(); +#else + /* Using zero below sets the defaults from termcap */ + setiparam("COLUMNS", 0); + setiparam("LINES", 0); +#endif + +#ifdef HAVE_GETRLIMIT + for (i = 0; i != RLIM_NLIMITS; i++) { + getrlimit(i, current_limits + i); + limits[i] = current_limits[i]; + } +#endif + + breaks = loops = 0; + lastmailcheck = time(NULL); + locallevel = sourcelevel = 0; + trapreturn = 0; + noerrexit = -1; + nohistsave = 1; + dirstack = newlinklist(); + bufstack = newlinklist(); + prepromptfns = newlinklist(); + hsubl = hsubr = NULL; + lastpid = 0; + bshin = SHIN ? fdopen(SHIN, "r") : stdin; + if (isset(SHINSTDIN) && !SHIN && unset(INTERACTIVE)) { +#ifdef _IONBF + setvbuf(stdin, NULL, _IONBF, 0); +#else + setlinebuf(stdin); +#endif + } + + times(&shtms); +} + +/* Initialize signal handling */ + +/**/ +void +init_signals(void) +{ + intr(); + +#ifndef QDEBUG + signal_ignore(SIGQUIT); +#endif + + install_handler(SIGHUP); + install_handler(SIGCHLD); +#ifdef SIGWINCH + install_handler(SIGWINCH); +#endif + if (interact) { + install_handler(SIGALRM); + signal_ignore(SIGTERM); + } + if (jobbing) { + long ttypgrp; + + while ((ttypgrp = gettygrp()) != -1 && ttypgrp != mypgrp) + kill(0, SIGTTIN); + if (ttypgrp == -1) { + opts[MONITOR] = 0; + } else { + signal_ignore(SIGTTOU); + signal_ignore(SIGTSTP); + signal_ignore(SIGTTIN); + attachtty(mypgrp); + } + } + if (islogin) { + signal_setmask(signal_mask(0)); + } else if (interact) { + sigset_t set; + + sigemptyset(&set); + sigaddset(&set, SIGINT); + sigaddset(&set, SIGQUIT); + signal_unblock(set); + } +} + +/* Source the init scripts. If called as "ksh" or "sh" * + * then we source the standard sh/ksh scripts instead of * + * the standard zsh scripts */ + +/**/ +void +run_init_scripts(void) +{ + noerrexit = -1; + + if (emulation == EMULATE_KSH || emulation == EMULATE_SH) { + if (islogin) + source("/etc/profile"); + if (unset(PRIVILEGED)) { + char *s = getsparam("ENV"); + if (islogin) + sourcehome(".profile"); + noerrs = 1; + if (s && !parsestr(s)) { + singsub(&s); + noerrs = 0; + source(s); + } + noerrs = 0; + } else + source("/etc/suid_profile"); + } else { +#ifdef GLOBAL_ZSHENV + source(GLOBAL_ZSHENV); +#endif + if (isset(RCS)) { + if (unset(PRIVILEGED)) + sourcehome(".zshenv"); + if (islogin) { +#ifdef GLOBAL_ZPROFILE + source(GLOBAL_ZPROFILE); +#endif + if (unset(PRIVILEGED)) + sourcehome(".zprofile"); + } + if (interact) { +#ifdef GLOBAL_ZSHRC + source(GLOBAL_ZSHRC); +#endif + if (unset(PRIVILEGED)) + sourcehome(".zshrc"); + } + if (islogin) { +#ifdef GLOBAL_ZLOGIN + source(GLOBAL_ZLOGIN); +#endif + if (unset(PRIVILEGED)) + sourcehome(".zlogin"); + } + } + } + noerrexit = 0; + nohistsave = 0; +} + +/* Miscellaneous initializations that happen after init scripts are run */ + +/**/ +void +init_misc(void) +{ + if (*zsh_name == 'r' || restricted) + dosetopt(RESTRICTED, 1, 0); + if (cmd) { + if (SHIN >= 10) + fclose(bshin); + SHIN = movefd(open("/dev/null", O_RDONLY | O_NOCTTY)); + bshin = fdopen(SHIN, "r"); + execstring(cmd, 0, 1); + stopmsg = 1; + zexit(lastval, 0); + } + + if (interact && isset(RCS)) + readhistfile(getsparam("HISTFILE"), 0); +} + +/* source a file */ + +/**/ +int +source(char *s) +{ + int tempfd, fd, cj, oldlineno; + int oldshst, osubsh, oloops; + FILE *obshin; + char *old_scriptname = scriptname; + + if (!s || (tempfd = movefd(open(unmeta(s), O_RDONLY | O_NOCTTY))) == -1) { + return 1; + } + + /* save the current shell state */ + fd = SHIN; /* store the shell input fd */ + obshin = bshin; /* store file handle for buffered shell input */ + osubsh = subsh; /* store whether we are in a subshell */ + cj = thisjob; /* store our current job number */ + oldlineno = lineno; /* store our current lineno */ + oloops = loops; /* stored the # of nested loops we are in */ + oldshst = opts[SHINSTDIN]; /* store current value of this option */ + + SHIN = tempfd; + bshin = fdopen(SHIN, "r"); + subsh = 0; + lineno = 0; + loops = 0; + dosetopt(SHINSTDIN, 0, 1); + scriptname = s; + + sourcelevel++; + loop(0, 0); /* loop through the file to be sourced */ + sourcelevel--; + fclose(bshin); + fdtable[SHIN] = 0; + + /* restore the current shell state */ + SHIN = fd; /* the shell input fd */ + bshin = obshin; /* file handle for buffered shell input */ + subsh = osubsh; /* whether we are in a subshell */ + thisjob = cj; /* current job number */ + lineno = oldlineno; /* our current lineno */ + loops = oloops; /* the # of nested loops we are in */ + dosetopt(SHINSTDIN, oldshst, 1); /* SHINSTDIN option */ + errflag = 0; + retflag = 0; + scriptname = old_scriptname; + + return 0; +} + +/* Try to source a file in the home directory */ + +/**/ +void +sourcehome(char *s) +{ + char buf[PATH_MAX]; + char *h; + + if (emulation == EMULATE_SH || emulation == EMULATE_KSH || + !(h = getsparam("ZDOTDIR"))) + h = home; + if (strlen(h) + strlen(s) + 1 >= PATH_MAX) { + zerr("path too long: %s", s, 0); + return; + } + sprintf(buf, "%s/%s", h, s); + source(buf); +} + +/**/ +void +init_bltinmods(void) +{ + static struct module mod = { NULL, 0, NULL, NULL }; +#include "bltinmods.list" + mod.nam = NULL; +} + +/* ZLE entry point pointers. They are defined here because the initial * + * values depend on whether ZLE is linked in or not -- if it is, we * + * avoid wasting space with the fallback functions. No other source * + * file needs to know which modules are linked in. */ + +#ifdef LINKED_XMOD_zle + +/**/ +ZleVoidFn trashzleptr; +/**/ +ZleVoidFn gotwordptr; +/**/ +ZleVoidFn refreshptr; +/**/ +ZleVoidIntFn spaceinlineptr; +/**/ +ZleReadFn zlereadptr; + +#else /* !LINKED_XMOD_zle */ + +ZleVoidFn trashzleptr = noop_function; +ZleVoidFn gotwordptr = noop_function; +ZleVoidFn refreshptr = noop_function; +ZleVoidIntFn spaceinlineptr = noop_function_int; +# ifdef UNLINKED_XMOD_zle +ZleReadFn zlereadptr = autoload_zleread; +# else /* !UNLINKED_XMOD_zle */ +ZleReadFn zlereadptr = fallback_zleread; +# endif /* !UNLINKED_XMOD_zle */ + +/**/ +void +noop_function(void) +{ + /* do nothing */ +} + +/**/ +void +noop_function_int(int nothing) +{ + /* do nothing */ +} + +# ifdef UNLINKED_XMOD_zle + +/**/ +static unsigned char * +autoload_zleread(char *lp, char *rp, int ha) +{ + zlereadptr = fallback_zleread; + load_module("zle"); + return zleread(lp, rp, ha); +} + +# endif /* UNLINKED_XMOD_zle */ + +/**/ +unsigned char * +fallback_zleread(char *lp, char *rp, int ha) +{ + char *pptbuf; + int pptlen; + + pptbuf = unmetafy(promptexpand(lp, 0, NULL, NULL), &pptlen); + write(2, (WRITE_ARG_2_T)pptbuf, pptlen); + free(pptbuf); + return (unsigned char *)shingetline(); +} + +#endif /* !LINKED_XMOD_zle */ + +/* compctl entry point pointers. Similar to the ZLE ones. */ + +#ifdef LINKED_XMOD_comp1 + +/**/ +CompctlReadFn compctlreadptr; + +#else /* !LINKED_XMOD_comp1 */ + +CompctlReadFn compctlreadptr = fallback_compctlread; + +/**/ +int +fallback_compctlread(char *name, char **args, char *ops, char *reply) +{ + zwarnnam(name, "option valid only in functions called from completion", + NULL, 0); + return 1; +} + +#endif /* !LINKED_XMOD_comp1 */ diff --git a/Src/input.c b/Src/input.c new file mode 100644 index 000000000..576341a7c --- /dev/null +++ b/Src/input.c @@ -0,0 +1,530 @@ +/* + * input.c - read and store lines of input + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + + +/* + * This file deals with input buffering, supplying characters to the + * history expansion code a character at a time. Input is stored on a + * stack, which allows insertion of strings into the input, possibly with + * flags marking the end of alias expansion, with minimal copying of + * strings. The same stack is used to record the fact that the input + * is a history or alias expansion and to store the alias while it is in use. + * + * Input is taken either from zle, if appropriate, or read directly from + * the input file, or may be supplied by some other part of the shell (such + * as `eval' or $(...) substitution). In the last case, it should be + * supplied by pushing a new level onto the stack, via inpush(input_string, + * flag, alias); if the current input really needs to be altered, use + * inputsetline(input_string, flag). `Flag' can include or's of INP_FREE + * (if the input string is to be freed when used), INP_CONT (if the input + * is to continue onto what's already in the input queue), INP_ALIAS + * (push supplied alias onto stack) or INP_HIST (ditto, but used to + * mark history expansion). `alias' is ignored unless INP_ALIAS or + * INP_HIST is supplied. INP_ALIAS is always set if INP_HIST is. + * + * Note that the input string is itself used as the input buffer: it is not + * copied, nor is it every written back to, so using a constant string + * should work. Consequently, when passing areas of memory from the heap + * it is necessary that that heap last as long as the operation of reading + * the string. After the string is read, the stack should be popped with + * inpop(), which effectively flushes any unread input as well as restoring + * the previous input state. + * + * The internal flag INP_ALCONT shows that the stack element was pushed + * by an alias expansion; it should not be needed elsewhere. + * + * The global variable inalmore is set to indicate aliases should + * continue to be expanded because the last alias expansion ended + * in a space. It is only reset after a complete word was read + * without expanding a new alias, in exalias(). + * + * PWS 1996/12/10 + */ + +#include "zsh.mdh" +#include "input.pro" + +/* the shell input fd */ + +/**/ +int SHIN; + +/* buffered shell input for non-interactive shells */ + +/**/ +FILE *bshin; + +/* != 0 means we are reading input from a string */ + +/**/ +int strin; + +/* total # of characters waiting to be read. */ + +/**/ +int inbufct; + +/* the flags controlling the input routines in input.c: see INP_* in zsh.h */ + +/**/ +int inbufflags; + +static char *inbuf; /* Current input buffer */ +static char *inbufptr; /* Pointer into input buffer */ +static char *inbufpush; /* Character at which to re-push alias */ +static int inbufleft; /* Characters left in current input + stack element */ + + + /* Input must be stacked since the input queue is used by + * various different parts of the shell. + */ + +struct instacks { + char *buf, *bufptr; + Alias alias; + int bufleft, bufct, flags; +}; +static struct instacks *instack, *instacktop; +/* + * Input stack size. We need to push the stack for aliases, history + * expansion, and reading from internal strings: only if these operations + * are nested do we need more than one extra level. Thus we shouldn't need + * too much space as a rule. Initially, INSTACK_INITIAL is allocated; if + * more is required, an extra INSTACK_EXPAND is added each time. + */ +#define INSTACK_INITIAL 4 +#define INSTACK_EXPAND 4 + +static int instacksz = INSTACK_INITIAL; + +/* Read a line from bshin. Convert tokens and * + * null characters to Meta c^32 character pairs. */ + +/**/ +char * +shingetline(void) +{ + char *line = NULL; + int ll = 0; + int c; + char buf[BUFSIZ]; + char *p; + + p = buf; + for (;;) { + do { + errno = 0; + c = fgetc(bshin); + } while (c < 0 && errno == EINTR); + if (c < 0 || c == '\n') { + if (c == '\n') + *p++ = '\n'; + if (p > buf) { + *p++ = '\0'; + line = zrealloc(line, ll + (p - buf)); + memcpy(line + ll, buf, p - buf); + } + return line; + } + if (imeta(c)) { + *p++ = Meta; + *p++ = c ^ 32; + } else + *p++ = c; + if (p >= buf + BUFSIZ - 1) { + line = zrealloc(line, ll + (p - buf) + 1); + memcpy(line + ll, buf, p - buf); + ll += p - buf; + line[ll] = '\0'; + p = buf; + } + } +} + +/* Get the next character from the input. + * Will call inputline() to get a new line where necessary. + */ + +/**/ +int +ingetc(void) +{ + char lastc; + + if (lexstop) + return ' '; + for (;;) { + if (inbufleft) { + inbufleft--; + inbufct--; + if (itok(lastc = STOUC(*inbufptr++))) + continue; + return lastc; + } + + /* If the next element down the input stack is a continuation of + * this, use it. + */ + if (inbufflags & INP_CONT) { + inpoptop(); + continue; + } + /* + * Otherwise, see if we have reached the end of input + * (due to an error, or to reading from a single string). + */ + if (strin || errflag) { + lexstop = 1; + return ' '; + } + /* As a last resort, get some more input */ + if (inputline()) + return ' '; + } +} + +/* Read a line from the current command stream and store it as input */ + +/**/ +static int +inputline(void) +{ + char *ingetcline, *ingetcpmptl = NULL, *ingetcpmptr = NULL; + + /* If reading code interactively, work out the prompts. */ + if (interact && isset(SHINSTDIN)) + if (!isfirstln) + ingetcpmptl = prompt2; + else { + ingetcpmptl = prompt; + if (rprompt) + ingetcpmptr = rprompt; + } + if (!(interact && isset(SHINSTDIN) && SHTTY != -1 && isset(USEZLE))) { + /* + * If not using zle, read the line straight from the input file. + * Possibly we don't get the whole line at once: in that case, + * we get another chunk with the next call to inputline(). + */ + + if (interact && isset(SHINSTDIN)) { + /* + * We may still be interactive (e.g. running under emacs), + * so output a prompt if necessary. We don't know enough + * about the input device to be able to handle an rprompt, + * though. + */ + char *pptbuf; + int pptlen; + pptbuf = unmetafy(promptexpand(ingetcpmptl, 0, NULL, NULL), &pptlen); + write(2, (WRITE_ARG_2_T)pptbuf, pptlen); + free(pptbuf); + } + ingetcline = shingetline(); + } else + ingetcline = (char *)zleread(ingetcpmptl, ingetcpmptr, 1); + if (!ingetcline) { + return lexstop = 1; + } + if (errflag) { + free(ingetcline); + return lexstop = errflag = 1; + } + /* Look for a space, to see if this shouldn't be put into history */ + if (isfirstln) + spaceflag = *ingetcline == ' '; + if (isset(VERBOSE)) { + /* Output the whole line read so far. */ + zputs(ingetcline, stderr); + fflush(stderr); + } + if (*ingetcline && ingetcline[strlen(ingetcline) - 1] == '\n') { + /* We've now read a complete line. */ + lineno++; + if (interact && isset(SUNKEYBOARDHACK) && isset(SHINSTDIN) && + SHTTY != -1 && *ingetcline && ingetcline[1] && + ingetcline[strlen(ingetcline) - 2] == '`') { + /* Junk an unmatched "`" at the end of the line. */ + int ct; + char *ptr; + + for (ct = 0, ptr = ingetcline; *ptr; ptr++) + if (*ptr == '`') + ct++; + if (ct & 1) { + ptr[-2] = '\n'; + ptr[-1] = '\0'; + } + } + } + isfirstch = 1; + /* Put this into the input channel. */ + inputsetline(ingetcline, INP_FREE); + + return 0; +} + +/* + * Put a string in the input queue: + * inbuf is only freeable if the flags include INP_FREE. + */ + +/**/ +static void +inputsetline(char *str, int flags) +{ + if ((inbufflags & INP_FREE) && inbuf) { + free(inbuf); + } + inbuf = inbufptr = str; + inbufleft = strlen(inbuf); + + /* + * inbufct must reflect the total number of characters left, + * as it used by other parts of the shell, so we need to take account + * of whether the input stack continues, and whether there + * is an extra space to add on at the end. + */ + if (flags & INP_CONT) + inbufct += inbufleft; + else + inbufct = inbufleft; + inbufflags = flags; +} + +/* + * Backup one character of the input. + * The last character can always be backed up, provided we didn't just + * expand an alias or a history reference. + * In fact, the character is ignored and the previous character is used. + * (If that's wrong, the bug is in the calling code. Use the #ifdef DEBUG + * code to check.) + */ + +/**/ +void +inungetc(int c) +{ + if (!lexstop) { + if (inbufptr != inbuf) { +#ifdef DEBUG + /* Just for debugging: enable only if foul play suspected. */ + if (inbufptr[-1] != (char) c) + fprintf(stderr, "Warning: backing up wrong character.\n"); +#endif + /* Just decrement the pointer: if it's not the same + * character being pushed back, we're in trouble anyway. + */ + inbufptr--; + inbufct++; + inbufleft++; + } +#ifdef DEBUG + else if (!(inbufflags & INP_CONT)) { + /* Just for debugging */ + fprintf(stderr, "Attempt to inungetc() at start of input.\n"); + } +#endif + else { + /* + * The character is being backed up from a previous input stack + * layer. However, there was an expansion in the middle, so we + * can't back up where we want to. Instead, we just push it + * onto the input stack as an extra character. + */ + char *cback = (char *)zcalloc(2); + cback[0] = (char) c; + inpush(cback, INP_FREE|INP_CONT, NULL); + } + /* If we are back at the start of a segment, + * we may need to restore an alias popped from the stack. + * Note this may be a dummy (history expansion) entry. + */ + if (inbufptr == inbufpush && inbufflags & INP_ALCONT) { + /* + * Go back up the stack over all entries which were alias + * expansions and were pushed with nothing remaining to read. + */ + do { + if (instacktop->alias) + instacktop->alias->inuse = 1; + instacktop++; + } while ((instacktop->flags & INP_ALCONT) && !instacktop->bufleft); + inbufflags = INP_CONT|INP_ALIAS; + inbufleft = 0; + inbuf = inbufptr = ""; + } + } +} + +/* stuff a whole file into the input queue and print it */ + +/**/ +int +stuff(char *fn) +{ + FILE *in; + char *buf; + int len; + + if (!(in = fopen(unmeta(fn), "r"))) { + zerr("can't open %s", fn, 0); + return 1; + } + fseek(in, 0, 2); + len = ftell(in); + fseek(in, 0, 0); + buf = (char *)zalloc(len + 1); + if (!(fread(buf, len, 1, in))) { + zerr("read error on %s", fn, 0); + fclose(in); + zfree(buf, len + 1); + return 1; + } + fclose(in); + buf[len] = '\0'; + fwrite(buf, len, 1, stderr); + fflush(stderr); + inputsetline(metafy(buf, len, META_REALLOC), INP_FREE); + return 0; +} + +/* flush input queue */ + +/**/ +void +inerrflush(void) +{ + while (!lexstop && inbufct) + ingetc(); +} + +/* Set some new input onto a new element of the input stack */ + +/**/ +void +inpush(char *str, int flags, Alias inalias) +{ + if (!instack) { + /* Initial stack allocation */ + instack = (struct instacks *)zalloc(instacksz*sizeof(struct instacks)); + instacktop = instack; + } + + instacktop->buf = inbuf; + instacktop->bufptr = inbufptr; + instacktop->bufleft = inbufleft; + instacktop->bufct = inbufct; + inbufflags &= ~INP_ALCONT; + if (flags & (INP_ALIAS|INP_HIST)) { + /* + * Text is expansion for history or alias, so continue + * back to old level when done. Also mark stack top + * as alias continuation so as to back up if necessary, + * and mark alias as in use. + */ + flags |= INP_CONT|INP_ALIAS; + instacktop->flags = inbufflags | INP_ALCONT; + if ((instacktop->alias = inalias)) + inalias->inuse = 1; + } else { + /* If we are continuing an alias expansion, record the alias + * expansion in new set of flags (do we need this?) + */ + if (((instacktop->flags = inbufflags) & INP_ALIAS) && + (flags & INP_CONT)) + flags |= INP_ALIAS; + } + + instacktop++; + if (instacktop == instack + instacksz) { + /* Expand the stack */ + instack = (struct instacks *) + realloc(instack, + (instacksz + INSTACK_EXPAND)*sizeof(struct instacks)); + instacktop = instack + instacksz; + instacksz += INSTACK_EXPAND; + } + /* + * We maintain the entry above the highest one with real + * text as a flag to inungetc() that it can stop re-pushing the stack. + */ + instacktop->flags = 0; + + inbufpush = inbuf = NULL; + + inputsetline(str, flags); +} + +/* Remove the top element of the stack */ + +/**/ +static void +inpoptop(void) +{ + if (inbuf && (inbufflags & INP_FREE)) + free(inbuf); + + instacktop--; + + inbuf = instacktop->buf; + inbufptr = inbufpush = instacktop->bufptr; + inbufleft = instacktop->bufleft; + inbufct = instacktop->bufct; + inbufflags = instacktop->flags; + + if (!(inbufflags & INP_ALCONT)) + return; + + if (instacktop->alias) { + char *t = instacktop->alias->text; + /* a real alias: mark it as unused. */ + instacktop->alias->inuse = 0; + if (*t && t[strlen(t) - 1] == ' ') { + inalmore = 1; + histbackword(); + } + } +} + +/* Remove the top element of the stack and all its continuations. */ + +/**/ +void +inpop(void) +{ + int remcont; + + do { + remcont = inbufflags & INP_CONT; + + inpoptop(); + } while (remcont); +} diff --git a/Src/jobs.c b/Src/jobs.c new file mode 100644 index 000000000..331902d9f --- /dev/null +++ b/Src/jobs.c @@ -0,0 +1,1361 @@ +/* + * jobs.c - job control + * + * 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 "jobs.pro" + +/* the process group of the shell */ + +/**/ +pid_t mypgrp; + +/* the job we are working on */ + +/**/ +int thisjob; + +/* the current job (+) */ + +/**/ +int curjob; + +/* the previous job (-) */ + +/**/ +int prevjob; + +/* the job table */ + +/**/ +struct job jobtab[MAXJOB]; + +/* shell timings */ + +/**/ +struct tms shtms; + +/* 1 if ttyctl -f has been executed */ + +/**/ +int ttyfrozen; + +/* empty job structure for quick clearing of jobtab entries */ + +static struct job zero; /* static variables are initialized to zero */ + +static struct timeval dtimeval, now; + +/* Diff two timevals for elapsed-time computations */ + +/**/ +static struct timeval * +dtime(struct timeval *dt, struct timeval *t1, struct timeval *t2) +{ + dt->tv_sec = t2->tv_sec - t1->tv_sec; + dt->tv_usec = t2->tv_usec - t1->tv_usec; + if (dt->tv_usec < 0) { + dt->tv_usec += 1000000.0; + dt->tv_sec -= 1.0; + } + return dt; +} + +/* change job table entry from stopped to running */ + +/**/ +void +makerunning(Job jn) +{ + Process pn; + + jn->stat &= ~STAT_STOPPED; + for (pn = jn->procs; pn; pn = pn->next) + if (WIFSTOPPED(pn->status) && + (!(jn->stat & STAT_SUPERJOB) || pn->next)) + pn->status = SP_RUNNING; + + if (jn->stat & STAT_SUPERJOB) + makerunning(jobtab + jn->other); +} + +/* Find process and job associated with pid. * + * Return 1 if search was successful, else return 0. */ + +/**/ +int +findproc(pid_t pid, Job *jptr, Process *pptr) +{ + Process pn; + int i; + + for (i = 1; i < MAXJOB; i++) + for (pn = jobtab[i].procs; pn; pn = pn->next) + if (pn->pid == pid) { + *pptr = pn; + *jptr = jobtab + i; + return 1; + } + + return 0; +} + +/* Update status of process that we have just WAIT'ed for */ + +/**/ +void +update_process(Process pn, int status) +{ + struct timezone dummy_tz; + long childs, childu; + + childs = shtms.tms_cstime; + childu = shtms.tms_cutime; + times(&shtms); /* get time-accounting info */ + + pn->status = status; /* save the status returned by WAIT */ + pn->ti.st = shtms.tms_cstime - childs; /* compute process system space time */ + pn->ti.ut = shtms.tms_cutime - childu; /* compute process user space time */ + + gettimeofday(&pn->endtime, &dummy_tz); /* record time process exited */ +} + +/* Update status of job, possibly printing it */ + +/**/ +void +update_job(Job jn) +{ + Process pn; + int job; + int val = 0, status = 0; + int somestopped = 0, inforeground = 0; + + for (pn = jn->procs; pn; pn = pn->next) { + if (pn->status == SP_RUNNING) /* some processes in this job are running */ + return; /* so no need to update job table entry */ + if (WIFSTOPPED(pn->status)) /* some processes are stopped */ + somestopped = 1; /* so job is not done, but entry needs updating */ + if (!pn->next) /* last job in pipeline determines exit status */ + val = (WIFSIGNALED(pn->status)) ? 0200 | WTERMSIG(pn->status) : + WEXITSTATUS(pn->status); + if (pn->pid == jn->gleader) /* if this process is process group leader */ + status = pn->status; + } + + job = jn - jobtab; /* compute job number */ + + if (somestopped) { + if (jn->stty_in_env && !jn->ty) { + jn->ty = (struct ttyinfo *) zalloc(sizeof(struct ttyinfo)); + gettyinfo(jn->ty); + } + if (jn->stat & STAT_STOPPED) + return; + } else { /* job is done, so remember return value */ + lastval2 = val; + /* If last process was run in the current shell, keep old status + * and let it handle its own traps + */ + if (job == thisjob && !(jn->stat & STAT_CURSH)) { + lastval = val; + inforeground = 1; + } + } + + if (shout && !ttyfrozen && !jn->stty_in_env && !zleactive && + job == thisjob && !somestopped && !(jn->stat & STAT_NOSTTY)) + gettyinfo(&shttyinfo); + + if (isset(MONITOR)) { + pid_t pgrp = gettygrp(); /* get process group of tty */ + + /* is this job in the foreground of an interactive shell? */ + if (mypgrp != pgrp && inforeground && + (jn->gleader == pgrp || (pgrp > 1 && kill(-pgrp, 0) == -1))) { + attachtty(mypgrp); + adjustwinsize(); /* check window size and adjust if necessary */ + } + } + + if (somestopped && jn->stat & STAT_SUPERJOB) + return; + jn->stat |= (somestopped) ? STAT_CHANGED | STAT_STOPPED : + STAT_CHANGED | STAT_DONE; + if ((jn->stat & (STAT_DONE | STAT_STOPPED)) == STAT_STOPPED) { + prevjob = curjob; + curjob = job; + } + if ((isset(NOTIFY) || job == thisjob) && (jn->stat & STAT_LOCKED)) { + printjob(jn, !!isset(LONGLISTJOBS), 0); + if (zleactive) + refresh(); + } + if (sigtrapped[SIGCHLD] && job != thisjob) + dotrap(SIGCHLD); + + /* When MONITOR is set, the foreground process runs in a different * + * process group from the shell, so the shell will not receive * + * terminal signals, therefore we we pretend that the shell got * + * the signal too. */ + if (inforeground && isset(MONITOR) && WIFSIGNALED(status)) { + int sig = WTERMSIG(status); + + if (sig == SIGINT || sig == SIGQUIT) { + if (sigtrapped[sig]) { + dotrap(sig); + /* We keep the errflag as set or not by dotrap. + * This is to fulfil the promise to carry on + * with the jobs if trap returns zero. + * Setting breaks = loops ensures a consistent return + * status if inside a loop. Maybe the code in loops + * should be changed. + */ + if (errflag) + breaks = loops; + } else { + breaks = loops; + errflag = 1; + } + } + } +} + +/* set the previous job to something reasonable */ + +/**/ +static void +setprevjob(void) +{ + int i; + + for (i = MAXJOB - 1; i; i--) + if ((jobtab[i].stat & STAT_INUSE) && (jobtab[i].stat & STAT_STOPPED) && + i != curjob && i != thisjob) { + prevjob = i; + return; + } + + for (i = MAXJOB - 1; i; i--) + if ((jobtab[i].stat & STAT_INUSE) && i != curjob && i != thisjob) { + prevjob = i; + return; + } + + prevjob = -1; +} + +static long clktck = 0; + +/**/ +static void +set_clktck(void) +{ +#ifdef _SC_CLK_TCK + if (!clktck) + /* fetch clock ticks per second from * + * sysconf only the first time */ + clktck = sysconf(_SC_CLK_TCK); +#else +# ifdef __NeXT__ + /* NeXTStep 3.3 defines CLK_TCK wrongly */ + clktck = 60; +# else +# ifdef CLK_TCK + clktck = CLK_TCK; +# else +# ifdef HZ + clktck = HZ; +# else + clktck = 60; +# endif +# endif +# endif +#endif +} + +/**/ +static void +printhhmmss(double secs) +{ + int mins = (int) secs / 60; + int hours = mins / 60; + + secs -= 60 * mins; + mins -= 60 * hours; + if (hours) + fprintf(stderr, "%d:%02d:%05.2f", hours, mins, secs); + else if (mins) + fprintf(stderr, "%d:%05.2f", mins, secs); + else + fprintf(stderr, "%.3f", secs); +} + +/**/ +static void +printtime(struct timeval *real, struct timeinfo *ti, char *desc) +{ + char *s; + double elapsed_time, user_time, system_time; + int percent; + + if (!desc) + desc = ""; + + set_clktck(); + /* go ahead and compute these, since almost every TIMEFMT will have them */ + elapsed_time = real->tv_sec + real->tv_usec / 1000000.0; + user_time = ti->ut / (double) clktck; + system_time = ti->st / (double) clktck; + percent = 100.0 * (ti->ut + ti->st) + / (clktck * real->tv_sec + clktck * real->tv_usec / 1000000.0); + + if (!(s = getsparam("TIMEFMT"))) + s = DEFAULT_TIMEFMT; + + for (; *s; s++) + if (*s == '%') + switch (*++s) { + case 'E': + fprintf(stderr, "%4.2fs", elapsed_time); + break; + case 'U': + fprintf(stderr, "%4.2fs", user_time); + break; + case 'S': + fprintf(stderr, "%4.2fs", system_time); + break; + case '*': + switch (*++s) { + case 'E': + printhhmmss(elapsed_time); + break; + case 'U': + printhhmmss(user_time); + break; + case 'S': + printhhmmss(system_time); + break; + default: + fprintf(stderr, "%%*"); + s--; + break; + } + break; + case 'P': + fprintf(stderr, "%d%%", percent); + break; + case 'J': + fprintf(stderr, "%s", desc); + break; + case '%': + putc('%', stderr); + break; + case '\0': + s--; + break; + default: + fprintf(stderr, "%%%c", *s); + break; + } else + putc(*s, stderr); + putc('\n', stderr); + fflush(stderr); +} + +/**/ +static void +dumptime(Job jn) +{ + Process pn; + + if (!jn->procs) + return; + for (pn = jn->procs; pn; pn = pn->next) + printtime(dtime(&dtimeval, &pn->bgtime, &pn->endtime), &pn->ti, pn->text); +} + +/* Check whether shell should report the amount of time consumed * + * by job. This will be the case if we have preceded the command * + * with the keyword time, or if REPORTTIME is non-negative and the * + * amount of time consumed by the job is greater than REPORTTIME */ + +/**/ +static int +should_report_time(Job j) +{ + Value v; + char *s = "REPORTTIME"; + int reporttime; + + /* if the time keyword was used */ + if (j->stat & STAT_TIMED) + return 1; + + if (!(v = getvalue(&s, 0)) || (reporttime = getintvalue(v)) < 0) + return 0; + + /* can this ever happen? */ + if (!j->procs) + return 0; + + set_clktck(); + return ((j->procs->ti.ut + j->procs->ti.st) / clktck >= reporttime); +} + +/* !(lng & 3) means jobs * + * (lng & 1) means jobs -l * + * (lng & 2) means jobs -p + * (lng & 4) means jobs -d + * + * synch = 0 means asynchronous + * synch = 1 means synchronous + * synch = 2 means called synchronously from jobs +*/ + +/**/ +void +printjob(Job jn, int lng, int synch) +{ + Process pn; + int job = jn - jobtab, len = 9, sig, sflag = 0, llen; + int conted = 0, lineleng = columns, skip = 0, doputnl = 0; + FILE *fout = (synch == 2) ? stdout : shout; + + if (jn->stat & STAT_NOPRINT) + return; + + if (lng < 0) { + conted = 1; + lng = 0; + } + +/* find length of longest signame, check to see */ +/* if we really need to print this job */ + + for (pn = jn->procs; pn; pn = pn->next) { + if (jn->stat & STAT_SUPERJOB && + jn->procs->status == SP_RUNNING && !pn->next) + pn->status = SP_RUNNING; + if (pn->status != SP_RUNNING) + if (WIFSIGNALED(pn->status)) { + sig = WTERMSIG(pn->status); + llen = strlen(sigmsg[sig]); + if (WCOREDUMP(pn->status)) + llen += 14; + if (llen > len) + len = llen; + if (sig != SIGINT && sig != SIGPIPE) + sflag = 1; + if (job == thisjob && sig == SIGINT) + doputnl = 1; + } else if (WIFSTOPPED(pn->status)) { + sig = WSTOPSIG(pn->status); + if ((int)strlen(sigmsg[sig]) > len) + len = strlen(sigmsg[sig]); + if (job == thisjob && sig == SIGTSTP) + doputnl = 1; + } else if (isset(PRINTEXITVALUE) && isset(SHINSTDIN) && + WEXITSTATUS(pn->status)) + sflag = 1; + } + +/* print if necessary */ + + if (interact && jobbing && ((jn->stat & STAT_STOPPED) || sflag || + job != thisjob)) { + int len2, fline = 1; + Process qn; + + if (!synch) + trashzle(); + if (doputnl && !synch) + putc('\n', fout); + for (pn = jn->procs; pn;) { + len2 = ((job == thisjob) ? 5 : 10) + len; /* 2 spaces */ + if (lng & 3) + qn = pn->next; + else + for (qn = pn->next; qn; qn = qn->next) { + if (qn->status != pn->status) + break; + if ((int)strlen(qn->text) + len2 + ((qn->next) ? 3 : 0) > lineleng) + break; + len2 += strlen(qn->text) + 2; + } + if (job != thisjob) + if (fline) + fprintf(fout, "[%ld] %c ", + (long)(jn - jobtab), + (job == curjob) ? '+' + : (job == prevjob) ? '-' : ' '); + else + fprintf(fout, (job > 9) ? " " : " "); + else + fprintf(fout, "zsh: "); + if (lng & 1) + fprintf(fout, "%ld ", (long) pn->pid); + else if (lng & 2) { + pid_t x = jn->gleader; + + fprintf(fout, "%ld ", (long) x); + do + skip++; + while ((x /= 10)); + skip++; + lng &= ~3; + } else + fprintf(fout, "%*s", skip, ""); + if (pn->status == SP_RUNNING) + if (!conted) + fprintf(fout, "running%*s", len - 7 + 2, ""); + else + fprintf(fout, "continued%*s", len - 9 + 2, ""); + else if (WIFEXITED(pn->status)) + if (WEXITSTATUS(pn->status)) + fprintf(fout, "exit %-4d%*s", WEXITSTATUS(pn->status), + len - 9 + 2, ""); + else + fprintf(fout, "done%*s", len - 4 + 2, ""); + else if (WIFSTOPPED(pn->status)) + fprintf(fout, "%-*s", len + 2, sigmsg[WSTOPSIG(pn->status)]); + else if (WCOREDUMP(pn->status)) + fprintf(fout, "%s (core dumped)%*s", + sigmsg[WTERMSIG(pn->status)], + (int)(len - 14 + 2 - strlen(sigmsg[WTERMSIG(pn->status)])), ""); + else + fprintf(fout, "%-*s", len + 2, sigmsg[WTERMSIG(pn->status)]); + for (; pn != qn; pn = pn->next) + fprintf(fout, (pn->next) ? "%s | " : "%s", pn->text); + putc('\n', fout); + fline = 0; + } + fflush(fout); + } else if (doputnl && interact && !synch) { + putc('\n', fout); + fflush(fout); + } + +/* print "(pwd now: foo)" messages: with (lng & 4) we are printing + * the directory where the job is running, otherwise the current directory + */ + + if ((lng & 4) || (interact && job == thisjob && strcmp(jn->pwd, pwd))) { + fprintf(shout, "(pwd %s: ", (lng & 4) ? "" : "now"); + fprintdir((lng & 4) ? jn->pwd : pwd, shout); + fprintf(shout, ")\n"); + fflush(shout); + } +/* delete job if done */ + + if (jn->stat & STAT_DONE) { + if (should_report_time(jn)) + dumptime(jn); + deletejob(jn); + if (job == curjob) { + curjob = prevjob; + prevjob = job; + } + if (job == prevjob) + setprevjob(); + } else + jn->stat &= ~STAT_CHANGED; +} + +/**/ +void +deletefilelist(LinkList file_list) +{ + char *s; + if (file_list) { + while ((s = (char *)getlinknode(file_list))) { + unlink(s); + zsfree(s); + } + zfree(file_list, sizeof(struct linklist)); + } +} + +/**/ +void +deletejob(Job jn) +{ + struct process *pn, *nx; + + pn = jn->procs; + jn->procs = NULL; + for (; pn; pn = nx) { + nx = pn->next; + zfree(pn, sizeof(struct process)); + } + zsfree(jn->pwd); + + deletefilelist(jn->filelist); + + if (jn->ty) + zfree(jn->ty, sizeof(struct ttyinfo)); + + *jn = zero; +} + +/* add a process to the current job */ + +/**/ +void +addproc(pid_t pid, char *text) +{ + Process pn; + struct timezone dummy_tz; + + pn = (Process) zcalloc(sizeof *pn); + pn->pid = pid; + if (text) + strcpy(pn->text, text); + else + *pn->text = '\0'; + gettimeofday(&pn->bgtime, &dummy_tz); + pn->status = SP_RUNNING; + pn->next = NULL; + + /* if this is the first process we are adding to * + * the job, then it's the group leader. */ + if (!jobtab[thisjob].gleader) + jobtab[thisjob].gleader = pid; + + /* attach this process to end of process list of current job */ + if (jobtab[thisjob].procs) { + Process n; + + for (n = jobtab[thisjob].procs; n->next; n = n->next); + pn->next = NULL; + n->next = pn; + } else { + /* first process for this job */ + jobtab[thisjob].procs = pn; + } + /* If the first process in the job finished before any others were * + * added, maybe STAT_DONE got set incorrectly. This can happen if * + * a $(...) was waited for and the last existing job in the * + * pipeline was already finished. We need to be very careful that * + * there was no call to printjob() between then and now, else * + * the job will already have been deleted from the table. */ + jobtab[thisjob].stat &= ~STAT_DONE; +} + +/* Check if we have files to delete. We need to check this to see * + * if it's all right to exec a command without forking in the last * + * component of subshells or after the `-c' option. */ + +/**/ +int +havefiles(void) +{ + int i; + + for (i = 1; i < MAXJOB; i++) + if (jobtab[i].stat && jobtab[i].filelist) + return 1; + return 0; + +} + +/* wait for a particular process */ + +/**/ +void +waitforpid(pid_t pid) +{ + int first = 1; + + /* child_block() around this loop in case #ifndef WNOHANG */ + child_block(); /* unblocked in child_suspend() */ + while (!errflag && (kill(pid, 0) >= 0 || errno != ESRCH)) { + if (first) + first = 0; + else + kill(pid, SIGCONT); + + child_suspend(SIGINT); + child_block(); + } + child_unblock(); +} + +/* wait for a job to finish */ + +/**/ +static void +waitjob(int job, int sig) +{ + Job jn = jobtab + job; + + child_block(); /* unblocked during child_suspend() */ + if (jn->procs) { /* if any forks were done */ + jn->stat |= STAT_LOCKED; + if (jn->stat & STAT_CHANGED) + printjob(jn, !!isset(LONGLISTJOBS), 1); + while (!errflag && jn->stat && + !(jn->stat & STAT_DONE) && + !(interact && (jn->stat & STAT_STOPPED))) { + child_suspend(sig); + /* Commenting this out makes ^C-ing a job started by a function + stop the whole function again. But I guess it will stop + something else from working properly, we have to find out + what this might be. --oberon + + errflag = 0; */ + if (jn->stat & STAT_SUPERJOB) { + Job sj = jobtab + jn->other; + if (sj->stat & STAT_DONE) { + struct process *p; + + for (p = sj->procs; p; p = p->next) + if (WIFSIGNALED(p->status)) { + killpg(jn->gleader, WTERMSIG(p->status)); + kill(sj->other, SIGCONT); + kill(sj->other, WTERMSIG(p->status)); + break; + } + if (!p) { + jn->stat &= ~STAT_SUPERJOB; + kill(sj->other, SIGCONT); + deletejob(sj); + } + curjob = jn - jobtab; + } + else if (sj->stat & STAT_STOPPED) { + struct process *p; + + jn->stat |= STAT_STOPPED; + for (p = jn->procs; p; p = p->next) + p->status = sj->procs->status; + curjob = jn - jobtab; + printjob(jn, !!isset(LONGLISTJOBS), 1); + break; + } + } + child_block(); + } + } else + deletejob(jn); + child_unblock(); +} + +/* wait for running job to finish */ + +/**/ +void +waitjobs(void) +{ + waitjob(thisjob, 0); + thisjob = -1; +} + +/* clear job table when entering subshells */ + +/**/ +void +clearjobtab(void) +{ + int i; + + for (i = 1; i < MAXJOB; i++) { + if (jobtab[i].pwd) + zsfree(jobtab[i].pwd); + if (jobtab[i].ty) + zfree(jobtab[i].ty, sizeof(struct ttyinfo)); + } + + memset(jobtab, 0, sizeof(jobtab)); /* zero out table */ +} + +/* Get a free entry in the job table and initialize it. */ + +/**/ +int +initjob(void) +{ + int i; + + for (i = 1; i < MAXJOB; i++) + if (!jobtab[i].stat) { + jobtab[i].stat = STAT_INUSE; + jobtab[i].pwd = ztrdup(pwd); + jobtab[i].gleader = 0; + return i; + } + + zerr("job table full or recursion limit exceeded", NULL, 0); + return -1; +} + +/* print pids for & */ + +/**/ +void +spawnjob(void) +{ + Process pn; + + /* if we are not in a subshell */ + if (!subsh) { + if (curjob == -1 || !(jobtab[curjob].stat & STAT_STOPPED)) { + curjob = thisjob; + setprevjob(); + } else if (prevjob == -1 || !(jobtab[prevjob].stat & STAT_STOPPED)) + prevjob = thisjob; + if (interact && jobbing && jobtab[thisjob].procs) { + fprintf(stderr, "[%d]", thisjob); + for (pn = jobtab[thisjob].procs; pn; pn = pn->next) + fprintf(stderr, " %ld", (long) pn->pid); + fprintf(stderr, "\n"); + fflush(stderr); + } + } + if (!jobtab[thisjob].procs) + deletejob(jobtab + thisjob); + else + jobtab[thisjob].stat |= STAT_LOCKED; + thisjob = -1; +} + +/**/ +void +shelltime(void) +{ + struct timeinfo ti; + struct timezone dummy_tz; + struct tms buf; + + times(&buf); + ti.ut = buf.tms_utime; + ti.st = buf.tms_stime; + gettimeofday(&now, &dummy_tz); + printtime(dtime(&dtimeval, &shtimer, &now), &ti, "shell"); + ti.ut = buf.tms_cutime; + ti.st = buf.tms_cstime; + printtime(dtime(&dtimeval, &shtimer, &now), &ti, "children"); +} + +/* see if jobs need printing */ + +/**/ +void +scanjobs(void) +{ + int i; + + for (i = 1; i < MAXJOB; i++) + if (jobtab[i].stat & STAT_CHANGED) + printjob(jobtab + i, 0, 1); +} + +/**** job control builtins ****/ + +/* This simple function indicates whether or not s may represent * + * a number. It returns true iff s consists purely of digits and * + * minuses. Note that minus may appear more than once, and the empty * + * string will produce a `true' response. */ + +/**/ +static int +isanum(char *s) +{ + while (*s == '-' || idigit(*s)) + s++; + return *s == '\0'; +} + +/* Make sure we have a suitable current and previous job set. */ + +/**/ +static void +setcurjob(void) +{ + if (curjob == thisjob || + (curjob != -1 && !(jobtab[curjob].stat & STAT_INUSE))) { + curjob = prevjob; + setprevjob(); + if (curjob == thisjob || + (curjob != -1 && !((jobtab[curjob].stat & STAT_INUSE) && + curjob != thisjob))) { + curjob = prevjob; + setprevjob(); + } + } +} + +/* Convert a job specifier ("%%", "%1", "%foo", "%?bar?", etc.) * + * to a job number. */ + +/**/ +static int +getjob(char *s, char *prog) +{ + int jobnum, returnval; + + /* if there is no %, treat as a name */ + if (*s != '%') + goto jump; + s++; + /* "%%", "%+" and "%" all represent the current job */ + if (*s == '%' || *s == '+' || !*s) { + if (curjob == -1) { + zwarnnam(prog, "no current job", NULL, 0); + returnval = -1; + goto done; + } + returnval = curjob; + goto done; + } + /* "%-" represents the previous job */ + if (*s == '-') { + if (prevjob == -1) { + zwarnnam(prog, "no previous job", NULL, 0); + returnval = -1; + goto done; + } + returnval = prevjob; + goto done; + } + /* a digit here means we have a job number */ + if (idigit(*s)) { + jobnum = atoi(s); + if (jobnum && jobnum < MAXJOB && jobtab[jobnum].stat && + !(jobtab[jobnum].stat & STAT_SUBJOB) && jobnum != thisjob) { + returnval = jobnum; + goto done; + } + zwarnnam(prog, "%%%s: no such job", s, 0); + returnval = -1; + goto done; + } + /* "%?" introduces a search string */ + if (*s == '?') { + struct process *pn; + + for (jobnum = MAXJOB - 1; jobnum >= 0; jobnum--) + if (jobtab[jobnum].stat && !(jobtab[jobnum].stat & STAT_SUBJOB) && + jobnum != thisjob) + for (pn = jobtab[jobnum].procs; pn; pn = pn->next) + if (strstr(pn->text, s + 1)) { + returnval = jobnum; + goto done; + } + zwarnnam(prog, "job not found: %s", s, 0); + returnval = -1; + goto done; + } + jump: + /* anything else is a job name, specified as a string that begins the + job's command */ + if ((jobnum = findjobnam(s)) != -1) { + returnval = jobnum; + goto done; + } + /* if we get here, it is because none of the above succeeded and went + to done */ + zwarnnam(prog, "job not found: %s", s, 0); + returnval = -1; + done: + return returnval; +} + +/* For jobs -Z (which modifies the shell's name as seen in ps listings). * + * hackzero is the start of the safely writable space, and hackspace is * + * its length, excluding a final NUL terminator that will always be left. */ + +static char *hackzero; +static int hackspace; + +/* Initialise the jobs -Z system. The technique is borrowed from perl: * + * check through the argument and environment space, to see how many of * + * the strings are in contiguous space. This determines the value of * + * hackspace. */ + +/**/ +void +init_hackzero(char **argv, char **envp) +{ + char *p, *q; + + hackzero = *argv; + p = strchr(hackzero, 0); + while(*++argv) { + q = *argv; + if(q != p+1) + goto done; + p = strchr(q, 0); + } + for(; *envp; envp++) { + q = *envp; + if(q != p+1) + goto done; + p = strchr(q, 0); + } + done: + hackspace = p - hackzero; +} + +/* bg, disown, fg, jobs, wait: most of the job control commands are * + * here. They all take the same type of argument. Exception: wait can * + * take a pid or a job specifier, whereas the others only work on jobs. */ + +/**/ +int +bin_fg(char *name, char **argv, char *ops, int func) +{ + int job, lng, firstjob = -1, retval = 0; + + if (ops['Z']) { + int len; + + if(isset(RESTRICTED)) { + zwarnnam(name, "-Z is restricted", NULL, 0); + return 1; + } + if(!argv[0] || argv[1]) { + zwarnnam(name, "-Z requires one argument", NULL, 0); + return 1; + } + unmetafy(*argv, &len); + if(len > hackspace) + len = hackspace; + memcpy(hackzero, *argv, len); + memset(hackzero + len, 0, hackspace - len); + return 0; + } + + lng = (ops['l']) ? 1 : (ops['p']) ? 2 : 0; + if (ops['d']) + lng |= 4; + + if ((func == BIN_FG || func == BIN_BG) && !jobbing) { + /* oops... maybe bg and fg should have been disabled? */ + zwarnnam(name, "no job control in this shell.", NULL, 0); + return 1; + } + + /* If necessary, update job table. */ + if (unset(NOTIFY)) + scanjobs(); + + setcurjob(); + + if (func == BIN_JOBS) + /* If you immediately type "exit" after "jobs", this * + * will prevent zexit from complaining about stopped jobs */ + stopmsg = 2; + if (!*argv) + /* This block handles all of the default cases (no arguments). bg, + fg and disown act on the current job, and jobs and wait act on all the + jobs. */ + if (func == BIN_FG || func == BIN_BG || func == BIN_DISOWN) { + /* W.r.t. the above comment, we'd better have a current job at this + point or else. */ + if (curjob == -1 || (jobtab[curjob].stat & STAT_NOPRINT)) { + zwarnnam(name, "no current job", NULL, 0); + return 1; + } + firstjob = curjob; + } else if (func == BIN_JOBS) { + /* List jobs. */ + for (job = 0; job != MAXJOB; job++) + if (job != thisjob && jobtab[job].stat) { + if ((!ops['r'] && !ops['s']) || + (ops['r'] && ops['s']) || + (ops['r'] && !(jobtab[job].stat & STAT_STOPPED)) || + (ops['s'] && jobtab[job].stat & STAT_STOPPED)) + printjob(job + jobtab, lng, 2); + } + return 0; + } else { /* Must be BIN_WAIT, so wait for all jobs */ + for (job = 0; job != MAXJOB; job++) + if (job != thisjob && jobtab[job].stat) + waitjob(job, SIGINT); + return 0; + } + + /* Defaults have been handled. We now have an argument or two, or three... + In the default case for bg, fg and disown, the argument will be provided by + the above routine. We now loop over the arguments. */ + for (; (firstjob != -1) || *argv; (void)(*argv && argv++)) { + int stopped, ocj = thisjob; + + if (func == BIN_WAIT && isanum(*argv)) { + /* wait can take a pid; the others can't. */ + waitforpid((long)atoi(*argv)); + retval = lastval2; + thisjob = ocj; + continue; + } + /* The only type of argument allowed now is a job spec. Check it. */ + job = (*argv) ? getjob(*argv, name) : firstjob; + firstjob = -1; + if (job == -1) { + retval = 1; + break; + } + if (!(jobtab[job].stat & STAT_INUSE) || + (jobtab[job].stat & STAT_NOPRINT)) { + zwarnnam(name, "no such job: %d", 0, job); + return 1; + } + /* We have a job number. Now decide what to do with it. */ + switch (func) { + case BIN_FG: + case BIN_BG: + case BIN_WAIT: + if (func == BIN_BG) + jobtab[job].stat |= STAT_NOSTTY; + if ((stopped = (jobtab[job].stat & STAT_STOPPED))) + makerunning(jobtab + job); + else if (func == BIN_BG) { + /* Silly to bg a job already running. */ + zwarnnam(name, "job already in background", NULL, 0); + thisjob = ocj; + return 1; + } + /* It's time to shuffle the jobs around! Reset the current job, + and pick a sensible secondary job. */ + if (curjob == job) { + curjob = prevjob; + prevjob = (func == BIN_BG) ? -1 : job; + } + if (prevjob == job || prevjob == -1) + setprevjob(); + if (curjob == -1) { + curjob = prevjob; + setprevjob(); + } + if (func != BIN_WAIT) + /* for bg and fg -- show the job we are operating on */ + printjob(jobtab + job, (stopped) ? -1 : 0, 1); + if (func != BIN_BG) { /* fg or wait */ + if (strcmp(jobtab[job].pwd, pwd)) { + fprintf(shout, "(pwd : "); + fprintdir(jobtab[job].pwd, shout); + fprintf(shout, ")\n"); + } + fflush(shout); + if (func != BIN_WAIT) { /* fg */ + thisjob = job; + attachtty(jobtab[job].gleader); + } + } + if (stopped) { + if (func != BIN_BG && jobtab[job].ty) + settyinfo(jobtab[job].ty); + killjb(jobtab + job, SIGCONT); + } + if (func == BIN_WAIT) + waitjob(job, SIGINT); + if (func != BIN_BG) { + waitjobs(); + retval = lastval2; + } + break; + case BIN_JOBS: + printjob(job + jobtab, lng, 2); + break; + case BIN_DISOWN: + deletejob(jobtab + job); + break; + } + thisjob = ocj; + } + return retval; +} + +/* kill: send a signal to a process. The process(es) may be specified * + * by job specifier (see above) or pid. A signal, defaulting to * + * SIGTERM, may be specified by name or number, preceded by a dash. */ + +/**/ +int +bin_kill(char *nam, char **argv, char *ops, int func) +{ + int sig = SIGTERM; + int returnval = 0; + + /* check for, and interpret, a signal specifier */ + if (*argv && **argv == '-') { + if (idigit((*argv)[1])) + /* signal specified by number */ + sig = atoi(*argv + 1); + else if ((*argv)[1] != '-' || (*argv)[2]) { + char *signame; + + /* with argument "-l" display the list of signal names */ + if ((*argv)[1] == 'l' && (*argv)[2] == '\0') { + if (argv[1]) { + while (*++argv) { + sig = zstrtol(*argv, &signame, 10); + if (signame == *argv) { + for (sig = 1; sig <= SIGCOUNT; sig++) + if (!cstrpcmp(sigs + sig, &signame)) + break; + if (sig > SIGCOUNT) { + zwarnnam(nam, "unknown signal: SIG%s", + signame, 0); + returnval++; + } else + printf("%d\n", sig); + } else { + if (*signame) { + zwarnnam(nam, "unknown signal: SIG%s", + signame, 0); + returnval++; + } else { + if (WIFSIGNALED(sig)) + sig = WTERMSIG(sig); + else if (WIFSTOPPED(sig)) + sig = WSTOPSIG(sig); + if (1 <= sig && sig <= SIGCOUNT) + printf("%s\n", sigs[sig]); + else + printf("%d\n", sig); + } + } + } + return returnval; + } + printf("%s", sigs[1]); + for (sig = 2; sig <= SIGCOUNT; sig++) + printf(" %s", sigs[sig]); + putchar('\n'); + return 0; + } + if ((*argv)[1] == 's' && (*argv)[2] == '\0') + signame = *++argv; + else + signame = *argv + 1; + + /* check for signal matching specified name */ + for (sig = 1; sig <= SIGCOUNT; sig++) + if (!cstrpcmp(sigs + sig, &signame)) + break; + if (*signame == '0' && !signame[1]) + sig = 0; + if (sig > SIGCOUNT) { + zwarnnam(nam, "unknown signal: SIG%s", signame, 0); + zwarnnam(nam, "type kill -l for a List of signals", NULL, 0); + return 1; + } + } + argv++; + } + + setcurjob(); + + /* Remaining arguments specify processes. Loop over them, and send the + signal (number sig) to each process. */ + for (; *argv; argv++) { + if (**argv == '%') { + /* job specifier introduced by '%' */ + int p; + + if ((p = getjob(*argv, nam)) == -1) { + returnval++; + continue; + } + if (killjb(jobtab + p, sig) == -1) { + zwarnnam("kill", "kill %s failed: %e", *argv, errno); + returnval++; + continue; + } + /* automatically update the job table if sending a SIGCONT to a + job, and send the job a SIGCONT if sending it a non-stopping + signal. */ + if (jobtab[p].stat & STAT_STOPPED) { + if (sig == SIGCONT) + jobtab[p].stat &= ~STAT_STOPPED; + if (sig != SIGKILL && sig != SIGCONT && sig != SIGTSTP + && sig != SIGTTOU && sig != SIGTTIN && sig != SIGSTOP) + killjb(jobtab + p, SIGCONT); + } + } else if (!isanum(*argv)) { + zwarnnam("kill", "illegal pid: %s", *argv, 0); + returnval++; + } else if (kill(atoi(*argv), sig) == -1) { + zwarnnam("kill", "kill %s failed: %e", *argv, errno); + returnval++; + } + } + return returnval < 126 ? returnval : 1; +} + +/* Suspend this shell */ + +/**/ +int +bin_suspend(char *name, char **argv, char *ops, int func) +{ + /* won't suspend a login shell, unless forced */ + if (islogin && !ops['f']) { + zwarnnam(name, "can't suspend login shell", NULL, 0); + return 1; + } + if (jobbing) { + /* stop ignoring signals */ + signal_default(SIGTTIN); + signal_default(SIGTSTP); + signal_default(SIGTTOU); + } + /* suspend ourselves with a SIGTSTP */ + kill(0, SIGTSTP); + if (jobbing) { + /* stay suspended */ + while (gettygrp() != mypgrp) { + sleep(1); + if (gettygrp() != mypgrp) + kill(0, SIGTTIN); + } + /* restore signal handling */ + signal_ignore(SIGTTOU); + signal_ignore(SIGTSTP); + signal_ignore(SIGTTIN); + } + return 0; +} + +/* find a job named s */ + +/**/ +int +findjobnam(char *s) +{ + int jobnum; + + for (jobnum = MAXJOB - 1; jobnum >= 0; jobnum--) + if (!(jobtab[jobnum].stat & (STAT_SUBJOB | STAT_NOPRINT)) && + jobtab[jobnum].stat && jobtab[jobnum].procs && jobnum != thisjob && + jobtab[jobnum].procs->text && strpfx(s, jobtab[jobnum].procs->text)) + return jobnum; + return -1; +} diff --git a/Src/lex.c b/Src/lex.c new file mode 100644 index 000000000..6f4f2dd20 --- /dev/null +++ b/Src/lex.c @@ -0,0 +1,1489 @@ +/* + * lex.c - lexical analysis + * + * 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 "lex.pro" + +/* tokens */ + +/**/ +char ztokens[] = "#$^*()$=|{}[]`<>?~`,'\"\\"; + +/* parts of the current token */ + +/**/ +char *yytext, *tokstr; +/**/ +int tok, tokfd; + +/* lexical analyzer error flag */ + +/**/ +int lexstop; + +/* if != 0, this is the first line of the command */ + +/**/ +int isfirstln; + +/* if != 0, this is the first char of the command (not including white space) */ + +/**/ +int isfirstch; + +/* flag that an alias should be expanded after expansion ending in space */ + +/**/ +int inalmore; + +/* don't do spelling correction */ + +/**/ +int nocorrect; + +/* the line buffer */ + +/**/ +unsigned char *line; + +/* cursor position and line length */ + +/**/ +int cs, ll; + +/* inwhat says what exactly we are in * + * (its value is one of the IN_* things). */ + +/**/ +int inwhat; + +/* 1 if x added to complete in a blank between words */ + +/**/ +int addedx; + +/* 1 if aliases should not be expanded */ + +/**/ +int noaliases; + +/* we are parsing a line sent to use by the editor */ + +/**/ +int zleparse; + +/**/ +int wordbeg; + +/**/ +int parbegin; + +/**/ +int parend; + +/* text of puctuation tokens */ + +static char *tokstrings[WHILE + 1] = { + NULL, /* NULLTOK 0 */ + ";", /* SEPER */ + "\\n", /* NEWLIN */ + ";", /* SEMI */ + ";;", /* DSEMI */ + "&", /* AMPER 5 */ + "(", /* INPAR */ + ")", /* OUTPAR */ + "||", /* DBAR */ + "&&", /* DAMPER */ + ")", /* OUTANG 10 */ + ">|", /* OUTANGBANG */ + ">>", /* DOUTANG */ + ">>|", /* DOUTANGBANG */ + "<", /* INANG */ + "<>", /* INOUTANG 15 */ + "<<", /* DINANG */ + "<<-", /* DINANGDASH */ + "<&", /* INANGAMP */ + ">&", /* OUTANGAMP */ + "&>", /* AMPOUTANG 20 */ + "&>|", /* OUTANGAMPBANG */ + ">>&", /* DOUTANGAMP */ + ">>&|", /* DOUTANGAMPBANG */ + "<<<", /* TRINANG */ + "|", /* BAR 25 */ + "|&", /* BARAMP */ + "()", /* INOUTPAR */ + "((", /* DINPAR */ + "))", /* DOUTPAR */ + "&|", /* AMPERBANG 30 */ + ";&", /* SEMIAMP */ +}; + +/* lexical state */ + +static int dbparens; +static int len = 0, bsiz = 256; +static char *bptr; + +struct lexstack { + struct lexstack *next; + + int incmdpos; + int incond; + int incasepat; + int dbparens; + int isfirstln; + int isfirstch; + int histactive; + int histdone; + int spaceflag; + int stophist; + int hlinesz; + char *hline; + char *hptr; + int tok; + int isnewlin; + char *tokstr; + char *yytext; + char *bptr; + int bsiz; + short *chwords; + int chwordlen; + int chwordpos; + int hwgetword; + int lexstop; + struct heredocs *hdocs; + + unsigned char *cstack; + int csp; +}; + +static struct lexstack *lstack = NULL; + +/* save the lexical state */ + +/* is this a hack or what? */ + +/**/ +void +lexsave(void) +{ + struct lexstack *ls; + + ls = (struct lexstack *)malloc(sizeof(struct lexstack)); + + ls->incmdpos = incmdpos; + ls->incond = incond; + ls->incasepat = incasepat; + ls->dbparens = dbparens; + ls->isfirstln = isfirstln; + ls->isfirstch = isfirstch; + ls->histactive = histactive; + ls->histdone = histdone; + ls->spaceflag = spaceflag; + ls->stophist = stophist; + ls->hline = chline; + ls->hptr = hptr; + ls->hlinesz = hlinesz; + ls->cstack = cmdstack; + ls->csp = cmdsp; + cmdstack = (unsigned char *)zalloc(256); + ls->tok = tok; + ls->isnewlin = isnewlin; + ls->tokstr = tokstr; + ls->yytext = yytext; + ls->bptr = bptr; + ls->bsiz = bsiz; + ls->chwords = chwords; + ls->chwordlen = chwordlen; + ls->chwordpos = chwordpos; + ls->hwgetword = hwgetword; + ls->lexstop = lexstop; + ls->hdocs = hdocs; + cmdsp = 0; + inredir = 0; + hdocs = NULL; + + ls->next = lstack; + lstack = ls; +} + +/* restore lexical state */ + +/**/ +void +lexrestore(void) +{ + struct lexstack *ln; + + DPUTS(!lstack, "BUG: lexrestore() without lexsave()"); + incmdpos = lstack->incmdpos; + incond = lstack->incond; + incasepat = lstack->incasepat; + dbparens = lstack->dbparens; + isfirstln = lstack->isfirstln; + isfirstch = lstack->isfirstch; + histactive = lstack->histactive; + histdone = lstack->histdone; + spaceflag = lstack->spaceflag; + stophist = lstack->stophist; + chline = lstack->hline; + hptr = lstack->hptr; + if (cmdstack) + free(cmdstack); + cmdstack = lstack->cstack; + cmdsp = lstack->csp; + tok = lstack->tok; + isnewlin = lstack->isnewlin; + tokstr = lstack->tokstr; + yytext = lstack->yytext; + bptr = lstack->bptr; + bsiz = lstack->bsiz; + chwords = lstack->chwords; + chwordlen = lstack->chwordlen; + chwordpos = lstack->chwordpos; + hwgetword = lstack->hwgetword; + lexstop = lstack->lexstop; + hdocs = lstack->hdocs; + hlinesz = lstack->hlinesz; + errflag = 0; + + ln = lstack->next; + free(lstack); + lstack = ln; +} + +/**/ +void +yylex(void) +{ + if (tok == LEXERR) + return; + do + tok = gettok(); + while (tok != ENDINPUT && exalias()); + if (tok == NEWLIN || tok == ENDINPUT) { + while (hdocs) { + struct heredocs *next = hdocs->next; + + hwbegin(0); + cmdpush(hdocs->rd->type == HEREDOC ? CS_HEREDOC : CS_HEREDOCD); + STOPHIST + hdocs->rd->name = gethere(hdocs->rd->name, hdocs->rd->type); + ALLOWHIST + cmdpop(); + hwend(); + hdocs->rd->type = HERESTR; + zfree(hdocs, sizeof(struct heredocs)); + hdocs = next; + } + } + if (tok != NEWLIN) + isnewlin = 0; + else + isnewlin = (inbufct) ? -1 : 1; + if (tok == SEMI || tok == NEWLIN) + tok = SEPER; +} + +/**/ +void +ctxtlex(void) +{ + static int oldpos; + + yylex(); + switch (tok) { + case SEPER: + case NEWLIN: + case SEMI: + case DSEMI: + case SEMIAMP: + case AMPER: + case AMPERBANG: + case INPAR: + case INBRACE: + case DBAR: + case DAMPER: + case BAR: + case BARAMP: + case INOUTPAR: + case DO: + case THEN: + case ELIF: + case ELSE: + case DOUTBRACK: + incmdpos = 1; + break; + case STRING: + /* case ENVSTRING: */ + case ENVARRAY: + case OUTPAR: + case CASE: + case DINBRACK: + incmdpos = 0; + break; + } + if (tok != DINPAR) + infor = tok == FOR ? 2 : 0; + if (IS_REDIROP(tok) || tok == FOR || tok == FOREACH || tok == SELECT) { + inredir = 1; + oldpos = incmdpos; + incmdpos = 0; + } else if (inredir) { + incmdpos = oldpos; + inredir = 0; + } +} + +#define LX1_BKSLASH 0 +#define LX1_COMMENT 1 +#define LX1_NEWLIN 2 +#define LX1_SEMI 3 +#define LX1_AMPER 5 +#define LX1_BAR 6 +#define LX1_INPAR 7 +#define LX1_OUTPAR 8 +#define LX1_INANG 13 +#define LX1_OUTANG 14 +#define LX1_OTHER 15 + +#define LX2_BREAK 0 +#define LX2_OUTPAR 1 +#define LX2_BAR 2 +#define LX2_STRING 3 +#define LX2_INBRACK 4 +#define LX2_OUTBRACK 5 +#define LX2_TILDE 6 +#define LX2_INPAR 7 +#define LX2_INBRACE 8 +#define LX2_OUTBRACE 9 +#define LX2_OUTANG 10 +#define LX2_INANG 11 +#define LX2_EQUALS 12 +#define LX2_BKSLASH 13 +#define LX2_QUOTE 14 +#define LX2_DQUOTE 15 +#define LX2_BQUOTE 16 +#define LX2_COMMA 17 +#define LX2_OTHER 18 +#define LX2_META 19 + +static unsigned char lexact1[256], lexact2[256], lextok2[256]; + +/**/ +void +initlextabs(void) +{ + int t0; + static char *lx1 = "\\q\n;!&|(){}[]<>"; + static char *lx2 = ";)|$[]~({}><=\\\'\"`,"; + + for (t0 = 0; t0 != 256; t0++) { + lexact1[t0] = LX1_OTHER; + lexact2[t0] = LX2_OTHER; + lextok2[t0] = t0; + } + for (t0 = 0; lx1[t0]; t0++) + lexact1[(int)lx1[t0]] = t0; + for (t0 = 0; lx2[t0]; t0++) + lexact2[(int)lx2[t0]] = t0; + lexact2['&'] = LX2_BREAK; + lexact2[STOUC(Meta)] = LX2_META; + lextok2['*'] = Star; + lextok2['?'] = Quest; + lextok2['{'] = Inbrace; + lextok2['['] = Inbrack; + lextok2['$'] = String; + lextok2['~'] = Tilde; + lextok2['#'] = Pound; + lextok2['^'] = Hat; +} + +/* initialize lexical state */ + +/**/ +void +lexinit(void) +{ + incond = incasepat = nocorrect = + infor = dbparens = lexstop = 0; + incmdpos = 1; + tok = ENDINPUT; +} + +/* add a char to the string buffer */ + +/**/ +void +add(int c) +{ + *bptr++ = c; + if (bsiz == ++len) { + int newbsiz; + + newbsiz = bsiz * 8; + while (newbsiz < inbufct) + newbsiz *= 2; + bptr = len + (tokstr = (char *)hrealloc(tokstr, bsiz, newbsiz)); + bsiz = newbsiz; + } +} + +#define SETPARBEGIN {if (zleparse && !(inbufflags & INP_ALIAS) && cs >= ll+1-inbufct) parbegin = inbufct;} +#define SETPAREND {\ + if (zleparse && !(inbufflags & INP_ALIAS) && parbegin != -1 && parend == -1)\ + if (cs >= ll + 1 - inbufct)\ + parbegin = -1;\ + else\ + parend = inbufct;} + +static int +cmd_or_math(int cs_type) +{ + int oldlen = len; + int c; + + cmdpush(cs_type); + c = dquote_parse(')', 0); + cmdpop(); + *bptr = '\0'; + if (!c) { + c = hgetc(); + if (c == ')') + return 1; + hungetc(c); + lexstop = 0; + c = ')'; + } + hungetc(c); + lexstop = 0; + while (len > oldlen) { + len--; + hungetc(itok(*--bptr) ? ztokens[*bptr - Pound] : *bptr); + } + hungetc('('); + return 0; +} + +static int +cmd_or_math_sub(void) +{ + int c = hgetc(); + + if (c == '(') { + add(Inpar); + add('('); + if (cmd_or_math(CS_MATHSUBST)) { + add(')'); + return 0; + } + bptr -= 2; + len -= 2; + } else { + hungetc(c); + lexstop = 0; + } + return skipcomm(); +} + +/**/ +int +gettok(void) +{ + int c, d; + int peekfd = -1, peek; + + MUSTUSEHEAP("gettok"); + beginning: + tokstr = NULL; + while (iblank(c = hgetc()) && !lexstop); + if (lexstop) + return (errflag) ? LEXERR : ENDINPUT; + isfirstln = 0; + wordbeg = inbufct - (qbang && c == bangchar); + hwbegin(-1-(qbang && c == bangchar)); + /* word includes the last character read and possibly \ before ! */ + if (dbparens) { + len = 0; + bptr = tokstr = (char *)ncalloc(bsiz = 256); + hungetc(c); + cmdpush(CS_MATH); + c = dquote_parse(infor ? ';' : ')', 0); + cmdpop(); + *bptr = '\0'; + if (!c && infor) { + infor--; + return DINPAR; + } + if (c || (c = hgetc()) != ')') { + hungetc(c); + return LEXERR; + } + dbparens = 0; + return DOUTPAR; + } else if (idigit(c)) { /* handle 1< foo */ + d = hgetc(); + if (d == '>' || d == '<') { + peekfd = c - '0'; + c = d; + } else { + hungetc(d); + lexstop = 0; + } + } + + /* chars in initial position in word */ + + if (c == hashchar && + (isset(INTERACTIVECOMMENTS) || + (!zleparse && !expanding && + (!interact || unset(SHINSTDIN) || strin)))) { + /* History is handled here to prevent extra * + * newlines being inserted into the history. */ + + while ((c = ingetc()) != '\n' && !lexstop) { + hwaddc(c); + addtoline(c); + } + + if (errflag) + peek = LEXERR; + else { + hwend(); + hwbegin(0); + hwaddc('\n'); + addtoline('\n'); + peek = NEWLIN; + } + return peek; + } + switch (lexact1[STOUC(c)]) { + case LX1_BKSLASH: + d = hgetc(); + if (d == '\n') + goto beginning; + hungetc(d); + lexstop = 0; + break; + case LX1_NEWLIN: + return NEWLIN; + case LX1_SEMI: + d = hgetc(); + if(d == ';') + return DSEMI; + else if(d == '&') + return SEMIAMP; + hungetc(d); + lexstop = 0; + return SEMI; + case LX1_AMPER: + d = hgetc(); + if (d == '&') + return DAMPER; + else if (d == '!' || d == '|') + return AMPERBANG; + else if (d == '>') { + d = hgetc(); + if (d == '!' || d == '|') + return OUTANGAMPBANG; + else if (d == '>') { + d = hgetc(); + if (d == '!' || d == '|') + return DOUTANGAMPBANG; + hungetc(d); + lexstop = 0; + return DOUTANGAMP; + } + hungetc(d); + lexstop = 0; + return AMPOUTANG; + } + hungetc(d); + lexstop = 0; + return AMPER; + case LX1_BAR: + d = hgetc(); + if (d == '|') + return DBAR; + else if (d == '&') + return BARAMP; + hungetc(d); + lexstop = 0; + return BAR; + case LX1_INPAR: + d = hgetc(); + if (d == '(') { + if (infor) { + dbparens = 1; + return DINPAR; + } + if (incmdpos) { + len = 0; + bptr = tokstr = (char *)ncalloc(bsiz = 256); + return cmd_or_math(CS_MATH) ? DINPAR : INPAR; + } + } else if (d == ')') + return INOUTPAR; + hungetc(d); + lexstop = 0; + if (!(incond == 1 || incmdpos)) + break; + return INPAR; + case LX1_OUTPAR: + return OUTPAR; + case LX1_INANG: + d = hgetc(); + if (!incmdpos && d == '(') { + hungetc(d); + lexstop = 0; + break; + } + if (d == '>') + peek = INOUTANG; + else if (idigit(d) || d == '-') { + int tbs = 256, n = 0, nc; + char *tbuf, *tbp, *ntb; + + tbuf = tbp = (char *)zalloc(tbs); + hungetc(d); + + while ((nc = hgetc()) && !lexstop) { + if (!idigit(nc) && nc != '-') + break; + *tbp++ = (char)nc; + if (++n == tbs) { + ntb = (char *)realloc(tbuf, tbs *= 2); + tbp += ntb - tbuf; + tbuf = ntb; + } + } + if (nc == '>' && !lexstop) { + hungetc(nc); + while (n--) + hungetc(*--tbp); + zfree(tbuf, tbs); + break; + } + if (nc && !lexstop) + hungetc(nc); + lexstop = 0; + while (n--) + hungetc(*--tbp); + zfree(tbuf, tbs); + peek = INANG; + } else if (d == '<') { + int e = hgetc(); + + if (e == '(') { + hungetc(e); + hungetc(d); + peek = INANG; + } else if (e == '<') + peek = TRINANG; + else if (e == '-') + peek = DINANGDASH; + else { + hungetc(e); + lexstop = 0; + peek = DINANG; + } + } else if (d == '&') + peek = INANGAMP; + else { + peek = INANG; + hungetc(d); + lexstop = 0; + } + tokfd = peekfd; + return peek; + case LX1_OUTANG: + d = hgetc(); + if (d == '(') { + hungetc(d); + break; + } else if (d == '&') { + d = hgetc(); + if (d == '!' || d == '|') + peek = OUTANGAMPBANG; + else { + hungetc(d); + lexstop = 0; + peek = OUTANGAMP; + } + } else if (d == '!' || d == '|') + peek = OUTANGBANG; + else if (d == '>') { + d = hgetc(); + if (d == '&') { + d = hgetc(); + if (d == '!' || d == '|') + peek = DOUTANGAMPBANG; + else { + hungetc(d); + lexstop = 0; + peek = DOUTANGAMP; + } + } else if (d == '!' || d == '|') + peek = DOUTANGBANG; + else if (d == '(') { + hungetc(d); + hungetc('>'); + peek = OUTANG; + } else { + hungetc(d); + lexstop = 0; + peek = DOUTANG; + if (isset(HISTALLOWCLOBBER)) + hwaddc('|'); + } + } else { + hungetc(d); + lexstop = 0; + peek = OUTANG; + if (!incond && isset(HISTALLOWCLOBBER)) + hwaddc('|'); + } + tokfd = peekfd; + return peek; + } + + /* we've started a string, now get the * + * rest of it, performing tokenization */ + return gettokstr(c, 0); +} + +/**/ +static int +gettokstr(int c, int sub) +{ + int bct = 0, pct = 0, brct = 0; + int intpos = 1, in_brace_param = 0; + int peek, inquote; +#ifdef DEBUG + int ocmdsp = cmdsp; +#endif + + peek = STRING; + if (!sub) { + len = 0; + bptr = tokstr = (char *)ncalloc(bsiz = 256); + } + for (;;) { + int act; + int e; + + if (inblank(c) && !in_brace_param && !pct) + act = LX2_BREAK; + else { + act = lexact2[STOUC(c)]; + c = lextok2[STOUC(c)]; + } + switch (act) { + case LX2_BREAK: + if (!in_brace_param && !sub) + goto brk; + break; + case LX2_META: + c = hgetc(); +#ifdef DEBUG + if (lexstop) { + fputs("BUG: input terminated by Meta\n", stderr); + fflush(stderr); + goto brk; + } +#endif + add(Meta); + break; + case LX2_OUTPAR: + if ((sub || in_brace_param) && isset(SHGLOB)) + break; + if (!in_brace_param && !pct--) + if (sub) { + pct = 0; + break; + } else + goto brk; + c = Outpar; + break; + case LX2_BAR: + if (!pct && !in_brace_param) + if (sub) + break; + else + goto brk; + if (unset(SHGLOB) || (!sub && !in_brace_param)) + c = Bar; + break; + case LX2_STRING: + e = hgetc(); + if (e == '[') { + cmdpush(CS_MATHSUBST); + add(String); + add(Inbrack); + c = dquote_parse(']', sub); + cmdpop(); + if (c) { + peek = LEXERR; + goto brk; + } + c = Outbrack; + } else if (e == '(') { + add(String); + c = cmd_or_math_sub(); + if (c) { + peek = LEXERR; + goto brk; + } + c = Outpar; + } else { + if (e == '{') { + add(c); + c = Inbrace; + ++bct; + cmdpush(CS_BRACEPAR); + if (!in_brace_param) + in_brace_param = bct; + } else { + hungetc(e); + lexstop = 0; + } + } + break; + case LX2_INBRACK: + if (!in_brace_param) + brct++; + c = Inbrack; + break; + case LX2_OUTBRACK: + if (!in_brace_param) + brct--; + if (brct < 0) + brct = 0; + c = Outbrack; + break; + case LX2_INPAR: + if ((sub || in_brace_param) && isset(SHGLOB)) + break; + if (!in_brace_param) { + if (!sub) { + e = hgetc(); + hungetc(e); + lexstop = 0; + if (e == ')' || + (incmdpos && !brct && peek != ENVSTRING)) + goto brk; + } + pct++; + } + c = Inpar; + break; + case LX2_INBRACE: + if (isset(IGNOREBRACES) || sub) + c = '{'; + else { + if (!len && incmdpos) { + add('{'); + *bptr = '\0'; + return STRING; + } + if (in_brace_param) + cmdpush(CS_BRACE); + bct++; + } + break; + case LX2_OUTBRACE: + if ((isset(IGNOREBRACES) || sub) && !in_brace_param) + break; + if (!bct) + break; + if (in_brace_param) + cmdpop(); + if (bct-- == in_brace_param) + in_brace_param = 0; + c = Outbrace; + break; + case LX2_COMMA: + if (unset(IGNOREBRACES) && !sub && bct > in_brace_param) + c = Comma; + break; + case LX2_OUTANG: + if (!intpos) + if (in_brace_param || sub) + break; + else + goto brk; + e = hgetc(); + if (e != '(') { + hungetc(e); + lexstop = 0; + goto brk; + } + add(Outang); + if (skipcomm()) { + peek = LEXERR; + goto brk; + } + c = Outpar; + break; + case LX2_INANG: + if (isset(SHGLOB) && sub) + break; + e = hgetc(); + if (!(idigit(e) || e == '-' || (e == '(' && intpos))) { + hungetc(e); + lexstop = 0; + if (in_brace_param || sub) + break; + goto brk; + } + c = Inang; + if (e == '(') { + add(c); + if (skipcomm()) { + peek = LEXERR; + goto brk; + } + c = Outpar; + } else { + add(c); + c = e; + while (c != '>' && !lexstop) + add(c), c = hgetc(); + c = Outang; + } + break; + case LX2_EQUALS: + if (intpos) { + e = hgetc(); + if (e != '(') { + hungetc(e); + lexstop = 0; + c = Equals; + } else { + add(Equals); + if (skipcomm()) { + peek = LEXERR; + goto brk; + } + c = Outpar; + } + } else if (!sub && peek != ENVSTRING && + incmdpos && !bct && !brct) { + char *t = tokstr; + if (idigit(*t)) + while (++t < bptr && idigit(*t)); + else { + while (iident(*t) && ++t < bptr); + if (t < bptr) { + *bptr = '\0'; + skipparens(Inbrack, Outbrack, &t); + } + } + if (t == bptr) { + e = hgetc(); + if (e == '(' && incmdpos) { + *bptr = '\0'; + return ENVARRAY; + } + hungetc(e); + lexstop = 0; + peek = ENVSTRING; + intpos = 2; + } else + c = Equals; + } else + c = Equals; + break; + case LX2_BKSLASH: + c = hgetc(); + if (c == '\n') { + c = hgetc(); + if (!lexstop) + continue; + } else + add(Bnull); + if (lexstop) + goto brk; + break; + case LX2_QUOTE: { + int strquote = (len && bptr[-1] == String); + + add(Snull); + cmdpush(CS_QUOTE); + for (;;) { + STOPHIST + while ((c = hgetc()) != '\'' && !lexstop) { + if (strquote && c == '\\') { + add(c); + c = hgetc(); + if (lexstop) + break; + } else if (!sub && isset(CSHJUNKIEQUOTES) && c == '\n') { + if (bptr[-1] == '\\') + bptr--, len--; + else + break; + } + add(c); + } + ALLOWHIST + if (c != '\'') { + zerr("unmatched \'", NULL, 0); + peek = LEXERR; + cmdpop(); + goto brk; + } + e = hgetc(); + if (e != '\'' || unset(RCQUOTES)) + break; + add(c); + } + cmdpop(); + hungetc(e); + lexstop = 0; + c = Snull; + break; + } + case LX2_DQUOTE: + add(Dnull); + cmdpush(CS_DQUOTE); + c = dquote_parse('"', sub); + cmdpop(); + if (c) { + zerr("unmatched \"", NULL, 0); + peek = LEXERR; + goto brk; + } + c = Dnull; + break; + case LX2_BQUOTE: + add(Tick); + cmdpush(CS_BQUOTE); + SETPARBEGIN + inquote = 0; + while ((c = hgetc()) != '`' && !lexstop) + if (c == '\\') { + c = hgetc(); + if (c != '\n') { + add(c == '`' || c == '\\' || c == '$' ? Bnull : '\\'); + add(c); + } + else if (!sub && isset(CSHJUNKIEQUOTES)) + add(c); + } else { + if (!sub && isset(CSHJUNKIEQUOTES) && c == '\n') { + break; + } + add(c); + if (c == '\'') + if ((inquote = !inquote)) + STOPHIST + else + ALLOWHIST + } + if (inquote) + ALLOWHIST + cmdpop(); + if (c != '`') { + zerr("unmatched `", NULL, 0); + peek = LEXERR; + goto brk; + } + c = Tick; + SETPAREND + break; + } + add(c); + c = hgetc(); + if (intpos) + intpos--; + if (lexstop) + break; + } + brk: + hungetc(c); + if (in_brace_param) { + while(bct-- >= in_brace_param) + cmdpop(); + zerr("closing brace expected", NULL, 0); + } else if (unset(IGNOREBRACES) && !sub && len > 1 && + peek == STRING && bptr[-1] == '}' && bptr[-2] != Bnull) { + /* hack to get {foo} command syntax work */ + bptr--; + len--; + lexstop = 0; + hungetc('}'); + } + *bptr = '\0'; + DPUTS(cmdsp != ocmdsp, "BUG: gettok: cmdstack changed."); + return peek; +} + +/**/ +static int +dquote_parse(char endchar, int sub) +{ + int pct = 0, brct = 0, bct = 0, intick = 0, err = 0; + int c; + int math = endchar == ')' || endchar == ']'; + int zlemath = math && cs > ll + addedx - inbufct; + + while (((c = hgetc()) != endchar || bct || + (math && ((pct > 0) || (brct > 0))) || + intick) && !lexstop) { + cont: + switch (c) { + case '\\': + c = hgetc(); + if (c != '\n') { + if (c == '$' || c == '\\' || (c == '}' && !intick && bct) || + c == endchar || c == '`') + add(Bnull); + else { + /* lexstop is implicitely handled here */ + add('\\'); + goto cont; + } + } else if (sub || unset(CSHJUNKIEQUOTES) || endchar != '"') + continue; + break; + case '\n': + err = !sub && isset(CSHJUNKIEQUOTES) && endchar == '"'; + break; + case '$': + if (intick) + break; + c = hgetc(); + if (c == '(') { + add(Qstring); + err = cmd_or_math_sub(); + c = Outpar; + } else if (c == '[') { + add(String); + add(Inbrack); + cmdpush(CS_MATHSUBST); + err = dquote_parse(']', sub); + cmdpop(); + c = Outbrack; + } else if (c == '{') { + add(Qstring); + c = Inbrace; + cmdpush(CS_BRACEPAR); + bct++; + } else if (c == '$') + add(Qstring); + else { + hungetc(c); + lexstop = 0; + c = Qstring; + } + break; + case '}': + if (intick || !bct) + break; + c = Outbrace; + bct--; + cmdpop(); + break; + case '`': + c = Qtick; + if (intick == 2) + ALLOWHIST + if ((intick = !intick)) { + SETPARBEGIN + cmdpush(CS_BQUOTE); + } else { + SETPAREND + cmdpop(); + } + break; + case '\'': + if (!intick) + break; + if (intick == 1) + intick = 2, STOPHIST + else + intick = 1, ALLOWHIST + break; + case '(': + pct++; + break; + case ')': + err = (!pct-- && math); + break; + case '[': + brct++; + break; + case ']': + err = (!brct-- && math); + break; + case '"': + if (intick || (!endchar && !bct)) + break; + if (bct) { + add(Dnull); + err = dquote_parse('"', sub); + c = Dnull; + } else + err = 1; + break; + } + if (err || lexstop) + break; + add(c); + } + if (intick == 2) + ALLOWHIST + if (intick) + cmdpop(); + while (bct--) + cmdpop(); + if (lexstop) + err = intick || endchar || err; + else if (err == 1) + err = c; + if (zlemath && cs <= ll + 1 - inbufct) + inwhat = IN_MATH; + return err; +} + +/* Tokenize a string given in s. Parsing is done as in double * + * quotes. This is usually called before singsub(). */ + +/**/ +int +parsestr(char *s) +{ + int l = strlen(s), err; + + HEAPALLOC { + lexsave(); + untokenize(s); + inpush(dupstring(s), 0, NULL); + strinbeg(); + stophist = 2; + len = 0; + bptr = tokstr = s; + bsiz = l + 1; + err = dquote_parse('\0', 1); + *bptr = '\0'; + strinend(); + inpop(); + DPUTS(cmdsp, "BUG: parsestr: cmdstack not empty."); + lexrestore(); + if (err) { + untokenize(s); + if (err > 32 && err < 127) + zerr("parse error near `%c'", NULL, err); + else + zerr("parse error", NULL, 0); + } + } LASTALLOC; + return err; +} + +/* Tokenize a string given in s. Parsing is done as if s were a normal * + * command-line argument but it may contain separators. This is used * + * to parse the right-hand side of ${...%...} substitutions. */ + +/**/ +int +parse_subst_string(char *s) +{ + int c, l = strlen(s), err; + + if (! *s) + return 0; + lexsave(); + untokenize(s); + inpush(dupstring(s), 0, NULL); + strinbeg(); + stophist = 2; + len = 0; + bptr = tokstr = s; + bsiz = l + 1; + c = hgetc(); + c = gettokstr(c, 1); + err = errflag; + strinend(); + inpop(); + DPUTS(cmdsp, "BUG: parse_subst_string: cmdstack not empty."); + lexrestore(); + errflag = err; + if (c == LEXERR) { + untokenize(s); + return 1; + } +#ifdef DEBUG + if (c != STRING || len != l || errflag) { + fprintf(stderr, "Oops. Bug in parse_subst_string: %s\n", + len < l ? "len < l" : errflag ? "errflag" : "c != STRING"); + fflush(stderr); + untokenize(s); + return 1; + } +#endif + return 0; +} + +/* expand aliases and reserved words */ + +/**/ +int +exalias(void) +{ + Alias an; + Reswd rw; + + hwend(); + if (interact && isset(SHINSTDIN) && !strin && !incasepat && + tok == STRING && !nocorrect && !(inbufflags & INP_ALIAS) && + (isset(CORRECTALL) || (isset(CORRECT) && incmdpos))) + spckword(&tokstr, 1, incmdpos, 1); + + if (!tokstr) { + yytext = tokstrings[tok]; + if (yytext) + yytext = dupstring(yytext); + return 0; + } + + if (has_token(tokstr)) { + char *p, *t; + + yytext = p = ncalloc(strlen(tokstr) + 1); + for (t = tokstr; (*p++ = itok(*t) ? ztokens[*t++ - Pound] : *t++);); + } else + yytext = tokstr; + + if (zleparse && !(inbufflags & INP_ALIAS)) { + int zp = zleparse; + + gotword(); + if (zp == 1 && !zleparse) { + return 0; + } + } + + if (tok == STRING) { + /* Check for an alias */ + an = noaliases ? NULL : (Alias) aliastab->getnode(aliastab, yytext); + if (an && !an->inuse && ((an->flags & ALIAS_GLOBAL) || incmdpos || + inalmore)) { + inpush(an->text, INP_ALIAS, an); + /* remove from history if it begins with space */ + if (isset(HISTIGNORESPACE) && an->text[0] == ' ') + remhist(); + lexstop = 0; + return 1; + } + + /* Then check for a reserved word */ + if ((incmdpos || + (unset(IGNOREBRACES) && yytext[0] == '}' && !yytext[1])) && + (rw = (Reswd) reswdtab->getnode(reswdtab, yytext))) { + tok = rw->token; + if (tok == DINBRACK) + incond = 1; + } else if (incond && !strcmp(yytext, "]]")) { + tok = DOUTBRACK; + incond = 0; + } else if (incond && yytext[0] == '!' && !yytext[1]) + tok = BANG; + } + inalmore = 0; + return 0; +} + +/* skip (...) */ + +/**/ +static int +skipcomm(void) +{ + int pct = 1, c; + + cmdpush(CS_CMDSUBST); + SETPARBEGIN + c = Inpar; + do { + add(c); + c = hgetc(); + if (itok(c) || lexstop) + break; + switch (c) { + case '(': + pct++; + break; + case ')': + pct--; + break; + case '\\': + add(c); + c = hgetc(); + break; + case '\'': { + int strquote = bptr[-1] == '$'; + add(c); + STOPHIST + while ((c = hgetc()) != '\'' && !lexstop) { + if (c == '\\' && strquote) { + add(c); + c = hgetc(); + } + add(c); + } + ALLOWHIST + break; + } + case '\"': + add(c); + while ((c = hgetc()) != '\"' && !lexstop) + if (c == '\\') { + add(c); + add(hgetc()); + } else + add(c); + break; + case '`': + add(c); + while ((c = hgetc()) != '`' && !lexstop) + if (c == '\\') + add(c), add(hgetc()); + else + add(c); + break; + } + } + while (pct); + if (!lexstop) + SETPAREND + cmdpop(); + return lexstop; +} diff --git a/Src/linklist.c b/Src/linklist.c new file mode 100644 index 000000000..62a962595 --- /dev/null +++ b/Src/linklist.c @@ -0,0 +1,222 @@ +/* + * linklist.c - linked lists + * + * 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 "linklist.pro" + +/* Get an empty linked list header */ + +/**/ +LinkList +newlinklist(void) +{ + LinkList list; + + list = (LinkList) alloc(sizeof *list); + list->first = NULL; + list->last = (LinkNode) list; + return list; +} + +/* Insert a node in a linked list after a given node */ + +/**/ +LinkNode +insertlinknode(LinkList list, LinkNode node, void *dat) +{ + LinkNode tmp, new; + + tmp = node->next; + node->next = new = (LinkNode) alloc(sizeof *tmp); + new->last = node; + new->dat = dat; + new->next = tmp; + if (tmp) + tmp->last = new; + else + list->last = new; + return new; +} + +/* Insert an already-existing node into a linked list after a given node */ + +/**/ +LinkNode +uinsertlinknode(LinkList list, LinkNode node, LinkNode new) +{ + LinkNode tmp = node->next; + node->next = new; + new->last = node; + new->next = tmp; + if (tmp) + tmp->last = new; + else + list->last = new; + return new; +} + +/* Insert a list in another list */ + +/**/ +void +insertlinklist(LinkList l, LinkNode where, LinkList x) +{ + LinkNode nx; + + nx = where->next; + if (!l->first) + return; + where->next = l->first; + l->last->next = nx; + l->first->last = where; + if (nx) + nx->last = l->last; + else + x->last = l->last; +} + +/* Get top node in a linked list */ + +/**/ +void * +getlinknode(LinkList list) +{ + void *dat; + LinkNode node; + + if (!(node = list->first)) + return NULL; + dat = node->dat; + list->first = node->next; + if (node->next) + node->next->last = (LinkNode) list; + else + list->last = (LinkNode) list; + zfree(node, sizeof(struct linknode)); + return dat; +} + +/* Get top node in a linked list without freeing */ + +/**/ +void * +ugetnode(LinkList list) +{ + void *dat; + LinkNode node; + + if (!(node = list->first)) + return NULL; + dat = node->dat; + list->first = node->next; + if (node->next) + node->next->last = (LinkNode) list; + else + list->last = (LinkNode) list; + return dat; +} + +/* Remove a node from a linked list */ + +/**/ +void * +remnode(LinkList list, LinkNode nd) +{ + void *dat; + + nd->last->next = nd->next; + if (nd->next) + nd->next->last = nd->last; + else + list->last = nd->last; + dat = nd->dat; + zfree(nd, sizeof(struct linknode)); + + return dat; +} + +/* Remove a node from a linked list without freeing */ + +/**/ +void * +uremnode(LinkList list, LinkNode nd) +{ + void *dat; + + nd->last->next = nd->next; + if (nd->next) + nd->next->last = nd->last; + else + list->last = nd->last; + dat = nd->dat; + return dat; +} + +/* Free a linked list */ + +/**/ +void +freelinklist(LinkList list, FreeFunc freefunc) +{ + LinkNode node, next; + + for (node = list->first; node; node = next) { + next = node->next; + if (freefunc) + freefunc(node->dat); + zfree(node, sizeof(struct linknode)); + } + zfree(list, sizeof(struct linklist)); +} + +/* Count the number of nodes in a linked list */ + +/**/ +int +countlinknodes(LinkList list) +{ + LinkNode nd; + int ct = 0; + + for (nd = firstnode(list); nd; incnode(nd), ct++); + return ct; +} + +/**/ +void +rolllist(LinkList l, LinkNode nd) +{ + l->last->next = l->first; + l->first->last = l->last; + l->first = nd; + l->last = nd->last; + nd->last = (LinkNode) l; + l->last->next = 0; +} + diff --git a/Src/loop.c b/Src/loop.c new file mode 100644 index 000000000..5fbf2b841 --- /dev/null +++ b/Src/loop.c @@ -0,0 +1,421 @@ +/* + * loop.c - loop execution + * + * 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 "loop.pro" + +/* # of nested loops we are in */ + +/**/ +int loops; + +/* # of continue levels */ + +/**/ +int contflag; + +/* # of break levels */ + +/**/ +int breaks; + +/**/ +int +execfor(Cmd cmd) +{ + List list; + Forcmd node; + char *str; + int val; + LinkList args; + + node = cmd->u.forcmd; + args = cmd->args; + if (node->condition) { + str = node->name; + singsub(&str); + if (!errflag) + matheval(str); + if (errflag) + return lastval = errflag; + } else if (!node->inflag) { + char **x; + + args = newlinklist(); + for (x = pparams; *x; x++) + addlinknode(args, ztrdup(*x)); + } + lastval = 0; + loops++; + pushheap(); + for (;;) { + if (node->condition) { + str = dupstring(node->condition); + singsub(&str); + if (!errflag) { + while (iblank(*str)) + str++; + if (*str) + val = matheval(str); + else + val = 1; + } + if (errflag) { + if (breaks) + breaks--; + lastval = 1; + break; + } + if (!val) + break; + } else { + str = (char *) ugetnode(args); + if (!str) + break; + setsparam(node->name, ztrdup(str)); + } + list = (List) dupstruct(node->list); + execlist(list, 1, (cmd->flags & CFLAG_EXEC) && empty(args)); + if (breaks) { + breaks--; + if (breaks || !contflag) + break; + contflag = 0; + } + if (node->condition && !errflag) { + str = dupstring(node->advance); + singsub(&str); + if (!errflag) + matheval(str); + } + if (errflag) { + if (breaks) + breaks--; + lastval = 1; + break; + } + freeheap(); + } + popheap(); + loops--; + return lastval; +} + +/**/ +int +execselect(Cmd cmd) +{ + List list; + Forcmd node; + char *str, *s; + LinkList args; + LinkNode n; + int i; + FILE *inp; + + node = cmd->u.forcmd; + args = cmd->args; + if (!node->inflag) { + char **x; + + args = newlinklist(); + for (x = pparams; *x; x++) + addlinknode(args, ztrdup(*x)); + } + if (empty(args)) + return 1; + loops++; + lastval = 0; + pushheap(); + inp = fdopen(dup((SHTTY == -1) ? 0 : SHTTY), "r"); + selectlist(args); + for (;;) { + for (;;) { + if (empty(bufstack)) { + if (interact && SHTTY != -1 && isset(USEZLE)) { + isfirstln = 1; + str = (char *)zleread(prompt3, NULL, 0); + } else { + str = promptexpand(prompt3, 0, NULL, NULL); + zputs(str, stderr); + free(str); + fflush(stderr); + str = fgets(zalloc(256), 256, inp); + } + } else + str = (char *)getlinknode(bufstack); + if (!str || errflag) { + if (breaks) + breaks--; + fprintf(stderr, "\n"); + fflush(stderr); + goto done; + } + if ((s = strchr(str, '\n'))) + *s = '\0'; + if (*str) + break; + selectlist(args); + } + setsparam("REPLY", ztrdup(str)); + i = atoi(str); + if (!i) + str = ""; + else { + for (i--, n = firstnode(args); n && i; incnode(n), i--); + if (n) + str = (char *) getdata(n); + else + str = ""; + } + setsparam(node->name, ztrdup(str)); + list = (List) dupstruct(node->list); + execlist(list, 1, 0); + freeheap(); + if (breaks) { + breaks--; + if (breaks || !contflag) + break; + contflag = 0; + } + if (errflag) + break; + } + done: + popheap(); + fclose(inp); + loops--; + return lastval; +} + +/* And this is used to print select lists. */ + +/**/ +static void +selectlist(LinkList l) +{ + size_t longest = 1, fct, fw = 0, colsz, t0, t1, ct; + LinkNode n; + char **arr, **ap; + + trashzle(); + ct = countlinknodes(l); + ap = arr = (char **)alloc((countlinknodes(l) + 1) * sizeof(char **)); + + for (n = (LinkNode) firstnode(l); n; incnode(n)) + *ap++ = (char *)getdata(n); + *ap = NULL; + for (ap = arr; *ap; ap++) + if (strlen(*ap) > longest) + longest = strlen(*ap); + t0 = ct; + longest++; + while (t0) + t0 /= 10, longest++; + /* to compensate for added ')' */ + fct = (columns - 1) / (longest + 3); + if (fct == 0) + fct = 1; + else + fw = (columns - 1) / fct; + colsz = (ct + fct - 1) / fct; + for (t1 = 0; t1 != colsz; t1++) { + ap = arr + t1; + do { + int t2 = strlen(*ap) + 2, t3; + + fprintf(stderr, "%d) %s", t3 = ap - arr + 1, *ap); + while (t3) + t2++, t3 /= 10; + for (; t2 < fw; t2++) + fputc(' ', stderr); + for (t0 = colsz; t0 && *ap; t0--, ap++); + } + while (*ap); + fputc('\n', stderr); + } + + /* Below is a simple attempt at doing it the Korn Way.. + ap = arr; + t0 = 0; + do { + t0++; + fprintf(stderr,"%d) %s\n",t0,*ap); + ap++; + } + while (*ap);*/ + fflush(stderr); +} + +/**/ +int +execwhile(Cmd cmd) +{ + List list; + struct whilecmd *node; + int olderrexit, oldval; + + olderrexit = noerrexit; + node = cmd->u.whilecmd; + oldval = 0; + pushheap(); + loops++; + for (;;) { + list = (List) dupstruct(node->cont); + noerrexit = 1; + execlist(list, 1, 0); + noerrexit = olderrexit; + if (!((lastval == 0) ^ node->cond)) { + if (breaks) + breaks--; + lastval = oldval; + break; + } + list = (List) dupstruct(node->loop); + execlist(list, 1, 0); + if (breaks) { + breaks--; + if (breaks || !contflag) + break; + contflag = 0; + } + freeheap(); + if (errflag) { + lastval = 1; + break; + } + oldval = lastval; + } + popheap(); + loops--; + return lastval; +} + +/**/ +int +execrepeat(Cmd cmd) +{ + List list; + int count; + + lastval = 0; + if (empty(cmd->args) || nextnode(firstnode(cmd->args))) { + zerr("bad argument for repeat", NULL, 0); + return 1; + } + count = atoi(peekfirst(cmd->args)); + pushheap(); + loops++; + while (count--) { + list = (List) dupstruct(cmd->u.list); + execlist(list, 1, 0); + freeheap(); + if (breaks) { + breaks--; + if (breaks || !contflag) + break; + contflag = 0; + } + if (errflag) { + lastval = 1; + break; + } + } + popheap(); + loops--; + return lastval; +} + +/**/ +int +execif(Cmd cmd) +{ + struct ifcmd *node; + int olderrexit; + List *i, *t; + + olderrexit = noerrexit; + node = cmd->u.ifcmd; + i = node->ifls; + t = node->thenls; + + if (!noerrexit) + noerrexit = 1; + while (*i) { + execlist(*i, 1, 0); + if (!lastval) + break; + i++; + t++; + } + noerrexit = olderrexit; + + if (*t) + execlist(*t, 1, cmd->flags & CFLAG_EXEC); + else + lastval = 0; + + return lastval; +} + +/**/ +int +execcase(Cmd cmd) +{ + struct casecmd *node; + char *word; + List *l; + char **p; + + node = cmd->u.casecmd; + l = node->lists; + p = node->pats; + + word = *p++; + singsub(&word); + untokenize(word); + lastval = 0; + + if (node) { + while (*p) { + char *pat = *p + 1; + singsub(&pat); + if (matchpat(word, pat)) { + do { + execlist(*l++, 1, **p == ';' && (cmd->flags & CFLAG_EXEC)); + } while(**p++ == '&' && *p); + break; + } + p++; + l++; + } + } + return lastval; +} + diff --git a/Src/main.c b/Src/main.c new file mode 100644 index 000000000..7ec8f07bd --- /dev/null +++ b/Src/main.c @@ -0,0 +1,99 @@ +/* + * main.c - the main() function + * + * 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 "main.pro" + +/**/ +int +main(int argc, char **argv) +{ + char **t; +#ifdef LC_ALL + setlocale(LC_ALL, ""); +#endif + + global_permalloc(); + + init_hackzero(argv, environ); + + for (t = argv; *t; *t = metafy(*t, -1, META_ALLOC), t++); + + if (!(zsh_name = strrchr(argv[0], '/'))) + zsh_name = argv[0]; + else + zsh_name++; + if (*zsh_name == '-') + zsh_name++; + + fdtable_size = OPEN_MAX; + fdtable = zcalloc(fdtable_size); + + createoptiontable(); + emulate(zsh_name, 1); /* initialises most options */ + opts[LOGINSHELL] = (**argv == '-'); + opts[MONITOR] = 1; /* may be unset in init_io() */ + opts[PRIVILEGED] = (getuid() != geteuid() || getgid() != getegid()); + opts[USEZLE] = 1; /* may be unset in init_io() */ + parseargs(argv); /* sets INTERACTIVE, SHINSTDIN and SINGLECOMMAND */ + + SHTTY = -1; + init_io(); + setupvals(); + init_signals(); + global_heapalloc(); + init_bltinmods(); + run_init_scripts(); + init_misc(); + + for (;;) { + do + loop(1,0); + while (tok != ENDINPUT && (tok != LEXERR || isset(SHINSTDIN))); + if (tok == LEXERR) { + stopmsg = 1; + zexit(lastval, 0); + } + if (!(isset(IGNOREEOF) && interact)) { +#if 0 + if (interact) + fputs(islogin ? "logout\n" : "exit\n", shout); +#endif + zexit(lastval, 0); + continue; + } + noexitct++; + if (noexitct >= 10) { + stopmsg = 1; + zexit(lastval, 0); + } + zerrnam("zsh", (!islogin) ? "use 'exit' to exit." + : "use 'logout' to logout.", NULL, 0); + } +} diff --git a/Src/makepro.awk b/Src/makepro.awk new file mode 100644 index 000000000..b5d2f3dc4 --- /dev/null +++ b/Src/makepro.awk @@ -0,0 +1,146 @@ +# +# makepro.awk - generate prototype lists +# + +BEGIN { + aborting = 0 + + # arg 1 is the name of the file to process + # arg 2 is the name of the subdirectory it is in + if(ARGC != 3) { + aborting = 1 + exit 1 + } + name = ARGV[1] + gsub(/^.*\//, "", name) + gsub(/\.c$/, "", name) + name = ARGV[2] "_" name + gsub(/\//, "_", name) + ARGC-- + + # `locals' is a list of local declarations, built up while global + # declarations are output. + locals = "" + + printf "#ifndef have_%s_globals\n", name + printf "#define have_%s_globals\n", name + printf "\n" +} + +# all relevant declarations are preceded by "/**/" on a line by itself + +/^\/\*\*\/$/ { + # The declaration is on following lines. The interesting part might + # be terminated by a `{' (`int foo(void) { }' or `int bar[] = {') + # or `;' (`int x;'). + line = "" + isfunc = 0 + while(1) { + if(getline <= 0) { + aborting = 1 + exit 1 + } + gsub(/\t/, " ") + line = line " " $0 + gsub(/\/\*([^*]|\*+[^*\/])*\*+\//, " ", line) + if(line ~ /\/\*/) + continue + # If it is a function definition, note so. + if(line ~ /\) *[{].*$/) #} + isfunc = 1 + if(sub(/ *[{;].*$/, "", line)) #} + break + } + # Put spaces around each identifier. + while(match(line, /[^_0-9A-Za-z ][_0-9A-Za-z]/) || + match(line, /[_0-9A-Za-z][^_0-9A-Za-z ]/)) + line = substr(line, 1, RSTART) " " substr(line, RSTART+1) + # Separate declarations into a type and a list of declarators. + # In each declarator, "@{" and "@}" are used in place of parens to + # mark function parameter lists, and "@!" is used in place of commas + # in parameter lists. "@<" and "@>" are used in place of + # non-parameter list parens. + gsub(/ _ +/, " _ ", line) + while(1) { + if(isfunc && match(line, /\([^()]*\)$/)) + line = substr(line, 1, RSTART-1) " _ (" substr(line, RSTART) ")" + else if(match(line, / _ \(\([^,()]*,/)) + line = substr(line, 1, RSTART+RLENGTH-2) "@!" substr(line, RSTART+RLENGTH) + else if(match(line, / _ \(\([^,()]*\)\)/)) + line = substr(line, 1, RSTART-1) "@{" substr(line, RSTART+5, RLENGTH-7) "@}" substr(line, RSTART+RLENGTH) + else if(match(line, /\([^,()]*\)/)) + line = substr(line, 1, RSTART-1) "@<" substr(line, RSTART+1, RLENGTH-2) "@>" substr(line, RSTART+RLENGTH) + else + break + } + sub(/^ */, "", line) + match(line, /^((const|enum|static|struct|union) +)*([_0-9A-Za-z]+ +|((char|double|float|int|long|short|unsigned|void) +)+)((const|static) +)*/) + dtype = substr(line, 1, RLENGTH) + sub(/ *$/, "", dtype) + islocal = " " dtype " " ~ / static / + line = substr(line, RLENGTH+1) "," + # Handle each declarator. + output = "" + while(match(line, /^[^,]*,/)) { + # Separate out the name from the declarator. Use "@+" and "@-" + # to bracket the name within the declarator. Strip off any + # initialiser. + dcltor = substr(line, 1, RLENGTH-1) + line = substr(line, RLENGTH+1) + sub(/\=.*$/, "", dcltor) + match(dcltor, /^([^_0-9A-Za-z]| const )*/) + dcltor = substr(dcltor, 1, RLENGTH) "@+" substr(dcltor, RLENGTH+1) + match(dcltor, /^.*@\+[_0-9A-Za-z]+/) + dcltor = substr(dcltor, 1, RLENGTH) "@-" substr(dcltor, RLENGTH+1) + dnam = dcltor + sub(/^.*@\+/, "", dnam) + sub(/@-.*$/, "", dnam) + + # Put parens etc. back + gsub(/@[{]/, " _((", dcltor) + gsub(/@}/, "))", dcltor) + gsub(/@</, "(", dcltor) + gsub(/@>/, ")", dcltor) + gsub(/@!/, ",", dcltor) + + # If this is a module boot/cleanup function, conditionally rename it. + if(" " dtype " " ~ / int / && dcltor ~ / *@\+(boot|cleanup)_[_0-9A-Za-z]+@- *_\(\( *Module +[_0-9A-Za-z]+ *\)\) */) { + modtype = dnam + sub(/_.*$/, "", modtype) + output = output "# if defined(DYNAMIC_NAME_CLASH_OK) && defined(MODULE)\n" + output = output "# define " dnam " " modtype "_\n" + output = output "# endif\n" + } + + # Format the declaration for output + dcl = dtype " " dcltor ";" + if(!islocal) + dcl = "extern " dcl + gsub(/@[+-]/, "", dcl) + gsub(/ +/, " ", dcl) + while(match(dcl, /[^_0-9A-Za-z] ./) || match(dcl, /. [^_0-9A-Za-z]/)) + dcl = substr(dcl, 1, RSTART) substr(dcl, RSTART+2) + output = output dcl "\n" + } + + # Output global declarations now, but save up locals until the end. + if(islocal) + locals = locals output + else + printf "%s", output +} + +END { + if(aborting) + exit 1 + printf "\n" + printf "#endif /* !have_%s_globals */\n", name + if(locals != "") { + printf "\n" + printf "#ifndef GLOBAL_PROTOTYPES\n" + printf "\n" + printf locals + printf "\n" + printf "#endif /* !GLOBAL_PROTOTYPES */\n" + } +} diff --git a/Src/math.c b/Src/math.c new file mode 100644 index 000000000..7a0a1f9bd --- /dev/null +++ b/Src/math.c @@ -0,0 +1,883 @@ +/* + * math.c - mathematical expression evaluation + * + * 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 "math.pro" + +/* nonzero means we are not evaluating, just parsing */ + +/**/ +int noeval; + +/* last input base we used */ + +/**/ +int lastbase; + +static char *ptr; + +static long yyval; +static LV yylval; + +static int mlevel = 0; + +/* != 0 means recognize unary plus, minus, etc. */ + +static int unary = 1; + +/* LR = left-to-right associativity * + * RL = right-to-left associativity * + * BOO = short-circuiting boolean */ + +#define LR 0 +#define RL 1 +#define BOOL 2 + +#define M_INPAR 0 +#define M_OUTPAR 1 +#define NOT 2 +#define COMP 3 +#define POSTPLUS 4 +#define POSTMINUS 5 +#define UPLUS 6 +#define UMINUS 7 +#define AND 8 +#define XOR 9 +#define OR 10 +#define MUL 11 +#define DIV 12 +#define MOD 13 +#define PLUS 14 +#define MINUS 15 +#define SHLEFT 16 +#define SHRIGHT 17 +#define LES 18 +#define LEQ 19 +#define GRE 20 +#define GEQ 21 +#define DEQ 22 +#define NEQ 23 +#define DAND 24 +#define DOR 25 +#define DXOR 26 +#define QUEST 27 +#define COLON 28 +#define EQ 29 +#define PLUSEQ 30 +#define MINUSEQ 31 +#define MULEQ 32 +#define DIVEQ 33 +#define MODEQ 34 +#define ANDEQ 35 +#define XOREQ 36 +#define OREQ 37 +#define SHLEFTEQ 38 +#define SHRIGHTEQ 39 +#define DANDEQ 40 +#define DOREQ 41 +#define DXOREQ 42 +#define COMMA 43 +#define EOI 44 +#define PREPLUS 45 +#define PREMINUS 46 +#define NUM 47 +#define ID 48 +#define POWER 49 +#define CID 50 +#define POWEREQ 51 +#define TOKCOUNT 52 + +/* precedences */ + +static int prec[TOKCOUNT] = +{ + 1, 137, 2, 2, 2, + 2, 2, 2, 4, 5, + 6, 8, 8, 8, 9, + 9, 3, 3, 10, 10, + 10, 10, 11, 11, 12, + 13, 13, 14, 14, 15, + 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, + 15, 15, 15, 16, 200, + 2, 2, 0, 0, 7, + 0, 15 +}; + +#define TOPPREC 16 +#define ARGPREC (TOPPREC-1) + +static int type[TOKCOUNT] = +{ + LR, LR, RL, RL, RL, + RL, RL, RL, LR, LR, + LR, LR, LR, LR, LR, + LR, LR, LR, LR, LR, + LR, LR, LR, LR, BOOL, + BOOL, LR, RL, RL, RL, + RL, RL, RL, RL, RL, + RL, RL, RL, RL, RL, + BOOL, BOOL, RL, RL, RL, + RL, RL, LR, LR, RL, + LR, RL +}; + +#define LVCOUNT 32 + +/* list of lvalues (variables) */ + +static int lvc; +static char **lvals; + + +/**/ +static int +zzlex(void) +{ + int cct = 0; + + for (;; cct = 0) + switch (*ptr++) { + case '+': + if (*ptr == '+' && (unary || !ialnum(*ptr))) { + ptr++; + return (unary) ? PREPLUS : POSTPLUS; + } + if (*ptr == '=') { + unary = 1; + ptr++; + return PLUSEQ; + } + return (unary) ? UPLUS : PLUS; + case '-': + if (*ptr == '-' && (unary || !ialnum(*ptr))) { + ptr++; + return (unary) ? PREMINUS : POSTMINUS; + } + if (*ptr == '=') { + unary = 1; + ptr++; + return MINUSEQ; + } + return (unary) ? UMINUS : MINUS; + case '(': + unary = 1; + return M_INPAR; + case ')': + return M_OUTPAR; + case '!': + if (*ptr == '=') { + unary = 1; + ptr++; + return NEQ; + } + return NOT; + case '~': + return COMP; + case '&': + unary = 1; + if (*ptr == '&') { + if (*++ptr == '=') { + ptr++; + return DANDEQ; + } + return DAND; + } else if (*ptr == '=') { + ptr++; + return ANDEQ; + } + return AND; + case '|': + unary = 1; + if (*ptr == '|') { + if (*++ptr == '=') { + ptr++; + return DOREQ; + } + return DOR; + } else if (*ptr == '=') { + ptr++; + return OREQ; + } + return OR; + case '^': + unary = 1; + if (*ptr == '^') { + if (*++ptr == '=') { + ptr++; + return DXOREQ; + } + return DXOR; + } else if (*ptr == '=') { + ptr++; + return XOREQ; + } + return XOR; + case '*': + unary = 1; + if (*ptr == '*') { + if (*++ptr == '=') { + ptr++; + return POWEREQ; + } + return POWER; + } + if (*ptr == '=') { + ptr++; + return MULEQ; + } + return MUL; + case '/': + unary = 1; + if (*ptr == '=') { + ptr++; + return DIVEQ; + } + return DIV; + case '%': + unary = 1; + if (*ptr == '=') { + ptr++; + return MODEQ; + } + return MOD; + case '<': + unary = 1; + if (*ptr == '<') { + if (*++ptr == '=') { + ptr++; + return SHLEFTEQ; + } + return SHLEFT; + } else if (*ptr == '=') { + ptr++; + return LEQ; + } + return LES; + case '>': + unary = 1; + if (*ptr == '>') { + if (*++ptr == '=') { + ptr++; + return SHRIGHTEQ; + } + return SHRIGHT; + } else if (*ptr == '=') { + ptr++; + return GEQ; + } + return GRE; + case '=': + unary = 1; + if (*ptr == '=') { + ptr++; + return DEQ; + } + return EQ; + case '$': + unary = 0; + yyval = mypid; + return NUM; + case '?': + if (unary) { + yyval = lastval; + unary = 0; + return NUM; + } + unary = 1; + return QUEST; + case ':': + unary = 1; + return COLON; + case ',': + unary = 1; + return COMMA; + case '\0': + unary = 1; + ptr--; + return EOI; + case '[': + unary = 0; + { + int base = zstrtol(ptr, &ptr, 10); + + if (*ptr == ']') + ptr++; + yyval = zstrtol(ptr, &ptr, lastbase = base); + return NUM; + } + case ' ': + case '\t': + case '\n': + break; + case '0': + if (*ptr == 'x' || *ptr == 'X') { + unary = 0; + /* Should we set lastbase here? */ + yyval = zstrtol(++ptr, &ptr, lastbase = 16); + return NUM; + } + /* Fall through! */ + default: + if (idigit(*--ptr)) { + unary = 0; + yyval = zstrtol(ptr, &ptr, 10); + + if (*ptr == '#') { + ptr++; + yyval = zstrtol(ptr, &ptr, lastbase = yyval); + } + return NUM; + } + if (*ptr == '#') { + if (*++ptr == '\\') { + ptr++; + yyval = *ptr == Meta ? *++ptr ^ 32 : *ptr; + ptr++; + unary = 0; + return NUM; + } + cct = 1; + } + if (iident(*ptr)) { + char *p, q; + + p = ptr; + if (lvc == LVCOUNT) { + zerr("too many identifiers (complain to author)", NULL, 0); + return EOI; + } + unary = 0; + while (iident(*++ptr)); + if (*ptr == '[') { + int l; + for (ptr++, l = 1; *ptr && l; ptr++) { + if (*ptr == '[') + l++; + if (*ptr == ']') + l--; + if (*ptr == '\\' && ptr[1]) + ptr++; + } + } + q = *ptr; + *ptr = '\0'; + lvals[yylval = lvc++] = ztrdup(p); + *ptr = q; + return cct ? CID : ID; + } + else if (cct) { + yyval = poundgetfn(NULL); + unary = 0; + return NUM; + } + return EOI; + } +} + +/* the value stack */ + +#define STACKSZ 100 +static int mtok; /* last token */ +static int sp = -1; /* stack pointer */ + +struct mathvalue { + LV lval; + long val; +}; + +static struct mathvalue *stack; + +/**/ +static void +push(long val, LV lval) +{ + if (sp == STACKSZ - 1) + zerr("stack overflow", NULL, 0); + else + sp++; + stack[sp].val = val; + stack[sp].lval = lval; +} + + +/**/ +static long +getcvar(LV s) +{ + char *t; + + if (!(t = getsparam(lvals[s]))) + return 0; + return STOUC(*t == Meta ? t[1] ^ 32 : *t); +} + + +/**/ +static long +setvar(LV s, long v) +{ + if (s == -1 || s >= lvc) { + zerr("lvalue required", NULL, 0); + return 0; + } + if (noeval) + return v; + setiparam(lvals[s], v); + return v; +} + + +/**/ +static int +notzero(long a) +{ + if (a == 0) { + zerr("division by zero", NULL, 0); + return 0; + } + return 1; +} + +/* macro to pop two values off the value stack */ +#define pop2() { \ + if (sp < 1) { \ + zerr("bad math expression: unbalanced stack", NULL, 0); \ + return; \ + } \ + b = stack[sp--].val; \ + a = stack[sp--].val; \ + } + +/* macro to pop three values off the value stack */ +#define pop3() { \ + if (sp < 2) { \ + zerr("bad math expression: unbalanced stack", NULL, 0); \ + return; \ + } \ + c = stack[sp--].val; \ + b = stack[sp--].val; \ + a = stack[sp--].val; \ + } + +#define nolval() {stack[sp].lval= -1;} +#define pushv(X) { push(X,-1); } +#define pop2lv() { pop2() lv = stack[sp+1].lval; } +#define set(X) { push(setvar(lv,X),lv); } + + +/**/ +void +op(int what) +{ + long a, b, c; + LV lv; + + if (sp < 0) { + zerr("bad math expression: stack empty", NULL, 0); + return; + } + switch (what) { + case NOT: + stack[sp].val = !stack[sp].val; + nolval(); + break; + case COMP: + stack[sp].val = ~stack[sp].val; + nolval(); + break; + case POSTPLUS: + (void)setvar(stack[sp].lval, stack[sp].val + 1); + break; + case POSTMINUS: + (void)setvar(stack[sp].lval, stack[sp].val - 1); + break; + case UPLUS: + nolval(); + break; + case UMINUS: + stack[sp].val = -stack[sp].val; + nolval(); + break; + case AND: + pop2(); + pushv(a & b); + break; + case XOR: + pop2(); + pushv(a ^ b); + break; + case OR: + pop2(); + pushv(a | b); + break; + case MUL: + pop2(); + pushv(a * b); + break; + case DIV: + pop2(); + if (notzero(b)) + pushv(a / b); + break; + case MOD: + pop2(); + if (notzero(b)) + pushv(a % b); + break; + case PLUS: + pop2(); + pushv(a + b); + break; + case MINUS: + pop2(); + pushv(a - b); + break; + case SHLEFT: + pop2(); + pushv(a << b); + break; + case SHRIGHT: + pop2(); + pushv(a >> b); + break; + case LES: + pop2(); + pushv((long)(a < b)); + break; + case LEQ: + pop2(); + pushv((long)(a <= b)); + break; + case GRE: + pop2(); + pushv((long)(a > b)); + break; + case GEQ: + pop2(); + pushv((long)(a >= b)); + break; + case DEQ: + pop2(); + pushv((long)(a == b)); + break; + case NEQ: + pop2(); + pushv((long)(a != b)); + break; + case DAND: + pop2(); + pushv((long)(a && b)); + break; + case DOR: + pop2(); + pushv((long)(a || b)); + break; + case DXOR: + pop2(); + pushv((long)((a && !b) || (!a && b))); + break; + case QUEST: + pop3(); + pushv((a) ? b : c); + break; + case COLON: + break; + case EQ: + pop2(); + lv = stack[sp + 1].lval; + set(b); + break; + case PLUSEQ: + pop2lv(); + set(a + b); + break; + case MINUSEQ: + pop2lv(); + set(a - b); + break; + case MULEQ: + pop2lv(); + set(a * b); + break; + case DIVEQ: + pop2lv(); + if (notzero(b)) + set(a / b); + break; + case MODEQ: + pop2lv(); + if (notzero(b)) + set(a % b); + break; + case ANDEQ: + pop2lv(); + set(a & b); + break; + case XOREQ: + pop2lv(); + set(a ^ b); + break; + case OREQ: + pop2lv(); + set(a | b); + break; + case SHLEFTEQ: + pop2lv(); + set(a << b); + break; + case SHRIGHTEQ: + pop2lv(); + set(a >> b); + break; + case DANDEQ: + pop2lv(); + set((long)(a && b)); + break; + case DOREQ: + pop2lv(); + set((long)(a || b)); + break; + case DXOREQ: + pop2lv(); + set((long)((a && !b) || (!a && b))); + break; + case COMMA: + pop2(); + pushv(b); + break; + case PREPLUS: + stack[sp].val = setvar(stack[sp].lval, + stack[sp].val + 1); + break; + case PREMINUS: + stack[sp].val = setvar(stack[sp].lval, + stack[sp].val - 1); + break; + case POWER: + pop2(); + if (b < 0) { + zerr("can't handle negative exponents", NULL, 0); + return; + } + for (c = 1; b--; c *= a); + pushv(c); + break; + case POWEREQ: + pop2lv(); + if (b < 0) { + zerr("can't handle negative exponents", NULL, 0); + return; + } + for (c = 1; b--; c *= a); + set(c); + break; + default: + zerr("out of integers", NULL, 0); + return; + } +} + + +/**/ +static void +bop(int tk) +{ + switch (tk) { + case DAND: + case DANDEQ: + if (!stack[sp].val) + noeval++; + break; + case DOR: + case DOREQ: + if (stack[sp].val) + noeval++; + break; + }; +} + + +/**/ +static long +mathevall(char *s, int prek, char **ep) +{ + int t0; + int xlastbase, xnoeval, xunary, xlvc; + char *xptr; + long xyyval; + LV xyylval; + char **xlvals = 0; + int xsp; + struct mathvalue *xstack = 0; + long ret; + + xlastbase = xnoeval = xunary = xlvc = xyyval = xyylval = xsp = 0; + xptr = NULL; + if (mlevel++) { + xlastbase = lastbase; + xnoeval = noeval; + xunary = unary; + xlvc = lvc; + xptr = ptr; + xyyval = yyval; + xyylval = yylval; + xlvals = lvals; + + xsp = sp; + xstack = stack; + } + stack = (struct mathvalue *)zalloc(STACKSZ*sizeof(struct mathvalue)); + lastbase = -1; + lvals = (char **)zcalloc(LVCOUNT*sizeof(char *)); + lvc = 0; + ptr = s; + sp = -1; + unary = 1; + mathparse(prek); + *ep = ptr; + if (sp) + zerr("bad math expression: unbalanced stack", NULL, 0); + for (t0 = 0; t0 != lvc; t0++) + zsfree(lvals[t0]); + + ret = stack[0].val; + + zfree(lvals, LVCOUNT*sizeof(char *)); + zfree(stack, STACKSZ*sizeof(struct mathvalue)); + if (--mlevel) { + lastbase = xlastbase; + noeval = xnoeval; + unary = xunary; + lvc = xlvc; + ptr = xptr; + yyval = xyyval; + yylval = xyylval; + lvals = xlvals; + + sp = xsp; + stack = xstack; + } + return ret; +} + + +/**/ +long +matheval(char *s) +{ + char *junk; + long x; + int xmtok = mtok; + + if (!*s) + return 0; + x = mathevall(s, TOPPREC, &junk); + mtok = xmtok; + if (*junk) + zerr("bad math expression: illegal character: %c", NULL, *junk); + return x; +} + + +/**/ +long +mathevalarg(char *s, char **ss) +{ + long x; + int xmtok = mtok; + + x = mathevall(s, ARGPREC, ss); + if (mtok == COMMA) + (*ss)--; + mtok = xmtok; + return x; +} + + +/* operator-precedence parse the string and execute */ + +/**/ +static void +mathparse(int pc) +{ + int q, otok, onoeval; + + if (errflag) + return; + mtok = zzlex(); + while (prec[mtok] <= pc) { + if (errflag) + return; + switch (mtok) { + case NUM: + push(yyval, -1); + break; + case ID: + push(getiparam(lvals[yylval]), yylval); + break; + case CID: + push(getcvar(yylval), yylval); + break; + case M_INPAR: + mathparse(TOPPREC); + if (mtok != M_OUTPAR) { + if (!errflag) + zerr("')' expected", NULL, 0); + return; + } + break; + case QUEST: + q = stack[sp].val; + + if (!q) + noeval++; + mathparse(prec[QUEST] - 1); + if (!q) + noeval--; + else + noeval++; + mathparse(prec[QUEST]); + if (q) + noeval--; + op(QUEST); + continue; + default: + otok = mtok; + onoeval = noeval; + if (type[otok] == BOOL) + bop(otok); + mathparse(prec[otok] - (type[otok] != RL)); + noeval = onoeval; + op(otok); + continue; + } + mtok = zzlex(); + } +} diff --git a/Src/mem.c b/Src/mem.c new file mode 100644 index 000000000..1145f8c5e --- /dev/null +++ b/Src/mem.c @@ -0,0 +1,1254 @@ +/* + * mem.c - memory management + * + * 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 "mem.pro" + +/* + There are two ways to allocate memory in zsh. The first way is + to call zalloc/zcalloc, which call malloc/calloc directly. It + is legal to call realloc() or free() on memory allocated this way. + The second way is to call halloc/hcalloc, which allocates memory + from one of the memory pools on the heap stack. Such memory pools + will automatically created when the heap allocation routines are + called. To be sure that they are freed at appropriate times + one should call pushheap() before one starts using heaps and + popheap() after that (when the memory allocated on the heaps since + the last pushheap() isn't needed anymore). + pushheap() saves the states of all currently allocated heaps and + popheap() resets them to the last state saved and destroys the + information about that state. If you called pushheap() and + allocated some memory on the heaps and then come to a place where + you don't need the allocated memory anymore but you still want + to allocate memory on the heap, you should call freeheap(). This + works like popheap(), only that it doesn't free the information + about the heap states (i.e. the heaps are like after the call to + pushheap() and you have to call popheap some time later). + + Memory allocated in this way does not have to be freed explicitly; + it will all be freed when the pool is destroyed. In fact, + attempting to free this memory may result in a core dump. + The pair of pointers ncalloc and alloc may point to either + zalloc & zcalloc or halloc & hcalloc; permalloc() sets them to the + former, and heapalloc() sets them to the latter. This can be useful. + For example, the dupstruct() routine duplicates a syntax tree, + allocating the new memory for the tree using alloc(). If you want + to duplicate a structure for a one-time use (i.e. to execute the list + in a for loop), call heapalloc(), then dupstruct(). If you want + to duplicate a structure in order to preserve it (i.e. a function + definition), call permalloc(), then dupstruct(). + + If we use zsh's own allocator we use a simple trick to avoid that + the (*real*) heap fills up with empty zsh-heaps: we allocate a + large block of memory before allocating a heap pool, this memory + is freed again immediately after the pool is allocated. If there + are only small blocks on the free list this guarantees that the + memory for the pool is at the end of the memory which means that + we can give it back to the system when the pool is freed. +*/ + +/* != 0 if we are allocating in the heaplist */ + +/**/ +int useheap; + +/* Current allocation pointers. ncalloc() is either zalloc() or halloc(); * + * alloc() is either zcalloc() or hcalloc(). */ + +/**/ +void *(*ncalloc) _((size_t)), *(*alloc) _((size_t)); + +#ifdef ZSH_MEM_WARNING +# ifndef DEBUG +# define DEBUG 1 +# endif +#endif + +#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG) + +static int h_m[1025], h_push, h_pop, h_free; + +#endif + +#define H_ISIZE sizeof(long) +#define HEAPSIZE (8192 - H_ISIZE) +#define HEAP_ARENA_SIZE (HEAPSIZE - sizeof(struct heap)) +#define HEAPFREE (16384 - H_ISIZE) + +/* set default allocation to heap stack */ + +/**/ +int +global_heapalloc(void) +{ + int luh = useheap; + + alloc = hcalloc; + ncalloc = halloc; + useheap = 1; + return luh; +} + +/* set default allocation to malloc() */ + +/**/ +int +global_permalloc(void) +{ + int luh = useheap; + + alloc = zcalloc; + ncalloc = zalloc; + useheap = 0; + return luh; +} + +/* heappush saves the current heap state using this structure */ + +struct heapstack { + struct heapstack *next; /* next one in list for this heap */ + size_t used; +}; + +/* A zsh heap. */ + +struct heap { + struct heap *next; /* next one */ + size_t used; /* bytes used from the heap */ + struct heapstack *sp; /* used by pushheap() to save the value used */ +#define arena(X) ((char *) (X) + sizeof(struct heap)) +}; + +/* list of zsh heap */ + +static Heap heaps; + +/* save states of zsh heaps */ + +/**/ +void +pushheap(void) +{ + Heap h; + Heapstack hs; + +#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG) + h_push++; +#endif + + for (h = heaps; h; h = h->next) { + DPUTS(!h->used, "BUG: empty heap"); + hs = (Heapstack) zalloc(sizeof(*hs)); + hs->next = h->sp; + h->sp = hs; + hs->used = h->used; + } +} + +/* reset heaps to previous state */ + +/**/ +void +freeheap(void) +{ + Heap h, hn, hl = NULL; + +#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG) + h_free++; +#endif + for (h = heaps; h; h = hn) { + hn = h->next; + if (h->sp) { +#ifdef ZSH_MEM_DEBUG + memset(arena(h) + h->sp->used, 0xff, h->used - h->sp->used); +#endif + h->used = h->sp->used; + hl = h; + } else + zfree(h, HEAPSIZE); + } + if (hl) + hl->next = NULL; + else + heaps = NULL; +} + +/* reset heap to previous state and destroy state information */ + +/**/ +void +popheap(void) +{ + Heap h, hn, hl = NULL; + Heapstack hs; + +#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG) + h_pop++; +#endif + + for (h = heaps; h; h = hn) { + hn = h->next; + if ((hs = h->sp)) { + h->sp = hs->next; +#ifdef ZSH_MEM_DEBUG + memset(arena(h) + hs->used, 0xff, h->used - hs->used); +#endif + h->used = hs->used; + zfree(hs, sizeof(*hs)); + + hl = h; + } else + zfree(h, HEAPSIZE); + } + if (hl) + hl->next = NULL; + else + heaps = NULL; +} + +/* allocate memory from the current memory pool */ + +/**/ +void * +halloc(size_t size) +{ + Heap h; + size_t n; + + size = (size + H_ISIZE - 1) & ~(H_ISIZE - 1); + +#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG) + h_m[size < 1024 ? (size / H_ISIZE) : 1024]++; +#endif + + /* find a heap with enough free space */ + + for (h = heaps; h; h = h->next) { + if (HEAP_ARENA_SIZE >= (n = size + h->used)) { + h->used = n; + return arena(h) + n - size; + } + } + + { + Heap hp; + /* not found, allocate new heap */ +#ifdef ZSH_MEM + static int called = 0; + void *foo = called ? (void *)malloc(HEAPFREE) : NULL; + /* tricky, see above */ +#endif + + queue_signals(); + n = HEAP_ARENA_SIZE > size ? HEAPSIZE : size + sizeof(*h); + for (hp = NULL, h = heaps; h; hp = h, h = h->next); + + h = (Heap) zalloc(n); + +#ifdef ZSH_MEM + if (called) + zfree(foo, HEAPFREE); + called = 1; +#endif + + h->used = size; + h->next = NULL; + h->sp = NULL; + + if (hp) + hp->next = h; + else + heaps = h; + + unqueue_signals(); + return arena(h); + } +} + +/**/ +void * +hrealloc(char *p, size_t old, size_t new) +{ + Heap h, ph; + + old = (old + H_ISIZE - 1) & ~(H_ISIZE - 1); + new = (new + H_ISIZE - 1) & ~(H_ISIZE - 1); + + if (old == new) + return p; + if (!old && !p) + return halloc(new); + + /* find the heap with p */ + + for (h = heaps, ph = NULL; h; ph = h, h = h->next) + if (p >= arena(h) && p < arena(h) + HEAP_ARENA_SIZE) + break; + + DPUTS(!h, "BUG: hrealloc() called for non-heap memory."); + DPUTS(h->sp && arena(h) + h->sp->used > p, + "BUG: hrealloc() wants to realloc pushed memory"); + + if (p + old < arena(h) + h->used) { + if (new > old) { + char *ptr = (char *) halloc(new); + memcpy(ptr, p, old); + return ptr; + } else + return new ? p : NULL; + } + + DPUTS(p + old != arena(h) + h->used, "BUG: hrealloc more than allocated"); + + if (p == arena(h)) { + if (!new) { + if (ph) + ph->next = h->next; + else + heaps = h->next; + zfree(h, HEAPSIZE); + return NULL; + } + if (old > HEAP_ARENA_SIZE || new > HEAP_ARENA_SIZE) { + size_t n = HEAP_ARENA_SIZE > new ? HEAPSIZE : new + sizeof(*h); + + if (ph) + ph->next = h = (Heap) realloc(h, n); + else + heaps = h = (Heap) realloc(h, n); + } + h->used = new; + return arena(h); + } + DPUTS(h->used > HEAP_ARENA_SIZE, "BUG: hrealloc at invalid address"); + if (h->used + (new - old) <= HEAP_ARENA_SIZE) { + h->used += new - old; + return p; + } else { + char *t = halloc(new); + memcpy(t, p, old > new ? new : old); + h->used -= old; +#ifdef ZSH_MEM_DEBUG + memset(p, 0xff, old); +#endif + return t; + } +} + +/* allocate memory from the current memory pool and clear it */ + +/**/ +void * +hcalloc(size_t size) +{ + void *ptr; + + ptr = halloc(size); + memset(ptr, 0, size); + return ptr; +} + +/* allocate permanent memory */ + +/**/ +void * +zalloc(size_t size) +{ + void *ptr; + + if (!size) + size = 1; + if (!(ptr = (void *) malloc(size))) { + zerr("fatal error: out of memory", NULL, 0); + exit(1); + } + + return ptr; +} + +/**/ +void * +zcalloc(size_t size) +{ + void *ptr; + + if (!size) + size = 1; + if (!(ptr = (void *) malloc(size))) { + zerr("fatal error: out of memory", NULL, 0); + exit(1); + } + memset(ptr, 0, size); + + return ptr; +} + +/* This front-end to realloc is used to make sure we have a realloc * + * that conforms to POSIX realloc. Older realloc's can fail if * + * passed a NULL pointer, but POSIX realloc should handle this. A * + * better solution would be for configure to check if realloc is * + * POSIX compliant, but I'm not sure how to do that. */ + +/**/ +void * +zrealloc(void *ptr, size_t size) +{ + if (ptr) { + if (size) { + /* Do normal realloc */ + if (!(ptr = (void *) realloc(ptr, size))) { + zerr("fatal error: out of memory", NULL, 0); + exit(1); + } + return ptr; + } + else + /* If ptr is not NULL, but size is zero, * + * then object pointed to is freed. */ + free(ptr); + } else { + /* If ptr is NULL, then behave like malloc */ + return malloc(size); + } + + return NULL; +} + +/**/ +char * +dupstring(const char *s) +{ + char *t; + + if (!s) + return NULL; + t = (char *)ncalloc(strlen((char *)s) + 1); + strcpy(t, s); + return t; +} + +/**/ +char * +ztrdup(const char *s) +{ + char *t; + + if (!s) + return NULL; + t = (char *)zalloc(strlen((char *)s) + 1); + strcpy(t, s); + return t; +} + +#ifdef ZSH_MEM + +/* + Below is a simple segment oriented memory allocator for systems on + which it is better than the system's one. Memory is given in blocks + aligned to an integer multiple of sizeof(long) (4 bytes on most machines, + but 8 bytes on e.g. a dec alpha). Each block is preceded by a header + which contains the length of the data part (in bytes). In allocated + blocks only this field of the structure m_hdr is senseful. In free + blocks the second field (next) is a pointer to the next free segment + on the free list. + + On top of this simple allocator there is a second allocator for small + chunks of data. It should be both faster and less space-consuming than + using the normal segment mechanism for such blocks. + For the first M_NSMALL-1 possible sizes memory is allocated in arrays + that can hold M_SNUM blocks. Each array is stored in one segment of the + main allocator. In these segments the third field of the header structure + (free) contains a pointer to the first free block in the array. The + last field (used) gives the number of already used blocks in the array. + + If the macro name ZSH_MEM_DEBUG is defined, some information about the memory + usage is stored. This information can than be viewed by calling the + builtin `mem' (which is only available if ZSH_MEM_DEBUG is set). + + If ZSH_MEM_WARNING is defined, error messages are printed in case of errors. + + If ZSH_SECURE_FREE is defined, free() checks if the given address is really + one that was returned by malloc(), it ignores it if it wasn't (printing + an error message if ZSH_MEM_WARNING is also defined). +*/ +#if !defined(__hpux) && !defined(DGUX) && !defined(__osf__) +# if defined(_BSD) +# ifndef HAVE_BRK_PROTO + extern int brk _((caddr_t)); +# endif +# ifndef HAVE_SBRK_PROTO + extern caddr_t sbrk _((int)); +# endif +# else +# ifndef HAVE_BRK_PROTO + extern int brk _((void *)); +# endif +# ifndef HAVE_SBRK_PROTO + extern void *sbrk _((int)); +# endif +# endif +#endif + +#if defined(_BSD) && !defined(STDC_HEADERS) +# define FREE_RET_T int +# define FREE_ARG_T char * +# define FREE_DO_RET +# define MALLOC_RET_T char * +# define MALLOC_ARG_T size_t +#else +# define FREE_RET_T void +# define FREE_ARG_T void * +# define MALLOC_RET_T void * +# define MALLOC_ARG_T size_t +#endif + +/* structure for building free list in blocks holding small blocks */ + +struct m_shdr { + struct m_shdr *next; /* next one on free list */ +}; + +struct m_hdr { + long len; /* length of memory block */ + struct m_hdr *next; /* if free: next on free list + if block of small blocks: next one with + small blocks of same size*/ + struct m_shdr *free; /* if block of small blocks: free list */ + long used; /* if block of small blocks: number of used + blocks */ +}; + + +/* alignment for memory blocks */ + +#define M_ALIGN (sizeof(long)) + +/* length of memory header, length of first field of memory header and + minimal size of a block left free (if we allocate memory and take a + block from the free list that is larger than needed, it must have at + least M_MIN extra bytes to be splitted; if it has, the rest is put on + the free list) */ + +#define M_HSIZE (sizeof(struct m_hdr)) +#define M_ISIZE (sizeof(long)) +#define M_MIN (2 * M_ISIZE) + +/* a pointer to the last free block, a pointer to the free list (the blocks + on this list are kept in order - lowest address first) */ + +static struct m_hdr *m_lfree, *m_free; + +/* system's pagesize */ + +static long m_pgsz = 0; + +/* the highest and the lowest valid memory addresses, kept for fast validity + checks in free() and to find out if and when we can give memory back to + the system */ + +static char *m_high, *m_low; + +/* Management of blocks for small blocks: + Such blocks are kept in lists (one list for each of the sizes that are + allocated in such blocks). The lists are stored in the m_small array. + M_SIDX() calculates the index into this array for a given size. M_SNUM + is the size (in small blocks) of such blocks. M_SLEN() calculates the + size of the small blocks held in a memory block, given a pointer to the + header of it. M_SBLEN() gives the size of a memory block that can hold + an array of small blocks, given the size of these small blocks. M_BSLEN() + caculates the size of the small blocks held in a memory block, given the + length of that block (including the header of the memory block. M_NSMALL + is the number of possible block sizes that small blocks should be used + for. */ + + +#define M_SIDX(S) ((S) / M_ISIZE) +#define M_SNUM 50 +#define M_SLEN(M) ((M)->len / M_SNUM) +#define M_SBLEN(S) ((S) * M_SNUM + sizeof(struct m_shdr *) + \ + sizeof(long) + sizeof(struct m_hdr *)) +#define M_BSLEN(S) (((S) - sizeof(struct m_shdr *) - \ + sizeof(long) - sizeof(struct m_hdr *)) / M_SNUM) +#define M_NSMALL 8 + +static struct m_hdr *m_small[M_NSMALL]; + +#ifdef ZSH_MEM_DEBUG + +static int m_s = 0, m_b = 0; +static int m_m[1025], m_f[1025]; + +static struct m_hdr *m_l; + +#endif /* ZSH_MEM_DEBUG */ + +MALLOC_RET_T +malloc(MALLOC_ARG_T size) +{ + struct m_hdr *m, *mp, *mt; + long n, s, os = 0; + struct heap *h, *hp, *hf = NULL, *hfp = NULL; + + /* some systems want malloc to return the highest valid address plus one + if it is called with an argument of zero */ + + if (!size) + return (MALLOC_RET_T) m_high; + + queue_signals(); /* just queue signals rather than handling them */ + + /* first call, get page size */ + + if (!m_pgsz) { + +#ifdef _SC_PAGESIZE + m_pgsz = sysconf(_SC_PAGESIZE); /* SVR4 */ +#else +# ifdef _SC_PAGE_SIZE + m_pgsz = sysconf(_SC_PAGE_SIZE); /* HPUX */ +# else + m_pgsz = getpagesize(); +# endif +#endif + + m_free = m_lfree = NULL; + } + size = (size + M_ALIGN - 1) & ~(M_ALIGN - 1); + + /* Do we need a small block? */ + + if ((s = M_SIDX(size)) && s < M_NSMALL) { + /* yep, find a memory block with free small blocks of the + appropriate size (if we find it in this list, this means that + it has room for at least one more small block) */ + for (mp = NULL, m = m_small[s]; m && !m->free; mp = m, m = m->next); + + if (m) { + /* we found one */ + struct m_shdr *sh = m->free; + + m->free = sh->next; + m->used++; + + /* if all small blocks in this block are allocated, the block is + put at the end of the list blocks wth small blocks of this + size (i.e., we try to keep blocks with free blocks at the + beginning of the list, to make the search faster */ + + if (m->used == M_SNUM && m->next) { + for (mt = m; mt->next; mt = mt->next); + + mt->next = m; + if (mp) + mp->next = m->next; + else + m_small[s] = m->next; + m->next = NULL; + } +#ifdef ZSH_MEM_DEBUG + m_m[size / M_ISIZE]++; +#endif + + unqueue_signals(); + return (MALLOC_RET_T) sh; + } + /* we still want a small block but there were no block with a free + small block of the requested size; so we use the real allocation + routine to allocate a block for small blocks of this size */ + os = size; + size = M_SBLEN(size); + } else + s = 0; + +/* search the free list for an block of at least the requested size */ + for (mp = NULL, m = m_free; m && m->len < size; mp = m, m = m->next); + + /* if there is an empty zsh heap at a lower address we steal it and take + the memory from it, putting the rest on the free list (remember + that the blocks on the free list are ordered) */ + + for (hp = NULL, h = heaps; h; hp = h, h = h->next) + if (!h->used && + (!hf || h < hf) && + (!m || ((char *)m) > ((char *)h))) + hf = h, hfp = hp; + + if (hf) { + /* we found such a heap */ + Heapstack hso, hsn; + + /* delete structures on the list holding the heap states */ + for (hso = hf->sp; hso; hso = hsn) { + hsn = hso->next; + zfree(hso, sizeof(*hso)); + } + /* take it from the list of heaps */ + if (hfp) + hfp->next = hf->next; + else + heaps = hf->next; + /* now we simply free it and than search the free list again */ + zfree(hf, HEAPSIZE); + + for (mp = NULL, m = m_free; m && m->len < size; mp = m, m = m->next); + } + if (!m) { + /* no matching free block was found, we have to request new + memory from the system */ + n = (size + M_HSIZE + m_pgsz - 1) & ~(m_pgsz - 1); + + if (((char *)(m = (struct m_hdr *)sbrk(n))) == ((char *)-1)) { + DPUTS(1, "MEM: allocation error at sbrk."); + unqueue_signals(); + return NULL; + } + /* set m_low, for the check in free() */ + if (!m_low) + m_low = (char *)m; + +#ifdef ZSH_MEM_DEBUG + m_s += n; + + if (!m_l) + m_l = m; +#endif + + /* save new highest address */ + m_high = ((char *)m) + n; + + /* initialize header */ + m->len = n - M_ISIZE; + m->next = NULL; + + /* put it on the free list and set m_lfree pointing to it */ + if ((mp = m_lfree)) + m_lfree->next = m; + m_lfree = m; + } + if ((n = m->len - size) > M_MIN) { + /* the block we want to use has more than M_MIN bytes plus the + number of bytes that were requested; we split it in two and + leave the rest on the free list */ + struct m_hdr *mtt = (struct m_hdr *)(((char *)m) + M_ISIZE + size); + + mtt->len = n - M_ISIZE; + mtt->next = m->next; + + m->len = size; + + /* put the rest on the list */ + if (m_lfree == m) + m_lfree = mtt; + + if (mp) + mp->next = mtt; + else + m_free = mtt; + } else if (mp) { + /* the block we found wasn't the first one on the free list */ + if (m == m_lfree) + m_lfree = mp; + mp->next = m->next; + } else { + /* it was the first one */ + m_free = m->next; + if (m == m_lfree) + m_lfree = m_free; + } + + if (s) { + /* we are allocating a block that should hold small blocks */ + struct m_shdr *sh, *shn; + + /* build the free list in this block and set `used' filed */ + m->free = sh = (struct m_shdr *)(((char *)m) + + sizeof(struct m_hdr) + os); + + for (n = M_SNUM - 2; n--; sh = shn) + shn = sh->next = sh + s; + sh->next = NULL; + + m->used = 1; + + /* put the block on the list of blocks holding small blocks if + this size */ + m->next = m_small[s]; + m_small[s] = m; + +#ifdef ZSH_MEM_DEBUG + m_m[os / M_ISIZE]++; +#endif + + unqueue_signals(); + return (MALLOC_RET_T) (((char *)m) + sizeof(struct m_hdr)); + } +#ifdef ZSH_MEM_DEBUG + m_m[m->len < (1024 * M_ISIZE) ? (m->len / M_ISIZE) : 1024]++; +#endif + + unqueue_signals(); + return (MALLOC_RET_T) & m->next; +} + +/* this is an internal free(); the second argument may, but need not hold + the size of the block the first argument is pointing to; if it is the + right size of this block, freeing it will be faster, though; the value + 0 for this parameter means: `don't know' */ + +/**/ +void +zfree(void *p, int sz) +{ + struct m_hdr *m = (struct m_hdr *)(((char *)p) - M_ISIZE), *mp, *mt = NULL; + int i; +# ifdef DEBUG + int osz = sz; +# endif + +#ifdef ZSH_SECURE_FREE + sz = 0; +#else + sz = (sz + M_ALIGN - 1) & ~(M_ALIGN - 1); +#endif + + if (!p) + return; + + /* first a simple check if the given address is valid */ + if (((char *)p) < m_low || ((char *)p) > m_high || + ((long)p) & (M_ALIGN - 1)) { + DPUTS(1, "BUG: attempt to free storage at invalid address"); + return; + } + + queue_signals(); + + fr_rec: + + if ((i = sz / M_ISIZE) < M_NSMALL || !sz) + /* if the given sizes says that it is a small block, find the + memory block holding it; we search all blocks with blocks + of at least the given size; if the size parameter is zero, + this means, that all blocks are searched */ + for (; i < M_NSMALL; i++) { + for (mp = NULL, mt = m_small[i]; + mt && (((char *)mt) > ((char *)p) || + (((char *)mt) + mt->len) < ((char *)p)); + mp = mt, mt = mt->next); + + if (mt) { + /* we found the block holding the small block */ + struct m_shdr *sh = (struct m_shdr *)p; + +#ifdef ZSH_SECURE_FREE + struct m_shdr *sh2; + + /* check if the given address is equal to the address of + the first small block plus an integer multiple of the + block size */ + if ((((char *)p) - (((char *)mt) + sizeof(struct m_hdr))) % + M_BSLEN(mt->len)) { + + DPUTS(1, "BUG: attempt to free storage at invalid address"); + unqueue_signals(); + return; + } + /* check, if the address is on the (block-intern) free list */ + for (sh2 = mt->free; sh2; sh2 = sh2->next) + if (((char *)p) == ((char *)sh2)) { + + DPUTS(1, "BUG: attempt to free already free storage"); + unqueue_signals(); + return; + } +#endif + DPUTS(M_BSLEN(mt->len) < osz, + "BUG: attempt to free more than allocated."); + +#ifdef ZSH_MEM_DEBUG + m_f[M_BSLEN(mt->len) / M_ISIZE]++; + memset(sh, 0xff, M_BSLEN(mt->len)); +#endif + + /* put the block onto the free list */ + sh->next = mt->free; + mt->free = sh; + + if (--mt->used) { + /* if there are still used blocks in this block, we + put it at the beginning of the list with blocks + holding small blocks of the same size (since we + know that there is at least one free block in it, + this will make allocation of small blocks faster; + it also guarantees that long living memory blocks + are preferred over younger ones */ + if (mp) { + mp->next = mt->next; + mt->next = m_small[i]; + m_small[i] = mt; + } + unqueue_signals(); + return; + } + /* if there are no more used small blocks in this + block, we free the whole block */ + if (mp) + mp->next = mt->next; + else + m_small[i] = mt->next; + + m = mt; + p = (void *) & m->next; + + break; + } else if (sz) { + /* if we didn't find a block and a size was given, try it + again as if no size were given */ + sz = 0; + goto fr_rec; + } + } +#ifdef ZSH_MEM_DEBUG + if (!mt) + m_f[m->len < (1024 * M_ISIZE) ? (m->len / M_ISIZE) : 1024]++; +#endif + +#ifdef ZSH_SECURE_FREE + /* search all memory blocks, if one of them is at the given address */ + for (mt = (struct m_hdr *)m_low; + ((char *)mt) < m_high; + mt = (struct m_hdr *)(((char *)mt) + M_ISIZE + mt->len)) + if (((char *)p) == ((char *)&mt->next)) + break; + + /* no block was found at the given address */ + if (((char *)mt) >= m_high) { + DPUTS(1, "BUG: attempt to free storage at invalid address"); + unqueue_signals(); + return; + } +#endif + + /* see if the block is on the free list */ + for (mp = NULL, mt = m_free; mt && mt < m; mp = mt, mt = mt->next); + + if (m == mt) { + /* it is, ouch! */ + DPUTS(1, "BUG: attempt to free already free storage"); + unqueue_signals(); + return; + } + DPUTS(m->len < osz, "BUG: attempt to free more than allocated"); +#ifdef ZSH_MEM_DEBUG + memset(p, 0xff, m->len); +#endif + if (mt && ((char *)mt) == (((char *)m) + M_ISIZE + m->len)) { + /* the block after the one we are freeing is free, we put them + together */ + m->len += mt->len + M_ISIZE; + m->next = mt->next; + + if (mt == m_lfree) + m_lfree = m; + } else + m->next = mt; + + if (mp && ((char *)m) == (((char *)mp) + M_ISIZE + mp->len)) { + /* the block before the one we are freeing is free, we put them + together */ + mp->len += m->len + M_ISIZE; + mp->next = m->next; + + if (m == m_lfree) + m_lfree = mp; + } else if (mp) + /* otherwise, we just put it on the free list */ + mp->next = m; + else { + m_free = m; + if (!m_lfree) + m_lfree = m_free; + } + + /* if the block we have just freed was at the end of the process heap + and now there is more than one page size of memory, we can give + it back to the system (and we do it ;-) */ + if ((((char *)m_lfree) + M_ISIZE + m_lfree->len) == m_high && + m_lfree->len >= m_pgsz + M_MIN) { + long n = (m_lfree->len - M_MIN) & ~(m_pgsz - 1); + + m_lfree->len -= n; + if (brk(m_high -= n) == -1) + DPUTS(1, "MEM: allocation error at brk."); + +#ifdef ZSH_MEM_DEBUG + m_b += n; +#endif + } + unqueue_signals(); +} + +FREE_RET_T +free(FREE_ARG_T p) +{ + zfree(p, 0); /* 0 means: size is unknown */ + +#ifdef FREE_DO_RET + return 0; +#endif +} + +/* this one is for strings (and only strings, real strings, real C strings, + those that have a zero byte at the end) */ + +/**/ +void +zsfree(char *p) +{ + if (p) + zfree(p, strlen(p) + 1); +} + +MALLOC_RET_T +realloc(MALLOC_RET_T p, MALLOC_ARG_T size) +{ + struct m_hdr *m = (struct m_hdr *)(((char *)p) - M_ISIZE), *mp, *mt; + char *r; + int i, l = 0; + + /* some system..., see above */ + if (!p && size) + return (MALLOC_RET_T) malloc(size); + /* and some systems even do this... */ + if (!p || !size) + return (MALLOC_RET_T) p; + + queue_signals(); /* just queue signals caught rather than handling them */ + + /* check if we are reallocating a small block, if we do, we have + to compute the size of the block from the sort of block it is in */ + for (i = 0; i < M_NSMALL; i++) { + for (mp = NULL, mt = m_small[i]; + mt && (((char *)mt) > ((char *)p) || + (((char *)mt) + mt->len) < ((char *)p)); + mp = mt, mt = mt->next); + + if (mt) { + l = M_BSLEN(mt->len); + break; + } + } + if (!l) + /* otherwise the size of the block is in the memory just before + the given address */ + l = m->len; + + /* now allocate the new block, copy the old contents, and free the + old block */ + r = malloc(size); + memcpy(r, (char *)p, (size > l) ? l : size); + free(p); + + unqueue_signals(); + return (MALLOC_RET_T) r; +} + +MALLOC_RET_T +calloc(MALLOC_ARG_T n, MALLOC_ARG_T size) +{ + long l; + char *r; + + if (!(l = n * size)) + return (MALLOC_RET_T) m_high; + + r = malloc(l); + + memset(r, 0, l); + + return (MALLOC_RET_T) r; +} + +#ifdef ZSH_MEM_DEBUG + +/**/ +int +bin_mem(char *name, char **argv, char *ops, int func) +{ + int i, ii, fi, ui, j; + struct m_hdr *m, *mf, *ms; + char *b, *c, buf[40]; + long u = 0, f = 0; + + if (ops['v']) { + printf("The lower and the upper addresses of the heap. Diff gives\n"); + printf("the difference between them, i.e. the size of the heap.\n\n"); + } + printf("low mem %ld\t high mem %ld\t diff %ld\n", + (long)m_l, (long)m_high, (long)(m_high - ((char *)m_l))); + + if (ops['v']) { + printf("\nThe number of bytes that were allocated using sbrk() and\n"); + printf("the number of bytes that were given back to the system\n"); + printf("via brk().\n"); + } + printf("\nsbrk %d\tbrk %d\n", m_s, m_b); + + if (ops['v']) { + printf("\nInformation about the sizes that were allocated or freed.\n"); + printf("For each size that were used the number of mallocs and\n"); + printf("frees is shown. Diff gives the difference between these\n"); + printf("values, i.e. the number of blocks of that size that is\n"); + printf("currently allocated. Total is the product of size and diff,\n"); + printf("i.e. the number of bytes that are allocated for blocks of\n"); + printf("this size.\n"); + } + printf("\nsize\tmalloc\tfree\tdiff\ttotal\n"); + for (i = 0; i < 1024; i++) + if (m_m[i] || m_f[i]) + printf("%ld\t%d\t%d\t%d\t%ld\n", (long)i * M_ISIZE, m_m[i], m_f[i], + m_m[i] - m_f[i], (long)i * M_ISIZE * (m_m[i] - m_f[i])); + + if (m_m[i] || m_f[i]) + printf("big\t%d\t%d\t%d\n", m_m[i], m_f[i], m_m[i] - m_f[i]); + + if (ops['v']) { + printf("\nThe list of memory blocks. For each block the following\n"); + printf("information is shown:\n\n"); + printf("num\tthe number of this block\n"); + printf("tnum\tlike num but counted separatedly for used and free\n"); + printf("\tblocks\n"); + printf("addr\tthe address of this block\n"); + printf("len\tthe length of the block\n"); + printf("state\tthe state of this block, this can be:\n"); + printf("\t used\tthis block is used for one big block\n"); + printf("\t free\tthis block is free\n"); + printf("\t small\tthis block is used for an array of small blocks\n"); + printf("cum\tthe accumulated sizes of the blocks, counted\n"); + printf("\tseparatedly for used and free blocks\n"); + printf("\nFor blocks holding small blocks the number of free\n"); + printf("blocks, the number of used blocks and the size of the\n"); + printf("blocks is shown. For otherwise used blocks the first few\n"); + printf("bytes are shown as an ASCII dump.\n"); + } + printf("\nblock list:\nnum\ttnum\taddr\tlen\tstate\tcum\n"); + for (m = m_l, mf = m_free, ii = fi = ui = 1; ((char *)m) < m_high; + m = (struct m_hdr *)(((char *)m) + M_ISIZE + m->len), ii++) { + for (j = 0, ms = NULL; j < M_NSMALL && !ms; j++) + for (ms = m_small[j]; ms; ms = ms->next) + if (ms == m) + break; + + if (m == mf) + buf[0] = '\0'; + else if (m == ms) + sprintf(buf, "%ld %ld %ld", M_SNUM - ms->used, ms->used, + (m->len - sizeof(struct m_hdr)) / M_SNUM + 1); + + else { + for (i = 0, b = buf, c = (char *)&m->next; i < 20 && i < m->len; + i++, c++) + *b++ = (*c >= ' ' && *c < 127) ? *c : '.'; + *b = '\0'; + } + + printf("%d\t%d\t%ld\t%ld\t%s\t%ld\t%s\n", ii, + (m == mf) ? fi++ : ui++, + (long)m, m->len, + (m == mf) ? "free" : ((m == ms) ? "small" : "used"), + (m == mf) ? (f += m->len) : (u += m->len), + buf); + + if (m == mf) + mf = mf->next; + } + + if (ops['v']) { + printf("\nHere is some information about the small blocks used.\n"); + printf("For each size the arrays with the number of free and the\n"); + printf("number of used blocks are shown.\n"); + } + printf("\nsmall blocks:\nsize\tblocks (free/used)\n"); + + for (i = 0; i < M_NSMALL; i++) + if (m_small[i]) { + printf("%ld\t", (long)i * M_ISIZE); + + for (ii = 0, m = m_small[i]; m; m = m->next) { + printf("(%ld/%ld) ", M_SNUM - m->used, m->used); + if (!((++ii) & 7)) + printf("\n\t"); + } + putchar('\n'); + } + if (ops['v']) { + printf("\n\nBelow is some information about the allocation\n"); + printf("behaviour of the zsh heaps. First the number of times\n"); + printf("pushheap(), popheap(), and freeheap() were called.\n"); + } + printf("\nzsh heaps:\n\n"); + + printf("push %d\tpop %d\tfree %d\n\n", h_push, h_pop, h_free); + + if (ops['v']) { + printf("\nThe next list shows for several sizes the number of times\n"); + printf("memory of this size were taken from heaps.\n\n"); + } + printf("size\tmalloc\ttotal\n"); + for (i = 0; i < 1024; i++) + if (h_m[i]) + printf("%ld\t%d\t%ld\n", (long)i * H_ISIZE, h_m[i], + (long)i * H_ISIZE * h_m[i]); + if (h_m[1024]) + printf("big\t%d\n", h_m[1024]); + + return 0; +} + +#endif + +#else /* not ZSH_MEM */ + +/**/ +void +zfree(void *p, int sz) +{ + if (p) + free(p); +} + +/**/ +void +zsfree(char *p) +{ + if (p) + free(p); +} + +#endif diff --git a/Src/mkbltnmlst.sh b/Src/mkbltnmlst.sh new file mode 100644 index 000000000..4a90ecd20 --- /dev/null +++ b/Src/mkbltnmlst.sh @@ -0,0 +1,60 @@ +#! /bin/sh +# +# mkbltnmlst.sh: generate boot code for linked-in modules +# +# Written by Andrew Main +# +srcdir=${srcdir-`echo $0|sed 's%/[^/][^/]*$%%'`} +test "x$srcdir" = "x$0" && srcdir=. +test "x$srcdir" = "x" && srcdir=. +MODBINS=${MODBINS-modules-bltin} +XMODCF=${XMODCF-$srcdir/xmods.conf} + +bin_mods=" zsh "`sed 's/^/ /;s/$/ /' $MODBINS` +x_mods=`cat $XMODCF` +. ./modules.index + +trap "rm -f $1; exit 1" 1 2 15 + +exec > $1 + +echo "#ifdef DYNAMIC" +for x_mod in $x_mods; do + case $bin_mods in + *" $x_mod "*) ;; + *) echo "/* non-linked-in known module \`$x_mod' */" + eval "loc=\$loc_$x_mod" + unset moddeps autobins + . $srcdir/../$loc/${x_mod}.mdd + for bin in $autobins; do + echo " add_autobin(\"$bin\", \"$x_mod\");" + done + for dep in $moddeps; do + case $bin_mods in + *" $dep "*) + echo " /* depends on \`$dep' */" ;; + *) echo " add_dep(\"$x_mod\", \"$dep\");" ;; + esac + done ;; + esac +done +echo "#endif /* DYNAMIC */" +echo +done_mods=" " +for bin_mod in $bin_mods; do + echo "/* linked-in module \`$bin_mod' */" + eval "loc=\$loc_$bin_mod" + unset moddeps + . $srcdir/../$loc/${bin_mod}.mdd + for dep in $moddeps; do + case $done_mods in + *" $dep "*) + echo " /* depends on \`$dep' */" ;; + *) echo >&2 "ERROR: linked-in module \`$bin_mod' depends on \`$dep'" + rm -f $1 + exit 1 ;; + esac + done + echo " mod.nam = \"$bin_mod\"; boot_$bin_mod(&mod);" + done_mods="$done_mods$bin_mod " +done diff --git a/Src/mkmakemod.sh b/Src/mkmakemod.sh new file mode 100644 index 000000000..b088929fa --- /dev/null +++ b/Src/mkmakemod.sh @@ -0,0 +1,315 @@ +#!/bin/sh +# +# mkmakemod.sh: generate Makefile.in files for module building +# +# Options: +# -m = file is already generated; only build the second stage +# -i = do not build second stage +# +# Args: +# $1 = subdirectory to look in, relative to $top_srcdir +# $2 = final output filename, within the $1 directory +# +# This script must be run from the top-level build directory, and $top_srcdir +# must be set correctly in the environment. +# +# This looks in $1, and uses all the *.mdd files there. Each .mdd file +# defines one module. The .mdd file is actually a shell script, which will +# be sourced. It may define the following shell variables: +# +# moddeps modules on which this module depends (default none) +# nozshdep non-empty indicates no dependence on the `zsh' pseudo-module +# alwayslink if non-empty, always link the module into the executable +# autobins builtins defined by the module, for autoloading +# objects .o files making up this module (*must* be defined) +# proto .pro files for this module (default generated from $objects) +# headers extra headers for this module (default none) +# hdrdeps extra headers on which the .mdh depends (default none) +# otherincs extra headers that are included indirectly (default none) +# +# The .mdd file may also include a Makefile.in fragment between lines +# `:<<\Make' and `Make' -- this will be copied into Makemod.in. +# +# The resulting Makemod.in knows how to build each module that is defined. +# For each module in also knows how to build a .mdh file. Each source file +# should #include the .mdh file for the module it is a part of. The .mdh +# file #includes the .mdh files for any module dependencies, then each of +# $headers, and then each of $proto (for global declarations). It will +# be recreated if any of the dependency .mdh files changes, or if any of +# $headers or $hdrdeps changes. When anything depends on it, all of $proto +# and $otherincs will be made up to date, but the .mdh file won't actually +# be rebuilt if those files change. +# +# The order of sections of the output file is thus: +# simple generated macros +# macros generated from *.mdd +# included Makemod.in.in +# rules generated from *.mdd +# The order dependencies are basically that the generated macros are required +# in Makemod.in.in, but some of the macros that it creates are needed in the +# later rules. +# + +# sed script to normalise a pathname +sed_normalise=' + s,^,/, + s,$,/, + :1 + s,/\./,/, + t1 + :2 + s,/[^/.][^/]*/\.\./,/, + s,/\.[^/.][^/]*/\.\./,/, + s,/\.\.[^/][^/]*/\.\./,/, + t2 + s,^/$,., + s,^/,, + s,\(.\)/$,\1, +' + +# decide which stages to process +first_stage=true +second_stage=true +if test ."$1" = .-m; then + shift + first_stage=false +elif test ."$1" = .-i; then + shift + second_stage=false +fi + +top_srcdir=`echo $top_srcdir | sed "$sed_normalise"` +the_subdir=$1 +the_makefile=$2 + +if $first_stage; then + + trap "rm -f $the_subdir/${the_makefile}.in" 1 2 15 + echo "creating $the_subdir/${the_makefile}.in" + exec 3>&1 >$the_subdir/${the_makefile}.in + echo "##### ${the_makefile}.in generated automatically by mkmakemod.sh" + echo "##### DO NOT EDIT!" + echo + echo "##### ===== DEFINITIONS ===== #####" + echo + echo "makefile = ${the_makefile}" + echo "dir_top = "`echo $the_subdir | sed 's,[^/][^/]*,..,g'` + echo "subdir = $the_subdir" + echo + + . Src/modules.index + bin_mods=" zsh "`sed 's/^/ /;s/$/ /' Src/modules-bltin` + if grep '%@D@%D%' config.status >/dev/null; then + is_dynamic=true + else + is_dynamic=false + fi + + here_modules= + all_subdirs= + all_modobjs= + all_modules= + all_mdds= + all_mdhs= + all_proto= + lastsub=// + for module in $module_list; do + eval "loc=\$loc_$module" + case $loc in + $the_subdir) + here_modules="$here_modules $module" + build=$is_dynamic + case $is_dynamic@$bin_mods in + *" $module "*) + build=true + all_modobjs="$all_modobjs modobjs.${module}" ;; + true@*) + all_modules="$all_modules ${module}.\$(DL_EXT)" ;; + esac + all_mdds="$all_mdds ${module}.mdd" + $build && all_mdhs="$all_mdhs ${module}.mdh" + $build && all_proto="$all_proto proto.${module}" + ;; + $lastsub | $lastsub/*) ;; + $the_subdir/*) + all_subdirs="$all_subdirs $loc" + lastsub=$loc + ;; + esac + done + all_subdirs=`echo "$all_subdirs" | sed "s' $the_subdir/' 'g"` + echo "MODOBJS =$all_modobjs" + echo "MODULES =$all_modules" + echo "MDDS =$all_mdds" + echo "MDHS =$all_mdhs" + echo "PROTOS =$all_proto" + echo "SUBDIRS =$all_subdirs" + echo + + echo "##### ===== INCLUDING Makemod.in.in ===== #####" + echo + cat $top_srcdir/Src/Makemod.in.in + echo + + case $the_subdir in + Src) modobjs_sed= ;; + Src/*) modobjs_sed="| sed 's\" \" "`echo $the_subdir | sed 's,^Src/,,'`"/\"g' " ;; + *) modobjs_sed="| sed 's\" \" ../$the_subdir/\"g' " ;; + esac + + other_mdhs= + remote_mdhs= + for module in $here_modules; do + + unset moddeps nozshdep alwayslink + unset autobins + unset objects proto headers hdrdeps otherincs + . $top_srcdir/$the_subdir/${module}.mdd + test -n "${moddeps+set}" || moddeps= + test -n "$nozshdep" || moddeps="$moddeps zsh" + test -n "${proto+set}" || + proto=`echo $objects '' | sed 's,\.o ,.pro ,g'` + + dobjects=`echo $objects '' | sed 's,\.o ,..o ,g'` + modhdeps= + for dep in $moddeps; do + eval "loc=\$loc_$dep" + case $the_subdir in + $loc) + mdh="${dep}.mdh" + ;; + $loc/*) + mdh="\$(dir_top)/$loc/${dep}.mdh" + case "$other_mdhs " in + *" $mdh "*) ;; + *) other_mdhs="$other_mdhs $mdh" ;; + esac + ;; + *) + mdh="\$(dir_top)/$loc/${dep}.mdh" + case "$remote_mdhs " in + *" $mdh "*) ;; + *) remote_mdhs="$remote_mdhs $mdh" ;; + esac + ;; + esac + modhdeps="$modhdeps $mdh" + done + + echo "##### ===== DEPENDENCIES GENERATED FROM ${module}.mdd ===== #####" + echo + echo "MODOBJS_${module} = $objects" + echo "MODDOBJS_${module} = $dobjects" + echo "PROTO_${module} = $proto" + echo "INCS_${module} = \$(PROTO_${module}) $otherincs" + echo + echo "proto.${module}: \$(PROTO_${module})" + echo "\$(PROTO_${module}): \$(PROTODEPS)" + echo + echo "modobjs.${module}: \$(MODOBJS_${module})" + echo " echo '' \$(MODOBJS_${module}) $modobjs_sed>> \$(dir_src)/stamp-modobjs.tmp" + echo + if test -z "$alwayslink"; then + echo "${module}.\$(DL_EXT): \$(MODDOBJS_${module})" + echo ' rm -f $@' + echo " \$(DLLINK) \$(MODDOBJS_${module}) \$(LIBS)" + echo + fi + echo "${module}.mdhi: ${module}.mdhs \$(INCS_${module})" + echo " @test -f \$@ || echo 'do not delete this file' > \$@" + echo + echo "${module}.mdhs: ${module}.mdd" + echo " @\$(MAKE) -f \$(makefile) \$(MAKEDEFS) ${module}.mdh.tmp" + echo " @if cmp -s ${module}.mdh ${module}.mdh.tmp; then \\" + echo " rm -f ${module}.mdh.tmp; \\" + echo " echo \"\\\`${module}.mdh' is up to date.\"; \\" + echo " else \\" + echo " mv -f ${module}.mdh.tmp ${module}.mdh; \\" + echo " echo \"Updated \\\`${module}.mdh'.\"; \\" + echo " fi" + echo " echo 'timestamp for ${module}.mdh against ${module}.mdd' > \$@" + echo + echo "${module}.mdh: ${modhdeps} ${headers} ${hdrdeps} ${module}.mdhi" + echo " @\$(MAKE) -f \$(makefile) \$(MAKEDEFS) ${module}.mdh.tmp" + echo " @mv -f ${module}.mdh.tmp ${module}.mdh" + echo " @echo \"Updated \\\`${module}.mdh'.\"" + echo + echo "${module}.mdh.tmp:" + echo " @( \\" + echo " echo '#ifndef have_${module}_module'; \\" + echo " echo '#define have_${module}_module'; \\" + echo " echo; \\" + if test -n "$moddeps"; then + echo " echo '/* Module dependencies */'; \\" + echo " for mod in $modhdeps; do \\" + echo " echo '# define USING_MODULE'; \\" + echo " echo '# include \"'\$\$mod'\"'; \\" + echo " done; \\" + echo " echo '# undef USING_MODULE'; \\" + echo " echo; \\" + fi + if test -n "$headers"; then + echo " echo '/* Extra headers for this module */'; \\" + echo " for hdr in $headers; do \\" + echo " if test -f \$\$hdr; then \\" + echo " echo '# include \"'\$\$hdr'\"'; \\" + echo " else \\" + echo " echo '# include \"\$(sdir)/'\$\$hdr'\"'; \\" + echo " fi; \\" + echo " done; \\" + echo " echo; \\" + fi + if test -n "$proto"; then + echo " echo '# define GLOBAL_PROTOTYPES'; \\" + echo " for pro in \$(PROTO_${module}); do \\" + echo " echo '# include \"'\$\$pro'\"'; \\" + echo " done; \\" + echo " echo '# undef GLOBAL_PROTOTYPES'; \\" + echo " echo; \\" + fi + echo " echo '#endif /* !have_${module}_module */'; \\" + echo " ) > \$@" + echo + echo "\$(MODOBJS_${module}) \$(MODDOBJS_${module}): ${module}.mdh" + sed -e '/^ *: *<< *\\Make *$/,/^Make$/!d' \ + -e 's/^ *: *<< *\\Make *$//; /^Make$/d' \ + < $top_srcdir/$the_subdir/${module}.mdd + echo + + done + + if test -n "$remote_mdhs$other_mdhs"; then + echo "##### ===== DEPENDENCIES FOR REMOTE MODULES ===== #####" + echo + for mdh in $remote_mdhs; do + echo "$mdh: FORCE" + echo " @cd @%@ && \$(MAKE) \$(MAKEDEFS) @%@$mdh" + echo + done | sed 's,^\(.*\)@%@\(.*\)@%@\(.*\)/\([^/]*\)$,\1\3\2\4,' + if test -n "$other_mdhs"; then + echo "${other_mdhs}:" + echo " false # should only happen with make -n" + echo + fi + fi + + echo "##### End of ${the_makefile}.in" + + exec >&3 3>&- + +fi + +if $second_stage; then + + trap "rm -f $the_subdir/${the_makefile}" 1 2 15 + + # The standard config.status requires the pathname for the .in file to + # be relative to the top of the source tree. As we have it in the build + # tree, this is a problem. zsh's configure script edits config.status, + # adding the feature that an input filename starting with "!" has the + # "!" removed and is not mangled further. + CONFIG_FILES=$the_subdir/${the_makefile}:\!$the_subdir/${the_makefile}.in CONFIG_HEADERS= ./config.status + +fi + +exit 0 diff --git a/Src/mkmodindex.sh b/Src/mkmodindex.sh new file mode 100644 index 000000000..b4616f638 --- /dev/null +++ b/Src/mkmodindex.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# +# mkmodindex.sh: search for *.mdd files, and index the modules +# +# $@ = directories to search from +# + +echo "# module index generated by mkmodindex.sh" +echo + +module_list=' ' +while test $# -ne 0; do + dir=$1 + shift + ( set $dir/*.mdd; test -f $1 ) || continue + dosubs=false + for mod in `echo '' $dir/*.mdd '' | sed 's, [^ ]*/, ,g;s,\.mdd , ,g'`; do + case `echo "$mod@ $module_list " | sed 's,^.*[^_0-9A-Za-z].*@,@@,'` in + @@*) + echo >&2 "WARNING: illegally named module \`$mod' in $dir" + echo >&2 " (ignoring it)" + ;; + *@*" $mod "*) + eval "loc=\$loc_$mod" + echo >&2 "WARNING: module \`$mod' (in $loc) duplicated in $dir" + echo >&2 " (ignoring duplicate)" + dosubs=true + ;; + *) + module_list="$module_list$mod " + echo "loc_$mod=$dir" + eval "loc_$mod=\$dir" + dosubs=true + ;; + esac + done + $dosubs && set `echo $dir/*/. '' | sed 's,/\. , ,g'` "$@" +done + +echo +echo $module_list | sed 's/^/module_list="/;s/$/"/' + +exit 0 diff --git a/Src/modentry.c b/Src/modentry.c new file mode 100644 index 000000000..63c4b825d --- /dev/null +++ b/Src/modentry.c @@ -0,0 +1,15 @@ +#include "zsh.mdh" + +int boot_ _((Module)); +int cleanup_ _((Module)); +int modentry _((int boot, Module m)); + +/**/ +int +modentry(int boot, Module m) +{ + if (boot) + return boot_(m); + else + return cleanup_(m); +} diff --git a/Src/module.c b/Src/module.c new file mode 100644 index 000000000..ce5989f07 --- /dev/null +++ b/Src/module.c @@ -0,0 +1,651 @@ +/* + * module.c - deal with dynamic modules + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1996-1997 Zoltán Hidvégi + * 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 Zoltán Hidvégi 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 Zoltán Hidvégi and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Zoltán Hidvégi 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 Zoltán Hidvégi and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zsh.mdh" +#include "module.pro" + +/* The `zsh' module contains all the base code that can't actually be built * + * as a separate module. It is initialised by main(), so there's nothing * + * for the boot function to do. */ + +/**/ +int +boot_zsh(Module m) +{ + return 0; +} + +/* addbuiltin() can be used to add a new builtin. It returns zero on * + * success, 1 on failure. The only possible type of failure is that * + * a builtin with the specified name already exists. An autoloaded * + * builtin can be replaced using this function. */ + +/**/ +int +addbuiltin(Builtin b) +{ + Builtin bn = (Builtin) builtintab->getnode2(builtintab, b->nam); + if (bn && (bn->flags & BINF_ADDED)) + return 1; + if (bn) + builtintab->freenode(builtintab->removenode(builtintab, b->nam)); + PERMALLOC { + builtintab->addnode(builtintab, b->nam, b); + } LASTALLOC; + return 0; +} + +/* Add multiple builtins. binl points to a table of `size' builtin * + * structures. Those for which (.flags & BINF_ADDED) is false are to be * + * added; that flag is set if they succeed. If any fail, an error * + * message is printed, using nam as the leading name. Returns 1 if all * + * additions succeed, 2 if some succeed and some fail, and 0 if all (and * + * at least 1) fail. The usual usage in a boot_*() function would be * + * return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); */ + +/**/ +int +addbuiltins(char const *nam, Builtin binl, int size) +{ + int hads = 0, hadf = 0, n; + + for(n = 0; n < size; n++) { + Builtin b = &binl[n]; + if(b->flags & BINF_ADDED) + continue; + if(addbuiltin(b)) { + zwarnnam(nam, "name clash when adding builtin `%s'", b->nam, 0); + hadf = 1; + } else { + b->flags |= BINF_ADDED; + hads = 2; + } + } + return hadf ? hads : 1; +} + +#ifdef DYNAMIC + +/* $module_path ($MODULE_PATH) */ + +/**/ +char **module_path; + +/* List of modules */ + +/**/ +LinkList modules; + +/* Define an autoloadable builtin. It returns 0 on success, or 1 on * + * failure. The only possible cause of failure is that a builtin * + * with the specified name already exists. */ + +/**/ +int +add_autobin(char *nam, char *module) +{ + Builtin bn = zcalloc(sizeof(*bn)); + bn->nam = ztrdup(nam); + bn->optstr = ztrdup(module); + return addbuiltin(bn); +} + +/* Remove the builtin added previously by addbuiltin(). Returns * + * zero on succes and -1 if there is no builtin with that name. */ + +/**/ +int +deletebuiltin(char *nam) +{ + Builtin bn; + + bn = (Builtin) builtintab->removenode(builtintab, nam); + if (!bn) + return -1; + builtintab->freenode((HashNode)bn); + return 0; +} + +/* Delete multiple builtins. binl points to a table of `size' builtin * + * structures. Those for which (.flags & BINF_ADDED) is true are to be * + * deleted; that flag is cleared. If any fail, an error message is * + * printed, using nam as the leading name. Returns 1 if all deletions * + * succeed, 2 if some succeed and some fail, and 0 if all (and at least * + * 1) fail. In normal use, from a cleanup_*() function, this return * + * value would be ignored -- the only cause of failure would be that a * + * wayward module had deleted our builtin without telling us. */ + +/**/ +int +deletebuiltins(char const *nam, Builtin binl, int size) +{ + int hads = 0, hadf = 0, n; + + for(n = 0; n < size; n++) { + Builtin b = &binl[n]; + if(!(b->flags & BINF_ADDED)) + continue; + if(deletebuiltin(b->nam)) { + zwarnnam(nam, "builtin `%s' already deleted", b->nam, 0); + hadf = 1; + } else + hads = 2; + b->flags &= ~BINF_ADDED; + } + return hadf ? hads : 1; +} + +#ifdef HAVE_DLFCN_H +# include <dlfcn.h> +#else +# include <sys/types.h> +# include <nlist.h> +# include <link.h> +#endif +#ifndef RTLD_LAZY +# define RTLD_LAZY 1 +#endif +#ifndef RTLD_GLOBAL +# define RTLD_GLOBAL 0 +#endif +#ifndef HAVE_DLCLOSE +# define dlclose(X) ((X), 0) +#endif + +#ifdef DLSYM_NEEDS_UNDERSCORE +# define STR_BOOT "_boot_" +# define STR_BOOT_S "_boot_%s" +# define STR_CLEANUP "_cleanup_" +# define STR_CLEANUP_S "_cleanup_%s" +#else /* !DLSYM_NEEDS_UNDERSCORE */ +# define STR_BOOT "boot_" +# define STR_BOOT_S "boot_%s" +# define STR_CLEANUP "cleanup_" +# define STR_CLEANUP_S "cleanup_%s" +#endif /* !DLSYM_NEEDS_UNDERSCORE */ +typedef int (*Module_func) _((Module)); + +/**/ +static void * +try_load_module(char const *name) +{ + char buf[PATH_MAX + 1]; + char **pp; + void *ret = NULL; + int l; + + if (strchr(name, '/')) { + ret = dlopen(unmeta(name), RTLD_LAZY | RTLD_GLOBAL); + if (ret || + unset(PATHDIRS) || + (*name == '/') || + (*name == '.' && name[1] == '/') || + (*name == '.' && name[1] == '.' && name[2] == '/')) + return ret; + } + + l = strlen(name) + 1; + for (pp = module_path; !ret && *pp; pp++) { + if (l + (**pp ? strlen(*pp) : 1) > PATH_MAX) + continue; + sprintf(buf, "%s/%s", **pp ? *pp : ".", name); + ret = dlopen(unmeta(buf), RTLD_LAZY | RTLD_GLOBAL); + } + + return ret; +} + +/**/ +static void * +do_load_module(char const *name) +{ + void *ret = NULL; + char buf[PATH_MAX + 1]; + + if (strlen(name) + strlen(DL_EXT) < PATH_MAX) { + sprintf(buf, "%s.%s", name, DL_EXT); + ret = try_load_module(buf); + } + if (!ret) + ret = try_load_module(name); + if (!ret) { + int waserr = errflag; + zerr("failed to load module: %s", name, 0); + errflag = waserr; + } + return ret; +} + +/**/ +static LinkNode +find_module(const char *name) +{ + Module m; + LinkNode node; + + for (node = firstnode(modules); node; incnode(node)) { + m = (Module) getdata(node); + if (!strcmp(m->nam, name)) + return node; + } + return NULL; +} + +/**/ +static int +init_module(Module m) +{ + char *s, *t; +#ifndef DYNAMIC_NAME_CLASH_OK + char buf[PATH_MAX + 1]; +#endif + Module_func fn; + + s = strrchr(m->nam, '/'); + if (s) + s = dupstring(++s); + else + s = m->nam; + if ((t = strrchr(s, '.'))) + *t = '\0'; +#ifdef DYNAMIC_NAME_CLASH_OK + fn = (Module_func) dlsym(m->handle, STR_BOOT); +#else /* !DYNAMIC_NAME_CLASH_OK */ + if (strlen(s) + 6 > PATH_MAX) + return 1; + sprintf(buf, STR_BOOT_S, s); + fn = (Module_func) dlsym(m->handle, buf); +#endif /* !DYNAMIC_NAME_CLASH_OK */ + if(fn) + return fn(m); + zwarnnam(m->nam, "no boot function", NULL, 0); + return 1; +} + +/**/ +Module +load_module(char const *name) +{ + Module m; + void *handle; + LinkNode node, n; + + if (!(node = find_module(name))) { + if (!(handle = do_load_module(name))) + return NULL; + m = zcalloc(sizeof(*m)); + m->nam = ztrdup(name); + m->handle = handle; + if (init_module(m)) { + dlclose(handle); + zsfree(m->nam); + zfree(m, sizeof(*m)); + return NULL; + } + PERMALLOC { + addlinknode(modules, m); + } LASTALLOC; + return m; + } + m = (Module) getdata(node); + if (m->handle) + return m; + if (m->flags & MOD_BUSY) { + zerr("circular dependencies for module %s", name, 0); + return NULL; + } + m->flags |= MOD_BUSY; + for (n = firstnode(m->deps); n; incnode(n)) + if (!load_module((char *) getdata(n))) { + m->flags &= ~MOD_BUSY; + return NULL; + } + m->flags &= ~MOD_BUSY; + if (!(m->handle = do_load_module(name))) + return NULL; + if (init_module(m)) { + dlclose(m->handle); + m->handle = NULL; + return NULL; + } + return m; +} + +/**/ +static int +cleanup_module(Module m) +{ + char *s, *t; +#ifndef DYNAMIC_NAME_CLASH_OK + char buf[PATH_MAX + 1]; +#endif + Module_func fn; + + s = strrchr(m->nam, '/'); + if (s) + s = dupstring(++s); + else + s = m->nam; + if ((t = strrchr(s, '.'))) + *t = '\0'; +#ifdef DYNAMIC_NAME_CLASH_OK + fn = (Module_func) dlsym(m->handle, STR_CLEANUP); +#else /* !DYNAMIC_NAME_CLASH_OK */ + if (strlen(s) + 9 > PATH_MAX) + return 1; + sprintf(buf, STR_CLEANUP_S, s); + fn = (Module_func) dlsym(m->handle, buf); +#endif /* !DYNAMIC_NAME_CLASH_OK */ + if(fn) + return fn(m); + zwarnnam(m->nam, "no cleanup function", NULL, 0); + return 1; +} + +/**/ +void +add_dep(char *name, char *from) +{ + LinkNode node; + Module m; + + PERMALLOC { + if (!(node = find_module(name))) { + m = zcalloc(sizeof(*m)); + m->nam = ztrdup(name); + addlinknode(modules, m); + } else + m = (Module) getdata(node); + if (!m->deps) + m->deps = newlinklist(); + for (node = firstnode(m->deps); + node && strcmp((char *) getdata(node), from); + incnode(node)); + if (!node) + addlinknode(m->deps, ztrdup(from)); + } LASTALLOC; +} + +/**/ +static void +autoloadscan(HashNode hn, int printflags) +{ + Builtin bn = (Builtin) hn; + + if(bn->flags & BINF_ADDED) + return; + if(printflags & PRINT_LIST) { + fputs("zmodload -a ", stdout); + if(bn->optstr[0] == '-') + fputs("-- ", stdout); + quotedzputs(bn->optstr, stdout); + if(strcmp(bn->nam, bn->optstr)) { + putchar(' '); + quotedzputs(bn->nam, stdout); + } + } else { + nicezputs(bn->nam, stdout); + if(strcmp(bn->nam, bn->optstr)) { + fputs(" (", stdout); + nicezputs(bn->optstr, stdout); + putchar(')'); + } + } + putchar('\n'); +} + +/**/ +int +bin_zmodload(char *nam, char **args, char *ops, int func) +{ + if(ops['d'] && ops['a']) { + zwarnnam(nam, "-d cannot be combined with -a", NULL, 0); + return 1; + } + if (ops['u'] && !*args) { + zwarnnam(nam, "what do you want to unload?", NULL, 0); + return 1; + } + if(ops['d']) + return bin_zmodload_dep(nam, args, ops); + else if(ops['a']) + return bin_zmodload_auto(nam, args, ops); + else + return bin_zmodload_load(nam, args, ops); +} + +/**/ +static int +bin_zmodload_dep(char *nam, char **args, char *ops) +{ + LinkNode node; + Module m; + if(ops['u']) { + /* remove dependencies */ + char *tnam = *args++; + node = find_module(tnam); + if (!node) + return 0; + m = (Module) getdata(node); + if(*args && m->deps) { + do { + for(node = firstnode(m->deps); node; incnode(node)) + if(!strcmp(*args, getdata(node))) { + zsfree(getdata(node)); + remnode(m->deps, node); + break; + } + } while(*++args); + if(empty(m->deps)) { + freelinklist(m->deps, freestr); + m->deps = NULL; + } + } else { + if (m->deps) { + freelinklist(m->deps, freestr); + m->deps = NULL; + } + } + if (!m->deps && !m->handle) { + remnode(modules, node); + zsfree(m->nam); + zfree(m, sizeof(*m)); + } + return 0; + } else if(!args[0] || !args[1]) { + /* list dependencies */ + for (node = firstnode(modules); node; incnode(node)) { + m = (Module) getdata(node); + if (m->deps && (!args[0] || !strcmp(args[0], m->nam))) { + LinkNode n; + if(ops['L']) { + printf("zmodload -d "); + if(m->nam[0] == '-') + fputs("-- ", stdout); + quotedzputs(m->nam, stdout); + } else { + nicezputs(m->nam, stdout); + putchar(':'); + } + for (n = firstnode(m->deps); n; incnode(n)) { + putchar(' '); + if(ops['L']) + quotedzputs((char *) getdata(n), stdout); + else + nicezputs((char *) getdata(n), stdout); + } + putchar('\n'); + } + } + return 0; + } else { + /* add dependencies */ + int ret = 0; + char *tnam = *args++; + + for(; *args; args++) { + if(isset(RESTRICTED) && strchr(*args, '/')) { + zwarnnam(nam, "%s: restricted", *args, 0); + ret = 1; + } else + add_dep(tnam, *args); + } + return ret; + } +} + +/**/ +static int +bin_zmodload_auto(char *nam, char **args, char *ops) +{ + int ret = 0; + if(ops['u']) { + /* remove autoloaded builtins */ + for (; *args; args++) { + Builtin bn = (Builtin) builtintab->getnode2(builtintab, *args); + if (!bn) { + if(!ops['i']) { + zwarnnam(nam, "%s: no such builtin", *args, 0); + ret = 1; + } + } else if (bn->flags & BINF_ADDED) { + zwarnnam(nam, "%s: builtin is already defined", *args, 0); + ret = 1; + } else + deletebuiltin(*args); + } + return ret; + } else if(!*args) { + /* list autoloaded builtins */ + scanhashtable(builtintab, 0, 0, 0, + autoloadscan, ops['L'] ? PRINT_LIST : 0); + return 0; + } else { + /* add autoloaded builtins */ + char *modnam; + modnam = *args++; + if(isset(RESTRICTED) && strchr(modnam, '/')) { + zwarnnam(nam, "%s: restricted", modnam, 0); + return 1; + } + do { + char *bnam = *args ? *args++ : modnam; + if (strchr(bnam, '/')) { + zwarnnam(nam, "%s: `/' is illegal in a builtin", bnam, 0); + ret = 1; + } else if (add_autobin(bnam, modnam) && !ops['i']) { + zwarnnam(nam, "failed to add builtin %s", bnam, 0); + ret = 1; + } + } while(*args); + return ret; + } +} + +/**/ +static int +bin_zmodload_load(char *nam, char **args, char *ops) +{ + LinkNode node; + Module m; + int ret = 0; + if(ops['u']) { + /* unload modules */ + for(; *args; args++) { + node = find_module(*args); + if (node) { + LinkNode mn, dn; + + for (mn = firstnode(modules); mn; incnode(mn)) { + m = (Module) getdata(mn); + if (m->deps && m->handle) + for (dn = firstnode(m->deps); dn; incnode(dn)) + if (!strcmp((char *) getdata(dn), *args)) { + zwarnnam(nam, "module %s is in use by another module and cannot be unloaded", *args, 0); + ret = 1; + goto cont; + } + } + + m = (Module) getdata(node); + if (m->handle && cleanup_module(m)) + ret = 1; + else { + if (m->handle) + dlclose(m->handle); + m->handle = NULL; + if(!m->deps) { + remnode(modules, node); + zsfree(m->nam); + zfree(m, sizeof(*m)); + } + } + } else if (!ops['i']) { + zwarnnam(nam, "no such module %s", *args, 0); + ret = 1; + } + cont: ; + } + return ret; + } else if(!*args) { + /* list modules */ + for (node = firstnode(modules); node; incnode(node)) { + m = (Module) getdata(node); + if (m->handle) { + if(ops['L']) { + printf("zmodload "); + if(m->nam[0] == '-') + fputs("-- ", stdout); + quotedzputs(m->nam, stdout); + } else + nicezputs(m->nam, stdout); + putchar('\n'); + } + } + return 0; + } else { + /* load modules */ + for (; *args; args++) { + node = find_module(*args); + if (node && ((Module) getdata(node))->handle) { + if (!ops['i']) { + zwarnnam(nam, "module %s already loaded.", *args, 0); + ret = 1; + } + } else if (isset(RESTRICTED) && strchr(*args, '/')) { + zwarnnam(nam, "%s: restricted", *args, 0); + ret = 1; + } else if (!load_module(*args)) + ret = 1; + } + return ret; + } +} + +#endif /* DYNAMIC */ diff --git a/Src/options.c b/Src/options.c new file mode 100644 index 000000000..745a6627c --- /dev/null +++ b/Src/options.c @@ -0,0 +1,663 @@ +/* + * options.c - shell options + * + * 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 "options.pro" + +/* current emulation (used to decide which set of option letters is used) */ + +/**/ +int emulation; + +/* the options; e.g. if opts[SHGLOB] != 0, SH_GLOB is turned on */ + +/**/ +char opts[OPT_SIZE]; + +/* Option name hash table */ + +/**/ +HashTable optiontab; + +typedef struct optname *Optname; + +struct optname { + HashNode next; /* next in hash chain */ + char *nam; /* hash data */ + int flags; + int optno; /* option number */ +}; + +/* The canonical option name table */ + +#define OPT_CSH EMULATE_CSH +#define OPT_KSH EMULATE_KSH +#define OPT_SH EMULATE_SH +#define OPT_ZSH EMULATE_ZSH + +#define OPT_ALL (OPT_CSH|OPT_KSH|OPT_SH|OPT_ZSH) +#define OPT_BOURNE (OPT_KSH|OPT_SH) +#define OPT_BSHELL (OPT_KSH|OPT_SH|OPT_ZSH) +#define OPT_NONBOURNE (OPT_ALL & ~OPT_BOURNE) +#define OPT_NONZSH (OPT_ALL & ~OPT_ZSH) + +#define OPT_EMULATE (1<<5) /* option is relevant to emulation */ +#define OPT_SPECIAL (1<<6) /* option should never be set by emulate() */ +#define OPT_ALIAS (1<<7) /* option is an alias to an other option */ + +#define defset(X) (!!((X)->flags & emulation)) + +static struct optname optns[] = { +{NULL, "allexport", 0, ALLEXPORT}, +{NULL, "alwayslastprompt", OPT_ALL, ALWAYSLASTPROMPT}, +{NULL, "alwaystoend", 0, ALWAYSTOEND}, +{NULL, "appendhistory", OPT_ALL, APPENDHISTORY}, +{NULL, "autocd", 0, AUTOCD}, +{NULL, "autolist", OPT_ALL, AUTOLIST}, +{NULL, "automenu", OPT_ALL, AUTOMENU}, +{NULL, "autonamedirs", 0, AUTONAMEDIRS}, +{NULL, "autoparamkeys", OPT_ALL, AUTOPARAMKEYS}, +{NULL, "autoparamslash", OPT_ALL, AUTOPARAMSLASH}, +{NULL, "autopushd", 0, AUTOPUSHD}, +{NULL, "autoremoveslash", OPT_ALL, AUTOREMOVESLASH}, +{NULL, "autoresume", 0, AUTORESUME}, +{NULL, "badpattern", OPT_EMULATE|OPT_NONBOURNE, BADPATTERN}, +{NULL, "banghist", OPT_EMULATE|OPT_NONBOURNE, BANGHIST}, +{NULL, "bareglobqual", OPT_EMULATE|OPT_ZSH, BAREGLOBQUAL}, +{NULL, "beep", OPT_ALL, BEEP}, +{NULL, "bgnice", OPT_EMULATE|OPT_NONBOURNE, BGNICE}, +{NULL, "braceccl", 0, BRACECCL}, +{NULL, "bsdecho", OPT_EMULATE|OPT_SH, BSDECHO}, +{NULL, "cdablevars", 0, CDABLEVARS}, +{NULL, "chaselinks", 0, CHASELINKS}, +{NULL, "clobber", OPT_ALL, CLOBBER}, +{NULL, "completealiases", 0, COMPLETEALIASES}, +{NULL, "completeinword", 0, COMPLETEINWORD}, +{NULL, "correct", 0, CORRECT}, +{NULL, "correctall", 0, CORRECTALL}, +{NULL, "cshjunkiehistory", OPT_EMULATE|OPT_CSH, CSHJUNKIEHISTORY}, +{NULL, "cshjunkieloops", OPT_EMULATE|OPT_CSH, CSHJUNKIELOOPS}, +{NULL, "cshjunkiequotes", OPT_EMULATE|OPT_CSH, CSHJUNKIEQUOTES}, +{NULL, "cshnullglob", OPT_EMULATE|OPT_CSH, CSHNULLGLOB}, +{NULL, "equals", OPT_EMULATE|OPT_ZSH, EQUALS}, +{NULL, "errexit", 0, ERREXIT}, +{NULL, "exec", OPT_ALL, EXECOPT}, +{NULL, "extendedglob", 0, EXTENDEDGLOB}, +{NULL, "extendedhistory", OPT_EMULATE|OPT_CSH, EXTENDEDHISTORY}, +{NULL, "flowcontrol", OPT_ALL, FLOWCONTROL}, +{NULL, "functionargzero", OPT_EMULATE|OPT_NONBOURNE, FUNCTIONARGZERO}, +{NULL, "glob", OPT_ALL, GLOBOPT}, +{NULL, "globassign", OPT_EMULATE|OPT_CSH, GLOBASSIGN}, +{NULL, "globcomplete", 0, GLOBCOMPLETE}, +{NULL, "globdots", 0, GLOBDOTS}, +{NULL, "globsubst", OPT_EMULATE|OPT_NONZSH, GLOBSUBST}, +{NULL, "hashcmds", OPT_ALL, HASHCMDS}, +{NULL, "hashdirs", OPT_ALL, HASHDIRS}, +{NULL, "hashlistall", OPT_ALL, HASHLISTALL}, +{NULL, "histallowclobber", 0, HISTALLOWCLOBBER}, +{NULL, "histbeep", OPT_ALL, HISTBEEP}, +{NULL, "histignoredups", 0, HISTIGNOREDUPS}, +{NULL, "histignorespace", 0, HISTIGNORESPACE}, +{NULL, "histnofunctions", 0, HISTNOFUNCTIONS}, +{NULL, "histnostore", 0, HISTNOSTORE}, +{NULL, "histreduceblanks", 0, HISTREDUCEBLANKS}, +{NULL, "histverify", 0, HISTVERIFY}, +{NULL, "hup", OPT_EMULATE|OPT_ZSH, HUP}, +{NULL, "ignorebraces", OPT_EMULATE|OPT_SH, IGNOREBRACES}, +{NULL, "ignoreeof", 0, IGNOREEOF}, +{NULL, "interactive", OPT_SPECIAL, INTERACTIVE}, +{NULL, "interactivecomments", OPT_EMULATE|OPT_BOURNE, INTERACTIVECOMMENTS}, +{NULL, "ksharrays", OPT_EMULATE|OPT_BOURNE, KSHARRAYS}, +{NULL, "kshautoload", OPT_EMULATE|OPT_BOURNE, KSHAUTOLOAD}, +{NULL, "kshglob", OPT_EMULATE|OPT_KSH, KSHGLOB}, +{NULL, "kshoptionprint", OPT_EMULATE|OPT_KSH, KSHOPTIONPRINT}, +{NULL, "listambiguous", OPT_ALL, LISTAMBIGUOUS}, +{NULL, "listbeep", OPT_ALL, LISTBEEP}, +{NULL, "listtypes", OPT_ALL, LISTTYPES}, +{NULL, "localoptions", OPT_EMULATE|OPT_KSH, LOCALOPTIONS}, +{NULL, "login", OPT_SPECIAL, LOGINSHELL}, +{NULL, "longlistjobs", 0, LONGLISTJOBS}, +{NULL, "magicequalsubst", 0, MAGICEQUALSUBST}, +{NULL, "mailwarning", 0, MAILWARNING}, +{NULL, "markdirs", 0, MARKDIRS}, +{NULL, "menucomplete", 0, MENUCOMPLETE}, +{NULL, "monitor", OPT_SPECIAL, MONITOR}, +{NULL, "multios", OPT_EMULATE|OPT_ZSH, MULTIOS}, +{NULL, "nomatch", OPT_EMULATE|OPT_NONBOURNE, NOMATCH}, +{NULL, "notify", OPT_ZSH, NOTIFY}, +{NULL, "nullglob", OPT_EMULATE, NULLGLOB}, +{NULL, "numericglobsort", 0, NUMERICGLOBSORT}, +{NULL, "overstrike", 0, OVERSTRIKE}, +{NULL, "pathdirs", 0, PATHDIRS}, +{NULL, "posixbuiltins", OPT_EMULATE|OPT_BOURNE, POSIXBUILTINS}, +{NULL, "printeightbit", 0, PRINTEIGHTBIT}, +{NULL, "printexitvalue", 0, PRINTEXITVALUE}, +{NULL, "privileged", OPT_SPECIAL, PRIVILEGED}, +{NULL, "promptbang", OPT_EMULATE|OPT_KSH, PROMPTBANG}, +{NULL, "promptcr", OPT_ALL, PROMPTCR}, +{NULL, "promptpercent", OPT_EMULATE|OPT_NONBOURNE, PROMPTPERCENT}, +{NULL, "promptsubst", OPT_EMULATE|OPT_KSH, PROMPTSUBST}, +{NULL, "pushdignoredups", 0, PUSHDIGNOREDUPS}, +{NULL, "pushdminus", 0, PUSHDMINUS}, +{NULL, "pushdsilent", 0, PUSHDSILENT}, +{NULL, "pushdtohome", 0, PUSHDTOHOME}, +{NULL, "rcexpandparam", 0, RCEXPANDPARAM}, +{NULL, "rcquotes", 0, RCQUOTES}, +{NULL, "rcs", OPT_ALL, RCS}, +{NULL, "recexact", 0, RECEXACT}, +{NULL, "restricted", OPT_SPECIAL, RESTRICTED}, +{NULL, "rmstarsilent", OPT_BOURNE, RMSTARSILENT}, +{NULL, "rmstarwait", 0, RMSTARWAIT}, +{NULL, "shfileexpansion", OPT_EMULATE|OPT_BOURNE, SHFILEEXPANSION}, +{NULL, "shglob", OPT_EMULATE|OPT_BOURNE, SHGLOB}, +{NULL, "shinstdin", OPT_SPECIAL, SHINSTDIN}, +{NULL, "shoptionletters", OPT_EMULATE|OPT_BOURNE, SHOPTIONLETTERS}, +{NULL, "shortloops", OPT_ALL, SHORTLOOPS}, +{NULL, "shwordsplit", OPT_EMULATE|OPT_BOURNE, SHWORDSPLIT}, +{NULL, "singlecommand", OPT_SPECIAL, SINGLECOMMAND}, +{NULL, "singlelinezle", OPT_KSH, SINGLELINEZLE}, +{NULL, "sunkeyboardhack", 0, SUNKEYBOARDHACK}, +{NULL, "unset", OPT_EMULATE|OPT_BSHELL, UNSET}, +{NULL, "verbose", 0, VERBOSE}, +{NULL, "xtrace", 0, XTRACE}, +{NULL, "zle", OPT_SPECIAL, USEZLE}, +{NULL, "braceexpand", OPT_ALIAS, /* ksh/bash */ -IGNOREBRACES}, +{NULL, "dotglob", OPT_ALIAS, /* bash */ GLOBDOTS}, +{NULL, "hashall", OPT_ALIAS, /* bash */ HASHCMDS}, +{NULL, "histappend", OPT_ALIAS, /* bash */ APPENDHISTORY}, +{NULL, "histexpand", OPT_ALIAS, /* bash */ BANGHIST}, +{NULL, "log", OPT_ALIAS, /* ksh */ -HISTNOFUNCTIONS}, +{NULL, "mailwarn", OPT_ALIAS, /* bash */ MAILWARNING}, +{NULL, "onecmd", OPT_ALIAS, /* bash */ SINGLECOMMAND}, +{NULL, "physical", OPT_ALIAS, /* ksh/bash */ CHASELINKS}, +{NULL, "promptvars", OPT_ALIAS, /* bash */ PROMPTSUBST}, +{NULL, "stdin", OPT_ALIAS, /* ksh */ SHINSTDIN}, +{NULL, "trackall", OPT_ALIAS, /* ksh */ HASHCMDS}, +{NULL, NULL, 0, 0} +}; + +/* Option letters */ + +#define optletters (isset(SHOPTIONLETTERS) ? kshletters : zshletters) + +#define FIRST_OPT '0' +#define LAST_OPT 'y' + +static short zshletters[LAST_OPT - FIRST_OPT + 1] = { + /* 0 */ CORRECT, + /* 1 */ PRINTEXITVALUE, + /* 2 */ -BADPATTERN, + /* 3 */ -NOMATCH, + /* 4 */ GLOBDOTS, + /* 5 */ NOTIFY, + /* 6 */ BGNICE, + /* 7 */ IGNOREEOF, + /* 8 */ MARKDIRS, + /* 9 */ AUTOLIST, + /* : */ 0, + /* ; */ 0, + /* < */ 0, + /* = */ 0, + /* > */ 0, + /* ? */ 0, + /* @ */ 0, + /* A */ 0, + /* B */ -BEEP, + /* C */ -CLOBBER, + /* D */ PUSHDTOHOME, + /* E */ PUSHDSILENT, + /* F */ -GLOBOPT, + /* G */ NULLGLOB, + /* H */ RMSTARSILENT, + /* I */ IGNOREBRACES, + /* J */ AUTOCD, + /* K */ -BANGHIST, + /* L */ SUNKEYBOARDHACK, + /* M */ SINGLELINEZLE, + /* N */ AUTOPUSHD, + /* O */ CORRECTALL, + /* P */ RCEXPANDPARAM, + /* Q */ PATHDIRS, + /* R */ LONGLISTJOBS, + /* S */ RECEXACT, + /* T */ CDABLEVARS, + /* U */ MAILWARNING, + /* V */ -PROMPTCR, + /* W */ AUTORESUME, + /* X */ LISTTYPES, + /* Y */ MENUCOMPLETE, + /* Z */ USEZLE, + /* [ */ 0, + /* \ */ 0, + /* ] */ 0, + /* ^ */ 0, + /* _ */ 0, + /* ` */ 0, + /* a */ ALLEXPORT, + /* b */ 0, + /* c */ 0, + /* d */ 0, + /* e */ ERREXIT, + /* f */ -RCS, + /* g */ HISTIGNORESPACE, + /* h */ HISTIGNOREDUPS, + /* i */ INTERACTIVE, + /* j */ 0, + /* k */ INTERACTIVECOMMENTS, + /* l */ LOGINSHELL, + /* m */ MONITOR, + /* n */ -EXECOPT, + /* o */ 0, + /* p */ PRIVILEGED, + /* q */ 0, + /* r */ RESTRICTED, + /* s */ SHINSTDIN, + /* t */ SINGLECOMMAND, + /* u */ -UNSET, + /* v */ VERBOSE, + /* w */ CHASELINKS, + /* x */ XTRACE, + /* y */ SHWORDSPLIT, +}; + +static short kshletters[LAST_OPT - FIRST_OPT + 1] = { + /* 0 */ 0, + /* 1 */ 0, + /* 2 */ 0, + /* 3 */ 0, + /* 4 */ 0, + /* 5 */ 0, + /* 6 */ 0, + /* 7 */ 0, + /* 8 */ 0, + /* 9 */ 0, + /* : */ 0, + /* ; */ 0, + /* < */ 0, + /* = */ 0, + /* > */ 0, + /* ? */ 0, + /* @ */ 0, + /* A */ 0, + /* B */ 0, + /* C */ -CLOBBER, + /* D */ 0, + /* E */ 0, + /* F */ 0, + /* G */ 0, + /* H */ 0, + /* I */ 0, + /* J */ 0, + /* K */ 0, + /* L */ 0, + /* M */ 0, + /* N */ 0, + /* O */ 0, + /* P */ 0, + /* Q */ 0, + /* R */ 0, + /* S */ 0, + /* T */ 0, + /* U */ 0, + /* V */ 0, + /* W */ 0, + /* X */ MARKDIRS, + /* Y */ 0, + /* Z */ 0, + /* [ */ 0, + /* \ */ 0, + /* ] */ 0, + /* ^ */ 0, + /* _ */ 0, + /* ` */ 0, + /* a */ ALLEXPORT, + /* b */ NOTIFY, + /* c */ 0, + /* d */ 0, + /* e */ ERREXIT, + /* f */ -GLOBOPT, + /* g */ 0, + /* h */ 0, + /* i */ INTERACTIVE, + /* j */ 0, + /* k */ 0, + /* l */ LOGINSHELL, + /* m */ MONITOR, + /* n */ -EXECOPT, + /* o */ 0, + /* p */ PRIVILEGED, + /* q */ 0, + /* r */ RESTRICTED, + /* s */ SHINSTDIN, + /* t */ SINGLECOMMAND, + /* u */ -UNSET, + /* v */ VERBOSE, + /* w */ 0, + /* x */ XTRACE, + /* y */ 0, +}; + +/* Initialisation of the option name hash table */ + +/**/ +static void +printoptionnode(HashNode hn, int set) +{ + Optname on = (Optname) hn; + int optno = on->optno; + + if (optno < 0) + optno = -optno; + if (isset(KSHOPTIONPRINT)) { + if (defset(on)) + printf("no%-20s%s\n", on->nam, isset(optno) ? "off" : "on"); + else + printf("%-22s%s\n", on->nam, isset(optno) ? "on" : "off"); + } else if (set == (isset(optno) ^ defset(on))) { + if (set ^ isset(optno)) + fputs("no", stdout); + puts(on->nam); + } +} + +/**/ +void +createoptiontable(void) +{ + Optname on; + + optiontab = newhashtable(101, "optiontab", NULL); + + optiontab->hash = hasher; + optiontab->emptytable = NULL; + optiontab->filltable = NULL; + optiontab->addnode = addhashnode; + optiontab->getnode = gethashnode; + optiontab->getnode2 = gethashnode2; + optiontab->removenode = NULL; + optiontab->disablenode = disablehashnode; + optiontab->enablenode = enablehashnode; + optiontab->freenode = NULL; + optiontab->printnode = printoptionnode; + + for (on = optns; on->nam; on++) + optiontab->addnode(optiontab, on->nam, on); +} + +/* Setting of default options */ + +/**/ +static void +setemulate(HashNode hn, int fully) +{ + Optname on = (Optname) hn; + + /* Set options: each non-special option is set according to the * + * current emulation mode if either it is considered relevant * + * to emulation or we are doing a full emulation (as indicated * + * by the `fully' parameter). */ + if (!(on->flags & OPT_ALIAS) && + ((fully && !(on->flags & OPT_SPECIAL)) || + (on->flags & OPT_EMULATE))) + opts[on->optno] = defset(on); +} + +/**/ +void +emulate(const char *zsh_name, int fully) +{ + char ch = *zsh_name; + + if (ch == 'r') + ch = zsh_name[1]; + + /* Work out the new emulation mode */ + if (ch == 'c') + emulation = EMULATE_CSH; + else if (ch == 'k') + emulation = EMULATE_KSH; + else if (ch == 's' || ch == 'b') + emulation = EMULATE_SH; + else + emulation = EMULATE_ZSH; + + scanhashtable(optiontab, 0, 0, 0, setemulate, fully); +} + +/* setopt, unsetopt */ + +/**/ +static void +setoption(HashNode hn, int value) +{ + dosetopt(((Optname) hn)->optno, value, 0); +} + +/**/ +int +bin_setopt(char *nam, char **args, char *ops, int isun) +{ + int action, optno, match = 0; + + /* With no arguments or options, display options. */ + if (!*args) { + scanhashtable(optiontab, 1, 0, OPT_ALIAS, optiontab->printnode, !isun); + return 0; + } + + /* loop through command line options (begins with "-" or "+") */ + while (*args && (**args == '-' || **args == '+')) { + action = (**args == '-') ^ isun; + if(!args[0][1]) + *args = "--"; + while (*++*args) { + if(**args == Meta) + *++*args ^= 32; + /* The pseudo-option `--' signifies the end of options. */ + if (**args == '-') { + 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 == 'm') { + match = 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: + + if (!match) { + /* Not globbing the arguments -- arguments are simply option names. */ + while (*args) { + if(!(optno = optlookup(*args++))) + zwarnnam(nam, "no such option: %s", args[-1], 0); + else if(dosetopt(optno, !isun, 0)) + zwarnnam(nam, "can't change option: %s", args[-1], 0); + } + } else { + /* Globbing option (-m) set. */ + while (*args) { + Comp com; + + /* Expand the current arg. */ + tokenize(*args); + if (!(com = parsereg(*args))) { + untokenize(*args); + zwarnnam(nam, "bad pattern: %s", *args, 0); + continue; + } + /* Loop over expansions. */ + scanmatchtable(optiontab, com, 0, OPT_ALIAS, setoption, !isun); + args++; + } + } + inittyptab(); + return 0; +} + +/* Identify an option name */ + +/**/ +int +optlookup(char const *name) +{ + char *s, *t; + Optname n; + + s = t = dupstring(name); + + /* exorcise underscores, and change to lowercase */ + while (*t) + if (*t == '_') + chuck(t); + else { + *t = tulower(*t); + t++; + } + + /* look up name in the table */ + if (s[0] == 'n' && s[1] == 'o' && + (n = (Optname) optiontab->getnode(optiontab, s + 2))) { + return -n->optno; + } else if ((n = (Optname) optiontab->getnode(optiontab, s))) + return n->optno; + else + return OPT_INVALID; +} + +/* Identify an option letter */ + +/**/ +int +optlookupc(char c) +{ + if(c < FIRST_OPT || c > LAST_OPT) + return 0; + + return optletters[c - FIRST_OPT]; +} + +/**/ +static void +restrictparam(char *nam) +{ + Param pm = (Param) paramtab->getnode(paramtab, nam); + + if (pm) { + pm->flags |= PM_SPECIAL | PM_RESTRICTED; + return; + } + createparam(nam, PM_SCALAR | PM_UNSET | PM_SPECIAL | PM_RESTRICTED); +} + +/* list of restricted parameters which are not otherwise special */ +static char *rparams[] = { + "SHELL", "HISTFILE", "LD_LIBRARY_PATH", "LD_AOUT_LIBRARY_PATH", + "LD_PRELOAD", "LD_AOUT_PRELOAD", NULL +}; + +/* Set or unset an option, as a result of user request. The option * + * number may be negative, indicating that the sense is reversed * + * from the usual meaning of the option. */ + +/**/ +int +dosetopt(int optno, int value, int force) +{ + if(!optno) + return -1; + if(optno < 0) { + optno = -optno; + value = !value; + } + if (optno == RESTRICTED) { + if (isset(RESTRICTED)) + return value ? 0 : -1; + if (value) { + char **s; + + for (s = rparams; *s; s++) + restrictparam(*s); + } + } else if(!force && (optno == INTERACTIVE || optno == SHINSTDIN || + optno == SINGLECOMMAND)) { + /* it is not permitted to change the value of these options */ + return -1; + } else if(!force && optno == USEZLE && value) { + /* we require a terminal in order to use ZLE */ + if(!interact || SHTTY == -1 || !shout) + return -1; + } else if(optno == PRIVILEGED && !value) { + /* unsetting PRIVILEGED causes the shell to make itself unprivileged */ +#ifdef HAVE_SETUID + setuid(getuid()); + setgid(getgid()); +#endif /* HAVE_SETUID */ + } + opts[optno] = value; + if (optno == BANGHIST || optno == SHINSTDIN) + inittyptab(); + return 0; +} + +/* Function to get value for special parameter `-' */ + +/**/ +char * +dashgetfn(Param pm) +{ + static char buf[LAST_OPT - FIRST_OPT + 2]; + char *val = buf; + int i; + + for(i = 0; i <= LAST_OPT - FIRST_OPT; i++) { + int optno = optletters[i]; + if(optno && ((optno > 0) ? isset(optno) : unset(-optno))) + *val++ = FIRST_OPT + i; + } + *val = '\0'; + return buf; +} diff --git a/Src/params.c b/Src/params.c new file mode 100644 index 000000000..4f7846820 --- /dev/null +++ b/Src/params.c @@ -0,0 +1,2191 @@ +/* + * params.c - parameters + * + * 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 "params.pro" + +#include "version.h" + +/* what level of localness we are at */ + +/**/ +int locallevel; + +/* Variables holding values of special parameters */ + +/**/ +char **pparams, /* $argv */ + **cdpath, /* $cdpath */ + **fignore, /* $fignore */ + **fpath, /* $fpath */ + **mailpath, /* $mailpath */ + **manpath, /* $manpath */ + **path, /* $path */ + **psvar, /* $psvar */ + **watch; /* $watch */ + +/**/ +char *argzero, /* $0 */ + *underscore, /* $_ */ + *home, /* $HOME */ + *hostnam, /* $HOST */ + *ifs, /* $IFS */ + *nullcmd, /* $NULLCMD */ + *oldpwd, /* $OLDPWD */ + *zoptarg, /* $OPTARG */ + *postedit, /* $POSTEDIT */ + *prompt, /* $PROMPT */ + *prompt2, /* $PROMPT2 */ + *prompt3, /* $PROMPT3 */ + *prompt4, /* $PROMPT4 */ + *pwd, /* $PWD */ + *readnullcmd, /* $READNULLCMD */ + *rprompt, /* $RPROMPT */ + *sprompt, /* $SPROMPT */ + *term, /* $TERM */ + *ttystrname, /* $TTY */ + *wordchars, /* $WORDCHARS */ + *zsh_name; /* $ZSH_NAME */ + +/**/ +long lastval, /* $? */ + mypid, /* $$ */ + lastpid, /* $! */ + columns, /* $COLUMNS */ + lineno, /* $LINENO */ + lines, /* $LINES */ + zoptind, /* $OPTIND */ + ppid, /* $PPID */ + shlvl; /* $SHLVL */ + +/* $histchars */ + +/**/ +unsigned char bangchar, hatchar, hashchar; + +/* $SECONDS = time(NULL) - shtimer.tv_sec */ + +/**/ +struct timeval shtimer; + +/* 0 if this $TERM setup is usable, otherwise it contains TERM_* flags */ + +/**/ +int termflags; + +/* Nodes for special parameters for parameter hash table */ + +static +#ifdef HAVE_UNION_INIT +# define BR(X) {X} +struct param +#else +# define BR(X) X +struct iparam { + struct hashnode *next; + char *nam; /* hash data */ + int flags; /* PM_* flags (defined in zsh.h) */ + void *value; + void (*func1) _((void)); /* set func */ + char *(*func2) _((void)); /* get func */ + void (*unsetfn) _((Param, int)); /* unset func */ + int ct; /* output base or field width */ + char *env; /* location in environment, if exported */ + char *ename; /* name of corresponding environment var */ + Param old; /* old struct for use with local */ + int level; /* if (old != NULL), level of localness */ +} +#endif +special_params[] ={ +#define SFN(X) BR(((void (*)_((Param, char *)))(X))) +#define GFN(X) BR(((char *(*)_((Param)))(X))) +#define IPDEF1(A,B,C,D) {NULL,A,PM_INTEGER|PM_SPECIAL|D,BR(NULL),SFN(C),GFN(B),stdunsetfn,10,NULL,NULL,NULL,0} +IPDEF1("#", poundgetfn, nullsetfn, PM_READONLY), +IPDEF1("ERRNO", errnogetfn, nullsetfn, PM_READONLY), +IPDEF1("GID", gidgetfn, gidsetfn, PM_DONTIMPORT | PM_RESTRICTED), +IPDEF1("EGID", egidgetfn, egidsetfn, PM_DONTIMPORT | PM_RESTRICTED), +IPDEF1("HISTSIZE", histsizegetfn, histsizesetfn, PM_RESTRICTED), +IPDEF1("RANDOM", randomgetfn, randomsetfn, 0), +IPDEF1("SECONDS", secondsgetfn, secondssetfn, 0), +IPDEF1("UID", uidgetfn, uidsetfn, PM_DONTIMPORT | PM_RESTRICTED), +IPDEF1("EUID", euidgetfn, euidsetfn, PM_DONTIMPORT | PM_RESTRICTED), +IPDEF1("TTYIDLE", ttyidlegetfn, nullsetfn, PM_READONLY), + +#define IPDEF2(A,B,C,D) {NULL,A,PM_SCALAR|PM_SPECIAL|D,BR(NULL),SFN(C),GFN(B),stdunsetfn,0,NULL,NULL,NULL,0} +IPDEF2("USERNAME", usernamegetfn, usernamesetfn, PM_DONTIMPORT|PM_RESTRICTED), +IPDEF2("-", dashgetfn, nullsetfn, PM_READONLY), +IPDEF2("histchars", histcharsgetfn, histcharssetfn, PM_DONTIMPORT), +IPDEF2("HOME", homegetfn, homesetfn, 0), +IPDEF2("TERM", termgetfn, termsetfn, 0), +IPDEF2("WORDCHARS", wordcharsgetfn, wordcharssetfn, 0), +IPDEF2("IFS", ifsgetfn, ifssetfn, PM_DONTIMPORT), +IPDEF2("_", underscoregetfn, nullsetfn, PM_READONLY), + +#ifdef LC_ALL +# define LCIPDEF(name) IPDEF2(name, strgetfn, lcsetfn, PM_UNSET) +IPDEF2("LANG", strgetfn, langsetfn, PM_UNSET), +IPDEF2("LC_ALL", strgetfn, lc_allsetfn, PM_UNSET), +# ifdef LC_COLLATE +LCIPDEF("LC_COLLATE"), +# endif +# ifdef LC_CTYPE +LCIPDEF("LC_CTYPE"), +# endif +# ifdef LC_MESSAGES +LCIPDEF("LC_MESSAGES"), +# endif +# ifdef LC_TIME +LCIPDEF("LC_TIME"), +# endif +#endif + +#define IPDEF4(A,B) {NULL,A,PM_INTEGER|PM_READONLY|PM_SPECIAL,BR((void *)B),SFN(nullsetfn),GFN(intvargetfn),stdunsetfn,10,NULL,NULL,NULL,0} +IPDEF4("!", &lastpid), +IPDEF4("$", &mypid), +IPDEF4("?", &lastval), +IPDEF4("LINENO", &lineno), +IPDEF4("PPID", &ppid), + +#define IPDEF5(A,B,F) {NULL,A,PM_INTEGER|PM_SPECIAL,BR((void *)B),SFN(F),GFN(intvargetfn),stdunsetfn,10,NULL,NULL,NULL,0} +IPDEF5("COLUMNS", &columns, zlevarsetfn), +IPDEF5("LINES", &lines, zlevarsetfn), +IPDEF5("OPTIND", &zoptind, intvarsetfn), +IPDEF5("SHLVL", &shlvl, intvarsetfn), + +#define IPDEF7(A,B) {NULL,A,PM_SCALAR|PM_SPECIAL,BR((void *)B),SFN(strvarsetfn),GFN(strvargetfn),stdunsetfn,0,NULL,NULL,NULL,0} +IPDEF7("OPTARG", &zoptarg), +IPDEF7("NULLCMD", &nullcmd), +IPDEF7("POSTEDIT", &postedit), +IPDEF7("READNULLCMD", &readnullcmd), +IPDEF7("RPROMPT", &rprompt), +IPDEF7("PS1", &prompt), +IPDEF7("PS2", &prompt2), +IPDEF7("PS3", &prompt3), +IPDEF7("PS4", &prompt4), +IPDEF7("RPS1", &rprompt), +IPDEF7("SPROMPT", &sprompt), +IPDEF7("0", &argzero), + +#define IPDEF8(A,B,C,D) {NULL,A,D|PM_SCALAR|PM_SPECIAL,BR((void *)B),SFN(colonarrsetfn),GFN(colonarrgetfn),stdunsetfn,0,NULL,C,NULL,0} +IPDEF8("CDPATH", &cdpath, "cdpath", 0), +IPDEF8("FIGNORE", &fignore, "fignore", 0), +IPDEF8("FPATH", &fpath, "fpath", 0), +IPDEF8("MAILPATH", &mailpath, "mailpath", 0), +IPDEF8("WATCH", &watch, "watch", 0), +IPDEF8("PATH", &path, "path", PM_RESTRICTED), +IPDEF8("PSVAR", &psvar, "psvar", 0), + +#ifdef DYNAMIC +/* MODULE_PATH is not imported for security reasons */ +IPDEF8("MODULE_PATH", &module_path, "module_path", PM_DONTIMPORT|PM_RESTRICTED), +#endif + +#define IPDEF9F(A,B,C,D) {NULL,A,D|PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT,BR((void *)B),SFN(arrvarsetfn),GFN(arrvargetfn),stdunsetfn,0,NULL,C,NULL,0} +#define IPDEF9(A,B,C) IPDEF9F(A,B,C,0) +IPDEF9("*", &pparams, NULL), +IPDEF9("@", &pparams, NULL), +{NULL, NULL}, + +/* The following parameters are not avaible in sh/ksh compatibility * + * mode. All of these has sh compatible equivalents. */ +IPDEF1("ARGC", poundgetfn, nullsetfn, PM_READONLY), +IPDEF2("HISTCHARS", histcharsgetfn, histcharssetfn, PM_DONTIMPORT), +IPDEF4("status", &lastval), +IPDEF7("prompt", &prompt), +IPDEF7("PROMPT", &prompt), +IPDEF7("PROMPT2", &prompt2), +IPDEF7("PROMPT3", &prompt3), +IPDEF7("PROMPT4", &prompt4), +IPDEF8("MANPATH", &manpath, "manpath", 0), +IPDEF9("argv", &pparams, NULL), +IPDEF9("fignore", &fignore, "FIGNORE"), +IPDEF9("cdpath", &cdpath, "CDPATH"), +IPDEF9("fpath", &fpath, "FPATH"), +IPDEF9("mailpath", &mailpath, "MAILPATH"), +IPDEF9("manpath", &manpath, "MANPATH"), +IPDEF9("psvar", &psvar, "PSVAR"), +IPDEF9("watch", &watch, "WATCH"), + +#ifdef DYNAMIC +IPDEF9F("module_path", &module_path, "MODULE_PATH", PM_RESTRICTED), +#endif +IPDEF9F("path", &path, "PATH", PM_RESTRICTED), + +{NULL, NULL} +}; +#undef BR + +static Param argvparam; + +/* hash table containing the parameters */ + +/**/ +HashTable paramtab; + +/* Set up parameter hash table. This will add predefined * + * parameter entries as well as setting up parameter table * + * entries for environment variables we inherit. */ + +/**/ +void +createparamtable(void) +{ + Param ip, pm; + char **new_environ, **envp, **envp2, **sigptr, **t; + char buf[50], *str, *iname; + int num_env; + + paramtab = newhashtable(151, "paramtab", NULL); + + paramtab->hash = hasher; + paramtab->emptytable = NULL; + paramtab->filltable = NULL; + paramtab->addnode = addhashnode; + paramtab->getnode = gethashnode2; + paramtab->getnode2 = gethashnode2; + paramtab->removenode = removehashnode; + paramtab->disablenode = NULL; + paramtab->enablenode = NULL; + paramtab->freenode = freeparamnode; + paramtab->printnode = printparamnode; + + /* Add the special parameters to the hash table */ + for (ip = special_params; ip->nam; ip++) + paramtab->addnode(paramtab, ztrdup(ip->nam), ip); + if (emulation != EMULATE_SH && emulation != EMULATE_KSH) + while ((++ip)->nam) + paramtab->addnode(paramtab, ztrdup(ip->nam), ip); + + argvparam = (Param) paramtab->getnode(paramtab, "*"); + + noerrs = 1; + + HEAPALLOC { + /* Add the standard non-special parameters which have to * + * be initialized before we copy the environment variables. * + * We don't want to override whatever values the users has * + * given them in the environment. */ + setiparam("MAILCHECK", 60); + setiparam("LOGCHECK", 60); + setiparam("KEYTIMEOUT", 40); + setiparam("LISTMAX", 100); +#ifdef HAVE_SELECT + setiparam("BAUD", getbaudrate(&shttyinfo)); /* get the output baudrate */ +#endif + setsparam("FCEDIT", ztrdup(DEFAULT_FCEDIT)); + setsparam("TMPPREFIX", ztrdup(DEFAULT_TMPPREFIX)); + setsparam("TIMEFMT", ztrdup(DEFAULT_TIMEFMT)); + setsparam("WATCHFMT", ztrdup(default_watchfmt)); + setsparam("HOST", ztrdup(hostnam)); + setsparam("LOGNAME", ztrdup((str = getlogin()) && *str ? str : cached_username)); + + /* Copy the environment variables we are inheriting to dynamic * + * memory, so we can do mallocs and frees on it. */ + num_env = arrlen(environ); + new_environ = (char **) zalloc(sizeof(char *) * (num_env + 1)); + *new_environ = NULL; + + /* Now incorporate environment variables we are inheriting * + * into the parameter hash table. */ + for (envp = new_environ, envp2 = environ; *envp2; envp2++) { + for (str = *envp2; *str && *str != '='; str++); + if (*str == '=') { + iname = NULL; + *str = '\0'; + if (!idigit(**envp2) && isident(*envp2) && !strchr(*envp2, '[')) { + iname = *envp2; + if ((!(pm = (Param) paramtab->getnode(paramtab, iname)) || + !(pm->flags & PM_DONTIMPORT)) && + (pm = setsparam(iname, metafy(str + 1, -1, META_DUP))) && + !(pm->flags & PM_EXPORTED)) { + *str = '='; + pm->flags |= PM_EXPORTED; + pm->env = *envp++ = ztrdup(*envp2); + *envp = NULL; + if (pm->flags & PM_SPECIAL) + pm->env = replenv(pm->env, getsparam(pm->nam)); + } + } + *str = '='; + } + } + environ = new_environ; + + pm = (Param) paramtab->getnode(paramtab, "HOME"); + if (!(pm->flags & PM_EXPORTED)) { + pm->flags |= PM_EXPORTED; + pm->env = addenv("HOME", home); + } + pm = (Param) paramtab->getnode(paramtab, "LOGNAME"); + if (!(pm->flags & PM_EXPORTED)) { + pm->flags |= PM_EXPORTED; + pm->env = addenv("LOGNAME", pm->u.str); + } + pm = (Param) paramtab->getnode(paramtab, "SHLVL"); + if (!(pm->flags & PM_EXPORTED)) + pm->flags |= PM_EXPORTED; + sprintf(buf, "%d", (int)++shlvl); + pm->env = addenv("SHLVL", buf); + + /* Add the standard non-special parameters */ + set_pwd_env(); + setsparam("MACHTYPE", ztrdup(MACHTYPE)); + setsparam("OSTYPE", ztrdup(OSTYPE)); + setsparam("TTY", ztrdup(ttystrname)); + setsparam("VENDOR", ztrdup(VENDOR)); + setsparam("ZSH_NAME", ztrdup(zsh_name)); + setsparam("ZSH_VERSION", ztrdup(ZSH_VERSION)); + setaparam("signals", sigptr = zalloc((SIGCOUNT+4) * sizeof(char *))); + for (t = sigs; (*sigptr++ = ztrdup(*t++)); ); + } LASTALLOC; + + noerrs = 0; +} + +/* Create a parameter, so that it can be assigned to. Returns NULL if the * + * parameter already exists or can't be created, otherwise returns the * + * parameter node. If a parameter of the same name exists in an outer * + * scope, it is hidden by a newly created parameter. An already existing * + * parameter node at the current level may be `created' and returned * + * provided it is unset and not special. If the parameter can't be * + * created because it already exists, the PM_UNSET flag is cleared. */ + +/**/ +Param +createparam(char *name, int flags) +{ + Param pm, oldpm; + + if (name != nulstring) { + oldpm = (Param) paramtab->getnode(paramtab, name); + + if (oldpm && oldpm->level == locallevel) { + if (!(oldpm->flags & PM_UNSET) || (oldpm->flags & PM_SPECIAL)) { + oldpm->flags &= ~PM_UNSET; + return NULL; + } + if ((oldpm->flags & PM_RESTRICTED) && isset(RESTRICTED)) { + zerr("%s: restricted", name, 0); + return NULL; + } + + pm = oldpm; + pm->ct = 0; + oldpm = pm->old; + } else { + pm = (Param) zcalloc(sizeof *pm); + if ((pm->old = oldpm)) { + /* needed to avoid freeing oldpm */ + paramtab->removenode(paramtab, name); + } + paramtab->addnode(paramtab, ztrdup(name), pm); + } + + if (isset(ALLEXPORT) && !oldpm) + flags |= PM_EXPORTED; + } else + pm = (Param) alloc(sizeof *pm); + pm->flags = flags; + + if(!(pm->flags & PM_SPECIAL)) { + switch (PM_TYPE(flags)) { + case PM_SCALAR: + pm->sets.cfn = strsetfn; + pm->gets.cfn = strgetfn; + break; + case PM_INTEGER: + pm->sets.ifn = intsetfn; + pm->gets.ifn = intgetfn; + break; + case PM_ARRAY: + pm->sets.afn = arrsetfn; + pm->gets.afn = arrgetfn; + break; + default: + DPUTS(1, "BUG: tried to create param node without valid flag"); + break; + } + pm->unsetfn = stdunsetfn; + } + return pm; +} + +/* Return 1 if the string s is a valid identifier, else return 0. */ + +/**/ +int +isident(char *s) +{ + char *ss; + int ne; + + ne = noeval; /* save the current value of noeval */ + if (!*s) /* empty string is definitely not valid */ + return 0; + + /* find the first character in `s' not in the iident type table */ + for (ss = s; *ss; ss++) + if (!iident(*ss)) + break; + + /* If this exhaust `s' or the next two characters * + * are [(, then it is a valid identifier. */ + if (!*ss || (*ss == '[' && ss[1] == '(')) + return 1; + + /* Else if the next character is not [, then it is * + * definitely not a valid identifier. */ + if (*ss != '[') + return 0; + noeval = 1; + (void)mathevalarg(++ss, &ss); + if (*ss == ',') + (void)mathevalarg(++ss, &ss); + noeval = ne; /* restore the value of noeval */ + if (*ss != ']' || ss[1]) + return 0; + return 1; +} + +static char **garr; + +/**/ +static long +getarg(char **str, int *inv, Value v, int a2, long *w) +{ + int num = 1, word = 0, rev = 0, ind = 0, down = 0, l, i; + char *s = *str, *sep = NULL, *t, sav, *d, **ta, **p, *tt; + long r = 0; + Comp c; + + /* first parse any subscription flags */ + if (*s == '(' || *s == Inpar) { + int escapes = 0; + int waste; + for (s++; *s != ')' && *s != Outpar && s != *str; s++) { + switch (*s) { + case 'r': + rev = 1; + down = ind = 0; + break; + case 'R': + rev = down = 1; + ind = 0; + break; + case 'i': + rev = ind = 1; + down = 0; + break; + case 'I': + rev = ind = down = 1; + break; + case 'w': + /* If the parameter is a scalar, then make subscription * + * work on a per-word basis instead of characters. */ + word = 1; + break; + case 'f': + word = 1; + sep = "\n"; + break; + case 'e': + /* obsolate compatibility flag without any real effect */ + break; + case 'n': + t = get_strarg(++s); + if (!*t) + goto flagerr; + sav = *t; + *t = '\0'; + num = mathevalarg(s + 1, &d); + if (!num) + num = 1; + *t = sav; + s = t; + break; + case 'p': + escapes = 1; + break; + case 's': + /* This gives the string that separates words * + * (for use with the `w' flag. */ + t = get_strarg(++s); + if (!*t) + goto flagerr; + sav = *t; + *t = '\0'; + sep = escapes ? getkeystring(s + 1, &waste, 1, &waste) : + dupstring(s + 1); + *t = sav; + s = t; + break; + default: + flagerr: + num = 1; + word = rev = ind = down = 0; + sep = NULL; + s = *str - 1; + } + } + if (s != *str) + s++; + } + if (num < 0) { + down = !down; + num = -num; + } + *inv = ind; + + for (t=s, i=0; *t && ((*t != ']' && *t != Outbrack && *t != ',') || i); t++) + if (*t == '[' || *t == Inbrack) + i++; + else if (*t == ']' || *t == Outbrack) + i--; + + if (!*t) + return 0; + s = dupstrpfx(s, t - s); + *str = tt = t; + if (parsestr(s)) + return 0; + singsub(&s); + + if (!rev) { + if (!(r = mathevalarg(s, &s)) || (isset(KSHARRAYS) && r >= 0)) + r++; + if (word && !v->isarr) { + s = t = getstrvalue(v); + i = wordcount(s, sep, 0); + if (r < 0) + r += i + 1; + if (r < 1) + r = 1; + if (r > i) + r = i; + if (!s || !*s) + return 0; + while ((d = findword(&s, sep)) && --r); + if (!d) + return 0; + + if (!a2 && *tt != ',') + *w = (long)(s - t) - 1; + + return (a2 ? s : d + 1) - t; + } else if (!v->isarr && !word) { + s = getstrvalue(v); + if (r > 0) { + for (t = s + r - 1; *s && s < t;) + if (*s++ == Meta) + s++, t++, r++; + } else { + r += ztrlen(s); + for (t = s + r; *s && s < t; r--) + if (*s++ == Meta) + t++, r++; + r -= strlen(s); + } + } + } else { + if (!v->isarr && !word) { + l = strlen(s); + if (a2) { + if (!l || *s != '*') { + d = (char *) ncalloc(l + 2); + *d = '*'; + strcpy(d + 1, s); + s = d; + } + } else { + if (!l || s[l - 1] != '*') { + d = (char *) ncalloc(l + 2); + strcpy(d, s); + strcat(d, "*"); + s = d; + } + } + } + tokenize(s); + + if ((c = parsereg(s))) { + if (v->isarr) { + ta = getarrvalue(v); + if (!ta || !*ta) + return 0; + if (down) + for (r = -1, p = ta + arrlen(ta) - 1; p >= ta; r--, p--) { + if (domatch(*p, c, 0) && !--num) + return r; + } else + for (r = 1, p = ta; *p; r++, p++) + if (domatch(*p, c, 0) && !--num) + return r; + } else if (word) { + ta = sepsplit(d = s = getstrvalue(v), sep, 1); + if (down) { + for (p = ta + (r = arrlen(ta)) - 1; p >= ta; p--, r--) + if (domatch(*p, c, 0) && !--num) + break; + if (p < ta) + return 0; + } else { + for (r = 1, p = ta; *p; r++, p++) + if (domatch(*p, c, 0) && !--num) + break; + if (!*p) + return 0; + } + if (a2) + r++; + for (i = 0; (t = findword(&d, sep)) && *t; i++) + if (!--r) { + r = (long)(t - s + (a2 ? -1 : 1)); + if (!a2 && *tt != ',') + *w = r + strlen(ta[i]) - 2; + return r; + } + return a2 ? -1 : 0; + } else { + d = getstrvalue(v); + if (!d || !*d) + return 0; + if (a2) { + if (down) + for (r = -2, t = d + strlen(d) - 1; t >= d; r--, t--) { + sav = *t; + *t = '\0'; + if (domatch(d, c, 0) && !--num) { + *t = sav; + return r; + } + *t = sav; + } else + for (r = 0, t = d; *t; r++, t++) { + sav = *t; + *t = '\0'; + if (domatch(d, c, 0) && !--num) { + *t = sav; + return r; + } + *t = sav; + } + } else { + if (down) + for (r = -1, t = d + strlen(d) - 1; t >= d; r--, t--) { + if (domatch(t, c, 0) && !--num) + return r; + } else + for (r = 1, t = d; *t; r++, t++) + if (domatch(t, c, 0) && !--num) + return r; + } + return 0; + } + } + } + return r; +} + +/**/ +int +getindex(char **pptr, Value v) +{ + int a, b, inv = 0; + char *s = *pptr, *tbrack; + + *s++ = '['; + for (tbrack = s; *tbrack && *tbrack != ']' && *tbrack != Outbrack; tbrack++) + if (itok(*tbrack)) + *tbrack = ztokens[*tbrack - Pound]; + if (*tbrack == Outbrack) + *tbrack = ']'; + if ((s[0] == '*' || s[0] == '@') && s[1] == ']') { + if (v->isarr) + v->isarr = (s[0] == '*') ? 1 : -1; + v->a = 0; + v->b = -1; + s += 2; + } else { + long we = 0, dummy; + + a = getarg(&s, &inv, v, 0, &we); + + if (inv) { + if (!v->isarr && a != 0) { + char *t, *p; + t = getstrvalue(v); + if (a > 0) { + for (p = t + a - 1; p-- > t; ) + if (*p == Meta) + a--; + } else + a = -ztrlen(t + a + strlen(t)); + } + if (a > 0 && isset(KSHARRAYS)) + a--; + v->inv = 1; + v->isarr = 0; + v->a = v->b = a; + if (*s == ',') { + zerr("invalid subscript", NULL, 0); + while (*s != ']' && *s != Outbrack) + s++; + *pptr = s; + return 1; + } + if (*s == ']' || *s == Outbrack) + s++; + } else { + if (a > 0) + a--; + if (*s == ',') { + s++; + b = getarg(&s, &inv, v, 1, &dummy); + if (b > 0) + b--; + } else { + b = we ? we : a; + } + if (*s == ']' || *s == Outbrack) { + s++; + if (v->isarr && a == b) + v->isarr = 0; + v->a = a; + v->b = b; + } else + s = *pptr; + } + } + *pptr = s; + return 0; +} + + +/**/ +Value +getvalue(char **pptr, int bracks) +{ + char *s, *t; + char sav; + Value v; + int ppar = 0; + + s = t = *pptr; + garr = NULL; + + if (idigit(*s)) + if (bracks >= 0) + ppar = zstrtol(s, &s, 10); + else + ppar = *s++ - '0'; + else if (iident(*s)) + while (iident(*s)) + s++; + else if (*s == Quest) + *s++ = '?'; + else if (*s == Pound) + *s++ = '#'; + else if (*s == String) + *s++ = '$'; + else if (*s == Qstring) + *s++ = '$'; + else if (*s == Star) + *s++ = '*'; + else if (*s == '#' || *s == '-' || *s == '?' || *s == '$' || + *s == '_' || *s == '!' || *s == '@' || *s == '*') + s++; + else + return NULL; + + if ((sav = *s)) + *s = '\0'; + if (ppar) { + v = (Value) hcalloc(sizeof *v); + v->pm = argvparam; + v->inv = 0; + v->a = v->b = ppar - 1; + if (sav) + *s = sav; + } else { + Param pm; + int isvarat; + + isvarat = !strcmp(t, "@"); + pm = (Param) paramtab->getnode(paramtab, *t == '0' ? "0" : t); + if (sav) + *s = sav; + *pptr = s; + if (!pm || (pm->flags & PM_UNSET)) + return NULL; + v = (Value) hcalloc(sizeof *v); + if (PM_TYPE(pm->flags) == PM_ARRAY) + v->isarr = isvarat ? -1 : 1; + v->pm = pm; + v->inv = 0; + v->a = 0; + v->b = -1; + if (bracks > 0 && (*s == '[' || *s == Inbrack)) { + if (getindex(&s, v)) { + *pptr = s; + return v; + } + } else if (v->isarr && iident(*t) && isset(KSHARRAYS)) + v->b = 0, v->isarr = 0; + } + if (!bracks && *s) + return NULL; + *pptr = s; + if (v->a > MAX_ARRLEN || + v->a < -MAX_ARRLEN) { + zerr("subscript to %s: %d", (v->a < 0) ? "small" : "big", v->a); + return NULL; + } + if (v->b > MAX_ARRLEN || + v->b < -MAX_ARRLEN) { + zerr("subscript to %s: %d", (v->b < 0) ? "small" : "big", v->b); + return NULL; + } + return v; +} + +/**/ +char * +getstrvalue(Value v) +{ + char *s, **ss; + static char buf[(sizeof(long) * 8) + 4]; + + if (!v) + return hcalloc(1); + HEAPALLOC { + if (v->inv) { + sprintf(buf, "%d", v->a); + s = dupstring(buf); + LASTALLOC_RETURN s; + } + + switch(PM_TYPE(v->pm->flags)) { + case PM_ARRAY: + if (v->isarr) + s = sepjoin(v->pm->gets.afn(v->pm), NULL); + else { + ss = v->pm->gets.afn(v->pm); + if (v->a < 0) + v->a += arrlen(ss); + s = (v->a >= arrlen(ss) || v->a < 0) ? (char *) hcalloc(1) : ss[v->a]; + } + LASTALLOC_RETURN s; + case PM_INTEGER: + convbase(s = buf, v->pm->gets.ifn(v->pm), v->pm->ct); + break; + case PM_SCALAR: + s = v->pm->gets.cfn(v->pm); + break; + default: + s = NULL; + DPUTS(1, "BUG: param node without valid type"); + break; + } + + if (v->a == 0 && v->b == -1) + LASTALLOC_RETURN s; + if (v->a < 0) + v->a += strlen(s); + if (v->b < 0) + v->b += strlen(s); + s = (v->a > (int)strlen(s)) ? dupstring("") : dupstring(s + v->a); + if (v->b < v->a) + s[0] = '\0'; + else if (v->b - v->a < (int)strlen(s)) + s[v->b - v->a + 1 + (s[v->b - v->a] == Meta)] = '\0'; + } LASTALLOC; + return s; +} + +static char *nular[] = {"", NULL}; + +/**/ +char ** +getarrvalue(Value v) +{ + char **s; + + if (!v) + return arrdup(nular); + if (v->inv) { + char buf[DIGBUFSIZE]; + + s = arrdup(nular); + sprintf(buf, "%d", v->a); + s[0] = dupstring(buf); + return s; + } + s = v->pm->gets.afn(v->pm); + if (v->a == 0 && v->b == -1) + return s; + if (v->a < 0) + v->a += arrlen(s); + if (v->b < 0) + v->b += arrlen(s); + if (v->a > arrlen(s) || v->a < 0) + s = arrdup(nular); + else + s = arrdup(s) + v->a; + if (v->b < v->a) + s[0] = NULL; + else if (v->b - v->a < arrlen(s)) + s[v->b - v->a + 1] = NULL; + return s; +} + +/**/ +long +getintvalue(Value v) +{ + if (!v || v->isarr) + return 0; + if (v->inv) + return v->a; + if (PM_TYPE(v->pm->flags) == PM_INTEGER) + return v->pm->gets.ifn(v->pm); + return matheval(getstrvalue(v)); +} + +/**/ +static void +setstrvalue(Value v, char *val) +{ + char buf[(sizeof(long) * 8) + 4]; + + if (v->pm->flags & PM_READONLY) { + zerr("read-only variable: %s", v->pm->nam, 0); + zsfree(val); + return; + } + if ((v->pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) { + zerr("%s: restricted", v->pm->nam, 0); + zsfree(val); + return; + } + switch (PM_TYPE(v->pm->flags)) { + case PM_SCALAR: + MUSTUSEHEAP("setstrvalue"); + if (v->a == 0 && v->b == -1) { + (v->pm->sets.cfn) (v->pm, val); + if (v->pm->flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z) && !v->pm->ct) + v->pm->ct = strlen(val); + } else { + char *z, *x; + int zlen; + + z = dupstring((v->pm->gets.cfn) (v->pm)); + zlen = strlen(z); + if (v->inv && unset(KSHARRAYS)) + v->a--, v->b--; + if (v->a < 0) { + v->a += zlen; + if (v->a < 0) + v->a = 0; + } + if (v->a > zlen) + v->a = zlen; + if (v->b < 0) + v->b += zlen; + if (v->b > zlen - 1) + v->b = zlen - 1; + x = (char *) zalloc(v->a + strlen(val) + zlen - v->b); + strncpy(x, z, v->a); + strcpy(x + v->a, val); + strcat(x + v->a, z + v->b + 1); + (v->pm->sets.cfn) (v->pm, x); + zsfree(val); + } + break; + case PM_INTEGER: + if (val) { + (v->pm->sets.ifn) (v->pm, matheval(val)); + zsfree(val); + } + if (!v->pm->ct && lastbase != -1) + v->pm->ct = lastbase; + break; + case PM_ARRAY: + MUSTUSEHEAP("setstrvalue"); + { + char **ss = (char **) zalloc(2 * sizeof(char *)); + + ss[0] = val; + ss[1] = NULL; + setarrvalue(v, ss); + } + break; + } + if ((!v->pm->env && !(v->pm->flags & PM_EXPORTED) && + !(isset(ALLEXPORT) && !v->pm->old)) || + (v->pm->flags & PM_ARRAY) || v->pm->ename) + return; + if (PM_TYPE(v->pm->flags) == PM_INTEGER) + convbase(val = buf, v->pm->gets.ifn(v->pm), v->pm->ct); + else + val = v->pm->gets.cfn(v->pm); + if (v->pm->env) + v->pm->env = replenv(v->pm->env, val); + else { + v->pm->flags |= PM_EXPORTED; + v->pm->env = addenv(v->pm->nam, val); + } +} + +/**/ +static void +setintvalue(Value v, long val) +{ + char buf[DIGBUFSIZE]; + + if (v->pm->flags & PM_READONLY) { + zerr("read-only variable: %s", v->pm->nam, 0); + return; + } + if ((v->pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) { + zerr("%s: restricted", v->pm->nam, 0); + return; + } + switch (PM_TYPE(v->pm->flags)) { + case PM_SCALAR: + case PM_ARRAY: + sprintf(buf, "%ld", val); + setstrvalue(v, ztrdup(buf)); + break; + case PM_INTEGER: + (v->pm->sets.ifn) (v->pm, val); + setstrvalue(v, NULL); + break; + } +} + +/**/ +static void +setarrvalue(Value v, char **val) +{ + if (v->pm->flags & PM_READONLY) { + zerr("read-only variable: %s", v->pm->nam, 0); + freearray(val); + return; + } + if ((v->pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) { + zerr("%s: restricted", v->pm->nam, 0); + freearray(val); + return; + } + if (PM_TYPE(v->pm->flags) != PM_ARRAY) { + freearray(val); + zerr("attempt to assign array value to non-array", NULL, 0); + return; + } + if (v->a == 0 && v->b == -1) + (v->pm->sets.afn) (v->pm, val); + else { + char **old, **new, **p, **q, **r; + int n, ll, i; + + if (v->inv && unset(KSHARRAYS)) + v->a--, v->b--; + q = old = v->pm->gets.afn(v->pm); + n = arrlen(old); + if (v->a < 0) + v->a += n; + if (v->b < 0) + v->b += n; + if (v->a < 0) + v->a = 0; + if (v->b < 0) + v->b = 0; + + ll = v->a + arrlen(val); + if (v->b < n) + ll += n - v->b; + + p = new = (char **) zcalloc(sizeof(char *) * (ll + 1)); + + for (i = 0; i < v->a; i++) + *p++ = i < n ? ztrdup(*q++) : ztrdup(""); + for (r = val; *r;) + *p++ = ztrdup(*r++); + if (v->b + 1 < n) + for (q = old + v->b + 1; *q;) + *p++ = ztrdup(*q++); + *p = NULL; + + (v->pm->sets.afn) (v->pm, new); + freearray(val); + } +} + +/* Retrieve an integer parameter */ + +/**/ +long +getiparam(char *s) +{ + Value v; + + if (!(v = getvalue(&s, 1))) + return 0; + return getintvalue(v); +} + +/* Retrieve a scalar (string) parameter */ + +/**/ +char * +getsparam(char *s) +{ + Value v; + + if (!(v = getvalue(&s, 0))) + return NULL; + return getstrvalue(v); +} + +/* Retrieve an array parameter */ + +/**/ +char ** +getaparam(char *s) +{ + Value v; + + if (!idigit(*s) && (v = getvalue(&s, 0)) && + PM_TYPE(v->pm->flags) == PM_ARRAY) + return v->pm->gets.afn(v->pm); + return NULL; +} + +/**/ +Param +setsparam(char *s, char *val) +{ + Value v; + char *t = s; + char *ss; + + if (!isident(s)) { + zerr("not an identifier: %s", s, 0); + zsfree(val); + errflag = 1; + return NULL; + } + if ((ss = strchr(s, '['))) { + *ss = '\0'; + if (!(v = getvalue(&s, 1))) + createparam(t, PM_ARRAY); + *ss = '['; + v = NULL; + } else { + if (!(v = getvalue(&s, 1))) + createparam(t, PM_SCALAR); + else if (PM_TYPE(v->pm->flags) == PM_ARRAY && + !(v->pm->flags & PM_SPECIAL) && unset(KSHARRAYS)) { + unsetparam(t); + createparam(t, PM_SCALAR); + v = NULL; + } + } + if (!v && !(v = getvalue(&t, 1))) { + zsfree(val); + return NULL; + } + setstrvalue(v, val); + return v->pm; +} + +/**/ +Param +setaparam(char *s, char **val) +{ + Value v; + char *t = s; + char *ss; + + if (!isident(s)) { + zerr("not an identifier: %s", s, 0); + freearray(val); + errflag = 1; + return NULL; + } + if ((ss = strchr(s, '['))) { + *ss = '\0'; + if (!(v = getvalue(&s, 1))) + createparam(t, PM_ARRAY); + *ss = '['; + v = NULL; + } else { + if (!(v = getvalue(&s, 1))) + createparam(t, PM_ARRAY); + else if (PM_TYPE(v->pm->flags) != PM_ARRAY && + !(v->pm->flags & PM_SPECIAL)) { + int uniq = v->pm->flags & PM_UNIQUE; + unsetparam(t); + createparam(t, PM_ARRAY | uniq); + v = NULL; + } + } + if (!v) + if (!(v = getvalue(&t, 1))) + return NULL; + if (isset(KSHARRAYS) && !ss) + /* the whole array should be set instead of only the first element */ + v->b = -1; + setarrvalue(v, val); + return v->pm; +} + +/**/ +Param +setiparam(char *s, long val) +{ + Value v; + char *t = s; + Param pm; + + if (!isident(s)) { + zerr("not an identifier: %s", s, 0); + errflag = 1; + return NULL; + } + if (!(v = getvalue(&s, 1))) { + pm = createparam(t, PM_INTEGER); + DPUTS(!pm, "BUG: parameter not created"); + pm->u.val = val; + return pm; + } + setintvalue(v, val); + return v->pm; +} + +/* Unset a parameter */ + +/**/ +void +unsetparam(char *s) +{ + Param pm; + + if ((pm = (Param) paramtab->getnode(paramtab, s))) + unsetparam_pm(pm, 0, 1); +} + +/* Unset a parameter */ + +/**/ +void +unsetparam_pm(Param pm, int altflag, int exp) +{ + Param oldpm, altpm; + + if ((pm->flags & PM_READONLY) && pm->level <= locallevel) { + zerr("read-only variable: %s", pm->nam, 0); + return; + } + if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) { + zerr("%s: restricted", pm->nam, 0); + return; + } + pm->unsetfn(pm, exp); + if ((pm->flags & PM_EXPORTED) && pm->env) { + delenv(pm->env); + zsfree(pm->env); + pm->env = NULL; + } + + /* remove it under its alternate name if necessary */ + if (pm->ename && !altflag) { + altpm = (Param) paramtab->getnode(paramtab, pm->ename); + if (altpm) + unsetparam_pm(altpm, 1, exp); + } + + /* If this was a local variable, we need to keep the old * + * struct so that it is resurrected at the right level. * + * This is partly because when an array/scalar value is set * + * and the parameter used to be the other sort, unsetparam() * + * is called. Beyond that, there is an ambiguity: should * + * foo() { local bar; unset bar; } make the global bar * + * available or not? The following makes the answer "no". */ + if (locallevel >= pm->level) + return; + + paramtab->removenode(paramtab, pm->nam); /* remove parameter node from table */ + + if (pm->old) { + oldpm = pm->old; + paramtab->addnode(paramtab, oldpm->nam, oldpm); + if ((PM_TYPE(oldpm->flags) == PM_SCALAR) && oldpm->sets.cfn == strsetfn) + adduserdir(oldpm->nam, oldpm->u.str, 0, 0); + } + + paramtab->freenode((HashNode) pm); /* free parameter node */ +} + +/* Standard function to unset a parameter. This is mostly delegated to * + * the specific set function. */ + +/**/ +void +stdunsetfn(Param pm, int exp) +{ + switch (PM_TYPE(pm->flags)) { + case PM_SCALAR: pm->sets.cfn(pm, NULL); break; + case PM_ARRAY: pm->sets.afn(pm, NULL); break; + } + pm->flags |= PM_UNSET; +} + +/* Function to get value of an integer parameter */ + +/**/ +static long +intgetfn(Param pm) +{ + return pm->u.val; +} + +/* Function to set value of an integer parameter */ + +/**/ +static void +intsetfn(Param pm, long x) +{ + pm->u.val = x; +} + +/* Function to get value of a scalar (string) parameter */ + +/**/ +char * +strgetfn(Param pm) +{ + return pm->u.str ? pm->u.str : (char *) hcalloc(1); +} + +/* Function to set value of a scalar (string) parameter */ + +/**/ +static void +strsetfn(Param pm, char *x) +{ + zsfree(pm->u.str); + pm->u.str = x; + adduserdir(pm->nam, x, 0, 0); +} + +/* Function to get value of an array parameter */ + +/**/ +static char ** +arrgetfn(Param pm) +{ + static char *nullarray = NULL; + + return pm->u.arr ? pm->u.arr : &nullarray; +} + +/* Function to set value of an array parameter */ + +/**/ +static void +arrsetfn(Param pm, char **x) +{ + if (pm->u.arr && pm->u.arr != x) + freearray(pm->u.arr); + if (pm->flags & PM_UNIQUE) + uniqarray(x); + pm->u.arr = x; +} + +/* This function is used as the set function for * + * special parameters that cannot be set by the user. */ + +/**/ +void +nullsetfn(Param pm, char *x) +{ + zsfree(x); +} + +/* Function to get value of generic special integer * + * parameter. data is pointer to global variable * + * containing the integer value. */ + +/**/ +long +intvargetfn(Param pm) +{ + return *((long *)pm->u.data); +} + +/* Function to set value of generic special integer * + * parameter. data is pointer to global variable * + * where the value is to be stored. */ + +/**/ +void +intvarsetfn(Param pm, long x) +{ + *((long *)pm->u.data) = x; +} + +/* Function to set value of any ZLE-related integer * + * parameter. data is pointer to global variable * + * where the value is to be stored. */ + +/**/ +void +zlevarsetfn(Param pm, long x) +{ + if ((long *)pm->u.data == & columns) { + if(x <= 0) + x = tccolumns > 0 ? tccolumns : 80; + if (x > 2) + termflags &= ~TERM_NARROW; + else + termflags |= TERM_NARROW; + } else if ((long *)pm->u.data == & lines) { + if(x <= 0) + x = tclines > 0 ? tclines : 24; + if (x > 2) + termflags &= ~TERM_SHORT; + else + termflags |= TERM_SHORT; + } + + *((long *)pm->u.data) = x; +} + +/* Function to set value of generic special scalar * + * parameter. data is pointer to a character pointer * + * representing the scalar (string). */ + +/**/ +void +strvarsetfn(Param pm, char *x) +{ + char **q = ((char **)pm->u.data); + + zsfree(*q); + *q = x; +} + +/* Function to get value of generic special scalar * + * parameter. data is pointer to a character pointer * + * representing the scalar (string). */ + +/**/ +char * +strvargetfn(Param pm) +{ + char *s = *((char **)pm->u.data); + + if (!s) + return hcalloc(1); + return s; +} + +/* Function to get value of generic special array * + * parameter. data is a pointer to the pointer to * + * a pointer (a pointer to a variable length array * + * of pointers). */ + +/**/ +char ** +arrvargetfn(Param pm) +{ + return *((char ***)pm->u.data); +} + +/* Function to set value of generic special array parameter. * + * data is pointer to a variable length array of pointers which * + * represents this array of scalars (strings). If pm->ename is * + * non NULL, then it is a colon separated environment variable * + * version of this array which will need to be updated. */ + +/**/ +void +arrvarsetfn(Param pm, char **x) +{ + char ***dptr = (char ***)pm->u.data; + + if (*dptr != x) + freearray(*dptr); + if (pm->flags & PM_UNIQUE) + uniqarray(x); + *dptr = x ? x : mkarray(NULL); + if (pm->ename && x) + arrfixenv(pm->ename, x); +} + +/**/ +char * +colonarrgetfn(Param pm) +{ + return zjoin(*(char ***)pm->u.data, ':'); +} + +/**/ +void +colonarrsetfn(Param pm, char *x) +{ + char ***dptr = (char ***)pm->u.data; + + freearray(*dptr); + *dptr = x ? colonsplit(x, pm->flags & PM_UNIQUE) : mkarray(NULL); + if (pm->ename) + arrfixenv(pm->nam, *dptr); + zsfree(x); +} + +/**/ +int +uniqarray(char **x) +{ + int changes = 0; + char **t, **p = x; + + if (!x || !*x) + return 0; + while (*++p) + for (t = x; t < p; t++) + if (!strcmp(*p, *t)) { + zsfree(*p); + for (t = p--; (*t = t[1]) != NULL; t++); + changes++; + break; + } + return changes; +} + +/* Function to get value of special parameter `#' and `ARGC' */ + +/**/ +long +poundgetfn(Param pm) +{ + return arrlen(pparams); +} + +/* Function to get value for special parameter `RANDOM' */ + +/**/ +long +randomgetfn(Param pm) +{ + return rand() & 0x7fff; +} + +/* Function to set value of special parameter `RANDOM' */ + +/**/ +void +randomsetfn(Param pm, long v) +{ + srand((unsigned int)v); +} + +/* Function to get value for special parameter `SECONDS' */ + +/**/ +long +secondsgetfn(Param pm) +{ + return time(NULL) - shtimer.tv_sec; +} + +/* Function to set value of special parameter `SECONDS' */ + +/**/ +void +secondssetfn(Param pm, long x) +{ + shtimer.tv_sec = time(NULL) - x; + shtimer.tv_usec = 0; +} + +/* Function to get value for special parameter `USERNAME' */ + +/**/ +char * +usernamegetfn(Param pm) +{ + return get_username(); +} + +/* Function to set value of special parameter `USERNAME' */ + +/**/ +void +usernamesetfn(Param pm, char *x) +{ +#if defined(HAVE_SETUID) && defined(HAVE_GETPWNAM) + struct passwd *pswd; + + if (x && (pswd = getpwnam(x)) && (pswd->pw_uid != cached_uid)) { +# ifdef HAVE_INITGROUPS + initgroups(x, pswd->pw_gid); +# endif + if(!setgid(pswd->pw_gid) && !setuid(pswd->pw_uid)) { + zsfree(cached_username); + cached_username = ztrdup(pswd->pw_name); + cached_uid = pswd->pw_uid; + } + } +#endif /* HAVE_SETUID && HAVE_GETPWNAM */ +} + +/* Function to get value for special parameter `UID' */ + +/**/ +long +uidgetfn(Param pm) +{ + return getuid(); +} + +/* Function to set value of special parameter `UID' */ + +/**/ +void +uidsetfn(Param pm, uid_t x) +{ +#ifdef HAVE_SETUID + setuid(x); +#endif +} + +/* Function to get value for special parameter `EUID' */ + +/**/ +long +euidgetfn(Param pm) +{ + return geteuid(); +} + +/* Function to set value of special parameter `EUID' */ + +/**/ +void +euidsetfn(Param pm, uid_t x) +{ +#ifdef HAVE_SETEUID + seteuid(x); +#endif +} + +/* Function to get value for special parameter `GID' */ + +/**/ +long +gidgetfn(Param pm) +{ + return getgid(); +} + +/* Function to set value of special parameter `GID' */ + +/**/ +void +gidsetfn(Param pm, gid_t x) +{ +#ifdef HAVE_SETUID + setgid(x); +#endif +} + +/* Function to get value for special parameter `EGID' */ + +/**/ +long +egidgetfn(Param pm) +{ + return getegid(); +} + +/* Function to set value of special parameter `EGID' */ + +/**/ +void +egidsetfn(Param pm, gid_t x) +{ +#ifdef HAVE_SETEUID + setegid(x); +#endif +} + +/**/ +long +ttyidlegetfn(Param pm) +{ + struct stat ttystat; + + if (SHTTY == -1 || fstat(SHTTY, &ttystat)) + return -1; + return time(NULL) - ttystat.st_atime; +} + +/* Function to get value for special parameter `IFS' */ + +/**/ +char * +ifsgetfn(Param pm) +{ + return ifs; +} + +/* Function to set value of special parameter `IFS' */ + +/**/ +void +ifssetfn(Param pm, char *x) +{ + zsfree(ifs); + ifs = x; + inittyptab(); +} + +/* Functions to set value of special parameters `LANG' and `LC_*' */ + +#ifdef LC_ALL +static struct localename { + char *name; + int category; +} lc_names[] = { +#ifdef LC_COLLATE + {"LC_COLLATE", LC_COLLATE}, +#endif +#ifdef LC_CTYPE + {"LC_CTYPE", LC_CTYPE}, +#endif +#ifdef LC_MESSAGES + {"LC_MESSAGES", LC_MESSAGES}, +#endif +#ifdef LC_TIME + {"LC_TIME", LC_TIME}, +#endif + {NULL, 0} +}; + +/**/ +static void +setlang(char *x) +{ + struct localename *ln; + + setlocale(LC_ALL, x ? x : ""); + for (ln = lc_names; ln->name; ln++) + if ((x = getsparam(ln->name))) + setlocale(ln->category, x); +} + +/**/ +void +lc_allsetfn(Param pm, char *x) +{ + strsetfn(pm, x); + if (!x) + setlang(getsparam("LANG")); + else + setlocale(LC_ALL, x); +} + +/**/ +void +langsetfn(Param pm, char *x) +{ + strsetfn(pm, x); + setlang(x); +} + +/**/ +void +lcsetfn(Param pm, char *x) +{ + struct localename *ln; + + strsetfn(pm, x); + if (getsparam("LC_ALL")) + return; + if (!x) + x = getsparam("LANG"); + + for (ln = lc_names; ln->name; ln++) + if (!strcmp(ln->name, pm->nam)) + setlocale(ln->category, x ? x : ""); +} +#endif + +/* Function to get value for special parameter `HISTSIZE' */ + +/**/ +long +histsizegetfn(Param pm) +{ + return histsiz; +} + +/* Function to set value of special parameter `HISTSIZE' */ + +/**/ +void +histsizesetfn(Param pm, long v) +{ + if ((histsiz = v) <= 2) + histsiz = 2; + resizehistents(); +} + +/* Function to get value for special parameter `ERRNO' */ + +/**/ +long +errnogetfn(Param pm) +{ + return errno; +} + +/* Function to get value for special parameter `histchar' */ + +/**/ +char * +histcharsgetfn(Param pm) +{ + static char buf[4]; + + buf[0] = bangchar; + buf[1] = hatchar; + buf[2] = hashchar; + buf[3] = '\0'; + return buf; +} + +/* Function to set value of special parameter `histchar' */ + +/**/ +void +histcharssetfn(Param pm, char *x) +{ + if (x) { + bangchar = x[0]; + hatchar = (bangchar) ? x[1] : '\0'; + hashchar = (hatchar) ? x[2] : '\0'; + zsfree(x); + } else { + bangchar = '!'; + hashchar = '#'; + hatchar = '^'; + } + inittyptab(); +} + +/* Function to get value for special parameter `HOME' */ + +/**/ +char * +homegetfn(Param pm) +{ + return home; +} + +/* Function to set value of special parameter `HOME' */ + +/**/ +void +homesetfn(Param pm, char *x) +{ + zsfree(home); + if (x && isset(CHASELINKS) && (home = xsymlink(x))) + zsfree(x); + else + home = x ? x : ztrdup(""); + finddir(NULL); +} + +/* Function to get value for special parameter `WORDCHARS' */ + +/**/ +char * +wordcharsgetfn(Param pm) +{ + return wordchars; +} + +/* Function to set value of special parameter `WORDCHARS' */ + +/**/ +void +wordcharssetfn(Param pm, char *x) +{ + zsfree(wordchars); + wordchars = x; + inittyptab(); +} + +/* Function to get value for special parameter `_' */ + +/**/ +char * +underscoregetfn(Param pm) +{ + return underscore; +} + +/* Function to get value for special parameter `TERM' */ + +/**/ +char * +termgetfn(Param pm) +{ + return term; +} + +/* Function to set value of special parameter `TERM' */ + +/**/ +void +termsetfn(Param pm, char *x) +{ + zsfree(term); + term = x ? x : ztrdup(""); + + /* If non-interactive, delay setting up term till we need it. */ + if (unset(INTERACTIVE) || !*term) + termflags |= TERM_UNKNOWN; + else + init_term(); +} + +/* We could probably replace the replenv with the actual code to * + * do the replacing, since we've already scanned for the string. */ + +/**/ +static void +arrfixenv(char *s, char **t) +{ + char **ep, *u; + int len_s; + Param pm; + + MUSTUSEHEAP("arrfixenv"); + if (t == path) + cmdnamtab->emptytable(cmdnamtab); + u = zjoin(t, ':'); + len_s = strlen(s); + pm = (Param) paramtab->getnode(paramtab, s); + for (ep = environ; *ep; ep++) + if (!strncmp(*ep, s, len_s) && (*ep)[len_s] == '=') { + pm->env = replenv(*ep, u); + return; + } + if (isset(ALLEXPORT)) + pm->flags |= PM_EXPORTED; + if (pm->flags & PM_EXPORTED) + pm->env = addenv(s, u); +} + +/* Given *name = "foo", it searchs the environment for string * + * "foo=bar", and returns a pointer to the beginning of "bar" */ + +/**/ +char * +zgetenv(char *name) +{ + char **ep, *s, *t; + + for (ep = environ; *ep; ep++) { + for (s = *ep, t = name; *s && *s == *t; s++, t++); + if (*s == '=' && !*t) + return s + 1; + } + return NULL; +} + +/* Change the value of an existing environment variable */ + +/**/ +char * +replenv(char *e, char *value) +{ + char **ep, *s; + int len_value; + + for (ep = environ; *ep; ep++) + if (*ep == e) { + for (len_value = 0, s = value; + *s && (*s++ != Meta || *s++ != 32); len_value++); + s = e; + while (*s++ != '='); + *ep = (char *) zrealloc(e, s - e + len_value + 1); + s = s - e + *ep - 1; + while (*s++) + if ((*s = *value++) == Meta) + *s = *value++ ^ 32; + return *ep; + } + return NULL; +} + +/* Given strings *name = "foo", *value = "bar", * + * return a new string *str = "foo=bar". */ + +/**/ +static char * +mkenvstr(char *name, char *value) +{ + char *str, *s; + int len_name, len_value; + + len_name = strlen(name); + for (len_value = 0, s = value; + *s && (*s++ != Meta || *s++ != 32); len_value++); + s = str = (char *) zalloc(len_name + len_value + 2); + strcpy(s, name); + s += len_name; + *s = '='; + while (*s++) + if ((*s = *value++) == Meta) + *s = *value++ ^ 32; + return str; +} + +/* Given *name = "foo", *value = "bar", add the * + * string "foo=bar" to the environment. Return a * + * pointer to the location of this new environment * + * string. */ + +/**/ +char * +addenv(char *name, char *value) +{ + char **ep, *s, *t; + int num_env; + + /* First check if there is already an environment * + * variable matching string `name'. */ + for (ep = environ; *ep; ep++) { + for (s = *ep, t = name; *s && *s == *t; s++, t++); + if (*s == '=' && !*t) { + zsfree(*ep); + return *ep = mkenvstr(name, value); + } + } + + /* Else we have to make room and add it */ + num_env = arrlen(environ); + environ = (char **) zrealloc(environ, (sizeof(char *)) * (num_env + 2)); + + /* Now add it at the end */ + ep = environ + num_env; + *ep = mkenvstr(name, value); + *(ep + 1) = NULL; + return *ep; +} + +/* Delete a pointer from the list of pointers to environment * + * variables by shifting all the other pointers up one slot. */ + +/**/ +void +delenv(char *x) +{ + char **ep; + + for (ep = environ; *ep; ep++) { + if (*ep == x) + break; + } + if (*ep) + for (; (ep[0] = ep[1]); ep++); +} + +/**/ +static void +convbase(char *s, long v, int base) +{ + int digs = 0; + unsigned long x; + + if (v < 0) + *s++ = '-', v = -v; + if (base <= 1) + base = 10; + + if (base != 10) { + sprintf(s, "%d#", base); + s += strlen(s); + } + for (x = v; x; digs++) + x /= base; + if (!digs) + digs = 1; + s[digs--] = '\0'; + x = v; + while (digs >= 0) { + int dig = x % base; + + s[digs--] = (dig < 10) ? '0' + dig : dig - 10 + 'A'; + x /= base; + } +} + +/* Start a parameter scope */ + +/**/ +void +startparamscope(void) +{ + locallevel++; +} + +/* End a parameter scope: delete the parameters local to the scope. */ + +/**/ +void +endparamscope(void) +{ + locallevel--; + scanhashtable(paramtab, 0, 0, 0, scanendscope, 0); +} + +/**/ +static void +scanendscope(HashNode hn, int flags) +{ + Param pm = (Param)hn; + if(pm->level > locallevel) + unsetparam_pm(pm, 0, 0); +} diff --git a/Src/parse.c b/Src/parse.c new file mode 100644 index 000000000..d42be2f2f --- /dev/null +++ b/Src/parse.c @@ -0,0 +1,1379 @@ +/* + * parse.c - parser + * + * 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 "parse.pro" + +/* != 0 if we are about to read a command word */ + +/**/ +int incmdpos; + +/* != 0 if we are in the middle of a [[ ... ]] */ + +/**/ +int incond; + +/* != 0 if we are after a redirection (for ctxtlex only) */ + +/**/ +int inredir; + +/* != 0 if we are about to read a case pattern */ + +/**/ +int incasepat; + +/* != 0 if we just read a newline */ + +/**/ +int isnewlin; + +/* != 0 if we are after a for keyword */ + +/**/ +int infor; + +/* list of here-documents */ + +/**/ +struct heredocs *hdocs; + +/* used in arrays of lists instead of NULL pointers */ + +/**/ +struct list dummy_list; + +#define YYERROR { tok = LEXERR; return NULL; } +#define YYERRORV { tok = LEXERR; return; } +#define COND_ERROR(X,Y) do{herrflush();zerr(X,Y,0);YYERROR}while(0) + +#define make_list() allocnode(N_LIST) +#define make_sublist() allocnode(N_SUBLIST) +#define make_pline() allocnode(N_PLINE) +#define make_cmd() allocnode(N_CMD) +#define make_forcmd() allocnode(N_FOR) +#define make_casecmd() allocnode(N_CASE) +#define make_ifcmd() allocnode(N_IF) +#define make_whilecmd() allocnode(N_WHILE) +#define make_varnode() allocnode(N_VARASG) +#define make_cond() allocnode(N_COND) + +/* + * event : ENDINPUT + * | SEPER + * | sublist [ SEPER | AMPER | AMPERBANG ] + */ +/**/ +List +parse_event(void) +{ + tok = ENDINPUT; + incmdpos = 1; + yylex(); + return par_event(); +} + +/**/ +static List +par_event(void) +{ + Sublist sl; + List l = NULL; + + while (tok == SEPER) { + if (isnewlin > 0) + return NULL; + yylex(); + } + if (tok == ENDINPUT) + return NULL; + if ((sl = par_sublist())) + if (tok == ENDINPUT) { + l = (List) make_list(); + l->type = Z_SYNC; + l->left = sl; + } else if (tok == SEPER) { + l = (List) make_list(); + l->type = Z_SYNC; + l->left = sl; + if (isnewlin <= 0) + yylex(); + } else if (tok == AMPER) { + l = (List) make_list(); + l->type = Z_ASYNC; + l->left = sl; + yylex(); + } else if (tok == AMPERBANG) { + l = (List) make_list(); + l->type = Z_ASYNC | Z_DISOWN; + l->left = sl; + yylex(); + } else + l = NULL; + if (!l) { + if (errflag) { + yyerror(); + return NULL; + } + herrflush(); + yyerror(); + return NULL; + } else { + l->right = par_event(); + } + return l; +} + +/**/ +List +parse_list(void) +{ + List ret; + + tok = ENDINPUT; + incmdpos = 1; + yylex(); + ret = par_list(); + if (tok == LEXERR) { + yyerror(); + return NULL; + } + return ret; +} + +/* + * list : { SEPER } [ sublist [ { SEPER | AMPER | AMPERBANG } list ] ] + */ + +/**/ +static List +par_list(void) +{ + Sublist sl; + List l = NULL; + + while (tok == SEPER) + yylex(); + if ((sl = par_sublist())) + if (tok == SEPER || tok == AMPER || tok == AMPERBANG) { + l = (List) make_list(); + l->left = sl; + l->type = (tok == SEPER) ? Z_SYNC : + (tok == AMPER) ? Z_ASYNC : Z_ASYNC | Z_DISOWN; + incmdpos = 1; + do { + yylex(); + } while (tok == SEPER); + l->right = par_list(); + } else { + l = (List) make_list(); + l->left = sl; + l->type = Z_SYNC; + } + return l; +} + +/**/ +static List +par_list1(void) +{ + Sublist sl; + List l = NULL; + + if ((sl = par_sublist())) { + l = (List) make_list(); + l->type = Z_SYNC; + l->left = sl; + } + return l; +} + +/* + * sublist : sublist2 [ ( DBAR | DAMPER ) { SEPER } sublist ] + */ + +/**/ +static Sublist +par_sublist(void) +{ + Sublist sl; + + if ((sl = par_sublist2())) + if (tok == DBAR || tok == DAMPER) { + int qtok = tok; + + cmdpush(tok == DBAR ? CS_CMDOR : CS_CMDAND); + yylex(); + while (tok == SEPER) + yylex(); + sl->right = par_sublist(); + sl->type = (qtok == DBAR) ? ORNEXT : ANDNEXT; + cmdpop(); + } + return sl; +} + +/* + * sublist2 : [ COPROC | BANG ] pline + */ + +/**/ +static Sublist +par_sublist2(void) +{ + Sublist sl; + Pline p; + + sl = (Sublist) make_sublist(); + if (tok == COPROC) { + sl->flags |= PFLAG_COPROC; + yylex(); + } else if (tok == BANG) { + sl->flags |= PFLAG_NOT; + yylex(); + } + if (!(p = par_pline()) && !sl->flags) + return NULL; + sl->left = p; + return sl; +} + +/* + * pline : cmd [ ( BAR | BARAMP ) { SEPER } pline ] + */ + +/**/ +static Pline +par_pline(void) +{ + Cmd c; + Pline p, p2; + + if (!(c = par_cmd())) + return NULL; + if (tok == BAR) { + cmdpush(CS_PIPE); + yylex(); + while (tok == SEPER) + yylex(); + p2 = par_pline(); + cmdpop(); + p = (Pline) make_pline(); + p->left = c; + p->right = p2; + p->type = PIPE; + return p; + } else if (tok == BARAMP) { + struct redir *rdr = (struct redir *)allocnode(N_REDIR); + + rdr->type = MERGEOUT; + rdr->fd1 = 2; + rdr->name = dupstring("1"); + addlinknode(c->redir, rdr); + + cmdpush(CS_ERRPIPE); + yylex(); + p2 = par_pline(); + cmdpop(); + p = (Pline) make_pline(); + p->left = c; + p->right = p2; + p->type = PIPE; + return p; + } else { + p = (Pline) make_pline(); + p->left = c; + p->type = END; + return p; + } +} + +/* + * cmd : { redir } ( for | case | if | while | repeat | + * subsh | funcdef | time | dinbrack | dinpar | simple ) { redir } + */ + +/**/ +static Cmd +par_cmd(void) +{ + Cmd c; + + c = (Cmd) make_cmd(); + c->lineno = lineno; + c->args = newlinklist(); + c->redir = newlinklist(); + c->vars = newlinklist(); + while (IS_REDIROP(tok)) + par_redir(c->redir); + switch (tok) { + case FOR: + cmdpush(CS_FOR); + par_for(c); + cmdpop(); + break; + case FOREACH: + cmdpush(CS_FOREACH); + par_for(c); + cmdpop(); + break; + case SELECT: + cmdpush(CS_SELECT); + par_for(c); + cmdpop(); + break; + case CASE: + cmdpush(CS_CASE); + par_case(c); + cmdpop(); + break; + case IF: + par_if(c); + break; + case WHILE: + cmdpush(CS_WHILE); + par_while(c); + cmdpop(); + break; + case UNTIL: + cmdpush(CS_UNTIL); + par_while(c); + cmdpop(); + break; + case REPEAT: + cmdpush(CS_REPEAT); + par_repeat(c); + cmdpop(); + break; + case INPAR: + cmdpush(CS_SUBSH); + par_subsh(c); + cmdpop(); + break; + case INBRACE: + cmdpush(CS_CURSH); + par_subsh(c); + cmdpop(); + break; + case FUNC: + cmdpush(CS_FUNCDEF); + par_funcdef(c); + cmdpop(); + break; + case TIME: + par_time(c); + break; + case DINBRACK: + cmdpush(CS_COND); + par_dinbrack(c); + cmdpop(); + break; + case DINPAR: + c->type = CARITH; + addlinknode(c->args, tokstr); + yylex(); + break; + default: + if (!par_simple(c)) + return NULL; + break; + } + while (IS_REDIROP(tok)) + par_redir(c->redir); + incmdpos = 1; + incasepat = 0; + incond = 0; + return c; +} + +/* + * for : ( FOR DINPAR expr SEMI expr SEMI expr DOUTPAR | + * ( FOR[EACH] | SELECT ) name ( "in" wordlist | INPAR wordlist OUTPAR ) ) + * { SEPER } ( DO list DONE | INBRACE list OUTBRACE | list ZEND | list1 ) + */ + +/**/ +static void +par_for(Cmd c) +{ + Forcmd f; + int csh = (tok == FOREACH); + + f = (Forcmd) make_forcmd(); + c->type = (tok == SELECT) ? CSELECT : CFOR; + incmdpos = 0; + infor = tok == FOR ? 2 : 0; + yylex(); + if (tok == DINPAR) { + yylex(); + if (tok != DINPAR) + YYERRORV; + f->name = tokstr; + yylex(); + if (tok != DINPAR) + YYERRORV; + f->condition = tokstr; + yylex(); + if (tok != DOUTPAR) + YYERRORV; + f->advance = tokstr; + infor = 0; + incmdpos = 1; + yylex(); + } else { + infor = 0; + if (tok != STRING || !isident(tokstr)) + YYERRORV; + f->name = tokstr; + incmdpos = 1; + yylex(); + if (tok == STRING && !strcmp(tokstr, "in")) { + f->inflag = 1; + incmdpos = 0; + yylex(); + c->args = par_wordlist(); + if (tok != SEPER) + YYERRORV; + } else if (tok == INPAR) { + f->inflag = 1; + incmdpos = 0; + yylex(); + c->args = par_nl_wordlist(); + if (tok != OUTPAR) + YYERRORV; + incmdpos = 1; + yylex(); + } + } + incmdpos = 1; + while (tok == SEPER) + yylex(); + if (tok == DO) { + yylex(); + f->list = par_list(); + if (tok != DONE) + YYERRORV; + yylex(); + } else if (tok == INBRACE) { + yylex(); + f->list = par_list(); + if (tok != OUTBRACE) + YYERRORV; + yylex(); + } else if (csh || isset(CSHJUNKIELOOPS)) { + f->list = par_list(); + if (tok != ZEND) + YYERRORV; + yylex(); + } else if (unset(SHORTLOOPS)) { + YYERRORV; + } else + f->list = par_list1(); + c->u.forcmd = f; +} + +/* + * case : CASE STRING { SEPER } ( "in" | INBRACE ) + { { SEPER } STRING { BAR STRING } OUTPAR + list [ DSEMI | SEMIAMP ] } + { SEPER } ( "esac" | OUTBRACE ) + */ + +/**/ +static void +par_case(Cmd c) +{ + int brflag; + LinkList pats, lists; + int n = 1; + char **pp; + List *ll; + LinkNode no; + struct casecmd *cc; + + c->type = CCASE; + incmdpos = 0; + yylex(); + if (tok != STRING) + YYERRORV; + pats = newlinklist(); + addlinknode(pats, tokstr); + incmdpos = 1; + yylex(); + while (tok == SEPER) + yylex(); + if (!(tok == STRING && !strcmp(tokstr, "in")) && tok != INBRACE) + YYERRORV; + brflag = (tok == INBRACE); + incasepat = 1; + incmdpos = 0; + yylex(); + cc = c->u.casecmd = (struct casecmd *)make_casecmd(); + lists = newlinklist(); + for (;;) { + char *str; + + while (tok == SEPER) + yylex(); + if (tok == OUTBRACE) + break; + if (tok != STRING) + YYERRORV; + if (!strcmp(tokstr, "esac")) + break; + str = ncalloc(strlen(tokstr) + 2); + *str = ';'; + strcpy(str + 1, tokstr); + incasepat = 0; + incmdpos = 1; + for (;;) { + yylex(); + if (tok == OUTPAR) { + incasepat = 0; + incmdpos = 1; + yylex(); + break; + } else if (tok == BAR) { + char *str2; + int sl = strlen(str); + + incasepat = 1; + incmdpos = 0; + str2 = ncalloc(sl + 2); + strcpy(str2, str); + str2[sl] = Bar; + str2[sl+1] = '\0'; + str = str2; + } else { + int sl = strlen(str); + + if (str[sl - 1] != Bar) { + /* POSIX allows (foo*) patterns */ + int pct; + char *s; + + for (s = str + 1, pct = 0; *s; s++) { + if (*s == Inpar) + pct++; + if (!pct) + break; + if (pct == 1) { + if (*s == Bar || *s == Inpar) + while (iblank(s[1])) + chuck(s+1); + if (*s == Bar || *s == Outpar) + while (iblank(s[-1]) && + (s < str+2 || s[-2] != Meta)) + chuck(--s); + } + if (*s == Outpar) + pct--; + } + if (*s || pct || s == str + 1) + YYERRORV; + break; + } else { + char *str2; + + if (tok != STRING) + YYERRORV; + str2 = ncalloc(sl + strlen(tokstr) + 1); + strcpy(str2, str); + strcpy(str2 + sl, tokstr); + str = str2; + } + } + } + addlinknode(pats, str); + addlinknode(lists, par_list()); + n++; + if ((tok == ESAC && !brflag) || (tok == OUTBRACE && brflag)) + break; + if(tok == SEMIAMP) + *str = '&'; + else if (tok != DSEMI) + YYERRORV; + incasepat = 1; + incmdpos = 0; + yylex(); + } + + incmdpos = 1; + yylex(); + + cc->pats = (char **)alloc((n + 1) * sizeof(char *)); + + for (pp = cc->pats, no = firstnode(pats); no; incnode(no)) + *pp++ = (char *)getdata(no); + *pp = NULL; + cc->lists = (List *) alloc((n + 1) * sizeof(List)); + for (ll = cc->lists, no = firstnode(lists); no; incnode(no), ll++) + if (!(*ll = (List) getdata(no))) + *ll = &dummy_list; + *ll = NULL; +} + +/* + * if : { ( IF | ELIF ) { SEPER } ( INPAR list OUTPAR | list ) + { SEPER } ( THEN list | INBRACE list OUTBRACE | list1 ) } + [ FI | ELSE list FI | ELSE { SEPER } INBRACE list OUTBRACE ] + (you get the idea...?) + */ + +/**/ +static void +par_if(Cmd c) +{ + struct ifcmd *i; + int xtok; + unsigned char nc; + LinkList ifsl, thensl; + LinkNode no; + int ni = 0, nt = 0, usebrace = 0; + List l, *ll; + + ifsl = newlinklist(); + thensl = newlinklist(); + + c->type = CIF; + for (;;) { + xtok = tok; + cmdpush(xtok == IF ? CS_IF : CS_ELIF); + yylex(); + if (xtok == FI) + break; + if (xtok == ELSE) + break; + while (tok == SEPER) + yylex(); + if (!(xtok == IF || xtok == ELIF)) { + cmdpop(); + YYERRORV; + } + addlinknode(ifsl, par_list()); + ni++; + incmdpos = 1; + while (tok == SEPER) + yylex(); + xtok = FI; + nc = cmdstack[cmdsp - 1] == CS_IF ? CS_IFTHEN : CS_ELIFTHEN; + if (tok == THEN) { + usebrace = 0; + cmdpop(); + cmdpush(nc); + yylex(); + addlinknode(thensl, par_list()); + nt++; + incmdpos = 1; + cmdpop(); + } else { + if (tok == INBRACE) { + usebrace = 1; + cmdpop(); + cmdpush(nc); + yylex(); + l = par_list(); + if (tok != OUTBRACE) { + cmdpop(); + YYERRORV; + } + addlinknode(thensl, l); + nt++; + yylex(); + incmdpos = 1; + if (tok == SEPER) + break; + cmdpop(); + } else if (unset(SHORTLOOPS)) { + cmdpop(); + YYERRORV; + } else { + cmdpop(); + cmdpush(nc); + addlinknode(thensl, par_list1()); + nt++; + incmdpos = 1; + break; + } + } + } + cmdpop(); + if (xtok == ELSE) { + cmdpush(CS_ELSE); + while (tok == SEPER) + yylex(); + if (tok == INBRACE && usebrace) { + yylex(); + l = par_list(); + if (tok != OUTBRACE) { + cmdpop(); + YYERRORV; + } + } else { + l = par_list(); + if (tok != FI) { + cmdpop(); + YYERRORV; + } + } + addlinknode(thensl, l); + nt++; + yylex(); + cmdpop(); + } + i = (struct ifcmd *)make_ifcmd(); + i->ifls = (List *) alloc((ni + 1) * sizeof(List)); + i->thenls = (List *) alloc((nt + 1) * sizeof(List)); + + for (ll = i->ifls, no = firstnode(ifsl); no; incnode(no), ll++) + if (!(*ll = (List) getdata(no))) + *ll = &dummy_list; + *ll = NULL; + for (ll = i->thenls, no = firstnode(thensl); no; incnode(no), ll++) + if (!(*ll = (List) getdata(no))) + *ll = &dummy_list; + *ll = NULL; + + c->u.ifcmd = i; +} + +/* + * while : ( WHILE | UNTIL ) ( INPAR list OUTPAR | list ) { SEPER } + ( DO list DONE | INBRACE list OUTBRACE | list ZEND ) + */ + +/**/ +static void +par_while(Cmd c) +{ + struct whilecmd *w; + + c->type = CWHILE; + w = c->u.whilecmd = (struct whilecmd *)make_whilecmd(); + w->cond = (tok == UNTIL); + yylex(); + w->cont = par_list(); + incmdpos = 1; + while (tok == SEPER) + yylex(); + if (tok == DO) { + yylex(); + w->loop = par_list(); + if (tok != DONE) + YYERRORV; + yylex(); + } else if (tok == INBRACE) { + yylex(); + w->loop = par_list(); + if (tok != OUTBRACE) + YYERRORV; + yylex(); + } else if (isset(CSHJUNKIELOOPS)) { + w->loop = par_list(); + if (tok != ZEND) + YYERRORV; + yylex(); + } else + YYERRORV; +} + +/* + * repeat : REPEAT STRING { SEPER } ( DO list DONE | list1 ) + */ + +/**/ +static void +par_repeat(Cmd c) +{ + c->type = CREPEAT; + incmdpos = 0; + yylex(); + if (tok != STRING) + YYERRORV; + addlinknode(c->args, tokstr); + incmdpos = 1; + yylex(); + while (tok == SEPER) + yylex(); + if (tok == DO) { + yylex(); + c->u.list = par_list(); + if (tok != DONE) + YYERRORV; + yylex(); + } else if (tok == INBRACE) { + yylex(); + c->u.list = par_list(); + if (tok != OUTBRACE) + YYERRORV; + yylex(); + } else if (isset(CSHJUNKIELOOPS)) { + c->u.list = par_list(); + if (tok != ZEND) + YYERRORV; + yylex(); + } else if (unset(SHORTLOOPS)) { + YYERRORV; + } else + c->u.list = par_list1(); +} + +/* + * subsh : ( INPAR | INBRACE ) list ( OUTPAR | OUTBRACE ) + */ + +/**/ +static void +par_subsh(Cmd c) +{ + c->type = (tok == INPAR) ? SUBSH : CURSH; + yylex(); + c->u.list = par_list(); + if (tok != ((c->type == SUBSH) ? OUTPAR : OUTBRACE)) + YYERRORV; + incmdpos = 1; + yylex(); +} + +/* + * funcdef : FUNCTION wordlist [ INOUTPAR ] { SEPER } + * ( list1 | INBRACE list OUTBRACE ) + */ + +/**/ +static void +par_funcdef(Cmd c) +{ + nocorrect = 1; + incmdpos = 0; + yylex(); + c->type = FUNCDEF; + c->args = newlinklist(); + incmdpos = 1; + while (tok == STRING) { + if (*tokstr == Inbrace && !tokstr[1]) { + tok = INBRACE; + break; + } + addlinknode(c->args, tokstr); + yylex(); + } + nocorrect = 0; + if (tok == INOUTPAR) + yylex(); + while (tok == SEPER) + yylex(); + if (tok == INBRACE) { + yylex(); + c->u.list = par_list(); + if (tok != OUTBRACE) + YYERRORV; + yylex(); + } else if (unset(SHORTLOOPS)) { + YYERRORV; + } else + c->u.list = par_list1(); +} + +/* + * time : TIME sublist2 + */ + +/**/ +static void +par_time(Cmd c) +{ + yylex(); + c->type = ZCTIME; + c->u.pline = par_sublist2(); +} + +/* + * dinbrack : DINBRACK cond DOUTBRACK + */ + +/**/ +static void +par_dinbrack(Cmd c) +{ + c->type = COND; + incond = 1; + incmdpos = 0; + yylex(); + c->u.cond = par_cond(); + if (tok != DOUTBRACK) + YYERRORV; + incond = 0; + incmdpos = 1; + yylex(); +} + +/* + * simple : { COMMAND | EXEC | NOGLOB | NOCORRECT | DASH } + { STRING | ENVSTRING | ENVARRAY wordlist OUTPAR | redir } + [ INOUTPAR { SEPER } ( list1 | INBRACE list OUTBRACE ) ] + */ + +/**/ +static Cmd +par_simple(Cmd c) +{ + int isnull = 1; + + c->type = SIMPLE; + for (;;) { + if (tok == NOCORRECT) + nocorrect = 1; + else if (tok == ENVSTRING) { + struct varasg *v = (struct varasg *)make_varnode(); + + v->type = PM_SCALAR; + equalsplit(v->name = tokstr, &v->str); + addlinknode(c->vars, v); + isnull = 0; + } else if (tok == ENVARRAY) { + struct varasg *v = (struct varasg *)make_varnode(); + int oldcmdpos = incmdpos; + + v->type = PM_ARRAY; + incmdpos = 0; + v->name = tokstr; + cmdpush(CS_ARRAY); + yylex(); + v->arr = par_nl_wordlist(); + cmdpop(); + if (tok != OUTPAR) + YYERROR; + incmdpos = oldcmdpos; + addlinknode(c->vars, v); + isnull = 0; + } else + break; + yylex(); + } + if (tok == AMPER || tok == AMPERBANG) + YYERROR; + for (;;) { + if (tok == STRING) { + incmdpos = 0; + addlinknode(c->args, tokstr); + yylex(); + } else if (IS_REDIROP(tok)) { + par_redir(c->redir); + } else if (tok == INOUTPAR) { + incmdpos = 1; + cmdpush(CS_FUNCDEF); + yylex(); + while (tok == SEPER) + yylex(); + if (tok == INBRACE) { + yylex(); + c->u.list = par_list(); + if (tok != OUTBRACE) { + cmdpop(); + YYERROR; + } + yylex(); + } else + c->u.list = (List) expandstruct((struct node *) par_cmd(), N_LIST); + cmdpop(); + c->type = FUNCDEF; + } else + break; + isnull = 0; + } + if (isnull && empty(c->redir)) + return NULL; + incmdpos = 1; + return c; +} + +/* + * condlex is yylex for normal parsing, but is altered to allow + * the test builtin to use par_cond. + */ + +/**/ +void (*condlex) _((void)) = yylex; + +/* + * cond : cond_1 { SEPER } [ DBAR { SEPER } cond ] + */ + +/**/ +Cond +par_cond(void) +{ + Cond c, c2; + + c = par_cond_1(); + while (tok == SEPER) + condlex(); + if (tok == DBAR) { + condlex(); + while (tok == SEPER) + condlex(); + c2 = (Cond) make_cond(); + c2->left = (void *) c; + c2->right = (void *) par_cond(); + c2->type = COND_OR; + return c2; + } + return c; +} + +/* + * cond_1 : cond_2 { SEPER } [ DAMPER { SEPER } cond_1 ] + */ + +/**/ +static Cond +par_cond_1(void) +{ + Cond c, c2; + + c = par_cond_2(); + while (tok == SEPER) + condlex(); + if (tok == DAMPER) { + condlex(); + while (tok == SEPER) + condlex(); + c2 = (Cond) make_cond(); + c2->left = (void *) c; + c2->right = (void *) par_cond_1(); + c2->type = COND_AND; + return c2; + } + return c; +} + +/* + * cond_2 : BANG cond_2 + | INPAR { SEPER } cond_2 { SEPER } OUTPAR + | STRING STRING STRING + | STRING STRING + | STRING ( INANG | OUTANG ) STRING + */ + +/**/ +static Cond +par_cond_2(void) +{ + Cond c, c2; + char *s1, *s2, *s3; + int dble = 0; + + if (condlex == testlex) { + /* See the description of test in POSIX 1003.2 */ + if (tok == NULLTOK) + /* no arguments: false */ + return par_cond_double(dupstring("-n"), dupstring("")); + if (!*testargs) { + /* one argument: [ foo ] is equivalent to [ -n foo ] */ + s1 = tokstr; + condlex(); + return par_cond_double(dupstring("-n"), s1); + } + if (testargs[1] && !testargs[2]) { + /* three arguments: if the second argument is a binary operator, * + * perform that binary test on the first and the trird argument */ + if (!strcmp(*testargs, "=") || + !strcmp(*testargs, "==") || + !strcmp(*testargs, "!=") || + (**testargs == '-' && get_cond_num(*testargs + 1) >= 0)) { + s1 = tokstr; + condlex(); + s2 = tokstr; + condlex(); + s3 = tokstr; + condlex(); + return par_cond_triple(s1, s2, s3); + } + } + } + if (tok == BANG) { + condlex(); + c = par_cond_2(); + c2 = (Cond) make_cond(); + c2->left = (void *) c; + c2->type = COND_NOT; + return c2; + } + if (tok == INPAR) { + condlex(); + while (tok == SEPER) + condlex(); + c = par_cond(); + while (tok == SEPER) + condlex(); + if (tok != OUTPAR) + YYERROR; + condlex(); + return c; + } + if (tok != STRING) + if (tok && tok != LEXERR && condlex == testlex) { + s1 = tokstr; + condlex(); + return par_cond_double("-n", s1); + } else + YYERROR; + s1 = tokstr; + if (condlex == testlex) + dble = (*s1 == '-' && strspn(s1+1, "abcdefghknoprstuwxzLONGS") == 1 + && !s1[2]); + condlex(); + if (tok == INANG || tok == OUTANG) { + int xtok = tok; + condlex(); + if (tok != STRING) + YYERROR; + s3 = tokstr; + condlex(); + c = (Cond) make_cond(); + c->left = (void *) s1; + c->right = (void *) s3; + c->type = (xtok == INANG) ? COND_STRLT : COND_STRGTR; + c->ntype = NT_SET(N_COND, NT_STR, NT_STR, 0, 0); + return c; + } + if (tok != STRING) + if (tok != LEXERR && condlex == testlex) { + if (!dble) + return par_cond_double("-n", s1); + else if (!strcmp(s1, "-t")) + return par_cond_double(s1, "1"); + } else + YYERROR; + s2 = tokstr; + incond++; /* parentheses do globbing */ + condlex(); + incond--; /* parentheses do grouping */ + if (tok == STRING && !dble) { + s3 = tokstr; + condlex(); + return par_cond_triple(s1, s2, s3); + } else + return par_cond_double(s1, s2); +} + +/* + * redir : ( OUTANG | ... | TRINANG ) STRING + */ + +static int redirtab[TRINANG - OUTANG + 1] = { + WRITE, + WRITENOW, + APP, + APPNOW, + READ, + READWRITE, + HEREDOC, + HEREDOCDASH, + MERGEIN, + MERGEOUT, + ERRWRITE, + ERRWRITENOW, + ERRAPP, + ERRAPPNOW, + HERESTR, +}; + +/**/ +static void +par_redir(LinkList l) +{ + struct redir *fn = (struct redir *)allocnode(N_REDIR); + int oldcmdpos, oldnc; + + oldcmdpos = incmdpos; + incmdpos = 0; + oldnc = nocorrect; + if (tok != INANG && tok != INOUTANG) + nocorrect = 1; + fn->type = redirtab[tok - OUTANG]; + fn->fd1 = tokfd; + yylex(); + if (tok != STRING && tok != ENVSTRING) + YYERRORV; + incmdpos = oldcmdpos; + nocorrect = oldnc; + + /* assign default fd */ + if (fn->fd1 == -1) + fn->fd1 = IS_READFD(fn->type) ? 0 : 1; + + fn->name = tokstr; + + switch (fn->type) { + case HEREDOC: + case HEREDOCDASH: { + /* <<[-] name */ + struct heredocs **hd; + + for (hd = &hdocs; *hd; hd = &(*hd)->next); + *hd = zalloc(sizeof(struct heredocs)); + (*hd)->next = NULL; + (*hd)->rd = fn; + break; + } + case WRITE: + case WRITENOW: + if (tokstr[0] == Outang && tokstr[1] == Inpar) + /* > >(...) */ + fn->type = OUTPIPE; + else if (tokstr[0] == Inang && tokstr[1] == Inpar) + YYERRORV; + break; + case READ: + if (tokstr[0] == Inang && tokstr[1] == Inpar) + /* < <(...) */ + fn->type = INPIPE; + else if (tokstr[0] == Outang && tokstr[1] == Inpar) + YYERRORV; + break; + case READWRITE: + if ((tokstr[0] == Inang || tokstr[0] == Outang) && tokstr[1] == Inpar) + fn->type = tokstr[0] == Inang ? INPIPE : OUTPIPE; + break; + } + yylex(); + addlinknode(l, fn); +} + +/* + * wordlist : { STRING } + */ + +/**/ +static LinkList +par_wordlist(void) +{ + LinkList l; + + l = newlinklist(); + while (tok == STRING) { + addlinknode(l, tokstr); + yylex(); + } + return l; +} + +/* + * nl_wordlist : { STRING | SEPER } + */ + +/**/ +static LinkList +par_nl_wordlist(void) +{ + LinkList l; + + l = newlinklist(); + while (tok == STRING || tok == SEPER) { + if (tok != SEPER) + addlinknode(l, tokstr); + yylex(); + } + return l; +} + +/**/ +static Cond +par_cond_double(char *a, char *b) +{ + Cond n = (Cond) make_cond(); + + if (a[0] != '-' || !a[1] || a[2]) + COND_ERROR("parse error: condition expected: %s", a); + n->left = (void *) b; + n->type = a[1]; + n->ntype = NT_SET(N_COND, NT_STR, NT_STR, 0, 0); + return n; +} + +/**/ +static int +get_cond_num(char *tst) +{ + static char *condstrs[] = + { + "nt", "ot", "ef", "eq", "ne", "lt", "gt", "le", "ge", NULL + }; + int t0; + + for (t0 = 0; condstrs[t0]; t0++) + if (!strcmp(condstrs[t0], tst)) + return t0; + return -1; +} + +/**/ +static Cond +par_cond_triple(char *a, char *b, char *c) +{ + Cond n = (Cond) make_cond(); + int t0; + + if ((b[0] == Equals || b[0] == '=') && + (!b[1] || ((b[1] == Equals || b[1] == '=') && !b[2]))) + n->type = COND_STREQ; + else if (b[0] == '!' && (b[1] == Equals || b[1] == '=') && !b[2]) + n->type = COND_STRNEQ; + else if (b[0] == '-') { + if ((t0 = get_cond_num(b + 1)) > -1) + n->type = t0 + COND_NT; + else + COND_ERROR("unrecognized condition: %s", b); + } else + COND_ERROR("condition expected: %s", b); + n->left = (void *) a; + n->right = (void *) c; + n->ntype = NT_SET(N_COND, NT_STR, NT_STR, 0, 0); + return n; +} + +/**/ +static void +yyerror(void) +{ + int t0; + + for (t0 = 0; t0 != 20; t0++) + if (!yytext || !yytext[t0] || yytext[t0] == '\n') + break; + if (t0 == 20) + zerr("parse error near `%l...'", yytext, 20); + else if (t0) + zerr("parse error near `%l'", yytext, t0); + else + zerr("parse error", NULL, 0); +} diff --git a/Src/prompt.c b/Src/prompt.c new file mode 100644 index 000000000..8c9216f95 --- /dev/null +++ b/Src/prompt.c @@ -0,0 +1,766 @@ +/* + * prompt.c - construct zsh prompts + * + * 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 "prompt.pro" + +/* text attribute mask */ + +/**/ +unsigned txtattrmask; + +/* text change - attribute change made by prompts */ + +/**/ +unsigned txtchange; + +/* the command stack for use with %_ in prompts */ + +/**/ +unsigned char *cmdstack; +/**/ +int cmdsp; + +/* parser states, for %_ */ + +static char *cmdnames[] = { + "for", "while", "repeat", "select", + "until", "if", "then", "else", + "elif", "math", "cond", "cmdor", + "cmdand", "pipe", "errpipe", "foreach", + "case", "function", "subsh", "cursh", + "array", "quote", "dquote", "bquote", + "cmdsubst", "mathsubst", "elif-then", "heredoc", + "heredocd", "brace", "braceparam", +}; + +/* The buffer into which an expanded and metafied prompt is being written, * + * and its size. */ + +static char *buf; +static int bufspc; + +/* bp is the pointer to the current position in the buffer, where the next * + * character will be added. */ + +static char *bp; + +/* bp1 is an auxilliary pointer into the buffer, which when non-NULL is * + * moved whenever the buffer is reallocated. It is used when data is * + * being temporarily held in the buffer. */ + +static char *bp1; + +/* The format string, for %-expansion. */ + +static char *fm; + +/* Current truncation string (metafied), the length at which truncation * + * occurs, and the direction in which it occurs. */ + +static char *truncstr; +static int trunclen, truncatleft; + +/* Current level of nesting of %{ / %} sequences. */ + +static int dontcount; + +/* Strings to use for %r and %R (for the spelling prompt). */ + +static char *rstring, *Rstring; + +/* If non-zero, Inpar, Outpar and Nularg can be added to the buffer. */ + +static int nonsp; + +/* Perform prompt expansion on a string, putting the result in a * + * permanently-allocated string. If ns is non-zero, this string * + * may have embedded Inpar and Outpar, which indicate a toggling * + * between spacing and non-spacing parts of the prompt, and * + * Nularg, which (in a non-spacing sequence) indicates a * + * `glitch' space. */ + +/**/ +char * +promptexpand(char *s, int ns, char *rs, char *Rs) +{ + if(!s) + return ztrdup(""); + + if ((termflags & TERM_UNKNOWN) && (unset(INTERACTIVE))) + init_term(); + + if (isset(PROMPTSUBST)) { + int olderr = errflag; + + HEAPALLOC { + s = dupstring(s); + if (!parsestr(s)) + singsub(&s); + } LASTALLOC; + /* Ignore errors in prompt substitution */ + errflag = olderr; + } + + rstring = rs; + Rstring = Rs; + nonsp = ns; + fm = s; + bp = buf = zalloc(bufspc = 256); + bp1 = NULL; + trunclen = 0; + putpromptchar(1, '\0'); + addbufspc(1); + if(dontcount) + *bp++ = Outpar; + *bp = 0; + return buf; +} + +/* Perform %- and !-expansion as required on a section of the prompt. The * + * section is ended by an instance of endchar. If doprint is 0, the valid * + * % sequences are merely skipped over, and nothing is stored. */ + +/**/ +static int +putpromptchar(int doprint, int endchar) +{ + char *ss, *tmbuf = NULL; + int t0, arg, test, sep; + struct tm *tm; + time_t timet; + Nameddir nd; + + for (; *fm && *fm != endchar; fm++) { + arg = 0; + if (*fm == '%' && isset(PROMPTPERCENT)) { + if (idigit(*++fm)) { + arg = zstrtol(fm, &fm, 10); + } + if (*fm == '(') { + int tc; + + if (idigit(*++fm)) { + arg = zstrtol(fm, &fm, 10); + } + test = 0; + ss = pwd; + switch (tc = *fm) { + case 'c': + case '.': + case '~': + if ((nd = finddir(ss))) { + arg--; + ss += strlen(nd->dir); + } + case '/': + case 'C': + for (; *ss; ss++) + if (*ss == '/') + arg--; + if (arg <= 0) + test = 1; + break; + case 't': + case 'T': + case 'd': + case 'D': + case 'w': + timet = time(NULL); + tm = localtime(&timet); + switch (tc) { + case 't': + test = (arg == tm->tm_min); + break; + case 'T': + test = (arg == tm->tm_hour); + break; + case 'd': + test = (arg == tm->tm_mday); + break; + case 'D': + test = (arg == tm->tm_mon); + break; + case 'w': + test = (arg == tm->tm_wday); + break; + } + break; + case '?': + if (lastval == arg) + test = 1; + break; + case '#': + if (geteuid() == arg) + test = 1; + break; + case 'g': + if (getegid() == arg) + test = 1; + break; + case 'L': + if (shlvl >= arg) + test = 1; + break; + case 'S': + if (time(NULL) - shtimer.tv_sec >= arg) + test = 1; + break; + case 'v': + if (arrlen(psvar) >= arg) + test = 1; + break; + case '_': + test = (cmdsp >= arg); + break; + case '!': + test = privasserted(); + break; + default: + test = -1; + break; + } + if (!*fm || !(sep = *++fm)) + return 0; + fm++; + if (!putpromptchar(test == 1 && doprint, sep) || !*++fm || + !putpromptchar(test == 0 && doprint, ')')) { + return 0; + } + continue; + } + if (!doprint) + switch(*fm) { + case '[': + while(idigit(*++fm)); + while(*++fm != ']'); + continue; + case '<': + while(*++fm != '<'); + continue; + case '>': + while(*++fm != '>'); + continue; + case 'D': + if(fm[1]=='{') + while(*++fm != '}'); + continue; + default: + continue; + } + switch (*fm) { + case '~': + if ((nd = finddir(pwd))) { + char *t = tricat("~", nd->nam, pwd + strlen(nd->dir)); + stradd(t); + zsfree(t); + break; + } + case 'd': + case '/': + stradd(pwd); + break; + case 'c': + case '.': + { + char *t; + + if ((nd = finddir(pwd))) + t = tricat("~", nd->nam, pwd + strlen(nd->dir)); + else + t = ztrdup(pwd); + if (!arg) + arg++; + for (ss = t + strlen(t); ss > t; ss--) + if (*ss == '/' && !--arg) { + ss++; + break; + } + if(*ss == '/' && ss[1] && ss != t) + ss++; + stradd(ss); + zsfree(t); + break; + } + case 'C': + if (!arg) + arg++; + for (ss = pwd + strlen(pwd); ss > pwd; ss--) + if (*ss == '/' && !--arg) { + ss++; + break; + } + if (*ss == '/' && ss[1] && (ss != pwd)) + ss++; + stradd(ss); + break; + case 'h': + case '!': + addbufspc(DIGBUFSIZE); + sprintf(bp, "%d", curhist); + bp += strlen(bp); + break; + case 'M': + stradd(hostnam); + break; + case 'm': + if (!arg) + arg++; + for (ss = hostnam; *ss; ss++) + if (*ss == '.' && !--arg) + break; + t0 = *ss; + *ss = '\0'; + stradd(hostnam); + *ss = t0; + break; + case 'S': + txtchangeset(TXTSTANDOUT, TXTNOSTANDOUT); + txtset(TXTSTANDOUT); + tsetcap(TCSTANDOUTBEG, 1); + break; + case 's': + txtchangeset(TXTNOSTANDOUT, TXTSTANDOUT); + txtset(TXTDIRTY); + txtunset(TXTSTANDOUT); + tsetcap(TCSTANDOUTEND, 1); + break; + case 'B': + txtchangeset(TXTBOLDFACE, TXTNOBOLDFACE); + txtset(TXTDIRTY); + txtset(TXTBOLDFACE); + tsetcap(TCBOLDFACEBEG, 1); + break; + case 'b': + txtchangeset(TXTNOBOLDFACE, TXTBOLDFACE); + txtchangeset(TXTNOSTANDOUT, TXTSTANDOUT); + txtchangeset(TXTNOUNDERLINE, TXTUNDERLINE); + txtset(TXTDIRTY); + txtunset(TXTBOLDFACE); + tsetcap(TCALLATTRSOFF, 1); + break; + case 'U': + txtchangeset(TXTUNDERLINE, TXTNOUNDERLINE); + txtset(TXTUNDERLINE); + tsetcap(TCUNDERLINEBEG, 1); + break; + case 'u': + txtchangeset(TXTNOUNDERLINE, TXTUNDERLINE); + txtset(TXTDIRTY); + txtunset(TXTUNDERLINE); + tsetcap(TCUNDERLINEEND, 1); + break; + case '[': + if (idigit(*++fm)) + trunclen = zstrtol(fm, &fm, 10); + else + trunclen = arg; + if (trunclen) { + truncatleft = *fm && *fm != ']' && *fm++ == '<'; + bp1 = bp; + while (*fm && *fm != ']') { + if (*fm == '\\' && fm[1]) + ++fm; + addbufspc(1); + *bp++ = *fm++; + } + addbufspc(2); + if (bp1 == bp) + *bp++ = '<'; + *bp = '\0'; + zsfree(truncstr); + truncstr = ztrdup(bp = bp1); + bp1 = NULL; + } else { + while (*fm && *fm != ']') { + if (*fm == '\\' && fm[1]) + fm++; + fm++; + } + } + if(!*fm) + return 0; + break; + case '<': + case '>': + if((trunclen = arg)) { + char ch = *fm++; + truncatleft = ch == '<'; + bp1 = bp; + while (*fm && *fm != ch) { + if (*fm == '\\' && fm[1]) + ++fm; + addbufspc(1); + *bp++ = *fm++; + } + addbufspc(1); + *bp = '\0'; + zsfree(truncstr); + truncstr = ztrdup(bp = bp1); + bp1 = NULL; + } else { + char ch = *fm++; + while(*fm && *fm != ch) { + if (*fm == '\\' && fm[1]) + fm++; + fm++; + } + } + if(!*fm) + return 0; + break; + case '{': /*}*/ + if (!dontcount++ && nonsp) { + addbufspc(1); + *bp++ = Inpar; + } + break; + case /*{*/ '}': + if (dontcount && !--dontcount && nonsp) { + addbufspc(1); + *bp++ = Outpar; + } + break; + case 't': + case '@': + case 'T': + case '*': + case 'w': + case 'W': + case 'D': + { + char *tmfmt, *dd; + + switch (*fm) { + case 'T': + tmfmt = "%K:%M"; + break; + case '*': + tmfmt = "%K:%M:%S"; + break; + case 'w': + tmfmt = "%a %f"; + break; + case 'W': + tmfmt = "%m/%d/%y"; + break; + case 'D': + if (fm[1] == '{') /*}*/ { + for (ss = fm + 2; *ss && *ss != /*{*/ '}'; ss++) + if(*ss == '\\' && ss[1]) + ss++; + dd = tmfmt = tmbuf = zalloc(ss - fm); + for (ss = fm + 2; *ss && *ss != /*{*/ '}'; ss++) { + if(*ss == '\\' && ss[1]) + ss++; + *dd++ = *ss; + } + *dd = 0; + fm = ss - !*ss; + } else + tmfmt = "%y-%m-%d"; + break; + default: + tmfmt = "%l:%M%p"; + break; + } + timet = time(NULL); + tm = localtime(&timet); + for(t0=80; ; t0*=2) { + addbufspc(t0); + if(ztrftime(bp, t0, tmfmt, tm) != t0) + break; + } + bp += strlen(bp); + free(tmbuf); + tmbuf = NULL; + break; + } + case 'n': + stradd(get_username()); + break; + case 'l': + if (*ttystrname) { + ss = (strncmp(ttystrname, "/dev/tty", 8) ? + ttystrname + 5 : ttystrname + 8); + stradd(ss); + } else + stradd("()"); + break; + case 'L': + addbufspc(DIGBUFSIZE); + sprintf(bp, "%ld", (long)shlvl); + bp += strlen(bp); + break; + case '?': + addbufspc(DIGBUFSIZE); + sprintf(bp, "%ld", (long)lastval); + bp += strlen(bp); + break; + case '%': + case ')': + addbufspc(1); + *bp++ = *fm; + break; + case '#': + addbufspc(1); + *bp++ = privasserted() ? '#' : '%'; + break; + case 'v': + if (!arg) + arg = 1; + if (arrlen(psvar) >= arg) + stradd(psvar[arg - 1]); + break; + case 'E': + tsetcap(TCCLEAREOL, 1); + break; + case '_': + if (cmdsp) { + if (arg > cmdsp || arg <= 0) + arg = cmdsp; + for (t0 = cmdsp - arg; arg--; t0++) { + stradd(cmdnames[cmdstack[t0]]); + if (arg) { + addbufspc(1); + *bp++=' '; + } + } + } + break; + case 'r': + if(rstring) + stradd(rstring); + break; + case 'R': + if(Rstring) + stradd(Rstring); + break; + case '\0': + return 0; + case Meta: + fm++; + break; + } + } else if(*fm == '!' && isset(PROMPTBANG)) { + if(doprint) + if(fm[1] == '!') { + fm++; + addbufspc(1); + pputc('!'); + } else { + addbufspc(DIGBUFSIZE); + sprintf(bp, "%d", curhist); + bp += strlen(bp); + } + } else { + char c = *fm == Meta ? *++fm ^ 32 : *fm; + + if (doprint) { + addbufspc(1); + pputc(c); + } + } + } + + return *fm; +} + +/* pputc adds a character to the buffer, metafying. There must * + * already be space. */ + +/**/ +static void +pputc(char c) +{ + if(imeta(STOUC(c))) { + *bp++ = Meta; + c ^= 32; + } + *bp++ = c; +} + +/* Make sure there is room for `need' more characters in the buffer. */ + +/**/ +static void +addbufspc(int need) +{ + need *= 2; /* for metafication */ + if((bp - buf) + need > bufspc) { + int bo = bp - buf; + int bo1 = bp1 ? bp1 - buf : -1; + + if(need & 255) + need = (need | 255) + 1; + buf = realloc(buf, bufspc += need); + bp = buf + bo; + if(bo1 != -1) + bp1 = buf + bo1; + } +} + +/* stradd() adds a metafied string to the prompt, * + * in a visible representation, doing truncation. */ + +/**/ +void +stradd(char *d) +{ + /* dlen is the full length of the string we want to add */ + int dlen = niceztrlen(d); + char *ps, *pd, *pc, *t; + int tlen, maxlen; + addbufspc(dlen); + /* This loop puts the nice representation of the string into the prompt * + * buffer. It might be modified later. Note that bp isn't changed. */ + for(ps=d, pd=bp; *ps; ps++) + for(pc=nicechar(*ps == Meta ? STOUC(*++ps)^32 : STOUC(*ps)); *pc; pc++) + *pd++ = *pc; + if(!trunclen || dlen <= trunclen) { + /* No truncation is needed, so update bp and return, * + * leaving the full string in the prompt. */ + bp += dlen; + return; + } + /* We need to truncate. t points to the truncation string -- which is * + * inserted literally, without nice representation. tlen is its * + * length, and maxlen is the amout of the main string that we want to * + * keep. Note that if the truncation string is longer than the * + * truncation length (tlen > trunclen), the truncation string is used * + * in full. */ + addbufspc(tlen = ztrlen(t = truncstr)); + maxlen = tlen < trunclen ? trunclen - tlen : 0; + if(truncatleft) { + memmove(bp + strlen(t), bp + dlen - maxlen, maxlen); + while(*t) + *bp++ = *t++; + bp += maxlen; + } else { + bp += maxlen; + while(*t) + *bp++ = *t++; + } +} + +/* tsetcap(), among other things, can write a termcap string into the buffer. */ + +/**/ +void +tsetcap(int cap, int flag) +{ + if (!(termflags & TERM_SHORT) && tcstr[cap]) { + switch(flag) { + case -1: + tputs(tcstr[cap], 1, putraw); + break; + case 0: + tputs(tcstr[cap], 1, putshout); + break; + case 1: + if (!dontcount && nonsp) { + addbufspc(1); + *bp++ = Inpar; + } + tputs(tcstr[cap], 1, putstr); + if (!dontcount && nonsp) { + int glitch = 0; + + if (cap == TCSTANDOUTBEG || cap == TCSTANDOUTEND) + glitch = tgetnum("sg"); + else if (cap == TCUNDERLINEBEG || cap == TCUNDERLINEEND) + glitch = tgetnum("ug"); + if(glitch < 0) + glitch = 0; + addbufspc(glitch + 1); + while(glitch--) + *bp++ = Nularg; + *bp++ = Outpar; + } + break; + } + + if (txtisset(TXTDIRTY)) { + txtunset(TXTDIRTY); + if (txtisset(TXTBOLDFACE) && cap != TCBOLDFACEBEG) + tsetcap(TCBOLDFACEBEG, flag); + if (txtisset(TXTSTANDOUT)) + tsetcap(TCSTANDOUTBEG, flag); + if (txtisset(TXTUNDERLINE)) + tsetcap(TCUNDERLINEBEG, flag); + } + } +} + +/**/ +int +putstr(int d) +{ + addbufspc(1); + pputc(d); + return 0; +} + +/* Count height etc. of a prompt string returned by promptexpand(). * + * This depends on the current terminal width, and tabs and * + * newlines require nontrivial processing. */ + +/**/ +void +countprompt(char *str, int *wp, int *hp) +{ + int w = 0, h = 1; + int s = 1; + for(; *str; str++) { + if(*str == Meta) + str++; + if(*str == Inpar) + s = 0; + else if(*str == Outpar) + s = 1; + else if(*str == Nularg) + w++; + else if(s) { + if(*str == '\t') + w = (w | 7) + 1; + else if(*str == '\n') + w = columns; + else + w++; + } + if(w >= columns) { + w = 0; + h++; + } + } + if(wp) + *wp = w; + if(hp) + *hp = h; +} diff --git a/Src/prototypes.h b/Src/prototypes.h new file mode 100644 index 000000000..f7f560111 --- /dev/null +++ b/Src/prototypes.h @@ -0,0 +1,120 @@ +/* + * prototypes.h - prototypes header file + * + * 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. + * + */ + +#ifndef HAVE_STDLIB_H +char *malloc _((size_t)); +char *realloc _((void *, size_t)); +char *calloc _((size_t, size_t)); +#endif + +#ifndef HAVE_TERMCAP_H +extern int tgetent _((char *bp, char *name)); +extern int tgetnum _((char *id)); +extern int tgetflag _((char *id)); +extern char *tgetstr _((char *id, char **area)); +extern char *tgoto _((char *cm, int destcol, int destline)); +extern int tputs _((char *cp, int affcnt, int (*outc) (int))); +#endif + +/* MISSING PROTOTYPES FOR VARIOUS OPERATING SYSTEMS */ + +/* HP/UX 9 c89 */ +#if defined(__hpux) && defined(_XPG3) && !defined(_POSIX1_1988) +# define WRITE_ARG_2_T void * +#else +# define WRITE_ARG_2_T char * +#endif + +#if defined(__hpux) && defined(_HPUX_SOURCE) +# define SELECT_ARG_2_T int * +#else +# define SELECT_ARG_2_T fd_set * +#endif + +#ifdef __osf__ +char *mktemp _((char *)); +#endif + +#if defined(__osf__) && defined(__alpha) && defined(__GNUC__) +/* Digital cc does not need these prototypes, gcc does need them */ +# ifndef HAVE_IOCTL_PROTO +int ioctl _((int d, unsigned long request, void *argp)); +# endif +int mknod _((const char *pathname, int mode, dev_t device)); +int nice _((int increment)); +int select _((int nfds, fd_set * readfds, fd_set * writefds, fd_set * exceptfds, struct timeval *timeout)); +#endif + +#if defined(DGUX) && defined(__STDC__) +/* Just plain missing. */ +extern int getrlimit _((int resource, struct rlimit *rlp)); +extern int setrlimit _((int resource, const struct rlimit *rlp)); +extern int getrusage _((int who, struct rusage *rusage)); +extern int gettimeofday _((struct timeval *tv, struct timezone *tz)); +extern int wait3 _((union wait *wait_status, int options, struct rusage *rusage)); +extern int getdomainname _((char *name, int maxlength)); +extern int select _((int nfds, fd_set * readfds, fd_set * writefds, fd_set * exceptfds, struct timeval *timeout)); +#endif /* DGUX and __STDC__ */ + +#ifdef __NeXT__ +extern pid_t getppid(void); +#endif + +#if defined(__sun__) && !defined(__SVR4) /* SunOS */ +extern char *strerror _((int errnum)); +#endif + +/**************************************************/ +/*** prototypes for functions built in compat.c ***/ +#ifndef HAVE_STRSTR +extern char *strstr _((const char *s, const char *t)); +#endif + +#ifndef HAVE_GETHOSTNAME +extern int gethostname _((char *name, size_t namelen)); +#endif + +#ifndef HAVE_GETTIMEOFDAY +extern int gettimeofday _((struct timeval *tv, struct timezone *tz)); +#endif + +#ifndef HAVE_DIFFTIME +extern double difftime _((time_t t2, time_t t1)); +#endif + +#ifndef HAVE_STRERROR +extern char *strerror _((int errnum)); +#endif + +/*** end of prototypes for functions in compat.c ***/ +/***************************************************/ + +#ifndef HAVE_MEMMOVE +extern void bcopy _((const void *, void *, int)); +#endif diff --git a/Src/signals.c b/Src/signals.c new file mode 100644 index 000000000..5dc19dd22 --- /dev/null +++ b/Src/signals.c @@ -0,0 +1,748 @@ +/* + * signals.c - signals handling code + * + * 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 "signals.pro" + +/* Array describing the state of each signal: an element contains * + * 0 for the default action or some ZSIG_* flags ored together. */ + +/**/ +int sigtrapped[VSIGCOUNT]; + +/* trap functions for each signal */ + +/**/ +List sigfuncs[VSIGCOUNT]; + +/* Variables used by signal queueing */ + +/**/ +int queueing_enabled, queue_front, queue_rear; +/**/ +int signal_queue[MAX_QUEUE_SIZE]; +/**/ +sigset_t signal_mask_queue[MAX_QUEUE_SIZE]; + +/* This is only used on machines that don't understand signal sets. * + * On SYSV machines this will represent the signals that are blocked * + * (held) using sighold. On machines which can't block signals at * + * all, we will simulate this by ignoring them and remembering them * + * in this variable. */ +#if !defined(POSIX_SIGNALS) && !defined(BSD_SIGNALS) +static sigset_t blocked_set; +#endif + +#ifdef POSIX_SIGNALS +# define signal_jmp_buf sigjmp_buf +# define signal_setjmp(b) sigsetjmp((b),1) +# define signal_longjmp(b,n) siglongjmp((b),(n)) +#else +# define signal_jmp_buf jmp_buf +# define signal_setjmp(b) setjmp(b) +# define signal_longjmp(b,n) longjmp((b),(n)) +#endif + +#ifdef NO_SIGNAL_BLOCKING +# define signal_process(sig) signal_ignore(sig) +# define signal_reset(sig) install_handler(sig) +#else +# define signal_process(sig) ; +# define signal_reset(sig) ; +#endif + +/* Install signal handler for given signal. * + * If possible, we want to make sure that interrupted * + * system calls are not restarted. */ + +/**/ +void +install_handler(int sig) +{ +#ifdef POSIX_SIGNALS + struct sigaction act; + + act.sa_handler = (SIGNAL_HANDTYPE) handler; + sigemptyset(&act.sa_mask); /* only block sig while in handler */ + act.sa_flags = 0; +# ifdef SA_INTERRUPT /* SunOS 4.x */ + if (interact) + act.sa_flags |= SA_INTERRUPT; /* make sure system calls are not restarted */ +# endif + sigaction(sig, &act, (struct sigaction *)NULL); +#else +# ifdef BSD_SIGNALS + struct sigvec vec; + + vec.sv_handler = (SIGNAL_HANDTYPE) handler; + vec.sv_mask = sigmask(sig); /* mask out this signal while in handler */ +# ifdef SV_INTERRUPT + vec.sv_flags = SV_INTERRUPT; /* make sure system calls are not restarted */ +# endif + sigvec(sig, &vec, (struct sigvec *)NULL); +# else +# ifdef SYSV_SIGNALS + /* we want sigset rather than signal because it will * + * block sig while in handler. signal usually doesn't */ + sigset(sig, handler); +# else /* NO_SIGNAL_BLOCKING (bummer) */ + signal(sig, handler); + +# endif /* SYSV_SIGNALS */ +# endif /* BSD_SIGNALS */ +#endif /* POSIX_SIGNALS */ +} + +/* enable ^C interrupts */ + +/**/ +void +intr(void) +{ + if (interact) + install_handler(SIGINT); +} + +#if 0 +/* disable ^C interrupts */ + +/**/ +void +nointr(void) +{ + if (interact) + signal_ignore(SIGINT); +} +#endif + +/* temporarily block ^C interrupts */ + +/**/ +void +holdintr(void) +{ + if (interact) + signal_block(signal_mask(SIGINT)); +} + +/* release ^C interrupts */ + +/**/ +void +noholdintr(void) +{ + if (interact) + signal_unblock(signal_mask(SIGINT)); +} + +/* create a signal mask containing * + * only the given signal */ + +/**/ +sigset_t +signal_mask(int sig) +{ + sigset_t set; + + sigemptyset(&set); + if (sig) + sigaddset(&set, sig); + return set; +} + +/* Block the signals in the given signal * + * set. Return the old signal set. */ + +/**/ +sigset_t +signal_block(sigset_t set) +{ + sigset_t oset; + +#ifdef POSIX_SIGNALS + sigprocmask(SIG_BLOCK, &set, &oset); +#else +# ifdef BSD_SIGNALS + oset = sigblock(set); +# else +# ifdef SYSV_SIGNALS + int i; + + oset = blocked_set; + for (i = 1; i <= NSIG; ++i) { + if (sigismember(&set, i) && !sigismember(&blocked_set, i)) { + sigaddset(&blocked_set, i); + sighold(i); + } + } +# else /* NO_SIGNAL_BLOCKING */ +/* We will just ignore signals if the system doesn't have * + * the ability to block them. */ + int i; + + oset = blocked_set; + for (i = 1; i <= NSIG; ++i) { + if (sigismember(&set, i) && !sigismember(&blocked_set, i)) { + sigaddset(&blocked_set, i); + signal_ignore(i); + } + } +# endif /* SYSV_SIGNALS */ +# endif /* BSD_SIGNALS */ +#endif /* POSIX_SIGNALS */ + + return oset; +} + +/* Unblock the signals in the given signal * + * set. Return the old signal set. */ + +/**/ +sigset_t +signal_unblock(sigset_t set) +{ + sigset_t oset; + +#ifdef POSIX_SIGNALS + sigprocmask(SIG_UNBLOCK, &set, &oset); +#else +# ifdef BSD_SIGNALS + sigfillset(&oset); + oset = sigsetmask(oset); + sigsetmask(oset & ~set); +# else +# ifdef SYSV_SIGNALS + int i; + + oset = blocked_set; + for (i = 1; i <= NSIG; ++i) { + if (sigismember(&set, i) && sigismember(&blocked_set, i)) { + sigdelset(&blocked_set, i); + sigrelse(i); + } + } +# else /* NO_SIGNAL_BLOCKING */ +/* On systems that can't block signals, we are just ignoring them. So * + * to unblock signals, we just reenable the signal handler for them. */ + int i; + + oset = blocked_set; + for (i = 1; i <= NSIG; ++i) { + if (sigismember(&set, i) && sigismember(&blocked_set, i)) { + sigdelset(&blocked_set, i); + install_handler(i); + } + } +# endif /* SYSV_SIGNALS */ +# endif /* BSD_SIGNALS */ +#endif /* POSIX_SIGNALS */ + + return oset; +} + +/* set the process signal mask to * + * be the given signal mask */ + +/**/ +sigset_t +signal_setmask(sigset_t set) +{ + sigset_t oset; + +#ifdef POSIX_SIGNALS + sigprocmask(SIG_SETMASK, &set, &oset); +#else +# ifdef BSD_SIGNALS + oset = sigsetmask(set); +# else +# ifdef SYSV_SIGNALS + int i; + + oset = blocked_set; + for (i = 1; i <= NSIG; ++i) { + if (sigismember(&set, i) && !sigismember(&blocked_set, i)) { + sigaddset(&blocked_set, i); + sighold(i); + } else if (!sigismember(&set, i) && sigismember(&blocked_set, i)) { + sigdelset(&blocked_set, i); + sigrelse(i); + } + } +# else /* NO_SIGNAL_BLOCKING */ + int i; + + oset = blocked_set; + for (i = 1; i < NSIG; ++i) { + if (sigismember(&set, i) && !sigismember(&blocked_set, i)) { + sigaddset(&blocked_set, i); + signal_ignore(i); + } else if (!sigismember(&set, i) && sigismember(&blocked_set, i)) { + sigdelset(&blocked_set, i); + install_handler(i); + } + } +# endif /* SYSV_SIGNALS */ +# endif /* BSD_SIGNALS */ +#endif /* POSIX_SIGNALS */ + + return oset; +} + +#if defined(NO_SIGNAL_BLOCKING) +static int suspend_longjmp = 0; +static signal_jmp_buf suspend_jmp_buf; +#endif + +/**/ +int +signal_suspend(int sig, int sig2) +{ + int ret; + +#ifdef POSIX_SIGNALS + sigset_t set; + + sigfillset(&set); + sigdelset(&set, sig); + sigdelset(&set, SIGHUP); /* still don't know why we add this? */ + if (sig2) + sigdelset(&set, sig2); + ret = sigsuspend(&set); +#else +# ifdef BSD_SIGNALS + sigset_t set; + + sigfillset(&set); + sigdelset(&set, sig); + if (sig2) + sigdelset(&set, sig2); + ret = sigpause(set); +# else +# ifdef SYSV_SIGNALS + ret = sigpause(sig); + +# else /* NO_SIGNAL_BLOCKING */ + /* need to use signal_longjmp to make this race-free * + * between the child_unblock() and pause() */ + if (signal_setjmp(suspend_jmp_buf) == 0) { + suspend_longjmp = 1; /* we want to signal_longjmp after catching signal */ + child_unblock(); /* do we need to unblock sig2 as well? */ + ret = pause(); + } + suspend_longjmp = 0; /* turn off using signal_longjmp since we are past * + * the pause() function. */ +# endif /* SYSV_SIGNALS */ +# endif /* BSD_SIGNALS */ +#endif /* POSIX_SIGNALS */ + + return ret; +} + +/* What flavor of waitpid/wait3/wait shall we use? */ + +#ifdef HAVE_WAITPID +# define WAIT(pid, statusp, options) waitpid(pid, statusp, options) +#else +# ifdef HAVE_WAIT3 +# define WAIT(pid, statusp, options) wait3((void *) statusp, options, NULL) +# else +# define WAIT(pid, statusp, options) wait(statusp) +# endif +#endif + +/* the signal handler */ + +/**/ +RETSIGTYPE +handler(int sig) +{ + sigset_t newmask, oldmask; + +#if defined(NO_SIGNAL_BLOCKING) + int do_jump; + signal_jmp_buf jump_to; +#endif + + signal_process(sig); + + sigfillset(&newmask); + oldmask = signal_block(newmask); /* Block all signals temporarily */ + +#if defined(NO_SIGNAL_BLOCKING) + do_jump = suspend_longjmp; /* do we need to longjmp to signal_suspend */ + suspend_longjmp = 0; /* In case a SIGCHLD somehow arrives */ + + if (sig == SIGCHLD) { /* Traps can cause nested child_suspend() */ + if (do_jump) + jump_to = suspend_jmp_buf; /* Copy suspend_jmp_buf */ + } +#endif + + if (queueing_enabled) { /* Are we queueing signals now? */ + int temp_rear = ++queue_rear % MAX_QUEUE_SIZE; + + DPUTS(temp_rear == queue_front, "BUG: signal queue full"); + if (temp_rear != queue_front) { /* Make sure it's not full (extremely unlikely) */ + queue_rear = temp_rear; /* ok, not full, so add to queue */ + signal_queue[queue_rear] = sig; /* save signal caught */ + signal_mask_queue[queue_rear] = oldmask; /* save current signal mask */ + } + signal_reset(sig); + return; + } + + signal_setmask(oldmask); /* Reset signal mask, signal traps ok now */ + + switch (sig) { + case SIGCHLD: + + /* keep WAITING until no more child processes to reap */ + for (;;) + cont: { + int old_errno = errno; /* save the errno, since WAIT may change it */ + int status; + Job jn; + Process pn; + pid_t pid; + pid_t *procsubpid = &cmdoutpid; + int *procsubval = &cmdoutval; + struct execstack *es = exstack; + + pid = WAIT(-1, &status, WNOHANG|WUNTRACED); /* reap the child process */ + + if (!pid) /* no more children to reap */ + break; + + /* check if child returned was from process substitution */ + for (;;) { + if (pid == *procsubpid) { + *procsubpid = 0; + if (WIFSIGNALED(status)) + *procsubval = (0200 | WTERMSIG(status)); + else + *procsubval = WEXITSTATUS(status); + times(&shtms); + goto cont; + } + if (!es) + break; + procsubpid = &es->cmdoutpid; + procsubval = &es->cmdoutval; + es = es->next; + } + + /* check for WAIT error */ + if (pid == -1) { + if (errno != ECHILD) + zerr("wait failed: %e", NULL, errno); + errno = old_errno; /* WAIT changed errno, so restore the original */ + break; + } + + /* Find the process and job containing this pid and update it. */ + if (findproc(pid, &jn, &pn)) { + update_process(pn, status); + update_job(jn); + } else { + /* If not found, update the shell record of time spent by + * children in sub processes anyway: otherwise, this + * will get added on to the next found process that terminates. + */ + times(&shtms); + } + } + break; + + case SIGHUP: + if (sigtrapped[SIGHUP]) + dotrap(SIGHUP); + else { + stopmsg = 1; + zexit(SIGHUP, 1); + } + break; + + case SIGINT: + if (sigtrapped[SIGINT]) + dotrap(SIGINT); + else { + if ((isset(PRIVILEGED) || isset(RESTRICTED)) && + isset(INTERACTIVE) && noerrexit < 0) + zexit(SIGINT, 1); + if (list_pipe || chline || simple_pline) { + breaks = loops; + errflag = 1; + inerrflush(); + } + } + break; + +#ifdef SIGWINCH + case SIGWINCH: + adjustwinsize(); /* check window size and adjust */ + if (sigtrapped[SIGWINCH]) + dotrap(SIGWINCH); + break; +#endif + + case SIGALRM: + if (sigtrapped[SIGALRM]) { + int tmout; + dotrap(SIGALRM); + if ((tmout = getiparam("TMOUT"))) + alarm(tmout); /* reset the alarm */ + } else { + int idle = ttyidlegetfn(NULL); + int tmout = getiparam("TMOUT"); + if (idle >= 0 && idle < tmout) + alarm(tmout - idle); + else { + errflag = noerrs = 0; + zerr("timeout", NULL, 0); + errflag = 0; + stopmsg = 1; + zexit(SIGALRM, 1); + } + } + break; + + default: + dotrap(sig); + break; + } /* end of switch(sig) */ + + signal_reset(sig); + +/* This is used to make signal_suspend() race-free */ +#if defined(NO_SIGNAL_BLOCKING) + if (do_jump) + signal_longjmp(jump_to, 1); +#endif + +} /* handler */ + + +/* SIGHUP any jobs left running */ + +/**/ +void +killrunjobs(int from_signal) +{ + int i, killed = 0; + + if (unset(HUP)) + return; + for (i = 1; i < MAXJOB; i++) + if ((from_signal || i != thisjob) && (jobtab[i].stat & STAT_LOCKED) && + !(jobtab[i].stat & STAT_NOPRINT) && + !(jobtab[i].stat & STAT_STOPPED)) { + if (killpg(jobtab[i].gleader, SIGHUP) != -1) + killed++; + } + if (killed) + zerr("warning: %d jobs SIGHUPed", NULL, killed); +} + + +/* send a signal to a job (simply involves kill if monitoring is on) */ + +/**/ +int +killjb(Job jn, int sig) +{ + Process pn; + int err = 0; + + if (jobbing) { + if (jn->stat & STAT_SUPERJOB) { + if (sig == SIGCONT) { + for (pn = jobtab[jn->other].procs; pn; pn = pn->next) + kill(pn->pid, sig); + + for (pn = jn->procs; pn->next; pn = pn->next) + err = kill(pn->pid, sig); + + return err; + } + + killpg(jobtab[jn->other].gleader, sig); + return killpg(jn->gleader, sig); + } + else + return (killpg(jn->gleader, sig)); + } + for (pn = jn->procs; pn; pn = pn->next) + if ((err = kill(pn->pid, sig)) == -1 && errno != ESRCH) + return -1; + return err; +} + +/**/ +int +settrap(int sig, List l) +{ + if (sig == -1) + return 1; + if (jobbing && (sig == SIGTTOU || sig == SIGTSTP || sig == SIGTTIN)) { + zerr("can't trap SIG%s in interactive shells", sigs[sig], 0); + return 1; + } + if (sigfuncs[sig]) + unsettrap(sig); + sigfuncs[sig] = l; + if (!l) { + sigtrapped[sig] = ZSIG_IGNORED; + if (sig && sig <= SIGCOUNT && +#ifdef SIGWINCH + sig != SIGWINCH && +#endif + sig != SIGCHLD) + signal_ignore(sig); + } else { + sigtrapped[sig] = ZSIG_TRAPPED; + if (sig && sig <= SIGCOUNT && +#ifdef SIGWINCH + sig != SIGWINCH && +#endif + sig != SIGCHLD) + install_handler(sig); + } + return 0; +} + +/**/ +void +unsettrap(int sig) +{ + int trapped; + + if (sig == -1 || !(trapped = sigtrapped[sig]) || + (jobbing && (sig == SIGTTOU || sig == SIGTSTP || sig == SIGTTIN))) { + return; + } + sigtrapped[sig] = 0; + if (sig == SIGINT && interact) { + /* PWS 1995/05/16: added test for interactive, also noholdintr() * + * as subshells ignoring SIGINT have it blocked from delivery */ + intr(); + noholdintr(); + } else if (sig == SIGHUP) + install_handler(sig); + else if (sig && sig <= SIGCOUNT && +#ifdef SIGWINCH + sig != SIGWINCH && +#endif + sig != SIGCHLD) + signal_default(sig); + if (trapped & ZSIG_FUNC) { + char func[20]; + HashNode hn; + + sprintf(func, "TRAP%s", sigs[sig]); + if ((hn = shfunctab->removenode(shfunctab, func))) + shfunctab->freenode(hn); + } else if (sigfuncs[sig]) { + freestruct(sigfuncs[sig]); + sigfuncs[sig] = NULL; + } +} + +/* Execute a trap function for a given signal, possibly + * with non-standard sigtrapped & sigfuncs values + */ + +/**/ +void +dotrapargs(int sig, int *sigtr, void *sigfn) +{ + LinkList args; + char *name, num[4]; + int trapret = 0; + int obreaks = breaks; + + /* if signal is being ignored or the trap function * + * is NULL, then return * + * * + * Also return if errflag is set. In fact, the code in the * + * function will test for this, but this way we keep status flags * + * intact without working too hard. Special cases (e.g. calling * + * a trap for SIGINT after the error flag was set) are handled * + * by the calling code. (PWS 1995/06/08). */ + if ((*sigtr & ZSIG_IGNORED) || !sigfn || errflag) + return; + + *sigtr |= ZSIG_IGNORED; + + lexsave(); + execsave(); + breaks = 0; + if (*sigtr & ZSIG_FUNC) { + PERMALLOC { + args = newlinklist(); + name = (char *) zalloc(5 + strlen(sigs[sig])); + sprintf(name, "TRAP%s", sigs[sig]); + addlinknode(args, name); + sprintf(num, "%d", sig); + addlinknode(args, num); + } LASTALLOC; + trapreturn = -1; + doshfunc(sigfn, args, 0, 1); + freelinklist(args, (FreeFunc) NULL); + zsfree(name); + } else HEAPALLOC { + execlist(dupstruct(sigfn), 1, 0); + } LASTALLOC; + if (trapreturn > 0) + trapret = trapreturn; + else if (errflag) + trapret = 1; + execrestore(); + lexrestore(); + + if (trapret > 0) { + breaks = loops; + errflag = 1; + } else { + breaks += obreaks; + if (breaks > loops) + breaks = loops; + } + + if (*sigtr != ZSIG_IGNORED) + *sigtr &= ~ZSIG_IGNORED; +} + +/* Standard call to execute a trap for a given signal */ + +/**/ +void +dotrap(int sig) +{ + dotrapargs(sig, sigtrapped+sig, sigfuncs[sig]); +} diff --git a/Src/signals.h b/Src/signals.h new file mode 100644 index 000000000..b6485e6b3 --- /dev/null +++ b/Src/signals.h @@ -0,0 +1,94 @@ +/* + * signals.h - header file for signals handling code + * + * 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. + * + */ + +#define SIGNAL_HANDTYPE RETSIGTYPE (*)_((int)) + +#ifndef HAVE_KILLPG +# define killpg(pgrp,sig) kill(-(pgrp),sig) +#endif + +#define SIGZERR (SIGCOUNT+1) +#define SIGDEBUG (SIGCOUNT+2) +#define VSIGCOUNT (SIGCOUNT+3) +#define SIGEXIT 0 + +#ifdef SV_BSDSIG +# define SV_INTERRUPT SV_BSDSIG +#endif + +/* If not a POSIX machine, then we create our * + * own POSIX style signal sets functions. */ +#ifndef POSIX_SIGNALS +# define sigemptyset(s) (*(s) = 0) +# if NSIG == 32 +# define sigfillset(s) (*(s) = ~(sigset_t)0, 0) +# else +# define sigfillset(s) (*(s) = (1 << NSIG) - 1, 0) +# endif +# define sigaddset(s,n) (*(s) |= (1 << ((n) - 1)), 0) +# define sigdelset(s,n) (*(s) &= ~(1 << ((n) - 1)), 0) +# define sigismember(s,n) ((*(s) & (1 << ((n) - 1))) != 0) +#endif /* ifndef POSIX_SIGNALS */ + +#define child_block() signal_block(signal_mask(SIGCHLD)) +#define child_unblock() signal_unblock(signal_mask(SIGCHLD)) +#define child_suspend(S) signal_suspend(SIGCHLD, S) + +/* ignore a signal */ +#define signal_ignore(S) signal(S, SIG_IGN) + +/* return a signal to it default action */ +#define signal_default(S) signal(S, SIG_DFL) + +/* Use a circular queue to save signals caught during * + * critical sections of code. You call queue_signals to * + * start queueing, and unqueue_signals to process the * + * queue and stop queueing. Since the kernel doesn't * + * queue signals, it is probably overkill for zsh to do * + * this, but it shouldn't hurt anything to do it anyway. */ + +/* Right now I'm queueing all signals, but maybe we only * + * need to queue SIGCHLD. Anybody know? */ + +#define MAX_QUEUE_SIZE 16 + +#define queue_signals() (queueing_enabled++) + +#define unqueue_signals() do { \ + DPUTS(!queueing_enabled, "BUG: unqueue_signals called but not queueing"); \ + if (!--queueing_enabled) { \ + while (queue_front != queue_rear) { /* while signals in queue */ \ + sigset_t oset; \ + queue_front = (queue_front + 1) % MAX_QUEUE_SIZE; \ + oset = signal_setmask(signal_mask_queue[queue_front]); \ + handler(signal_queue[queue_front]); /* handle queued signal */ \ + signal_setmask(oset); \ + } \ + } \ +} while (0) diff --git a/Src/signames.awk b/Src/signames.awk new file mode 100755 index 000000000..5d2eeb61e --- /dev/null +++ b/Src/signames.awk @@ -0,0 +1,98 @@ +# +# {g,n}awk script to generate signames.c +# +# NB: On SunOS 4.1.3 - user-functions don't work properly, also \" problems +# Without 0 + hacks some nawks compare numbers as strings +# +/^[\t ]*#[\t ]*define[\t _]*SIG[A-Z][A-Z0-9]*[\t ]*[1-9][0-9]*/ { + sigindex = index($0, "SIG") + sigtail = substr($0, sigindex, 80) + split(sigtail, tmp) + signam = substr(tmp[1], 4, 20) + signum = tmp[2] + if (sig[signum] == "") { + sig[signum] = signam + if (0 + max < 0 + signum && signum < 60) + max = signum + if (signam == "ABRT") { msg[signum] = "abort" } + if (signam == "ALRM") { msg[signum] = "alarm" } + if (signam == "BUS") { msg[signum] = "bus error" } + if (signam == "CHLD") { msg[signum] = "death of child" } + if (signam == "CLD") { msg[signum] = "death of child" } + if (signam == "CONT") { msg[signum] = "continued" } + if (signam == "EMT") { msg[signum] = "EMT instruction" } + if (signam == "FPE") { msg[signum] = "floating point exception" } + if (signam == "HUP") { msg[signum] = "hangup" } + if (signam == "ILL") { msg[signum] = "illegal hardware instruction" } + if (signam == "INFO") { msg[signum] = "status request from keyboard" } + if (signam == "INT") { msg[signum] = "interrupt" } + if (signam == "IO") { msg[signum] = "i/o ready" } + if (signam == "IOT") { msg[signum] = "IOT instruction" } + if (signam == "KILL") { msg[signum] = "killed" } + if (signam == "LOST") { msg[signum] = "resource lost" } + if (signam == "PIPE") { msg[signum] = "broken pipe" } + if (signam == "POLL") { msg[signum] = "pollable event occurred" } + if (signam == "PROF") { msg[signum] = "profile signal" } + if (signam == "PWR") { msg[signum] = "power fail" } + if (signam == "QUIT") { msg[signum] = "quit" } + if (signam == "SEGV") { msg[signum] = "segmentation fault" } + if (signam == "SYS") { msg[signum] = "invalid system call" } + if (signam == "TERM") { msg[signum] = "terminated" } + if (signam == "TRAP") { msg[signum] = "trace trap" } + if (signam == "URG") { msg[signum] = "urgent condition" } + if (signam == "USR1") { msg[signum] = "user-defined signal 1" } + if (signam == "USR2") { msg[signum] = "user-defined signal 2" } + if (signam == "VTALRM") { msg[signum] = "virtual time alarm" } + if (signam == "WINCH") { msg[signum] = "window size changed" } + if (signam == "XCPU") { msg[signum] = "cpu limit exceeded" } + if (signam == "XFSZ") { msg[signum] = "file size limit exceeded" } + } +} + +END { + ps = "%s" + ifdstr = sprintf("# ifdef USE_SUSPENDED\n\t%csuspended%s%c,\n%s else\n\t%cstopped%s%c,\n# endif\n", 34, ps, 34, "#", 34, ps, 34) + + printf "/** signames.c **/\n" + printf "/** architecture-customized signames.c for zsh **/\n" + printf "\n" + printf "#define SIGCOUNT\t%d\n", max + printf "\n" + printf "#include %czsh.mdh%c\n", 34, 34 + printf "\n" + printf "/**/\n" + printf "char *sigmsg[SIGCOUNT+2] = {\n" + printf "\t%c%s%c,\n", 34, "done", 34 + + for (i = 1; i <= 0 + max; i++) + if (msg[i] == "") { + if (sig[i] == "") + printf("\t%c%c,\n", 34, 34) + else if (sig[i] == "STOP") + printf ifdstr, " (signal)", " (signal)" + else if (sig[i] == "TSTP") + printf ifdstr, "", "" + else if (sig[i] == "TTIN") + printf ifdstr, " (tty input)", " (tty input)" + else if (sig[i] == "TTOU") + printf ifdstr, " (tty output)", " (tty output)" + else + printf("\t%cSIG%s%c,\n", 34, sig[i], 34) + } else + printf("\t%c%s%c,\n", 34, msg[i], 34) + print "\tNULL" + print "};" + print "" + print "/**/" + printf "char *sigs[SIGCOUNT+4] = {\n" + printf("\t%cEXIT%c,\n", 34, 34) + for (i = 1; i <= 0 + max; i++) + if (sig[i] == "") + printf("\t%c%d%c,\n", 34, i, 34) + else + printf("\t%c%s%c,\n", 34, sig[i], 34) + printf("\t%cZERR%c,\n", 34, 34) + printf("\t%cDEBUG%c,\n", 34, 34) + print "\tNULL" + print "};" +} diff --git a/Src/signames1.awk b/Src/signames1.awk new file mode 100644 index 000000000..27d21ac7b --- /dev/null +++ b/Src/signames1.awk @@ -0,0 +1,19 @@ +# This is an awk script which finds out what the possibilities for +# the signal names are, and dumps them out so that cpp can turn them +# into numbers. Since we don't need to decide here what the +# real signals are, we can afford to be generous about definitions, +# in case the definitions are in terms of other definitions. +# However, we need to avoid definitions with parentheses, which will +# mess up the syntax. +BEGIN { printf "#include <signal.h>\n\n" } + +/^[\t ]*#[\t ]*define[\t _]*SIG[A-Z][A-Z0-9]*[\t ][\t ]*[^(\t ]/ { + sigindex = index($0, "SIG") + sigtail = substr($0, sigindex, 80) + split(sigtail, tmp) + signam = substr(tmp[1], 4, 20) + if (substr($0, sigindex-1, 1) == "_") + printf("XXNAMES XXSIG%s _SIG%s\n", signam, signam) + else + printf("XXNAMES XXSIG%s SIG%s\n", signam, signam) +} diff --git a/Src/signames2.awk b/Src/signames2.awk new file mode 100644 index 000000000..3aea76ee3 --- /dev/null +++ b/Src/signames2.awk @@ -0,0 +1,100 @@ +# +# {g,n}awk script to generate signames.c +# This version relies on the previous output of the preprocessor +# on sigtmp.c, sigtmp.out, which is in turn generated by signames1.awk. +# +# NB: On SunOS 4.1.3 - user-functions don't work properly, also \" problems +# Without 0 + hacks some nawks compare numbers as strings +# +/^XXNAMES XXSIG[A-Z][A-Z0-9]* [1-9][0-9]*/ { + sigindex = index($0, "SIG") + sigtail = substr($0, sigindex, 80) + split(sigtail, tmp) + signam = substr(tmp[1], 4, 20) + signum = tmp[2] + if (sig[signum] == "") { + sig[signum] = signam + if (0 + max < 0 + signum && signum < 60) + max = signum + if (signam == "ABRT") { msg[signum] = "abort" } + if (signam == "ALRM") { msg[signum] = "alarm" } + if (signam == "BUS") { msg[signum] = "bus error" } + if (signam == "CHLD") { msg[signum] = "death of child" } + if (signam == "CLD") { msg[signum] = "death of child" } + if (signam == "CONT") { msg[signum] = "continued" } + if (signam == "EMT") { msg[signum] = "EMT instruction" } + if (signam == "FPE") { msg[signum] = "floating point exception" } + if (signam == "HUP") { msg[signum] = "hangup" } + if (signam == "ILL") { msg[signum] = "illegal hardware instruction" } + if (signam == "INFO") { msg[signum] = "status request from keyboard" } + if (signam == "INT") { msg[signum] = "interrupt" } + if (signam == "IO") { msg[signum] = "i/o ready" } + if (signam == "IOT") { msg[signum] = "IOT instruction" } + if (signam == "KILL") { msg[signum] = "killed" } + if (signam == "LOST") { msg[signum] = "resource lost" } + if (signam == "PIPE") { msg[signum] = "broken pipe" } + if (signam == "POLL") { msg[signum] = "pollable event occurred" } + if (signam == "PROF") { msg[signum] = "profile signal" } + if (signam == "PWR") { msg[signum] = "power fail" } + if (signam == "QUIT") { msg[signum] = "quit" } + if (signam == "SEGV") { msg[signum] = "segmentation fault" } + if (signam == "SYS") { msg[signum] = "invalid system call" } + if (signam == "TERM") { msg[signum] = "terminated" } + if (signam == "TRAP") { msg[signum] = "trace trap" } + if (signam == "URG") { msg[signum] = "urgent condition" } + if (signam == "USR1") { msg[signum] = "user-defined signal 1" } + if (signam == "USR2") { msg[signum] = "user-defined signal 2" } + if (signam == "VTALRM") { msg[signum] = "virtual time alarm" } + if (signam == "WINCH") { msg[signum] = "window size changed" } + if (signam == "XCPU") { msg[signum] = "cpu limit exceeded" } + if (signam == "XFSZ") { msg[signum] = "file size limit exceeded" } + } +} + +END { + ps = "%s" + ifdstr = sprintf("# ifdef USE_SUSPENDED\n\t%csuspended%s%c,\n%s else\n\t%cstopped%s%c,\n# endif\n", 34, ps, 34, "#", 34, ps, 34) + + printf "/** signames.c **/\n" + printf "/** architecture-customized signames.c for zsh **/\n" + printf "\n" + printf "#define SIGCOUNT\t%d\n", max + printf "\n" + printf "#include %czsh.mdh%c\n", 34, 34 + printf "\n" + printf "/**/\n" + printf "char *sigmsg[SIGCOUNT+2] = {\n" + printf "\t%c%s%c,\n", 34, "done", 34 + + for (i = 1; i <= 0 + max; i++) + if (msg[i] == "") { + if (sig[i] == "") + printf("\t%c%c,\n", 34, 34) + else if (sig[i] == "STOP") + printf ifdstr, " (signal)", " (signal)" + else if (sig[i] == "TSTP") + printf ifdstr, "", "" + else if (sig[i] == "TTIN") + printf ifdstr, " (tty input)", " (tty input)" + else if (sig[i] == "TTOU") + printf ifdstr, " (tty output)", " (tty output)" + else + printf("\t%cSIG%s%c,\n", 34, sig[i], 34) + } else + printf("\t%c%s%c,\n", 34, msg[i], 34) + print "\tNULL" + print "};" + print "" + print "/**/" + printf "char *sigs[SIGCOUNT+4] = {\n" + printf("\t%cEXIT%c,\n", 34, 34) + for (i = 1; i <= 0 + max; i++) + if (sig[i] == "") + printf("\t%c%d%c,\n", 34, i, 34) + else + printf("\t%c%s%c,\n", 34, sig[i], 34) + printf("\t%cZERR%c,\n", 34, 34) + printf("\t%cDEBUG%c,\n", 34, 34) + print "\tNULL" + print "};" +} diff --git a/Src/subst.c b/Src/subst.c new file mode 100644 index 000000000..8f840d266 --- /dev/null +++ b/Src/subst.c @@ -0,0 +1,1773 @@ +/* + * subst.c - various substitutions + * + * 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 "subst.pro" + +/**/ +char nulstring[] = {Nularg, '\0'}; + +/* Do substitutions before fork. These are: + * - Process substitution: <(...), >(...), =(...) + * - Parameter substitution + * - Command substitution + * Followed by + * - Quote removal + * - Brace expansion + * - Tilde and equals substitution + * + * Bits 0 and 1 of flags are used in filesub. + * bit 0 is set when we are doing MAGIC_EQUALSUBST or normal + * assignment but not a typeset. + * bit 1 is set on a real assignment (both typeset and normal). + * bit 2 is a flag to paramsubst (single word sub) + */ + +/**/ +void +prefork(LinkList list, int flags) +{ + LinkNode node; + + MUSTUSEHEAP("prefork"); + for (node = firstnode(list); node; incnode(node)) { + char *str, *str3; + + str = str3 = (char *)getdata(node); + if ((*str == Inang || *str == Outang || *str == Equals) && + str[1] == Inpar) { + if (*str == Inang || *str == Outang) + setdata(node, (void *) getproc(str)); /* <(...) or >(...) */ + else + setdata(node, (void *) getoutputfile(str)); /* =(...) */ + if (!getdata(node)) + return; + } else { + if (isset(SHFILEEXPANSION)) + filesub((char **)getaddrdata(node), flags & 3); + if (!(node = stringsubst(list, node, flags & 4))) + return; + } + } + for (node = firstnode(list); node; incnode(node)) { + if (*(char *)getdata(node)) { + remnulargs(getdata(node)); + if (unset(IGNOREBRACES) && !(flags & 4)) + while (hasbraces(getdata(node))) + xpandbraces(list, &node); + if (unset(SHFILEEXPANSION)) + filesub((char **)getaddrdata(node), flags & 3); + } else if (!(flags & 4)) + uremnode(list, node); + if (errflag) + return; + } +} + +/**/ +static LinkNode +stringsubst(LinkList list, LinkNode node, int ssub) +{ + int qt; + char *str3 = (char *)getdata(node); + char *str = str3; + + while (!errflag && *str) { + if ((qt = *str == Qstring) || *str == String) + if (str[1] == Inpar) { + str++; + goto comsub; + } else if (str[1] == Inbrack) { + /* $[...] */ + char *str2 = str; + str2++; + if (skipparens(Inbrack, Outbrack, &str2)) { + zerr("closing bracket missing", NULL, 0); + return NULL; + } + str2[-1] = *str = '\0'; + str = arithsubst(str + 2, &str3, str2); + setdata(node, (void *) str3); + continue; + } else if (str[1] == Snull) { + str = getkeystring(str, NULL, 4, NULL); + continue; + } else { + node = paramsubst(list, node, &str, qt, ssub); + if (errflag || !node) + return NULL; + str3 = (char *)getdata(node); + continue; + } + else if ((qt = *str == Qtick) || *str == Tick) + comsub: { + LinkList pl; + char *s, *str2 = str; + char endchar; + int l1, l2; + + if (*str == Inpar) { + endchar = Outpar; + str[-1] = '\0'; + if (skipparens(Inpar, Outpar, &str)) + DPUTS(1, "BUG: parse error in command substitution"); + str--; + } else { + endchar = *str; + *str = '\0'; + + while (*++str != endchar) + DPUTS(!*str, "BUG: parse error in command substitution"); + } + *str++ = '\0'; + if (endchar == Outpar && str2[1] == '(' && str[-2] == ')') { + /* Math substitution of the form $((...)) */ + str = arithsubst(str2 + 1, &str3, str); + setdata(node, (void *) str3); + continue; + } + + /* It is a command substitution, which will be parsed again * + * by the lexer, so we untokenize it first, but we cannot use * + * untokenize() since in the case of `...` some Bnulls should * + * be left unchanged. Note that the lexer doesn't tokenize * + * the body of a command substitution so if there are some * + * tokens here they are from a ${(e)~...} substitution. */ + for (str = str2; *++str; ) + if (itok(*str) && *str != Nularg && + !(endchar != Outpar && *str == Bnull && + (str[1] == '$' || str[1] == '\\' || str[1] == '`' || + (qt && str[1] == '"')))) + *str = ztokens[*str - Pound]; + str++; + if (!(pl = getoutput(str2 + 1, qt || ssub))) { + zerr("parse error in command substitution", NULL, 0); + return NULL; + } + if (endchar == Outpar) + str2--; + if (!(s = (char *) ugetnode(pl))) { + str = strcpy(str2, str); + continue; + } + if (!qt && ssub && isset(GLOBSUBST)) + tokenize(s); + l1 = str2 - str3; + l2 = strlen(s); + if (nonempty(pl)) { + LinkNode n = lastnode(pl); + str2 = (char *) ncalloc(l1 + l2 + 1); + strcpy(str2, str3); + strcpy(str2 + l1, s); + setdata(node, str2); + insertlinklist(pl, node, list); + s = (char *) getdata(node = n); + l1 = 0; + l2 = strlen(s); + } + str2 = (char *) ncalloc(l1 + l2 + strlen(str) + 1); + if (l1) + strcpy(str2, str3); + strcpy(str2 + l1, s); + str = strcpy(str2 + l1 + l2, str); + str3 = str2; + setdata(node, str3); + continue; + } + str++; + } + return errflag ? NULL : node; +} + +/**/ +void +globlist(LinkList list) +{ + LinkNode node, next; + + badcshglob = 0; + for (node = firstnode(list); !errflag && node; node = next) { + next = nextnode(node); + glob(list, node); + } + if (badcshglob == 1) + zerr("no match", NULL, 0); +} + +/* perform substitution on a single word */ + +/**/ +void +singsub(char **s) +{ + LinkList foo; + + foo = newlinklist(); + addlinknode(foo, *s); + prefork(foo, 4); + if (errflag) + return; + *s = (char *) ugetnode(foo); + DPUTS(nonempty(foo), "BUG: singsub() produced more than one word!"); +} + +/* Perform substitution on a single word. Unlike with singsub, the * + * result can have more than one words. A single word result is sroted * + * in *s and *isarr is set to zero; otherwise *isarr is set to 1 and * + * the result is stored in *a. If `a' is zero a multiple word result is * + * joined using sep or the IFS parameter if sep is zero and the result * + * is returned in *s. The return value is true iff the expansion * + * resulted in an empty list */ + +/**/ +static int +multsub(char **s, char ***a, int *isarr, char *sep) +{ + LinkList foo; + int l; + char **r, **p; + + foo = newlinklist(); + addlinknode(foo, *s); + prefork(foo, 0); + if (errflag) { + if (isarr) + *isarr = 0; + return 0; + } + if ((l = countlinknodes(foo)) > 1) { + p = r = ncalloc((l + 1) * sizeof(char*)); + while (nonempty(foo)) + *p++ = (char *)ugetnode(foo); + *p = NULL; + if (a) { + *a = r; + *isarr = 1; + return 0; + } + *s = sepjoin(r, NULL); + return 0; + } + if (l) + *s = (char *) ugetnode(foo); + else + *s = dupstring(""); + if (isarr) + *isarr = 0; + return !l; +} + +/* ~, = subs: assign = 2 => typeset; assign = 1 => something that looks + like an assignment but may not be; assign = 3 => normal assignment */ + +/**/ +void +filesub(char **namptr, int assign) +{ + char *sub = NULL, *str, *ptr; + int len; + + filesubstr(namptr, assign); + + if (!assign) + return; + + if (assign < 3) + if ((*namptr)[1] && (sub = strchr(*namptr + 1, Equals))) { + if (assign == 1) + for (ptr = *namptr; ptr != sub; ptr++) + if (!iident(*ptr) && !INULL(*ptr)) + return; + str = sub + 1; + if ((sub[1] == Tilde || sub[1] == Equals) && filesubstr(&str, assign)) { + sub[1] = '\0'; + *namptr = dyncat(*namptr, str); + } + } else + return; + + ptr = *namptr; + while ((sub = strchr(ptr, ':'))) { + str = sub + 1; + len = sub - *namptr; + if ((sub[1] == Tilde || sub[1] == Equals) && filesubstr(&str, assign)) { + sub[1] = '\0'; + *namptr = dyncat(*namptr, str); + } + ptr = *namptr + len + 1; + } +} + +/**/ +int +filesubstr(char **namptr, int assign) +{ +#define isend(c) ( !(c) || (c)=='/' || (c)==Inpar || (assign && (c)==':') ) +#define isend2(c) ( !(c) || (c)==Inpar || (assign && (c)==':') ) + char *str = *namptr; + + if (*str == Tilde && str[1] != '=' && str[1] != Equals) { + char *ptr; + int val; + + val = zstrtol(str + 1, &ptr, 10); + if (isend(str[1])) { /* ~ */ + *namptr = dyncat(home, str + 1); + return 1; + } else if (str[1] == '+' && isend(str[2])) { /* ~+ */ + *namptr = dyncat(pwd, str + 2); + return 1; + } else if (str[1] == '-' && isend(str[2])) { /* ~- */ + char *tmp; + *namptr = dyncat((tmp = oldpwd) ? tmp : pwd, str + 2); + return 1; + } else if (!inblank(str[1]) && isend(*ptr) && + (!idigit(str[1]) || (ptr - str < 4))) { + char *ds; + + if (val < 0) + val = -val; + ds = dstackent(str[1], val); + if (!ds) + return 0; + *namptr = dyncat(ds, ptr); + return 1; + } else if (iuser(str[1])) { /* ~foo */ + char *ptr, *hom, save; + + for (ptr = ++str; *ptr && iuser(*ptr); ptr++); + save = *ptr; + if (!isend(save)) + return 0; + *ptr = 0; + if (!(hom = getnameddir(str))) { + if (isset(NOMATCH)) + zerr("no such user or named directory: %s", str, 0); + *ptr = save; + return 0; + } + *ptr = save; + *namptr = dyncat(hom, ptr); + return 1; + } + } else if (*str == Equals && isset(EQUALS) && str[1]) { /* =foo */ + char sav, *pp, *cnam; + + for (pp = str + 1; !isend2(*pp); pp++); + sav = *pp; + *pp = 0; + if (!(cnam = findcmd(str + 1))) { + Alias a = (Alias) aliastab->getnode(aliastab, str + 1); + + if (a) + cnam = ztrdup(a->text); + else { + if (isset(NOMATCH)) + zerr("%s not found", str + 1, 0); + return 0; + } + } + *namptr = dupstring(cnam); + zsfree(cnam); + if (sav) { + *pp = sav; + *namptr = dyncat(*namptr, pp); + } + return 1; + } + return 0; +#undef isend +#undef isend2 +} + +/**/ +static char * +strcatsub(char **d, char *pb, char *pe, char *src, int l, char *s, int glbsub) +{ + int pl = pe - pb; + char *dest = ncalloc(pl + l + (s ? strlen(s) : 0) + 1); + + *d = dest; + strncpy(dest, pb, pl); + dest += pl; + strcpy(dest, src); + if (glbsub) + tokenize(dest); + dest += l; + if (s) + strcpy(dest, s); + return dest; +} + +typedef int (*CompareFn) _((const void *, const void *)); + +/**/ +int +strpcmp(const void *a, const void *b) +{ +#ifdef HAVE_STRCOLL + return strcoll(*(char **)a, *(char **)b); +#else + return strcmp(*(char **)a, *(char **)b); +#endif +} + +/**/ +int +invstrpcmp(const void *a, const void *b) +{ +#ifdef HAVE_STRCOLL + return -strcoll(*(char **)a, *(char **)b); +#else + return -strcmp(*(char **)a, *(char **)b); +#endif +} + +/**/ +int +cstrpcmp(const void *a, const void *b) +{ +#ifdef HAVE_STRCOLL + VARARR(char, c, strlen(*(char **) a) + 1); + VARARR(char, d, strlen(*(char **) b) + 1); + char *s, *t; + int cmp; + + for (s = *(char **) a, t = c; (*t++ = tulower(*s++));); + for (s = *(char **) b, t = d; (*t++ = tulower(*s++));); + + cmp = strcoll(c, d); + + return cmp; +#else + char *c = *(char **)a, *d = *(char **)b; + + for (; *c && tulower(*c) == tulower(*d); c++, d++); + + return (int)STOUC(tulower(*c)) - (int)STOUC(tulower(*d)); +#endif +} + +/**/ +int +invcstrpcmp(const void *a, const void *b) +{ +#ifdef HAVE_STRCOLL + VARARR(char, c, strlen(*(char **) a) + 1); + VARARR(char, d, strlen(*(char **) b) + 1); + char *s, *t; + int cmp; + + for (s = *(char **) a, t = c; (*t++ = tulower(*s++));); + for (s = *(char **) b, t = d; (*t++ = tulower(*s++));); + + cmp = strcoll(c, d); + + return -cmp; +#else + char *c = *(char **)a, *d = *(char **)b; + + for (; *c && tulower(*c) == tulower(*d); c++, d++); + + return (int)STOUC(tulower(*d)) - (int)STOUC(tulower(*c)); +#endif +} + +/**/ +static char * +dopadding(char *str, int prenum, int postnum, char *preone, char *postone, char *premul, char *postmul) +{ + char def[3], *ret, *t, *r; + int ls, ls2, lpreone, lpostone, lpremul, lpostmul, lr, f, m, c, cc; + + def[0] = *ifs ? *ifs : ' '; + def[1] = *ifs == Meta ? ifs[1] ^ 32 : '\0'; + def[2] = '\0'; + if (preone && !*preone) + preone = def; + if (postone && !*postone) + postone = def; + if (!premul || !*premul) + premul = def; + if (!postmul || !*postmul) + postmul = def; + + ls = strlen(str); + lpreone = preone ? strlen(preone) : 0; + lpostone = postone ? strlen(postone) : 0; + lpremul = strlen(premul); + lpostmul = strlen(postmul); + + lr = prenum + postnum; + + if (lr == ls) + return str; + + r = ret = (char *)halloc(lr + 1); + + if (prenum) { + if (postnum) { + ls2 = ls / 2; + + f = prenum - ls2; + if (f <= 0) + for (str -= f, c = prenum; c--; *r++ = *str++); + else { + if (f <= lpreone) + for (c = f, t = preone + lpreone - f; c--; *r++ = *t++); + else { + f -= lpreone; + if ((m = f % lpremul)) + for (c = m, t = premul + lpremul - m; c--; *r++ = *t++); + for (cc = f / lpremul; cc--;) + for (c = lpremul, t = premul; c--; *r++ = *t++); + for (c = lpreone; c--; *r++ = *preone++); + } + for (c = ls2; c--; *r++ = *str++); + } + ls2 = ls - ls2; + f = postnum - ls2; + if (f <= 0) + for (c = postnum; c--; *r++ = *str++); + else { + for (c = ls2; c--; *r++ = *str++); + if (f <= lpostone) + for (c = f; c--; *r++ = *postone++); + else { + f -= lpostone; + for (c = lpostone; c--; *r++ = *postone++); + for (cc = f / lpostmul; cc--;) + for (c = lpostmul, t = postmul; c--; *r++ = *t++); + if ((m = f % lpostmul)) + for (; m--; *r++ = *postmul++); + } + } + } else { + f = prenum - ls; + if (f <= 0) + for (c = prenum, str -= f; c--; *r++ = *str++); + else { + if (f <= lpreone) + for (c = f, t = preone + lpreone - f; c--; *r++ = *t++); + else { + f -= lpreone; + if ((m = f % lpremul)) + for (c = m, t = premul + lpremul - m; c--; *r++ = *t++); + for (cc = f / lpremul; cc--;) + for (c = lpremul, t = premul; c--; *r++ = *t++); + for (c = lpreone; c--; *r++ = *preone++); + } + for (c = ls; c--; *r++ = *str++); + } + } + } else if (postnum) { + f = postnum - ls; + if (f <= 0) + for (c = postnum; c--; *r++ = *str++); + else { + for (c = ls; c--; *r++ = *str++); + if (f <= lpostone) + for (c = f; c--; *r++ = *postone++); + else { + f -= lpostone; + for (c = lpostone; c--; *r++ = *postone++); + for (cc = f / lpostmul; cc--;) + for (c = lpostmul, t = postmul; c--; *r++ = *t++); + if ((m = f % lpostmul)) + for (; m--; *r++ = *postmul++); + } + } + } + *r = '\0'; + + return ret; +} + +/**/ +char * +get_strarg(char *s) +{ + char t = *s++; + + if (!t) + return s - 1; + + switch (t) { + case '(': + t = ')'; + break; + case '[': + t = ']'; + break; + case '{': + t = '}'; + break; + case '<': + t = '>'; + break; + case Inpar: + t = Outpar; + break; + case Inang: + t = Outang; + break; + case Inbrace: + t = Outbrace; + break; + case Inbrack: + t = Outbrack; + break; + } + + while (*s && *s != t) + s++; + + return s; +} + +/**/ +static int +get_intarg(char **s) +{ + char *t = get_strarg(*s + 1); + char *p, sav; + long ret; + + if (!*t) + return -1; + sav = *t; + *t = '\0'; + p = dupstring(*s + 2); + *s = t; + *t = sav; + if (parsestr(p)) + return -1; + singsub(&p); + if (errflag) + return -1; + ret = matheval(p); + if (errflag) + return -1; + if (ret < 0) + ret = -ret; + return ret < 0 ? -ret : ret; +} + +/* parameter substitution */ + +#define isstring(c) ((c) == '$' || (char)(c) == String || (char)(c) == Qstring) +#define isbrack(c) ((c) == '[' || (char)(c) == Inbrack) + +/**/ +LinkNode +paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub) +{ + char *aptr = *str; + char *s = aptr, *fstr, *idbeg, *idend, *ostr = (char *) getdata(n); + int colf; /* != 0 means we found a colon after the name */ + int doub = 0; /* != 0 means we have %%, not %, or ##, not # */ + int isarr = 0; + int plan9 = isset(RCEXPANDPARAM); + int globsubst = isset(GLOBSUBST); + int getlen = 0; + int whichlen = 0; + int chkset = 0; + int vunset = 0; + int spbreak = isset(SHWORDSPLIT) && !ssub && !qt; + char *val = NULL, **aval = NULL; + unsigned int fwidth = 0; + Value v; + int flags = 0; + int flnum = 0; + int substr = 0; + int sortit = 0, casind = 0; + int casmod = 0; + char *sep = NULL, *spsep = NULL; + char *premul = NULL, *postmul = NULL, *preone = NULL, *postone = NULL; + long prenum = 0, postnum = 0; + int copied = 0; + int arrasg = 0; + int eval = 0; + int nojoin = 0; + char inbrace = 0; /* != 0 means ${...}, otherwise $... */ + + *s++ = '\0'; + if (!ialnum(*s) && *s != '#' && *s != Pound && *s != '-' && + *s != '!' && *s != '$' && *s != String && *s != Qstring && + *s != '?' && *s != Quest && *s != '_' && + *s != '*' && *s != Star && *s != '@' && *s != '{' && + *s != Inbrace && *s != '=' && *s != Equals && *s != Hat && + *s != '^' && *s != '~' && *s != Tilde && *s != '+') { + s[-1] = '$'; + *str = s; + return n; + } + DPUTS(*s == '{', "BUG: inbrace == '{' in paramsubst()"); + if (*s == Inbrace) { + inbrace = 1; + s++; + if (*s == '(' || *s == Inpar) { + char *t, sav; + int tt = 0; + long num; + int escapes = 0; + int klen; +#define UNTOK_AND_ESCAPE(X) {\ + untokenize(X = dupstring(s + 1));\ + if (escapes) {\ + X = getkeystring(X, &klen, 3, NULL);\ + X = metafy(X, klen, META_HREALLOC);\ + }\ + } + + for (s++; *s != ')' && *s != Outpar; s++, tt = 0) { + switch (*s) { + case ')': + case Outpar: + break; + case 'A': + arrasg = 1; + break; + case '@': + nojoin = 1; + break; + case 'M': + flags |= 8; + break; + case 'R': + flags |= 16; + break; + case 'B': + flags |= 32; + break; + case 'E': + flags |= 64; + break; + case 'N': + flags |= 128; + break; + case 'S': + substr = 1; + break; + case 'I': + flnum = get_intarg(&s); + if (flnum < 0) + goto flagerr; + break; + + case 'L': + casmod = 2; + break; + case 'U': + casmod = 1; + break; + case 'C': + casmod = 3; + break; + + case 'o': + sortit = 1; + break; + case 'O': + sortit = 2; + break; + case 'i': + casind = 1; + break; + case 'e': + eval = 1; + break; + + case 'c': + whichlen = 1; + break; + case 'w': + whichlen = 2; + break; + case 'W': + whichlen = 3; + break; + + case 'f': + spsep = "\n"; + break; + case 'F': + sep = "\n"; + break; + + case 's': + tt = 1; + /* fall through */ + case 'j': + t = get_strarg(++s); + if (*t) { + sav = *t; + *t = '\0'; + if (tt) + UNTOK_AND_ESCAPE(spsep) + else + UNTOK_AND_ESCAPE(sep) + *t = sav; + s = t; + } else + goto flagerr; + break; + + case 'l': + tt = 1; + /* fall through */ + case 'r': + sav = s[1]; + num = get_intarg(&s); + if (num < 0) + goto flagerr; + if (tt) + prenum = num; + else + postnum = num; + if (s[1] != sav) + break; + t = get_strarg(++s); + if (!*t) + goto flagerr; + sav = *t; + *t = '\0'; + if (tt) + UNTOK_AND_ESCAPE(premul) + else + UNTOK_AND_ESCAPE(postmul) + *t = sav; + sav = *s; + s = t + 1; + if (*s != sav) { + s--; + break; + } + t = get_strarg(s); + if (!*t) + goto flagerr; + sav = *t; + *t = '\0'; + if (tt) + UNTOK_AND_ESCAPE(preone) + else + UNTOK_AND_ESCAPE(postone) + *t = sav; + s = t; + break; + + case 'p': + escapes = 1; + break; + + default: + flagerr: + zerr("error in flags", NULL, 0); + return NULL; + } + } + s++; + } + } + if (sortit) + sortit += (casind << 1); + + if (!premul) + premul = " "; + if (!postmul) + postmul = " "; + + for (;;) { + if (*s == '^' || *s == Hat) { + if (*++s == '^' || *s == Hat) { + plan9 = 0; + s++; + } else + plan9 = 1; + } else if (*s == '=' || *s == Equals) { + if (*++s == '=' || *s == Equals) { + spbreak = 0; + s++; + } else + spbreak = 1; + } else if ((*s == '#' || *s == Pound) && + (iident(s[1]) + || s[1] == '*' || s[1] == Star || s[1] == '@' + || (isstring(s[1]) && (s[2] == Inbrace || s[2] == Inpar)))) + getlen = 1 + whichlen, s++; + else if (*s == '~' || *s == Tilde) { + if (*++s == '~' || *s == Tilde) { + globsubst = 0; + s++; + } else + globsubst = 1; + } else if (*s == '+') + if (iident(s[1])) + chkset = 1, s++; + else if (!inbrace) { + *aptr = '$'; + *str = aptr + 1; + return n; + } else { + zerr("bad substitution", NULL, 0); + return NULL; + } + else + break; + } + globsubst = globsubst && !qt; + + idbeg = s; + if (s[-1] && isstring(*s) && (s[1] == Inbrace || s[1] == Inpar)) { + int sav; + int quoted = *s == Qstring; + + val = s++; + skipparens(*s, *s == Inpar ? Outpar : Outbrace, &s); + sav = *s; + *s = 0; + if (multsub(&val, &aval, &isarr, NULL) && quoted) { + isarr = -1; + aval = alloc(sizeof(char *)); + } + if (isarr) + isarr = -1; + copied = 1; + *s = sav; + v = (Value) NULL; + } else if (!(v = getvalue(&s, (unset(KSHARRAYS) || inbrace) ? 1 : -1))) + vunset = 1; + while (v || ((inbrace || (unset(KSHARRAYS) && vunset)) && isbrack(*s))) { + if (!v) { + Param pm; + char *os = s; + + if (!isbrack(*s)) + break; + if (vunset) { + val = dupstring(""); + isarr = 0; + } + pm = createparam(nulstring, isarr ? PM_ARRAY : PM_SCALAR); + if (isarr) + pm->u.arr = aval; + else + pm->u.str = val; + v = (Value) hcalloc(sizeof *v); + v->isarr = isarr; + v->pm = pm; + v->b = -1; + if (getindex(&s, v) || s == os) + break; + } + if ((isarr = v->isarr)) + aval = getarrvalue(v); + else { + if (v->pm->flags & PM_ARRAY) { + int tmplen = arrlen(v->pm->gets.afn(v->pm)); + + if (v->a < 0) + v->a += tmplen + v->inv; + if (!v->inv && (v->a >= tmplen || v->a < 0)) + vunset = 1; + } + if (!vunset) { + val = getstrvalue(v); + fwidth = v->pm->ct ? v->pm->ct : strlen(val); + switch (v->pm->flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) { + char *t; + unsigned int t0; + + case PM_LEFT: + case PM_LEFT | PM_RIGHT_Z: + t = val; + if (v->pm->flags & PM_RIGHT_Z) + while (*t == '0') + t++; + else + while (iblank(*t)) + t++; + val = (char *)ncalloc(fwidth + 1); + val[fwidth] = '\0'; + if ((t0 = strlen(t)) > fwidth) + t0 = fwidth; + memset(val, ' ', fwidth); + strncpy(val, t, t0); + break; + case PM_RIGHT_B: + case PM_RIGHT_Z: + case PM_RIGHT_Z | PM_RIGHT_B: + if (strlen(val) < fwidth) { + t = (char *)ncalloc(fwidth + 1); + memset(t, (v->pm->flags & PM_RIGHT_B) ? ' ' : '0', fwidth); + if ((t0 = strlen(val)) > fwidth) + t0 = fwidth; + strcpy(t + (fwidth - t0), val); + val = t; + } else { + t = (char *)ncalloc(fwidth + 1); + t[fwidth] = '\0'; + strncpy(t, val + strlen(val) - fwidth, fwidth); + val = t; + } + break; + } + switch (v->pm->flags & (PM_LOWER | PM_UPPER)) { + char *t; + + case PM_LOWER: + t = val; + for (; *t; t++) + *t = tulower(*t); + break; + case PM_UPPER: + t = val; + for (; *t; t++) + *t = tuupper(*t); + break; + } + } + } + v = NULL; + if (!inbrace) + break; + } + if (isarr) { + if (nojoin) + isarr = -1; + if (qt && !getlen && isarr > 0) { + val = sepjoin(aval, sep); + isarr = 0; + } + } + + idend = s; + if ((colf = *s == ':')) + s++; + + + /* fstr is to be the text following the substitution. If we have * + * braces, we look for it here, else we infer it later on. */ + fstr = s; + if (inbrace) { + int bct; + for (bct = 1;; fstr++) { + if (!*fstr) + break; + else if (*fstr == Inbrace) + bct++; + else if (*fstr == Outbrace && !--bct) + break; + } + + if (bct) { + noclosebrace: + zerr("closing brace expected", NULL, 0); + return NULL; + } + if (*fstr) + *fstr++ = '\0'; + } + + /* Check for ${..?..} or ${..=..} or one of those. * + * Only works if the name is in braces. */ + + if (inbrace && (*s == '-' || + *s == '+' || + *s == ':' || + *s == '=' || *s == Equals || + *s == '%' || + *s == '#' || *s == Pound || + *s == '?' || *s == Quest)) { + + if (!flnum) + flnum++; + if (*s == '%') + flags |= 1; + + /* Check for ${..%%..} or ${..##..} */ + if ((*s == '%' || *s == '#' || *s == Pound) && *s == s[1]) { + s++; + doub = 1; + } + s++; + + flags |= (doub << 1) | (substr << 2) | (colf << 8); + if (!(flags & 0xf8)) + flags |= 16; + + if (colf && !vunset) + vunset = (isarr) ? !*aval : !*val || (*val == Nularg && !val[1]); + + switch (s[-1]) { + case '+': + if (vunset) { + val = dupstring(""); + copied = 1; + isarr = 0; + break; + } + vunset = 1; + /* Fall Through! */ + case '-': + if (vunset) { + val = dupstring(s); + multsub(&val, &aval, &isarr, NULL); + copied = 1; + } + break; + case ':': + if (*s != '=' && *s != Equals) + goto noclosebrace; + vunset = 1; + s++; + /* Fall through */ + case '=': + case Equals: + if (vunset) { + char sav = *idend; + int l; + + *idend = '\0'; + val = dupstring(s); + isarr = 0; + if (spsep || spbreak || !arrasg) + multsub(&val, NULL, NULL, sep); + else + multsub(&val, &aval, &isarr, NULL); + if (arrasg) { + char *arr[2], **t, **a, **p; + if (spsep || spbreak) { + aval = sepsplit(val, spsep, 0); + isarr = 2; + sep = spsep = NULL; + spbreak = 0; + l = arrlen(aval); + if (l && !*(aval[l-1])) + l--; + if (l && !**aval) + l--, t = aval + 1; + else + t = aval; + } else if (!isarr) { + arr[0] = val; + arr[1] = NULL; + t = aval = arr; + l = 1; + } else + l = arrlen(aval), t = aval; + p = a = zalloc(sizeof(char *) * (l + 1)); + while (l--) { + untokenize(*t); + *p++ = ztrdup(*t++); + } + *p++ = NULL; + setaparam(idbeg, a); + } else { + untokenize(val); + setsparam(idbeg, ztrdup(val)); + } + *idend = sav; + copied = 1; + } + break; + case '?': + case Quest: + if (vunset) { + char *msg; + + *idend = '\0'; + msg = tricat(idbeg, ": ", *s ? s : "parameter not set"); + zerr("%s", msg, 0); + zsfree(msg); + if (!interact) + exit(1); + return NULL; + } + break; + case '%': + case '#': + case Pound: + if (qt) + if (parse_subst_string(s)) { + zerr("parse error in ${...%c...} substitution", + NULL, s[-1]); + return NULL; + } + singsub(&s); + + if (!vunset && isarr) { + char **ap = aval; + char **pp = aval = (char **)ncalloc(sizeof(char *) * (arrlen(aval) + 1)); + + while ((*pp = *ap++)) { + if (getmatch(pp, s, flags, flnum)) + pp++; + } + copied = 1; + } else { + if (vunset) + val = dupstring(""); + getmatch(&val, s, flags, flnum); + copied = 1; + } + break; + } + } else { /* no ${...=...} or anything, but possible modifiers. */ + if (chkset) { + val = dupstring(vunset ? "0" : "1"); + isarr = 0; + } else if (vunset) { + if (unset(UNSET)) { + *idend = '\0'; + zerr("%s: parameter not set", idbeg, 0); + return NULL; + } + val = dupstring(""); + } + if (colf) { + s--; + if (unset(KSHARRAYS) || inbrace) { + if (!isarr) + modify(&val, &s); + else { + char *ss; + char **ap = aval; + char **pp = aval = (char **)ncalloc(sizeof(char *) * (arrlen(aval) + 1)); + + while ((*pp = *ap++)) { + ss = s; + modify(pp++, &ss); + } + if (pp == aval) { + char *t = ""; + ss = s; + modify(&t, &ss); + } + s = ss; + } + if (inbrace && *s) { + if (*s == ':' && !imeta(s[1])) + zerr("unrecognized modifier `%c'", NULL, s[1]); + else + zerr("unrecognized modifier", NULL, 0); + return NULL; + } + } + } + if (!inbrace) + fstr = s; + } + if (errflag) + return NULL; + if (getlen) { + long len = 0; + char buf[14]; + + if (isarr) { + char **ctr; + int sl = sep ? ztrlen(sep) : 1; + + if (getlen == 1) + for (ctr = aval; *ctr; ctr++, len++); + else if (getlen == 2) { + if (*aval) + for (len = -sl, ctr = aval; + len += sl + ztrlen(*ctr), *++ctr;); + } + else + for (ctr = aval; + *ctr; + len += wordcount(*ctr, spsep, getlen > 3), ctr++); + } else { + if (getlen < 3) + len = ztrlen(val); + else + len = wordcount(val, spsep, getlen > 3); + } + + sprintf(buf, "%ld", len); + val = dupstring(buf); + isarr = 0; + } + if (isarr > 0 && !plan9 && (!aval || !aval[0])) { + val = dupstring(""); + isarr = 0; + } else if (isarr && aval && aval[0] && !aval[1]) { + val = aval[0]; + isarr = 0; + } + /* ssub is true when we are called from singsub (via prefork). + * It means that we must join arrays and should not split words. */ + if (ssub || spbreak || spsep || sep) { + if (isarr) + val = sepjoin(aval, sep), isarr = 0; + if (!ssub && (spbreak || spsep)) { + aval = sepsplit(val, spsep, 0); + if (!aval || !aval[0]) + val = dupstring(""); + else if (!aval[1]) + val = aval[0]; + else + isarr = 2; + } + } + if (casmod) { + if (isarr) { + char **ap; + + if (!copied) + aval = arrdup(aval), copied = 1; + ap = aval; + + if (casmod == 1) + for (; *ap; ap++) + makeuppercase(ap); + else if (casmod == 2) + for (; *ap; ap++) + makelowercase(ap); + else + for (; *ap; ap++) + makecapitals(ap); + + } else { + if (!copied) + val = dupstring(val), copied = 1; + if (casmod == 1) + makeuppercase(&val); + else if (casmod == 2) + makelowercase(&val); + else + makecapitals(&val); + } + } + if (isarr) { + char *x; + char *y; + int xlen; + int i; + LinkNode on = n; + + if (!aval[0] && !plan9) { + if (aptr > (char *) getdata(n) && + aptr[-1] == Dnull && *fstr == Dnull) + *--aptr = '\0', fstr++; + y = (char *)ncalloc((aptr - ostr) + strlen(fstr) + 1); + strcpy(y, ostr); + *str = y + (aptr - ostr); + strcpy(*str, fstr); + setdata(n, y); + return n; + } + if (sortit) { + static CompareFn sortfn[] = { + strpcmp, invstrpcmp, cstrpcmp, invcstrpcmp + }; + + if (!copied) + aval = arrdup(aval); + + i = arrlen(aval); + if (i && (*aval[i-1] || --i)) + qsort(aval, i, sizeof(char *), sortfn[sortit-1]); + } + if (plan9) { + LinkList tl = newlinklist(); + LinkNode tn; + + *--fstr = Marker; + addlinknode(tl, fstr); + if (!eval && !stringsubst(tl, firstnode(tl), ssub)) + return NULL; + *str = aptr; + tn = firstnode(tl); + while ((x = *aval++)) { + if (prenum || postnum) + x = dopadding(x, prenum, postnum, preone, postone, + premul, postmul); + if (eval && parsestr(x)) + return NULL; + xlen = strlen(x); + for (tn = firstnode(tl); + tn && *(y = (char *) getdata(tn)) == Marker; + incnode(tn)) { + strcatsub(&y, ostr, aptr, x, xlen, y + 1, globsubst); + if (qt && !*y && isarr != 2) + y = dupstring(nulstring); + if (plan9) + setdata(n, (void *) y), plan9 = 0; + else + insertlinknode(l, n, (void *) y), incnode(n); + } + } + for (; tn; incnode(tn)) { + y = (char *) getdata(tn); + if (*y == Marker) + continue; + if (qt && !*y && isarr != 2) + y = dupstring(nulstring); + if (plan9) + setdata(n, (void *) y), plan9 = 0; + else + insertlinknode(l, n, (void *) y), incnode(n); + } + if (plan9) { + uremnode(l, n); + return n; + } + } else { + x = aval[0]; + if (prenum || postnum) + x = dopadding(x, prenum, postnum, preone, postone, + premul, postmul); + if (eval && parsestr(x)) + return NULL; + xlen = strlen(x); + strcatsub(&y, ostr, aptr, x, xlen, NULL, globsubst); + if (qt && !*y && isarr != 2) + y = dupstring(nulstring); + setdata(n, (void *) y); + + i = 1; + /* aval[1] is non-null here */ + while (aval[i + 1]) { + x = aval[i++]; + if (prenum || postnum) + x = dopadding(x, prenum, postnum, preone, postone, + premul, postmul); + if (eval && parsestr(x)) + return NULL; + if (qt && !*x && isarr != 2) + y = dupstring(nulstring); + else { + y = dupstring(x); + if (globsubst) + tokenize(y); + } + insertlinknode(l, n, (void *) y), incnode(n); + } + + x = aval[i]; + if (prenum || postnum) + x = dopadding(x, prenum, postnum, preone, postone, + premul, postmul); + if (eval && parsestr(x)) + return NULL; + xlen = strlen(x); + *str = strcatsub(&y, aptr, aptr, x, xlen, fstr, globsubst); + if (qt && !*y && isarr != 2) + y = dupstring(nulstring); + insertlinknode(l, n, (void *) y), incnode(n); + } + if (eval) + n = on; + } else { + int xlen; + char *x; + char *y; + + x = val; + if (prenum || postnum) + x = dopadding(x, prenum, postnum, preone, postone, + premul, postmul); + if (eval && parsestr(x)) + return NULL; + xlen = strlen(x); + *str = strcatsub(&y, ostr, aptr, x, xlen, fstr, globsubst); + if (qt && !*y && isarr != 2) + y = dupstring(nulstring); + setdata(n, (void *) y); + } + if (eval) + *str = (char *) getdata(n); + + return n; +} + +/* + * Arithmetic substitution: `a' is the string to be evaluated, `bptr' + * points to the beginning of the string containing it. The tail of + * the string is given by `rest'. *bptr is modified with the substituted + * string. The function returns a pointer to the tail in the substituted + * string. + */ + +/**/ +static char * +arithsubst(char *a, char **bptr, char *rest) +{ + char *s = *bptr, *t, buf[DIGBUFSIZE]; + char *b = buf; + long v; + + singsub(&a); + v = matheval(a); + sprintf(buf, "%ld", v); + t = *bptr = (char *)ncalloc(strlen(*bptr) + strlen(buf) + strlen(rest) + 1); + t--; + while ((*++t = *s++)); + t--; + while ((*++t = *b++)); + strcat(t, rest); + return t; +} + +/**/ +void +modify(char **str, char **ptr) +{ + char *ptr1, *ptr2, *ptr3, del, *lptr, c, *test, *sep, *t, *tt, tc, *e; + char *copy, *all, *tmp, sav; + int gbal, wall, rec, al, nl; + + test = NULL; + + if (**ptr == ':') + *str = dupstring(*str); + + while (**ptr == ':') { + lptr = *ptr; + (*ptr)++; + wall = gbal = 0; + rec = 1; + c = '\0'; + sep = NULL; + + for (; !c && **ptr;) { + switch (**ptr) { + case 'h': + case 'r': + case 'e': + case 't': + case 'l': + case 'u': + c = **ptr; + break; + + case 's': + c = **ptr; + (*ptr)++; + ptr1 = *ptr; + del = *ptr1++; + for (ptr2 = ptr1; *ptr2 != del && *ptr2; ptr2++); + if (!*ptr2) { + zerr("bad substitution", NULL, 0); + return; + } + *ptr2++ = '\0'; + for (ptr3 = ptr2; *ptr3 != del && *ptr3; ptr3++); + if ((sav = *ptr3)) + *ptr3++ = '\0'; + if (*ptr1) { + zsfree(hsubl); + hsubl = ztrdup(ptr1); + } + if (!hsubl) { + zerr("no previous substitution", NULL, 0); + return; + } + zsfree(hsubr); + for (tt = hsubl; *tt; tt++) + if (INULL(*tt)) + chuck(tt--); + untokenize(hsubl); + for (tt = hsubr = ztrdup(ptr2); *tt; tt++) + if (INULL(*tt)) + chuck(tt--); + ptr2[-1] = del; + if (sav) + ptr3[-1] = sav; + *ptr = ptr3 - 1; + break; + + case '&': + c = 's'; + break; + + case 'g': + (*ptr)++; + gbal = 1; + break; + + case 'w': + wall = 1; + (*ptr)++; + break; + case 'W': + wall = 1; + (*ptr)++; + ptr1 = get_strarg(ptr2 = *ptr); + if ((sav = *ptr1)) + *ptr1 = '\0'; + sep = dupstring(ptr2 + 1); + if (sav) + *ptr1 = sav; + *ptr = ptr1 + 1; + c = '\0'; + break; + + case 'f': + rec = -1; + (*ptr)++; + break; + case 'F': + rec = get_intarg(ptr); + (*ptr)++; + break; + default: + *ptr = lptr; + return; + } + } + (*ptr)++; + if (!c) { + *ptr = lptr; + return; + } + if (rec < 0) + test = dupstring(*str); + + while (rec--) { + if (wall) { + al = 0; + all = NULL; + for (t = e = *str; (tt = findword(&e, sep));) { + tc = *e; + *e = '\0'; + copy = dupstring(tt); + *e = tc; + switch (c) { + case 'h': + remtpath(©); + break; + case 'r': + remtext(©); + break; + case 'e': + rembutext(©); + break; + case 't': + remlpaths(©); + break; + case 'l': + downcase(©); + break; + case 'u': + upcase(©); + break; + case 's': + if (hsubl && hsubr) + subst(©, hsubl, hsubr, gbal); + break; + } + tc = *tt; + *tt = '\0'; + nl = al + strlen(t) + strlen(copy); + ptr1 = tmp = (char *)halloc(nl + 1); + if (all) + for (ptr2 = all; *ptr2;) + *ptr1++ = *ptr2++; + for (ptr2 = t; *ptr2;) + *ptr1++ = *ptr2++; + *tt = tc; + for (ptr2 = copy; *ptr2;) + *ptr1++ = *ptr2++; + *ptr1 = '\0'; + al = nl; + all = tmp; + t = e; + } + *str = all; + + } else { + switch (c) { + case 'h': + remtpath(str); + break; + case 'r': + remtext(str); + break; + case 'e': + rembutext(str); + break; + case 't': + remlpaths(str); + break; + case 'l': + downcase(str); + break; + case 'u': + upcase(str); + break; + case 's': + if (hsubl && hsubr) { + char *oldstr = *str; + + subst(str, hsubl, hsubr, gbal); + if (*str != oldstr) { + *str = dupstring(oldstr = *str); + zsfree(oldstr); + } + } + break; + } + } + if (rec < 0) { + if (!strcmp(test, *str)) + rec = 0; + else + test = dupstring(*str); + } + } + } +} + +/* get a directory stack entry */ + +/**/ +static char * +dstackent(char ch, int val) +{ + int backwards; + LinkNode end=(LinkNode)dirstack, n; + + backwards = ch == (isset(PUSHDMINUS) ? '+' : '-'); + if(!backwards && !val--) + return pwd; + if (backwards) + for (n=lastnode(dirstack); n != end && val; val--, n=prevnode(n)); + else + for (end=NULL, n=firstnode(dirstack); n && val; val--, n=nextnode(n)); + if (n == end) { + if (isset(NOMATCH)) + zerr("not enough directory stack entries.", NULL, 0); + return NULL; + } + return (char *)getdata(n); +} diff --git a/Src/system.h b/Src/system.h new file mode 100644 index 000000000..e42f3b891 --- /dev/null +++ b/Src/system.h @@ -0,0 +1,598 @@ +/* + * system.h - system configuration header file + * + * 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. + * + */ + +#ifdef __hpux +# define _INCLUDE_POSIX_SOURCE 1 +# define _INCLUDE_XOPEN_SOURCE 1 +# define _INCLUDE_HPUX_SOURCE 1 +#endif + +#ifdef sinix +# define _XPG_IV 1 +#endif + +/* NeXT has half-implemented POSIX support * + * which currently fools configure */ +#ifdef __NeXT__ +# undef HAVE_TERMIOS_H +# undef HAVE_SYS_UTSNAME_H +#endif + +#ifdef PROTOTYPES +# define _(Args) Args +#else +# define _(Args) () +#endif + +#ifndef HAVE_ALLOCA +# define alloca halloc +#else +# ifdef __GNUC__ +# define alloca __builtin_alloca +# else +# if HAVE_ALLOCA_H +# include <alloca.h> +# else +# ifdef _AIX + # pragma alloca +# else +# ifndef alloca +char *alloca _((size_t)); +# endif +# endif +# endif +# endif +#endif + +#ifdef HAVE_LIBC_H /* NeXT */ +# include <libc.h> +#endif + +#ifdef HAVE_SYS_TYPES_H +# include <sys/types.h> +#endif + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#include <stdio.h> +#include <ctype.h> +#include <sys/stat.h> +#include <signal.h> +#include <setjmp.h> + +#ifdef HAVE_PWD_H +# include <pwd.h> +#endif + +#ifdef HAVE_GRP_H +# include <grp.h> +#endif + +#ifdef HAVE_DIRENT_H +# include <dirent.h> +#else /* !HAVE_DIRENT_H */ +# ifdef HAVE_SYS_NDIR_H +# include <sys/ndir.h> +# endif +# ifdef HAVE_SYS_DIR_H +# include <sys/dir.h> +# endif +# ifdef HAVE_NDIR_H +# include <ndir.h> +# endif +# define dirent direct +# undef HAVE_STRUCT_DIRENT_D_INO +# undef HAVE_STRUCT_DIRENT_D_STAT +# ifdef HAVE_STRUCT_DIRECT_D_INO +# define HAVE_STRUCT_DIRENT_D_INO HAVE_STRUCT_DIRECT_D_INO +# endif +# ifdef HAVE_STRUCT_DIRECT_D_STAT +# define HAVE_STRUCT_DIRENT_D_STAT HAVE_STRUCT_DIRECT_D_STAT +# endif +#endif /* !HAVE_DIRENT_H */ + +#ifdef HAVE_STDLIB_H +# ifdef ZSH_MEM + /* malloc and calloc are macros in GNU's stdlib.h unless the + * the __MALLOC_0_RETURNS_NULL macro is defined */ +# define __MALLOC_0_RETURNS_NULL +# endif +# include <stdlib.h> +#endif + +#ifdef HAVE_ERRNO_H +# include <errno.h> +#endif + +#ifdef TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +#else +# ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +# else +# include <time.h> +# endif +#endif + +/* This is needed by some old SCO unices */ +#ifndef HAVE_STRUCT_TIMEZONE +struct timezone { + int tz_minuteswest; + int tz_dsttime; +}; +#endif + +/* There's more than one non-standard way to get at this data */ +#if !defined(HAVE_STRUCT_DIRENT_D_INO) && defined(HAVE_STRUCT_DIRENT_D_STAT) +# define d_ino d_stat.st_ino +# define HAVE_STRUCT_DIRENT_D_INO HAVE_STRUCT_DIRENT_D_STAT +#endif /* !HAVE_STRUCT_DIRENT_D_INO && HAVE_STRUCT_DIRENT_D_STAT */ + +/* Sco needs the following include for struct utimbuf * + * which is strange considering we do not use that * + * anywhere in the code */ +#ifdef __sco +# include <utime.h> +#endif + +#ifdef HAVE_SYS_TIMES_H +# include <sys/times.h> +#endif + +#if STDC_HEADERS || HAVE_STRING_H +# include <string.h> +/* An ANSI string.h and pre-ANSI memory.h might conflict. */ +# if !STDC_HEADERS && HAVE_MEMORY_H +# include <memory.h> +# endif /* not STDC_HEADERS and HAVE_MEMORY_H */ +#else /* not STDC_HEADERS and not HAVE_STRING_H */ +# include <strings.h> +/* memory.h and strings.h conflict on some systems. */ +#endif /* not STDC_HEADERS and not HAVE_STRING_H */ + +#ifdef HAVE_LOCALE_H +# include <locale.h> +#endif + +#ifdef HAVE_LIMITS_H +# include <limits.h> +#endif + +#ifdef HAVE_VARIABLE_LENGTH_ARRAYS +# define VARARR(X,Y,Z) X (Y)[Z] +#else +# define VARARR(X,Y,Z) X *(Y) = (X *) alloca(sizeof(X) * (Z)) +#endif + +/* we should be getting this value from pathconf(_PC_PATH_MAX) */ +/* but this is too much trouble */ +#ifndef PATH_MAX +# ifdef MAXPATHLEN +# define PATH_MAX MAXPATHLEN +# else + /* so we will just pick something */ +# define PATH_MAX 1024 +# endif +#endif + +/* we should be getting this value from sysconf(_SC_OPEN_MAX) */ +/* but this is too much trouble */ +#ifndef OPEN_MAX +# ifdef NOFILE +# define OPEN_MAX NOFILE +# else + /* so we will just pick something */ +# define OPEN_MAX 64 +# endif +#endif + +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#else +# include <sys/file.h> +#endif + +/* The following will only be defined if <sys/wait.h> is POSIX. * + * So we don't have to worry about union wait. But some machines * + * (NeXT) include <sys/wait.h> from other include files, so we * + * need to undef and then redefine the wait macros if <sys/wait.h> * + * is not POSIX. */ + +#ifdef HAVE_SYS_WAIT_H +# include <sys/wait.h> +#else +# undef WIFEXITED +# undef WEXITSTATUS +# undef WIFSIGNALED +# undef WTERMSIG +# undef WCOREDUMP +# undef WIFSTOPPED +# undef WSTOPSIG +#endif + +/* missing macros for wait/waitpid/wait3 */ +#ifndef WIFEXITED +# define WIFEXITED(X) (((X)&0377)==0) +#endif +#ifndef WEXITSTATUS +# define WEXITSTATUS(X) (((X)>>8)&0377) +#endif +#ifndef WIFSIGNALED +# define WIFSIGNALED(X) (((X)&0377)!=0&&((X)&0377)!=0177) +#endif +#ifndef WTERMSIG +# define WTERMSIG(X) ((X)&0177) +#endif +#ifndef WCOREDUMP +# define WCOREDUMP(X) ((X)&0200) +#endif +#ifndef WIFSTOPPED +# define WIFSTOPPED(X) (((X)&0377)==0177) +#endif +#ifndef WSTOPSIG +# define WSTOPSIG(X) (((X)>>8)&0377) +#endif + +#ifdef HAVE_SYS_SELECT_H +# ifndef TIME_H_SELECT_H_CONFLICTS +# include <sys/select.h> +# endif +#endif + +#ifdef HAVE_SYS_FILIO_H +# include <sys/filio.h> +#endif + +#ifdef HAVE_TERMIOS_H +# ifdef __sco + /* termios.h includes sys/termio.h instead of sys/termios.h; * + * hence the declaration for struct termios is missing */ +# include <sys/termios.h> +# else +# include <termios.h> +# endif +# ifdef _POSIX_VDISABLE +# define VDISABLEVAL _POSIX_VDISABLE +# else +# define VDISABLEVAL 0 +# endif +# define HAS_TIO 1 +#else /* not TERMIOS */ +# ifdef HAVE_TERMIO_H +# include <termio.h> +# define VDISABLEVAL -1 +# define HAS_TIO 1 +# else /* not TERMIOS and TERMIO */ +# include <sgtty.h> +# endif /* HAVE_TERMIO_H */ +#endif /* HAVE_TERMIOS_H */ + +#ifdef HAVE_TERMCAP_H +# include <termcap.h> +#endif + +#if defined(GWINSZ_IN_SYS_IOCTL) || defined(CLOBBERS_TYPEAHEAD) +# include <sys/ioctl.h> +#endif +#ifdef WINSIZE_IN_PTEM +# include <sys/stream.h> +# include <sys/ptem.h> +#endif + +#ifdef HAVE_SYS_PARAM_H +# include <sys/param.h> +#endif + +#ifdef HAVE_SYS_UTSNAME_H +# include <sys/utsname.h> +#endif + +#define DEFAULT_WORDCHARS "*?_-.[]~=/&;!#$%^(){}<>" +#define DEFAULT_TIMEFMT "%J %U user %S system %P cpu %*E total" + +/* Posix getpgrp takes no argument, while the BSD version * + * takes the process ID as an argument */ +#ifdef GETPGRP_VOID +# define GETPGRP() getpgrp() +#else +# define GETPGRP() getpgrp(0) +#endif + +#ifndef HAVE_GETLOGIN +# define getlogin() cuserid(NULL) +#endif + +#ifdef HAVE_SETPGID +# define setpgrp setpgid +#endif + +/* can we set the user/group id of a process */ + +#ifndef HAVE_SETUID +# ifdef HAVE_SETREUID +# define setuid(X) setreuid(X,X) +# define setgid(X) setregid(X,X) +# define HAVE_SETUID +# endif +#endif + +/* can we set the effective user/group id of a process */ + +#ifndef HAVE_SETEUID +# ifdef HAVE_SETREUID +# define seteuid(X) setreuid(-1,X) +# define setegid(X) setregid(-1,X) +# define HAVE_SETEUID +# else +# ifdef HAVE_SETRESUID +# define seteuid(X) setresuid(-1,X,-1) +# define setegid(X) setresgid(-1,X,-1) +# define HAVE_SETEUID +# endif +# endif +#endif + +#ifdef HAVE_SYS_RESOURCE_H +# include <sys/resource.h> +# if defined(__hpux) && !defined(RLIMIT_CPU) +/* HPUX does have the BSD rlimits in the kernel. Officially they are * + * unsupported but quite a few of them like RLIMIT_CORE seem to work. * + * All the following are in the <sys/resource.h> but made visible * + * only for the kernel. */ +# define RLIMIT_CPU 0 +# define RLIMIT_FSIZE 1 +# define RLIMIT_DATA 2 +# define RLIMIT_STACK 3 +# define RLIMIT_CORE 4 +# define RLIMIT_RSS 5 +# define RLIMIT_NOFILE 6 +# define RLIMIT_OPEN_MAX RLIMIT_NOFILE +# define RLIM_NLIMITS 7 +# define RLIM_INFINITY 0x7fffffff +# endif +#endif + +/* we use the SVR4 constant instead of the BSD one */ +#if !defined(RLIMIT_NOFILE) && defined(RLIMIT_OFILE) +# define RLIMIT_NOFILE RLIMIT_OFILE +#endif +#if !defined(RLIMIT_VMEM) && defined(RLIMIT_AS) +# define RLIMIT_VMEM RLIMIT_AS +#endif + +#ifdef HAVE_SYS_CAPABILITY_H +# include <sys/capability.h> +#endif + +/* DIGBUFSIZ is the length of a buffer which can hold the -LONG_MAX-1 * + * converted to printable decimal form including the sign and the * + * terminating null character. Below 0.30103 > lg 2. */ +#define DIGBUFSIZE ((int)(((sizeof(long) * 8) - 1) * 0.30103) + 3) + +/* If your stat macros are broken, we will * + * just undefine them. */ + +#ifdef STAT_MACROS_BROKEN +# undef S_ISBLK +# undef S_ISCHR +# undef S_ISDIR +# undef S_ISFIFO +# undef S_ISLNK +# undef S_ISMPB +# undef S_ISMPC +# undef S_ISNWK +# undef S_ISOFD +# undef S_ISOFL +# undef S_ISREG +# undef S_ISSOCK +#endif /* STAT_MACROS_BROKEN. */ + +/* If you are missing the stat macros, we * + * define our own */ + +#ifndef S_IFMT +# define S_IFMT 0170000 +#endif + +#if !defined(S_ISBLK) && defined(S_IFBLK) +# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) +#endif +#if !defined(S_ISCHR) && defined(S_IFCHR) +# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) +#endif +#if !defined(S_ISDIR) && defined(S_IFDIR) +# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#endif +#if !defined(S_ISFIFO) && defined(S_IFIFO) +# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) +#endif +#if !defined(S_ISLNK) && defined(S_IFLNK) +# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) +#endif +#if !defined(S_ISMPB) && defined(S_IFMPB) /* V7 */ +# define S_ISMPB(m) (((m) & S_IFMT) == S_IFMPB) +#endif +#if !defined(S_ISMPC) && defined(S_IFMPC) /* V7 */ +# define S_ISMPC(m) (((m) & S_IFMT) == S_IFMPC) +#endif +#if !defined(S_ISNWK) && defined(S_IFNWK) /* HP/UX */ +# define S_ISNWK(m) (((m) & S_IFMT) == S_IFNWK) +#endif +#if !defined(S_ISOFD) && defined(S_IFOFD) /* Cray */ +# define S_ISOFD(m) (((m) & S_IFMT) == S_IFOFD) +#endif +#if !defined(S_ISOFL) && defined(S_IFOFL) /* Cray */ +# define S_ISOFL(m) (((m) & S_IFMT) == S_IFOFL) +#endif +#if !defined(S_ISREG) && defined(S_IFREG) +# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#endif +#if !defined(S_ISSOCK) && defined(S_IFSOCK) +# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) +#endif + +/* We will pretend to have all file types on any system. */ + +#ifndef S_ISBLK +# define S_ISBLK(m) ((void)(m), 0) +#endif +#ifndef S_ISCHR +# define S_ISCHR(m) ((void)(m), 0) +#endif +#ifndef S_ISDIR +# define S_ISDIR(m) ((void)(m), 0) +#endif +#ifndef S_ISFIFO +# define S_ISFIFO(m) ((void)(m), 0) +#endif +#ifndef S_ISLNK +# define S_ISLNK(m) ((void)(m), 0) +#endif +#ifndef S_ISMPB +# define S_ISMPB(m) ((void)(m), 0) +#endif +#ifndef S_ISMPC +# define S_ISMPC(m) ((void)(m), 0) +#endif +#ifndef S_ISNWK +# define S_ISNWK(m) ((void)(m), 0) +#endif +#ifndef S_ISOFD +# define S_ISOFD(m) ((void)(m), 0) +#endif +#ifndef S_ISOFL +# define S_ISOFL(m) ((void)(m), 0) +#endif +#ifndef S_ISREG +# define S_ISREG(m) ((void)(m), 0) +#endif +#ifndef S_ISSOCK +# define S_ISSOCK(m) ((void)(m), 0) +#endif + +/* file mode permission bits */ + +#ifndef S_ISUID +# define S_ISUID 04000 +#endif +#ifndef S_ISGID +# define S_ISGID 02000 +#endif +#ifndef S_ISVTX +# define S_ISVTX 01000 +#endif +#ifndef S_IRUSR +# define S_IRUSR 00400 +#endif +#ifndef S_IWUSR +# define S_IWUSR 00200 +#endif +#ifndef S_IXUSR +# define S_IXUSR 00100 +#endif +#ifndef S_IRGRP +# define S_IRGRP 00040 +#endif +#ifndef S_IWGRP +# define S_IWGRP 00020 +#endif +#ifndef S_IXGRP +# define S_IXGRP 00010 +#endif +#ifndef S_IROTH +# define S_IROTH 00004 +#endif +#ifndef S_IWOTH +# define S_IWOTH 00002 +#endif +#ifndef S_IXOTH +# define S_IXOTH 00001 +#endif +#ifndef S_IRWXU +# define S_IRWXU (S_IRUSR|S_IWUSR|S_IXUSR) +#endif +#ifndef S_IRWXG +# define S_IRWXG (S_IRGRP|S_IWGRP|S_IXGRP) +#endif +#ifndef S_IRWXO +# define S_IRWXO (S_IROTH|S_IWOTH|S_IXOTH) +#endif +#ifndef S_IRUGO +# define S_IRUGO (S_IRUSR|S_IRGRP|S_IROTH) +#endif +#ifndef S_IWUGO +# define S_IWUGO (S_IWUSR|S_IWGRP|S_IWOTH) +#endif +#ifndef S_IXUGO +# define S_IXUGO (S_IXUSR|S_IXGRP|S_IXOTH) +#endif + +#ifndef HAVE_LSTAT +# define lstat stat +#endif + +#ifndef HAVE_READLINK +# define readlink(PATH, BUF, BUFSZ) \ + ((void)(PATH), (void)(BUF), (void)(BUFSZ), errno = ENOSYS, -1) +#endif + +#ifndef F_OK /* missing macros for access() */ +# define F_OK 0 +# define X_OK 1 +# define W_OK 2 +# define R_OK 4 +#endif + +#ifndef HAVE_MEMCPY +# define memcpy memmove +#endif + +#ifndef HAVE_MEMMOVE +# define memmove(dest, src, len) bcopy((src), (dest), (len)) +#endif + +#ifndef offsetof +# define offsetof(TYPE, MEM) ((char *)&((TYPE *)0)->MEM - (char *)(TYPE *)0) +#endif + +extern char **environ; + +/* These variables are sometimes defined in, * + * and needed by, the termcap library. */ +#if MUST_DEFINE_OSPEED +extern char PC, *BC, *UP; +extern short ospeed; +#endif + +#ifndef O_NOCTTY +# define O_NOCTTY 0 +#endif diff --git a/Src/text.c b/Src/text.c new file mode 100644 index 000000000..b7df8012f --- /dev/null +++ b/Src/text.c @@ -0,0 +1,526 @@ +/* + * text.c - textual representations of syntax trees + * + * 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 "text.pro" + +static char *tptr, *tbuf, *tlim; +static int tsiz, tindent, tnewlins; + +/* add a character to the text buffer */ + +/**/ +static void +taddchr(int c) +{ + *tptr++ = c; + if (tptr == tlim) { + if (!tbuf) { + tptr--; + return; + } + tbuf = realloc(tbuf, tsiz *= 2); + tlim = tbuf + tsiz; + tptr = tbuf + tsiz / 2; + } +} + +/* add a string to the text buffer */ + +/**/ +static void +taddstr(char *s) +{ + int sl = strlen(s); + + while (tptr + sl >= tlim) { + int x = tptr - tbuf; + + if (!tbuf) + return; + tbuf = realloc(tbuf, tsiz *= 2); + tlim = tbuf + tsiz; + tptr = tbuf + x; + } + strcpy(tptr, s); + tptr += sl; +} + +#if 0 +/* add an integer to the text buffer */ + +/**/ +void +taddint(int x) +{ + char buf[DIGBUFSIZE]; + + sprintf(buf, "%d", x); + taddstr(buf); +} +#endif + +/* add a newline, or something equivalent, to the text buffer */ + +/**/ +static void +taddnl(void) +{ + int t0; + + if (tnewlins) { + taddchr('\n'); + for (t0 = 0; t0 != tindent; t0++) + taddchr('\t'); + } else + taddstr("; "); +} + +/* get a permanent textual representation of n */ + +/**/ +char * +getpermtext(struct node *n) +{ + tnewlins = 1; + tbuf = (char *)zalloc(tsiz = 32); + tptr = tbuf; + tlim = tbuf + tsiz; + tindent = 1; + gettext2(n); + *tptr = '\0'; + untokenize(tbuf); + return tbuf; +} + +/* get a representation of n in a job text buffer */ + +/**/ +char * +getjobtext(struct node *n) +{ + static char jbuf[JOBTEXTSIZE]; + + tnewlins = 0; + tbuf = NULL; + tptr = jbuf; + tlim = tptr + JOBTEXTSIZE - 1; + tindent = 1; + gettext2(n); + *tptr = '\0'; + untokenize(jbuf); + return jbuf; +} + +#define gt2(X) gettext2((struct node *) (X)) + +/* + "gettext2" or "type checking and how to avoid it" + an epic function by Paul Falstad +*/ + +#define _Cond(X) ((Cond) (X)) +#define _Cmd(X) ((Cmd) (X)) +#define _Pline(X) ((Pline) (X)) +#define _Sublist(X) ((Sublist) (X)) +#define _List(X) ((List) (X)) +#define _casecmd(X) ((struct casecmd *) (X)) +#define _ifcmd(X) ((struct ifcmd *) (X)) +#define _whilecmd(X) ((struct whilecmd *) (X)) + +/**/ +static void +gettext2(struct node *n) +{ + Cmd nn; + + if (!n || ((List) n) == &dummy_list) + return; + switch (NT_TYPE(n->ntype)) { + case N_LIST: + gt2(_List(n)->left); + if (_List(n)->type & Z_ASYNC) { + taddstr(" &"); + if (_List(n)->type & Z_DISOWN) + taddstr("|"); + } + simplifyright(_List(n)); + if (_List(n)->right) { + if (tnewlins) + taddnl(); + else + taddstr((_List(n)->type & Z_ASYNC) ? " " : "; "); + gt2(_List(n)->right); + } + break; + case N_SUBLIST: + if (_Sublist(n)->flags & PFLAG_NOT) + taddstr("! "); + if (_Sublist(n)->flags & PFLAG_COPROC) + taddstr("coproc "); + gt2(_Sublist(n)->left); + if (_Sublist(n)->right) { + taddstr((_Sublist(n)->type == ORNEXT) ? " || " : " && "); + gt2(_Sublist(n)->right); + } + break; + case N_PLINE: + gt2(_Pline(n)->left); + if (_Pline(n)->type == PIPE) { + taddstr(" | "); + gt2(_Pline(n)->right); + } + break; + case N_CMD: + nn = _Cmd(n); + switch (nn->type) { + case SIMPLE: + getsimptext(nn); + break; + case SUBSH: + taddstr("( "); + tindent++; + gt2(nn->u.list); + tindent--; + taddstr(" )"); + break; + case ZCTIME: + taddstr("time "); + tindent++; + gt2(nn->u.pline); + tindent--; + break; + case FUNCDEF: + taddlist(nn->args); + taddstr(" () {"); + tindent++; + taddnl(); + gt2(nn->u.list); + tindent--; + taddnl(); + taddstr("}"); + break; + case CURSH: + taddstr("{ "); + tindent++; + gt2(nn->u.list); + tindent--; + taddstr(" }"); + break; + case CFOR: + case CSELECT: + taddstr((nn->type == CFOR) ? "for " : "select "); + if (nn->u.forcmd->condition) { + taddstr("(("); + taddstr(nn->u.forcmd->name); + taddstr("; "); + taddstr(nn->u.forcmd->condition); + taddstr("; "); + taddstr(nn->u.forcmd->advance); + taddstr(")) do"); + } else { + taddstr(nn->u.forcmd->name); + if (nn->u.forcmd->inflag) { + taddstr(" in "); + taddlist(nn->args); + } + taddnl(); + taddstr("do"); + } + tindent++; + taddnl(); + gt2(nn->u.forcmd->list); + tindent--; + taddnl(); + taddstr("done"); + break; + case CIF: + gt2(nn->u.ifcmd); + taddstr("fi"); + break; + case CCASE: + gt2(nn->u.casecmd); + break; + case COND: + taddstr("[[ "); + gt2(nn->u.cond); + taddstr(" ]]"); + break; + case CARITH: + taddstr("(("); + taddlist(nn->args); + taddstr("))"); + break; + case CREPEAT: + taddstr("repeat "); + taddlist(nn->args); + taddnl(); + taddstr("do"); + tindent++; + taddnl(); + gt2(nn->u.list); + tindent--; + taddnl(); + taddstr("done"); + break; + case CWHILE: + gt2(nn->u.whilecmd); + break; + } + getredirs(nn); + break; + case N_COND: + getcond(_Cond(n), 0); + break; + case N_CASE: + { + List *l; + char **p; + + l = _casecmd(n)->lists; + p = _casecmd(n)->pats; + + taddstr("case "); + taddstr(*p++); + taddstr(" in"); + tindent++; + for (; *l; p++, l++) { + if (tnewlins) + taddnl(); + else + taddchr(' '); + taddstr(*p + 1); + taddstr(") "); + tindent++; + gt2(*l); + tindent--; + taddstr(" ;"); + taddchr(**p); + } + tindent--; + if (tnewlins) + taddnl(); + else + taddchr(' '); + taddstr("esac"); + break; + } + case N_IF: + { + List *i, *t; + + taddstr("if "); + for (i = _ifcmd(n)->ifls, t = _ifcmd(n)->thenls; *i; i++, t++) { + tindent++; + gt2(*i); + tindent--; + taddnl(); + taddstr("then"); + tindent++; + taddnl(); + gt2(*t); + tindent--; + taddnl(); + if (i[1]) { + taddstr("elif "); + } + } + if (*t) { + taddstr("else"); + tindent++; + taddnl(); + gt2(*t); + tindent--; + taddnl(); + } + break; + } + case N_WHILE: + taddstr((_whilecmd(n)->cond) ? "until " : "while "); + tindent++; + gt2(_whilecmd(n)->cont); + tindent--; + taddnl(); + taddstr("do"); + tindent++; + taddnl(); + gt2(_whilecmd(n)->loop); + tindent--; + taddnl(); + taddstr("done"); + break; + } +} + +/* Print a condition bracketed by [[ ... ]]. * + * With addpar non-zero, parenthesise the subexpression. */ + +/**/ +static void +getcond(Cond nm, int addpar) +{ + static char *c1[] = + { + "=", "!=", "<", ">", "-nt", "-ot", "-ef", "-eq", + "-ne", "-lt", "-gt", "-le", "-ge" + }; + + if (addpar) + taddstr("( "); + switch (nm->type) { + case COND_NOT: + taddstr("! "); + getcond(nm->left, _Cond(nm->left)->type <= COND_OR); + break; + case COND_AND: + getcond(nm->left, _Cond(nm->left)->type == COND_OR); + taddstr(" && "); + getcond(nm->right, _Cond(nm->right)->type == COND_OR); + break; + case COND_OR: + /* This is deliberately over-generous with parentheses: * + * in fact omitting them gives correct precedence. */ + getcond(nm->left, _Cond(nm->left)->type == COND_AND); + taddstr(" || "); + getcond(nm->right, _Cond(nm->right)->type == COND_AND); + break; + default: + if (nm->type <= COND_GE) { + /* Binary test: `a = b' etc. */ + taddstr(nm->left); + taddstr(" "); + taddstr(c1[nm->type - COND_STREQ]); + taddstr(" "); + taddstr(nm->right); + } else { + /* Unary test: `-f foo' etc. */ + char c2[4]; + + c2[0] = '-'; + c2[1] = nm->type; + c2[2] = ' '; + c2[3] = '\0'; + taddstr(c2); + taddstr(nm->left); + } + break; + } + if (addpar) + taddstr(" )"); +} + +/**/ +static void +getsimptext(Cmd cmd) +{ + LinkNode n; + + for (n = firstnode(cmd->vars); n; incnode(n)) { + struct varasg *v = (struct varasg *)getdata(n); + + taddstr(v->name); + taddchr('='); + if (PM_TYPE(v->type) == PM_ARRAY) { + taddchr('('); + taddlist(v->arr); + taddstr(") "); + } else { + taddstr(v->str); + taddchr(' '); + } + } + taddlist(cmd->args); +} + +/**/ +void +getredirs(Cmd cmd) +{ + LinkNode n; + static char *fstr[] = + { + ">", ">|", ">>", ">>|", "&>", "&>|", "&>>", "&>>|", "<>", "<", + "<<", "<<-", "<<<", "<&", ">&", NULL /* >&- */, "<", ">" + }; + + taddchr(' '); + for (n = firstnode(cmd->redir); n; incnode(n)) { + struct redir *f = (struct redir *)getdata(n); + + switch (f->type) { + case WRITE: + case WRITENOW: + case APP: + case APPNOW: + case ERRWRITE: + case ERRWRITENOW: + case ERRAPP: + case ERRAPPNOW: + case READ: + case READWRITE: + case HERESTR: + case MERGEIN: + case MERGEOUT: + case INPIPE: + case OUTPIPE: + if (f->fd1 != (IS_READFD(f->type) ? 0 : 1)) + taddchr('0' + f->fd1); + taddstr(fstr[f->type]); + taddchr(' '); + taddstr(f->name); + taddchr(' '); + break; +#ifdef DEBUG + case CLOSE: + DPUTS(1, "BUG: CLOSE in getredirs()"); + taddchr(f->fd1 + '0'); + taddstr(">&- "); + break; + default: + DPUTS(1, "BUG: unknown redirection in getredirs()"); +#endif + } + } + tptr--; +} + +/**/ +static void +taddlist(LinkList l) +{ + LinkNode n; + + if (!(n = firstnode(l))) + return; + for (; n; incnode(n)) { + taddstr(getdata(n)); + taddchr(' '); + } + tptr--; +} diff --git a/Src/utils.c b/Src/utils.c new file mode 100644 index 000000000..3619fa95d --- /dev/null +++ b/Src/utils.c @@ -0,0 +1,3726 @@ +/* + * utils.c - miscellaneous utilities + * + * 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 "utils.pro" + +/* Print an error */ + +/**/ +void +zwarnnam(const char *cmd, const char *fmt, const char *str, int num) +{ + int waserr; + + waserr = errflag; + zerrnam(cmd, fmt, str, num); + errflag = waserr; +} + +/* name of script being sourced */ + +/**/ +char *scriptname; + +/**/ +void +zerr(const char *fmt, const char *str, int num) +{ + if (errflag || noerrs) + return; + errflag = 1; + trashzle(); + /* + * scriptname is set when sourcing scripts, so that we get the + * correct name instead of the generic name of whatever + * program/script is running. + */ + nicezputs(isset(SHINSTDIN) ? "zsh" : + scriptname ? scriptname : argzero, stderr); + fputs(": ", stderr); + zerrnam(NULL, fmt, str, num); +} + +/**/ +void +zerrnam(const char *cmd, const char *fmt, const char *str, int num) +{ + if (cmd) { + if (errflag || noerrs) + return; + errflag = 1; + trashzle(); + if(unset(SHINSTDIN)) { + nicezputs(scriptname ? scriptname : argzero, stderr); + fputs(": ", stderr); + } + nicezputs(cmd, stderr); + fputs(": ", stderr); + } + while (*fmt) + if (*fmt == '%') { + fmt++; + switch (*fmt++) { + case 's': + nicezputs(str, stderr); + break; + case 'l': { + char *s; + num = metalen(str, num); + s = halloc(num + 1); + memcpy(s, str, num); + s[num] = '\0'; + nicezputs(s, stderr); + break; + } + case 'd': + fprintf(stderr, "%d", num); + break; + case '%': + putc('%', stderr); + break; + case 'c': + fputs(nicechar(num), stderr); + break; + case 'e': + /* print the corresponding message for this errno */ + if (num == EINTR) { + fputs("interrupt\n", stderr); + errflag = 1; + return; + } + /* If the message is not about I/O problems, it looks better * + * if we uncapitalize the first letter of the message */ + if (num == EIO) + fputs(strerror(num), stderr); + else { + char *errmsg = strerror(num); + fputc(tulower(errmsg[0]), stderr); + fputs(errmsg + 1, stderr); + } + break; + } + } else { + putc(*fmt == Meta ? *++fmt ^ 32 : *fmt, stderr); + fmt++; + } + if (unset(SHINSTDIN) && lineno) + fprintf(stderr, " [%ld]\n", lineno); + else + putc('\n', stderr); + fflush(stderr); +} + +/* Output a single character, for the termcap routines. * + * This is used instead of putchar since it can be a macro. */ + +/**/ +int +putraw(int c) +{ + putc(c, stdout); + return 0; +} + +/* Output a single character, for the termcap routines. */ + +/**/ +int +putshout(int c) +{ + putc(c, shout); + return 0; +} + +/* Turn a character into a visible representation thereof. The visible * + * string is put together in a static buffer, and this function returns * + * a pointer to it. Printable characters stand for themselves, DEL is * + * represented as "^?", newline and tab are represented as "\n" and * + * "\t", and normal control characters are represented in "^C" form. * + * Characters with bit 7 set, if unprintable, are represented as "\M-" * + * followed by the visible representation of the character with bit 7 * + * stripped off. Tokens are interpreted, rather than being treated as * + * literal characters. */ + +/**/ +char * +nicechar(int c) +{ + static char buf[6]; + char *s = buf; + c &= 0xff; + if (isprint(c)) + goto done; + if (c & 0x80) { + if (isset(PRINTEIGHTBIT)) + goto done; + *s++ = '\\'; + *s++ = 'M'; + *s++ = '-'; + c &= 0x7f; + if(isprint(c)) + goto done; + } + if (c == 0x7f) { + *s++ = '^'; + c = '?'; + } else if (c == '\n') { + *s++ = '\\'; + c = 'n'; + } else if (c == '\t') { + *s++ = '\\'; + c = 't'; + } else if (c < 0x20) { + *s++ = '^'; + c += 0x40; + } + done: + *s++ = c; + *s = 0; + return buf; +} + +#if 0 +/* Output a string's visible representation. */ + +/**/ +void +nicefputs(char *s, FILE *f) +{ + for (; *s; s++) + fputs(nicechar(STOUC(*s)), f); +} +#endif + +/* Return the length of the visible representation of a string. */ + +/**/ +size_t +nicestrlen(char *s) +{ + size_t l = 0; + + for (; *s; s++) + l += strlen(nicechar(STOUC(*s))); + return l; +} + +/* get a symlink-free pathname for s relative to PWD */ + +/**/ +char * +findpwd(char *s) +{ + char *t; + + if (*s == '/') + return xsymlink(s); + s = tricat((pwd[1]) ? pwd : "", "/", s); + t = xsymlink(s); + zsfree(s); + return t; +} + +/* Check whether a string contains the * + * name of the present directory. */ + +/**/ +int +ispwd(char *s) +{ + struct stat sbuf, tbuf; + + if (stat(unmeta(s), &sbuf) == 0 && stat(".", &tbuf) == 0) + if (sbuf.st_dev == tbuf.st_dev && sbuf.st_ino == tbuf.st_ino) + return 1; + return 0; +} + +static char xbuf[PATH_MAX*2]; + +/**/ +static char ** +slashsplit(char *s) +{ + char *t, **r, **q; + int t0; + + if (!*s) + return (char **) zcalloc(sizeof(char **)); + + for (t = s, t0 = 0; *t; t++) + if (*t == '/') + t0++; + q = r = (char **) zalloc(sizeof(char **) * (t0 + 2)); + + while ((t = strchr(s, '/'))) { + *q++ = ztrduppfx(s, t - s); + while (*t == '/') + t++; + if (!*t) { + *q = NULL; + return r; + } + s = t; + } + *q++ = ztrdup(s); + *q = NULL; + return r; +} + +/* expands symlinks and .. or . expressions */ +/* if flag = 0, only expand .. and . expressions */ + +/**/ +static int +xsymlinks(char *s, int flag) +{ + char **pp, **opp; + char xbuf2[PATH_MAX*2], xbuf3[PATH_MAX*2]; + int t0, ret = 0; + + opp = pp = slashsplit(s); + for (; *pp; pp++) { + if (!strcmp(*pp, ".")) { + zsfree(*pp); + continue; + } + if (!strcmp(*pp, "..")) { + char *p; + + zsfree(*pp); + if (!strcmp(xbuf, "/")) + continue; + p = xbuf + strlen(xbuf); + while (*--p != '/'); + *p = '\0'; + continue; + } + if (unset(CHASELINKS)) { + strcat(xbuf, "/"); + strcat(xbuf, *pp); + zsfree(*pp); + continue; + } + sprintf(xbuf2, "%s/%s", xbuf, *pp); + t0 = readlink(unmeta(xbuf2), xbuf3, PATH_MAX); + if (t0 == -1 || !flag) { + strcat(xbuf, "/"); + strcat(xbuf, *pp); + zsfree(*pp); + } else { + ret = 1; + metafy(xbuf3, t0, META_NOALLOC); + if (*xbuf3 == '/') { + strcpy(xbuf, ""); + xsymlinks(xbuf3 + 1, flag); + } else + xsymlinks(xbuf3, flag); + zsfree(*pp); + } + } + free(opp); + return ret; +} + +/* expand symlinks in s, and remove other weird things */ + +/**/ +char * +xsymlink(char *s) +{ + if (unset(CHASELINKS)) + return ztrdup(s); + if (*s != '/') + return NULL; + *xbuf = '\0'; + if (!xsymlinks(s + 1, 1)) + return ztrdup(s); + if (!*xbuf) + return ztrdup("/"); + return ztrdup(xbuf); +} + +/**/ +void +print_if_link(char *s) +{ + int chase; + + if (*s == '/') { + chase = opts[CHASELINKS]; + opts[CHASELINKS] = 1; + *xbuf = '\0'; + if (xsymlinks(s + 1, 1)) + printf(" -> "), zputs(*xbuf ? xbuf : "/", stdout); + opts[CHASELINKS] = chase; + } +} + +/* print a directory */ + +/**/ +void +fprintdir(char *s, FILE *f) +{ + Nameddir d = finddir(s); + + if (!d) + fputs(unmeta(s), f); + else { + putc('~', f); + fputs(unmeta(d->nam), f); + fputs(unmeta(s + strlen(d->dir)), f); + } +} + +/* Returns the current username. It caches the username * + * and uid to try to avoid requerying the password files * + * or NIS/NIS+ database. */ + +/**/ +uid_t cached_uid; +/**/ +char *cached_username; + +/**/ +char * +get_username(void) +{ +#ifdef HAVE_GETPWUID + struct passwd *pswd; + uid_t current_uid; + + current_uid = getuid(); + if (current_uid != cached_uid) { + cached_uid = current_uid; + zsfree(cached_username); + if ((pswd = getpwuid(current_uid))) + cached_username = ztrdup(pswd->pw_name); + else + cached_username = ztrdup(""); + } +#else /* !HAVE_GETPWUID */ + cached_uid = current_uid; +#endif /* !HAVE_GETPWUID */ + return cached_username; +} + +/* static variables needed by finddir(). */ + +static char *finddir_full; +static Nameddir finddir_last; +static int finddir_best; + +/* ScanFunc used by finddir(). */ + +/**/ +static void +finddir_scan(HashNode hn, int flags) +{ + Nameddir nd = (Nameddir) hn; + + if(nd->diff > finddir_best && !dircmp(nd->dir, finddir_full)) { + finddir_last=nd; + finddir_best=nd->diff; + } +} + +/* See if a path has a named directory as its prefix. * + * If passed a NULL argument, it will invalidate any * + * cached information. */ + +/**/ +Nameddir +finddir(char *s) +{ + static struct nameddir homenode = { NULL, "", 0, NULL, 0 }; + static int ffsz; + + /* Invalidate directory cache if argument is NULL. This is called * + * whenever a node is added to or removed from the hash table, and * + * whenever the value of $HOME changes. (On startup, too.) */ + if (!s) { + homenode.dir = home; + homenode.diff = strlen(home); + if(homenode.diff==1) + homenode.diff = 0; + if(!finddir_full) + finddir_full = zalloc(ffsz = PATH_MAX); + finddir_full[0] = 0; + return finddir_last = NULL; + } + + if(!strcmp(s, finddir_full) && *finddir_full) + return finddir_last; + + if(strlen(s) >= ffsz) { + free(finddir_full); + finddir_full = zalloc(ffsz = strlen(s) * 2); + } + strcpy(finddir_full, s); + finddir_best=0; + finddir_last=NULL; + finddir_scan((HashNode)&homenode, 0); + scanhashtable(nameddirtab, 0, 0, 0, finddir_scan, 0); + return finddir_last; +} + +/* add a named directory */ + +/**/ +void +adduserdir(char *s, char *t, int flags, int always) +{ + Nameddir nd; + + /* We don't maintain a hash table in non-interactive shells. */ + if (!interact) + return; + + /* The ND_USERNAME flag means that this possible hash table * + * entry is derived from a passwd entry. Such entries are * + * subordinate to explicitly generated entries. */ + if ((flags & ND_USERNAME) && nameddirtab->getnode2(nameddirtab, s)) + return; + + /* Normal parameter assignments generate calls to this function, * + * with always==0. Unless the AUTO_NAME_DIRS option is set, we * + * don't let such assignments actually create directory names. * + * Instead, a reference to the parameter as a directory name can * + * cause the actual creation of the hash table entry. */ + if (!always && unset(AUTONAMEDIRS) && + !nameddirtab->getnode2(nameddirtab, s)) + return; + + if (!t || *t != '/' || strlen(t) >= PATH_MAX) { + /* We can't use this value as a directory, so simply remove * + * the corresponding entry in the hash table, if any. */ + HashNode hn = nameddirtab->removenode(nameddirtab, s); + + if(hn) + nameddirtab->freenode(hn); + return; + } + + /* add the name */ + nd = (Nameddir) zcalloc(sizeof *nd); + nd->flags = flags; + nd->dir = ztrdup(t); + nameddirtab->addnode(nameddirtab, ztrdup(s), nd); +} + +/* Get a named directory: this function can cause a directory name * + * to be added to the hash table, if it isn't there already. */ + +/**/ +char * +getnameddir(char *name) +{ + Param pm; + char *str; + Nameddir nd; + + /* Check if it is already in the named directory table */ + if ((nd = (Nameddir) nameddirtab->getnode(nameddirtab, name))) + return dupstring(nd->dir); + + /* Check if there is a scalar parameter with this name whose value * + * begins with a `/'. If there is, add it to the hash table and * + * return the new value. */ + if ((pm = (Param) paramtab->getnode(paramtab, name)) && + (PM_TYPE(pm->flags) == PM_SCALAR) && + (str = getsparam(name)) && *str == '/') { + adduserdir(name, str, 0, 1); + return str; + } + +#ifdef HAVE_GETPWNAM + { + /* Retrieve an entry from the password table/database for this user. */ + struct passwd *pw; + if ((pw = getpwnam(name))) { + char *dir = xsymlink(pw->pw_dir); + adduserdir(name, dir, ND_USERNAME, 1); + str = dupstring(dir); + zsfree(dir); + return str; + } + } +#endif /* HAVE_GETPWNAM */ + + /* There are no more possible sources of directory names, so give up. */ + return NULL; +} + +/**/ +static int +dircmp(char *s, char *t) +{ + if (s) { + for (; *s == *t; s++, t++) + if (!*s) + return 0; + if (!*s && *t == '/') + return 0; + } + return 1; +} + +/* extra functions to call before displaying the prompt */ + +/**/ +LinkList prepromptfns; + +/* the last time we checked mail */ + +/**/ +time_t lastmailcheck; + +/* the last time we checked the people in the WATCH variable */ + +/**/ +time_t lastwatch; + +/* do pre-prompt stuff */ + +/**/ +void +preprompt(void) +{ + static time_t lastperiodic; + LinkNode ln; + List list; + int period = getiparam("PERIOD"); + int mailcheck = getiparam("MAILCHECK"); + + /* If NOTIFY is not set, then check for completed * + * jobs before we print the prompt. */ + if (unset(NOTIFY)) + scanjobs(); + if (errflag) + return; + + /* If a shell function named "precmd" exists, * + * then execute it. */ + if ((list = getshfunc("precmd")) != &dummy_list) + doshfunc(list, NULL, 0, 1); + if (errflag) + return; + + /* If 1) the parameter PERIOD exists, 2) the shell function * + * "periodic" exists, 3) it's been greater than PERIOD since we * + * executed "periodic", then execute it now. */ + if (period && (time(NULL) > lastperiodic + period) && + (list = getshfunc("periodic")) != &dummy_list) { + doshfunc(list, NULL, 0, 1); + lastperiodic = time(NULL); + } + if (errflag) + return; + + /* If WATCH is set, then check for the * + * specified login/logout events. */ + if (watch) { + if ((int) difftime(time(NULL), lastwatch) > getiparam("LOGCHECK")) { + dowatch(); + lastwatch = time(NULL); + } + } + if (errflag) + return; + + /* Check mail */ + if (mailcheck && (int) difftime(time(NULL), lastmailcheck) > mailcheck) { + char *mailfile; + + if (mailpath && *mailpath && **mailpath) + checkmailpath(mailpath); + else if ((mailfile = getsparam("MAIL")) && *mailfile) { + char *x[2]; + + x[0] = mailfile; + x[1] = NULL; + checkmailpath(x); + } + lastmailcheck = time(NULL); + } + + /* Some people have claimed that C performs type * + * checking, but they were later found to be lying. */ + for(ln = firstnode(prepromptfns); ln; ln = nextnode(ln)) + (**(void (**) _((void)))getdata(ln))(); +} + +/**/ +static void +checkmailpath(char **s) +{ + struct stat st; + char *v, *u, c; + + while (*s) { + for (v = *s; *v && *v != '?'; v++); + c = *v; + *v = '\0'; + if (c != '?') + u = NULL; + else + u = v + 1; + if (**s == 0) { + *v = c; + zerr("empty MAILPATH component: %s", *s, 0); + } else if (stat(unmeta(*s), &st) == -1) { + if (errno != ENOENT) + zerr("%e: %s", *s, errno); + } else if (S_ISDIR(st.st_mode)) { + LinkList l; + DIR *lock = opendir(unmeta(*s)); + char buf[PATH_MAX * 2], **arr, **ap; + int ct = 1; + + if (lock) { + char *fn; + HEAPALLOC { + pushheap(); + l = newlinklist(); + while ((fn = zreaddir(lock, 1)) && !errflag) { + if (u) + sprintf(buf, "%s/%s?%s", *s, fn, u); + else + sprintf(buf, "%s/%s", *s, fn); + addlinknode(l, dupstring(buf)); + ct++; + } + closedir(lock); + ap = arr = (char **) alloc(ct * sizeof(char *)); + + while ((*ap++ = (char *)ugetnode(l))); + checkmailpath(arr); + popheap(); + } LASTALLOC; + } + } else { + if (st.st_size && st.st_atime <= st.st_mtime && + st.st_mtime > lastmailcheck) + if (!u) { + fprintf(shout, "You have new mail.\n"); + fflush(shout); + } else { + char *usav = underscore; + + underscore = *s; + HEAPALLOC { + u = dupstring(u); + if (! parsestr(u)) { + singsub(&u); + zputs(u, shout); + fputc('\n', shout); + fflush(shout); + } + underscore = usav; + } LASTALLOC; + } + if (isset(MAILWARNING) && st.st_atime > st.st_mtime && + st.st_atime > lastmailcheck && st.st_size) { + fprintf(shout, "The mail in %s has been read.\n", unmeta(*s)); + fflush(shout); + } + } + *v = c; + s++; + } +} + +/**/ +void +freestr(void *a) +{ + zsfree(a); +} + +/**/ +void +gettyinfo(struct ttyinfo *ti) +{ + if (SHTTY != -1) { +#ifdef HAVE_TERMIOS_H +# ifdef HAVE_TCGETATTR + if (tcgetattr(SHTTY, &ti->tio) == -1) +# else + if (ioctl(SHTTY, TCGETS, &ti->tio) == -1) +# endif + zerr("bad tcgets: %e", NULL, errno); +#else +# ifdef HAVE_TERMIO_H + ioctl(SHTTY, TCGETA, &ti->tio); +# else + ioctl(SHTTY, TIOCGETP, &ti->sgttyb); + ioctl(SHTTY, TIOCLGET, &ti->lmodes); + ioctl(SHTTY, TIOCGETC, &ti->tchars); + ioctl(SHTTY, TIOCGLTC, &ti->ltchars); +# endif +#endif + } +} + +/**/ +void +settyinfo(struct ttyinfo *ti) +{ + if (SHTTY != -1) { +#ifdef HAVE_TERMIOS_H +# ifdef HAVE_TCGETATTR +# ifndef TCSADRAIN +# define TCSADRAIN 1 /* XXX Princeton's include files are screwed up */ +# endif + tcsetattr(SHTTY, TCSADRAIN, &ti->tio); + /* if (tcsetattr(SHTTY, TCSADRAIN, &ti->tio) == -1) */ +# else + ioctl(SHTTY, TCSETS, &ti->tio); + /* if (ioctl(SHTTY, TCSETS, &ti->tio) == -1) */ +# endif + /* zerr("settyinfo: %e",NULL,errno)*/ ; +#else +# ifdef HAVE_TERMIO_H + ioctl(SHTTY, TCSETA, &ti->tio); +# else + ioctl(SHTTY, TIOCSETN, &ti->sgttyb); + ioctl(SHTTY, TIOCLSET, &ti->lmodes); + ioctl(SHTTY, TIOCSETC, &ti->tchars); + ioctl(SHTTY, TIOCSLTC, &ti->ltchars); +# endif +#endif + } +} + +/* the default tty state */ + +/**/ +struct ttyinfo shttyinfo; + +/* != 0 if we need to call resetvideo() */ + +/**/ +int resetneeded; + +#ifdef TIOCGWINSZ +/* window size changed */ + +/**/ +int winchanged; +#endif + +/* check the size of the window and adjust if necessary */ + +/**/ +void +adjustwinsize(void) +{ +#ifdef TIOCGWINSZ + int oldcols = columns, oldrows = lines; + + if (SHTTY == -1) + return; + + ioctl(SHTTY, TIOCGWINSZ, (char *)&shttyinfo.winsize); + setiparam("COLUMNS", shttyinfo.winsize.ws_col); + setiparam("LINES", shttyinfo.winsize.ws_row); + if (zleactive && (oldcols != columns || oldrows != lines)) { + resetneeded = winchanged = 1; + refresh(); + } +#endif /* TIOCGWINSZ */ +} + +/* Move a fd to a place >= 10 and mark the new fd in fdtable. If the fd * + * is already >= 10, it is not moved. If it is invalid, -1 is returned. */ + +/**/ +int +movefd(int fd) +{ + if(fd != -1 && fd < 10) { +#ifdef F_DUPFD + int fe = fcntl(fd, F_DUPFD, 10); +#else + int fe = movefd(dup(fd)); +#endif + zclose(fd); + fd = fe; + } + if(fd != -1) { + if (fd > max_zsh_fd) { + while (fd >= fdtable_size) + fdtable = zrealloc(fdtable, (fdtable_size *= 2)); + max_zsh_fd = fd; + } + fdtable[fd] = 1; + } + return fd; +} + +/* Move fd x to y. If x == -1, fd y is closed. */ + +/**/ +void +redup(int x, int y) +{ + if(x < 0) + zclose(y); + else if (x != y) { + while (y >= fdtable_size) + fdtable = zrealloc(fdtable, (fdtable_size *= 2)); + dup2(x, y); + if ((fdtable[y] = fdtable[x]) && y > max_zsh_fd) + max_zsh_fd = y; + zclose(x); + } +} + +/* Close the given fd, and clear it from fdtable. */ + +/**/ +int +zclose(int fd) +{ + if (fd >= 0) { + fdtable[fd] = 0; + while (max_zsh_fd > 0 && !fdtable[max_zsh_fd]) + max_zsh_fd--; + if (fd == coprocin) + coprocin = -1; + if (fd == coprocout) + coprocout = -1; + } + return close(fd); +} + +/* Get a file name relative to $TMPPREFIX which * + * is unique, for use as a temporary file. */ + +/**/ +char * +gettempname(void) +{ + char *s; + + if (!(s = getsparam("TMPPREFIX"))) + s = DEFAULT_TMPPREFIX; + + return ((char *) mktemp(dyncat(unmeta(s), "XXXXXX"))); +} + +/* Check if a string contains a token */ + +/**/ +int +has_token(const char *s) +{ + while(*s) + if(itok(*s++)) + return 1; + return 0; +} + +/* Delete a character in a string */ + +/**/ +void +chuck(char *str) +{ + while ((str[0] = str[1])) + str++; +} + +/**/ +int +tulower(int c) +{ + c &= 0xff; + return (isupper(c) ? tolower(c) : c); +} + +/**/ +int +tuupper(int c) +{ + c &= 0xff; + return (islower(c) ? toupper(c) : c); +} + +/* copy len chars from t into s, and null terminate */ + +/**/ +void +ztrncpy(char *s, char *t, int len) +{ + while (len--) + *s++ = *t++; + *s = '\0'; +} + +/* copy t into *s and update s */ + +/**/ +void +strucpy(char **s, char *t) +{ + char *u = *s; + + while ((*u++ = *t++)); + *s = u - 1; +} + +/**/ +void +struncpy(char **s, char *t, int n) +{ + char *u = *s; + + while (n--) + *u++ = *t++; + *s = u; + *u = '\0'; +} + +/* Return the number of elements in an array of pointers. * + * It doesn't count the NULL pointer at the end. */ + +/**/ +int +arrlen(char **s) +{ + int count; + + for (count = 0; *s; s++, count++); + return count; +} + +/* Skip over a balanced pair of parenthesis. */ + +/**/ +int +skipparens(char inpar, char outpar, char **s) +{ + int level; + + if (**s != inpar) + return -1; + + for (level = 1; *++*s && level;) + if (**s == inpar) + ++level; + else if (**s == outpar) + --level; + + return level; +} + +/* Convert string to long. This function (without the z) * + * is contained in the ANSI standard C library, but a lot * + * of them seem to be broken. */ + +/**/ +long +zstrtol(const char *s, char **t, int base) +{ + long ret = 0; + int neg; + + while (inblank(*s)) + s++; + + if ((neg = (*s == '-'))) + s++; + else if (*s == '+') + s++; + + if (!base) + if (*s != '0') + base = 10; + else if (*++s == 'x' || *s == 'X') + base = 16, s++; + else + base = 8; + + if (base <= 10) + for (; *s >= '0' && *s < ('0' + base); s++) + ret = ret * base + *s - '0'; + else + for (; idigit(*s) || (*s >= 'a' && *s < ('a' + base - 10)) + || (*s >= 'A' && *s < ('A' + base - 10)); s++) + ret = ret * base + (idigit(*s) ? (*s - '0') : (*s & 0x1f) + 9); + if (t) + *t = (char *)s; + return neg ? -ret : ret; +} + +/**/ +int +setblock_stdin(void) +{ +#ifdef O_NDELAY +# ifdef O_NONBLOCK +# define NONBLOCK (O_NDELAY|O_NONBLOCK) +# else /* !O_NONBLOCK */ +# define NONBLOCK O_NDELAY +# endif /* !O_NONBLOCK */ +#else /* !O_NDELAY */ +# ifdef O_NONBLOCK +# define NONBLOCK O_NONBLOCK +# else /* !O_NONBLOCK */ +# define NONBLOCK 0 +# endif /* !O_NONBLOCK */ +#endif /* !O_NDELAY */ + +#if NONBLOCK + struct stat st; + long mode; + + if (!fstat(0, &st) && !S_ISREG(st.st_mode)) { + mode = fcntl(0, F_GETFL); + if (mode != -1 && (mode & NONBLOCK) && + !fcntl(0, F_SETFL, mode & ~NONBLOCK)) + return 1; + } +#endif /* NONBLOCK */ + return 0; + +#undef NONBLOCK +} + +/**/ +int +checkrmall(char *s) +{ + fprintf(shout, "zsh: sure you want to delete all the files in "); + if (*s != '/') { + nicezputs(pwd[1] ? unmeta(pwd) : "", shout); + fputc('/', shout); + } + nicezputs(s, shout); + if(isset(RMSTARWAIT)) { + fputs("? (waiting ten seconds)", shout); + fflush(shout); + beep(); + sleep(10); + fputc('\n', shout); + } + fputs(" [yn]? ", shout); + fflush(shout); + beep(); + return (getquery("ny", 1) == 'y'); +} + +/**/ +int +getquery(char *valid_chars, int purge) +{ + char c, d; + int isem = !strcmp(term, "emacs"); + +#ifdef FIONREAD + int val = 0; +#endif + + attachtty(mypgrp); + if (!isem) + setcbreak(); + +#ifdef FIONREAD + ioctl(SHTTY, FIONREAD, (char *)&val); + if(purge) { + while(val--) + read(SHTTY, &c, 1); + } else if (val) { + if (!isem) + settyinfo(&shttyinfo); + write(SHTTY, "n\n", 2); + return 'n'; + } +#endif + while (read(SHTTY, &c, 1) == 1) { + if (c == 'Y' || c == '\t') + c = 'y'; + else if (c == 'N') + c = 'n'; + if (!valid_chars) + break; + if (c == '\n') { + c = *valid_chars; + break; + } + if (strchr(valid_chars, c)) { + write(SHTTY, "\n", 1); + break; + } + beep(); + if (icntrl(c)) + write(SHTTY, "\b \b", 3); + write(SHTTY, "\b \b", 3); + } + if (isem) { + if (c != '\n') + while (read(SHTTY, &d, 1) == 1 && d != '\n'); + } else { + settyinfo(&shttyinfo); + if (c != '\n' && !valid_chars) + write(SHTTY, "\n", 1); + } + return (int)c; +} + +static int d; +static char *guess, *best; + +/**/ +static void +spscan(HashNode hn, int scanflags) +{ + int nd; + + nd = spdist(hn->nam, guess, (int) strlen(guess) / 4 + 1); + if (nd <= d) { + best = hn->nam; + d = nd; + } +} + +/* spellcheck a word */ +/* fix s ; if hist is nonzero, fix the history list too */ + +/**/ +void +spckword(char **s, int hist, int cmd, int ask) +{ + char *t, *u; + int x; + char ic = '\0'; + int ne; + int preflen = 0; + + if ((histdone & HISTFLAG_NOEXEC) || **s == '-' || **s == '%') + return; + if (!strcmp(*s, "in")) + return; + if (!(*s)[0] || !(*s)[1]) + return; + if (shfunctab->getnode(shfunctab, *s) || + builtintab->getnode(builtintab, *s) || + cmdnamtab->getnode(cmdnamtab, *s) || + aliastab->getnode(aliastab, *s) || + reswdtab->getnode(reswdtab, *s)) + return; + else if (isset(HASHLISTALL)) { + cmdnamtab->filltable(cmdnamtab); + if (cmdnamtab->getnode(cmdnamtab, *s)) + return; + } + t = *s; + if (*t == Tilde || *t == Equals || *t == String) + t++; + for (; *t; t++) + if (itok(*t)) + return; + best = NULL; + for (t = *s; *t; t++) + if (*t == '/') + break; + if (**s == Tilde && !*t) + return; + if (**s == String && !*t) { + guess = *s + 1; + if (*t || !ialpha(*guess)) + return; + ic = String; + d = 100; + scanhashtable(paramtab, 1, 0, 0, spscan, 0); + } else if (**s == Equals) { + if (*t) + return; + if (hashcmd(guess = *s + 1, pathchecked)) + return; + d = 100; + ic = Equals; + scanhashtable(aliastab, 1, 0, 0, spscan, 0); + scanhashtable(cmdnamtab, 1, 0, 0, spscan, 0); + } else { + guess = *s; + if (*guess == Tilde || *guess == String) { + ic = *guess; + if (!*++t) + return; + guess = dupstring(guess); + ne = noerrs; + noerrs = 1; + singsub(&guess); + noerrs = ne; + if (!guess) + return; + preflen = strlen(guess) - strlen(t); + } + if (access(unmeta(guess), F_OK) == 0) + return; + if ((u = spname(guess)) != guess) + best = u; + if (!*t && cmd) { + if (hashcmd(guess, pathchecked)) + return; + d = 100; + scanhashtable(reswdtab, 1, 0, 0, spscan, 0); + scanhashtable(aliastab, 1, 0, 0, spscan, 0); + scanhashtable(shfunctab, 1, 0, 0, spscan, 0); + scanhashtable(builtintab, 1, 0, 0, spscan, 0); + scanhashtable(cmdnamtab, 1, 0, 0, spscan, 0); + } + } + if (errflag) + return; + if (best && (int)strlen(best) > 1 && strcmp(best, guess)) { + if (ic) { + if (preflen) { + /* do not correct the result of an expansion */ + if (strncmp(guess, best, preflen)) + return; + /* replace the temporarily expanded prefix with the original */ + u = (char *) ncalloc(t - *s + strlen(best + preflen) + 1); + strncpy(u, *s, t - *s); + strcpy(u + (t - *s), best + preflen); + } else { + u = (char *) ncalloc(strlen(best) + 2); + strcpy(u + 1, best); + } + best = u; + guess = *s; + *guess = *best = ztokens[ic - Pound]; + } + if (ask) { + char *pptbuf; + pptbuf = promptexpand(sprompt, 0, best, guess); + zputs(pptbuf, shout); + free(pptbuf); + fflush(shout); + beep(); + x = getquery("nyae ", 0); + } else + x = 'y'; + if (x == 'y' || x == ' ') { + *s = dupstring(best); + if (hist) + hwrep(best); + } else if (x == 'a') { + histdone |= HISTFLAG_NOEXEC; + } else if (x == 'e') { + histdone |= HISTFLAG_NOEXEC | HISTFLAG_RECALL; + } + if (ic) + **s = ic; + } +} + +/**/ +int +ztrftime(char *buf, int bufsize, char *fmt, struct tm *tm) +{ + int hr12; +#ifndef HAVE_STRFTIME + static char *astr[] = + {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + static char *estr[] = + {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", + "Aug", "Sep", "Oct", "Nov", "Dec"}; +#else + char *origbuf = buf; +#endif + char tmp[3]; + + + tmp[0] = '%'; + tmp[2] = '\0'; + while (*fmt) + if (*fmt == '%') { + fmt++; + switch (*fmt++) { + case 'd': + *buf++ = '0' + tm->tm_mday / 10; + *buf++ = '0' + tm->tm_mday % 10; + break; + case 'e': + case 'f': + if (tm->tm_mday > 9) + *buf++ = '0' + tm->tm_mday / 10; + else if (fmt[-1] == 'e') + *buf++ = ' '; + *buf++ = '0' + tm->tm_mday % 10; + break; + case 'k': + case 'K': + if (tm->tm_hour > 9) + *buf++ = '0' + tm->tm_hour / 10; + else if (fmt[-1] == 'k') + *buf++ = ' '; + *buf++ = '0' + tm->tm_hour % 10; + break; + case 'l': + case 'L': + hr12 = tm->tm_hour % 12; + if (hr12 == 0) + hr12 = 12; + if (hr12 > 9) + *buf++ = '1'; + else if (fmt[-1] == 'l') + *buf++ = ' '; + *buf++ = '0' + (hr12 % 10); + break; + case 'm': + *buf++ = '0' + (tm->tm_mon + 1) / 10; + *buf++ = '0' + (tm->tm_mon + 1) % 10; + break; + case 'M': + *buf++ = '0' + tm->tm_min / 10; + *buf++ = '0' + tm->tm_min % 10; + break; + case 'S': + *buf++ = '0' + tm->tm_sec / 10; + *buf++ = '0' + tm->tm_sec % 10; + break; + case 'y': + *buf++ = '0' + (tm->tm_year / 10) % 10; + *buf++ = '0' + tm->tm_year % 10; + break; +#ifndef HAVE_STRFTIME + case 'a': + strucpy(&buf, astr[tm->tm_wday]); + break; + case 'b': + strucpy(&buf, estr[tm->tm_mon]); + break; + case 'p': + *buf++ = (tm->tm_hour > 11) ? 'p' : 'a'; + *buf++ = 'm'; + break; + default: + *buf++ = '%'; + if (fmt[-1] != '%') + *buf++ = fmt[-1]; +#else + default: + *buf = '\0'; + tmp[1] = fmt[-1]; + strftime(buf, bufsize - strlen(origbuf), tmp, tm); + buf += strlen(buf); +#endif + break; + } + } else + *buf++ = *fmt++; + *buf = '\0'; + return 0; +} + +/**/ +char * +zjoin(char **arr, int delim) +{ + int len = 0; + char **s, *ret, *ptr; + + for (s = arr; *s; s++) + len += strlen(*s) + 1; + if (!len) + return ""; + ptr = ret = (char *) ncalloc(len); + for (s = arr; *s; s++) { + strucpy(&ptr, *s); + if (delim) + *ptr++ = delim; + } + ptr[-1] = '\0'; + return ret; +} + +/* Split a string containing a colon separated list * + * of items into an array of strings. */ + +/**/ +char ** +colonsplit(char *s, int uniq) +{ + int ct; + char *t, **ret, **ptr, **p; + + for (t = s, ct = 0; *t; t++) /* count number of colons */ + if (*t == ':') + ct++; + ptr = ret = (char **) zalloc(sizeof(char **) * (ct + 2)); + + t = s; + do { + s = t; + /* move t to point at next colon */ + for (; *t && *t != ':'; t++); + if (uniq) + for (p = ret; p < ptr; p++) + if (strlen(*p) == t - s && ! strncmp(*p, s, t - s)) + goto cont; + *ptr = (char *) zalloc((t - s) + 1); + ztrncpy(*ptr++, s, t - s); + cont: ; + } + while (*t++); + *ptr = NULL; + return ret; +} + +/**/ +static int +skipwsep(char **s) +{ + char *t = *s; + int i = 0; + + while (*t && iwsep(*t == Meta ? t[1] ^ 32 : *t)) { + if (*t == Meta) + t++; + t++; + i++; + } + *s = t; + return i; +} + +/**/ +char ** +spacesplit(char *s, int allownull) +{ + char *t, **ret, **ptr; + + ptr = ret = (char **) ncalloc(sizeof(*ret) * (wordcount(s, NULL, -!allownull) + 1)); + + t = s; + skipwsep(&s); + if (*s && isep(*s == Meta ? s[1] ^ 32 : *s)) + *ptr++ = dupstring(allownull ? "" : nulstring); + else if (!allownull && t != s) + *ptr++ = dupstring(""); + while (*s) { + if (isep(*s == Meta ? s[1] ^ 32 : *s)) { + if (*s == Meta) + s++; + s++; + skipwsep(&s); + } + t = s; + findsep(&s, NULL); + if (s > t || allownull) { + *ptr = (char *) ncalloc((s - t) + 1); + ztrncpy(*ptr++, t, s - t); + } else + *ptr++ = dupstring(nulstring); + t = s; + skipwsep(&s); + } + if (!allownull && t != s) + *ptr++ = dupstring(""); + *ptr = NULL; + return ret; +} + +/**/ +static int +findsep(char **s, char *sep) +{ + int i; + char *t, *tt; + + if (!sep) { + for (t = *s; *t; t++) { + if (*t == Meta) { + if (isep(t[1] ^ 32)) + break; + t++; + } else if (isep(*t)) + break; + } + i = t - *s; + *s = t; + return i; + } + if (!sep[0]) { + return **s ? ++*s, 1 : -1; + } + for (i = 0; **s; i++) { + for (t = sep, tt = *s; *t && *tt && *t == *tt; t++, tt++); + if (!*t) + return i; + if (*(*s)++ == Meta) { + (*s)++; +#ifdef DEBUG + if (! **s) + fprintf(stderr, "BUG: unexpected end of string in findsep()\n"); +#endif + } + } + return -1; +} + +/**/ +char * +findword(char **s, char *sep) +{ + char *r, *t; + int sl; + + if (!**s) + return NULL; + + if (sep) { + sl = strlen(sep); + r = *s; + while (! findsep(s, sep)) { + r = *s += sl; + } + return r; + } + for (t = *s; *t; t++) { + if (*t == Meta) { + if (! isep(t[1] ^ 32)) + break; + t++; + } else if (! isep(*t)) + break; + } + *s = t; + findsep(s, sep); + return t; +} + +/**/ +int +wordcount(char *s, char *sep, int mul) +{ + int r, sl, c; + + if (sep) { + r = 1; + sl = strlen(sep); + for (; (c = findsep(&s, sep)) >= 0; s += sl) + if ((c && *(s + sl)) || mul) + r++; + } else { + char *t = s; + + r = 0; + if (mul <= 0) + skipwsep(&s); + if ((*s && isep(*s == Meta ? s[1] ^ 32 : *s)) || + (mul < 0 && t != s)) + r++; + for (; *s; r++) { + if (isep(*s == Meta ? s[1] ^ 32 : *s)) { + if (*s == Meta) + s++; + s++; + if (mul <= 0) + skipwsep(&s); + } + findsep(&s, NULL); + t = s; + if (mul <= 0) + skipwsep(&s); + } + if (mul < 0 && t != s) + r++; + } + return r; +} + +/**/ +char * +sepjoin(char **s, char *sep) +{ + char *r, *p, **t; + int l, sl, elide = 0; + char sepbuf[3]; + + if (!*s) + return ""; + if (!sep) { + elide = 1; + sep = sepbuf; + sepbuf[0] = *ifs; + sepbuf[1] = *ifs == Meta ? ifs[1] ^ 32 : '\0'; + sepbuf[2] = '\0'; + } + sl = strlen(sep); + for (t = s, l = 1 - sl; *t; l += strlen(*t) + sl, t++); + r = p = (char *) ncalloc(l); + t = s; + while (*t) { + strucpy(&p, *t); + if (*++t) + strucpy(&p, sep); + } + *p = '\0'; + return r; +} + +/**/ +char ** +sepsplit(char *s, char *sep, int allownull) +{ + int n, sl; + char *t, *tt, **r, **p; + + if (!sep) + return spacesplit(s, allownull); + + sl = strlen(sep); + n = wordcount(s, sep, 1); + r = p = (char **) ncalloc((n + 1) * sizeof(char *)); + + for (t = s; n--;) { + tt = t; + findsep(&t, sep); + *p = (char *) ncalloc(t - tt + 1); + strncpy(*p, tt, t - tt); + (*p)[t - tt] = '\0'; + p++; + t += sl; + } + *p = NULL; + + return r; +} + +/* Get the definition of a shell function */ + +/**/ +List +getshfunc(char *nam) +{ + Shfunc shf; + + if (!(shf = (Shfunc) shfunctab->getnode(shfunctab, nam))) + return &dummy_list; + + return shf->funcdef; +} + +/* allocate a tree element */ + +static int sizetab[N_COUNT] = { + sizeof(struct list), + sizeof(struct sublist), + sizeof(struct pline), + sizeof(struct cmd), + sizeof(struct redir), + sizeof(struct cond), + sizeof(struct forcmd), + sizeof(struct casecmd), + sizeof(struct ifcmd), + sizeof(struct whilecmd), + sizeof(struct varasg), + sizeof(struct autofn), +}; + +static int offstab[N_COUNT] = { + offsetof(struct list, left), + offsetof(struct sublist, left), + offsetof(struct pline, left), + offsetof(struct cmd, u), + offsetof(struct redir, name), + offsetof(struct cond, left), + offsetof(struct forcmd, name), + offsetof(struct casecmd, pats), + offsetof(struct ifcmd, ifls), + offsetof(struct whilecmd, cont), + offsetof(struct varasg, name), + sizeof(struct autofn), +}; + +static int flagtab[N_COUNT] = { + NT_SET(N_LIST, NT_NODE, NT_NODE, 0, 0), + NT_SET(N_SUBLIST, NT_NODE, NT_NODE, 0, 0), + NT_SET(N_PLINE, NT_NODE, NT_NODE, 0, 0), + NT_SET(N_CMD, NT_NODE, NT_STR | NT_LIST, NT_NODE | NT_LIST, NT_NODE | NT_LIST), + NT_SET(N_REDIR, NT_STR, 0, 0, 0), + NT_SET(N_COND, NT_NODE, NT_NODE, 0, 0), + NT_SET(N_FOR, NT_STR, NT_STR, NT_STR, NT_NODE), + NT_SET(N_CASE, NT_STR | NT_ARR, NT_NODE | NT_ARR, 0, 0), + NT_SET(N_IF, NT_NODE | NT_ARR, NT_NODE | NT_ARR, 0, 0), + NT_SET(N_WHILE, NT_NODE, NT_NODE, 0, 0), + NT_SET(N_VARASG, NT_STR, NT_STR, NT_STR | NT_LIST, 0), + NT_SET(N_AUTOFN, 0, 0, 0, 0), +}; + +/**/ +void * +allocnode(int type) +{ + struct node *n; + + n = (struct node *) alloc(sizetab[type]); + memset((void *) n, 0, sizetab[type]); + n->ntype = flagtab[type]; + if (useheap) + n->ntype |= NT_HEAP; + + return (void *) n; +} + +/**/ +void * +dupstruct(void *a) +{ + struct node *n, *r; + + n = (struct node *) a; + if (!a || ((List) a) == &dummy_list) + return (void *) a; + + if ((n->ntype & NT_HEAP) && !useheap) { + HEAPALLOC { + n = (struct node *) dupstruct2((void *) n); + } LASTALLOC; + n = simplifystruct(n); + } + r = (struct node *)dupstruct2((void *) n); + + if (!(n->ntype & NT_HEAP) && useheap) + r = expandstruct(r, N_LIST); + + return (void *) r; +} + +/**/ +static struct node * +simplifystruct(struct node *n) +{ + if (!n || ((List) n) == &dummy_list) + return n; + + switch (NT_TYPE(n->ntype)) { + case N_LIST: + { + List l = (List) n; + + l->left = (Sublist) simplifystruct((struct node *)l->left); + if ((l->type & Z_SYNC) && !l->right) + return (struct node *)l->left; + } + break; + case N_SUBLIST: + { + Sublist sl = (Sublist) n; + + sl->left = (Pline) simplifystruct((struct node *)sl->left); + if (sl->type == END && !sl->flags && !sl->right) + return (struct node *)sl->left; + } + break; + case N_PLINE: + { + Pline pl = (Pline) n; + + pl->left = (Cmd) simplifystruct((struct node *)pl->left); + if (pl->type == END && !pl->right) + return (struct node *)pl->left; + } + break; + case N_CMD: + { + Cmd c = (Cmd) n; + int i = 0; + + if (empty(c->args)) + c->args = NULL, i++; + if (empty(c->redir)) + c->redir = NULL, i++; + if (empty(c->vars)) + c->vars = NULL, i++; + + c->u.list = (List) simplifystruct((struct node *)c->u.list); + if (i == 3 && !c->flags && + (c->type == CWHILE || c->type == CIF || + c->type == COND)) + return (struct node *)c->u.list; + } + break; + case N_FOR: + { + Forcmd f = (Forcmd) n; + + f->list = (List) simplifystruct((struct node *)f->list); + } + break; + case N_CASE: + { + struct casecmd *c = (struct casecmd *)n; + List *l; + + for (l = c->lists; *l; l++) + *l = (List) simplifystruct((struct node *)*l); + } + break; + case N_IF: + { + struct ifcmd *i = (struct ifcmd *)n; + List *l; + + for (l = i->ifls; *l; l++) + *l = (List) simplifystruct((struct node *)*l); + for (l = i->thenls; *l; l++) + *l = (List) simplifystruct((struct node *)*l); + } + break; + case N_WHILE: + { + struct whilecmd *w = (struct whilecmd *)n; + + w->cont = (List) simplifystruct((struct node *)w->cont); + w->loop = (List) simplifystruct((struct node *)w->loop); + } + } + + return n; +} + +/**/ +struct node * +expandstruct(struct node *n, int exp) +{ + struct node *m; + + if (!n || ((List) n) == &dummy_list) + return n; + + if (exp != N_COUNT && exp != NT_TYPE(n->ntype)) { + switch (exp) { + case N_LIST: + { + List l; + + m = (struct node *) allocnode(N_LIST); + l = (List) m; + l->type = Z_SYNC; + l->left = (Sublist) expandstruct(n, N_SUBLIST); + + return (struct node *)l; + } + case N_SUBLIST: + { + Sublist sl; + + m = (struct node *) allocnode(N_SUBLIST); + sl = (Sublist) m; + sl->type = END; + sl->left = (Pline) expandstruct(n, N_PLINE); + + return (struct node *)sl; + } + case N_PLINE: + { + Pline pl; + + m = (struct node *) allocnode(N_PLINE); + pl = (Pline) m; + pl->type = END; + pl->left = (Cmd) expandstruct(n, N_CMD); + + return (struct node *)pl; + } + case N_CMD: + { + Cmd c; + + m = (struct node *) allocnode(N_CMD); + c = (Cmd) m; + switch (NT_TYPE(n->ntype)) { + case N_WHILE: + c->type = CWHILE; + break; + case N_IF: + c->type = CIF; + break; + case N_COND: + c->type = COND; + } + c->u.list = (List) expandstruct(n, NT_TYPE(n->ntype)); + c->args = newlinklist(); + c->vars = newlinklist(); + c->redir = newlinklist(); + + return (struct node *)c; + } + } + } else + switch (NT_TYPE(n->ntype)) { + case N_LIST: + { + List l = (List) n; + + l->left = (Sublist) expandstruct((struct node *)l->left, + N_SUBLIST); + l->right = (List) expandstruct((struct node *)l->right, + N_LIST); + } + break; + case N_SUBLIST: + { + Sublist sl = (Sublist) n; + + sl->left = (Pline) expandstruct((struct node *)sl->left, + N_PLINE); + sl->right = (Sublist) expandstruct((struct node *)sl->right, + N_SUBLIST); + } + break; + case N_PLINE: + { + Pline pl = (Pline) n; + + pl->left = (Cmd) expandstruct((struct node *)pl->left, + N_CMD); + pl->right = (Pline) expandstruct((struct node *)pl->right, + N_PLINE); + } + break; + case N_CMD: + { + Cmd c = (Cmd) n; + + if (!c->args) + c->args = newlinklist(); + if (!c->vars) + c->vars = newlinklist(); + if (!c->redir) + c->redir = newlinklist(); + + switch (c->type) { + case CFOR: + case CSELECT: + c->u.list = (List) expandstruct((struct node *)c->u.list, + N_FOR); + break; + case CWHILE: + c->u.list = (List) expandstruct((struct node *)c->u.list, + N_WHILE); + break; + case CIF: + c->u.list = (List) expandstruct((struct node *)c->u.list, + N_IF); + break; + case CCASE: + c->u.list = (List) expandstruct((struct node *)c->u.list, + N_CASE); + break; + case COND: + c->u.list = (List) expandstruct((struct node *)c->u.list, + N_COND); + break; + case ZCTIME: + c->u.list = (List) expandstruct((struct node *)c->u.list, + N_SUBLIST); + break; + case AUTOFN: + c->u.list = (List) expandstruct((struct node *)c->u.list, + N_AUTOFN); + break; + default: + c->u.list = (List) expandstruct((struct node *)c->u.list, + N_LIST); + } + } + break; + case N_FOR: + { + Forcmd f = (Forcmd) n; + + f->list = (List) expandstruct((struct node *)f->list, + N_LIST); + } + break; + case N_CASE: + { + struct casecmd *c = (struct casecmd *)n; + List *l; + + for (l = c->lists; *l; l++) + *l = (List) expandstruct((struct node *)*l, N_LIST); + } + break; + case N_IF: + { + struct ifcmd *i = (struct ifcmd *)n; + List *l; + + for (l = i->ifls; *l; l++) + *l = (List) expandstruct((struct node *)*l, N_LIST); + for (l = i->thenls; *l; l++) + *l = (List) expandstruct((struct node *)*l, N_LIST); + } + break; + case N_WHILE: + { + struct whilecmd *w = (struct whilecmd *)n; + + w->cont = (List) expandstruct((struct node *)w->cont, + N_LIST); + w->loop = (List) expandstruct((struct node *)w->loop, + N_LIST); + } + } + + return n; +} + +/* duplicate a syntax tree */ + +/**/ +static void * +dupstruct2(void *a) +{ + void **onodes, **nnodes, *ret, *n, *on; + int type, heap; + size_t nodeoffs; + + if (!a || ((List) a) == &dummy_list) + return a; + type = *(int *)a; + ret = alloc(sizetab[NT_TYPE(type)]); + memcpy(ret, a, nodeoffs = offstab[NT_TYPE(type)]); + *(int*)ret = (type & ~NT_HEAP) | (useheap ? NT_HEAP : 0); + onodes = (void **) ((char *)a + nodeoffs); + nnodes = (void **) ((char *)ret + nodeoffs); + heap = type & NT_HEAP; + for (type = (type & 0xffff00) >> 4; (type >>= 4); *nnodes++ = n) { + if (!(on = *onodes++)) + n = NULL; + else { + switch (type & 0xf) { + case NT_NODE: + n = dupstruct2(on); + break; + case NT_STR: + n = dupstring(on); + break; + case NT_LIST | NT_NODE: + if (heap) + if (useheap) + n = duplist(on, (VFunc) dupstruct2); + else + n = list2arr(on, (VFunc) dupstruct2); + else if (useheap) + n = arr2list(on, (VFunc) dupstruct2); + else + n = duparray(on, (VFunc) dupstruct2); + break; + case NT_LIST | NT_STR: + if (heap) + if (useheap) + n = duplist(on, (VFunc) dupstring); + else + n = list2arr(on, (VFunc) ztrdup); + else if (useheap) + n = arr2list(on, (VFunc) dupstring); + else + n = duparray(on, (VFunc) ztrdup); + break; + case NT_NODE | NT_ARR: + n = duparray(on, (VFunc) dupstruct2); + break; + case NT_STR | NT_ARR: + n = duparray(on, (VFunc) (useheap ? dupstring : ztrdup)); + break; + default: + DPUTS(1, "BUG: bad node type in dupstruct2()"); + abort(); + } + } + } + return ret; +} + +/* free a syntax tree */ + +/**/ +void +freestruct(void *a) +{ + void **nodes, *n; + int type, size; + + if (!a || ((List) a) == &dummy_list) + return; + + type = * (int *) a; + nodes = (void **) ((char *)a + offstab[NT_TYPE(type)]); + size = sizetab[NT_TYPE(type)]; + for (type = (type & 0xffff00) >> 4; (type >>= 4);) { + if ((n = *nodes++)) { + switch (type & 0xf) { + case NT_NODE: + freestruct(n); + break; + case NT_STR: + zsfree((char *) n); + break; + case NT_LIST | NT_NODE: + case NT_NODE | NT_ARR: + { + void **p = (void **) n; + + while (*p) + freestruct(*p++); + zfree(n, sizeof(void *) * (p + 1 - (void **) n)); + break; + } + case NT_LIST | NT_STR: + case NT_STR | NT_ARR: + freearray((char **) n); + break; + default: + DPUTS(1, "BUG: bad node type in freenode()"); + abort(); + } + } + } + DPUTS(size != ((char *) nodes) - ((char *) a), + "BUG: size wrong in freenode()"); + zfree(a, size); +} + +/**/ +static LinkList +duplist(LinkList l, VFunc func) +{ + LinkList ret; + LinkNode node; + + ret = newlinklist(); + for (node = firstnode(l); node; incnode(node)) + addlinknode(ret, func(getdata(node))); + return ret; +} + +/**/ +static char ** +duparray(char **arr, VFunc func) +{ + char **ret, **rr; + + ret = (char **) alloc((arrlen(arr) + 1) * sizeof(char *)); + for (rr = ret; *arr;) + *rr++ = (char *)func(*arr++); + *rr = NULL; + + return ret; +} + +/**/ +static char ** +list2arr(LinkList l, VFunc func) +{ + char **arr, **r; + LinkNode n; + + arr = r = (char **) alloc((countlinknodes(l) + 1) * sizeof(char *)); + + for (n = firstnode(l); n; incnode(n)) + *r++ = (char *)func(getdata(n)); + *r = NULL; + + return arr; +} + +/**/ +static LinkList +arr2list(char **arr, VFunc func) +{ + LinkList l = newlinklist(); + + while (*arr) + addlinknode(l, func(*arr++)); + + return l; +} + +/**/ +char ** +mkarray(char *s) +{ + char **t = (char **) zalloc((s) ? (2 * sizeof s) : (sizeof s)); + + if ((*t = s)) + t[1] = NULL; + return t; +} + +/**/ +void +beep(void) +{ + if (isset(BEEP)) + write(SHTTY, "\07", 1); +} + +/**/ +void +freearray(char **s) +{ + char **t = s; + + while (*s) + zsfree(*s++); + free(t); +} + +/**/ +int +equalsplit(char *s, char **t) +{ + for (; *s && *s != '='; s++); + if (*s == '=') { + *s++ = '\0'; + *t = s; + return 1; + } + return 0; +} + +/* see if the right side of a list is trivial */ + +/**/ +void +simplifyright(List l) +{ + Cmd c; + + if (l == &dummy_list || !l->right) + return; + if (l->right->right || l->right->left->right || + l->right->left->flags || l->right->left->left->right || + l->left->flags) + return; + c = l->left->left->left; + if (c->type != SIMPLE || nonempty(c->args) || nonempty(c->redir) + || nonempty(c->vars)) + return; + l->right = NULL; + return; +} + +/* the ztypes table */ + +/**/ +short int typtab[256]; + +/* initialize the ztypes table */ + +/**/ +void +inittyptab(void) +{ + int t0; + char *s; + + for (t0 = 0; t0 != 256; t0++) + typtab[t0] = 0; + for (t0 = 0; t0 != 32; t0++) + typtab[t0] = typtab[t0 + 128] = ICNTRL; + typtab[127] = ICNTRL; + for (t0 = '0'; t0 <= '9'; t0++) + typtab[t0] = IDIGIT | IALNUM | IWORD | IIDENT | IUSER; + for (t0 = 'a'; t0 <= 'z'; t0++) + typtab[t0] = typtab[t0 - 'a' + 'A'] = IALPHA | IALNUM | IIDENT | IUSER | IWORD; + for (t0 = 0240; t0 != 0400; t0++) + typtab[t0] = IALPHA | IALNUM | IIDENT | IUSER | IWORD; + typtab['_'] = IIDENT | IUSER; + typtab['-'] = IUSER; + typtab[' '] |= IBLANK | INBLANK; + typtab['\t'] |= IBLANK | INBLANK; + typtab['\n'] |= INBLANK; + typtab['\0'] |= IMETA; + typtab[STOUC(Meta) ] |= IMETA; + typtab[STOUC(Marker)] |= IMETA; + for (t0 = (int)STOUC(Pound); t0 <= (int)STOUC(Nularg); t0++) + typtab[t0] |= ITOK | IMETA; + for (s = ifs ? ifs : DEFAULT_IFS; *s; s++) { + if (inblank(*s)) + if (s[1] == *s) + s++; + else + typtab[STOUC(*s)] |= IWSEP; + typtab[STOUC(*s == Meta ? *++s ^ 32 : *s)] |= ISEP; + } + for (s = wordchars ? wordchars : DEFAULT_WORDCHARS; *s; s++) + typtab[STOUC(*s == Meta ? *++s ^ 32 : *s)] |= IWORD; + for (s = SPECCHARS; *s; s++) + typtab[STOUC(*s)] |= ISPECIAL; + if (isset(BANGHIST) && bangchar && interact && isset(SHINSTDIN)) + typtab[bangchar] |= ISPECIAL; +} + +/**/ +char ** +arrdup(char **s) +{ + char **x, **y; + + y = x = (char **) ncalloc(sizeof(char *) * (arrlen(s) + 1)); + + while ((*x++ = dupstring(*s++))); + return y; +} + +/**/ +static char * +spname(char *oldname) +{ + char *p, spnameguess[PATH_MAX + 1], spnamebest[PATH_MAX + 1]; + static char newname[PATH_MAX + 1]; + char *new = newname, *old; + int bestdist = 200, thisdist; + + old = oldname; + for (;;) { + while (*old == '/') + *new++ = *old++; + *new = '\0'; + if (*old == '\0') + return newname; + p = spnameguess; + for (; *old != '/' && *old != '\0'; old++) + if (p < spnameguess + PATH_MAX) + *p++ = *old; + *p = '\0'; + if ((thisdist = mindist(newname, spnameguess, spnamebest)) >= 3) { + if (bestdist < 3) { + strcpy(new, spnameguess); + strcat(new, old); + return newname; + } else + return NULL; + } else + bestdist = thisdist; + for (p = spnamebest; (*new = *p++);) + new++; + } +} + +/**/ +static int +mindist(char *dir, char *mindistguess, char *mindistbest) +{ + int mindistd, nd; + DIR *dd; + char *fn; + char buf[PATH_MAX]; + + if (dir[0] == '\0') + dir = "."; + mindistd = 100; + sprintf(buf, "%s/%s", dir, mindistguess); + if (access(unmeta(buf), F_OK) == 0) { + strcpy(mindistbest, mindistguess); + return 0; + } + if (!(dd = opendir(unmeta(dir)))) + return mindistd; + while ((fn = zreaddir(dd, 0))) { + nd = spdist(fn, mindistguess, + (int)strlen(mindistguess) / 4 + 1); + if (nd <= mindistd) { + strcpy(mindistbest, fn); + mindistd = nd; + if (mindistd == 0) + break; + } + } + closedir(dd); + return mindistd; +} + +/**/ +static int +spdist(char *s, char *t, int thresh) +{ + char *p, *q; + char *keymap = + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\ +\t1234567890-=\t\ +\tqwertyuiop[]\t\ +\tasdfghjkl;'\n\t\ +\tzxcvbnm,./\t\t\t\ +\n\n\n\n\n\n\n\n\n\n\n\n\n\n\ +\t!@#$%^&*()_+\t\ +\tQWERTYUIOP{}\t\ +\tASDFGHJKL:\"\n\t\ +\tZXCVBNM<>?\n\n\t\ +\n\n\n\n\n\n\n\n\n\n\n\n\n\n"; + + if (!strcmp(s, t)) + return 0; +/* any number of upper/lower mistakes allowed (dist = 1) */ + for (p = s, q = t; *p && tulower(*p) == tulower(*q); p++, q++); + if (!*p && !*q) + return 1; + if (!thresh) + return 200; + for (p = s, q = t; *p && *q; p++, q++) + if (*p == *q) + continue; /* don't consider "aa" transposed, ash */ + else if (p[1] == q[0] && q[1] == p[0]) /* transpositions */ + return spdist(p + 2, q + 2, thresh - 1) + 1; + else if (p[1] == q[0]) /* missing letter */ + return spdist(p + 1, q + 0, thresh - 1) + 2; + else if (p[0] == q[1]) /* missing letter */ + return spdist(p + 0, q + 1, thresh - 1) + 2; + else if (*p != *q) + break; + if ((!*p && strlen(q) == 1) || (!*q && strlen(p) == 1)) + return 2; + for (p = s, q = t; *p && *q; p++, q++) + if (p[0] != q[0] && p[1] == q[1]) { + int t0; + char *z; + + /* mistyped letter */ + + if (!(z = strchr(keymap, p[0])) || *z == '\n' || *z == '\t') + return spdist(p + 1, q + 1, thresh - 1) + 1; + t0 = z - keymap; + if (*q == keymap[t0 - 15] || *q == keymap[t0 - 14] || + *q == keymap[t0 - 13] || + *q == keymap[t0 - 1] || *q == keymap[t0 + 1] || + *q == keymap[t0 + 13] || *q == keymap[t0 + 14] || + *q == keymap[t0 + 15]) + return spdist(p + 1, q + 1, thresh - 1) + 2; + return 200; + } else if (*p != *q) + break; + return 200; +} + +/* set cbreak mode, or the equivalent */ + +/**/ +void +setcbreak(void) +{ + struct ttyinfo ti; + + ti = shttyinfo; +#ifdef HAS_TIO + ti.tio.c_lflag &= ~ICANON; + ti.tio.c_cc[VMIN] = 1; + ti.tio.c_cc[VTIME] = 0; +#else + ti.sgttyb.sg_flags |= CBREAK; +#endif + settyinfo(&ti); +} + +/* give the tty to some process */ + +/**/ +void +attachtty(pid_t pgrp) +{ + static int ep = 0; + + if (jobbing) { +#ifdef HAVE_TCSETPGRP + if (SHTTY != -1 && tcsetpgrp(SHTTY, pgrp) == -1 && !ep) +#else +# if ardent + if (SHTTY != -1 && setpgrp() == -1 && !ep) +# else + int arg = pgrp; + + if (SHTTY != -1 && ioctl(SHTTY, TIOCSPGRP, &arg) == -1 && !ep) +# endif +#endif + { + if (pgrp != mypgrp && kill(pgrp, 0) == -1) + attachtty(mypgrp); + else { + if (errno != ENOTTY) + { + zerr("can't set tty pgrp: %e", NULL, errno); + fflush(stderr); + } + opts[MONITOR] = 0; + ep = 1; + errflag = 0; + } + } + } +} + +/* get the process group associated with the tty */ + +/**/ +pid_t +gettygrp(void) +{ + pid_t arg; + + if (SHTTY == -1) + return -1; + +#ifdef HAVE_TCSETPGRP + arg = tcgetpgrp(SHTTY); +#else + ioctl(SHTTY, TIOCGPGRP, &arg); +#endif + + return arg; +} + +/* Return the output baudrate */ + +#ifdef HAVE_SELECT +/**/ +long +getbaudrate(struct ttyinfo *shttyinfo) +{ + long speedcode; + +#ifdef HAS_TIO +# if defined(HAVE_TCGETATTR) && defined(HAVE_TERMIOS_H) + speedcode = cfgetospeed(&shttyinfo->tio); +# else + speedcode = shttyinfo->tio.c_cflag & CBAUD; +# endif +#else + speedcode = shttyinfo->sgttyb.sg_ospeed; +#endif + + switch (speedcode) { + case B0: + return (0L); + case B50: + return (50L); + case B75: + return (75L); + case B110: + return (110L); + case B134: + return (134L); + case B150: + return (150L); + case B200: + return (200L); + case B300: + return (300L); + case B600: + return (600L); +#ifdef _B900 + case _B900: + return (900L); +#endif + case B1200: + return (1200L); + case B1800: + return (1800L); + case B2400: + return (2400L); +#ifdef _B3600 + case _B3600: + return (3600L); +#endif + case B4800: + return (4800L); +#ifdef _B7200 + case _B7200: + return (7200L); +#endif + case B9600: + return (9600L); +#ifdef B19200 + case B19200: + return (19200L); +#else +# ifdef EXTA + case EXTA: + return (19200L); +# endif +#endif +#ifdef B38400 + case B38400: + return (38400L); +#else +# ifdef EXTB + case EXTB: + return (38400L); +# endif +#endif +#ifdef B57600 + case B57600: + return (57600L); +#endif +#ifdef B115200 + case B115200: + return (115200L); +#endif +#ifdef B230400 + case B230400: + return (230400L); +#endif +#ifdef B460800 + case B460800: + return (460800L); +#endif + default: + if (speedcode >= 100) + return speedcode; + break; + } + return (0L); +} +#endif + +/* Escape tokens and null characters. Buf is the string which should be * + * escaped. len is the length of the string. If len is -1, buf should be * + * null terminated. If len is non-negative and the third paramerer is not * + * META_DUP, buf should point to an at least len+1 long memory area. The * + * return value points to the quoted string. If the given string does not * + * contain any special character which should be quoted and the third * + * parameter is not META_(HEAP|)DUP, buf is returned unchanged (a * + * terminating null character is appended to buf if necessary). Otherwise * + * the third `heap' argument determines the method used to allocate space * + * for the result. It can have the following values: * + * META_REALLOC: use zrealloc on buf * + * META_HREALLOC: use hrealloc on buf * + * META_USEHEAP: get memory from the heap. This leaves buf unchanged. * + * META_NOALLOC: buf points to a memory area which is long enough to hold * + * the quoted form, just quote it and return buf. * + * META_STATIC: store the quoted string in a static area. The original * + * sting should be at most PATH_MAX long. * + * META_ALLOC: allocate memory for the new string with zalloc(). * + * META_DUP: leave buf unchanged and allocate space for the return * + * value even if buf does not contains special characters * + * META_HEAPDUP: same as META_DUP, but uses the heap */ + +/**/ +char * +metafy(char *buf, int len, int heap) +{ + int meta = 0; + char *t, *p, *e; + static char mbuf[PATH_MAX*2+1]; + + if (len == -1) { + for (e = buf, len = 0; *e; len++) + if (imeta(*e++)) + meta++; + } else + for (e = buf; e < buf + len;) + if (imeta(*e++)) + meta++; + + if (meta || heap == META_DUP || heap == META_HEAPDUP) { + switch (heap) { + case META_REALLOC: + buf = zrealloc(buf, len + meta + 1); + break; + case META_HREALLOC: + buf = hrealloc(buf, len, len + meta + 1); + break; + case META_ALLOC: + case META_DUP: + buf = memcpy(zalloc(len + meta + 1), buf, len); + break; + case META_USEHEAP: + case META_HEAPDUP: + buf = memcpy(halloc(len + meta + 1), buf, len); + break; + case META_STATIC: +#ifdef DEBUG + if (len > PATH_MAX) { + fprintf(stderr, "BUG: len = %d > PATH_MAX in metafy\n", len); + fflush(stderr); + } +#endif + buf = memcpy(mbuf, buf, len); + break; +#ifdef DEBUG + case META_NOALLOC: + break; + default: + fprintf(stderr, "BUG: metafy called with invaild heap value\n"); + fflush(stderr); + break; +#endif + } + p = buf + len; + e = t = buf + len + meta; + while (meta) { + if (imeta(*--t = *--p)) { + *t-- ^= 32; + *t = Meta; + meta--; + } + } + } + *e = '\0'; + return buf; +} + +/**/ +char * +unmetafy(char *s, int *len) +{ + char *p, *t; + + for (p = s; *p && *p != Meta; p++); + for (t = p; (*t = *p++);) + if (*t++ == Meta) + t[-1] = *p++ ^ 32; + if (len) + *len = t - s; + return s; +} + +/* Return the character length of a metafied substring, given the * + * unmetafied substring length. */ + +/**/ +int +metalen(const char *s, int len) +{ + int mlen = len; + + while (len--) { + if (*s++ == Meta) { + mlen++; + s++; + } + } + return mlen; +} + +/* This function converts a zsh internal string to a form which can be * + * passed to a system call as a filename. The result is stored in a * + * single static area. NULL returned if the result is longer than * + * 4 * PATH_MAX. */ + +/**/ +char * +unmeta(const char *file_name) +{ + static char fn[4 * PATH_MAX]; + char *p; + const char *t; + + for (t = file_name, p = fn; *t && p < fn + 4 * PATH_MAX - 1; p++) + if ((*p = *t++) == Meta) + *p = *t++ ^ 32; + if (*t) + return NULL; + if (p - fn == t - file_name) + return (char *) file_name; + *p = '\0'; + return fn; +} + +/* Unmetafy and compare two strings, with unsigned characters. * + * "a\0" sorts after "a". */ + +/**/ +int +ztrcmp(unsigned char const *s1, unsigned char const *s2) +{ + int c1, c2; + + while(*s1 && *s1 == *s2) { + s1++; + s2++; + } + + if(!(c1 = *s1)) + c1 = -1; + else if(c1 == STOUC(Meta)) + c1 = *++s1 ^ 32; + if(!(c2 = *s2)) + c2 = -1; + else if(c2 == STOUC(Meta)) + c2 = *++s2 ^ 32; + + if(c1 == c2) + return 0; + else if(c1 < c2) + return -1; + else + return 1; +} + +/* Return zero if the metafied string s and the non-metafied, * + * len-long string r are the same. Return -1 if r is a prefix * + * of s. Return 1 if r is the lowercase version of s. Return * + * 2 is r is the lowercase prefix of s and return 3 otherwise. */ + +/**/ +int +metadiffer(char const *s, char const *r, int len) +{ + int l = len; + + while (l-- && *s && *r++ == (*s == Meta ? *++s ^ 32 : *s)) + s++; + if (*s && l < 0) + return -1; + if (l < 0) + return 0; + if (!*s) + return 3; + s -= len - l - 1; + r -= len - l; + while (len-- && *s && *r++ == tulower(*s == Meta ? *++s ^ 32 : *s)) + s++; + if (*s && len < 0) + return 2; + if (len < 0) + return 1; + return 3; +} + +/* Return the unmetafied length of a metafied string. */ + +/**/ +int +ztrlen(char const *s) +{ + int l; + + for (l = 0; *s; l++) + if (*s++ == Meta) { +#ifdef DEBUG + if (! *s) + fprintf(stderr, "BUG: unexpected end of string in ztrlen()\n"); + else +#endif + s++; + } + return l; +} + +/* Subtract two pointers in a metafied string. */ + +/**/ +int +ztrsub(char const *t, char const *s) +{ + int l = t - s; + + while (s != t) + if (*s++ == Meta) { +#ifdef DEBUG + if (! *s || s == t) + fprintf(stderr, "BUG: substring ends in the middle of a metachar in ztrsub()\n"); + else +#endif + s++; + l--; + } + return l; +} + +/**/ +char * +zreaddir(DIR *dir, int ignoredots) +{ + struct dirent *de; + + do { + de = readdir(dir); + if(!de) + return NULL; + } while(ignoredots && de->d_name[0] == '.' && + (!de->d_name[1] || (de->d_name[1] == '.' && !de->d_name[2]))); + + return metafy(de->d_name, -1, META_STATIC); +} + +/* Unmetafy and output a string. Tokens are skipped. */ + +/**/ +int +zputs(char const *s, FILE *stream) +{ + int c; + + while (*s) { + if (*s == Meta) + c = *++s ^ 32; + else if(itok(*s)) { + s++; + continue; + } else + c = *s; + s++; + if (fputc(c, stream) < 0) + return EOF; + } + return 0; +} + +/* Create a visibly-represented duplicate of a string. */ + +/**/ +char * +niceztrdup(char const *s) +{ + int c, len = strlen(s) * 5; + char *buf = zalloc(len); + char *p = buf, *n, *ret; + + while ((c = *s++)) { + if (itok(c)) + if (c <= Comma) + c = ztokens[c - Pound]; + else + continue; + if (c == Meta) + c = *s++ ^ 32; + n = nicechar(c); + while(*n) + *p++ = *n++; + } + ret = metafy(buf, p - buf, META_DUP); + zfree(buf, len); + return ret; +} + +/* Unmetafy and output a string, displaying special characters readably. */ + +/**/ +int +nicezputs(char const *s, FILE *stream) +{ + int c; + + while ((c = *s++)) { + if (itok(c)) + if (c <= Comma) + c = ztokens[c - Pound]; + else + continue; + if (c == Meta) + c = *s++ ^ 32; + if(fputs(nicechar(c), stream) < 0) + return EOF; + } + return 0; +} + +/* Return the length of the visible representation of a metafied string. */ + +/**/ +size_t +niceztrlen(char const *s) +{ + size_t l = 0; + int c; + + while ((c = *s++)) { + if (itok(c)) + if (c <= Comma) + c = ztokens[c - Pound]; + else + continue; + if (c == Meta) + c = *s++ ^ 32; + l += strlen(nicechar(STOUC(c))); + } + return l; +} + +/* check for special characters in the string */ + +/**/ +int +hasspecial(char const *s) +{ + for (; *s; s++) + if (ispecial(*s == Meta ? *++s ^ 32 : *s)) + return 1; + return 0; +} + +/* Unmetafy and output a string, quoted if it contains special characters. */ + +/**/ +int +quotedzputs(char const *s, FILE *stream) +{ + int inquote = 0, c; + + /* check for empty string */ + if(!*s) + return fputs("''", stream); + + if (!hasspecial(s)) + return zputs(s, stream); + + if (isset(RCQUOTES)) { + /* use rc-style quotes-within-quotes for the whole string */ + if(fputc('\'', stream) < 0) + return EOF; + while(*s) { + if (*s == Meta) + c = *++s ^ 32; + else + c = *s; + s++; + if (c == '\'') { + if(fputc('\'', stream) < 0) + return EOF; + } else if(c == '\n' && isset(CSHJUNKIEQUOTES)) { + if(fputc('\\', stream) < 0) + return EOF; + } + if(fputc(c, stream) < 0) + return EOF; + } + if(fputc('\'', stream) < 0) + return EOF; + } else { + /* use Bourne-style quoting, avoiding empty quoted strings */ + while(*s) { + if (*s == Meta) + c = *++s ^ 32; + else + c = *s; + s++; + if (c == '\'') { + if(inquote) { + if(fputc('\'', stream) < 0) + return EOF; + inquote=0; + } + if(fputs("\\'", stream) < 0) + return EOF; + } else { + if (!inquote) { + if(fputc('\'', stream) < 0) + return EOF; + inquote=1; + } + if(c == '\n' && isset(CSHJUNKIEQUOTES)) { + if(fputc('\\', stream) < 0) + return EOF; + } + if(fputc(c, stream) < 0) + return EOF; + } + } + if (inquote) { + if(fputc('\'', stream) < 0) + return EOF; + } + } + return 0; +} + +/* Double-quote a metafied string. */ + +/**/ +char * +dquotedztrdup(char const *s) +{ + int len = strlen(s) * 4 + 2; + char *buf = zalloc(len); + char *p = buf, *ret; + + if(isset(CSHJUNKIEQUOTES)) { + int inquote = 0; + + while(*s) { + int c = *s++; + + if (c == Meta) + c = *s++ ^ 32; + switch(c) { + case '"': + case '$': + case '`': + if(inquote) { + *p++ = '"'; + inquote = 0; + } + *p++ = '\\'; + *p++ = c; + break; + default: + if(!inquote) { + *p++ = '"'; + inquote = 1; + } + if(c == '\n') + *p++ = '\\'; + *p++ = c; + break; + } + } + if (inquote) + *p++ = '"'; + } else { + int pending = 0; + + *p++ = '"'; + while(*s) { + int c = *s++; + + if (c == Meta) + c = *s++ ^ 32; + switch(c) { + case '\\': + if(pending) + *p++ = '\\'; + *p++ = '\\'; + pending = 1; + break; + case '"': + case '$': + case '`': + if(pending) + *p++ = '\\'; + *p++ = '\\'; + /* fall through */ + default: + *p++ = c; + pending = 0; + break; + } + } + if(pending) + *p++ = '\\'; + *p++ = '"'; + } + ret = metafy(buf, p - buf, META_DUP); + zfree(buf, len); + return ret; +} + +#if 0 +/* Unmetafy and output a string, double quoting it in its entirety. */ + +/**/ +int +dquotedzputs(char const *s, FILE *stream) +{ + char *d = dquotedztrdup(s); + int ret = zputs(d, stream); + + zsfree(d); + return ret; +} +#endif + +/**/ +char * +getkeystring(char *s, int *len, int fromwhere, int *misc) +{ + char *buf; + char *t, *u = NULL; + char svchar = '\0'; + int meta = 0, control = 0; + + if (fromwhere != 4) + buf = halloc(strlen(s) + 1); + else { + buf = s; + s += 2; + } + for (t = buf; *s; s++) { + if (*s == '\\' && s[1]) { + switch (*++s) { + case 'a': +#ifdef __STDC__ + *t++ = '\a'; +#else + *t++ = '\07'; +#endif + break; + case 'n': + *t++ = '\n'; + break; + case 'b': + *t++ = '\b'; + break; + case 't': + *t++ = '\t'; + break; + case 'v': + *t++ = '\v'; + break; + case 'f': + *t++ = '\f'; + break; + case 'r': + *t++ = '\r'; + break; + case 'E': + if (!fromwhere) { + *t++ = '\\', s--; + continue; + } + case 'e': + *t++ = '\033'; + break; + case 'M': + if (fromwhere) { + if (s[1] == '-') + s++; + meta = 1 + control; /* preserve the order of ^ and meta */ + } else + *t++ = '\\', s--; + continue; + case 'C': + if (fromwhere) { + if (s[1] == '-') + s++; + control = 1; + } else + *t++ = '\\', s--; + continue; + case Meta: + *t++ = '\\', s--; + break; + case 'c': + if (fromwhere < 2) { + *misc = 1; + break; + } + default: + if ((idigit(*s) && *s < '8') || *s == 'x') { + if (!fromwhere) + if (*s == '0') + s++; + else if (*s != 'x') { + *t++ = '\\', s--; + continue; + } + if (s[1] && s[2] && s[3]) { + svchar = s[3]; + s[3] = '\0'; + u = s; + } + *t++ = zstrtol(s + (*s == 'x'), &s, + (*s == 'x') ? 16 : 8); + if (svchar) { + u[3] = svchar; + svchar = '\0'; + } + s--; + } else { + if (!fromwhere && *s != '\\') + *t++ = '\\'; + *t++ = *s; + } + break; + } + } else if (fromwhere == 4 && *s == Snull) { + for (u = t; (*u++ = *s++);); + return t + 1; + } else if (*s == '^' && fromwhere == 2) { + control = 1; + continue; + } else if (*s == Meta) + *t++ = *++s ^ 32; + else + *t++ = *s; + if (meta == 2) { + t[-1] |= 0x80; + meta = 0; + } + if (control) { + if (t[-1] == '?') + t[-1] = 0x7f; + else + t[-1] &= 0x9f; + control = 0; + } + if (meta) { + t[-1] |= 0x80; + meta = 0; + } + if (fromwhere == 4 && imeta(t[-1])) { + *t = t[-1] ^ 32; + t[-1] = Meta; + t++; + } + } + DPUTS(fromwhere == 4, "BUG: unterminated $' substitution"); + *t = '\0'; + *len = t - buf; + return buf; +} + +/* Return non-zero if s is a prefix of t. */ + +/**/ +int +strpfx(char *s, char *t) +{ + while (*s && *s == *t) + s++, t++; + return !*s; +} + +/* Return non-zero if s is a suffix of t. */ + +/**/ +int +strsfx(char *s, char *t) +{ + int ls = strlen(s), lt = strlen(t); + + if (ls <= lt) + return !strcmp(t + lt - ls, s); + return 0; +} + +/**/ +char * +dupstrpfx(const char *s, int len) +{ + char *r = ncalloc(len + 1); + + memcpy(r, s, len); + r[len] = '\0'; + return r; +} + +/**/ +char * +ztrduppfx(const char *s, int len) +{ + char *r = zalloc(len + 1); + + memcpy(r, s, len); + r[len] = '\0'; + return r; +} + +/* Append a string to an allocated string, reallocating to make room. */ + +/**/ +char * +appstr(char *base, char const *append) +{ + return strcat(realloc(base, strlen(base) + strlen(append) + 1), append); +} + +/**/ +static int +upchdir(int n) +{ + char buf[PATH_MAX]; + char *s; + int err = -1; + + while (n > 0) { + for (s = buf; s < buf + PATH_MAX - 4 && n--; ) + *s++ = '.', *s++ = '.', *s++ = '/'; + s[-1] = '\0'; + if (chdir(buf)) + return err; + err = -2; + } + return 0; +} + +/* Change directory, without following symlinks. Returns 0 on success, -1 * + * on failure. Sets errno to ENOTDIR if any symlinks are encountered. If * + * fchdir() fails, or the current directory is unreadable, we might end up * + * in an unwanted directory in case of failure. */ + +/**/ +int +lchdir(char const *path, struct dirsav *d, int hard) +{ + char const *pptr; + int level; + struct stat st1; + struct dirsav ds; +#ifdef HAVE_LSTAT + char buf[PATH_MAX + 1], *ptr; + int err; + struct stat st2; +#endif + + if (!d) { + ds.ino = ds.dev = 0; + ds.dirname = NULL; + ds.dirfd = -1; + d = &ds; + } +#ifdef HAVE_LSTAT + if ((*path == '/' || !hard) && + (d != &ds || hard)){ +#else + if (*path == '/') { +#endif + level = -1; +#ifdef HAVE_FCHDIR + if (d->dirfd < 0 && (d->dirfd = open(".", O_RDONLY | O_NOCTTY)) < 0 && + zgetdir(d) && *d->dirname != '/') + d->dirfd = open("..", O_RDONLY | O_NOCTTY); +#else + if (!d->dirname) + zgetdir(d); +#endif + } else { + level = 0; + if (!d->dev && !d->ino) { + stat(".", &st1); + d->dev = st1.st_dev; + d->ino = st1.st_ino; + } + } + +#ifdef HAVE_LSTAT + if (!hard) +#endif + { + if (d != &ds) { + for (pptr = path; *pptr; level++) { + while (*pptr && *pptr++ != '/'); + while (*pptr == '/') + pptr++; + } + d->level = level; + } + return zchdir((char *) path); + } +#ifdef HAVE_LSTAT + if (*path == '/') + chdir("/"); + for(;;) { + while(*path == '/') + path++; + if(!*path) { + if (d == &ds) { + zsfree(ds.dirname); + if (ds.dirfd >=0) + close(ds.dirfd); + } else + d->level = level; + return 0; + } + for(pptr = path; *++pptr && *pptr != '/'; ) ; + if(pptr - path > PATH_MAX) { + err = ENAMETOOLONG; + break; + } + for(ptr = buf; path != pptr; ) + *ptr++ = *path++; + *ptr = 0; + if(lstat(buf, &st1)) { + err = errno; + break; + } + if(!S_ISDIR(st1.st_mode)) { + err = ENOTDIR; + break; + } + if(chdir(buf)) { + err = errno; + break; + } + if (level >= 0) + level++; + if(lstat(".", &st2)) { + err = errno; + break; + } + if(st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) { + err = ENOTDIR; + break; + } + } + if (restoredir(d)) { + if (d == &ds) { + zsfree(ds.dirname); + if (ds.dirfd >=0) + close(ds.dirfd); + } + errno = err; + return -2; + } + if (d == &ds) { + zsfree(ds.dirname); + if (ds.dirfd >=0) + close(ds.dirfd); + } + errno = err; + return -1; +#endif /* HAVE_LSTAT */ +} + +/**/ +int +restoredir(struct dirsav *d) +{ + int err = 0; + struct stat sbuf; + + if (d->dirname && *d->dirname == '/') + return chdir(d->dirname); +#ifdef HAVE_FCHDIR + if (d->dirfd >= 0) { + if (!fchdir(d->dirfd)) { + if (!d->dirname) { + return 0; + } else if (chdir(d->dirname)) { + close(d->dirfd); + d->dirfd = -1; + err = -2; + } + } else { + close(d->dirfd); + d->dirfd = err = -1; + } + } else +#endif + if (d->level > 0) + err = upchdir(d->level); + else if (d->level < 0) + err = -1; + if (d->dev || d->ino) { + stat(".", &sbuf); + if (sbuf.st_ino != d->ino || sbuf.st_dev != d->dev) + err = -2; + } + return err; +} + +/* Get a signal number from a string */ + +/**/ +int +getsignum(char *s) +{ + int x, i; + + /* check for a signal specified by number */ + x = atoi(s); + if (idigit(*s) && x >= 0 && x < VSIGCOUNT) + return x; + + /* search for signal by name */ + for (i = 0; i < VSIGCOUNT; i++) + if (!strcmp(s, sigs[i])) + return i; + + /* no matching signal */ + return -1; +} + +/* Check whether the shell is running with privileges in effect. * + * This is the case if EITHER the euid is zero, OR (if the system * + * supports POSIX.1e (POSIX.6) capability sets) the process' * + * Effective or Inheritable capability sets are non-empty. */ + +/**/ +int +privasserted(void) +{ + if(!geteuid()) + return 1; +#ifdef HAVE_CAP_GET_PROC + { + cap_t caps = cap_get_proc(); + if(caps) { + /* POSIX doesn't define a way to test whether a capability set * + * is empty or not. Typical. I hope this is conforming... */ + cap_flag_value_t val; + cap_value_t n; + for(n = 0; !cap_get_flag(caps, n, CAP_EFFECTIVE, &val); n++) + if(val || + (!cap_get_flag(caps, n, CAP_INHERITABLE, &val) && val)) { + cap_free(&caps); + return 1; + } + cap_free(&caps); + } + } +#endif /* HAVE_CAP_GET_PROC */ + return 0; +} + +#ifdef DEBUG + +/**/ +void +dputs(char *message) +{ + fprintf(stderr, "%s\n", message); + fflush(stderr); +} + +#endif /* DEBUG */ + +/**/ +int +mode_to_octal(mode_t mode) +{ + int m = 0; + + if(mode & S_ISUID) + m |= 04000; + if(mode & S_ISGID) + m |= 02000; + if(mode & S_ISVTX) + m |= 01000; + if(mode & S_IRUSR) + m |= 00400; + if(mode & S_IWUSR) + m |= 00200; + if(mode & S_IXUSR) + m |= 00100; + if(mode & S_IRGRP) + m |= 00040; + if(mode & S_IWGRP) + m |= 00020; + if(mode & S_IXGRP) + m |= 00010; + if(mode & S_IROTH) + m |= 00004; + if(mode & S_IWOTH) + m |= 00002; + if(mode & S_IXOTH) + m |= 00001; + return m; +} diff --git a/Src/watch.c b/Src/watch.c new file mode 100644 index 000000000..2532e0a63 --- /dev/null +++ b/Src/watch.c @@ -0,0 +1,586 @@ +/* + * watch.c - login/logout watching + * + * 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" + +/* Headers for utmp/utmpx structures */ +#ifdef HAVE_UTMP_H +# include <utmp.h> +#endif +#ifdef HAVE_UTMPX_H +# include <utmpx.h> +#endif + +/* Find utmp file */ +#if !defined(REAL_UTMP_FILE) && defined(UTMP_FILE) +# define REAL_UTMP_FILE UTMP_FILE +#endif +#if !defined(REAL_UTMP_FILE) && defined(_PATH_UTMP) +# define REAL_UTMP_FILE _PATH_UTMP +#endif +#if !defined(REAL_UTMP_FILE) && defined(PATH_UTMP_FILE) +# define REAL_UTMP_FILE PATH_UTMP_FILE +#endif + +/* Find wtmp file */ +#if !defined(REAL_WTMP_FILE) && defined(WTMP_FILE) +# define REAL_WTMP_FILE WTMP_FILE +#endif +#if !defined(REAL_WTMP_FILE) && defined(_PATH_WTMP) +# define REAL_WTMP_FILE _PATH_WTMP +#endif +#if !defined(REAL_WTMP_FILE) && defined(PATH_WTMP_FILE) +# define REAL_WTMP_FILE PATH_WTMP_FILE +#endif + +/* Find utmpx file */ +#if !defined(REAL_UTMPX_FILE) && defined(UTMPX_FILE) +# define REAL_UTMPX_FILE UTMPX_FILE +#endif +#if !defined(REAL_UTMPX_FILE) && defined(_PATH_UTMPX) +# define REAL_UTMPX_FILE _PATH_UTMPX +#endif +#if !defined(REAL_UTMPX_FILE) && defined(PATH_UTMPX_FILE) +# define REAL_UTMPX_FILE PATH_UTMPX_FILE +#endif + +/* Find wtmpx file */ +#if !defined(REAL_WTMPX_FILE) && defined(WTMPX_FILE) +# define REAL_WTMPX_FILE WTMPX_FILE +#endif +#if !defined(REAL_WTMPX_FILE) && defined(_PATH_WTMPX) +# define REAL_WTMPX_FILE _PATH_WTMPX +#endif +#if !defined(REAL_WTMPX_FILE) && defined(PATH_WTMPX_FILE) +# define REAL_WTMPX_FILE PATH_WTMPX_FILE +#endif + +/* Decide which structure to use. We use a structure that exists in * + * the headers, and require that its corresponding utmp file exist. * + * (wtmp is less important.) */ + +#if !defined(WATCH_STRUCT_UTMP) && defined(HAVE_STRUCT_UTMPX) && defined(REAL_UTMPX_FILE) +# define WATCH_STRUCT_UTMP struct utmpx +# ifdef HAVE_STRUCT_UTMPX_UT_XTIME +# undef ut_time +# define ut_time ut_xtime +# else /* !HAVE_STRUCT_UTMPX_UT_XTIME */ +# ifdef HAVE_STRUCT_UTMPX_UT_TV +# undef ut_time +# define ut_time ut_tv.tv_sec +# endif /* HAVE_STRUCT_UTMPX_UT_TV */ +# endif /* !HAVE_STRUCT_UTMPX_UT_XTIME */ +# define WATCH_UTMP_FILE REAL_UTMPX_FILE +# ifdef REAL_WTMPX_FILE +# define WATCH_WTMP_FILE REAL_WTMPX_FILE +# endif +# ifdef HAVE_STRUCT_UTMPX_UT_HOST +# define WATCH_UTMP_UT_HOST 1 +# endif +#endif + +#if !defined(WATCH_STRUCT_UTMP) && defined(HAVE_STRUCT_UTMP) && defined(REAL_UTMP_FILE) +# define WATCH_STRUCT_UTMP struct utmp +# define WATCH_UTMP_FILE REAL_UTMP_FILE +# ifdef REAL_WTMP_FILE +# define WATCH_WTMP_FILE REAL_WTMP_FILE +# endif +# ifdef HAVE_STRUCT_UTMP_UT_HOST +# define WATCH_UTMP_UT_HOST 1 +# endif +#endif + +#ifdef WATCH_UTMP_UT_HOST +# define DEFAULT_WATCHFMT "%n has %a %l from %m." +#else /* !WATCH_UTMP_UT_HOST */ +# define DEFAULT_WATCHFMT "%n has %a %l." +#endif /* !WATCH_UTMP_UT_HOST */ + +/**/ +char const * const default_watchfmt = DEFAULT_WATCHFMT; + +#ifdef WATCH_STRUCT_UTMP + +# include "watch.pro" + +# ifndef WATCH_WTMP_FILE +# define WATCH_WTMP_FILE "/dev/null" +# endif + +static int wtabsz; +static WATCH_STRUCT_UTMP *wtab; +static time_t lastutmpcheck; + +/* get the time of login/logout for WATCH */ + +/**/ +static time_t +getlogtime(WATCH_STRUCT_UTMP *u, int inout) +{ + FILE *in; + WATCH_STRUCT_UTMP uu; + int first = 1; + int srchlimit = 50; /* max number of wtmp records to search */ + + if (inout) + return u->ut_time; + if (!(in = fopen(WATCH_WTMP_FILE, "r"))) + return time(NULL); + fseek(in, 0, 2); + do { + if (fseek(in, ((first) ? -1 : -2) * sizeof(WATCH_STRUCT_UTMP), 1)) { + fclose(in); + return time(NULL); + } + first = 0; + if (!fread(&uu, sizeof(WATCH_STRUCT_UTMP), 1, in)) { + fclose(in); + return time(NULL); + } + if (uu.ut_time < lastwatch || !srchlimit--) { + fclose(in); + return time(NULL); + } + } + while (memcmp(&uu, u, sizeof(uu))); + + do + if (!fread(&uu, sizeof(WATCH_STRUCT_UTMP), 1, in)) { + fclose(in); + return time(NULL); + } + while (strncmp(uu.ut_line, u->ut_line, sizeof(u->ut_line))); + fclose(in); + return uu.ut_time; +} + +/* Mutually recursive call to handle ternaries in $WATCHFMT */ + +# define BEGIN3 '(' +# define END3 ')' + +/**/ +static char * +watch3ary(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt) +{ + int truth = 1, sep; + + switch (*fmt++) { + case 'n': + truth = (u->ut_name[0] != 0); + break; + case 'a': + truth = inout; + break; + case 'l': + if (!strncmp(u->ut_line, "tty", 3)) + truth = (u->ut_line[3] != 0); + else + truth = (u->ut_line[0] != 0); + break; +# ifdef WATCH_UTMP_UT_HOST + case 'm': + case 'M': + truth = (u->ut_host[0] != 0); + break; +# endif /* WATCH_UTMP_UT_HOST */ + default: + prnt = 0; /* Skip unknown conditionals entirely */ + break; + } + sep = *fmt++; + fmt = watchlog2(inout, u, fmt, (truth && prnt), sep); + return watchlog2(inout, u, fmt, (!truth && prnt), END3); +} + +/* print a login/logout event */ + +/**/ +static char * +watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini) +{ + char buf[40], buf2[80]; + time_t timet; + struct tm *tm; + char *fm2; +# ifdef WATCH_UTMP_UT_HOST + char *p; + int i; +# endif /* WATCH_UTMP_UT_HOST */ + + while (*fmt) + if (*fmt == '\\') + if (*++fmt) { + if (prnt) + putchar(*fmt); + ++fmt; + } else if (fini) + return fmt; + else + break; + else if (*fmt == fini) + return ++fmt; + else if (*fmt != '%') { + if (prnt) + putchar(*fmt); + ++fmt; + } else { + if (*++fmt == BEGIN3) + fmt = watch3ary(inout, u, ++fmt, prnt); + else if (!prnt) + ++fmt; + else + switch (*(fm2 = fmt++)) { + case 'n': + printf("%.*s", (int)sizeof(u->ut_name), u->ut_name); + break; + case 'a': + printf("%s", (!inout) ? "logged off" : "logged on"); + break; + case 'l': + if (!strncmp(u->ut_line, "tty", 3)) + printf("%.*s", (int)sizeof(u->ut_line) - 3, u->ut_line + 3); + else + printf("%.*s", (int)sizeof(u->ut_line), u->ut_line); + break; +# ifdef WATCH_UTMP_UT_HOST + case 'm': + for (p = u->ut_host, i = sizeof(u->ut_host); i && *p; i--, p++) { + if (*p == '.' && !idigit(p[1])) + break; + putchar(*p); + } + break; + case 'M': + printf("%.*s", (int)sizeof(u->ut_host), u->ut_host); + break; +# endif /* WATCH_UTMP_UT_HOST */ + case 'T': + case 't': + case '@': + case 'W': + case 'w': + case 'D': + switch (*fm2) { + case '@': + case 't': + fm2 = "%l:%M%p"; + break; + case 'T': + fm2 = "%K:%M"; + break; + case 'w': + fm2 = "%a %f"; + break; + case 'W': + fm2 = "%m/%d/%y"; + break; + case 'D': + if (fm2[1] == '{') { + char *dd, *ss; + int n = 79; + + for (ss = fm2 + 2, dd = buf2; + n-- && *ss && *ss != '}'; ++ss, ++dd) + *dd = *((*ss == '\\' && ss[1]) ? ++ss : ss); + if (*ss == '}') { + *dd = '\0'; + fmt = ss + 1; + fm2 = buf2; + } + else fm2 = "%y-%m-%d"; + } + else fm2 = "%y-%m-%d"; + break; + } + timet = getlogtime(u, inout); + tm = localtime(&timet); + ztrftime(buf, 40, fm2, tm); + printf("%s", (*buf == ' ') ? buf + 1 : buf); + break; + case '%': + putchar('%'); + break; + case 'S': + txtset(TXTSTANDOUT); + tsetcap(TCSTANDOUTBEG, -1); + break; + case 's': + txtset(TXTDIRTY); + txtunset(TXTSTANDOUT); + tsetcap(TCSTANDOUTEND, -1); + break; + case 'B': + txtset(TXTDIRTY); + txtset(TXTBOLDFACE); + tsetcap(TCBOLDFACEBEG, -1); + break; + case 'b': + txtset(TXTDIRTY); + txtunset(TXTBOLDFACE); + tsetcap(TCALLATTRSOFF, -1); + break; + case 'U': + txtset(TXTUNDERLINE); + tsetcap(TCUNDERLINEBEG, -1); + break; + case 'u': + txtset(TXTDIRTY); + txtunset(TXTUNDERLINE); + tsetcap(TCUNDERLINEEND, -1); + break; + default: + putchar('%'); + putchar(*fm2); + break; + } + } + if (prnt) + putchar('\n'); + + return fmt; +} + +/* check the List for login/logouts */ + +/**/ +static void +watchlog(int inout, WATCH_STRUCT_UTMP *u, char **w, char *fmt) +{ + char *v, *vv, sav; + int bad; + + if (!*u->ut_name) + return; + + if (*w && !strcmp(*w, "all")) { + (void)watchlog2(inout, u, fmt, 1, 0); + return; + } + if (*w && !strcmp(*w, "notme") && + strncmp(u->ut_name, get_username(), sizeof(u->ut_name))) { + (void)watchlog2(inout, u, fmt, 1, 0); + return; + } + for (; *w; w++) { + bad = 0; + v = *w; + if (*v != '@' && *v != '%') { + for (vv = v; *vv && *vv != '@' && *vv != '%'; vv++); + sav = *vv; + *vv = '\0'; + if (strncmp(u->ut_name, v, sizeof(u->ut_name))) + bad = 1; + *vv = sav; + v = vv; + } + for (;;) + if (*v == '%') { + for (vv = ++v; *vv && *vv != '@'; vv++); + sav = *vv; + *vv = '\0'; + if (strncmp(u->ut_line, v, sizeof(u->ut_line))) + bad = 1; + *vv = sav; + v = vv; + } +# ifdef WATCH_UTMP_UT_HOST + else if (*v == '@') { + for (vv = ++v; *vv && *vv != '%'; vv++); + sav = *vv; + *vv = '\0'; + if (strncmp(u->ut_host, v, strlen(v))) + bad = 1; + *vv = sav; + v = vv; + } +# endif /* WATCH_UTMP_UT_HOST */ + else + break; + if (!bad) { + (void)watchlog2(inout, u, fmt, 1, 0); + return; + } + } +} + +/* compare 2 utmp entries */ + +/**/ +static int +ucmp(WATCH_STRUCT_UTMP *u, WATCH_STRUCT_UTMP *v) +{ + if (u->ut_time == v->ut_time) + return strncmp(u->ut_line, v->ut_line, sizeof(u->ut_line)); + return u->ut_time - v->ut_time; +} + +/* initialize the user List */ + +/**/ +static void +readwtab(void) +{ + WATCH_STRUCT_UTMP *uptr; + int wtabmax = 32; + FILE *in; + + wtabsz = 0; + if (!(in = fopen(WATCH_UTMP_FILE, "r"))) + return; + uptr = wtab = (WATCH_STRUCT_UTMP *)zalloc(wtabmax * sizeof(WATCH_STRUCT_UTMP)); + while (fread(uptr, sizeof(WATCH_STRUCT_UTMP), 1, in)) +# ifdef USER_PROCESS + if (uptr->ut_type == USER_PROCESS) +# else /* !USER_PROCESS */ + if (uptr->ut_name[0]) +# endif /* !USER_PROCESS */ + { + uptr++; + if (++wtabsz == wtabmax) + uptr = (wtab = (WATCH_STRUCT_UTMP *)realloc((void *) wtab, (wtabmax *= 2) * + sizeof(WATCH_STRUCT_UTMP))) + wtabsz; + } + fclose(in); + + if (wtabsz) + qsort((void *) wtab, wtabsz, sizeof(WATCH_STRUCT_UTMP), + (int (*) _((const void *, const void *)))ucmp); +} + +/* Check for login/logout events; executed before * + * each prompt if WATCH is set */ + +/**/ +void +dowatch(void) +{ + FILE *in; + WATCH_STRUCT_UTMP *utab, *uptr, *wptr; + struct stat st; + char **s; + char *fmt; + int utabsz = 0, utabmax = wtabsz + 4; + int uct, wct; + + s = watch; + if (!(fmt = getsparam("WATCHFMT"))) + fmt = DEFAULT_WATCHFMT; + + holdintr(); + if (!wtab) { + readwtab(); + noholdintr(); + return; + } + if ((stat(WATCH_UTMP_FILE, &st) == -1) || (st.st_mtime <= lastutmpcheck)) { + noholdintr(); + return; + } + lastutmpcheck = st.st_mtime; + uptr = utab = (WATCH_STRUCT_UTMP *) zalloc(utabmax * sizeof(WATCH_STRUCT_UTMP)); + + if (!(in = fopen(WATCH_UTMP_FILE, "r"))) { + free(utab); + noholdintr(); + return; + } + while (fread(uptr, sizeof *uptr, 1, in)) +# ifdef USER_PROCESS + if (uptr->ut_type == USER_PROCESS) +# else /* !USER_PROCESS */ + if (uptr->ut_name[0]) +# endif /* !USER_PROCESS */ + { + uptr++; + if (++utabsz == utabmax) + uptr = (utab = (WATCH_STRUCT_UTMP *)realloc((void *) utab, (utabmax *= 2) * + sizeof(WATCH_STRUCT_UTMP))) + utabsz; + } + fclose(in); + noholdintr(); + if (errflag) { + free(utab); + return; + } + if (utabsz) + qsort((void *) utab, utabsz, sizeof(WATCH_STRUCT_UTMP), + (int (*) _((const void *, const void *)))ucmp); + + wct = wtabsz; + uct = utabsz; + uptr = utab; + wptr = wtab; + if (errflag) { + free(utab); + return; + } + while ((uct || wct) && !errflag) + if (!uct || (wct && ucmp(uptr, wptr) > 0)) + wct--, watchlog(0, wptr++, s, fmt); + else if (!wct || (uct && ucmp(uptr, wptr) < 0)) + uct--, watchlog(1, uptr++, s, fmt); + else + uptr++, wptr++, wct--, uct--; + free(wtab); + wtab = utab; + wtabsz = utabsz; + fflush(stdout); +} + +/**/ +int +bin_log(char *nam, char **argv, char *ops, int func) +{ + if (!watch) + return 1; + if (wtab) + free(wtab); + wtab = (WATCH_STRUCT_UTMP *)zalloc(1); + wtabsz = 0; + lastutmpcheck = 0; + dowatch(); + return 0; +} + +#else /* !WATCH_STRUCT_UTMP */ + +/**/ +void dowatch(void) +{ +} + +/**/ +int +bin_log(char *nam, char **argv, char *ops, int func) +{ + return bin_notavail(nam, argv, ops, func); +} + +#endif /* !WATCH_STRUCT_UTMP */ diff --git a/Src/xmods.conf b/Src/xmods.conf new file mode 100644 index 000000000..c36105721 --- /dev/null +++ b/Src/xmods.conf @@ -0,0 +1,5 @@ +rlimits +comp1 +zle +compctl +sched diff --git a/Src/zsh.export b/Src/zsh.export new file mode 100644 index 000000000..8f676c7fd --- /dev/null +++ b/Src/zsh.export @@ -0,0 +1,235 @@ +#! +SHTTY +addbuiltins +addedx +addhashnode +aliastab +alloc_stackp +appstr +arrdup +arrlen +attachtty +bangchar +bin_notavail +breaks +bufstack +builtintab +chline +chuck +clearjobtab +closem +cmdnamtab +columns +compctlreadptr +coprocin +coprocout +countlinknodes +countprompt +createparam +ctxtlex +curhist +current_limits +deletebuiltins +deletehashtable +domatch +doshfunc +dputs +dquotedztrdup +dummy_list +dupstring +dupstrpfx +dyncat +emptyhashtable +endparamscope +errflag +excs +execstring +exlast +expanding +fallback_compctlread +fallback_zleread +fignore +file_type +filesub +filesubstr +findcmd +firsthist +freearray +freeheap +getaparam +gethashnode +gethashnode2 +getiparam +getkeystring +getlinknode +getshfunc +getsparam +glob_pre +glob_suf +global_heapalloc +global_permalloc +globlist +gotwordptr +halloc +hasam +hashcmd +hasher +hasspecial +haswilds +hcalloc +hgetc +hgetline +histentarr +histentct +hptr +hrealloc +inbufct +incmdpos +incond +init_io +init_shout +init_term +inpop +inpush +inredir +insertlinknode +intr +inwhat +isfirstln +jobtab +lastpid +lastval +lchdir +lexrestore +lexsave +lexstop +limits +line +lines +locallevel +metadiffer +metafy +metalen +mode_to_octal +mypgrp +mypid +nameddirtab +ncalloc +newhashtable +newlinklist +nicechar +nicezputs +niceztrdup +niceztrlen +noaliases +noerrs +noop_function +noop_function_int +optiontab +opts +paramtab +parbegin +parend +parsereg +parsestr +path +pathchecked +popheap +postedit +ppid +prefork +prepromptfns +printif +printqt +promptexpand +pushheap +putshout +pwd +quietgetevent +quietgethist +quotedzputs +refreshptr +remlpaths +remnulargs +removehashnode +resetneeded +restoredir +reswdtab +retflag +scanhashtable +setaparam +setlimits +setsparam +settyinfo +shfunctab +shingetline +shout +shttyinfo +singsub +skipparens +spaceinlineptr +spacesplit +spckword +startparamscope +stdunsetfn +stophist +stopmsg +strinbeg +strinend +strpfx +strsfx +strucpy +struncpy +tclen +tcstr +termflags +tgoto +tok +tokenize +tokstr +tputs +trashzleptr +tricat +tsetcap +ttystrname +tulower +tuupper +txtchange +typtab +ugetnode +uinsertlinknode +unmeta +unmetafy +untokenize +uremnode +useheap +winchanged +wordbeg +zalloc +zbeep +zcalloc +zchdir +zerr +zerrnam +zexit +zfree +zgetdir +zgetenv +zjoin +zleactive +zleparse +zlereadptr +zputs +zreaddir +zsetlimit +zsfree +zshcs +zshll +zstrtol +ztokens +ztrdup +ztrduppfx +ztrftime +ztrlen +ztrsub +zwarnnam diff --git a/Src/zsh.h b/Src/zsh.h new file mode 100644 index 000000000..e96fc6e86 --- /dev/null +++ b/Src/zsh.h @@ -0,0 +1,1293 @@ +/* + * zsh.h - standard header file + * + * 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. + * + */ + +#define trashzle() trashzleptr() +#define zleread(X,Y,H) zlereadptr(X,Y,H) +#define spaceinline(X) spaceinlineptr(X) +#define gotword() gotwordptr() +#define refresh() refreshptr() + +#define compctlread(N,A,O,R) compctlreadptr(N,A,O,R) + +/* A few typical macros */ +#define minimum(a,b) ((a) < (b) ? (a) : (b)) + +/* math.c */ +typedef int LV; + +/* Character tokens are sometimes casted to (unsigned char)'s. * + * Unfortunately, some compilers don't correctly cast signed to * + * unsigned promotions; i.e. (int)(unsigned char)((char) -1) evaluates * + * to -1, instead of 255 like it should. We circumvent the troubles * + * of such shameful delinquency by casting to a larger unsigned type * + * then back down to unsigned char. */ + +#ifdef BROKEN_SIGNED_TO_UNSIGNED_CASTING +# define STOUC(X) ((unsigned char)(unsigned short)(X)) +#else +# define STOUC(X) ((unsigned char)(X)) +#endif + +/* Meta together with the character following Meta denotes the character * + * which is the exclusive or of 32 and the character following Meta. * + * This is used to represent characters which otherwise has special * + * meaning for zsh. These are the characters for which the imeta() test * + * is true: the null character, and the characters from Meta to Marker. */ + +#define Meta ((char) 0x83) + +/* Note that the fourth character in DEFAULT_IFS is Meta * + * followed by a space which denotes the null character. */ + +#define DEFAULT_IFS " \t\n\203 " + +/* Character tokens */ +#define Pound ((char) 0x84) +#define String ((char) 0x85) +#define Hat ((char) 0x86) +#define Star ((char) 0x87) +#define Inpar ((char) 0x88) +#define Outpar ((char) 0x89) +#define Qstring ((char) 0x8a) +#define Equals ((char) 0x8b) +#define Bar ((char) 0x8c) +#define Inbrace ((char) 0x8d) +#define Outbrace ((char) 0x8e) +#define Inbrack ((char) 0x8f) +#define Outbrack ((char) 0x90) +#define Tick ((char) 0x91) +#define Inang ((char) 0x92) +#define Outang ((char) 0x93) +#define Quest ((char) 0x94) +#define Tilde ((char) 0x95) +#define Qtick ((char) 0x96) +#define Comma ((char) 0x97) +#define Snull ((char) 0x98) +#define Dnull ((char) 0x99) +#define Bnull ((char) 0x9a) +#define Nularg ((char) 0x9b) + +#define INULL(x) (((x) & 0xfc) == 0x98) + +/* Marker used in paramsubst for rc_expand_param */ +#define Marker ((char) 0x9c) + +/* chars that need to be quoted if meant literally */ + +#define SPECCHARS "#$^*()=|{}[]`<>?~;&\n\t \\\'\"" + +enum { + NULLTOK, /* 0 */ + SEPER, + NEWLIN, + SEMI, + DSEMI, + AMPER, /* 5 */ + INPAR, + OUTPAR, + DBAR, + DAMPER, + OUTANG, /* 10 */ + OUTANGBANG, + DOUTANG, + DOUTANGBANG, + INANG, + INOUTANG, /* 15 */ + DINANG, + DINANGDASH, + INANGAMP, + OUTANGAMP, + AMPOUTANG, /* 20 */ + OUTANGAMPBANG, + DOUTANGAMP, + DOUTANGAMPBANG, + TRINANG, + BAR, /* 25 */ + BARAMP, + INOUTPAR, + DINPAR, + DOUTPAR, + AMPERBANG, /* 30 */ + SEMIAMP, + DOUTBRACK, + STRING, + ENVSTRING, + ENVARRAY, /* 35 */ + ENDINPUT, + LEXERR, + + /* Tokens for reserved words */ + BANG, /* ! */ + DINBRACK, /* [[ */ + INBRACE, /* { */ /* 40 */ + OUTBRACE, /* } */ + CASE, /* case */ + COPROC, /* coproc */ + DO, /* do */ + DONE, /* done */ /* 45 */ + ELIF, /* elif */ + ELSE, /* else */ + ZEND, /* end */ + ESAC, /* esac */ + FI, /* fi */ /* 50 */ + FOR, /* for */ + FOREACH, /* foreach */ + FUNC, /* function */ + IF, /* if */ + NOCORRECT, /* nocorrect */ /* 55 */ + REPEAT, /* repeat */ + SELECT, /* select */ + THEN, /* then */ + TIME, /* time */ + UNTIL, /* until */ /* 60 */ + WHILE /* while */ +}; + +/* Redirection types. If you modify this, you may also have to modify * + * redirtab in parse.c and getredirs() in text.c and the IS_* macros * + * below. */ + +enum { + WRITE, /* > */ + WRITENOW, /* >| */ + APP, /* >> */ + APPNOW, /* >>| */ + ERRWRITE, /* &>, >& */ + ERRWRITENOW, /* >&| */ + ERRAPP, /* >>& */ + ERRAPPNOW, /* >>&| */ + READWRITE, /* <> */ + READ, /* < */ + HEREDOC, /* << */ + HEREDOCDASH, /* <<- */ + HERESTR, /* <<< */ + MERGEIN, /* <&n */ + MERGEOUT, /* >&n */ + CLOSE, /* >&-, <&- */ + INPIPE, /* < <(...) */ + OUTPIPE /* > >(...) */ +}; + +#define IS_WRITE_FILE(X) ((X)>=WRITE && (X)<=READWRITE) +#define IS_APPEND_REDIR(X) (IS_WRITE_FILE(X) && ((X) & 2)) +#define IS_CLOBBER_REDIR(X) (IS_WRITE_FILE(X) && ((X) & 1)) +#define IS_ERROR_REDIR(X) ((X)>=ERRWRITE && (X)<=ERRAPPNOW) +#define IS_READFD(X) (((X)>=READWRITE && (X)<=MERGEIN) || (X)==INPIPE) +#define IS_REDIROP(X) ((X)>=OUTANG && (X)<=TRINANG) + +/* Flags for input stack */ +#define INP_FREE (1<<0) /* current buffer can be free'd */ +#define INP_ALIAS (1<<1) /* expanding alias or history */ +#define INP_HIST (1<<2) /* expanding history */ +#define INP_CONT (1<<3) /* continue onto previously stacked input */ +#define INP_ALCONT (1<<4) /* stack is continued from alias expn. */ + +/* Flags for metafy */ +#define META_REALLOC 0 +#define META_USEHEAP 1 +#define META_STATIC 2 +#define META_DUP 3 +#define META_ALLOC 4 +#define META_NOALLOC 5 +#define META_HEAPDUP 6 +#define META_HREALLOC 7 + + +/**************************/ +/* Abstract types for zsh */ +/**************************/ + +typedef struct linknode *LinkNode; +typedef struct linklist *LinkList; +typedef struct hashnode *HashNode; +typedef struct hashtable *HashTable; + +typedef struct reswd *Reswd; +typedef struct alias *Alias; +typedef struct param *Param; +typedef struct cmdnam *Cmdnam; +typedef struct shfunc *Shfunc; +typedef struct builtin *Builtin; +typedef struct nameddir *Nameddir; +typedef struct module *Module; + +typedef struct process *Process; +typedef struct job *Job; +typedef struct value *Value; +typedef struct varasg *Varasg; +typedef struct cond *Cond; +typedef struct cmd *Cmd; +typedef struct pline *Pline; +typedef struct sublist *Sublist; +typedef struct list *List; +typedef struct comp *Comp; +typedef struct redir *Redir; +typedef struct complist *Complist; +typedef struct heap *Heap; +typedef struct heapstack *Heapstack; +typedef struct histent *Histent; +typedef struct forcmd *Forcmd; +typedef struct autofn *AutoFn; + +typedef struct asgment *Asgment; + + +/********************************/ +/* Definitions for linked lists */ +/********************************/ + +/* linked list abstract data type */ + +struct linknode { + LinkNode next; + LinkNode last; + void *dat; +}; + +struct linklist { + LinkNode first; + LinkNode last; +}; + +/* Macros for manipulating link lists */ + +#define addlinknode(X,Y) insertlinknode(X,(X)->last,Y) +#define uaddlinknode(X,Y) uinsertlinknode(X,(X)->last,Y) +#define empty(X) ((X)->first == NULL) +#define nonempty(X) ((X)->first != NULL) +#define firstnode(X) ((X)->first) +#define getaddrdata(X) (&((X)->dat)) +#define getdata(X) ((X)->dat) +#define setdata(X,Y) ((X)->dat = (Y)) +#define lastnode(X) ((X)->last) +#define nextnode(X) ((X)->next) +#define prevnode(X) ((X)->last) +#define peekfirst(X) ((X)->first->dat) +#define pushnode(X,Y) insertlinknode(X,(LinkNode) X,Y) +#define incnode(X) (X = nextnode(X)) +#define gethistent(X) (histentarr+((X)%histentct)) + + +/********************************/ +/* Definitions for syntax trees */ +/********************************/ + +/* struct list, struct sublist, struct pline, etc. all fit the form * + * of this structure and are used interchangably. The ptrs may hold * + * integers or pointers, depending on the type of the node. */ + +/* Generic node structure for syntax trees */ +struct node { + int ntype; /* node type */ +}; + +#define N_LIST 0 +#define N_SUBLIST 1 +#define N_PLINE 2 +#define N_CMD 3 +#define N_REDIR 4 +#define N_COND 5 +#define N_FOR 6 +#define N_CASE 7 +#define N_IF 8 +#define N_WHILE 9 +#define N_VARASG 10 +#define N_AUTOFN 11 +#define N_COUNT 12 + +/* values for types[4] */ + +#define NT_EMPTY 0 +#define NT_NODE 1 +#define NT_STR 2 +#define NT_LIST 4 +#define NT_ARR 8 + +#define NT_TYPE(T) ((T) & 0xff) +#define NT_N(T, N) (((T) >> (8 + (N) * 4)) & 0xf) +#define NT_SET(T0, T1, T2, T3, T4) \ + ((T0) | ((T1) << 8) | ((T2) << 12) | ((T3) << 16) | ((T4) << 20)) +#define NT_HEAP (1 << 30) + +/* tree element for lists */ + +struct list { + int ntype; /* node type */ + int type; + Sublist left; + List right; +}; + +/* These are control flags that are passed * + * down the execution pipeline. */ +#define Z_TIMED (1<<0) /* pipeline is being timed */ +#define Z_SYNC (1<<1) /* run this sublist synchronously (;) */ +#define Z_ASYNC (1<<2) /* run this sublist asynchronously (&) */ +#define Z_DISOWN (1<<3) /* run this sublist without job control (&|) */ + +/* tree element for sublists */ + +struct sublist { + int ntype; /* node type */ + int type; + int flags; /* see PFLAGs below */ + Pline left; + Sublist right; +}; + +#define ORNEXT 10 /* || */ +#define ANDNEXT 11 /* && */ + +#define PFLAG_NOT 1 /* ! ... */ +#define PFLAG_COPROC 32 /* coproc ... */ + +/* tree element for pipes */ + +struct pline { + int ntype; /* node type */ + int type; + Cmd left; + Pline right; +}; + +#define END 0 /* pnode *right is null */ +#define PIPE 1 /* pnode *right is the rest of the pipeline */ + +/* tree element for commands */ + +struct cmd { + int ntype; /* node type */ + int type; + int flags; /* see CFLAGs below */ + int lineno; /* lineno of script for command */ + union { + List list; /* for SUBSH/CURSH/SHFUNC */ + Forcmd forcmd; + struct casecmd *casecmd; + struct ifcmd *ifcmd; + struct whilecmd *whilecmd; + Sublist pline; + Cond cond; + AutoFn autofn; + void *generic; + } u; + LinkList args; /* command & argmument List (char *'s) */ + LinkList redir; /* i/o redirections (struct redir *'s) */ + LinkList vars; /* param assignments (struct varasg *'s) */ +}; + +/* cmd types */ +#define SIMPLE 0 +#define SUBSH 1 +#define CURSH 2 +#define ZCTIME 3 +#define FUNCDEF 4 +#define CFOR 5 +#define CWHILE 6 +#define CREPEAT 7 +#define CIF 8 +#define CCASE 9 +#define CSELECT 10 +#define COND 11 +#define CARITH 12 +#define AUTOFN 13 + +/* flags for command modifiers */ +#define CFLAG_EXEC (1<<0) /* exec ... */ + +/* tree element for redirection lists */ + +struct redir { + int ntype; /* node type */ + int type; + int fd1, fd2; + char *name; +}; + +/* tree element for conditionals */ + +struct cond { + int ntype; /* node type */ + int type; /* can be cond_type, or a single */ + /* letter (-a, -b, ...) */ + void *left, *right; +}; + +#define COND_NOT 0 +#define COND_AND 1 +#define COND_OR 2 +#define COND_STREQ 3 +#define COND_STRNEQ 4 +#define COND_STRLT 5 +#define COND_STRGTR 6 +#define COND_NT 7 +#define COND_OT 8 +#define COND_EF 9 +#define COND_EQ 10 +#define COND_NE 11 +#define COND_LT 12 +#define COND_GT 13 +#define COND_LE 14 +#define COND_GE 15 + +struct forcmd { /* for/select */ +/* Cmd->args contains list of words to loop thru */ + int ntype; /* node type */ + int inflag; /* if there is an in ... clause */ + char *name; /* initializer or parameter name */ + char *condition; /* arithmetic terminating condition */ + char *advance; /* evaluated after each loop */ + List list; /* list to look through for each name */ +}; + +struct casecmd { +/* Cmd->args contains word to test */ + int ntype; /* node type */ + char **pats; + List *lists; /* list to execute */ +}; + + +/* A command like "if foo then bar elif baz then fubar else fooble" */ +/* generates a tree like: */ +/* */ +/* struct ifcmd a = { next = &b, ifl = "foo", thenl = "bar" } */ +/* struct ifcmd b = { next = &c, ifl = "baz", thenl = "fubar" } */ +/* struct ifcmd c = { next = NULL, ifl = NULL, thenl = "fooble" } */ + +struct ifcmd { + int ntype; /* node type */ + List *ifls; + List *thenls; +}; + +struct whilecmd { + int ntype; /* node type */ + int cond; /* 0 for while, 1 for until */ + List cont; /* condition */ + List loop; /* list to execute until condition met */ +}; + +/* node for autoloading functions */ + +struct autofn { + int ntype; /* node type */ + Shfunc shf; /* the shell function to define */ +}; + +/* The number of fds space is allocated for * + * each time a multio must increase in size. */ +#define MULTIOUNIT 8 + +/* A multio is a list of fds associated with a certain fd. * + * Thus if you do "foo >bar >ble", the multio for fd 1 will have * + * two fds, the result of open("bar",...), and the result of * + * open("ble",....). */ + +/* structure used for multiple i/o redirection */ +/* one for each fd open */ + +struct multio { + int ct; /* # of redirections on this fd */ + int rflag; /* 0 if open for reading, 1 if open for writing */ + int pipe; /* fd of pipe if ct > 1 */ + int fds[MULTIOUNIT]; /* list of src/dests redirected to/from this fd */ +}; + +/* variable assignment tree element */ + +struct varasg { + int ntype; /* node type */ + int type; /* nonzero means array */ + char *name; + char *str; /* should've been a union here. oh well */ + LinkList arr; +}; + +/* lvalue for variable assignment/expansion */ + +struct value { + int isarr; + Param pm; /* parameter node */ + int inv; /* should we return the index ? */ + int a; /* first element of array slice, or -1 */ + int b; /* last element of array slice, or -1 */ +}; + +/* structure for foo=bar assignments */ + +struct asgment { + struct asgment *next; + char *name; + char *value; +}; + +#define MAX_ARRLEN 262144 + + +/********************************************/ +/* Defintions for job table and job control */ +/********************************************/ + +/* size of job table */ +#define MAXJOB 50 + +/* entry in the job table */ + +struct job { + pid_t gleader; /* process group leader of this job */ + pid_t other; /* subjob id or subshell pid */ + int stat; /* see STATs below */ + char *pwd; /* current working dir of shell when * + * this job was spawned */ + struct process *procs; /* list of processes */ + LinkList filelist; /* list of files to delete when done */ + int stty_in_env; /* if STTY=... is present */ + struct ttyinfo *ty; /* the modes specified by STTY */ +}; + +#define STAT_CHANGED (1<<0) /* status changed and not reported */ +#define STAT_STOPPED (1<<1) /* all procs stopped or exited */ +#define STAT_TIMED (1<<2) /* job is being timed */ +#define STAT_DONE (1<<3) /* job is done */ +#define STAT_LOCKED (1<<4) /* shell is finished creating this job, */ + /* may be deleted from job table */ +#define STAT_NOPRINT (1<<5) /* job was killed internally, */ + /* we don't want to show that */ +#define STAT_INUSE (1<<6) /* this job entry is in use */ +#define STAT_SUPERJOB (1<<7) /* job has a subjob */ +#define STAT_SUBJOB (1<<8) /* job is a subjob */ +#define STAT_CURSH (1<<9) /* last command is in current shell */ +#define STAT_NOSTTY (1<<10) /* the tty settings are not inherited */ + /* from this job when it exits. */ + +#define SP_RUNNING -1 /* fake status for jobs currently running */ + +struct timeinfo { + long ut; /* user space time */ + long st; /* system space time */ +}; + +#define JOBTEXTSIZE 80 + +/* node in job process lists */ + +struct process { + struct process *next; + pid_t pid; /* process id */ + char text[JOBTEXTSIZE]; /* text to print when 'jobs' is run */ + int status; /* return code from waitpid/wait3() */ + struct timeinfo ti; + struct timeval bgtime; /* time job was spawned */ + struct timeval endtime; /* time job exited */ +}; + +struct execstack { + struct execstack *next; + + LinkList args; + pid_t list_pipe_pid; + int nowait; + int pline_level; + int list_pipe_child; + int list_pipe_job; + char list_pipe_text[JOBTEXTSIZE]; + int lastval; + int noeval; + int badcshglob; + pid_t cmdoutpid; + int cmdoutval; + int trapreturn; + int noerrs; + int subsh_close; + char *underscore; +}; + +struct heredocs { + struct heredocs *next; + Redir rd; +}; + +struct dirsav { + int dirfd, level; + char *dirname; + dev_t dev; + ino_t ino; +}; + +/*******************************/ +/* Definitions for Hash Tables */ +/*******************************/ + +typedef void *(*VFunc) _((void *)); +typedef void (*FreeFunc) _((void *)); + +typedef unsigned (*HashFunc) _((char *)); +typedef void (*TableFunc) _((HashTable)); +typedef void (*AddNodeFunc) _((HashTable, char *, void *)); +typedef HashNode (*GetNodeFunc) _((HashTable, char *)); +typedef HashNode (*RemoveNodeFunc) _((HashTable, char *)); +typedef void (*FreeNodeFunc) _((HashNode)); + +/* type of function that is passed to * + * scanhashtable or scanmatchtable */ +typedef void (*ScanFunc) _((HashNode, int)); + +typedef void (*PrintTableStats) _((HashTable)); + +/* hash table for standard open hashing */ + +struct hashtable { + /* HASHTABLE DATA */ + int hsize; /* size of nodes[] (number of hash values) */ + int ct; /* number of elements */ + HashNode *nodes; /* array of size hsize */ + + /* HASHTABLE METHODS */ + HashFunc hash; /* pointer to hash function for this table */ + TableFunc emptytable; /* pointer to function to empty table */ + TableFunc filltable; /* pointer to function to fill table */ + AddNodeFunc addnode; /* pointer to function to add new node */ + GetNodeFunc getnode; /* pointer to function to get an enabled node */ + GetNodeFunc getnode2; /* pointer to function to get node */ + /* (getnode2 will ignore DISABLED flag) */ + RemoveNodeFunc removenode; /* pointer to function to delete a node */ + ScanFunc disablenode; /* pointer to function to disable a node */ + ScanFunc enablenode; /* pointer to function to enable a node */ + FreeNodeFunc freenode; /* pointer to function to free a node */ + ScanFunc printnode; /* pointer to function to print a node */ + +#ifdef HASHTABLE_INTERNAL_MEMBERS + HASHTABLE_INTERNAL_MEMBERS /* internal use in hashtable.c */ +#endif +}; + +/* generic hash table node */ + +struct hashnode { + HashNode next; /* next in hash chain */ + char *nam; /* hash key */ + int flags; /* various flags */ +}; + +/* The flag to disable nodes in a hash table. Currently * + * you can disable builtins, shell functions, aliases and * + * reserved words. */ +#define DISABLED (1<<0) + +/* node in shell reserved word hash table (reswdtab) */ + +struct reswd { + HashNode next; /* next in hash chain */ + char *nam; /* name of reserved word */ + int flags; /* flags */ + int token; /* corresponding lexer token */ +}; + +/* node in alias hash table (aliastab) */ + +struct alias { + HashNode next; /* next in hash chain */ + char *nam; /* hash data */ + int flags; /* flags for alias types */ + char *text; /* expansion of alias */ + int inuse; /* alias is being expanded */ +}; + +/* is this alias global */ +#define ALIAS_GLOBAL (1<<1) + +/* node in command path hash table (cmdnamtab) */ + +struct cmdnam { + HashNode next; /* next in hash chain */ + char *nam; /* hash data */ + int flags; + union { + char **name; /* full pathname for external commands */ + char *cmd; /* file name for hashed commands */ + } + u; +}; + +/* flag for nodes explicitly added to * + * cmdnamtab with hash builtin */ +#define HASHED (1<<1) + +/* node in shell function hash table (shfunctab) */ + +struct shfunc { + HashNode next; /* next in hash chain */ + char *nam; /* name of shell function */ + int flags; /* various flags */ + List funcdef; /* function definition */ +}; + +/* node in builtin command hash table (builtintab) */ + +typedef int (*HandlerFunc) _((char *, char **, char *, int)); +#define NULLBINCMD ((HandlerFunc) 0) + +struct builtin { + HashNode next; /* next in hash chain */ + char *nam; /* name of builtin */ + int flags; /* various flags */ + HandlerFunc handlerfunc; /* pointer to function that executes this builtin */ + int minargs; /* minimum number of arguments */ + int maxargs; /* maximum number of arguments, or -1 for no limit */ + int funcid; /* xbins (see above) for overloaded handlerfuncs */ + char *optstr; /* string of legal options */ + char *defopts; /* options set by default for overloaded handlerfuncs */ +}; + +#define BUILTIN(name, flags, handler, min, max, funcid, optstr, defopts) \ + { NULL, name, flags, handler, min, max, funcid, optstr, defopts } +#define BIN_PREFIX(name, flags) \ + BUILTIN(name, flags | BINF_PREFIX, NULLBINCMD, 0, 0, 0, NULL, NULL) + +/* builtin flags */ +/* DISABLE IS DEFINED AS (1<<0) */ +#define BINF_PLUSOPTS (1<<1) /* +xyz legal */ +#define BINF_R (1<<2) /* this is the builtin `r' (fc -e -) */ +#define BINF_PRINTOPTS (1<<3) +#define BINF_ADDED (1<<4) /* is in the builtins hash table */ +#define BINF_FCOPTS (1<<5) +#define BINF_TYPEOPT (1<<6) +#define BINF_ECHOPTS (1<<7) +#define BINF_MAGICEQUALS (1<<8) /* needs automatic MAGIC_EQUAL_SUBST substitution */ +#define BINF_PREFIX (1<<9) +#define BINF_DASH (1<<10) +#define BINF_BUILTIN (1<<11) +#define BINF_COMMAND (1<<12) +#define BINF_EXEC (1<<13) +#define BINF_NOGLOB (1<<14) +#define BINF_PSPECIAL (1<<15) + +#define BINF_TYPEOPTS (BINF_TYPEOPT|BINF_PLUSOPTS) + +struct module { + char *nam; + int flags; + void *handle; + LinkList deps; +}; + +#define MOD_BUSY (1<<0) + +/* node used in parameter hash table (paramtab) */ + +struct param { + HashNode next; /* next in hash chain */ + char *nam; /* hash data */ + int flags; /* PM_* flags */ + + /* the value of this parameter */ + union { + void *data; /* used by special parameter functions */ + char **arr; /* value if declared array (PM_ARRAY) */ + char *str; /* value if declared string (PM_SCALAR) */ + long val; /* value if declared integer (PM_INTEGER) */ + } u; + + /* pointer to function to set value of this parameter */ + union { + void (*cfn) _((Param, char *)); + void (*ifn) _((Param, long)); + void (*afn) _((Param, char **)); + } sets; + + /* pointer to function to get value of this parameter */ + union { + char *(*cfn) _((Param)); + long (*ifn) _((Param)); + char **(*afn) _((Param)); + } gets; + + /* pointer to function to unset this parameter */ + void (*unsetfn) _((Param, int)); + + int ct; /* output base or field width */ + char *env; /* location in environment, if exported */ + char *ename; /* name of corresponding environment var */ + Param old; /* old struct for use with local */ + int level; /* if (old != NULL), level of localness */ +}; + +/* flags for parameters */ + +/* parameter types */ +#define PM_SCALAR 0 /* scalar */ +#define PM_ARRAY (1<<0) /* array */ +#define PM_INTEGER (1<<1) /* integer */ + +#define PM_TYPE(X) (X & (PM_SCALAR|PM_INTEGER|PM_ARRAY)) + +#define PM_LEFT (1<<2) /* left justify and remove leading blanks */ +#define PM_RIGHT_B (1<<3) /* right justify and fill with leading blanks */ +#define PM_RIGHT_Z (1<<4) /* right justify and fill with leading zeros */ +#define PM_LOWER (1<<5) /* all lower case */ + +/* The following are the same since they * + * both represent -u option to typeset */ +#define PM_UPPER (1<<6) /* all upper case */ +#define PM_UNDEFINED (1<<6) /* undefined (autoloaded) shell function */ + +#define PM_READONLY (1<<7) /* readonly */ +#define PM_TAGGED (1<<8) /* tagged */ +#define PM_EXPORTED (1<<9) /* exported */ +#define PM_UNIQUE (1<<10) /* remove duplicates */ +#define PM_SPECIAL (1<<11) /* special builtin parameter */ +#define PM_DONTIMPORT (1<<12) /* do not import this variable */ +#define PM_RESTRICTED (1<<13) /* cannot be changed in restricted mode */ +#define PM_UNSET (1<<14) /* has null value */ + +/* node for named directory hash table (nameddirtab) */ + +struct nameddir { + HashNode next; /* next in hash chain */ + char *nam; /* directory name */ + int flags; /* see below */ + char *dir; /* the directory in full */ + int diff; /* strlen(.dir) - strlen(.nam) */ +}; + +/* flags for named directories */ +/* DISABLED is defined (1<<0) */ +#define ND_USERNAME (1<<1) /* nam is actually a username */ + + +/* flags for controlling printing of hash table nodes */ +#define PRINT_NAMEONLY (1<<0) +#define PRINT_TYPE (1<<1) +#define PRINT_LIST (1<<2) + +/* flags for printing for the whence builtin */ +#define PRINT_WHENCE_CSH (1<<3) +#define PRINT_WHENCE_VERBOSE (1<<4) +#define PRINT_WHENCE_SIMPLE (1<<5) +#define PRINT_WHENCE_FUNCDEF (1<<6) +#define PRINT_WHENCE_WORD (1<<7) + +/***********************************/ +/* Definitions for history control */ +/***********************************/ + +/* history entry */ + +struct histent { + char *text; /* the history line itself */ + char *zle_text; /* the edited history line */ + time_t stim; /* command started time (datestamp) */ + time_t ftim; /* command finished time */ + short *words; /* Position of words in history */ + /* line: as pairs of start, end */ + int nwords; /* Number of words in history line */ + int flags; /* Misc flags */ +}; + +#define HIST_OLD 0x00000001 /* Command is already written to disk*/ +#define HIST_READ 0x00000002 /* Command was read back from disk*/ + +/* Parts of the code where history expansion is disabled * + * should be within a pair of STOPHIST ... ALLOWHIST */ + +#define STOPHIST (stophist += 4); +#define ALLOWHIST (stophist -= 4); + +#define HISTFLAG_DONE 1 +#define HISTFLAG_NOEXEC 2 +#define HISTFLAG_RECALL 4 + + +/******************************************/ +/* Definitions for programable completion */ +/******************************************/ + +/* Nothing special. */ +#define IN_NOTHING 0 +/* In command position. */ +#define IN_CMD 1 +/* In a mathematical environment. */ +#define IN_MATH 2 +/* In a condition. */ +#define IN_COND 3 +/* In a parameter assignment (e.g. `foo=bar'). */ +#define IN_ENV 4 + + +/******************************/ +/* Definition for zsh options */ +/******************************/ + +/* Possible values of emulation */ + +#define EMULATE_CSH (1<<1) /* C shell */ +#define EMULATE_KSH (1<<2) /* Korn shell */ +#define EMULATE_SH (1<<3) /* Bourne shell */ +#define EMULATE_ZSH (1<<4) /* `native' mode */ + +/* option indices */ + +enum { + OPT_INVALID, + ALLEXPORT, + ALWAYSLASTPROMPT, + ALWAYSTOEND, + APPENDHISTORY, + AUTOCD, + AUTOLIST, + AUTOMENU, + AUTONAMEDIRS, + AUTOPARAMKEYS, + AUTOPARAMSLASH, + AUTOPUSHD, + AUTOREMOVESLASH, + AUTORESUME, + BADPATTERN, + BANGHIST, + BAREGLOBQUAL, + BEEP, + BGNICE, + BRACECCL, + BSDECHO, + CDABLEVARS, + CHASELINKS, + CLOBBER, + COMPLETEALIASES, + COMPLETEINWORD, + CORRECT, + CORRECTALL, + CSHJUNKIEHISTORY, + CSHJUNKIELOOPS, + CSHJUNKIEQUOTES, + CSHNULLGLOB, + EQUALS, + ERREXIT, + EXECOPT, + EXTENDEDGLOB, + EXTENDEDHISTORY, + FLOWCONTROL, + FUNCTIONARGZERO, + GLOBOPT, + GLOBASSIGN, + GLOBCOMPLETE, + GLOBDOTS, + GLOBSUBST, + HASHCMDS, + HASHDIRS, + HASHLISTALL, + HISTALLOWCLOBBER, + HISTBEEP, + HISTIGNOREDUPS, + HISTIGNORESPACE, + HISTNOFUNCTIONS, + HISTNOSTORE, + HISTREDUCEBLANKS, + HISTVERIFY, + HUP, + IGNOREBRACES, + IGNOREEOF, + INTERACTIVE, + INTERACTIVECOMMENTS, + KSHARRAYS, + KSHAUTOLOAD, + KSHGLOB, + KSHOPTIONPRINT, + LISTAMBIGUOUS, + LISTBEEP, + LISTTYPES, + LOCALOPTIONS, + LOGINSHELL, + LONGLISTJOBS, + MAGICEQUALSUBST, + MAILWARNING, + MARKDIRS, + MENUCOMPLETE, + MONITOR, + MULTIOS, + NOMATCH, + NOTIFY, + NULLGLOB, + NUMERICGLOBSORT, + OVERSTRIKE, + PATHDIRS, + POSIXBUILTINS, + PRINTEIGHTBIT, + PRINTEXITVALUE, + PRIVILEGED, + PROMPTBANG, + PROMPTCR, + PROMPTPERCENT, + PROMPTSUBST, + PUSHDIGNOREDUPS, + PUSHDMINUS, + PUSHDSILENT, + PUSHDTOHOME, + RCEXPANDPARAM, + RCQUOTES, + RCS, + RECEXACT, + RESTRICTED, + RMSTARSILENT, + RMSTARWAIT, + SHFILEEXPANSION, + SHGLOB, + SHINSTDIN, + SHOPTIONLETTERS, + SHORTLOOPS, + SHWORDSPLIT, + SINGLECOMMAND, + SINGLELINEZLE, + SUNKEYBOARDHACK, + UNSET, + VERBOSE, + XTRACE, + USEZLE, + OPT_SIZE +}; + +#undef isset +#define isset(X) (opts[X]) +#define unset(X) (!opts[X]) + +#define interact (isset(INTERACTIVE)) +#define jobbing (isset(MONITOR)) +#define islogin (isset(LOGINSHELL)) + +/***********************************************/ +/* Defintions for terminal and display control */ +/***********************************************/ + +/* tty state structure */ + +struct ttyinfo { +#ifdef HAVE_TERMIOS_H + struct termios tio; +#else +# ifdef HAVE_TERMIO_H + struct termio tio; +# else + struct sgttyb sgttyb; + int lmodes; + struct tchars tchars; + struct ltchars ltchars; +# endif +#endif +#ifdef TIOCGWINSZ + struct winsize winsize; +#endif +}; + +/* defines for whether tabs expand to spaces */ +#if defined(HAVE_TERMIOS_H) || defined(HAVE_TERMIO_H) +#define SGTTYFLAG shttyinfo.tio.c_oflag +#else /* we're using sgtty */ +#define SGTTYFLAG shttyinfo.sgttyb.sg_flags +#endif +# ifdef TAB3 +#define SGTABTYPE TAB3 +# else +# ifdef OXTABS +#define SGTABTYPE OXTABS +# else +#define SGTABTYPE XTABS +# endif +# endif + +/* flags for termflags */ + +#define TERM_BAD 0x01 /* terminal has extremely basic capabilities */ +#define TERM_UNKNOWN 0x02 /* unknown terminal type */ +#define TERM_NOUP 0x04 /* terminal has no up capability */ +#define TERM_SHORT 0x08 /* terminal is < 3 lines high */ +#define TERM_NARROW 0x10 /* terminal is < 3 columns wide */ + +/* interesting termcap strings */ + +#define TCCLEARSCREEN 0 +#define TCLEFT 1 +#define TCMULTLEFT 2 +#define TCRIGHT 3 +#define TCMULTRIGHT 4 +#define TCUP 5 +#define TCMULTUP 6 +#define TCDOWN 7 +#define TCMULTDOWN 8 +#define TCDEL 9 +#define TCMULTDEL 10 +#define TCINS 11 +#define TCMULTINS 12 +#define TCCLEAREOD 13 +#define TCCLEAREOL 14 +#define TCINSLINE 15 +#define TCDELLINE 16 +#define TCNEXTTAB 17 +#define TCBOLDFACEBEG 18 +#define TCSTANDOUTBEG 19 +#define TCUNDERLINEBEG 20 +#define TCALLATTRSOFF 21 +#define TCSTANDOUTEND 22 +#define TCUNDERLINEEND 23 +#define TC_COUNT 24 + +#define tccan(X) (tclen[X]) + +#define TXTBOLDFACE 0x01 +#define TXTSTANDOUT 0x02 +#define TXTUNDERLINE 0x04 +#define TXTDIRTY 0x80 + +#define txtisset(X) (txtattrmask & (X)) +#define txtset(X) (txtattrmask |= (X)) +#define txtunset(X) (txtattrmask &= ~(X)) + +#define TXTNOBOLDFACE 0x10 +#define TXTNOSTANDOUT 0x20 +#define TXTNOUNDERLINE 0x40 + +#define txtchangeisset(X) (txtchange & (X)) +#define txtchangeset(X, Y) (txtchange |= (X), txtchange &= ~(Y)) + +/****************************************/ +/* Definitions for the %_ prompt escape */ +/****************************************/ + +#define cmdpush(X) if (!(cmdsp >= 0 && cmdsp < 256)) {;} else cmdstack[cmdsp++]=(X) +#ifdef DEBUG +# define cmdpop() if (cmdsp <= 0) { \ + fputs("BUG: cmdstack empty\n", stderr); \ + fflush(stderr); \ + } else cmdsp-- +#else +# define cmdpop() if (cmdsp <= 0) {;} else cmdsp-- +#endif + +#define CS_FOR 0 +#define CS_WHILE 1 +#define CS_REPEAT 2 +#define CS_SELECT 3 +#define CS_UNTIL 4 +#define CS_IF 5 +#define CS_IFTHEN 6 +#define CS_ELSE 7 +#define CS_ELIF 8 +#define CS_MATH 9 +#define CS_COND 10 +#define CS_CMDOR 11 +#define CS_CMDAND 12 +#define CS_PIPE 13 +#define CS_ERRPIPE 14 +#define CS_FOREACH 15 +#define CS_CASE 16 +#define CS_FUNCDEF 17 +#define CS_SUBSH 18 +#define CS_CURSH 19 +#define CS_ARRAY 20 +#define CS_QUOTE 21 +#define CS_DQUOTE 22 +#define CS_BQUOTE 23 +#define CS_CMDSUBST 24 +#define CS_MATHSUBST 25 +#define CS_ELIFTHEN 26 +#define CS_HEREDOC 27 +#define CS_HEREDOCD 28 +#define CS_BRACE 29 +#define CS_BRACEPAR 30 + +/********************* + * Memory management * + *********************/ + +#ifndef DEBUG +# define HEAPALLOC do { int nonlocal_useheap = global_heapalloc(); do + +# define PERMALLOC do { int nonlocal_useheap = global_permalloc(); do + +# define LASTALLOC while (0); \ + if (nonlocal_useheap) global_heapalloc(); \ + else global_permalloc(); \ + } while(0) + +# define LASTALLOC_RETURN \ + if ((nonlocal_useheap ? global_heapalloc() : \ + global_permalloc()), 0) {;} else return +#else +# define HEAPALLOC do { int nonlocal_useheap = global_heapalloc(); \ + alloc_stackp++; do + +# define PERMALLOC do { int nonlocal_useheap = global_permalloc(); \ + alloc_stackp++; do + +# define LASTALLOC while (0); alloc_stackp--; \ + if (nonlocal_useheap) global_heapalloc(); \ + else global_permalloc(); \ + } while(0) + +# define LASTALLOC_RETURN \ + if ((nonlocal_useheap ? global_heapalloc() : \ + global_permalloc()),alloc_stackp--,0){;}else return +#endif + +/****************/ +/* Debug macros */ +/****************/ + +#ifdef DEBUG +# define DPUTS(X,Y) if (!(X)) {;} else dputs(Y) +# define MUSTUSEHEAP(X) if (useheap) {;} else \ + fprintf(stderr, "BUG: permanent allocation in %s\n", X), \ + fflush(stderr) +#else +# define DPUTS(X,Y) +# define MUSTUSEHEAP(X) +#endif + +/**************************/ +/* Signal handling macros */ +/**************************/ + +/* These used in the sigtrapped[] array */ + +#define ZSIG_TRAPPED (1<<0) +#define ZSIG_IGNORED (1<<1) +#define ZSIG_FUNC (1<<2) + +/****************/ +/* Entry points */ +/****************/ + +/* compctl entry point pointers */ + +typedef int (*CompctlReadFn) _((char *, char **, char *, char *)); + +/* ZLE entry point pointers */ + +typedef void (*ZleVoidFn) _((void)); +typedef void (*ZleVoidIntFn) _((int)); +typedef unsigned char * (*ZleReadFn) _((char *, char *, int)); diff --git a/Src/zsh.mdd b/Src/zsh.mdd new file mode 100644 index 000000000..244029d65 --- /dev/null +++ b/Src/zsh.mdd @@ -0,0 +1,71 @@ +nozshdep=1 +alwayslink=1 + +# autobins not specified because of alwayslink + +objects="builtin.o compat.o cond.o exec.o glob.o hashtable.o \ +hist.o init.o input.o jobs.o lex.o linklist.o loop.o math.o \ +mem.o module.o options.o params.o parse.o prompt.o signals.o \ +signames.o subst.o text.o utils.o watch.o" + +headers="../config.h system.h zsh.h sigcount.h signals.h \ +prototypes.h hashtable.h ztype.h" + +:<<\Make +signames.c: signames.awk @SIGNAL_H@ + $(AWK) -f $(sdir)/signames.awk @SIGNAL_H@ > $@ + +sigcount.h: signames.c + grep 'define.*SIGCOUNT' signames.c > $@ + +init.o: bltinmods.list zshpaths.h zshxmods.h + +params.o: version.h + +version.h: $(sdir_top)/Config/version.mk + echo '#define ZSH_VERSION "'$(VERSION)'"' > $@ + +zshpaths.h: FORCE + @echo '#define MODULE_DIR "'$(MODDIR)'"' > zshpaths.h.tmp + @if cmp -s zshpaths.h zshpaths.h.tmp; then \ + rm -f zshpaths.h.tmp; \ + echo "\`zshpaths.h' is up to date." ; \ + else \ + mv -f zshpaths.h.tmp zshpaths.h; \ + echo "Updated \`zshpaths.h'." ; \ + fi + +bltinmods.list: modules.stamp modules-bltin xmods.conf mkbltnmlst.sh + srcdir='$(sdir)' MODBINS='modules-bltin' \ + XMODCF='$(sdir)/xmods.conf' $(SHELL) $(sdir)/mkbltnmlst.sh $@ + +zshxmods.h: modules-bltin xmods.conf + @echo "Creating \`$@'." + @( \ + binmods=`sed 's/^/ /;s/$$/ /' modules-bltin`; \ + for mod in `cat $(sdir_src)/xmods.conf`; do \ + case $$binmods in \ + *" $$mod "*) \ + echo "#define LINKED_XMOD_$$mod 1" ;; \ + *) echo "#ifdef DYNAMIC"; \ + echo "# define UNLINKED_XMOD_$$mod 1"; \ + echo "#endif" ;; \ + esac; \ + done; \ + echo; \ + for mod in $$binmods; do \ + echo "int boot_$$mod _((Module));"; \ + done; \ + ) > $@ + +clean-here: clean.zsh +clean.zsh: + rm -f sigcount.h signames.c bltinmods.list version.h zshpaths.h zshxmods.h + +# This is not properly part of this module, but it is built as if it were. +main.o: main.c zsh.mdh main.pro + $(CC) -c -I. $(CPPFLAGS) $(DEFS) $(CFLAGS) -o $@ $(sdir)/main.c + +main.pro: $(PROTODEPS) +proto.zsh: main.pro +Make diff --git a/Src/ztype.h b/Src/ztype.h new file mode 100644 index 000000000..595ff0588 --- /dev/null +++ b/Src/ztype.h @@ -0,0 +1,58 @@ +/* + * ztype.h - character classification macros + * + * 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. + * + */ + +#define IDIGIT (1 << 0) +#define IALNUM (1 << 1) +#define IBLANK (1 << 2) +#define INBLANK (1 << 3) +#define ITOK (1 << 4) +#define ISEP (1 << 5) +#define IALPHA (1 << 6) +#define IIDENT (1 << 7) +#define IUSER (1 << 8) +#define ICNTRL (1 << 9) +#define IWORD (1 << 10) +#define ISPECIAL (1 << 11) +#define IMETA (1 << 12) +#define IWSEP (1 << 13) +#define _icom(X,Y) (typtab[STOUC(X)] & Y) +#define idigit(X) _icom(X,IDIGIT) +#define ialnum(X) _icom(X,IALNUM) +#define iblank(X) _icom(X,IBLANK) /* blank, not including \n */ +#define inblank(X) _icom(X,INBLANK) /* blank or \n */ +#define itok(X) _icom(X,ITOK) +#define isep(X) _icom(X,ISEP) +#define ialpha(X) _icom(X,IALPHA) +#define iident(X) _icom(X,IIDENT) +#define iuser(X) _icom(X,IUSER) /* username char */ +#define icntrl(X) _icom(X,ICNTRL) +#define iword(X) _icom(X,IWORD) +#define ispecial(X) _icom(X,ISPECIAL) +#define imeta(X) _icom(X,IMETA) +#define iwsep(X) _icom(X,IWSEP) |