summaryrefslogtreecommitdiff
path: root/dotfiles/system/.zsh/modules/Src
diff options
context:
space:
mode:
Diffstat (limited to 'dotfiles/system/.zsh/modules/Src')
-rw-r--r--dotfiles/system/.zsh/modules/Src/.cvsignore35
-rw-r--r--dotfiles/system/.zsh/modules/Src/.distfiles2
-rw-r--r--dotfiles/system/.zsh/modules/Src/.exrc2
-rw-r--r--dotfiles/system/.zsh/modules/Src/.indent.pro27
-rw-r--r--dotfiles/system/.zsh/modules/Src/Makefile.in164
-rw-r--r--dotfiles/system/.zsh/modules/Src/Makemod.in.in192
-rw-r--r--dotfiles/system/.zsh/modules/Src/aloxaf/.cvsignore18
-rw-r--r--dotfiles/system/.zsh/modules/Src/aloxaf/.distfiles2
-rw-r--r--dotfiles/system/.zsh/modules/Src/aloxaf/.exrc2
-rw-r--r--dotfiles/system/.zsh/modules/Src/aloxaf/.gitignore8
-rw-r--r--dotfiles/system/.zsh/modules/Src/aloxaf/fzftab.c546
-rw-r--r--dotfiles/system/.zsh/modules/Src/aloxaf/fzftab.mdd7
-rw-r--r--dotfiles/system/.zsh/modules/Src/builtin.c7236
-rw-r--r--dotfiles/system/.zsh/modules/Src/compat.c742
-rw-r--r--dotfiles/system/.zsh/modules/Src/exec.c6250
-rw-r--r--dotfiles/system/.zsh/modules/Src/glob.c3913
-rw-r--r--dotfiles/system/.zsh/modules/Src/hashtable.c1617
-rw-r--r--dotfiles/system/.zsh/modules/Src/hashtable.h69
-rw-r--r--dotfiles/system/.zsh/modules/Src/init.c1792
-rw-r--r--dotfiles/system/.zsh/modules/Src/input.c701
-rw-r--r--dotfiles/system/.zsh/modules/Src/jobs.c2894
-rw-r--r--dotfiles/system/.zsh/modules/Src/lex.c2203
-rw-r--r--dotfiles/system/.zsh/modules/Src/loop.c795
-rw-r--r--dotfiles/system/.zsh/modules/Src/makepro.awk166
-rw-r--r--dotfiles/system/.zsh/modules/Src/mem.c1899
-rw-r--r--dotfiles/system/.zsh/modules/Src/mkbltnmlst.sh116
-rw-r--r--dotfiles/system/.zsh/modules/Src/mkmakemod.sh468
-rw-r--r--dotfiles/system/.zsh/modules/Src/module.c3641
-rw-r--r--dotfiles/system/.zsh/modules/Src/options.c955
-rw-r--r--dotfiles/system/.zsh/modules/Src/params.c5884
-rw-r--r--dotfiles/system/.zsh/modules/Src/parse.c3977
-rw-r--r--dotfiles/system/.zsh/modules/Src/pattern.c4336
-rw-r--r--dotfiles/system/.zsh/modules/Src/prompt.c2046
-rw-r--r--dotfiles/system/.zsh/modules/Src/prototypes.h134
-rw-r--r--dotfiles/system/.zsh/modules/Src/signals.c1479
-rw-r--r--dotfiles/system/.zsh/modules/Src/signals.h142
-rw-r--r--dotfiles/system/.zsh/modules/Src/signames1.awk19
-rw-r--r--dotfiles/system/.zsh/modules/Src/signames2.awk106
-rw-r--r--dotfiles/system/.zsh/modules/Src/string.c213
-rw-r--r--dotfiles/system/.zsh/modules/Src/utils.c7520
-rw-r--r--dotfiles/system/.zsh/modules/Src/wcwidth9.h1325
-rw-r--r--dotfiles/system/.zsh/modules/Src/zsh.h3305
-rw-r--r--dotfiles/system/.zsh/modules/Src/zsh.mdd147
-rw-r--r--dotfiles/system/.zsh/modules/Src/zsh.rc8
-rw-r--r--dotfiles/system/.zsh/modules/Src/zsh_system.h900
-rw-r--r--dotfiles/system/.zsh/modules/Src/ztype.h89
46 files changed, 68092 insertions, 0 deletions
diff --git a/dotfiles/system/.zsh/modules/Src/.cvsignore b/dotfiles/system/.zsh/modules/Src/.cvsignore
new file mode 100644
index 0000000..47b3191
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/.cvsignore
@@ -0,0 +1,35 @@
+*.dll
+*.epro
+*.export
+*.mdh
+*.mdh.tmp
+*.mdhi
+*.mdhs
+*.o
+*.o.c
+*.so
+*.swp
+*.syms
+Makefile
+Makemod.in Makemod
+[_a-zA-Z0-9]*.pro
+ansi2knr
+bltinmods.list
+cscope.out
+libzsh.so*
+modules-bltin
+modules.index
+modules.index.tmp
+modules.stamp
+patchlevel.h
+sigcount.h
+signames.c signames2.c
+stamp-modobjs
+stamp-modobjs.tmp
+tags TAGS
+version.h
+zsh
+zshcurses.h
+zshpaths.h
+zshterm.h
+zshxmods.h
diff --git a/dotfiles/system/.zsh/modules/Src/.distfiles b/dotfiles/system/.zsh/modules/Src/.distfiles
new file mode 100644
index 0000000..f03668b
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/.distfiles
@@ -0,0 +1,2 @@
+DISTFILES_SRC='
+'
diff --git a/dotfiles/system/.zsh/modules/Src/.exrc b/dotfiles/system/.zsh/modules/Src/.exrc
new file mode 100644
index 0000000..91d0b39
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/.exrc
@@ -0,0 +1,2 @@
+set ai
+set sw=4
diff --git a/dotfiles/system/.zsh/modules/Src/.indent.pro b/dotfiles/system/.zsh/modules/Src/.indent.pro
new file mode 100644
index 0000000..1b41514
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/.indent.pro
@@ -0,0 +1,27 @@
+--dont-format-comments
+--procnames-start-lines
+--no-parameter-indentation
+--indent-level4
+--line-comments-indentation4
+--cuddle-else
+--brace-indent0
+--dont-star-comments
+--blank-lines-after-declarations
+--blank-lines-after-procedures
+--no-blank-lines-after-commas
+--comment-indentation33
+--declaration-comment-column33
+--no-comment-delimiters-on-blank-lines
+--continuation-indentation4
+--case-indentation0
+--else-endif-column33
+--no-space-after-casts
+--no-blank-before-sizeof
+--declaration-indentation0
+--continue-at-parentheses
+--no-space-after-function-call-names
+--swallow-optional-blank-lines
+--dont-space-special-semicolon
+--tab-size8
+--line-length132
+--braces-on-if-line
diff --git a/dotfiles/system/.zsh/modules/Src/Makefile.in b/dotfiles/system/.zsh/modules/Src/Makefile.in
new file mode 100644
index 0000000..2987b64
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/Makefile.in
@@ -0,0 +1,164 @@
+#
+# 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@
+
+# source/build directories
+VPATH = @srcdir@
+sdir = @srcdir@
+sdir_top = @top_srcdir@
+INSTALL = @INSTALL@
+LN = @LN@
+
+@DEFS_MK@
+
+sdir_src = $(sdir)
+dir_src = .
+
+# ========= DEPENDENCIES FOR BUILDING ==========
+
+LINK = $(CC) $(LDFLAGS) $(EXELDFLAGS) $(EXTRA_LDFLAGS) -o $@
+DLLINK = $(DLLD) $(LDFLAGS) $(LIBLDFLAGS) $(DLLDFLAGS) -o $@
+
+all: zsh.export modules
+.PHONY: all
+
+modules: headers
+.PHONY: modules
+
+L = @L@
+
+LSTMP =
+LLIST =
+NSTMP = stamp-modobjs
+NLIST = `cat stamp-modobjs`
+
+LIBZSH = libzsh-$(VERSION).$(DL_EXT)
+NIBZSH =
+INSTLIB = @INSTLIB@
+UNINSTLIB = @UNINSTLIB@
+
+ZSH_EXPORT = $(EXPOPT)zsh.export
+ZSH_NXPORT =
+ENTRYOBJ = modentry..o
+NNTRYOBJ =
+
+LDRUNPATH = LD_RUN_PATH=$(libdir)/$(tzsh)
+NDRUNPATH =
+
+EXTRAZSHOBJS = @EXTRAZSHOBJS@
+
+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
+.PHONY: modobjs
+
+rm-modobjs-tmp:
+ rm -f stamp-modobjs.tmp
+.PHONY: rm-modobjs-tmp
+
+@CONFIG_MK@
+
+Makemod: $(CONFIG_INCS) $(dir_top)/config.modules
+ @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
+prep: Makemod
+ @$(MAKE) -f Makemod $(MAKEDEFS) prep || rm -f Makemod
+.PHONY: prep
+
+FORCE:
+.PHONY: FORCE
+
+# ========== LINKING IN MODULES ==========
+
+mymods.conf:
+ @echo Linking with the standard modules.
+
+modules: $(@E@NTRYOBJ)
+
+$(ENTRYOBJ): FORCE
+ @$(MAKE) -f Makemod $(MAKEDEFS) $@
+
+# ========== DEPENDENCIES FOR CLEANUP ==========
+
+# Since module cleanup rules depend on Makemod, they come first. This
+# forces module stuff to get cleaned before Makemod itself gets
+# deleted.
+
+mostlyclean-here:
+ rm -f stamp-modobjs stamp-modobjs.tmp
+.PHONY: mostlyclean-here
+
+clean-here:
+ rm -f modules.stamp zsh$(EXEEXT)
+ rm -f libzsh-*.$(DL_EXT)
+.PHONY: clean-here
+
+distclean-here:
+ rm -f TAGS tags
+ rm -f Makefile
+.PHONY: distclean-here
+
+mostlyclean: mostlyclean-modules
+clean: clean-modules
+distclean: distclean-modules
+realclean: realclean-modules
+.PHONY: mostlyclean clean distclean realclean
+
+# Don't remake Makemod just to delete things, even if it doesn't exist.
+mostlyclean-modules clean-modules distclean-modules realclean-modules:
+ if test -f Makemod; then \
+ $(MAKE) -f Makemod $(MAKEDEFS) `echo $@ | sed 's/-modules//'`; \
+ fi; \
+ exit 0
+.PHONY: mostlyclean-modules clean-modules distclean-modules \
+ realclean-modules
+
+@CLEAN_MK@
+
+# ========== RECURSIVE MAKES ==========
+
+modobjs modules headers proto zsh.export: Makemod
+ @$(MAKE) -f Makemod $(MAKEDEFS) $@
+.PHONY: headers proto
diff --git a/dotfiles/system/.zsh/modules/Src/Makemod.in.in b/dotfiles/system/.zsh/modules/Src/Makemod.in.in
new file mode 100644
index 0000000..ea0cdc3
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/Makemod.in.in
@@ -0,0 +1,192 @@
+#
+# 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@
+
+# source/build directories
+VPATH = @srcdir@
+sdir = @srcdir@
+sdir_top = @top_srcdir@
+INSTALL = @INSTALL@
+
+@DEFS_MK@
+
+sdir_src = $(sdir_top)/Src
+dir_src = $(dir_top)/Src
+
+# ========== COMPILATION RULES ==========
+
+DNCFLAGS =
+
+COMPILE = $(CC) -c -I. -I$(dir_top)/Src -I$(sdir_top)/Src -I$(sdir_top)/Src/Zle -I$(sdir) $(CPPFLAGS) $(DEFS) $(CFLAGS) $(D@L@CFLAGS)
+DLCOMPILE = $(CC) -c -I. -I$(dir_top)/Src -I$(sdir_top)/Src -I$(sdir_top)/Src/Zle -I$(sdir) $(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_ .syms .pro .epro
+
+.c$(ANSI@U@OBJ):
+ $(COMPILE) -o $@ $<
+ @rm -f $(dir_src)/stamp-modobjs
+
+.c$(KNR@U@OBJ):
+ @ANSI2KNR@ $< > $@.c
+ $(COMPILE) -o $@ $@.c
+ rm -f $@.c
+ @rm -f $(dir_src)/stamp-modobjs
+
+.c.$(ANSI@U@OBJ):
+ $(DLCOMPILE) -o $@ $<
+
+.c.$(KNR@U@OBJ):
+ @ANSI2KNR@ $< > $@.c
+ $(DLCOMPILE) -o $@ $@.c
+ rm -f $@.c
+
+.c.syms:
+ $(AWK) -f $(sdir_src)/makepro.awk $< $(subdir) > $@
+
+.syms.epro:
+ (echo '/* Generated automatically */'; sed -n '/^E/{s/^E//;p;}' < $<) \
+ > $@
+ (echo '/* Generated automatically */'; sed -n '/^L/{s/^L//;p;}' < $<) \
+ > `echo $@ | sed 's/\.epro$$/.pro/'`
+
+PROTODEPS = $(sdir_src)/makepro.awk
+
+# ========== DEPENDENCIES FOR BUILDING ==========
+
+all: modobjs modules
+.PHONY: all
+
+modobjs: $(MODOBJS)
+modules: $(MODULES)
+headers: $(MDHS)
+proto: $(PROTOS)
+.PHONY: modobjs modules headers proto
+
+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
+.PHONY: prep
+
+headers: $(dir_src)/modules.stamp
+$(dir_src)/modules.stamp: $(MDDS)
+ $(MAKE) -f $(makefile) $(MAKEDEFS) prep
+ echo 'timestamp for *.mdd files' > $@
+.PHONY: headers
+
+FORCE:
+.PHONY: FORCE
+
+# ========== DEPENDENCIES FOR INSTALLING ==========
+
+install: install.bin install.modules
+uninstall: uninstall.bin uninstall.modules
+.PHONY: install uninstall
+
+install.bin: install.bin-here
+uninstall.bin: uninstall.bin-here
+install.modules: install.modules-here
+uninstall.modules: uninstall.modules-here
+.PHONY: install.bin uninstall.bin install.modules uninstall.modules
+
+install.bin-here uninstall.bin-here:
+install.modules-here uninstall.modules-here:
+.PHONY: install.bin-here install.modules-here
+
+# ========== DEPENDENCIES FOR CLEANUP ==========
+
+@CLEAN_MK@
+
+mostlyclean-here:
+ rm -f *.o *.export *.$(DL_EXT)
+.PHONY: mostlyclean-here
+
+clean-here:
+ rm -f *.o.c *.syms *.pro *.epro *.mdh *.mdhi *.mdhs *.mdh.tmp
+.PHONY: clean-here
+
+distclean-here:
+ rm -f $(makefile) $(makefile).in
+.PHONY: distclean-here
+
+# ========== 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_top)/config.modules
+ @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)
+
diff --git a/dotfiles/system/.zsh/modules/Src/aloxaf/.cvsignore b/dotfiles/system/.zsh/modules/Src/aloxaf/.cvsignore
new file mode 100644
index 0000000..f72db84
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/aloxaf/.cvsignore
@@ -0,0 +1,18 @@
+Makefile
+Makefile.in
+*.export
+so_locations
+*.pro
+*.epro
+*.syms
+*.o
+*.o.c
+*.so
+*.mdh
+*.mdhi
+*.mdhs
+*.mdh.tmp
+*.swp
+errnames.c errcount.h
+*.dll
+curses_keys.h
diff --git a/dotfiles/system/.zsh/modules/Src/aloxaf/.distfiles b/dotfiles/system/.zsh/modules/Src/aloxaf/.distfiles
new file mode 100644
index 0000000..f03668b
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/aloxaf/.distfiles
@@ -0,0 +1,2 @@
+DISTFILES_SRC='
+'
diff --git a/dotfiles/system/.zsh/modules/Src/aloxaf/.exrc b/dotfiles/system/.zsh/modules/Src/aloxaf/.exrc
new file mode 100644
index 0000000..91d0b39
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/aloxaf/.exrc
@@ -0,0 +1,2 @@
+set ai
+set sw=4
diff --git a/dotfiles/system/.zsh/modules/Src/aloxaf/.gitignore b/dotfiles/system/.zsh/modules/Src/aloxaf/.gitignore
new file mode 100644
index 0000000..92f708e
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/aloxaf/.gitignore
@@ -0,0 +1,8 @@
+Makefile.in
+*.epro
+*.export
+*.mdh
+*.mdhi
+*.mdhs
+*.pro
+*.syms
diff --git a/dotfiles/system/.zsh/modules/Src/aloxaf/fzftab.c b/dotfiles/system/.zsh/modules/Src/aloxaf/fzftab.c
new file mode 100644
index 0000000..60b6330
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/aloxaf/fzftab.c
@@ -0,0 +1,546 @@
+#include "fzftab.mdh"
+#include "fzftab.pro"
+#include <malloc.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+const char* get_color(char* file, const struct stat* sb);
+const char* get_suffix(char* file, const struct stat* sb);
+const char* colorize_from_mode(char* file, const struct stat* sb);
+const char* colorize_from_name(char* file);
+char** fzf_tab_colorize(char* file);
+int compile_patterns(char* nam, char** list_colors);
+char* ftb_strcat(char* dst, int n, ...);
+
+/* Indixes into the terminal string arrays. */
+#define COL_NO 0
+#define COL_FI 1
+#define COL_DI 2
+#define COL_LN 3
+#define COL_PI 4
+#define COL_SO 5
+#define COL_BD 6
+#define COL_CD 7
+#define COL_OR 8
+#define COL_MI 9
+#define COL_SU 10
+#define COL_SG 11
+#define COL_TW 12
+#define COL_OW 13
+#define COL_ST 14
+#define COL_EX 15
+#define COL_LC 16
+#define COL_RC 17
+#define COL_EC 18
+#define COL_TC 19
+#define COL_SP 20
+#define COL_MA 21
+#define COL_HI 22
+#define COL_DU 23
+#define COL_SA 24
+
+#define NUM_COLS 25
+
+/* Names of the terminal strings. */
+static char* colnames[] = { "no", "fi", "di", "ln", "pi", "so", "bd", "cd", "or", "mi", "su", "sg",
+ "tw", "ow", "st", "ex", "lc", "rc", "ec", "tc", "sp", "ma", "hi", "du", "sa", NULL };
+
+/* Default values. */
+static char* defcols[]
+ = { "0", "0", "1;31", "1;36", "33", "1;35", "1;33", "1;33", NULL, NULL, "37;41", "30;43",
+ "30;42", "34;42", "37;44", "1;32", "\033[", "m", NULL, "0", "0", "7", NULL, NULL, "0" };
+
+static char* fzf_tab_module_version;
+
+struct pattern {
+ Patprog pat;
+ char color[50];
+};
+
+static int opt_list_type = OPT_INVALID;
+static int pat_cnt = 0;
+static struct pattern* name_color = NULL;
+static char mode_color[NUM_COLS][20];
+
+// TODO: use ZLS_COLORS ?
+int compile_patterns(char* nam, char** list_colors)
+{
+ // clean old name_color and set pat_cnt = 0
+ if (pat_cnt != 0) {
+ while (--pat_cnt) {
+ freepatprog(name_color[pat_cnt].pat);
+ }
+ free(name_color);
+ }
+ // initialize mode_color with default value
+ for (int i = 0; i < NUM_COLS; i++) {
+ if (defcols[i]) {
+ strcpy(mode_color[i], defcols[i]);
+ }
+ }
+ // empty array, just return
+ if (list_colors == NULL) {
+ return 0;
+ }
+
+ // how many pattens?
+ while (list_colors[pat_cnt] != NULL) {
+ pat_cnt++;
+ }
+ name_color = zshcalloc(pat_cnt * sizeof(struct pattern));
+
+ for (int i = 0; i < pat_cnt; i++) {
+ char* pat = ztrdup(list_colors[i]);
+ char* color = strrchr(pat, '=');
+ if (color == NULL)
+ continue;
+ *color = '\0';
+ tokenize(pat);
+ remnulargs(pat);
+
+ // mode=color
+ bool skip = false;
+ for (int j = 0; j < NUM_COLS; j++) {
+ if (strpfx(colnames[j], list_colors[i])) {
+ strcpy(mode_color[j], color + 1);
+ name_color[i].pat = NULL;
+ skip = true;
+ }
+ }
+ if (skip) {
+ continue;
+ }
+
+ // name=color
+ if (!(name_color[i].pat = patcompile(pat, PAT_ZDUP, NULL))) {
+ zwarnnam(nam, "bad pattern: %s", list_colors[i]);
+ return 1;
+ }
+
+ strcpy(name_color[i].color, color + 1);
+ free(pat);
+ }
+ return 0;
+}
+
+// TODO: use zsh mod_export function `file_type` ?
+const char* get_suffix(char* file, const struct stat* sb)
+{
+ struct stat sb2;
+ mode_t filemode = sb->st_mode;
+
+ 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))
+ if (strpfx(mode_color[COL_LN], "target")) {
+ if (stat(file, &sb2) == -1) {
+ return "@";
+ }
+ return get_suffix(file, &sb2);
+ } else {
+ return "@";
+ }
+ else if (S_ISREG(filemode))
+ return (filemode & S_IXUGO) ? "*" : "";
+ else if (S_ISSOCK(filemode))
+ return "=";
+ else
+ return "?";
+}
+
+const char* get_color(char* file, const struct stat* sb)
+{
+ // no list-colors, return empty color
+ if (pat_cnt == 0) {
+ return "";
+ }
+ const char* ret;
+ if ((ret = colorize_from_mode(file, sb)) || (ret = colorize_from_name(file))) {
+ return ret;
+ }
+ return mode_color[COL_FI];
+}
+
+const char* colorize_from_name(char* file)
+{
+ for (int i = 0; i < pat_cnt; i++) {
+ if (name_color && name_color[i].pat && pattry(name_color[i].pat, file)) {
+ return name_color[i].color;
+ }
+ }
+ return NULL;
+}
+
+const char* colorize_from_mode(char* file, const struct stat* sb)
+{
+ struct stat sb2;
+
+ switch (sb->st_mode & S_IFMT) {
+ case S_IFDIR:
+ return mode_color[COL_DI];
+ case S_IFLNK: {
+ if (strpfx(mode_color[COL_LN], "target")) {
+ if (stat(file, &sb2) == -1) {
+ return mode_color[COL_OR];
+ }
+ return get_color(file, &sb2);
+ }
+ }
+ case S_IFIFO:
+ return mode_color[COL_PI];
+ case S_IFSOCK:
+ return mode_color[COL_SO];
+ case S_IFBLK:
+ return mode_color[COL_BD];
+ case S_IFCHR:
+ return mode_color[COL_CD];
+ default:
+ break;
+ }
+
+ if (sb->st_mode & S_ISUID) {
+ return mode_color[COL_SU];
+ } else if (sb->st_mode & S_ISGID) {
+ return mode_color[COL_SG];
+ // tw ow st ??
+ } else if (sb->st_mode & S_IXUSR) {
+ return mode_color[COL_EX];
+ }
+
+ /* Check for suffix alias */
+ size_t len = strlen(file);
+ /* shortest valid suffix format is a.b */
+ if (len > 2) {
+ const char* suf = file + len - 1;
+ while (suf > file + 1) {
+ if (suf[-1] == '.') {
+ if (sufaliastab->getnode(sufaliastab, suf)) {
+ return mode_color[COL_SA];
+ }
+ break;
+ }
+ suf--;
+ }
+ }
+
+ return NULL;
+}
+
+struct {
+ char** array;
+ size_t len;
+ size_t size;
+} ftb_compcap;
+
+// Usage:
+// initialize fzf-tab-generate-compcap -i
+// output to _ftb_compcap fzf-tab-generate-compcap -o
+// add a entry fzf-tab-generate-compcap word desc opts
+static int bin_fzf_tab_compcap_generate(char* nam, char** args, Options ops, UNUSED(int func))
+{
+ if (OPT_ISSET(ops, 'o')) {
+ // write final result to _ftb_compcap
+ setaparam("_ftb_compcap", ftb_compcap.array);
+ ftb_compcap.array = NULL;
+ return 0;
+ } else if (OPT_ISSET(ops, 'i')) {
+ // init
+ if (ftb_compcap.array)
+ freearray(ftb_compcap.array);
+ ftb_compcap.array = zshcalloc(1024 * sizeof(char*));
+ ftb_compcap.len = 0, ftb_compcap.size = 1024;
+ return 0;
+ }
+ if (ftb_compcap.array == NULL) {
+ zwarnnam(nam, "please initialize it first");
+ return 1;
+ }
+
+ // get paramaters
+ char **words = getaparam(args[0]), **dscrs = getaparam(args[1]), *opts = getsparam(args[2]);
+ if (!words || !dscrs || !opts) {
+ zwarnnam(nam, "wrong argument");
+ return 1;
+ }
+
+ char *bs = metafy("\2", 1, META_DUP), *nul = metafy("\0word\0", 6, META_DUP);
+ size_t dscrs_cnt = arrlen(dscrs);
+
+ for (int i = 0; words[i]; i++) {
+ // TODO: replace '\n'
+ char* buffer = zshcalloc(256 * sizeof(char));
+ char* dscr = i < dscrs_cnt ? dscrs[i] : words[i];
+
+ buffer = ftb_strcat(buffer, 5, dscr, bs, opts, nul, words[i]);
+ ftb_compcap.array[ftb_compcap.len++] = buffer;
+
+ if (ftb_compcap.len == ftb_compcap.size) {
+ ftb_compcap.array
+ = zrealloc(ftb_compcap.array, (1024 + ftb_compcap.size) * sizeof(char*));
+ ftb_compcap.size += 1024;
+ memset(ftb_compcap.array + ftb_compcap.size - 1024, 0, 1024 * sizeof(char*));
+ }
+ }
+
+ zsfree(bs);
+ zsfree(nul);
+
+ return 0;
+}
+
+// cat n string, return the pointer to the new string
+// `size` is the size of dst
+// dst will be reallocated if is not big enough
+char* ftb_strcat(char* dst, int n, ...)
+{
+ va_list valist;
+ va_start(valist, n);
+
+ char* final = dst;
+#ifdef __linux
+ size_t size = malloc_usable_size(dst);
+#elif __APPLE__
+ size_t size = malloc_size(dst);
+#endif
+ size_t max_len = size - 1;
+
+ for (int i = 0, idx = 0; i < n; i++) {
+ char* src = va_arg(valist, char*);
+ for (; *src != '\0'; dst++, src++, idx++) {
+ if (idx == max_len) {
+ size += size / 2;
+ final = zrealloc(final, size);
+ max_len = size - 1;
+ dst = &final[idx];
+ }
+ *dst = *src;
+ }
+ }
+ *dst = '\0';
+ va_end(valist);
+
+ return final;
+}
+
+// accept an
+char** fzf_tab_colorize(char* file)
+{
+ // TODO: can avoid so many zalloc here?
+ file = unmeta(file);
+
+ struct stat sb;
+ if (lstat(file, &sb) == -1) {
+ return NULL;
+ }
+
+ const char* suffix = "";
+ if (isset(opt_list_type)) {
+ suffix = get_suffix(file, &sb);
+ }
+ const char* color = get_color(file, &sb);
+
+ char* symlink = "";
+ const char* symcolor = "";
+ if ((sb.st_mode & S_IFMT) == S_IFLNK) {
+ symlink = zalloc(PATH_MAX);
+ int end = readlink(file, symlink, PATH_MAX);
+ symlink[end] = '\0';
+ if (stat(file, &sb) == -1) {
+ symcolor = mode_color[COL_OR];
+ } else {
+ char tmp[PATH_MAX];
+ realpath(file, tmp);
+ symcolor = get_color(file, &sb);
+ }
+ }
+
+ char** reply = zshcalloc((4 + 1) * sizeof(char*));
+
+ if (strlen(color) != 0) {
+ reply[0] = zalloc(128);
+ reply[1] = zalloc(128);
+ sprintf(reply[0], "%s%s%s", mode_color[COL_LC], color, mode_color[COL_RC]);
+ sprintf(reply[1], "%s%s%s", mode_color[COL_LC], "0", mode_color[COL_RC]);
+ } else {
+ reply[0] = ztrdup("");
+ reply[1] = ztrdup("");
+ }
+
+ reply[2] = ztrdup(suffix);
+
+ if (symlink[0] != '\0') {
+ reply[3] = zalloc(PATH_MAX);
+ sprintf(reply[3], "%s%s%s%s%s%s%s", mode_color[COL_LC], symcolor, mode_color[COL_RC],
+ symlink, mode_color[COL_LC], "0", mode_color[COL_RC]);
+ free(symlink);
+ } else {
+ reply[3] = ztrdup("");
+ }
+ for (int i = 0; i < 4; i++) {
+ reply[i] = metafy(reply[i], -1, META_REALLOC);
+ }
+
+ return reply;
+}
+
+static int bin_fzf_tab_candidates_generate(char* nam, char** args, Options ops, UNUSED(int func))
+{
+ if (OPT_ISSET(ops, 'i')) {
+ // compile list_colors pattern
+ if (*args == NULL) {
+ zwarnnam(nam, "please specify an array");
+ return 1;
+ } else {
+ char** array = getaparam(*args);
+ return compile_patterns(nam, array);
+ }
+ }
+
+ char **ftb_compcap = getaparam("_ftb_compcap"), **group_colors = getaparam("group_colors"),
+ *default_color = getsparam("default_color"), *prefix = getsparam("prefix");
+
+ size_t ftb_compcap_len = arrlen(ftb_compcap);
+
+ char *bs = metafy("\b", 1, META_DUP), *nul = metafy("\0", 1, META_DUP),
+ *soh = metafy("\2", 1, META_DUP);
+
+ char **candidates = zshcalloc(sizeof(char*) * (ftb_compcap_len + 1)),
+ *filepath = zshcalloc(PATH_MAX * sizeof(char)), *dpre = zshcalloc(128),
+ *dsuf = zshcalloc(128);
+
+ char* first_word = zshcalloc(512 * sizeof(char));
+ int same_word = 1;
+
+ for (int i = 0; i < ftb_compcap_len; i++) {
+ char *word = "", *group = NULL, *realdir = NULL;
+ strcpy(dpre, "");
+ strcpy(dsuf, "");
+
+ // extract all the variables what we need
+ char** compcap = sepsplit(ftb_compcap[i], soh, 1, 0);
+ char* desc = compcap[0];
+ char** info = sepsplit(compcap[1], nul, 1, 0);
+
+ for (int j = 0; info[j]; j += 2) {
+ if (!strcmp(info[j], "word")) {
+ word = info[j + 1];
+ // unquote word
+ parse_subst_string(word);
+ remnulargs(word);
+ untokenize(word);
+ } else if (!strcmp(info[j], "group")) {
+ group = info[j + 1];
+ } else if (!strcmp(info[j], "realdir")) {
+ realdir = info[j + 1];
+ }
+ }
+
+ // check if all the words are the same
+ if (i == 0) {
+ first_word = ftb_strcat(first_word, 1, word);
+ } else if (same_word && strcmp(word, first_word) != 0) {
+ same_word = 0;
+ }
+
+ // add character and color to describe the type of the files
+ if (realdir) {
+ filepath = ftb_strcat(filepath, 2, realdir, word);
+ char** reply = fzf_tab_colorize(filepath);
+ if (reply != NULL) {
+ dpre = ftb_strcat(dpre, 2, reply[1], reply[0]);
+ if (reply[3][0] != '\0') {
+ dsuf = ftb_strcat(dsuf, 4, reply[1], reply[2], " -> ", reply[3]);
+ } else {
+ dsuf = ftb_strcat(dsuf, 2, reply[1], reply[2]);
+ }
+ if (dpre[0] != '\0') {
+ setiparam("colorful", 1);
+ }
+ freearray(reply);
+ }
+ }
+
+ char* result = zshcalloc(256 * sizeof(char));
+
+ // add color to description if they have group index
+ if (group) {
+ // use strtol ?
+ char* color = group_colors[atoi(group) - 1];
+ // add prefix
+ result = ftb_strcat(
+ result, 11, group, bs, color, prefix, dpre, nul, group, bs, desc, nul, dsuf);
+ } else {
+ result = ftb_strcat(result, 6, default_color, dpre, nul, desc, nul, dsuf);
+ }
+ // quotedzputs(result, stdout);
+ // putchar('\n');
+ candidates[i] = result;
+
+ freearray(info);
+ freearray(compcap);
+ }
+
+ setaparam("tcandidates", candidates);
+ setiparam("same_word", same_word);
+ zsfree(dpre);
+ zsfree(dsuf);
+ zsfree(filepath);
+ zsfree(first_word);
+
+ return 0;
+}
+
+static struct builtin bintab[] = {
+ BUILTIN("fzf-tab-compcap-generate", 0, bin_fzf_tab_compcap_generate, 0, -1, 0, "io", NULL),
+ BUILTIN("fzf-tab-candidates-generate", 0, bin_fzf_tab_candidates_generate, 0, -1, 0, "i", NULL),
+};
+
+static struct paramdef patab[] = {
+ STRPARAMDEF("FZF_TAB_MODULE_VERSION", &fzf_tab_module_version),
+};
+
+// clang-format off
+static struct features module_features = {
+ bintab, sizeof(bintab) / sizeof(*bintab),
+ NULL, 0,
+ NULL, 0,
+ patab, sizeof(patab) / sizeof(*patab),
+ 0,
+};
+// clang-format on
+
+int setup_(UNUSED(Module m)) { return 0; }
+
+int features_(Module m, char*** features)
+{
+ *features = featuresarray(m, &module_features);
+ return 0;
+}
+
+int enables_(Module m, int** enables) { return handlefeatures(m, &module_features, enables); }
+
+int boot_(UNUSED(Module m))
+{
+ fzf_tab_module_version = ztrdup("0.2.2");
+ // different zsh version may have different value of list_type's index
+ // so query it dynamically
+ opt_list_type = optlookup("list_types");
+ return 0;
+}
+
+int cleanup_(UNUSED(Module m)) { return setfeatureenables(m, &module_features, NULL); }
+
+int finish_(UNUSED(Module m))
+{
+ printf("fzf-tab module unloaded.\n");
+ fflush(stdout);
+ return 0;
+}
diff --git a/dotfiles/system/.zsh/modules/Src/aloxaf/fzftab.mdd b/dotfiles/system/.zsh/modules/Src/aloxaf/fzftab.mdd
new file mode 100644
index 0000000..371bb95
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/aloxaf/fzftab.mdd
@@ -0,0 +1,7 @@
+name=aloxaf/fzftab
+link=dynamic
+load=no
+
+autofeatures="b:fzf-tab-colorize p:FZF_TAB_MODULE_VERSION"
+
+objects="fzftab.o"
diff --git a/dotfiles/system/.zsh/modules/Src/builtin.c b/dotfiles/system/.zsh/modules/Src/builtin.c
new file mode 100644
index 0000000..93fa911
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/builtin.c
@@ -0,0 +1,7236 @@
+/*
+ * builtin.c - builtin commands
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+/* this is defined so we get the prototype for open_memstream */
+#define _GNU_SOURCE 1
+
+#include "zsh.mdh"
+#include "builtin.pro"
+
+/* Builtins in the main executable */
+
+static struct builtin builtins[] =
+{
+ BIN_PREFIX("-", BINF_DASH),
+ BIN_PREFIX("builtin", BINF_BUILTIN),
+ BIN_PREFIX("command", BINF_COMMAND),
+ BIN_PREFIX("exec", BINF_EXEC),
+ BIN_PREFIX("noglob", BINF_NOGLOB),
+ BUILTIN("[", BINF_HANDLES_OPTS, bin_test, 0, -1, BIN_BRACKET, NULL, NULL),
+ BUILTIN(".", BINF_PSPECIAL, bin_dot, 1, -1, 0, NULL, NULL),
+ BUILTIN(":", BINF_PSPECIAL, bin_true, 0, -1, 0, NULL, NULL),
+ BUILTIN("alias", BINF_MAGICEQUALS | BINF_PLUSOPTS, bin_alias, 0, -1, 0, "Lgmrs", NULL),
+ BUILTIN("autoload", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "dmktrRTUwWXz", "u"),
+ BUILTIN("bg", 0, bin_fg, 0, -1, BIN_BG, NULL, NULL),
+ BUILTIN("break", BINF_PSPECIAL, bin_break, 0, 1, BIN_BREAK, NULL, NULL),
+ BUILTIN("bye", 0, bin_break, 0, 1, BIN_EXIT, NULL, NULL),
+ BUILTIN("cd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 2, BIN_CD, "qsPL", NULL),
+ BUILTIN("chdir", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 2, BIN_CD, "qsPL", NULL),
+ BUILTIN("continue", BINF_PSPECIAL, bin_break, 0, 1, BIN_CONTINUE, NULL, NULL),
+ BUILTIN("declare", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%klmp:%rtuxz", NULL),
+ BUILTIN("dirs", 0, bin_dirs, 0, -1, 0, "clpv", NULL),
+ BUILTIN("disable", 0, bin_enable, 0, -1, BIN_DISABLE, "afmprs", NULL),
+ BUILTIN("disown", 0, bin_fg, 0, -1, BIN_DISOWN, NULL, NULL),
+ BUILTIN("echo", BINF_SKIPINVALID, bin_print, 0, -1, BIN_ECHO, "neE", "-"),
+ BUILTIN("emulate", 0, bin_emulate, 0, -1, 0, "lLR", NULL),
+ BUILTIN("enable", 0, bin_enable, 0, -1, BIN_ENABLE, "afmprs", NULL),
+ BUILTIN("eval", BINF_PSPECIAL, bin_eval, 0, -1, BIN_EVAL, NULL, NULL),
+ BUILTIN("exit", BINF_PSPECIAL, bin_break, 0, 1, BIN_EXIT, NULL, NULL),
+ BUILTIN("export", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "E:%F:%HL:%R:%TUZ:%afhi:%lp:%rtu", "xg"),
+ BUILTIN("false", 0, bin_false, 0, -1, 0, NULL, NULL),
+ /*
+ * We used to behave as if the argument to -e was optional.
+ * But that's actually not useful, so it's more consistent to
+ * cause an error.
+ */
+ BUILTIN("fc", 0, bin_fc, 0, -1, BIN_FC, "aAdDe:EfiIlLmnpPrRt:W", NULL),
+ BUILTIN("fg", 0, bin_fg, 0, -1, BIN_FG, NULL, NULL),
+ BUILTIN("float", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "E:%F:%HL:%R:%Z:%ghlp:%rtux", "E"),
+ BUILTIN("functions", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "kmMstTuUWx:z", NULL),
+ BUILTIN("getln", 0, bin_read, 0, -1, 0, "ecnAlE", "zr"),
+ BUILTIN("getopts", 0, bin_getopts, 2, -1, 0, NULL, NULL),
+ BUILTIN("hash", BINF_MAGICEQUALS, bin_hash, 0, -1, 0, "Ldfmrv", NULL),
+
+#ifdef ZSH_HASH_DEBUG
+ BUILTIN("hashinfo", 0, bin_hashinfo, 0, 0, 0, NULL, NULL),
+#endif
+
+ BUILTIN("history", 0, bin_fc, 0, -1, BIN_FC, "adDEfiLmnpPrt:", "l"),
+ BUILTIN("integer", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "HL:%R:%Z:%ghi:%lp:%rtux", "i"),
+ BUILTIN("jobs", 0, bin_fg, 0, -1, BIN_JOBS, "dlpZrs", NULL),
+ BUILTIN("kill", BINF_HANDLES_OPTS, bin_kill, 0, -1, 0, NULL, NULL),
+ BUILTIN("let", 0, bin_let, 1, -1, 0, NULL, NULL),
+ BUILTIN("local", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%ahi:%lp:%rtux", NULL),
+ BUILTIN("log", 0, bin_log, 0, 0, 0, NULL, NULL),
+ BUILTIN("logout", 0, bin_break, 0, 1, BIN_LOGOUT, NULL, NULL),
+
+#if defined(ZSH_MEM) & defined(ZSH_MEM_DEBUG)
+ BUILTIN("mem", 0, bin_mem, 0, 0, 0, "v", NULL),
+#endif
+
+#if defined(ZSH_PAT_DEBUG)
+ BUILTIN("patdebug", 0, bin_patdebug, 1, -1, 0, "p", NULL),
+#endif
+
+ BUILTIN("popd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 1, BIN_POPD, "q", NULL),
+ BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "abcC:Df:ilmnNoOpPrRsSu:v:x:X:z-", NULL),
+ BUILTIN("printf", BINF_SKIPINVALID | BINF_SKIPDASH, bin_print, 1, -1, BIN_PRINTF, "v:", NULL),
+ BUILTIN("pushd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 2, BIN_PUSHD, "qsPL", NULL),
+ BUILTIN("pushln", 0, bin_print, 0, -1, BIN_PRINT, NULL, "-nz"),
+ BUILTIN("pwd", 0, bin_pwd, 0, 0, 0, "rLP", NULL),
+ BUILTIN("r", 0, bin_fc, 0, -1, BIN_R, "IlLnr", NULL),
+ BUILTIN("read", 0, bin_read, 0, -1, 0, "cd:ek:%lnpqrst:%zu:AE", NULL),
+ BUILTIN("readonly", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, BIN_READONLY, "AE:%F:%HL:%R:%TUZ:%afghi:%lptux", "r"),
+ BUILTIN("rehash", 0, bin_hash, 0, 0, 0, "df", "r"),
+ BUILTIN("return", BINF_PSPECIAL, bin_break, 0, 1, BIN_RETURN, NULL, NULL),
+ BUILTIN("set", BINF_PSPECIAL | BINF_HANDLES_OPTS, bin_set, 0, -1, 0, NULL, NULL),
+ BUILTIN("setopt", 0, bin_setopt, 0, -1, BIN_SETOPT, NULL, NULL),
+ BUILTIN("shift", BINF_PSPECIAL, bin_shift, 0, -1, 0, "p", NULL),
+ BUILTIN("source", BINF_PSPECIAL, bin_dot, 1, -1, 0, NULL, NULL),
+ BUILTIN("suspend", 0, bin_suspend, 0, 0, 0, "f", NULL),
+ BUILTIN("test", BINF_HANDLES_OPTS, bin_test, 0, -1, BIN_TEST, NULL, NULL),
+ BUILTIN("ttyctl", 0, bin_ttyctl, 0, 0, 0, "fu", NULL),
+ BUILTIN("times", BINF_PSPECIAL, bin_times, 0, 0, 0, NULL, NULL),
+ BUILTIN("trap", BINF_PSPECIAL | BINF_HANDLES_OPTS, bin_trap, 0, -1, 0, NULL, NULL),
+ BUILTIN("true", 0, bin_true, 0, -1, 0, NULL, NULL),
+ BUILTIN("type", 0, bin_whence, 0, -1, 0, "ampfsSw", "v"),
+ BUILTIN("typeset", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%klp:%rtuxmz", NULL),
+ BUILTIN("umask", 0, bin_umask, 0, 1, 0, "S", NULL),
+ BUILTIN("unalias", 0, bin_unhash, 0, -1, BIN_UNALIAS, "ams", NULL),
+ BUILTIN("unfunction", 0, bin_unhash, 1, -1, BIN_UNFUNCTION, "m", "f"),
+ BUILTIN("unhash", 0, bin_unhash, 1, -1, BIN_UNHASH, "adfms", NULL),
+ BUILTIN("unset", BINF_PSPECIAL, bin_unset, 1, -1, BIN_UNSET, "fmv", NULL),
+ BUILTIN("unsetopt", 0, bin_setopt, 0, -1, BIN_UNSETOPT, NULL, NULL),
+ BUILTIN("wait", 0, bin_fg, 0, -1, BIN_WAIT, NULL, NULL),
+ BUILTIN("whence", 0, bin_whence, 0, -1, 0, "acmpvfsSwx:", NULL),
+ BUILTIN("where", 0, bin_whence, 0, -1, 0, "pmsSwx:", "ca"),
+ BUILTIN("which", 0, bin_whence, 0, -1, 0, "ampsSwx:", "c"),
+ BUILTIN("zmodload", 0, bin_zmodload, 0, -1, 0, "AFRILP:abcfdilmpsue", NULL),
+ BUILTIN("zcompile", 0, bin_zcompile, 0, -1, 0, "tUMRcmzka", NULL),
+};
+
+/****************************************/
+/* Builtin Command Hash Table Functions */
+/****************************************/
+
+/* hash table containing builtin commands */
+
+/**/
+mod_export HashTable builtintab;
+
+/**/
+void
+createbuiltintable(void)
+{
+ builtintab = newhashtable(85, "builtintab", NULL);
+
+ builtintab->hash = hasher;
+ builtintab->emptytable = NULL;
+ builtintab->filltable = NULL;
+ builtintab->cmpnodes = strcmp;
+ builtintab->addnode = addhashnode;
+ builtintab->getnode = gethashnode;
+ builtintab->getnode2 = gethashnode2;
+ builtintab->removenode = removehashnode;
+ builtintab->disablenode = disablehashnode;
+ builtintab->enablenode = enablehashnode;
+ builtintab->freenode = freebuiltinnode;
+ builtintab->printnode = printbuiltinnode;
+
+ (void)addbuiltins("zsh", builtins, sizeof(builtins)/sizeof(*builtins));
+}
+
+/* Print a builtin */
+
+/**/
+static void
+printbuiltinnode(HashNode hn, int printflags)
+{
+ Builtin bn = (Builtin) hn;
+
+ if (printflags & PRINT_WHENCE_WORD) {
+ printf("%s: builtin\n", bn->node.nam);
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_CSH) {
+ printf("%s: shell built-in command\n", bn->node.nam);
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_VERBOSE) {
+ printf("%s is a shell builtin\n", bn->node.nam);
+ return;
+ }
+
+ /* default is name only */
+ printf("%s\n", bn->node.nam);
+}
+
+/**/
+static void
+freebuiltinnode(HashNode hn)
+{
+ Builtin bn = (Builtin) hn;
+
+ if(!(bn->node.flags & BINF_ADDED)) {
+ zsfree(bn->node.nam);
+ zsfree(bn->optstr);
+ zfree(bn, sizeof(struct builtin));
+ }
+}
+
+/**/
+void
+init_builtins(void)
+{
+ if (!EMULATION(EMULATE_ZSH)) {
+ HashNode hn = reswdtab->getnode2(reswdtab, "repeat");
+ if (hn)
+ reswdtab->disablenode(hn, 0);
+ }
+}
+
+/* Make sure we have space for a new option and increment. */
+
+#define OPT_ALLOC_CHUNK 16
+
+/**/
+static int
+new_optarg(Options ops)
+{
+ /* Argument index must be a non-zero 6-bit number. */
+ if (ops->argscount == 63)
+ return 1;
+ if (ops->argsalloc == ops->argscount) {
+ char **newptr =
+ (char **)zhalloc((ops->argsalloc + OPT_ALLOC_CHUNK) *
+ sizeof(char *));
+ if (ops->argsalloc)
+ memcpy(newptr, ops->args, ops->argsalloc * sizeof(char *));
+ ops->args = newptr;
+ ops->argsalloc += OPT_ALLOC_CHUNK;
+ }
+ ops->argscount++;
+ return 0;
+}
+
+
+/* execute a builtin handler function after parsing the arguments */
+
+/**/
+int
+execbuiltin(LinkList args, LinkList assigns, Builtin bn)
+{
+ char *pp, *name, *optstr;
+ int flags, argc, execop, xtr = isset(XTRACE);
+ struct options ops;
+
+ /* initialise options structure */
+ memset(ops.ind, 0, MAX_OPS*sizeof(unsigned char));
+ ops.args = NULL;
+ ops.argscount = ops.argsalloc = 0;
+
+ /* initialize some local variables */
+ name = (char *) ugetnode(args);
+
+ if (!bn->handlerfunc) {
+ DPUTS(1, "Missing builtin detected too late");
+ deletebuiltin(bn->node.nam);
+ return 1;
+ }
+ /* get some information about the command */
+ flags = bn->node.flags;
+ optstr = bn->optstr;
+
+ /* Set up the argument list. */
+ /* count the arguments */
+ argc = countlinknodes(args);
+
+ {
+ /*
+ * Keep all arguments, including options, in an array.
+ * We don't actually need the option part of the argument
+ * after option processing, but it makes XTRACE output
+ * much simpler.
+ */
+ VARARR(char *, argarr, argc + 1);
+ char **argv;
+
+ /*
+ * Get the actual arguments, into argv. Remember argarr
+ * may be an array declaration, depending on the compiler.
+ */
+ argv = argarr;
+ while ((*argv++ = (char *)ugetnode(args)));
+ argv = argarr;
+
+ /* Sort out the options. */
+ if (optstr) {
+ char *arg = *argv;
+ int sense; /* 1 for -x, 0 for +x */
+ /* while arguments look like options ... */
+ while (arg &&
+ /* Must begin with - or maybe + */
+ ((sense = (*arg == '-')) ||
+ ((flags & BINF_PLUSOPTS) && *arg == '+'))) {
+ /* Digits aren't arguments unless the command says they are. */
+ if (!(flags & BINF_KEEPNUM) && idigit(arg[1]))
+ break;
+ /* For cd and friends, a single dash is not an option. */
+ if ((flags & BINF_SKIPDASH) && !arg[1])
+ break;
+ if ((flags & BINF_DASHDASHVALID) && !strcmp(arg, "--")) {
+ /*
+ * Need to skip this before checking whether this is
+ * really an option.
+ */
+ argv++;
+ break;
+ }
+ /*
+ * Unrecognised options to echo etc. are not really
+ * options.
+ *
+ * Note this flag is not smart enough to handle option
+ * arguments. In fact, ideally it shouldn't be added
+ * to any new builtins, to preserve standard option
+ * handling as much as possible.
+ */
+ if (flags & BINF_SKIPINVALID) {
+ char *p = arg;
+ while (*++p && strchr(optstr, (int) *p));
+ if (*p)
+ break;
+ }
+ /* handle -- or - (ops.ind['-']), and +
+ * (ops.ind['-'] and ops.ind['+']) */
+ if (arg[1] == '-')
+ arg++;
+ if (!arg[1]) {
+ ops.ind['-'] = 1;
+ if (!sense)
+ ops.ind['+'] = 1;
+ }
+ /* save options in ops, as long as they are in bn->optstr */
+ while (*++arg) {
+ char *optptr;
+ if ((optptr = strchr(optstr, execop = (int)*arg))) {
+ ops.ind[(int)*arg] = (sense) ? 1 : 2;
+ if (optptr[1] == ':') {
+ char *argptr = NULL;
+ if (optptr[2] == ':') {
+ if (arg[1])
+ argptr = arg+1;
+ /* Optional argument in same word*/
+ } else if (optptr[2] == '%') {
+ /* Optional numeric argument in same
+ * or next word. */
+ if (arg[1] && idigit(arg[1]))
+ argptr = arg+1;
+ else if (argv[1] && idigit(*argv[1]))
+ argptr = arg = *++argv;
+ } else {
+ /* Mandatory argument */
+ if (arg[1])
+ argptr = arg+1;
+ else if ((arg = *++argv))
+ argptr = arg;
+ else {
+ zwarnnam(name, "argument expected: -%c",
+ execop);
+ return 1;
+ }
+ }
+ if (argptr) {
+ if (new_optarg(&ops)) {
+ zwarnnam(name,
+ "too many option arguments");
+ return 1;
+ }
+ ops.ind[execop] |= ops.argscount << 2;
+ ops.args[ops.argscount-1] = argptr;
+ while (arg[1])
+ arg++;
+ }
+ }
+ } else
+ break;
+ }
+ /* The above loop may have exited on an invalid option. (We *
+ * assume that any option requiring metafication is invalid.) */
+ if (*arg) {
+ if(*arg == Meta)
+ *++arg ^= 32;
+ zwarnnam(name, "bad option: %c%c", "+-"[sense], *arg);
+ return 1;
+ }
+ arg = *++argv;
+ /* for the "print" builtin, the options after -R are treated as
+ options to "echo" */
+ if ((flags & BINF_PRINTOPTS) && ops.ind['R'] &&
+ !ops.ind['f']) {
+ optstr = "ne";
+ flags |= BINF_SKIPINVALID;
+ }
+ /* the option -- indicates the end of the options */
+ if (ops.ind['-'])
+ break;
+ }
+ } else if (!(flags & BINF_HANDLES_OPTS) && *argv &&
+ !strcmp(*argv, "--")) {
+ ops.ind['-'] = 1;
+ argv++;
+ }
+
+ /* handle built-in options, for overloaded handler functions */
+ if ((pp = bn->defopts)) {
+ while (*pp) {
+ /* only if not already set */
+ if (!ops.ind[(int)*pp])
+ ops.ind[(int)*pp] = 1;
+ pp++;
+ }
+ }
+
+ /* Fix the argument count by subtracting option arguments */
+ argc -= argv - argarr;
+
+ if (errflag) {
+ errflag &= ~ERRFLAG_ERROR;
+ return 1;
+ }
+
+ /* check that the argument count lies within the specified bounds */
+ if (argc < bn->minargs || (argc > bn->maxargs && bn->maxargs != -1)) {
+ zwarnnam(name, (argc < bn->minargs)
+ ? "not enough arguments" : "too many arguments");
+ return 1;
+ }
+
+ /* display execution trace information, if required */
+ if (xtr) {
+ /* Use full argument list including options for trace output */
+ char **fullargv = argarr;
+ printprompt4();
+ fprintf(xtrerr, "%s", name);
+ while (*fullargv) {
+ fputc(' ', xtrerr);
+ quotedzputs(*fullargv++, xtrerr);
+ }
+ if (assigns) {
+ LinkNode node;
+ for (node = firstnode(assigns); node; incnode(node)) {
+ Asgment asg = (Asgment)node;
+ fputc(' ', xtrerr);
+ quotedzputs(asg->name, xtrerr);
+ if (asg->flags & ASG_ARRAY) {
+ fprintf(xtrerr, "=(");
+ if (asg->value.array) {
+ if (asg->flags & ASG_KEY_VALUE) {
+ LinkNode keynode, valnode;
+ keynode = firstnode(asg->value.array);
+ for (;;) {
+ if (!keynode)
+ break;
+ valnode = nextnode(keynode);
+ if (!valnode)
+ break;
+ fputc('[', xtrerr);
+ quotedzputs((char *)getdata(keynode),
+ xtrerr);
+ fprintf(stderr, "]=");
+ quotedzputs((char *)getdata(valnode),
+ xtrerr);
+ keynode = nextnode(valnode);
+ }
+ } else {
+ LinkNode arrnode;
+ for (arrnode = firstnode(asg->value.array);
+ arrnode;
+ incnode(arrnode)) {
+ fputc(' ', xtrerr);
+ quotedzputs((char *)getdata(arrnode),
+ xtrerr);
+ }
+ }
+ }
+ fprintf(xtrerr, " )");
+ } else if (asg->value.scalar) {
+ fputc('=', xtrerr);
+ quotedzputs(asg->value.scalar, xtrerr);
+ }
+ }
+ }
+ fputc('\n', xtrerr);
+ fflush(xtrerr);
+ }
+ /* call the handler function, and return its return value */
+ if (flags & BINF_ASSIGN)
+ {
+ /*
+ * Takes two sets of arguments.
+ */
+ HandlerFuncAssign assignfunc = (HandlerFuncAssign)bn->handlerfunc;
+ return (*(assignfunc)) (name, argv, assigns, &ops, bn->funcid);
+ }
+ else
+ {
+ return (*(bn->handlerfunc)) (name, argv, &ops, bn->funcid);
+ }
+ }
+}
+
+/* Enable/disable an element in one of the internal hash tables. *
+ * With no arguments, it lists all the currently enabled/disabled *
+ * elements in that particular hash table. */
+
+/**/
+int
+bin_enable(char *name, char **argv, Options ops, int func)
+{
+ HashTable ht;
+ HashNode hn;
+ ScanFunc scanfunc;
+ Patprog pprog;
+ int flags1 = 0, flags2 = 0;
+ int match = 0, returnval = 0;
+
+ /* Find out which hash table we are working with. */
+ if (OPT_ISSET(ops,'p')) {
+ return pat_enables(name, argv, func == BIN_ENABLE);
+ } else if (OPT_ISSET(ops,'f'))
+ ht = shfunctab;
+ else if (OPT_ISSET(ops,'r'))
+ ht = reswdtab;
+ else if (OPT_ISSET(ops,'s'))
+ ht = sufaliastab;
+ else if (OPT_ISSET(ops,'a'))
+ ht = aliastab;
+ else
+ ht = builtintab;
+
+ /* Do we want to enable or disable? */
+ if (func == BIN_ENABLE) {
+ flags2 = DISABLED;
+ scanfunc = ht->enablenode;
+ } else {
+ flags1 = DISABLED;
+ scanfunc = ht->disablenode;
+ }
+
+ /* Given no arguments, print the names of the enabled/disabled elements *
+ * in this hash table. If func == BIN_ENABLE, then scanhashtable will *
+ * print nodes NOT containing the DISABLED flag, else scanhashtable will *
+ * print nodes containing the DISABLED flag. */
+ if (!*argv) {
+ queue_signals();
+ scanhashtable(ht, 1, flags1, flags2, ht->printnode, 0);
+ unqueue_signals();
+ return 0;
+ }
+
+ /* With -m option, treat arguments as glob patterns. */
+ if (OPT_ISSET(ops,'m')) {
+ for (; *argv; argv++) {
+ queue_signals();
+
+ /* parse pattern */
+ tokenize(*argv);
+ if ((pprog = patcompile(*argv, PAT_STATIC, 0)))
+ match += scanmatchtable(ht, pprog, 0, 0, 0, scanfunc, 0);
+ else {
+ untokenize(*argv);
+ zwarnnam(name, "bad pattern : %s", *argv);
+ returnval = 1;
+ }
+ unqueue_signals();
+ }
+ /* If we didn't match anything, we return 1. */
+ if (!match)
+ returnval = 1;
+ return returnval;
+ }
+
+ /* Take arguments literally -- do not glob */
+ queue_signals();
+ for (; *argv; argv++) {
+ if ((hn = ht->getnode2(ht, *argv))) {
+ scanfunc(hn, 0);
+ } else {
+ zwarnnam(name, "no such hash table element: %s", *argv);
+ returnval = 1;
+ }
+ }
+ unqueue_signals();
+ return returnval;
+}
+
+/* set: either set the shell options, or set the shell arguments, *
+ * or declare an array, or show various things */
+
+/**/
+int
+bin_set(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
+{
+ int action, optno, array = 0, hadopt = 0,
+ hadplus = 0, hadend = 0, sort = 0;
+ char **x, *arrayname = NULL;
+
+ /* Obsolescent sh compatibility: set - is the same as set +xv *
+ * and set - args is the same as set +xv -- args */
+ if (!EMULATION(EMULATE_ZSH) && *args && **args == '-' && !args[0][1]) {
+ dosetopt(VERBOSE, 0, 0, opts);
+ dosetopt(XTRACE, 0, 0, opts);
+ if (!args[1])
+ return 0;
+ }
+
+ /* loop through command line options (begins with "-" or "+") */
+ while (*args && (**args == '-' || **args == '+')) {
+ action = (**args == '-');
+ hadplus |= !action;
+ if(!args[0][1])
+ *args = "--";
+ while (*++*args) {
+ if(**args == Meta)
+ *++*args ^= 32;
+ if(**args != '-' || action)
+ hadopt = 1;
+ /* The pseudo-option `--' signifies the end of options. */
+ if (**args == '-') {
+ hadend = 1;
+ args++;
+ goto doneoptions;
+ } else if (**args == 'o') {
+ if (!*++*args)
+ args++;
+ if (!*args) {
+ printoptionstates(hadplus);
+ inittyptab();
+ return 0;
+ }
+ if(!(optno = optlookup(*args)))
+ zerrnam(nam, "no such option: %s", *args);
+ else if(dosetopt(optno, action, 0, opts))
+ zerrnam(nam, "can't change option: %s", *args);
+ break;
+ } else if(**args == 'A') {
+ if(!*++*args)
+ args++;
+ array = action ? 1 : -1;
+ arrayname = *args;
+ if (!arrayname)
+ goto doneoptions;
+ else if (!isset(KSHARRAYS))
+ {
+ args++;
+ goto doneoptions;
+ }
+ break;
+ } else if (**args == 's')
+ sort = action ? 1 : -1;
+ else {
+ if (!(optno = optlookupc(**args)))
+ zerrnam(nam, "bad option: -%c", **args);
+ else if(dosetopt(optno, action, 0, opts))
+ zerrnam(nam, "can't change option: -%c", **args);
+ }
+ }
+ args++;
+ }
+ if (errflag)
+ return 1;
+ doneoptions:
+ inittyptab();
+
+ /* Show the parameters, possibly with values */
+ queue_signals();
+ if (!arrayname)
+ {
+ if (!hadopt && !*args)
+ scanhashtable(paramtab, 1, 0, 0, paramtab->printnode,
+ hadplus ? PRINT_NAMEONLY : 0);
+
+ if (array) {
+ /* display arrays */
+ scanhashtable(paramtab, 1, PM_ARRAY, 0, paramtab->printnode,
+ hadplus ? PRINT_NAMEONLY : 0);
+ }
+ if (!*args && !hadend) {
+ unqueue_signals();
+ return 0;
+ }
+ }
+ if (sort)
+ strmetasort(args, sort < 0 ? SORTIT_BACKWARDS : 0, NULL);
+ if (array) {
+ /* create an array with the specified elements */
+ char **a = NULL, **y;
+ int len = arrlen(args);
+
+ if (array < 0 && (a = getaparam(arrayname)) && arrlen_gt(a, len)) {
+ a += len;
+ len += arrlen(a);
+ }
+ for (x = y = zalloc((len + 1) * sizeof(char *)); len--;) {
+ if (!*args)
+ args = a;
+ *y++ = ztrdup(*args++);
+ }
+ *y++ = NULL;
+ setaparam(arrayname, x);
+ } else {
+ /* set shell arguments */
+ freearray(pparams);
+ pparams = zarrdup(args);
+ }
+ unqueue_signals();
+ return 0;
+}
+
+/**** directory-handling builtins ****/
+
+/**/
+int doprintdir = 0; /* set in exec.c (for autocd) */
+
+/* pwd: display the name of the current directory */
+
+/**/
+int
+bin_pwd(UNUSED(char *name), UNUSED(char **argv), Options ops, UNUSED(int func))
+{
+ if (OPT_ISSET(ops,'r') || OPT_ISSET(ops,'P') ||
+ (isset(CHASELINKS) && !OPT_ISSET(ops,'L')))
+ printf("%s\n", zgetcwd());
+ else {
+ zputs(pwd, stdout);
+ putchar('\n');
+ }
+ return 0;
+}
+
+/* the directory stack */
+
+/**/
+mod_export LinkList dirstack;
+
+/* dirs: list the directory stack, or replace it with a provided list */
+
+/**/
+int
+bin_dirs(UNUSED(char *name), char **argv, Options ops, UNUSED(int func))
+{
+ LinkList l;
+
+ queue_signals();
+ /* with -v, -p or no arguments display the directory stack */
+ if (!(*argv || OPT_ISSET(ops,'c')) || OPT_ISSET(ops,'v') ||
+ OPT_ISSET(ops,'p')) {
+ LinkNode node;
+ char *fmt;
+ int pos = 1;
+
+ /* with the -v option, display a numbered list, starting at zero */
+ if (OPT_ISSET(ops,'v')) {
+ printf("0\t");
+ fmt = "\n%d\t";
+ /* with the -p option, display entries one per line */
+ } else if (OPT_ISSET(ops,'p'))
+ fmt = "\n";
+ else
+ fmt = " ";
+ if (OPT_ISSET(ops,'l'))
+ zputs(pwd, stdout);
+ else
+ fprintdir(pwd, stdout);
+ for (node = firstnode(dirstack); node; incnode(node)) {
+ printf(fmt, pos++);
+ if (OPT_ISSET(ops,'l'))
+ zputs(getdata(node), stdout);
+ else
+ fprintdir(getdata(node), stdout);
+
+ }
+ unqueue_signals();
+ putchar('\n');
+ return 0;
+ }
+ /* replace the stack with the specified directories */
+ l = znewlinklist();
+ while (*argv)
+ zaddlinknode(l, ztrdup(*argv++));
+ freelinklist(dirstack, freestr);
+ dirstack = l;
+ unqueue_signals();
+ return 0;
+}
+
+/* cd, chdir, pushd, popd */
+
+/**/
+void
+set_pwd_env(void)
+{
+ Param pm;
+
+ /* update the PWD and OLDPWD shell parameters */
+
+ pm = (Param) paramtab->getnode(paramtab, "PWD");
+ if (pm && PM_TYPE(pm->node.flags) != PM_SCALAR) {
+ pm->node.flags &= ~PM_READONLY;
+ unsetparam_pm(pm, 0, 1);
+ }
+
+ pm = (Param) paramtab->getnode(paramtab, "OLDPWD");
+ if (pm && PM_TYPE(pm->node.flags) != PM_SCALAR) {
+ pm->node.flags &= ~PM_READONLY;
+ unsetparam_pm(pm, 0, 1);
+ }
+
+ assignsparam("PWD", ztrdup(pwd), 0);
+ assignsparam("OLDPWD", ztrdup(oldpwd), 0);
+
+ pm = (Param) paramtab->getnode(paramtab, "PWD");
+ if (!(pm->node.flags & PM_EXPORTED))
+ addenv(pm, pwd);
+ pm = (Param) paramtab->getnode(paramtab, "OLDPWD");
+ if (!(pm->node.flags & PM_EXPORTED))
+ addenv(pm, oldpwd);
+}
+
+/* set if we are resolving links to their true paths */
+static int chasinglinks;
+
+/* The main pwd changing function. The real work is done by other *
+ * functions. cd_get_dest() does the initial argument processing; *
+ * cd_do_chdir() actually changes directory, if possible; cd_new_pwd() *
+ * does the ancillary processing associated with actually changing *
+ * directory. */
+
+/**/
+int
+bin_cd(char *nam, char **argv, Options ops, int func)
+{
+ LinkNode dir;
+ struct stat st1, st2;
+
+ if (isset(RESTRICTED)) {
+ zwarnnam(nam, "restricted");
+ return 1;
+ }
+ doprintdir = (doprintdir == -1);
+
+ chasinglinks = OPT_ISSET(ops,'P') ||
+ (isset(CHASELINKS) && !OPT_ISSET(ops,'L'));
+ queue_signals();
+ zpushnode(dirstack, ztrdup(pwd));
+ if (!(dir = cd_get_dest(nam, argv, OPT_ISSET(ops,'s'), func))) {
+ zsfree(getlinknode(dirstack));
+ unqueue_signals();
+ return 1;
+ }
+ cd_new_pwd(func, dir, OPT_ISSET(ops, 'q'));
+
+ if (stat(unmeta(pwd), &st1) < 0) {
+ setjobpwd();
+ zsfree(pwd);
+ pwd = NULL;
+ pwd = metafy(zgetcwd(), -1, META_DUP);
+ } else if (stat(".", &st2) < 0) {
+ if (chdir(unmeta(pwd)) < 0)
+ zwarn("unable to chdir(%s): %e", pwd, errno);
+ } else if (st1.st_ino != st2.st_ino || st1.st_dev != st2.st_dev) {
+ if (chasinglinks) {
+ setjobpwd();
+ zsfree(pwd);
+ pwd = NULL;
+ pwd = metafy(zgetcwd(), -1, META_DUP);
+ } else if (chdir(unmeta(pwd)) < 0)
+ zwarn("unable to chdir(%s): %e", pwd, errno);
+ }
+ unqueue_signals();
+ return 0;
+}
+
+/* Get directory to chdir to */
+
+/**/
+static LinkNode
+cd_get_dest(char *nam, char **argv, int hard, int func)
+{
+ LinkNode dir = NULL;
+ LinkNode target;
+ char *dest;
+
+ if (!argv[0]) {
+ if (func == BIN_POPD && !nextnode(firstnode(dirstack))) {
+ zwarnnam(nam, "directory stack empty");
+ return NULL;
+ }
+ if (func == BIN_PUSHD && unset(PUSHDTOHOME))
+ dir = nextnode(firstnode(dirstack));
+ if (dir)
+ zinsertlinknode(dirstack, dir, getlinknode(dirstack));
+ else if (func != BIN_POPD) {
+ if (!home) {
+ zwarnnam(nam, "HOME not set");
+ return NULL;
+ }
+ zpushnode(dirstack, ztrdup(home));
+ }
+ } else if (!argv[1]) {
+ int dd;
+ char *end;
+
+ doprintdir++;
+ if (argv[0][1] && (argv[0][0] == '+' || argv[0][0] == '-')
+ && strspn(argv[0]+1, "0123456789") == strlen(argv[0]+1)) {
+ dd = zstrtol(argv[0] + 1, &end, 10);
+ if (*end == '\0') {
+ if ((argv[0][0] == '+') ^ isset(PUSHDMINUS))
+ for (dir = firstnode(dirstack); dir && dd; dd--, incnode(dir));
+ else
+ for (dir = lastnode(dirstack); dir != (LinkNode) dirstack && dd;
+ dd--, dir = prevnode(dir));
+ if (!dir || dir == (LinkNode) dirstack) {
+ zwarnnam(nam, "no such entry in dir stack");
+ return NULL;
+ }
+ }
+ }
+ if (!dir)
+ zpushnode(dirstack, ztrdup(strcmp(argv[0], "-")
+ ? (doprintdir--, argv[0]) : oldpwd));
+ } else {
+ char *u, *d;
+ int len1, len2, len3;
+
+ if (!(u = strstr(pwd, argv[0]))) {
+ zwarnnam(nam, "string not in pwd: %s", argv[0]);
+ return NULL;
+ }
+ len1 = strlen(argv[0]);
+ len2 = strlen(argv[1]);
+ len3 = u - pwd;
+ d = (char *)zalloc(len3 + len2 + strlen(u + len1) + 1);
+ strncpy(d, pwd, len3);
+ strcpy(d + len3, argv[1]);
+ strcat(d, u + len1);
+ zpushnode(dirstack, d);
+ doprintdir++;
+ }
+
+ target = dir;
+ if (func == BIN_POPD) {
+ if (!dir) {
+ target = dir = firstnode(dirstack);
+ } else if (dir != firstnode(dirstack)) {
+ return dir;
+ }
+ dir = nextnode(dir);
+ }
+ if (!dir) {
+ dir = firstnode(dirstack);
+ }
+ if (!dir || !getdata(dir)) {
+ DPUTS(1, "Directory not set, not detected early enough");
+ return NULL;
+ }
+ if (!(dest = cd_do_chdir(nam, getdata(dir), hard))) {
+ if (!target)
+ zsfree(getlinknode(dirstack));
+ if (func == BIN_POPD)
+ zsfree(remnode(dirstack, dir));
+ return NULL;
+ }
+ if (dest != (char *)getdata(dir)) {
+ zsfree(getdata(dir));
+ setdata(dir, dest);
+ }
+ return target ? target : dir;
+}
+
+/* Change to given directory, if possible. This function works out *
+ * exactly how the directory should be interpreted, including cdpath *
+ * and CDABLEVARS. For each possible interpretation of the given *
+ * path, this calls cd_try_chdir(), which attempts to chdir to that *
+ * particular path. */
+
+/**/
+static char *
+cd_do_chdir(char *cnam, char *dest, int hard)
+{
+ char **pp, *ret;
+ int hasdot = 0, eno = ENOENT;
+ /*
+ * nocdpath indicates that cdpath should not be used.
+ * This is the case iff dest is a relative path
+ * whose first segment is . or .., but if the path is
+ * absolute then cdpath won't be used anyway.
+ */
+ int nocdpath;
+#ifdef __CYGWIN__
+ /*
+ * Normalize path under Cygwin to avoid messing with
+ * DOS style names with drives in them
+ */
+ static char buf[PATH_MAX+1];
+#ifdef HAVE_CYGWIN_CONV_PATH
+ cygwin_conv_path(CCP_WIN_A_TO_POSIX | CCP_RELATIVE, dest, buf,
+ PATH_MAX);
+#else
+#ifndef _SYS_CYGWIN_H
+ void cygwin_conv_to_posix_path(const char *, char *);
+#endif
+
+ cygwin_conv_to_posix_path(dest, buf);
+#endif
+ dest = buf;
+#endif
+ nocdpath = dest[0] == '.' &&
+ (dest[1] == '/' || !dest[1] || (dest[1] == '.' &&
+ (dest[2] == '/' || !dest[2])));
+
+ /*
+ * If we have an absolute path, use it as-is only
+ */
+ if (*dest == '/') {
+ if ((ret = cd_try_chdir(NULL, dest, hard)))
+ return ret;
+ zwarnnam(cnam, "%e: %s", errno, dest);
+ return NULL;
+ }
+
+ /*
+ * If cdpath is being used, check it for ".".
+ * Don't bother doing this if POSIXCD is set, we don't
+ * need to know (though it doesn't actually matter).
+ */
+ if (!nocdpath && !isset(POSIXCD))
+ for (pp = cdpath; *pp; pp++)
+ if (!(*pp)[0] || ((*pp)[0] == '.' && (*pp)[1] == '\0'))
+ hasdot = 1;
+ /*
+ * If
+ * (- there is no . in cdpath
+ * - or cdpath is not being used)
+ * - and the POSIXCD option is not set
+ * try the directory as-is (i.e. from .)
+ */
+ if (!hasdot && !isset(POSIXCD)) {
+ if ((ret = cd_try_chdir(NULL, dest, hard)))
+ return ret;
+ if (errno != ENOENT)
+ eno = errno;
+ }
+ /* if cdpath is being used, try given directory relative to each element in
+ cdpath in turn */
+ if (!nocdpath)
+ for (pp = cdpath; *pp; pp++) {
+ if ((ret = cd_try_chdir(*pp, dest, hard))) {
+ if (isset(POSIXCD)) {
+ /*
+ * For POSIX we need to print the directory
+ * any time CDPATH was used, except in the
+ * special case of an empty segment being
+ * treated as a ".".
+ */
+ if (**pp)
+ doprintdir++;
+ } else {
+ if (strcmp(*pp, ".")) {
+ doprintdir++;
+ }
+ }
+ return ret;
+ }
+ if (errno != ENOENT)
+ eno = errno;
+ }
+ /*
+ * POSIX requires us to check "." after CDPATH rather than before.
+ */
+ if (isset(POSIXCD)) {
+ if ((ret = cd_try_chdir(NULL, dest, hard)))
+ return ret;
+ if (errno != ENOENT)
+ eno = errno;
+ }
+
+ /* handle the CDABLEVARS option */
+ if ((ret = cd_able_vars(dest))) {
+ if ((ret = cd_try_chdir(NULL, ret,hard))) {
+ doprintdir++;
+ return ret;
+ }
+ if (errno != ENOENT)
+ eno = errno;
+ }
+
+ /* If we got here, it means that we couldn't chdir to any of the
+ multitudinous possible paths allowed by zsh. We've run out of options!
+ Add more here! */
+ zwarnnam(cnam, "%e: %s", eno, dest);
+ return NULL;
+}
+
+/* If the CDABLEVARS option is set, return the new *
+ * interpretation of the given path. */
+
+/**/
+char *
+cd_able_vars(char *s)
+{
+ char *rest, save;
+
+ if (isset(CDABLEVARS)) {
+ for (rest = s; *rest && *rest != '/'; rest++);
+ save = *rest;
+ *rest = 0;
+ s = getnameddir(s);
+ *rest = save;
+
+ if (s && *rest)
+ s = dyncat(s, rest);
+
+ return s;
+ }
+ return NULL;
+}
+
+/* Attempt to change to a single given directory. The directory, *
+ * for the convenience of the calling function, may be provided in *
+ * two parts, which must be concatenated before attempting to chdir. *
+ * Returns NULL if the chdir fails. If the directory change is *
+ * possible, it is performed, and a pointer to the new full pathname *
+ * is returned. */
+
+/**/
+static char *
+cd_try_chdir(char *pfix, char *dest, int hard)
+{
+ char *buf;
+ int dlen, dochaselinks = 0;
+
+ /* handle directory prefix */
+ if (pfix && *pfix) {
+ if (*pfix == '/') {
+#ifdef __CYGWIN__
+/* NB: Don't turn "/"+"bin" into "//"+"bin" by mistake! "//bin" may *
+ * not be what user really wants (probably wants "/bin"), but *
+ * "//bin" could be valid too (see fixdir())! This is primarily for *
+ * handling CDPATH correctly. Likewise for "//"+"bin" not becoming *
+ * "///bin" (aka "/bin"). */
+ int root = pfix[1] == '\0' || (pfix[1] == '/' && pfix[2] == '\0');
+ buf = tricat(pfix, ( root ? "" : "/" ), dest);
+#else
+ buf = tricat(pfix, "/", dest);
+#endif
+ } else {
+ int pfl = strlen(pfix);
+ dlen = strlen(pwd);
+ if (dlen == 1 && *pwd == '/')
+ dlen = 0;
+ buf = zalloc(dlen + pfl + strlen(dest) + 3);
+ if (dlen)
+ strcpy(buf, pwd);
+ buf[dlen] = '/';
+ strcpy(buf + dlen + 1, pfix);
+ buf[dlen + 1 + pfl] = '/';
+ strcpy(buf + dlen + pfl + 2, dest);
+ }
+ } else if (*dest == '/')
+ buf = ztrdup(dest);
+ else {
+ dlen = strlen(pwd);
+ if (pwd[dlen-1] == '/')
+ --dlen;
+ buf = zalloc(dlen + strlen(dest) + 2);
+ strcpy(buf, pwd);
+ buf[dlen] = '/';
+ strcpy(buf + dlen + 1, dest);
+ }
+
+ /* Normalise path. See the definition of fixdir() for what this means.
+ * We do not do this if we are chasing links.
+ */
+ if (!chasinglinks)
+ dochaselinks = fixdir(buf);
+ else
+ unmetafy(buf, &dlen);
+
+ /* We try the full path first. If that fails, try the
+ * argument to cd relatively. This is useful if the cwd
+ * or a parent directory is renamed in the interim.
+ */
+ if (lchdir(buf, NULL, hard) &&
+ (pfix || *dest == '/' || lchdir(unmeta(dest), NULL, hard))) {
+ free(buf);
+ return NULL;
+ }
+ /* the chdir succeeded, so decide if we should force links to be chased */
+ if (dochaselinks)
+ chasinglinks = 1;
+ return metafy(buf, -1, META_NOALLOC);
+}
+
+/* do the extra processing associated with changing directory */
+
+/**/
+static void
+cd_new_pwd(int func, LinkNode dir, int quiet)
+{
+ char *new_pwd, *s;
+ int dirstacksize;
+
+ if (func == BIN_PUSHD)
+ rolllist(dirstack, dir);
+ new_pwd = remnode(dirstack, dir);
+
+ if (func == BIN_POPD && firstnode(dirstack)) {
+ zsfree(new_pwd);
+ new_pwd = getlinknode(dirstack);
+ } else if (func == BIN_CD && unset(AUTOPUSHD))
+ zsfree(getlinknode(dirstack));
+
+ if (chasinglinks) {
+ s = findpwd(new_pwd);
+ if (s) {
+ zsfree(new_pwd);
+ new_pwd = s;
+ }
+ }
+ if (isset(PUSHDIGNOREDUPS)) {
+ LinkNode n;
+ for (n = firstnode(dirstack); n; incnode(n)) {
+ if (!strcmp(new_pwd, getdata(n))) {
+ zsfree(remnode(dirstack, n));
+ break;
+ }
+ }
+ }
+
+ /* shift around the pwd variables, to make oldpwd and pwd relate to the
+ current (i.e. new) pwd */
+ zsfree(oldpwd);
+ oldpwd = pwd;
+ setjobpwd();
+ pwd = new_pwd;
+ set_pwd_env();
+
+ if (isset(INTERACTIVE) || isset(POSIXCD)) {
+ if (func != BIN_CD && isset(INTERACTIVE)) {
+ if (unset(PUSHDSILENT) && !quiet)
+ printdirstack();
+ } else if (doprintdir) {
+ fprintdir(pwd, stdout);
+ putchar('\n');
+ }
+ }
+
+ /* execute the chpwd function */
+ fflush(stdout);
+ fflush(stderr);
+ if (!quiet)
+ callhookfunc("chpwd", NULL, 1, NULL);
+
+ dirstacksize = getiparam("DIRSTACKSIZE");
+ /* handle directory stack sizes out of range */
+ if (dirstacksize > 0) {
+ int remove = countlinknodes(dirstack) -
+ (dirstacksize < 2 ? 2 : dirstacksize);
+ while (remove-- >= 0)
+ zsfree(remnode(dirstack, lastnode(dirstack)));
+ }
+}
+
+/* Print the directory stack */
+
+/**/
+static void
+printdirstack(void)
+{
+ LinkNode node;
+
+ fprintdir(pwd, stdout);
+ for (node = firstnode(dirstack); node; incnode(node)) {
+ putchar(' ');
+ fprintdir(getdata(node), stdout);
+ }
+ putchar('\n');
+}
+
+/* Normalise a path. Segments consisting of ., and foo/.. *
+ * combinations, are removed and the path is unmetafied.
+ * Returns 1 if we found a ../ path which should force links to
+ * be chased, 0 otherwise.
+ */
+
+/**/
+int
+fixdir(char *src)
+{
+ char *dest = src, *d0 = dest;
+#ifdef __CYGWIN__
+ char *s0 = src;
+#endif
+ /* This function is always called with n path containing at
+ * least one slash, either because one was input by the user or
+ * because the caller has prepended either pwd or a cdpath dir.
+ * If asked to make a relative change and pwd is set to ".",
+ * the current directory has been removed out from under us,
+ * so force links to be chased.
+ *
+ * Ordinarily we can't get here with "../" as the first component
+ * but handle the silly special case of ".." in cdpath.
+ *
+ * Order of comparisons here looks funny, but it short-circuits
+ * most rapidly in the event of a false condition. Set to 2
+ * here so we still obey the (lack of) CHASEDOTS option after
+ * the first "../" is preserved (test chasedots > 1 below).
+ */
+ int chasedots = (src[0] == '.' && pwd[0] == '.' && pwd[1] == '\0' &&
+ (src[1] == '/' || (src[1] == '.' && src[2] == '/'))) * 2;
+
+/*** if have RFS superroot directory ***/
+#ifdef HAVE_SUPERROOT
+ /* allow /.. segments to remain */
+ while (*src == '/' && src[1] == '.' && src[2] == '.' &&
+ (!src[3] || src[3] == '/')) {
+ *dest++ = '/';
+ *dest++ = '.';
+ *dest++ = '.';
+ src += 3;
+ }
+#endif
+
+ for (;;) {
+ /* compress multiple /es into single */
+ if (*src == '/') {
+#ifdef __CYGWIN__
+ /* allow leading // under cygwin, but /// still becomes / */
+ if (src == s0 && src[1] == '/' && src[2] != '/')
+ *dest++ = *src++;
+#endif
+ *dest++ = *src++;
+ while (*src == '/')
+ src++;
+ }
+ /* if we are at the end of the input path, remove a trailing / (if it
+ exists), and return ct */
+ if (!*src) {
+ while (dest > d0 + 1 && dest[-1] == '/')
+ dest--;
+ *dest = '\0';
+ return chasedots;
+ }
+ if (src[0] == '.' && src[1] == '.' &&
+ (src[2] == '\0' || src[2] == '/')) {
+ if (isset(CHASEDOTS) || chasedots > 1) {
+ chasedots = 1;
+ /* and treat as normal path segment */
+ } else {
+ if (dest > d0 + 1) {
+ /*
+ * remove a foo/.. combination:
+ * first check foo exists, else return.
+ */
+ struct stat st;
+ *dest = '\0';
+ if (stat(d0, &st) < 0 || !S_ISDIR(st.st_mode)) {
+ char *ptrd, *ptrs;
+ if (dest == src)
+ *dest = '.';
+ for (ptrs = src, ptrd = dest; *ptrs; ptrs++, ptrd++)
+ *ptrd = (*ptrs == Meta) ? (*++ptrs ^ 32) : *ptrs;
+ *ptrd = '\0';
+ return 1;
+ }
+ for (dest--; dest > d0 + 1 && dest[-1] != '/'; dest--);
+ if (dest[-1] != '/')
+ dest--;
+ }
+ src++;
+ while (*++src == '/');
+ continue;
+ }
+ }
+ if (src[0] == '.' && (src[1] == '/' || src[1] == '\0')) {
+ /* skip a . section */
+ while (*++src == '/');
+ } else {
+ /* copy a normal segment into the output */
+ while (*src != '/' && *src != '\0')
+ if ((*dest++ = *src++) == Meta)
+ dest[-1] = *src++ ^ 32;
+ }
+ }
+ /* unreached */
+}
+
+/**/
+mod_export void
+printqt(char *str)
+{
+ /* Print str, but turn any single quote into '\'' or ''. */
+ for (; *str; str++)
+ if (*str == '\'')
+ printf(isset(RCQUOTES) ? "''" : "'\\''");
+ else
+ putchar(*str);
+}
+
+/**/
+mod_export void
+printif(char *str, int c)
+{
+ /* If flag c has an argument, print that */
+ if (str) {
+ printf(" -%c ", c);
+ quotedzputs(str, stdout);
+ }
+}
+
+/**** history list functions ****/
+
+/* fc, history, r */
+
+/**/
+int
+bin_fc(char *nam, char **argv, Options ops, int func)
+{
+ zlong first = -1, last = -1;
+ int retval;
+ char *s;
+ struct asgment *asgf = NULL, *asgl = NULL;
+ Patprog pprog = NULL;
+
+ /* fc is only permitted in interactive shells */
+#ifdef FACIST_INTERACTIVE
+ if (!interact) {
+ zwarnnam(nam, "not interactive shell");
+ return 1;
+ }
+#endif
+ if (OPT_ISSET(ops,'p')) {
+ char *hf = "";
+ zlong hs = DEFAULT_HISTSIZE;
+ zlong shs = 0;
+ int level = OPT_ISSET(ops,'a') ? locallevel : -1;
+ if (*argv) {
+ hf = *argv++;
+ if (*argv) {
+ char *check;
+ hs = zstrtol(*argv++, &check, 10);
+ if (*check) {
+ zwarnnam("fc", "HISTSIZE must be an integer");
+ return 1;
+ }
+ if (*argv) {
+ shs = zstrtol(*argv++, &check, 10);
+ if (*check) {
+ zwarnnam("fc", "SAVEHIST must be an integer");
+ return 1;
+ }
+ } else
+ shs = hs;
+ if (*argv) {
+ zwarnnam("fc", "too many arguments");
+ return 1;
+ }
+ } else {
+ hs = histsiz;
+ shs = savehistsiz;
+ }
+ }
+ if (!pushhiststack(hf, hs, shs, level))
+ return 1;
+ if (*hf) {
+ struct stat st;
+ if (stat(hf, &st) >= 0 || errno != ENOENT)
+ readhistfile(hf, 1, HFILE_USE_OPTIONS);
+ }
+ return 0;
+ }
+ if (OPT_ISSET(ops,'P')) {
+ if (*argv) {
+ zwarnnam("fc", "too many arguments");
+ return 1;
+ }
+ return !saveandpophiststack(-1, HFILE_USE_OPTIONS);
+ }
+ /* with the -m option, the first argument is taken *
+ * as a pattern that history lines have to match */
+ if (*argv && OPT_ISSET(ops,'m')) {
+ tokenize(*argv);
+ if (!(pprog = patcompile(*argv++, 0, NULL))) {
+ zwarnnam(nam, "invalid match pattern");
+ return 1;
+ }
+ }
+ queue_signals();
+ if (OPT_ISSET(ops,'R')) {
+ /* read history from a file */
+ readhistfile(*argv, 1, OPT_ISSET(ops,'I') ? HFILE_SKIPOLD : 0);
+ unqueue_signals();
+ return 0;
+ }
+ if (OPT_ISSET(ops,'W')) {
+ /* write history to a file */
+ savehistfile(*argv, 1, OPT_ISSET(ops,'I') ? HFILE_SKIPOLD : 0);
+ unqueue_signals();
+ return 0;
+ }
+ if (OPT_ISSET(ops,'A')) {
+ /* append history to a file */
+ savehistfile(*argv, 1, HFILE_APPEND |
+ (OPT_ISSET(ops,'I') ? HFILE_SKIPOLD : 0));
+ unqueue_signals();
+ return 0;
+ }
+
+ if (zleactive) {
+ unqueue_signals();
+ zwarnnam(nam, "no interactive history within ZLE");
+ return 1;
+ }
+
+ /* put foo=bar type arguments into the substitution list */
+ while (*argv && equalsplit(*argv, &s)) {
+ Asgment a = (Asgment) zhalloc(sizeof *a);
+
+ if (!**argv) {
+ zwarnnam(nam, "invalid replacement pattern: =%s", s);
+ return 1;
+ }
+ if (!asgf)
+ asgf = asgl = a;
+ else {
+ asgl->node.next = &a->node;
+ asgl = a;
+ }
+ a->name = *argv;
+ a->flags = 0;
+ a->value.scalar = s;
+ a->node.next = a->node.prev = NULL;
+ argv++;
+ }
+ /* interpret and check first history line specifier */
+ if (*argv) {
+ first = fcgetcomm(*argv);
+ if (first == -1) {
+ unqueue_signals();
+ return 1;
+ }
+ argv++;
+ }
+ /* interpret and check second history line specifier */
+ if (*argv) {
+ last = fcgetcomm(*argv);
+ if (last == -1) {
+ unqueue_signals();
+ return 1;
+ }
+ argv++;
+ }
+ /* There is a maximum of two history specifiers. At least, there *
+ * will be as long as the history list is one-dimensional. */
+ if (*argv) {
+ unqueue_signals();
+ zwarnnam("fc", "too many arguments");
+ return 1;
+ }
+ /* default values of first and last, and range checking */
+ if (last == -1) {
+ if (OPT_ISSET(ops,'l') && first < curhist) {
+ /*
+ * When listing base our calculations on curhist,
+ * to show anything added since the edited history line.
+ * Also, in that case curhist will have been modified
+ * past the current history line; then we want to
+ * show everything, because the user expects to
+ * see the result of "print -s". Otherwise, we subtract
+ * -1 from the line, because the user doesn't usually expect
+ * to see the command line that caused history to be
+ * listed.
+ */
+ last = (curline.histnum == curhist) ? addhistnum(curhist,-1,0)
+ : curhist;
+ if (last < firsthist())
+ last = firsthist();
+ }
+ else
+ last = first;
+ }
+ if (first == -1) {
+ /*
+ * When listing, we want to see everything that's been
+ * added to the history, including by print -s, so use
+ * curhist.
+ * When reexecuting, we want to restrict to the last edited
+ * command line to avoid giving the user a nasty turn
+ * if some helpful soul ran "print -s 'rm -rf /'".
+ */
+ first = OPT_ISSET(ops,'l')? addhistnum(curhist,-16,0)
+ : addhistnum(curline.histnum,-1,0);
+ if (first < 1)
+ first = 1;
+ if (last < first)
+ last = first;
+ }
+ if (OPT_ISSET(ops,'l')) {
+ /* list the required part of the history */
+ retval = fclist(stdout, ops, first, last, asgf, pprog, 0);
+ unqueue_signals();
+ }
+ else {
+ /* edit history file, and (if successful) use the result as a new command */
+ int tempfd;
+ FILE *out;
+ char *fil;
+
+ retval = 1;
+ if ((tempfd = gettempfile(NULL, 1, &fil)) < 0
+ || ((out = fdopen(tempfd, "w")) == NULL)) {
+ unqueue_signals();
+ zwarnnam("fc", "can't open temp file: %e", errno);
+ } else {
+ /*
+ * Nasty behaviour results if we use the current history
+ * line here. Treat it as if it doesn't exist, unless
+ * that gives us an empty range.
+ */
+ if (last >= curhist) {
+ last = curhist - 1;
+ if (first > last) {
+ unqueue_signals();
+ zwarnnam("fc",
+ "current history line would recurse endlessly, aborted");
+ fclose(out);
+ unlink(fil);
+ return 1;
+ }
+ }
+ ops->ind['n'] = 1; /* No line numbers here. */
+ if (!fclist(out, ops, first, last, asgf, pprog, 1)) {
+ char *editor;
+
+ if (func == BIN_R)
+ editor = "-";
+ else if (OPT_HASARG(ops, 'e'))
+ editor = OPT_ARG(ops, 'e');
+ else
+ editor = getsparam("FCEDIT");
+ if (!editor)
+ editor = getsparam("EDITOR");
+ if (!editor)
+ editor = DEFAULT_FCEDIT;
+
+ unqueue_signals();
+ if (fcedit(editor, fil)) {
+ if (stuff(fil))
+ zwarnnam("fc", "%e: %s", errno, fil);
+ else {
+ loop(0,1);
+ retval = lastval;
+ }
+ }
+ } else
+ unqueue_signals();
+ }
+ unlink(fil);
+ }
+ return retval;
+}
+
+/* History handling functions: these are called by ZLE, as well as *
+ * the actual builtins. fcgetcomm() gets a history line, specified *
+ * either by number or leading string. fcsubs() performs a given *
+ * set of simple old=new substitutions on a given command line. *
+ * fclist() outputs a given range of history lines to a text file. */
+
+/* get the history event associated with s */
+
+/**/
+static zlong
+fcgetcomm(char *s)
+{
+ zlong cmd;
+
+ /* First try to match a history number. Negative *
+ * numbers indicate reversed numbering. */
+ if ((cmd = atoi(s)) != 0 || *s == '0') {
+ if (cmd < 0)
+ cmd = addhistnum(curline.histnum,cmd,HIST_FOREIGN);
+ if (cmd < 0)
+ cmd = 0;
+ return cmd;
+ }
+ /* not a number, so search by string */
+ cmd = hcomsearch(s);
+ if (cmd == -1)
+ zwarnnam("fc", "event not found: %s", s);
+ return cmd;
+}
+
+/* Perform old=new substitutions. Uses the asgment structure from zsh.h, *
+ * which is essentially a linked list of string,replacement pairs. */
+
+/**/
+static int
+fcsubs(char **sp, struct asgment *sub)
+{
+ char *oldstr, *newstr, *oldpos, *newpos, *newmem, *s = *sp;
+ int subbed = 0;
+
+ /* loop through the linked list */
+ while (sub) {
+ oldstr = sub->name;
+ newstr = sub->value.scalar;
+ sub = (Asgment)sub->node.next;
+ oldpos = s;
+ /* loop over occurences of oldstr in s, replacing them with newstr */
+ while ((newpos = (char *)strstr(oldpos, oldstr))) {
+ newmem = (char *) zhalloc(1 + (newpos - s)
+ + strlen(newstr) + strlen(newpos + strlen(oldstr)));
+ ztrncpy(newmem, s, newpos - s);
+ strcat(newmem, newstr);
+ oldpos = newmem + strlen(newmem);
+ strcat(newmem, newpos + strlen(oldstr));
+ s = newmem;
+ subbed = 1;
+ }
+ }
+ *sp = s;
+ return subbed;
+}
+
+/* Print a series of history events to a file. The file pointer is *
+ * given by f, and the required range of events by first and last. *
+ * subs is an optional list of foo=bar substitutions to perform on the *
+ * history lines before output. com is an optional comp structure *
+ * that the history lines are required to match. n, r, D and d are *
+ * options: n indicates that each line should be numbered. r indicates *
+ * that the lines should be output in reverse order (newest first). *
+ * D indicates that the real time taken by each command should be *
+ * output. d indicates that the time of execution of each command *
+ * should be output; d>1 means that the date should be output too; d>3 *
+ * means that mm/dd/yyyy form should be used for the dates, as opposed *
+ * to dd.mm.yyyy form; d>7 means that yyyy-mm-dd form should be used. */
+
+/**/
+static int
+fclist(FILE *f, Options ops, zlong first, zlong last,
+ struct asgment *subs, Patprog pprog, int is_command)
+{
+ int fclistdone = 0, xflags = 0;
+ zlong tmp;
+ char *s, *tdfmt, *timebuf;
+ Histent ent;
+
+ /* reverse range if required */
+ if (OPT_ISSET(ops,'r')) {
+ tmp = last;
+ last = first;
+ first = tmp;
+ }
+ if (is_command && first > last) {
+ zwarnnam("fc", "history events can't be executed backwards, aborted");
+ if (f != stdout)
+ fclose(f);
+ return 1;
+ }
+
+ ent = gethistent(first, first < last? GETHIST_DOWNWARD : GETHIST_UPWARD);
+ if (!ent || (first < last? ent->histnum > last : ent->histnum < last)) {
+ if (first == last) {
+ char buf[DIGBUFSIZE];
+ convbase(buf, first, 10);
+ zwarnnam("fc", "no such event: %s", buf);
+ } else
+ zwarnnam("fc", "no events in that range");
+ if (f != stdout)
+ fclose(f);
+ return 1;
+ }
+
+ if (OPT_ISSET(ops,'d') || OPT_ISSET(ops,'f') ||
+ OPT_ISSET(ops,'E') || OPT_ISSET(ops,'i') ||
+ OPT_ISSET(ops,'t')) {
+ if (OPT_ISSET(ops,'t')) {
+ tdfmt = OPT_ARG(ops,'t');
+ } else if (OPT_ISSET(ops,'i')) {
+ tdfmt = "%Y-%m-%d %H:%M";
+ } else if (OPT_ISSET(ops,'E')) {
+ tdfmt = "%f.%-m.%Y %H:%M";
+ } else if (OPT_ISSET(ops,'f')) {
+ tdfmt = "%-m/%f/%Y %H:%M";
+ } else {
+ tdfmt = "%H:%M";
+ }
+ timebuf = zhalloc(256);
+ } else {
+ tdfmt = timebuf = NULL;
+ }
+
+ /* xflags exclude events */
+ if (OPT_ISSET(ops,'L')) {
+ xflags |= HIST_FOREIGN;
+ }
+ if (OPT_ISSET(ops,'I')) {
+ xflags |= HIST_READ;
+ }
+
+ for (;;) {
+ if (ent->node.flags & xflags)
+ s = NULL;
+ else
+ s = dupstring(ent->node.nam);
+ /* this if does the pattern matching, if required */
+ if (s && (!pprog || pattry(pprog, s))) {
+ /* perform substitution */
+ fclistdone |= (subs ? fcsubs(&s, subs) : 1);
+
+ /* do numbering */
+ if (!OPT_ISSET(ops,'n')) {
+ char buf[DIGBUFSIZE];
+ convbase(buf, ent->histnum, 10);
+ fprintf(f, "%5s%c ", buf,
+ ent->node.flags & HIST_FOREIGN ? '*' : ' ');
+ }
+ /* output actual time (and possibly date) of execution of the
+ command, if required */
+ if (tdfmt != NULL) {
+ struct tm *ltm;
+ int len;
+ ltm = localtime(&ent->stim);
+ if ((len = ztrftime(timebuf, 256, tdfmt, ltm, 0L)) >= 0) {
+ fwrite(timebuf, 1, len, f);
+ fprintf(f, " ");
+ }
+ }
+ /* display the time taken by the command, if required */
+ if (OPT_ISSET(ops,'D')) {
+ long diff;
+ diff = (ent->ftim) ? ent->ftim - ent->stim : 0;
+ fprintf(f, "%ld:%02ld ", diff / 60, diff % 60);
+ }
+
+ /* output the command */
+ if (f == stdout) {
+ nicezputs(s, f);
+ putc('\n', f);
+ } else {
+ int len;
+ unmetafy(s, &len);
+ fwrite(s, 1, len, f);
+ putc('\n', f);
+ }
+ }
+ /* move on to the next history line, or quit the loop */
+ if (first < last) {
+ if (!(ent = down_histent(ent)) || ent->histnum > last)
+ break;
+ }
+ else {
+ if (!(ent = up_histent(ent)) || ent->histnum < last)
+ break;
+ }
+ }
+
+ /* final processing */
+ if (f != stdout)
+ fclose(f);
+ if (!fclistdone) {
+ if (subs)
+ zwarnnam("fc", "no substitutions performed");
+ else if (xflags || pprog)
+ zwarnnam("fc", "no matching events found");
+ return 1;
+ }
+ return 0;
+}
+
+/* edit a history file */
+
+/**/
+static int
+fcedit(char *ename, char *fn)
+{
+ char *s;
+
+ if (!strcmp(ename, "-"))
+ return 1;
+
+ s = tricat(ename, " ", fn);
+ execstring(s, 1, 0, "fc");
+ zsfree(s);
+
+ return !lastval;
+}
+
+/**** parameter builtins ****/
+
+/* Separate an argument into name=value parts, returning them in an *
+ * asgment structure. Because the asgment structure used is global, *
+ * only one of these can be active at a time. The string s gets placed *
+ * in this global structure, so it needs to be in permanent memory. */
+
+/**/
+static Asgment
+getasg(char ***argvp, LinkList assigns)
+{
+ char *s = **argvp;
+ static struct asgment asg;
+
+ /* sanity check for valid argument */
+ if (!s) {
+ if (assigns) {
+ Asgment asgp = (Asgment)firstnode(assigns);
+ if (!asgp)
+ return NULL;
+ (void)uremnode(assigns, &asgp->node);
+ return asgp;
+ }
+ return NULL;
+ }
+
+ /* check if name is empty */
+ if (*s == '=') {
+ zerr("bad assignment");
+ return NULL;
+ }
+ asg.name = s;
+ asg.flags = 0;
+
+ /* search for `=' */
+ for (; *s && *s != '='; s++);
+
+ /* found `=', so return with a value */
+ if (*s) {
+ *s = '\0';
+ asg.value.scalar = s + 1;
+ } else {
+ /* didn't find `=', so we only have a name */
+ asg.value.scalar = NULL;
+ }
+ (*argvp)++;
+ return &asg;
+}
+
+/* for new special parameters */
+enum {
+ NS_NONE,
+ NS_NORMAL,
+ NS_SECONDS
+};
+
+static const struct gsu_scalar tiedarr_gsu =
+{ tiedarrgetfn, tiedarrsetfn, tiedarrunsetfn };
+
+/* Install a base if we are turning on a numeric option with an argument */
+
+static int
+typeset_setbase(const char *name, Param pm, Options ops, int on, int always)
+{
+ char *arg = NULL;
+
+ if ((on & PM_INTEGER) && OPT_HASARG(ops,'i'))
+ arg = OPT_ARG(ops,'i');
+ else if ((on & PM_EFLOAT) && OPT_HASARG(ops,'E'))
+ arg = OPT_ARG(ops,'E');
+ else if ((on & PM_FFLOAT) && OPT_HASARG(ops,'F'))
+ arg = OPT_ARG(ops,'F');
+
+ if (arg) {
+ char *eptr;
+ int base = (int)zstrtol(arg, &eptr, 10);
+ if (*eptr) {
+ if (on & PM_INTEGER)
+ zwarnnam(name, "bad base value: %s", arg);
+ else
+ zwarnnam(name, "bad precision value: %s", arg);
+ return 1;
+ }
+ if ((on & PM_INTEGER) && (base < 2 || base > 36)) {
+ zwarnnam(name, "invalid base (must be 2 to 36 inclusive): %d",
+ base);
+ return 1;
+ }
+ pm->base = base;
+ } else if (always)
+ pm->base = 0;
+
+ return 0;
+}
+
+/* Install a width if we are turning on a padding option with an argument */
+
+static int
+typeset_setwidth(const char * name, Param pm, Options ops, int on, int always)
+{
+ char *arg = NULL;
+
+ if ((on & PM_LEFT) && OPT_HASARG(ops,'L'))
+ arg = OPT_ARG(ops,'L');
+ else if ((on & PM_RIGHT_B) && OPT_HASARG(ops,'R'))
+ arg = OPT_ARG(ops,'R');
+ else if ((on & PM_RIGHT_Z) && OPT_HASARG(ops,'Z'))
+ arg = OPT_ARG(ops,'Z');
+
+ if (arg) {
+ char *eptr;
+ pm->width = (int)zstrtol(arg, &eptr, 10);
+ if (*eptr) {
+ zwarnnam(name, "bad width value: %s", arg);
+ return 1;
+ }
+ } else if (always)
+ pm->width = 0;
+
+ return 0;
+}
+
+/* function to set a single parameter */
+
+/**/
+static Param
+typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
+ int on, int off, int roff, Asgment asg, Param altpm,
+ Options ops, int joinchar)
+{
+ int usepm, tc, keeplocal = 0, newspecial = NS_NONE, readonly, dont_set = 0;
+ char *subscript;
+
+ /*
+ * Do we use the existing pm? Note that this isn't the end of the
+ * story, because if we try and create a new pm at the same
+ * locallevel as an unset one we use the pm struct anyway: that's
+ * handled in createparam(). Here we just avoid using it for the
+ * present tests if it's unset.
+ *
+ * POSIXBUILTINS horror: we need to retain the 'readonly' or 'export'
+ * flags of an unset parameter.
+ */
+ usepm = pm && (!(pm->node.flags & PM_UNSET) ||
+ (isset(POSIXBUILTINS) &&
+ (pm->node.flags & (PM_READONLY|PM_EXPORTED))));
+
+ /*
+ * We need to compare types with an existing pm if special,
+ * even if that's unset
+ */
+ if (!usepm && pm && (pm->node.flags & PM_SPECIAL))
+ usepm = 2; /* indicate that we preserve the PM_UNSET flag */
+
+ /*
+ * Don't use an existing param if
+ * - the local level has changed, and
+ * - we are really locallizing the parameter
+ */
+ if (usepm && locallevel != pm->level && (on & PM_LOCAL)) {
+ /*
+ * If the original parameter was special and we're creating
+ * a new one, we need to keep it special.
+ *
+ * The -h (hide) flag prevents an existing special being made
+ * local. It can be applied either to the special or in the
+ * typeset/local statement for the local variable.
+ */
+ if ((pm->node.flags & PM_SPECIAL)
+ && !(on & PM_HIDE) && !(pm->node.flags & PM_HIDE & ~off))
+ newspecial = NS_NORMAL;
+ usepm = 0;
+ }
+
+ /* attempting a type conversion, or making a tied colonarray? */
+ tc = 0;
+ if (ASG_ARRAYP(asg) && PM_TYPE(on) == PM_SCALAR &&
+ !(usepm && (PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED))))
+ on |= PM_ARRAY;
+ if (usepm && ASG_ARRAYP(asg) && newspecial == NS_NONE &&
+ PM_TYPE(pm->node.flags) != PM_ARRAY &&
+ PM_TYPE(pm->node.flags) != PM_HASHED) {
+ if (on & (PM_EFLOAT|PM_FFLOAT|PM_INTEGER)) {
+ zerrnam(cname, "%s: can't assign array value to non-array", pname);
+ return NULL;
+ }
+ if (pm->node.flags & PM_SPECIAL) {
+ zerrnam(cname, "%s: can't assign array value to non-array special", pname);
+ return NULL;
+ }
+ tc = 1;
+ usepm = 0;
+ }
+ else if (usepm || newspecial != NS_NONE) {
+ int chflags = ((off & pm->node.flags) | (on & ~pm->node.flags)) &
+ (PM_INTEGER|PM_EFLOAT|PM_FFLOAT|PM_HASHED|
+ PM_ARRAY|PM_TIED|PM_AUTOLOAD);
+ /* keep the parameter if just switching between floating types */
+ if ((tc = chflags && chflags != (PM_EFLOAT|PM_FFLOAT)))
+ usepm = 0;
+ }
+
+ /*
+ * Extra checks if converting the type of a parameter, or if
+ * trying to remove readonlyness. It's dangerous doing either
+ * with a special or a parameter which isn't loaded yet (which
+ * may be special when it is loaded; we can't tell yet).
+ */
+ if ((readonly =
+ ((usepm || newspecial != NS_NONE) &&
+ (off & pm->node.flags & PM_READONLY))) ||
+ tc) {
+ if (pm->node.flags & PM_SPECIAL) {
+ int err = 1;
+ if (!readonly && !strcmp(pname, "SECONDS"))
+ {
+ /*
+ * We allow SECONDS to change type between integer
+ * and floating point. If we are creating a new
+ * local copy we check the type here and allow
+ * a new special to be created with that type.
+ * We then need to make sure the correct type
+ * for the special is restored at the end of the scope.
+ * If we are changing the type of an existing
+ * parameter, we do the whole thing here.
+ */
+ if (newspecial != NS_NONE)
+ {
+ /*
+ * The first test allows `typeset' to copy the
+ * existing type. This is the usual behaviour
+ * for making special parameters local.
+ */
+ if (PM_TYPE(on) == 0 || PM_TYPE(on) == PM_INTEGER ||
+ PM_TYPE(on) == PM_FFLOAT || PM_TYPE(on) == PM_EFLOAT)
+ {
+ newspecial = NS_SECONDS;
+ err = 0; /* and continue */
+ tc = 0; /* but don't do a normal conversion */
+ }
+ } else if (!setsecondstype(pm, on, off)) {
+ if (asg->value.scalar &&
+ !(pm = assignsparam(
+ pname, ztrdup(asg->value.scalar), 0)))
+ return NULL;
+ usepm = 1;
+ err = 0;
+ }
+ }
+ if (err)
+ {
+ zerrnam(cname, "%s: can't change type of a special parameter",
+ pname);
+ return NULL;
+ }
+ } else if (pm->node.flags & PM_AUTOLOAD) {
+ zerrnam(cname, "%s: can't change type of autoloaded parameter",
+ pname);
+ return NULL;
+ }
+ }
+ else if (newspecial != NS_NONE && strcmp(pname, "SECONDS") == 0)
+ newspecial = NS_SECONDS;
+
+ if (isset(POSIXBUILTINS)) {
+ /*
+ * Stricter rules about retaining readonly attribute in this case.
+ */
+ if ((on & (PM_READONLY|PM_EXPORTED)) &&
+ (!usepm || (pm->node.flags & PM_UNSET)) &&
+ !ASG_VALUEP(asg))
+ on |= PM_UNSET;
+ else if (usepm && (pm->node.flags & PM_READONLY) &&
+ !(on & PM_READONLY)) {
+ zerr("read-only variable: %s", pm->node.nam);
+ return NULL;
+ }
+ /* This is handled by createparam():
+ if (usepm && (pm->node.flags & PM_EXPORTED) && !(off & PM_EXPORTED))
+ on |= PM_EXPORTED;
+ */
+ }
+
+ /*
+ * A parameter will be local if
+ * 1. we are re-using an existing local parameter
+ * or
+ * 2. we are not using an existing parameter, but
+ * i. there is already a parameter, which will be hidden
+ * or
+ * ii. we are creating a new local parameter
+ */
+ if (usepm) {
+ if ((asg->flags & ASG_ARRAY) ?
+ !(PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)) :
+ (asg->value.scalar && (PM_TYPE(pm->node.flags &
+ (PM_ARRAY|PM_HASHED))))) {
+ zerrnam(cname, "%s: inconsistent type for assignment", pname);
+ return NULL;
+ }
+ on &= ~PM_LOCAL;
+ if (!on && !roff && !ASG_VALUEP(asg)) {
+ if (OPT_ISSET(ops,'p'))
+ paramtab->printnode(&pm->node, PRINT_TYPESET);
+ else if (!OPT_ISSET(ops,'g') &&
+ (unset(TYPESETSILENT) || OPT_ISSET(ops,'m')))
+ paramtab->printnode(&pm->node, PRINT_INCLUDEVALUE);
+ return pm;
+ }
+ if ((pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerrnam(cname, "%s: restricted", pname);
+ return pm;
+ }
+ if ((on & PM_UNIQUE) && !(pm->node.flags & PM_READONLY & ~off)) {
+ Param apm;
+ char **x;
+ if (PM_TYPE(pm->node.flags) == PM_ARRAY) {
+ x = (*pm->gsu.a->getfn)(pm);
+ uniqarray(x);
+ if (pm->node.flags & PM_SPECIAL) {
+ if (zheapptr(x))
+ x = zarrdup(x);
+ (*pm->gsu.a->setfn)(pm, x);
+ } else if (pm->ename && x)
+ arrfixenv(pm->ename, x);
+ } else if (PM_TYPE(pm->node.flags) == PM_SCALAR && pm->ename &&
+ (apm =
+ (Param) paramtab->getnode(paramtab, pm->ename))) {
+ x = (*apm->gsu.a->getfn)(apm);
+ uniqarray(x);
+ if (x)
+ arrfixenv(pm->node.nam, x);
+ }
+ }
+ if (usepm == 2) /* do not change the PM_UNSET flag */
+ pm->node.flags = (pm->node.flags | (on & ~PM_READONLY)) & ~off;
+ else {
+ /*
+ * Keep unset if using readonly in POSIX mode.
+ */
+ if (!(on & PM_READONLY) || !isset(POSIXBUILTINS))
+ off |= PM_UNSET;
+ pm->node.flags = (pm->node.flags |
+ (on & ~PM_READONLY)) & ~off;
+ }
+ if (on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) {
+ if (typeset_setwidth(cname, pm, ops, on, 0))
+ return NULL;
+ }
+ if (on & (PM_INTEGER | PM_EFLOAT | PM_FFLOAT)) {
+ if (typeset_setbase(cname, pm, ops, on, 0))
+ return NULL;
+ }
+ if (!(pm->node.flags & (PM_ARRAY|PM_HASHED))) {
+ if (pm->node.flags & PM_EXPORTED) {
+ if (!(pm->node.flags & PM_UNSET) && !pm->env && !ASG_VALUEP(asg))
+ addenv(pm, getsparam(pname));
+ } else if (pm->env && !(pm->node.flags & PM_HASHELEM))
+ delenv(pm);
+ DPUTS(ASG_ARRAYP(asg), "BUG: typeset got array value where scalar expected");
+ if (asg->value.scalar &&
+ !(pm = assignsparam(pname, ztrdup(asg->value.scalar), 0)))
+ return NULL;
+ } else if (asg->flags & ASG_ARRAY) {
+ int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0;
+ if (!(pm = assignaparam(pname, asg->value.array ?
+ zlinklist2array(asg->value.array) :
+ mkarray(NULL), flags)))
+ return NULL;
+ }
+ if (errflag)
+ return NULL;
+ pm->node.flags |= (on & PM_READONLY);
+ if (OPT_ISSET(ops,'p'))
+ paramtab->printnode(&pm->node, PRINT_TYPESET);
+ return pm;
+ }
+
+ if ((asg->flags & ASG_ARRAY) ?
+ !(on & (PM_ARRAY|PM_HASHED)) :
+ (asg->value.scalar && (on & (PM_ARRAY|PM_HASHED)))) {
+ zerrnam(cname, "%s: inconsistent type for assignment", pname);
+ return NULL;
+ }
+
+ /*
+ * We're here either because we're creating a new parameter,
+ * or we're adding a parameter at a different local level,
+ * or we're converting the type of a parameter. In the
+ * last case only, we need to delete the old parameter.
+ */
+ if (tc) {
+ /* Maintain existing readonly/exported status... */
+ on |= ~off & (PM_READONLY|PM_EXPORTED) & pm->node.flags;
+ /* ...but turn off existing readonly so we can delete it */
+ pm->node.flags &= ~PM_READONLY;
+ /*
+ * If we're just changing the type, we should keep the
+ * variable at the current level of localness.
+ */
+ keeplocal = pm->level;
+ /*
+ * Try to carry over a value, but not when changing from,
+ * to, or between non-scalar types.
+ *
+ * (We can do better now, but it does have user-visible
+ * implications.)
+ */
+ if (!ASG_VALUEP(asg) && !((pm->node.flags|on) & (PM_ARRAY|PM_HASHED))) {
+ asg->value.scalar = dupstring(getsparam(pname));
+ asg->flags = 0;
+ }
+ /* pname may point to pm->nam which is about to disappear */
+ pname = dupstring(pname);
+ unsetparam_pm(pm, 0, 1);
+ }
+
+ if (newspecial != NS_NONE) {
+ Param tpm, pm2;
+ if ((pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerrnam(cname, "%s: restricted", pname);
+ return pm;
+ }
+ if (pm->node.flags & PM_SINGLE) {
+ zerrnam(cname, "%s: can only have a single instance", pname);
+ return pm;
+ }
+ /*
+ * For specials, we keep the same struct but zero everything.
+ * Maybe it would be easier to create a new struct but copy
+ * the get/set methods.
+ */
+ tpm = (Param) zshcalloc(sizeof *tpm);
+
+ tpm->node.nam = pm->node.nam;
+ if (pm->ename &&
+ (pm2 = (Param) paramtab->getnode(paramtab, pm->ename)) &&
+ pm2->level == locallevel) {
+ /* This is getting silly, but anyway: if one of a path/PATH
+ * pair has already been made local at the current level, we
+ * have to make sure that the other one does not have its value
+ * saved: since that comes from an internal variable it will
+ * already reflect the local value, so restoring it on exit
+ * would be wrong.
+ *
+ * This problem is also why we make sure we have a copy
+ * of the environment entry in tpm->env, rather than relying
+ * on the restored value to provide it.
+ */
+ tpm->node.flags = pm->node.flags | PM_NORESTORE;
+ } else {
+ copyparam(tpm, pm, 1);
+ }
+ tpm->old = pm->old;
+ tpm->level = pm->level;
+ tpm->base = pm->base;
+ tpm->width = pm->width;
+ if (pm->env)
+ delenv(pm);
+ tpm->env = NULL;
+
+ pm->old = tpm;
+ /*
+ * The remaining on/off flags should be harmless to use,
+ * because we've checked for unpleasant surprises above.
+ */
+ pm->node.flags = (PM_TYPE(pm->node.flags) | on | PM_SPECIAL) & ~off;
+ /*
+ * Readonlyness of special parameters must be preserved.
+ */
+ pm->node.flags |= tpm->node.flags & PM_READONLY;
+ if (newspecial == NS_SECONDS) {
+ /* We save off the raw internal value of the SECONDS var */
+ tpm->u.dval = getrawseconds();
+ setsecondstype(pm, on, off);
+ }
+
+ /*
+ * Final tweak: if we've turned on one of the flags with
+ * numbers, we should use the appropriate integer.
+ */
+ if (on & (PM_LEFT|PM_RIGHT_B|PM_RIGHT_Z)) {
+ if (typeset_setwidth(cname, pm, ops, on, 1))
+ return NULL;
+ }
+ if (on & (PM_INTEGER|PM_EFLOAT|PM_FFLOAT)) {
+ if (typeset_setbase(cname, pm, ops, on, 1))
+ return NULL;
+ }
+ } else if ((subscript = strchr(pname, '['))) {
+ if (on & PM_READONLY) {
+ zerrnam(cname,
+ "%s: can't create readonly array elements", pname);
+ return NULL;
+ } else if ((on & PM_LOCAL) && locallevel) {
+ *subscript = 0;
+ pm = (Param) (paramtab == realparamtab ?
+ /* getnode2() to avoid autoloading */
+ paramtab->getnode2(paramtab, pname) :
+ paramtab->getnode(paramtab, pname));
+ *subscript = '[';
+ if (!pm || pm->level != locallevel) {
+ zerrnam(cname,
+ "%s: can't create local array elements", pname);
+ return NULL;
+ }
+ }
+ if (PM_TYPE(on) == PM_SCALAR && !ASG_ARRAYP(asg)) {
+ /*
+ * This will either complain about bad identifiers, or will set
+ * a hash element or array slice. This once worked by accident,
+ * creating a stray parameter along the way via createparam(),
+ * now called below in the isident() branch.
+ */
+ if (!(pm = assignsparam(
+ pname,
+ ztrdup(asg->value.scalar ? asg->value.scalar : ""), 0)))
+ return NULL;
+ dont_set = 1;
+ asg->flags = 0;
+ keeplocal = 0;
+ on = pm->node.flags;
+ } else if (PM_TYPE(on) == PM_ARRAY && ASG_ARRAYP(asg)) {
+ int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0;
+ if (!(pm = assignaparam(pname, asg->value.array ?
+ zlinklist2array(asg->value.array) :
+ mkarray(NULL), flags)))
+ return NULL;
+ dont_set = 1;
+ keeplocal = 0;
+ on = pm->node.flags;
+ } else {
+ zerrnam(cname,
+ "%s: inconsistent array element or slice assignment", pname);
+ return NULL;
+ }
+ }
+ /*
+ * As we can hide existing parameters, we allow a name if
+ * it's not a normal identifier but is one of the special
+ * set found in the parameter table. The second test is
+ * because we can set individual positional parameters;
+ * however "0" is not a positional parameter and is OK.
+ *
+ * It would be neater to extend isident() and be clearer
+ * about where we allow various parameter types. It's
+ * not entirely clear to me isident() should reject
+ * specially named parameters given that it accepts digits.
+ */
+ else if ((isident(pname) || paramtab->getnode(paramtab, pname))
+ && (!idigit(*pname) || !strcmp(pname, "0"))) {
+ /*
+ * Create a new node for a parameter with the flags in `on' minus the
+ * readonly flag
+ */
+ pm = createparam(pname, on & ~PM_READONLY);
+ if (!pm) {
+ if (on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z |
+ PM_INTEGER | PM_EFLOAT | PM_FFLOAT))
+ zerrnam(cname, "can't change variable attribute: %s", pname);
+ return NULL;
+ }
+ if (on & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) {
+ if (typeset_setwidth(cname, pm, ops, on, 0))
+ return NULL;
+ }
+ if (on & (PM_INTEGER | PM_EFLOAT | PM_FFLOAT)) {
+ if (typeset_setbase(cname, pm, ops, on, 0))
+ return NULL;
+ }
+ } else {
+ if (idigit(*pname))
+ zerrnam(cname, "not an identifier: %s", pname);
+ else
+ zerrnam(cname, "not valid in this context: %s", pname);
+ return NULL;
+ }
+
+ if (altpm && PM_TYPE(pm->node.flags) == PM_SCALAR) {
+ /*
+ * It seems safer to set this here than in createparam(),
+ * to make sure we only ever use the colonarr functions
+ * when u.data is correctly set.
+ */
+ struct tieddata *tdp = (struct tieddata *)
+ zalloc(sizeof(struct tieddata));
+ if (!tdp)
+ return NULL;
+ tdp->joinchar = joinchar;
+ tdp->arrptr = &altpm->u.arr;
+
+ pm->gsu.s = &tiedarr_gsu;
+ pm->u.data = tdp;
+ }
+
+ if (keeplocal)
+ pm->level = keeplocal;
+ else if (on & PM_LOCAL)
+ pm->level = locallevel;
+ if (ASG_VALUEP(asg) && !dont_set) {
+ Param ipm = pm;
+ if (pm->node.flags & (PM_ARRAY|PM_HASHED)) {
+ char **arrayval;
+ int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0;
+ if (!ASG_ARRAYP(asg)) {
+ /*
+ * Attempt to assign a scalar value to an array.
+ * This can happen if the array is special.
+ * We'll be lenient and guess what the user meant.
+ * This is how normal assigment works.
+ */
+ if (*asg->value.scalar) {
+ /* Array with one value */
+ arrayval = mkarray(ztrdup(asg->value.scalar));
+ } else {
+ /* Empty array */
+ arrayval = mkarray(NULL);
+ }
+ } else if (asg->value.array)
+ arrayval = zlinklist2array(asg->value.array);
+ else
+ arrayval = mkarray(NULL);
+ if (!(pm=assignaparam(pname, arrayval, flags)))
+ return NULL;
+ } else {
+ DPUTS(ASG_ARRAYP(asg), "BUG: inconsistent array value for scalar");
+ if (!(pm = assignsparam(pname, ztrdup(asg->value.scalar), 0)))
+ return NULL;
+ }
+ if (pm != ipm) {
+ DPUTS(ipm->node.flags != pm->node.flags,
+ "BUG: parameter recreated with wrong flags");
+ unsetparam_pm(ipm, 0, 1);
+ }
+ } else if (newspecial != NS_NONE &&
+ !(pm->old->node.flags & (PM_NORESTORE|PM_READONLY))) {
+ /*
+ * We need to use the special setting function to re-initialise
+ * the special parameter to empty.
+ */
+ switch (PM_TYPE(pm->node.flags)) {
+ case PM_SCALAR:
+ pm->gsu.s->setfn(pm, ztrdup(""));
+ break;
+ case PM_INTEGER:
+ /*
+ * Restricted integers are dangerous to initialize to 0,
+ * so don't do that.
+ */
+ if (!(pm->old->node.flags & PM_RESTRICTED))
+ pm->gsu.i->setfn(pm, 0);
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ pm->gsu.f->setfn(pm, 0.0);
+ break;
+ case PM_ARRAY:
+ pm->gsu.a->setfn(pm, mkarray(NULL));
+ break;
+ case PM_HASHED:
+ pm->gsu.h->setfn(pm, newparamtable(17, pm->node.nam));
+ break;
+ }
+ }
+ pm->node.flags |= (on & PM_READONLY);
+
+ if (OPT_ISSET(ops,'p'))
+ paramtab->printnode(&pm->node, PRINT_TYPESET);
+
+ return pm;
+}
+
+/*
+ * declare, export, float, integer, local, readonly, typeset
+ *
+ * Note the difference in interface from most builtins, covered by the
+ * BINF_ASSIGN builtin flag. This is only made use of by builtins
+ * called by reserved word, which only covers declare, local, readonly
+ * and typeset. Otherwise assigns is NULL.
+ */
+
+/**/
+int
+bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func)
+{
+ Param pm;
+ Asgment asg;
+ Patprog pprog;
+ char *optstr = TYPESET_OPTSTR;
+ int on = 0, off = 0, roff, bit = PM_ARRAY;
+ int i;
+ int returnval = 0, printflags = 0;
+ int hasargs;
+
+ /* hash -f is really the builtin `functions' */
+ if (OPT_ISSET(ops,'f'))
+ return bin_functions(name, argv, ops, func);
+
+ /* POSIX handles "readonly" specially */
+ if (func == BIN_READONLY && isset(POSIXBUILTINS) && !OPT_PLUS(ops, 'g'))
+ ops->ind['g'] = 1;
+
+ /* Translate the options into PM_* flags. *
+ * Unfortunately, this depends on the order *
+ * these flags are defined in zsh.h */
+ for (; *optstr; optstr++, bit <<= 1)
+ {
+ int optval = STOUC(*optstr);
+ if (OPT_MINUS(ops,optval))
+ on |= bit;
+ else if (OPT_PLUS(ops,optval))
+ off |= bit;
+ }
+ roff = off;
+
+ /* Sanity checks on the options. Remove conflicting options. */
+ if (on & PM_FFLOAT) {
+ off |= PM_UPPER | PM_ARRAY | PM_HASHED | PM_INTEGER | PM_EFLOAT;
+ /* Allow `float -F' to work even though float sets -E by default */
+ on &= ~PM_EFLOAT;
+ }
+ if (on & PM_EFLOAT)
+ off |= PM_UPPER | PM_ARRAY | PM_HASHED | PM_INTEGER | PM_FFLOAT;
+ if (on & PM_INTEGER)
+ off |= PM_UPPER | PM_ARRAY | PM_HASHED | PM_EFLOAT | PM_FFLOAT;
+ /*
+ * Allowing -Z with -L is a feature: left justify, suppressing
+ * leading zeroes.
+ */
+ if (on & (PM_LEFT|PM_RIGHT_Z))
+ off |= PM_RIGHT_B;
+ if (on & PM_RIGHT_B)
+ off |= PM_LEFT | PM_RIGHT_Z;
+ if (on & PM_UPPER)
+ off |= PM_LOWER;
+ if (on & PM_LOWER)
+ off |= PM_UPPER;
+ if (on & PM_HASHED)
+ off |= PM_ARRAY;
+ if (on & PM_TIED)
+ off |= PM_INTEGER | PM_EFLOAT | PM_FFLOAT | PM_ARRAY | PM_HASHED;
+
+ on &= ~off;
+
+ queue_signals();
+
+ /* Given no arguments, list whatever the options specify. */
+ if (OPT_ISSET(ops,'p')) {
+ printflags |= PRINT_TYPESET;
+ if (OPT_HASARG(ops,'p')) {
+ char *eptr;
+ int pflag = (int)zstrtol(OPT_ARG(ops,'p'), &eptr, 10);
+ if (pflag == 1 && !*eptr)
+ printflags |= PRINT_LINE;
+ else if (pflag || *eptr) {
+ zwarnnam(name, "bad argument to -p: %s", OPT_ARG(ops,'p'));
+ unqueue_signals();
+ return 1;
+ }
+ /* -p0 treated as -p for consistency */
+ }
+ }
+ hasargs = *argv != NULL || (assigns && firstnode(assigns));
+ if (!hasargs) {
+ if (!OPT_ISSET(ops,'p')) {
+ if (!(on|roff))
+ printflags |= PRINT_TYPE;
+ if (roff || OPT_ISSET(ops,'+'))
+ printflags |= PRINT_NAMEONLY;
+ }
+ scanhashtable(paramtab, 1, on|roff, 0, paramtab->printnode, printflags);
+ unqueue_signals();
+ return 0;
+ }
+
+ if (!(OPT_ISSET(ops,'g') || OPT_ISSET(ops,'x') || OPT_ISSET(ops,'m')) ||
+ OPT_PLUS(ops,'g') || *name == 'l' ||
+ (!isset(GLOBALEXPORT) && !OPT_ISSET(ops,'g')))
+ on |= PM_LOCAL;
+
+ if (on & PM_TIED) {
+ Param apm;
+ struct asgment asg0, asg2;
+ char *oldval = NULL, *joinstr;
+ int joinchar, nargs;
+
+ if (OPT_ISSET(ops,'m')) {
+ zwarnnam(name, "incompatible options for -T");
+ unqueue_signals();
+ return 1;
+ }
+ on &= ~off;
+ nargs = arrlen(argv) + (assigns ? countlinknodes(assigns) : 0);
+ if (nargs < 2) {
+ zwarnnam(name, "-T requires names of scalar and array");
+ unqueue_signals();
+ return 1;
+ }
+ if (nargs > 3) {
+ zwarnnam(name, "too many arguments for -T");
+ unqueue_signals();
+ return 1;
+ }
+
+ if (!(asg = getasg(&argv, assigns))) {
+ unqueue_signals();
+ return 1;
+ }
+ asg0 = *asg;
+ if (ASG_ARRAYP(&asg0)) {
+ unqueue_signals();
+ zwarnnam(name, "first argument of tie must be scalar: %s",
+ asg0.name);
+ return 1;
+ }
+
+ if (!(asg = getasg(&argv, assigns))) {
+ unqueue_signals();
+ return 1;
+ }
+ if (!ASG_ARRAYP(asg) && asg->value.scalar) {
+ unqueue_signals();
+ zwarnnam(name, "second argument of tie must be array: %s",
+ asg->name);
+ return 1;
+ }
+
+ if (!strcmp(asg0.name, asg->name)) {
+ unqueue_signals();
+ zerrnam(name, "can't tie a variable to itself: %s", asg0.name);
+ return 1;
+ }
+ if (strchr(asg0.name, '[') || strchr(asg->name, '[')) {
+ unqueue_signals();
+ zerrnam(name, "can't tie array elements: %s", asg0.name);
+ return 1;
+ }
+ if (ASG_VALUEP(asg) && ASG_VALUEP(&asg0)) {
+ unqueue_signals();
+ zerrnam(name, "only one tied parameter can have value: %s", asg0.name);
+ return 1;
+ }
+
+ /*
+ * Third argument, if given, is character used to join
+ * the elements of the array in the scalar.
+ */
+ if (*argv)
+ joinstr = *argv;
+ else if (assigns && firstnode(assigns)) {
+ Asgment nextasg = (Asgment)firstnode(assigns);
+ if (ASG_ARRAYP(nextasg) || ASG_VALUEP(nextasg)) {
+ zwarnnam(name, "third argument of tie must be join character");
+ unqueue_signals();
+ return 1;
+ }
+ joinstr = nextasg->name;
+ } else
+ joinstr = NULL;
+ if (!joinstr)
+ joinchar = ':';
+ else if (!*joinstr)
+ joinchar = 0;
+ else if (*joinstr == Meta)
+ joinchar = joinstr[1] ^ 32;
+ else
+ joinchar = *joinstr;
+ /*
+ * Keep the old value of the scalar. We need to do this
+ * here as if it is already tied to the same array it
+ * will be unset when we retie the array. This is all
+ * so that typeset -T is idempotent.
+ *
+ * We also need to remember here whether the damn thing is
+ * exported and pass that along. Isn't the world complicated?
+ */
+ if ((pm = (Param) paramtab->getnode(paramtab, asg0.name))
+ && !(pm->node.flags & PM_UNSET)
+ && (locallevel == pm->level || !(on & PM_LOCAL))) {
+ if (pm->node.flags & PM_TIED) {
+ unqueue_signals();
+ if (PM_TYPE(pm->node.flags) != PM_SCALAR) {
+ zwarnnam(name, "already tied as non-scalar: %s", asg0.name);
+ } else if (!strcmp(asg->name, pm->ename)) {
+ /*
+ * Already tied in the fashion requested.
+ */
+ struct tieddata *tdp = (struct tieddata*)pm->u.data;
+ int flags = (asg->flags & ASG_KEY_VALUE) ?
+ ASSPM_KEY_VALUE : 0;
+ /* Update join character */
+ tdp->joinchar = joinchar;
+ if (asg0.value.scalar)
+ assignsparam(asg0.name, ztrdup(asg0.value.scalar), 0);
+ else if (asg->value.array)
+ assignaparam(
+ asg->name, zlinklist2array(asg->value.array),flags);
+ return 0;
+ } else {
+ zwarnnam(name, "can't tie already tied scalar: %s",
+ asg0.name);
+ }
+ return 1;
+ }
+ if (!asg0.value.scalar && !asg->value.array &&
+ !(PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)))
+ oldval = ztrdup(getsparam(asg0.name));
+ on |= (pm->node.flags & PM_EXPORTED);
+ }
+ /*
+ * Create the tied array; this is normal except that
+ * it has the PM_TIED flag set. Do it first because
+ * we need the address.
+ *
+ * Don't attempt to set it yet, it's too early
+ * to be exported properly.
+ */
+ asg2.name = asg->name;
+ asg2.flags = 0;
+ asg2.value.array = (LinkList)0;
+ if (!(apm=typeset_single(name, asg->name,
+ (Param)paramtab->getnode(paramtab,
+ asg->name),
+ func, (on | PM_ARRAY) & ~PM_EXPORTED,
+ off, roff, &asg2, NULL, ops, 0))) {
+ if (oldval)
+ zsfree(oldval);
+ unqueue_signals();
+ return 1;
+ }
+ /*
+ * Create the tied colonarray. We make it as a normal scalar
+ * and fix up the oddities later.
+ */
+ if (!(pm=typeset_single(name, asg0.name,
+ (Param)paramtab->getnode(paramtab,
+ asg0.name),
+ func, on, off, roff, &asg0, apm,
+ ops, joinchar))) {
+ if (oldval)
+ zsfree(oldval);
+ unsetparam_pm(apm, 1, 1);
+ unqueue_signals();
+ return 1;
+ }
+
+ /*
+ * pm->ename is only deleted when the struct is, so
+ * we need to free it here if it already exists.
+ */
+ if (pm->ename)
+ zsfree(pm->ename);
+ pm->ename = ztrdup(asg->name);
+ if (apm->ename)
+ zsfree(apm->ename);
+ apm->ename = ztrdup(asg0.name);
+ if (asg->value.array) {
+ int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0;
+ assignaparam(asg->name, zlinklist2array(asg->value.array), flags);
+ } else if (oldval)
+ assignsparam(asg0.name, oldval, 0);
+ unqueue_signals();
+
+ return 0;
+ }
+ if (off & PM_TIED) {
+ unqueue_signals();
+ zerrnam(name, "use unset to remove tied variables");
+ return 1;
+ }
+
+ /* With the -m option, treat arguments as glob patterns */
+ if (OPT_ISSET(ops,'m')) {
+ if (!OPT_ISSET(ops,'p')) {
+ if (!(on|roff))
+ printflags |= PRINT_TYPE;
+ if (!on)
+ printflags |= PRINT_NAMEONLY;
+ }
+
+ while ((asg = getasg(&argv, assigns))) {
+ LinkList pmlist = newlinklist();
+ LinkNode pmnode;
+
+ tokenize(asg->name); /* expand argument */
+ if (!(pprog = patcompile(asg->name, 0, NULL))) {
+ untokenize(asg->name);
+ zwarnnam(name, "bad pattern : %s", asg->name);
+ returnval = 1;
+ continue;
+ }
+ if (OPT_PLUS(ops,'m') && !ASG_VALUEP(asg)) {
+ scanmatchtable(paramtab, pprog, 1, on|roff, 0,
+ paramtab->printnode, printflags);
+ continue;
+ }
+ /*
+ * Search through the parameter table and change all parameters
+ * matching the glob pattern to have these flags and/or value.
+ * Bad news: if the parameter gets altered, e.g. by
+ * a type conversion, then paramtab can be shifted around,
+ * so we need to store the parameters to alter on a separate
+ * list for later use.
+ */
+ for (i = 0; i < paramtab->hsize; i++) {
+ for (pm = (Param) paramtab->nodes[i]; pm;
+ pm = (Param) pm->node.next) {
+ if (((pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) ||
+ (pm->node.flags & PM_UNSET))
+ continue;
+ if (pattry(pprog, pm->node.nam))
+ addlinknode(pmlist, pm);
+ }
+ }
+ for (pmnode = firstnode(pmlist); pmnode; incnode(pmnode)) {
+ pm = (Param) getdata(pmnode);
+ if (!typeset_single(name, pm->node.nam, pm, func, on, off, roff,
+ asg, NULL, ops, 0))
+ returnval = 1;
+ }
+ }
+ unqueue_signals();
+ return returnval;
+ }
+
+ /* Take arguments literally. Don't glob */
+ while ((asg = getasg(&argv, assigns))) {
+ HashNode hn = (paramtab == realparamtab ?
+ /* getnode2() to avoid autoloading */
+ paramtab->getnode2(paramtab, asg->name) :
+ paramtab->getnode(paramtab, asg->name));
+ if (OPT_ISSET(ops,'p')) {
+ if (hn)
+ paramtab->printnode(hn, printflags);
+ else {
+ zwarnnam(name, "no such variable: %s", asg->name);
+ returnval = 1;
+ }
+ continue;
+ }
+ if (!typeset_single(name, asg->name, (Param)hn,
+ func, on, off, roff, asg, NULL,
+ ops, 0))
+ returnval = 1;
+ }
+ unqueue_signals();
+ return returnval;
+}
+
+/* Helper for bin_functions() when run as "autoload -X" */
+
+/**/
+int
+eval_autoload(Shfunc shf, char *name, Options ops, int func)
+{
+ if (!(shf->node.flags & PM_UNDEFINED))
+ return 1;
+
+ if (shf->funcdef) {
+ freeeprog(shf->funcdef);
+ shf->funcdef = &dummy_eprog;
+ }
+ if (OPT_MINUS(ops,'X')) {
+ char *fargv[3];
+ fargv[0] = name;
+ fargv[1] = "\"$@\"";
+ fargv[2] = 0;
+ shf->funcdef = mkautofn(shf);
+ return bin_eval(name, fargv, ops, func);
+ }
+
+ return !loadautofn(shf, (OPT_ISSET(ops,'k') ? 2 :
+ (OPT_ISSET(ops,'z') ? 0 : 1)), 1,
+ OPT_ISSET(ops,'d'));
+}
+
+/* Helper for bin_functions() for -X and -r options */
+
+/**/
+static int
+check_autoload(Shfunc shf, char *name, Options ops, int func)
+{
+ if (OPT_ISSET(ops,'X'))
+ {
+ return eval_autoload(shf, name, ops, func);
+ }
+ if ((OPT_ISSET(ops,'r') || OPT_ISSET(ops,'R')) &&
+ (shf->node.flags & PM_UNDEFINED))
+ {
+ char *dir_path;
+ if (shf->filename && (shf->node.flags & PM_LOADDIR)) {
+ char *spec_path[2];
+ spec_path[0] = shf->filename;
+ spec_path[1] = NULL;
+ if (getfpfunc(shf->node.nam, NULL, &dir_path, spec_path, 1)) {
+ /* shf->filename is already correct. */
+ return 0;
+ }
+ if (!OPT_ISSET(ops,'d')) {
+ if (OPT_ISSET(ops,'R')) {
+ zerr("%s: function definition file not found",
+ shf->node.nam);
+ return 1;
+ }
+ return 0;
+ }
+ }
+ if (getfpfunc(shf->node.nam, NULL, &dir_path, NULL, 1)) {
+ dircache_set(&shf->filename, NULL);
+ if (*dir_path != '/') {
+ dir_path = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP),
+ "/", dir_path);
+ dir_path = xsymlink(dir_path, 1);
+ }
+ dircache_set(&shf->filename, dir_path);
+ shf->node.flags |= PM_LOADDIR;
+ return 0;
+ }
+ if (OPT_ISSET(ops,'R')) {
+ zerr("%s: function definition file not found",
+ shf->node.nam);
+ return 1;
+ }
+ /* with -r, we don't flag an error, just let it be found later. */
+ }
+ return 0;
+}
+
+/* List a user-defined math function. */
+static void
+listusermathfunc(MathFunc p)
+{
+ int showargs;
+
+ if (p->module)
+ showargs = 3;
+ else if (p->maxargs != (p->minargs ? p->minargs : -1))
+ showargs = 2;
+ else if (p->minargs)
+ showargs = 1;
+ else
+ showargs = 0;
+
+ printf("functions -M%s %s", (p->flags & MFF_STR) ? "s" : "", p->name);
+ if (showargs) {
+ printf(" %d", p->minargs);
+ showargs--;
+ }
+ if (showargs) {
+ printf(" %d", p->maxargs);
+ showargs--;
+ }
+ if (showargs) {
+ /*
+ * function names are not required to consist of ident characters
+ */
+ putchar(' ');
+ quotedzputs(p->module, stdout);
+ showargs--;
+ }
+ putchar('\n');
+}
+
+
+static void
+add_autoload_function(Shfunc shf, char *funcname)
+{
+ char *nam;
+ if (*funcname == '/' && funcname[1] &&
+ (nam = strrchr(funcname, '/')) && nam[1] &&
+ (shf->node.flags & PM_UNDEFINED)) {
+ char *dir;
+ nam = strrchr(funcname, '/');
+ if (nam == funcname) {
+ dir = "/";
+ } else {
+ *nam++ = '\0';
+ dir = funcname;
+ }
+ dircache_set(&shf->filename, NULL);
+ dircache_set(&shf->filename, dir);
+ shf->node.flags |= PM_LOADDIR;
+ shf->node.flags |= PM_ABSPATH_USED;
+ shfunctab->addnode(shfunctab, ztrdup(nam), shf);
+ } else {
+ Shfunc shf2;
+ Funcstack fs;
+ const char *calling_f = NULL;
+ char buf[PATH_MAX+1];
+
+ /* Find calling function */
+ for (fs = funcstack; fs; fs = fs->prev) {
+ if (fs->tp == FS_FUNC && fs->name && (!shf->node.nam || 0 != strcmp(fs->name,shf->node.nam))) {
+ calling_f = fs->name;
+ break;
+ }
+ }
+
+ /* Get its directory */
+ if (calling_f) {
+ /* Should contain load directory, and be loaded via absolute path */
+ if ((shf2 = (Shfunc) shfunctab->getnode2(shfunctab, calling_f))
+ && (shf2->node.flags & PM_LOADDIR) && (shf2->node.flags & PM_ABSPATH_USED)
+ && shf2->filename)
+ {
+ if (strlen(shf2->filename) + strlen(funcname) + 1 < PATH_MAX)
+ {
+ sprintf(buf, "%s/%s", shf2->filename, funcname);
+ /* Set containing directory if the function file
+ * exists (do normal FPATH processing otherwise) */
+ if (!access(buf, R_OK)) {
+ dircache_set(&shf->filename, NULL);
+ dircache_set(&shf->filename, shf2->filename);
+ shf->node.flags |= PM_LOADDIR;
+ shf->node.flags |= PM_ABSPATH_USED;
+ }
+ }
+ }
+ }
+
+ shfunctab->addnode(shfunctab, ztrdup(funcname), shf);
+ }
+}
+
+/* Display or change the attributes of shell functions. *
+ * If called as autoload, it will define a new autoloaded *
+ * (undefined) shell function. */
+
+/**/
+int
+bin_functions(char *name, char **argv, Options ops, int func)
+{
+ Patprog pprog;
+ Shfunc shf;
+ int i, returnval = 0;
+ int on = 0, off = 0, pflags = 0, roff, expand = 0;
+
+ /* Do we have any flags defined? */
+ if (OPT_PLUS(ops,'u'))
+ off |= PM_UNDEFINED;
+ else if (OPT_MINUS(ops,'u') || OPT_ISSET(ops,'X'))
+ on |= PM_UNDEFINED;
+ if (OPT_MINUS(ops,'U'))
+ on |= PM_UNALIASED|PM_UNDEFINED;
+ else if (OPT_PLUS(ops,'U'))
+ off |= PM_UNALIASED;
+ if (OPT_MINUS(ops,'t'))
+ on |= PM_TAGGED;
+ else if (OPT_PLUS(ops,'t'))
+ off |= PM_TAGGED;
+ if (OPT_MINUS(ops,'T'))
+ on |= PM_TAGGED_LOCAL;
+ else if (OPT_PLUS(ops,'T'))
+ off |= PM_TAGGED_LOCAL;
+ if (OPT_MINUS(ops,'W'))
+ on |= PM_WARNNESTED;
+ else if (OPT_PLUS(ops,'W'))
+ off |= PM_WARNNESTED;
+ roff = off;
+ if (OPT_MINUS(ops,'z')) {
+ on |= PM_ZSHSTORED;
+ off |= PM_KSHSTORED;
+ } else if (OPT_PLUS(ops,'z')) {
+ off |= PM_ZSHSTORED;
+ roff |= PM_ZSHSTORED;
+ }
+ if (OPT_MINUS(ops,'k')) {
+ on |= PM_KSHSTORED;
+ off |= PM_ZSHSTORED;
+ } else if (OPT_PLUS(ops,'k')) {
+ off |= PM_KSHSTORED;
+ roff |= PM_KSHSTORED;
+ }
+ if (OPT_MINUS(ops,'d')) {
+ on |= PM_CUR_FPATH;
+ off |= PM_CUR_FPATH;
+ } else if (OPT_PLUS(ops,'d')) {
+ off |= PM_CUR_FPATH;
+ roff |= PM_CUR_FPATH;
+ }
+
+ if ((off & PM_UNDEFINED) || (OPT_ISSET(ops,'k') && OPT_ISSET(ops,'z')) ||
+ (OPT_ISSET(ops,'x') && !OPT_HASARG(ops,'x')) ||
+ (OPT_MINUS(ops,'X') && (OPT_ISSET(ops,'m') || !scriptname))) {
+ zwarnnam(name, "invalid option(s)");
+ return 1;
+ }
+
+ if (OPT_ISSET(ops,'x')) {
+ char *eptr;
+ expand = (int)zstrtol(OPT_ARG(ops,'x'), &eptr, 10);
+ if (*eptr) {
+ zwarnnam(name, "number expected after -x");
+ return 1;
+ }
+ if (expand == 0) /* no indentation at all */
+ expand = -1;
+ }
+
+ if (OPT_PLUS(ops,'f') || roff || OPT_ISSET(ops,'+'))
+ pflags |= PRINT_NAMEONLY;
+
+ if (OPT_MINUS(ops,'M') || OPT_PLUS(ops,'M')) {
+ MathFunc p, q, prev;
+ /*
+ * Add/remove/list function as mathematical.
+ */
+ if (on || off || pflags || OPT_ISSET(ops,'X') || OPT_ISSET(ops,'u')
+ || OPT_ISSET(ops,'U') || OPT_ISSET(ops,'w')) {
+ zwarnnam(name, "invalid option(s)");
+ return 1;
+ }
+ if (!*argv) {
+ /* List functions. */
+ queue_signals();
+ for (p = mathfuncs; p; p = p->next)
+ if (p->flags & MFF_USERFUNC)
+ listusermathfunc(p);
+ unqueue_signals();
+ } else if (OPT_ISSET(ops,'m')) {
+ /* List matching functions. */
+ for (; *argv; argv++) {
+ queue_signals();
+ tokenize(*argv);
+ if ((pprog = patcompile(*argv, PAT_STATIC, 0))) {
+ for (p = mathfuncs, q = NULL; p; q = p) {
+ MathFunc next;
+ do {
+ next = NULL;
+ if ((p->flags & MFF_USERFUNC) &&
+ pattry(pprog, p->name)) {
+ if (OPT_PLUS(ops,'M')) {
+ next = p->next;
+ removemathfunc(q, p);
+ p = next;
+ } else
+ listusermathfunc(p);
+ }
+ /* if we deleted one, retry with the new p */
+ } while (next);
+ if (p)
+ p = p->next;
+ }
+ } else {
+ untokenize(*argv);
+ zwarnnam(name, "bad pattern : %s", *argv);
+ returnval = 1;
+ }
+ unqueue_signals();
+ }
+ } else if (OPT_PLUS(ops,'M')) {
+ /* Delete functions. -m is allowed but is handled above. */
+ for (; *argv; argv++) {
+ queue_signals();
+ for (p = mathfuncs, q = NULL; p; q = p, p = p->next) {
+ if (!strcmp(p->name, *argv)) {
+ if (!(p->flags & MFF_USERFUNC)) {
+ zwarnnam(name, "+M %s: is a library function",
+ *argv);
+ returnval = 1;
+ break;
+ }
+ removemathfunc(q, p);
+ break;
+ }
+ }
+ unqueue_signals();
+ }
+ } else {
+ /* Add a function */
+ int minargs, maxargs;
+ char *funcname = *argv++;
+ char *modname = NULL;
+ char *ptr;
+
+ if (OPT_ISSET(ops,'s')) {
+ minargs = maxargs = 1;
+ } else {
+ minargs = 0;
+ maxargs = -1;
+ }
+
+ ptr = itype_end(funcname, IIDENT, 0);
+ if (idigit(*funcname) || funcname == ptr || *ptr) {
+ zwarnnam(name, "-M %s: bad math function name", funcname);
+ return 1;
+ }
+
+ if (*argv) {
+ minargs = (int)zstrtol(*argv, &ptr, 0);
+ if (minargs < 0 || *ptr) {
+ zwarnnam(name, "-M: invalid min number of arguments: %s",
+ *argv);
+ return 1;
+ }
+ if (OPT_ISSET(ops,'s') && minargs != 1) {
+ zwarnnam(name, "-Ms: must take a single string argument");
+ return 1;
+ }
+ maxargs = minargs;
+ argv++;
+ }
+ if (*argv) {
+ maxargs = (int)zstrtol(*argv, &ptr, 0);
+ if (maxargs < -1 ||
+ (maxargs != -1 && maxargs < minargs) ||
+ *ptr) {
+ zwarnnam(name,
+ "-M: invalid max number of arguments: %s",
+ *argv);
+ return 1;
+ }
+ if (OPT_ISSET(ops,'s') && maxargs != 1) {
+ zwarnnam(name, "-Ms: must take a single string argument");
+ return 1;
+ }
+ argv++;
+ }
+ if (*argv)
+ modname = *argv++;
+ if (*argv) {
+ zwarnnam(name, "-M: too many arguments");
+ return 1;
+ }
+
+ p = (MathFunc)zshcalloc(sizeof(struct mathfunc));
+ p->name = ztrdup(funcname);
+ p->flags = MFF_USERFUNC;
+ if (OPT_ISSET(ops,'s'))
+ p->flags |= MFF_STR;
+ p->module = modname ? ztrdup(modname) : NULL;
+ p->minargs = minargs;
+ p->maxargs = maxargs;
+
+ queue_signals();
+ for (q = mathfuncs, prev = NULL; q; prev = q, q = q->next) {
+ if (!strcmp(q->name, funcname)) {
+ removemathfunc(prev, q);
+ break;
+ }
+ }
+
+ p->next = mathfuncs;
+ mathfuncs = p;
+ unqueue_signals();
+ }
+
+ return returnval;
+ }
+
+ if (OPT_MINUS(ops,'X')) {
+ Funcstack fs;
+ char *funcname = NULL;
+ int ret;
+ if (*argv && argv[1]) {
+ zwarnnam(name, "-X: too many arguments");
+ return 1;
+ }
+ queue_signals();
+ for (fs = funcstack; fs; fs = fs->prev) {
+ if (fs->tp == FS_FUNC) {
+ /*
+ * dupstring here is paranoia but unlikely to be
+ * problematic
+ */
+ funcname = dupstring(fs->name);
+ break;
+ }
+ }
+ if (!funcname)
+ {
+ zerrnam(name, "bad autoload");
+ ret = 1;
+ } else {
+ if ((shf = (Shfunc) shfunctab->getnode(shfunctab, funcname))) {
+ DPUTS(!shf->funcdef,
+ "BUG: Calling autoload from empty function");
+ } else {
+ shf = (Shfunc) zshcalloc(sizeof *shf);
+ shfunctab->addnode(shfunctab, ztrdup(funcname), shf);
+ }
+ if (*argv) {
+ dircache_set(&shf->filename, NULL);
+ dircache_set(&shf->filename, *argv);
+ on |= PM_LOADDIR;
+ }
+ shf->node.flags = on;
+ ret = eval_autoload(shf, funcname, ops, func);
+ }
+ unqueue_signals();
+ return ret;
+ } else if (!*argv) {
+ /* If no arguments given, we will print functions. If flags *
+ * are given, we will print only functions containing these *
+ * flags, else we'll print them all. */
+ int ret = 0;
+
+ queue_signals();
+ if (OPT_ISSET(ops,'U') && !OPT_ISSET(ops,'u'))
+ on &= ~PM_UNDEFINED;
+ scanshfunc(1, on|off, DISABLED, shfunctab->printnode,
+ pflags, expand);
+ unqueue_signals();
+ return ret;
+ }
+
+ /* With the -m option, treat arguments as glob patterns */
+ if (OPT_ISSET(ops,'m')) {
+ on &= ~PM_UNDEFINED;
+ for (; *argv; argv++) {
+ queue_signals();
+ /* expand argument */
+ tokenize(*argv);
+ if ((pprog = patcompile(*argv, PAT_STATIC, 0))) {
+ /* with no options, just print all functions matching the glob pattern */
+ if (!(on|off) && !OPT_ISSET(ops,'X')) {
+ scanmatchshfunc(pprog, 1, 0, DISABLED,
+ shfunctab->printnode, pflags, expand);
+ } else {
+ /* apply the options to all functions matching the glob pattern */
+ for (i = 0; i < shfunctab->hsize; i++) {
+ for (shf = (Shfunc) shfunctab->nodes[i]; shf;
+ shf = (Shfunc) shf->node.next)
+ if (pattry(pprog, shf->node.nam) &&
+ !(shf->node.flags & DISABLED)) {
+ shf->node.flags = (shf->node.flags |
+ (on & ~PM_UNDEFINED)) & ~off;
+ if (check_autoload(shf, shf->node.nam,
+ ops, func)) {
+ returnval = 1;
+ }
+ }
+ }
+ }
+ } else {
+ untokenize(*argv);
+ zwarnnam(name, "bad pattern : %s", *argv);
+ returnval = 1;
+ }
+ unqueue_signals();
+ }
+ return returnval;
+ }
+
+ /* Take the arguments literally -- do not glob */
+ queue_signals();
+ for (; *argv; argv++) {
+ if (OPT_ISSET(ops,'w'))
+ returnval = dump_autoload(name, *argv, on, ops, func);
+ else if ((shf = (Shfunc) shfunctab->getnode(shfunctab, *argv))) {
+ /* if any flag was given */
+ if (on|off) {
+ /* turn on/off the given flags */
+ shf->node.flags = (shf->node.flags | (on & ~PM_UNDEFINED)) & ~off;
+ if (check_autoload(shf, shf->node.nam, ops, func))
+ returnval = 1;
+ } else
+ /* no flags, so just print */
+ printshfuncexpand(&shf->node, pflags, expand);
+ } else if (on & PM_UNDEFINED) {
+ int signum = -1, ok = 1;
+
+ if (!strncmp(*argv, "TRAP", 4) &&
+ (signum = getsignum(*argv + 4)) != -1) {
+ /*
+ * Because of the possibility of alternative names,
+ * we must remove the trap explicitly.
+ */
+ removetrapnode(signum);
+ }
+
+ if (**argv == '/') {
+ char *base = strrchr(*argv, '/') + 1;
+ if (*base &&
+ (shf = (Shfunc) shfunctab->getnode(shfunctab, base))) {
+ char *dir;
+ /* turn on/off the given flags */
+ shf->node.flags =
+ (shf->node.flags | (on & ~PM_UNDEFINED)) & ~off;
+ if (shf->node.flags & PM_UNDEFINED) {
+ /* update path if not yet loaded */
+ if (base == *argv + 1)
+ dir = "/";
+ else {
+ dir = *argv;
+ base[-1] = '\0';
+ }
+ dircache_set(&shf->filename, NULL);
+ dircache_set(&shf->filename, dir);
+ }
+ if (check_autoload(shf, shf->node.nam, ops, func))
+ returnval = 1;
+ continue;
+ }
+ }
+
+ /* Add a new undefined (autoloaded) function to the *
+ * hash table with the corresponding flags set. */
+ shf = (Shfunc) zshcalloc(sizeof *shf);
+ shf->node.flags = on;
+ shf->funcdef = mkautofn(shf);
+ shfunc_set_sticky(shf);
+ add_autoload_function(shf, *argv);
+
+ if (signum != -1) {
+ if (settrap(signum, NULL, ZSIG_FUNC)) {
+ shfunctab->removenode(shfunctab, *argv);
+ shfunctab->freenode(&shf->node);
+ returnval = 1;
+ ok = 0;
+ }
+ }
+
+ if (ok && check_autoload(shf, shf->node.nam, ops, func))
+ returnval = 1;
+ } else
+ returnval = 1;
+ }
+ unqueue_signals();
+ return returnval;
+}
+
+/**/
+Eprog
+mkautofn(Shfunc shf)
+{
+ Eprog p;
+
+ p = (Eprog) zalloc(sizeof(*p));
+ p->len = 5 * sizeof(wordcode);
+ p->prog = (Wordcode) zalloc(p->len);
+ p->strs = NULL;
+ p->shf = shf;
+ p->npats = 0;
+ p->nref = 1; /* allocated from permanent storage */
+ p->pats = (Patprog *) p->prog;
+ p->flags = EF_REAL;
+ p->dump = NULL;
+
+ p->prog[0] = WCB_LIST((Z_SYNC | Z_END), 0);
+ p->prog[1] = WCB_SUBLIST(WC_SUBLIST_END, 0, 3);
+ p->prog[2] = WCB_PIPE(WC_PIPE_END, 0);
+ p->prog[3] = WCB_AUTOFN();
+ p->prog[4] = WCB_END();
+
+ return p;
+}
+
+/* unset: unset parameters */
+
+/**/
+int
+bin_unset(char *name, char **argv, Options ops, int func)
+{
+ Param pm, next;
+ Patprog pprog;
+ char *s;
+ int match = 0, returnval = 0;
+ int i;
+
+ /* unset -f is the same as unfunction */
+ if (OPT_ISSET(ops,'f'))
+ return bin_unhash(name, argv, ops, func);
+
+ /* with -m option, treat arguments as glob patterns */
+ if (OPT_ISSET(ops,'m')) {
+ while ((s = *argv++)) {
+ queue_signals();
+ /* expand */
+ tokenize(s);
+ if ((pprog = patcompile(s, PAT_STATIC, NULL))) {
+ /* Go through the parameter table, and unset any matches */
+ for (i = 0; i < paramtab->hsize; i++) {
+ for (pm = (Param) paramtab->nodes[i]; pm; pm = next) {
+ /* record pointer to next, since we may free this one */
+ next = (Param) pm->node.next;
+ if ((!(pm->node.flags & PM_RESTRICTED) ||
+ unset(RESTRICTED)) &&
+ pattry(pprog, pm->node.nam)) {
+ unsetparam_pm(pm, 0, 1);
+ match++;
+ }
+ }
+ }
+ } else {
+ untokenize(s);
+ zwarnnam(name, "bad pattern : %s", s);
+ returnval = 1;
+ }
+ unqueue_signals();
+ }
+ /* If we didn't match anything, we return 1. */
+ if (!match)
+ returnval = 1;
+ return returnval;
+ }
+
+ /* do not glob -- unset the given parameter */
+ queue_signals();
+ while ((s = *argv++)) {
+ char *ss = strchr(s, '['), *subscript = 0;
+ if (ss) {
+ char *sse;
+ *ss = 0;
+ if ((sse = parse_subscript(ss+1, 1, ']'))) {
+ *sse = 0;
+ subscript = dupstring(ss+1);
+ *sse = ']';
+ remnulargs(subscript);
+ untokenize(subscript);
+ }
+ }
+ if ((ss && !subscript) || !isident(s)) {
+ if (ss)
+ *ss = '[';
+ zerrnam(name, "%s: invalid parameter name", s);
+ returnval = 1;
+ continue;
+ }
+ pm = (Param) (paramtab == realparamtab ?
+ /* getnode2() to avoid autoloading */
+ paramtab->getnode2(paramtab, s) :
+ paramtab->getnode(paramtab, s));
+ /*
+ * Unsetting an unset variable is not an error.
+ * This appears to be reasonably standard behaviour.
+ */
+ if (!pm)
+ continue;
+ else if ((pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerrnam(name, "%s: restricted", pm->node.nam);
+ returnval = 1;
+ } else if (ss) {
+ if (PM_TYPE(pm->node.flags) == PM_HASHED) {
+ HashTable tht = paramtab;
+ if ((paramtab = pm->gsu.h->getfn(pm)))
+ unsetparam(subscript);
+ paramtab = tht;
+ } else if (PM_TYPE(pm->node.flags) == PM_SCALAR ||
+ PM_TYPE(pm->node.flags) == PM_ARRAY) {
+ struct value vbuf;
+ vbuf.isarr = (PM_TYPE(pm->node.flags) == PM_ARRAY ?
+ SCANPM_ARRONLY : 0);
+ vbuf.pm = pm;
+ vbuf.flags = 0;
+ vbuf.start = 0;
+ vbuf.end = -1;
+ vbuf.arr = 0;
+ *ss = '[';
+ if (getindex(&ss, &vbuf, SCANPM_ASSIGNING) == 0 &&
+ vbuf.pm && !(vbuf.pm->node.flags & PM_UNSET)) {
+ if (PM_TYPE(pm->node.flags) == PM_SCALAR) {
+ setstrvalue(&vbuf, ztrdup(""));
+ } else {
+ /* start is after the element for reverse index */
+ int start = vbuf.start - !!(vbuf.flags & VALFLAG_INV);
+ if (arrlen_gt(vbuf.pm->u.arr, start)) {
+ char *arr[2];
+ arr[0] = "";
+ arr[1] = 0;
+ setarrvalue(&vbuf, zarrdup(arr));
+ }
+ }
+ }
+ returnval = errflag;
+ errflag &= ~ERRFLAG_ERROR;
+ } else {
+ zerrnam(name, "%s: invalid element for unset", s);
+ returnval = 1;
+ }
+ } else {
+ if (unsetparam_pm(pm, 0, 1))
+ returnval = 1;
+ }
+ if (ss)
+ *ss = '[';
+ }
+ unqueue_signals();
+ return returnval;
+}
+
+/* type, whence, which, command */
+
+static LinkList matchednodes;
+
+static void
+fetchcmdnamnode(HashNode hn, UNUSED(int printflags))
+{
+ Cmdnam cn = (Cmdnam) hn;
+ addlinknode(matchednodes, cn->node.nam);
+}
+
+/**/
+int
+bin_whence(char *nam, char **argv, Options ops, int func)
+{
+ HashNode hn;
+ Patprog pprog;
+ int returnval = 0;
+ int printflags = 0;
+ int aliasflags;
+ int csh, all, v, wd;
+ int informed = 0;
+ int expand = 0;
+ char *cnam, **allmatched = 0;
+
+ /* Check some option information */
+ csh = OPT_ISSET(ops,'c');
+ v = OPT_ISSET(ops,'v');
+ all = OPT_ISSET(ops,'a');
+ wd = OPT_ISSET(ops,'w');
+
+ if (OPT_ISSET(ops,'x')) {
+ char *eptr;
+ expand = (int)zstrtol(OPT_ARG(ops,'x'), &eptr, 10);
+ if (*eptr) {
+ zwarnnam(nam, "number expected after -x");
+ return 1;
+ }
+ if (expand == 0) /* no indentation at all */
+ expand = -1;
+ }
+
+ if (OPT_ISSET(ops,'w'))
+ printflags |= PRINT_WHENCE_WORD;
+ else if (OPT_ISSET(ops,'c'))
+ printflags |= PRINT_WHENCE_CSH;
+ else if (OPT_ISSET(ops,'v'))
+ printflags |= PRINT_WHENCE_VERBOSE;
+ else
+ printflags |= PRINT_WHENCE_SIMPLE;
+ if (OPT_ISSET(ops,'f'))
+ printflags |= PRINT_WHENCE_FUNCDEF;
+
+ if (func == BIN_COMMAND)
+ if (OPT_ISSET(ops,'V')) {
+ printflags = aliasflags = PRINT_WHENCE_VERBOSE;
+ v = 1;
+ } else {
+ aliasflags = PRINT_LIST;
+ printflags = PRINT_WHENCE_SIMPLE;
+ v = 0;
+ }
+ else
+ aliasflags = printflags;
+
+ /* With -m option -- treat arguments as a glob patterns */
+ if (OPT_ISSET(ops,'m')) {
+ cmdnamtab->filltable(cmdnamtab);
+ if (all) {
+ pushheap();
+ matchednodes = newlinklist();
+ }
+ queue_signals();
+ for (; *argv; argv++) {
+ /* parse the pattern */
+ tokenize(*argv);
+ if (!(pprog = patcompile(*argv, PAT_STATIC, NULL))) {
+ untokenize(*argv);
+ zwarnnam(nam, "bad pattern : %s", *argv);
+ returnval = 1;
+ continue;
+ }
+ if (!OPT_ISSET(ops,'p')) {
+ /* -p option is for path search only. *
+ * We're not using it, so search for ... */
+
+ /* aliases ... */
+ informed +=
+ scanmatchtable(aliastab, pprog, 1, 0, DISABLED,
+ aliastab->printnode, printflags);
+
+ /* and reserved words ... */
+ informed +=
+ scanmatchtable(reswdtab, pprog, 1, 0, DISABLED,
+ reswdtab->printnode, printflags);
+
+ /* and shell functions... */
+ informed +=
+ scanmatchshfunc(pprog, 1, 0, DISABLED,
+ shfunctab->printnode, printflags, expand);
+
+ /* and builtins. */
+ informed +=
+ scanmatchtable(builtintab, pprog, 1, 0, DISABLED,
+ builtintab->printnode, printflags);
+ }
+ /* Done search for `internal' commands, if the -p option *
+ * was not used. Now search the path. */
+ informed +=
+ scanmatchtable(cmdnamtab, pprog, 1, 0, 0,
+ (all ? fetchcmdnamnode : cmdnamtab->printnode),
+ printflags);
+ run_queued_signals();
+ }
+ unqueue_signals();
+ if (all) {
+ allmatched = argv = zlinklist2array(matchednodes);
+ matchednodes = NULL;
+ popheap();
+ } else
+ return returnval || !informed;
+ }
+
+ /* Take arguments literally -- do not glob */
+ queue_signals();
+ for (; *argv; argv++) {
+ if (!OPT_ISSET(ops,'p') && !allmatched) {
+ char *suf;
+
+ /* Look for alias */
+ if ((hn = aliastab->getnode(aliastab, *argv))) {
+ aliastab->printnode(hn, aliasflags);
+ informed = 1;
+ if (!all)
+ continue;
+ }
+ /* Look for suffix alias */
+ if ((suf = strrchr(*argv, '.')) && suf[1] &&
+ suf > *argv && suf[-1] != Meta &&
+ (hn = sufaliastab->getnode(sufaliastab, suf+1))) {
+ sufaliastab->printnode(hn, printflags);
+ informed = 1;
+ if (!all)
+ continue;
+ }
+ /* Look for reserved word */
+ if ((hn = reswdtab->getnode(reswdtab, *argv))) {
+ reswdtab->printnode(hn, printflags);
+ informed = 1;
+ if (!all)
+ continue;
+ }
+ /* Look for shell function */
+ if ((hn = shfunctab->getnode(shfunctab, *argv))) {
+ printshfuncexpand(hn, printflags, expand);
+ informed = 1;
+ if (!all)
+ continue;
+ }
+ /* Look for builtin command */
+ if ((hn = builtintab->getnode(builtintab, *argv))) {
+ builtintab->printnode(hn, printflags);
+ informed = 1;
+ if (!all)
+ continue;
+ }
+ /* Look for commands that have been added to the *
+ * cmdnamtab with the builtin `hash foo=bar'. */
+ if ((hn = cmdnamtab->getnode(cmdnamtab, *argv)) && (hn->flags & HASHED)) {
+ cmdnamtab->printnode(hn, printflags);
+ informed = 1;
+ if (!all)
+ continue;
+ }
+ }
+
+ /* Option -a is to search the entire path, *
+ * rather than just looking for one match. */
+ if (all && **argv != '/') {
+ char **pp, *buf;
+
+ pushheap();
+ for (pp = path; *pp; pp++) {
+ if (**pp) {
+ buf = zhtricat(*pp, "/", *argv);
+ } else buf = dupstring(*argv);
+
+ if (iscom(buf)) {
+ if (wd) {
+ printf("%s: command\n", *argv);
+ } else {
+ if (v && !csh) {
+ zputs(*argv, stdout), fputs(" is ", stdout);
+ quotedzputs(buf, stdout);
+ } else
+ zputs(buf, stdout);
+ if (OPT_ISSET(ops,'s') || OPT_ISSET(ops, 'S'))
+ print_if_link(buf, OPT_ISSET(ops, 'S'));
+ fputc('\n', stdout);
+ }
+ informed = 1;
+ }
+ }
+ if (!informed && (wd || v || csh)) {
+ /* this is information and not an error so, as in csh, use stdout */
+ zputs(*argv, stdout);
+ puts(wd ? ": none" : " not found");
+ returnval = 1;
+ }
+ popheap();
+ } else if (func == BIN_COMMAND && OPT_ISSET(ops,'p') &&
+ (hn = builtintab->getnode(builtintab, *argv))) {
+ /*
+ * Special case for "command -p[vV]" which needs to
+ * show a builtin in preference to an external command.
+ */
+ builtintab->printnode(hn, printflags);
+ informed = 1;
+ } else if ((cnam = findcmd(*argv, 1,
+ func == BIN_COMMAND &&
+ OPT_ISSET(ops,'p')))) {
+ /* Found external command. */
+ if (wd) {
+ printf("%s: command\n", *argv);
+ } else {
+ if (v && !csh) {
+ zputs(*argv, stdout), fputs(" is ", stdout);
+ quotedzputs(cnam, stdout);
+ } else
+ zputs(cnam, stdout);
+ if (OPT_ISSET(ops,'s') || OPT_ISSET(ops,'S'))
+ print_if_link(cnam, OPT_ISSET(ops,'S'));
+ fputc('\n', stdout);
+ }
+ informed = 1;
+ } else {
+ /* Not found at all. That's not an error as such so this goes to stdout */
+ if (v || csh || wd)
+ zputs(*argv, stdout), puts(wd ? ": none" : " not found");
+ returnval = 1;
+ }
+ }
+ if (allmatched)
+ freearray(allmatched);
+
+ unqueue_signals();
+ return returnval || !informed;
+}
+
+/**** command & named directory hash table builtins ****/
+
+/*****************************************************************
+ * hash -- explicitly hash a command. *
+ * 1) Given no arguments, list the hash table. *
+ * 2) The -m option prints out commands in the hash table that *
+ * match a given glob pattern. *
+ * 3) The -f option causes the entire path to be added to the *
+ * hash table (cannot be combined with any arguments). *
+ * 4) The -r option causes the entire hash table to be discarded *
+ * (cannot be combined with any arguments). *
+ * 5) Given argument of the form foo=bar, add element to command *
+ * hash table, so that when `foo' is entered, then `bar' is *
+ * executed. *
+ * 6) Given arguments not of the previous form, add it to the *
+ * command hash table as if it were being executed. *
+ * 7) The -d option causes analogous things to be done using *
+ * the named directory hash table. *
+ *****************************************************************/
+
+/**/
+int
+bin_hash(char *name, char **argv, Options ops, UNUSED(int func))
+{
+ HashTable ht;
+ Patprog pprog;
+ Asgment asg;
+ int returnval = 0;
+ int printflags = 0;
+
+ if (OPT_ISSET(ops,'d'))
+ ht = nameddirtab;
+ else
+ ht = cmdnamtab;
+
+ if (OPT_ISSET(ops,'r') || OPT_ISSET(ops,'f')) {
+ /* -f and -r can't be used with any arguments */
+ if (*argv) {
+ zwarnnam("hash", "too many arguments");
+ return 1;
+ }
+
+ /* empty the hash table */
+ if (OPT_ISSET(ops,'r'))
+ ht->emptytable(ht);
+
+ /* fill the hash table in a standard way */
+ if (OPT_ISSET(ops,'f'))
+ ht->filltable(ht);
+
+ return 0;
+ }
+
+ if (OPT_ISSET(ops,'L')) printflags |= PRINT_LIST;
+
+ /* Given no arguments, display current hash table. */
+ if (!*argv) {
+ queue_signals();
+ scanhashtable(ht, 1, 0, 0, ht->printnode, printflags);
+ unqueue_signals();
+ return 0;
+ }
+
+ queue_signals();
+ while (*argv) {
+ void *hn;
+ if (OPT_ISSET(ops,'m')) {
+ /* with the -m option, treat the argument as a glob pattern */
+ tokenize(*argv); /* expand */
+ if ((pprog = patcompile(*argv, PAT_STATIC, NULL))) {
+ /* display matching hash table elements */
+ scanmatchtable(ht, pprog, 1, 0, 0, ht->printnode, printflags);
+ } else {
+ untokenize(*argv);
+ zwarnnam(name, "bad pattern : %s", *argv);
+ returnval = 1;
+ }
+ argv++;
+ continue;
+ }
+ if (!(asg = getasg(&argv, NULL))) {
+ zwarnnam(name, "bad assignment");
+ returnval = 1;
+ break;
+ } else if (ASG_VALUEP(asg)) {
+ if(isset(RESTRICTED)) {
+ zwarnnam(name, "restricted: %s", asg->value.scalar);
+ returnval = 1;
+ } else {
+ /* The argument is of the form foo=bar, *
+ * so define an entry for the table. */
+ if(OPT_ISSET(ops,'d')) {
+ /* shouldn't return NULL if asg->name is not NULL */
+ if (*itype_end(asg->name, IUSER, 0)) {
+ zwarnnam(name,
+ "invalid character in directory name: %s",
+ asg->name);
+ returnval = 1;
+ continue;
+ } else {
+ Nameddir nd = hn = zshcalloc(sizeof *nd);
+ nd->node.flags = 0;
+ nd->dir = ztrdup(asg->value.scalar);
+ }
+ } else {
+ Cmdnam cn = hn = zshcalloc(sizeof *cn);
+ cn->node.flags = HASHED;
+ cn->u.cmd = ztrdup(asg->value.scalar);
+ }
+ ht->addnode(ht, ztrdup(asg->name), hn);
+ if(OPT_ISSET(ops,'v'))
+ ht->printnode(hn, 0);
+ }
+ } else if (!(hn = ht->getnode2(ht, asg->name))) {
+ /* With no `=value' part to the argument, *
+ * work out what it ought to be. */
+ if(OPT_ISSET(ops,'d')) {
+ if(!getnameddir(asg->name)) {
+ zwarnnam(name, "no such directory name: %s", asg->name);
+ returnval = 1;
+ }
+ } else {
+ if (!hashcmd(asg->name, path)) {
+ zwarnnam(name, "no such command: %s", asg->name);
+ returnval = 1;
+ }
+ }
+ if(OPT_ISSET(ops,'v') && (hn = ht->getnode2(ht, asg->name)))
+ ht->printnode(hn, 0);
+ } else if(OPT_ISSET(ops,'v'))
+ ht->printnode(hn, 0);
+ }
+ unqueue_signals();
+ return returnval;
+}
+
+/* unhash: remove specified elements from a hash table */
+
+/**/
+int
+bin_unhash(char *name, char **argv, Options ops, int func)
+{
+ HashTable ht;
+ HashNode hn, nhn;
+ Patprog pprog;
+ int match = 0, returnval = 0, all = 0;
+ int i;
+
+ /* Check which hash table we are working with. */
+ if (func == BIN_UNALIAS) {
+ if (OPT_ISSET(ops,'s'))
+ ht = sufaliastab; /* suffix aliases */
+ else
+ ht = aliastab; /* aliases */
+ if (OPT_ISSET(ops, 'a')) {
+ if (*argv) {
+ zwarnnam(name, "-a: too many arguments");
+ return 1;
+ }
+ all = 1;
+ } else if (!*argv) {
+ zwarnnam(name, "not enough arguments");
+ return 1;
+ }
+ } else if (OPT_ISSET(ops,'d'))
+ ht = nameddirtab; /* named directories */
+ else if (OPT_ISSET(ops,'f'))
+ ht = shfunctab; /* shell functions */
+ else if (OPT_ISSET(ops,'s'))
+ ht = sufaliastab; /* suffix aliases, must precede aliases */
+ else if (func == BIN_UNHASH && (OPT_ISSET(ops,'a')))
+ ht = aliastab; /* aliases */
+ else
+ ht = cmdnamtab; /* external commands */
+
+ if (all) {
+ queue_signals();
+ for (i = 0; i < ht->hsize; i++) {
+ for (hn = ht->nodes[i]; hn; hn = nhn) {
+ /* record pointer to next, since we may free this one */
+ nhn = hn->next;
+ ht->freenode(ht->removenode(ht, hn->nam));
+ }
+ }
+ unqueue_signals();
+ return 0;
+ }
+
+ /* With -m option, treat arguments as glob patterns. *
+ * "unhash -m '*'" is legal, but not recommended. */
+ if (OPT_ISSET(ops,'m')) {
+ for (; *argv; argv++) {
+ queue_signals();
+ /* expand argument */
+ tokenize(*argv);
+ if ((pprog = patcompile(*argv, PAT_STATIC, NULL))) {
+ /* remove all nodes matching glob pattern */
+ for (i = 0; i < ht->hsize; i++) {
+ for (hn = ht->nodes[i]; hn; hn = nhn) {
+ /* record pointer to next, since we may free this one */
+ nhn = hn->next;
+ if (pattry(pprog, hn->nam)) {
+ ht->freenode(ht->removenode(ht, hn->nam));
+ match++;
+ }
+ }
+ }
+ } else {
+ untokenize(*argv);
+ zwarnnam(name, "bad pattern : %s", *argv);
+ returnval = 1;
+ }
+ unqueue_signals();
+ }
+ /* If we didn't match anything, we return 1. */
+ if (!match)
+ returnval = 1;
+ return returnval;
+ }
+
+ /* Take arguments literally -- do not glob */
+ queue_signals();
+ for (; *argv; argv++) {
+ if ((hn = ht->removenode(ht, *argv))) {
+ ht->freenode(hn);
+ } else if (func == BIN_UNSET && isset(POSIXBUILTINS)) {
+ /* POSIX: unset: "Unsetting a variable or function that was *
+ * not previously set shall not be considered an error." */
+ returnval = 0;
+ } else {
+ zwarnnam(name, "no such hash table element: %s", *argv);
+ returnval = 1;
+ }
+ }
+ unqueue_signals();
+ return returnval;
+}
+
+/**** alias builtins ****/
+
+/* alias: display or create aliases. */
+
+/**/
+int
+bin_alias(char *name, char **argv, Options ops, UNUSED(int func))
+{
+ Alias a;
+ Patprog pprog;
+ Asgment asg;
+ int returnval = 0;
+ int flags1 = 0, flags2 = DISABLED;
+ int printflags = 0;
+ int type_opts;
+ HashTable ht = aliastab;
+
+ /* Did we specify the type of alias? */
+ type_opts = OPT_ISSET(ops, 'r') + OPT_ISSET(ops, 'g') +
+ OPT_ISSET(ops, 's');
+ if (type_opts) {
+ if (type_opts > 1) {
+ zwarnnam(name, "illegal combination of options");
+ return 1;
+ }
+ if (OPT_ISSET(ops,'g'))
+ flags1 |= ALIAS_GLOBAL;
+ else
+ flags2 |= ALIAS_GLOBAL;
+ if (OPT_ISSET(ops, 's')) {
+ /*
+ * Although we keep suffix aliases in a different table,
+ * it is useful to be able to distinguish Alias structures
+ * without reference to the table, so we have a separate
+ * flag, too.
+ */
+ flags1 |= ALIAS_SUFFIX;
+ ht = sufaliastab;
+ } else
+ flags2 |= ALIAS_SUFFIX;
+ }
+
+ if (OPT_ISSET(ops,'L'))
+ printflags |= PRINT_LIST;
+ else if (OPT_PLUS(ops,'g') || OPT_PLUS(ops,'r') || OPT_PLUS(ops,'s') ||
+ OPT_PLUS(ops,'m') || OPT_ISSET(ops,'+'))
+ printflags |= PRINT_NAMEONLY;
+
+ /* In the absence of arguments, list all aliases. If a command *
+ * line flag is specified, list only those of that type. */
+ if (!*argv) {
+ queue_signals();
+ scanhashtable(ht, 1, flags1, flags2, ht->printnode, printflags);
+ unqueue_signals();
+ return 0;
+ }
+
+ /* With the -m option, treat the arguments as *
+ * glob patterns of aliases to display. */
+ if (OPT_ISSET(ops,'m')) {
+ for (; *argv; argv++) {
+ queue_signals();
+ tokenize(*argv); /* expand argument */
+ if ((pprog = patcompile(*argv, PAT_STATIC, NULL))) {
+ /* display the matching aliases */
+ scanmatchtable(ht, pprog, 1, flags1, flags2,
+ ht->printnode, printflags);
+ } else {
+ untokenize(*argv);
+ zwarnnam(name, "bad pattern : %s", *argv);
+ returnval = 1;
+ }
+ unqueue_signals();
+ }
+ return returnval;
+ }
+
+ /* Take arguments literally. Don't glob */
+ queue_signals();
+ while ((asg = getasg(&argv, NULL))) {
+ if (asg->value.scalar && !OPT_ISSET(ops,'L')) {
+ /* The argument is of the form foo=bar and we are not *
+ * forcing a listing with -L, so define an alias */
+ ht->addnode(ht, ztrdup(asg->name),
+ createaliasnode(ztrdup(asg->value.scalar), flags1));
+ } else if ((a = (Alias) ht->getnode(ht, asg->name))) {
+ /* display alias if appropriate */
+ if (!type_opts || ht == sufaliastab ||
+ (OPT_ISSET(ops,'r') &&
+ !(a->node.flags & (ALIAS_GLOBAL|ALIAS_SUFFIX))) ||
+ (OPT_ISSET(ops,'g') && (a->node.flags & ALIAS_GLOBAL)))
+ ht->printnode(&a->node, printflags);
+ } else
+ returnval = 1;
+ }
+ unqueue_signals();
+ return returnval;
+}
+
+
+/**** miscellaneous builtins ****/
+
+/* true, : (colon) */
+
+/**/
+int
+bin_true(UNUSED(char *name), UNUSED(char **argv), UNUSED(Options ops), UNUSED(int func))
+{
+ return 0;
+}
+
+/* false builtin */
+
+/**/
+int
+bin_false(UNUSED(char *name), UNUSED(char **argv), UNUSED(Options ops), UNUSED(int func))
+{
+ return 1;
+}
+
+/* the zle buffer stack */
+
+/**/
+mod_export LinkList bufstack;
+
+/* echo, print, printf, pushln */
+
+#define print_val(VAL) \
+ if (prec >= 0) \
+ count += fprintf(fout, spec, width, prec, VAL); \
+ else \
+ count += fprintf(fout, spec, width, VAL);
+
+/*
+ * Because of the use of getkeystring() to interpret the arguments,
+ * the elements of args spend a large part of the function unmetafied
+ * with the lengths in len. This may have seemed a good idea once.
+ * As we are stuck with this for now, we need to be very careful
+ * deciding what state args is in.
+ */
+
+/**/
+int
+bin_print(char *name, char **args, Options ops, int func)
+{
+ int flen, width, prec, type, argc, n, narg, curlen = 0;
+ int nnl = 0, fmttrunc = 0, ret = 0, maxarg = 0, nc = 0;
+ int flags[6], *len, visarr = 0;
+ char *start, *endptr, *c, *d, *flag, *buf = NULL, spec[14], *fmt = NULL;
+ char **first, **argp, *curarg, *flagch = "'0+- #", save = '\0', nullstr = '\0';
+ size_t rcount = 0, count = 0;
+ size_t *cursplit = 0, *splits = 0;
+ FILE *fout = stdout;
+#ifdef HAVE_OPEN_MEMSTREAM
+ size_t mcount;
+#define ASSIGN_MSTREAM(BUF,FOUT) \
+ do { \
+ if ((FOUT = open_memstream(&BUF, &mcount)) == NULL) { \
+ zwarnnam(name, "open_memstream failed"); \
+ return 1; \
+ } \
+ } while (0)
+ /*
+ * Some implementations of open_memstream() have a bug such that,
+ * if fflush() is followed by fclose(), another NUL byte is written
+ * to the buffer at the wrong position. Therefore we must fclose()
+ * before reading.
+ */
+#define READ_MSTREAM(BUF,FOUT) \
+ ((fclose(FOUT) == 0) ? mcount : (size_t)-1)
+#define CLOSE_MSTREAM(FOUT) 0
+
+#else /* simulate HAVE_OPEN_MEMSTREAM */
+
+#define ASSIGN_MSTREAM(BUF,FOUT) \
+ do { \
+ int tempfd; \
+ char *tmpf; \
+ if ((tempfd = gettempfile(NULL, 1, &tmpf)) < 0) { \
+ zwarnnam(name, "can't open temp file: %e", errno); \
+ return 1; \
+ } \
+ unlink(tmpf); \
+ if ((FOUT = fdopen(tempfd, "w+")) == NULL) { \
+ close(tempfd); \
+ zwarnnam(name, "can't open temp file: %e", errno); \
+ return 1; \
+ } \
+ } while (0)
+#define READ_MSTREAM(BUF,FOUT) \
+ ((((count = ftell(FOUT)), (BUF = (char *)zalloc(count + 1))) && \
+ ((fseek(FOUT, 0L, SEEK_SET) == 0) && !(BUF[count] = '\0')) && \
+ (fread(BUF, 1, count, FOUT) == count)) ? count : (size_t)-1)
+#define CLOSE_MSTREAM(FOUT) fclose(FOUT)
+
+#endif
+
+#define IS_MSTREAM(FOUT) \
+ (FOUT != stdout && \
+ (OPT_ISSET(ops,'z') || OPT_ISSET(ops,'s') || OPT_ISSET(ops,'v')))
+
+ /* Testing EBADF special-cases >&- redirections */
+#define CLOSE_CLEANLY(FOUT) \
+ (IS_MSTREAM(FOUT) ? CLOSE_MSTREAM(FOUT) == 0 : \
+ ((FOUT == stdout) ? (fflush(FOUT) == 0 || errno == EBADF) : \
+ (fclose(FOUT) == 0))) /* implies error for -u on a closed fd */
+
+ Histent ent;
+ mnumber mnumval;
+ double doubleval;
+ int intval;
+ zlong zlongval;
+ zulong zulongval;
+ char *stringval;
+
+ /* Error check option combinations and option arguments */
+
+ if (OPT_ISSET(ops, 'z') +
+ OPT_ISSET(ops, 's') + OPT_ISSET(ops, 'S') +
+ OPT_ISSET(ops, 'v') > 1) {
+ zwarnnam(name, "only one of -s, -S, -v, or -z allowed");
+ return 1;
+ }
+ if ((OPT_ISSET(ops, 'z') | OPT_ISSET(ops, 's') | OPT_ISSET(ops, 'S')) +
+ (OPT_ISSET(ops, 'c') | OPT_ISSET(ops, 'C')) > 1) {
+ zwarnnam(name, "-c or -C not allowed with -s, -S, or -z");
+ return 1;
+ }
+ if ((OPT_ISSET(ops, 'z') | OPT_ISSET(ops, 'v') |
+ OPT_ISSET(ops, 's') | OPT_ISSET(ops, 'S')) +
+ (OPT_ISSET(ops, 'p') | OPT_ISSET(ops, 'u')) > 1) {
+ zwarnnam(name, "-p or -u not allowed with -s, -S, -v, or -z");
+ return 1;
+ }
+ /*
+ if (OPT_ISSET(ops, 'f') &&
+ (OPT_ISSET(ops, 'S') || OPT_ISSET(ops, 'c') || OPT_ISSET(ops, 'C'))) {
+ zwarnnam(name, "-f not allowed with -c, -C, or -S");
+ return 1;
+ }
+ */
+
+ /* -C -- number of columns */
+ if (!fmt && OPT_ISSET(ops,'C')) {
+ char *eptr, *argptr = OPT_ARG(ops,'C');
+ nc = (int)zstrtol(argptr, &eptr, 10);
+ if (*eptr) {
+ zwarnnam(name, "number expected after -%c: %s", 'C', argptr);
+ return 1;
+ }
+ if (nc <= 0) {
+ zwarnnam(name, "invalid number of columns: %s", argptr);
+ return 1;
+ }
+ }
+
+ if (func == BIN_PRINTF) {
+ if (!strcmp(*args, "--") && !*++args) {
+ zwarnnam(name, "not enough arguments");
+ return 1;
+ }
+ fmt = *args++;
+ } else if (func == BIN_ECHO && isset(BSDECHO))
+ ops->ind['E'] = 1;
+ else if (OPT_HASARG(ops,'f'))
+ fmt = OPT_ARG(ops,'f');
+ if (fmt)
+ fmt = getkeystring(fmt, &flen, OPT_ISSET(ops,'b') ? GETKEYS_BINDKEY :
+ GETKEYS_PRINTF_FMT, &fmttrunc);
+
+ first = args;
+
+ /* -m option -- treat the first argument as a pattern and remove
+ * arguments not matching */
+ if (OPT_ISSET(ops,'m')) {
+ Patprog pprog;
+ char **t, **p;
+
+ if (!*args) {
+ zwarnnam(name, "no pattern specified");
+ return 1;
+ }
+ queue_signals();
+ tokenize(*args);
+ if (!(pprog = patcompile(*args, PAT_STATIC, NULL))) {
+ untokenize(*args);
+ zwarnnam(name, "bad pattern: %s", *args);
+ unqueue_signals();
+ return 1;
+ }
+ for (t = p = ++args; *p; p++)
+ if (pattry(pprog, *p))
+ *t++ = *p;
+ *t = NULL;
+ first = args;
+ unqueue_signals();
+ if (fmt && !*args) return 0;
+ }
+ /* compute lengths, and interpret according to -P, -D, -e, etc. */
+ argc = arrlen(args);
+ len = (int *) hcalloc(argc * sizeof(int));
+ for (n = 0; n < argc; n++) {
+ /* first \ sequences */
+ if (fmt ||
+ (!OPT_ISSET(ops,'e') &&
+ (OPT_ISSET(ops,'R') || OPT_ISSET(ops,'r') || OPT_ISSET(ops,'E'))))
+ unmetafy(args[n], &len[n]);
+ else {
+ int escape_how;
+ if (OPT_ISSET(ops,'b'))
+ escape_how = GETKEYS_BINDKEY;
+ else if (func != BIN_ECHO && !OPT_ISSET(ops,'e'))
+ escape_how = GETKEYS_PRINT;
+ else
+ escape_how = GETKEYS_ECHO;
+ args[n] = getkeystring(args[n], &len[n], escape_how, &nnl);
+ if (nnl) {
+ /* If there was a \c escape, make this the last arg. */
+ argc = n + 1;
+ args[argc] = NULL;
+ }
+ }
+ /* -P option -- interpret as a prompt sequence */
+ if (OPT_ISSET(ops,'P')) {
+ /*
+ * promptexpand uses permanent storage: to avoid
+ * messy memory management, stick it on the heap
+ * instead.
+ */
+ char *str = unmetafy(
+ promptexpand(metafy(args[n], len[n], META_NOALLOC),
+ 0, NULL, NULL, NULL),
+ &len[n]);
+ args[n] = dupstrpfx(str, len[n]);
+ free(str);
+ }
+ /* -D option -- interpret as a directory, and use ~ */
+ if (OPT_ISSET(ops,'D')) {
+ Nameddir d;
+
+ queue_signals();
+ /* TODO: finddir takes a metafied file */
+ d = finddir(args[n]);
+ if (d) {
+ int dirlen = strlen(d->dir);
+ char *arg = zhalloc(len[n] - dirlen + strlen(d->node.nam) + 2);
+ sprintf(arg, "~%s%s", d->node.nam, args[n] + dirlen);
+ args[n] = arg;
+ len[n] = strlen(args[n]);
+ }
+ unqueue_signals();
+ }
+ }
+
+ /* -o and -O -- sort the arguments */
+ if (OPT_ISSET(ops,'o') || OPT_ISSET(ops,'O')) {
+ int flags;
+
+ if (fmt && !*args)
+ return 0;
+ flags = OPT_ISSET(ops,'i') ? SORTIT_IGNORING_CASE : 0;
+ if (OPT_ISSET(ops,'O'))
+ flags |= SORTIT_BACKWARDS;
+ strmetasort(args, flags, len);
+ }
+
+ /* -u and -p -- output to other than standard output */
+ if ((OPT_HASARG(ops,'u') || OPT_ISSET(ops,'p')) &&
+ /* rule out conflicting options -- historical precedence */
+ ((!fmt && (OPT_ISSET(ops,'c') || OPT_ISSET(ops,'C'))) ||
+ !(OPT_ISSET(ops, 'z') || OPT_ISSET(ops, 'v') ||
+ OPT_ISSET(ops, 's') || OPT_ISSET(ops, 'S')))) {
+ int fdarg, fd;
+
+ if (OPT_ISSET(ops, 'p')) {
+ fdarg = coprocout;
+ if (fdarg < 0) {
+ zwarnnam(name, "-p: no coprocess");
+ return 1;
+ }
+ } else {
+ char *argptr = OPT_ARG(ops,'u'), *eptr;
+ /* Handle undocumented feature that -up worked */
+ if (!strcmp(argptr, "p")) {
+ fdarg = coprocout;
+ if (fdarg < 0) {
+ zwarnnam(name, "-p: no coprocess");
+ return 1;
+ }
+ } else {
+ fdarg = (int)zstrtol(argptr, &eptr, 10);
+ if (*eptr) {
+ zwarnnam(name, "number expected after -u: %s", argptr);
+ return 1;
+ }
+ }
+ }
+
+ if ((fd = dup(fdarg)) < 0) {
+ zwarnnam(name, "bad file number: %d", fdarg);
+ return 1;
+ }
+ if ((fout = fdopen(fd, "w")) == 0) {
+ close(fd);
+ zwarnnam(name, "bad mode on fd %d", fd);
+ return 1;
+ }
+ }
+
+ if (OPT_ISSET(ops, 'v') ||
+ (fmt && (OPT_ISSET(ops,'z') || OPT_ISSET(ops,'s'))))
+ ASSIGN_MSTREAM(buf,fout);
+
+ /* -c -- output in columns */
+ if (!fmt && (OPT_ISSET(ops,'c') || OPT_ISSET(ops,'C'))) {
+ int l, nr, sc, n, t, i;
+#ifdef MULTIBYTE_SUPPORT
+ int *widths;
+
+ if (isset(MULTIBYTE)) {
+ int *wptr;
+
+ /*
+ * We need the character widths to align output in
+ * columns.
+ */
+ wptr = widths = (int *) zhalloc(argc * sizeof(int));
+ for (i = 0; i < argc && args[i]; i++, wptr++) {
+ int l = len[i], width = 0;
+ char *aptr = args[i];
+ mbstate_t mbs;
+
+ memset(&mbs, 0, sizeof(mbstate_t));
+ while (l > 0) {
+ wchar_t wc;
+ size_t cnt;
+ int wcw;
+
+ /*
+ * Prevent misaligned columns due to escape sequences by
+ * skipping over them. Octals \033 and \233 are the
+ * possible escape characters recognized by ANSI.
+ *
+ * It ought to be possible to do this in the case
+ * of prompt expansion by propagating the information
+ * about escape sequences (currently we strip this
+ * out).
+ */
+ if (*aptr == '\033' || *aptr == '\233') {
+ for (aptr++, l--;
+ l && !isalpha(STOUC(*aptr));
+ aptr++, l--)
+ ;
+ aptr++;
+ l--;
+ continue;
+ }
+
+ cnt = mbrtowc(&wc, aptr, l, &mbs);
+
+ if (cnt == MB_INCOMPLETE || cnt == MB_INVALID)
+ {
+ /* treat as ordinary string */
+ width += l;
+ break;
+ }
+ wcw = WCWIDTH(wc);
+ /* treat unprintable as 0 */
+ if (wcw > 0)
+ width += wcw;
+ /* skip over NUL normally */
+ if (cnt == 0)
+ cnt = 1;
+ aptr += cnt;
+ l -= cnt;
+ }
+ widths[i] = width;
+ }
+ }
+ else
+ widths = len;
+#else
+ int *widths = len;
+#endif
+
+ if (OPT_ISSET(ops,'C')) {
+ /*
+ * n: number of elements
+ * nc: number of columns (above)
+ * nr: number of rows
+ */
+ n = arrlen(args);
+ nr = (n + nc - 1) / nc;
+
+ /*
+ * i: loop counter
+ * l: maximum length seen
+ *
+ * Ignore lengths in last column since they don't affect
+ * the separation.
+ */
+ for (i = l = 0; i < argc; i++) {
+ if (OPT_ISSET(ops, 'a')) {
+ if ((i % nc) == nc - 1)
+ continue;
+ } else {
+ if (i >= nr * (nc - 1))
+ break;
+ }
+ if (l < widths[i])
+ l = widths[i];
+ }
+ sc = l + 2;
+ }
+ else
+ {
+ /*
+ * n: loop counter
+ * l: maximum length seen
+ */
+ for (n = l = 0; n < argc; n++)
+ if (l < widths[n])
+ l = widths[n];
+
+ /*
+ * sc: column width
+ * nc: number of columns (at least one)
+ */
+ sc = l + 2;
+ nc = (zterm_columns + 1) / sc;
+ if (!nc)
+ nc = 1;
+ nr = (n + nc - 1) / nc;
+ }
+
+ if (OPT_ISSET(ops,'a')) /* print across, i.e. columns first */
+ n = 0;
+ for (i = 0; i < nr; i++) {
+ if (OPT_ISSET(ops,'a'))
+ {
+ int ic;
+ for (ic = 0; ic < nc && n < argc; ic++, n++)
+ {
+ fwrite(args[n], len[n], 1, fout);
+ l = widths[n];
+ if (n < argc)
+ for (; l < sc; l++)
+ fputc(' ', fout);
+ }
+ }
+ else
+ {
+ n = i;
+ do {
+ fwrite(args[n], len[n], 1, fout);
+ l = widths[n];
+ for (t = nr; t && n < argc; t--, n++);
+ if (n < argc)
+ for (; l < sc; l++)
+ fputc(' ', fout);
+ } while (n < argc);
+ }
+ fputc(OPT_ISSET(ops,'N') ? '\0' : '\n', fout);
+ }
+ if (IS_MSTREAM(fout) && (rcount = READ_MSTREAM(buf,fout)) == -1)
+ ret = 1;
+ if (!CLOSE_CLEANLY(fout) || ret) {
+ zwarnnam(name, "write error: %e", errno);
+ ret = 1;
+ }
+ if (buf) {
+ /* assert: we must be doing -v at this point */
+ queue_signals();
+ if (ret)
+ free(buf);
+ else
+ setsparam(OPT_ARG(ops, 'v'),
+ metafy(buf, rcount, META_REALLOC));
+ unqueue_signals();
+ }
+ return ret;
+ }
+
+ /* normal output */
+ if (!fmt) {
+ if (OPT_ISSET(ops, 'z') || OPT_ISSET(ops, 'v') ||
+ OPT_ISSET(ops, 's') || OPT_ISSET(ops, 'S')) {
+ /*
+ * We don't want the arguments unmetafied after all.
+ */
+ for (n = 0; n < argc; n++)
+ metafy(args[n], len[n], META_NOALLOC);
+ }
+
+ /* -z option -- push the arguments onto the editing buffer stack */
+ if (OPT_ISSET(ops,'z')) {
+ queue_signals();
+ zpushnode(bufstack, sepjoin(args, NULL, 0));
+ unqueue_signals();
+ return 0;
+ }
+ /* -s option -- add the arguments to the history list */
+ if (OPT_ISSET(ops,'s') || OPT_ISSET(ops,'S')) {
+ int nwords = 0, nlen, iwords;
+ char **pargs = args;
+
+ queue_signals();
+ while (*pargs++)
+ nwords++;
+ if (nwords) {
+ if (OPT_ISSET(ops,'S')) {
+ int wordsize;
+ short *words;
+ if (nwords > 1) {
+ zwarnnam(name, "option -S takes a single argument");
+ unqueue_signals();
+ return 1;
+ }
+ words = NULL;
+ wordsize = 0;
+ histsplitwords(*args, &words, &wordsize, &nwords, 1);
+ ent = prepnexthistent();
+ ent->words = (short *)zalloc(nwords*sizeof(short));
+ memcpy(ent->words, words, nwords*sizeof(short));
+ free(words);
+ ent->nwords = nwords/2;
+ } else {
+ ent = prepnexthistent();
+ ent->words = (short *)zalloc(nwords*2*sizeof(short));
+ ent->nwords = nwords;
+ nlen = iwords = 0;
+ for (pargs = args; *pargs; pargs++) {
+ ent->words[iwords++] = nlen;
+ nlen += strlen(*pargs);
+ ent->words[iwords++] = nlen;
+ nlen++;
+ }
+ }
+ } else {
+ ent = prepnexthistent();
+ ent->words = (short *)NULL;
+ }
+ ent->node.nam = zjoin(args, ' ', 0);
+ ent->stim = ent->ftim = time(NULL);
+ ent->node.flags = 0;
+ addhistnode(histtab, ent->node.nam, ent);
+ unqueue_signals();
+ return 0;
+ }
+
+ if (OPT_HASARG(ops, 'x') || OPT_HASARG(ops, 'X')) {
+ char *eptr;
+ int expand, startpos = 0;
+ int all = OPT_HASARG(ops, 'X');
+ char *xarg = all ? OPT_ARG(ops, 'X') : OPT_ARG(ops, 'x');
+
+ expand = (int)zstrtol(xarg, &eptr, 10);
+ if (*eptr || expand <= 0) {
+ zwarnnam(name, "positive integer expected after -%c: %s", 'x',
+ xarg);
+ return 1;
+ }
+ for (; *args; args++, len++) {
+ startpos = zexpandtabs(*args, *len, expand, startpos, fout,
+ all);
+ if (args[1]) {
+ if (OPT_ISSET(ops, 'l')) {
+ fputc('\n', fout);
+ startpos = 0;
+ } else if (OPT_ISSET(ops,'N')) {
+ fputc('\0', fout);
+ } else {
+ fputc(' ', fout);
+ startpos++;
+ }
+ }
+ }
+ } else {
+ for (; *args; args++, len++) {
+ fwrite(*args, *len, 1, fout);
+ if (args[1])
+ fputc(OPT_ISSET(ops,'l') ? '\n' :
+ OPT_ISSET(ops,'N') ? '\0' : ' ', fout);
+ }
+ }
+ if (!(OPT_ISSET(ops,'n') || nnl ||
+ (OPT_ISSET(ops, 'v') && !OPT_ISSET(ops, 'l'))))
+ fputc(OPT_ISSET(ops,'N') ? '\0' : '\n', fout);
+ if (IS_MSTREAM(fout) && (rcount = READ_MSTREAM(buf,fout)) == -1)
+ ret = 1;
+ if (!CLOSE_CLEANLY(fout) || ret) {
+ zwarnnam(name, "write error: %e", errno);
+ ret = 1;
+ }
+ if (buf) {
+ /* assert: we must be doing -v at this point */
+ queue_signals();
+ if (ret)
+ free(buf);
+ else
+ setsparam(OPT_ARG(ops, 'v'),
+ metafy(buf, rcount, META_REALLOC));
+ unqueue_signals();
+ }
+ return ret;
+ }
+
+ /*
+ * All the remaining code in this function is for printf-style
+ * output (printf itself, or print -f). We still have to handle
+ * special cases of printing to a ZLE buffer or the history, however.
+ */
+
+ if (OPT_ISSET(ops,'v')) {
+ struct value vbuf;
+ char* s = OPT_ARG(ops,'v');
+ Value v = getvalue(&vbuf, &s, 0);
+ visarr = v && PM_TYPE(v->pm->node.flags) == PM_ARRAY;
+ }
+ /* printf style output */
+ *spec = '%';
+ argp = args;
+ do {
+ rcount = count;
+ if (argp > args && visarr) { /* reusing format string */
+ if (!splits)
+ cursplit = splits = (size_t *)zhalloc(sizeof(size_t) *
+ (arrlen(args) / (argp - args) + 1));
+ *cursplit++ = count;
+ }
+ if (maxarg) {
+ first += maxarg;
+ argc -= maxarg;
+ maxarg = 0;
+ }
+ for (c = fmt; c-fmt < flen; c++) {
+ if (*c != '%') {
+ putc(*c, fout);
+ ++count;
+ continue;
+ }
+
+ start = c++;
+ if (*c == '%') {
+ putc('%', fout);
+ ++count;
+ continue;
+ }
+
+ type = prec = -1;
+ width = 0;
+ curarg = NULL;
+ d = spec + 1;
+
+ if (*c >= '1' && *c <= '9') {
+ narg = strtoul(c, &endptr, 0);
+ if (*endptr == '$') {
+ c = endptr + 1;
+ DPUTS(narg <= 0, "specified zero or negative arg");
+ if (narg > argc) {
+ zwarnnam(name, "%d: argument specifier out of range",
+ narg);
+ if (fout != stdout)
+ fclose(fout);
+#ifdef HAVE_OPEN_MEMSTREAM
+ if (buf)
+ free(buf);
+#endif
+ return 1;
+ } else {
+ if (narg > maxarg) maxarg = narg;
+ curarg = *(first + narg - 1);
+ curlen = len[first - args + narg - 1];
+ }
+ }
+ }
+
+ /* copy only one of each flag as spec has finite size */
+ memset(flags, 0, sizeof(flags));
+ while (*c && (flag = strchr(flagch, *c))) {
+ if (!flags[flag - flagch]) {
+ flags[flag - flagch] = 1;
+ *d++ = *c;
+ }
+ c++;
+ }
+
+ if (idigit(*c)) {
+ width = strtoul(c, &endptr, 0);
+ c = endptr;
+ } else if (*c == '*') {
+ if (idigit(*++c)) {
+ narg = strtoul(c, &endptr, 0);
+ if (*endptr == '$') {
+ c = endptr + 1;
+ if (narg > argc || narg <= 0) {
+ zwarnnam(name,
+ "%d: argument specifier out of range",
+ narg);
+ if (fout != stdout)
+ fclose(fout);
+#ifdef HAVE_OPEN_MEMSTREAM
+ if (buf)
+ free(buf);
+#endif
+ return 1;
+ } else {
+ if (narg > maxarg) maxarg = narg;
+ argp = first + narg - 1;
+ }
+ }
+ }
+ if (*argp) {
+ width = (int)mathevali(*argp++);
+ if (errflag) {
+ errflag &= ~ERRFLAG_ERROR;
+ ret = 1;
+ }
+ }
+ }
+ *d++ = '*';
+
+ if (*c == '.') {
+ if (*++c == '*') {
+ if (idigit(*++c)) {
+ narg = strtoul(c, &endptr, 0);
+ if (*endptr == '$') {
+ c = endptr + 1;
+ if (narg > argc || narg <= 0) {
+ zwarnnam(name,
+ "%d: argument specifier out of range",
+ narg);
+ if (fout != stdout)
+ fclose(fout);
+#ifdef HAVE_OPEN_MEMSTREAM
+ if (buf)
+ free(buf);
+#endif
+ return 1;
+ } else {
+ if (narg > maxarg) maxarg = narg;
+ argp = first + narg - 1;
+ }
+ }
+ }
+
+ if (*argp) {
+ prec = (int)mathevali(*argp++);
+ if (errflag) {
+ errflag &= ~ERRFLAG_ERROR;
+ ret = 1;
+ }
+ }
+ } else if (idigit(*c)) {
+ prec = strtoul(c, &endptr, 0);
+ c = endptr;
+ } else
+ prec = 0;
+ if (prec >= 0) *d++ = '.', *d++ = '*';
+ }
+
+ /* ignore any size modifier */
+ if (*c == 'l' || *c == 'L' || *c == 'h') c++;
+
+ if (!curarg && *argp) {
+ curarg = *argp;
+ curlen = len[argp++ - args];
+ }
+ d[1] = '\0';
+ switch (*d = *c) {
+ case 'c':
+ if (curarg)
+ intval = *curarg;
+ else
+ intval = 0;
+ print_val(intval);
+ break;
+ case 's':
+ case 'b':
+ if (curarg) {
+ char *b, *ptr;
+ int lbytes, lchars, lleft;
+#ifdef MULTIBYTE_SUPPORT
+ mbstate_t mbs;
+#endif
+
+ if (*c == 'b') {
+ b = getkeystring(metafy(curarg, curlen, META_USEHEAP),
+ &lbytes,
+ OPT_ISSET(ops,'b') ? GETKEYS_BINDKEY :
+ GETKEYS_PRINTF_ARG, &nnl);
+ } else {
+ b = curarg;
+ lbytes = curlen;
+ }
+ /*
+ * Handle width/precision here and use fwrite so that
+ * nul characters can be output.
+ *
+ * First, examine width of string given that it
+ * may contain multibyte characters. The output
+ * widths are for characters, so we need to count
+ * (in lchars). However, if we need to truncate
+ * the string we need the width in bytes (in lbytes).
+ */
+ ptr = b;
+#ifdef MULTIBYTE_SUPPORT
+ memset(&mbs, 0, sizeof(mbs));
+#endif
+
+ for (lchars = 0, lleft = lbytes; lleft > 0; lchars++) {
+ int chars;
+
+ if (lchars == prec) {
+ /* Truncate at this point. */
+ lbytes = ptr - b;
+ break;
+ }
+#ifdef MULTIBYTE_SUPPORT
+ if (isset(MULTIBYTE)) {
+ chars = mbrlen(ptr, lleft, &mbs);
+ if (chars < 0) {
+ /*
+ * Invalid/incomplete character at this
+ * point. Assume all the rest are a
+ * single byte. That's about the best we
+ * can do.
+ */
+ lchars += lleft;
+ lbytes = (ptr - b) + lleft;
+ break;
+ } else if (chars == 0) {
+ /* NUL, handle as real character */
+ chars = 1;
+ }
+ }
+ else /* use the non-multibyte code below */
+#endif
+ chars = 1; /* compiler can optimise this...*/
+ lleft -= chars;
+ ptr += chars;
+ }
+ if (width > 0 && flags[3]) width = -width;
+ if (width > 0 && lchars < width)
+ count += fprintf(fout, "%*c", width - lchars, ' ');
+ count += fwrite(b, 1, lbytes, fout);
+ if (width < 0 && lchars < -width)
+ count += fprintf(fout, "%*c", -width - lchars, ' ');
+ if (nnl) {
+ /* If the %b arg had a \c escape, truncate the fmt. */
+ flen = c - fmt + 1;
+ fmttrunc = 1;
+ }
+ } else if (width)
+ count += fprintf(fout, "%*c", width, ' ');
+ break;
+ case 'q':
+ stringval = curarg ?
+ quotestring(metafy(curarg, curlen, META_USEHEAP),
+ QT_BACKSLASH_SHOWNULL) : &nullstr;
+ *d = 's';
+ print_val(unmetafy(stringval, &curlen));
+ break;
+ case 'd':
+ case 'i':
+ type=1;
+ break;
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'g':
+ case 'G':
+ type=2;
+ break;
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X':
+ type=3;
+ break;
+ case 'n':
+ if (curarg) setiparam(curarg, count - rcount);
+ break;
+ default:
+ if (*c) {
+ save = c[1];
+ c[1] = '\0';
+ }
+ zwarnnam(name, "%s: invalid directive", start);
+ if (*c) c[1] = save;
+ /* Why do we care about a clean close here? */
+ if (!CLOSE_CLEANLY(fout))
+ zwarnnam(name, "write error: %e", errno);
+#ifdef HAVE_OPEN_MEMSTREAM
+ if (buf)
+ free(buf);
+#endif
+ return 1;
+ }
+
+ if (type > 0) {
+ if (curarg && (*curarg == '\'' || *curarg == '"' )) {
+ convchar_t cc;
+#ifdef MULTIBYTE_SUPPORT
+ if (isset(MULTIBYTE)) {
+ mb_charinit();
+ (void)mb_metacharlenconv(metafy(curarg+1, curlen-1,
+ META_USEHEAP), &cc);
+ }
+ else
+ cc = WEOF;
+ if (cc == WEOF)
+ cc = (curlen > 1) ? STOUC(curarg[1]) : 0;
+#else
+ cc = (curlen > 1) ? STOUC(curarg[1]) : 0;
+#endif
+ if (type == 2) {
+ doubleval = cc;
+ print_val(doubleval);
+ } else {
+ intval = cc;
+ print_val(intval);
+ }
+ } else {
+ switch (type) {
+ case 1:
+#ifdef ZSH_64_BIT_TYPE
+ *d++ = 'l';
+#endif
+ *d++ = 'l', *d++ = *c, *d = '\0';
+ zlongval = (curarg) ? mathevali(curarg) : 0;
+ if (errflag) {
+ zlongval = 0;
+ errflag &= ~ERRFLAG_ERROR;
+ ret = 1;
+ }
+ print_val(zlongval)
+ break;
+ case 2:
+ if (curarg) {
+ char *eptr;
+ /*
+ * First attempt to parse as a floating
+ * point constant. If we go through
+ * a math evaluation, we can lose
+ * mostly unimportant information
+ * that people in standards organizations
+ * worry about.
+ */
+ doubleval = strtod(curarg, &eptr);
+ /*
+ * If it didn't parse as a constant,
+ * parse it as an expression.
+ */
+ if (*eptr != '\0') {
+ mnumval = matheval(curarg);
+ doubleval = (mnumval.type & MN_FLOAT) ?
+ mnumval.u.d : (double)mnumval.u.l;
+ }
+ } else doubleval = 0;
+ if (errflag) {
+ doubleval = 0;
+ errflag &= ~ERRFLAG_ERROR;
+ ret = 1;
+ }
+ /* force consistent form for Inf/NaN output */
+ if (isnan(doubleval))
+ count += fputs("nan", fout);
+ else if (isinf(doubleval))
+ count += fputs((doubleval < 0.0) ? "-inf" : "inf", fout);
+ else
+ print_val(doubleval)
+ break;
+ case 3:
+#ifdef ZSH_64_BIT_UTYPE
+ *d++ = 'l';
+#endif
+ *d++ = 'l', *d++ = *c, *d = '\0';
+ if (!curarg)
+ zulongval = (zulong)0;
+ else if (!zstrtoul_underscore(curarg, &zulongval))
+ zulongval = mathevali(curarg);
+ if (errflag) {
+ zulongval = 0;
+ errflag &= ~ERRFLAG_ERROR;
+ ret = 1;
+ }
+ print_val(zulongval)
+ }
+ }
+ }
+ if (maxarg && (argp - first > maxarg))
+ maxarg = argp - first;
+ }
+
+ if (maxarg) argp = first + maxarg;
+ /* if there are remaining args, reuse format string */
+ } while (*argp && argp != first && !fmttrunc && !OPT_ISSET(ops,'r'));
+
+ if (IS_MSTREAM(fout)) {
+ queue_signals();
+ if ((rcount = READ_MSTREAM(buf,fout)) == -1) {
+ zwarnnam(name, "i/o error: %e", errno);
+ if (buf)
+ free(buf);
+ } else {
+ if (visarr && splits) {
+ char **arrayval = zshcalloc((cursplit - splits + 2) * sizeof(char *));
+ for (;cursplit >= splits; cursplit--) {
+ int start = cursplit == splits ? 0 : cursplit[-1];
+ arrayval[cursplit - splits] =
+ metafy(buf + start, count - start, META_DUP);
+ count = start;
+ }
+ setaparam(OPT_ARG(ops, 'v'), arrayval);
+ free(buf);
+ } else {
+ stringval = metafy(buf, rcount, META_REALLOC);
+ if (OPT_ISSET(ops,'z')) {
+ zpushnode(bufstack, stringval);
+ } else if (OPT_ISSET(ops,'v')) {
+ setsparam(OPT_ARG(ops, 'v'), stringval);
+ } else {
+ ent = prepnexthistent();
+ ent->node.nam = stringval;
+ ent->stim = ent->ftim = time(NULL);
+ ent->node.flags = 0;
+ ent->words = (short *)NULL;
+ addhistnode(histtab, ent->node.nam, ent);
+ }
+ }
+ }
+ unqueue_signals();
+ }
+
+ if (!CLOSE_CLEANLY(fout))
+ {
+ zwarnnam(name, "write error: %e", errno);
+ ret = 1;
+ }
+ return ret;
+}
+
+/* shift builtin */
+
+/**/
+int
+bin_shift(char *name, char **argv, Options ops, UNUSED(int func))
+{
+ int num = 1, l, ret = 0;
+ char **s;
+
+ /* optional argument can be either numeric or an array */
+ queue_signals();
+ if (*argv && !getaparam(*argv)) {
+ num = mathevali(*argv++);
+ if (errflag) {
+ unqueue_signals();
+ return 1;
+ }
+ }
+
+ if (num < 0) {
+ unqueue_signals();
+ zwarnnam(name, "argument to shift must be non-negative");
+ return 1;
+ }
+
+ if (*argv) {
+ for (; *argv; argv++)
+ if ((s = getaparam(*argv))) {
+ if (arrlen_lt(s, num)) {
+ zwarnnam(name, "shift count must be <= $#");
+ ret++;
+ continue;
+ }
+ if (OPT_ISSET(ops,'p')) {
+ char **s2, **src, **dst;
+ int count;
+ l = arrlen(s);
+ src = s;
+ dst = s2 = (char **)zalloc((l - num + 1) * sizeof(char *));
+ for (count = l - num; count; count--)
+ *dst++ = ztrdup(*src++);
+ *dst = NULL;
+ s = s2;
+ } else {
+ s = zarrdup(s + num);
+ }
+ setaparam(*argv, s);
+ }
+ } else {
+ if (num > (l = arrlen(pparams))) {
+ zwarnnam(name, "shift count must be <= $#");
+ ret = 1;
+ } else {
+ s = zalloc((l - num + 1) * sizeof(char *));
+ if (OPT_ISSET(ops,'p')) {
+ memcpy(s, pparams, (l - num) * sizeof(char *));
+ s[l-num] = NULL;
+ while (num--)
+ zsfree(pparams[l-1-num]);
+ } else {
+ memcpy(s, pparams + num, (l - num + 1) * sizeof(char *));
+ while (num--)
+ zsfree(pparams[num]);
+ }
+ zfree(pparams, (l + 1) * sizeof(char *));
+ pparams = s;
+ }
+ }
+ unqueue_signals();
+ return ret;
+}
+
+/*
+ * Position of getopts option within OPTIND argument with multiple options.
+ */
+
+/**/
+int optcind;
+
+/* getopts: automagical option handling for shell scripts */
+
+/**/
+int
+bin_getopts(UNUSED(char *name), char **argv, UNUSED(Options ops), UNUSED(int func))
+{
+ int lenstr, lenoptstr, quiet, lenoptbuf;
+ char *optstr = unmetafy(*argv++, &lenoptstr), *var = *argv++;
+ char **args = (*argv) ? argv : pparams;
+ char *str, optbuf[2] = " ", *p, opch;
+
+ /* zoptind keeps count of the current argument number. The *
+ * user can set it to zero to start a new option parse. */
+ if (zoptind < 1) {
+ /* first call */
+ zoptind = 1;
+ optcind = 0;
+ }
+ if (arrlen_lt(args, zoptind))
+ /* no more options */
+ return 1;
+
+ /* leading ':' in optstr means don't print an error message */
+ quiet = *optstr == ':';
+ optstr += quiet;
+ lenoptstr -= quiet;
+
+ /* find place in relevant argument */
+ str = unmetafy(dupstring(args[zoptind - 1]), &lenstr);
+ if (!lenstr) /* Definitely not an option. */
+ return 1;
+ if(optcind >= lenstr) {
+ optcind = 0;
+ if(!args[zoptind++])
+ return 1;
+ str = unmetafy(dupstring(args[zoptind - 1]), &lenstr);
+ }
+ if(!optcind) {
+ if(lenstr < 2 || (*str != '-' && *str != '+'))
+ return 1;
+ if(lenstr == 2 && str[0] == '-' && str[1] == '-') {
+ zoptind++;
+ return 1;
+ }
+ optcind++;
+ }
+ opch = str[optcind++];
+ if(str[0] == '+') {
+ optbuf[0] = '+';
+ lenoptbuf = 2;
+ } else
+ lenoptbuf = 1;
+ optbuf[lenoptbuf - 1] = opch;
+
+ /* check for legality */
+ if(opch == ':' || !(p = memchr(optstr, opch, lenoptstr))) {
+ p = "?";
+ err:
+ zsfree(zoptarg);
+ setsparam(var, ztrdup(p));
+ if(quiet) {
+ zoptarg = metafy(optbuf, lenoptbuf, META_DUP);
+ } else {
+ zwarn(*p == '?' ? "bad option: %c%c" :
+ "argument expected after %c%c option",
+ "?-+"[lenoptbuf], opch);
+ zoptarg=ztrdup("");
+ }
+ return 0;
+ }
+
+ /* check for required argument */
+ if(p[1] == ':') {
+ if(optcind == lenstr) {
+ if(!args[zoptind]) {
+ p = ":";
+ goto err;
+ }
+ p = ztrdup(args[zoptind++]);
+ } else
+ p = metafy(str+optcind, lenstr-optcind, META_DUP);
+ /*
+ * Careful: I've just changed the following two lines from
+ * optcind = ztrlen(args[zoptind - 1]);
+ * and it's a rigorous theorem that every change in getopts breaks
+ * something. See zsh-workers/9095 for the bug fixed here.
+ * PWS 2000/05/02
+ */
+ optcind = 0;
+ zoptind++;
+ zsfree(zoptarg);
+ zoptarg = p;
+ } else {
+ zsfree(zoptarg);
+ zoptarg = ztrdup("");
+ }
+
+ setsparam(var, metafy(optbuf, lenoptbuf, META_DUP));
+ return 0;
+}
+
+/* Flag that we should exit the shell as soon as all functions return. */
+/**/
+mod_export int
+exit_pending;
+
+/* Shell level at which we exit if exit_pending */
+/**/
+mod_export int
+exit_level;
+
+/* break, bye, continue, exit, logout, return -- most of these take *
+ * one numeric argument, and the other (logout) is related to return. *
+ * (return is treated as a logout when in a login shell.) */
+
+/**/
+int
+bin_break(char *name, char **argv, UNUSED(Options ops), int func)
+{
+ int num = lastval, nump = 0, implicit;
+
+ /* handle one optional numeric argument */
+ implicit = !*argv;
+ if (*argv) {
+ num = mathevali(*argv++);
+ nump = 1;
+ }
+
+ if (nump > 0 && (func == BIN_CONTINUE || func == BIN_BREAK) && num <= 0) {
+ zerrnam(name, "argument is not positive: %d", num);
+ return 1;
+ }
+
+ switch (func) {
+ case BIN_CONTINUE:
+ if (!loops) { /* continue is only permitted in loops */
+ zerrnam(name, "not in while, until, select, or repeat loop");
+ return 1;
+ }
+ contflag = 1; /* FALLTHROUGH */
+ case BIN_BREAK:
+ if (!loops) { /* break is only permitted in loops */
+ zerrnam(name, "not in while, until, select, or repeat loop");
+ return 1;
+ }
+ breaks = nump ? minimum(num,loops) : 1;
+ break;
+ case BIN_RETURN:
+ if ((isset(INTERACTIVE) && isset(SHINSTDIN))
+ || locallevel || sourcelevel) {
+ retflag = 1;
+ breaks = loops;
+ lastval = num;
+ if (trap_state == TRAP_STATE_PRIMED && trap_return == -2
+ /*
+ * With POSIX, "return" on its own in a trap doesn't
+ * update $? --- we keep the status from before the
+ * trap.
+ */
+ && !(isset(POSIXTRAPS) && implicit)) {
+ trap_state = TRAP_STATE_FORCE_RETURN;
+ trap_return = lastval;
+ }
+ return lastval;
+ }
+ zexit(num, 0); /* else treat return as logout/exit */
+ break;
+ case BIN_LOGOUT:
+ if (unset(LOGINSHELL)) {
+ zerrnam(name, "not login shell");
+ return 1;
+ }
+ /*FALLTHROUGH*/
+ case BIN_EXIT:
+ if (locallevel > forklevel && shell_exiting != -1) {
+ /*
+ * We don't exit directly from functions to allow tidying
+ * up, in particular EXIT traps. We still need to perform
+ * the usual interactive tests to see if we can exit at
+ * all, however.
+ *
+ * If we are forked, we exit the shell at the function depth
+ * at which we became a subshell, hence the comparison.
+ *
+ * If we are already exiting... give this all up as
+ * a bad job.
+ */
+ if (stopmsg || (zexit(0,2), !stopmsg)) {
+ retflag = 1;
+ breaks = loops;
+ exit_pending = (num << 1) | 1;
+ exit_level = locallevel;
+ }
+ } else
+ zexit(num, 0);
+ break;
+ }
+ return 0;
+}
+
+/* we have printed a 'you have stopped (running) jobs.' message */
+
+/**/
+mod_export int stopmsg;
+
+/* check to see if user has jobs running/stopped */
+
+/**/
+static void
+checkjobs(void)
+{
+ int i;
+
+ for (i = 1; i <= maxjob; i++)
+ if (i != thisjob && (jobtab[i].stat & STAT_LOCKED) &&
+ !(jobtab[i].stat & STAT_NOPRINT) &&
+ (isset(CHECKRUNNINGJOBS) || jobtab[i].stat & STAT_STOPPED))
+ break;
+ if (i <= maxjob) {
+ if (jobtab[i].stat & STAT_STOPPED) {
+
+#ifdef USE_SUSPENDED
+ zerr("you have suspended jobs.");
+#else
+ zerr("you have stopped jobs.");
+#endif
+
+ } else
+ zerr("you have running jobs.");
+ stopmsg = 1;
+ }
+}
+
+/*
+ * -1 if the shell is already committed to exit.
+ * positive if zexit() was already called.
+ */
+
+/**/
+int shell_exiting;
+
+/* exit the shell. val is the return value of the shell. *
+ * from_where is
+ * 1 if zexit is called because of a signal
+ * 2 if we can't actually exit yet (e.g. functions need
+ * terminating) but should perform the usual interactive tests.
+ */
+
+/**/
+mod_export void
+zexit(int val, int from_where)
+{
+ /* Don't do anything recursively: see below */
+ if (shell_exiting == -1)
+ return;
+
+ if (isset(MONITOR) && !stopmsg && from_where != 1) {
+ scanjobs(); /* check if jobs need printing */
+ if (isset(CHECKJOBS))
+ checkjobs(); /* check if any jobs are running/stopped */
+ if (stopmsg) {
+ stopmsg = 2;
+ return;
+ }
+ }
+ /* Positive in_exit means we have been here before */
+ if (from_where == 2 || (shell_exiting++ && from_where))
+ return;
+
+ /*
+ * We're now committed to exiting. Set shell_exiting to -1 to
+ * indicate we shouldn't do any recursive processing.
+ */
+ shell_exiting = -1;
+ /*
+ * We want to do all remaining processing regardless of preceding
+ * errors, even user interrupts.
+ */
+ errflag = 0;
+
+ if (isset(MONITOR)) {
+ /* send SIGHUP to any jobs left running */
+ killrunjobs(from_where == 1);
+ }
+ if (isset(RCS) && interact) {
+ if (!nohistsave) {
+ int writeflags = HFILE_USE_OPTIONS;
+ if (from_where == 1)
+ writeflags |= HFILE_NO_REWRITE;
+ saveandpophiststack(1, writeflags);
+ savehistfile(NULL, 1, writeflags);
+ }
+ if (islogin && !subsh) {
+ sourcehome(".zlogout");
+#ifdef GLOBAL_ZLOGOUT
+ if (isset(RCS) && isset(GLOBALRCS))
+ source(GLOBAL_ZLOGOUT);
+#endif
+ }
+ }
+ lastval = val;
+ /*
+ * Now we are committed to exiting any previous state
+ * is irrelevant. Ensure trap can run.
+ */
+ errflag = intrap = 0;
+ if (sigtrapped[SIGEXIT])
+ dotrap(SIGEXIT);
+ callhookfunc("zshexit", NULL, 1, NULL);
+ runhookdef(EXITHOOK, NULL);
+ if (opts[MONITOR] && interact && (SHTTY != -1)) {
+ release_pgrp();
+ }
+ if (mypid != getpid())
+ _exit(val);
+ else
+ exit(val);
+}
+
+/* . (dot), source */
+
+/**/
+int
+bin_dot(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
+{
+ char **old, *old0 = NULL;
+ int diddot = 0, dotdot = 0;
+ char *s, **t, *enam, *arg0, *buf;
+ struct stat st;
+ enum source_return ret;
+
+ if (!*argv)
+ return 0;
+ old = pparams;
+ /* get arguments for the script */
+ if (argv[1])
+ pparams = zarrdup(argv + 1);
+
+ enam = arg0 = ztrdup(*argv);
+ if (isset(FUNCTIONARGZERO)) {
+ old0 = argzero;
+ argzero = ztrdup(arg0);
+ }
+ s = unmeta(enam);
+ errno = ENOENT;
+ ret = SOURCE_NOT_FOUND;
+ /* for source only, check in current directory first */
+ if (*name != '.' && access(s, F_OK) == 0
+ && stat(s, &st) >= 0 && !S_ISDIR(st.st_mode)) {
+ diddot = 1;
+ ret = source(enam);
+ }
+ if (ret == SOURCE_NOT_FOUND) {
+ /* use a path with / in it */
+ for (s = arg0; *s; s++)
+ if (*s == '/') {
+ if (*arg0 == '.') {
+ if (arg0 + 1 == s)
+ ++diddot;
+ else if (arg0[1] == '.' && arg0 + 2 == s)
+ ++dotdot;
+ }
+ ret = source(arg0);
+ break;
+ }
+ if (!*s || (ret == SOURCE_NOT_FOUND &&
+ isset(PATHDIRS) && diddot < 2 && dotdot == 0)) {
+ pushheap();
+ /* search path for script */
+ for (t = path; *t; t++) {
+ if (!(*t)[0] || ((*t)[0] == '.' && !(*t)[1])) {
+ if (diddot)
+ continue;
+ diddot = 1;
+ buf = dupstring(arg0);
+ } else
+ buf = zhtricat(*t, "/", arg0);
+
+ s = unmeta(buf);
+ if (access(s, F_OK) == 0 && stat(s, &st) >= 0
+ && !S_ISDIR(st.st_mode)) {
+ ret = source(enam = buf);
+ break;
+ }
+ }
+ popheap();
+ }
+ }
+ /* clean up and return */
+ if (argv[1]) {
+ freearray(pparams);
+ pparams = old;
+ }
+ if (ret == SOURCE_NOT_FOUND) {
+ if (isset(POSIXBUILTINS)) {
+ /* hard error in POSIX (we'll exit later) */
+ zerrnam(name, "%e: %s", errno, enam);
+ } else {
+ zwarnnam(name, "%e: %s", errno, enam);
+ }
+ }
+ zsfree(arg0);
+ if (old0) {
+ zsfree(argzero);
+ argzero = old0;
+ }
+ return ret == SOURCE_OK ? lastval : 128 - ret;
+}
+
+/*
+ * common for bin_emulate and bin_eval
+ */
+
+static int
+eval(char **argv)
+{
+ Eprog prog;
+ char *oscriptname = scriptname;
+ int oineval = ineval, fpushed;
+ struct funcstack fstack;
+
+ /*
+ * If EVALLINENO is not set, we use the line number of the
+ * environment and must flag this up to exec.c. Otherwise,
+ * we use a special script name to indicate the special line number.
+ */
+ ineval = !isset(EVALLINENO);
+ if (!ineval) {
+ scriptname = "(eval)";
+ fstack.prev = funcstack;
+ fstack.name = scriptname;
+ fstack.caller = funcstack ? funcstack->name : dupstring(argzero);
+ fstack.lineno = lineno;
+ fstack.tp = FS_EVAL;
+
+ /*
+ * To get file line numbers, we need to know if parent is
+ * the original script/shell or a sourced file, in which
+ * case we use the line number raw, or a function or eval,
+ * in which case we need to deduce where that came from.
+ *
+ * This replicates the logic for working out the information
+ * for $funcfiletrace---eval is similar to an inlined function
+ * call from a tracing perspective.
+ */
+ if (!funcstack || funcstack->tp == FS_SOURCE) {
+ fstack.flineno = fstack.lineno;
+ fstack.filename = fstack.caller;
+ } else {
+ fstack.flineno = funcstack->flineno + lineno;
+ /*
+ * Line numbers in eval start from 1, not zero,
+ * so offset by one to get line in file.
+ */
+ if (funcstack->tp == FS_EVAL)
+ fstack.flineno--;
+ fstack.filename = funcstack->filename;
+ if (!fstack.filename)
+ fstack.filename = "";
+ }
+ funcstack = &fstack;
+
+ fpushed = 1;
+ } else
+ fpushed = 0;
+
+ prog = parse_string(zjoin(argv, ' ', 1), 1);
+ if (prog) {
+ if (wc_code(*prog->prog) != WC_LIST) {
+ /* No code to execute */
+ lastval = 0;
+ } else {
+ execode(prog, 1, 0, "eval");
+
+ if (errflag && !lastval)
+ lastval = errflag;
+ }
+ } else {
+ lastval = 1;
+ }
+
+ if (fpushed)
+ funcstack = funcstack->prev;
+
+ errflag &= ~ERRFLAG_ERROR;
+ scriptname = oscriptname;
+ ineval = oineval;
+
+ return lastval;
+}
+
+/* emulate: set emulation mode and optionally evaluate shell code */
+
+/**/
+int
+bin_emulate(char *nam, char **argv, Options ops, UNUSED(int func))
+{
+ int opt_L = OPT_ISSET(ops, 'L');
+ int opt_R = OPT_ISSET(ops, 'R');
+ int opt_l = OPT_ISSET(ops, 'l');
+ int saveemulation, savehackchar;
+ int ret = 1, new_emulation;
+ unsigned int savepatterns;
+ char saveopts[OPT_SIZE], new_opts[OPT_SIZE];
+ char *cmd = 0;
+ const char *shname = *argv;
+ LinkList optlist;
+ LinkNode optnode;
+ Emulation_options save_sticky;
+ OptIndex *on_ptr, *off_ptr;
+
+ /* without arguments just print current emulation */
+ if (!shname) {
+ if (opt_L || opt_R) {
+ zwarnnam(nam, "not enough arguments");
+ return 1;
+ }
+
+ switch(SHELL_EMULATION()) {
+ case EMULATE_CSH:
+ shname = "csh";
+ break;
+
+ case EMULATE_KSH:
+ shname = "ksh";
+ break;
+
+ case EMULATE_SH:
+ shname = "sh";
+ break;
+
+ default:
+ shname = "zsh";
+ break;
+ }
+
+ printf("%s\n", shname);
+ return 0;
+ }
+
+ /* with single argument set current emulation */
+ if (!argv[1]) {
+ char *cmdopts;
+ if (opt_l) {
+ cmdopts = (char *)zhalloc(OPT_SIZE);
+ memcpy(cmdopts, opts, OPT_SIZE);
+ } else
+ cmdopts = opts;
+ emulate(shname, opt_R, &emulation, cmdopts);
+ if (opt_L)
+ cmdopts[LOCALOPTIONS] = cmdopts[LOCALTRAPS] =
+ cmdopts[LOCALPATTERNS] = 1;
+ if (opt_l) {
+ list_emulate_options(cmdopts, opt_R);
+ return 0;
+ }
+ clearpatterndisables();
+ return 0;
+ }
+
+ if (opt_l) {
+ zwarnnam(nam, "too many arguments for -l");
+ return 1;
+ }
+
+ argv++;
+ memcpy(saveopts, opts, sizeof(opts));
+ memcpy(new_opts, opts, sizeof(opts));
+ savehackchar = keyboardhackchar;
+ emulate(shname, opt_R, &new_emulation, new_opts);
+ optlist = newlinklist();
+ if (parseopts(nam, &argv, new_opts, &cmd, optlist, 0)) {
+ ret = 1;
+ goto restore;
+ }
+
+ /* parseopts() has consumed anything that looks like an option */
+ if (*argv) {
+ zwarnnam(nam, "unknown argument %s", *argv);
+ goto restore;
+ }
+
+ savepatterns = savepatterndisables();
+ /*
+ * All emulations start with an empty set of pattern disables,
+ * hence no special "sticky" behaviour is required.
+ */
+ clearpatterndisables();
+
+ saveemulation = emulation;
+ emulation = new_emulation;
+ memcpy(opts, new_opts, sizeof(opts));
+ /* If "-c command" is given, evaluate command using specified
+ * emulation mode.
+ */
+ if (cmd) {
+ if (opt_L) {
+ zwarnnam(nam, "option -L incompatible with -c");
+ goto restore2;
+ }
+ *--argv = cmd; /* on stack, never free()d, see execbuiltin() */
+ } else {
+ if (opt_L)
+ opts[LOCALOPTIONS] = opts[LOCALTRAPS] = opts[LOCALPATTERNS] = 1;
+ return 0;
+ }
+
+ save_sticky = sticky;
+ sticky = hcalloc(sizeof(*sticky));
+ sticky->emulation = emulation;
+ for (optnode = firstnode(optlist); optnode; incnode(optnode)) {
+ /* Data is index into new_opts */
+ char *optptr = (char *)getdata(optnode);
+ if (*optptr)
+ sticky->n_on_opts++;
+ else
+ sticky->n_off_opts++;
+ }
+ if (sticky->n_on_opts)
+ on_ptr = sticky->on_opts =
+ zhalloc(sticky->n_on_opts * sizeof(*sticky->on_opts));
+ else
+ on_ptr = NULL;
+ if (sticky->n_off_opts)
+ off_ptr = sticky->off_opts = zhalloc(sticky->n_off_opts *
+ sizeof(*sticky->off_opts));
+ else
+ off_ptr = NULL;
+ for (optnode = firstnode(optlist); optnode; incnode(optnode)) {
+ /* Data is index into new_opts */
+ char *optptr = (char *)getdata(optnode);
+ int optno = optptr - new_opts;
+ if (*optptr)
+ *on_ptr++ = optno;
+ else
+ *off_ptr++ = optno;
+ }
+ ret = eval(argv);
+ sticky = save_sticky;
+restore2:
+ emulation = saveemulation;
+ memcpy(opts, saveopts, sizeof(opts));
+ restorepatterndisables(savepatterns);
+restore:
+ keyboardhackchar = savehackchar;
+ inittyptab(); /* restore banghist */
+ return ret;
+}
+
+/* eval: simple evaluation */
+
+/**/
+mod_export int ineval;
+
+/**/
+int
+bin_eval(UNUSED(char *nam), char **argv, UNUSED(Options ops), UNUSED(int func))
+{
+ return eval(argv);
+}
+
+static char *zbuf;
+static int readfd;
+
+/* Read a character from readfd, or from the buffer zbuf. Return EOF on end of
+file/buffer. */
+
+/* read: get a line of input, or (for compctl functions) return some *
+ * useful data about the state of the editing line. The -E and -e *
+ * options mean that the result should be sent to stdout. -e means, *
+ * in addition, that the result should not actually be assigned to *
+ * the specified parameters. */
+
+/**/
+int
+bin_read(char *name, char **args, Options ops, UNUSED(int func))
+{
+ char *reply, *readpmpt;
+ int bsiz, c = 0, gotnl = 0, al = 0, first, nchars = 1, bslash, keys = 0;
+ int haso = 0; /* true if /dev/tty has been opened specially */
+ int isem = !strcmp(term, "emacs"), izle = zleactive;
+ char *buf, *bptr, *firstarg, *zbuforig;
+ LinkList readll = newlinklist();
+ FILE *oshout = NULL;
+ int readchar = -1, val, resettty = 0;
+ struct ttyinfo saveti;
+ char d;
+ long izle_timeout = 0;
+#ifdef MULTIBYTE_SUPPORT
+ wchar_t delim = L'\n', wc;
+ mbstate_t mbs;
+ char *laststart;
+ size_t ret;
+#else
+ char delim = '\n';
+#endif
+
+ if (OPT_HASARG(ops,c='k')) {
+ char *eptr, *optarg = OPT_ARG(ops,c);
+ nchars = (int)zstrtol(optarg, &eptr, 10);
+ if (*eptr) {
+ zwarnnam(name, "number expected after -%c: %s", c, optarg);
+ return 1;
+ }
+ }
+ /* This `*args++ : *args' looks a bit weird, but it works around a bug
+ * in gcc-2.8.1 under DU 4.0. */
+ firstarg = (*args && **args == '?' ? *args++ : *args);
+ reply = *args ? *args++ : OPT_ISSET(ops,'A') ? "reply" : "REPLY";
+
+ if (OPT_ISSET(ops,'A') && *args) {
+ zwarnnam(name, "only one array argument allowed");
+ return 1;
+ }
+
+ /* handle compctl case */
+ if(OPT_ISSET(ops,'l') || OPT_ISSET(ops,'c'))
+ return compctlreadptr(name, args, ops, reply);
+
+ if ((OPT_ISSET(ops,'k') || OPT_ISSET(ops,'q')) &&
+ !OPT_ISSET(ops,'u') && !OPT_ISSET(ops,'p')) {
+ if (!zleactive) {
+ if (SHTTY == -1) {
+ /* need to open /dev/tty specially */
+ if ((SHTTY = open("/dev/tty", O_RDWR|O_NOCTTY)) != -1) {
+ haso = 1;
+ oshout = shout;
+ init_shout();
+ }
+ } else if (!shout) {
+ /* We need an output FILE* on the tty */
+ init_shout();
+ }
+ /* We should have a SHTTY opened by now. */
+ if (SHTTY == -1) {
+ /* Unfortunately, we didn't. */
+ fprintf(stderr, "not interactive and can't open terminal\n");
+ fflush(stderr);
+ return 1;
+ }
+ if (unset(INTERACTIVE))
+ gettyinfo(&shttyinfo);
+ /* attach to the tty */
+ attachtty(mypgrp);
+ if (!isem)
+ setcbreak();
+ readfd = SHTTY;
+ }
+ keys = 1;
+ } else if (OPT_HASARG(ops,'u') && !OPT_ISSET(ops,'p')) {
+ /* -u means take input from the specified file descriptor. */
+ char *eptr, *argptr = OPT_ARG(ops,'u');
+ /* The old code handled -up, but that was never documented. Still...*/
+ if (!strcmp(argptr, "p")) {
+ readfd = coprocin;
+ if (readfd < 0) {
+ zwarnnam(name, "-p: no coprocess");
+ return 1;
+ }
+ } else {
+ readfd = (int)zstrtol(argptr, &eptr, 10);
+ if (*eptr) {
+ zwarnnam(name, "number expected after -%c: %s", 'u', argptr);
+ return 1;
+ }
+ }
+#if 0
+ /* This code is left as a warning to future generations --- pws. */
+ for (readfd = 9; readfd && !OPT_ISSET(ops,readfd + '0'); --readfd);
+#endif
+ izle = 0;
+ } else if (OPT_ISSET(ops,'p')) {
+ readfd = coprocin;
+ if (readfd < 0) {
+ zwarnnam(name, "-p: no coprocess");
+ return 1;
+ }
+ izle = 0;
+ } else
+ readfd = izle = 0;
+
+ if (OPT_ISSET(ops,'s') && SHTTY != -1) {
+ struct ttyinfo ti;
+ gettyinfo(&ti);
+ saveti = ti;
+ resettty = 1;
+#ifdef HAS_TIO
+ ti.tio.c_lflag &= ~ECHO;
+#else
+ ti.sgttyb.sg_flags &= ~ECHO;
+#endif
+ settyinfo(&ti);
+ }
+
+ /* handle prompt */
+ if (firstarg) {
+ for (readpmpt = firstarg;
+ *readpmpt && *readpmpt != '?'; readpmpt++);
+ if (*readpmpt++) {
+ if (keys || isatty(0)) {
+ zputs(readpmpt, (shout ? shout : stderr));
+ fflush(shout ? shout : stderr);
+ }
+ readpmpt[-1] = '\0';
+ }
+ }
+
+ if (OPT_ISSET(ops,'d')) {
+ char *delimstr = OPT_ARG(ops,'d');
+#ifdef MULTIBYTE_SUPPORT
+ wint_t wi;
+
+ if (isset(MULTIBYTE)) {
+ mb_charinit();
+ (void)mb_metacharlenconv(delimstr, &wi);
+ }
+ else
+ wi = WEOF;
+ if (wi != WEOF)
+ delim = (wchar_t)wi;
+ else
+ delim = (wchar_t)((delimstr[0] == Meta) ?
+ delimstr[1] ^ 32 : delimstr[0]);
+#else
+ delim = (delimstr[0] == Meta) ? delimstr[1] ^ 32 : delimstr[0];
+#endif
+ if (SHTTY != -1) {
+ struct ttyinfo ti;
+ gettyinfo(&ti);
+ if (! resettty) {
+ saveti = ti;
+ resettty = 1;
+ }
+#ifdef HAS_TIO
+ ti.tio.c_lflag &= ~ICANON;
+ ti.tio.c_cc[VMIN] = 1;
+ ti.tio.c_cc[VTIME] = 0;
+#else
+ ti.sgttyb.sg_flags |= CBREAK;
+#endif
+ settyinfo(&ti);
+ }
+ }
+ if (OPT_ISSET(ops,'t')) {
+ zlong timeout = 0;
+ if (OPT_HASARG(ops,'t')) {
+ mnumber mn = zero_mnumber;
+ mn = matheval(OPT_ARG(ops,'t'));
+ if (errflag)
+ return 1;
+ if (mn.type == MN_FLOAT) {
+ mn.u.d *= 1e6;
+ timeout = (zlong)mn.u.d;
+ } else {
+ timeout = (zlong)mn.u.l * (zlong)1000000;
+ }
+ }
+ if (izle) {
+ /*
+ * Timeout is in 100ths of a second rather than us.
+ * See calc_timeout() in zle_main for format of this.
+ */
+ timeout = -(timeout/(zlong)10000 + 1L);
+ izle_timeout = (long)timeout;
+#ifdef LONG_MAX
+ /* saturate if range exceeded */
+ if ((zlong)izle_timeout != timeout)
+ izle_timeout = LONG_MAX;
+#endif
+ } else {
+ if (readfd == -1 ||
+ !read_poll(readfd, &readchar, keys && !zleactive,
+ timeout)) {
+ if (keys && !zleactive && !isem)
+ settyinfo(&shttyinfo);
+ else if (resettty && SHTTY != -1)
+ settyinfo(&saveti);
+ if (haso) {
+ fclose(shout);
+ shout = oshout;
+ SHTTY = -1;
+ }
+ return OPT_ISSET(ops,'q') ? 2 : 1;
+ }
+ }
+ }
+
+#ifdef MULTIBYTE_SUPPORT
+ memset(&mbs, 0, sizeof(mbs));
+#endif
+
+ /*
+ * option -k means read only a given number of characters (default 1)
+ * option -q means get one character, and interpret it as a Y or N
+ */
+ if (OPT_ISSET(ops,'k') || OPT_ISSET(ops,'q')) {
+ int eof = 0;
+ /* allocate buffer space for result */
+#ifdef MULTIBYTE_SUPPORT
+ bptr = buf = (char *)zalloc(nchars*MB_CUR_MAX+1);
+#else
+ bptr = buf = (char *)zalloc(nchars+1);
+#endif
+
+ do {
+ if (izle) {
+ zleentry(ZLE_CMD_GET_KEY, izle_timeout, NULL, &val);
+ if (val < 0) {
+ eof = 1;
+ break;
+ }
+ *bptr = (char) val;
+#ifdef MULTIBYTE_SUPPORT
+ if (isset(MULTIBYTE)) {
+ ret = mbrlen(bptr++, 1, &mbs);
+ if (ret == MB_INVALID)
+ memset(&mbs, 0, sizeof(mbs));
+ /* treat invalid as single character */
+ if (ret != MB_INCOMPLETE)
+ nchars--;
+ continue;
+ } else {
+ bptr++;
+ nchars--;
+ }
+#else
+ bptr++;
+ nchars--;
+#endif
+ } else {
+ /* If read returns 0, is end of file */
+ if (readchar >= 0) {
+ *bptr = readchar;
+ val = 1;
+ readchar = -1;
+ } else {
+ while ((val = read(readfd, bptr, nchars)) < 0) {
+ if (errno != EINTR ||
+ errflag || retflag || breaks || contflag)
+ break;
+ }
+ if (val <= 0) {
+ eof = 1;
+ break;
+ }
+ }
+
+#ifdef MULTIBYTE_SUPPORT
+ if (isset(MULTIBYTE)) {
+ while (val > 0) {
+ ret = mbrlen(bptr, val, &mbs);
+ if (ret == MB_INCOMPLETE) {
+ bptr += val;
+ break;
+ } else {
+ if (ret == MB_INVALID) {
+ memset(&mbs, 0, sizeof(mbs));
+ /* treat as single byte */
+ ret = 1;
+ }
+ else if (ret == 0) /* handle null as normal char */
+ ret = 1;
+ else if (ret > (size_t)val) {
+ /* Some mbrlen()s return the full char len */
+ ret = val;
+ }
+ nchars--;
+ val -= ret;
+ bptr += ret;
+ }
+ }
+ continue;
+ }
+#endif
+ /* decrement number of characters read from number required */
+ nchars -= val;
+
+ /* increment pointer past read characters */
+ bptr += val;
+ }
+ } while (nchars > 0);
+
+ if (!izle && !OPT_ISSET(ops,'u') && !OPT_ISSET(ops,'p')) {
+ /* dispose of result appropriately, etc. */
+ if (isem)
+ while (val > 0 && read(SHTTY, &d, 1) == 1 && d != '\n');
+ else {
+ settyinfo(&shttyinfo);
+ resettty = 0;
+ }
+ if (haso) {
+ fclose(shout); /* close(SHTTY) */
+ shout = oshout;
+ SHTTY = -1;
+ }
+ }
+
+ if (OPT_ISSET(ops,'q'))
+ {
+ /*
+ * Keep eof as status but status is now whether we read
+ * 'y' or 'Y'. If we timed out, status is 2.
+ */
+ if (eof)
+ eof = 2;
+ else
+ eof = (bptr - buf != 1 || (buf[0] != 'y' && buf[0] != 'Y'));
+ buf[0] = eof ? 'n' : 'y';
+ bptr = buf + 1;
+ }
+ if (OPT_ISSET(ops,'e') || OPT_ISSET(ops,'E'))
+ fwrite(buf, bptr - buf, 1, stdout);
+ if (!OPT_ISSET(ops,'e'))
+ setsparam(reply, metafy(buf, bptr - buf, META_REALLOC));
+ else
+ zfree(buf, bptr - buf + 1);
+ if (resettty && SHTTY != -1)
+ settyinfo(&saveti);
+ return eof;
+ }
+
+ /* All possible special types of input have been exhausted. Take one line,
+ and assign words to the parameters until they run out. Leftover words go
+ onto the last parameter. If an array is specified, all the words become
+ separate elements of the array. */
+
+ zbuforig = zbuf = (!OPT_ISSET(ops,'z')) ? NULL :
+ (nonempty(bufstack)) ? (char *) getlinknode(bufstack) : ztrdup("");
+ first = 1;
+ bslash = 0;
+ while (*args || (OPT_ISSET(ops,'A') && !gotnl)) {
+ sigset_t s = child_unblock();
+ buf = bptr = (char *)zalloc(bsiz = 64);
+#ifdef MULTIBYTE_SUPPORT
+ laststart = buf;
+ ret = MB_INCOMPLETE;
+#endif
+ /* get input, a character at a time */
+ while (!gotnl) {
+ c = zread(izle, &readchar, izle_timeout);
+ /* \ at the end of a line indicates a continuation *
+ * line, except in raw mode (-r option) */
+#ifdef MULTIBYTE_SUPPORT
+ if (c == EOF) {
+ /* not waiting to be completed any more */
+ ret = 0;
+ break;
+ }
+ *bptr = (char)c;
+ if (isset(MULTIBYTE)) {
+ ret = mbrtowc(&wc, bptr, 1, &mbs);
+ if (!ret) /* NULL */
+ ret = 1;
+ } else {
+ ret = 1;
+ wc = (wchar_t)c;
+ }
+ if (ret != MB_INCOMPLETE) {
+ if (ret == MB_INVALID) {
+ memset(&mbs, 0, sizeof(mbs));
+ /* Treat this as a single character */
+ wc = (wchar_t)c;
+ laststart = bptr;
+ }
+ if (bslash && wc == delim) {
+ bslash = 0;
+ continue;
+ }
+ if (wc == delim)
+ break;
+ /*
+ * `first' is non-zero if any separator we encounter is a
+ * non-whitespace separator, which means that anything
+ * (even an empty string) between, before or after separators
+ * is significant. If it is zero, we have a whitespace
+ * separator, which shouldn't cause extra empty strings to
+ * be emitted. Hence the test for (*buf || first) when
+ * we assign the result of reading a word.
+ */
+ if (!bslash && wcsitype(wc, ISEP)) {
+ if (bptr != buf ||
+ (!(c < 128 && iwsep(c)) && first)) {
+ first |= !(c < 128 && iwsep(c));
+ break;
+ }
+ first |= !(c < 128 && iwsep(c));
+ continue;
+ }
+ bslash = (wc == L'\\' && !bslash && !OPT_ISSET(ops,'r'));
+ if (bslash)
+ continue;
+ first = 0;
+ }
+ if (imeta(STOUC(*bptr))) {
+ bptr[1] = bptr[0] ^ 32;
+ bptr[0] = Meta;
+ bptr += 2;
+ }
+ else
+ bptr++;
+ if (ret != MB_INCOMPLETE)
+ laststart = bptr;
+#else
+ if (c == EOF)
+ break;
+ if (bslash && c == delim) {
+ bslash = 0;
+ continue;
+ }
+ if (c == delim)
+ break;
+ /*
+ * `first' is non-zero if any separator we encounter is a
+ * non-whitespace separator, which means that anything
+ * (even an empty string) between, before or after separators
+ * is significant. If it is zero, we have a whitespace
+ * separator, which shouldn't cause extra empty strings to
+ * be emitted. Hence the test for (*buf || first) when
+ * we assign the result of reading a word.
+ */
+ if (!bslash && isep(c)) {
+ if (bptr != buf || (!iwsep(c) && first)) {
+ first |= !iwsep(c);
+ break;
+ }
+ first |= !iwsep(c);
+ continue;
+ }
+ bslash = c == '\\' && !bslash && !OPT_ISSET(ops,'r');
+ if (bslash)
+ continue;
+ first = 0;
+ if (imeta(c)) {
+ *bptr++ = Meta;
+ *bptr++ = c ^ 32;
+ } else
+ *bptr++ = c;
+#endif
+ /* increase the buffer size, if necessary */
+ if (bptr >= buf + bsiz - 1) {
+ int blen = bptr - buf;
+#ifdef MULTIBYTE_SUPPORT
+ int llen = laststart - buf;
+#endif
+
+ buf = realloc(buf, bsiz *= 2);
+ bptr = buf + blen;
+#ifdef MULTIBYTE_SUPPORT
+ laststart = buf + llen;
+#endif
+ }
+ }
+ signal_setmask(s);
+#ifdef MULTIBYTE_SUPPORT
+ if (c == EOF) {
+ gotnl = 1;
+ *bptr = '\0'; /* see below */
+ } else if (ret == MB_INCOMPLETE) {
+ /*
+ * We can only get here if there is an EOF in the
+ * middle of a character... safest to keep the debris,
+ * I suppose.
+ */
+ *bptr = '\0';
+ } else {
+ if (wc == delim)
+ gotnl = 1;
+ *laststart = '\0';
+ }
+#else
+ if (c == delim || c == EOF)
+ gotnl = 1;
+ *bptr = '\0';
+#endif
+ /* dispose of word appropriately */
+ if (OPT_ISSET(ops,'e') ||
+ /*
+ * When we're doing an array assignment, we'll
+ * handle echoing at that point. In all other
+ * cases (including -A with no assignment)
+ * we'll do it here.
+ */
+ (OPT_ISSET(ops,'E') && !OPT_ISSET(ops,'A'))) {
+ zputs(buf, stdout);
+ putchar('\n');
+ }
+ if (!OPT_ISSET(ops,'e') && (*buf || first || gotnl)) {
+ if (OPT_ISSET(ops,'A')) {
+ addlinknode(readll, buf);
+ al++;
+ } else
+ setsparam(reply, buf);
+ } else
+ free(buf);
+ if (!OPT_ISSET(ops,'A'))
+ reply = *args++;
+ }
+ /* handle EOF */
+ if (c == EOF) {
+ if (readfd == coprocin) {
+ close(coprocin);
+ close(coprocout);
+ coprocin = coprocout = -1;
+ }
+ }
+ /* final assignment (and display) of array parameter */
+ if (OPT_ISSET(ops,'A')) {
+ char **pp, **p = NULL;
+ LinkNode n;
+
+ p = (OPT_ISSET(ops,'e') ? (char **)NULL
+ : (char **)zalloc((al + 1) * sizeof(char *)));
+
+ for (pp = p, n = firstnode(readll); n; incnode(n)) {
+ if (OPT_ISSET(ops,'E')) {
+ zputs((char *) getdata(n), stdout);
+ putchar('\n');
+ }
+ if (p)
+ *pp++ = (char *)getdata(n);
+ else
+ zsfree(getdata(n));
+ }
+ if (p) {
+ *pp++ = NULL;
+ setaparam(reply, p);
+ }
+ if (resettty && SHTTY != -1)
+ settyinfo(&saveti);
+ return c == EOF;
+ }
+ buf = bptr = (char *)zalloc(bsiz = 64);
+#ifdef MULTIBYTE_SUPPORT
+ laststart = buf;
+ ret = MB_INCOMPLETE;
+#endif
+ /* any remaining part of the line goes into one parameter */
+ bslash = 0;
+ if (!gotnl) {
+ sigset_t s = child_unblock();
+ for (;;) {
+ c = zread(izle, &readchar, izle_timeout);
+#ifdef MULTIBYTE_SUPPORT
+ if (c == EOF) {
+ /* not waiting to be completed any more */
+ ret = 0;
+ break;
+ }
+ *bptr = (char)c;
+ if (isset(MULTIBYTE)) {
+ ret = mbrtowc(&wc, bptr, 1, &mbs);
+ if (!ret) /* NULL */
+ ret = 1;
+ } else {
+ ret = 1;
+ wc = (wchar_t)c;
+ }
+ if (ret != MB_INCOMPLETE) {
+ if (ret == MB_INVALID) {
+ memset(&mbs, 0, sizeof(mbs));
+ /* Treat this as a single character */
+ wc = (wchar_t)c;
+ laststart = bptr;
+ }
+ /*
+ * \ at the end of a line introduces a continuation line,
+ * except in raw mode (-r option)
+ */
+ if (bslash && wc == delim) {
+ bslash = 0;
+ continue;
+ }
+ if (wc == delim && !zbuf)
+ break;
+ if (!bslash && bptr == buf && wcsitype(wc, ISEP)) {
+ if (c < 128 && iwsep(c))
+ continue;
+ else if (!first) {
+ first = 1;
+ continue;
+ }
+ }
+ bslash = (wc == L'\\' && !bslash && !OPT_ISSET(ops,'r'));
+ if (bslash)
+ continue;
+ }
+ if (imeta(STOUC(*bptr))) {
+ bptr[1] = bptr[0] ^ 32;
+ bptr[0] = Meta;
+ bptr += 2;
+ }
+ else
+ bptr++;
+ if (ret != MB_INCOMPLETE)
+ laststart = bptr;
+#else
+ /* \ at the end of a line introduces a continuation line, except in
+ raw mode (-r option) */
+ if (bslash && c == delim) {
+ bslash = 0;
+ continue;
+ }
+ if (c == EOF || (c == delim && !zbuf))
+ break;
+ if (!bslash && isep(c) && bptr == buf) {
+ if (iwsep(c))
+ continue;
+ else if (!first) {
+ first = 1;
+ continue;
+ }
+ }
+ bslash = c == '\\' && !bslash && !OPT_ISSET(ops,'r');
+ if (bslash)
+ continue;
+ if (imeta(c)) {
+ *bptr++ = Meta;
+ *bptr++ = c ^ 32;
+ } else
+ *bptr++ = c;
+#endif
+ /* increase the buffer size, if necessary */
+ if (bptr >= buf + bsiz - 1) {
+ int blen = bptr - buf;
+#ifdef MULTIBYTE_SUPPORT
+ int llen = laststart - buf;
+#endif
+
+ buf = realloc(buf, bsiz *= 2);
+ bptr = buf + blen;
+#ifdef MULTIBYTE_SUPPORT
+ laststart = buf + llen;
+#endif
+ }
+ }
+ signal_setmask(s);
+ }
+#ifdef MULTIBYTE_SUPPORT
+ if (ret != MB_INCOMPLETE)
+ bptr = laststart;
+#endif
+ /*
+ * Strip trailing IFS whitespace.
+ * iwsep can only be certain single-byte ASCII bytes, but we
+ * must check the byte isn't metafied.
+ */
+ while (bptr > buf) {
+ if (bptr > buf + 1 && bptr[-2] == Meta) {
+ /* non-ASCII, can't be IWSEP */
+ break;
+ } else if (iwsep(bptr[-1]))
+ bptr--;
+ else
+ break;
+ }
+ *bptr = '\0';
+ if (resettty && SHTTY != -1)
+ settyinfo(&saveti);
+ /* final assignment of reply, etc. */
+ if (OPT_ISSET(ops,'e') || OPT_ISSET(ops,'E')) {
+ zputs(buf, stdout);
+ putchar('\n');
+ }
+ if (!OPT_ISSET(ops,'e'))
+ setsparam(reply, buf);
+ else
+ zsfree(buf);
+ if (zbuforig) {
+ char first = *zbuforig;
+
+ zsfree(zbuforig);
+ if (!first)
+ return 1;
+ } else if (c == EOF) {
+ if (readfd == coprocin) {
+ close(coprocin);
+ close(coprocout);
+ coprocin = coprocout = -1;
+ }
+ return 1;
+ }
+ /*
+ * The following is to ensure a failure to set the parameter
+ * causes a non-zero status return. There are arguments for
+ * turning a non-zero status into errflag more widely.
+ */
+ return errflag;
+}
+
+/**/
+static int
+zread(int izle, int *readchar, long izle_timeout)
+{
+ char cc, retry = 0;
+ int ret;
+
+ if (izle) {
+ int c;
+ zleentry(ZLE_CMD_GET_KEY, izle_timeout, NULL, &c);
+
+ return (c < 0 ? EOF : c);
+ }
+ /* use zbuf if possible */
+ if (zbuf) {
+ /* If zbuf points to anything, it points to the next character in the
+ buffer. This may be a null byte to indicate EOF. If reading from the
+ buffer, move on the buffer pointer. */
+ if (*zbuf == Meta)
+ return zbuf++, STOUC(*zbuf++ ^ 32);
+ else
+ return (*zbuf) ? STOUC(*zbuf++) : EOF;
+ }
+ if (*readchar >= 0) {
+ cc = *readchar;
+ *readchar = -1;
+ return STOUC(cc);
+ }
+ for (;;) {
+ /* read a character from readfd */
+ ret = read(readfd, &cc, 1);
+ switch (ret) {
+ case 1:
+ /* return the character read */
+ return STOUC(cc);
+ case -1:
+#if defined(EAGAIN) || defined(EWOULDBLOCK)
+ if (!retry && readfd == 0 && (
+# ifdef EAGAIN
+ errno == EAGAIN
+# ifdef EWOULDBLOCK
+ ||
+# endif /* EWOULDBLOCK */
+# endif /* EAGAIN */
+# ifdef EWOULDBLOCK
+ errno == EWOULDBLOCK
+# endif /* EWOULDBLOCK */
+ ) && setblock_stdin()) {
+ retry = 1;
+ continue;
+ } else
+#endif /* EAGAIN || EWOULDBLOCK */
+ if (errno == EINTR && !(errflag || retflag || breaks || contflag))
+ continue;
+ break;
+ }
+ return EOF;
+ }
+}
+
+/* holds arguments for testlex() */
+/**/
+char **testargs, **curtestarg;
+
+/* test, [: the old-style general purpose logical expression builtin */
+
+/**/
+void
+testlex(void)
+{
+ if (tok == LEXERR)
+ return;
+
+ tokstr = *(curtestarg = testargs);
+ if (!*testargs) {
+ /* if tok is already zero, reading past the end: error */
+ tok = tok ? NULLTOK : LEXERR;
+ return;
+ } else if (!strcmp(*testargs, "-o"))
+ tok = DBAR;
+ else if (!strcmp(*testargs, "-a"))
+ tok = DAMPER;
+ else if (!strcmp(*testargs, "!"))
+ tok = BANG;
+ else if (!strcmp(*testargs, "("))
+ tok = INPAR;
+ else if (!strcmp(*testargs, ")"))
+ tok = OUTPAR;
+ else
+ tok = STRING;
+ testargs++;
+}
+
+/**/
+int
+bin_test(char *name, char **argv, UNUSED(Options ops), int func)
+{
+ char **s;
+ Eprog prog;
+ struct estate state;
+ int nargs, sense = 0, ret;
+
+ /* if "test" was invoked as "[", it needs a matching "]" *
+ * which is subsequently ignored */
+ if (func == BIN_BRACKET) {
+ for (s = argv; *s; s++);
+ if (s == argv || strcmp(s[-1], "]")) {
+ zwarnnam(name, "']' expected");
+ return 2;
+ }
+ s[-1] = NULL;
+ }
+ /* an empty argument list evaluates to false (1) */
+ if (!*argv)
+ return 1;
+
+ /*
+ * Implement some XSI extensions to POSIX here.
+ * See
+ * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html
+ */
+ nargs = arrlen(argv);
+ if (nargs == 3 || nargs == 4)
+ {
+ /*
+ * As parentheses are an extension, we need to be careful ---
+ * if this is a three-argument expression that could
+ * be a binary operator, prefer that.
+ */
+ if (!strcmp(argv[0], "(") && !strcmp(argv[nargs-1],")") &&
+ (nargs != 3 || !is_cond_binary_op(argv[1]))) {
+ argv[nargs-1] = NULL;
+ argv++;
+ }
+ if (nargs == 4 && !strcmp("!", argv[0])) {
+ sense = 1;
+ argv++;
+ }
+ }
+
+ zcontext_save();
+ testargs = argv;
+ tok = NULLTOK;
+ condlex = testlex;
+ testlex();
+ prog = parse_cond();
+ condlex = zshlex;
+
+ if (errflag) {
+ errflag &= ~ERRFLAG_ERROR;
+ zcontext_restore();
+ return 2;
+ }
+
+ if (!prog || tok == LEXERR) {
+ zwarnnam(name, tokstr ? "parse error" : "argument expected");
+ zcontext_restore();
+ return 2;
+ }
+ zcontext_restore();
+
+ if (*curtestarg) {
+ zwarnnam(name, "too many arguments");
+ return 2;
+ }
+
+ /* syntax is OK, so evaluate */
+
+ state.prog = prog;
+ state.pc = prog->prog;
+ state.strs = prog->strs;
+
+ ret = evalcond(&state, name);
+ if (ret < 2 && sense)
+ ret = ! ret;
+
+ return ret;
+}
+
+/* display a time, provided in units of 1/60s, as minutes and seconds */
+#define pttime(X) printf("%ldm%ld.%02lds",((long) (X))/(60 * clktck),\
+ ((long) (X))/clktck%clktck,\
+ ((long) (X))*100/clktck%100)
+
+/* times: display, in a two-line format, the times provided by times(3) */
+
+/**/
+int
+bin_times(UNUSED(char *name), UNUSED(char **argv), UNUSED(Options ops), UNUSED(int func))
+{
+ struct tms buf;
+ long clktck = get_clktck();
+
+ /* get time accounting information */
+ if (times(&buf) == -1)
+ return 1;
+ pttime(buf.tms_utime); /* user time */
+ putchar(' ');
+ pttime(buf.tms_stime); /* system time */
+ putchar('\n');
+ pttime(buf.tms_cutime); /* user time, children */
+ putchar(' ');
+ pttime(buf.tms_cstime); /* system time, children */
+ putchar('\n');
+ return 0;
+}
+
+/* trap: set/unset signal traps */
+
+/**/
+int
+bin_trap(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
+{
+ Eprog prog;
+ char *arg, *s;
+ int sig;
+
+ if (*argv && !strcmp(*argv, "--"))
+ argv++;
+
+ /* If given no arguments, list all currently-set traps */
+ if (!*argv) {
+ queue_signals();
+ for (sig = 0; sig < VSIGCOUNT; sig++) {
+ if (sigtrapped[sig] & ZSIG_FUNC) {
+ HashNode hn;
+
+ if ((hn = gettrapnode(sig, 0)))
+ shfunctab->printnode(hn, 0);
+ DPUTS(!hn, "BUG: I did not find any trap functions!");
+ } else if (sigtrapped[sig]) {
+ const char *name = getsigname(sig);
+ if (!siglists[sig])
+ printf("trap -- '' %s\n", name);
+ else {
+ s = getpermtext(siglists[sig], NULL, 0);
+ printf("trap -- ");
+ quotedzputs(s, stdout);
+ printf(" %s\n", name);
+ zsfree(s);
+ }
+ }
+ }
+ unqueue_signals();
+ return 0;
+ }
+
+ /* If we have a signal number, unset the specified *
+ * signals. With only -, remove all traps. */
+ if ((getsignum(*argv) != -1) || (!strcmp(*argv, "-") && argv++)) {
+ if (!*argv) {
+ for (sig = 0; sig < VSIGCOUNT; sig++)
+ unsettrap(sig);
+ } else {
+ for (; *argv; argv++) {
+ sig = getsignum(*argv);
+ if (sig == -1) {
+ zwarnnam(name, "undefined signal: %s", *argv);
+ break;
+ }
+ unsettrap(sig);
+ }
+ }
+ return *argv != NULL;
+ }
+
+ /* Sort out the command to execute on trap */
+ arg = *argv++;
+ if (!*arg)
+ prog = &dummy_eprog;
+ else if (!(prog = parse_string(arg, 1))) {
+ zwarnnam(name, "couldn't parse trap command");
+ return 1;
+ }
+
+ /* set traps */
+ for (; *argv; argv++) {
+ Eprog t;
+ int flags;
+
+ sig = getsignum(*argv);
+ if (sig == -1) {
+ zwarnnam(name, "undefined signal: %s", *argv);
+ break;
+ }
+ if (idigit(**argv) ||
+ !strcmp(sigs[sig], *argv) ||
+ (!strncmp("SIG", *argv, 3) && !strcmp(sigs[sig], *argv+3))) {
+ /* The signal was specified by number or by canonical name (with
+ * or without SIG prefix).
+ */
+ flags = 0;
+ }
+ else {
+ /*
+ * Record that the signal is used under an assumed name.
+ * If we ever have more than one alias per signal this
+ * will need improving.
+ */
+ flags = ZSIG_ALIAS;
+ }
+ t = dupeprog(prog, 0);
+ if (settrap(sig, t, flags))
+ freeeprog(t);
+ }
+ return *argv != NULL;
+}
+
+/**/
+int
+bin_ttyctl(UNUSED(char *name), UNUSED(char **argv), Options ops, UNUSED(int func))
+{
+ if (OPT_ISSET(ops,'f'))
+ ttyfrozen = 1;
+ else if (OPT_ISSET(ops,'u'))
+ ttyfrozen = 0;
+ else
+ printf("tty is %sfrozen\n", ttyfrozen ? "" : "not ");
+ return 0;
+}
+
+/* let -- mathematical evaluation */
+
+/**/
+int
+bin_let(UNUSED(char *name), char **argv, UNUSED(Options ops), UNUSED(int func))
+{
+ mnumber val = zero_mnumber;
+
+ while (*argv)
+ val = matheval(*argv++);
+ /* Errors in math evaluation in let are non-fatal. */
+ errflag &= ~ERRFLAG_ERROR;
+ /* should test for fabs(val.u.d) < epsilon? */
+ return (val.type == MN_INTEGER) ? val.u.l == 0 : val.u.d == 0.0;
+}
+
+/* umask command. umask may be specified as octal digits, or in the *
+ * symbolic form that chmod(1) uses. Well, a subset of it. Remember *
+ * that only the bottom nine bits of umask are used, so there's no *
+ * point allowing the set{u,g}id and sticky bits to be specified. */
+
+/**/
+int
+bin_umask(char *nam, char **args, Options ops, UNUSED(int func))
+{
+ mode_t um;
+ char *s = *args;
+
+ /* Get the current umask. */
+ um = umask(0);
+ umask(um);
+ /* No arguments means to display the current setting. */
+ if (!s) {
+ if (OPT_ISSET(ops,'S')) {
+ char *who = "ugo";
+
+ while (*who) {
+ char *what = "rwx";
+ printf("%c=", *who++);
+ while (*what) {
+ if (!(um & 0400))
+ putchar(*what);
+ um <<= 1;
+ what++;
+ }
+ putchar(*who ? ',' : '\n');
+ }
+ } else {
+ if (um & 0700)
+ putchar('0');
+ printf("%03o\n", (unsigned)um);
+ }
+ return 0;
+ }
+
+ if (idigit(*s)) {
+ /* Simple digital umask. */
+ um = zstrtol(s, &s, 8);
+ if (*s) {
+ zwarnnam(nam, "bad umask");
+ return 1;
+ }
+ } else {
+ /* Symbolic notation -- slightly complicated. */
+ int whomask, umaskop, mask;
+
+ /* More than one symbolic argument may be used at once, each separated
+ by commas. */
+ for (;;) {
+ /* First part of the argument -- who does this apply to?
+ u=owner, g=group, o=other. */
+ whomask = 0;
+ while (*s == 'u' || *s == 'g' || *s == 'o' || *s == 'a')
+ if (*s == 'u')
+ s++, whomask |= 0700;
+ else if (*s == 'g')
+ s++, whomask |= 0070;
+ else if (*s == 'o')
+ s++, whomask |= 0007;
+ else if (*s == 'a')
+ s++, whomask |= 0777;
+ /* Default whomask is everyone. */
+ if (!whomask)
+ whomask = 0777;
+ /* Operation may be +, - or =. */
+ umaskop = (int)*s;
+ if (!(umaskop == '+' || umaskop == '-' || umaskop == '=')) {
+ if (umaskop)
+ zwarnnam(nam, "bad symbolic mode operator: %c", umaskop);
+ else
+ zwarnnam(nam, "bad umask");
+ return 1;
+ }
+ /* Permissions mask -- r=read, w=write, x=execute. */
+ mask = 0;
+ while (*++s && *s != ',')
+ if (*s == 'r')
+ mask |= 0444 & whomask;
+ else if (*s == 'w')
+ mask |= 0222 & whomask;
+ else if (*s == 'x')
+ mask |= 0111 & whomask;
+ else {
+ zwarnnam(nam, "bad symbolic mode permission: %c", *s);
+ return 1;
+ }
+ /* Apply parsed argument to um. */
+ if (umaskop == '+')
+ um &= ~mask;
+ else if (umaskop == '-')
+ um |= mask;
+ else /* umaskop == '=' */
+ um = (um | (whomask)) & ~mask;
+ if (*s == ',')
+ s++;
+ else
+ break;
+ }
+ if (*s) {
+ zwarnnam(nam, "bad character in symbolic mode: %c", *s);
+ return 1;
+ }
+ }
+
+ /* Finally, set the new umask. */
+ umask(um);
+ return 0;
+}
+
+/* Generic builtin for facilities not available on this OS */
+
+/**/
+mod_export int
+bin_notavail(char *nam, UNUSED(char **argv), UNUSED(Options ops), UNUSED(int func))
+{
+ zwarnnam(nam, "not available on this system");
+ return 1;
+}
diff --git a/dotfiles/system/.zsh/modules/Src/compat.c b/dotfiles/system/.zsh/modules/Src/compat.c
new file mode 100644
index 0000000..7b5c441
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/compat.c
@@ -0,0 +1,742 @@
+/*
+ * compat.c - compatibility 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)
+{
+ const 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
+
+
+/* Provide clock time with nanoseconds */
+
+/**/
+mod_export int
+zgettime(struct timespec *ts)
+{
+ int ret = -1;
+
+#ifdef HAVE_CLOCK_GETTIME
+ struct timespec dts;
+ if (clock_gettime(CLOCK_REALTIME, &dts) < 0) {
+ zwarn("unable to retrieve time: %e", errno);
+ ret--;
+ } else {
+ ret++;
+ ts->tv_sec = (time_t) dts.tv_sec;
+ ts->tv_nsec = (long) dts.tv_nsec;
+ }
+#endif
+
+ if (ret) {
+ struct timeval dtv;
+ struct timezone dtz;
+ gettimeofday(&dtv, &dtz);
+ ret++;
+ ts->tv_sec = (time_t) dtv.tv_sec;
+ ts->tv_nsec = (long) dtv.tv_usec * 1000;
+ }
+
+ return ret;
+}
+
+
+/* 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
+
+
+#if 0
+/* pathconf(_PC_PATH_MAX) is not currently useful to zsh. The value *
+ * returned varies depending on a number of factors, e.g. the amount *
+ * of memory available to the operating system at a given time; thus *
+ * it can't be used for buffer allocation, or even as an indication *
+ * of whether an attempt to use or create a given pathname may fail *
+ * at any future time. *
+ * *
+ * The call is also permitted to fail if the argument path is not an *
+ * existing directory, so even to make sense of that one must search *
+ * for a valid directory somewhere in the path and adjust. Even if *
+ * it succeeds, the return value is relative to the input directory, *
+ * and therefore potentially relative to the length of the shortest *
+ * path either to that directory or to our working directory. *
+ * *
+ * Finally, see the note below for glibc; detection of pathconf() is *
+ * not by itself an indication that it works reliably. */
+
+/* The documentation for pathconf() says something like: *
+ * The limit is returned, if one exists. If the system does *
+ * not have a limit for the requested resource, -1 is *
+ * returned, and errno is unchanged. If there is an error, *
+ * -1 is returned, and errno is set to reflect the nature of *
+ * the error. *
+ * *
+ * System calls are not permitted to set errno to 0; but we must (or *
+ * some other flag value) in order to determine that the resource is *
+ * unlimited. What use is leaving errno unchanged? Instead, define *
+ * a wrapper that resets errno to 0 and returns 0 for "the system *
+ * does not have a limit," so that -1 always means a real error. */
+
+/**/
+mod_export long
+zpathmax(char *dir)
+{
+#ifdef HAVE_PATHCONF
+ long pathmax;
+
+ errno = 0;
+ if ((pathmax = pathconf(dir, _PC_PATH_MAX)) >= 0) {
+ /* Some versions of glibc pathconf return a hardwired value! */
+ return pathmax;
+ } else if (errno == EINVAL || errno == ENOENT || errno == ENOTDIR) {
+ /* Work backward to find a directory, until we run out of path. */
+ char *tail = strrchr(dir, '/');
+ while (tail > dir && tail[-1] == '/')
+ --tail;
+ if (tail > dir) {
+ *tail = 0;
+ pathmax = zpathmax(dir);
+ *tail = '/';
+ } else {
+ errno = 0;
+ if (tail)
+ pathmax = pathconf("/", _PC_PATH_MAX);
+ else
+ pathmax = pathconf(".", _PC_PATH_MAX);
+ }
+ if (pathmax > 0) {
+ long taillen = (tail ? strlen(tail) : (strlen(dir) + 1));
+ if (taillen < pathmax)
+ return pathmax - taillen;
+ else
+ errno = ENAMETOOLONG;
+ }
+ }
+ if (errno)
+ return -1;
+ else
+ return 0; /* pathmax should be considered unlimited */
+#else
+ long dirlen = strlen(dir);
+
+ /* The following is wrong if dir is not an absolute path. */
+ return ((long) ((dirlen >= PATH_MAX) ?
+ ((errno = ENAMETOOLONG), -1) :
+ ((errno = 0), PATH_MAX - dirlen)));
+#endif
+}
+#endif /* 0 */
+
+/**/
+#ifdef HAVE_SYSCONF
+/*
+ * This is replaced by a macro from system.h if not HAVE_SYSCONF.
+ * 0 is returned by sysconf if _SC_OPEN_MAX is unavailable;
+ * -1 is returned on error
+ *
+ * Neither of these should happen, but resort to OPEN_MAX rather
+ * than return 0 or -1 just in case.
+ *
+ * We'll limit the open maximum to ZSH_INITIAL_OPEN_MAX to
+ * avoid probing ridiculous numbers of file descriptors.
+ */
+
+/**/
+mod_export long
+zopenmax(void)
+{
+ long openmax;
+
+ if ((openmax = sysconf(_SC_OPEN_MAX)) < 1) {
+ openmax = OPEN_MAX;
+ } else if (openmax > OPEN_MAX) {
+ /* On some systems, "limit descriptors unlimited" or the *
+ * equivalent will set openmax to a huge number. Unless *
+ * there actually is a file descriptor > OPEN_MAX already *
+ * open, nothing in zsh requires the true maximum, and in *
+ * fact it causes inefficiency elsewhere if we report it. *
+ * So, report the maximum of OPEN_MAX or the largest open *
+ * descriptor (is there a better way to find that?). */
+ long i, j = OPEN_MAX;
+ if (openmax > ZSH_INITIAL_OPEN_MAX)
+ openmax = ZSH_INITIAL_OPEN_MAX;
+ for (i = j; i < openmax; i += (errno != EINTR)) {
+ errno = 0;
+ if (fcntl(i, F_GETFL, 0) < 0 &&
+ (errno == EBADF || errno == EINTR))
+ continue;
+ j = i;
+ }
+ openmax = j;
+ }
+
+ return (max_zsh_fd > openmax) ? max_zsh_fd : openmax;
+}
+
+/**/
+#endif
+
+/*
+ * Rationalise the current directory, returning the string.
+ *
+ * If "d" is not NULL, it is used to store information about the
+ * directory. The returned name is also present in d->dirname and is in
+ * permanently allocated memory. The handling of this case depends on
+ * whether the fchdir() system call is available; if it is, it is assumed
+ * the caller is able to restore the current directory. On successfully
+ * identifying the directory the function returns immediately rather
+ * than ascending the hierarchy.
+ *
+ * If "d" is NULL, no assumption about the caller's behaviour is
+ * made. The returned string is in heap memory. This case is
+ * always handled by changing directory up the hierarchy.
+ *
+ * On Cygwin or other systems where USE_GETCWD is defined (at the
+ * time of writing only QNX), we skip all the above and use the
+ * getcwd() system call.
+ */
+
+/**/
+mod_export char *
+zgetdir(struct dirsav *d)
+{
+ char nbuf[PATH_MAX+3];
+ char *buf;
+ int bufsiz, pos;
+ struct stat sbuf;
+ ino_t pino;
+ dev_t pdev;
+#if !defined(__CYGWIN__) && !defined(USE_GETCWD)
+ struct dirent *de;
+ DIR *dir;
+ dev_t dev;
+ ino_t ino;
+ int len;
+#endif
+
+ buf = zhalloc(bufsiz = PATH_MAX+1);
+ pos = bufsiz - 1;
+ buf[pos] = '\0';
+ strcpy(nbuf, "../");
+ if (stat(".", &sbuf) < 0) {
+ return NULL;
+ }
+
+ /* Record the initial inode and device */
+ pino = sbuf.st_ino;
+ pdev = sbuf.st_dev;
+ if (d)
+ d->ino = pino, d->dev = pdev;
+#if !defined(__CYGWIN__) && !defined(USE_GETCWD)
+#ifdef HAVE_FCHDIR
+ else
+#endif
+ holdintr();
+
+ for (;;) {
+ /* Examine the parent of the current directory. */
+ if (stat("..", &sbuf) < 0)
+ break;
+
+ /* Inode and device of curtent directory */
+ ino = pino;
+ dev = pdev;
+ /* Inode and device of current directory's parent */
+ pino = sbuf.st_ino;
+ pdev = sbuf.st_dev;
+
+ /* If they're the same, we've reached the root directory. */
+ 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;
+ }
+
+ /* Search the parent for the current directory. */
+ 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) {
+ /* Found the directory we're currently in */
+ 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 */
+ {
+ /* Maybe found directory, need to check device & inode */
+ 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; /* Not found */
+ /*
+ * We get the "/" free just by copying from nbuf+2 instead
+ * of nbuf+3, which is where we copied the path component.
+ * This means buf[pos] is always a "/".
+ */
+ len = strlen(nbuf + 2);
+ pos -= len;
+ while (pos <= 1) {
+ char *newbuf = zhalloc(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;
+ }
+
+ /*
+ * Fix up the directory, if necessary.
+ * We're changing back down the hierarchy, ignore the
+ * "/" at buf[pos].
+ */
+ if (d) {
+#ifndef HAVE_FCHDIR
+ if (buf[pos])
+ zchdir(buf + pos + 1);
+ noholdintr();
+#endif
+ return NULL;
+ }
+
+ if (buf[pos])
+ zchdir(buf + pos + 1);
+ noholdintr();
+
+#else /* __CYGWIN__, USE_GETCWD cases */
+
+ if (!getcwd(buf, bufsiz)) {
+ if (d) {
+ return NULL;
+ }
+ } else {
+ if (d) {
+ return d->dirname = ztrdup(buf);
+ }
+ return buf;
+ }
+#endif
+
+ /*
+ * Something bad happened.
+ * This has been seen when inside a special directory,
+ * such as the Netapp .snapshot directory, that doesn't
+ * appear as a directory entry in the parent directory.
+ * We'll just need our best guess.
+ *
+ * We only get here from zgetcwd(); let that fall back to pwd.
+ */
+
+ return NULL;
+}
+
+/*
+ * Try to find the current directory.
+ * If we couldn't work it out internally, fall back to getcwd().
+ * If it fails, fall back to pwd; if zgetcwd() is being used
+ * to set pwd, pwd should be NULL and we just return ".".
+ */
+
+/**/
+char *
+zgetcwd(void)
+{
+ char *ret = zgetdir(NULL);
+#ifdef HAVE_GETCWD
+ if (!ret) {
+#ifdef GETCWD_CALLS_MALLOC
+ char *cwd = getcwd(NULL, 0);
+ if (cwd) {
+ ret = dupstring(cwd);
+ free(cwd);
+ }
+#else
+ char *cwdbuf = zalloc(PATH_MAX+1);
+ ret = getcwd(cwdbuf, PATH_MAX);
+ if (ret)
+ ret = dupstring(ret);
+ zfree(cwdbuf, PATH_MAX+1);
+#endif /* GETCWD_CALLS_MALLOC */
+ }
+#endif /* HAVE_GETCWD */
+ if (!ret)
+ ret = unmeta(pwd);
+ if (!ret)
+ ret = dupstring(".");
+ return ret;
+}
+
+/*
+ * chdir with arbitrary long pathname. Returns 0 on success, -1 on normal *
+ * failure and -2 when chdir failed and the current directory is lost.
+ *
+ * This is to be treated as if at system level, so dir is unmetafied but
+ * terminated by a NULL.
+ */
+
+/**/
+mod_export int
+zchdir(char *dir)
+{
+ char *s;
+ int currdir = -2;
+
+ for (;;) {
+ if (!*dir || chdir(dir) == 0) {
+#ifdef HAVE_FCHDIR
+ if (currdir >= 0)
+ close(currdir);
+#endif
+ 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) < 0) {
+ *s = '/';
+ break;
+ }
+#ifndef HAVE_FCHDIR
+ currdir = -1;
+#endif
+ *s = '/';
+ while (*++s == '/')
+ ;
+ dir = s;
+ }
+#ifdef HAVE_FCHDIR
+ if (currdir >= 0) {
+ if (fchdir(currdir) < 0) {
+ close(currdir);
+ return -2;
+ }
+ close(currdir);
+ return -1;
+ }
+#endif
+ return currdir == -2 ? -1 : -2;
+}
+
+/*
+ * How to print out a 64 bit integer. This isn't needed (1) if longs
+ * are 64 bit, since ordinary %ld will work (2) if we couldn't find a
+ * 64 bit type anyway.
+ */
+/**/
+#ifdef ZSH_64_BIT_TYPE
+/**/
+mod_export char *
+output64(zlong val)
+{
+ static char llbuf[DIGBUFSIZE];
+ convbase(llbuf, val, 0);
+ return llbuf;
+}
+/**/
+#endif /* ZSH_64_BIT_TYPE */
+
+/**/
+#ifndef HAVE_STRTOUL
+
+/*
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Convert a string to an unsigned long integer.
+ *
+ * Ignores `locale' stuff. Assumes that the upper and lower case
+ * alphabets and digits are each contiguous.
+ */
+
+/**/
+unsigned long
+strtoul(nptr, endptr, base)
+ const char *nptr;
+ char **endptr;
+ int base;
+{
+ const char *s;
+ unsigned long acc, cutoff;
+ int c;
+ int neg, any, cutlim;
+
+ /* endptr may be NULL */
+
+ s = nptr;
+ do {
+ c = (unsigned char) *s++;
+ } while (isspace(c));
+ if (c == '-') {
+ neg = 1;
+ c = *s++;
+ } else {
+ neg = 0;
+ if (c == '+')
+ c = *s++;
+ }
+ if ((base == 0 || base == 16) &&
+ c == '0' && (*s == 'x' || *s == 'X')) {
+ c = s[1];
+ s += 2;
+ base = 16;
+ }
+ if (base == 0)
+ base = c == '0' ? 8 : 10;
+
+ cutoff = ULONG_MAX / (unsigned long)base;
+ cutlim = (int)(ULONG_MAX % (unsigned long)base);
+ for (acc = 0, any = 0;; c = (unsigned char) *s++) {
+ if (isdigit(c))
+ c -= '0';
+ else if (isalpha(c)) {
+ c -= isupper(c) ? 'A' - 10 : 'a' - 10;
+ } else
+ break;
+ if (c >= base)
+ break;
+ if (any < 0)
+ continue;
+ if (acc > cutoff || (acc == cutoff && c > cutlim)) {
+ any = -1;
+ acc = ULONG_MAX;
+ errno = ERANGE;
+ } else {
+ any = 1;
+ acc *= (unsigned long)base;
+ acc += c;
+ }
+ }
+ if (neg && any > 0)
+ acc = -acc;
+ if (endptr != NULL)
+ *endptr = any ? s - 1 : nptr;
+ return (acc);
+}
+
+/**/
+#endif /* HAVE_STRTOUL */
+
+/**/
+#ifdef ENABLE_UNICODE9
+#include "./wcwidth9.h"
+
+/**/
+int
+u9_wcwidth(wchar_t ucs)
+{
+ int w = wcwidth9(ucs);
+ if (w < -1)
+ return 1;
+ return w;
+}
+
+/**/
+int
+u9_iswprint(wint_t ucs)
+{
+ if (ucs == 0)
+ return 0;
+ return wcwidth9(ucs) != -1;
+}
+
+/**/
+#endif /* ENABLE_UNICODE9 */
+
+/**/
+#if defined(__APPLE__) && defined(BROKEN_ISPRINT)
+
+/**/
+int
+isprint_ascii(int c)
+{
+ if (!strcmp(nl_langinfo(CODESET), "UTF-8"))
+ return (c >= 0x20 && c <= 0x7e);
+ else
+ return isprint(c);
+}
+
+/**/
+#endif /* __APPLE__ && BROKEN_ISPRINT */
diff --git a/dotfiles/system/.zsh/modules/Src/exec.c b/dotfiles/system/.zsh/modules/Src/exec.c
new file mode 100644
index 0000000..615a508
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/exec.c
@@ -0,0 +1,6250 @@
+/*
+ * 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"
+
+/* Flags for last argument of addvars */
+
+enum {
+ /* Export the variable for "VAR=val cmd ..." */
+ ADDVAR_EXPORT = 1 << 0,
+ /* Apply restrictions for variable */
+ ADDVAR_RESTRICT = 1 << 1,
+ /* Variable list is being restored later */
+ ADDVAR_RESTORE = 1 << 2
+};
+
+/* Structure in which to save values around shell function call */
+
+struct funcsave {
+ char opts[OPT_SIZE];
+ char *argv0;
+ int zoptind, lastval, optcind, numpipestats;
+ int *pipestats;
+ char *scriptname;
+ int breaks, contflag, loops, emulation, noerrexit, oflags, restore_sticky;
+ Emulation_options sticky;
+ struct funcstack fstack;
+};
+typedef struct funcsave *Funcsave;
+
+/*
+ * used to suppress ERREXIT and trapping of SIGZERR, SIGEXIT.
+ * Bits from noerrexit_bits.
+ */
+
+/**/
+int noerrexit;
+
+/* used to suppress ERREXIT or ERRRETURN for one occurrence: 0 or 1 */
+
+/**/
+int this_noerrexit;
+
+/*
+ * noerrs = 1: suppress error messages
+ * noerrs = 2: don't set errflag on parse error, either
+ */
+
+/**/
+mod_export int noerrs;
+
+/* do not save history on exec and exit */
+
+/**/
+int nohistsave;
+
+/* error flag: bits from enum errflag_bits */
+
+/**/
+mod_export int errflag;
+
+/*
+ * State of trap return value. Value is from enum trap_state.
+ */
+
+/**/
+int trap_state;
+
+/*
+ * Value associated with return from a trap.
+ * This is only active if we are inside a trap, else its value
+ * is irrelevant. It is initialised to -1 for a function trap and
+ * -2 for a non-function trap and if negative is decremented as
+ * we go deeper into functions and incremented as we come back up.
+ * The value is used to decide if an explicit "return" should cause
+ * a return from the caller of the trap; it does this by setting
+ * trap_return to a status (i.e. a non-negative value).
+ *
+ * In summary, trap_return is
+ * - zero unless we are in a trap
+ * - negative in a trap unless it has triggered. Code uses this
+ * to detect an active trap.
+ * - non-negative in a trap once it was triggered. It should remain
+ * non-negative until restored after execution of the trap.
+ */
+
+/**/
+int trap_return;
+
+/* != 0 if this is a subshell */
+
+/**/
+int subsh;
+
+/* != 0 if we have a return pending */
+
+/**/
+mod_export 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. */
+
+/**/
+mod_export unsigned char *fdtable;
+
+/* The allocated size of fdtable */
+
+/**/
+int fdtable_size;
+
+/* The highest fd that marked with nonzero in fdtable */
+
+/**/
+mod_export int max_zsh_fd;
+
+/* input fd from the coprocess */
+
+/**/
+mod_export int coprocin;
+
+/* output fd from the coprocess */
+
+/**/
+mod_export int coprocout;
+
+/* count of file locks recorded in fdtable */
+
+/**/
+int fdtable_flocks;
+
+
+/* != 0 if the line editor is active */
+
+/**/
+mod_export int zleactive;
+
+/* pid of process undergoing 'process substitution' */
+
+/**/
+pid_t cmdoutpid;
+
+/* pid of last process started by <(...), >(...) */
+
+/**/
+mod_export pid_t procsubstpid;
+
+/* exit status of process undergoing 'process substitution' */
+
+/**/
+int cmdoutval;
+
+/*
+ * This is set by an exiting $(...) substitution to indicate we need
+ * to retain the status. We initialize it to zero if we think we need
+ * to reset the status for a command.
+ */
+
+/**/
+int use_cmdoutval;
+
+/* The context in which a shell function is called, see SFC_* in zsh.h. */
+
+/**/
+mod_export int sfcontext;
+
+/* Stack to save some variables before executing a signal handler function */
+
+/**/
+struct execstack *exstack;
+
+/* Stack with names of function calls, 'source' calls, and 'eval' calls
+ * currently active. */
+
+/**/
+mod_export Funcstack funcstack;
+
+#define execerr() \
+ do { \
+ if (!forked) { \
+ redir_err = lastval = 1; \
+ goto done; \
+ } else { \
+ _exit(1); \
+ } \
+ } while (0)
+
+static int doneps4;
+static char *STTYval;
+static char *blank_env[] = { NULL };
+
+/* Execution functions. */
+
+static int (*execfuncs[WC_COUNT-WC_CURSH]) _((Estate, int)) = {
+ execcursh, exectime, NULL /* execfuncdef handled specially */,
+ execfor, execselect,
+ execwhile, execrepeat, execcase, execif, execcond,
+ execarith, execautofn, exectry
+};
+
+/* structure for command builtin for when it is used with -v or -V */
+static struct builtin commandbn =
+ BUILTIN("command", 0, bin_whence, 0, -1, BIN_COMMAND, "pvV", NULL);
+
+/* parse string into a list */
+
+/**/
+mod_export Eprog
+parse_string(char *s, int reset_lineno)
+{
+ Eprog p;
+ zlong oldlineno;
+
+ zcontext_save();
+ inpush(s, INP_LINENO, NULL);
+ strinbeg(0);
+ oldlineno = lineno;
+ if (reset_lineno)
+ lineno = 1;
+ p = parse_list();
+ lineno = oldlineno;
+ if (tok == LEXERR && !lastval)
+ lastval = 1;
+ strinend();
+ inpop();
+ zcontext_restore();
+ return p;
+}
+
+/**/
+#ifdef HAVE_GETRLIMIT
+
+/* the resource limits for the shell and its children */
+
+/**/
+mod_export struct rlimit current_limits[RLIM_NLIMITS], limits[RLIM_NLIMITS];
+
+/**/
+mod_export 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", errno);
+ limits[limnum] = current_limits[limnum];
+ return -1;
+ }
+ current_limits[limnum] = limits[limnum];
+ }
+ return 0;
+}
+
+/**/
+mod_export 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(struct timeval *tv)
+{
+ pid_t pid;
+ struct timezone dummy_tz;
+
+ /*
+ * Is anybody willing to explain this test?
+ */
+ if (thisjob != -1 && thisjob >= jobtabsize - 1 && !expandjobtab()) {
+ zerr("job table full");
+ return -1;
+ }
+ if (tv)
+ gettimeofday(tv, &dummy_tz);
+ /*
+ * Queueing signals is necessary on Linux because fork()
+ * manipulates mutexes, leading to deadlock in memory
+ * allocation. We don't expect fork() to be particularly
+ * zippy anyway.
+ */
+ queue_signals();
+ pid = fork();
+ unqueue_signals();
+ if (pid == -1) {
+ zerr("fork failed: %e", errno);
+ return -1;
+ }
+#ifdef HAVE_GETRLIMIT
+ if (!pid)
+ /* set resource limits for the child process */
+ setlimits(NULL);
+#endif
+ return pid;
+}
+
+/*
+ * Allen Edeln gebiet ich Andacht,
+ * Hohen und Niedern von Heimdalls Geschlecht;
+ * Ich will list_pipe's Wirken kuenden
+ * Die aeltesten Sagen, der ich mich entsinne...
+ *
+ * In most shells, if you do something like:
+ *
+ * cat foo | while read a; do grep $a bar; done
+ *
+ * the shell forks and executes the loop in the sub-shell thus created.
+ * In zsh this traditionally executes the loop in the current shell, which
+ * is nice to have if the loop does something to change the shell, like
+ * setting parameters or calling builtins.
+ * Putting the loop in a sub-shell makes life easy, because the shell only
+ * has to put it into the job-structure and then treats it as a normal
+ * process. Suspending and interrupting is no problem then.
+ * Some years ago, zsh either couldn't suspend such things at all, or
+ * it got really messed up when users tried to do it. As a solution, we
+ * implemented the list_pipe-stuff, which has since then become a reason
+ * for many nightmares.
+ * Pipelines like the one above are executed by the functions in this file
+ * which call each other (and sometimes recursively). The one above, for
+ * example would lead to a function call stack roughly like:
+ *
+ * execlist->execpline->execcmd->execwhile->execlist->execpline
+ *
+ * (when waiting for the grep, ignoring execpline2 for now). At this time,
+ * zsh has built two job-table entries for it: one for the cat and one for
+ * the grep. If the user hits ^Z at this point (and jobbing is used), the
+ * shell is notified that the grep was suspended. The list_pipe flag is
+ * used to tell the execpline where it was waiting that it was in a pipeline
+ * with a shell construct at the end (which may also be a shell function or
+ * several other things). When zsh sees the suspended grep, it forks to let
+ * the sub-shell execute the rest of the while loop. The parent shell walks
+ * up in the function call stack to the first execpline. There it has to find
+ * out that it has just forked and then has to add information about the sub-
+ * shell (its pid and the text for it) in the job entry of the cat. The pid
+ * is passed down in the list_pipe_pid variable.
+ * But there is a problem: the suspended grep is a child of the parent shell
+ * and can't be adopted by the sub-shell. So the parent shell also has to
+ * keep the information about this process (more precisely: this pipeline)
+ * by keeping the job table entry it created for it. The fact that there
+ * are two jobs which have to be treated together is remembered by setting
+ * the STAT_SUPERJOB flag in the entry for the cat-job (which now also
+ * contains a process-entry for the whole loop -- the sub-shell) and by
+ * setting STAT_SUBJOB in the job of the grep-job. With that we can keep
+ * sub-jobs from being displayed and we can handle an fg/bg on the super-
+ * job correctly. When the super-job is continued, the shell also wakes up
+ * the sub-job. But then, the grep will exit sometime. Now the parent shell
+ * has to remember not to try to wake it up again (in case of another ^Z).
+ * It also has to wake up the sub-shell (which suspended itself immediately
+ * after creation), so that the rest of the loop is executed by it.
+ * But there is more: when the sub-shell is created, the cat may already
+ * have exited, so we can't put the sub-shell in the process group of it.
+ * In this case, we put the sub-shell in the process group of the parent
+ * shell and in any case, the sub-shell has to put all commands executed
+ * by it into its own process group, because only this way the parent
+ * shell can control them since it only knows the process group of the sub-
+ * shell. Of course, this information is also important when putting a job
+ * in the foreground, where we have to attach its process group to the
+ * controlling tty.
+ * All this is made more difficult because we have to handle return values
+ * correctly. If the grep is signaled, its exit status has to be propagated
+ * back to the parent shell which needs it to set the exit status of the
+ * super-job. And of course, when the grep is signaled (including ^C), the
+ * loop has to be stopped, etc.
+ * The code for all this is distributed over three files (exec.c, jobs.c,
+ * and signals.c) and none of them is a simple one. So, all in all, there
+ * may still be bugs, but considering the complexity (with race conditions,
+ * signal handling, and all that), this should probably be expected.
+ */
+
+/**/
+int list_pipe = 0, simple_pline = 0;
+
+static pid_t list_pipe_pid;
+static struct timeval list_pipe_start;
+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(Estate state, int do_exec)
+{
+ Wordcode end = state->pc + WC_CURSH_SKIP(state->pc[-1]);
+
+ /* Skip word only used for try/always */
+ state->pc++;
+
+ /*
+ * The test thisjob != -1 was added because sometimes thisjob
+ * can be invalid at this point. The case in question was
+ * in a precmd function after operations involving background
+ * jobs.
+ *
+ * This is because sometimes we bypass job control to execute
+ * very simple functions via execssimple().
+ */
+ if (!list_pipe && thisjob != -1 && thisjob != list_pipe_job &&
+ !hasprocs(thisjob))
+ deletejob(jobtab + thisjob, 0);
+ cmdpush(CS_CURSH);
+ execlist(state, 1, do_exec);
+ cmdpop();
+
+ state->pc = end;
+ this_noerrexit = 1;
+
+ return lastval;
+}
+
+/* execve after handling $_ and #! */
+
+#define POUNDBANGLIMIT 64
+
+/**/
+static int
+zexecve(char *pth, char **argv, char **newenvp)
+{
+ int eno;
+ static char buf[PATH_MAX * 2+1];
+ char **eep;
+
+ unmetafy(pth, NULL);
+ for (eep = argv; *eep; eep++)
+ if (*eep != pth)
+ unmetafy(*eep, NULL);
+ buf[0] = '_';
+ buf[1] = '=';
+ if (*pth == '/')
+ strcpy(buf + 2, pth);
+ else
+ sprintf(buf + 2, "%s/%s", pwd, pth);
+ zputenv(buf);
+#ifndef FD_CLOEXEC
+ closedumps();
+#endif
+
+ if (newenvp == NULL)
+ newenvp = environ;
+ winch_unblock();
+ execve(pth, argv, newenvp);
+
+ /* 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 || eno == ENOENT) {
+ char execvebuf[POUNDBANGLIMIT + 1], *ptr, *ptr2, *argv0;
+ int fd, ct, t0;
+
+ if ((fd = open(pth, O_RDONLY|O_NOCTTY)) >= 0) {
+ argv0 = *argv;
+ *argv = pth;
+ execvebuf[0] = '\0';
+ 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')
+ break;
+ while (inblank(execvebuf[t0]))
+ execvebuf[t0--] = '\0';
+ execvebuf[POUNDBANGLIMIT] = '\0';
+ for (ptr = execvebuf + 2; *ptr && *ptr == ' '; ptr++);
+ for (ptr2 = ptr; *ptr && *ptr != ' '; ptr++);
+ if (eno == ENOENT) {
+ char *pprog;
+ if (*ptr)
+ *ptr = '\0';
+ if (*ptr2 != '/' &&
+ (pprog = pathprog(ptr2, NULL))) {
+ argv[-2] = ptr2;
+ argv[-1] = ptr + 1;
+ winch_unblock();
+ execve(pprog, argv - 2, newenvp);
+ }
+ zerr("%s: bad interpreter: %s: %e", pth, ptr2,
+ eno);
+ } else if (*ptr) {
+ *ptr = '\0';
+ argv[-2] = ptr2;
+ argv[-1] = ptr + 1;
+ winch_unblock();
+ execve(ptr2, argv - 2, newenvp);
+ } else {
+ argv[-1] = ptr2;
+ winch_unblock();
+ execve(ptr2, argv - 1, newenvp);
+ }
+ } else if (eno == ENOEXEC) {
+ argv[-1] = "sh";
+ winch_unblock();
+ execve("/bin/sh", argv - 1, newenvp);
+ }
+ } else if (eno == ENOEXEC) {
+ for (t0 = 0; t0 != ct; t0++)
+ if (!execvebuf[t0])
+ break;
+ if (t0 == ct) {
+ argv[-1] = "sh";
+ winch_unblock();
+ execve("/bin/sh", argv - 1, newenvp);
+ }
+ }
+ } 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);
+}
+
+/*
+ * Attempt to handle command not found.
+ * Return 0 if the condition was handled, non-zero otherwise.
+ */
+
+/**/
+static int
+commandnotfound(char *arg0, LinkList args)
+{
+ Shfunc shf = (Shfunc)
+ shfunctab->getnode(shfunctab, "command_not_found_handler");
+
+ if (!shf) {
+ lastval = 127;
+ return 1;
+ }
+
+ pushnode(args, arg0);
+ lastval = doshfunc(shf, args, 1);
+ return 0;
+}
+
+/*
+ * Search the default path for cmd.
+ * pbuf of length plen is the buffer to use.
+ * Return NULL if not found.
+ */
+
+static char *
+search_defpath(char *cmd, char *pbuf, int plen)
+{
+ char *ps = DEFAULT_PATH, *pe = NULL, *s;
+
+ for (ps = DEFAULT_PATH; ps; ps = pe ? pe+1 : NULL) {
+ pe = strchr(ps, ':');
+ if (*ps == '/') {
+ s = pbuf;
+ if (pe) {
+ if (pe - ps >= plen)
+ continue;
+ struncpy(&s, ps, pe-ps);
+ } else {
+ if (strlen(ps) >= plen)
+ continue;
+ strucpy(&s, ps);
+ }
+ *s++ = '/';
+ if ((s - pbuf) + strlen(cmd) >= plen)
+ continue;
+ strucpy(&s, cmd);
+ if (iscom(pbuf))
+ return pbuf;
+ }
+ }
+ return NULL;
+}
+
+/* execute an external command */
+
+/**/
+static void
+execute(LinkList args, int flags, int defpath)
+{
+ Cmdnam cn;
+ char buf[MAXCMDLEN+1], buf2[MAXCMDLEN+1];
+ char *s, *z, *arg0;
+ char **argv, **pp, **newenvp = NULL;
+ int eno = 0, ee;
+
+ arg0 = (char *) peekfirst(args);
+ if (isset(RESTRICTED) && (strchr(arg0, '/') || defpath)) {
+ zerr("%s: restricted", arg0);
+ _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 ((s = STTYval) && isatty(0) && (GETPGRP() == getpid())) {
+ char *t = tricat("stty", " ", s);
+
+ STTYval = 0; /* this prevents infinite recursion */
+ zsfree(s);
+ execstring(t, 1, 0, "stty");
+ zsfree(t);
+ } else if (s) {
+ STTYval = 0;
+ zsfree(s);
+ }
+
+ /* 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));
+ /*
+ * Note we don't do anything with the parameter structure
+ * for ARGV0: that's OK since we're about to exec or exit
+ * on failure.
+ */
+#ifdef USE_SET_UNSET_ENV
+ unsetenv("ARGV0");
+#else
+ delenvvalue(z - 6);
+#endif
+ } else if (flags & BINF_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);
+ if (flags & BINF_CLEARENV)
+ newenvp = blank_env;
+
+ /*
+ * Note that we don't close fd's attached to process substitution
+ * here, which should be visible to external processes.
+ */
+ closem(FDT_XTRACE, 0);
+#ifndef FD_CLOEXEC
+ if (SHTTY != -1) {
+ close(SHTTY);
+ SHTTY = -1;
+ }
+#endif
+ child_unblock();
+ if ((int) strlen(arg0) >= PATH_MAX) {
+ zerr("command too long: %s", arg0);
+ _exit(1);
+ }
+ for (s = arg0; *s; s++)
+ if (*s == '/') {
+ int lerrno = zexecve(arg0, argv, newenvp);
+ if (arg0 == s || unset(PATHDIRS) ||
+ (arg0[0] == '.' && (arg0 + 1 == s ||
+ (arg0[1] == '.' && arg0 + 2 == s)))) {
+ zerr("%e: %s", lerrno, arg0);
+ _exit((lerrno == EACCES || lerrno == ENOEXEC) ? 126 : 127);
+ }
+ break;
+ }
+
+ /* for command -p, search the default path */
+ if (defpath) {
+ char pbuf[PATH_MAX+1];
+ char *dptr;
+
+ if (!search_defpath(arg0, pbuf, PATH_MAX)) {
+ if (commandnotfound(arg0, args) == 0)
+ _exit(lastval);
+ zerr("command not found: %s", arg0);
+ _exit(127);
+ }
+
+ ee = zexecve(pbuf, argv, newenvp);
+
+ if ((dptr = strrchr(pbuf, '/')))
+ *dptr = '\0';
+ if (isgooderr(ee, *pbuf ? pbuf : "/"))
+ eno = ee;
+
+ } else {
+
+ if ((cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0))) {
+ char nn[PATH_MAX+1], *dptr;
+
+ if (cn->node.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, newenvp);
+ if (isgooderr(ee, *pp))
+ eno = ee;
+ } else if (**pp != '/') {
+ z = buf;
+ strucpy(&z, *pp);
+ *z++ = '/';
+ strcpy(z, arg0);
+ ee = zexecve(buf, argv, newenvp);
+ if (isgooderr(ee, *pp))
+ eno = ee;
+ }
+ strcpy(nn, cn->u.name ? *(cn->u.name) : "");
+ strcat(nn, "/");
+ strcat(nn, cn->node.nam);
+ }
+ ee = zexecve(nn, argv, newenvp);
+
+ 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, newenvp);
+ if (isgooderr(ee, *pp))
+ eno = ee;
+ } else {
+ z = buf;
+ strucpy(&z, *pp);
+ *z++ = '/';
+ strcpy(z, arg0);
+ ee = zexecve(buf, argv, newenvp);
+ if (isgooderr(ee, *pp))
+ eno = ee;
+ }
+ }
+
+ if (eno)
+ zerr("%e: %s", eno, arg0);
+ else if (commandnotfound(arg0, args) == 0)
+ _exit(lastval);
+ else
+ zerr("command not found: %s", arg0);
+ _exit((eno == EACCES || eno == ENOEXEC) ? 126 : 127);
+}
+
+#define RET_IF_COM(X) { if (iscom(X)) return docopy ? dupstring(X) : arg0; }
+
+/*
+ * Get the full pathname of an external command.
+ * If the second argument is zero, return the first argument if found;
+ * if non-zero, return the path using heap memory. (RET_IF_COM(X),
+ * above).
+ * If the third argument is non-zero, use the system default path
+ * instead of the current path.
+ */
+
+/**/
+mod_export char *
+findcmd(char *arg0, int docopy, int default_path)
+{
+ char **pp;
+ char *z, *s, buf[MAXCMDLEN];
+ Cmdnam cn;
+
+ if (default_path)
+ {
+ if (search_defpath(arg0, buf, MAXCMDLEN))
+ return docopy ? dupstring(buf) : arg0;
+ return NULL;
+ }
+ cn = (Cmdnam) cmdnamtab->getnode(cmdnamtab, arg0);
+ if (!cn && isset(HASHCMDS) && !isrelative(arg0))
+ cn = hashcmd(arg0, path);
+ if ((int) strlen(arg0) > PATH_MAX)
+ return NULL;
+ if ((s = strchr(arg0, '/'))) {
+ RET_IF_COM(arg0);
+ if (arg0 == s || unset(PATHDIRS) || !strncmp(arg0, "./", 2) ||
+ !strncmp(arg0, "../", 3)) {
+ return NULL;
+ }
+ }
+ if (cn) {
+ char nn[PATH_MAX+1];
+
+ if (cn->node.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);
+ RET_IF_COM(buf);
+ }
+ strcpy(nn, cn->u.name ? *(cn->u.name) : "");
+ strcat(nn, "/");
+ strcat(nn, cn->node.nam);
+ }
+ RET_IF_COM(nn);
+ }
+ for (pp = path; *pp; pp++) {
+ z = buf;
+ if (**pp) {
+ strucpy(&z, *pp);
+ *z++ = '/';
+ }
+ strcpy(z, arg0);
+ RET_IF_COM(buf);
+ }
+ return NULL;
+}
+
+/*
+ * Return TRUE if the given path denotes an executable regular file, or a
+ * symlink to one.
+ */
+
+/**/
+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];
+
+ if (cn->node.flags & HASHED)
+ strcpy(fullnam, cn->u.cmd);
+ else if (!cn->u.name)
+ return 0;
+ else {
+ strcpy(fullnam, *(cn->u.name));
+ strcat(fullnam, "/");
+ strcat(fullnam, cn->node.nam);
+ }
+ return iscom(fullnam);
+}
+
+/*
+ * Return TRUE if the given path contains a dot or dot-dot component
+ * and does not start with a slash.
+ */
+
+/**/
+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;
+}
+
+/**/
+mod_export Cmdnam
+hashcmd(char *arg0, char **pp)
+{
+ Cmdnam cn;
+ char *s, buf[PATH_MAX+1];
+ char **pq;
+
+ for (; *pp; pp++)
+ if (**pp == '/') {
+ s = buf;
+ struncpy(&s, *pp, PATH_MAX);
+ *s++ = '/';
+ if ((s - buf) + strlen(arg0) >= PATH_MAX)
+ continue;
+ strcpy(s, arg0);
+ if (iscom(buf))
+ break;
+ }
+
+ if (!*pp)
+ return NULL;
+
+ cn = (Cmdnam) zshcalloc(sizeof *cn);
+ cn->node.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;
+}
+
+/**/
+int
+forklevel;
+
+/* Arguments to entersubsh() */
+enum {
+ /* Subshell is to be run asynchronously (else synchronously) */
+ ESUB_ASYNC = 0x01,
+ /*
+ * Perform process group and tty handling and clear the
+ * (real) job table, since it won't be any longer valid
+ */
+ ESUB_PGRP = 0x02,
+ /* Don't unset traps */
+ ESUB_KEEPTRAP = 0x04,
+ /* This is only a fake entry to a subshell */
+ ESUB_FAKE = 0x08,
+ /* Release the process group if pid is the shell's process group */
+ ESUB_REVERTPGRP = 0x10,
+ /* Don't handle the MONITOR option even if previously set */
+ ESUB_NOMONITOR = 0x20,
+ /* This is a subshell where job control is allowed */
+ ESUB_JOB_CONTROL = 0x40
+};
+
+/**/
+static void
+entersubsh(int flags)
+{
+ int i, sig, monitor, job_control_ok;
+
+ if (!(flags & ESUB_KEEPTRAP))
+ for (sig = 0; sig < SIGCOUNT; sig++)
+ if (!(sigtrapped[sig] & ZSIG_FUNC))
+ unsettrap(sig);
+ monitor = isset(MONITOR);
+ job_control_ok = monitor && (flags & ESUB_JOB_CONTROL) && isset(POSIXJOBS);
+ if (flags & ESUB_NOMONITOR)
+ opts[MONITOR] = 0;
+ if (!isset(MONITOR)) {
+ if (flags & ESUB_ASYNC) {
+ settrap(SIGINT, NULL, 0);
+ settrap(SIGQUIT, NULL, 0);
+ if (isatty(0)) {
+ close(0);
+ if (open("/dev/null", O_RDWR | O_NOCTTY)) {
+ zerr("can't open /dev/null: %e", errno);
+ _exit(1);
+ }
+ }
+ }
+ } else if (thisjob != -1 && (flags & ESUB_PGRP)) {
+ if (jobtab[list_pipe_job].gleader && (list_pipe || list_pipe_child)) {
+ if (setpgrp(0L, jobtab[list_pipe_job].gleader) == -1 ||
+ killpg(jobtab[list_pipe_job].gleader, 0) == -1) {
+ jobtab[list_pipe_job].gleader =
+ jobtab[thisjob].gleader = (list_pipe_child ? mypgrp : getpid());
+ setpgrp(0L, jobtab[list_pipe_job].gleader);
+ if (!(flags & ESUB_ASYNC))
+ attachtty(jobtab[thisjob].gleader);
+ }
+ }
+ else if (!jobtab[thisjob].gleader ||
+ setpgrp(0L, jobtab[thisjob].gleader) == -1) {
+ /*
+ * This is the standard point at which a newly started
+ * process gets put into the foreground by taking over
+ * the terminal. Note that in normal circumstances we do
+ * this only from the process itself. This only works if
+ * we are still ignoring SIGTTOU at this point; in this
+ * case ignoring the signal has the special effect that
+ * the operation is allowed to work (in addition to not
+ * causing the shell to be suspended).
+ */
+ 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 (!(flags & ESUB_ASYNC))
+ attachtty(jobtab[thisjob].gleader);
+ }
+ }
+ if (!(flags & ESUB_FAKE))
+ subsh = 1;
+ /*
+ * Increment the visible parameter ZSH_SUBSHELL even if this
+ * is a fake subshell because we are exec'ing at the end.
+ * Logically this should be equivalent to a real subshell so
+ * we don't hang out the dirty washing.
+ */
+ zsh_subshell++;
+ if ((flags & ESUB_REVERTPGRP) && getpid() == mypgrp)
+ release_pgrp();
+ shout = NULL;
+ if (flags & ESUB_NOMONITOR) {
+ /*
+ * Allowing any form of interactive signalling here is
+ * actively harmful as we are in a context where there is no
+ * control over the process.
+ */
+ signal_ignore(SIGTTOU);
+ signal_ignore(SIGTTIN);
+ signal_ignore(SIGTSTP);
+ } else if (!job_control_ok) {
+ /*
+ * If this process is not going to be doing job control,
+ * we don't want to do special things with the corresponding
+ * signals. If it is, we need to keep the special behaviour:
+ * see note about attachtty() above.
+ */
+ signal_default(SIGTTOU);
+ signal_default(SIGTTIN);
+ signal_default(SIGTSTP);
+ }
+ if (interact) {
+ signal_default(SIGTERM);
+ if (!(sigtrapped[SIGINT] & ZSIG_IGNORED))
+ signal_default(SIGINT);
+ if (!(sigtrapped[SIGPIPE]))
+ signal_default(SIGPIPE);
+ }
+ if (!(sigtrapped[SIGQUIT] & ZSIG_IGNORED))
+ signal_default(SIGQUIT);
+ /*
+ * sigtrapped[sig] == ZSIG_IGNORED for signals that remain ignored,
+ * but other trapped signals are temporarily blocked when intrap,
+ * and must be unblocked before continuing into the subshell. This
+ * is orthogonal to what the default handler for the signal may be.
+ *
+ * Start loop at 1 because 0 is SIGEXIT
+ */
+ if (intrap)
+ for (sig = 1; sig < SIGCOUNT; sig++)
+ if (sigtrapped[sig] && sigtrapped[sig] != ZSIG_IGNORED)
+ signal_unblock(signal_mask(sig));
+ if (!job_control_ok)
+ opts[MONITOR] = 0;
+ opts[USEZLE] = 0;
+ zleactive = 0;
+ /*
+ * If we've saved fd's for later restoring, we're never going
+ * to restore them now, so just close them.
+ */
+ for (i = 10; i <= max_zsh_fd; i++) {
+ if (fdtable[i] & FDT_SAVED_MASK)
+ zclose(i);
+ }
+ if (flags & ESUB_PGRP)
+ clearjobtab(monitor);
+ get_usage();
+ forklevel = locallevel;
+}
+
+/* execute a string */
+
+/**/
+mod_export void
+execstring(char *s, int dont_change_job, int exiting, char *context)
+{
+ Eprog prog;
+
+ pushheap();
+ if (isset(VERBOSE)) {
+ zputs(s, stderr);
+ fputc('\n', stderr);
+ fflush(stderr);
+ }
+ if ((prog = parse_string(s, 0)))
+ execode(prog, dont_change_job, exiting, context);
+ popheap();
+}
+
+/**/
+mod_export void
+execode(Eprog p, int dont_change_job, int exiting, char *context)
+{
+ struct estate s;
+ static int zsh_eval_context_len;
+ int alen;
+
+ if (!zsh_eval_context_len) {
+ zsh_eval_context_len = 16;
+ alen = 0;
+ zsh_eval_context = (char **)zalloc(zsh_eval_context_len *
+ sizeof(*zsh_eval_context));
+ } else {
+ alen = arrlen(zsh_eval_context);
+ if (zsh_eval_context_len == alen + 1) {
+ zsh_eval_context_len *= 2;
+ zsh_eval_context = zrealloc(zsh_eval_context,
+ zsh_eval_context_len *
+ sizeof(*zsh_eval_context));
+ }
+ }
+ zsh_eval_context[alen] = context;
+ zsh_eval_context[alen+1] = NULL;
+
+ s.prog = p;
+ s.pc = p->prog;
+ s.strs = p->strs;
+ useeprog(p); /* Mark as in use */
+
+ execlist(&s, dont_change_job, exiting);
+
+ freeeprog(p); /* Free if now unused */
+
+ /*
+ * zsh_eval_context may have been altered by a recursive
+ * call, but that's OK since we're using the global value.
+ */
+ zsh_eval_context[alen] = NULL;
+}
+
+/* Execute a simplified command. This is used to execute things that
+ * will run completely in the shell, so that we can by-pass all that
+ * nasty job-handling and redirection stuff in execpline and execcmd. */
+
+/**/
+static int
+execsimple(Estate state)
+{
+ wordcode code = *state->pc++;
+ int lv, otj;
+
+ if (errflag)
+ return (lastval = 1);
+
+ if (!isset(EXECOPT))
+ return lastval = 0;
+
+ /* In evaluated traps, don't modify the line number. */
+ if (!IN_EVAL_TRAP() && !ineval && code)
+ lineno = code - 1;
+
+ code = wc_code(*state->pc++);
+
+ /*
+ * Because we're bypassing job control, ensure the called
+ * code doesn't see the current job.
+ */
+ otj = thisjob;
+ thisjob = -1;
+
+ if (code == WC_ASSIGN) {
+ cmdoutval = 0;
+ addvars(state, state->pc - 1, 0);
+ setunderscore("");
+ if (isset(XTRACE)) {
+ fputc('\n', xtrerr);
+ fflush(xtrerr);
+ }
+ lv = (errflag ? errflag : cmdoutval);
+ } else {
+ int q = queue_signal_level();
+ dont_queue_signals();
+ if (code == WC_FUNCDEF)
+ lv = execfuncdef(state, NULL);
+ else
+ lv = (execfuncs[code - WC_CURSH])(state, 0);
+ restore_queue_signals(q);
+ }
+
+ thisjob = otj;
+
+ return lastval = lv;
+}
+
+/* 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(Estate state, int dont_change_job, int exiting)
+{
+ static int donetrap;
+ Wordcode next;
+ wordcode code;
+ int ret, cj, csp, ltype;
+ int old_pline_level, old_list_pipe, old_list_pipe_job;
+ char *old_list_pipe_text;
+ zlong oldlineno;
+ /*
+ * ERREXIT only forces the shell to exit if the last command in a &&
+ * or || fails. This is the case even if an earlier command is a
+ * shell function or other current shell structure, so we have to set
+ * noerrexit here if the sublist is not of type END.
+ */
+ int oldnoerrexit = noerrexit;
+
+ queue_signals();
+
+ cj = thisjob;
+ old_pline_level = pline_level;
+ old_list_pipe = list_pipe;
+ old_list_pipe_job = list_pipe_job;
+ if (*list_pipe_text)
+ old_list_pipe_text = ztrdup(list_pipe_text);
+ else
+ old_list_pipe_text = NULL;
+ oldlineno = lineno;
+
+ if (sourcelevel && unset(SHINSTDIN)) {
+ pline_level = list_pipe = list_pipe_job = 0;
+ *list_pipe_text = '\0';
+ }
+
+ /* Loop over all sets of comands separated by newline, *
+ * semi-colon or ampersand (`sublists'). */
+ code = *state->pc++;
+ if (wc_code(code) != WC_LIST) {
+ /* Empty list; this returns status zero. */
+ lastval = 0;
+ }
+ while (wc_code(code) == WC_LIST && !breaks && !retflag && !errflag) {
+ int donedebug;
+ int this_donetrap = 0;
+ this_noerrexit = 0;
+
+ ltype = WC_LIST_TYPE(code);
+ csp = cmdsp;
+
+ if (!IN_EVAL_TRAP() && !ineval) {
+ /*
+ * Ensure we have a valid line number for debugging,
+ * unless we are in an evaluated trap in which case
+ * we retain the line number from the context.
+ * This was added for DEBUGBEFORECMD but I've made
+ * it unconditional to keep dependencies to a minimum.
+ *
+ * The line number is updated for individual pipelines.
+ * This isn't necessary for debug traps since they only
+ * run once per sublist.
+ */
+ wordcode code2 = *state->pc, lnp1 = 0;
+ if (ltype & Z_SIMPLE) {
+ lnp1 = code2;
+ } else if (wc_code(code2) == WC_SUBLIST) {
+ if (WC_SUBLIST_FLAGS(code2) == WC_SUBLIST_SIMPLE)
+ lnp1 = state->pc[1];
+ else
+ lnp1 = WC_PIPE_LINENO(state->pc[1]);
+ }
+ if (lnp1)
+ lineno = lnp1 - 1;
+ }
+
+ if (sigtrapped[SIGDEBUG] && isset(DEBUGBEFORECMD) && !intrap) {
+ Wordcode pc2 = state->pc;
+ int oerrexit_opt = opts[ERREXIT];
+ Param pm;
+ opts[ERREXIT] = 0;
+ noerrexit = NOERREXIT_EXIT | NOERREXIT_RETURN;
+ if (ltype & Z_SIMPLE) /* skip the line number */
+ pc2++;
+ pm = assignsparam("ZSH_DEBUG_CMD",
+ getpermtext(state->prog, pc2, 0),
+ 0);
+
+ exiting = donetrap;
+ ret = lastval;
+ dotrap(SIGDEBUG);
+ if (!retflag)
+ lastval = ret;
+ donetrap = exiting;
+ noerrexit = oldnoerrexit;
+ /*
+ * Only execute the trap once per sublist, even
+ * if the DEBUGBEFORECMD option changes.
+ */
+ donedebug = isset(ERREXIT) ? 2 : 1;
+ opts[ERREXIT] = oerrexit_opt;
+ if (pm)
+ unsetparam_pm(pm, 0, 1);
+ } else
+ donedebug = intrap ? 1 : 0;
+
+ /* Reset donetrap: this ensures that a trap is only *
+ * called once for each sublist that fails. */
+ donetrap = 0;
+ if (ltype & Z_SIMPLE) {
+ next = state->pc + WC_LIST_SKIP(code);
+ if (donedebug != 2)
+ execsimple(state);
+ state->pc = next;
+ goto sublist_done;
+ }
+
+ /* Loop through code followed by &&, ||, or end of sublist. */
+ code = *state->pc++;
+ if (donedebug == 2) {
+ /* Skip sublist. */
+ while (wc_code(code) == WC_SUBLIST) {
+ state->pc = state->pc + WC_SUBLIST_SKIP(code);
+ if (WC_SUBLIST_TYPE(code) == WC_SUBLIST_END)
+ break;
+ code = *state->pc++;
+ }
+ donetrap = 1;
+ /* yucky but consistent... */
+ goto sublist_done;
+ }
+ while (wc_code(code) == WC_SUBLIST) {
+ int isend = (WC_SUBLIST_TYPE(code) == WC_SUBLIST_END);
+ next = state->pc + WC_SUBLIST_SKIP(code);
+ if (!oldnoerrexit)
+ noerrexit = isend ? 0 : NOERREXIT_EXIT | NOERREXIT_RETURN;
+ if (WC_SUBLIST_FLAGS(code) & WC_SUBLIST_NOT) {
+ /* suppress errexit for "! this_command" */
+ if (isend)
+ this_noerrexit = 1;
+ /* suppress errexit for ! <list-of-shell-commands> */
+ noerrexit = NOERREXIT_EXIT | NOERREXIT_RETURN;
+ }
+ switch (WC_SUBLIST_TYPE(code)) {
+ case WC_SUBLIST_END:
+ /* End of sublist; just execute, ignoring status. */
+ if (WC_SUBLIST_FLAGS(code) & WC_SUBLIST_SIMPLE)
+ execsimple(state);
+ else
+ execpline(state, code, ltype, (ltype & Z_END) && exiting);
+ state->pc = next;
+ goto sublist_done;
+ break;
+ case WC_SUBLIST_AND:
+ /* If the return code is non-zero, we skip pipelines until *
+ * we find a sublist followed by ORNEXT. */
+ if ((ret = ((WC_SUBLIST_FLAGS(code) & WC_SUBLIST_SIMPLE) ?
+ execsimple(state) :
+ execpline(state, code, Z_SYNC, 0)))) {
+ state->pc = next;
+ code = *state->pc++;
+ next = state->pc + WC_SUBLIST_SKIP(code);
+ while (wc_code(code) == WC_SUBLIST &&
+ WC_SUBLIST_TYPE(code) == WC_SUBLIST_AND) {
+ state->pc = next;
+ code = *state->pc++;
+ next = state->pc + WC_SUBLIST_SKIP(code);
+ }
+ if (wc_code(code) != WC_SUBLIST) {
+ /* We've skipped to the end of the list, not executing *
+ * the final pipeline, so don't perform error handling *
+ * for this sublist. */
+ this_donetrap = 1;
+ goto sublist_done;
+ } else if (WC_SUBLIST_TYPE(code) == WC_SUBLIST_END) {
+ this_donetrap = 1;
+ /*
+ * Treat this in the same way as if we reached
+ * the end of the sublist normally.
+ */
+ state->pc = next;
+ goto sublist_done;
+ }
+ }
+ cmdpush(CS_CMDAND);
+ break;
+ case WC_SUBLIST_OR:
+ /* If the return code is zero, we skip pipelines until *
+ * we find a sublist followed by ANDNEXT. */
+ if (!(ret = ((WC_SUBLIST_FLAGS(code) & WC_SUBLIST_SIMPLE) ?
+ execsimple(state) :
+ execpline(state, code, Z_SYNC, 0)))) {
+ state->pc = next;
+ code = *state->pc++;
+ next = state->pc + WC_SUBLIST_SKIP(code);
+ while (wc_code(code) == WC_SUBLIST &&
+ WC_SUBLIST_TYPE(code) == WC_SUBLIST_OR) {
+ state->pc = next;
+ code = *state->pc++;
+ next = state->pc + WC_SUBLIST_SKIP(code);
+ }
+ if (wc_code(code) != WC_SUBLIST) {
+ /* We've skipped to the end of the list, not executing *
+ * the final pipeline, so don't perform error handling *
+ * for this sublist. */
+ this_donetrap = 1;
+ goto sublist_done;
+ } else if (WC_SUBLIST_TYPE(code) == WC_SUBLIST_END) {
+ this_donetrap = 1;
+ /*
+ * Treat this in the same way as if we reached
+ * the end of the sublist normally.
+ */
+ state->pc = next;
+ goto sublist_done;
+ }
+ }
+ cmdpush(CS_CMDOR);
+ break;
+ }
+ state->pc = next;
+ code = *state->pc++;
+ }
+ state->pc--;
+sublist_done:
+
+ /*
+ * See hairy code near the end of execif() for the
+ * following. "noerrexit " only applies until
+ * we hit execcmd on the way down. We're now
+ * on the way back up, so don't restore it.
+ */
+ if (!(oldnoerrexit & NOERREXIT_UNTIL_EXEC))
+ noerrexit = oldnoerrexit;
+
+ if (sigtrapped[SIGDEBUG] && !isset(DEBUGBEFORECMD) && !donedebug) {
+ /*
+ * Save and restore ERREXIT for consistency with
+ * DEBUGBEFORECMD, even though it's not used.
+ */
+ int oerrexit_opt = opts[ERREXIT];
+ opts[ERREXIT] = 0;
+ noerrexit = NOERREXIT_EXIT | NOERREXIT_RETURN;
+ exiting = donetrap;
+ ret = lastval;
+ dotrap(SIGDEBUG);
+ if (!retflag)
+ lastval = ret;
+ donetrap = exiting;
+ noerrexit = oldnoerrexit;
+ opts[ERREXIT] = oerrexit_opt;
+ }
+
+ cmdsp = csp;
+
+ /* Check whether we are suppressing traps/errexit *
+ * (typically in init scripts) and if we haven't *
+ * already performed them for this sublist. */
+ if (!this_noerrexit && !donetrap && !this_donetrap) {
+ if (sigtrapped[SIGZERR] && lastval &&
+ !(noerrexit & NOERREXIT_EXIT)) {
+ dotrap(SIGZERR);
+ donetrap = 1;
+ }
+ if (lastval) {
+ int errreturn = isset(ERRRETURN) &&
+ (isset(INTERACTIVE) || locallevel || sourcelevel) &&
+ !(noerrexit & NOERREXIT_RETURN);
+ int errexit = (isset(ERREXIT) ||
+ (isset(ERRRETURN) && !errreturn)) &&
+ !(noerrexit & NOERREXIT_EXIT);
+ if (errexit) {
+ if (sigtrapped[SIGEXIT])
+ dotrap(SIGEXIT);
+ if (mypid != getpid())
+ _exit(lastval);
+ else
+ exit(lastval);
+ }
+ if (errreturn) {
+ retflag = 1;
+ breaks = loops;
+ }
+ }
+ }
+ if (ltype & Z_END)
+ break;
+ code = *state->pc++;
+ }
+ pline_level = old_pline_level;
+ list_pipe = old_list_pipe;
+ list_pipe_job = old_list_pipe_job;
+ if (old_list_pipe_text) {
+ strcpy(list_pipe_text, old_list_pipe_text);
+ zsfree(old_list_pipe_text);
+ } else {
+ *list_pipe_text = '\0';
+ }
+ lineno = oldlineno;
+ if (dont_change_job)
+ thisjob = cj;
+
+ if (exiting && sigtrapped[SIGEXIT]) {
+ dotrap(SIGEXIT);
+ /* Make sure this doesn't get executed again. */
+ sigtrapped[SIGEXIT] = 0;
+ }
+
+ unqueue_signals();
+}
+
+/* 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(Estate state, wordcode slcode, int how, int last1)
+{
+ int ipipe[2], opipe[2];
+ int pj, newjob;
+ int old_simple_pline = simple_pline;
+ int slflags = WC_SUBLIST_FLAGS(slcode);
+ wordcode code = *state->pc++;
+ static int lastwj, lpforked;
+
+ if (wc_code(code) != WC_PIPE)
+ return lastval = (slflags & WC_SUBLIST_NOT) != 0;
+ else if (slflags & WC_SUBLIST_NOT)
+ last1 = 0;
+
+ /* If trap handlers are allowed to run here, they may start another
+ * external job in the middle of us starting this one, which can
+ * result in jobs being reaped before their job table entries have
+ * been initialized, which in turn leads to waiting forever for
+ * jobs that no longer exist. So don't do that.
+ */
+ queue_signals();
+
+ pj = thisjob;
+ ipipe[0] = ipipe[1] = opipe[0] = opipe[1] = 0;
+ child_block();
+
+ /*
+ * Get free entry in job table and initialize it. This is currently
+ * the only call to initjob() (apart from a minor exception in
+ * clearjobtab()), so this is also the only place where we can
+ * expand the job table under us.
+ */
+ if ((thisjob = newjob = initjob()) == -1) {
+ child_unblock();
+ unqueue_signals();
+ return 1;
+ }
+ if (how & Z_TIMED)
+ jobtab[thisjob].stat |= STAT_TIMED;
+
+ if (slflags & WC_SUBLIST_COPROC) {
+ how = Z_ASYNC;
+ if (coprocin >= 0) {
+ zclose(coprocin);
+ zclose(coprocout);
+ }
+ if (mpipe(ipipe) < 0) {
+ coprocin = coprocout = -1;
+ slflags &= ~WC_SUBLIST_COPROC;
+ } else if (mpipe(opipe) < 0) {
+ close(ipipe[0]);
+ close(ipipe[1]);
+ coprocin = coprocout = -1;
+ slflags &= ~WC_SUBLIST_COPROC;
+ } else {
+ coprocin = ipipe[0];
+ coprocout = opipe[1];
+ fdtable[coprocin] = fdtable[coprocout] = FDT_UNUSED;
+ }
+ }
+ /* This used to set list_pipe_pid=0 unconditionally, but in things
+ * like `ls|if true; then sleep 20; cat; fi' where the sleep was
+ * stopped, the top-level execpline() didn't get the pid for the
+ * sub-shell because it was overwritten. */
+ if (!pline_level++) {
+ list_pipe_pid = 0;
+ nowait = 0;
+ simple_pline = (WC_PIPE_TYPE(code) == WC_PIPE_END);
+ list_pipe_job = newjob;
+ }
+ lastwj = lpforked = 0;
+ execpline2(state, code, how, opipe[0], ipipe[1], last1);
+ pline_level--;
+ if (how & Z_ASYNC) {
+ lastwj = newjob;
+
+ if (thisjob == list_pipe_job)
+ list_pipe_job = 0;
+ jobtab[thisjob].stat |= STAT_NOSTTY;
+ if (slflags & WC_SUBLIST_COPROC) {
+ zclose(ipipe[1]);
+ zclose(opipe[0]);
+ }
+ if (how & Z_DISOWN) {
+ pipecleanfilelist(jobtab[thisjob].filelist, 0);
+ deletejob(jobtab + thisjob, 1);
+ thisjob = -1;
+ }
+ else
+ spawnjob();
+ child_unblock();
+ unqueue_signals();
+ /* Executing background code resets shell status */
+ return lastval = 0;
+ } else {
+ if (newjob != lastwj) {
+ Job jn = jobtab + newjob;
+ int updated;
+
+ if (newjob == list_pipe_job && list_pipe_child)
+ _exit(0);
+
+ lastwj = thisjob = newjob;
+
+ if (list_pipe || (pline_level && !(how & Z_TIMED)))
+ jn->stat |= STAT_NOPRINT;
+
+ if (nowait) {
+ if(!pline_level) {
+ int jobsub;
+ struct process *pn, *qn;
+
+ curjob = newjob;
+ DPUTS(!list_pipe_pid, "invalid list_pipe_pid");
+ addproc(list_pipe_pid, list_pipe_text, 0,
+ &list_pipe_start);
+
+ /* If the super-job contains only the sub-shell, the
+ sub-shell is the group leader. */
+ if (!jn->procs->next || lpforked == 2) {
+ jn->gleader = list_pipe_pid;
+ jn->stat |= STAT_SUBLEADER;
+ /*
+ * Pick up any subjob that's still lying around
+ * as it's now our responsibility.
+ * If we find it we're a SUPERJOB.
+ */
+ for (jobsub = 1; jobsub <= maxjob; jobsub++) {
+ Job jnsub = jobtab + jobsub;
+ if (jnsub->stat & STAT_SUBJOB_ORPHANED) {
+ jn->other = jobsub;
+ jn->stat |= STAT_SUPERJOB;
+ jnsub->stat &= ~STAT_SUBJOB_ORPHANED;
+ jnsub->other = list_pipe_pid;
+ }
+ }
+ }
+ 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 | STAT_LOCKED |
+ STAT_INUSE;
+ printjob(jn, !!isset(LONGLISTJOBS), 1);
+ }
+ else if (newjob != list_pipe_job)
+ deletejob(jn, 0);
+ else
+ lastwj = -1;
+ }
+
+ errbrk_saved = 0;
+ for (; !nowait;) {
+ if (list_pipe_child) {
+ jn->stat |= STAT_NOPRINT;
+ makerunning(jn);
+ }
+ if (!(jn->stat & STAT_LOCKED)) {
+ updated = hasprocs(thisjob);
+ waitjobs(); /* deals with signal queue */
+ child_block();
+ } else
+ updated = 0;
+ if (!updated &&
+ list_pipe_job && hasprocs(list_pipe_job) &&
+ !(jobtab[list_pipe_job].stat & STAT_STOPPED)) {
+ int q = queue_signal_level();
+ child_unblock();
+ child_block();
+ dont_queue_signals();
+ restore_queue_signals(q);
+ }
+ if (list_pipe_child &&
+ jn->stat & STAT_DONE &&
+ lastval2 & 0200)
+ killpg(mypgrp, lastval2 & ~0200);
+ if (!list_pipe_child && !lpforked && !subsh && jobbing &&
+ (list_pipe || last1 || pline_level) &&
+ ((jn->stat & STAT_STOPPED) ||
+ (list_pipe_job && pline_level &&
+ (jobtab[list_pipe_job].stat & STAT_STOPPED)))) {
+ pid_t pid = 0;
+ int synch[2];
+ struct timeval bgtime;
+
+ /*
+ * A pipeline with the shell handling the right
+ * hand side was stopped. We'll fork to allow
+ * it to continue.
+ */
+ if (pipe(synch) < 0 || (pid = zfork(&bgtime)) == -1) {
+ /* Failure */
+ if (pid < 0) {
+ close(synch[0]);
+ close(synch[1]);
+ } else
+ zerr("pipe failed: %e", errno);
+ zleentry(ZLE_CMD_TRASH);
+ fprintf(stderr, "zsh: job can't be suspended\n");
+ fflush(stderr);
+ makerunning(jn);
+ killjb(jn, SIGCONT);
+ thisjob = newjob;
+ }
+ else if (pid) {
+ /*
+ * Parent: job control is here. If the job
+ * started for the RHS of the pipeline is still
+ * around, then its a SUBJOB and the job for
+ * earlier parts of the pipeeline is its SUPERJOB.
+ * The newly forked shell isn't recorded as a
+ * separate job here, just as list_pipe_pid.
+ * If the superjob exits (it may already have
+ * done so, see child branch below), we'll use
+ * list_pipe_pid to form the basis of a
+ * replacement job --- see SUBLEADER code above.
+ */
+ char dummy;
+
+ lpforked =
+ (killpg(jobtab[list_pipe_job].gleader, 0) == -1 ? 2 : 1);
+ list_pipe_pid = pid;
+ list_pipe_start = bgtime;
+ nowait = 1;
+ errflag |= ERRFLAG_ERROR;
+ breaks = loops;
+ close(synch[1]);
+ read_loop(synch[0], &dummy, 1);
+ close(synch[0]);
+ /* If this job has finished, we leave it as a
+ * normal (non-super-) job. */
+ if (!(jn->stat & STAT_DONE)) {
+ jobtab[list_pipe_job].other = newjob;
+ jobtab[list_pipe_job].stat |= STAT_SUPERJOB;
+ jn->stat |= STAT_SUBJOB | STAT_NOPRINT;
+ jn->other = list_pipe_pid; /* see zsh.h */
+ if (hasprocs(list_pipe_job))
+ jn->gleader = jobtab[list_pipe_job].gleader;
+ }
+ if ((list_pipe || last1) && hasprocs(list_pipe_job))
+ killpg(jobtab[list_pipe_job].gleader, SIGSTOP);
+ break;
+ }
+ else {
+ close(synch[0]);
+ entersubsh(ESUB_ASYNC);
+ /*
+ * At this point, we used to attach this process
+ * to the process group of list_pipe_job (the
+ * new superjob) any time that was still available.
+ * That caused problems in at least two
+ * cases because this forked shell was then
+ * suspended with the right hand side of the
+ * pipeline, and the SIGSTOP below suspended
+ * it a second time when it was continued.
+ *
+ * It's therefore not clear entirely why you'd ever
+ * do anything other than the following, but no
+ * doubt we'll find out...
+ */
+ setpgrp(0L, mypgrp = getpid());
+ close(synch[1]);
+ kill(getpid(), SIGSTOP);
+ list_pipe = 0;
+ list_pipe_child = 1;
+ opts[INTERACTIVE] = 0;
+ if (errbrk_saved) {
+ /*
+ * Keep any user interrupt bit in errflag.
+ */
+ errflag = prev_errflag | (errflag & ERRFLAG_INT);
+ breaks = prev_breaks;
+ }
+ break;
+ }
+ }
+ else if (subsh && jn->stat & STAT_STOPPED)
+ thisjob = newjob;
+ else
+ break;
+ }
+ child_unblock();
+ unqueue_signals();
+
+ if (list_pipe && (lastval & 0200) && pj >= 0 &&
+ (!(jn->stat & STAT_INUSE) || (jn->stat & STAT_DONE))) {
+ deletejob(jn, 0);
+ jn = jobtab + pj;
+ if (jn->gleader)
+ killjb(jn, lastval & ~0200);
+ }
+ if (list_pipe_child ||
+ ((jn->stat & STAT_DONE) &&
+ (list_pipe || (pline_level && !(jn->stat & STAT_SUBJOB)))))
+ deletejob(jn, 0);
+ thisjob = pj;
+ }
+ else
+ unqueue_signals();
+ if ((slflags & WC_SUBLIST_NOT) && !errflag)
+ lastval = !lastval;
+ }
+ if (!pline_level)
+ simple_pline = old_simple_pline;
+ return lastval;
+}
+
+/* execute pipeline. This function assumes the `pline' is not NULL. */
+
+/**/
+static void
+execpline2(Estate state, wordcode pcode,
+ int how, int input, int output, int last1)
+{
+ struct execcmd_params eparams;
+
+ if (breaks || retflag)
+ return;
+
+ /* In evaluated traps, don't modify the line number. */
+ if (!IN_EVAL_TRAP() && !ineval && WC_PIPE_LINENO(pcode))
+ lineno = WC_PIPE_LINENO(pcode) - 1;
+
+ if (pline_level == 1) {
+ if ((how & Z_ASYNC) || !sfcontext)
+ strcpy(list_pipe_text,
+ getjobtext(state->prog,
+ state->pc + (WC_PIPE_TYPE(pcode) == WC_PIPE_END ?
+ 0 : 1)));
+ else
+ list_pipe_text[0] = '\0';
+ }
+ if (WC_PIPE_TYPE(pcode) == WC_PIPE_END) {
+ execcmd_analyse(state, &eparams);
+ execcmd_exec(state, &eparams, input, output, how, last1 ? 1 : 2, -1);
+ } else {
+ int pipes[2];
+ int old_list_pipe = list_pipe;
+ Wordcode next = state->pc + (*state->pc);
+
+ ++state->pc;
+ execcmd_analyse(state, &eparams);
+
+ if (mpipe(pipes) < 0) {
+ /* FIXME */
+ }
+
+ addfilelist(NULL, pipes[0]);
+ execcmd_exec(state, &eparams, input, pipes[1], how, 0, pipes[0]);
+ zclose(pipes[1]);
+ state->pc = next;
+
+ /* if another execpline() is invoked because the command is *
+ * a list it must know that we're already in a pipeline */
+ cmdpush(CS_PIPE);
+ list_pipe = 1;
+ execpline2(state, *state->pc++, how, pipes[0], output, last1);
+ list_pipe = old_list_pipe;
+ cmdpop();
+ }
+}
+
+/* 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 **) hcalloc((countlinknodes(list) + 4) *
+ sizeof(char *));
+
+ if (isset(XTRACE)) {
+ if (!doneps4)
+ printprompt4();
+
+ for (node = firstnode(list); node; incnode(node)) {
+ *ptr++ = (char *)getdata(node);
+ quotedzputs(getdata(node), xtrerr);
+ if (nextnode(node))
+ fputc(' ', xtrerr);
+ }
+ fputc('\n', xtrerr);
+ fflush(xtrerr);
+ } else {
+ for (node = firstnode(list); node; incnode(node))
+ *ptr++ = (char *)getdata(node);
+ }
+ *ptr = NULL;
+ return (argv);
+}
+
+/**/
+mod_export void
+untokenize(char *s)
+{
+ if (*s) {
+ int c;
+
+ while ((c = *s++))
+ if (itok(c)) {
+ char *p = s - 1;
+
+ if (c != Nularg)
+ *p++ = ztokens[c - Pound];
+
+ while ((c = *s++)) {
+ if (itok(c)) {
+ if (c != Nularg)
+ *p++ = ztokens[c - Pound];
+ } else
+ *p++ = c;
+ }
+ *p = '\0';
+ break;
+ }
+ }
+}
+
+
+/*
+ * Given a tokenized string, output it to standard output in
+ * such a way that it's clear which tokens are active.
+ * Hence Star becomes an unquoted "*", while a "*" becomes "\*".
+ *
+ * The code here is a kind of amalgamation of the tests in
+ * zshtokenize() and untokenize() with some outputting.
+ */
+
+/**/
+void
+quote_tokenized_output(char *str, FILE *file)
+{
+ char *s = str;
+
+ for (; *s; s++) {
+ switch (*s) {
+ case Meta:
+ putc(*++s ^ 32, file);
+ continue;
+
+ case Nularg:
+ /* Do nothing. I think. */
+ continue;
+
+ case '\\':
+ case '<':
+ case '>':
+ case '(':
+ case '|':
+ case ')':
+ case '^':
+ case '#':
+ case '~':
+ case '[':
+ case ']':
+ case '*':
+ case '?':
+ case '$':
+ case ' ':
+ putc('\\', file);
+ break;
+
+ case '\t':
+ fputs("$'\\t'", file);
+ continue;
+
+ case '\n':
+ fputs("$'\\n'", file);
+ continue;
+
+ case '\r':
+ fputs("$'\\r'", file);
+ continue;
+
+ case '=':
+ if (s == str)
+ putc('\\', file);
+ break;
+
+ default:
+ if (itok(*s)) {
+ putc(ztokens[*s - Pound], file);
+ continue;
+ }
+ break;
+ }
+
+ putc(*s, file);
+ }
+}
+
+/* Check that we can use a parameter for allocating a file descriptor. */
+
+static int
+checkclobberparam(struct redir *f)
+{
+ struct value vbuf;
+ Value v;
+ char *s = f->varid;
+ int fd;
+
+ if (!s)
+ return 1;
+
+ if (!(v = getvalue(&vbuf, &s, 0)))
+ return 1;
+
+ if (v->pm->node.flags & PM_READONLY) {
+ zwarn("can't allocate file descriptor to readonly parameter %s",
+ f->varid);
+ /* don't flag a system error for this */
+ errno = 0;
+ return 0;
+ }
+
+ /*
+ * We can't clobber the value in the parameter if it's
+ * already an opened file descriptor --- that means it's a decimal
+ * integer corresponding to an opened file descriptor,
+ * not merely an expression that evaluates to a file descriptor.
+ */
+ if (!isset(CLOBBER) && (s = getstrvalue(v)) &&
+ (fd = (int)zstrtol(s, &s, 10)) >= 0 && !*s &&
+ fd <= max_zsh_fd && fdtable[fd] == FDT_EXTERNAL) {
+ zwarn("can't clobber parameter %s containing file descriptor %d",
+ f->varid, fd);
+ /* don't flag a system error for this */
+ errno = 0;
+ return 0;
+ }
+ return 1;
+}
+
+/* Open a file for writing redirection */
+
+/**/
+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, int type)
+{
+ if (fd >= 0 && mfds[fd] && mfds[fd]->ct >= 2) {
+ struct multio *mn = mfds[fd];
+ char buf[TCBUFSIZE];
+ int len, i;
+ pid_t pid;
+ struct timeval bgtime;
+
+ /*
+ * We need to block SIGCHLD in case the process
+ * we are spawning terminates before the job table
+ * is set up to handle it.
+ */
+ child_block();
+ if ((pid = zfork(&bgtime))) {
+ for (i = 0; i < mn->ct; i++)
+ zclose(mn->fds[i]);
+ zclose(mn->pipe);
+ if (pid == -1) {
+ mfds[fd] = NULL;
+ child_unblock();
+ return;
+ }
+ mn->ct = 1;
+ mn->fds[0] = fd;
+ addproc(pid, NULL, 1, &bgtime);
+ child_unblock();
+ return;
+ }
+ /* pid == 0 */
+ child_unblock();
+ closeallelse(mn);
+ if (mn->rflag) {
+ /* tee process */
+ while ((len = read(mn->pipe, buf, TCBUFSIZE)) != 0) {
+ if (len < 0) {
+ if (errno == EINTR)
+ continue;
+ else
+ break;
+ }
+ for (i = 0; i < mn->ct; i++)
+ write_loop(mn->fds[i], buf, len);
+ }
+ } else {
+ /* cat process */
+ for (i = 0; i < mn->ct; i++)
+ while ((len = read(mn->fds[i], buf, TCBUFSIZE)) != 0) {
+ if (len < 0) {
+ if (errno == EINTR)
+ continue;
+ else
+ break;
+ }
+ write_loop(mn->pipe, buf, len);
+ }
+ }
+ _exit(0);
+ } else if (fd >= 0 && type == REDIR_CLOSE)
+ mfds[fd] = NULL;
+}
+
+/* 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;
+ long openmax;
+
+ openmax = fdtable_size;
+
+ for (i = 0; i < openmax; 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.
+ *
+ * If varid is not NULL, we open an fd above 10 and set the parameter
+ * named varid to that value. fd1 is not used.
+ */
+
+/**/
+static void
+addfd(int forked, int *save, struct multio **mfds, int fd1, int fd2, int rflag,
+ char *varid)
+{
+ int pipes[2];
+
+ if (varid) {
+ /* fd will be over 10, don't touch mfds */
+ fd1 = movefd(fd2);
+ if (fd1 == -1) {
+ zerr("cannot moved fd %d: %e", fd2, errno);
+ return;
+ } else {
+ fdtable[fd1] = FDT_EXTERNAL;
+ setiparam(varid, (zlong)fd1);
+ /*
+ * If setting the parameter failed, close the fd else
+ * it will leak.
+ */
+ if (errflag)
+ zclose(fd1);
+ }
+ } else if (!mfds[fd1] || unset(MULTIOS)) {
+ if(!mfds[fd1]) { /* starting a new multio */
+ mfds[fd1] = (struct multio *) zhalloc(sizeof(struct multio));
+ if (!forked && save[fd1] == -2) {
+ if (fd1 == fd2)
+ save[fd1] = -1;
+ else {
+ int fdN = movefd(fd1);
+ /*
+ * fd1 may already be closed here, so
+ * ignore bad file descriptor error
+ */
+ if (fdN < 0) {
+ if (errno != EBADF) {
+ zerr("cannot duplicate fd %d: %e", fd1, errno);
+ mfds[fd1] = NULL;
+ closemnodes(mfds);
+ return;
+ }
+ } else {
+ DPUTS(fdtable[fdN] != FDT_INTERNAL,
+ "Saved file descriptor not marked as internal");
+ fdtable[fdN] |= FDT_SAVED_MASK;
+ }
+ save[fd1] = fdN;
+ }
+ }
+ }
+ if (!varid)
+ 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", fd1);
+ closemnodes(mfds);
+ return;
+ }
+ if (mfds[fd1]->ct == 1) { /* split the stream */
+ int fdN = movefd(fd1);
+ if (fdN < 0) {
+ zerr("multio failed for fd %d: %e", fd1, errno);
+ closemnodes(mfds);
+ return;
+ }
+ mfds[fd1]->fds[0] = fdN;
+ fdN = movefd(fd2);
+ if (fdN < 0) {
+ zerr("multio failed for fd %d: %e", fd2, errno);
+ closemnodes(mfds);
+ return;
+ }
+ mfds[fd1]->fds[1] = fdN;
+ if (mpipe(pipes) < 0) {
+ zerr("multio failed for fd %d: %e", fd2, errno);
+ closemnodes(mfds);
+ return;
+ }
+ mfds[fd1]->pipe = pipes[1 - rflag];
+ redup(pipes[rflag], fd1);
+ mfds[fd1]->ct = 2;
+ } else { /* add another fd to an already split stream */
+ int fdN;
+ 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);
+ }
+ if ((fdN = movefd(fd2)) < 0) {
+ zerr("multio failed for fd %d: %e", fd2, errno);
+ closemnodes(mfds);
+ return;
+ }
+ mfds[fd1]->fds[mfds[fd1]->ct++] = fdN;
+ }
+ }
+}
+
+/**/
+static void
+addvars(Estate state, Wordcode pc, int addflags)
+{
+ LinkList vl;
+ int xtr, isstr, htok = 0;
+ char **arr, **ptr, *name;
+ int flags;
+
+ Wordcode opc = state->pc;
+ wordcode ac;
+ local_list1(svl);
+
+ /*
+ * Warn when creating a global without using typeset -g in a
+ * function. Don't do this if there is a list of variables marked
+ * to be restored after the command, since then the assignment
+ * is implicitly scoped.
+ */
+ flags = !(addflags & ADDVAR_RESTORE) ? ASSPM_WARN : 0;
+ xtr = isset(XTRACE);
+ if (xtr) {
+ printprompt4();
+ doneps4 = 1;
+ }
+ state->pc = pc;
+ while (wc_code(ac = *state->pc++) == WC_ASSIGN) {
+ int myflags = flags;
+ name = ecgetstr(state, EC_DUPTOK, &htok);
+ if (htok)
+ untokenize(name);
+ if (WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC)
+ myflags |= ASSPM_AUGMENT;
+ if (xtr)
+ fprintf(xtrerr,
+ WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC ? "%s+=" : "%s=", name);
+ if ((isstr = (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR))) {
+ init_list1(svl, ecgetstr(state, EC_DUPTOK, &htok));
+ vl = &svl;
+ } else {
+ vl = ecgetlist(state, WC_ASSIGN_NUM(ac), EC_DUPTOK, &htok);
+ if (errflag) {
+ state->pc = opc;
+ return;
+ }
+ }
+
+ if (vl && htok) {
+ int prefork_ret = 0;
+ prefork(vl, (isstr ? (PREFORK_SINGLE|PREFORK_ASSIGN) :
+ PREFORK_ASSIGN), &prefork_ret);
+ if (errflag) {
+ state->pc = opc;
+ return;
+ }
+ if (prefork_ret & PREFORK_KEY_VALUE)
+ myflags |= ASSPM_KEY_VALUE;
+ if (!isstr || (isset(GLOBASSIGN) && isstr &&
+ haswilds((char *)getdata(firstnode(vl))))) {
+ globlist(vl, prefork_ret);
+ /* Unset the parameter to force it to be recreated
+ * as either scalar or array depending on how many
+ * matches were found for the glob.
+ */
+ if (isset(GLOBASSIGN) && isstr)
+ unsetparam(name);
+ if (errflag) {
+ state->pc = opc;
+ return;
+ }
+ }
+ }
+ if (isstr && (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) {
+ quotedzputs(val, xtrerr);
+ fputc(' ', xtrerr);
+ }
+ if ((addflags & ADDVAR_EXPORT) && !strchr(name, '[')) {
+ if ((addflags & ADDVAR_RESTRICT) && isset(RESTRICTED) &&
+ (pm = (Param) paramtab->removenode(paramtab, name)) &&
+ (pm->node.flags & PM_RESTRICTED)) {
+ zerr("%s: restricted", pm->node.nam);
+ zsfree(val);
+ state->pc = opc;
+ return;
+ }
+ if (strcmp(name, "STTY") == 0) {
+ zsfree(STTYval);
+ STTYval = ztrdup(val);
+ }
+ allexp = opts[ALLEXPORT];
+ opts[ALLEXPORT] = 1;
+ if (isset(KSHARRAYS))
+ unsetparam(name);
+ pm = assignsparam(name, val, myflags);
+ opts[ALLEXPORT] = allexp;
+ } else
+ pm = assignsparam(name, val, myflags);
+ if (errflag) {
+ state->pc = opc;
+ return;
+ }
+ continue;
+ }
+ if (vl) {
+ ptr = arr = (char **) zalloc(sizeof(char *) *
+ (countlinknodes(vl) + 1));
+
+ while (nonempty(vl))
+ *ptr++ = ztrdup((char *) ugetnode(vl));
+ } else
+ ptr = arr = (char **) zalloc(sizeof(char *));
+
+ *ptr = NULL;
+ if (xtr) {
+ fprintf(xtrerr, "( ");
+ for (ptr = arr; *ptr; ptr++) {
+ quotedzputs(*ptr, xtrerr);
+ fputc(' ', xtrerr);
+ }
+ fprintf(xtrerr, ") ");
+ }
+ assignaparam(name, arr, myflags);
+ if (errflag) {
+ state->pc = opc;
+ return;
+ }
+ }
+ state->pc = opc;
+}
+
+/**/
+void
+setunderscore(char *str)
+{
+ queue_signals();
+ if (str && *str) {
+ int l = strlen(str) + 1, nl = (l + 31) & ~31;
+
+ if (nl > underscorelen || (underscorelen - nl) > 64) {
+ zfree(zunderscore, underscorelen);
+ zunderscore = (char *) zalloc(underscorelen = nl);
+ }
+ strcpy(zunderscore, str);
+ underscoreused = l;
+ } else {
+ if (underscorelen > 128) {
+ zfree(zunderscore, underscorelen);
+ zunderscore = (char *) zalloc(underscorelen = 32);
+ }
+ *zunderscore = '\0';
+ underscoreused = 1;
+ }
+ unqueue_signals();
+}
+
+/* These describe the type of expansions that need to be done on the words
+ * used in the thing we are about to execute. They are set in execcmd() and
+ * used in execsubst() which might be called from one of the functions
+ * called from execcmd() (like execfor() and so on). */
+
+static int esprefork, esglob = 1;
+
+/**/
+void
+execsubst(LinkList strs)
+{
+ if (strs) {
+ prefork(strs, esprefork, NULL);
+ if (esglob && !errflag) {
+ LinkList ostrs = strs;
+ globlist(strs, 0);
+ strs = ostrs;
+ }
+ }
+}
+
+/*
+ * Check if a builtin requires an autoload and if so
+ * deal with it. This may return NULL.
+ */
+
+/**/
+static HashNode
+resolvebuiltin(const char *cmdarg, HashNode hn)
+{
+ if (!((Builtin) hn)->handlerfunc) {
+ char *modname = dupstring(((Builtin) hn)->optstr);
+ /*
+ * Ensure the module is loaded and the
+ * feature corresponding to the builtin
+ * is enabled.
+ */
+ (void)ensurefeature(modname, "b:",
+ (hn->flags & BINF_AUTOALL) ? NULL :
+ hn->nam);
+ hn = builtintab->getnode(builtintab, cmdarg);
+ if (!hn) {
+ lastval = 1;
+ zerr("autoloading module %s failed to define builtin: %s",
+ modname, cmdarg);
+ return NULL;
+ }
+ }
+ return hn;
+}
+
+/*
+ * We are about to execute a command at the lowest level of the
+ * hierarchy. Analyse the parameters from the wordcode.
+ */
+
+/**/
+static void
+execcmd_analyse(Estate state, Execcmd_params eparams)
+{
+ wordcode code;
+ int i;
+
+ eparams->beg = state->pc;
+ eparams->redir =
+ (wc_code(*state->pc) == WC_REDIR ? ecgetredirs(state) : NULL);
+ if (wc_code(*state->pc) == WC_ASSIGN) {
+ cmdoutval = 0;
+ eparams->varspc = state->pc;
+ while (wc_code((code = *state->pc)) == WC_ASSIGN)
+ state->pc += (WC_ASSIGN_TYPE(code) == WC_ASSIGN_SCALAR ?
+ 3 : WC_ASSIGN_NUM(code) + 2);
+ } else
+ eparams->varspc = NULL;
+
+ code = *state->pc++;
+
+ eparams->type = wc_code(code);
+ eparams->postassigns = 0;
+
+ /* It would be nice if we could use EC_DUPTOK instead of EC_DUP here.
+ * But for that we would need to check/change all builtins so that
+ * they don't modify their argument strings. */
+ switch (eparams->type) {
+ case WC_SIMPLE:
+ eparams->args = ecgetlist(state, WC_SIMPLE_ARGC(code), EC_DUP,
+ &eparams->htok);
+ eparams->assignspc = NULL;
+ break;
+
+ case WC_TYPESET:
+ eparams->args = ecgetlist(state, WC_TYPESET_ARGC(code), EC_DUP,
+ &eparams->htok);
+ eparams->postassigns = *state->pc++;
+ eparams->assignspc = state->pc;
+ for (i = 0; i < eparams->postassigns; i++) {
+ code = *state->pc;
+ DPUTS(wc_code(code) != WC_ASSIGN,
+ "BUG: miscounted typeset assignments");
+ state->pc += (WC_ASSIGN_TYPE(code) == WC_ASSIGN_SCALAR ?
+ 3 : WC_ASSIGN_NUM(code) + 2);
+ }
+ break;
+
+ default:
+ eparams->args = NULL;
+ eparams->assignspc = NULL;
+ eparams->htok = 0;
+ break;
+ }
+}
+
+/*
+ * Transfer the first node of args to preargs, performing
+ * prefork expansion on the way if necessary.
+ */
+static void execcmd_getargs(LinkList preargs, LinkList args, int expand)
+{
+ if (!firstnode(args)) {
+ return;
+ } else if (expand) {
+ local_list0(svl);
+ init_list0(svl);
+ /* not init_list1, as we need real nodes */
+ addlinknode(&svl, uremnode(args, firstnode(args)));
+ /* Analysing commands, so vanilla options to prefork */
+ prefork(&svl, 0, NULL);
+ joinlists(preargs, &svl);
+ } else {
+ addlinknode(preargs, uremnode(args, firstnode(args)));
+ }
+}
+
+/**/
+static int
+execcmd_fork(Estate state, int how, int type, Wordcode varspc,
+ LinkList *filelistp, char *text, int oautocont,
+ int close_if_forked)
+{
+ pid_t pid;
+ int synch[2], flags;
+ char dummy;
+ struct timeval bgtime;
+
+ child_block();
+
+ if (pipe(synch) < 0) {
+ zerr("pipe failed: %e", errno);
+ return -1;
+ } else if ((pid = zfork(&bgtime)) == -1) {
+ close(synch[0]);
+ close(synch[1]);
+ lastval = 1;
+ errflag |= ERRFLAG_ERROR;
+ return -1;
+ }
+ if (pid) {
+ close(synch[1]);
+ read_loop(synch[0], &dummy, 1);
+ close(synch[0]);
+ if (how & Z_ASYNC) {
+ lastpid = (zlong) pid;
+ } else if (!jobtab[thisjob].stty_in_env && varspc) {
+ /* search for STTY=... */
+ Wordcode p = varspc;
+ wordcode ac;
+
+ while (wc_code(ac = *p) == WC_ASSIGN) {
+ if (!strcmp(ecrawstr(state->prog, p + 1, NULL), "STTY")) {
+ jobtab[thisjob].stty_in_env = 1;
+ break;
+ }
+ p += (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR ?
+ 3 : WC_ASSIGN_NUM(ac) + 2);
+ }
+ }
+ addproc(pid, text, 0, &bgtime);
+ if (oautocont >= 0)
+ opts[AUTOCONTINUE] = oautocont;
+ pipecleanfilelist(jobtab[thisjob].filelist, 1);
+ return pid;
+ }
+
+ /* pid == 0 */
+ close(synch[0]);
+ flags = ((how & Z_ASYNC) ? ESUB_ASYNC : 0) | ESUB_PGRP;
+ if ((type != WC_SUBSH) && !(how & Z_ASYNC))
+ flags |= ESUB_KEEPTRAP;
+ if (type == WC_SUBSH && !(how & Z_ASYNC))
+ flags |= ESUB_JOB_CONTROL;
+ *filelistp = jobtab[thisjob].filelist;
+ entersubsh(flags);
+ close(synch[1]);
+ zclose(close_if_forked);
+
+ if (sigtrapped[SIGINT] & ZSIG_IGNORED)
+ holdintr();
+ /*
+ * EXIT traps shouldn't be called even if we forked to run
+ * shell code as this isn't the main shell.
+ */
+ sigtrapped[SIGEXIT] = 0;
+#ifdef HAVE_NICE
+ /* Check if we should run background jobs at a lower priority. */
+ if ((how & Z_ASYNC) && isset(BGNICE))
+ if (nice(5) < 0)
+ zwarn("nice(5) failed: %e", errno);
+#endif /* HAVE_NICE */
+
+ return 0;
+}
+
+/*
+ * Execute a command at the lowest level of the hierarchy.
+ */
+
+/**/
+static void
+execcmd_exec(Estate state, Execcmd_params eparams,
+ int input, int output, int how, int last1, int close_if_forked)
+{
+ HashNode hn = NULL;
+ LinkList filelist = NULL;
+ LinkNode node;
+ Redir fn;
+ struct multio *mfds[10];
+ char *text;
+ int save[10];
+ int fil, dfil, is_cursh, do_exec = 0, redir_err = 0, i;
+ int nullexec = 0, magic_assign = 0, forked = 0, old_lastval;
+ int is_shfunc = 0, is_builtin = 0, is_exec = 0, use_defpath = 0;
+ /* Various flags to the command. */
+ int cflags = 0, orig_cflags = 0, checked = 0, oautocont = -1;
+ FILE *oxtrerr = xtrerr, *newxtrerr = NULL;
+ /*
+ * Retrieve parameters for quick reference (they are unique
+ * to us so we can modify the structure if we want).
+ */
+ LinkList args = eparams->args;
+ LinkList redir = eparams->redir;
+ Wordcode varspc = eparams->varspc;
+ int type = eparams->type;
+ /*
+ * preargs comes from expanding the head of the args list
+ * in order to check for prefix commands.
+ */
+ LinkList preargs;
+
+ doneps4 = 0;
+
+ /*
+ * If assignment but no command get the status from variable
+ * assignment.
+ */
+ old_lastval = lastval;
+ if (!args && varspc)
+ lastval = errflag ? errflag : cmdoutval;
+ /*
+ * If there are arguments, we should reset the status for the
+ * command before execution---unless we are using the result of a
+ * command substitution, which will be indicated by setting
+ * use_cmdoutval to 1. We haven't kicked those off yet, so
+ * there's no race.
+ */
+ use_cmdoutval = !args;
+
+ 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 == WC_SIMPLE || type == WC_TYPESET) && args && nonempty(args) &&
+ *(char *)peekfirst(args) == '%') {
+ if (how & Z_DISOWN) {
+ oautocont = opts[AUTOCONTINUE];
+ opts[AUTOCONTINUE] = 1;
+ }
+ 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 == WC_SIMPLE && (how & Z_SYNC) &&
+ args && nonempty(args) && (!redir || empty(redir)) && !input &&
+ !nextnode(firstnode(args))) {
+ if (unset(NOTIFY))
+ scanjobs();
+ if (findjobnam(peekfirst(args)) != -1)
+ pushnode(args, dupstring("fg"));
+ }
+
+ if ((how & Z_ASYNC) || output) {
+ /*
+ * If running in the background, or not the last command in a
+ * pipeline, we don't need any of the rest of this function to
+ * affect the state in the main shell, so fork immediately.
+ *
+ * In other cases we may need to process the command line
+ * a bit further before we make the decision.
+ */
+ text = getjobtext(state->prog, eparams->beg);
+ switch (execcmd_fork(state, how, type, varspc, &filelist,
+ text, oautocont, close_if_forked)) {
+ case -1:
+ goto fatal;
+ case 0:
+ break;
+ default:
+ return;
+ }
+ last1 = forked = 1;
+ } else
+ text = NULL;
+
+ /* 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 == WC_SIMPLE || type == WC_TYPESET) && args) {
+ /*
+ * preargs contains args that have been expanded by prefork.
+ * Running execcmd_getargs() causes any argument available
+ * in args to be exanded where necessary and transferred to
+ * preargs. We call execcmd_getargs() every time we need to
+ * analyse an argument not available in preargs, though there is
+ * no guarantee a further argument will be available.
+ */
+ preargs = newlinklist();
+ execcmd_getargs(preargs, args, eparams->htok);
+ while (nonempty(preargs)) {
+ char *cmdarg = (char *) peekfirst(preargs);
+ checked = !has_token(cmdarg);
+ if (!checked)
+ break;
+ if (type == WC_TYPESET &&
+ (hn = builtintab->getnode2(builtintab, cmdarg))) {
+ /*
+ * If reserved word for typeset command found (and so
+ * enabled), use regardless of whether builtin is
+ * enabled as we share the implementation.
+ *
+ * Reserved words take precedence over shell functions.
+ */
+ checked = 1;
+ } else if (isset(POSIXBUILTINS) && (cflags & BINF_EXEC)) {
+ /*
+ * POSIX doesn't allow "exec" to operate on builtins
+ * or shell functions.
+ */
+ break;
+ } else {
+ 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;
+ }
+ }
+ orig_cflags |= cflags;
+ cflags &= ~BINF_BUILTIN & ~BINF_COMMAND;
+ cflags |= hn->flags;
+ if (!(hn->flags & BINF_PREFIX)) {
+ is_builtin = 1;
+
+ /* autoload the builtin if necessary */
+ if (!(hn = resolvebuiltin(cmdarg, hn))) {
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+ if (type != WC_TYPESET)
+ magic_assign = (hn->flags & BINF_MAGICEQUALS);
+ break;
+ }
+ checked = 0;
+ /*
+ * We usually don't need the argument containing the
+ * precommand modifier itself. Exception: when "command"
+ * will implemented by a call to "whence", in which case
+ * we'll simply re-insert the argument.
+ */
+ uremnode(preargs, firstnode(preargs));
+ if (!firstnode(preargs)) {
+ execcmd_getargs(preargs, args, eparams->htok);
+ if (!firstnode(preargs))
+ break;
+ }
+ if ((cflags & BINF_COMMAND)) {
+ /*
+ * Check for options to "command".
+ * If just -p, this is handled here: use the default
+ * path to execute.
+ * If -v or -V, possibly with -p, dispatch to bin_whence
+ * but with flag to indicate special handling of -p.
+ * Otherwise, just leave marked as BINF_COMMAND
+ * modifier with no additional action.
+ */
+ LinkNode argnode, oldnode, pnode = NULL;
+ char *argdata, *cmdopt;
+ int has_p = 0, has_vV = 0, has_other = 0;
+ argnode = firstnode(preargs);
+ argdata = (char *) getdata(argnode);
+ while (IS_DASH(*argdata)) {
+ /* Just to be definite, stop on single "-", too, */
+ if (!argdata[1] ||
+ (IS_DASH(argdata[1]) && !argdata[2]))
+ break;
+ for (cmdopt = argdata+1; *cmdopt; cmdopt++) {
+ switch (*cmdopt) {
+ case 'p':
+ /*
+ * If we've got this multiple times (command
+ * -p -p) we'll treat the second -p as a
+ * command because we only remove one below.
+ * Don't think that's a big issue, and it's
+ * also traditional behaviour.
+ */
+ has_p = 1;
+ pnode = argnode;
+ break;
+ case 'v':
+ case 'V':
+ has_vV = 1;
+ break;
+ default:
+ has_other = 1;
+ break;
+ }
+ }
+ if (has_other) {
+ /* Don't know how to handle this, so don't */
+ has_p = has_vV = 0;
+ break;
+ }
+
+ oldnode = argnode;
+ argnode = nextnode(argnode);
+ if (!argnode) {
+ execcmd_getargs(preargs, args, eparams->htok);
+ if (!(argnode = nextnode(oldnode)))
+ break;
+ }
+ argdata = (char *) getdata(argnode);
+ }
+ if (has_vV) {
+ /*
+ * Leave everything alone, dispatch to whence.
+ * We need to put the name back in the list.
+ */
+ pushnode(preargs, "command");
+ hn = &commandbn.node;
+ is_builtin = 1;
+ break;
+ } else if (has_p) {
+ /* Use default path */
+ use_defpath = 1;
+ /*
+ * We don't need this node as we're not treating
+ * "command" as a builtin this time.
+ */
+ if (pnode)
+ uremnode(preargs, pnode);
+ }
+ /*
+ * Else just any trailing
+ * end-of-options marker. This can only occur
+ * if we just had -p or something including more
+ * than just -p, -v and -V, in which case we behave
+ * as if this is command [non-option-stuff]. This
+ * isn't a good place for standard option handling.
+ */
+ if (IS_DASH(argdata[0]) && IS_DASH(argdata[1]) && !argdata[2])
+ uremnode(preargs, argnode);
+ } else if (cflags & BINF_EXEC) {
+ /*
+ * Check for compatibility options to exec builtin.
+ * It would be nice to do these more generically,
+ * but currently we don't have a mechanism for
+ * precommand modifiers.
+ */
+ LinkNode argnode = firstnode(preargs), oldnode;
+ char *argdata = (char *) getdata(argnode);
+ char *cmdopt, *exec_argv0 = NULL;
+ /*
+ * Careful here: we want to make sure a final dash
+ * is passed through in order that it still behaves
+ * as a precommand modifier (zsh equivalent of -l).
+ * It has to be last, but I think that's OK since
+ * people aren't likely to mix the option style
+ * with the zsh style.
+ */
+ while (argdata && IS_DASH(*argdata) && strlen(argdata) >= 2) {
+ oldnode = argnode;
+ argnode = nextnode(oldnode);
+ if (!argnode) {
+ execcmd_getargs(preargs, args, eparams->htok);
+ argnode = nextnode(oldnode);
+ }
+ if (!argnode) {
+ zerr("exec requires a command to execute");
+ lastval = 1;
+ errflag |= ERRFLAG_ERROR;
+ goto done;
+ }
+ uremnode(preargs, oldnode);
+ if (IS_DASH(argdata[0]) && IS_DASH(argdata[1]) && !argdata[2])
+ break;
+ for (cmdopt = &argdata[1]; *cmdopt; ++cmdopt) {
+ switch (*cmdopt) {
+ case 'a':
+ /* argument is ARGV0 string */
+ if (cmdopt[1]) {
+ exec_argv0 = cmdopt+1;
+ /* position on last non-NULL character */
+ cmdopt += strlen(cmdopt+1);
+ } else {
+ if (!argnode) {
+ zerr("exec requires a command to execute");
+ lastval = 1;
+ errflag |= ERRFLAG_ERROR;
+ goto done;
+ }
+ if (!nextnode(argnode))
+ execcmd_getargs(preargs, args,
+ eparams->htok);
+ if (!nextnode(argnode)) {
+ zerr("exec flag -a requires a parameter");
+ lastval = 1;
+ errflag |= ERRFLAG_ERROR;
+ goto done;
+ }
+ exec_argv0 = (char *) getdata(argnode);
+ oldnode = argnode;
+ argnode = nextnode(argnode);
+ uremnode(args, oldnode);
+ }
+ break;
+ case 'c':
+ cflags |= BINF_CLEARENV;
+ break;
+ case 'l':
+ cflags |= BINF_DASH;
+ break;
+ default:
+ zerr("unknown exec flag -%c", *cmdopt);
+ lastval = 1;
+ errflag |= ERRFLAG_ERROR;
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+ }
+ if (!argnode)
+ break;
+ argdata = (char *) getdata(argnode);
+ }
+ if (exec_argv0) {
+ char *str, *s;
+ exec_argv0 = dupstring(exec_argv0);
+ remnulargs(exec_argv0);
+ untokenize(exec_argv0);
+ size_t sz = strlen(exec_argv0);
+ str = s = zalloc(5 + 1 + sz + 1);
+ strcpy(s, "ARGV0=");
+ s+=6;
+ strcpy(s, exec_argv0);
+ zputenv(str);
+ }
+ }
+ hn = NULL;
+ if ((cflags & BINF_COMMAND) && unset(POSIXBUILTINS))
+ break;
+ if (!nonempty(preargs))
+ execcmd_getargs(preargs, args, eparams->htok);
+ }
+ } else
+ preargs = NULL;
+
+ /* if we get this far, it is OK to pay attention to lastval again */
+ if (noerrexit & NOERREXIT_UNTIL_EXEC)
+ noerrexit = 0;
+
+ /* Do prefork substitutions.
+ *
+ * Decide if we need "magic" handling of ~'s etc. in
+ * assignment-like arguments.
+ * - If magic_assign is set, we are using a builtin of the
+ * tyepset family, but did not recognise this as a keyword,
+ * so need guess-o-matic behaviour.
+ * - Otherwise, if we did recognise the keyword, we never need
+ * guess-o-matic behaviour as the argument was properly parsed
+ * as such.
+ * - Otherwise, use the behaviour specified by the MAGIC_EQUAL_SUBST
+ * option.
+ */
+ esprefork = (magic_assign ||
+ (isset(MAGICEQUALSUBST) && type != WC_TYPESET)) ?
+ PREFORK_TYPESET : 0;
+
+ if (args) {
+ if (eparams->htok)
+ prefork(args, esprefork, NULL);
+ if (preargs)
+ args = joinlists(preargs, args);
+ }
+
+ if (type == WC_SIMPLE || type == WC_TYPESET) {
+ int unglobbed = 0;
+
+ for (;;) {
+ char *cmdarg;
+
+ if (!(cflags & BINF_NOGLOB))
+ while (!checked && !errflag && args && nonempty(args) &&
+ has_token((char *) peekfirst(args)))
+ zglob(args, firstnode(args), 0);
+ 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)
+ do_exec = 1;
+
+ /* Empty command */
+ if (!args || empty(args)) {
+ if (redir && nonempty(redir)) {
+ if (do_exec) {
+ /* Was this "exec < foobar"? */
+ nullexec = 1;
+ break;
+ } else if (varspc) {
+ nullexec = 2;
+ break;
+ } else if (!nullcmd || !*nullcmd || opts[CSHNULLCMD] ||
+ (cflags & BINF_PREFIX)) {
+ zerr("redirection with no command");
+ lastval = 1;
+ errflag |= ERRFLAG_ERROR;
+ if (forked)
+ _exit(lastval);
+ return;
+ } else if (!nullcmd || !*nullcmd || opts[SHNULLCMD]) {
+ if (!args)
+ args = newlinklist();
+ addlinknode(args, dupstring(":"));
+ } else if (readnullcmd && *readnullcmd &&
+ ((Redir) peekfirst(redir))->type == REDIR_READ &&
+ !nextnode(firstnode(redir))) {
+ if (!args)
+ args = newlinklist();
+ addlinknode(args, dupstring(readnullcmd));
+ } else {
+ if (!args)
+ args = newlinklist();
+ addlinknode(args, dupstring(nullcmd));
+ }
+ } else if ((cflags & BINF_PREFIX) && (cflags & BINF_COMMAND)) {
+ lastval = 0;
+ if (forked)
+ _exit(lastval);
+ return;
+ } else {
+ /*
+ * No arguments. Reset the status if there were
+ * arguments before and no command substitution
+ * has provided a status.
+ */
+ if (badcshglob == 1) {
+ zerr("no match");
+ lastval = 1;
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+ cmdoutval = use_cmdoutval ? lastval : 0;
+ if (varspc) {
+ /* Make sure $? is still correct for assignment */
+ lastval = old_lastval;
+ addvars(state, varspc, 0);
+ }
+ if (errflag)
+ lastval = 1;
+ else
+ lastval = cmdoutval;
+ if (isset(XTRACE)) {
+ fputc('\n', xtrerr);
+ fflush(xtrerr);
+ }
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+ } else if (isset(RESTRICTED) && (cflags & BINF_EXEC) && do_exec) {
+ zerrnam("exec", "%s: restricted",
+ (char *) getdata(firstnode(args)));
+ lastval = 1;
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+
+ /*
+ * Quit looking for a command if:
+ * - there was an error; or
+ * - we checked the simple cases needing MAGIC_EQUAL_SUBST; or
+ * - we know we already found a builtin (because either:
+ * - we loaded a builtin from a module, or
+ * - we have determined there are options which would
+ * require us to use the "command" builtin); or
+ * - we aren't using POSIX and so BINF_COMMAND indicates a zsh
+ * precommand modifier is being used in place of the
+ * builtin
+ * - we are using POSIX and this is an EXEC, so we can't
+ * execute a builtin or function.
+ */
+ if (errflag || checked || is_builtin ||
+ (isset(POSIXBUILTINS) ?
+ (cflags & BINF_EXEC) : (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) {
+ zwarn("no such builtin: %s", cmdarg);
+ lastval = 1;
+ if (oautocont >= 0)
+ opts[AUTOCONTINUE] = oautocont;
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+ break;
+ }
+ if (!(hn->flags & BINF_PREFIX)) {
+ is_builtin = 1;
+
+ /* autoload the builtin if necessary */
+ if (!(hn = resolvebuiltin(cmdarg, hn))) {
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+ break;
+ }
+ cflags &= ~BINF_BUILTIN & ~BINF_COMMAND;
+ cflags |= hn->flags;
+ uremnode(args, firstnode(args));
+ hn = NULL;
+ }
+ }
+
+ if (errflag) {
+ if (!lastval)
+ lastval = 1;
+ if (oautocont >= 0)
+ opts[AUTOCONTINUE] = oautocont;
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+
+ /* Get the text associated with this command. */
+ if (!text &&
+ (!sfcontext && (jobbing || (how & Z_TIMED))))
+ text = getjobtext(state->prog, eparams->beg);
+
+ /*
+ * Set up special parameter $_
+ * For execfuncdef we may need to take account of an
+ * anonymous function with arguments.
+ */
+ if (type != WC_FUNCDEF)
+ setunderscore((args && nonempty(args)) ?
+ ((char *) getdata(lastnode(args))) : "");
+
+ /* Warn about "rm *" */
+ if (type == WC_SIMPLE && interact && unset(RMSTARSILENT) &&
+ isset(SHINSTDIN) && args && 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)) {
+ errflag |= ERRFLAG_ERROR;
+ break;
+ }
+ } else if (l >= 2 && s[l - 2] == '/' && s[l - 1] == Star) {
+ char t = s[l - 2];
+ int rmall;
+
+ s[l - 2] = 0;
+ rmall = checkrmall(s);
+ s[l - 2] = t;
+
+ if (!rmall) {
+ errflag |= ERRFLAG_ERROR;
+ break;
+ }
+ }
+ }
+ }
+
+ if (type == WC_FUNCDEF) {
+ /*
+ * The first word of a function definition is a list of
+ * names. If this is empty, we're doing an anonymous function:
+ * in that case redirections are handled normally.
+ * If not, it's a function definition: then we don't do
+ * redirections here but pass in the list of redirections to
+ * be stored for recall with the function.
+ */
+ if (*state->pc != 0) {
+ /* Nonymous, don't do redirections here */
+ redir = NULL;
+ }
+ } else if (is_shfunc || type == WC_AUTOFN) {
+ Shfunc shf;
+ if (is_shfunc)
+ shf = (Shfunc)hn;
+ else {
+ shf = loadautofn(state->prog->shf, 1, 0, 0);
+ if (shf)
+ state->prog->shf = shf;
+ else {
+ /*
+ * This doesn't set errflag, so just return now.
+ */
+ lastval = 1;
+ if (oautocont >= 0)
+ opts[AUTOCONTINUE] = oautocont;
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+ }
+ /*
+ * A function definition may have a list of additional
+ * redirections to apply, so retrieve it.
+ */
+ if (shf->redir) {
+ struct estate s;
+ LinkList redir2;
+
+ s.prog = shf->redir;
+ s.pc = shf->redir->prog;
+ s.strs = shf->redir->strs;
+ redir2 = ecgetredirs(&s);
+ if (!redir)
+ redir = redir2;
+ else {
+ while (nonempty(redir2))
+ addlinknode(redir, ugetnode(redir2));
+ }
+ }
+ }
+
+ if (errflag) {
+ lastval = 1;
+ if (oautocont >= 0)
+ opts[AUTOCONTINUE] = oautocont;
+ if (forked)
+ _exit(lastval);
+ return;
+ }
+
+ if ((type == WC_SIMPLE || type == WC_TYPESET) && !nullexec) {
+ char *s;
+ char trycd = (isset(AUTOCD) && isset(SHINSTDIN) &&
+ (!redir || empty(redir)) && args && !empty(args) &&
+ !nextnode(firstnode(args)) && *(char *)peekfirst(args));
+
+ DPUTS((!args || empty(args)), "BUG: empty(args) in exec.c");
+ if (!hn) {
+ /* Resolve external commands */
+ char *cmdarg = (char *) peekfirst(args);
+ char **checkpath = pathchecked;
+ int dohashcmd = isset(HASHCMDS);
+
+ hn = cmdnamtab->getnode(cmdnamtab, cmdarg);
+ if (hn && trycd && !isreallycom((Cmdnam)hn)) {
+ if (!(((Cmdnam)hn)->node.flags & HASHED)) {
+ checkpath = path;
+ dohashcmd = 1;
+ }
+ cmdnamtab->removenode(cmdnamtab, cmdarg);
+ cmdnamtab->freenode(hn);
+ hn = NULL;
+ }
+ if (!hn && dohashcmd && strcmp(cmdarg, "..")) {
+ for (s = cmdarg; *s && *s != '/'; s++);
+ if (!*s)
+ hn = (HashNode) hashcmd(cmdarg, checkpath);
+ }
+ }
+
+ /* 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("--"));
+ 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 || nullexec || type >= WC_CURSH);
+
+ /**************************************************************************
+ * Do we need to fork? We need to fork if: *
+ * 1) The command is supposed to run in the background. This *
+ * case is now handled above (forked = 1 here). (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 the command is the last stage in a subshell, or is the *
+ * last command after the option `-c'. *
+ * 2) We don't have any traps set. *
+ * 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 (!forked) {
+ if (!do_exec &&
+ (((is_builtin || is_shfunc) && output) ||
+ (!is_cursh && (last1 != 1 || nsigtrapped || havefiles() ||
+ fdtable_flocks)))) {
+ switch (execcmd_fork(state, how, type, varspc, &filelist,
+ text, oautocont, close_if_forked)) {
+ case -1:
+ goto fatal;
+ case 0:
+ break;
+ default:
+ return;
+ }
+ forked = 1;
+ } 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;
+ if (!jobtab[thisjob].procs)
+ jobtab[thisjob].stat |= STAT_NOPRINT;
+ if (is_builtin)
+ jobtab[thisjob].stat |= STAT_BUILTIN;
+ } 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 we are in a subshell environment anyway, say we're forked,
+ * even if we're actually not forked because we know the
+ * subshell is exiting. This ensures SHLVL reflects the current
+ * shell, and also optimises out any save/restore we'd need to
+ * do if we were returning to the main shell.
+ */
+ if (type == WC_SUBSH)
+ forked = 1;
+ }
+ }
+
+ if ((esglob = !(cflags & BINF_NOGLOB)) && args && eparams->htok) {
+ LinkList oargs = args;
+ globlist(args, 0);
+ args = oargs;
+ }
+ if (errflag) {
+ lastval = 1;
+ goto err;
+ }
+
+ /* Make a copy of stderr for xtrace output before redirecting */
+ fflush(xtrerr);
+ if (isset(XTRACE) && xtrerr == stderr &&
+ (type < WC_SUBSH || type == WC_TIMED)) {
+ if ((newxtrerr = fdopen(movefd(dup(fileno(stderr))), "w"))) {
+ xtrerr = newxtrerr;
+ fdtable[fileno(xtrerr)] = FDT_XTRACE;
+ }
+ }
+
+ /* Add pipeline input/output to mnodes */
+ if (input)
+ addfd(forked, save, mfds, 0, input, 0, NULL);
+ if (output)
+ addfd(forked, save, mfds, 1, output, 1, NULL);
+
+ /* Do process substitutions */
+ if (redir)
+ spawnpipes(redir, nullexec);
+
+ /* Do io redirections */
+ while (redir && nonempty(redir)) {
+ fn = (Redir) ugetnode(redir);
+
+ DPUTS(fn->type == REDIR_HEREDOC || fn->type == REDIR_HEREDOCDASH,
+ "BUG: unexpanded here document");
+ if (fn->type == REDIR_INPIPE) {
+ if (!checkclobberparam(fn) || fn->fd2 == -1) {
+ if (fn->fd2 != -1)
+ zclose(fn->fd2);
+ closemnodes(mfds);
+ fixfds(save);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fn->fd2, 0, fn->varid);
+ } else if (fn->type == REDIR_OUTPIPE) {
+ if (!checkclobberparam(fn) || fn->fd2 == -1) {
+ if (fn->fd2 != -1)
+ zclose(fn->fd2);
+ closemnodes(mfds);
+ fixfds(save);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fn->fd2, 1, fn->varid);
+ } else {
+ int closed;
+ if (fn->type != REDIR_HERESTR && xpandredir(fn, redir))
+ continue;
+ if (errflag) {
+ closemnodes(mfds);
+ fixfds(save);
+ execerr();
+ }
+ if (isset(RESTRICTED) && IS_WRITE_FILE(fn->type)) {
+ zwarn("writing redirection not allowed in restricted mode");
+ execerr();
+ }
+ if (unset(EXECOPT))
+ continue;
+ switch(fn->type) {
+ case REDIR_HERESTR:
+ if (!checkclobberparam(fn))
+ fil = -1;
+ else
+ fil = getherestr(fn);
+ if (fil == -1) {
+ if (errno && errno != EINTR)
+ zwarn("can't create temp file for here document: %e",
+ errno);
+ closemnodes(mfds);
+ fixfds(save);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fil, 0, fn->varid);
+ break;
+ case REDIR_READ:
+ case REDIR_READWRITE:
+ if (!checkclobberparam(fn))
+ fil = -1;
+ else if (fn->type == REDIR_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)
+ zwarn("%e: %s", errno, fn->name);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fil, 0, fn->varid);
+ /* If this is 'exec < file', read from stdin, *
+ * not terminal, unless `file' is a terminal. */
+ if (nullexec == 1 && fn->fd1 == 0 &&
+ isset(SHINSTDIN) && interact && !zleactive)
+ init_io(NULL);
+ break;
+ case REDIR_CLOSE:
+ if (fn->varid) {
+ char *s = fn->varid, *t;
+ struct value vbuf;
+ Value v;
+ int bad = 0;
+
+ if (!(v = getvalue(&vbuf, &s, 0))) {
+ bad = 1;
+ } else if (v->pm->node.flags & PM_READONLY) {
+ bad = 2;
+ } else {
+ s = getstrvalue(v);
+ if (errflag)
+ bad = 1;
+ else {
+ fn->fd1 = zstrtol(s, &t, 0);
+ if (s == t)
+ bad = 1;
+ else if (*t) {
+ /* Check for base#number format */
+ if (*t == '#' && *s != '0')
+ fn->fd1 = zstrtol(s = t+1, &t, fn->fd1);
+ if (s == t || *t)
+ bad = 1;
+ }
+ if (!bad && fn->fd1 <= max_zsh_fd) {
+ if (fn->fd1 >= 10 &&
+ (fdtable[fn->fd1] & FDT_TYPE_MASK) ==
+ FDT_INTERNAL)
+ bad = 3;
+ }
+ }
+ }
+ if (bad) {
+ const char *bad_msg[] = {
+ "parameter %s does not contain a file descriptor",
+ "can't close file descriptor from readonly parameter %s",
+ "file descriptor %d used by shell, not closed"
+ };
+ if (bad > 2)
+ zwarn(bad_msg[bad-1], fn->fd1);
+ else
+ zwarn(bad_msg[bad-1], fn->varid);
+ execerr();
+ }
+ }
+ /*
+ * Note we may attempt to close an fd beyond max_zsh_fd:
+ * OK as long as we never look in fdtable for it.
+ */
+ closed = 0;
+ if (!forked && fn->fd1 < 10 && save[fn->fd1] == -2) {
+ save[fn->fd1] = movefd(fn->fd1);
+ if (save[fn->fd1] >= 0) {
+ /*
+ * The original fd is now closed, we don't need
+ * to do it below.
+ */
+ closed = 1;
+ }
+ }
+ if (fn->fd1 < 10)
+ closemn(mfds, fn->fd1, REDIR_CLOSE);
+ /*
+ * Only report failures to close file descriptors
+ * if they're under user control as we don't know
+ * what the previous status of others was.
+ */
+ if (!closed && zclose(fn->fd1) < 0 && fn->varid) {
+ zwarn("failed to close file descriptor %d: %e",
+ fn->fd1, errno);
+ }
+ break;
+ case REDIR_MERGEIN:
+ case REDIR_MERGEOUT:
+ if (fn->fd2 < 10)
+ closemn(mfds, fn->fd2, fn->type);
+ if (!checkclobberparam(fn))
+ fil = -1;
+ else if (fn->fd2 > 9 &&
+ /*
+ * If the requested fd is > max_zsh_fd,
+ * the shell doesn't know about it.
+ * Just assume the user knows what they're
+ * doing.
+ */
+ (fn->fd2 <= max_zsh_fd &&
+ ((fdtable[fn->fd2] != FDT_UNUSED &&
+ fdtable[fn->fd2] != FDT_EXTERNAL) ||
+ fn->fd2 == coprocin ||
+ fn->fd2 == coprocout))) {
+ fil = -1;
+ errno = EBADF;
+ } else {
+ int fd = fn->fd2;
+ if(fd == -2)
+ fd = (fn->type == REDIR_MERGEOUT) ? coprocout : coprocin;
+ fil = movefd(dup(fd));
+ }
+ if (fil == -1) {
+ char fdstr[DIGBUFSIZE];
+
+ closemnodes(mfds);
+ fixfds(save);
+ if (fn->fd2 != -2)
+ sprintf(fdstr, "%d", fn->fd2);
+ if (errno)
+ zwarn("%s: %e", fn->fd2 == -2 ? "coprocess" : fdstr,
+ errno);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fil,
+ fn->type == REDIR_MERGEOUT, fn->varid);
+ break;
+ default:
+ if (!checkclobberparam(fn))
+ fil = -1;
+ else if (IS_APPEND_REDIR(fn->type))
+ fil = open(unmeta(fn->name),
+ ((unset(CLOBBER) && unset(APPENDCREATE)) &&
+ !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 = movefd(dup(fil));
+ else
+ dfil = 0;
+ if (fil == -1 || dfil == -1) {
+ if(fil != -1)
+ close(fil);
+ closemnodes(mfds);
+ fixfds(save);
+ if (errno && errno != EINTR)
+ zwarn("%e: %s", errno, fn->name);
+ execerr();
+ }
+ addfd(forked, save, mfds, fn->fd1, fil, 1, fn->varid);
+ if(IS_ERROR_REDIR(fn->type))
+ addfd(forked, save, mfds, 2, dfil, 1, NULL);
+ break;
+ }
+ /* May be error in addfd due to setting parameter. */
+ if (errflag) {
+ closemnodes(mfds);
+ fixfds(save);
+ execerr();
+ }
+ }
+ }
+
+ /* We are done with redirection. close the mnodes, *
+ * spawning tee/cat processes as necessary. */
+ for (i = 0; i < 10; i++)
+ if (mfds[i] && mfds[i]->ct >= 2)
+ closemn(mfds, i, REDIR_CLOSE);
+
+ if (nullexec) {
+ /*
+ * If nullexec is 2, we have variables to add with the redirections
+ * in place. If nullexec is 1, we may have variables but they
+ * need the standard restore logic.
+ */
+ if (varspc) {
+ LinkList restorelist = 0, removelist = 0;
+ if (!isset(POSIXBUILTINS) && nullexec != 2)
+ save_params(state, varspc, &restorelist, &removelist);
+ addvars(state, varspc, 0);
+ if (restorelist)
+ restore_params(restorelist, removelist);
+ }
+ lastval = errflag ? errflag : cmdoutval;
+ if (nullexec == 1) {
+ /*
+ * If nullexec is 1 we specifically *don't* restore the original
+ * fd's before returning.
+ */
+ for (i = 0; i < 10; i++)
+ if (save[i] != -2)
+ zclose(save[i]);
+ goto done;
+ }
+ if (isset(XTRACE)) {
+ fputc('\n', xtrerr);
+ fflush(xtrerr);
+ }
+ } else if (isset(EXECOPT) && !errflag) {
+ int q = queue_signal_level();
+ /*
+ * 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) {
+ int flags = ((how & Z_ASYNC) ? ESUB_ASYNC : 0) |
+ ESUB_PGRP | ESUB_FAKE;
+ if (type != WC_SUBSH)
+ flags |= ESUB_KEEPTRAP;
+ if ((do_exec || (type >= WC_CURSH && last1 == 1))
+ && !forked)
+ flags |= ESUB_REVERTPGRP;
+ entersubsh(flags);
+ }
+ if (type == WC_FUNCDEF) {
+ Eprog redir_prog;
+ if (!redir && wc_code(*eparams->beg) == WC_REDIR) {
+ /*
+ * We're not using a redirection from the currently
+ * parsed environment, which is what we'd do for an
+ * anonymous function, but there are redirections we
+ * should store with the new function.
+ */
+ struct estate s;
+
+ s.prog = state->prog;
+ s.pc = eparams->beg;
+ s.strs = state->prog->strs;
+
+ /*
+ * The copy uses the wordcode parsing area, so save and
+ * restore state.
+ */
+ zcontext_save();
+ redir_prog = eccopyredirs(&s);
+ zcontext_restore();
+ } else
+ redir_prog = NULL;
+
+ dont_queue_signals();
+ lastval = execfuncdef(state, redir_prog);
+ restore_queue_signals(q);
+ }
+ else if (type >= WC_CURSH) {
+ if (last1 == 1)
+ do_exec = 1;
+ dont_queue_signals();
+ if (type == WC_AUTOFN) {
+ /*
+ * We pre-loaded this to get any redirs.
+ * So we execuate a simplified function here.
+ */
+ lastval = execautofn_basic(state, do_exec);
+ } else
+ lastval = (execfuncs[type - WC_CURSH])(state, do_exec);
+ restore_queue_signals(q);
+ } else if (is_builtin || is_shfunc) {
+ LinkList restorelist = 0, removelist = 0;
+ int do_save = 0;
+ /* builtin or shell function */
+
+ if (!forked) {
+ if (isset(POSIXBUILTINS)) {
+ /*
+ * If it's a function or special builtin --- save
+ * if it's got "command" in front.
+ * If it's a normal command --- save.
+ */
+ if (is_shfunc || (hn->flags & (BINF_PSPECIAL|BINF_ASSIGN)))
+ do_save = (orig_cflags & BINF_COMMAND);
+ else
+ do_save = 1;
+ } else {
+ /*
+ * Save if it's got "command" in front or it's
+ * not a magic-equals assignment.
+ */
+ if ((cflags & (BINF_COMMAND|BINF_ASSIGN)) || !magic_assign)
+ do_save = 1;
+ }
+ if (do_save && varspc)
+ save_params(state, varspc, &restorelist, &removelist);
+ }
+ if (varspc) {
+ /* Export this if the command is a shell function,
+ * but not if it's a builtin.
+ */
+ int flags = 0;
+ if (is_shfunc)
+ flags |= ADDVAR_EXPORT;
+ if (restorelist)
+ flags |= ADDVAR_RESTORE;
+
+ addvars(state, varspc, flags);
+ if (errflag) {
+ if (restorelist)
+ restore_params(restorelist, removelist);
+ lastval = 1;
+ fixfds(save);
+ goto done;
+ }
+ }
+
+ if (is_shfunc) {
+ /* It's a shell function */
+ pipecleanfilelist(filelist, 0);
+ execshfunc((Shfunc) hn, args);
+ } else {
+ /* It's a builtin */
+ LinkList assigns = (LinkList)0;
+ int postassigns = eparams->postassigns;
+ if (forked)
+ closem(FDT_INTERNAL, 0);
+ if (postassigns) {
+ Wordcode opc = state->pc;
+ state->pc = eparams->assignspc;
+ assigns = newlinklist();
+ while (postassigns--) {
+ int htok;
+ wordcode ac = *state->pc++;
+ char *name = ecgetstr(state, EC_DUPTOK, &htok);
+ Asgment asg;
+ local_list1(svl);
+
+ DPUTS(wc_code(ac) != WC_ASSIGN,
+ "BUG: bad assignment list for typeset");
+ if (htok) {
+ init_list1(svl, name);
+ if (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR &&
+ WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC) {
+ char *data;
+ /*
+ * Special case: this is a name only, so
+ * it's not required to be a single
+ * expansion. Furthermore, for
+ * consistency with the builtin
+ * interface, it may expand into
+ * scalar assignments:
+ * ass=(one=two three=four)
+ * typeset a=b $ass
+ */
+ /* Unused dummy value for name */
+ (void)ecgetstr(state, EC_DUPTOK, &htok);
+ prefork(&svl, PREFORK_TYPESET, NULL);
+ if (errflag) {
+ state->pc = opc;
+ break;
+ }
+ globlist(&svl, 0);
+ if (errflag) {
+ state->pc = opc;
+ break;
+ }
+ while ((data = ugetnode(&svl))) {
+ char *ptr;
+ asg = (Asgment)zhalloc(sizeof(struct asgment));
+ asg->flags = 0;
+ if ((ptr = strchr(data, '='))) {
+ *ptr++ = '\0';
+ asg->name = data;
+ asg->value.scalar = ptr;
+ } else {
+ asg->name = data;
+ asg->value.scalar = NULL;
+ }
+ uaddlinknode(assigns, &asg->node);
+ }
+ continue;
+ }
+ prefork(&svl, PREFORK_SINGLE, NULL);
+ name = empty(&svl) ? "" :
+ (char *)getdata(firstnode(&svl));
+ }
+ untokenize(name);
+ asg = (Asgment)zhalloc(sizeof(struct asgment));
+ asg->name = name;
+ if (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR) {
+ char *val = ecgetstr(state, EC_DUPTOK, &htok);
+ asg->flags = 0;
+ if (WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC) {
+ /* Fake assignment, no value */
+ asg->value.scalar = NULL;
+ } else {
+ if (htok) {
+ init_list1(svl, val);
+ prefork(&svl,
+ PREFORK_SINGLE|PREFORK_ASSIGN,
+ NULL);
+ if (errflag) {
+ state->pc = opc;
+ break;
+ }
+ /*
+ * No globassign for typeset
+ * arguments, thank you
+ */
+ val = empty(&svl) ? "" :
+ (char *)getdata(firstnode(&svl));
+ }
+ untokenize(val);
+ asg->value.scalar = val;
+ }
+ } else {
+ asg->flags = ASG_ARRAY;
+ asg->value.array =
+ ecgetlist(state, WC_ASSIGN_NUM(ac),
+ EC_DUPTOK, &htok);
+ if (asg->value.array)
+ {
+ if (!errflag) {
+ int prefork_ret = 0;
+ prefork(asg->value.array, PREFORK_ASSIGN,
+ &prefork_ret);
+ if (errflag) {
+ state->pc = opc;
+ break;
+ }
+ if (prefork_ret & PREFORK_KEY_VALUE)
+ asg->flags |= ASG_KEY_VALUE;
+ globlist(asg->value.array, prefork_ret);
+ }
+ if (errflag) {
+ state->pc = opc;
+ break;
+ }
+ }
+ }
+
+ uaddlinknode(assigns, &asg->node);
+ }
+ state->pc = opc;
+ }
+ dont_queue_signals();
+ if (!errflag) {
+ int ret = execbuiltin(args, assigns, (Builtin) hn);
+ /*
+ * In case of interruption assume builtin status
+ * is less useful than what interrupt set.
+ */
+ if (!(errflag & ERRFLAG_INT))
+ lastval = ret;
+ }
+ if (do_save & BINF_COMMAND)
+ errflag &= ~ERRFLAG_ERROR;
+ restore_queue_signals(q);
+ fflush(stdout);
+ if (save[1] == -2) {
+ if (ferror(stdout)) {
+ zwarn("write error: %e", errno);
+ clearerr(stdout);
+ }
+ } else
+ clearerr(stdout);
+ }
+ if (isset(PRINTEXITVALUE) && isset(SHINSTDIN) &&
+ lastval && !subsh) {
+#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
+ fprintf(stderr, "zsh: exit %lld\n", lastval);
+#else
+ fprintf(stderr, "zsh: exit %ld\n", (long)lastval);
+#endif
+ fflush(stderr);
+ }
+
+ if (do_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(NULL, 1, HFILE_USE_OPTIONS);
+ exit(lastval);
+ }
+ if (restorelist)
+ restore_params(restorelist, removelist);
+
+ } else {
+ if (!subsh) {
+ /* for either implicit or explicit "exec", decrease $SHLVL
+ * as we're now done as a shell */
+ if (!forked)
+ setiparam("SHLVL", --shlvl);
+
+ /* If we are exec'ing a command, and we are not *
+ * in a subshell, then save the history file. */
+ if (do_exec && isset(RCS) && interact && !nohistsave)
+ savehistfile(NULL, 1, HFILE_USE_OPTIONS);
+ }
+ if (type == WC_SIMPLE || type == WC_TYPESET) {
+ if (varspc) {
+ int addflags = ADDVAR_EXPORT|ADDVAR_RESTRICT;
+ if (forked)
+ addflags |= ADDVAR_RESTORE;
+ addvars(state, varspc, addflags);
+ if (errflag)
+ _exit(1);
+ }
+ closem(FDT_INTERNAL, 0);
+ if (coprocin != -1) {
+ zclose(coprocin);
+ coprocin = -1;
+ }
+ if (coprocout != -1) {
+ zclose(coprocout);
+ coprocout = -1;
+ }
+#ifdef HAVE_GETRLIMIT
+ if (!forked)
+ setlimits(NULL);
+#endif
+ if (how & Z_ASYNC) {
+ zsfree(STTYval);
+ STTYval = 0;
+ }
+ execute(args, cflags, use_defpath);
+ } else { /* ( ... ) */
+ DPUTS(varspc,
+ "BUG: assignment before complex command");
+ list_pipe = 0;
+ pipecleanfilelist(filelist, 0);
+ /* If we're forked (and we should be), no need to return */
+ DPUTS(last1 != 1 && !forked, "BUG: not exiting?");
+ DPUTS(type != WC_SUBSH, "Not sure what we're doing.");
+ /* Skip word only used for try/always blocks */
+ state->pc++;
+ execlist(state, 0, 1);
+ }
+ }
+ }
+
+ err:
+ if (forked) {
+ /*
+ * So what's going on here then? Well, I'm glad you asked.
+ *
+ * If we create multios for use in a subshell we do
+ * this after forking, in this function above. That
+ * means that the current (sub)process is responsible
+ * for clearing them up. However, the processes won't
+ * go away until we have closed the fd's talking to them.
+ * Since we're about to exit the shell there's nothing
+ * to stop us closing all fd's (including the ones 0 to 9
+ * that we usually leave alone).
+ *
+ * Then we wait for any processes. When we forked,
+ * we cleared the jobtable and started a new job just for
+ * any oddments like this, so if there aren't any we won't
+ * need to wait. The result of not waiting is that
+ * the multios haven't flushed the fd's properly, leading
+ * to obscure missing data.
+ *
+ * It would probably be cleaner to ensure that the
+ * parent shell handled multios, but that requires
+ * some architectural changes which are likely to be
+ * hairy.
+ */
+ for (i = 0; i < 10; i++)
+ if (fdtable[i] != FDT_UNUSED)
+ close(i);
+ closem(FDT_UNUSED, 1);
+ if (thisjob != -1)
+ waitjobs();
+ _exit(lastval);
+ }
+ fixfds(save);
+
+ done:
+ if (isset(POSIXBUILTINS) &&
+ (cflags & (BINF_PSPECIAL|BINF_EXEC)) &&
+ !(orig_cflags & BINF_COMMAND)) {
+ /*
+ * For POSIX-compatible behaviour with special
+ * builtins (including exec which we don't usually
+ * classify as a builtin) we treat all errors as fatal.
+ * The "command" builtin is not special so resets this behaviour.
+ */
+ forked |= zsh_subshell;
+ fatal:
+ if (redir_err || errflag) {
+ if (!isset(INTERACTIVE)) {
+ if (forked)
+ _exit(1);
+ else
+ exit(1);
+ }
+ errflag |= ERRFLAG_ERROR;
+ }
+ }
+ if (newxtrerr) {
+ fil = fileno(newxtrerr);
+ fclose(newxtrerr);
+ xtrerr = oxtrerr;
+ zclose(fil);
+ }
+
+ zsfree(STTYval);
+ STTYval = 0;
+ if (oautocont >= 0)
+ opts[AUTOCONTINUE] = oautocont;
+}
+
+/* Arrange to have variables restored. */
+
+/**/
+static void
+save_params(Estate state, Wordcode pc, LinkList *restore_p, LinkList *remove_p)
+{
+ Param pm;
+ char *s;
+ wordcode ac;
+
+ *restore_p = newlinklist();
+ *remove_p = newlinklist();
+
+ while (wc_code(ac = *pc) == WC_ASSIGN) {
+ s = ecrawstr(state->prog, pc + 1, NULL);
+ if ((pm = (Param) paramtab->getnode(paramtab, s))) {
+ Param tpm;
+ if (pm->env)
+ delenv(pm);
+ if (!(pm->node.flags & PM_SPECIAL)) {
+ /*
+ * We used to remove ordinary parameters from the
+ * table, but that meant "HELLO=$HELLO shellfunc"
+ * failed because the expansion of $HELLO hasn't
+ * been done at this point. Instead, copy the
+ * parameter: in this case, we'll insert the
+ * copied parameter straight back into the parameter
+ * table so we want to be sure everything is
+ * properly set up and in permanent memory.
+ */
+ tpm = (Param) zshcalloc(sizeof *tpm);
+ tpm->node.nam = ztrdup(pm->node.nam);
+ copyparam(tpm, pm, 0);
+ pm = tpm;
+ } else if (!(pm->node.flags & PM_READONLY) &&
+ (unset(RESTRICTED) || !(pm->node.flags & PM_RESTRICTED))) {
+ /*
+ * In this case we're just saving parts of
+ * the parameter in a tempory, so use heap allocation
+ * and don't bother copying every detail.
+ */
+ tpm = (Param) hcalloc(sizeof *tpm);
+ tpm->node.nam = pm->node.nam;
+ copyparam(tpm, pm, 1);
+ pm = tpm;
+ }
+ addlinknode(*remove_p, dupstring(s));
+ addlinknode(*restore_p, pm);
+ } else
+ addlinknode(*remove_p, dupstring(s));
+
+ pc += (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR ?
+ 3 : WC_ASSIGN_NUM(ac) + 2);
+ }
+}
+
+/* Restore saved parameters after executing a shfunc or builtin */
+
+/**/
+static void
+restore_params(LinkList restorelist, LinkList removelist)
+{
+ Param pm;
+ char *s;
+
+ /* remove temporary parameters */
+ while ((s = (char *) ugetnode(removelist))) {
+ if ((pm = (Param) paramtab->getnode(paramtab, s)) &&
+ !(pm->node.flags & PM_SPECIAL)) {
+ pm->node.flags &= ~PM_READONLY;
+ unsetparam_pm(pm, 0, 0);
+ }
+ }
+
+ if (restorelist) {
+ /* restore saved parameters */
+ while ((pm = (Param) ugetnode(restorelist))) {
+ if (pm->node.flags & PM_SPECIAL) {
+ Param tpm = (Param) paramtab->getnode(paramtab, pm->node.nam);
+
+ DPUTS(!tpm || PM_TYPE(pm->node.flags) != PM_TYPE(tpm->node.flags) ||
+ !(pm->node.flags & PM_SPECIAL),
+ "BUG: in restoring special parameters");
+ if (!pm->env && tpm->env)
+ delenv(tpm);
+ tpm->node.flags = pm->node.flags;
+ switch (PM_TYPE(pm->node.flags)) {
+ case PM_SCALAR:
+ tpm->gsu.s->setfn(tpm, pm->u.str);
+ break;
+ case PM_INTEGER:
+ tpm->gsu.i->setfn(tpm, pm->u.val);
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ tpm->gsu.f->setfn(tpm, pm->u.dval);
+ break;
+ case PM_ARRAY:
+ tpm->gsu.a->setfn(tpm, pm->u.arr);
+ break;
+ case PM_HASHED:
+ tpm->gsu.h->setfn(tpm, pm->u.hash);
+ break;
+ }
+ pm = tpm;
+ } else {
+ paramtab->addnode(paramtab, pm->node.nam, pm);
+ }
+ if ((pm->node.flags & PM_EXPORTED) && ((s = getsparam(pm->node.nam))))
+ addenv(pm, s);
+ }
+ }
+}
+
+/* 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;
+}
+
+/*
+ * Close internal shell fds.
+ *
+ * Close any that are marked as used if "how" is FDT_UNUSED, else
+ * close any with the value "how".
+ *
+ * If "all" is zero, we'll skip cases where we need the file
+ * descriptor to be visible externally.
+ */
+
+/**/
+mod_export void
+closem(int how, int all)
+{
+ int i;
+
+ for (i = 10; i <= max_zsh_fd; i++)
+ if (fdtable[i] != FDT_UNUSED &&
+ /*
+ * Process substitution needs to be visible to user;
+ * fd's are explicitly cleaned up by filelist handling.
+ */
+ (all || fdtable[i] != FDT_PROC_SUBST) &&
+ (how == FDT_UNUSED || (fdtable[i] & FDT_TYPE_MASK) == how)) {
+ if (i == SHTTY)
+ SHTTY = -1;
+ zclose(i);
+ }
+}
+
+/* convert here document into a here string */
+
+/**/
+char *
+gethere(char **strp, int typ)
+{
+ char *buf;
+ int bsiz, qt = 0, strip = 0;
+ char *s, *t, *bptr, c;
+ char *str = *strp;
+
+ for (s = str; *s; s++)
+ if (inull(*s)) {
+ qt = 1;
+ break;
+ }
+ str = quotesubst(str);
+ untokenize(str);
+ if (typ == REDIR_HEREDOCDASH) {
+ strip = 1;
+ while (*str == '\t')
+ str++;
+ }
+ *strp = str;
+ bptr = buf = zalloc(bsiz = 256);
+ for (;;) {
+ t = bptr;
+
+ while ((c = hgetc()) == '\t' && strip)
+ ;
+ for (;;) {
+ if (bptr >= buf + bsiz - 2) {
+ ptrdiff_t toff = t - buf;
+ ptrdiff_t bptroff = bptr - buf;
+ char *newbuf = realloc(buf, 2 * bsiz);
+ if (!newbuf) {
+ /* out of memory */
+ zfree(buf, bsiz);
+ return NULL;
+ }
+ buf = newbuf;
+ t = buf + toff;
+ bptr = buf + bptroff;
+ bsiz *= 2;
+ }
+ if (lexstop || c == '\n')
+ break;
+ if (!qt && c == '\\') {
+ *bptr++ = c;
+ c = hgetc();
+ if (c == '\n') {
+ bptr--;
+ c = hgetc();
+ continue;
+ }
+ }
+ *bptr++ = c;
+ c = hgetc();
+ }
+ *bptr = '\0';
+ if (!strcmp(t, str))
+ break;
+ if (lexstop) {
+ t = bptr;
+ break;
+ }
+ *bptr++ = '\n';
+ }
+ *t = '\0';
+ s = buf;
+ buf = dupstring(buf);
+ zfree(s, bsiz);
+ if (!qt) {
+ int ef = errflag;
+
+ parsestr(&buf);
+
+ if (!(errflag & ERRFLAG_ERROR)) {
+ /* Retain any user interrupt error */
+ errflag = ef | (errflag & ERRFLAG_INT);
+ }
+ }
+ return buf;
+}
+
+/* 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);
+ /*
+ * For real here-strings we append a newline, as if the
+ * string given was a complete command line.
+ *
+ * For here-strings from here documents, we use the original
+ * text exactly.
+ */
+ if (!(fn->flags & REDIRF_FROM_HEREDOC))
+ t[len++] = '\n';
+ if ((fd = gettempfile(NULL, 1, &s)) < 0)
+ return -1;
+ write_loop(fd, t, len);
+ close(fd);
+ fd = open(s, O_RDONLY | O_NOCTTY);
+ unlink(s);
+ return fd;
+}
+
+/*
+ * Test if some wordcode starts with a simple redirection of type
+ * redir_type. If it does, return the name of the file, copied onto
+ * the heap. If it doesn't, return NULL.
+ */
+
+static char *
+simple_redir_name(Eprog prog, int redir_type)
+{
+ Wordcode pc;
+
+ pc = prog->prog;
+ if (prog != &dummy_eprog &&
+ wc_code(pc[0]) == WC_LIST && (WC_LIST_TYPE(pc[0]) & Z_END) &&
+ wc_code(pc[1]) == WC_SUBLIST && !WC_SUBLIST_FLAGS(pc[1]) &&
+ WC_SUBLIST_TYPE(pc[1]) == WC_SUBLIST_END &&
+ wc_code(pc[2]) == WC_PIPE && WC_PIPE_TYPE(pc[2]) == WC_PIPE_END &&
+ wc_code(pc[3]) == WC_REDIR && WC_REDIR_TYPE(pc[3]) == redir_type &&
+ !WC_REDIR_VARID(pc[3]) &&
+ !pc[4] &&
+ wc_code(pc[6]) == WC_SIMPLE && !WC_SIMPLE_ARGC(pc[6])) {
+ return dupstring(ecrawstr(prog, pc + 5, NULL));
+ }
+
+ return NULL;
+}
+
+/* $(...) */
+
+/**/
+LinkList
+getoutput(char *cmd, int qt)
+{
+ Eprog prog;
+ int pipes[2];
+ pid_t pid;
+ char *s;
+
+ int onc = nocomments;
+ nocomments = (interact && unset(INTERACTIVECOMMENTS));
+ prog = parse_string(cmd, 0);
+ nocomments = onc;
+
+ if (!prog)
+ return NULL;
+
+ if ((s = simple_redir_name(prog, REDIR_READ))) {
+ /* $(< word) */
+ int stream;
+ LinkList retval;
+ int readerror;
+
+ singsub(&s);
+ if (errflag)
+ return NULL;
+ untokenize(s);
+ if ((stream = open(unmeta(s), O_RDONLY | O_NOCTTY)) == -1) {
+ zwarn("%e: %s", errno, s);
+ lastval = cmdoutval = 1;
+ return newlinklist();
+ }
+ retval = readoutput(stream, qt, &readerror);
+ if (readerror) {
+ zwarn("error when reading %s: %e", s, readerror);
+ lastval = cmdoutval = 1;
+ }
+ return retval;
+ }
+ if (mpipe(pipes) < 0) {
+ errflag |= ERRFLAG_ERROR;
+ cmdoutpid = 0;
+ return NULL;
+ }
+ child_block();
+ cmdoutval = 0;
+ if ((cmdoutpid = pid = zfork(NULL)) == -1) {
+ /* fork error */
+ zclose(pipes[0]);
+ zclose(pipes[1]);
+ errflag |= ERRFLAG_ERROR;
+ cmdoutpid = 0;
+ child_unblock();
+ return NULL;
+ } else if (pid) {
+ LinkList retval;
+
+ zclose(pipes[1]);
+ retval = readoutput(pipes[0], qt, NULL);
+ fdtable[pipes[0]] = FDT_UNUSED;
+ waitforpid(pid, 0); /* unblocks */
+ lastval = cmdoutval;
+ return retval;
+ }
+ /* pid == 0 */
+ child_unblock();
+ zclose(pipes[0]);
+ redup(pipes[1], 1);
+ entersubsh(ESUB_PGRP|ESUB_NOMONITOR);
+ cmdpush(CS_CMDSUBST);
+ execode(prog, 0, 1, "cmdsubst");
+ cmdpop();
+ close(1);
+ _exit(lastval);
+ zerr("exit returned in child!!");
+ kill(getpid(), SIGKILL);
+ return NULL;
+}
+
+/* read output of command substitution */
+
+/**/
+mod_export LinkList
+readoutput(int in, int qt, int *readerror)
+{
+ LinkList ret;
+ char *buf, *ptr;
+ int bsiz, c, cnt = 0;
+ FILE *fin;
+ int q = queue_signal_level();
+
+ fin = fdopen(in, "r");
+ ret = newlinklist();
+ ptr = buf = (char *) hcalloc(bsiz = 64);
+ /*
+ * We need to be sensitive to SIGCHLD else we can be
+ * stuck forever with important processes unreaped.
+ * The case that triggered this was where the exiting
+ * process is group leader of the foreground process and we need
+ * to reclaim the terminal else ^C doesn't work.
+ */
+ dont_queue_signals();
+ child_unblock();
+ 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;
+ queue_signals();
+ pp = (char *) hcalloc(bsiz *= 2);
+ dont_queue_signals();
+
+ memcpy(pp, buf, cnt - 1);
+ ptr = (buf = pp) + cnt - 1;
+ }
+ *ptr++ = c;
+ }
+ child_block();
+ restore_queue_signals(q);
+ if (readerror)
+ *readerror = ferror(fin) ? errno : 0;
+ 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, 1, 0);
+
+ while (*words) {
+ if (isset(GLOBSUBST))
+ shtokenize(*words);
+ addlinknode(ret, *words++);
+ }
+ }
+ return ret;
+}
+
+/**/
+static Eprog
+parsecmd(char *cmd, char **eptr)
+{
+ char *str;
+ Eprog prog;
+
+ for (str = cmd + 2; *str && *str != Outpar; str++);
+ if (!*str || cmd[1] != Inpar) {
+ /*
+ * This can happen if the expression is being parsed
+ * inside another construct, e.g. as a value within ${..:..} etc.
+ * So print a proper error message instead of the not very
+ * useful but traditional "oops".
+ */
+ char *errstr = dupstrpfx(cmd, 2);
+ untokenize(errstr);
+ zerr("unterminated `%s...)'", errstr);
+ return NULL;
+ }
+ *str = '\0';
+ if (eptr)
+ *eptr = str+1;
+ if (!(prog = parse_string(cmd + 2, 0))) {
+ zerr("parse error in process substitution");
+ return NULL;
+ }
+ return prog;
+}
+
+/* =(...) */
+
+/**/
+char *
+getoutputfile(char *cmd, char **eptr)
+{
+ pid_t pid;
+ char *nam;
+ Eprog prog;
+ int fd;
+ char *s;
+
+ if (thisjob == -1){
+ zerr("process substitution %s cannot be used here", cmd);
+ return NULL;
+ }
+ if (!(prog = parsecmd(cmd, eptr)))
+ return NULL;
+ if (!(nam = gettempname(NULL, 1)))
+ return NULL;
+
+ if ((s = simple_redir_name(prog, REDIR_HERESTR))) {
+ /*
+ * =(<<<stuff). Optimise a la $(<file). It's
+ * effectively the reverse, converting a string into a file name
+ * rather than vice versa.
+ */
+ singsub(&s);
+ if (errflag)
+ s = NULL;
+ else
+ untokenize(s);
+ }
+
+ if (!s) /* Unclear why we need to do this before open() */
+ child_block(); /* but it has been so for a long time: leave it */
+
+ if ((fd = open(nam, O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY, 0600)) < 0) {
+ zerr("process substitution failed: %e", errno);
+ free(nam);
+ if (!s)
+ child_unblock();
+ return NULL;
+ } else {
+ char *suffix = getsparam("TMPSUFFIX");
+ if (suffix && *suffix && !strstr(suffix, "/")) {
+ suffix = dyncat(nam, unmeta(suffix));
+ if (link(nam, suffix) == 0) {
+ addfilelist(nam, 0);
+ nam = suffix;
+ }
+ }
+ }
+ addfilelist(nam, 0);
+
+ if (s) {
+ /* optimised here-string */
+ int len;
+ unmetafy(s, &len);
+ write_loop(fd, s, len);
+ close(fd);
+ return nam;
+ }
+
+ if ((cmdoutpid = pid = zfork(NULL)) == -1) {
+ /* fork or open error */
+ child_unblock();
+ return nam;
+ } else if (pid) {
+ int os;
+
+ close(fd);
+ os = jobtab[thisjob].stat;
+ waitforpid(pid, 0);
+ cmdoutval = 0;
+ jobtab[thisjob].stat = os;
+ return nam;
+ }
+
+ /* pid == 0 */
+ redup(fd, 1);
+ entersubsh(ESUB_PGRP|ESUB_NOMONITOR);
+ cmdpush(CS_CMDSUBST);
+ execode(prog, 0, 1, "equalsubst");
+ cmdpop();
+ close(1);
+ _exit(lastval);
+ zerr("exit returned in child!!");
+ 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(NULL, 1);
+
+ if (!tnam) {
+ zerr("failed to create named pipe: %e", errno);
+ return NULL;
+ }
+# ifdef HAVE_MKFIFO
+ if (mkfifo(tnam, 0600) < 0){
+# else
+ if (mknod(tnam, 0010600, 0) < 0){
+# endif
+ zerr("failed to create named pipe: %s, %e", tnam, errno);
+ return NULL;
+ }
+ return tnam;
+}
+#endif /* ! PATH_DEV_FD && HAVE_FIFOS */
+
+/* <(...) or >(...) */
+
+/**/
+char *
+getproc(char *cmd, char **eptr)
+{
+#if !defined(HAVE_FIFOS) && !defined(PATH_DEV_FD)
+ zerr("doesn't look like your system supports FIFOs.");
+ return NULL;
+#else
+ Eprog prog;
+ int out = *cmd == Inang;
+ char *pnam;
+ pid_t pid;
+ struct timeval bgtime;
+
+#ifndef PATH_DEV_FD
+ int fd;
+ if (thisjob == -1) {
+ zerr("process substitution %s cannot be used here", cmd);
+ return NULL;
+ }
+ if (!(pnam = namedpipe()))
+ return NULL;
+ if (!(prog = parsecmd(cmd, eptr)))
+ return NULL;
+ addfilelist(pnam, 0);
+
+ if ((pid = zfork(&bgtime))) {
+ if (pid == -1)
+ return NULL;
+ if (!out)
+ addproc(pid, NULL, 1, &bgtime);
+ procsubstpid = pid;
+ return pnam;
+ }
+ closem(FDT_UNUSED, 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(ESUB_ASYNC|ESUB_PGRP);
+ redup(fd, out);
+#else /* PATH_DEV_FD */
+ int pipes[2], fd;
+
+ if (thisjob == -1) {
+ zerr("process substitution %s cannot be used here", cmd);
+ return NULL;
+ }
+ pnam = zhalloc(strlen(PATH_DEV_FD) + 1 + DIGBUFSIZE);
+ if (!(prog = parsecmd(cmd, eptr)))
+ return NULL;
+ if (mpipe(pipes) < 0)
+ return NULL;
+ if ((pid = zfork(&bgtime))) {
+ sprintf(pnam, "%s/%d", PATH_DEV_FD, pipes[!out]);
+ zclose(pipes[out]);
+ if (pid == -1)
+ {
+ zclose(pipes[!out]);
+ return NULL;
+ }
+ fd = pipes[!out];
+ fdtable[fd] = FDT_PROC_SUBST;
+ addfilelist(NULL, fd);
+ if (!out)
+ {
+ addproc(pid, NULL, 1, &bgtime);
+ }
+ procsubstpid = pid;
+ return pnam;
+ }
+ entersubsh(ESUB_ASYNC|ESUB_PGRP);
+ redup(pipes[out], out);
+ closem(FDT_UNUSED, 0); /* this closes pipes[!out] as well */
+#endif /* PATH_DEV_FD */
+
+ cmdpush(CS_CMDSUBST);
+ execode(prog, 0, 1, out ? "outsubst" : "insubst");
+ cmdpop();
+ zclose(out);
+ _exit(lastval);
+ return NULL;
+#endif /* HAVE_FIFOS and PATH_DEV_FD not defined */
+}
+
+/*
+ * > >(...) or < <(...) (does not use named pipes)
+ *
+ * If the second argument is 1, this is part of
+ * an "exec < <(...)" or "exec > >(...)" and we shouldn't
+ * wait for the job to finish before continuing.
+ */
+
+/**/
+static int
+getpipe(char *cmd, int nullexec)
+{
+ Eprog prog;
+ int pipes[2], out = *cmd == Inang;
+ pid_t pid;
+ struct timeval bgtime;
+ char *ends;
+
+ if (!(prog = parsecmd(cmd, &ends)))
+ return -1;
+ if (*ends) {
+ zerr("invalid syntax for process substitution in redirection");
+ return -1;
+ }
+ if (mpipe(pipes) < 0)
+ return -1;
+ if ((pid = zfork(&bgtime))) {
+ zclose(pipes[out]);
+ if (pid == -1) {
+ zclose(pipes[!out]);
+ return -1;
+ }
+ if (!nullexec)
+ addproc(pid, NULL, 1, &bgtime);
+ procsubstpid = pid;
+ return pipes[!out];
+ }
+ entersubsh(ESUB_PGRP);
+ redup(pipes[out], out);
+ closem(FDT_UNUSED, 0); /* this closes pipes[!out] as well */
+ cmdpush(CS_CMDSUBST);
+ execode(prog, 0, 1, out ? "outsubst" : "insubst");
+ cmdpop();
+ _exit(lastval);
+ return 0;
+}
+
+/* open pipes with fds >= 10 */
+
+/**/
+static int
+mpipe(int *pp)
+{
+ if (pipe(pp) < 0) {
+ zerr("pipe failed: %e", errno);
+ return -1;
+ }
+ pp[0] = movefd(pp[0]);
+ pp[1] = movefd(pp[1]);
+ return 0;
+}
+
+/*
+ * Do process substitution with redirection
+ *
+ * If the second argument is 1, this is part of
+ * an "exec < <(...)" or "exec > >(...)" and we shouldn't
+ * wait for the job to finish before continuing.
+ * Likewise, we shouldn't wait if we are opening the file
+ * descriptor using the {fd}>>(...) notation since it stays
+ * valid for subsequent commands.
+ */
+
+/**/
+static void
+spawnpipes(LinkList l, int nullexec)
+{
+ LinkNode n;
+ Redir f;
+ char *str;
+
+ n = firstnode(l);
+ for (; n; incnode(n)) {
+ f = (Redir) getdata(n);
+ if (f->type == REDIR_OUTPIPE || f->type == REDIR_INPIPE) {
+ str = f->name;
+ f->fd2 = getpipe(str, nullexec || f->varid);
+ }
+ }
+}
+
+/* evaluate a [[ ... ]] */
+
+/**/
+static int
+execcond(Estate state, UNUSED(int do_exec))
+{
+ int stat;
+
+ state->pc--;
+ if (isset(XTRACE)) {
+ printprompt4();
+ fprintf(xtrerr, "[[");
+ tracingcond++;
+ }
+ cmdpush(CS_COND);
+ stat = evalcond(state, NULL);
+ /*
+ * 2 indicates a syntax error. For compatibility, turn this
+ * into a shell error.
+ */
+ if (stat == 2)
+ errflag |= ERRFLAG_ERROR;
+ cmdpop();
+ if (isset(XTRACE)) {
+ fprintf(xtrerr, " ]]\n");
+ fflush(xtrerr);
+ tracingcond--;
+ }
+ return stat;
+}
+
+/* evaluate a ((...)) arithmetic command */
+
+/**/
+static int
+execarith(Estate state, UNUSED(int do_exec))
+{
+ char *e;
+ mnumber val = zero_mnumber;
+ int htok = 0;
+
+ if (isset(XTRACE)) {
+ printprompt4();
+ fprintf(xtrerr, "((");
+ }
+ cmdpush(CS_MATH);
+ e = ecgetstr(state, EC_DUPTOK, &htok);
+ if (htok)
+ singsub(&e);
+ if (isset(XTRACE))
+ fprintf(xtrerr, " %s", e);
+
+ val = matheval(e);
+
+ cmdpop();
+
+ if (isset(XTRACE)) {
+ fprintf(xtrerr, " ))\n");
+ fflush(xtrerr);
+ }
+ if (errflag) {
+ errflag &= ~ERRFLAG_ERROR;
+ return 2;
+ }
+ /* should test for fabs(val.u.d) < epsilon? */
+ return (val.type == MN_INTEGER) ? val.u.l == 0 : val.u.d == 0.0;
+}
+
+/* perform time ... command */
+
+/**/
+static int
+exectime(Estate state, UNUSED(int do_exec))
+{
+ int jb;
+
+ jb = thisjob;
+ if (WC_TIMED_TYPE(state->pc[-1]) == WC_TIMED_EMPTY) {
+ shelltime();
+ return 0;
+ }
+ execpline(state, *state->pc++, Z_TIMED|Z_SYNC, 0);
+ thisjob = jb;
+ return lastval;
+}
+
+/* Define a shell function */
+
+static const char *const ANONYMOUS_FUNCTION_NAME = "(anon)";
+
+/**/
+static int
+execfuncdef(Estate state, Eprog redir_prog)
+{
+ Shfunc shf;
+ char *s = NULL;
+ int signum, nprg, sbeg, nstrs, npats, len, plen, i, htok = 0, ret = 0;
+ int anon_func = 0;
+ Wordcode beg = state->pc, end;
+ Eprog prog;
+ Patprog *pp;
+ LinkList names;
+
+ end = beg + WC_FUNCDEF_SKIP(state->pc[-1]);
+ names = ecgetlist(state, *state->pc++, EC_DUPTOK, &htok);
+ nprg = end - beg;
+ sbeg = *state->pc++;
+ nstrs = *state->pc++;
+ npats = *state->pc++;
+
+ nprg = (end - state->pc);
+ plen = nprg * sizeof(wordcode);
+ len = plen + (npats * sizeof(Patprog)) + nstrs;
+
+ if (htok && names) {
+ execsubst(names);
+ if (errflag) {
+ state->pc = end;
+ return 1;
+ }
+ }
+
+ DPUTS(!names && redir_prog,
+ "Passing redirection to anon function definition.");
+ while (!names || (s = (char *) ugetnode(names))) {
+ if (!names) {
+ prog = (Eprog) zhalloc(sizeof(*prog));
+ prog->nref = -1; /* on the heap */
+ } else {
+ prog = (Eprog) zalloc(sizeof(*prog));
+ prog->nref = 1; /* allocated from permanent storage */
+ }
+ prog->npats = npats;
+ prog->len = len;
+ if (state->prog->dump || !names) {
+ if (!names) {
+ prog->flags = EF_HEAP;
+ prog->dump = NULL;
+ prog->pats = pp = (Patprog *) zhalloc(npats * sizeof(Patprog));
+ } else {
+ prog->flags = EF_MAP;
+ incrdumpcount(state->prog->dump);
+ prog->dump = state->prog->dump;
+ prog->pats = pp = (Patprog *) zalloc(npats * sizeof(Patprog));
+ }
+ prog->prog = state->pc;
+ prog->strs = state->strs + sbeg;
+ } else {
+ prog->flags = EF_REAL;
+ prog->pats = pp = (Patprog *) zalloc(len);
+ prog->prog = (Wordcode) (prog->pats + npats);
+ prog->strs = (char *) (prog->prog + nprg);
+ prog->dump = NULL;
+ memcpy(prog->prog, state->pc, plen);
+ memcpy(prog->strs, state->strs + sbeg, nstrs);
+ }
+ for (i = npats; i--; pp++)
+ *pp = dummy_patprog1;
+ prog->shf = NULL;
+
+ shf = (Shfunc) zalloc(sizeof(*shf));
+ shf->funcdef = prog;
+ shf->node.flags = 0;
+ /* No dircache here, not a directory */
+ shf->filename = ztrdup(scriptfilename);
+ shf->lineno =
+ (funcstack && (funcstack->tp == FS_FUNC ||
+ funcstack->tp == FS_EVAL)) ?
+ funcstack->flineno + lineno :
+ lineno;
+ /*
+ * redir_prog is permanently allocated --- but if
+ * this function has multiple names we need an additional
+ * one. Original redir_prog used with the last name
+ * because earlier functions are freed in case of duplicate
+ * names.
+ */
+ if (names && nonempty(names) && redir_prog)
+ shf->redir = dupeprog(redir_prog, 0);
+ else {
+ shf->redir = redir_prog;
+ redir_prog = 0;
+ }
+ shfunc_set_sticky(shf);
+
+ if (!names) {
+ /*
+ * Anonymous function, execute immediately.
+ * Function name is "(anon)".
+ */
+ LinkList args;
+
+ anon_func = 1;
+ shf->node.flags |= PM_ANONYMOUS;
+
+ state->pc = end;
+ end += *state->pc++;
+ args = ecgetlist(state, *state->pc++, EC_DUPTOK, &htok);
+
+ if (htok && args) {
+ execsubst(args);
+ if (errflag) {
+ freeeprog(shf->funcdef);
+ if (shf->redir) /* shouldn't be */
+ freeeprog(shf->redir);
+ dircache_set(&shf->filename, NULL);
+ zfree(shf, sizeof(*shf));
+ state->pc = end;
+ return 1;
+ }
+ }
+
+ setunderscore((args && nonempty(args)) ?
+ ((char *) getdata(lastnode(args))) : "");
+
+ if (!args)
+ args = newlinklist();
+ shf->node.nam = (char *) ANONYMOUS_FUNCTION_NAME;
+ pushnode(args, shf->node.nam);
+
+ execshfunc(shf, args);
+ ret = lastval;
+
+ if (isset(PRINTEXITVALUE) && isset(SHINSTDIN) &&
+ lastval) {
+#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
+ fprintf(stderr, "zsh: exit %lld\n", lastval);
+#else
+ fprintf(stderr, "zsh: exit %ld\n", (long)lastval);
+#endif
+ fflush(stderr);
+ }
+
+ freeeprog(shf->funcdef);
+ if (shf->redir) /* shouldn't be */
+ freeeprog(shf->redir);
+ dircache_set(&shf->filename, NULL);
+ zfree(shf, sizeof(*shf));
+ break;
+ } else {
+ /* is this shell function a signal trap? */
+ if (!strncmp(s, "TRAP", 4) &&
+ (signum = getsignum(s + 4)) != -1) {
+ if (settrap(signum, NULL, ZSIG_FUNC)) {
+ freeeprog(shf->funcdef);
+ dircache_set(&shf->filename, NULL);
+ zfree(shf, sizeof(*shf));
+ state->pc = end;
+ return 1;
+ }
+
+ /*
+ * Remove the old node explicitly in case it has
+ * an alternative name
+ */
+ removetrapnode(signum);
+ }
+ shfunctab->addnode(shfunctab, ztrdup(s), shf);
+ }
+ }
+ if (!anon_func)
+ setunderscore("");
+ if (redir_prog) {
+ /* For completeness, shouldn't happen */
+ freeeprog(redir_prog);
+ }
+ state->pc = end;
+ return ret;
+}
+
+/* Duplicate a sticky emulation */
+
+/**/
+
+mod_export Emulation_options
+sticky_emulation_dup(Emulation_options src, int useheap)
+{
+ Emulation_options newsticky = useheap ?
+ hcalloc(sizeof(*src)) : zshcalloc(sizeof(*src));
+ newsticky->emulation = src->emulation;
+ if (src->n_on_opts) {
+ size_t sz = src->n_on_opts * sizeof(*src->on_opts);
+ newsticky->n_on_opts = src->n_on_opts;
+ newsticky->on_opts = useheap ? zhalloc(sz) : zalloc(sz);
+ memcpy(newsticky->on_opts, src->on_opts, sz);
+ }
+ if (src->n_off_opts) {
+ size_t sz = src->n_off_opts * sizeof(*src->off_opts);
+ newsticky->n_off_opts = src->n_off_opts;
+ newsticky->off_opts = useheap ? zhalloc(sz) : zalloc(sz);
+ memcpy(newsticky->off_opts, src->off_opts, sz);
+ }
+
+ return newsticky;
+}
+
+/* Set the sticky emulation attributes for a shell function */
+
+/**/
+
+mod_export void
+shfunc_set_sticky(Shfunc shf)
+{
+ if (sticky)
+ shf->sticky = sticky_emulation_dup(sticky, 0);
+ else
+ shf->sticky = NULL;
+}
+
+
+/* Main entry point to execute a shell function. */
+
+/**/
+static void
+execshfunc(Shfunc shf, LinkList args)
+{
+ LinkList last_file_list = NULL;
+ unsigned char *ocs;
+ int ocsp, osfc;
+
+ if (errflag)
+ return;
+
+ /* thisjob may be invalid if we're called via execsimple: see execcursh */
+ if (!list_pipe && thisjob != -1 && thisjob != list_pipe_job &&
+ !hasprocs(thisjob)) {
+ /* 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, 0);
+ }
+
+ if (isset(XTRACE)) {
+ LinkNode lptr;
+ printprompt4();
+ if (args)
+ for (lptr = firstnode(args); lptr; incnode(lptr)) {
+ if (lptr != firstnode(args))
+ fputc(' ', xtrerr);
+ quotedzputs((char *)getdata(lptr), xtrerr);
+ }
+ fputc('\n', xtrerr);
+ fflush(xtrerr);
+ }
+ queue_signals();
+ ocs = cmdstack;
+ ocsp = cmdsp;
+ cmdstack = (unsigned char *) zalloc(CMDSTACKSZ);
+ cmdsp = 0;
+ if ((osfc = sfcontext) == SFC_NONE)
+ sfcontext = SFC_DIRECT;
+ xtrerr = stderr;
+
+ doshfunc(shf, args, 0);
+
+ sfcontext = osfc;
+ free(cmdstack);
+ cmdstack = ocs;
+ cmdsp = ocsp;
+
+ if (!list_pipe)
+ deletefilelist(last_file_list, 0);
+ unqueue_signals();
+}
+
+/*
+ * 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.
+ *
+ * There are two cases because if we are doing all-singing, all-dancing
+ * non-simple code we load the shell function early in execcmd() (the
+ * action also present in the non-basic version) to check if
+ * there are redirections that need to be handled at that point.
+ * Then we call execautofn_basic() to do the rest.
+ */
+
+/**/
+static int
+execautofn_basic(Estate state, UNUSED(int do_exec))
+{
+ Shfunc shf;
+ char *oldscriptname, *oldscriptfilename;
+
+ shf = state->prog->shf;
+
+ /*
+ * Probably we didn't know the filename where this function was
+ * defined yet.
+ */
+ if (funcstack && !funcstack->filename)
+ funcstack->filename = getshfuncfile(shf);
+
+ oldscriptname = scriptname;
+ oldscriptfilename = scriptfilename;
+ scriptname = dupstring(shf->node.nam);
+ scriptfilename = getshfuncfile(shf);
+ execode(shf->funcdef, 1, 0, "loadautofunc");
+ scriptname = oldscriptname;
+ scriptfilename = oldscriptfilename;
+
+ return lastval;
+}
+
+/**/
+static int
+execautofn(Estate state, UNUSED(int do_exec))
+{
+ Shfunc shf;
+
+ if (!(shf = loadautofn(state->prog->shf, 1, 0, 0)))
+ return 1;
+
+ state->prog->shf = shf;
+ return execautofn_basic(state, 0);
+}
+
+/*
+ * Helper function to install the source file name of a shell function
+ * just autoloaded.
+ *
+ * We attempt to do this efficiently as the typical case is the
+ * directory part is a well-known directory, which is cached, and
+ * the non-directory part is the same as the node name.
+ */
+
+/**/
+static void
+loadautofnsetfile(Shfunc shf, char *fdir)
+{
+ /*
+ * If shf->filename is already the load directory ---
+ * keep it as we can still use it to get the load file.
+ * This makes autoload with an absolute path particularly efficient.
+ */
+ if (!(shf->node.flags & PM_LOADDIR) ||
+ strcmp(shf->filename, fdir) != 0) {
+ /* Old directory name not useful... */
+ dircache_set(&shf->filename, NULL);
+ if (fdir) {
+ /* ...can still cache directory */
+ shf->node.flags |= PM_LOADDIR;
+ dircache_set(&shf->filename, fdir);
+ } else {
+ /* ...no separate directory part to cache, for some reason. */
+ shf->node.flags &= ~PM_LOADDIR;
+ shf->filename = ztrdup(shf->node.nam);
+ }
+ }
+}
+
+/**/
+Shfunc
+loadautofn(Shfunc shf, int fksh, int autol, int current_fpath)
+{
+ int noalias = noaliases, ksh = 1;
+ Eprog prog;
+ char *fdir; /* Directory path where func found */
+
+ pushheap();
+
+ noaliases = (shf->node.flags & PM_UNALIASED);
+ if (shf->filename && shf->filename[0] == '/' &&
+ (shf->node.flags & PM_LOADDIR))
+ {
+ char *spec_path[2];
+ spec_path[0] = dupstring(shf->filename);
+ spec_path[1] = NULL;
+ prog = getfpfunc(shf->node.nam, &ksh, &fdir, spec_path, 0);
+ if (prog == &dummy_eprog &&
+ (current_fpath || (shf->node.flags & PM_CUR_FPATH)))
+ prog = getfpfunc(shf->node.nam, &ksh, &fdir, NULL, 0);
+ }
+ else
+ prog = getfpfunc(shf->node.nam, &ksh, &fdir, NULL, 0);
+ noaliases = noalias;
+
+ if (ksh == 1) {
+ ksh = fksh;
+ if (ksh == 1)
+ ksh = (shf->node.flags & PM_KSHSTORED) ? 2 :
+ (shf->node.flags & PM_ZSHSTORED) ? 0 : 1;
+ }
+
+ if (prog == &dummy_eprog) {
+ /* We're not actually in the function; decrement locallevel */
+ locallevel--;
+ zwarn("%s: function definition file not found", shf->node.nam);
+ locallevel++;
+ popheap();
+ return NULL;
+ }
+ if (!prog) {
+ popheap();
+ return NULL;
+ }
+ if (ksh == 2 || (ksh == 1 && isset(KSHAUTOLOAD))) {
+ if (autol) {
+ prog->flags |= EF_RUN;
+
+ freeeprog(shf->funcdef);
+ if (prog->flags & EF_MAP)
+ shf->funcdef = prog;
+ else
+ shf->funcdef = dupeprog(prog, 0);
+ shf->node.flags &= ~PM_UNDEFINED;
+ loadautofnsetfile(shf, fdir);
+ } else {
+ VARARR(char, n, strlen(shf->node.nam) + 1);
+ strcpy(n, shf->node.nam);
+ execode(prog, 1, 0, "evalautofunc");
+ shf = (Shfunc) shfunctab->getnode(shfunctab, n);
+ if (!shf || (shf->node.flags & PM_UNDEFINED)) {
+ /* We're not actually in the function; decrement locallevel */
+ locallevel--;
+ zwarn("%s: function not defined by file", n);
+ locallevel++;
+ popheap();
+ return NULL;
+ }
+ }
+ } else {
+ freeeprog(shf->funcdef);
+ if (prog->flags & EF_MAP)
+ shf->funcdef = stripkshdef(prog, shf->node.nam);
+ else
+ shf->funcdef = dupeprog(stripkshdef(prog, shf->node.nam), 0);
+ shf->node.flags &= ~PM_UNDEFINED;
+ loadautofnsetfile(shf, fdir);
+ }
+ popheap();
+
+ return shf;
+}
+
+/*
+ * Check if a sticky emulation differs from the current one.
+ */
+
+/**/
+
+int sticky_emulation_differs(Emulation_options sticky2)
+{
+ /* If no new sticky emulation, not a different emulation */
+ if (!sticky2)
+ return 0;
+ /* If no current sticky emulation, different */
+ if (!sticky)
+ return 1;
+ /* If basic emulation different, different */
+ if (sticky->emulation != sticky2->emulation)
+ return 1;
+ /* If differing numbers of options, different */
+ if (sticky->n_on_opts != sticky2->n_on_opts ||
+ sticky->n_off_opts != sticky2->n_off_opts)
+ return 1;
+ /*
+ * We need to compare option arrays, if non-null.
+ * We made parseopts() create the list of options in option
+ * order to make this easy.
+ */
+ /* If different options turned on, different */
+ if (sticky->n_on_opts &&
+ memcmp(sticky->on_opts, sticky2->on_opts,
+ sticky->n_on_opts * sizeof(*sticky->on_opts)) != 0)
+ return 1;
+ /* If different options turned on, different */
+ if (sticky->n_off_opts &&
+ memcmp(sticky->off_opts, sticky2->off_opts,
+ sticky->n_off_opts * sizeof(*sticky->off_opts)) != 0)
+ return 1;
+ return 0;
+}
+
+/*
+ * execute a shell function
+ *
+ * name is the name of the function
+ *
+ * prog is the code to execute
+ *
+ * doshargs, if set, are parameters to pass to the function,
+ * in which the first element is the function name (even if
+ * FUNCTIONARGZERO is set as this is handled inside this function).
+ *
+ * If noreturnval is nonzero, then reset the current return
+ * value (lastval) to its value before the shell function
+ * was executed. However, in any case return the status value
+ * from the function (i.e. if noreturnval is not set, this
+ * will be the same as lastval).
+ */
+
+/**/
+mod_export int
+doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval)
+{
+ char **pptab, **x;
+ int ret;
+ char *name = shfunc->node.nam;
+ int flags = shfunc->node.flags;
+ char *fname = dupstring(name);
+ Eprog prog;
+ static int oflags;
+ static int funcdepth;
+ Heap funcheap;
+
+ queue_signals(); /* Lots of memory and global state changes coming */
+
+ NEWHEAPS(funcheap) {
+ /*
+ * Save data in heap rather than on stack to keep recursive
+ * function cost down --- use of heap memory should be efficient
+ * at this point. Saving is not actually massive.
+ */
+ Funcsave funcsave = zhalloc(sizeof(struct funcsave));
+ funcsave->scriptname = scriptname;
+ funcsave->argv0 = NULL;
+ funcsave->breaks = breaks;
+ funcsave->contflag = contflag;
+ funcsave->loops = loops;
+ funcsave->lastval = lastval;
+ funcsave->pipestats = NULL;
+ funcsave->numpipestats = numpipestats;
+ funcsave->noerrexit = noerrexit;
+ if (trap_state == TRAP_STATE_PRIMED)
+ trap_return--;
+ /*
+ * Suppression of ERR_RETURN is turned off in function scope.
+ */
+ noerrexit &= ~NOERREXIT_RETURN;
+ if (noreturnval) {
+ /*
+ * Easiest to use the heap here since we're bracketed
+ * immediately by a pushheap/popheap pair.
+ */
+ size_t bytes = sizeof(int)*numpipestats;
+ funcsave->pipestats = (int *)zhalloc(bytes);
+ memcpy(funcsave->pipestats, pipestats, bytes);
+ }
+
+ starttrapscope();
+ startpatternscope();
+
+ pptab = pparams;
+ if (!(flags & PM_UNDEFINED))
+ scriptname = dupstring(name);
+ funcsave->zoptind = zoptind;
+ funcsave->optcind = optcind;
+ if (!isset(POSIXBUILTINS)) {
+ zoptind = 1;
+ optcind = 0;
+ }
+
+ /* 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(funcsave->opts, opts, sizeof(opts));
+ funcsave->emulation = emulation;
+ funcsave->sticky = sticky;
+
+ if (sticky_emulation_differs(shfunc->sticky)) {
+ /*
+ * Function is marked for sticky emulation.
+ * Enable it now.
+ *
+ * We deliberately do not do this if the sticky emulation
+ * in effect is the same as that requested. This enables
+ * option setting naturally within emulation environments.
+ * Note that a difference in EMULATE_FULLY (emulate with
+ * or without -R) counts as a different environment.
+ *
+ * This propagates the sticky emulation to subfunctions.
+ */
+ sticky = sticky_emulation_dup(shfunc->sticky, 1);
+ emulation = sticky->emulation;
+ funcsave->restore_sticky = 1;
+ installemulation(emulation, opts);
+ if (sticky->n_on_opts) {
+ OptIndex *onptr;
+ for (onptr = sticky->on_opts;
+ onptr < sticky->on_opts + sticky->n_on_opts;
+ onptr++)
+ opts[*onptr] = 1;
+ }
+ if (sticky->n_off_opts) {
+ OptIndex *offptr;
+ for (offptr = sticky->off_opts;
+ offptr < sticky->off_opts + sticky->n_off_opts;
+ offptr++)
+ opts[*offptr] = 0;
+ }
+ /* All emulations start with pattern disables clear */
+ clearpatterndisables();
+ } else
+ funcsave->restore_sticky = 0;
+
+ if (flags & (PM_TAGGED|PM_TAGGED_LOCAL))
+ opts[XTRACE] = 1;
+ else if (oflags & PM_TAGGED_LOCAL) {
+ if (shfunc->node.nam == ANONYMOUS_FUNCTION_NAME /* pointer comparison */)
+ flags |= PM_TAGGED_LOCAL;
+ else
+ opts[XTRACE] = 0;
+ }
+ if (flags & PM_WARNNESTED)
+ opts[WARNNESTEDVAR] = 1;
+ else if (oflags & PM_WARNNESTED) {
+ if (shfunc->node.nam == ANONYMOUS_FUNCTION_NAME)
+ flags |= PM_WARNNESTED;
+ else
+ opts[WARNNESTEDVAR] = 0;
+ }
+ funcsave->oflags = oflags;
+ /*
+ * oflags is static, because we compare it on the next recursive
+ * call. Hence also we maintain a saved version for restoring
+ * the previous value of oflags after the call.
+ */
+ oflags = flags;
+ opts[PRINTEXITVALUE] = 0;
+ if (doshargs) {
+ LinkNode node;
+
+ node = firstnode(doshargs);
+ pparams = x = (char **) zshcalloc(((sizeof *x) *
+ (1 + countlinknodes(doshargs))));
+ if (isset(FUNCTIONARGZERO)) {
+ funcsave->argv0 = argzero;
+ argzero = ztrdup(getdata(node));
+ }
+ /* first node contains name regardless of option */
+ node = node->next;
+ for (; node; node = node->next, x++)
+ *x = ztrdup(getdata(node));
+ } else {
+ pparams = (char **) zshcalloc(sizeof *pparams);
+ if (isset(FUNCTIONARGZERO)) {
+ funcsave->argv0 = argzero;
+ argzero = ztrdup(argzero);
+ }
+ }
+ ++funcdepth;
+ if (zsh_funcnest >= 0 && funcdepth > zsh_funcnest) {
+ zerr("maximum nested function level reached; increase FUNCNEST?");
+ lastval = 1;
+ goto undoshfunc;
+ }
+ funcsave->fstack.name = dupstring(name);
+ /*
+ * The caller is whatever is immediately before on the stack,
+ * unless we're at the top, in which case it's the script
+ * or interactive shell name.
+ */
+ funcsave->fstack.caller = funcstack ? funcstack->name :
+ dupstring(funcsave->argv0 ? funcsave->argv0 : argzero);
+ funcsave->fstack.lineno = lineno;
+ funcsave->fstack.prev = funcstack;
+ funcsave->fstack.tp = FS_FUNC;
+ funcstack = &funcsave->fstack;
+
+ funcsave->fstack.flineno = shfunc->lineno;
+ funcsave->fstack.filename = getshfuncfile(shfunc);
+
+ prog = shfunc->funcdef;
+ if (prog->flags & EF_RUN) {
+ Shfunc shf;
+
+ prog->flags &= ~EF_RUN;
+
+ runshfunc(prog, NULL, funcsave->fstack.name);
+
+ if (!(shf = (Shfunc) shfunctab->getnode(shfunctab,
+ (name = fname)))) {
+ zwarn("%s: function not defined by file", name);
+ if (noreturnval)
+ errflag |= ERRFLAG_ERROR;
+ else
+ lastval = 1;
+ goto doneshfunc;
+ }
+ prog = shf->funcdef;
+ }
+ runshfunc(prog, wrappers, funcsave->fstack.name);
+ doneshfunc:
+ funcstack = funcsave->fstack.prev;
+ undoshfunc:
+ --funcdepth;
+ if (retflag) {
+ /*
+ * This function is forced to return.
+ */
+ retflag = 0;
+ /*
+ * The calling function isn't necessarily forced to return,
+ * but it should be made sensitive to ERR_EXIT and
+ * ERR_RETURN as the assumptions we made at the end of
+ * constructs within this function no longer apply. If
+ * there are cases where this is not true, they need adding
+ * to C03traps.ztst.
+ */
+ this_noerrexit = 0;
+ breaks = funcsave->breaks;
+ }
+ freearray(pparams);
+ if (funcsave->argv0) {
+ zsfree(argzero);
+ argzero = funcsave->argv0;
+ }
+ pparams = pptab;
+ if (!isset(POSIXBUILTINS)) {
+ zoptind = funcsave->zoptind;
+ optcind = funcsave->optcind;
+ }
+ scriptname = funcsave->scriptname;
+ oflags = funcsave->oflags;
+
+ endpatternscope(); /* before restoring old LOCALPATTERNS */
+
+ if (funcsave->restore_sticky) {
+ /*
+ * If we switched to an emulation environment just for
+ * this function, we interpret the option and emulation
+ * switch as being a firewall between environments.
+ */
+ memcpy(opts, funcsave->opts, sizeof(opts));
+ emulation = funcsave->emulation;
+ sticky = funcsave->sticky;
+ } else if (isset(LOCALOPTIONS)) {
+ /* restore all shell options except PRIVILEGED and RESTRICTED */
+ funcsave->opts[PRIVILEGED] = opts[PRIVILEGED];
+ funcsave->opts[RESTRICTED] = opts[RESTRICTED];
+ memcpy(opts, funcsave->opts, sizeof(opts));
+ emulation = funcsave->emulation;
+ } else {
+ /* just restore a couple. */
+ opts[XTRACE] = funcsave->opts[XTRACE];
+ opts[PRINTEXITVALUE] = funcsave->opts[PRINTEXITVALUE];
+ opts[LOCALOPTIONS] = funcsave->opts[LOCALOPTIONS];
+ opts[LOCALLOOPS] = funcsave->opts[LOCALLOOPS];
+ opts[WARNNESTEDVAR] = funcsave->opts[WARNNESTEDVAR];
+ }
+
+ if (opts[LOCALLOOPS]) {
+ if (contflag)
+ zwarn("`continue' active at end of function scope");
+ if (breaks)
+ zwarn("`break' active at end of function scope");
+ breaks = funcsave->breaks;
+ contflag = funcsave->contflag;
+ loops = funcsave->loops;
+ }
+
+ endtrapscope();
+
+ if (trap_state == TRAP_STATE_PRIMED)
+ trap_return++;
+ ret = lastval;
+ noerrexit = funcsave->noerrexit;
+ if (noreturnval) {
+ lastval = funcsave->lastval;
+ numpipestats = funcsave->numpipestats;
+ memcpy(pipestats, funcsave->pipestats, sizeof(int)*numpipestats);
+ }
+ } OLDHEAPS;
+
+ unqueue_signals();
+
+ /*
+ * Exit with a tidy up.
+ * Only leave if we're at the end of the appropriate function ---
+ * not a nested function. As we usually skip the function body,
+ * the only likely case where we need that second test is
+ * when we have an "always" block. The endparamscope() has
+ * already happened, hence the "+1" here.
+ *
+ * If we are in an exit trap, finish it first... we wouldn't set
+ * exit_pending if we were already in one.
+ */
+ if (exit_pending && exit_level >= locallevel+1 && !in_exit_trap) {
+ if (locallevel > forklevel) {
+ /* Still functions to return: force them to do so. */
+ retflag = 1;
+ breaks = loops;
+ } else {
+ /*
+ * All functions finished: time to exit the shell.
+ * We already did the `stopmsg' test when the
+ * exit command was handled.
+ */
+ stopmsg = 1;
+ zexit(exit_pending >> 1, 0);
+ }
+ }
+
+ return ret;
+}
+
+/* This finally executes a shell function and any function wrappers *
+ * defined by modules. This works by calling the wrapper function which *
+ * in turn has to call back this function with the arguments it gets. */
+
+/**/
+mod_export void
+runshfunc(Eprog prog, FuncWrap wrap, char *name)
+{
+ int cont, ouu;
+ char *ou;
+
+ queue_signals();
+
+ ou = zalloc(ouu = underscoreused);
+ if (ou)
+ memcpy(ou, zunderscore, underscoreused);
+
+ while (wrap) {
+ wrap->module->wrapper++;
+ cont = wrap->handler(prog, wrap->next, name);
+ wrap->module->wrapper--;
+
+ if (!wrap->module->wrapper &&
+ (wrap->module->node.flags & MOD_UNLOAD))
+ unload_module(wrap->module);
+
+ if (!cont) {
+ if (ou)
+ zfree(ou, ouu);
+ unqueue_signals();
+ return;
+ }
+ wrap = wrap->next;
+ }
+ startparamscope();
+ execode(prog, 1, 0, "shfunc"); /* handles signal unqueueing */
+ if (ou) {
+ setunderscore(ou);
+ zfree(ou, ouu);
+ }
+ endparamscope();
+
+ unqueue_signals();
+}
+
+/*
+ * Search fpath for an undefined function. Finds the file, and returns the
+ * list of its contents.
+ *
+ * If test is 0, load the function.
+ *
+ * If test_only is 1, don't load function, just test for it:
+ * Non-null return means function was found
+ *
+ * *fdir points to path at which found (as passed in, not duplicated)
+ */
+
+/**/
+Eprog
+getfpfunc(char *s, int *ksh, char **fdir, char **alt_path, int test_only)
+{
+ char **pp, buf[PATH_MAX+1];
+ off_t len;
+ off_t rlen;
+ char *d;
+ Eprog r;
+ int fd;
+
+ pp = alt_path ? alt_path : 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);
+ if ((r = try_dump_file(*pp, s, buf, ksh, test_only))) {
+ if (fdir)
+ *fdir = *pp;
+ return r;
+ }
+ unmetafy(buf, NULL);
+ if (!access(buf, R_OK) && (fd = open(buf, O_RDONLY | O_NOCTTY)) != -1) {
+ struct stat st;
+ if (!fstat(fd, &st) && S_ISREG(st.st_mode) &&
+ (len = lseek(fd, 0, 2)) != -1) {
+ if (test_only) {
+ close(fd);
+ if (fdir)
+ *fdir = *pp;
+ return &dummy_eprog;
+ }
+ d = (char *) zalloc(len + 1);
+ lseek(fd, 0, 0);
+ if ((rlen = read(fd, d, len)) >= 0) {
+ char *oldscriptname = scriptname;
+
+ close(fd);
+ d[rlen] = '\0';
+ d = metafy(d, rlen, META_REALLOC);
+
+ scriptname = dupstring(s);
+ r = parse_string(d, 1);
+ scriptname = oldscriptname;
+
+ if (fdir)
+ *fdir = *pp;
+
+ zfree(d, len + 1);
+
+ return r;
+ } else
+ close(fd);
+
+ zfree(d, len + 1);
+ } else
+ close(fd);
+ }
+ }
+ return test_only ? NULL : &dummy_eprog;
+}
+
+/* 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. */
+
+/**/
+Eprog
+stripkshdef(Eprog prog, char *name)
+{
+ Wordcode pc;
+ wordcode code;
+ char *ptr1, *ptr2;
+
+ if (!prog)
+ return NULL;
+ pc = prog->prog;
+ code = *pc++;
+ if (wc_code(code) != WC_LIST ||
+ (WC_LIST_TYPE(code) & (Z_SYNC|Z_END|Z_SIMPLE)) != (Z_SYNC|Z_END|Z_SIMPLE))
+ return prog;
+ pc++;
+ code = *pc++;
+ if (wc_code(code) != WC_FUNCDEF || *pc != 1)
+ return prog;
+
+ /*
+ * See if name of function requested (name) is same as
+ * name of function in word code. name may still have "-"
+ * tokenised. The word code shouldn't, as function names should be
+ * untokenised, but reports say it sometimes does.
+ */
+ ptr1 = name;
+ ptr2 = ecrawstr(prog, pc + 1, NULL);
+ while (*ptr1 && *ptr2) {
+ if (*ptr1 != *ptr2 && *ptr1 != Dash && *ptr1 != '-' &&
+ *ptr2 != Dash && *ptr2 != '-')
+ break;
+ ptr1++;
+ ptr2++;
+ }
+ if (*ptr1 || *ptr2)
+ return prog;
+
+ {
+ Eprog ret;
+ Wordcode end = pc + WC_FUNCDEF_SKIP(code);
+ int sbeg = pc[2], nstrs = pc[3], nprg, npats = pc[4], plen, len, i;
+ Patprog *pp;
+
+ pc += 5;
+
+ nprg = end - pc;
+ plen = nprg * sizeof(wordcode);
+ len = plen + (npats * sizeof(Patprog)) + nstrs;
+
+ if (prog->flags & EF_MAP) {
+ ret = prog;
+ free(prog->pats);
+ ret->pats = pp = (Patprog *) zalloc(npats * sizeof(Patprog));
+ ret->prog = pc;
+ ret->strs = prog->strs + sbeg;
+ } else {
+ ret = (Eprog) zhalloc(sizeof(*ret));
+ ret->flags = EF_HEAP;
+ ret->pats = pp = (Patprog *) zhalloc(len);
+ ret->prog = (Wordcode) (ret->pats + npats);
+ ret->strs = (char *) (ret->prog + nprg);
+ memcpy(ret->prog, pc, plen);
+ memcpy(ret->strs, prog->strs + sbeg, nstrs);
+ ret->dump = NULL;
+ }
+ ret->len = len;
+ ret->npats = npats;
+ for (i = npats; i--; pp++)
+ *pp = dummy_patprog1;
+ ret->shf = NULL;
+
+ return ret;
+ }
+}
+
+/* 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+1], **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, *us2 = NULL;
+ int ret;
+
+ /*
+ * If CHASEDOTS and CHASELINKS are not set, we want to rationalize the
+ * path by removing foo/.. combinations in the logical rather than
+ * the physical path. If either is set, we test the physical path.
+ */
+ if (!isset(CHASEDOTS) && !isset(CHASELINKS)) {
+ if (*s != '/')
+ us = tricat(pwd[1] ? pwd : "", "/", s);
+ else
+ us = ztrdup(s);
+ fixdir(us2 = us);
+ } else
+ us = unmeta(s);
+ ret = !(access(us, X_OK) || stat(us, &buf) || !S_ISDIR(buf.st_mode));
+ if (us2)
+ free(us2);
+ return ret;
+}
+
+/**/
+void
+execsave(void)
+{
+ struct execstack *es;
+
+ es = (struct execstack *) zalloc(sizeof(struct execstack));
+ 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->use_cmdoutval = use_cmdoutval;
+ es->procsubstpid = procsubstpid;
+ es->trap_return = trap_return;
+ es->trap_state = trap_state;
+ es->trapisfunc = trapisfunc;
+ es->traplocallevel = traplocallevel;
+ es->noerrs = noerrs;
+ es->this_noerrexit = this_noerrexit;
+ es->underscore = ztrdup(zunderscore);
+ es->next = exstack;
+ exstack = es;
+ noerrs = cmdoutpid = 0;
+}
+
+/**/
+void
+execrestore(void)
+{
+ struct execstack *en = exstack;
+
+ DPUTS(!exstack, "BUG: execrestore() without execsave()");
+
+ queue_signals();
+ exstack = exstack->next;
+
+ list_pipe_pid = en->list_pipe_pid;
+ nowait = en->nowait;
+ pline_level = en->pline_level;
+ list_pipe_child = en->list_pipe_child;
+ list_pipe_job = en->list_pipe_job;
+ strcpy(list_pipe_text, en->list_pipe_text);
+ lastval = en->lastval;
+ noeval = en->noeval;
+ badcshglob = en->badcshglob;
+ cmdoutpid = en->cmdoutpid;
+ cmdoutval = en->cmdoutval;
+ use_cmdoutval = en->use_cmdoutval;
+ procsubstpid = en->procsubstpid;
+ trap_return = en->trap_return;
+ trap_state = en->trap_state;
+ trapisfunc = en->trapisfunc;
+ traplocallevel = en->traplocallevel;
+ noerrs = en->noerrs;
+ this_noerrexit = en->this_noerrexit;
+ setunderscore(en->underscore);
+ zsfree(en->underscore);
+ free(en);
+
+ unqueue_signals();
+}
diff --git a/dotfiles/system/.zsh/modules/Src/glob.c b/dotfiles/system/.zsh/modules/Src/glob.c
new file mode 100644
index 0000000..ed2c90b
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/glob.c
@@ -0,0 +1,3913 @@
+/*
+ * 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"
+
+#if defined(OFF_T_IS_64_BIT) && defined(__GNUC__)
+# define ALIGN64 __attribute__((aligned(8)))
+#else
+# define ALIGN64
+#endif
+
+/* flag for CSHNULLGLOB */
+
+typedef struct gmatch *Gmatch;
+
+struct gmatch {
+ /* Metafied file name */
+ char *name;
+ /* Unmetafied file name; embedded nulls can't occur in file names */
+ char *uname;
+ /*
+ * Array of sort strings: one for each GS_EXEC sort type in
+ * the glob qualifiers.
+ */
+ char **sortstrs;
+ off_t size ALIGN64;
+ long atime;
+ long mtime;
+ long ctime;
+ long links;
+ off_t _size ALIGN64;
+ long _atime;
+ long _mtime;
+ long _ctime;
+ long _links;
+#ifdef GET_ST_ATIME_NSEC
+ long ansec;
+ long _ansec;
+#endif
+#ifdef GET_ST_MTIME_NSEC
+ long mnsec;
+ long _mnsec;
+#endif
+#ifdef GET_ST_CTIME_NSEC
+ long cnsec;
+ long _cnsec;
+#endif
+};
+
+#define GS_NAME 1
+#define GS_DEPTH 2
+#define GS_EXEC 4
+
+#define GS_SHIFT_BASE 8
+
+#define GS_SIZE (GS_SHIFT_BASE)
+#define GS_ATIME (GS_SHIFT_BASE << 1)
+#define GS_MTIME (GS_SHIFT_BASE << 2)
+#define GS_CTIME (GS_SHIFT_BASE << 3)
+#define GS_LINKS (GS_SHIFT_BASE << 4)
+
+#define GS_SHIFT 5
+#define GS__SIZE (GS_SIZE << GS_SHIFT)
+#define GS__ATIME (GS_ATIME << GS_SHIFT)
+#define GS__MTIME (GS_MTIME << GS_SHIFT)
+#define GS__CTIME (GS_CTIME << GS_SHIFT)
+#define GS__LINKS (GS_LINKS << GS_SHIFT)
+
+#define GS_DESC (GS_SHIFT_BASE << (2*GS_SHIFT))
+#define GS_NONE (GS_SHIFT_BASE << (2*GS_SHIFT+1))
+
+#define GS_NORMAL (GS_SIZE | GS_ATIME | GS_MTIME | GS_CTIME | GS_LINKS)
+#define GS_LINKED (GS_NORMAL << GS_SHIFT)
+
+/**/
+int badcshglob;
+
+/**/
+int pathpos; /* position in pathbuf (needed by pattern code) */
+
+/*
+ * pathname buffer (needed by pattern code).
+ * It is currently believed the string in here is stored metafied and is
+ * unmetafied temporarily as needed by system calls.
+ */
+
+/**/
+char *pathbuf;
+
+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_SECONDS 5
+
+#define TT_BYTES 0
+#define TT_POSIX_BLOCKS 1
+#define TT_KILOBYTES 2
+#define TT_MEGABYTES 3
+#define TT_GIGABYTES 4
+#define TT_TERABYTES 5
+
+
+typedef int (*TestMatchFunc) _((char *, struct stat *, off_t, char *));
+
+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 */
+ off_t data ALIGN64; /* 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 */
+ char *sdata; /* currently only: expression to eval */
+};
+
+/* Prefix, suffix for doing zle trickery */
+
+/**/
+mod_export char *glob_pre, *glob_suf;
+
+/* Element of a glob sort */
+struct globsort {
+ /* Sort type */
+ int tp;
+ /* Sort code to eval, if type is GS_EXEC */
+ char *exec;
+};
+
+/* Maximum entries in sort array */
+#define MAX_SORTS (12)
+
+/* struct to easily save/restore current state */
+
+struct globdata {
+ int gd_pathpos;
+ char *gd_pathbuf;
+
+ int gd_matchsz; /* size of matchbuf */
+ int gd_matchct; /* number of matches found */
+ int gd_pathbufsz; /* size of pathbuf */
+ int gd_pathbufcwd; /* where did we chdir()'ed */
+ Gmatch gd_matchbuf; /* array of matches */
+ Gmatch gd_matchptr; /* &matchbuf[matchct] */
+ char *gd_colonmod; /* colon modifiers in qualifier list */
+
+ /* Qualifiers pertaining to current pattern */
+ struct qual *gd_quals;
+
+ /* Other state values for current pattern */
+ int gd_qualct, gd_qualorct;
+ int gd_range, gd_amc, gd_units;
+ int gd_gf_nullglob, gd_gf_markdirs, gd_gf_noglobdots, gd_gf_listtypes;
+ int gd_gf_numsort;
+ int gd_gf_follow, gd_gf_sorts, gd_gf_nsorts;
+ struct globsort gd_gf_sortlist[MAX_SORTS];
+ LinkList gd_gf_pre_words, gd_gf_post_words;
+
+ char *gd_glob_pre, *gd_glob_suf;
+};
+
+/* The variable with the current globbing state and convenience macros */
+
+static struct globdata curglobdata;
+
+#define matchsz (curglobdata.gd_matchsz)
+#define matchct (curglobdata.gd_matchct)
+#define pathbufsz (curglobdata.gd_pathbufsz)
+#define pathbufcwd (curglobdata.gd_pathbufcwd)
+#define matchbuf (curglobdata.gd_matchbuf)
+#define matchptr (curglobdata.gd_matchptr)
+#define colonmod (curglobdata.gd_colonmod)
+#define quals (curglobdata.gd_quals)
+#define qualct (curglobdata.gd_qualct)
+#define qualorct (curglobdata.gd_qualorct)
+#define g_range (curglobdata.gd_range)
+#define g_amc (curglobdata.gd_amc)
+#define g_units (curglobdata.gd_units)
+#define gf_nullglob (curglobdata.gd_gf_nullglob)
+#define gf_markdirs (curglobdata.gd_gf_markdirs)
+#define gf_noglobdots (curglobdata.gd_gf_noglobdots)
+#define gf_listtypes (curglobdata.gd_gf_listtypes)
+#define gf_numsort (curglobdata.gd_gf_numsort)
+#define gf_follow (curglobdata.gd_gf_follow)
+#define gf_sorts (curglobdata.gd_gf_sorts)
+#define gf_nsorts (curglobdata.gd_gf_nsorts)
+#define gf_sortlist (curglobdata.gd_gf_sortlist)
+#define gf_pre_words (curglobdata.gd_gf_pre_words)
+#define gf_post_words (curglobdata.gd_gf_post_words)
+
+/* and macros for save/restore */
+
+#define save_globstate(N) \
+ do { \
+ queue_signals(); \
+ memcpy(&(N), &curglobdata, sizeof(struct globdata)); \
+ (N).gd_pathpos = pathpos; \
+ (N).gd_pathbuf = pathbuf; \
+ (N).gd_glob_pre = glob_pre; \
+ (N).gd_glob_suf = glob_suf; \
+ pathbuf = NULL; \
+ unqueue_signals(); \
+ } while (0)
+
+#define restore_globstate(N) \
+ do { \
+ queue_signals(); \
+ zfree(pathbuf, pathbufsz); \
+ memcpy(&curglobdata, &(N), sizeof(struct globdata)); \
+ pathpos = (N).gd_pathpos; \
+ pathbuf = (N).gd_pathbuf; \
+ glob_pre = (N).gd_glob_pre; \
+ glob_suf = (N).gd_glob_suf; \
+ unqueue_signals(); \
+ } while (0)
+
+/* pathname component in filename patterns */
+
+struct complist {
+ Complist next;
+ Patprog pat;
+ int closure; /* 1 if this is a (foo/)# */
+ int follow; /* 1 to go thru symlinks */
+};
+
+/* 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, int l)
+{
+ DPUTS(!pathbuf, "BUG: pathbuf not initialised");
+ while (pathpos + l + 1 >= pathbufsz)
+ pathbuf = zrealloc(pathbuf, pathbufsz *= 2);
+ while (l--)
+ pathbuf[pathpos++] = *s++;
+ pathbuf[pathpos++] = '/';
+ 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 checked 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+1];
+
+ 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) {
+ /*
+ * Don't add the '.' if the path so far is empty, since
+ * then we get bogus empty strings inserted as files.
+ */
+ buf[pathpos - pathbufcwd] = '.';
+ buf[pathpos - pathbufcwd + 1] = '\0';
+ l = 0;
+ }
+ unmetafy(buf, NULL);
+ if (!st) {
+ char lbuf[1];
+ return access(buf, F_OK) && (!l || readlink(buf, lbuf, 1) < 0);
+ }
+ return l ? lstat(buf, st) : stat(buf, st);
+}
+
+/* This may be set by qualifier functions to an array of strings to insert
+ * into the list instead of the original string. */
+
+static char **inserts;
+
+/* add a match to the list */
+
+/**/
+static void
+insert(char *s, int checked)
+{
+ struct stat buf, buf2, *bp;
+ char *news = s;
+ int statted = 0;
+
+ queue_signals();
+ inserts = NULL;
+
+ 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)) {
+ unqueue_signals();
+ 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 *) hcalloc(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)) {
+ unqueue_signals();
+ return;
+ }
+ news = dyncat(pathbuf, news);
+
+ statted = 1;
+ qo = quals;
+ for (qn = qo; qn && qn->func;) {
+ g_range = qn->range;
+ g_amc = qn->amc;
+ g_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) (news, bp, qn->data, qn->sdata))
+ ^ qn->sense) & 1) {
+ /* Try next alternative, or return if there are no more */
+ if (!(qo = qo->or)) {
+ unqueue_signals();
+ return;
+ }
+ qn = qo;
+ continue;
+ }
+ qn = qn->next;
+ }
+ } else if (!checked) {
+ if (statfullpath(s, &buf, 1)) {
+ unqueue_signals();
+ return;
+ }
+ statted = 1;
+ news = dyncat(pathbuf, news);
+ } else
+ news = dyncat(pathbuf, news);
+
+ while (!inserts || (news = dupstring(*inserts++))) {
+ if (colonmod) {
+ /* Handle the remainder of the qualifier: e.g. (:r:s/foo/bar/). */
+ char *mod = colonmod;
+ modify(&news, &mod);
+ }
+ if (!statted && (gf_sorts & GS_NORMAL)) {
+ statfullpath(s, &buf, 1);
+ statted = 1;
+ }
+ if (!(statted & 2) && (gf_sorts & GS_LINKED)) {
+ if (statted) {
+ if (!S_ISLNK(buf.st_mode) || statfullpath(s, &buf2, 0))
+ memcpy(&buf2, &buf, sizeof(buf));
+ } else if (statfullpath(s, &buf2, 0))
+ statfullpath(s, &buf2, 1);
+ statted |= 2;
+ }
+ matchptr->name = news;
+ if (statted & 1) {
+ matchptr->size = buf.st_size;
+ matchptr->atime = buf.st_atime;
+ matchptr->mtime = buf.st_mtime;
+ matchptr->ctime = buf.st_ctime;
+ matchptr->links = buf.st_nlink;
+#ifdef GET_ST_ATIME_NSEC
+ matchptr->ansec = GET_ST_ATIME_NSEC(buf);
+#endif
+#ifdef GET_ST_MTIME_NSEC
+ matchptr->mnsec = GET_ST_MTIME_NSEC(buf);
+#endif
+#ifdef GET_ST_CTIME_NSEC
+ matchptr->cnsec = GET_ST_CTIME_NSEC(buf);
+#endif
+ }
+ if (statted & 2) {
+ matchptr->_size = buf2.st_size;
+ matchptr->_atime = buf2.st_atime;
+ matchptr->_mtime = buf2.st_mtime;
+ matchptr->_ctime = buf2.st_ctime;
+ matchptr->_links = buf2.st_nlink;
+#ifdef GET_ST_ATIME_NSEC
+ matchptr->_ansec = GET_ST_ATIME_NSEC(buf2);
+#endif
+#ifdef GET_ST_MTIME_NSEC
+ matchptr->_mnsec = GET_ST_MTIME_NSEC(buf2);
+#endif
+#ifdef GET_ST_CTIME_NSEC
+ matchptr->_cnsec = GET_ST_CTIME_NSEC(buf2);
+#endif
+ }
+ matchptr++;
+
+ if (++matchct == matchsz) {
+ matchbuf = (Gmatch)zrealloc((char *)matchbuf,
+ sizeof(struct gmatch) * (matchsz *= 2));
+
+ matchptr = matchbuf + matchct;
+ }
+ if (!inserts)
+ break;
+ }
+ unqueue_signals();
+ return;
+}
+
+/* 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, int shortcircuit)
+{
+ Patprog p;
+ int closure;
+ int pbcwdsav = pathbufcwd;
+ int errssofar = errsfound;
+ struct dirsav ds;
+
+ if (!q || errflag)
+ return;
+ init_dirsav(&ds);
+
+ 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, shortcircuit);
+ if (shortcircuit && shortcircuit == matchct)
+ return;
+ }
+ }
+ p = q->pat;
+ /* Now the actual matching for the current path section. */
+ if (p->flags & PAT_PURES) {
+ /*
+ * It's a straight string to the end of the path section.
+ */
+ char *str = (char *)p + p->startoff;
+ int l = p->patmlen;
+
+ if (l + !l + pathpos - pathbufcwd >= PATH_MAX) {
+ int err;
+
+ if (l >= PATH_MAX)
+ return;
+ err = lchdir(unmeta(pathbuf + pathbufcwd), &ds, 0);
+ if (err == -1)
+ return;
+ if (err) {
+ zerr("current directory lost during glob");
+ return;
+ }
+ pathbufcwd = pathpos;
+ }
+ if (q->next) {
+ /* Not the last path section. Just add it to the path. */
+ int oppos = pathpos;
+
+ if (!errflag) {
+ int add = 1;
+
+ if (q->closure && *pathbuf) {
+ if (!strcmp(str, "."))
+ add = 0;
+ else if (!strcmp(str, "..")) {
+ struct stat sc, sr;
+
+ add = (stat("/", &sr) || stat(unmeta(pathbuf), &sc) ||
+ sr.st_ino != sc.st_ino ||
+ sr.st_dev != sc.st_dev);
+ }
+ }
+ if (add) {
+ addpath(str, l);
+ if (!closure || !statfullpath("", NULL, 1)) {
+ scanner((q->closure) ? q : q->next, shortcircuit);
+ if (shortcircuit && shortcircuit == matchct)
+ return;
+ }
+ pathbuf[pathpos = oppos] = '\0';
+ }
+ }
+ } else {
+ if (str[l])
+ str = dupstrpfx(str, l);
+ insert(str, 0);
+ if (shortcircuit && shortcircuit == matchct)
+ return;
+ }
+ } else {
+ /* Do pattern matching on current path section. */
+ char *fn = pathbuf[pathbufcwd] ? unmeta(pathbuf + pathbufcwd) : ".";
+ int dirs = !!q->next;
+ DIR *lock = opendir(fn);
+ char *subdirs = NULL;
+ int subdirlen = 0;
+
+ 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;
+ errsfound = errssofar;
+ if (pattry(p, fn)) {
+ /* 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(unmeta(pathbuf + pathbufcwd), &ds, 0);
+ if (err == -1)
+ break;
+ if (err) {
+ zerr("current directory lost during glob");
+ break;
+ }
+ pathbufcwd = pathpos;
+ }
+ if (dirs) {
+ int l;
+
+ /*
+ * If not the last component in the path:
+ *
+ * If we made an approximation in the new path segment,
+ * then it is possible we made too many errors. For
+ * example, (ab)#(cb)# will match the directory abcb
+ * with one error if allowed to, even though it can
+ * match with none. This will stop later parts of the
+ * path matching, so we need to check by reducing the
+ * maximum number of errors and seeing if the directory
+ * still matches. Luckily, this is not a terribly
+ * common case, since complex patterns typically occur
+ * in the last part of the path which is not affected
+ * by this problem.
+ */
+ if (errsfound > errssofar) {
+ forceerrs = errsfound - 1;
+ while (forceerrs >= errssofar) {
+ errsfound = errssofar;
+ if (!pattry(p, fn))
+ break;
+ forceerrs = errsfound - 1;
+ }
+ errsfound = forceerrs + 1;
+ forceerrs = -1;
+ }
+ if (closure) {
+ /* if matching multiple directories */
+ struct stat buf;
+
+ if (statfullpath(fn, &buf, !q->follow)) {
+ if (errno != ENOENT && errno != EINTR &&
+ errno != ENOTDIR && !errflag) {
+ zwarn("%e: %s", errno, fn);
+ }
+ continue;
+ }
+ if (!S_ISDIR(buf.st_mode))
+ continue;
+ }
+ l = strlen(fn) + 1;
+ subdirs = hrealloc(subdirs, subdirlen, subdirlen + l
+ + sizeof(int));
+ strcpy(subdirs + subdirlen, fn);
+ subdirlen += l;
+ /* store the count of errors made so far, too */
+ memcpy(subdirs + subdirlen, (char *)&errsfound,
+ sizeof(int));
+ subdirlen += sizeof(int);
+ } else {
+ /* if the last filename component, just add it */
+ insert(fn, 1);
+ if (shortcircuit && shortcircuit == matchct) {
+ closedir(lock);
+ return;
+ }
+ }
+ }
+ }
+ closedir(lock);
+ if (subdirs) {
+ int oppos = pathpos;
+
+ for (fn = subdirs; fn < subdirs+subdirlen; ) {
+ int l = strlen(fn);
+ addpath(fn, l);
+ fn += l + 1;
+ memcpy((char *)&errsfound, fn, sizeof(int));
+ fn += sizeof(int);
+ /* scan next level */
+ scanner((q->closure) ? q : q->next, shortcircuit);
+ if (shortcircuit && shortcircuit == matchct)
+ return;
+ pathbuf[pathpos = oppos] = '\0';
+ }
+ hrealloc(subdirs, subdirlen, 0);
+ }
+ }
+ if (pbcwdsav < pathbufcwd) {
+ if (restoredir(&ds))
+ zerr("current directory lost during glob");
+ zsfree(ds.dirname);
+ if (ds.dirfd >= 0)
+ close(ds.dirfd);
+ pathbufcwd = pbcwdsav;
+ }
+ return;
+}
+
+/* This function tokenizes a zsh glob pattern */
+
+/**/
+static Complist
+parsecomplist(char *instr)
+{
+ Patprog p1;
+ Complist l1;
+ char *str;
+ int compflags = gf_noglobdots ? (PAT_FILE|PAT_NOGLD) : PAT_FILE;
+
+ if (instr[0] == Star && instr[1] == Star) {
+ int shortglob = 0;
+ if (instr[2] == '/' || (instr[2] == Star && instr[3] == '/')
+ || (shortglob = isset(GLOBSTARSHORT))) {
+ /* Match any number of directories. */
+ int follow;
+
+ /* with three stars, follow symbolic links */
+ follow = (instr[2] == Star);
+ /*
+ * With GLOBSTARSHORT, leave a star in place for the
+ * pattern inside the directory.
+ */
+ instr += ((shortglob ? 1 : 3) + follow);
+
+ /* Now get the next path component if there is one. */
+ l1 = (Complist) zhalloc(sizeof *l1);
+ if ((l1->next = parsecomplist(instr)) == NULL) {
+ errflag |= ERRFLAG_ERROR;
+ return NULL;
+ }
+ l1->pat = patcompile(NULL, compflags | PAT_ANY, NULL);
+ l1->closure = 1; /* ...zero or more times. */
+ l1->follow = follow;
+ return l1;
+ }
+ }
+
+ /* Parse repeated directories such as (dir/)# and (dir/)## */
+ if (*(str = instr) == zpc_special[ZPC_INPAR] &&
+ !skipparens(Inpar, Outpar, (char **)&str) &&
+ *str == zpc_special[ZPC_HASH] && str[-2] == '/') {
+ instr++;
+ if (!(p1 = patcompile(instr, compflags, &instr)))
+ return NULL;
+ if (instr[0] == '/' && instr[1] == Outpar && instr[2] == Pound) {
+ int pdflag = 0;
+
+ instr += 3;
+ if (*instr == Pound) {
+ pdflag = 1;
+ instr++;
+ }
+ l1 = (Complist) zhalloc(sizeof *l1);
+ l1->pat = p1;
+ /* special case (/)# to avoid infinite recursion */
+ l1->closure = (*((char *)p1 + p1->startoff)) ? 1 + pdflag : 0;
+ l1->follow = 0;
+ l1->next = parsecomplist(instr);
+ return (l1->pat) ? l1 : NULL;
+ }
+ } else {
+ /* parse single path component */
+ if (!(p1 = patcompile(instr, compflags|PAT_FILET, &instr)))
+ return NULL;
+ /* then do the remaining path components */
+ if (*instr == '/' || !*instr) {
+ int ef = *instr == '/';
+
+ l1 = (Complist) zhalloc(sizeof *l1);
+ l1->pat = p1;
+ l1->closure = 0;
+ l1->next = ef ? parsecomplist(instr+1) : NULL;
+ return (ef && !l1->next) ? NULL : l1;
+ }
+ }
+ errflag |= ERRFLAG_ERROR;
+ return NULL;
+}
+
+/* turn a string into a Complist struct: this has path components */
+
+/**/
+static Complist
+parsepat(char *str)
+{
+ long assert;
+ int ignore;
+
+ patcompstart();
+ /*
+ * Check for initial globbing flags, so that they don't form
+ * a bogus path component.
+ */
+ if ((*str == zpc_special[ZPC_INPAR] && str[1] == zpc_special[ZPC_HASH]) ||
+ (*str == zpc_special[ZPC_KSH_AT] && str[1] == Inpar &&
+ str[2] == zpc_special[ZPC_HASH])) {
+ str += (*str == Inpar) ? 2 : 3;
+ if (!patgetglobflags(&str, &assert, &ignore))
+ return NULL;
+ }
+
+ /* Now there is no (#X) in front, we can check the path. */
+ if (!pathbuf)
+ pathbuf = zalloc(pathbufsz = PATH_MAX+1);
+ 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';
+
+ return parsecomplist(str);
+}
+
+/* get number after qualifier */
+
+/**/
+static off_t
+qgetnum(char **s)
+{
+ off_t v = 0;
+
+ if (!idigit(**s)) {
+ zerr("number expected");
+ return 0;
+ }
+ while (idigit(**s))
+ v = v * 10 + *(*s)++ - '0';
+ return v;
+}
+
+/* get mode spec after qualifier */
+
+/**/
+static zlong
+qgetmodespec(char **s)
+{
+ zlong yes = 0, no = 0, val, mask, t;
+ char *p = *s, c, how, end;
+
+ if ((c = *p) == '=' || c == Equals || c == '+' || c == '-' ||
+ c == '?' || c == Quest || (c >= '0' && c <= '7')) {
+ end = 0;
+ c = 0;
+ } else {
+ end = (c == '<' ? '>' :
+ (c == '[' ? ']' :
+ (c == '{' ? '}' :
+ (c == Inang ? Outang :
+ (c == Inbrack ? Outbrack :
+ (c == Inbrace ? Outbrace : c))))));
+ p++;
+ }
+ do {
+ mask = 0;
+ while (((c = *p) == 'u' || c == 'g' || c == 'o' || c == 'a') && end) {
+ switch (c) {
+ case 'o': mask |= 01007; break;
+ case 'g': mask |= 02070; break;
+ case 'u': mask |= 04700; break;
+ case 'a': mask |= 07777; break;
+ }
+ p++;
+ }
+ how = ((c == '+' || c == '-') ? c : '=');
+ if (c == '+' || c == '-' || c == '=' || c == Equals)
+ p++;
+ val = 0;
+ if (mask) {
+ while ((c = *p++) != ',' && c != end) {
+ switch (c) {
+ case 'x': val |= 00111; break;
+ case 'w': val |= 00222; break;
+ case 'r': val |= 00444; break;
+ case 's': val |= 06000; break;
+ case 't': val |= 01000; break;
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ t = ((zlong) c - '0');
+ val |= t | (t << 3) | (t << 6);
+ break;
+ default:
+ zerr("invalid mode specification");
+ return 0;
+ }
+ }
+ if (how == '=' || how == '+') {
+ yes |= val & mask;
+ val = ~val;
+ }
+ if (how == '=' || how == '-')
+ no |= val & mask;
+ } else if (!(end && c == end) && c != ',' && c) {
+ t = 07777;
+ while ((c = *p) == '?' || c == Quest ||
+ (c >= '0' && c <= '7')) {
+ if (c == '?' || c == Quest) {
+ t = (t << 3) | 7;
+ val <<= 3;
+ } else {
+ t <<= 3;
+ val = (val << 3) | ((zlong) c - '0');
+ }
+ p++;
+ }
+ if (end && c != end && c != ',') {
+ zerr("invalid mode specification");
+ return 0;
+ }
+ if (how == '=') {
+ yes = (yes & ~t) | val;
+ no = (no & ~t) | (~val & ~t);
+ } else if (how == '+')
+ yes |= val;
+ else
+ no |= val;
+ } else {
+ zerr("invalid mode specification");
+ return 0;
+ }
+ } while (end && c != end);
+
+ *s = p;
+ return ((yes & 07777) | ((no & 07777) << 12));
+}
+
+static int
+gmatchcmp(Gmatch a, Gmatch b)
+{
+ int i;
+ off_t r = 0L;
+ struct globsort *s;
+ char **asortstrp = NULL, **bsortstrp = NULL;
+
+ for (i = gf_nsorts, s = gf_sortlist; i; i--, s++) {
+ switch (s->tp & ~GS_DESC) {
+ case GS_NAME:
+ r = zstrcmp(b->uname, a->uname,
+ gf_numsort ? SORTIT_NUMERICALLY : 0);
+ break;
+ case GS_DEPTH:
+ {
+ char *aptr = a->name, *bptr = b->name;
+ int slasha = 0, slashb = 0;
+ /* Count slashes. Trailing slashes don't count. */
+ while (*aptr && *aptr == *bptr)
+ aptr++, bptr++;
+ /* Like I just said... */
+ if ((!*aptr || !*bptr) && aptr > a->name && aptr[-1] == '/')
+ aptr--, bptr--;
+ if (*aptr)
+ for (; aptr[1]; aptr++)
+ if (*aptr == '/') {
+ slasha = 1;
+ break;
+ }
+ if (*bptr)
+ for (; bptr[1]; bptr++)
+ if (*bptr == '/') {
+ slashb = 1;
+ break;
+ }
+ r = slasha - slashb;
+ }
+ break;
+ case GS_EXEC:
+ if (!asortstrp) {
+ asortstrp = a->sortstrs;
+ bsortstrp = b->sortstrs;
+ } else {
+ asortstrp++;
+ bsortstrp++;
+ }
+ r = zstrcmp(*bsortstrp, *asortstrp,
+ gf_numsort ? SORTIT_NUMERICALLY : 0);
+ break;
+ case GS_SIZE:
+ r = b->size - a->size;
+ break;
+ case GS_ATIME:
+ r = a->atime - b->atime;
+#ifdef GET_ST_ATIME_NSEC
+ if (!r)
+ r = a->ansec - b->ansec;
+#endif
+ break;
+ case GS_MTIME:
+ r = a->mtime - b->mtime;
+#ifdef GET_ST_MTIME_NSEC
+ if (!r)
+ r = a->mnsec - b->mnsec;
+#endif
+ break;
+ case GS_CTIME:
+ r = a->ctime - b->ctime;
+#ifdef GET_ST_CTIME_NSEC
+ if (!r)
+ r = a->cnsec - b->cnsec;
+#endif
+ break;
+ case GS_LINKS:
+ r = b->links - a->links;
+ break;
+ case GS__SIZE:
+ r = b->_size - a->_size;
+ break;
+ case GS__ATIME:
+ r = a->_atime - b->_atime;
+#ifdef GET_ST_ATIME_NSEC
+ if (!r)
+ r = a->_ansec - b->_ansec;
+#endif
+ break;
+ case GS__MTIME:
+ r = a->_mtime - b->_mtime;
+#ifdef GET_ST_MTIME_NSEC
+ if (!r)
+ r = a->_mnsec - b->_mnsec;
+#endif
+ break;
+ case GS__CTIME:
+ r = a->_ctime - b->_ctime;
+#ifdef GET_ST_CTIME_NSEC
+ if (!r)
+ r = a->_cnsec - b->_cnsec;
+#endif
+ break;
+ case GS__LINKS:
+ r = b->_links - a->_links;
+ break;
+ }
+ if (r)
+ return (s->tp & GS_DESC) ?
+ (r < 0L ? 1 : -1) :
+ (r > 0L ? 1 : -1);
+ }
+ return 0;
+}
+
+/*
+ * Duplicate a list of qualifiers using the `next' linkage (not the
+ * `or' linkage). Return the head element and set *last (if last non-NULL)
+ * to point to the last element of the new list. All allocation is on the
+ * heap (or off the heap?)
+ */
+static struct qual *dup_qual_list(struct qual *orig, struct qual **lastp)
+{
+ struct qual *qfirst = NULL, *qlast = NULL;
+
+ while (orig) {
+ struct qual *qnew = (struct qual *)zhalloc(sizeof(struct qual));
+ *qnew = *orig;
+ qnew->next = qnew->or = NULL;
+
+ if (!qfirst)
+ qfirst = qnew;
+ if (qlast)
+ qlast->next = qnew;
+ qlast = qnew;
+
+ orig = orig->next;
+ }
+
+ if (lastp)
+ *lastp = qlast;
+ return qfirst;
+}
+
+
+/*
+ * Get a glob string for execution, following e, P or + qualifiers.
+ * Pointer is character after the e, P or +.
+ */
+
+/**/
+static char *
+glob_exec_string(char **sp)
+{
+ char sav, *tt, *sdata, *s = *sp;
+ int plus;
+
+ if (s[-1] == '+') {
+ plus = 0;
+ tt = itype_end(s, IIDENT, 0);
+ if (tt == s)
+ {
+ zerr("missing identifier after `+'");
+ return NULL;
+ }
+ } else {
+ tt = get_strarg(s, &plus);
+ if (!*tt)
+ {
+ zerr("missing end of string");
+ return NULL;
+ }
+ }
+
+ sav = *tt;
+ *tt = '\0';
+ sdata = dupstring(s + plus);
+ untokenize(sdata);
+ *tt = sav;
+ if (sav)
+ *sp = tt + plus;
+ else
+ *sp = tt;
+
+ return sdata;
+}
+
+/*
+ * Insert a glob match.
+ * If there were words to prepend given by the P glob qualifier, do so.
+ */
+static void
+insert_glob_match(LinkList list, LinkNode next, char *data)
+{
+ if (gf_pre_words) {
+ LinkNode added;
+ for (added = firstnode(gf_pre_words); added; incnode(added)) {
+ next = insertlinknode(list, next, dupstring(getdata(added)));
+ }
+ }
+
+ next = insertlinknode(list, next, data);
+
+ if (gf_post_words) {
+ LinkNode added;
+ for (added = firstnode(gf_post_words); added; incnode(added)) {
+ next = insertlinknode(list, next, dupstring(getdata(added)));
+ }
+ }
+}
+
+/*
+ * Return
+ * 1 if str ends in bare glob qualifiers
+ * 2 if str ends in non-bare glob qualifiers (#q)
+ * 0 otherwise.
+ *
+ * str is the string to check.
+ * sl is its length (to avoid recalculation).
+ * nobareglob is 1 if bare glob qualifiers are not allowed.
+ * *sp, if sp is not null, will be a pointer to the opening parenthesis.
+ */
+
+/**/
+int
+checkglobqual(char *str, int sl, int nobareglob, char **sp)
+{
+ char *s;
+ int paren, ret = 1;
+
+ if (str[sl - 1] != Outpar)
+ return 0;
+
+ /* Check these are really qualifiers, not a set of *
+ * alternatives or exclusions. We can be more *
+ * lenient with an explicit (#q) than with a bare *
+ * set of qualifiers. */
+ paren = 0;
+ for (s = str + sl - 2; *s && (*s != Inpar || paren); s--) {
+ switch (*s) {
+ case Outpar:
+ paren++; /*FALLTHROUGH*/
+ case Bar:
+ if (!zpc_disables[ZPC_BAR])
+ nobareglob = 1;
+ break;
+ case Tilde:
+ if (isset(EXTENDEDGLOB) && !zpc_disables[ZPC_TILDE])
+ nobareglob = 1;
+ break;
+ case Inpar:
+ paren--;
+ break;
+ }
+ if (s == str)
+ break;
+ }
+ if (*s != Inpar)
+ return 0;
+ if (isset(EXTENDEDGLOB) && !zpc_disables[ZPC_HASH] && s[1] == Pound) {
+ if (s[2] != 'q')
+ return 0;
+ ret = 2;
+ } else if (nobareglob)
+ return 0;
+
+ if (sp)
+ *sp = s;
+
+ return ret;
+}
+
+/* Main entry point to the globbing code for filename globbing. *
+ * np points to a node in the list which will be expanded *
+ * into a series of nodes. */
+
+/**/
+void
+zglob(LinkList list, LinkNode np, int nountok)
+{
+ 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 */
+ int first = 0, end = -1; /* index of first match to return */
+ /* and index+1 of the last match */
+ struct globdata saved; /* saved glob state */
+ int nobareglob = !isset(BAREGLOBQUAL);
+ int shortcircuit = 0; /* How many files to match; */
+ /* 0 means no limit */
+
+ if (unset(GLOBOPT) || !haswilds(ostr) || unset(EXECOPT)) {
+ if (!nountok)
+ untokenize(ostr);
+ return;
+ }
+ save_globstate(saved);
+
+ str = dupstring(ostr);
+ uremnode(list, np);
+
+ /* quals will hold the complete list of qualifiers (file static). */
+ quals = NULL;
+ /*
+ * qualct and qualorct indicate we have qualifiers in the last
+ * alternative, or a set of alternatives, respectively. They
+ * are not necessarily an accurate count, however.
+ */
+ qualct = qualorct = 0;
+ /*
+ * colonmod is a concatenated list of all colon modifiers found in
+ * all sets of qualifiers.
+ */
+ colonmod = NULL;
+ /* The gf_* flags are qualifiers which are applied globally. */
+ gf_nullglob = isset(NULLGLOB);
+ gf_markdirs = isset(MARKDIRS);
+ gf_listtypes = gf_follow = 0;
+ gf_noglobdots = unset(GLOBDOTS);
+ gf_numsort = isset(NUMERICGLOBSORT);
+ gf_sorts = gf_nsorts = 0;
+ gf_pre_words = gf_post_words = NULL;
+
+ /* Check for qualifiers */
+ while (!nobareglob ||
+ (isset(EXTENDEDGLOB) && !zpc_disables[ZPC_HASH])) {
+ struct qual *newquals;
+ char *s;
+ int sense, qualsfound;
+ off_t data;
+ char *sdata, *newcolonmod, *ptr;
+ int (*func) _((char *, Statptr, off_t, char *));
+
+ /*
+ * Initialise state variables for current file pattern.
+ * newquals is the root for the linked list of all qualifiers.
+ * qo is the root of the current list of alternatives.
+ * ql is the end of the current alternative where the `next' will go.
+ * qn is the current qualifier node to be added.
+ *
+ * Here is an attempt at a diagram. An `or' is added horizontally
+ * to the top line, a `next' at the bottom of the right hand line.
+ * `qn' is usually NULL unless a new `or' has just been added.
+ *
+ * quals -> x -> x -> qo
+ * | | |
+ * x x x
+ * | |
+ * x ql
+ *
+ * In fact, after each loop the complete set is in the file static
+ * `quals'. Then, if we have a second set of qualifiers, we merge
+ * the lists together. This is only tricky if one or both have an
+ * `or' in them; then we need to distribute over all alternatives.
+ */
+ newquals = qo = qn = ql = NULL;
+
+ sl = strlen(str);
+ if (!(qualsfound = checkglobqual(str, sl, nobareglob, &s)))
+ break;
+
+ /* Real qualifiers found. */
+ nobareglob = 1;
+ sense = 0; /* bit 0 for match (0)/don't match (1) */
+ /* bit 1 for follow links (2), don't (0) */
+ data = 0; /* Any numerical argument required */
+ sdata = NULL; /* Any list argument required */
+ newcolonmod = NULL; /* Contains trailing colon modifiers */
+
+ str[sl-1] = 0;
+ *s++ = 0;
+ if (qualsfound == 2)
+ s += 2;
+ for (ptr = s; *ptr; ptr++)
+ if (*ptr == Dash)
+ *ptr = '-';
+ while (*s && !newcolonmod) {
+ func = (int (*) _((char *, Statptr, off_t, char *)))0;
+ 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. */
+ newcolonmod = s - 1;
+ untokenize(newcolonmod);
+ if (colonmod) {
+ /* remember we're searching backwards */
+ colonmod = dyncat(newcolonmod, colonmod);
+ } else
+ colonmod = newcolonmod;
+ break;
+ case Hat:
+ case '^':
+ /* Toggle sense: go from positive to *
+ * negative match and vice versa. */
+ sense ^= 1;
+ break;
+ case '-':
+ case Dash:
+ /* 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;
+ g_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;
+ int arglen;
+
+ /* Find matching delimiters */
+ tt = get_strarg(s, &arglen);
+ if (!*tt) {
+ zerr("missing delimiter for 'u' glob qualifier");
+ data = 0;
+ } else {
+#ifdef USE_GETPWNAM
+ struct passwd *pw;
+ sav = *tt;
+ *tt = '\0';
+
+ if ((pw = getpwnam(s + arglen)))
+ data = pw->pw_uid;
+ else {
+ zerr("unknown username '%s'", s + arglen);
+ data = 0;
+ }
+ *tt = sav;
+#else /* !USE_GETPWNAM */
+ sav = *tt;
+ *tt = '\0';
+ zerr("unable to resolve non-numeric username '%s'", s + arglen);
+ *tt = sav;
+ data = 0;
+#endif /* !USE_GETPWNAM */
+ if (sav)
+ s = tt + arglen;
+ 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;
+ int arglen;
+
+ tt = get_strarg(s, &arglen);
+ if (!*tt) {
+ zerr("missing delimiter for 'g' glob qualifier");
+ data = 0;
+ } else {
+#ifdef USE_GETGRNAM
+ struct group *gr;
+ sav = *tt;
+ *tt = '\0';
+
+ if ((gr = getgrnam(s + arglen)))
+ data = gr->gr_gid;
+ else {
+ zerr("unknown group");
+ data = 0;
+ }
+ *tt = sav;
+#else /* !USE_GETGRNAM */
+ sav = *tt;
+ zerr("unknown group");
+ data = 0;
+#endif /* !USE_GETGRNAM */
+ if (sav)
+ s = tt + arglen;
+ else
+ s = tt;
+ }
+ }
+ break;
+ case 'f':
+ /* Match modes with chmod-spec. */
+ func = qualmodeflags;
+ data = qgetmodespec(&s);
+ break;
+ case 'F':
+ func = qualnonemptydir;
+ 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 'n':
+ /* Numeric glob sort */
+ gf_numsort = !(sense & 1);
+ break;
+ case 'Y':
+ {
+ /* Short circuit: limit number of matches */
+ const char *s_saved = s;
+ shortcircuit = !(sense & 1);
+ if (shortcircuit) {
+ /* Parse the argument. */
+ data = qgetnum(&s);
+ if ((shortcircuit = data) != data) {
+ /* Integer overflow */
+ zerr("value too big: Y%s", s_saved);
+ restore_globstate(saved);
+ return;
+ }
+ }
+ break;
+ }
+ case 'a':
+ /* Access time in given range */
+ g_amc = 0;
+ func = qualtime;
+ goto getrange;
+ case 'm':
+ /* Modification time in given range */
+ g_amc = 1;
+ func = qualtime;
+ goto getrange;
+ case 'c':
+ /* Inode creation time in given range */
+ g_amc = 2;
+ func = qualtime;
+ goto getrange;
+ case 'L':
+ /* File size (Length) in given range */
+ func = qualsize;
+ g_amc = -1;
+ /* Get size multiplier */
+ g_units = TT_BYTES;
+ if (*s == 'p' || *s == 'P')
+ g_units = TT_POSIX_BLOCKS, ++s;
+ else if (*s == 'k' || *s == 'K')
+ g_units = TT_KILOBYTES, ++s;
+ else if (*s == 'm' || *s == 'M')
+ g_units = TT_MEGABYTES, ++s;
+#if defined(ZSH_64_BIT_TYPE) || defined(LONG_IS_64_BIT)
+ else if (*s == 'g' || *s == 'G')
+ g_units = TT_GIGABYTES, ++s;
+ else if (*s == 't' || *s == 'T')
+ g_units = TT_TERABYTES, ++s;
+#endif
+ getrange:
+ /* Get time multiplier */
+ if (g_amc >= 0) {
+ g_units = TT_DAYS;
+ if (*s == 'h')
+ g_units = TT_HOURS, ++s;
+ else if (*s == 'm')
+ g_units = TT_MINS, ++s;
+ else if (*s == 'w')
+ g_units = TT_WEEKS, ++s;
+ else if (*s == 'M')
+ g_units = TT_MONTHS, ++s;
+ else if (*s == 's')
+ g_units = TT_SECONDS, ++s;
+ else if (*s == 'd')
+ ++s;
+ }
+ /* See if it's greater than, equal to, or less than */
+ if ((g_range = *s == '+' ? 1 : IS_DASH(*s) ? -1 : 0))
+ ++s;
+ data = qgetnum(&s);
+ break;
+
+ case 'o':
+ case 'O':
+ {
+ int t;
+ char *send;
+
+ if (gf_nsorts == MAX_SORTS) {
+ zerr("too many glob sort specifiers");
+ restore_globstate(saved);
+ return;
+ }
+
+ /* usually just one character */
+ send = s+1;
+ switch (*s) {
+ case 'n': t = GS_NAME; break;
+ case 'L': t = GS_SIZE; break;
+ case 'l': t = GS_LINKS; break;
+ case 'a': t = GS_ATIME; break;
+ case 'm': t = GS_MTIME; break;
+ case 'c': t = GS_CTIME; break;
+ case 'd': t = GS_DEPTH; break;
+ case 'N': t = GS_NONE; break;
+ case 'e':
+ case '+':
+ {
+ t = GS_EXEC;
+ if ((gf_sortlist[gf_nsorts].exec =
+ glob_exec_string(&send)) == NULL)
+ {
+ restore_globstate(saved);
+ return;
+ }
+ break;
+ }
+ default:
+ zerr("unknown sort specifier");
+ restore_globstate(saved);
+ return;
+ }
+ if ((sense & 2) &&
+ (t & (GS_SIZE|GS_ATIME|GS_MTIME|GS_CTIME|GS_LINKS)))
+ t <<= GS_SHIFT; /* HERE: GS_EXEC? */
+ if (t != GS_EXEC) {
+ if (gf_sorts & t) {
+ zerr("doubled sort specifier");
+ restore_globstate(saved);
+ return;
+ }
+ }
+ gf_sorts |= t;
+ gf_sortlist[gf_nsorts++].tp = t |
+ (((sense & 1) ^ (s[-1] == 'O')) ? GS_DESC : 0);
+ s = send;
+ break;
+ }
+ case '+':
+ case 'e':
+ {
+ char *tt;
+
+ tt = glob_exec_string(&s);
+
+ if (tt == NULL) {
+ data = 0;
+ } else {
+ func = qualsheval;
+ sdata = tt;
+ }
+ break;
+ }
+ case '[':
+ case Inbrack:
+ {
+ char *os = --s;
+ struct value v;
+
+ v.isarr = SCANPM_WANTVALS;
+ v.pm = NULL;
+ v.end = -1;
+ v.flags = 0;
+ if (getindex(&s, &v, 0) || s == os) {
+ zerr("invalid subscript");
+ restore_globstate(saved);
+ return;
+ }
+ first = v.start;
+ end = v.end;
+ break;
+ }
+ case 'P':
+ {
+ char *tt;
+ tt = glob_exec_string(&s);
+
+ if (tt != NULL)
+ {
+ LinkList *words = sense & 1 ? &gf_post_words : &gf_pre_words;
+ if (!*words)
+ *words = newlinklist();
+ addlinknode(*words, tt);
+ }
+ break;
+ }
+ default:
+ untokenize(--s);
+ zerr("unknown file attribute: %c", *s);
+ restore_globstate(saved);
+ 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 (!newquals)
+ newquals = qo = qn;
+ qn->func = func;
+ qn->sense = sense;
+ qn->data = data;
+ qn->sdata = sdata;
+ qn->range = g_range;
+ qn->units = g_units;
+ qn->amc = g_amc;
+
+ qn = NULL;
+ qualct++;
+ }
+ if (errflag) {
+ restore_globstate(saved);
+ return;
+ }
+ }
+
+ if (quals && newquals) {
+ /* Merge previous group of qualifiers with new set. */
+ if (quals->or || newquals->or) {
+ /* The hard case. */
+ struct qual *qorhead = NULL, *qortail = NULL;
+ /*
+ * Distribute in the most trivial way, by creating
+ * all possible combinations of the two sets and chaining
+ * these into one long set of alternatives given
+ * by qorhead and qortail.
+ */
+ for (qn = newquals; qn; qn = qn->or) {
+ for (qo = quals; qo; qo = qo->or) {
+ struct qual *qfirst, *qlast;
+ int islast = !qn->or && !qo->or;
+ /* Generate first set of qualifiers... */
+ if (islast) {
+ /* Last time round: don't bother copying. */
+ qfirst = qn;
+ for (qlast = qfirst; qlast->next;
+ qlast = qlast->next)
+ ;
+ } else
+ qfirst = dup_qual_list(qn, &qlast);
+ /* ... link into new `or' chain ... */
+ if (!qorhead)
+ qorhead = qfirst;
+ if (qortail)
+ qortail->or = qfirst;
+ qortail = qfirst;
+ /* ... and concatenate second set. */
+ qlast->next = islast ? qo : dup_qual_list(qo, NULL);
+ }
+ }
+ quals = qorhead;
+ } else {
+ /*
+ * Easy: we can just chain the qualifiers together.
+ * This is an optimisation; the code above will work, too.
+ * We retain the original left to right ordering --- remember
+ * we are searching for sets of qualifiers from the right.
+ */
+ qn = newquals;
+ for ( ; newquals->next; newquals = newquals->next)
+ ;
+ newquals->next = quals;
+ quals = qn;
+ }
+ } else if (newquals)
+ quals = newquals;
+ }
+ q = parsepat(str);
+ if (!q || errflag) { /* if parsing failed */
+ restore_globstate(saved);
+ if (unset(BADPATTERN)) {
+ if (!nountok)
+ untokenize(ostr);
+ insertlinknode(list, node, ostr);
+ return;
+ }
+ errflag &= ~ERRFLAG_ERROR;
+ zerr("bad pattern: %s", ostr);
+ return;
+ }
+ if (!gf_nsorts) {
+ gf_sortlist[0].tp = gf_sorts = (shortcircuit ? GS_NONE : GS_NAME);
+ gf_nsorts = 1;
+ }
+ /* Initialise receptacle for matched files, *
+ * expanded by insert() where necessary. */
+ matchptr = matchbuf = (Gmatch)zalloc((matchsz = 16) *
+ sizeof(struct gmatch));
+ matchct = 0;
+ pattrystart();
+
+ /* The actual processing takes place here: matches go into *
+ * matchbuf. This is the only top-level call to scanner(). */
+ scanner(q, shortcircuit);
+
+ /* 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);
+ zfree(matchbuf, 0);
+ restore_globstate(saved);
+ return;
+ } else {
+ /* treat as an ordinary string */
+ untokenize(matchptr->name = dupstring(ostr));
+ matchptr++;
+ matchct = 1;
+ }
+ }
+
+ if (!(gf_sortlist[0].tp & GS_NONE)) {
+ /*
+ * Get the strings to use for sorting by executing
+ * the code chunk. We allow more than one of these.
+ */
+ int nexecs = 0;
+ struct globsort *sortp;
+ struct globsort *lastsortp = gf_sortlist + gf_nsorts;
+ Gmatch gmptr;
+
+ /* First find out if there are any GS_EXECs, counting them. */
+ for (sortp = gf_sortlist; sortp < lastsortp; sortp++)
+ {
+ if (sortp->tp & GS_EXEC)
+ nexecs++;
+ }
+
+ if (nexecs) {
+ Gmatch tmpptr;
+ int iexec = 0;
+
+ /* Yes; allocate enough space for strings for each */
+ for (tmpptr = matchbuf; tmpptr < matchptr; tmpptr++)
+ tmpptr->sortstrs = (char **)zhalloc(nexecs*sizeof(char*));
+
+ /* Loop over each one, incrementing iexec */
+ for (sortp = gf_sortlist; sortp < lastsortp; sortp++)
+ {
+ /* Ignore unless this is a GS_EXEC */
+ if (sortp->tp & GS_EXEC) {
+ Eprog prog;
+
+ if ((prog = parse_string(sortp->exec, 0))) {
+ int ef = errflag, lv = lastval;
+
+ /* Parsed OK, execute for each name */
+ for (tmpptr = matchbuf; tmpptr < matchptr; tmpptr++) {
+ setsparam("REPLY", ztrdup(tmpptr->name));
+ execode(prog, 1, 0, "globsort");
+ if (!errflag)
+ tmpptr->sortstrs[iexec] =
+ dupstring(getsparam("REPLY"));
+ else
+ tmpptr->sortstrs[iexec] = tmpptr->name;
+ }
+
+ /* Retain any user interrupt error status */
+ errflag = ef | (errflag & ERRFLAG_INT);
+ lastval = lv;
+ } else {
+ /* Failed, let's be safe */
+ for (tmpptr = matchbuf; tmpptr < matchptr; tmpptr++)
+ tmpptr->sortstrs[iexec] = tmpptr->name;
+ }
+
+ iexec++;
+ }
+ }
+ }
+
+ /*
+ * Where necessary, create unmetafied version of names
+ * for comparison. If no Meta characters just point
+ * to original string. All on heap.
+ */
+ for (gmptr = matchbuf; gmptr < matchptr; gmptr++)
+ {
+ if (strchr(gmptr->name, Meta))
+ {
+ int dummy;
+ gmptr->uname = dupstring(gmptr->name);
+ unmetafy(gmptr->uname, &dummy);
+ } else {
+ gmptr->uname = gmptr->name;
+ }
+ }
+
+ /* Sort arguments in to lexical (and possibly numeric) order. *
+ * This is reversed to facilitate insertion into the list. */
+ qsort((void *) & matchbuf[0], matchct, sizeof(struct gmatch),
+ (int (*) _((const void *, const void *)))gmatchcmp);
+ }
+
+ if (first < 0) {
+ first += matchct;
+ if (first < 0)
+ first = 0;
+ }
+ if (end < 0)
+ end += matchct + 1;
+ else if (end > matchct)
+ end = matchct;
+ if ((end -= first) > 0) {
+ if (gf_sortlist[0].tp & GS_NONE) {
+ /* Match list was never reversed, so insert back to front. */
+ matchptr = matchbuf + matchct - first - 1;
+ while (end-- > 0) {
+ /* insert matches in the arg list */
+ insert_glob_match(list, node, matchptr->name);
+ matchptr--;
+ }
+ } else {
+ matchptr = matchbuf + matchct - first - end;
+ while (end-- > 0) {
+ /* insert matches in the arg list */
+ insert_glob_match(list, node, matchptr->name);
+ matchptr++;
+ }
+ }
+ } else if (!badcshglob && !isset(NOMATCH) && matchct == 1) {
+ insert_glob_match(list, node, (--matchptr)->name);
+ }
+ zfree(matchbuf, 0);
+
+ restore_globstate(saved);
+}
+
+/* Return the trailing character for marking file types */
+
+/**/
+mod_export 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 */
+
+/**/
+mod_export 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, c;
+
+ for (bc = 0; (c = *str); ++str)
+ if (c == Inbrace) {
+ if (!bc && str[1] == Outbrace)
+ *str++ = '{', *str = '}';
+ else
+ bc++;
+ } else if (c == 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) {
+ if (bracechardots(str-1, NULL, NULL))
+ return 1;
+ lbr = str - 1;
+ if (IS_DASH(*str))
+ str++;
+ while (idigit(*str))
+ str++;
+ if (*str == '.' && str[1] == '.') {
+ str++; str++;
+ if (IS_DASH(*str))
+ str++;
+ while (idigit(*str))
+ str++;
+ if (*str == Outbrace &&
+ (idigit(lbr[1]) || idigit(str[-1])))
+ return 1;
+ else if (*str == '.' && str[1] == '.') {
+ str++; str++;
+ if (IS_DASH(*str))
+ str++;
+ while (idigit(*str))
+ 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 redirtab)
+{
+ char *nam;
+ struct redir *ff;
+ int ret = 0;
+ local_list1(fake);
+
+ /* Stick the name in a list... */
+ init_list1(fake, fn->name);
+ /* ...which undergoes all the usual shell expansions */
+ prefork(&fake, isset(MULTIOS) ? 0 : PREFORK_SINGLE, NULL);
+ /* Globbing is only done for multios. */
+ if (!errflag && isset(MULTIOS))
+ globlist(&fake, 0);
+ 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 == REDIR_MERGEIN || fn->type == REDIR_MERGEOUT) {
+ if (IS_DASH(s[0]) && !s[1])
+ fn->type = REDIR_CLOSE;
+ else if (s[0] == 'p' && !s[1])
+ fn->fd2 = -2;
+ else {
+ while (idigit(*s))
+ s++;
+ if (!*s && s > fn->name)
+ fn->fd2 = zstrtol(fn->name, NULL, 10);
+ else if (fn->type == REDIR_MERGEIN)
+ zerr("file number expected");
+ else
+ fn->type = REDIR_ERRWRITE;
+ }
+ }
+ } else if (fn->type == REDIR_MERGEIN)
+ zerr("file number expected");
+ else {
+ if (fn->type == REDIR_MERGEOUT)
+ fn->type = REDIR_ERRWRITE;
+ while ((nam = (char *)ugetnode(&fake))) {
+ /* Loop over matches, duplicating the *
+ * redirection for each file found. */
+ ff = (struct redir *) zhalloc(sizeof *ff);
+ *ff = *fn;
+ ff->name = nam;
+ addlinknode(redirtab, ff);
+ ret = 1;
+ }
+ }
+ return ret;
+}
+
+/*
+ * Check for a brace expansion of the form {<char>..<char>}.
+ * On input str must be positioned at an Inbrace, but the sequence
+ * of characters beyond that has not necessarily been checked.
+ * Return 1 if found else 0.
+ *
+ * The other parameters are optionaland if the function returns 1 are
+ * used to return:
+ * - *c1p: the first character in the expansion.
+ * - *c2p: the final character in the expansion.
+ */
+
+/**/
+static int
+bracechardots(char *str, convchar_t *c1p, convchar_t *c2p)
+{
+ convchar_t cstart, cend;
+ char *pnext = str + 1, *pconv, convstr[2];
+ if (itok(*pnext)) {
+ if (*pnext == Inbrace)
+ return 0;
+ convstr[0] = ztokens[*pnext - Pound];
+ convstr[1] = '\0';
+ pconv = convstr;
+ } else
+ pconv = pnext;
+ MB_METACHARINIT();
+ pnext += MB_METACHARLENCONV(pconv, &cstart);
+ if (
+#ifdef MULTIBYTE_SUPPORT
+ cstart == WEOF ||
+#else
+ !cstart ||
+#endif
+ pnext[0] != '.' || pnext[1] != '.')
+ return 0;
+ pnext += 2;
+ if (!*pnext)
+ return 0;
+ if (itok(*pnext)) {
+ if (*pnext == Inbrace)
+ return 0;
+ convstr[0] = ztokens[*pnext - Pound];
+ convstr[1] = '\0';
+ pconv = convstr;
+ } else
+ pconv = pnext;
+ MB_METACHARINIT();
+ pnext += MB_METACHARLENCONV(pconv, &cend);
+ if (
+#ifdef MULTIBYTE_SUPPORT
+ cend == WEOF ||
+#else
+ !cend ||
+#endif
+ *pnext != Outbrace)
+ return 0;
+ if (c1p)
+ *c1p = cstart;
+ if (c2p)
+ *c2p = cend;
+ return 1;
+}
+
+/* brace expansion */
+
+/**/
+mod_export 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} */
+ ++str2;
+ }
+ }
+ 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, *dots2 = NULL;
+ LinkNode olast = last;
+ /* Get the first number of the range */
+ zlong rstart, rend;
+ int err = 0, rev = 0, rincr = 1;
+ int wid1, wid2, wid3, strp;
+ convchar_t cstart, cend;
+
+ if (bracechardots(str, &cstart, &cend)) {
+ int lenalloc;
+ /*
+ * This is a character range.
+ */
+ if (cend < cstart) {
+ convchar_t ctmp = cend;
+ cend = cstart;
+ cstart = ctmp;
+ rev = 1;
+ }
+ uremnode(list, node);
+ strp = str - str3;
+ lenalloc = strp + strlen(str2+1) + 1;
+ do {
+#ifdef MULTIBYTE_SUPPORT
+ char *ncptr;
+ int nclen;
+ mb_charinit();
+ ncptr = wcs_nicechar(cend, NULL, NULL);
+ nclen = strlen(ncptr);
+ p = zhalloc(lenalloc + nclen);
+ memcpy(p, str3, strp);
+ memcpy(p + strp, ncptr, nclen);
+ strcpy(p + strp + nclen, str2 + 1);
+#else
+ p = zhalloc(lenalloc + 1);
+ memcpy(p, str3, strp);
+ sprintf(p + strp, "%c", cend);
+ strcat(p + strp, str2 + 1);
+#endif
+ insertlinknode(list, last, p);
+ if (rev) /* decreasing: add in reverse order. */
+ last = nextnode(last);
+ } while (cend-- > cstart);
+ *np = nextnode(olast);
+ return;
+ }
+
+ /* Get the first number of the range */
+ rstart = zstrtol(str+1,&dots,10);
+ rend = 0;
+ wid1 = (dots - str) - 1;
+ wid2 = (str2 - dots) - 2;
+ wid3 = 0;
+ 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)
+ err++;
+ /* check for {num1..num2..incr} */
+ if (p != str2) {
+ wid2 = (p - dots) - 2;
+ dots2 = p;
+ if (dotdot == 2 && *p == '.' && p[1] == '.') {
+ rincr = zstrtol(p+2, &p, 10);
+ wid3 = p - dots2 - 2;
+ if (p != str2 || !rincr)
+ err++;
+ } else
+ err++;
+ }
+ }
+ if (!err) {
+ /* If either no. begins with a zero, pad the output with *
+ * zeroes. Otherwise, set min width to 0 to suppress them.
+ * str+1 is the first number in the range, dots+2 the last,
+ * and dots2+2 is the increment if that's given. */
+ /* TODO: sorry about this */
+ int minw = (str[1] == '0' ||
+ (IS_DASH(str[1]) && str[2] == '0'))
+ ? wid1
+ : (dots[2] == '0' ||
+ (IS_DASH(dots[2]) && dots[3] == '0'))
+ ? wid2
+ : (dots2 && (dots2[2] == '0' ||
+ (IS_DASH(dots2[2]) && dots2[3] == '0')))
+ ? wid3
+ : 0;
+ if (rincr < 0) {
+ /* Handle negative increment */
+ rincr = -rincr;
+ rev = !rev;
+ }
+ if (rstart > rend) {
+ /* Handle decreasing ranges correctly. */
+ zlong rt = rend;
+ rend = rstart;
+ rstart = rt;
+ rev = !rev;
+ } else if (rincr > 1) {
+ /* when incr > 1, range is aligned to the highest number of str1,
+ * compensate for this so that it is aligned to the first number */
+ rend -= (rend - rstart) % rincr;
+ }
+ uremnode(list, node);
+ for (; rend >= rstart; rend -= rincr) {
+ /* Node added in at end, so do highest first */
+ p = dupstring(str3);
+#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
+ sprintf(p + strp, "%0*lld", minw, rend);
+#else
+ sprintf(p + strp, "%0*ld", minw, (long)rend);
+#endif
+ 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;
+ unsigned int len, pl;
+ int lastch = -1;
+
+ uremnode(list, node);
+ memset(ccl, 0, sizeof(ccl) / sizeof(ccl[0]));
+ for (p = str + 1; 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 (IS_DASH((char)c1) && lastch >= 0 &&
+ p < str2 && lastch <= (int)c2) {
+ while (lastch < (int)c2)
+ ccl[lastch++] = 1;
+ lastch = -1;
+ } else
+ ccl[lastch = c1] = 1;
+ }
+ pl = str - str3;
+ len = pl + strlen(++str2) + 2;
+ for (p = ccl + 256; p-- > ccl;)
+ if (*p) {
+ c1 = p - ccl;
+ if (imeta(c1)) {
+ str = hcalloc(len + 1);
+ str[pl] = Meta;
+ str[pl+1] = c1 ^ 32;
+ strcpy(str + pl + 2, str2);
+ } else {
+ str = hcalloc(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 *) hcalloc(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)
+{
+ Patprog p;
+ int ret;
+
+ queue_signals(); /* Protect PAT_STATIC */
+
+ if (!(p = patcompile(b, PAT_STATIC, NULL))) {
+ zerr("bad pattern: %s", b);
+ ret = 0;
+ } else
+ ret = pattry(p, a);
+
+ unqueue_signals();
+
+ return ret;
+}
+
+/* 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 imd->ustr
+ * and finishes e characters in: 0 <= b <= e <= imd->ulen on input
+ * (yes, empty matches should work).
+ *
+ * imd->flags is a set of the SUB_* matches defined in zsh.h from
+ * SUB_MATCH onwards; the lower parts are ignored.
+ *
+ * imd->replstr is the replacement string for a substitution
+ *
+ * imd->replstr is metafied and the values put in imd->repllist are metafied.
+ */
+
+/**/
+static char *
+get_match_ret(Imatchdata imd, int b, int e)
+{
+ char buf[80], *r, *p, *rr, *replstr = imd->replstr;
+ int ll = 0, bl = 0, t = 0, add = 0, fl = imd->flags, i;
+
+ /* Account for b and e referring to unmetafied string */
+ for (p = imd->ustr; p < imd->ustr + b; p++)
+ if (imeta(*p))
+ add++;
+ b += add;
+ for (; p < imd->ustr + e; p++)
+ if (imeta(*p))
+ add++;
+ e += add;
+
+ /* Everything now refers to metafied lengths. */
+ if (replstr || (fl & SUB_LIST)) {
+ if (fl & SUB_DOSUBST) {
+ replstr = dupstring(replstr);
+ singsub(&replstr);
+ untokenize(replstr);
+ }
+ if ((fl & (SUB_GLOBAL|SUB_LIST)) && imd->repllist) {
+ /* We are replacing the chunk, just add this to the list */
+ Repldata rd = (Repldata)
+ ((fl & SUB_LIST) ? zalloc(sizeof(*rd)) : zhalloc(sizeof(*rd)));
+ rd->b = b;
+ rd->e = e;
+ rd->replstr = replstr;
+ if (fl & SUB_LIST)
+ zaddlinknode(imd->repllist, rd);
+ else
+ addlinknode(imd->repllist, rd);
+ return imd->mstr;
+ }
+ ll += strlen(replstr);
+ }
+ if (fl & SUB_MATCH) /* matched portion */
+ ll += 1 + (e - b);
+ if (fl & SUB_REST) /* unmatched portion */
+ ll += 1 + (imd->mlen - (e - b));
+ if (fl & SUB_BIND) {
+ /* position of start of matched portion */
+ sprintf(buf, "%d ", MB_METASTRLEN2END(imd->mstr, 0, imd->mstr+b) + 1);
+ ll += (bl = strlen(buf));
+ }
+ if (fl & SUB_EIND) {
+ /* position of end of matched portion */
+ sprintf(buf + bl, "%d ",
+ MB_METASTRLEN2END(imd->mstr, 0, imd->mstr+e) + 1);
+ ll += (bl = strlen(buf));
+ }
+ if (fl & SUB_LEN) {
+ /* length of matched portion */
+ sprintf(buf + bl, "%d ", MB_METASTRLEN2END(imd->mstr+b, 0,
+ imd->mstr+e));
+ ll += (bl = strlen(buf));
+ }
+ if (bl)
+ buf[bl - 1] = '\0';
+
+ rr = r = (char *) hcalloc(ll);
+
+ if (fl & SUB_MATCH) {
+ /* copy matched portion to new buffer */
+ for (i = b, p = imd->mstr + b; i < e; i++)
+ *rr++ = *p++;
+ t = 1;
+ }
+ if (fl & SUB_REST) {
+ /* 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 = imd->mstr; i < b; i++)
+ *rr++ = *p++;
+ if (replstr)
+ for (p = replstr; *p; )
+ *rr++ = *p++;
+ for (i = e, p = imd->mstr + e; i < imd->mlen; 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;
+}
+
+static Patprog
+compgetmatch(char *pat, int *flp, char **replstrp)
+{
+ Patprog p;
+ /*
+ * Flags to pattern compiler: use static buffer since we only
+ * have one pattern at a time; we will try the must-match test ourselves,
+ * so tell the pattern compiler we are scanning.
+ */
+
+ /* int patflags = PAT_STATIC|PAT_SCAN|PAT_NOANCH;*/
+
+ /* Unfortunately, PAT_STATIC doesn't work if we have a replstr with
+ * something like ${x#...} in it which will be singsub()ed below because
+ * that would overwrite the pattern buffer. */
+
+ int patflags = PAT_SCAN|PAT_NOANCH | (*replstrp ? 0 : PAT_STATIC);
+
+ /*
+ * Search is anchored to the end of the string if we want to match
+ * it all, or if we are matching at the end of the string and not
+ * using substrings.
+ */
+ if ((*flp & SUB_ALL) || ((*flp & SUB_END) && !(*flp & SUB_SUBSTR)))
+ patflags &= ~PAT_NOANCH;
+ p = patcompile(pat, patflags, NULL);
+ if (!p) {
+ zerr("bad pattern: %s", pat);
+ return NULL;
+ }
+ if (*replstrp) {
+ if (p->patnpar || (p->globend & GF_MATCHREF)) {
+ /*
+ * Either backreferences or match references, so we
+ * need to re-substitute replstr each time round.
+ */
+ *flp |= SUB_DOSUBST;
+ } else {
+ singsub(replstrp);
+ untokenize(*replstrp);
+ }
+ }
+
+ return p;
+}
+
+/*
+ * This is called from paramsubst to get the match for ${foo#bar} etc.
+ * fl is a set of the SUB_* flags defined in zsh.h
+ * *sp points to the string we have to modify. The n'th match will be
+ * returned in *sp. The heap is used to get memory for the result string.
+ * replstr is the replacement string from a ${.../orig/repl}, in
+ * which case pat is the original.
+ *
+ * n is now ignored unless we are looking for a substring, in
+ * which case the n'th match from the start is counted such that
+ * there is no more than one match from each position.
+ */
+
+/**/
+int
+getmatch(char **sp, char *pat, int fl, int n, char *replstr)
+{
+ Patprog p;
+
+ if (!(p = compgetmatch(pat, &fl, &replstr)))
+ return 1;
+
+ return igetmatch(sp, p, fl, n, replstr, NULL);
+}
+
+/*
+ * This is the corresponding function for array variables.
+ * Matching is done with the same pattern on each element.
+ */
+
+/**/
+void
+getmatcharr(char ***ap, char *pat, int fl, int n, char *replstr)
+{
+ char **arr = *ap, **pp;
+ Patprog p;
+
+ if (!(p = compgetmatch(pat, &fl, &replstr)))
+ return;
+
+ *ap = pp = hcalloc(sizeof(char *) * (arrlen(arr) + 1));
+ while ((*pp = *arr++))
+ if (igetmatch(pp, p, fl, n, replstr, NULL))
+ pp++;
+}
+
+/*
+ * Match against str using pattern pp; return a list of
+ * Repldata matches in the linked list *repllistp; this is
+ * in permanent storage and to be freed by freematchlist()
+ */
+
+/**/
+mod_export int
+getmatchlist(char *str, Patprog p, LinkList *repllistp)
+{
+ char **sp = &str;
+
+ /*
+ * We don't care if we have longest or shortest match, but SUB_LONG
+ * is cheaper since the pattern code does that by default.
+ * We need SUB_GLOBAL to get all matches.
+ * We need SUB_SUBSTR to scan through for substrings.
+ * We need SUB_LIST to activate the special handling of the list
+ * passed in.
+ */
+ return igetmatch(sp, p, SUB_LONG|SUB_GLOBAL|SUB_SUBSTR|SUB_LIST,
+ 0, NULL, repllistp);
+}
+
+static void
+freerepldata(void *ptr)
+{
+ zfree(ptr, sizeof(struct repldata));
+}
+
+/**/
+mod_export void
+freematchlist(LinkList repllist)
+{
+ freelinklist(repllist, freerepldata);
+}
+
+/**/
+static void
+set_pat_start(Patprog p, int offs)
+{
+ /*
+ * If we are messing around with the test string by advancing up
+ * it from the start, we need to tell the pattern matcher that
+ * a start-of-string assertion, i.e. (#s), should fail. Hence
+ * we test whether the offset of the real start of string from
+ * the actual start, passed as offs, is zero.
+ */
+ if (offs)
+ p->flags |= PAT_NOTSTART;
+ else
+ p->flags &= ~PAT_NOTSTART;
+}
+
+/**/
+static void
+set_pat_end(Patprog p, char null_me)
+{
+ /*
+ * If we are messing around with the string by shortening it at the
+ * tail, we need to tell the pattern matcher that an end-of-string
+ * assertion, i.e. (#e), should fail. Hence we test whether
+ * the character null_me about to be zapped is or is not already a null.
+ */
+ if (null_me)
+ p->flags |= PAT_NOTEND;
+ else
+ p->flags &= ~PAT_NOTEND;
+}
+
+/**/
+#ifdef MULTIBYTE_SUPPORT
+
+/*
+ * Increment *tp over character which may be multibyte.
+ * Return number of bytes.
+ * All unmetafied here.
+ */
+
+/**/
+static int iincchar(char **tp, int left)
+{
+ char *t = *tp;
+ int mbclen = mb_charlenconv(t, left, NULL);
+ *tp = t + mbclen;
+
+ return mbclen;
+}
+
+/**/
+static int
+igetmatch(char **sp, Patprog p, int fl, int n, char *replstr,
+ LinkList *repllistp)
+{
+ char *s = *sp, *t, *tmatch, *send;
+ /*
+ * Note that ioff counts (possibly multibyte) characters in the
+ * character set (Meta's are not included), while l counts characters in
+ * the metafied string.
+ *
+ * umlen is a counter for (unmetafied) byte lengths---neither characters
+ * nor raw byte indices; this is simply an optimisation for allocation.
+ * umltot is the full length of the string in this scheme.
+ *
+ * l is the raw string length, used together with any pointers into
+ * the string (typically t).
+ */
+ int ioff, l = strlen(*sp), matched = 1, umltot = ztrlen(*sp);
+ int umlen, nmatches;
+ struct patstralloc patstralloc;
+ struct imatchdata imd;
+
+ (void)patallocstr(p, s, l, umltot, 1, &patstralloc);
+ s = patstralloc.alloced;
+ DPUTS(!s, "forced patallocstr failed");
+ send = s + umltot;
+
+ imd.mstr = *sp;
+ imd.mlen = l;
+ imd.ustr = s;
+ imd.ulen = umltot;
+ imd.flags = fl;
+ imd.replstr = replstr;
+ imd.repllist = NULL;
+
+ /* perform must-match test for complex closures */
+ if (p->mustoff)
+ {
+ char *muststr = (char *)p + p->mustoff;
+
+ matched = 0;
+ if (p->patmlen <= umltot)
+ {
+ for (t = s; t <= send - p->patmlen; t++)
+ {
+ if (!memcmp(muststr, t, p->patmlen)) {
+ matched = 1;
+ break;
+ }
+ }
+ }
+ }
+
+ /* in case we used the prog before... */
+ p->flags &= ~(PAT_NOTSTART|PAT_NOTEND);
+
+ if (fl & SUB_ALL) {
+ int i = matched && pattrylen(p, s, umltot, 0, &patstralloc, 0);
+ if (!i) {
+ /* Perform under no-match conditions */
+ umltot = 0;
+ imd.replstr = NULL;
+ }
+ *sp = get_match_ret(&imd, 0, umltot);
+ if (! **sp && (((fl & SUB_MATCH) && !i) || ((fl & SUB_REST) && i)))
+ return 0;
+ return 1;
+ }
+ if (matched) {
+ /*
+ * The default behaviour is to match at the start; this
+ * is modified by SUB_END and SUB_SUBSTR. SUB_END matches
+ * at the end of the string instead of the start. SUB_SUBSTR
+ * without SUB_END matches substrings searching from the start;
+ * with SUB_END it matches substrings searching from the end.
+ *
+ * The possibilities are further modified by whether we want the
+ * longest (SUB_LONG) or shortest possible match.
+ *
+ * SUB_START is only used in the case where we are also
+ * forcing a match at the end (SUB_END with no SUB_SUBSTR,
+ * with or without SUB_LONG), to indicate we should match
+ * the entire string.
+ */
+ switch (fl & (SUB_END|SUB_LONG|SUB_SUBSTR)) {
+ case 0:
+ case SUB_LONG:
+ /*
+ * Largest/smallest possible match at head of string.
+ * First get the longest match...
+ */
+ if (pattrylen(p, s, umltot, 0, &patstralloc, 0)) {
+ /* patmatchlen returns unmetafied length in this case */
+ int mlen = patmatchlen();
+ if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
+ send = s + mlen;
+ /*
+ * ... now we know whether it's worth looking for the
+ * shortest, which we do by brute force.
+ */
+ mb_charinit();
+ for (t = s, umlen = 0; t < send; ) {
+ set_pat_end(p, *t);
+ if (pattrylen(p, s, umlen, 0, &patstralloc, 0)) {
+ mlen = patmatchlen();
+ break;
+ }
+ umlen += iincchar(&t, send - t);
+ }
+ }
+ *sp = get_match_ret(&imd, 0, mlen);
+ return 1;
+ }
+ break;
+
+ case SUB_END:
+ /*
+ * Smallest possible match at tail of string.
+ * As we can only be sure we've got wide characters right
+ * when going forwards, we need to match at every point
+ * until we fail and record the last successful match.
+ *
+ * It's important that we return the last successful match
+ * so that match, mbegin, mend and MATCH, MBEGIN, MEND are
+ * correct.
+ */
+ mb_charinit();
+ tmatch = NULL;
+ for (ioff = 0, t = s, umlen = umltot; t < send; ioff++) {
+ set_pat_start(p, t-s);
+ if (pattrylen(p, t, umlen, 0, &patstralloc, ioff))
+ tmatch = t;
+ if (fl & SUB_START)
+ break;
+ umlen -= iincchar(&t, send - t);
+ }
+ if (tmatch) {
+ *sp = get_match_ret(&imd, tmatch - s, umltot);
+ return 1;
+ }
+ if (!(fl & SUB_START) && pattrylen(p, s + umltot, 0, 0,
+ &patstralloc, ioff)) {
+ *sp = get_match_ret(&imd, umltot, umltot);
+ return 1;
+ }
+ break;
+
+ case (SUB_END|SUB_LONG):
+ /* Largest possible match at tail of string: *
+ * move forward along string until we get a match. *
+ * Again there's no optimisation. */
+ mb_charinit();
+ for (ioff = 0, t = s, umlen = umltot; t <= send ; ioff++) {
+ set_pat_start(p, t-s);
+ if (pattrylen(p, t, umlen, 0, &patstralloc, ioff)) {
+ *sp = get_match_ret(&imd, t-s, umltot);
+ return 1;
+ }
+ if (fl & SUB_START)
+ break;
+ if (t == send)
+ break;
+ umlen -= iincchar(&t, send - t);
+ }
+ if (!(fl & SUB_START) && pattrylen(p, send, 0, 0,
+ &patstralloc, ioff)) {
+ *sp = get_match_ret(&imd, umltot, umltot);
+ return 1;
+ }
+ break;
+
+ case SUB_SUBSTR:
+ /* Smallest at start, but matching substrings. */
+ set_pat_start(p, l);
+ if (!(fl & SUB_GLOBAL) &&
+ pattrylen(p, send, 0, 0, &patstralloc, 0) &&
+ !--n) {
+ *sp = get_match_ret(&imd, 0, 0);
+ return 1;
+ } /* fall through */
+ case (SUB_SUBSTR|SUB_LONG):
+ /* longest or smallest at start with substrings */
+ t = s;
+ if (fl & SUB_GLOBAL) {
+ imd.repllist = (fl & SUB_LIST) ? znewlinklist() : newlinklist();
+ if (repllistp)
+ *repllistp = imd.repllist;
+ }
+ ioff = 0; /* offset into string */
+ umlen = umltot;
+ mb_charinit();
+ do {
+ /* loop over all matches for global substitution */
+ matched = 0;
+ for (; t <= send; ioff++) {
+ /* Find the longest match from this position. */
+ set_pat_start(p, t-s);
+ if (pattrylen(p, t, umlen, 0, &patstralloc, ioff)) {
+ char *mpos = t + patmatchlen();
+ if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
+ char *ptr;
+ int umlen2;
+ /*
+ * If searching for the shortest match,
+ * start with a zero length and increase
+ * it until we reach the longest possible
+ * match, accepting the first successful
+ * match.
+ */
+ for (ptr = t, umlen2 = 0; ptr < mpos;) {
+ set_pat_end(p, *ptr);
+ if (pattrylen(p, t, umlen2, 0,
+ &patstralloc, ioff)) {
+ mpos = t + patmatchlen();
+ break;
+ }
+ umlen2 += iincchar(&ptr, mpos - ptr);
+ }
+ }
+ if (!--n || (n <= 0 && (fl & SUB_GLOBAL))) {
+ *sp = get_match_ret(&imd, t-s, mpos-s);
+ if (mpos == t)
+ mpos += mb_charlenconv(mpos, send - mpos, NULL);
+ }
+ if (!(fl & SUB_GLOBAL)) {
+ if (n) {
+ /*
+ * Looking for a later match: in this case,
+ * we can continue looking for matches from
+ * the next character, even if it overlaps
+ * with what we just found.
+ */
+ umlen -= iincchar(&t, send - t);
+ continue;
+ } else {
+ return 1;
+ }
+ }
+ /*
+ * For a global match, we need to skip the stuff
+ * which is already marked for replacement.
+ */
+ matched = 1;
+ if (t == send)
+ break;
+ while (t < mpos) {
+ ioff++;
+ umlen -= iincchar(&t, send - t);
+ }
+ break;
+ }
+ if (t == send)
+ break;
+ umlen -= iincchar(&t, send - t);
+ }
+ } while (matched && t < send);
+ /*
+ * check if we can match a blank string, if so do it
+ * at the start. Goodness knows if this is a good idea
+ * with global substitution, so it doesn't happen.
+ */
+ set_pat_start(p, l);
+ if ((fl & (SUB_LONG|SUB_GLOBAL)) == SUB_LONG &&
+ pattrylen(p, send, 0, 0, &patstralloc, 0) && !--n) {
+ *sp = get_match_ret(&imd, 0, 0);
+ return 1;
+ }
+ break;
+
+ case (SUB_END|SUB_SUBSTR):
+ case (SUB_END|SUB_LONG|SUB_SUBSTR):
+ /* Longest/shortest at end, matching substrings. */
+ if (!(fl & SUB_LONG)) {
+ set_pat_start(p, l);
+ if (pattrylen(p, send, 0, 0, &patstralloc, umltot) &&
+ !--n) {
+ *sp = get_match_ret(&imd, umltot, umltot);
+ return 1;
+ }
+ }
+ /*
+ * If multibyte characters are present we need to start from the
+ * beginning. This is a bit unpleasant because we can't tell in
+ * advance how many times it will match and from where, so if n is
+ * greater then 1 we will need to count the number of times it
+ * matched and then go through again until we reach the right
+ * point. (Either that or record every single match in a list,
+ * which isn't stupid; it involves more memory management at this
+ * level but less use of the pattern matcher.)
+ */
+ nmatches = 0;
+ tmatch = NULL;
+ mb_charinit();
+ for (ioff = 0, t = s, umlen = umltot; t < send; ioff++) {
+ set_pat_start(p, t-s);
+ if (pattrylen(p, t, umlen, 0, &patstralloc, ioff)) {
+ nmatches++;
+ tmatch = t;
+ }
+ umlen -= iincchar(&t, send - t);
+ }
+ if (nmatches) {
+ char *mpos;
+ if (n > 1) {
+ /*
+ * We need to find the n'th last match.
+ */
+ n = nmatches - n;
+ mb_charinit();
+ for (ioff = 0, t = s, umlen = umltot; t < send; ioff++) {
+ set_pat_start(p, t-s);
+ if (pattrylen(p, t, umlen, 0, &patstralloc, ioff) &&
+ !n--) {
+ tmatch = t;
+ break;
+ }
+ umlen -= iincchar(&t, send - t);
+ }
+ }
+ mpos = tmatch + patmatchlen();
+ /* Look for the shortest match if necessary */
+ if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
+ for (t = tmatch, umlen = 0; t < mpos; ) {
+ set_pat_end(p, *t);
+ if (pattrylen(p, tmatch, umlen, 0,
+ &patstralloc, ioff)) {
+ mpos = tmatch + patmatchlen();
+ break;
+ }
+ umlen += iincchar(&t, mpos - t);
+ }
+ }
+ *sp = get_match_ret(&imd, tmatch-s, mpos-s);
+ return 1;
+ }
+ set_pat_start(p, l);
+ if ((fl & SUB_LONG) && pattrylen(p, send, 0, 0,
+ &patstralloc, umltot) &&
+ !--n) {
+ *sp = get_match_ret(&imd, umltot, umltot);
+ return 1;
+ }
+ break;
+ }
+ }
+
+ if (imd.repllist && nonempty(imd.repllist)) {
+ /* Put all the bits of a global search and replace together. */
+ LinkNode nd;
+ Repldata rd;
+ int lleft;
+ char *ptr, *start;
+ int i;
+
+ /*
+ * Use metafied string again.
+ * Results from get_match_ret in repllist are all metafied.
+ */
+ s = *sp;
+ if (!(fl & SUB_LIST)) {
+ lleft = 0; /* size of returned string */
+ i = 0; /* start of last chunk we got from *sp */
+ for (nd = firstnode(imd.repllist); nd; incnode(nd)) {
+ rd = (Repldata) getdata(nd);
+ lleft += rd->b - i; /* previous chunk of *sp */
+ lleft += strlen(rd->replstr); /* the replaced bit */
+ i = rd->e; /* start of next chunk of *sp */
+ }
+ lleft += l - i; /* final chunk from *sp */
+ start = t = zhalloc(lleft+1);
+ i = 0;
+ for (nd = firstnode(imd.repllist); nd; incnode(nd)) {
+ rd = (Repldata) getdata(nd);
+ memcpy(t, s + i, rd->b - i);
+ t += rd->b - i;
+ ptr = rd->replstr;
+ while (*ptr)
+ *t++ = *ptr++;
+ i = rd->e;
+ }
+ memcpy(t, s + i, l - i);
+ start[lleft] = '\0';
+ *sp = (char *)start;
+ }
+ return 1;
+ }
+ if (fl & SUB_LIST) { /* safety: don't think this can happen */
+ return 0;
+ }
+
+ /* munge the whole string: no match, so no replstr */
+ imd.replstr = NULL;
+ imd.repllist = NULL;
+ *sp = get_match_ret(&imd, 0, 0);
+ return (fl & SUB_RETFAIL) ? 0 : 1;
+}
+
+/**/
+#else
+
+/*
+ * Increment pointer which may be on a Meta (x is a pointer variable),
+ * returning the incremented value (i.e. like pre-increment).
+ */
+#define METAINC(x) ((x) += (*(x) == Meta) ? 2 : 1)
+
+/**/
+static int
+igetmatch(char **sp, Patprog p, int fl, int n, char *replstr,
+ LinkList *repllistp)
+{
+ char *s = *sp, *t, *send;
+ /*
+ * Note that ioff and uml count characters in the character
+ * set (Meta's are not included), while l counts characters in the
+ * metafied string. umlen is a counter for (unmetafied) character
+ * lengths.
+ */
+ int ioff, l = strlen(*sp), uml = ztrlen(*sp), matched = 1, umlen;
+ struct patstralloc patstralloc;
+ struct imatchdata imd;
+
+ (void)patallocstr(p, s, l, uml, 1, &patstralloc);
+ s = patstralloc.alloced;
+ DPUTS(!s, "forced patallocstr failed");
+ send = s + uml;
+
+ imd.mstr = *sp;
+ imd.mlen = l;
+ imd.ustr = s;
+ imd.ulen = uml;
+ imd.flags = fl;
+ imd.replstr = replstr;
+ imd.repllist = NULL;
+
+ /* perform must-match test for complex closures */
+ if (p->mustoff)
+ {
+ char *muststr = (char *)p + p->mustoff;
+
+ matched = 0;
+ if (p->patmlen <= uml)
+ {
+ for (t = s; t <= send - p->patmlen; t++)
+ {
+ if (!memcmp(muststr, t, p->patmlen)) {
+ matched = 1;
+ break;
+ }
+ }
+ }
+ }
+
+ /* in case we used the prog before... */
+ p->flags &= ~(PAT_NOTSTART|PAT_NOTEND);
+
+ if (fl & SUB_ALL) {
+ int i = matched && pattrylen(p, s, uml, 0, &patstralloc, 0);
+ if (!i)
+ imd.replstr = NULL;
+ *sp = get_match_ret(&imd, 0, i ? l : 0);
+ if (! **sp && (((fl & SUB_MATCH) && !i) || ((fl & SUB_REST) && i)))
+ return 0;
+ return 1;
+ }
+ if (matched) {
+ switch (fl & (SUB_END|SUB_LONG|SUB_SUBSTR)) {
+ case 0:
+ case SUB_LONG:
+ /*
+ * Largest/smallest possible match at head of string.
+ * First get the longest match...
+ */
+ if (pattrylen(p, s, uml, 0, &patstralloc, 0)) {
+ /* patmatchlen returns metafied length, as we need */
+ int mlen = patmatchlen();
+ if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
+ send = s + mlen;
+ /*
+ * ... now we know whether it's worth looking for the
+ * shortest, which we do by brute force.
+ */
+ for (t = s, umlen = 0; t < s + mlen; METAINC(t), umlen++) {
+ set_pat_end(p, *t);
+ if (pattrylen(p, s, umlen, 0, &patstralloc, 0)) {
+ mlen = patmatchlen();
+ break;
+ }
+ }
+ }
+ *sp = get_match_ret(&imd, 0, mlen);
+ return 1;
+ }
+ break;
+
+ case SUB_END:
+ /* Smallest possible match at tail of string: *
+ * move back down string until we get a match. *
+ * There's no optimization here. */
+ for (ioff = uml, t = send, umlen = 0; t >= s;
+ t--, ioff--, umlen++) {
+ set_pat_start(p, t-s);
+ if (pattrylen(p, t, umlen, 0, &patstralloc, ioff)) {
+ *sp = get_match_ret(&imd, t - s, uml);
+ return 1;
+ }
+ }
+ break;
+
+ case (SUB_END|SUB_LONG):
+ /* Largest possible match at tail of string: *
+ * move forward along string until we get a match. *
+ * Again there's no optimisation. */
+ for (ioff = 0, t = s, umlen = uml; t < send;
+ ioff++, t++, umlen--) {
+ set_pat_start(p, t-s);
+ if (pattrylen(p, t, send - t, umlen, &patstralloc, ioff)) {
+ *sp = get_match_ret(&imd, t-s, uml);
+ return 1;
+ }
+ }
+ break;
+
+ case SUB_SUBSTR:
+ /* Smallest at start, but matching substrings. */
+ set_pat_start(p, l);
+ if (!(fl & SUB_GLOBAL) &&
+ pattrylen(p, send, 0, 0, &patstralloc, 0) && !--n) {
+ *sp = get_match_ret(&imd, 0, 0);
+ return 1;
+ } /* fall through */
+ case (SUB_SUBSTR|SUB_LONG):
+ /* longest or smallest at start with substrings */
+ t = s;
+ if (fl & SUB_GLOBAL) {
+ imd.repllist = newlinklist();
+ if (repllistp)
+ *repllistp = imd.repllist;
+ }
+ ioff = 0; /* offset into string */
+ umlen = uml;
+ do {
+ /* loop over all matches for global substitution */
+ matched = 0;
+ for (; t < send; t++, ioff++, umlen--) {
+ /* Find the longest match from this position. */
+ set_pat_start(p, t-s);
+ if (pattrylen(p, t, send - t, umlen, &patstralloc, ioff)) {
+ char *mpos = t + patmatchlen();
+ if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
+ char *ptr;
+ int umlen2;
+ for (ptr = t, umlen2 = 0; ptr < mpos;
+ ptr++, umlen2++) {
+ set_pat_end(p, *ptr);
+ if (pattrylen(p, t, ptr - t, umlen2,
+ &patstralloc, ioff)) {
+ mpos = t + patmatchlen();
+ break;
+ }
+ }
+ }
+ if (!--n || (n <= 0 && (fl & SUB_GLOBAL))) {
+ *sp = get_match_ret(&imd, t-s, mpos-s);
+ if (mpos == t)
+ mpos++;
+ }
+ if (!(fl & SUB_GLOBAL)) {
+ if (n) {
+ /*
+ * Looking for a later match: in this case,
+ * we can continue looking for matches from
+ * the next character, even if it overlaps
+ * with what we just found.
+ */
+ continue;
+ } else {
+ return 1;
+ }
+ }
+ /*
+ * For a global match, we need to skip the stuff
+ * which is already marked for replacement.
+ */
+ matched = 1;
+ while (t < mpos) {
+ ioff++;
+ umlen--;
+ t++;
+ }
+ break;
+ }
+ }
+ } while (matched);
+ /*
+ * check if we can match a blank string, if so do it
+ * at the start. Goodness knows if this is a good idea
+ * with global substitution, so it doesn't happen.
+ */
+ set_pat_start(p, l);
+ if ((fl & (SUB_LONG|SUB_GLOBAL)) == SUB_LONG &&
+ pattrylen(p, send, 0, 0, &patstralloc, 0) && !--n) {
+ *sp = get_match_ret(&imd, 0, 0);
+ return 1;
+ }
+ break;
+
+ case (SUB_END|SUB_SUBSTR):
+ case (SUB_END|SUB_LONG|SUB_SUBSTR):
+ /* Longest/shortest at end, matching substrings. */
+ if (!(fl & SUB_LONG)) {
+ set_pat_start(p, l);
+ if (pattrylen(p, send, 0, 0, &patstralloc, uml) && !--n) {
+ *sp = get_match_ret(&imd, uml, uml);
+ return 1;
+ }
+ }
+ for (ioff = uml - 1, t = send - 1, umlen = 1; t >= s;
+ t--, ioff--, umlen++) {
+ set_pat_start(p, t-s);
+ if (pattrylen(p, t, send - t, umlen, &patstralloc, ioff) &&
+ !--n) {
+ /* Found the longest match */
+ char *mpos = t + patmatchlen();
+ if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
+ char *ptr;
+ int umlen2;
+ for (ptr = t, umlen2 = 0; ptr < mpos;
+ ptr++, umlen2++) {
+ set_pat_end(p, *ptr);
+ if (pattrylen(p, t, umlen2, 0, &patstralloc,
+ ioff)) {
+ mpos = t + patmatchlen();
+ break;
+ }
+ }
+ }
+ *sp = get_match_ret(&imd, t-s, mpos-s);
+ return 1;
+ }
+ }
+ set_pat_start(p, l);
+ if ((fl & SUB_LONG) && pattrylen(p, send, 0, 0,
+ &patstralloc, uml) &&
+ !--n) {
+ *sp = get_match_ret(&imd, uml, uml);
+ return 1;
+ }
+ break;
+ }
+ }
+
+ if (imd.repllist && nonempty(imd.repllist)) {
+ /* Put all the bits of a global search and replace together. */
+ LinkNode nd;
+ Repldata rd;
+ int lleft = 0; /* size of returned string */
+ char *ptr, *start;
+ int i;
+
+ /*
+ * Use metafied string again.
+ * Results from get_match_ret in repllist are all metafied.
+ */
+ s = *sp;
+ i = 0; /* start of last chunk we got from *sp */
+ for (nd = firstnode(imd.repllist); nd; incnode(nd)) {
+ rd = (Repldata) getdata(nd);
+ lleft += rd->b - i; /* previous chunk of *sp */
+ lleft += strlen(rd->replstr); /* the replaced bit */
+ i = rd->e; /* start of next chunk of *sp */
+ }
+ lleft += l - i; /* final chunk from *sp */
+ start = t = zhalloc(lleft+1);
+ i = 0;
+ for (nd = firstnode(imd.repllist); nd; incnode(nd)) {
+ rd = (Repldata) getdata(nd);
+ memcpy(t, s + i, rd->b - i);
+ t += rd->b - i;
+ ptr = rd->replstr;
+ while (*ptr)
+ *t++ = *ptr++;
+ i = rd->e;
+ }
+ memcpy(t, s + i, l - i);
+ start[lleft] = '\0';
+ *sp = (char *)start;
+ return 1;
+ }
+
+ /* munge the whole string: no match, so no replstr */
+ imd.replstr = NULL;
+ imd.repllist = NULL;
+ *sp = get_match_ret(&imd, 0, 0);
+ return 1;
+}
+
+/**/
+#endif /* MULTIBYTE_SUPPORT */
+
+/* blindly turn a string into a tokenised expression without lexing */
+
+/**/
+mod_export void
+tokenize(char *s)
+{
+ zshtokenize(s, 0);
+}
+
+/*
+ * shtokenize is used when we tokenize a string with GLOB_SUBST set.
+ * In that case we need to retain backslashes when we turn the
+ * pattern back into a string, so that the string is not
+ * modified if it failed to match a pattern.
+ *
+ * It may be modified by the effect of SH_GLOB which turns off
+ * various zsh-specific options.
+ */
+
+/**/
+mod_export void
+shtokenize(char *s)
+{
+ int flags = ZSHTOK_SUBST;
+ if (isset(SHGLOB))
+ flags |= ZSHTOK_SHGLOB;
+ zshtokenize(s, flags);
+}
+
+/**/
+static void
+zshtokenize(char *s, int flags)
+{
+ char *t;
+ int bslash = 0;
+
+ for (; *s; s++) {
+ cont:
+ switch (*s) {
+ case Meta:
+ /* skip both Meta and following character */
+ s++;
+ break;
+ case Bnull:
+ case Bnullkeep:
+ case '\\':
+ if (bslash) {
+ s[-1] = (flags & ZSHTOK_SUBST) ? Bnullkeep : Bnull;
+ break;
+ }
+ bslash = 1;
+ continue;
+ case '<':
+ if (flags & ZSHTOK_SHGLOB)
+ break;
+ if (bslash) {
+ s[-1] = (flags & ZSHTOK_SUBST) ? Bnullkeep : Bnull;
+ break;
+ }
+ t = s;
+ while (idigit(*++s));
+ if (!IS_DASH(*s))
+ goto cont;
+ while (idigit(*++s));
+ if (*s != '>')
+ goto cont;
+ *t = Inang;
+ *s = Outang;
+ break;
+ case '(':
+ case '|':
+ case ')':
+ if (flags & ZSHTOK_SHGLOB)
+ break;
+ /*FALLTHROUGH*/
+ case '>':
+ case '^':
+ case '#':
+ case '~':
+ case '[':
+ case ']':
+ case '*':
+ case '?':
+ case '=':
+ case '-':
+ case '!':
+ for (t = ztokens; *t; t++) {
+ if (*t == *s) {
+ if (bslash)
+ s[-1] = (flags & ZSHTOK_SUBST) ? Bnullkeep : Bnull;
+ else
+ *s = (t - ztokens) + Pound;
+ break;
+ }
+ }
+ break;
+ }
+ bslash = 0;
+ }
+}
+
+/* remove unnecessary Nulargs */
+
+/**/
+mod_export void
+remnulargs(char *s)
+{
+ if (*s) {
+ char *o = s, c;
+
+ while ((c = *s++))
+ if (c == Bnullkeep) {
+ /*
+ * An active backslash that needs to be turned back into
+ * a real backslash for output. However, we don't
+ * do that yet since we need to ignore it during
+ * pattern matching.
+ */
+ continue;
+ } else if (inull(c)) {
+ char *t = s - 1;
+
+ while ((c = *s++)) {
+ if (c == Bnullkeep)
+ *t++ = '\\';
+ else if (!inull(c))
+ *t++ = c;
+ }
+ *t = '\0';
+ if (!*o) {
+ o[0] = Nularg;
+ o[1] = '\0';
+ }
+ break;
+ }
+ }
+}
+
+/* qualifier functions: mostly self-explanatory, see glob(). */
+
+/* device number */
+
+/**/
+static int
+qualdev(UNUSED(char *name), struct stat *buf, off_t dv, UNUSED(char *dummy))
+{
+ return (off_t)buf->st_dev == dv;
+}
+
+/* number of hard links to file */
+
+/**/
+static int
+qualnlink(UNUSED(char *name), struct stat *buf, off_t ct, UNUSED(char *dummy))
+{
+ return (g_range < 0 ? buf->st_nlink < ct :
+ g_range > 0 ? buf->st_nlink > ct :
+ buf->st_nlink == ct);
+}
+
+/* user ID */
+
+/**/
+static int
+qualuid(UNUSED(char *name), struct stat *buf, off_t uid, UNUSED(char *dummy))
+{
+ return buf->st_uid == uid;
+}
+
+/* group ID */
+
+/**/
+static int
+qualgid(UNUSED(char *name), struct stat *buf, off_t gid, UNUSED(char *dummy))
+{
+ return buf->st_gid == gid;
+}
+
+/* device special file? */
+
+/**/
+static int
+qualisdev(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
+{
+ return S_ISBLK(buf->st_mode) || S_ISCHR(buf->st_mode);
+}
+
+/* block special file? */
+
+/**/
+static int
+qualisblk(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
+{
+ return S_ISBLK(buf->st_mode);
+}
+
+/* character special file? */
+
+/**/
+static int
+qualischr(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
+{
+ return S_ISCHR(buf->st_mode);
+}
+
+/* directory? */
+
+/**/
+static int
+qualisdir(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
+{
+ return S_ISDIR(buf->st_mode);
+}
+
+/* FIFO? */
+
+/**/
+static int
+qualisfifo(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
+{
+ return S_ISFIFO(buf->st_mode);
+}
+
+/* symbolic link? */
+
+/**/
+static int
+qualislnk(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
+{
+ return S_ISLNK(buf->st_mode);
+}
+
+/* regular file? */
+
+/**/
+static int
+qualisreg(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
+{
+ return S_ISREG(buf->st_mode);
+}
+
+/* socket? */
+
+/**/
+static int
+qualissock(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
+{
+ return S_ISSOCK(buf->st_mode);
+}
+
+/* given flag is set in mode */
+
+/**/
+static int
+qualflags(UNUSED(char *name), struct stat *buf, off_t mod, UNUSED(char *dummy))
+{
+ return mode_to_octal(buf->st_mode) & mod;
+}
+
+/* mode matches specification */
+
+/**/
+static int
+qualmodeflags(UNUSED(char *name), struct stat *buf, off_t mod, UNUSED(char *dummy))
+{
+ long v = mode_to_octal(buf->st_mode), y = mod & 07777, n = mod >> 12;
+
+ return ((v & y) == y && !(v & n));
+}
+
+/* regular executable file? */
+
+/**/
+static int
+qualiscom(UNUSED(char *name), struct stat *buf, UNUSED(off_t mod), UNUSED(char *dummy))
+{
+ return S_ISREG(buf->st_mode) && (buf->st_mode & S_IXUGO);
+}
+
+/* size in required range? */
+
+/**/
+static int
+qualsize(UNUSED(char *name), struct stat *buf, off_t size, UNUSED(char *dummy))
+{
+#if defined(ZSH_64_BIT_TYPE) || defined(LONG_IS_64_BIT)
+# define QS_CAST_SIZE()
+ zlong scaled = buf->st_size;
+#else
+# define QS_CAST_SIZE() (unsigned long)
+ unsigned long scaled = (unsigned long)buf->st_size;
+#endif
+
+ switch (g_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;
+#if defined(ZSH_64_BIT_TYPE) || defined(LONG_IS_64_BIT)
+ case TT_GIGABYTES:
+ scaled += ZLONG_CONST(1073741823);
+ scaled /= ZLONG_CONST(1073741824);
+ break;
+ case TT_TERABYTES:
+ scaled += ZLONG_CONST(1099511627775);
+ scaled /= ZLONG_CONST(1099511627776);
+ break;
+#endif
+ }
+
+ return (g_range < 0 ? scaled < QS_CAST_SIZE() size :
+ g_range > 0 ? scaled > QS_CAST_SIZE() size :
+ scaled == QS_CAST_SIZE() size);
+#undef QS_CAST_SIZE
+}
+
+/* time in required range? */
+
+/**/
+static int
+qualtime(UNUSED(char *name), struct stat *buf, off_t days, UNUSED(char *dummy))
+{
+ time_t now, diff;
+
+ time(&now);
+ diff = now - (g_amc == 0 ? buf->st_atime : g_amc == 1 ? buf->st_mtime :
+ buf->st_ctime);
+ /* handle multipliers indicating units */
+ switch (g_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 (g_range < 0 ? diff < days :
+ g_range > 0 ? diff > days :
+ diff == days);
+}
+
+/* evaluate a string */
+
+/**/
+static int
+qualsheval(char *name, UNUSED(struct stat *buf), UNUSED(off_t days), char *str)
+{
+ Eprog prog;
+
+ if ((prog = parse_string(str, 0))) {
+ int ef = errflag, lv = lastval, ret;
+ int cshglob = badcshglob;
+
+ unsetparam("reply");
+ setsparam("REPLY", ztrdup(name));
+ badcshglob = 0;
+
+ execode(prog, 1, 0, "globqual");
+
+ if ((ret = lastval))
+ badcshglob |= cshglob;
+ /* Retain any user interrupt error status */
+ errflag = ef | (errflag & ERRFLAG_INT);
+ lastval = lv;
+
+ if (!(inserts = getaparam("reply")) &&
+ !(inserts = gethparam("reply"))) {
+ char *tmp;
+
+ if ((tmp = getsparam("reply")) || (tmp = getsparam("REPLY"))) {
+ static char *tmparr[2];
+
+ tmparr[0] = tmp;
+ tmparr[1] = NULL;
+
+ inserts = tmparr;
+ }
+ }
+
+ return !ret;
+ }
+ return 0;
+}
+
+/**/
+static int
+qualnonemptydir(char *name, struct stat *buf, UNUSED(off_t days), UNUSED(char *str))
+{
+ DIR *dirh;
+ struct dirent *de;
+ int unamelen;
+ char *uname = unmetafy(dupstring(name), &unamelen);
+
+ if (!S_ISDIR(buf->st_mode))
+ return 0;
+
+ if (buf->st_nlink > 2)
+ return 1;
+
+ if (!(dirh = opendir(uname)))
+ return 0;
+
+ while ((de = readdir(dirh))) {
+ if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) {
+ closedir(dirh);
+ return 1;
+ }
+ }
+
+ closedir(dirh);
+ return 0;
+}
diff --git a/dotfiles/system/.zsh/modules/Src/hashtable.c b/dotfiles/system/.zsh/modules/Src/hashtable.c
new file mode 100644
index 0000000..b7baa31
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/hashtable.c
@@ -0,0 +1,1617 @@
+/*
+ * 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 *hashtab;
+ 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 */
+
+/**/
+mod_export unsigned
+hasher(const char *str)
+{
+ unsigned hashval = 0, c;
+
+ while ((c = *((unsigned char *) str++)))
+ hashval += (hashval << 5) + c;
+
+ return hashval;
+}
+
+/* Get a new hash table */
+
+/**/
+mod_export HashTable
+newhashtable(int size, UNUSED(char const *name), UNUSED(PrintTableStats printinfo))
+{
+ HashTable ht;
+
+ ht = (HashTable) zshcalloc(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 *) zshcalloc(size * sizeof(HashNode));
+ ht->hsize = size;
+ ht->ct = 0;
+ ht->scan = NULL;
+ ht->scantab = NULL;
+ return ht;
+}
+
+/* Delete a hash table. After this function has been used, any *
+ * existing pointers to the hash table are invalid. */
+
+/**/
+mod_export 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;
+ zsfree(ht->tablename);
+#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. nodeptr points *
+ * 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. */
+
+/**/
+mod_export void
+addhashnode(HashTable ht, char *nam, void *nodeptr)
+{
+ HashNode oldnode = addhashnode2(ht, nam, nodeptr);
+ if (oldnode)
+ ht->freenode(oldnode);
+}
+
+/* Add a node to a hash table, returning the old node on replacement. */
+
+/**/
+HashNode
+addhashnode2(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 NULL;
+ }
+
+ /* else check if the first node contains the same key */
+ if (ht->cmpnodes(hp->nam, hn->nam) == 0) {
+ ht->nodes[hashval] = hn;
+ replacing:
+ hn->next = hp->next;
+ if(ht->scan) {
+ if(ht->scan->sorted) {
+ HashNode *hashtab = ht->scan->u.s.hashtab;
+ int i;
+ for(i = ht->scan->u.s.ct; i--; )
+ if(hashtab[i] == hp)
+ hashtab[i] = hn;
+ } else if(ht->scan->u.u == hp)
+ ht->scan->u.u = hn;
+ }
+ return hp;
+ }
+
+ /* else run through the list and check all the keys */
+ hq = hp;
+ hp = hp->next;
+ for (; hp; hq = hp, hp = hp->next) {
+ if (ht->cmpnodes(hp->nam, hn->nam) == 0) {
+ 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);
+ return NULL;
+}
+
+/* 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 */
+
+/**/
+mod_export HashNode
+gethashnode(HashTable ht, const char *nam)
+{
+ unsigned hashval;
+ HashNode hp;
+
+ hashval = ht->hash(nam) % ht->hsize;
+ for (hp = ht->nodes[hashval]; hp; hp = hp->next) {
+ if (ht->cmpnodes(hp->nam, nam) == 0) {
+ 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. */
+
+/**/
+mod_export HashNode
+gethashnode2(HashTable ht, const char *nam)
+{
+ unsigned hashval;
+ HashNode hp;
+
+ hashval = ht->hash(nam) % ht->hsize;
+ for (hp = ht->nodes[hashval]; hp; hp = hp->next) {
+ if (ht->cmpnodes(hp->nam, nam) == 0)
+ 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 */
+
+/**/
+mod_export HashNode
+removehashnode(HashTable ht, const 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 (ht->cmpnodes(hp->nam, nam) == 0) {
+ ht->nodes[hashval] = hp->next;
+ gotit:
+ ht->ct--;
+ if(ht->scan) {
+ if(ht->scan->sorted) {
+ HashNode *hashtab = ht->scan->u.s.hashtab;
+ int i;
+ for(i = ht->scan->u.s.ct; i--; )
+ if(hashtab[i] == hp)
+ hashtab[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 (ht->cmpnodes(hp->nam, nam) == 0) {
+ 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, UNUSED(int flags))
+{
+ hn->flags |= DISABLED;
+}
+
+/* Enable a node in a hash table */
+
+/**/
+void
+enablehashnode(HashNode hn, UNUSED(int flags))
+{
+ hn->flags &= ~DISABLED;
+}
+
+/* Compare two hash table entries by name */
+
+/**/
+static int
+hnamcmp(const void *ap, const void *bp)
+{
+ HashNode a = *(HashNode *)ap;
+ HashNode b = *(HashNode *)bp;
+ return ztrcmp(a->nam, 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.
+ *
+ * pprog, if non-NULL, is a pattern that must match the name
+ * of the node.
+ *
+ * The function returns the number of matches, as reduced by pprog, flags1
+ * and flags2.
+ */
+
+/**/
+mod_export int
+scanmatchtable(HashTable ht, Patprog pprog, int sorted,
+ int flags1, int flags2, ScanFunc scanfunc, int scanflags)
+{
+ int match = 0;
+ struct scanstatus st;
+
+ /*
+ * scantab is currently only used by modules to scan
+ * tables where the contents are generated on the fly from
+ * other objects. Note the fact that in this case pprog,
+ * sorted, flags1 and flags2 are ignore.
+ */
+ if (!pprog && ht->scantab) {
+ ht->scantab(ht, scanfunc, scanflags);
+ return ht->ct;
+ }
+ if (sorted) {
+ int i, ct = ht->ct;
+ VARARR(HashNode, hnsorttab, ct);
+ HashNode *htp, hn;
+
+ /*
+ * Because the structure might change under our feet,
+ * we can't apply the flags and the pattern before sorting,
+ * tempting though that is.
+ */
+ 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.hashtab = hnsorttab;
+ st.u.s.ct = ct;
+ ht->scan = &st;
+
+ for (htp = hnsorttab, i = 0; i < ct; i++, htp++) {
+ if ((!flags1 || ((*htp)->flags & flags1)) &&
+ !((*htp)->flags & flags2) &&
+ (!pprog || pattry(pprog, (*htp)->nam))) {
+ match++;
+ 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 ((!flags1 || (hn->flags & flags1)) && !(hn->flags & flags2)
+ && (!pprog || pattry(pprog, hn->nam))) {
+ match++;
+ scanfunc(hn, scanflags);
+ }
+ }
+
+ ht->scan = NULL;
+ }
+
+ return match;
+}
+
+
+/**/
+mod_export int
+scanhashtable(HashTable ht, int sorted, int flags1, int flags2,
+ ScanFunc scanfunc, int scanflags)
+{
+ return scanmatchtable(ht, NULL, sorted, flags1, flags2,
+ scanfunc, scanflags);
+}
+
+/* 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 *) zshcalloc(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 *) zshcalloc(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 */
+
+/**/
+mod_export 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(UNUSED(char *nam), UNUSED(char **args), UNUSED(Options ops), UNUSED(int func))
+{
+ HashTable ht;
+
+ printf("----------------------------------------------------\n");
+ queue_signals();
+ for(ht = firstht; ht; ht = ht->next) {
+ ht->printinfo(ht);
+ printf("----------------------------------------------------\n");
+ }
+ unqueue_signals();
+ return 0;
+}
+
+/**/
+#endif /* ZSH_HASH_DEBUG */
+
+/********************************/
+/* Command Hash Table Functions */
+/********************************/
+
+/* hash table containing external commands */
+
+/**/
+mod_export HashTable cmdnamtab;
+
+/* how far we've hashed the PATH so far */
+
+/**/
+mod_export 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->cmpnodes = strcmp;
+ 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, *unmetadir, *pathbuf, *pathptr;
+ int dirlen;
+#if defined(_WIN32) || defined(__CYGWIN__)
+ char *exe;
+#endif /* _WIN32 || _CYGWIN__ */
+
+ if (isrelative(*dirp))
+ return;
+ unmetadir = unmeta(*dirp);
+ if (!(dir = opendir(unmetadir)))
+ return;
+
+ dirlen = strlen(unmetadir);
+ pathbuf = (char *)zalloc(dirlen + PATH_MAX + 2);
+ sprintf(pathbuf, "%s/", unmetadir);
+ pathptr = pathbuf + dirlen + 1;
+
+ while ((fn = zreaddir(dir, 1))) {
+ if (!cmdnamtab->getnode(cmdnamtab, fn)) {
+ char *fname = ztrdup(fn);
+ struct stat statbuf;
+ int add = 0, dummylen;
+
+ unmetafy(fn, &dummylen);
+ if (strlen(fn) > PATH_MAX) {
+ /* Too heavy to do all the allocation */
+ add = 1;
+ } else {
+ strcpy(pathptr, fn);
+ /*
+ * This is the same test as for the glob qualifier for
+ * executable plain files.
+ */
+ if (unset(HASHEXECUTABLESONLY) ||
+ (access(pathbuf, X_OK) == 0 &&
+ stat(pathbuf, &statbuf) == 0 &&
+ S_ISREG(statbuf.st_mode) && (statbuf.st_mode & S_IXUGO)))
+ add = 1;
+ }
+ if (add) {
+ cn = (Cmdnam) zshcalloc(sizeof *cn);
+ cn->node.flags = 0;
+ cn->u.name = dirp;
+ cmdnamtab->addnode(cmdnamtab, fname, cn);
+ } else
+ zsfree(fname);
+ }
+#if defined(_WIN32) || defined(__CYGWIN__)
+ /* Hash foo.exe as foo, since when no real foo exists, foo.exe
+ will get executed by DOS automatically. This quiets
+ spurious corrections when CORRECT or CORRECT_ALL is set. */
+ if ((exe = strrchr(fn, '.')) &&
+ (exe[1] == 'E' || exe[1] == 'e') &&
+ (exe[2] == 'X' || exe[2] == 'x') &&
+ (exe[3] == 'E' || exe[3] == 'e') && exe[4] == 0) {
+ *exe = 0;
+ if (!cmdnamtab->getnode(cmdnamtab, fn)) {
+ cn = (Cmdnam) zshcalloc(sizeof *cn);
+ cn->node.flags = 0;
+ cn->u.name = dirp;
+ cmdnamtab->addnode(cmdnamtab, ztrdup(fn), cn);
+ }
+ }
+#endif /* _WIN32 || __CYGWIN__ */
+ }
+ closedir(dir);
+ zfree(pathbuf, dirlen + PATH_MAX + 2);
+}
+
+/* Go through user's PATH and add everything to *
+ * the command hashtable. */
+
+/**/
+static void
+fillcmdnamtable(UNUSED(HashTable ht))
+{
+ char **pq;
+
+ for (pq = pathchecked; *pq; pq++)
+ hashdir(pq);
+
+ pathchecked = pq;
+}
+
+/**/
+static void
+freecmdnamnode(HashNode hn)
+{
+ Cmdnam cn = (Cmdnam) hn;
+
+ zsfree(cn->node.nam);
+ if (cn->node.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->node.nam, (cn->node.flags & HASHED) ?
+ "hashed" : "command");
+ return;
+ }
+
+ if ((printflags & PRINT_WHENCE_CSH) || (printflags & PRINT_WHENCE_SIMPLE)) {
+ if (cn->node.flags & HASHED) {
+ zputs(cn->u.cmd, stdout);
+ putchar('\n');
+ } else {
+ zputs(*(cn->u.name), stdout);
+ putchar('/');
+ zputs(cn->node.nam, stdout);
+ putchar('\n');
+ }
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_VERBOSE) {
+ if (cn->node.flags & HASHED) {
+ nicezputs(cn->node.nam, stdout);
+ printf(" is hashed to ");
+ nicezputs(cn->u.cmd, stdout);
+ putchar('\n');
+ } else {
+ nicezputs(cn->node.nam, stdout);
+ printf(" is ");
+ nicezputs(*(cn->u.name), stdout);
+ putchar('/');
+ nicezputs(cn->node.nam, stdout);
+ putchar('\n');
+ }
+ return;
+ }
+
+ if (printflags & PRINT_LIST) {
+ printf("hash ");
+
+ if(cn->node.nam[0] == '-')
+ printf("-- ");
+ }
+
+ if (cn->node.flags & HASHED) {
+ quotedzputs(cn->node.nam, stdout);
+ putchar('=');
+ quotedzputs(cn->u.cmd, stdout);
+ putchar('\n');
+ } else {
+ quotedzputs(cn->node.nam, stdout);
+ putchar('=');
+ quotedzputs(*(cn->u.name), stdout);
+ putchar('/');
+ quotedzputs(cn->node.nam, stdout);
+ putchar('\n');
+ }
+}
+
+/***************************************/
+/* Shell Function Hash Table Functions */
+/***************************************/
+
+/* hash table containing the shell functions */
+
+/**/
+mod_export HashTable shfunctab;
+
+/**/
+void
+createshfunctable(void)
+{
+ shfunctab = newhashtable(7, "shfunctab", NULL);
+
+ shfunctab->hash = hasher;
+ shfunctab->emptytable = NULL;
+ shfunctab->filltable = NULL;
+ shfunctab->cmpnodes = strcmp;
+ 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(UNUSED(HashTable ht), const char *nam)
+{
+ HashNode hn;
+ int signum;
+
+ if (!strncmp(nam, "TRAP", 4) && (signum = getsignum(nam + 4)) != -1)
+ hn = removetrap(signum);
+ else
+ hn = removehashnode(shfunctab, nam);
+
+ return hn;
+}
+
+/* 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, UNUSED(int flags))
+{
+ hn->flags |= DISABLED;
+ if (!strncmp(hn->nam, "TRAP", 4)) {
+ int signum = getsignum(hn->nam + 4);
+ if (signum != -1) {
+ sigtrapped[signum] &= ~ZSIG_FUNC;
+ 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, UNUSED(int flags))
+{
+ Shfunc shf = (Shfunc) hn;
+
+ shf->node.flags &= ~DISABLED;
+ if (!strncmp(shf->node.nam, "TRAP", 4)) {
+ int signum = getsignum(shf->node.nam + 4);
+ if (signum != -1) {
+ settrap(signum, NULL, ZSIG_FUNC);
+ }
+ }
+}
+
+/**/
+static void
+freeshfuncnode(HashNode hn)
+{
+ Shfunc shf = (Shfunc) hn;
+
+ zsfree(shf->node.nam);
+ if (shf->funcdef)
+ freeeprog(shf->funcdef);
+ if (shf->redir)
+ freeeprog(shf->redir);
+ dircache_set(&shf->filename, NULL);
+ if (shf->sticky) {
+ if (shf->sticky->n_on_opts)
+ zfree(shf->sticky->on_opts,
+ shf->sticky->n_on_opts * sizeof(*shf->sticky->on_opts));
+ if (shf->sticky->n_off_opts)
+ zfree(shf->sticky->off_opts,
+ shf->sticky->n_off_opts * sizeof(*shf->sticky->off_opts));
+ zfree(shf->sticky, sizeof(*shf->sticky));
+ }
+ zfree(shf, sizeof(struct shfunc));
+}
+
+/* Print a shell function */
+
+/**/
+static void
+printshfuncnode(HashNode hn, int printflags)
+{
+ Shfunc f = (Shfunc) hn;
+ char *t = 0;
+
+ if ((printflags & PRINT_NAMEONLY) ||
+ ((printflags & PRINT_WHENCE_SIMPLE) &&
+ !(printflags & PRINT_WHENCE_FUNCDEF))) {
+ zputs(f->node.nam, stdout);
+ putchar('\n');
+ return;
+ }
+
+ if ((printflags & (PRINT_WHENCE_VERBOSE|PRINT_WHENCE_WORD)) &&
+ !(printflags & PRINT_WHENCE_FUNCDEF)) {
+ nicezputs(f->node.nam, stdout);
+ printf((printflags & PRINT_WHENCE_WORD) ? ": function" :
+ (f->node.flags & PM_UNDEFINED) ?
+ " is an autoload shell function" :
+ " is a shell function");
+ if ((printflags & PRINT_WHENCE_VERBOSE) && f->filename) {
+ printf(" from ");
+ quotedzputs(f->filename, stdout);
+ if (f->node.flags & PM_LOADDIR) {
+ printf("/");
+ quotedzputs(f->node.nam, stdout);
+ }
+ }
+ putchar('\n');
+ return;
+ }
+
+ quotedzputs(f->node.nam, stdout);
+ if (f->funcdef || f->node.flags & PM_UNDEFINED) {
+ printf(" () {\n");
+ zoutputtab(stdout);
+ if (f->node.flags & PM_UNDEFINED) {
+ printf("%c undefined\n", hashchar);
+ zoutputtab(stdout);
+ } else
+ t = getpermtext(f->funcdef, NULL, 1);
+ if (f->node.flags & (PM_TAGGED|PM_TAGGED_LOCAL)) {
+ printf("%c traced\n", hashchar);
+ zoutputtab(stdout);
+ }
+ if (!t) {
+ char *fopt = "UtTkzc";
+ int flgs[] = {
+ PM_UNALIASED, PM_TAGGED, PM_TAGGED_LOCAL,
+ PM_KSHSTORED, PM_ZSHSTORED, PM_CUR_FPATH, 0
+ };
+ int fl;;
+
+ zputs("builtin autoload -X", stdout);
+ for (fl=0;fopt[fl];fl++)
+ if (f->node.flags & flgs[fl]) putchar(fopt[fl]);
+ if (f->filename && (f->node.flags & PM_LOADDIR)) {
+ putchar(' ');
+ zputs(f->filename, stdout);
+ }
+ } else {
+ zputs(t, stdout);
+ zsfree(t);
+ if (f->funcdef->flags & EF_RUN) {
+ printf("\n");
+ zoutputtab(stdout);
+ quotedzputs(f->node.nam, stdout);
+ printf(" \"$@\"");
+ }
+ }
+ printf("\n}");
+ } else {
+ printf(" () { }");
+ }
+ if (f->redir) {
+ t = getpermtext(f->redir, NULL, 1);
+ if (t) {
+ zputs(t, stdout);
+ zsfree(t);
+ }
+ }
+
+ putchar('\n');
+}
+
+/*
+ * Wrap scanmatchtable for shell functions with optional
+ * expansion of leading tabs.
+ * expand = 0 is standard: use hard tabs.
+ * expand > 0 uses that many spaces.
+ * expand < 0 uses no identation.
+ *
+ * Note this function and the following two are called with
+ * interrupts queued, so saving and restoring text_expand_tabs
+ * is safe.
+ */
+
+/**/
+mod_export int
+scanmatchshfunc(Patprog pprog, int sorted, int flags1, int flags2,
+ ScanFunc scanfunc, int scanflags, int expand)
+{
+ int ret, save_expand;
+
+ save_expand = text_expand_tabs;
+ text_expand_tabs = expand;
+ ret = scanmatchtable(shfunctab, pprog, sorted, flags1, flags2,
+ scanfunc, scanflags);
+ text_expand_tabs = save_expand;
+
+ return ret;
+}
+
+/* Wrap scanhashtable to expand tabs for shell functions */
+
+/**/
+mod_export int
+scanshfunc(int sorted, int flags1, int flags2,
+ ScanFunc scanfunc, int scanflags, int expand)
+{
+ return scanmatchshfunc(NULL, sorted, flags1, flags2,
+ scanfunc, scanflags, expand);
+}
+
+/* Wrap shfunctab->printnode to expand tabs */
+
+/**/
+mod_export void
+printshfuncexpand(HashNode hn, int printflags, int expand)
+{
+ int save_expand;
+
+ save_expand = text_expand_tabs;
+ text_expand_tabs = expand;
+ shfunctab->printnode(hn, printflags);
+ text_expand_tabs = save_expand;
+}
+
+/*
+ * Get a heap-duplicated name of the shell function, for
+ * use in tracing.
+ */
+
+/**/
+mod_export char *
+getshfuncfile(Shfunc shf)
+{
+ if (shf->node.flags & PM_LOADDIR) {
+ return zhtricat(shf->filename, "/", shf->node.nam);
+ } else if (shf->filename) {
+ return dupstring(shf->filename);
+ } else {
+ return NULL;
+ }
+}
+
+/**************************************/
+/* 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, "declare", 0}, TYPESET},
+ {{NULL, "do", 0}, DOLOOP},
+ {{NULL, "done", 0}, DONE},
+ {{NULL, "elif", 0}, ELIF},
+ {{NULL, "else", 0}, ELSE},
+ {{NULL, "end", 0}, ZEND},
+ {{NULL, "esac", 0}, ESAC},
+ {{NULL, "export", 0}, TYPESET},
+ {{NULL, "fi", 0}, FI},
+ {{NULL, "float", 0}, TYPESET},
+ {{NULL, "for", 0}, FOR},
+ {{NULL, "foreach", 0}, FOREACH},
+ {{NULL, "function", 0}, FUNC},
+ {{NULL, "if", 0}, IF},
+ {{NULL, "integer", 0}, TYPESET},
+ {{NULL, "local", 0}, TYPESET},
+ {{NULL, "nocorrect", 0}, NOCORRECT},
+ {{NULL, "readonly", 0}, TYPESET},
+ {{NULL, "repeat", 0}, REPEAT},
+ {{NULL, "select", 0}, SELECT},
+ {{NULL, "then", 0}, THEN},
+ {{NULL, "time", 0}, TIME},
+ {{NULL, "typeset", 0}, TYPESET},
+ {{NULL, "until", 0}, UNTIL},
+ {{NULL, "while", 0}, WHILE},
+ {{NULL, NULL, 0}, 0}
+};
+
+/* hash table containing the reserved words */
+
+/**/
+mod_export 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->cmpnodes = strcmp;
+ 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->node.nam; rw++)
+ reswdtab->addnode(reswdtab, rw->node.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->node.nam);
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_CSH) {
+ printf("%s: shell reserved word\n", rw->node.nam);
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_VERBOSE) {
+ printf("%s is a reserved word\n", rw->node.nam);
+ return;
+ }
+
+ /* default is name only */
+ printf("%s\n", rw->node.nam);
+}
+
+/********************************/
+/* Aliases Hash Table Functions */
+/********************************/
+
+/* hash table containing the aliases */
+
+/**/
+mod_export HashTable aliastab;
+
+/* has table containing suffix aliases */
+
+/**/
+mod_export HashTable sufaliastab;
+
+/* Create new hash tables for aliases */
+
+/**/
+void
+createaliastable(HashTable ht)
+{
+ ht->hash = hasher;
+ ht->emptytable = NULL;
+ ht->filltable = NULL;
+ ht->cmpnodes = strcmp;
+ ht->addnode = addhashnode;
+ ht->getnode = gethashnode;
+ ht->getnode2 = gethashnode2;
+ ht->removenode = removehashnode;
+ ht->disablenode = disablehashnode;
+ ht->enablenode = enablehashnode;
+ ht->freenode = freealiasnode;
+ ht->printnode = printaliasnode;
+}
+
+/**/
+void
+createaliastables(void)
+{
+ /* Table for regular and global aliases */
+
+ aliastab = newhashtable(23, "aliastab", NULL);
+
+ createaliastable(aliastab);
+
+ /* add the default aliases */
+ aliastab->addnode(aliastab, ztrdup("run-help"), createaliasnode(ztrdup("man"), 0));
+ aliastab->addnode(aliastab, ztrdup("which-command"), createaliasnode(ztrdup("whence"), 0));
+
+
+ /* Table for suffix aliases --- make this smaller */
+
+ sufaliastab = newhashtable(11, "sufaliastab", NULL);
+
+ createaliastable(sufaliastab);
+}
+
+/* Create a new alias node */
+
+/**/
+mod_export Alias
+createaliasnode(char *txt, int flags)
+{
+ Alias al;
+
+ al = (Alias) zshcalloc(sizeof *al);
+ al->node.flags = flags;
+ al->text = txt;
+ al->inuse = 0;
+ return al;
+}
+
+/**/
+static void
+freealiasnode(HashNode hn)
+{
+ Alias al = (Alias) hn;
+
+ zsfree(al->node.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->node.nam, stdout);
+ putchar('\n');
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_WORD) {
+ if (a->node.flags & ALIAS_SUFFIX)
+ printf("%s: suffix alias\n", a->node.nam);
+ else if (a->node.flags & ALIAS_GLOBAL)
+ printf("%s: global alias\n", a->node.nam);
+ else
+ printf("%s: alias\n", a->node.nam);
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_SIMPLE) {
+ zputs(a->text, stdout);
+ putchar('\n');
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_CSH) {
+ nicezputs(a->node.nam, stdout);
+ printf(": ");
+ if (a->node.flags & ALIAS_SUFFIX)
+ printf("suffix ");
+ else if (a->node.flags & ALIAS_GLOBAL)
+ printf("globally ");
+ printf ("aliased to ");
+ nicezputs(a->text, stdout);
+ putchar('\n');
+ return;
+ }
+
+ if (printflags & PRINT_WHENCE_VERBOSE) {
+ nicezputs(a->node.nam, stdout);
+ printf(" is a");
+ if (a->node.flags & ALIAS_SUFFIX)
+ printf(" suffix");
+ else if (a->node.flags & ALIAS_GLOBAL)
+ printf(" global");
+ else
+ printf("n");
+ printf(" alias for ");
+ nicezputs(a->text, stdout);
+ putchar('\n');
+ return;
+ }
+
+ if (printflags & PRINT_LIST) {
+ /* Fast fail on unrepresentable values. */
+ if (strchr(a->node.nam, '=')) {
+ zwarn("invalid alias '%s' encountered while printing aliases",
+ a->node.nam);
+ /* ### TODO: Return an error status to the C caller */
+ return;
+ }
+
+ /* Normal path. */
+ printf("alias ");
+ if (a->node.flags & ALIAS_SUFFIX)
+ printf("-s ");
+ else if (a->node.flags & ALIAS_GLOBAL)
+ printf("-g ");
+
+ /* If an alias begins with `-' or `+', then we must output `-- '
+ * first, so that it is not interpreted as an option. */
+ if(a->node.nam[0] == '-' || a->node.nam[0] == '+')
+ printf("-- ");
+ }
+
+ quotedzputs(a->node.nam, stdout);
+ putchar('=');
+ quotedzputs(a->text, stdout);
+
+ putchar('\n');
+}
+
+/*************************************/
+/* History Line Hash Table Functions */
+/*************************************/
+
+/**/
+void
+createhisttable(void)
+{
+ histtab = newhashtable(599, "histtab", NULL);
+
+ histtab->hash = histhasher;
+ histtab->emptytable = emptyhisttable;
+ histtab->filltable = NULL;
+ histtab->cmpnodes = histstrcmp;
+ histtab->addnode = addhistnode;
+ histtab->getnode = gethashnode2;
+ histtab->getnode2 = gethashnode2;
+ histtab->removenode = removehashnode;
+ histtab->disablenode = NULL;
+ histtab->enablenode = NULL;
+ histtab->freenode = freehistnode;
+ histtab->printnode = NULL;
+}
+
+/**/
+unsigned
+histhasher(const char *str)
+{
+ unsigned hashval = 0;
+
+ while (inblank(*str)) str++;
+
+ while (*str) {
+ if (inblank(*str)) {
+ do str++; while (inblank(*str));
+ if (*str)
+ hashval += (hashval << 5) + ' ';
+ }
+ else
+ hashval += (hashval << 5) + *(unsigned char *)str++;
+ }
+ return hashval;
+}
+
+/**/
+void
+emptyhisttable(HashTable ht)
+{
+ emptyhashtable(ht);
+ if (hist_ring)
+ histremovedups();
+}
+
+/* Compare two strings with normalized white-space */
+
+/**/
+int
+histstrcmp(const char *str1, const char *str2)
+{
+ while (inblank(*str1)) str1++;
+ while (inblank(*str2)) str2++;
+ while (*str1 && *str2) {
+ if (inblank(*str1)) {
+ if (!inblank(*str2))
+ break;
+ do str1++; while (inblank(*str1));
+ do str2++; while (inblank(*str2));
+ }
+ else {
+ if (*str1 != *str2)
+ break;
+ str1++;
+ str2++;
+ }
+ }
+ return *str1 - *str2;
+}
+
+/**/
+void
+addhistnode(HashTable ht, char *nam, void *nodeptr)
+{
+ HashNode oldnode = addhashnode2(ht, nam, nodeptr);
+ Histent he = (Histent)nodeptr;
+ if (oldnode && oldnode != (HashNode)nodeptr) {
+ if (he->node.flags & HIST_MAKEUNIQUE
+ || (he->node.flags & HIST_FOREIGN && (Histent)oldnode == he->up)) {
+ (void) addhashnode2(ht, oldnode->nam, oldnode); /* restore hash */
+ he->node.flags |= HIST_DUP;
+ he->node.flags &= ~HIST_MAKEUNIQUE;
+ }
+ else {
+ oldnode->flags |= HIST_DUP;
+ if (hist_ignore_all_dups)
+ freehistnode(oldnode); /* Remove the old dup */
+ }
+ }
+ else
+ he->node.flags &= ~HIST_MAKEUNIQUE;
+}
+
+/**/
+void
+freehistnode(HashNode nodeptr)
+{
+ freehistdata((Histent)nodeptr, 1);
+ zfree(nodeptr, sizeof (struct histent));
+}
+
+/**/
+void
+freehistdata(Histent he, int unlink)
+{
+ if (!he)
+ return;
+
+ if (he == &curline)
+ return;
+
+ if (!(he->node.flags & (HIST_DUP | HIST_TMPSTORE)))
+ removehashnode(histtab, he->node.nam);
+
+ zsfree(he->node.nam);
+ if (he->nwords)
+ zfree(he->words, he->nwords*2*sizeof(short));
+
+ if (unlink) {
+ if (!--histlinect)
+ hist_ring = NULL;
+ else {
+ if (he == hist_ring)
+ hist_ring = hist_ring->up;
+ he->up->down = he->down;
+ he->down->up = he->up;
+ }
+ }
+}
+
+
+/***********************************************************************
+ * Directory name cache mechanism
+ *
+ * The idea of this is that there are various shell structures,
+ * notably functions, that record the directories with which they
+ * are associated. Rather than store the full string each time,
+ * we store a pointer to the same location and count the references.
+ * This is optimised so that retrieval is quick at the expense of
+ * searching the list when setting up the structure, which is a much
+ * rarer operation.
+ *
+ * There is nothing special about the fact that the strings are
+ * directories, except for the assumptions for efficiency that many
+ * structures will point to the same one, and that there are not too
+ * many different directories associated with the shell.
+ **********************************************************************/
+
+struct dircache_entry
+{
+ /* Name of directory in cache */
+ char *name;
+ /* Number of references to it */
+ int refs;
+};
+
+/*
+ * dircache is the cache, of length dircache_size.
+ * dircache_lastentry is the last entry used, an optimisation
+ * for multiple references to the same directory, e.g
+ * "autoload /blah/blah/\*".
+ */
+static struct dircache_entry *dircache, *dircache_lastentry;
+static int dircache_size;
+
+/*
+ * Set *name to point to a cached version of value.
+ * value is copied so may come from any source.
+ *
+ * If value is NULL, look for the existing value of *name (safe if this
+ * too is NULL) and remove a reference to it from the cache. If it's
+ * not found in the cache, it's assumed to be an allocated string and
+ * freed --- this currently occurs for a shell function that's been
+ * loaded as the filename is now a full path, not just a directory,
+ * though we may one day optimise this to a cached directory plus a
+ * name, too. Note --- the function does *not* otherwise check
+ * if *name points to something already cached, so this is
+ * necessary any time *name may already be in the cache.
+ */
+
+/**/
+mod_export void
+dircache_set(char **name, char *value)
+{
+ struct dircache_entry *dcptr, *dcnew;
+
+ if (!value) {
+ if (!*name)
+ return;
+ if (!dircache_size) {
+ zsfree(*name);
+ *name = NULL;
+ return;
+ }
+
+ for (dcptr = dircache; dcptr < dircache + dircache_size; dcptr++)
+ {
+ /* Must be a pointer much, not a string match */
+ if (*name == dcptr->name)
+ {
+ --dcptr->refs;
+ if (!dcptr->refs) {
+ ptrdiff_t ind = dcptr - dircache;
+ zsfree(dcptr->name);
+ --dircache_size;
+
+ if (!dircache_size) {
+ zfree(dircache, sizeof(*dircache));
+ dircache = NULL;
+ dircache_lastentry = NULL;
+ *name = NULL;
+ return;
+ }
+ dcnew = (struct dircache_entry *)
+ zalloc(dircache_size * sizeof(*dcnew));
+ if (ind)
+ memcpy(dcnew, dircache, ind * sizeof(*dcnew));
+ if (ind < dircache_size)
+ memcpy(dcnew + ind, dcptr + 1,
+ (dircache_size - ind) * sizeof(*dcnew));
+ zfree(dircache, (dircache_size+1)*sizeof(*dcnew));
+ dircache = dcnew;
+ dircache_lastentry = NULL;
+ }
+ *name = NULL;
+ return;
+ }
+ }
+ zsfree(*name);
+ *name = NULL;
+ } else {
+ /*
+ * As the function path has been resolved to a particular
+ * location, we'll store it as an absolute path.
+ */
+ if (*value != '/') {
+ value = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP),
+ "/", value);
+ value = xsymlink(value, 1);
+ }
+ /*
+ * We'll maintain the cache at exactly the right size rather
+ * than overallocating. The rationale here is that typically
+ * we'll get a lot of functions in a small number of directories
+ * so the complexity overhead of maintaining a separate count
+ * isn't really matched by the efficiency gain.
+ */
+ if (dircache_lastentry &&
+ !strcmp(value, dircache_lastentry->name)) {
+ *name = dircache_lastentry->name;
+ ++dircache_lastentry->refs;
+ return;
+ } else if (!dircache_size) {
+ dircache_size = 1;
+ dcptr = dircache =
+ (struct dircache_entry *)zalloc(sizeof(*dircache));
+ } else {
+ for (dcptr = dircache; dcptr < dircache + dircache_size; dcptr++)
+ {
+ if (!strcmp(value, dcptr->name)) {
+ *name = dcptr->name;
+ ++dcptr->refs;
+ return;
+ }
+ }
+ ++dircache_size;
+ dircache = (struct dircache_entry *)
+ zrealloc(dircache, sizeof(*dircache) * dircache_size);
+ dcptr = dircache + dircache_size - 1;
+ }
+ dcptr->name = ztrdup(value);
+ *name = dcptr->name;
+ dcptr->refs = 1;
+ dircache_lastentry = dcptr;
+ }
+}
diff --git a/dotfiles/system/.zsh/modules/Src/hashtable.h b/dotfiles/system/.zsh/modules/Src/hashtable.h
new file mode 100644
index 0000000..21398e1
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/hashtable.h
@@ -0,0 +1,69 @@
+/*
+ * 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_R 17
+#define BIN_PUSHLINE 18
+#define BIN_LOGOUT 19
+#define BIN_TEST 20
+#define BIN_BRACKET 21
+#define BIN_READONLY 22
+#define BIN_ECHO 23
+#define BIN_DISABLE 24
+#define BIN_ENABLE 25
+#define BIN_PRINTF 26
+#define BIN_COMMAND 27
+#define BIN_UNHASH 28
+#define BIN_UNALIAS 29
+#define BIN_UNFUNCTION 30
+#define BIN_UNSET 31
+
+/* These currently depend on being 0 and 1. */
+#define BIN_SETOPT 0
+#define BIN_UNSETOPT 1
diff --git a/dotfiles/system/.zsh/modules/Src/init.c b/dotfiles/system/.zsh/modules/Src/init.c
new file mode 100644
index 0000000..e9e6be9
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/init.c
@@ -0,0 +1,1792 @@
+/*
+ * 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 "zshpaths.h"
+#include "zshxmods.h"
+
+#include "init.pro"
+
+#include "version.h"
+
+/**/
+int noexitct = 0;
+
+/* buffer for $_ and its length */
+
+/**/
+char *zunderscore;
+
+/**/
+int underscorelen, underscoreused;
+
+/* what level of sourcing we are at */
+
+/**/
+int sourcelevel;
+
+/* the shell tty fd */
+
+/**/
+mod_export int SHTTY;
+
+/* the FILE attached to the shell tty */
+
+/**/
+mod_export FILE *shout;
+
+/* termcap strings */
+
+/**/
+mod_export char *tcstr[TC_COUNT];
+
+/* lengths of each termcap string */
+
+/**/
+mod_export int tclen[TC_COUNT];
+
+/* Values of the li, co and am entries */
+
+/**/
+int tclines, tccolumns;
+/**/
+mod_export int hasam, hasbw, hasxn, hasye;
+
+/* Value of the Co (max_colors) entry: may not be set */
+
+/**/
+mod_export int tccolours;
+
+/* SIGCHLD mask */
+
+/**/
+mod_export sigset_t sigchld_mask;
+
+/**/
+mod_export struct hookdef zshhooks[] = {
+ HOOKDEF("exit", NULL, HOOKF_ALL),
+ HOOKDEF("before_trap", NULL, HOOKF_ALL),
+ HOOKDEF("after_trap", NULL, HOOKF_ALL),
+};
+
+/* keep executing lists until EOF found */
+
+/**/
+enum loop_return
+loop(int toplevel, int justonce)
+{
+ Eprog prog;
+ int err, non_empty = 0;
+
+ queue_signals();
+ pushheap();
+ if (!toplevel)
+ zcontext_save();
+ for (;;) {
+ freeheap();
+ if (stophist == 3) /* re-entry via preprompt() */
+ hend(NULL);
+ hbegin(1); /* init history mech */
+ if (isset(SHINSTDIN)) {
+ setblock_stdin();
+ if (interact && toplevel) {
+ int hstop = stophist;
+ stophist = 3;
+ /*
+ * Reset all errors including the interrupt error status
+ * immediately, so preprompt runs regardless of what
+ * just happened. We'll reset again below as a
+ * precaution to ensure we get back to the command line
+ * no matter what.
+ */
+ errflag = 0;
+ preprompt();
+ if (stophist != 3)
+ hbegin(1);
+ else
+ stophist = hstop;
+ /*
+ * Reset all errors, including user interupts.
+ * This is what allows ^C in an interactive shell
+ * to return us to the command line.
+ */
+ errflag = 0;
+ }
+ }
+ use_exit_printed = 0;
+ intr(); /* interrupts on */
+ lexinit(); /* initialize lexical state */
+ if (!(prog = parse_event(ENDINPUT))) {
+ /* if we couldn't parse a list */
+ hend(NULL);
+ if ((tok == ENDINPUT && !errflag) ||
+ (tok == LEXERR && (!isset(SHINSTDIN) || !toplevel)) ||
+ justonce)
+ break;
+ if (exit_pending) {
+ /*
+ * Something down there (a ZLE function?) decided
+ * to exit when there was stuff to clear up.
+ * Handle that now.
+ */
+ stopmsg = 1;
+ zexit(exit_pending >> 1, 0);
+ }
+ if (tok == LEXERR && !lastval)
+ lastval = 1;
+ continue;
+ }
+ if (hend(prog)) {
+ enum lextok toksav = tok;
+
+ non_empty = 1;
+ if (toplevel &&
+ (getshfunc("preexec") ||
+ paramtab->getnode(paramtab, "preexec" HOOK_SUFFIX))) {
+ LinkList args;
+ char *cmdstr;
+
+ /*
+ * As we're about to freeheap() or popheap()
+ * anyway, there's no gain in using permanent
+ * storage here.
+ */
+ args = newlinklist();
+ addlinknode(args, "preexec");
+ /* If curline got dumped from the history, we don't know
+ * what the user typed. */
+ if (hist_ring && curline.histnum == curhist)
+ addlinknode(args, hist_ring->node.nam);
+ else
+ addlinknode(args, "");
+ addlinknode(args, dupstring(getjobtext(prog, NULL)));
+ addlinknode(args, cmdstr = getpermtext(prog, NULL, 0));
+
+ callhookfunc("preexec", args, 1, NULL);
+
+ /* The only permanent storage is from getpermtext() */
+ zsfree(cmdstr);
+ /*
+ * Note this does *not* remove a user interrupt error
+ * condition, even though we're at the top level loop:
+ * that would be inconsistent with the case where
+ * we didn't execute a preexec function. This is
+ * an implementation detail that an interrupting user
+ * does't care about.
+ */
+ errflag &= ~ERRFLAG_ERROR;
+ }
+ if (stopmsg) /* unset 'you have stopped jobs' flag */
+ stopmsg--;
+ execode(prog, 0, 0, toplevel ? "toplevel" : "file");
+ tok = toksav;
+ if (toplevel)
+ noexitct = 0;
+ }
+ if (ferror(stderr)) {
+ zerr("write error");
+ clearerr(stderr);
+ }
+ if (subsh) /* how'd we get this far in a subshell? */
+ exit(lastval);
+ if (((!interact || sourcelevel) && errflag) || retflag)
+ break;
+ if (isset(SINGLECOMMAND) && toplevel) {
+ dont_queue_signals();
+ if (sigtrapped[SIGEXIT])
+ dotrap(SIGEXIT);
+ exit(lastval);
+ }
+ if (justonce)
+ break;
+ }
+ err = errflag;
+ if (!toplevel)
+ zcontext_restore();
+ popheap();
+ unqueue_signals();
+
+ if (err)
+ return LOOP_ERROR;
+ if (!non_empty)
+ return LOOP_EMPTY;
+ return LOOP_OK;
+}
+
+static int restricted;
+
+/**/
+static void
+parseargs(char *zsh_name, char **argv, char **runscript, char **cmdptr)
+{
+ char **x;
+ LinkList paramlist;
+ int flags = PARSEARGS_TOPLEVEL;
+ if (**argv == '-')
+ flags |= PARSEARGS_LOGIN;
+
+ argzero = posixzero = *argv++;
+ SHIN = 0;
+
+ /*
+ * parseopts sets up some options after we deal with emulation in
+ * order to be consistent --- the code in parseopts_setemulate() is
+ * matched by code at the end of the present function.
+ */
+
+ if (parseopts(zsh_name, &argv, opts, cmdptr, NULL, flags))
+ exit(1);
+
+ /*
+ * USEZLE remains set if the shell has access to a terminal and
+ * is not reading from some other source as indicated by SHINSTDIN.
+ * SHINSTDIN becomes set below if there is no command argument,
+ * but it is the explicit setting (or not) that matters to USEZLE.
+ * USEZLE may also become unset in init_io() if the shell is not
+ * interactive or the terminal cannot be re-opened read/write.
+ */
+ if (opts[SHINSTDIN])
+ opts[USEZLE] = (opts[USEZLE] && isatty(0));
+
+ paramlist = znewlinklist();
+ if (*argv) {
+ if (unset(SHINSTDIN)) {
+ posixzero = *argv;
+ if (*cmdptr)
+ argzero = *argv;
+ else
+ *runscript = *argv;
+ opts[INTERACTIVE] &= 1;
+ argv++;
+ }
+ while (*argv)
+ zaddlinknode(paramlist, ztrdup(*argv++));
+ } else if (!*cmdptr)
+ opts[SHINSTDIN] = 1;
+ if(isset(SINGLECOMMAND))
+ opts[INTERACTIVE] &= 1;
+ opts[INTERACTIVE] = !!opts[INTERACTIVE];
+ if (opts[MONITOR] == 2)
+ opts[MONITOR] = opts[INTERACTIVE];
+ if (opts[HASHDIRS] == 2)
+ opts[HASHDIRS] = opts[INTERACTIVE];
+ pparams = x = (char **) zshcalloc((countlinknodes(paramlist) + 1) * sizeof(char *));
+
+ while ((*x++ = (char *)getlinknode(paramlist)));
+ free(paramlist);
+ argzero = ztrdup(argzero);
+ posixzero = ztrdup(posixzero);
+}
+
+/* Insert into list in order of pointer value */
+
+/**/
+static void
+parseopts_insert(LinkList optlist, char *base, int optno)
+{
+ LinkNode node;
+ void *ptr = base + (optno < 0 ? -optno : optno);
+
+ for (node = firstnode(optlist); node; incnode(node)) {
+ if (ptr < getdata(node)) {
+ insertlinknode(optlist, prevnode(node), ptr);
+ return;
+ }
+ }
+
+ addlinknode(optlist, ptr);
+}
+
+/*
+ * This sets the global emulation plus the options we traditionally
+ * set immediately after that. This is just for historical consistency
+ * --- I don't think those options actually need to be set here.
+ */
+static void parseopts_setemulate(char *nam, int flags)
+{
+ emulate(nam, 1, &emulation, opts); /* initialises most options */
+ opts[LOGINSHELL] = ((flags & PARSEARGS_LOGIN) != 0);
+ opts[PRIVILEGED] = (getuid() != geteuid() || getgid() != getegid());
+
+ /* 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;
+ /*
+ * MONITOR is similar: we initialise it to 2, and if it's
+ * still 2 at the end, we set it to the value of INTERACTIVE.
+ */
+ opts[MONITOR] = 2; /* may be unset in init_io() */
+ opts[HASHDIRS] = 2; /* same relationship to INTERACTIVE */
+ opts[USEZLE] = 1; /* see below, related to SHINSTDIN */
+ opts[SHINSTDIN] = 0;
+ opts[SINGLECOMMAND] = 0;
+}
+
+/*
+ * Parse shell options.
+ *
+ * If (flags & PARSEARGS_TOPLEVEL):
+ * - we are doing shell initilisation
+ * - nam is the name under which the shell was started
+ * - set up emulation and standard options based on that.
+ * Otherwise:
+ * - nam is a command name
+ * - don't exit on failure.
+ *
+ * If optlist is not NULL, it used to form a list of pointers
+ * into new_opts indicating which options have been changed.
+ */
+
+/**/
+mod_export int
+parseopts(char *nam, char ***argvp, char *new_opts, char **cmdp,
+ LinkList optlist, int flags)
+{
+ int optionbreak = 0;
+ int action, optno;
+ char **argv = *argvp;
+ int toplevel = ((flags & PARSEARGS_TOPLEVEL) != 0u);
+ int emulate_required = toplevel;
+ char *top_emulation = nam;
+
+ *cmdp = 0;
+#define WARN_OPTION(F, S) \
+ do { \
+ if (!toplevel) \
+ zwarnnam(nam, F, S); \
+ else \
+ zerr(F, S); \
+ } while (0)
+#define LAST_OPTION(N) \
+ do { \
+ if (!toplevel) { \
+ if (*argv) \
+ argv++; \
+ goto doneargv; \
+ } else exit(N); \
+ } while(0)
+
+ /* loop through command line options (begins with "-" or "+") */
+ while (!optionbreak && *argv && (**argv == '-' || **argv == '+')) {
+ char *args = *argv;
+ action = (**argv == '-');
+ if (!argv[0][1])
+ *argv = "--";
+ while (*++*argv) {
+ if (**argv == '-') {
+ if (!argv[0][1]) {
+ /* The pseudo-option `--' signifies the end of options. */
+ argv++;
+ goto doneoptions;
+ }
+ if (!toplevel || *argv != args+1 || **argv != '-')
+ goto badoptionstring;
+ /* GNU-style long options */
+ ++*argv;
+ if (!strcmp(*argv, "version")) {
+ printf("zsh %s (%s-%s-%s)\n",
+ ZSH_VERSION, MACHTYPE, VENDOR, OSTYPE);
+ LAST_OPTION(0);
+ }
+ if (!strcmp(*argv, "help")) {
+ printhelp();
+ LAST_OPTION(0);
+ }
+ if (!strcmp(*argv, "emulate")) {
+ ++argv;
+ if (!*argv) {
+ zerr("--emulate: argument required");
+ exit(1);
+ }
+ if (!emulate_required) {
+ zerr("--emulate: must precede other options");
+ exit(1);
+ }
+ top_emulation = *argv;
+ break;
+ }
+ /* `-' characters are allowed in long options */
+ for(args = *argv; *args; args++)
+ if(*args == '-')
+ *args = '_';
+ goto longoptions;
+ }
+
+ if (unset(SHOPTIONLETTERS) && **argv == 'b') {
+ if (emulate_required) {
+ parseopts_setemulate(top_emulation, flags);
+ emulate_required = 0;
+ }
+ /* -b ends options at the end of this argument */
+ optionbreak = 1;
+ } else if (**argv == 'c') {
+ if (emulate_required) {
+ parseopts_setemulate(top_emulation, flags);
+ emulate_required = 0;
+ }
+ /* -c command */
+ *cmdp = *argv;
+ new_opts[INTERACTIVE] &= 1;
+ if (toplevel)
+ scriptname = scriptfilename = ztrdup("zsh");
+ } else if (**argv == 'o') {
+ if (!*++*argv)
+ argv++;
+ if (!*argv) {
+ WARN_OPTION("string expected after -o", NULL);
+ return 1;
+ }
+ longoptions:
+ if (emulate_required) {
+ parseopts_setemulate(top_emulation, flags);
+ emulate_required = 0;
+ }
+ if (!(optno = optlookup(*argv))) {
+ WARN_OPTION("no such option: %s", *argv);
+ return 1;
+ } else if (optno == RESTRICTED && toplevel) {
+ restricted = action;
+ } else if ((optno == EMACSMODE || optno == VIMODE) && !toplevel) {
+ WARN_OPTION("can't change option: %s", *argv);
+ } else {
+ if (dosetopt(optno, action, toplevel, new_opts) &&
+ !toplevel) {
+ WARN_OPTION("can't change option: %s", *argv);
+ } else if (optlist) {
+ parseopts_insert(optlist, new_opts, optno);
+ }
+ }
+ break;
+ } else if (isspace(STOUC(**argv))) {
+ /* zsh's typtab not yet set, have to use ctype */
+ while (*++*argv)
+ if (!isspace(STOUC(**argv))) {
+ badoptionstring:
+ WARN_OPTION("bad option string: '%s'", args);
+ return 1;
+ }
+ break;
+ } else {
+ if (emulate_required) {
+ parseopts_setemulate(top_emulation, flags);
+ emulate_required = 0;
+ }
+ if (!(optno = optlookupc(**argv))) {
+ WARN_OPTION("bad option: -%c", **argv);
+ return 1;
+ } else if (optno == RESTRICTED && toplevel) {
+ restricted = action;
+ } else if ((optno == EMACSMODE || optno == VIMODE) &&
+ !toplevel) {
+ WARN_OPTION("can't change option: %s", *argv);
+ } else {
+ if (dosetopt(optno, action, toplevel, new_opts) &&
+ !toplevel) {
+ WARN_OPTION("can't change option: -%c", **argv);
+ } else if (optlist) {
+ parseopts_insert(optlist, new_opts, optno);
+ }
+ }
+ }
+ }
+ argv++;
+ }
+ doneoptions:
+ if (*cmdp) {
+ if (!*argv) {
+ WARN_OPTION("string expected after -%s", *cmdp);
+ return 1;
+ }
+ *cmdp = *argv++;
+ }
+ doneargv:
+ *argvp = argv;
+ if (emulate_required) {
+ parseopts_setemulate(top_emulation, flags);
+ emulate_required = 0;
+ }
+ return 0;
+}
+
+/**/
+static void
+printhelp(void)
+{
+ printf("Usage: %s [<options>] [<argument> ...]\n", argzero);
+ printf("\nSpecial options:\n");
+ printf(" --help show this message, then exit\n");
+ printf(" --version show zsh version number, then exit\n");
+ if(unset(SHOPTIONLETTERS))
+ printf(" -b end option processing, like --\n");
+ printf(" -c take first argument as a command to execute\n");
+ printf(" -o OPTION set an option by name (see below)\n");
+ printf("\nNormal options are named. An option may be turned on by\n");
+ printf("`-o OPTION', `--OPTION', `+o no_OPTION' or `+-no-OPTION'. An\n");
+ printf("option may be turned off by `-o no_OPTION', `--no-OPTION',\n");
+ printf("`+o OPTION' or `+-OPTION'. Options are listed below only in\n");
+ printf("`--OPTION' or `--no-OPTION' form.\n");
+ printoptionlist();
+}
+
+/**/
+mod_export void
+init_io(char *cmd)
+{
+ 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);
+ }
+#else
+ (void)cmd;
+#endif
+
+ if (shout) {
+ /*
+ * Check if shout was set to stderr, if so don't close it.
+ * We do this if we are interactive but don't have a
+ * terminal.
+ */
+ if (shout != stderr)
+ fclose(shout);
+ shout = 0;
+ }
+ if (SHTTY != -1) {
+ zclose(SHTTY);
+ SHTTY = -1;
+ }
+
+ /* Send xtrace output to stderr -- see execcmd() */
+ xtrerr = stderr;
+
+ /* 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));
+#ifdef TIOCNXCL
+ /*
+ * See if the terminal claims to be busy. If so, and fd 0
+ * is a terminal, try and set non-exclusive use for that.
+ * This is something to do with Solaris over-cleverness.
+ */
+ if (SHTTY == -1 && errno == EBUSY)
+ ioctl(0, TIOCNXCL, 0);
+#endif
+ }
+ /*
+ * xterm, rxvt and probably all terminal emulators except
+ * dtterm on Solaris 2.6 & 7 have a bug. Applications are
+ * unable to open /dev/tty or /dev/pts/<terminal number here>
+ * because something in Sun's STREAMS modules doesn't like
+ * it. The open() call fails with EBUSY which is not even
+ * listed as a possibility in the open(2) man page. So we'll
+ * try to outsmart The Company. -- <dave@srce.hr>
+ *
+ * Presumably there's no harm trying this on any OS, given that
+ * isatty(0) worked but opening the tty didn't. Possibly we won't
+ * get the tty read/write, but it's the best we can do -- pws
+ *
+ * Try both stdin and stdout before trying /dev/tty. -- Bart
+ */
+#if defined(HAVE_FCNTL_H) && defined(F_GETFL)
+#define rdwrtty(fd) ((fcntl(fd, F_GETFL, 0) & O_RDWR) == O_RDWR)
+#else
+#define rdwrtty(fd) 1
+#endif
+ if (SHTTY == -1 && rdwrtty(0)) {
+ SHTTY = movefd(dup(0));
+ }
+ }
+ if (SHTTY == -1 && isatty(1) && rdwrtty(1) &&
+ (SHTTY = movefd(dup(1))) != -1) {
+ zsfree(ttystrname);
+ ttystrname = ztrdup(ttyname(1));
+ }
+ if (SHTTY == -1 &&
+ (SHTTY = movefd(open("/dev/tty", O_RDWR | O_NOCTTY))) != -1) {
+ zsfree(ttystrname);
+ ttystrname = ztrdup(ttyname(SHTTY));
+ }
+ if (SHTTY == -1) {
+ zsfree(ttystrname);
+ ttystrname = ztrdup("");
+ } else {
+#ifdef FD_CLOEXEC
+ long fdflags = fcntl(SHTTY, F_GETFD, 0);
+ if (fdflags != (long)-1) {
+ fdflags |= FD_CLOEXEC;
+ fcntl(SHTTY, F_SETFD, fdflags);
+ }
+#endif
+ if (!ttystrname)
+ ttystrname = ztrdup("/dev/tty");
+ }
+
+ /* We will only use zle if shell is interactive, *
+ * SHTTY != -1, and shout != 0 */
+ if (interact) {
+ init_shout();
+ if(!SHTTY || !shout)
+ opts[USEZLE] = 0;
+ } else
+ opts[USEZLE] = 0;
+
+#ifdef JOB_CONTROL
+ /* If interactive, make sure the shell is in the foreground and is the
+ * process group leader.
+ */
+ mypid = (zlong)getpid();
+ if (opts[MONITOR] && (SHTTY != -1)) {
+ origpgrp = GETPGRP();
+ acquire_pgrp(); /* might also clear opts[MONITOR] */
+ } else
+ opts[MONITOR] = 0;
+#else
+ opts[MONITOR] = 0;
+#endif
+}
+
+/**/
+mod_export void
+init_shout(void)
+{
+ static char shoutbuf[BUFSIZ];
+#if defined(JOB_CONTROL) && defined(TIOCSETD) && defined(NTTYDISC)
+ int ldisc;
+#endif
+
+ if (SHTTY == -1)
+ {
+ /* Since we're interactive, it's nice to have somewhere to write. */
+ shout = stderr;
+ return;
+ }
+
+#if defined(JOB_CONTROL) && defined(TIOCSETD) && defined(NTTYDISC)
+ ldisc = NTTYDISC;
+ ioctl(SHTTY, TIOCSETD, (char *)&ldisc);
+#endif
+
+ /* Associate terminal file descriptor with a FILE pointer */
+ shout = fdopen(SHTTY, "w");
+#ifdef _IOFBF
+ if (shout)
+ 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", "ch",
+ "ku", "kd", "kl", "kr", "sc", "rc", "bc", "AF", "AB"
+};
+
+/**/
+mod_export char *
+tccap_get_name(int cap)
+{
+ if (cap >= TC_COUNT) {
+#ifdef DEBUG
+ dputs("name of invalid capability %d requested", cap);
+#endif
+ return "";
+ }
+ return tccapnams[cap];
+}
+
+/* Initialise termcap */
+
+/**/
+mod_export 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) != TGETENT_SUCCESS)
+#else
+ if (tgetent(termbuf, term) != TGETENT_SUCCESS)
+#endif
+ {
+ if (interact)
+ zerr("can't find terminal definition for %s", term);
+ errflag &= ~ERRFLAG_ERROR;
+ 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");
+ hasbw = tgetflag("bw");
+ hasxn = tgetflag("xn"); /* also check for newline wraparound glitch */
+ hasye = tgetflag("YE"); /* print in last column does carriage return */
+
+ tclines = tgetnum("li");
+ tccolumns = tgetnum("co");
+ tccolours = 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 {
+ zsfree(tcstr[TCUP]);
+ tcstr[TCUP] = NULL;
+ termflags |= TERM_NOUP;
+ }
+
+ /* most termcaps don't define "bc" because they use \b. */
+ if (!tccan(TCBACKSPACE)) {
+ zsfree(tcstr[TCBACKSPACE]);
+ tcstr[TCBACKSPACE] = ztrdup("\b");
+ tclen[TCBACKSPACE] = 1;
+ }
+
+ /* if there's no termcap entry for cursor left, use backspace. */
+ if (!tccan(TCLEFT)) {
+ zsfree(tcstr[TCLEFT]);
+ tcstr[TCLEFT] = ztrdup(tcstr[TCBACKSPACE]);
+ tclen[TCLEFT] = tclen[TCBACKSPACE];
+ }
+
+ if (tccan(TCSAVECURSOR) && !tccan(TCRESTRCURSOR)) {
+ tclen[TCSAVECURSOR] = 0;
+ zsfree(tcstr[TCSAVECURSOR]);
+ tcstr[TCSAVECURSOR] = NULL;
+ }
+
+ /* 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)) {
+ zsfree(tcstr[TCCLEARSCREEN]);
+ tcstr[TCCLEARSCREEN] = ztrdup("\14");
+ tclen[TCCLEARSCREEN] = 1;
+ }
+ rprompt_indent = 1; /* If you change this, update rprompt_indent_unsetfn() */
+ /* The following is an attempt at a heuristic,
+ * but it fails in some cases */
+ /* rprompt_indent = ((hasam && !hasbw) || hasye || !tccan(TCLEFT)); */
+ }
+ return 1;
+}
+
+/* Initialize lots of global variables and hash tables */
+
+/**/
+void
+setupvals(char *cmd, char *runscript, char *zsh_name)
+{
+#ifdef USE_GETPWUID
+ struct passwd *pswd;
+#endif
+ struct timezone dummy_tz;
+ char *ptr;
+ int i, j;
+#if defined(SITEFPATH_DIR) || defined(FPATH_DIR) || defined (ADDITIONAL_FPATH) || defined(FIXED_FPATH_DIR)
+#define FPATH_NEEDS_INIT 1
+ char **fpathptr;
+# if defined(FPATH_DIR) && defined(FPATH_SUBDIRS)
+ char *fpath_subdirs[] = FPATH_SUBDIRS;
+# endif
+# if defined(ADDITIONAL_FPATH)
+ char *more_fndirs[] = ADDITIONAL_FPATH;
+ int more_fndirs_len;
+# endif
+# ifdef FIXED_FPATH_DIR
+# define FIXED_FPATH_LEN 1
+# else
+# define FIXED_FPATH_LEN 0
+# endif
+# ifdef SITEFPATH_DIR
+# define SITE_FPATH_LEN 1
+# else
+# define SITE_FPATH_LEN 0
+# endif
+ int fpathlen = FIXED_FPATH_LEN + SITE_FPATH_LEN;
+#endif
+ int close_fds[10], tmppipe[2];
+
+ /*
+ * Workaround a problem with NIS (in one guise or another) which
+ * grabs file descriptors and keeps them for future reference.
+ * We don't want these to be in the range where the user can
+ * open fd's, i.e. 0 to 9 inclusive. So we make sure all
+ * fd's in that range are in use.
+ */
+ memset(close_fds, 0, 10*sizeof(int));
+ if (pipe(tmppipe) == 0) {
+ /*
+ * Strategy: Make sure we have at least fd 0 open (hence
+ * the pipe). From then on, keep dup'ing until we are
+ * up to 9. If we go over the top, close immediately, else
+ * mark for later closure.
+ */
+ i = -1; /* max fd we have checked */
+ while (i < 9) {
+ /* j is current fd */
+ if (i < tmppipe[0])
+ j = tmppipe[0];
+ else if (i < tmppipe[1])
+ j = tmppipe[1];
+ else {
+ j = dup(0);
+ if (j == -1)
+ break;
+ }
+ if (j < 10)
+ close_fds[j] = 1;
+ else
+ close(j);
+ if (i < j)
+ i = j;
+ }
+ if (i < tmppipe[0])
+ close(tmppipe[0]);
+ if (i < tmppipe[1])
+ close(tmppipe[1]);
+ }
+
+ (void)addhookdefs(NULL, zshhooks, sizeof(zshhooks)/sizeof(*zshhooks));
+
+ init_eprog();
+
+ zero_mnumber.type = MN_INTEGER;
+ zero_mnumber.u.l = 0;
+
+ noeval = 0;
+ curhist = 0;
+ histsiz = DEFAULT_HISTSIZE;
+ inithist();
+
+ cmdstack = (unsigned char *) zalloc(CMDSTACKSZ);
+ 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 */
+
+ /* 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);
+
+#ifdef FPATH_NEEDS_INIT
+# ifdef FPATH_DIR
+# ifdef FPATH_SUBDIRS
+ fpathlen += sizeof(fpath_subdirs)/sizeof(char *);
+# else /* FPATH_SUBDIRS */
+ fpathlen++;
+# endif /* FPATH_SUBDIRS */
+# endif /* FPATH_DIR */
+# if defined(ADDITIONAL_FPATH)
+ more_fndirs_len = sizeof(more_fndirs)/sizeof(char *);
+ fpathlen += more_fndirs_len;
+# endif /* ADDITONAL_FPATH */
+ fpath = fpathptr = (char **)zalloc((fpathlen+1)*sizeof(char *));
+# ifdef FIXED_FPATH_DIR
+ *fpathptr++ = ztrdup(FIXED_FPATH_DIR);
+ fpathlen--;
+# endif
+# ifdef SITEFPATH_DIR
+ *fpathptr++ = ztrdup(SITEFPATH_DIR);
+ fpathlen--;
+# endif /* SITEFPATH_DIR */
+# if defined(ADDITIONAL_FPATH)
+ for (j = 0; j < more_fndirs_len; j++)
+ *fpathptr++ = ztrdup(more_fndirs[j]);
+# endif
+# ifdef FPATH_DIR
+# ifdef FPATH_SUBDIRS
+# ifdef ADDITIONAL_FPATH
+ for (j = more_fndirs_len; j < fpathlen; j++)
+ *fpathptr++ = tricat(FPATH_DIR, "/", fpath_subdirs[j - more_fndirs_len]);
+# else
+ for (j = 0; j < fpathlen; j++)
+ *fpathptr++ = tricat(FPATH_DIR, "/", fpath_subdirs[j]);
+#endif
+# else
+ *fpathptr++ = ztrdup(FPATH_DIR);
+# endif
+# endif
+ *fpathptr = NULL;
+#else /* FPATH_NEEDS_INIT */
+ fpath = mkarray(NULL);
+#endif /* FPATH_NEEDS_INIT */
+
+ mailpath = mkarray(NULL);
+ watch = mkarray(NULL);
+ psvar = mkarray(NULL);
+ module_path = mkarray(ztrdup(MODULE_DIR));
+ modulestab = newmoduletable(17, "modules");
+ linkedmodules = znewlinklist();
+
+ /* Set default prompts */
+ if(unset(INTERACTIVE)) {
+ prompt = ztrdup("");
+ prompt2 = ztrdup("");
+ } else if (EMULATION(EMULATE_KSH|EMULATE_SH)) {
+ prompt = ztrdup(privasserted() ? "# " : "$ ");
+ prompt2 = ztrdup("> ");
+ } else {
+ prompt = ztrdup("%m%# ");
+ prompt2 = ztrdup("%_> ");
+ }
+ prompt3 = ztrdup("?# ");
+ prompt4 = EMULATION(EMULATE_KSH|EMULATE_SH)
+ ? ztrdup("+ ") : ztrdup("+%N:%i> ");
+ sprompt = ztrdup("zsh: correct '%R' to '%r' [nyae]? ");
+
+ ifs = EMULATION(EMULATE_KSH|EMULATE_SH) ?
+ ztrdup(DEFAULT_IFS_SH) : ztrdup(DEFAULT_IFS);
+ wordchars = ztrdup(DEFAULT_WORDCHARS);
+ postedit = ztrdup("");
+ zunderscore = (char *) zalloc(underscorelen = 32);
+ underscoreused = 1;
+ *zunderscore = '\0';
+
+ zoptarg = ztrdup("");
+ zoptind = 1;
+
+ ppid = (zlong) getppid();
+ mypid = (zlong) getpid();
+ term = ztrdup("");
+
+ nullcmd = ztrdup("cat");
+ readnullcmd = ztrdup(DEFAULT_READNULLCMD);
+
+ /* We cache the uid so we know when to *
+ * recheck the info for `USERNAME' */
+ cached_uid = getuid();
+
+ /* Get password entry and set info for `USERNAME' */
+#ifdef USE_GETPWUID
+ if ((pswd = getpwuid(cached_uid))) {
+ if (EMULATION(EMULATE_ZSH))
+ home = metafy(pswd->pw_dir, -1, META_DUP);
+ cached_username = ztrdup(pswd->pw_name);
+ }
+ else
+#endif /* USE_GETPWUID */
+ {
+ if (EMULATION(EMULATE_ZSH))
+ home = ztrdup("/");
+ cached_username = ztrdup("");
+ }
+
+ /*
+ * Try a cheap test to see if we can initialize `PWD' from `HOME'.
+ * In non-native emulations HOME must come from the environment;
+ * we're not allowed to set it locally.
+ */
+ if (EMULATION(EMULATE_ZSH))
+ ptr = home;
+ else
+ ptr = zgetenv("HOME");
+ if (ptr && ispwd(ptr))
+ pwd = ztrdup(ptr);
+ else if ((ptr = zgetenv("PWD")) && (strlen(ptr) < PATH_MAX) &&
+ (ptr = metafy(ptr, -1, META_STATIC), ispwd(ptr)))
+ pwd = ztrdup(ptr);
+ else {
+ pwd = NULL;
+ 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 */
+ createaliastables(); /* create hash tables 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 parameter hash table */
+
+ condtab = NULL;
+ wrappers = NULL;
+
+#ifdef TIOCGWINSZ
+ adjustwinsize(0);
+#else
+ /* columns and lines are normally zero, unless something different *
+ * was inhereted from the environment. If either of them are zero *
+ * the setiparam calls below set them to the defaults from termcap */
+ setiparam("COLUMNS", zterm_columns);
+ setiparam("LINES", zterm_lines);
+#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;
+ sfcontext = SFC_NONE;
+ trap_return = 0;
+ trap_state = TRAP_STATE_INACTIVE;
+ noerrexit = NOERREXIT_EXIT | NOERREXIT_RETURN | NOERREXIT_SIGNAL;
+ nohistsave = 1;
+ dirstack = znewlinklist();
+ bufstack = znewlinklist();
+ hsubl = hsubr = NULL;
+ lastpid = 0;
+
+ get_usage();
+
+ /* Close the file descriptors we opened to block off 0 to 9 */
+ for (i = 0; i < 10; i++)
+ if (close_fds[i])
+ close(i);
+
+ /* Colour sequences for outputting colours in prompts and zle */
+ set_default_colour_sequences();
+
+ if (cmd)
+ setsparam("ZSH_EXECUTION_STRING", ztrdup(cmd));
+ if (runscript)
+ setsparam("ZSH_SCRIPT", ztrdup(runscript));
+ setsparam("ZSH_NAME", ztrdup(zsh_name)); /* NOTE: already metafied early in zsh_main() */
+}
+
+/*
+ * Setup shell input, opening any script file (runscript, may be NULL).
+ * This is deferred until we have a path to search, in case
+ * PATHSCRIPT is set for sh-compatible behaviour.
+ */
+static void
+setupshin(char *runscript)
+{
+ if (runscript) {
+ char *funmeta, *sfname = NULL;
+ struct stat st;
+
+ funmeta = unmeta(runscript);
+ /*
+ * Always search the current directory first.
+ */
+ if (access(funmeta, F_OK) == 0 &&
+ stat(funmeta, &st) >= 0 &&
+ !S_ISDIR(st.st_mode))
+ sfname = runscript;
+ else if (isset(PATHSCRIPT) && !strchr(runscript, '/')) {
+ /*
+ * With the PATHSCRIPT option, search the path if no
+ * path was given in the script name.
+ */
+ funmeta = pathprog(runscript, &sfname);
+ }
+ if (!sfname ||
+ (SHIN = movefd(open(funmeta, O_RDONLY | O_NOCTTY)))
+ == -1) {
+ zerr("can't open input file: %s", runscript);
+ exit(127);
+ }
+ scriptfilename = sfname;
+ sfname = argzero; /* copy to avoid race condition */
+ argzero = ztrdup(runscript);
+ zsfree(sfname); /* argzero ztrdup'd in parseargs */
+ }
+ /*
+ * We only initialise line numbering once there is a script to
+ * read commands from.
+ */
+ lineno = 1;
+ /*
+ * Finish setting up SHIN and its relatives.
+ */
+ bshin = SHIN ? fdopen(SHIN, "r") : stdin;
+ if (isset(SHINSTDIN) && !SHIN && unset(INTERACTIVE)) {
+#ifdef _IONBF
+ setvbuf(stdin, NULL, _IONBF, 0);
+#else
+ setlinebuf(stdin);
+#endif
+ }
+}
+
+/* Initialize signal handling */
+
+/**/
+void
+init_signals(void)
+{
+ if (interact) {
+ int i;
+ signal_setmask(signal_mask(0));
+ for (i=0; i<NSIG; ++i)
+ signal_default(i);
+ }
+ sigchld_mask = signal_mask(SIGCHLD);
+
+ intr();
+
+#ifndef QDEBUG
+ signal_ignore(SIGQUIT);
+#endif
+
+ if (signal_ignore(SIGHUP) == SIG_IGN)
+ opts[HUP] = 0;
+ else
+ install_handler(SIGHUP);
+ install_handler(SIGCHLD);
+#ifdef SIGWINCH
+ install_handler(SIGWINCH);
+ winch_block(); /* See utils.c:preprompt() */
+#endif
+ if (interact) {
+ install_handler(SIGPIPE);
+ install_handler(SIGALRM);
+ signal_ignore(SIGTERM);
+ }
+ if (jobbing) {
+ signal_ignore(SIGTTOU);
+ signal_ignore(SIGTSTP);
+ signal_ignore(SIGTTIN);
+ }
+}
+
+/* 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 = NOERREXIT_EXIT | NOERREXIT_RETURN | NOERREXIT_SIGNAL;
+
+ if (EMULATION(EMULATE_KSH|EMULATE_SH)) {
+ if (islogin)
+ source("/etc/profile");
+ if (unset(PRIVILEGED)) {
+ if (islogin)
+ sourcehome(".profile");
+
+ if (interact) {
+ noerrs = 2;
+ char *s = getsparam("ENV");
+ if (s) {
+ s = dupstring(s);
+ if (!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) && unset(PRIVILEGED))
+ {
+ if (interact) {
+ /*
+ * Always attempt to load the newuser module to perform
+ * checks for new zsh users. Don't care if we can't load it.
+ */
+ if (!load_module("zsh/newuser", NULL, 1)) {
+ /* Unload it immediately. */
+ unload_named_module("zsh/newuser", "zsh", 1);
+ }
+ }
+
+ sourcehome(".zshenv");
+ }
+ if (islogin) {
+#ifdef GLOBAL_ZPROFILE
+ if (isset(RCS) && isset(GLOBALRCS))
+ source(GLOBAL_ZPROFILE);
+#endif
+ if (isset(RCS) && unset(PRIVILEGED))
+ sourcehome(".zprofile");
+ }
+ if (interact) {
+#ifdef GLOBAL_ZSHRC
+ if (isset(RCS) && isset(GLOBALRCS))
+ source(GLOBAL_ZSHRC);
+#endif
+ if (isset(RCS) && unset(PRIVILEGED))
+ sourcehome(".zshrc");
+ }
+ if (islogin) {
+#ifdef GLOBAL_ZLOGIN
+ if (isset(RCS) && isset(GLOBALRCS))
+ source(GLOBAL_ZLOGIN);
+#endif
+ if (isset(RCS) && unset(PRIVILEGED))
+ sourcehome(".zlogin");
+ }
+ }
+ noerrexit = 0;
+ nohistsave = 0;
+}
+
+/* Miscellaneous initializations that happen after init scripts are run */
+
+/**/
+void
+init_misc(char *cmd, char *zsh_name)
+{
+#ifndef RESTRICTED_R
+ if ( restricted )
+#else
+ if (*zsh_name == 'r' || restricted)
+#endif
+ dosetopt(RESTRICTED, 1, 0, opts);
+ if (cmd) {
+ if (SHIN >= 10)
+ fclose(bshin);
+ SHIN = movefd(open("/dev/null", O_RDONLY | O_NOCTTY));
+ bshin = fdopen(SHIN, "r");
+ execstring(cmd, 0, 1, "cmdarg");
+ stopmsg = 1;
+ zexit(lastval, 0);
+ }
+
+ if (interact && isset(RCS))
+ readhistfile(NULL, 0, HFILE_USE_OPTIONS);
+}
+
+/*
+ * source a file
+ * Returns one of the SOURCE_* enum values.
+ */
+
+/**/
+mod_export enum source_return
+source(char *s)
+{
+ Eprog prog;
+ int tempfd = -1, fd, cj;
+ zlong oldlineno;
+ int oldshst, osubsh, oloops;
+ FILE *obshin;
+ char *old_scriptname = scriptname, *us;
+ char *old_scriptfilename = scriptfilename;
+ unsigned char *ocs;
+ int ocsp;
+ int otrap_return = trap_return, otrap_state = trap_state;
+ struct funcstack fstack;
+ enum source_return ret = SOURCE_OK;
+
+ if (!s ||
+ (!(prog = try_source_file((us = unmeta(s)))) &&
+ (tempfd = movefd(open(us, O_RDONLY | O_NOCTTY))) == -1)) {
+ return SOURCE_NOT_FOUND;
+ }
+
+ /* 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 */
+ ocs = cmdstack;
+ ocsp = cmdsp;
+ cmdstack = (unsigned char *) zalloc(CMDSTACKSZ);
+ cmdsp = 0;
+
+ if (!prog) {
+ SHIN = tempfd;
+ bshin = fdopen(SHIN, "r");
+ }
+ subsh = 0;
+ lineno = 1;
+ loops = 0;
+ dosetopt(SHINSTDIN, 0, 1, opts);
+ scriptname = s;
+ scriptfilename = s;
+
+ if (isset(SOURCETRACE)) {
+ printprompt4();
+ fprintf(xtrerr ? xtrerr : stderr, "<sourcetrace>\n");
+ }
+
+ /*
+ * The special return behaviour of traps shouldn't
+ * trigger in files sourced from traps; the return
+ * is just a return from the file.
+ */
+ trap_state = TRAP_STATE_INACTIVE;
+
+ sourcelevel++;
+
+ fstack.name = scriptfilename;
+ fstack.caller = funcstack ? funcstack->name :
+ dupstring(old_scriptfilename ? old_scriptfilename : "zsh");
+ fstack.flineno = 0;
+ fstack.lineno = oldlineno;
+ fstack.filename = scriptfilename;
+ fstack.prev = funcstack;
+ fstack.tp = FS_SOURCE;
+ funcstack = &fstack;
+
+ if (prog) {
+ pushheap();
+ errflag &= ~ERRFLAG_ERROR;
+ execode(prog, 1, 0, "filecode");
+ popheap();
+ if (errflag)
+ ret = SOURCE_ERROR;
+ } else {
+ /* loop through the file to be sourced */
+ switch (loop(0, 0))
+ {
+ case LOOP_OK:
+ /* nothing to do but compilers like a complete enum */
+ break;
+
+ case LOOP_EMPTY:
+ /* Empty code resets status */
+ lastval = 0;
+ break;
+
+ case LOOP_ERROR:
+ ret = SOURCE_ERROR;
+ break;
+ }
+ }
+ funcstack = funcstack->prev;
+ sourcelevel--;
+
+ trap_state = otrap_state;
+ trap_return = otrap_return;
+
+ /* restore the current shell state */
+ if (prog)
+ freeeprog(prog);
+ else {
+ fclose(bshin);
+ fdtable[SHIN] = FDT_UNUSED;
+ 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, opts); /* SHINSTDIN option */
+ errflag &= ~ERRFLAG_ERROR;
+ if (!exit_pending)
+ retflag = 0;
+ scriptname = old_scriptname;
+ scriptfilename = old_scriptfilename;
+ zfree(cmdstack, CMDSTACKSZ);
+ cmdstack = ocs;
+ cmdsp = ocsp;
+
+ return ret;
+}
+
+/* Try to source a file in the home directory */
+
+/**/
+void
+sourcehome(char *s)
+{
+ char *h;
+
+ queue_signals();
+ if (EMULATION(EMULATE_SH|EMULATE_KSH) || !(h = getsparam_u("ZDOTDIR"))) {
+ h = home;
+ if (!h) {
+ unqueue_signals();
+ return;
+ }
+ }
+
+ {
+ /* Let source() complain if path is too long */
+ VARARR(char, buf, strlen(h) + strlen(s) + 2);
+ sprintf(buf, "%s/%s", h, s);
+ unqueue_signals();
+ source(buf);
+ }
+}
+
+/**/
+void
+init_bltinmods(void)
+{
+
+#include "bltinmods.list"
+
+ (void)load_module("zsh/main", NULL, 0);
+}
+
+/**/
+mod_export void
+noop_function(void)
+{
+ /* do nothing */
+}
+
+/**/
+mod_export void
+noop_function_int(UNUSED(int nothing))
+{
+ /* do nothing */
+}
+
+/*
+ * ZLE entry point pointer.
+ * No other source file needs to know which modules are linked in.
+ */
+/**/
+mod_export ZleEntryPoint zle_entry_ptr;
+
+/*
+ * State of loading of zle.
+ * 0 = Not loaded, not attempted.
+ * 1 = Loaded successfully
+ * 2 = Failed to load.
+ */
+/**/
+mod_export int zle_load_state;
+
+/**/
+mod_export char *
+zleentry(VA_ALIST1(int cmd))
+VA_DCL
+{
+ char *ret = NULL;
+ va_list ap;
+ VA_DEF_ARG(int cmd);
+
+ VA_START(ap, cmd);
+ VA_GET_ARG(ap, cmd, int);
+
+#if defined(LINKED_XMOD_zshQszle) || defined(UNLINKED_XMOD_zshQszle)
+ /* autoload */
+ switch (zle_load_state) {
+ case 0:
+ /*
+ * Some commands don't require us to load ZLE.
+ * These also have no fallback.
+ */
+ if (cmd != ZLE_CMD_TRASH && cmd != ZLE_CMD_RESET_PROMPT &&
+ cmd != ZLE_CMD_REFRESH)
+ {
+ if (load_module("zsh/zle", NULL, 0) != 1) {
+ (void)load_module("zsh/compctl", NULL, 0);
+ ret = zle_entry_ptr(cmd, ap);
+ /* Don't execute fallback code */
+ cmd = -1;
+ } else {
+ zle_load_state = 2;
+ /* Execute fallback code below */
+ }
+ }
+ break;
+
+ case 1:
+ ret = zle_entry_ptr(cmd, ap);
+ /* Don't execute fallback code */
+ cmd = -1;
+ break;
+
+ case 2:
+ /* Execute fallback code */
+ break;
+ }
+#endif
+
+ switch (cmd) {
+ /*
+ * Only the read command really needs a fallback if zle
+ * is not available. ZLE_CMD_GET_LINE has traditionally
+ * had local code in bufferwords() to do this, but that'
+ * probably only because bufferwords() is part of completion
+ * and so everything to do with it is horribly complicated.
+ */
+ case ZLE_CMD_READ:
+ {
+ char *pptbuf, **lp;
+ int pptlen;
+
+ lp = va_arg(ap, char **);
+
+ pptbuf = unmetafy(promptexpand(lp ? *lp : NULL, 0, NULL, NULL,
+ NULL),
+ &pptlen);
+ write_loop(2, pptbuf, pptlen);
+ free(pptbuf);
+
+ ret = shingetline();
+ break;
+ }
+
+ case ZLE_CMD_GET_LINE:
+ {
+ int *ll, *cs;
+
+ ll = va_arg(ap, int *);
+ cs = va_arg(ap, int *);
+ *ll = *cs = 0;
+ ret = ztrdup("");
+ break;
+ }
+ }
+
+ va_end(ap);
+ return ret;
+}
+
+/* compctl entry point pointers. Similar to the ZLE ones. */
+
+/**/
+mod_export CompctlReadFn compctlreadptr = fallback_compctlread;
+
+/**/
+mod_export int
+fallback_compctlread(char *name, UNUSED(char **args), UNUSED(Options ops), UNUSED(char *reply))
+{
+ zwarnnam(name, "no loaded module provides read for completion context");
+ return 1;
+}
+
+/*
+ * Used by zle to indicate it has already printed a "use 'exit' to exit"
+ * message.
+ */
+/**/
+mod_export int use_exit_printed;
+
+/*
+ * This is real main entry point. This has to be mod_export'ed
+ * so zsh.exe can found it on Cygwin
+ */
+
+/**/
+mod_export int
+zsh_main(UNUSED(int argc), char **argv)
+{
+ char **t, *runscript = NULL, *zsh_name;
+ char *cmd; /* argument to -c */
+ int t0;
+#ifdef USE_LOCALE
+ setlocale(LC_ALL, "");
+#endif
+
+ init_jobs(argv, environ);
+
+ /*
+ * Provisionally set up the type table to allow metafication.
+ * This will be done properly when we have decided if we are
+ * interactive
+ */
+ 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 (t = argv; *t; *t = metafy(*t, -1, META_ALLOC), t++);
+
+ zsh_name = argv[0];
+ do {
+ char *arg0 = zsh_name;
+ if (!(zsh_name = strrchr(arg0, '/')))
+ zsh_name = arg0;
+ else
+ zsh_name++;
+ if (*zsh_name == '-')
+ zsh_name++;
+ if (strcmp(zsh_name, "su") == 0) {
+ char *sh = zgetenv("SHELL");
+ if (sh && *sh && arg0 != sh)
+ zsh_name = sh;
+ else
+ break;
+ } else
+ break;
+ } while (zsh_name);
+
+ fdtable_size = zopenmax();
+ fdtable = zshcalloc(fdtable_size*sizeof(*fdtable));
+ fdtable[0] = fdtable[1] = fdtable[2] = FDT_EXTERNAL;
+
+ createoptiontable();
+ /* sets emulation, LOGINSHELL, PRIVILEGED, ZLE, INTERACTIVE,
+ * SHINSTDIN and SINGLECOMMAND */
+ parseargs(zsh_name, argv, &runscript, &cmd);
+
+ SHTTY = -1;
+ init_io(cmd);
+ setupvals(cmd, runscript, zsh_name);
+
+ init_signals();
+ init_bltinmods();
+ init_builtins();
+ run_init_scripts();
+ setupshin(runscript);
+ init_misc(cmd, zsh_name);
+
+ for (;;) {
+ /*
+ * See if we can free up some of jobtab.
+ * We only do this at top level, because if we are
+ * executing stuff we may refer to them by job pointer.
+ */
+ int errexit = 0;
+ maybeshrinkjobtab();
+
+ do {
+ /* Reset return from top level which gets us back here */
+ retflag = 0;
+ loop(1,0);
+ if (errflag && !interact && !isset(CONTINUEONERROR)) {
+ errexit = 1;
+ break;
+ }
+ } while (tok != ENDINPUT && (tok != LEXERR || isset(SHINSTDIN)));
+ if (tok == LEXERR || errexit) {
+ /* Make sure a fatal error exits with non-zero status */
+ if (!lastval)
+ lastval = 1;
+ 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);
+ }
+ /*
+ * Don't print the message if it was already handled by
+ * zle, since that makes special arrangements to keep
+ * the display tidy.
+ */
+ if (!use_exit_printed)
+ zerrnam("zsh", (!islogin) ? "use 'exit' to exit."
+ : "use 'logout' to logout.");
+ }
+}
diff --git a/dotfiles/system/.zsh/modules/Src/input.c b/dotfiles/system/.zsh/modules/Src/input.c
new file mode 100644
index 0000000..9787ded
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/input.c
@@ -0,0 +1,701 @@
+/*
+ * 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 flags INP_ALCONT and INP_HISTCONT show that the stack
+ * element was pushed by an alias or history expansion; they 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
+ */
+
+#ifdef HAVE_STDIO_H
+#include <stdio.h>
+#endif
+
+#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. */
+
+/**/
+mod_export 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. */
+
+/**/
+mod_export char *
+shingetline(void)
+{
+ char *line = NULL;
+ int ll = 0;
+ int c;
+ char buf[BUFSIZ];
+ char *p;
+ int q = queue_signal_level();
+
+ p = buf;
+ winch_unblock();
+ dont_queue_signals();
+ for (;;) {
+ /* Can't fgets() here because we need to accept '\0' bytes */
+ do {
+ errno = 0;
+ c = fgetc(bshin);
+ } while (c < 0 && errno == EINTR);
+ if (c < 0 || c == '\n') {
+ winch_block();
+ restore_queue_signals(q);
+ 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) {
+ winch_block();
+ queue_signals();
+ line = zrealloc(line, ll + (p - buf) + 1);
+ memcpy(line + ll, buf, p - buf);
+ ll += p - buf;
+ line[ll] = '\0';
+ p = buf;
+ winch_unblock();
+ dont_queue_signals();
+ }
+ }
+}
+
+/* Get the next character from the input.
+ * Will call inputline() to get a new line where necessary.
+ */
+
+/**/
+int
+ingetc(void)
+{
+ int lastc = ' ';
+
+ if (lexstop)
+ return ' ';
+ for (;;) {
+ if (inbufleft) {
+ inbufleft--;
+ inbufct--;
+ if (itok(lastc = STOUC(*inbufptr++)))
+ continue;
+ if (((inbufflags & INP_LINENO) || !strin) && lastc == '\n')
+ lineno++;
+ break;
+ }
+
+ /*
+ * See if we have reached the end of input
+ * (due to an error, or to reading from a single string).
+ * Check the remaining characters left, since if there aren't
+ * any we don't want to pop the stack---it'll mark any aliases
+ * as not in use before we've finished processing.
+ */
+ if (!inbufct && (strin || errflag)) {
+ lexstop = 1;
+ break;
+ }
+ /* If the next element down the input stack is a continuation of
+ * this, use it.
+ */
+ if (inbufflags & INP_CONT) {
+ inpoptop();
+ continue;
+ }
+ /* As a last resort, get some more input */
+ if (inputline())
+ break;
+ }
+ if (!lexstop)
+ zshlex_raw_add(lastc);
+ return lastc;
+}
+
+/* Read a line from the current command stream and store it as input */
+
+/**/
+static int
+inputline(void)
+{
+ char *ingetcline, **ingetcpmptl = NULL, **ingetcpmptr = NULL;
+ int context = ZLCON_LINE_START;
+
+ /* If reading code interactively, work out the prompts. */
+ if (interact && isset(SHINSTDIN)) {
+ if (!isfirstln) {
+ ingetcpmptl = &prompt2;
+ if (rprompt2)
+ ingetcpmptr = &rprompt2;
+ context = ZLCON_LINE_CONT;
+ }
+ 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 ? *ingetcpmptl : NULL,
+ 0, NULL, NULL, NULL), &pptlen);
+ write_loop(2, pptbuf, pptlen);
+ free(pptbuf);
+ }
+ ingetcline = shingetline();
+ } else {
+ /*
+ * Since we may have to read multiple lines before getting
+ * a complete piece of input, we tell zle not to restore the
+ * original tty settings after reading each chunk. Instead,
+ * this is done when the history mechanism for the current input
+ * terminates, which is not until we have the whole input.
+ * This is supposed to minimise problems on systems that clobber
+ * typeahead when the terminal settings are altered.
+ * pws 1998/03/12
+ */
+ int flags = ZLRF_HISTORY|ZLRF_NOSETTY;
+ if (isset(IGNOREEOF))
+ flags |= ZLRF_IGNOREEOF;
+ ingetcline = zleentry(ZLE_CMD_READ, ingetcpmptl, ingetcpmptr,
+ flags, context);
+ histdone |= HISTFLAG_SETTY;
+ }
+ if (!ingetcline) {
+ return lexstop = 1;
+ }
+ if (errflag) {
+ free(ingetcline);
+ errflag |= ERRFLAG_ERROR;
+ return lexstop = 1;
+ }
+ if (isset(VERBOSE)) {
+ /* Output the whole line read so far. */
+ zputs(ingetcline, stderr);
+ fflush(stderr);
+ }
+ if (keyboardhackchar && *ingetcline &&
+ ingetcline[strlen(ingetcline) - 1] == '\n' &&
+ interact && isset(SHINSTDIN) &&
+ SHTTY != -1 && ingetcline[1])
+ {
+ char *stripptr = ingetcline + strlen(ingetcline) - 2;
+ if (*stripptr == keyboardhackchar) {
+ /* Junk an unwanted character at the end of the line.
+ (key too close to return key) */
+ int ct = 1; /* force odd */
+ char *ptr;
+
+ if (keyboardhackchar == '\'' || keyboardhackchar == '"' ||
+ keyboardhackchar == '`') {
+ /*
+ * for the chars above, also require an odd count before
+ * junking
+ */
+ for (ct = 0, ptr = ingetcline; *ptr; ptr++)
+ if (*ptr == keyboardhackchar)
+ ct++;
+ }
+ if (ct & 1) {
+ stripptr[0] = '\n';
+ stripptr[1] = '\0';
+ }
+ }
+ }
+ isfirstch = 1;
+ if ((inbufflags & INP_APPEND) && inbuf) {
+ /*
+ * We need new input but need to be able to back up
+ * over the old input, so append this line.
+ * Pushing the line onto the stack doesn't have the right
+ * effect.
+ *
+ * This is quite a simple and inefficient fix, but currently
+ * we only need it when backing up over a multi-line $((...
+ * that turned out to be a command substitution rather than
+ * a math substitution, which is a very special case.
+ * So it's not worth rewriting.
+ */
+ char *oinbuf = inbuf;
+ int newlen = strlen(ingetcline);
+ int oldlen = (int)(inbufptr - inbuf) + inbufleft;
+ if (inbufflags & INP_FREE) {
+ inbuf = realloc(inbuf, oldlen + newlen + 1);
+ } else {
+ inbuf = zalloc(oldlen + newlen + 1);
+ memcpy(inbuf, oinbuf, oldlen);
+ }
+ inbufptr += inbuf - oinbuf;
+ strcpy(inbuf + oldlen, ingetcline);
+ free(ingetcline);
+ inbufleft += newlen;
+ inbufct += newlen;
+ inbufflags |= INP_FREE;
+ } else {
+ /* 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)
+{
+ queue_signals();
+
+ 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;
+
+ unqueue_signals();
+}
+
+/*
+ * 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++;
+ if (((inbufflags & INP_LINENO) || !strin) && c == '\n')
+ lineno--;
+ }
+ else if (!(inbufflags & INP_CONT)) {
+#ifdef DEBUG
+ /* Just for debugging */
+ fprintf(stderr, "Attempt to inungetc() at start of input.\n");
+#endif
+ zerr("Garbled input at %c (binary file as commands?)", c);
+ return;
+ }
+ 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 *)zshcalloc(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|INP_HISTCONT))) {
+ /*
+ * 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|INP_HISTCONT))
+ && !instacktop->bufleft);
+ if (inbufflags & INP_HISTCONT)
+ inbufflags = INP_CONT|INP_ALIAS|INP_HIST;
+ else
+ inbufflags = INP_CONT|INP_ALIAS;
+ inbufleft = 0;
+ inbuf = inbufptr = "";
+ }
+ zshlex_raw_back();
+ }
+}
+
+/* stuff a whole file into the input queue and print it */
+
+/**/
+int
+stuff(char *fn)
+{
+ FILE *in;
+ char *buf;
+ off_t len;
+
+ if (!(in = fopen(unmeta(fn), "r"))) {
+ zerr("can't open %s", fn);
+ 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);
+ 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 */
+
+/**/
+mod_export 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|INP_HISTCONT);
+ 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;
+ if (flags & INP_HIST)
+ instacktop->flags = inbufflags | INP_HISTCONT;
+ else
+ 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 (!lexstop) {
+ inbufflags &= ~(INP_ALCONT|INP_HISTCONT);
+ while (inbufptr > inbuf) {
+ inbufptr--;
+ inbufct++;
+ inbufleft++;
+ /*
+ * As elsewhere in input and history mechanisms:
+ * unwinding aliases and unwinding history have different
+ * implications as aliases are after the lexer while
+ * history is before, but they're both pushed onto
+ * the input stack.
+ */
+ if ((inbufflags & (INP_ALIAS|INP_HIST|INP_RAW_KEEP)) == INP_ALIAS)
+ zshlex_raw_back();
+ }
+ }
+
+ 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|INP_HISTCONT)))
+ 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. */
+
+/**/
+mod_export void
+inpop(void)
+{
+ int remcont;
+
+ do {
+ remcont = inbufflags & INP_CONT;
+
+ inpoptop();
+ } while (remcont);
+}
+
+/*
+ * Expunge any aliases from the input stack; they shouldn't appear
+ * in the history and need to be flushed explicitly when we encounter
+ * an error.
+ */
+
+/**/
+void
+inpopalias(void)
+{
+ while (inbufflags & INP_ALIAS)
+ inpoptop();
+}
+
+
+/*
+ * Get pointer to remaining string to read.
+ */
+
+/**/
+char *
+ingetptr(void)
+{
+ return inbufptr;
+}
+
+/*
+ * Check if the current input line, including continuations, is
+ * expanding an alias. This does not detect alias expansions that
+ * have been fully processed and popped from the input stack.
+ * If there is an alias, the most recently expanded is returned,
+ * else NULL.
+ */
+
+/**/
+char *input_hasalias(void)
+{
+ int flags = inbufflags;
+ struct instacks *instackptr = instacktop;
+
+ for (;;)
+ {
+ if (!(flags & INP_CONT))
+ break;
+ instackptr--;
+ if (instackptr->alias)
+ return instackptr->alias->node.nam;
+ flags = instackptr->flags;
+ }
+
+ return NULL;
+}
diff --git a/dotfiles/system/.zsh/modules/Src/jobs.c b/dotfiles/system/.zsh/modules/Src/jobs.c
new file mode 100644
index 0000000..38b3d89
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/jobs.c
@@ -0,0 +1,2894 @@
+/*
+ * 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 at startup (equal to mypgprp, except
+ when we started without being process group leader */
+
+/**/
+mod_export pid_t origpgrp;
+
+/* the process group of the shell */
+
+/**/
+mod_export pid_t mypgrp;
+
+/* the job we are working on */
+
+/**/
+mod_export int thisjob;
+
+/* the current job (+) */
+
+/**/
+mod_export int curjob;
+
+/* the previous job (-) */
+
+/**/
+mod_export int prevjob;
+
+/* the job table */
+
+/**/
+mod_export struct job *jobtab;
+
+/* Size of the job table. */
+
+/**/
+mod_export int jobtabsize;
+
+/* The highest numbered job in the jobtable */
+
+/**/
+mod_export int maxjob;
+
+/* If we have entered a subshell, the original shell's job table. */
+static struct job *oldjobtab;
+
+/* The size of that. */
+static int oldmaxjob;
+
+/* shell timings */
+
+/**/
+#ifdef HAVE_GETRUSAGE
+/**/
+static struct rusage child_usage;
+/**/
+#else
+/**/
+static struct tms shtms;
+/**/
+#endif
+
+/* 1 if ttyctl -f has been executed */
+
+/**/
+mod_export int ttyfrozen;
+
+/* Previous values of errflag and breaks if the signal handler had to
+ * change them. And a flag saying if it did that. */
+
+/**/
+int prev_errflag, prev_breaks, errbrk_saved;
+
+/**/
+int numpipestats, pipestats[MAX_PIPESTATS];
+
+/* 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 0
+ if (WIFSTOPPED(pn->status) &&
+ (!(jn->stat & STAT_SUPERJOB) || pn->next))
+ pn->status = SP_RUNNING;
+#endif
+ if (WIFSTOPPED(pn->status))
+ 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, int aux)
+{
+ Process pn;
+ int i;
+
+ *jptr = NULL;
+ *pptr = NULL;
+ for (i = 1; i <= maxjob; i++)
+ {
+ /*
+ * We are only interested in jobs with processes still
+ * marked as live. Careful in case there's an identical
+ * process number in a job we haven't quite got around
+ * to deleting.
+ */
+ if (jobtab[i].stat & STAT_DONE)
+ continue;
+
+ for (pn = aux ? jobtab[i].auxprocs : jobtab[i].procs;
+ pn; pn = pn->next)
+ {
+ /*
+ * Make sure we match a process that's still running.
+ *
+ * When a job contains two pids, one terminated pid and one
+ * running pid, then the condition (jobtab[i].stat &
+ * STAT_DONE) will not stop these pids from being candidates
+ * for the findproc result (which is supposed to be a
+ * RUNNING pid), and if the terminated pid is an identical
+ * process number for the pid identifying the running
+ * process we are trying to find (after pid number
+ * wrapping), then we need to avoid returning the terminated
+ * pid, otherwise the shell would block and wait forever for
+ * the termination of the process which pid we were supposed
+ * to return in a different job.
+ */
+ if (pn->pid == pid) {
+ *pptr = pn;
+ *jptr = jobtab + i;
+ if (pn->status == SP_RUNNING)
+ return 1;
+ }
+ }
+ }
+
+ return (*pptr && *jptr);
+}
+
+/* Does the given job number have any processes? */
+
+/**/
+int
+hasprocs(int job)
+{
+ Job jn;
+
+ if (job < 0) {
+ DPUTS(1, "job number invalid in hasprocs");
+ return 0;
+ }
+ jn = jobtab + job;
+
+ return jn->procs || jn->auxprocs;
+}
+
+/* Find the super-job of a sub-job. */
+
+/**/
+static int
+super_job(int sub)
+{
+ int i;
+
+ for (i = 1; i <= maxjob; i++)
+ if ((jobtab[i].stat & STAT_SUPERJOB) &&
+ jobtab[i].other == sub &&
+ jobtab[i].gleader)
+ return i;
+ return 0;
+}
+
+/**/
+static int
+handle_sub(int job, int fg)
+{
+ /* job: superjob; sj: subjob. */
+ Job jn = jobtab + job, sj = jobtab + jn->other;
+
+ if ((sj->stat & STAT_DONE) || (!sj->procs && !sj->auxprocs)) {
+ struct process *p;
+
+ for (p = sj->procs; p; p = p->next) {
+ if (WIFSIGNALED(p->status)) {
+ if (jn->gleader != mypgrp && jn->procs->next)
+ killpg(jn->gleader, WTERMSIG(p->status));
+ else
+ kill(jn->procs->pid, WTERMSIG(p->status));
+ kill(sj->other, SIGCONT);
+ kill(sj->other, WTERMSIG(p->status));
+ break;
+ }
+ }
+ if (!p) {
+ int cp;
+
+ jn->stat &= ~STAT_SUPERJOB;
+ jn->stat |= STAT_WASSUPER;
+
+ if ((cp = ((WIFEXITED(jn->procs->status) ||
+ WIFSIGNALED(jn->procs->status)) &&
+ killpg(jn->gleader, 0) == -1))) {
+ Process p;
+ for (p = jn->procs; p->next; p = p->next);
+ jn->gleader = p->pid;
+ }
+ /* This deleted the job too early if the parent
+ shell waited for a command in a list that will
+ be executed by the sub-shell (e.g.: if we have
+ `ls|if true;then sleep 20;cat;fi' and ^Z the
+ sleep, the rest will be executed by a sub-shell,
+ but the parent shell gets notified for the
+ sleep.
+ deletejob(sj, 0); */
+ /* If this super-job contains only the sub-shell,
+ we have to attach the tty to its process group
+ now. */
+ if ((fg || thisjob == job) &&
+ (!jn->procs->next || cp || jn->procs->pid != jn->gleader))
+ attachtty(jn->gleader);
+ kill(sj->other, SIGCONT);
+ if (jn->stat & STAT_DISOWN)
+ {
+ deletejob(jn, 1);
+ }
+ }
+ curjob = jn - jobtab;
+ } else if (sj->stat & STAT_STOPPED) {
+ struct process *p;
+
+ jn->stat |= STAT_STOPPED;
+ for (p = jn->procs; p; p = p->next)
+ if (p->status == SP_RUNNING ||
+ (!WIFEXITED(p->status) && !WIFSIGNALED(p->status)))
+ p->status = sj->procs->status;
+ curjob = jn - jobtab;
+ printjob(jn, !!isset(LONGLISTJOBS), 1);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* Get the latest usage information */
+
+/**/
+void
+get_usage(void)
+{
+#ifdef HAVE_GETRUSAGE
+ getrusage(RUSAGE_CHILDREN, &child_usage);
+#else
+ times(&shtms);
+#endif
+}
+
+
+#if !defined HAVE_WAIT3 || !defined HAVE_GETRUSAGE
+/* Update status of process that we have just WAIT'ed for */
+
+/**/
+void
+update_process(Process pn, int status)
+{
+ struct timezone dummy_tz;
+#ifdef HAVE_GETRUSAGE
+ struct timeval childs = child_usage.ru_stime;
+ struct timeval childu = child_usage.ru_utime;
+#else
+ long childs = shtms.tms_cstime;
+ long childu = shtms.tms_cutime;
+#endif
+
+ /* get time-accounting info */
+ get_usage();
+ gettimeofday(&pn->endtime, &dummy_tz); /* record time process exited */
+
+ pn->status = status; /* save the status returned by WAIT */
+#ifdef HAVE_GETRUSAGE
+ dtime(&pn->ti.ru_stime, &childs, &child_usage.ru_stime);
+ dtime(&pn->ti.ru_utime, &childu, &child_usage.ru_utime);
+#else
+ pn->ti.st = shtms.tms_cstime - childs; /* compute process system space time */
+ pn->ti.ut = shtms.tms_cutime - childu; /* compute process user space time */
+#endif
+}
+#endif
+
+/*
+ * Called when the current shell is behaving as if it received
+ * a interactively generated signal (sig).
+ *
+ * As we got the signal or are pretending we did, we need to pretend
+ * anything attached to a CURSH process got it, too.
+ */
+/**/
+void
+check_cursh_sig(int sig)
+{
+ int i, j;
+
+ if (!errflag)
+ return;
+ for (i = 1; i <= maxjob; i++) {
+ if ((jobtab[i].stat & (STAT_CURSH|STAT_DONE)) ==
+ STAT_CURSH) {
+ for (j = 0; j < 2; j++) {
+ Process pn = j ? jobtab[i].auxprocs : jobtab[i].procs;
+ for (; pn; pn = pn->next) {
+ if (pn->status == SP_RUNNING) {
+ kill(pn->pid, sig);
+ }
+ }
+ }
+ }
+ }
+}
+
+/**/
+void
+storepipestats(Job jn, int inforeground, int fixlastval)
+{
+ int i, pipefail = 0, jpipestats[MAX_PIPESTATS];
+ Process p;
+
+ for (p = jn->procs, i = 0; p && i < MAX_PIPESTATS; p = p->next, i++) {
+ jpipestats[i] = (WIFSIGNALED(p->status) ?
+ 0200 | WTERMSIG(p->status) :
+ (WIFSTOPPED(p->status) ?
+ 0200 | WEXITSTATUS(p->status) :
+ WEXITSTATUS(p->status)));
+ if (jpipestats[i])
+ pipefail = jpipestats[i];
+ }
+ if (inforeground) {
+ memcpy(pipestats, jpipestats, sizeof(int)*i);
+ if ((jn->stat & STAT_CURSH) && i < MAX_PIPESTATS)
+ pipestats[i++] = lastval;
+ numpipestats = i;
+ }
+
+ if (fixlastval) {
+ if (jn->stat & STAT_CURSH) {
+ if (!lastval && isset(PIPEFAIL))
+ lastval = pipefail;
+ } else if (isset(PIPEFAIL))
+ lastval = pipefail;
+ }
+}
+
+/* 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->auxprocs; pn; pn = pn->next) {
+#ifdef WIFCONTINUED
+ if (WIFCONTINUED(pn->status))
+ pn->status = SP_RUNNING;
+#endif
+ if (pn->status == SP_RUNNING)
+ return;
+ }
+
+ for (pn = jn->procs; pn; pn = pn->next) {
+#ifdef WIFCONTINUED
+ if (WIFCONTINUED(pn->status)) {
+ jn->stat &= ~STAT_STOPPED;
+ pn->status = SP_RUNNING;
+ }
+#endif
+ 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) :
+ (WIFSTOPPED(pn->status) ?
+ 0200 | WEXITSTATUS(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) {
+ if (jn->stat & STAT_SUBJOB) {
+ /* If we have `cat foo|while read a; grep $a bar;done'
+ * and have hit ^Z, the sub-job is stopped, but the
+ * super-job may still be running, waiting to be stopped
+ * or to exit. So we have to send it a SIGTSTP. */
+ int i;
+
+ if ((i = super_job(job)))
+ killpg(jobtab[i].gleader, SIGTSTP);
+ }
+ return;
+ }
+ }
+ { /* job is done or stopped, remember return value */
+ lastval2 = val;
+ /* If last process was run in the current shell, keep old status
+ * and let it handle its own traps, but always allow the test
+ * for the pgrp.
+ */
+ if (jn->stat & STAT_CURSH)
+ inforeground = 1;
+ else if (job == thisjob) {
+ lastval = val;
+ inforeground = 2;
+ }
+ }
+
+ if (shout && shout != stderr && !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))) {
+ if (list_pipe) {
+ if (somestopped || (pgrp > 1 && kill(-pgrp, 0) == -1)) {
+ attachtty(mypgrp);
+ /* check window size and adjust if necessary */
+ adjustwinsize(0);
+ } else {
+ /*
+ * Oh, dear, we're right in the middle of some confusion
+ * of shell jobs on the righthand side of a pipeline, so
+ * it's death to call attachtty() just yet. Mark the
+ * fact in the job, so that the attachtty() will be called
+ * when the job is finally deleted.
+ */
+ jn->stat |= STAT_ATTACH;
+ }
+ /* If we have `foo|while true; (( x++ )); done', and hit
+ * ^C, we have to stop the loop, too. */
+ if ((val & 0200) && inforeground == 1 &&
+ ((val & ~0200) == SIGINT || (val & ~0200) == SIGQUIT)) {
+ if (!errbrk_saved) {
+ errbrk_saved = 1;
+ prev_breaks = breaks;
+ prev_errflag = errflag;
+ }
+ breaks = loops;
+ errflag |= ERRFLAG_INT;
+ inerrflush();
+ }
+ } else {
+ attachtty(mypgrp);
+ /* check window size and adjust if necessary */
+ adjustwinsize(0);
+ }
+ }
+ } else if (list_pipe && (val & 0200) && inforeground == 1 &&
+ ((val & ~0200) == SIGINT || (val & ~0200) == SIGQUIT)) {
+ if (!errbrk_saved) {
+ errbrk_saved = 1;
+ prev_breaks = breaks;
+ prev_errflag = errflag;
+ }
+ breaks = loops;
+ errflag |= ERRFLAG_INT;
+ inerrflush();
+ }
+ 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)) {
+ /* This may be redundant with printjob() but note that inforeground
+ * is true here for STAT_CURSH jobs even when job != thisjob, most
+ * likely because thisjob = -1 from exec.c:execsimple() trickery.
+ * However, if we reset lastval here we break it for printjob().
+ */
+ storepipestats(jn, inforeground, 0);
+ }
+ if (!inforeground &&
+ (jn->stat & (STAT_SUBJOB | STAT_DONE)) == (STAT_SUBJOB | STAT_DONE)) {
+ int su;
+
+ if ((su = super_job(jn - jobtab)))
+ handle_sub(su, 0);
+ }
+ if ((jn->stat & (STAT_DONE | STAT_STOPPED)) == STAT_STOPPED) {
+ prevjob = curjob;
+ curjob = job;
+ }
+ if ((isset(NOTIFY) || job == thisjob) && (jn->stat & STAT_LOCKED)) {
+ if (printjob(jn, !!isset(LONGLISTJOBS), 0) &&
+ zleactive)
+ zleentry(ZLE_CMD_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 pretend that the shell got *
+ * the signal too. */
+ if (inforeground == 2 && 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 |= ERRFLAG_INT;
+ }
+ check_cursh_sig(sig);
+ }
+ }
+}
+
+/* set the previous job to something reasonable */
+
+/**/
+static void
+setprevjob(void)
+{
+ int i;
+
+ for (i = maxjob; i; i--)
+ if ((jobtab[i].stat & STAT_INUSE) && (jobtab[i].stat & STAT_STOPPED) &&
+ !(jobtab[i].stat & STAT_SUBJOB) && i != curjob && i != thisjob) {
+ prevjob = i;
+ return;
+ }
+
+ for (i = maxjob; i; i--)
+ if ((jobtab[i].stat & STAT_INUSE) && !(jobtab[i].stat & STAT_SUBJOB) &&
+ i != curjob && i != thisjob) {
+ prevjob = i;
+ return;
+ }
+
+ prevjob = -1;
+}
+
+/**/
+long
+get_clktck(void)
+{
+ static long clktck;
+
+#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
+
+ return clktck;
+}
+
+/**/
+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, child_times_t *ti, char *desc)
+{
+ char *s;
+ double elapsed_time, user_time, system_time;
+#ifdef HAVE_GETRUSAGE
+ double total_time;
+#endif
+ int percent, desclen;
+
+ if (!desc)
+ {
+ desc = "";
+ desclen = 0;
+ }
+ else
+ {
+ desc = dupstring(desc);
+ unmetafy(desc, &desclen);
+ }
+
+ /* go ahead and compute these, since almost every TIMEFMT will have them */
+ elapsed_time = real->tv_sec + real->tv_usec / 1000000.0;
+
+#ifdef HAVE_GETRUSAGE
+ user_time = ti->ru_utime.tv_sec + ti->ru_utime.tv_usec / 1000000.0;
+ system_time = ti->ru_stime.tv_sec + ti->ru_stime.tv_usec / 1000000.0;
+ total_time = user_time + system_time;
+ percent = 100.0 * total_time
+ / (real->tv_sec + real->tv_usec / 1000000.0);
+#else
+ {
+ long clktck = get_clktck();
+ 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);
+ }
+#endif
+
+ queue_signals();
+ if (!(s = getsparam("TIMEFMT")))
+ s = DEFAULT_TIMEFMT;
+ else
+ s = unmetafy(s, NULL);
+
+ 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 'm':
+ switch (*++s) {
+ case 'E':
+ fprintf(stderr, "%0.fms", elapsed_time * 1000.0);
+ break;
+ case 'U':
+ fprintf(stderr, "%0.fms", user_time * 1000.0);
+ break;
+ case 'S':
+ fprintf(stderr, "%0.fms", system_time * 1000.0);
+ break;
+ default:
+ fprintf(stderr, "%%m");
+ s--;
+ break;
+ }
+ break;
+ case 'u':
+ switch (*++s) {
+ case 'E':
+ fprintf(stderr, "%0.fus", elapsed_time * 1000000.0);
+ break;
+ case 'U':
+ fprintf(stderr, "%0.fus", user_time * 1000000.0);
+ break;
+ case 'S':
+ fprintf(stderr, "%0.fus", system_time * 1000000.0);
+ break;
+ default:
+ fprintf(stderr, "%%u");
+ s--;
+ break;
+ }
+ 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;
+#ifdef HAVE_STRUCT_RUSAGE_RU_NSWAP
+ case 'W':
+ fprintf(stderr, "%ld", ti->ru_nswap);
+ break;
+#endif
+#ifdef HAVE_STRUCT_RUSAGE_RU_IXRSS
+ case 'X':
+ fprintf(stderr, "%ld",
+ total_time ?
+ (long)(ti->ru_ixrss / total_time) :
+ (long)0);
+ break;
+#endif
+#ifdef HAVE_STRUCT_RUSAGE_RU_IDRSS
+ case 'D':
+ fprintf(stderr, "%ld",
+ total_time ?
+ (long) ((ti->ru_idrss
+#ifdef HAVE_STRUCT_RUSAGE_RU_ISRSS
+ + ti->ru_isrss
+#endif
+ ) / total_time) :
+ (long)0);
+ break;
+#endif
+#if defined(HAVE_STRUCT_RUSAGE_RU_IDRSS) || \
+ defined(HAVE_STRUCT_RUSAGE_RU_ISRSS) || \
+ defined(HAVE_STRUCT_RUSAGE_RU_IXRSS)
+ case 'K':
+ /* treat as D if X not available */
+ fprintf(stderr, "%ld",
+ total_time ?
+ (long) ((
+#ifdef HAVE_STRUCT_RUSAGE_RU_IXRSS
+ ti->ru_ixrss
+#else
+ 0
+#endif
+#ifdef HAVE_STRUCT_RUSAGE_RU_IDRSS
+ + ti->ru_idrss
+#endif
+#ifdef HAVE_STRUCT_RUSAGE_RU_ISRSS
+ + ti->ru_isrss
+#endif
+ ) / total_time) :
+ (long)0);
+ break;
+#endif
+#ifdef HAVE_STRUCT_RUSAGE_RU_MAXRSS
+ case 'M':
+ fprintf(stderr, "%ld", ti->ru_maxrss / 1024);
+ break;
+#endif
+#ifdef HAVE_STRUCT_RUSAGE_RU_MAJFLT
+ case 'F':
+ fprintf(stderr, "%ld", ti->ru_majflt);
+ break;
+#endif
+#ifdef HAVE_STRUCT_RUSAGE_RU_MINFLT
+ case 'R':
+ fprintf(stderr, "%ld", ti->ru_minflt);
+ break;
+#endif
+#ifdef HAVE_STRUCT_RUSAGE_RU_INBLOCK
+ case 'I':
+ fprintf(stderr, "%ld", ti->ru_inblock);
+ break;
+#endif
+#ifdef HAVE_STRUCT_RUSAGE_RU_OUBLOCK
+ case 'O':
+ fprintf(stderr, "%ld", ti->ru_oublock);
+ break;
+#endif
+#ifdef HAVE_STRUCT_RUSAGE_RU_MSGRCV
+ case 'r':
+ fprintf(stderr, "%ld", ti->ru_msgrcv);
+ break;
+#endif
+#ifdef HAVE_STRUCT_RUSAGE_RU_MSGSND
+ case 's':
+ fprintf(stderr, "%ld", ti->ru_msgsnd);
+ break;
+#endif
+#ifdef HAVE_STRUCT_RUSAGE_RU_NSIGNALS
+ case 'k':
+ fprintf(stderr, "%ld", ti->ru_nsignals);
+ break;
+#endif
+#ifdef HAVE_STRUCT_RUSAGE_RU_NVCSW
+ case 'w':
+ fprintf(stderr, "%ld", ti->ru_nvcsw);
+ break;
+#endif
+#ifdef HAVE_STRUCT_RUSAGE_RU_NIVCSW
+ case 'c':
+ fprintf(stderr, "%ld", ti->ru_nivcsw);
+ break;
+#endif
+ case 'J':
+ fwrite(desc, sizeof(char), desclen, stderr);
+ break;
+ case '%':
+ putc('%', stderr);
+ break;
+ case '\0':
+ s--;
+ break;
+ default:
+ fprintf(stderr, "%%%c", *s);
+ break;
+ } else
+ putc(*s, stderr);
+ unqueue_signals();
+ putc('\n', stderr);
+ fflush(stderr);
+}
+
+/**/
+static void
+dumptime(Job jn)
+{
+ Process pn;
+ struct timeval dtimeval;
+
+ 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)
+{
+ struct value vbuf;
+ Value v;
+ char *s = "REPORTTIME";
+ int save_errflag = errflag;
+ zlong reporttime = -1;
+#ifdef HAVE_GETRUSAGE
+ char *sm = "REPORTMEMORY";
+ zlong reportmemory = -1;
+#endif
+
+ /* if the time keyword was used */
+ if (j->stat & STAT_TIMED)
+ return 1;
+
+ queue_signals();
+ errflag = 0;
+ if ((v = getvalue(&vbuf, &s, 0)))
+ reporttime = getintvalue(v);
+#ifdef HAVE_GETRUSAGE
+ if ((v = getvalue(&vbuf, &sm, 0)))
+ reportmemory = getintvalue(v);
+#endif
+ errflag = save_errflag;
+ unqueue_signals();
+ if (reporttime < 0
+#ifdef HAVE_GETRUSAGE
+ && reportmemory < 0
+#endif
+ )
+ return 0;
+ /* can this ever happen? */
+ if (!j->procs)
+ return 0;
+ if (zleactive)
+ return 0;
+
+ if (reporttime >= 0)
+ {
+#ifdef HAVE_GETRUSAGE
+ reporttime -= j->procs->ti.ru_utime.tv_sec +
+ j->procs->ti.ru_stime.tv_sec;
+ if (j->procs->ti.ru_utime.tv_usec +
+ j->procs->ti.ru_stime.tv_usec >= 1000000)
+ reporttime--;
+ if (reporttime <= 0)
+ return 1;
+#else
+ {
+ clktck = get_clktck();
+ if ((j->procs->ti.ut + j->procs->ti.st) / clktck >= reporttime)
+ return 1;
+ }
+#endif
+ }
+
+#ifdef HAVE_GETRUSAGE
+ if (reportmemory >= 0 &&
+ j->procs->ti.ru_maxrss / 1024 > reportmemory)
+ return 1;
+#endif
+
+ return 0;
+}
+
+/* !(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
+ * synch = 3 means called synchronously from bg or fg
+ *
+ * Returns 1 if some output was done.
+ *
+ * The function also deletes the job if it was done, even it
+ * is not printed.
+ */
+
+/**/
+int
+printjob(Job jn, int lng, int synch)
+{
+ Process pn;
+ int job, len = 9, sig, sflag = 0, llen;
+ int conted = 0, lineleng = zterm_columns, skip = 0, doputnl = 0;
+ int doneprint = 0, skip_print = 0;
+ FILE *fout = (synch == 2 || !shout) ? stdout : shout;
+
+ if (synch > 1 && oldjobtab != NULL)
+ job = jn - oldjobtab;
+ else
+ job = jn - jobtab;
+ DPUTS3(job < 0 || job > (oldjobtab && synch > 1 ? oldmaxjob : maxjob),
+ "bogus job number, jn = %L, jobtab = %L, oldjobtab = %L",
+ (long)jn, (long)jobtab, (long)oldjobtab);
+
+ if (jn->stat & STAT_NOPRINT) {
+ skip_print = 1;
+ }
+
+ if (lng < 0) {
+ conted = 1;
+ lng = !!isset(LONGLISTJOBS);
+ }
+
+/* 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;
+ if (isset(PRINTEXITVALUE) && isset(SHINSTDIN)) {
+ sflag = 1;
+ skip_print = 0;
+ }
+ } 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;
+ skip_print = 0;
+ }
+ }
+ }
+
+ if (skip_print) {
+ if (jn->stat & STAT_DONE) {
+ /* This looks silly, but see update_job() */
+ if (synch <= 1)
+ storepipestats(jn, job == thisjob, job == thisjob);
+ if (should_report_time(jn))
+ dumptime(jn);
+ deletejob(jn, 0);
+ if (job == curjob) {
+ curjob = prevjob;
+ prevjob = job;
+ }
+ if (job == prevjob)
+ setprevjob();
+ }
+ return 0;
+ }
+
+ /*
+ * - Always print if called from jobs
+ * - Otherwise, require MONITOR option ("jobbing") and some
+ * change of state
+ * - also either the shell is interactive or this is synchronous.
+ */
+ if (synch == 2 ||
+ ((interact || synch) && jobbing &&
+ ((jn->stat & STAT_STOPPED) || sflag || job != thisjob))) {
+ int len2, fline = 1;
+ /* POSIX requires just the job text for bg and fg */
+ int plainfmt = (synch == 3) && isset(POSIXJOBS);
+ /* use special format for current job, except in `jobs' */
+ int thisfmt = job == thisjob && synch != 2;
+ Process qn;
+
+ if (!synch)
+ zleentry(ZLE_CMD_TRASH);
+ if (doputnl && !synch) {
+ doneprint = 1;
+ putc('\n', fout);
+ }
+ for (pn = jn->procs; pn;) {
+ len2 = (thisfmt ? 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;
+ }
+ doneprint = 1;
+ if (!plainfmt) {
+ if (!thisfmt || lng) {
+ if (fline)
+ fprintf(fout, "[%ld] %c ",
+ (long)job,
+ (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) {
+ char *txt = dupstring(pn->text);
+ int txtlen;
+ unmetafy(txt, &txtlen);
+ fwrite(txt, sizeof(char), txtlen, fout);
+ if (pn->next)
+ fputs(" | ", fout);
+ }
+ putc('\n', fout);
+ fline = 0;
+ }
+ fflush(fout);
+ } else if (doputnl && interact && !synch) {
+ doneprint = 1;
+ 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 &&
+ jn->pwd && strcmp(jn->pwd, pwd))) {
+ doneprint = 1;
+ fprintf(fout, "(pwd %s: ", (lng & 4) ? "" : "now");
+ fprintdir(((lng & 4) && jn->pwd) ? jn->pwd : pwd, fout);
+ fprintf(fout, ")\n");
+ fflush(fout);
+ }
+
+ /* delete job if done */
+
+ if (jn->stat & STAT_DONE) {
+ /* This looks silly, but see update_job() */
+ if (synch <= 1)
+ storepipestats(jn, job == thisjob, job == thisjob);
+ if (should_report_time(jn))
+ dumptime(jn);
+ deletejob(jn, 0);
+ if (job == curjob) {
+ curjob = prevjob;
+ prevjob = job;
+ }
+ if (job == prevjob)
+ setprevjob();
+ } else
+ jn->stat &= ~STAT_CHANGED;
+
+ return doneprint;
+}
+
+/* Add a file to be deleted or fd to be closed to the current job */
+
+/**/
+void
+addfilelist(const char *name, int fd)
+{
+ Jobfile jf = (Jobfile)zalloc(sizeof(struct jobfile));
+ LinkList ll = jobtab[thisjob].filelist;
+
+ if (!ll)
+ ll = jobtab[thisjob].filelist = znewlinklist();
+ if (name)
+ {
+ jf->u.name = ztrdup(name);
+ jf->is_fd = 0;
+ }
+ else
+ {
+ jf->u.fd = fd;
+ jf->is_fd = 1;
+ }
+ zaddlinknode(ll, jf);
+}
+
+/* Clean up pipes no longer needed associated with a job */
+
+/**/
+void
+pipecleanfilelist(LinkList filelist, int proc_subst_only)
+{
+ LinkNode node;
+
+ if (!filelist)
+ return;
+ node = firstnode(filelist);
+ while (node) {
+ Jobfile jf = (Jobfile)getdata(node);
+ if (jf->is_fd &&
+ (!proc_subst_only || fdtable[jf->u.fd] == FDT_PROC_SUBST)) {
+ LinkNode next = nextnode(node);
+ zclose(jf->u.fd);
+ (void)remnode(filelist, node);
+ zfree(jf, sizeof(*jf));
+ node = next;
+ } else
+ incnode(node);
+ }
+}
+
+/* Finished with list of files for a job */
+
+/**/
+void
+deletefilelist(LinkList file_list, int disowning)
+{
+ Jobfile jf;
+ if (file_list) {
+ while ((jf = (Jobfile)getlinknode(file_list))) {
+ if (jf->is_fd) {
+ if (!disowning)
+ zclose(jf->u.fd);
+ } else {
+ if (!disowning)
+ unlink(jf->u.name);
+ zsfree(jf->u.name);
+ }
+ zfree(jf, sizeof(*jf));
+ }
+ zfree(file_list, sizeof(struct linklist));
+ }
+}
+
+/**/
+void
+freejob(Job jn, int deleting)
+{
+ struct process *pn, *nx;
+
+ pn = jn->procs;
+ jn->procs = NULL;
+ for (; pn; pn = nx) {
+ nx = pn->next;
+ zfree(pn, sizeof(struct process));
+ }
+
+ pn = jn->auxprocs;
+ jn->auxprocs = NULL;
+ for (; pn; pn = nx) {
+ nx = pn->next;
+ zfree(pn, sizeof(struct process));
+ }
+
+ if (jn->ty)
+ zfree(jn->ty, sizeof(struct ttyinfo));
+ if (jn->pwd)
+ zsfree(jn->pwd);
+ jn->pwd = NULL;
+ if (jn->stat & STAT_WASSUPER) {
+ /* careful in case we shrink and move the job table */
+ int job = jn - jobtab;
+ if (deleting)
+ deletejob(jobtab + jn->other, 0);
+ else
+ freejob(jobtab + jn->other, 0);
+ jn = jobtab + job;
+ }
+ jn->gleader = jn->other = 0;
+ jn->stat = jn->stty_in_env = 0;
+ jn->filelist = NULL;
+ jn->ty = NULL;
+
+ /* Find the new highest job number. */
+ if (maxjob == jn - jobtab) {
+ while (maxjob && !(jobtab[maxjob].stat & STAT_INUSE))
+ maxjob--;
+ }
+}
+
+/*
+ * We are actually finished with this job, rather
+ * than freeing it to make space.
+ *
+ * If "disowning" is set, files associated with the job are not
+ * actually deleted --- and won't be as there is nothing left
+ * to clear up.
+ */
+
+/**/
+void
+deletejob(Job jn, int disowning)
+{
+ deletefilelist(jn->filelist, disowning);
+ if (jn->stat & STAT_ATTACH) {
+ attachtty(mypgrp);
+ adjustwinsize(0);
+ }
+ if (jn->stat & STAT_SUPERJOB) {
+ Job jno = jobtab + jn->other;
+ if (jno->stat & STAT_SUBJOB)
+ jno->stat |= STAT_SUBJOB_ORPHANED;
+ }
+
+ freejob(jn, 1);
+}
+
+/*
+ * Add a process to the current job.
+ * The third argument is 1 if we are adding a process which is not
+ * part of the main pipeline but an auxiliary process used for
+ * handling MULTIOS or process substitution. We will wait for it
+ * but not display job information about it.
+ */
+
+/**/
+void
+addproc(pid_t pid, char *text, int aux, struct timeval *bgtime)
+{
+ Process pn, *pnlist;
+
+ DPUTS(thisjob == -1, "No valid job in addproc.");
+ pn = (Process) zshcalloc(sizeof *pn);
+ pn->pid = pid;
+ if (text)
+ strcpy(pn->text, text);
+ else
+ *pn->text = '\0';
+ pn->status = SP_RUNNING;
+ pn->next = NULL;
+
+ if (!aux)
+ {
+ pn->bgtime = *bgtime;
+ /* 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 */
+ pnlist = &jobtab[thisjob].procs;
+ }
+ else
+ pnlist = &jobtab[thisjob].auxprocs;
+
+ if (*pnlist) {
+ Process n;
+
+ for (n = *pnlist; n->next; n = n->next);
+ n->next = pn;
+ } else {
+ /* first process for this job */
+ *pnlist = 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.
+ * wait_cmd indicates this is from the interactive wait command,
+ * in which case the behaviour is a little different: the command
+ * itself can be interrupted by a trapped signal.
+ */
+
+/**/
+int
+waitforpid(pid_t pid, int wait_cmd)
+{
+ int first = 1, q = queue_signal_level();
+
+ /* child_block() around this loop in case #ifndef WNOHANG */
+ dont_queue_signals();
+ child_block(); /* unblocked in signal_suspend() */
+ queue_traps(wait_cmd);
+
+ /* This function should never be called with a pid that is not a
+ * child of the current shell. Consequently, if kill(0, pid)
+ * fails here with ESRCH, the child has already been reaped. In
+ * the loop body, we expect this to happen in signal_suspend()
+ * via zhandler(), after which this test terminates the loop.
+ */
+ while (!errflag && (kill(pid, 0) >= 0 || errno != ESRCH)) {
+ if (first)
+ first = 0;
+ else if (!wait_cmd)
+ kill(pid, SIGCONT);
+
+ last_signal = -1;
+ signal_suspend(SIGCHLD, wait_cmd);
+ if (last_signal != SIGCHLD && wait_cmd && last_signal >= 0 &&
+ (sigtrapped[last_signal] & ZSIG_TRAPPED)) {
+ /* wait command interrupted, but no error: return */
+ restore_queue_signals(q);
+ return 128 + last_signal;
+ }
+ child_block();
+ }
+ unqueue_traps();
+ child_unblock();
+ restore_queue_signals(q);
+
+ return 0;
+}
+
+/*
+ * Wait for a job to finish.
+ * wait_cmd indicates this is from the wait builtin; see
+ * wait_cmd in waitforpid().
+ */
+
+/**/
+static int
+zwaitjob(int job, int wait_cmd)
+{
+ int q = queue_signal_level();
+ Job jn = jobtab + job;
+
+ child_block(); /* unblocked during signal_suspend() */
+ queue_traps(wait_cmd);
+ dont_queue_signals();
+ if (jn->procs || jn->auxprocs) { /* if any forks were done */
+ jn->stat |= STAT_LOCKED;
+ if (jn->stat & STAT_CHANGED)
+ printjob(jn, !!isset(LONGLISTJOBS), 1);
+ if (jn->filelist) {
+ /*
+ * The main shell is finished with any file descriptors used
+ * for process substitution associated with this job: close
+ * them to indicate to listeners there's no more input.
+ *
+ * Note we can't safely delete temporary files yet as these
+ * are directly visible to other processes. However,
+ * we can't deadlock on the fact that those still exist, so
+ * that's not a problem.
+ */
+ pipecleanfilelist(jn->filelist, 0);
+ }
+ while (!(errflag & ERRFLAG_ERROR) && jn->stat &&
+ !(jn->stat & STAT_DONE) &&
+ !(interact && (jn->stat & STAT_STOPPED))) {
+ signal_suspend(SIGCHLD, wait_cmd);
+ if (last_signal != SIGCHLD && wait_cmd && last_signal >= 0 &&
+ (sigtrapped[last_signal] & ZSIG_TRAPPED))
+ {
+ /* builtin wait interrupted by trapped signal */
+ restore_queue_signals(q);
+ return 128 + last_signal;
+ }
+ /* 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
+
+ When attempting to separate errors and interrupts, we
+ assumed because of the previous comment it would be OK
+ to remove ERRFLAG_ERROR and leave ERRFLAG_INT set, since
+ that's the one related to ^C. But that doesn't work.
+ There's something more here we don't understand. --pws
+
+ The change above to ignore ERRFLAG_INT in the loop test
+ solves a problem wherein child processes that ignore the
+ INT signal were never waited-for. Clearing the flag here
+ still seems the wrong thing, but perhaps ERRFLAG_INT
+ should be saved and restored around signal_suspend() to
+ prevent it being lost within a signal trap? --Bart
+
+ errflag = 0; */
+
+ if (subsh) {
+ killjb(jn, SIGCONT);
+ jn->stat &= ~STAT_STOPPED;
+ }
+ if (jn->stat & STAT_SUPERJOB)
+ if (handle_sub(jn - jobtab, 1))
+ break;
+ child_block();
+ }
+ } else {
+ deletejob(jn, 0);
+ pipestats[0] = lastval;
+ numpipestats = 1;
+ }
+ restore_queue_signals(q);
+ unqueue_traps();
+ child_unblock();
+
+ return 0;
+}
+
+/* wait for running job to finish */
+
+/**/
+void
+waitjobs(void)
+{
+ Job jn = jobtab + thisjob;
+ DPUTS(thisjob == -1, "No valid job in waitjobs.");
+
+ if (jn->procs || jn->auxprocs)
+ zwaitjob(thisjob, 0);
+ else {
+ deletejob(jn, 0);
+ pipestats[0] = lastval;
+ numpipestats = 1;
+ }
+ thisjob = -1;
+}
+
+/* clear job table when entering subshells */
+
+/**/
+mod_export void
+clearjobtab(int monitor)
+{
+ int i;
+
+ if (isset(POSIXJOBS))
+ oldmaxjob = 0;
+ for (i = 1; i <= maxjob; i++) {
+ /*
+ * See if there is a jobtable worth saving.
+ * We never free the saved version; it only happens
+ * once for each subshell of a shell with job control,
+ * so doesn't create a leak.
+ */
+ if (monitor && !isset(POSIXJOBS) && jobtab[i].stat)
+ oldmaxjob = i+1;
+ else if (jobtab[i].stat & STAT_INUSE)
+ freejob(jobtab + i, 0);
+ }
+
+ if (monitor && oldmaxjob) {
+ int sz = oldmaxjob * sizeof(struct job);
+ if (oldjobtab)
+ free(oldjobtab);
+ oldjobtab = (struct job *)zalloc(sz);
+ memcpy(oldjobtab, jobtab, sz);
+
+ /* Don't report any job we're part of */
+ if (thisjob != -1 && thisjob < oldmaxjob)
+ memset(oldjobtab+thisjob, 0, sizeof(struct job));
+ }
+
+ memset(jobtab, 0, jobtabsize * sizeof(struct job)); /* zero out table */
+ maxjob = 0;
+
+ /*
+ * Although we don't have job control in subshells, we
+ * sometimes needs control structures for other purposes such
+ * as multios. Grab a job for this purpose; any will do
+ * since we've freed them all up (so there's no question
+ * of problems with the job table size here).
+ */
+ thisjob = initjob();
+}
+
+static int initnewjob(int i)
+{
+ jobtab[i].stat = STAT_INUSE;
+ if (jobtab[i].pwd) {
+ zsfree(jobtab[i].pwd);
+ jobtab[i].pwd = NULL;
+ }
+ jobtab[i].gleader = 0;
+
+ if (i > maxjob)
+ maxjob = i;
+
+ return i;
+}
+
+/* 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)
+ return initnewjob(i);
+ if (maxjob + 1 < jobtabsize)
+ return initnewjob(maxjob+1);
+
+ if (expandjobtab())
+ return initnewjob(i);
+
+ zerr("job table full or recursion limit exceeded");
+ return -1;
+}
+
+/**/
+void
+setjobpwd(void)
+{
+ int i;
+
+ for (i = 1; i <= maxjob; i++)
+ if (jobtab[i].stat && !jobtab[i].pwd)
+ jobtab[i].pwd = ztrdup(pwd);
+}
+
+/* print pids for & */
+
+/**/
+void
+spawnjob(void)
+{
+ Process pn;
+
+ DPUTS(thisjob == -1, "No valid job in spawnjob.");
+ /* 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 (jobbing && jobtab[thisjob].procs) {
+ FILE *fout = shout ? shout : stdout;
+ fprintf(fout, "[%d]", thisjob);
+ for (pn = jobtab[thisjob].procs; pn; pn = pn->next)
+ fprintf(fout, " %ld", (long) pn->pid);
+ fprintf(fout, "\n");
+ fflush(fout);
+ }
+ }
+ if (!hasprocs(thisjob))
+ deletejob(jobtab + thisjob, 0);
+ else {
+ jobtab[thisjob].stat |= STAT_LOCKED;
+ pipecleanfilelist(jobtab[thisjob].filelist, 0);
+ }
+ thisjob = -1;
+}
+
+/**/
+void
+shelltime(void)
+{
+ struct timezone dummy_tz;
+ struct timeval dtimeval, now;
+ child_times_t ti;
+#ifndef HAVE_GETRUSAGE
+ struct tms buf;
+#endif
+
+ gettimeofday(&now, &dummy_tz);
+
+#ifdef HAVE_GETRUSAGE
+ getrusage(RUSAGE_SELF, &ti);
+#else
+ times(&buf);
+
+ ti.ut = buf.tms_utime;
+ ti.st = buf.tms_stime;
+#endif
+ printtime(dtime(&dtimeval, &shtimer, &now), &ti, "shell");
+
+#ifdef HAVE_GETRUSAGE
+ getrusage(RUSAGE_CHILDREN, &ti);
+#else
+ ti.ut = buf.tms_cutime;
+ ti.st = buf.tms_cstime;
+#endif
+ printtime(&dtimeval, &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, !!isset(LONGLISTJOBS), 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. */
+
+/**/
+mod_export int
+getjob(const char *s, const char *prog)
+{
+ int jobnum, returnval, mymaxjob;
+ Job myjobtab;
+
+ if (oldjobtab) {
+ myjobtab = oldjobtab;
+ mymaxjob = oldmaxjob;
+ } else {
+ myjobtab= jobtab;
+ mymaxjob = maxjob;
+ }
+
+ /* 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) {
+ if (prog)
+ zwarnnam(prog, "no current job");
+ returnval = -1;
+ goto done;
+ }
+ returnval = curjob;
+ goto done;
+ }
+ /* "%-" represents the previous job */
+ if (*s == '-') {
+ if (prevjob == -1) {
+ if (prog)
+ zwarnnam(prog, "no previous job");
+ 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 <= mymaxjob && myjobtab[jobnum].stat &&
+ !(myjobtab[jobnum].stat & STAT_SUBJOB) &&
+ /*
+ * If running jobs in a subshell, we are allowed to
+ * refer to the "current" job (it's not really the
+ * current job in the subshell). It's possible we
+ * should reset thisjob to -1 on entering the subshell.
+ */
+ (myjobtab == oldjobtab || jobnum != thisjob)) {
+ returnval = jobnum;
+ goto done;
+ }
+ if (prog)
+ zwarnnam(prog, "%%%s: no such job", s);
+ returnval = -1;
+ goto done;
+ }
+ /* "%?" introduces a search string */
+ if (*s == '?') {
+ struct process *pn;
+
+ for (jobnum = mymaxjob; jobnum >= 0; jobnum--)
+ if (myjobtab[jobnum].stat &&
+ !(myjobtab[jobnum].stat & STAT_SUBJOB) &&
+ jobnum != thisjob)
+ for (pn = myjobtab[jobnum].procs; pn; pn = pn->next)
+ if (strstr(pn->text, s + 1)) {
+ returnval = jobnum;
+ goto done;
+ }
+ if (prog)
+ zwarnnam(prog, "job not found: %s", s);
+ 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);
+ returnval = -1;
+ done:
+ return returnval;
+}
+
+#ifndef HAVE_SETPROCTITLE
+/* 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;
+#endif
+
+
+/* Initialise job handling. */
+
+/**/
+void
+init_jobs(char **argv, char **envp)
+{
+#ifndef HAVE_SETPROCTITLE
+ char *p, *q;
+#endif
+ size_t init_bytes = MAXJOBS_ALLOC*sizeof(struct job);
+
+ /*
+ * Initialise the job table. If this fails, we're in trouble.
+ */
+ jobtab = (struct job *)zalloc(init_bytes);
+ if (!jobtab) {
+ zerr("failed to allocate job table, aborting.");
+ exit(1);
+ }
+ jobtabsize = MAXJOBS_ALLOC;
+ memset(jobtab, 0, init_bytes);
+
+#ifndef HAVE_SETPROCTITLE
+ /*
+ * 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.
+ */
+ hackzero = *argv;
+ p = strchr(hackzero, 0);
+ while(*++argv) {
+ q = *argv;
+ if(q != p+1)
+ goto done;
+ p = strchr(q, 0);
+ }
+#if !defined(HAVE_PUTENV) && !defined(USE_SET_UNSET_ENV)
+ for(; *envp; envp++) {
+ q = *envp;
+ if(q != p+1)
+ goto done;
+ p = strchr(q, 0);
+ }
+#endif
+ done:
+ hackspace = p - hackzero;
+#endif
+}
+
+
+/*
+ * We have run out of space in the job table.
+ * Expand it by an additional MAXJOBS_ALLOC slots.
+ */
+
+/*
+ * An arbitrary limit on the absolute maximum size of the job table.
+ * This prevents us taking over the entire universe.
+ * Ought to be a multiple of MAXJOBS_ALLOC, but doesn't need to be.
+ */
+#define MAX_MAXJOBS 1000
+
+/**/
+int
+expandjobtab(void)
+{
+ int newsize = jobtabsize + MAXJOBS_ALLOC;
+ struct job *newjobtab;
+
+ if (newsize > MAX_MAXJOBS)
+ return 0;
+
+ newjobtab = (struct job *)zrealloc(jobtab, newsize * sizeof(struct job));
+ if (!newjobtab)
+ return 0;
+
+ /*
+ * Clear the new section of the table; this is necessary for
+ * the jobs to appear unused.
+ */
+ memset(newjobtab + jobtabsize, 0, MAXJOBS_ALLOC * sizeof(struct job));
+
+ jobtab = newjobtab;
+ jobtabsize = newsize;
+
+ return 1;
+}
+
+
+/*
+ * See if we can reduce the job table. We can if we go over
+ * a MAXJOBS_ALLOC boundary. However, we leave a boundary,
+ * currently 20 jobs, so that we have a place for immediate
+ * expansion and don't play ping pong with the job table size.
+ */
+
+/**/
+void
+maybeshrinkjobtab(void)
+{
+ int jobbound;
+
+ queue_signals();
+ jobbound = maxjob + MAXJOBS_ALLOC - (maxjob % MAXJOBS_ALLOC);
+ if (jobbound < jobtabsize && jobbound > maxjob + 20) {
+ struct job *newjobtab;
+
+ /* Hope this can't fail, but anyway... */
+ newjobtab = (struct job *)zrealloc(jobtab,
+ jobbound*sizeof(struct job));
+
+ if (newjobtab) {
+ jobtab = newjobtab;
+ jobtabsize = jobbound;
+ }
+ }
+ unqueue_signals();
+}
+
+/*
+ * Definitions for the background process stuff recorded below.
+ * This would be more efficient as a hash, but
+ * - that's quite heavyweight for something not needed very often
+ * - we need some kind of ordering as POSIX allows us to limit
+ * the size of the list to the value of _SC_CHILD_MAX and clearly
+ * we want to clear the oldest first
+ * - cases with a long list of background jobs where the user doesn't
+ * wait for a large number, and then does wait for one (the only
+ * inefficient case) are rare
+ * - in the context of waiting for an external process, looping
+ * over a list isn't so very inefficient.
+ * Enough excuses already.
+ */
+
+/* Data in the link list, a key (process ID) / value (exit status) pair. */
+struct bgstatus {
+ pid_t pid;
+ int status;
+};
+typedef struct bgstatus *Bgstatus;
+/* The list of those entries */
+static LinkList bgstatus_list;
+/* Count of entries. Reaches value of _SC_CHILD_MAX and stops. */
+static long bgstatus_count;
+
+/*
+ * Remove and free a bgstatus entry.
+ */
+static void rembgstatus(LinkNode node)
+{
+ zfree(remnode(bgstatus_list, node), sizeof(struct bgstatus));
+ bgstatus_count--;
+}
+
+/*
+ * Record the status of a background process that exited so we
+ * can execute the builtin wait for it.
+ *
+ * We can't execute the wait builtin for something that exited in the
+ * foreground as it's not visible to the user, so don't bother recording.
+ */
+
+/**/
+void
+addbgstatus(pid_t pid, int status)
+{
+ static long child_max;
+ Bgstatus bgstatus_entry;
+
+ if (!child_max) {
+#ifdef _SC_CHILD_MAX
+ child_max = sysconf(_SC_CHILD_MAX);
+ if (!child_max) /* paranoia */
+#endif
+ {
+ /* Be inventive */
+ child_max = 1024L;
+ }
+ }
+
+ if (!bgstatus_list) {
+ bgstatus_list = znewlinklist();
+ /*
+ * We're not always robust about memory failures, but
+ * this is pretty deep in the shell basics to be failing owing
+ * to memory, and a failure to wait is reported loudly, so test
+ * and fail silently here.
+ */
+ if (!bgstatus_list)
+ return;
+ }
+ if (bgstatus_count == child_max) {
+ /* Overflow. List is in order, remove first */
+ rembgstatus(firstnode(bgstatus_list));
+ }
+ bgstatus_entry = (Bgstatus)zalloc(sizeof(*bgstatus_entry));
+ if (!bgstatus_entry) {
+ /* See note above */
+ return;
+ }
+ bgstatus_entry->pid = pid;
+ bgstatus_entry->status = status;
+ if (!zaddlinknode(bgstatus_list, bgstatus_entry)) {
+ zfree(bgstatus_entry, sizeof(*bgstatus_entry));
+ return;
+ }
+ bgstatus_count++;
+}
+
+/*
+ * See if pid has a recorded exit status.
+ * Note we make no guarantee that the PIDs haven't wrapped, so this
+ * may not be the right process.
+ *
+ * This is only used by wait, which must only work on each
+ * pid once, so we need to remove the entry if we find it.
+ */
+
+static int getbgstatus(pid_t pid)
+{
+ LinkNode node;
+ Bgstatus bgstatus_entry;
+
+ if (!bgstatus_list)
+ return -1;
+ for (node = firstnode(bgstatus_list); node; incnode(node)) {
+ bgstatus_entry = (Bgstatus)getdata(node);
+ if (bgstatus_entry->pid == pid) {
+ int status = bgstatus_entry->status;
+ rembgstatus(node);
+ return status;
+ }
+ }
+ return -1;
+}
+
+/* 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, Options ops, int func)
+{
+ int job, lng, firstjob = -1, retval = 0, ofunc = func;
+
+ if (OPT_ISSET(ops,'Z')) {
+ int len;
+
+ if(isset(RESTRICTED)) {
+ zwarnnam(name, "-Z is restricted");
+ return 1;
+ }
+ if(!argv[0] || argv[1]) {
+ zwarnnam(name, "-Z requires one argument");
+ return 1;
+ }
+ queue_signals();
+ unmetafy(*argv, &len);
+#ifdef HAVE_SETPROCTITLE
+ setproctitle("%s", *argv);
+#else
+ if(len > hackspace)
+ len = hackspace;
+ memcpy(hackzero, *argv, len);
+ memset(hackzero + len, 0, hackspace - len);
+#endif
+ unqueue_signals();
+ return 0;
+ }
+
+ if (func == BIN_JOBS) {
+ lng = (OPT_ISSET(ops,'l')) ? 1 : (OPT_ISSET(ops,'p')) ? 2 : 0;
+ if (OPT_ISSET(ops,'d'))
+ lng |= 4;
+ } else {
+ lng = !!isset(LONGLISTJOBS);
+ }
+
+ 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.");
+ return 1;
+ }
+
+ queue_signals();
+ /*
+ * In case any processes changed state recently, wait for them.
+ * This updates stopped processes (but we should have been
+ * signalled about those, up to inevitable races), and also
+ * continued processes if that feature is available.
+ */
+ wait_for_processes();
+
+ /* If necessary, update job table. */
+ if (unset(NOTIFY))
+ scanjobs();
+
+ if (func != BIN_JOBS || isset(MONITOR) || !oldmaxjob)
+ 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");
+ unqueue_signals();
+ return 1;
+ }
+ firstjob = curjob;
+ } else if (func == BIN_JOBS) {
+ /* List jobs. */
+ struct job *jobptr;
+ int curmaxjob, ignorejob;
+ if (unset(MONITOR) && oldmaxjob) {
+ jobptr = oldjobtab;
+ curmaxjob = oldmaxjob ? oldmaxjob - 1 : 0;
+ ignorejob = 0;
+ } else {
+ jobptr = jobtab;
+ curmaxjob = maxjob;
+ ignorejob = thisjob;
+ }
+ for (job = 0; job <= curmaxjob; job++, jobptr++)
+ if (job != ignorejob && jobptr->stat) {
+ if ((!OPT_ISSET(ops,'r') && !OPT_ISSET(ops,'s')) ||
+ (OPT_ISSET(ops,'r') && OPT_ISSET(ops,'s')) ||
+ (OPT_ISSET(ops,'r') &&
+ !(jobptr->stat & STAT_STOPPED)) ||
+ (OPT_ISSET(ops,'s') && jobptr->stat & STAT_STOPPED))
+ printjob(jobptr, lng, 2);
+ }
+ unqueue_signals();
+ return 0;
+ } else { /* Must be BIN_WAIT, so wait for all jobs */
+ for (job = 0; job <= maxjob; job++)
+ if (job != thisjob && jobtab[job].stat &&
+ !(jobtab[job].stat & STAT_NOPRINT))
+ retval = zwaitjob(job, 1);
+ unqueue_signals();
+ return retval;
+ }
+ }
+
+ /* 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, jstat;
+
+ func = ofunc;
+
+ if (func == BIN_WAIT && isanum(*argv)) {
+ /* wait can take a pid; the others can't. */
+ pid_t pid = (long)atoi(*argv);
+ Job j;
+ Process p;
+
+ if (findproc(pid, &j, &p, 0)) {
+ if (j->stat & STAT_STOPPED) {
+ retval = (killjb(j, SIGCONT) != 0);
+ if (retval == 0)
+ makerunning(j);
+ }
+ if (retval == 0) {
+ /*
+ * returns 0 for normal exit, else signal+128
+ * in which case we should return that status.
+ */
+ retval = waitforpid(pid, 1);
+ }
+ if (retval == 0) {
+ if ((retval = getbgstatus(pid)) < 0) {
+ retval = lastval2;
+ }
+ }
+ } else if ((retval = getbgstatus(pid)) < 0) {
+ zwarnnam(name, "pid %d is not a child of this shell", pid);
+ /* presumably lastval2 doesn't tell us a heck of a lot? */
+ retval = 1;
+ }
+ thisjob = ocj;
+ continue;
+ }
+ if (func != BIN_JOBS && oldjobtab != NULL) {
+ zwarnnam(name, "can't manipulate jobs in subshell");
+ unqueue_signals();
+ return 1;
+ }
+ /* 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;
+ }
+ jstat = oldjobtab ? oldjobtab[job].stat : jobtab[job].stat;
+ if (!(jstat & STAT_INUSE) ||
+ (jstat & STAT_NOPRINT)) {
+ zwarnnam(name, "%s: no such job", *argv);
+ unqueue_signals();
+ return 1;
+ }
+ /* If AUTO_CONTINUE is set (automatically make stopped jobs running
+ * on disown), we actually do a bg and then delete the job table entry. */
+
+ if (isset(AUTOCONTINUE) && func == BIN_DISOWN &&
+ jstat & STAT_STOPPED)
+ func = BIN_BG;
+
+ /* 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;
+ jobtab[job].stat &= ~STAT_CURSH;
+ }
+ if ((stopped = (jobtab[job].stat & STAT_STOPPED))) {
+ makerunning(jobtab + job);
+ if (func == BIN_BG) {
+ /* Set $! to indicate this was backgrounded */
+ Process pn = jobtab[job].procs;
+ for (;;) {
+ Process next = pn->next;
+ if (!next) {
+ lastpid = (zlong) pn->pid;
+ break;
+ }
+ pn = next;
+ }
+ }
+ } else if (func == BIN_BG) {
+ /* Silly to bg a job already running. */
+ zwarnnam(name, "job already in background");
+ thisjob = ocj;
+ unqueue_signals();
+ 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 : lng, 3);
+ if (func != BIN_BG) { /* fg or wait */
+ if (jobtab[job].pwd && strcmp(jobtab[job].pwd, pwd)) {
+ FILE *fout = (func == BIN_JOBS || !shout) ? stdout : shout;
+ fprintf(fout, "(pwd : ");
+ fprintdir(jobtab[job].pwd, fout);
+ fprintf(fout, ")\n");
+ fflush(fout);
+ }
+ if (func != BIN_WAIT) { /* fg */
+ thisjob = job;
+ if ((jobtab[job].stat & STAT_SUPERJOB) &&
+ ((!jobtab[job].procs->next ||
+ (jobtab[job].stat & STAT_SUBLEADER) ||
+ killpg(jobtab[job].gleader, 0) == -1)) &&
+ jobtab[jobtab[job].other].gleader)
+ attachtty(jobtab[jobtab[job].other].gleader);
+ else
+ 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)
+ {
+ retval = zwaitjob(job, 1);
+ if (!retval)
+ retval = lastval2;
+ }
+ else if (func != BIN_BG) {
+ /*
+ * HERE: there used not to be an "else" above. How
+ * could it be right to wait for the foreground job
+ * when we've just been told to wait for another
+ * job (and done it)?
+ */
+ waitjobs();
+ retval = lastval2;
+ } else if (ofunc == BIN_DISOWN)
+ deletejob(jobtab + job, 1);
+ break;
+ case BIN_JOBS:
+ printjob(job + (oldjobtab ? oldjobtab : jobtab), lng, 2);
+ break;
+ case BIN_DISOWN:
+ if (jobtab[job].stat & STAT_SUPERJOB) {
+ jobtab[job].stat |= STAT_DISOWN;
+ continue;
+ }
+ if (jobtab[job].stat & STAT_STOPPED) {
+ char buf[20], *pids = "";
+
+ if (jobtab[job].stat & STAT_SUPERJOB) {
+ Process pn;
+
+ for (pn = jobtab[jobtab[job].other].procs; pn; pn = pn->next) {
+ sprintf(buf, " -%d", pn->pid);
+ pids = dyncat(pids, buf);
+ }
+ for (pn = jobtab[job].procs; pn->next; pn = pn->next) {
+ sprintf(buf, " %d", pn->pid);
+ pids = dyncat(pids, buf);
+ }
+ if (!jobtab[jobtab[job].other].procs && pn) {
+ sprintf(buf, " %d", pn->pid);
+ pids = dyncat(pids, buf);
+ }
+ } else {
+ sprintf(buf, " -%d", jobtab[job].gleader);
+ pids = buf;
+ }
+ zwarnnam(name,
+#ifdef USE_SUSPENDED
+ "warning: job is suspended, use `kill -CONT%s' to resume",
+#else
+ "warning: job is stopped, use `kill -CONT%s' to resume",
+#endif
+ pids);
+ }
+ deletejob(jobtab + job, 1);
+ break;
+ }
+ thisjob = ocj;
+ }
+ unqueue_signals();
+ return retval;
+}
+
+static const struct {
+ const char *name;
+ int num;
+} alt_sigs[] = {
+#if defined(SIGCHLD) && defined(SIGCLD)
+#if SIGCHLD == SIGCLD
+ { "CLD", SIGCLD },
+#endif
+#endif
+#if defined(SIGPOLL) && defined(SIGIO)
+#if SIGPOLL == SIGIO
+ { "IO", SIGIO },
+#endif
+#endif
+#if !defined(SIGERR)
+ /*
+ * If SIGERR is not defined by the operating system, use it
+ * as an alias for SIGZERR.
+ */
+ { "ERR", SIGZERR },
+#endif
+ { NULL, 0 }
+};
+
+/* 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, UNUSED(Options ops), UNUSED(int func))
+{
+ int sig = SIGTERM;
+ int returnval = 0;
+
+ /* check for, and interpret, a signal specifier */
+ if (*argv && **argv == '-') {
+ if (idigit((*argv)[1])) {
+ char *endp;
+ /* signal specified by number */
+ sig = zstrtol(*argv + 1, &endp, 10);
+ if (*endp) {
+ zwarnnam(nam, "invalid signal number: %s", *argv);
+ return 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) {
+ if (!strncmp(signame, "SIG", 3))
+ signame += 3;
+ for (sig = 1; sig <= SIGCOUNT; sig++)
+ if (!strcasecmp(sigs[sig], signame))
+ break;
+ if (sig > SIGCOUNT) {
+ int i;
+
+ for (i = 0; alt_sigs[i].name; i++)
+ if (!strcasecmp(alt_sigs[i].name, signame))
+ {
+ sig = alt_sigs[i].num;
+ break;
+ }
+ }
+ if (sig > SIGCOUNT) {
+ zwarnnam(nam, "unknown signal: SIG%s",
+ signame);
+ returnval++;
+ } else
+ printf("%d\n", sig);
+ } else {
+ if (*signame) {
+ zwarnnam(nam, "unknown signal: SIG%s",
+ signame);
+ 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] == 'n' && (*argv)[2] == '\0') {
+ char *endp;
+
+ if (!*++argv) {
+ zwarnnam(nam, "-n: argument expected");
+ return 1;
+ }
+ sig = zstrtol(*argv, &endp, 10);
+ if (*endp) {
+ zwarnnam(nam, "invalid signal number: %s", *argv);
+ return 1;
+ }
+ } else {
+ if (!((*argv)[1] == 's' && (*argv)[2] == '\0'))
+ signame = *argv + 1;
+ else if (!(*++argv)) {
+ zwarnnam(nam, "-s: argument expected");
+ return 1;
+ } else
+ signame = *argv;
+ if (!*signame) {
+ zwarnnam(nam, "-: signal name expected");
+ return 1;
+ }
+ signame = casemodify(signame, CASMOD_UPPER);
+ if (!strncmp(signame, "SIG", 3))
+ signame+=3;
+
+ /* check for signal matching specified name */
+ for (sig = 1; sig <= SIGCOUNT; sig++)
+ if (!strcmp(*(sigs + sig), signame))
+ break;
+ if (*signame == '0' && !signame[1])
+ sig = 0;
+ if (sig > SIGCOUNT) {
+ int i;
+
+ for (i = 0; alt_sigs[i].name; i++)
+ if (!strcmp(alt_sigs[i].name, signame))
+ {
+ sig = alt_sigs[i].num;
+ break;
+ }
+ }
+ if (sig > SIGCOUNT) {
+ zwarnnam(nam, "unknown signal: SIG%s", signame);
+ zwarnnam(nam, "type kill -l for a list of signals");
+ return 1;
+ }
+ }
+ }
+ argv++;
+ }
+
+ /* Discard the standard "-" and "--" option breaks */
+ if (*argv && (*argv)[0] == '-' && (!(*argv)[1] || (*argv)[1] == '-'))
+ argv++;
+
+ if (!*argv) {
+ zwarnnam(nam, "not enough arguments");
+ return 1;
+ }
+
+ queue_signals();
+ 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) {
+#ifndef WIFCONTINUED
+ /* With WIFCONTINUED we find this out properly */
+ if (sig == SIGCONT)
+ makerunning(jobtab + p);
+#endif
+ 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);
+ returnval++;
+ } else {
+ int pid = atoi(*argv);
+ if (kill(pid, sig) == -1) {
+ zwarnnam("kill", "kill %s failed: %e", *argv, errno);
+ returnval++;
+ }
+#ifndef WIFCONTINUED
+ else if (sig == SIGCONT) {
+ Job jn;
+ Process pn;
+ /* With WIFCONTINUED we find this out properly */
+ if (findproc(pid, &jn, &pn, 0)) {
+ if (WIFSTOPPED(pn->status))
+ pn->status = SP_RUNNING;
+ }
+ }
+#endif
+ }
+ }
+ unqueue_signals();
+
+ return returnval < 126 ? returnval : 1;
+}
+/* Get a signal number from a string */
+
+/**/
+mod_export int
+getsignum(const 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 */
+ if (!strncmp(s, "SIG", 3))
+ s += 3;
+
+ for (i = 0; i < VSIGCOUNT; i++)
+ if (!strcmp(s, sigs[i]))
+ return i;
+
+ for (i = 0; alt_sigs[i].name; i++)
+ {
+ if (!strcmp(s, alt_sigs[i].name))
+ return alt_sigs[i].num;
+ }
+
+ /* no matching signal */
+ return -1;
+}
+
+/* Get the name for a signal. */
+
+/**/
+mod_export const char *
+getsigname(int sig)
+{
+ if (sigtrapped[sig] & ZSIG_ALIAS)
+ {
+ int i;
+ for (i = 0; alt_sigs[i].name; i++)
+ if (sig == alt_sigs[i].num)
+ return alt_sigs[i].name;
+ }
+ else
+ return sigs[sig];
+
+ /* shouldn't reach here */
+#ifdef DEBUG
+ dputs("Bad alias flag for signal");
+#endif
+ return "";
+}
+
+
+/* Get the function node for a trap, taking care about alternative names */
+/**/
+HashNode
+gettrapnode(int sig, int ignoredisable)
+{
+ char fname[20];
+ HashNode hn;
+ HashNode (*getptr)(HashTable ht, const char *name);
+ int i;
+ if (ignoredisable)
+ getptr = shfunctab->getnode2;
+ else
+ getptr = shfunctab->getnode;
+
+ sprintf(fname, "TRAP%s", sigs[sig]);
+ if ((hn = getptr(shfunctab, fname)))
+ return hn;
+
+ for (i = 0; alt_sigs[i].name; i++) {
+ if (alt_sigs[i].num == sig) {
+ sprintf(fname, "TRAP%s", alt_sigs[i].name);
+ if ((hn = getptr(shfunctab, fname)))
+ return hn;
+ }
+ }
+
+ return NULL;
+}
+
+/* Remove a TRAP function under any name for the signal */
+
+/**/
+void
+removetrapnode(int sig)
+{
+ HashNode hn = gettrapnode(sig, 1);
+ if (hn) {
+ shfunctab->removenode(shfunctab, hn->nam);
+ shfunctab->freenode(hn);
+ }
+}
+
+/* Suspend this shell */
+
+/**/
+int
+bin_suspend(char *name, UNUSED(char **argv), Options ops, UNUSED(int func))
+{
+ /* won't suspend a login shell, unless forced */
+ if (islogin && !OPT_ISSET(ops,'f')) {
+ zwarnnam(name, "can't suspend login shell");
+ return 1;
+ }
+ if (jobbing) {
+ /* stop ignoring signals */
+ signal_default(SIGTTIN);
+ signal_default(SIGTSTP);
+ signal_default(SIGTTOU);
+
+ /* Move ourselves back to the process group we came from */
+ release_pgrp();
+ }
+
+ /* suspend ourselves with a SIGTSTP */
+ killpg(origpgrp, SIGTSTP);
+
+ if (jobbing) {
+ acquire_pgrp();
+ /* restore signal handling */
+ signal_ignore(SIGTTOU);
+ signal_ignore(SIGTSTP);
+ signal_ignore(SIGTTIN);
+ }
+ return 0;
+}
+
+/* find a job named s */
+
+/**/
+int
+findjobnam(const char *s)
+{
+ int jobnum;
+
+ for (jobnum = maxjob; jobnum >= 0; jobnum--)
+ if (!(jobtab[jobnum].stat & (STAT_SUBJOB | STAT_NOPRINT)) &&
+ jobtab[jobnum].stat && jobtab[jobnum].procs && jobnum != thisjob &&
+ jobtab[jobnum].procs->text[0] && strpfx(s, jobtab[jobnum].procs->text))
+ return jobnum;
+ return -1;
+}
+
+
+/* make sure we are a process group leader by creating a new process
+ group if necessary */
+
+/**/
+void
+acquire_pgrp(void)
+{
+ long ttpgrp;
+ sigset_t blockset, oldset;
+
+ if ((mypgrp = GETPGRP()) >= 0) {
+ long lastpgrp = mypgrp;
+ sigemptyset(&blockset);
+ sigaddset(&blockset, SIGTTIN);
+ sigaddset(&blockset, SIGTTOU);
+ sigaddset(&blockset, SIGTSTP);
+ oldset = signal_block(blockset);
+ while ((ttpgrp = gettygrp()) != -1 && ttpgrp != mypgrp) {
+ mypgrp = GETPGRP();
+ if (mypgrp == mypid) {
+ if (!interact)
+ break; /* attachtty() will be a no-op, give up */
+ signal_setmask(oldset);
+ attachtty(mypgrp); /* Might generate SIGT* */
+ signal_block(blockset);
+ }
+ if (mypgrp == gettygrp())
+ break;
+ signal_setmask(oldset);
+ if (read(0, NULL, 0) != 0) {} /* Might generate SIGT* */
+ signal_block(blockset);
+ mypgrp = GETPGRP();
+ if (mypgrp == lastpgrp && !interact)
+ break; /* Unlikely that pgrp will ever change */
+ lastpgrp = mypgrp;
+ }
+ if (mypgrp != mypid) {
+ if (setpgrp(0, 0) == 0) {
+ mypgrp = mypid;
+ attachtty(mypgrp);
+ } else
+ opts[MONITOR] = 0;
+ }
+ signal_setmask(oldset);
+ } else
+ opts[MONITOR] = 0;
+}
+
+/* revert back to the process group we came from (before acquire_pgrp) */
+
+/**/
+void
+release_pgrp(void)
+{
+ if (origpgrp != mypgrp) {
+ /* in linux pid namespaces, origpgrp may never have been set */
+ if (origpgrp) {
+ attachtty(origpgrp);
+ setpgrp(0, origpgrp);
+ }
+ mypgrp = origpgrp;
+ }
+}
diff --git a/dotfiles/system/.zsh/modules/Src/lex.c b/dotfiles/system/.zsh/modules/Src/lex.c
new file mode 100644
index 0000000..44ad880
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/lex.c
@@ -0,0 +1,2203 @@
+/*
+ * 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"
+
+#define LEX_HEAP_SIZE (32)
+
+/* tokens */
+
+/**/
+mod_export char ztokens[] = "#$^*(())$=|{}[]`<>>?~`,-!'\"\\\\";
+
+/* parts of the current token */
+
+/**/
+char *zshlextext;
+/**/
+mod_export char *tokstr;
+/**/
+mod_export enum lextok tok;
+/**/
+mod_export int tokfd;
+
+/*
+ * Line number at which the first character of a token was found.
+ * We always set this in gettok(), which is always called from
+ * zshlex() unless we have reached an error. So it is always
+ * valid when parsing. It is not useful during execution
+ * of the parsed structure.
+ */
+
+/**/
+zlong toklineno;
+
+/* lexical analyzer error flag */
+
+/**/
+mod_export int lexstop;
+
+/* if != 0, this is the first line of the command */
+
+/**/
+mod_export 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.
+ * Bit 1 is only valid for the current word. It's
+ * set when we detect a lookahead that stops the word from
+ * needing correction.
+ */
+
+/**/
+int nocorrect;
+
+/*
+ * TBD: the following exported variables are part of the non-interface
+ * with ZLE for completion. They are poorly named and the whole
+ * scheme is incredibly brittle. One piece of robustness is applied:
+ * the variables are only set if LEXFLAGS_ZLE is set. Improvements
+ * should therefore concentrate on areas with this flag set.
+ *
+ * Cursor position and line length in zle when the line is
+ * metafied for access from the main shell.
+ */
+
+/**/
+mod_export int zlemetacs, zlemetall;
+
+/* inwhat says what exactly we are in *
+ * (its value is one of the IN_* things). */
+
+/**/
+mod_export int inwhat;
+
+/* 1 if x added to complete in a blank between words */
+
+/**/
+mod_export int addedx;
+
+/* wb and we hold the beginning/end position of the word we are completing. */
+
+/**/
+mod_export int wb, we;
+
+/**/
+mod_export int wordbeg;
+
+/**/
+mod_export int parbegin;
+
+/**/
+mod_export int parend;
+
+
+/* 1 if aliases should not be expanded */
+
+/**/
+mod_export int noaliases;
+
+/*
+ * If non-zero, we are parsing a line sent to use by the editor, or some
+ * other string that's not part of standard command input (e.g. eval is
+ * part of normal command input).
+ *
+ * Set of bits from LEXFLAGS_*.
+ *
+ * Note that although it is passed into the lexer as an input, the
+ * lexer can set it to zero after finding the word it's searching for.
+ * This only happens if the line being parsed actually does come from
+ * ZLE, and hence the bit LEXFLAGS_ZLE is set.
+ */
+
+/**/
+mod_export int lexflags;
+
+/* don't recognize comments */
+
+/**/
+mod_export int nocomments;
+
+/* add raw input characters while parsing command substitution */
+
+/**/
+int lex_add_raw;
+
+/* variables associated with the above */
+
+static char *tokstr_raw;
+static struct lexbufstate lexbuf_raw;
+
+/* text of punctuation tokens */
+
+/**/
+mod_export 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 */
+ ";|", /* SEMIBAR */
+};
+
+/* lexical state */
+
+static int dbparens;
+static struct lexbufstate lexbuf = { NULL, 256, 0 };
+
+/* save lexical context */
+
+/**/
+void
+lex_context_save(struct lex_stack *ls, int toplevel)
+{
+ (void)toplevel;
+
+ ls->dbparens = dbparens;
+ ls->isfirstln = isfirstln;
+ ls->isfirstch = isfirstch;
+ ls->lexflags = lexflags;
+
+ ls->tok = tok;
+ ls->tokstr = tokstr;
+ ls->zshlextext = zshlextext;
+ ls->lexbuf = lexbuf;
+ ls->lex_add_raw = lex_add_raw;
+ ls->tokstr_raw = tokstr_raw;
+ ls->lexbuf_raw = lexbuf_raw;
+ ls->lexstop = lexstop;
+ ls->toklineno = toklineno;
+
+ tokstr = zshlextext = lexbuf.ptr = NULL;
+ lexbuf.siz = 256;
+ tokstr_raw = lexbuf_raw.ptr = NULL;
+ lexbuf_raw.siz = lexbuf_raw.len = lex_add_raw = 0;
+}
+
+/* restore lexical context */
+
+/**/
+mod_export void
+lex_context_restore(const struct lex_stack *ls, int toplevel)
+{
+ (void)toplevel;
+
+ dbparens = ls->dbparens;
+ isfirstln = ls->isfirstln;
+ isfirstch = ls->isfirstch;
+ lexflags = ls->lexflags;
+ tok = ls->tok;
+ tokstr = ls->tokstr;
+ zshlextext = ls->zshlextext;
+ lexbuf = ls->lexbuf;
+ lex_add_raw = ls->lex_add_raw;
+ tokstr_raw = ls->tokstr_raw;
+ lexbuf_raw = ls->lexbuf_raw;
+ lexstop = ls->lexstop;
+ toklineno = ls->toklineno;
+}
+
+/**/
+void
+zshlex(void)
+{
+ if (tok == LEXERR)
+ return;
+ do {
+ if (inrepeat_)
+ ++inrepeat_;
+ if (inrepeat_ == 3 && isset(SHORTLOOPS))
+ incmdpos = 1;
+ tok = gettok();
+ } while (tok != ENDINPUT && exalias());
+ nocorrect &= 1;
+ if (tok == NEWLIN || tok == ENDINPUT) {
+ while (hdocs) {
+ struct heredocs *next = hdocs->next;
+ char *doc, *munged_term;
+
+ hwbegin(0);
+ cmdpush(hdocs->type == REDIR_HEREDOC ? CS_HEREDOC : CS_HEREDOCD);
+ munged_term = dupstring(hdocs->str);
+ STOPHIST
+ doc = gethere(&munged_term, hdocs->type);
+ ALLOWHIST
+ cmdpop();
+ hwend();
+ if (!doc) {
+ zerr("here document too large");
+ while (hdocs) {
+ next = hdocs->next;
+ zfree(hdocs, sizeof(struct heredocs));
+ hdocs = next;
+ }
+ tok = LEXERR;
+ break;
+ }
+ setheredoc(hdocs->pc, REDIR_HERESTR, doc, hdocs->str,
+ munged_term);
+ zfree(hdocs, sizeof(struct heredocs));
+ hdocs = next;
+ }
+ }
+ if (tok != NEWLIN)
+ isnewlin = 0;
+ else
+ isnewlin = (inbufct) ? -1 : 1;
+ if (tok == SEMI || (tok == NEWLIN && !(lexflags & LEXFLAGS_NEWLINE)))
+ tok = SEPER;
+}
+
+/**/
+mod_export void
+ctxtlex(void)
+{
+ static int oldpos;
+
+ zshlex();
+ switch (tok) {
+ case SEPER:
+ case NEWLIN:
+ case SEMI:
+ case DSEMI:
+ case SEMIAMP:
+ case SEMIBAR:
+ case AMPER:
+ case AMPERBANG:
+ case INPAR:
+ case INBRACE:
+ case DBAR:
+ case DAMPER:
+ case BAR:
+ case BARAMP:
+ case INOUTPAR:
+ case DOLOOP:
+ case THEN:
+ case ELIF:
+ case ELSE:
+ case DOUTBRACK:
+ incmdpos = 1;
+ break;
+ case STRING:
+ case TYPESET:
+ /* case ENVSTRING: */
+ case ENVARRAY:
+ case OUTPAR:
+ case CASE:
+ case DINBRACK:
+ incmdpos = 0;
+ break;
+
+ default:
+ /* nothing to do, keep compiler happy */
+ 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_DASH 18
+#define LX2_BANG 19
+#define LX2_OTHER 20
+#define LX2_META 21
+
+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)
+{
+ nocorrect = dbparens = lexstop = 0;
+ tok = ENDINPUT;
+}
+
+/* add a char to the string buffer */
+
+/**/
+void
+add(int c)
+{
+ *lexbuf.ptr++ = c;
+ if (lexbuf.siz == ++lexbuf.len) {
+ int newbsiz = lexbuf.siz * 2;
+
+ if (newbsiz > inbufct && inbufct > lexbuf.siz)
+ newbsiz = inbufct;
+
+ tokstr = (char *)hrealloc(tokstr, lexbuf.siz, newbsiz);
+ lexbuf.ptr = tokstr + lexbuf.len;
+ /* len == bsiz, so bptr is at the start of newly allocated memory */
+ memset(lexbuf.ptr, 0, newbsiz - lexbuf.siz);
+ lexbuf.siz = newbsiz;
+ }
+}
+
+#define SETPARBEGIN { \
+ if ((lexflags & LEXFLAGS_ZLE) && !(inbufflags & INP_ALIAS) && \
+ zlemetacs >= zlemetall+1-inbufct) \
+ parbegin = inbufct; \
+ }
+#define SETPAREND { \
+ if ((lexflags & LEXFLAGS_ZLE) && !(inbufflags & INP_ALIAS) && \
+ parbegin != -1 && parend == -1) { \
+ if (zlemetacs >= zlemetall + 1 - inbufct) \
+ parbegin = -1; \
+ else \
+ parend = inbufct; \
+ } \
+ }
+
+enum {
+ CMD_OR_MATH_CMD,
+ CMD_OR_MATH_MATH,
+ CMD_OR_MATH_ERR
+};
+
+/*
+ * Return one of the above. If it couldn't be
+ * parsed as math, but there was no gross error, it's a command.
+ */
+
+static int
+cmd_or_math(int cs_type)
+{
+ int oldlen = lexbuf.len;
+ int c;
+ int oinflags = inbufflags;
+
+ cmdpush(cs_type);
+ inbufflags |= INP_APPEND;
+ c = dquote_parse(')', 0);
+ if (!(oinflags & INP_APPEND))
+ inbufflags &= ~INP_APPEND;
+ cmdpop();
+ *lexbuf.ptr = '\0';
+ if (!c) {
+ /* Successfully parsed, see if it was math */
+ c = hgetc();
+ if (c == ')')
+ return CMD_OR_MATH_MATH; /* yes */
+ hungetc(c);
+ lexstop = 0;
+ c = ')';
+ } else if (lexstop) {
+ /* we haven't got anything to unget */
+ return CMD_OR_MATH_ERR;
+ }
+ /* else unsuccessful: unget the whole thing */
+ hungetc(c);
+ lexstop = 0;
+ while (lexbuf.len > oldlen && !(errflag & ERRFLAG_ERROR)) {
+ lexbuf.len--;
+ hungetc(itok(*--lexbuf.ptr) ?
+ ztokens[*lexbuf.ptr - Pound] : *lexbuf.ptr);
+ }
+ if (errflag)
+ return CMD_OR_MATH_ERR;
+ hungetc('(');
+ return errflag ? CMD_OR_MATH_ERR : CMD_OR_MATH_CMD;
+}
+
+
+/*
+ * Parse either a $(( ... )) or a $(...)
+ * Return the same as cmd_or_math().
+ */
+static int
+cmd_or_math_sub(void)
+{
+ int c = hgetc(), ret;
+
+ if (c == '(') {
+ int lexpos = (int)(lexbuf.ptr - tokstr);
+ add(Inpar);
+ add('(');
+ if ((ret = cmd_or_math(CS_MATHSUBST)) == CMD_OR_MATH_MATH) {
+ tokstr[lexpos] = Inparmath;
+ add(')');
+ return CMD_OR_MATH_MATH;
+ }
+ if (ret == CMD_OR_MATH_ERR)
+ return CMD_OR_MATH_ERR;
+ lexbuf.ptr -= 2;
+ lexbuf.len -= 2;
+ } else {
+ hungetc(c);
+ lexstop = 0;
+ }
+ return skipcomm() ? CMD_OR_MATH_ERR : CMD_OR_MATH_CMD;
+}
+
+/* Check whether we're looking at valid numeric globbing syntax *
+ * (/\<[0-9]*-[0-9]*\>/). Call pointing just after the opening "<". *
+ * Leaves the input in the same place, returning 0 or 1. */
+
+/**/
+static int
+isnumglob(void)
+{
+ int c, ec = '-', ret = 0;
+ int tbs = 256, n = 0;
+ char *tbuf = (char *)zalloc(tbs);
+
+ while(1) {
+ c = hgetc();
+ if(lexstop) {
+ lexstop = 0;
+ break;
+ }
+ tbuf[n++] = c;
+ if(!idigit(c)) {
+ if(c != ec)
+ break;
+ if(ec == '>') {
+ ret = 1;
+ break;
+ }
+ ec = '>';
+ }
+ if(n == tbs)
+ tbuf = (char *)realloc(tbuf, tbs *= 2);
+ }
+ while(n--)
+ hungetc(tbuf[n]);
+ zfree(tbuf, tbs);
+ return ret;
+}
+
+/**/
+static enum lextok
+gettok(void)
+{
+ int c, d;
+ int peekfd = -1;
+ enum lextok peek;
+
+ beginning:
+ tokstr = NULL;
+ while (iblank(c = hgetc()) && !lexstop);
+ toklineno = lineno;
+ if (lexstop)
+ return (errflag) ? LEXERR : ENDINPUT;
+ isfirstln = 0;
+ if ((lexflags & LEXFLAGS_ZLE) && !(inbufflags & INP_ALIAS))
+ wordbeg = inbufct - (qbang && c == bangchar);
+ hwbegin(-1-(qbang && c == bangchar));
+ /* word includes the last character read and possibly \ before ! */
+ if (dbparens) {
+ lexbuf.len = 0;
+ lexbuf.ptr = tokstr = (char *) hcalloc(lexbuf.siz = LEX_HEAP_SIZE);
+ hungetc(c);
+ cmdpush(CS_MATH);
+ c = dquote_parse(infor ? ';' : ')', 0);
+ cmdpop();
+ *lexbuf.ptr = '\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 = hgetc();
+ if(d == '>') {
+ peekfd = c - '0';
+ hungetc('>');
+ c = '&';
+ } else {
+ hungetc(d);
+ lexstop = 0;
+ hungetc('&');
+ }
+ } else if (d == '>' || d == '<') {
+ peekfd = c - '0';
+ c = d;
+ } else {
+ hungetc(d);
+ lexstop = 0;
+ }
+ }
+
+ /* chars in initial position in word */
+
+ /*
+ * Handle comments. There are some special cases when this
+ * is not normal command input: lexflags implies we are examining
+ * a line lexically without it being used for normal command input.
+ */
+ if (c == hashchar && !nocomments &&
+ (isset(INTERACTIVECOMMENTS) ||
+ ((!lexflags || (lexflags & LEXFLAGS_COMMENTS)) && !expanding &&
+ (!interact || unset(SHINSTDIN) || strin)))) {
+ /* History is handled here to prevent extra *
+ * newlines being inserted into the history. */
+
+ if (lexflags & LEXFLAGS_COMMENTS_KEEP) {
+ lexbuf.len = 0;
+ lexbuf.ptr = tokstr =
+ (char *)hcalloc(lexbuf.siz = LEX_HEAP_SIZE);
+ add(c);
+ }
+ hwabort();
+ while ((c = ingetc()) != '\n' && !lexstop) {
+ hwaddc(c);
+ addtoline(c);
+ if (lexflags & LEXFLAGS_COMMENTS_KEEP)
+ add(c);
+ }
+
+ if (errflag)
+ peek = LEXERR;
+ else {
+ if (lexflags & LEXFLAGS_COMMENTS_KEEP) {
+ *lexbuf.ptr = '\0';
+ if (!lexstop)
+ hungetc(c);
+ peek = STRING;
+ } else {
+ hwend();
+ hwbegin(0);
+ hwaddc('\n');
+ addtoline('\n');
+ /*
+ * If splitting a line and removing comments,
+ * we don't want a newline token since it's
+ * treated specially.
+ */
+ if ((lexflags & LEXFLAGS_COMMENTS_STRIP) && lexstop)
+ peek = ENDINPUT;
+ else
+ 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;
+ else if (d == '|')
+ return SEMIBAR;
+ hungetc(d);
+ lexstop = 0;
+ return SEMI;
+ case LX1_AMPER:
+ d = hgetc();
+ if (d == '&')
+ return DAMPER;
+ else if (d == '!' || d == '|')
+ return AMPERBANG;
+ else if (d == '>') {
+ tokfd = peekfd;
+ 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 == '|' && !incasepat)
+ 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 || (isset(SHGLOB) && !isset(KSHGLOB))) {
+ lexbuf.len = 0;
+ lexbuf.ptr = tokstr = (char *)
+ hcalloc(lexbuf.siz = LEX_HEAP_SIZE);
+ switch (cmd_or_math(CS_MATH)) {
+ case CMD_OR_MATH_MATH:
+ return DINPAR;
+
+ case CMD_OR_MATH_CMD:
+ /*
+ * Not math, so we don't return the contents
+ * as a string in this case.
+ */
+ tokstr = NULL;
+ return INPAR;
+
+ case CMD_OR_MATH_ERR:
+ /*
+ * LEXFLAGS_ACTIVE means we came from bufferwords(),
+ * so we treat as an incomplete math expression
+ */
+ if (lexflags & LEXFLAGS_ACTIVE)
+ tokstr = dyncat("((", tokstr ? tokstr : "");
+ /* fall through */
+
+ default:
+ return LEXERR;
+ }
+ }
+ } else if (d == ')')
+ return INOUTPAR;
+ hungetc(d);
+ lexstop = 0;
+ if (!(isset(SHGLOB) || incond == 1 || incmdpos))
+ break;
+ return INPAR;
+ case LX1_OUTPAR:
+ return OUTPAR;
+ case LX1_INANG:
+ d = hgetc();
+ if (d == '(') {
+ hungetc(d);
+ lexstop = 0;
+ unpeekfd:
+ if(peekfd != -1) {
+ hungetc(c);
+ c = '0' + peekfd;
+ }
+ break;
+ }
+ if (d == '>') {
+ peek = INOUTANG;
+ } 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 {
+ hungetc(d);
+ if(isnumglob())
+ goto unpeekfd;
+ peek = INANG;
+ }
+ tokfd = peekfd;
+ return peek;
+ case LX1_OUTANG:
+ d = hgetc();
+ if (d == '(') {
+ hungetc(d);
+ goto unpeekfd;
+ } 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);
+}
+
+/*
+ * Get the remains of a token string. This has two uses.
+ * When called from gettok(), with sub = 0, we have already identified
+ * any interesting initial character and want to get the rest of
+ * what we now know is a string. However, the string may still include
+ * metacharacters and potentially substitutions.
+ *
+ * When called from parse_subst_string() with sub = 1, we are not
+ * fully parsing a command line, merely tokenizing a string.
+ * In this case we always add characters to the parsed string
+ * unless there is a parse error.
+ */
+
+/**/
+static enum lextok
+gettokstr(int c, int sub)
+{
+ int bct = 0, pct = 0, brct = 0, seen_brct = 0, fdpar = 0;
+ int intpos = 1, in_brace_param = 0;
+ int inquote, unmatched = 0;
+ enum lextok peek;
+#ifdef DEBUG
+ int ocmdsp = cmdsp;
+#endif
+
+ peek = STRING;
+ if (!sub) {
+ lexbuf.len = 0;
+ lexbuf.ptr = tokstr = (char *) hcalloc(lexbuf.siz = LEX_HEAP_SIZE);
+ }
+ for (;;) {
+ int act;
+ int e;
+ int inbl = inblank(c);
+
+ if (fdpar && !inbl && c != ')')
+ fdpar = 0;
+
+ if (inbl && !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 (fdpar) {
+ /* this is a single word `( )', treat as INOUTPAR */
+ add(c);
+ *lexbuf.ptr = '\0';
+ return INOUTPAR;
+ }
+ 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);
+ switch (cmd_or_math_sub()) {
+ case CMD_OR_MATH_CMD:
+ c = Outpar;
+ break;
+
+ case CMD_OR_MATH_MATH:
+ c = Outparmath;
+ break;
+
+ default:
+ peek = LEXERR;
+ goto brk;
+ }
+ } else {
+ if (e == '{') {
+ add(c);
+ c = Inbrace;
+ ++bct;
+ cmdpush(CS_BRACEPAR);
+ if (!in_brace_param) {
+ if ((in_brace_param = bct))
+ seen_brct = 0;
+ }
+ } else {
+ hungetc(e);
+ lexstop = 0;
+ }
+ }
+ break;
+ case LX2_INBRACK:
+ if (!in_brace_param) {
+ brct++;
+ seen_brct = 1;
+ }
+ c = Inbrack;
+ break;
+ case LX2_OUTBRACK:
+ if (!in_brace_param)
+ brct--;
+ if (brct < 0)
+ brct = 0;
+ c = Outbrack;
+ break;
+ case LX2_INPAR:
+ if (isset(SHGLOB)) {
+ if (sub || in_brace_param)
+ break;
+ if (incasepat > 0 && !lexbuf.len)
+ return INPAR;
+ if (!isset(KSHGLOB) && lexbuf.len)
+ goto brk;
+ }
+ if (!in_brace_param) {
+ if (!sub) {
+ e = hgetc();
+ hungetc(e);
+ lexstop = 0;
+ /* For command words, parentheses are only
+ * special at the start. But now we're tokenising
+ * the remaining string. So I don't see what
+ * the old incmdpos test here is for.
+ * pws 1999/6/8
+ *
+ * Oh, no.
+ * func1( )
+ * is a valid function definition in [k]sh. The best
+ * thing we can do, without really nasty lookahead tricks,
+ * is break if we find a blank after a parenthesis. At
+ * least this can't happen inside braces or brackets. We
+ * only allow this with SHGLOB (set for both sh and ksh).
+ *
+ * Things like `print @( |foo)' should still
+ * work, because [k]sh don't allow multiple words
+ * in a function definition, so we only do this
+ * in command position.
+ * pws 1999/6/14
+ */
+ if (e == ')' || (isset(SHGLOB) && inblank(e) && !bct &&
+ !brct && !intpos && incmdpos)) {
+ /*
+ * Either a () token, or a command word with
+ * something suspiciously like a ksh function
+ * definition.
+ * The current word isn't spellcheckable.
+ */
+ nocorrect |= 2;
+ goto brk;
+ }
+ }
+ /*
+ * This also handles the [k]sh `foo( )' function definition.
+ * Maintain a variable fdpar, set as long as a single set of
+ * parentheses contains only space. Then if we get to the
+ * closing parenthesis and it is still set, we can assume we
+ * have a function definition. Only do this at the start of
+ * the word, since the (...) must be a separate token.
+ */
+ if (!pct++ && isset(SHGLOB) && intpos && !bct && !brct)
+ fdpar = 1;
+ }
+ c = Inpar;
+ break;
+ case LX2_INBRACE:
+ if (isset(IGNOREBRACES) || sub)
+ c = '{';
+ else {
+ if (!lexbuf.len && incmdpos) {
+ add('{');
+ *lexbuf.ptr = '\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 (in_brace_param || sub)
+ break;
+ e = hgetc();
+ if (e != '(') {
+ hungetc(e);
+ lexstop = 0;
+ goto brk;
+ }
+ add(OutangProc);
+ if (skipcomm()) {
+ peek = LEXERR;
+ goto brk;
+ }
+ c = Outpar;
+ break;
+ case LX2_INANG:
+ if (isset(SHGLOB) && sub)
+ break;
+ e = hgetc();
+ if (!(in_brace_param || sub) && e == '(') {
+ add(Inang);
+ if (skipcomm()) {
+ peek = LEXERR;
+ goto brk;
+ }
+ c = Outpar;
+ break;
+ }
+ hungetc(e);
+ if(isnumglob()) {
+ add(Inang);
+ while ((c = hgetc()) != '>')
+ add(c);
+ c = Outang;
+ break;
+ }
+ lexstop = 0;
+ if (in_brace_param || sub)
+ break;
+ goto brk;
+ case LX2_EQUALS:
+ if (!sub) {
+ 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 (peek != ENVSTRING &&
+ (incmdpos || intypeset) && !bct && !brct) {
+ char *t = tokstr;
+ if (idigit(*t))
+ while (++t < lexbuf.ptr && idigit(*t));
+ else {
+ int sav = *lexbuf.ptr;
+ *lexbuf.ptr = '\0';
+ t = itype_end(t, IIDENT, 0);
+ if (t < lexbuf.ptr) {
+ skipparens(Inbrack, Outbrack, &t);
+ } else {
+ *lexbuf.ptr = sav;
+ }
+ }
+ if (*t == '+')
+ t++;
+ if (t == lexbuf.ptr) {
+ e = hgetc();
+ if (e == '(') {
+ *lexbuf.ptr = '\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 (c == STOUC(Meta)) {
+ c = hgetc();
+#ifdef DEBUG
+ if (lexstop) {
+ fputs("BUG: input terminated by Meta\n", stderr);
+ fflush(stderr);
+ goto brk;
+ }
+#endif
+ add(Meta);
+ }
+ }
+ if (lexstop)
+ goto brk;
+ break;
+ case LX2_QUOTE: {
+ int strquote = (lexbuf.len && lexbuf.ptr[-1] == String);
+
+ add(Snull);
+ cmdpush(CS_QUOTE);
+ for (;;) {
+ STOPHIST
+ while ((c = hgetc()) != '\'' && !lexstop) {
+ if (strquote && c == '\\') {
+ c = hgetc();
+ if (lexstop)
+ break;
+ /*
+ * Mostly we don't need to do anything special
+ * with escape backslashes or closing quotes
+ * inside $'...'; however in completion we
+ * need to be able to strip multiple backslashes
+ * neatly.
+ */
+ if (c == '\\' || c == '\'')
+ add(Bnull);
+ else
+ add('\\');
+ } else if (!sub && isset(CSHJUNKIEQUOTES) && c == '\n') {
+ if (lexbuf.ptr[-1] == '\\')
+ lexbuf.ptr--, lexbuf.len--;
+ else
+ break;
+ }
+ add(c);
+ }
+ ALLOWHIST
+ if (c != '\'') {
+ unmatched = '\'';
+ /* Not an error when called from bufferwords() */
+ if (!(lexflags & LEXFLAGS_ACTIVE))
+ peek = LEXERR;
+ cmdpop();
+ goto brk;
+ }
+ e = hgetc();
+ if (e != '\'' || unset(RCQUOTES) || strquote)
+ 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) {
+ unmatched = '"';
+ /* Not an error when called from bufferwords() */
+ if (!(lexflags & LEXFLAGS_ACTIVE))
+ 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 != '`') {
+ unmatched = '`';
+ /* Not an error when called from bufferwords() */
+ if (!(lexflags & LEXFLAGS_ACTIVE))
+ peek = LEXERR;
+ goto brk;
+ }
+ c = Tick;
+ SETPAREND
+ break;
+ case LX2_DASH:
+ /*
+ * - shouldn't be treated as a special character unless
+ * we're in a pattern. Unfortunately, working out for
+ * sure in complicated expressions whether we're in a
+ * pattern is tricky. So we'll make it special and
+ * turn it back any time we don't need it special.
+ * This is not ideal as it's a lot of work.
+ */
+ c = Dash;
+ break;
+ case LX2_BANG:
+ /*
+ * Same logic as Dash, for ! to perform negation in range.
+ */
+ if (seen_brct)
+ c = Bang;
+ else
+ c = '!';
+ }
+ add(c);
+ c = hgetc();
+ if (intpos)
+ intpos--;
+ if (lexstop)
+ break;
+ }
+ brk:
+ if (errflag) {
+ if (in_brace_param) {
+ while(bct-- >= in_brace_param)
+ cmdpop();
+ }
+ return LEXERR;
+ }
+ hungetc(c);
+ if (unmatched && !(lexflags & LEXFLAGS_ACTIVE))
+ zerr("unmatched %c", unmatched);
+ if (in_brace_param) {
+ while(bct-- >= in_brace_param)
+ cmdpop();
+ zerr("closing brace expected");
+ } else if (unset(IGNOREBRACES) && !sub && lexbuf.len > 1 &&
+ peek == STRING && lexbuf.ptr[-1] == '}' &&
+ lexbuf.ptr[-2] != Bnull) {
+ /* hack to get {foo} command syntax work */
+ lexbuf.ptr--;
+ lexbuf.len--;
+ lexstop = 0;
+ hungetc('}');
+ }
+ *lexbuf.ptr = '\0';
+ DPUTS(cmdsp != ocmdsp, "BUG: gettok: cmdstack changed.");
+ return peek;
+}
+
+
+/*
+ * Parse input as if in double quotes.
+ * endchar is the end character to expect.
+ * sub has got something to do with whether we are doing quoted substitution.
+ * Return non-zero for error (character to unget), else zero
+ */
+
+/**/
+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 == ']' || infor;
+ int zlemath = math && zlemetacs > zlemetall + 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 == '`' ||
+ (endchar == ']' && (c == '[' || c == ']' ||
+ c == '(' || c == ')' ||
+ c == '{' || c == '}' ||
+ (c == '"' && sub))))
+ add(Bnull);
+ else {
+ /* lexstop is implicitly 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);
+ switch (cmd_or_math_sub()) {
+ case CMD_OR_MATH_CMD:
+ c = Outpar;
+ break;
+
+ case CMD_OR_MATH_MATH:
+ c = Outparmath;
+ break;
+
+ default:
+ err = 1;
+ break;
+ }
+ } 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 '(':
+ if (!math || !bct)
+ pct++;
+ break;
+ case ')':
+ if (!math || !bct)
+ err = (!pct-- && math);
+ break;
+ case '[':
+ if (!math || !bct)
+ brct++;
+ break;
+ case ']':
+ if (!math || !bct)
+ err = (!brct-- && math);
+ break;
+ case '"':
+ if (intick || (endchar != '"' && !bct))
+ break;
+ if (bct) {
+ add(Dnull);
+ cmdpush(CS_DQUOTE);
+ err = dquote_parse('"', sub);
+ cmdpop();
+ 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) {
+ /*
+ * TODO: as far as I can see, this hack is used in gettokstr()
+ * to hungetc() a character on an error. However, I don't
+ * understand what that actually gets us, and we can't guarantee
+ * it's a character anyway, because of the previous test.
+ *
+ * We use the same feature in cmd_or_math where we actually do
+ * need to unget if we decide it's really a command substitution.
+ * We try to handle the other case by testing for lexstop.
+ */
+ err = c;
+ }
+ if (zlemath && zlemetacs <= zlemetall + 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().
+ *
+ * parsestr() is noisier, reporting an error if the parse failed.
+ *
+ * On entry, *s must point to a string allocated from the stack of
+ * exactly the right length, i.e. strlen(*s) + 1, as the string
+ * is used as the lexical token string whose memory management
+ * demands this. Usually the input string will therefore be
+ * the result of an immediately preceding dupstring().
+ */
+
+/**/
+mod_export int
+parsestr(char **s)
+{
+ int err;
+
+ if ((err = parsestrnoerr(s))) {
+ untokenize(*s);
+ if (!(errflag & ERRFLAG_INT)) {
+ if (err > 32 && err < 127)
+ zerr("parse error near `%c'", err);
+ else
+ zerr("parse error");
+ }
+ }
+ return err;
+}
+
+/**/
+mod_export int
+parsestrnoerr(char **s)
+{
+ int l = strlen(*s), err;
+
+ zcontext_save();
+ untokenize(*s);
+ inpush(dupstring(*s), 0, NULL);
+ strinbeg(0);
+ lexbuf.len = 0;
+ lexbuf.ptr = tokstr = *s;
+ lexbuf.siz = l + 1;
+ err = dquote_parse('\0', 1);
+ if (tokstr)
+ *s = tokstr;
+ *lexbuf.ptr = '\0';
+ strinend();
+ inpop();
+ DPUTS(cmdsp, "BUG: parsestr: cmdstack not empty.");
+ zcontext_restore();
+ return err;
+}
+
+/*
+ * Parse a subscript in string s.
+ * sub is passed down to dquote_parse().
+ * endchar is the final character.
+ * Return the next character, or NULL.
+ */
+/**/
+mod_export char *
+parse_subscript(char *s, int sub, int endchar)
+{
+ int l = strlen(s), err, toklen;
+ char *t;
+
+ if (!*s || *s == endchar)
+ return 0;
+ zcontext_save();
+ untokenize(t = dupstring(s));
+ inpush(t, 0, NULL);
+ strinbeg(0);
+ /*
+ * Warning to Future Generations:
+ *
+ * This way of passing the subscript through the lexer is brittle.
+ * Code above this for several layers assumes that when we tokenise
+ * the input it goes into the same place as the original string.
+ * However, the lexer may overwrite later bits of the string or
+ * reallocate it, in particular when expanding aliaes. To get
+ * around this, we copy the string and then copy it back. This is a
+ * bit more robust but still relies on the underlying assumption of
+ * length preservation.
+ */
+ lexbuf.len = 0;
+ lexbuf.ptr = tokstr = dupstring(s);
+ lexbuf.siz = l + 1;
+ err = dquote_parse(endchar, sub);
+ toklen = (int)(lexbuf.ptr - tokstr);
+ DPUTS(toklen > l, "Bad length for parsed subscript");
+ memcpy(s, tokstr, toklen);
+ if (err) {
+ char *strend = s + toklen;
+ err = *strend;
+ *strend = '\0';
+ untokenize(s);
+ *strend = err;
+ s = NULL;
+ } else {
+ s += toklen;
+ }
+ strinend();
+ inpop();
+ DPUTS(cmdsp, "BUG: parse_subscript: cmdstack not empty.");
+ zcontext_restore();
+ return s;
+}
+
+/* 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. */
+
+/**/
+mod_export int
+parse_subst_string(char *s)
+{
+ int c, l = strlen(s), err;
+ char *ptr;
+ enum lextok ctok;
+
+ if (!*s || !strcmp(s, nulstring))
+ return 0;
+ zcontext_save();
+ untokenize(s);
+ inpush(dupstring(s), 0, NULL);
+ strinbeg(0);
+ lexbuf.len = 0;
+ lexbuf.ptr = tokstr = s;
+ lexbuf.siz = l + 1;
+ c = hgetc();
+ ctok = gettokstr(c, 1);
+ err = errflag;
+ strinend();
+ inpop();
+ DPUTS(cmdsp, "BUG: parse_subst_string: cmdstack not empty.");
+ zcontext_restore();
+ /* Keep any interrupt error status */
+ errflag = err | (errflag & ERRFLAG_INT);
+ if (ctok == LEXERR) {
+ untokenize(s);
+ return 1;
+ }
+#ifdef DEBUG
+ /*
+ * Historical note: we used to check here for olen (the value of lexbuf.len
+ * before zcontext_restore()) == l, but that's not necessarily the case if
+ * we stripped an RCQUOTE.
+ */
+ if (ctok != STRING || (errflag && !noerrs)) {
+ fprintf(stderr, "Oops. Bug in parse_subst_string: %s\n",
+ errflag ? "errflag" : "ctok != STRING");
+ fflush(stderr);
+ untokenize(s);
+ return 1;
+ }
+#endif
+ /* Check for $'...' quoting. This needs special handling. */
+ for (ptr = s; *ptr; )
+ {
+ if (*ptr == String && ptr[1] == Snull)
+ {
+ char *t;
+ int len, tlen, diff;
+ t = getkeystring(ptr + 2, &len, GETKEYS_DOLLARS_QUOTE, NULL);
+ len += 2;
+ tlen = strlen(t);
+ diff = len - tlen;
+ /*
+ * Yuk.
+ * parse_subst_string() currently handles strings in-place.
+ * That's not so easy to fix without knowing whether
+ * additional memory should come off the heap or
+ * otherwise. So we cheat by copying the unquoted string
+ * into place, unless it's too long. That's not the
+ * normal case, but I'm worried there are pathological
+ * cases with converting metafied multibyte strings.
+ * If someone can prove there aren't I will be very happy.
+ */
+ if (diff < 0) {
+ DPUTS(1, "$'...' subst too long: fix get_parse_string()");
+ return 1;
+ }
+ memcpy(ptr, t, tlen);
+ ptr += tlen;
+ if (diff > 0) {
+ char *dptr = ptr;
+ char *sptr = ptr + diff;
+ while ((*dptr++ = *sptr++))
+ ;
+ }
+ } else
+ ptr++;
+ }
+ return 0;
+}
+
+/* Called below to report word positions. */
+
+/**/
+static void
+gotword(void)
+{
+ int nwe = zlemetall + 1 - inbufct + (addedx == 2 ? 1 : 0);
+ if (zlemetacs <= nwe) {
+ int nwb = zlemetall - wordbeg + addedx;
+ if (zlemetacs >= nwb) {
+ wb = nwb;
+ we = nwe;
+ } else {
+ wb = zlemetacs + addedx;
+ if (we < wb)
+ we = wb;
+ }
+ lexflags = 0;
+ }
+}
+
+/* Check if current lex text matches an alias: 1 if so, else 0 */
+
+static int
+checkalias(void)
+{
+ Alias an;
+
+ if (!zshlextext)
+ return 0;
+
+ if (!noaliases && isset(ALIASESOPT) &&
+ (!isset(POSIXALIASES) ||
+ (tok == STRING && !reswdtab->getnode(reswdtab, zshlextext)))) {
+ char *suf;
+
+ an = (Alias) aliastab->getnode(aliastab, zshlextext);
+ if (an && !an->inuse &&
+ ((an->node.flags & ALIAS_GLOBAL) ||
+ (incmdpos && tok == STRING) || inalmore)) {
+ if (!lexstop) {
+ /*
+ * Tokens that don't require a space after, get one,
+ * because they are treated as if preceded by one.
+ */
+ int c = hgetc();
+ hungetc(c);
+ if (!iblank(c))
+ inpush(" ", INP_ALIAS, 0);
+ }
+ inpush(an->text, INP_ALIAS, an);
+ if (an->text[0] == ' ' && !(an->node.flags & ALIAS_GLOBAL))
+ aliasspaceflag = 1;
+ lexstop = 0;
+ return 1;
+ }
+ if ((suf = strrchr(zshlextext, '.')) && suf[1] &&
+ suf > zshlextext && suf[-1] != Meta &&
+ (an = (Alias)sufaliastab->getnode(sufaliastab, suf+1)) &&
+ !an->inuse && incmdpos) {
+ inpush(dupstring(zshlextext), INP_ALIAS, an);
+ inpush(" ", INP_ALIAS, NULL);
+ inpush(an->text, INP_ALIAS, NULL);
+ lexstop = 0;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* expand aliases and reserved words */
+
+/**/
+int
+exalias(void)
+{
+ Reswd rw;
+
+ hwend();
+ if (interact && isset(SHINSTDIN) && !strin && incasepat <= 0 &&
+ tok == STRING && !nocorrect && !(inbufflags & INP_ALIAS) &&
+ (isset(CORRECTALL) || (isset(CORRECT) && incmdpos)))
+ spckword(&tokstr, 1, incmdpos, 1);
+
+ if (!tokstr) {
+ zshlextext = tokstrings[tok];
+
+ if (tok == NEWLIN)
+ return 0;
+ return checkalias();
+ } else {
+ VARARR(char, copy, (strlen(tokstr) + 1));
+
+ if (has_token(tokstr)) {
+ char *p, *t;
+
+ zshlextext = p = copy;
+ for (t = tokstr;
+ (*p++ = itok(*t) ? ztokens[*t++ - Pound] : *t++););
+ } else
+ zshlextext = tokstr;
+
+ if ((lexflags & LEXFLAGS_ZLE) && !(inbufflags & INP_ALIAS)) {
+ int zp = lexflags;
+
+ gotword();
+ if ((zp & LEXFLAGS_ZLE) && !lexflags) {
+ if (zshlextext == copy)
+ zshlextext = tokstr;
+ return 0;
+ }
+ }
+
+ if (tok == STRING) {
+ /* Check for an alias */
+ if ((zshlextext != copy || !isset(POSIXALIASES)) && checkalias()) {
+ if (zshlextext == copy)
+ zshlextext = tokstr;
+ return 1;
+ }
+
+ /* Then check for a reserved word */
+ if ((incmdpos ||
+ (unset(IGNOREBRACES) && unset(IGNORECLOSEBRACES) &&
+ zshlextext[0] == '}' && !zshlextext[1])) &&
+ (rw = (Reswd) reswdtab->getnode(reswdtab, zshlextext))) {
+ tok = rw->token;
+ inrepeat_ = (tok == REPEAT);
+ if (tok == DINBRACK)
+ incond = 1;
+ } else if (incond && !strcmp(zshlextext, "]]")) {
+ tok = DOUTBRACK;
+ incond = 0;
+ } else if (incond == 1 && zshlextext[0] == '!' && !zshlextext[1])
+ tok = BANG;
+ }
+ inalmore = 0;
+ if (zshlextext == copy)
+ zshlextext = tokstr;
+ }
+ return 0;
+}
+
+/**/
+void
+zshlex_raw_add(int c)
+{
+ if (!lex_add_raw)
+ return;
+
+ *lexbuf_raw.ptr++ = c;
+ if (lexbuf_raw.siz == ++lexbuf_raw.len) {
+ int newbsiz = lexbuf_raw.siz * 2;
+
+ tokstr_raw = (char *)hrealloc(tokstr_raw, lexbuf_raw.siz, newbsiz);
+ lexbuf_raw.ptr = tokstr_raw + lexbuf_raw.len;
+ memset(lexbuf_raw.ptr, 0, newbsiz - lexbuf_raw.siz);
+ lexbuf_raw.siz = newbsiz;
+ }
+}
+
+/**/
+void
+zshlex_raw_back(void)
+{
+ if (!lex_add_raw)
+ return;
+ lexbuf_raw.ptr--;
+ lexbuf_raw.len--;
+}
+
+/**/
+int
+zshlex_raw_mark(int offset)
+{
+ if (!lex_add_raw)
+ return 0;
+ return lexbuf_raw.len + offset;
+}
+
+/**/
+void
+zshlex_raw_back_to_mark(int mark)
+{
+ if (!lex_add_raw)
+ return;
+ lexbuf_raw.ptr = tokstr_raw + mark;
+ lexbuf_raw.len = mark;
+}
+
+/*
+ * Skip (...) for command-style substitutions: $(...), <(...), >(...)
+ *
+ * In order to ensure we don't stop at closing parentheses with
+ * some other syntactic significance, we'll parse the input until
+ * we find an unmatched closing parenthesis. However, we'll throw
+ * away the result of the parsing and just keep the string we've built
+ * up on the way.
+ */
+
+/**/
+static int
+skipcomm(void)
+{
+#ifdef ZSH_OLD_SKIPCOMM
+ int pct = 1, c, start = 1;
+
+ cmdpush(CS_CMDSUBST);
+ SETPARBEGIN
+ c = Inpar;
+ do {
+ int iswhite;
+ add(c);
+ c = hgetc();
+ if (itok(c) || lexstop)
+ break;
+ iswhite = inblank(c);
+ switch (c) {
+ case '(':
+ pct++;
+ break;
+ case ')':
+ pct--;
+ break;
+ case '\\':
+ add(c);
+ c = hgetc();
+ break;
+ case '\'': {
+ int strquote = lexbuf.ptr[-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;
+ case '#':
+ if (start) {
+ add(c);
+ while ((c = hgetc()) != '\n' && !lexstop)
+ add(c);
+ iswhite = 1;
+ }
+ break;
+ }
+ start = iswhite;
+ }
+ while (pct);
+ if (!lexstop)
+ SETPAREND
+ cmdpop();
+ return lexstop;
+#else
+ char *new_tokstr;
+ int new_lexstop, new_lex_add_raw;
+ int save_infor = infor;
+ struct lexbufstate new_lexbuf;
+
+ infor = 0;
+ cmdpush(CS_CMDSUBST);
+ SETPARBEGIN
+ add(Inpar);
+
+ new_lex_add_raw = lex_add_raw + 1;
+ if (!lex_add_raw) {
+ /*
+ * We'll combine the string so far with the input
+ * read in for the command substitution. To do this
+ * we'll just propagate the current tokstr etc. as the
+ * variables used for adding raw input, and
+ * ensure we swap those for the real tokstr etc. at the end.
+ *
+ * However, we need to save and restore the rest of the
+ * lexical and parse state as we're effectively parsing
+ * an internal string. Because we're still parsing it from
+ * the original input source (we have to --- we don't know
+ * when to stop inputting it otherwise and can't rely on
+ * the input being recoverable until we've read it) we need
+ * to keep the same history context.
+ */
+ new_tokstr = tokstr;
+ new_lexbuf = lexbuf;
+
+ /*
+ * If we're expanding an alias at this point, we need the whole
+ * remaining text as part of the string for the command in
+ * parentheses, so don't backtrack. This is different from the
+ * usual case where the alias is fully within the command, where
+ * we want the unexpanded text so that it will be expanded
+ * again when the command in the parentheses is executed.
+ *
+ * I never wanted to be a software engineer, you know.
+ */
+ if (inbufflags & INP_ALIAS)
+ inbufflags |= INP_RAW_KEEP;
+ zcontext_save_partial(ZCONTEXT_LEX|ZCONTEXT_PARSE);
+ hist_in_word(1);
+ } else {
+ /*
+ * Set up for nested command subsitution, however
+ * we don't actually need the string until we get
+ * back to the top level and recover the lot.
+ * The $() body just appears empty.
+ *
+ * We do need to propagate the raw variables which would
+ * otherwise by cleared, though.
+ */
+ new_tokstr = tokstr_raw;
+ new_lexbuf = lexbuf_raw;
+
+ zcontext_save_partial(ZCONTEXT_LEX|ZCONTEXT_PARSE);
+ }
+ tokstr_raw = new_tokstr;
+ lexbuf_raw = new_lexbuf;
+ lex_add_raw = new_lex_add_raw;
+ /*
+ * Don't do any ZLE specials down here: they're only needed
+ * when we return the string from the recursive parse.
+ * (TBD: this probably means we should be initialising lexflags
+ * more consistently.)
+ *
+ * Note that in that case we're still using the ZLE line reading
+ * function at the history layer --- this is consistent with the
+ * intention of maintaining the history and input layers across
+ * the recursive parsing.
+ *
+ * Also turn off LEXFLAGS_NEWLINE because this is already skipping
+ * across the entire construct, and parse_event() needs embedded
+ * newlines to be "real" when looking for the OUTPAR token.
+ */
+ lexflags &= ~(LEXFLAGS_ZLE|LEXFLAGS_NEWLINE);
+ dbparens = 0; /* restored by zcontext_restore_partial() */
+
+ if (!parse_event(OUTPAR) || tok != OUTPAR) {
+ if (strin) {
+ /*
+ * Get the rest of the string raw since we don't
+ * know where this token ends.
+ */
+ while (!lexstop)
+ (void)ingetc();
+ } else
+ lexstop = 1;
+ }
+ /* Outpar lexical token gets added in caller if present */
+
+ /*
+ * We're going to keep the full raw input string
+ * as the current token string after popping the stack.
+ */
+ new_tokstr = tokstr_raw;
+ new_lexbuf = lexbuf_raw;
+ /*
+ * We're also going to propagate the lexical state:
+ * if we couldn't parse the command substitution we
+ * can't continue.
+ */
+ new_lexstop = lexstop;
+
+ zcontext_restore_partial(ZCONTEXT_LEX|ZCONTEXT_PARSE);
+
+ if (lex_add_raw) {
+ /*
+ * Keep going, so retain the raw variables.
+ */
+ tokstr_raw = new_tokstr;
+ lexbuf_raw = new_lexbuf;
+ } else {
+ if (!new_lexstop) {
+ /* Ignore the ')' added on input */
+ new_lexbuf.len--;
+ *--new_lexbuf.ptr = '\0';
+ }
+
+ /*
+ * Convince the rest of lex.c we were examining a string
+ * all along.
+ */
+ tokstr = new_tokstr;
+ lexbuf = new_lexbuf;
+ lexstop = new_lexstop;
+ hist_in_word(0);
+ }
+
+ if (!lexstop)
+ SETPAREND
+ cmdpop();
+ infor = save_infor;
+
+ return lexstop;
+#endif
+}
diff --git a/dotfiles/system/.zsh/modules/Src/loop.c b/dotfiles/system/.zsh/modules/Src/loop.c
new file mode 100644
index 0000000..1013aeb
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/loop.c
@@ -0,0 +1,795 @@
+/*
+ * 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 */
+
+/**/
+mod_export int contflag;
+
+/* # of break levels */
+
+/**/
+mod_export int breaks;
+
+/**/
+int
+execfor(Estate state, int do_exec)
+{
+ Wordcode end, loop;
+ wordcode code = state->pc[-1];
+ int iscond = (WC_FOR_TYPE(code) == WC_FOR_COND), ctok = 0, atok = 0;
+ int last = 0;
+ char *name, *str, *cond = NULL, *advance = NULL;
+ zlong val = 0;
+ LinkList vars = NULL, args = NULL;
+ int old_simple_pline = simple_pline;
+
+ /* See comments in execwhile() */
+ simple_pline = 1;
+
+ end = state->pc + WC_FOR_SKIP(code);
+
+ if (iscond) {
+ str = dupstring(ecgetstr(state, EC_NODUP, NULL));
+ singsub(&str);
+ if (isset(XTRACE)) {
+ char *str2 = dupstring(str);
+ untokenize(str2);
+ printprompt4();
+ fprintf(xtrerr, "%s\n", str2);
+ fflush(xtrerr);
+ }
+ if (!errflag) {
+ matheval(str);
+ }
+ if (errflag) {
+ state->pc = end;
+ simple_pline = old_simple_pline;
+ return 1;
+ }
+ cond = ecgetstr(state, EC_NODUP, &ctok);
+ advance = ecgetstr(state, EC_NODUP, &atok);
+ } else {
+ vars = ecgetlist(state, *state->pc++, EC_NODUP, NULL);
+
+ if (WC_FOR_TYPE(code) == WC_FOR_LIST) {
+ int htok = 0;
+
+ if (!(args = ecgetlist(state, *state->pc++, EC_DUPTOK, &htok))) {
+ state->pc = end;
+ simple_pline = old_simple_pline;
+ return 0;
+ }
+ if (htok) {
+ execsubst(args);
+ if (errflag) {
+ state->pc = end;
+ simple_pline = old_simple_pline;
+ return 1;
+ }
+ }
+ } else {
+ char **x;
+
+ args = newlinklist();
+ for (x = pparams; *x; x++)
+ addlinknode(args, dupstring(*x));
+ }
+ }
+
+ if (!args || empty(args))
+ lastval = 0;
+
+ loops++;
+ pushheap();
+ cmdpush(CS_FOR);
+ loop = state->pc;
+ while (!last) {
+ if (iscond) {
+ if (ctok) {
+ str = dupstring(cond);
+ singsub(&str);
+ } else
+ str = cond;
+ if (!errflag) {
+ while (iblank(*str))
+ str++;
+ if (*str) {
+ if (isset(XTRACE)) {
+ printprompt4();
+ fprintf(xtrerr, "%s\n", str);
+ fflush(xtrerr);
+ }
+ val = mathevali(str);
+ } else
+ val = 1;
+ }
+ if (errflag) {
+ if (breaks)
+ breaks--;
+ lastval = 1;
+ break;
+ }
+ if (!val)
+ break;
+ } else {
+ LinkNode node;
+ int count = 0;
+ for (node = firstnode(vars); node; incnode(node))
+ {
+ name = (char *)getdata(node);
+ if (!args || !(str = (char *) ugetnode(args)))
+ {
+ if (count) {
+ str = "";
+ last = 1;
+ } else
+ break;
+ }
+ if (isset(XTRACE)) {
+ printprompt4();
+ fprintf(xtrerr, "%s=%s\n", name, str);
+ fflush(xtrerr);
+ }
+ setsparam(name, ztrdup(str));
+ count++;
+ }
+ if (!count)
+ break;
+ }
+ state->pc = loop;
+ execlist(state, 1, do_exec && args && empty(args));
+ if (breaks) {
+ breaks--;
+ if (breaks || !contflag)
+ break;
+ contflag = 0;
+ }
+ if (retflag)
+ break;
+ if (iscond && !errflag) {
+ if (atok) {
+ str = dupstring(advance);
+ singsub(&str);
+ } else
+ str = advance;
+ if (isset(XTRACE)) {
+ printprompt4();
+ fprintf(xtrerr, "%s\n", str);
+ fflush(xtrerr);
+ }
+ if (!errflag)
+ matheval(str);
+ }
+ if (errflag) {
+ if (breaks)
+ breaks--;
+ lastval = 1;
+ break;
+ }
+ freeheap();
+ }
+ popheap();
+ cmdpop();
+ loops--;
+ simple_pline = old_simple_pline;
+ state->pc = end;
+ this_noerrexit = 1;
+ return lastval;
+}
+
+/**/
+int
+execselect(Estate state, UNUSED(int do_exec))
+{
+ Wordcode end, loop;
+ wordcode code = state->pc[-1];
+ char *str, *s, *name;
+ LinkNode n;
+ int i, usezle;
+ FILE *inp;
+ size_t more;
+ LinkList args;
+ int old_simple_pline = simple_pline;
+
+ /* See comments in execwhile() */
+ simple_pline = 1;
+
+ end = state->pc + WC_FOR_SKIP(code);
+ name = ecgetstr(state, EC_NODUP, NULL);
+
+ if (WC_SELECT_TYPE(code) == WC_SELECT_PPARAM) {
+ char **x;
+
+ args = newlinklist();
+ for (x = pparams; *x; x++)
+ addlinknode(args, dupstring(*x));
+ } else {
+ int htok = 0;
+
+ if (!(args = ecgetlist(state, *state->pc++, EC_DUPTOK, &htok))) {
+ state->pc = end;
+ simple_pline = old_simple_pline;
+ return 0;
+ }
+ if (htok) {
+ execsubst(args);
+ if (errflag) {
+ state->pc = end;
+ simple_pline = old_simple_pline;
+ return 1;
+ }
+ }
+ }
+ if (!args || empty(args)) {
+ state->pc = end;
+ simple_pline = old_simple_pline;
+ return 0;
+ }
+ loops++;
+
+ pushheap();
+ cmdpush(CS_SELECT);
+ usezle = interact && SHTTY != -1 && isset(USEZLE);
+ inp = fdopen(dup(usezle ? SHTTY : 0), "r");
+ more = selectlist(args, 0);
+ loop = state->pc;
+ for (;;) {
+ for (;;) {
+ if (empty(bufstack)) {
+ if (usezle) {
+ int oef = errflag;
+
+ isfirstln = 1;
+ str = zleentry(ZLE_CMD_READ, &prompt3, NULL,
+ 0, ZLCON_SELECT);
+ if (errflag)
+ str = NULL;
+ /* Keep any user interrupt error status */
+ errflag = oef | (errflag & ERRFLAG_INT);
+ } else {
+ str = promptexpand(prompt3, 0, NULL, NULL, NULL);
+ zputs(str, stderr);
+ free(str);
+ fflush(stderr);
+ str = fgets(zhalloc(256), 256, inp);
+ }
+ } else
+ str = (char *)getlinknode(bufstack);
+ if (!str && !errflag)
+ setsparam("REPLY", ztrdup("")); /* EOF (user pressed Ctrl+D) */
+ if (!str || errflag) {
+ if (breaks)
+ breaks--;
+ fprintf(stderr, "\n");
+ fflush(stderr);
+ goto done;
+ }
+ if ((s = strchr(str, '\n')))
+ *s = '\0';
+ if (*str)
+ break;
+ more = selectlist(args, more);
+ }
+ 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(name, ztrdup(str));
+ state->pc = loop;
+ execlist(state, 1, 0);
+ freeheap();
+ if (breaks) {
+ breaks--;
+ if (breaks || !contflag)
+ break;
+ contflag = 0;
+ }
+ if (retflag || errflag)
+ break;
+ }
+ done:
+ cmdpop();
+ popheap();
+ fclose(inp);
+ loops--;
+ simple_pline = old_simple_pline;
+ state->pc = end;
+ this_noerrexit = 1;
+ return lastval;
+}
+
+/* And this is used to print select lists. */
+
+/**/
+size_t
+selectlist(LinkList l, size_t start)
+{
+ size_t longest = 1, fct, fw = 0, colsz, t0, t1, ct;
+ char **arr, **ap;
+
+ zleentry(ZLE_CMD_TRASH);
+ arr = hlinklist2array(l, 0);
+ for (ap = arr; *ap; ap++)
+ if (strlen(*ap) > longest)
+ longest = strlen(*ap);
+ t0 = ct = ap - arr;
+ longest++;
+ while (t0)
+ t0 /= 10, longest++;
+ /* to compensate for added ')' */
+ fct = (zterm_columns - 1) / (longest + 3);
+ if (fct == 0)
+ fct = 1;
+ else
+ fw = (zterm_columns - 1) / fct;
+ colsz = (ct + fct - 1) / fct;
+ for (t1 = start; t1 != colsz && t1 - start < zterm_lines - 2; t1++) {
+ ap = arr + t1;
+ do {
+ size_t t2 = strlen(*ap) + 2;
+ int 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);
+
+ return t1 < colsz ? t1 : 0;
+}
+
+/**/
+int
+execwhile(Estate state, UNUSED(int do_exec))
+{
+ Wordcode end, loop;
+ wordcode code = state->pc[-1];
+ int olderrexit, oldval, isuntil = (WC_WHILE_TYPE(code) == WC_WHILE_UNTIL);
+ int old_simple_pline = simple_pline;
+
+ end = state->pc + WC_WHILE_SKIP(code);
+ olderrexit = noerrexit;
+ oldval = 0;
+ pushheap();
+ cmdpush(isuntil ? CS_UNTIL : CS_WHILE);
+ loops++;
+ loop = state->pc;
+
+ if (loop[0] == WC_END && loop[1] == WC_END) {
+
+ /* This is an empty loop. Make sure the signal handler sets the
+ * flags and then just wait for someone hitting ^C. */
+
+ simple_pline = 1;
+
+ while (!breaks)
+ ;
+ breaks--;
+
+ simple_pline = old_simple_pline;
+ } else
+ for (;;) {
+ state->pc = loop;
+ noerrexit = NOERREXIT_EXIT | NOERREXIT_RETURN;
+
+ /* In case the test condition is a functional no-op,
+ * make sure signal handlers recognize ^C to end the loop. */
+ simple_pline = 1;
+
+ execlist(state, 1, 0);
+
+ simple_pline = old_simple_pline;
+ noerrexit = olderrexit;
+ if (!((lastval == 0) ^ isuntil)) {
+ if (breaks)
+ breaks--;
+ if (!retflag)
+ lastval = oldval;
+ break;
+ }
+ if (retflag)
+ break;
+
+ /* In case the loop body is also a functional no-op,
+ * make sure signal handlers recognize ^C as above. */
+ simple_pline = 1;
+
+ execlist(state, 1, 0);
+
+ simple_pline = old_simple_pline;
+ if (breaks) {
+ breaks--;
+ if (breaks || !contflag)
+ break;
+ contflag = 0;
+ }
+ if (errflag) {
+ lastval = 1;
+ break;
+ }
+ if (retflag)
+ break;
+ freeheap();
+ oldval = lastval;
+ }
+ cmdpop();
+ popheap();
+ loops--;
+ state->pc = end;
+ this_noerrexit = 1;
+ return lastval;
+}
+
+/**/
+int
+execrepeat(Estate state, UNUSED(int do_exec))
+{
+ Wordcode end, loop;
+ wordcode code = state->pc[-1];
+ int count, htok = 0;
+ char *tmp;
+ int old_simple_pline = simple_pline;
+
+ /* See comments in execwhile() */
+ simple_pline = 1;
+
+ end = state->pc + WC_REPEAT_SKIP(code);
+
+ lastval = 0;
+ tmp = ecgetstr(state, EC_DUPTOK, &htok);
+ if (htok)
+ singsub(&tmp);
+ count = mathevali(tmp);
+ if (errflag)
+ return 1;
+ pushheap();
+ cmdpush(CS_REPEAT);
+ loops++;
+ loop = state->pc;
+ while (count-- > 0) {
+ state->pc = loop;
+ execlist(state, 1, 0);
+ freeheap();
+ if (breaks) {
+ breaks--;
+ if (breaks || !contflag)
+ break;
+ contflag = 0;
+ }
+ if (errflag) {
+ lastval = 1;
+ break;
+ }
+ if (retflag)
+ break;
+ }
+ cmdpop();
+ popheap();
+ loops--;
+ simple_pline = old_simple_pline;
+ state->pc = end;
+ this_noerrexit = 1;
+ return lastval;
+}
+
+/**/
+int
+execif(Estate state, int do_exec)
+{
+ Wordcode end, next;
+ wordcode code = state->pc[-1];
+ int olderrexit, s = 0, run = 0;
+
+ olderrexit = noerrexit;
+ end = state->pc + WC_IF_SKIP(code);
+
+ noerrexit |= NOERREXIT_EXIT | NOERREXIT_RETURN;
+ while (state->pc < end) {
+ code = *state->pc++;
+ if (wc_code(code) != WC_IF ||
+ (run = (WC_IF_TYPE(code) == WC_IF_ELSE))) {
+ if (run)
+ run = 2;
+ break;
+ }
+ next = state->pc + WC_IF_SKIP(code);
+ cmdpush(s ? CS_ELIF : CS_IF);
+ execlist(state, 1, 0);
+ cmdpop();
+ if (!lastval) {
+ run = 1;
+ break;
+ }
+ if (retflag)
+ break;
+ s = 1;
+ state->pc = next;
+ }
+
+ if (run) {
+ /* we need to ignore lastval until we reach execcmd() */
+ if (olderrexit)
+ noerrexit = olderrexit;
+ else if (lastval)
+ noerrexit |= NOERREXIT_EXIT | NOERREXIT_RETURN | NOERREXIT_UNTIL_EXEC;
+ else
+ noerrexit &= ~ (NOERREXIT_EXIT | NOERREXIT_RETURN);
+ cmdpush(run == 2 ? CS_ELSE : (s ? CS_ELIFTHEN : CS_IFTHEN));
+ execlist(state, 1, do_exec);
+ cmdpop();
+ } else {
+ noerrexit = olderrexit;
+ if (!retflag)
+ lastval = 0;
+ }
+ state->pc = end;
+ this_noerrexit = 1;
+
+ return lastval;
+}
+
+/**/
+int
+execcase(Estate state, int do_exec)
+{
+ Wordcode end, next;
+ wordcode code = state->pc[-1];
+ char *word, *pat;
+ int npat, save, nalts, ialt, patok, anypatok;
+ Patprog *spprog, pprog;
+
+ end = state->pc + WC_CASE_SKIP(code);
+
+ word = ecgetstr(state, EC_DUP, NULL);
+ singsub(&word);
+ untokenize(word);
+ anypatok = 0;
+
+ cmdpush(CS_CASE);
+ while (state->pc < end) {
+ code = *state->pc++;
+ if (wc_code(code) != WC_CASE)
+ break;
+
+ save = 0;
+ next = state->pc + WC_CASE_SKIP(code);
+ nalts = *state->pc++;
+ ialt = patok = 0;
+
+ if (isset(XTRACE)) {
+ printprompt4();
+ fprintf(xtrerr, "case %s (", word);
+ }
+
+ while (!patok && nalts) {
+ npat = state->pc[1];
+ spprog = state->prog->pats + npat;
+ pprog = NULL;
+ pat = NULL;
+
+ queue_signals();
+
+ if (isset(XTRACE)) {
+ int htok = 0;
+ pat = dupstring(ecrawstr(state->prog, state->pc, &htok));
+ if (htok)
+ singsub(&pat);
+
+ if (ialt++)
+ fprintf(stderr, " | ");
+ quote_tokenized_output(pat, xtrerr);
+ }
+
+ if (*spprog != dummy_patprog1 && *spprog != dummy_patprog2)
+ pprog = *spprog;
+
+ if (!pprog) {
+ if (!pat) {
+ char *opat;
+ int htok = 0;
+
+ pat = dupstring(opat = ecrawstr(state->prog,
+ state->pc, &htok));
+ if (htok)
+ singsub(&pat);
+ save = (!(state->prog->flags & EF_HEAP) &&
+ !strcmp(pat, opat) && *spprog != dummy_patprog2);
+ }
+ if (!(pprog = patcompile(pat, (save ? PAT_ZDUP : PAT_STATIC),
+ NULL)))
+ zerr("bad pattern: %s", pat);
+ else if (save)
+ *spprog = pprog;
+ }
+ if (pprog && pattry(pprog, word))
+ patok = anypatok = 1;
+ state->pc += 2;
+ nalts--;
+
+ unqueue_signals();
+ }
+ state->pc += 2 * nalts;
+ if (isset(XTRACE)) {
+ fprintf(xtrerr, ")\n");
+ fflush(xtrerr);
+ }
+ if (patok) {
+ execlist(state, 1, ((WC_CASE_TYPE(code) == WC_CASE_OR) &&
+ do_exec));
+ while (!retflag && wc_code(code) == WC_CASE &&
+ WC_CASE_TYPE(code) == WC_CASE_AND && state->pc < end) {
+ state->pc = next;
+ code = *state->pc++;
+ next = state->pc + WC_CASE_SKIP(code);
+ nalts = *state->pc++;
+ state->pc += 2 * nalts;
+ execlist(state, 1, ((WC_CASE_TYPE(code) == WC_CASE_OR) &&
+ do_exec));
+ }
+ if (WC_CASE_TYPE(code) != WC_CASE_TESTAND)
+ break;
+ }
+ state->pc = next;
+ }
+ cmdpop();
+
+ state->pc = end;
+
+ if (!anypatok)
+ lastval = 0;
+ this_noerrexit = 1;
+
+ return lastval;
+}
+
+/*
+ * Errflag from `try' block, may be reset in `always' block.
+ * Accessible from an integer parameter, so needs to be a zlong.
+ */
+
+/**/
+zlong
+try_errflag = -1;
+
+/**
+ * Corresponding interrupt error status form `try' block.
+ */
+
+/**/
+zlong
+try_interrupt = -1;
+
+/**/
+zlong
+try_tryflag = 0;
+
+/**/
+int
+exectry(Estate state, int do_exec)
+{
+ Wordcode end, always;
+ int endval;
+ int save_retflag, save_breaks, save_contflag;
+ zlong save_try_errflag, save_try_tryflag, save_try_interrupt;
+
+ end = state->pc + WC_TRY_SKIP(state->pc[-1]);
+ always = state->pc + 1 + WC_TRY_SKIP(*state->pc);
+ state->pc++;
+ pushheap();
+ cmdpush(CS_CURSH);
+
+ /* The :try clause */
+ save_try_tryflag = try_tryflag;
+ try_tryflag = 1;
+
+ execlist(state, 1, do_exec);
+
+ try_tryflag = save_try_tryflag;
+
+ /* Don't record errflag here, may be reset. However, */
+ /* endval should show failure when there is an error. */
+ endval = lastval ? lastval : errflag;
+
+ freeheap();
+
+ cmdpop();
+ cmdpush(CS_ALWAYS);
+
+ /* The always clause. */
+ save_try_errflag = try_errflag;
+ save_try_interrupt = try_interrupt;
+ try_errflag = (zlong)(errflag & ERRFLAG_ERROR);
+ try_interrupt = (zlong)((errflag & ERRFLAG_INT) ? 1 : 0);
+ /* We need to reset all errors to allow the block to execute */
+ errflag = 0;
+ save_retflag = retflag;
+ retflag = 0;
+ save_breaks = breaks;
+ breaks = 0;
+ save_contflag = contflag;
+ contflag = 0;
+
+ state->pc = always;
+ execlist(state, 1, do_exec);
+
+ if (try_errflag)
+ errflag |= ERRFLAG_ERROR;
+ else
+ errflag &= ~ERRFLAG_ERROR;
+ if (try_interrupt)
+ errflag |= ERRFLAG_INT;
+ else
+ errflag &= ~ERRFLAG_INT;
+ try_errflag = save_try_errflag;
+ try_interrupt = save_try_interrupt;
+ if (!retflag)
+ retflag = save_retflag;
+ if (!breaks)
+ breaks = save_breaks;
+ if (!contflag)
+ contflag = save_contflag;
+
+ cmdpop();
+ popheap();
+ state->pc = end;
+
+ return endval;
+}
diff --git a/dotfiles/system/.zsh/modules/Src/makepro.awk b/dotfiles/system/.zsh/modules/Src/makepro.awk
new file mode 100644
index 0000000..0498c15
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/makepro.awk
@@ -0,0 +1,166 @@
+#
+# 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--
+
+ printf "E#ifndef have_%s_globals\n", name
+ printf "E#define have_%s_globals\n", name
+ printf "E\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
+ }
+ if (line == "" && $0 ~ /^[ \t]*#/) {
+ # Directly after the /**/ was a preprocessor line.
+ # Spit it out and re-start the outer loop.
+ printf "E%s\n", $0
+ printf "L%s\n", $0
+ next
+ }
+ gsub(/\t/, " ")
+ line = line " " $0
+ gsub(/\/\*([^*]|\*+[^*\/])*\*+\//, " ", line)
+ if(line ~ /\/\*/)
+ continue
+ # If it is a function definition, note so.
+ if(line ~ /\) *(VA_DCL )*[{].*$/) #}
+ isfunc = 1
+ if(sub(/ *[{;].*$/, "", line)) #}
+ break
+ }
+ if (!match(line, /VA_ALIST/)) {
+ # 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|mod_export|static|struct|union) +)*([_0-9A-Za-z]+ +|((char|double|float|int|long|short|unsigned|void) +)+)((const|static) +)*/)
+ dtype = substr(line, 1, RLENGTH)
+ sub(/ *$/, "", dtype)
+ if(" " dtype " " ~ / static /)
+ locality = "L"
+ else
+ locality = "E"
+ exported = " " dtype " " ~ / mod_export /
+ line = substr(line, RLENGTH+1) ","
+ # Handle each declarator.
+ if (match(line, /VA_ALIST/)) {
+ # Already has VARARGS handling.
+
+ # Put parens etc. back
+ gsub(/@[{]/, "((", line)
+ gsub(/@}/, "))", line)
+ gsub(/@</, "(", line)
+ gsub(/@>/, ")", line)
+ gsub(/@!/, ",", line)
+ sub(/,$/, ";", line)
+ gsub(/mod_export/, "mod_import_function", dtype)
+ gsub(/VA_ALIST/, "VA_ALIST_PROTO", line)
+ sub(/ VA_DCL/, "", line)
+
+ if(locality ~ /E/)
+ dtype = "extern " dtype
+
+ if (match(line, /[_0-9A-Za-z]+\(VA_ALIST/))
+ dnam = substr(line, RSTART, RLENGTH-9)
+
+ # If this is exported, add it to the exported symbol list.
+ if (exported)
+ printf "X%s\n", dnam
+
+ printf "%s%s %s\n", locality, dtype, line
+ } else {
+ 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 exported, add it to the exported symbol list.
+ if(exported)
+ printf "X%s\n", dnam
+
+ # Format the declaration for output
+ dcl = dtype " " dcltor ";"
+ if(locality ~ /E/)
+ dcl = "extern " dcl
+ if(isfunc)
+ gsub(/ mod_export /, " mod_import_function ", dcl)
+ else
+ gsub(/ mod_export /, " mod_import_variable ", 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)
+ printf "%s%s\n", locality, dcl
+ }
+ }
+}
+
+END {
+ if(aborting)
+ exit 1
+ printf "E\n"
+ printf "E#endif /* !have_%s_globals */\n", name
+}
diff --git a/dotfiles/system/.zsh/modules/Src/mem.c b/dotfiles/system/.zsh/modules/Src/mem.c
new file mode 100644
index 0000000..77e4375
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/mem.c
@@ -0,0 +1,1899 @@
+/*
+ * 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/zshcalloc, which call malloc/calloc directly. It
+ is legal to call realloc() or free() on memory allocated this way.
+ The second way is to call zhalloc/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.
+
+ If possible, the heaps are allocated using mmap() so that the
+ (*real*) heap isn't filled up with empty zsh heaps. If mmap()
+ is not available and zsh's own allocator is used, we use a simple trick
+ to avoid that: 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.
+
+ hrealloc(char *p, size_t old, size_t new) is an optimisation
+ with a similar interface to realloc(). Typically the new size
+ will be larger than the old one, since there is no gain in
+ shrinking the allocation (indeed, that will confused hrealloc()
+ since it will forget that the unused space once belonged to this
+ pointer). However, new == 0 is a special case; then if we
+ had to allocate a special heap for this memory it is freed at
+ that point.
+*/
+
+#if defined(HAVE_SYS_MMAN_H) && defined(HAVE_MMAP) && defined(HAVE_MUNMAP)
+
+#include <sys/mman.h>
+
+/*
+ * This definition is designed to enable use of memory mapping on MacOS.
+ * However, performance tests indicate that MacOS mapped regions are
+ * somewhat slower to allocate than memory from malloc(), so whether
+ * using this improves performance depends on details of zhalloc().
+ */
+#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS)
+#define MAP_ANONYMOUS MAP_ANON
+#endif
+
+#if defined(MAP_ANONYMOUS) && defined(MAP_PRIVATE)
+
+#define USE_MMAP 1
+#define MMAP_FLAGS (MAP_ANONYMOUS | MAP_PRIVATE)
+
+#endif
+#endif
+
+#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
+
+/* Make sure we align to the longest fundamental type. */
+union mem_align {
+ zlong l;
+ double d;
+};
+
+#define H_ISIZE sizeof(union mem_align)
+#define HEAPSIZE (16384 - H_ISIZE)
+/* Memory available for user data in default arena size */
+#define HEAP_ARENA_SIZE (HEAPSIZE - sizeof(struct heap))
+#define HEAPFREE (16384 - H_ISIZE)
+
+/* Memory available for user data in heap h */
+#define ARENA_SIZEOF(h) ((h)->size - sizeof(struct heap))
+
+/* list of zsh heaps */
+
+static Heap heaps;
+
+/* a heap with free space, not always correct (it will be the last heap
+ * if that was newly allocated but it may also be another one) */
+
+static Heap fheap;
+
+/**/
+#ifdef ZSH_HEAP_DEBUG
+/*
+ * The heap ID we'll allocate next.
+ *
+ * We'll avoid using 0 as that means zero-initialised memory
+ * containing a heap ID is (correctly) marked as invalid.
+ */
+static Heapid next_heap_id = (Heapid)1;
+
+/*
+ * The ID of the heap from which we last allocated heap memory.
+ * In theory, since we carefully avoid allocating heap memory during
+ * interrupts, after any call to zhalloc() or wrappers this should
+ * be the ID of the heap containing the memory just returned.
+ */
+/**/
+mod_export Heapid last_heap_id;
+
+/*
+ * Stack of heaps saved by new_heaps().
+ * Assumes old_heaps() will come along and restore it later
+ * (outputs an error if old_heaps() is called out of sequence).
+ */
+static LinkList heaps_saved;
+
+/*
+ * Debugging verbosity. This must be set from a debugger.
+ * An 'or' of bits from the enum heap_debug_verbosity.
+ */
+static volatile int heap_debug_verbosity;
+
+/*
+ * Generate a heap identifier that's unique up to unsigned integer wrap.
+ *
+ * For the purposes of debugging we won't bother trying to make a
+ * heap_id globally unique, which would require checking all existing
+ * heaps every time we create an ID and still wouldn't do what we
+ * ideally want, which is to make sure the IDs of valid heaps are
+ * different from the IDs of no-longer-valid heaps. Given that,
+ * we'll just assume that if we haven't tracked the problem when the
+ * ID wraps we're out of luck. We could change the type to a long long
+ * if we wanted more room
+ */
+
+static Heapid
+new_heap_id(void)
+{
+ return next_heap_id++;
+}
+
+/**/
+#endif
+
+/* Use new heaps from now on. This returns the old heap-list. */
+
+/**/
+mod_export Heap
+new_heaps(void)
+{
+ Heap h;
+
+ queue_signals();
+ h = heaps;
+
+ fheap = heaps = NULL;
+ unqueue_signals();
+
+#ifdef ZSH_HEAP_DEBUG
+ if (heap_debug_verbosity & HDV_NEW) {
+ fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT
+ " saved, new heaps created.\n", h->heap_id);
+ }
+ if (!heaps_saved)
+ heaps_saved = znewlinklist();
+ zpushnode(heaps_saved, h);
+#endif
+ return h;
+}
+
+/* Re-install the old heaps again, freeing the new ones. */
+
+/**/
+mod_export void
+old_heaps(Heap old)
+{
+ Heap h, n;
+
+ queue_signals();
+ for (h = heaps; h; h = n) {
+ n = h->next;
+ DPUTS(h->sp, "BUG: old_heaps() with pushed heaps");
+#ifdef ZSH_HEAP_DEBUG
+ if (heap_debug_verbosity & HDV_FREE) {
+ fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT
+ "freed in old_heaps().\n", h->heap_id);
+ }
+#endif
+#ifdef USE_MMAP
+ munmap((void *) h, h->size);
+#else
+ zfree(h, HEAPSIZE);
+#endif
+#ifdef ZSH_VALGRIND
+ VALGRIND_DESTROY_MEMPOOL((char *)h);
+#endif
+ }
+ heaps = old;
+#ifdef ZSH_HEAP_DEBUG
+ if (heap_debug_verbosity & HDV_OLD) {
+ fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT
+ "restored.\n", heaps->heap_id);
+ }
+ {
+ Heap myold = heaps_saved ? getlinknode(heaps_saved) : NULL;
+ if (old != myold)
+ {
+ fprintf(stderr, "HEAP DEBUG: invalid old heap " HEAPID_FMT
+ ", expecting " HEAPID_FMT ".\n", old->heap_id,
+ myold->heap_id);
+ }
+ }
+#endif
+ fheap = NULL;
+ unqueue_signals();
+}
+
+/* Temporarily switch to other heaps (or back again). */
+
+/**/
+mod_export Heap
+switch_heaps(Heap new)
+{
+ Heap h;
+
+ queue_signals();
+ h = heaps;
+
+#ifdef ZSH_HEAP_DEBUG
+ if (heap_debug_verbosity & HDV_SWITCH) {
+ fprintf(stderr, "HEAP DEBUG: heap temporarily switched from "
+ HEAPID_FMT " to " HEAPID_FMT ".\n", h->heap_id, new->heap_id);
+ }
+#endif
+ heaps = new;
+ fheap = NULL;
+ unqueue_signals();
+
+ return h;
+}
+
+/* save states of zsh heaps */
+
+/**/
+mod_export void
+pushheap(void)
+{
+ Heap h;
+ Heapstack hs;
+
+ queue_signals();
+
+#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
+ h_push++;
+#endif
+
+ for (h = heaps; h; h = h->next) {
+ DPUTS(!h->used && h->next, "BUG: empty heap");
+ hs = (Heapstack) zalloc(sizeof(*hs));
+ hs->next = h->sp;
+ h->sp = hs;
+ hs->used = h->used;
+#ifdef ZSH_HEAP_DEBUG
+ hs->heap_id = h->heap_id;
+ h->heap_id = new_heap_id();
+ if (heap_debug_verbosity & HDV_PUSH) {
+ fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT " pushed, new id is "
+ HEAPID_FMT ".\n",
+ hs->heap_id, h->heap_id);
+ }
+#endif
+ }
+ unqueue_signals();
+}
+
+/* reset heaps to previous state */
+
+/**/
+mod_export void
+freeheap(void)
+{
+ Heap h, hn, hl = NULL;
+
+ queue_signals();
+
+#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
+ h_free++;
+#endif
+
+ /*
+ * When pushheap() is called, it sweeps over the entire heaps list of
+ * arenas and marks every one of them with the amount of free space in
+ * that arena at that moment. zhalloc() is then allowed to grab bits
+ * out of any of those arenas that have free space.
+ *
+ * Whenever fheap is NULL here, the loop below sweeps back over the
+ * entire heap list again, resetting the free space in every arena to
+ * the amount stashed by pushheap() and finding the arena with the most
+ * free space to optimize zhalloc()'s next search. When there's a lot
+ * of stuff already on the heap, this is an enormous amount of work,
+ * and performance goes to hell.
+ *
+ * Therefore, we defer freeing the most recently allocated arena until
+ * we reach popheap().
+ *
+ * However, if the arena to which fheap points is unused, we want to
+ * reclaim space in earlier arenas, so we have no choice but to do the
+ * sweep for a new fheap.
+ */
+ if (fheap && !fheap->sp)
+ fheap = NULL; /* We used to do this unconditionally */
+ /*
+ * In other cases, either fheap is already correct, or it has never
+ * been set and this loop will do it, or it'll be reset from scratch
+ * on the next popheap(). So all that's needed here is to pick up
+ * the scan wherever the last pass [or the last popheap()] left off.
+ */
+ for (h = (fheap ? fheap : heaps); h; h = hn) {
+ hn = h->next;
+ if (h->sp) {
+#ifdef ZSH_MEM_DEBUG
+#ifdef ZSH_VALGRIND
+ VALGRIND_MAKE_MEM_UNDEFINED((char *)arena(h) + h->sp->used,
+ h->used - h->sp->used);
+#endif
+ memset(arena(h) + h->sp->used, 0xff, h->used - h->sp->used);
+#endif
+ h->used = h->sp->used;
+ if (!fheap) {
+ if (h->used < ARENA_SIZEOF(h))
+ fheap = h;
+ } else if (ARENA_SIZEOF(h) - h->used >
+ ARENA_SIZEOF(fheap) - fheap->used)
+ fheap = h;
+ hl = h;
+#ifdef ZSH_HEAP_DEBUG
+ /*
+ * As the free makes the heap invalid, give it a new
+ * identifier. We're not popping it, so don't use
+ * the one in the heap stack.
+ */
+ {
+ Heapid new_id = new_heap_id();
+ if (heap_debug_verbosity & HDV_FREE) {
+ fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT
+ " freed, new id is " HEAPID_FMT ".\n",
+ h->heap_id, new_id);
+ }
+ h->heap_id = new_id;
+ }
+#endif
+#ifdef ZSH_VALGRIND
+ VALGRIND_MEMPOOL_TRIM((char *)h, (char *)arena(h), h->used);
+#endif
+ } else {
+ if (fheap == h)
+ fheap = NULL;
+ if (h->next) {
+ /* We want to cut this out of the arena list if we can */
+ if (h == heaps)
+ hl = heaps = h->next;
+ else if (hl && hl->next == h)
+ hl->next = h->next;
+ else {
+ DPUTS(hl, "hl->next != h when freeing");
+ hl = h;
+ continue;
+ }
+ h->next = NULL;
+ } else {
+ /* Leave an empty arena at the end until popped */
+ h->used = 0;
+ fheap = hl = h;
+ break;
+ }
+#ifdef USE_MMAP
+ munmap((void *) h, h->size);
+#else
+ zfree(h, HEAPSIZE);
+#endif
+#ifdef ZSH_VALGRIND
+ VALGRIND_DESTROY_MEMPOOL((char *)h);
+#endif
+ }
+ }
+ if (hl)
+ hl->next = NULL;
+ else
+ heaps = fheap = NULL;
+
+ unqueue_signals();
+}
+
+/* reset heap to previous state and destroy state information */
+
+/**/
+mod_export void
+popheap(void)
+{
+ Heap h, hn, hl = NULL;
+ Heapstack hs;
+
+ queue_signals();
+
+#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
+ h_pop++;
+#endif
+
+ fheap = NULL;
+ for (h = heaps; h; h = hn) {
+ hn = h->next;
+ if ((hs = h->sp)) {
+ h->sp = hs->next;
+#ifdef ZSH_MEM_DEBUG
+#ifdef ZSH_VALGRIND
+ VALGRIND_MAKE_MEM_UNDEFINED((char *)arena(h) + hs->used,
+ h->used - hs->used);
+#endif
+ memset(arena(h) + hs->used, 0xff, h->used - hs->used);
+#endif
+ h->used = hs->used;
+#ifdef ZSH_HEAP_DEBUG
+ if (heap_debug_verbosity & HDV_POP) {
+ fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT
+ " popped, old heap was " HEAPID_FMT ".\n",
+ h->heap_id, hs->heap_id);
+ }
+ h->heap_id = hs->heap_id;
+#endif
+#ifdef ZSH_VALGRIND
+ VALGRIND_MEMPOOL_TRIM((char *)h, (char *)arena(h), h->used);
+#endif
+ if (!fheap) {
+ if (h->used < ARENA_SIZEOF(h))
+ fheap = h;
+ } else if (ARENA_SIZEOF(h) - h->used >
+ ARENA_SIZEOF(fheap) - fheap->used)
+ fheap = h;
+ zfree(hs, sizeof(*hs));
+
+ hl = h;
+ } else {
+ if (h->next) {
+ /* We want to cut this out of the arena list if we can */
+ if (h == heaps)
+ hl = heaps = h->next;
+ else if (hl && hl->next == h)
+ hl->next = h->next;
+ else {
+ DPUTS(hl, "hl->next != h when popping");
+ hl = h;
+ continue;
+ }
+ h->next = NULL;
+ } else if (hl == h) /* This is the last arena of all */
+ hl = NULL;
+#ifdef USE_MMAP
+ munmap((void *) h, h->size);
+#else
+ zfree(h, HEAPSIZE);
+#endif
+#ifdef ZSH_VALGRIND
+ VALGRIND_DESTROY_MEMPOOL((char *)h);
+#endif
+ }
+ }
+ if (hl)
+ hl->next = NULL;
+ else
+ heaps = NULL;
+
+ unqueue_signals();
+}
+
+#ifdef USE_MMAP
+/*
+ * Utility function to allocate a heap area of at least *n bytes.
+ * *n will be rounded up to the next page boundary.
+ */
+static Heap
+mmap_heap_alloc(size_t *n)
+{
+ Heap h;
+ static size_t pgsz = 0;
+
+ if (!pgsz) {
+
+#ifdef _SC_PAGESIZE
+ pgsz = sysconf(_SC_PAGESIZE); /* SVR4 */
+#else
+# ifdef _SC_PAGE_SIZE
+ pgsz = sysconf(_SC_PAGE_SIZE); /* HPUX */
+# else
+ pgsz = getpagesize();
+# endif
+#endif
+
+ pgsz--;
+ }
+ *n = (*n + pgsz) & ~pgsz;
+ h = (Heap) mmap(NULL, *n, PROT_READ | PROT_WRITE,
+ MMAP_FLAGS, -1, 0);
+ if (h == ((Heap) -1)) {
+ zerr("fatal error: out of heap memory");
+ exit(1);
+ }
+
+ return h;
+}
+#endif
+
+/* check whether a pointer is within a memory pool */
+
+/**/
+mod_export void *
+zheapptr(void *p)
+{
+ Heap h;
+ queue_signals();
+ for (h = heaps; h; h = h->next)
+ if ((char *)p >= arena(h) &&
+ (char *)p + H_ISIZE < arena(h) + ARENA_SIZEOF(h))
+ break;
+ unqueue_signals();
+ return (h ? p : 0);
+}
+
+/* allocate memory from the current memory pool */
+
+/**/
+mod_export void *
+zhalloc(size_t size)
+{
+ Heap h, hp = NULL;
+ size_t n;
+#ifdef ZSH_VALGRIND
+ size_t req_size = size;
+
+ if (size == 0)
+ return NULL;
+#endif
+
+ size = (size + H_ISIZE - 1) & ~(H_ISIZE - 1);
+
+ queue_signals();
+
+#if defined(ZSH_MEM) && defined(ZSH_MEM_DEBUG)
+ h_m[size < (1024 * H_ISIZE) ? (size / H_ISIZE) : 1024]++;
+#endif
+
+ /* find a heap with enough free space */
+
+ /*
+ * This previously assigned:
+ * h = ((fheap && ARENA_SIZEOF(fheap) >= (size + fheap->used))
+ * ? fheap : heaps);
+ * but we think that nothing upstream of fheap has more free space,
+ * so why start over at heaps just because fheap has too little?
+ */
+ for (h = (fheap ? fheap : heaps); h; h = h->next) {
+ hp = h;
+ if (ARENA_SIZEOF(h) >= (n = size + h->used)) {
+ void *ret;
+
+ h->used = n;
+ ret = arena(h) + n - size;
+ unqueue_signals();
+#ifdef ZSH_HEAP_DEBUG
+ last_heap_id = h->heap_id;
+ if (heap_debug_verbosity & HDV_ALLOC) {
+ fprintf(stderr, "HEAP DEBUG: allocated memory from heap "
+ HEAPID_FMT ".\n", h->heap_id);
+ }
+#endif
+#ifdef ZSH_VALGRIND
+ VALGRIND_MEMPOOL_ALLOC((char *)h, (char *)ret, req_size);
+#endif
+ return ret;
+ }
+ }
+ {
+ /* not found, allocate new heap */
+#if defined(ZSH_MEM) && !defined(USE_MMAP)
+ static int called = 0;
+ void *foo = called ? (void *)malloc(HEAPFREE) : NULL;
+ /* tricky, see above */
+#endif
+
+ n = HEAP_ARENA_SIZE > size ? HEAPSIZE : size + sizeof(*h);
+
+#ifdef USE_MMAP
+ h = mmap_heap_alloc(&n);
+#else
+ h = (Heap) zalloc(n);
+#endif
+
+#if defined(ZSH_MEM) && !defined(USE_MMAP)
+ if (called)
+ zfree(foo, HEAPFREE);
+ called = 1;
+#endif
+
+ h->size = n;
+ h->used = size;
+ h->next = NULL;
+ h->sp = NULL;
+#ifdef ZSH_HEAP_DEBUG
+ h->heap_id = new_heap_id();
+ if (heap_debug_verbosity & HDV_CREATE) {
+ fprintf(stderr, "HEAP DEBUG: create new heap " HEAPID_FMT ".\n",
+ h->heap_id);
+ }
+#endif
+#ifdef ZSH_VALGRIND
+ VALGRIND_CREATE_MEMPOOL((char *)h, 0, 0);
+ VALGRIND_MAKE_MEM_NOACCESS((char *)arena(h),
+ n - ((char *)arena(h)-(char *)h));
+ VALGRIND_MEMPOOL_ALLOC((char *)h, (char *)arena(h), req_size);
+#endif
+
+ DPUTS(hp && hp->next, "failed to find end of chain in zhalloc");
+ if (hp)
+ hp->next = h;
+ else
+ heaps = h;
+ fheap = h;
+
+ unqueue_signals();
+#ifdef ZSH_HEAP_DEBUG
+ last_heap_id = h->heap_id;
+ if (heap_debug_verbosity & HDV_ALLOC) {
+ fprintf(stderr, "HEAP DEBUG: allocated memory from heap "
+ HEAPID_FMT ".\n", h->heap_id);
+ }
+#endif
+ return arena(h);
+ }
+}
+
+/**/
+mod_export void *
+hrealloc(char *p, size_t old, size_t new)
+{
+ Heap h, ph;
+
+#ifdef ZSH_VALGRIND
+ size_t new_req = new;
+#endif
+
+ old = (old + H_ISIZE - 1) & ~(H_ISIZE - 1);
+ new = (new + H_ISIZE - 1) & ~(H_ISIZE - 1);
+
+ if (old == new)
+ return p;
+ if (!old && !p)
+#ifdef ZSH_VALGRIND
+ return zhalloc(new_req);
+#else
+ return zhalloc(new);
+#endif
+
+ /* find the heap with p */
+
+ queue_signals();
+ for (h = heaps, ph = NULL; h; ph = h, h = h->next)
+ if (p >= arena(h) && p < arena(h) + ARENA_SIZEOF(h))
+ 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 the end of the old chunk is before the used pointer,
+ * more memory has been zhalloc'ed afterwards.
+ * We can't tell if that's still in use, obviously, since
+ * that's the whole point of heap memory.
+ * We have no choice other than to grab some more memory
+ * somewhere else and copy in the old stuff.
+ */
+ if (p + old < arena(h) + h->used) {
+ if (new > old) {
+#ifdef ZSH_VALGRIND
+ char *ptr = (char *) zhalloc(new_req);
+#else
+ char *ptr = (char *) zhalloc(new);
+#endif
+ memcpy(ptr, p, old);
+#ifdef ZSH_MEM_DEBUG
+ memset(p, 0xff, old);
+#endif
+#ifdef ZSH_VALGRIND
+ VALGRIND_MEMPOOL_FREE((char *)h, (char *)p);
+ /*
+ * zhalloc() marked h,ptr,new as an allocation so we don't
+ * need to do that here.
+ */
+#endif
+ unqueue_signals();
+ return ptr;
+ } else {
+#ifdef ZSH_VALGRIND
+ VALGRIND_MEMPOOL_FREE((char *)h, (char *)p);
+ if (p) {
+ VALGRIND_MEMPOOL_ALLOC((char *)h, (char *)p,
+ new_req);
+ VALGRIND_MAKE_MEM_DEFINED((char *)h, (char *)p);
+ }
+#endif
+ unqueue_signals();
+ return new ? p : NULL;
+ }
+ }
+
+ DPUTS(p + old != arena(h) + h->used, "BUG: hrealloc more than allocated");
+
+ /*
+ * We now know there's nothing afterwards in the heap, now see if
+ * there's nothing before. Then we can reallocate the whole thing.
+ * Otherwise, we need to keep the stuff at the start of the heap,
+ * then allocate a new one too; this is handled below. (This will
+ * guarantee we occupy a full heap next time round, provided we
+ * don't use the heap for anything else.)
+ */
+ if (p == arena(h)) {
+#ifdef ZSH_HEAP_DEBUG
+ Heapid heap_id = h->heap_id;
+#endif
+ /*
+ * Zero new seems to be a special case saying we've finished
+ * with the specially reallocated memory, see scanner() in glob.c.
+ */
+ if (!new) {
+ if (ph)
+ ph->next = h->next;
+ else
+ heaps = h->next;
+ fheap = NULL;
+#ifdef USE_MMAP
+ munmap((void *) h, h->size);
+#else
+ zfree(h, HEAPSIZE);
+#endif
+#ifdef ZSH_VALGRIND
+ VALGRIND_DESTROY_MEMPOOL((char *)h);
+#endif
+ unqueue_signals();
+ return NULL;
+ }
+ if (new > ARENA_SIZEOF(h)) {
+ Heap hnew;
+ /*
+ * Not enough memory in this heap. Allocate a new
+ * one of sufficient size.
+ *
+ * To avoid this happening too often, allocate
+ * chunks in multiples of HEAPSIZE.
+ * (Historical note: there didn't used to be any
+ * point in this since we didn't consistently record
+ * the allocated size of the heap, but now we do.)
+ */
+ size_t n = (new + sizeof(*h) + HEAPSIZE);
+ n -= n % HEAPSIZE;
+ fheap = NULL;
+
+#ifdef USE_MMAP
+ {
+ /*
+ * I don't know any easy portable way of requesting
+ * a mmap'd segment be extended, so simply allocate
+ * a new one and copy.
+ */
+ hnew = mmap_heap_alloc(&n);
+ /* Copy the entire heap, header (with next pointer) included */
+ memcpy(hnew, h, h->size);
+ munmap((void *)h, h->size);
+ }
+#else
+ hnew = (Heap) realloc(h, n);
+#endif
+#ifdef ZSH_VALGRIND
+ VALGRIND_MEMPOOL_FREE((char *)h, p);
+ VALGRIND_DESTROY_MEMPOOL((char *)h);
+ VALGRIND_CREATE_MEMPOOL((char *)hnew, 0, 0);
+ VALGRIND_MEMPOOL_ALLOC((char *)hnew, (char *)arena(hnew),
+ new_req);
+ VALGRIND_MAKE_MEM_DEFINED((char *)hnew, (char *)arena(hnew));
+#endif
+ h = hnew;
+
+ h->size = n;
+ if (ph)
+ ph->next = h;
+ else
+ heaps = h;
+ }
+#ifdef ZSH_VALGRIND
+ else {
+ VALGRIND_MEMPOOL_FREE((char *)h, (char *)p);
+ VALGRIND_MEMPOOL_ALLOC((char *)h, (char *)p, new_req);
+ VALGRIND_MAKE_MEM_DEFINED((char *)h, (char *)p);
+ }
+#endif
+ h->used = new;
+#ifdef ZSH_HEAP_DEBUG
+ h->heap_id = heap_id;
+#endif
+ unqueue_signals();
+ return arena(h);
+ }
+#ifndef USE_MMAP
+ DPUTS(h->used > ARENA_SIZEOF(h), "BUG: hrealloc at invalid address");
+#endif
+ if (h->used + (new - old) <= ARENA_SIZEOF(h)) {
+ h->used += new - old;
+ unqueue_signals();
+#ifdef ZSH_VALGRIND
+ VALGRIND_MEMPOOL_FREE((char *)h, (char *)p);
+ VALGRIND_MEMPOOL_ALLOC((char *)h, (char *)p, new_req);
+ VALGRIND_MAKE_MEM_DEFINED((char *)h, (char *)p);
+#endif
+ return p;
+ } else {
+ char *t = zhalloc(new);
+ memcpy(t, p, old > new ? new : old);
+ h->used -= old;
+#ifdef ZSH_MEM_DEBUG
+ memset(p, 0xff, old);
+#endif
+#ifdef ZSH_VALGRIND
+ VALGRIND_MEMPOOL_FREE((char *)h, (char *)p);
+ /* t already marked as allocated by zhalloc() */
+#endif
+ unqueue_signals();
+ return t;
+ }
+}
+
+/**/
+#ifdef ZSH_HEAP_DEBUG
+/*
+ * Check if heap_id is the identifier of a currently valid heap,
+ * including any heap buried on the stack, or of permanent memory.
+ * Return 0 if so, else 1.
+ *
+ * This gets confused by use of switch_heaps(). That's because so do I.
+ */
+
+/**/
+mod_export int
+memory_validate(Heapid heap_id)
+{
+ Heap h;
+ Heapstack hs;
+ LinkNode node;
+
+ if (heap_id == HEAPID_PERMANENT)
+ return 0;
+
+ queue_signals();
+ for (h = heaps; h; h = h->next) {
+ if (h->heap_id == heap_id) {
+ unqueue_signals();
+ return 0;
+ }
+ for (hs = heaps->sp; hs; hs = hs->next) {
+ if (hs->heap_id == heap_id) {
+ unqueue_signals();
+ return 0;
+ }
+ }
+ }
+
+ if (heaps_saved) {
+ for (node = firstnode(heaps_saved); node; incnode(node)) {
+ for (h = (Heap)getdata(node); h; h = h->next) {
+ if (h->heap_id == heap_id) {
+ unqueue_signals();
+ return 0;
+ }
+ for (hs = heaps->sp; hs; hs = hs->next) {
+ if (hs->heap_id == heap_id) {
+ unqueue_signals();
+ return 0;
+ }
+ }
+ }
+ }
+ }
+
+ unqueue_signals();
+ return 1;
+}
+/**/
+#endif
+
+/* allocate memory from the current memory pool and clear it */
+
+/**/
+mod_export void *
+hcalloc(size_t size)
+{
+ void *ptr;
+
+ ptr = zhalloc(size);
+ memset(ptr, 0, size);
+ return ptr;
+}
+
+/* allocate permanent memory */
+
+/**/
+mod_export void *
+zalloc(size_t size)
+{
+ void *ptr;
+
+ if (!size)
+ size = 1;
+ queue_signals();
+ if (!(ptr = (void *) malloc(size))) {
+ zerr("fatal error: out of memory");
+ exit(1);
+ }
+ unqueue_signals();
+
+ return ptr;
+}
+
+/**/
+mod_export void *
+zshcalloc(size_t size)
+{
+ void *ptr = zalloc(size);
+ if (!size)
+ size = 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. */
+
+/**/
+mod_export void *
+zrealloc(void *ptr, size_t size)
+{
+ queue_signals();
+ if (ptr) {
+ if (size) {
+ /* Do normal realloc */
+ if (!(ptr = (void *) realloc(ptr, size))) {
+ zerr("fatal error: out of memory");
+ exit(1);
+ }
+ unqueue_signals();
+ return ptr;
+ }
+ else
+ /* If ptr is not NULL, but size is zero, *
+ * then object pointed to is freed. */
+ free(ptr);
+
+ ptr = NULL;
+ } else {
+ /* If ptr is NULL, then behave like malloc */
+ if (!(ptr = (void *) malloc(size))) {
+ zerr("fatal error: out of memory");
+ exit(1);
+ }
+ }
+ unqueue_signals();
+
+ return ptr;
+}
+
+/**/
+#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(union mem_align), which will
+ probably be 64-bit as it is the longer of zlong or double. 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 */
+#ifdef PAD_64_BIT
+ /* dummy to make this 64-bit aligned */
+ struct m_shdr *dummy;
+#endif
+};
+
+struct m_hdr {
+ zlong len; /* length of memory block */
+#if defined(PAD_64_BIT) && !defined(ZSH_64_BIT_TYPE)
+ /* either 1 or 2 zlong's, whichever makes up 64 bits. */
+ zlong dummy1;
+#endif
+ 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 */
+ zlong used; /* if block of small blocks: number of used
+ blocks */
+#if defined(PAD_64_BIT) && !defined(ZSH_64_BIT_TYPE)
+ zlong dummy2;
+#endif
+};
+
+
+/* alignment for memory blocks */
+
+#define M_ALIGN (sizeof(union mem_align))
+
+/* 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))
+#if defined(PAD_64_BIT) && !defined(ZSH_64_BIT_TYPE)
+# define M_ISIZE (2*sizeof(zlong))
+#else
+# define M_ISIZE (sizeof(zlong))
+#endif
+#define M_MIN (2 * M_ISIZE)
+
+/* M_FREE is the number of bytes that have to be free before memory is
+ * given back to the system
+ * M_KEEP is the number of bytes that will be kept when memory is given
+ * back; note that this has to be less than M_FREE
+ * M_ALLOC is the number of extra bytes to request from the system */
+
+#define M_FREE 32768
+#define M_KEEP 16384
+#define M_ALLOC M_KEEP
+
+/* 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()
+ calculates 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 128
+#define M_SLEN(M) ((M)->len / M_SNUM)
+#if defined(PAD_64_BIT) && !defined(ZSH_64_BIT_TYPE)
+/* Include the dummy in the alignment */
+#define M_SBLEN(S) ((S) * M_SNUM + sizeof(struct m_shdr *) + \
+ 2*sizeof(zlong) + sizeof(struct m_hdr *))
+#define M_BSLEN(S) (((S) - sizeof(struct m_shdr *) - \
+ 2*sizeof(zlong) - sizeof(struct m_hdr *)) / M_SNUM)
+#else
+#define M_SBLEN(S) ((S) * M_SNUM + sizeof(struct m_shdr *) + \
+ sizeof(zlong) + sizeof(struct m_hdr *))
+#define M_BSLEN(S) (((S) - sizeof(struct m_shdr *) - \
+ sizeof(zlong) - sizeof(struct m_hdr *)) / M_SNUM)
+#endif
+#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;
+#ifndef USE_MMAP
+ struct heap *h, *hp, *hf = NULL, *hfp = NULL;
+#endif
+
+ /* some systems want malloc to return the highest valid address plus one
+ if it is called with an argument of zero.
+
+ TODO: really? Suppose we allocate more memory, so
+ that this is now in bounds, then a more rational application
+ that thinks it can free() anything it malloc'ed, even
+ of zero length, calls free for it? Aren't we in big
+ trouble? Wouldn't it be safer just to allocate some
+ memory anyway?
+
+ If the above comment is really correct, then at least
+ we need to check in free() if we're freeing memory
+ at m_high.
+ */
+
+ if (!size)
+#if 1
+ size = 1;
+#else
+ return (MALLOC_RET_T) m_high;
+#endif
+
+ 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 with 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);
+
+#ifndef USE_MMAP
+
+ /* 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);
+ }
+#endif
+ if (!m) {
+ long nal;
+ /* no matching free block was found, we have to request new
+ memory from the system */
+ n = (size + M_HSIZE + M_ALLOC + m_pgsz - 1) & ~(m_pgsz - 1);
+
+ if (((char *)(m = (struct m_hdr *)sbrk(n))) == ((char *)-1)) {
+ DPUTS1(1, "MEM: allocation error at sbrk, size %L.", n);
+ unqueue_signals();
+ return NULL;
+ }
+ if ((nal = ((long)(char *)m) & (M_ALIGN-1))) {
+ if ((char *)sbrk(M_ALIGN - nal) == (char *)-1) {
+ DPUTS(1, "MEM: allocation error at sbrk.");
+ unqueue_signals();
+ return NULL;
+ }
+ m = (struct m_hdr *) ((char *)m + (M_ALIGN - nal));
+ }
+ /* 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' */
+
+/**/
+mod_export 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 + M_FREE) {
+ long n = (m_lfree->len - M_MIN - M_KEEP) & ~(m_pgsz - 1);
+
+ m_lfree->len -= n;
+#ifdef HAVE_BRK
+ if (brk(m_high -= n) == -1) {
+#else
+ m_high -= n;
+ if (sbrk(-n) == (void *)-1) {
+#endif /* HAVE_BRK */
+ 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) */
+
+/**/
+mod_export 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), *mt;
+ char *r;
+ int i, l = 0;
+
+ /* some system..., see above */
+ if (!p && size) {
+ queue_signals();
+ r = malloc(size);
+ unqueue_signals();
+ return (MALLOC_RET_T) r;
+ }
+
+ /* 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 (mt = m_small[i];
+ mt && (((char *)mt) > ((char *)p) ||
+ (((char *)mt) + mt->len) < ((char *)p));
+ 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;
+
+ /*
+ * use realloc() (with a NULL `p` argument it behaves exactly the same
+ * as malloc() does) to prevent an infinite loop caused by sibling-call
+ * optimizations (the malloc() call would otherwise be replaced by an
+ * unconditional branch back to line 1719 ad infinitum).
+ */
+ r = realloc(NULL, l);
+
+ memset(r, 0, l);
+
+ return (MALLOC_RET_T) r;
+}
+
+#ifdef ZSH_MEM_DEBUG
+
+/**/
+int
+bin_mem(char *name, char **argv, Options 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, to, cu;
+
+ queue_signals();
+ if (OPT_ISSET(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 (OPT_ISSET(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 (OPT_ISSET(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. The last field gives the accumulated number of\n");
+ printf("bytes for all sizes.\n");
+ }
+ printf("\nsize\tmalloc\tfree\tdiff\ttotal\tcum\n");
+ for (i = 0, cu = 0; i < 1024; i++)
+ if (m_m[i] || m_f[i]) {
+ to = (long) i * M_ISIZE * (m_m[i] - m_f[i]);
+ printf("%ld\t%d\t%d\t%d\t%ld\t%ld\n",
+ (long)i * M_ISIZE, m_m[i], m_f[i], m_m[i] - m_f[i],
+ to, (cu += to));
+ }
+
+ 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 (OPT_ISSET(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 separately 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("\tseparately 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\t\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", (long)(M_SNUM - ms->used),
+ (long)ms->used,
+ (long)(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, (long)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 (OPT_ISSET(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) ", (long)(M_SNUM - m->used),
+ (long)m->used);
+ if (!((++ii) & 7))
+ printf("\n\t");
+ }
+ putchar('\n');
+ }
+ if (OPT_ISSET(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 (OPT_ISSET(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]);
+
+ unqueue_signals();
+ return 0;
+}
+
+#endif
+
+/**/
+#else /* not ZSH_MEM */
+
+/**/
+mod_export void
+zfree(void *p, UNUSED(int sz))
+{
+ free(p);
+}
+
+/**/
+mod_export void
+zsfree(char *p)
+{
+ free(p);
+}
+
+/**/
+#endif
diff --git a/dotfiles/system/.zsh/modules/Src/mkbltnmlst.sh b/dotfiles/system/.zsh/modules/Src/mkbltnmlst.sh
new file mode 100644
index 0000000..c4611d8
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/mkbltnmlst.sh
@@ -0,0 +1,116 @@
+#! /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=.
+CFMOD=${CFMOD-$srcdir/../config.modules}
+
+bin_mods="`grep ' link=static' $CFMOD | sed -e '/^#/d' \
+-e 's/ .*/ /' -e 's/^name=/ /'`"
+
+x_mods="`grep ' load=yes' $CFMOD | sed -e '/^#/d' -e '/ link=no/d' \
+-e 's/ .*/ /' -e 's/^name=/ /'`"
+
+trap "rm -f $1; exit 1" 1 2 15
+
+exec > $1
+
+for x_mod in $x_mods; do
+ modfile="`grep '^name='$x_mod' ' $CFMOD | sed -e 's/^.* modfile=//' \
+ -e 's/ .*//'`"
+ if test "x$modfile" = x; then
+ echo >&2 "WARNING: no name for \`$x_mod' in $CFMOD (ignored)"
+ continue
+ fi
+ case "$bin_mods" in
+ *" $x_mod "*)
+ echo "/* linked-in known module \`$x_mod' */"
+ linked=yes
+ ;;
+ *)
+ echo "#ifdef DYNAMIC"
+ echo "/* non-linked-in known module \`$x_mod' */"
+ linked=no
+ esac
+ unset moddeps autofeatures autofeatures_emu
+ . $srcdir/../$modfile
+ if test "x$autofeatures" != x; then
+ if test "x$autofeatures_emu" != x; then
+ echo " {"
+ echo " char *zsh_features[] = { "
+ for feature in $autofeatures; do
+ echo " \"$feature\","
+ done
+ echo " NULL"
+ echo " }; "
+ echo " char *emu_features[] = { "
+ for feature in $autofeatures_emu; do
+ echo " \"$feature\","
+ done
+ echo " NULL"
+ echo " }; "
+ echo " autofeatures(\"zsh\", \"$x_mod\","
+ echo " EMULATION(EMULATE_ZSH) ? zsh_features : emu_features,"
+ echo " 0, 1);"
+ echo " }"
+ else
+ echo " if (EMULATION(EMULATE_ZSH)) {"
+ echo " char *features[] = { "
+ for feature in $autofeatures; do
+ echo " \"$feature\","
+ done
+ echo " NULL"
+ echo " }; "
+ echo " autofeatures(\"zsh\", \"$x_mod\", features, 0, 1);"
+ echo " }"
+ fi
+ fi
+ for dep in $moddeps; do
+ echo " add_dep(\"$x_mod\", \"$dep\");"
+ done
+ test "x$linked" = xno && echo "#endif"
+done
+
+echo
+done_mods=" "
+for bin_mod in $bin_mods; do
+ q_bin_mod=`echo $bin_mod | sed 's,Q,Qq,g;s,_,Qu,g;s,/,Qs,g'`
+ modfile="`grep '^name='$bin_mod' ' $CFMOD | sed -e 's/^.* modfile=//' \
+ -e 's/ .*//'`"
+ echo "/* linked-in module \`$bin_mod' */"
+ unset moddeps
+ . $srcdir/../$modfile
+ for dep in $moddeps; do
+ # This assumes there are no circular dependencies in the builtin
+ # modules. Better ordering of config.modules would be necessary
+ # to enforce stricter dependency checking.
+ case $bin_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 " {"
+ echo " extern int setup_${q_bin_mod} _((Module));"
+ echo " extern int boot_${q_bin_mod} _((Module));"
+ echo " extern int features_${q_bin_mod} _((Module,char***));"
+ echo " extern int enables_${q_bin_mod} _((Module,int**));"
+ echo " extern int cleanup_${q_bin_mod} _((Module));"
+ echo " extern int finish_${q_bin_mod} _((Module));"
+ echo
+ echo " register_module(\"$bin_mod\","
+ echo " setup_${q_bin_mod},"
+ echo " features_${q_bin_mod},"
+ echo " enables_${q_bin_mod},"
+ echo " boot_${q_bin_mod},"
+ echo " cleanup_${q_bin_mod}, finish_${q_bin_mod});"
+ echo " }"
+ done_mods="$done_mods$bin_mod "
+done
diff --git a/dotfiles/system/.zsh/modules/Src/mkmakemod.sh b/dotfiles/system/.zsh/modules/Src/mkmakemod.sh
new file mode 100644
index 0000000..140bf70
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/mkmakemod.sh
@@ -0,0 +1,468 @@
+#!/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:
+#
+# name name of this module
+# moddeps modules on which this module depends (default none)
+# nozshdep non-empty indicates no dependence on the `zsh/main' pseudo-module
+# alwayslink if non-empty, always link the module into the executable
+# autofeatures features defined by the module, for autoloading
+# autofeatures_emu As autofeatures, but for non-zsh emulation modes
+# objects .o files making up this module (*must* be defined)
+# proto .syms 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 it 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 .epro (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 the .epros
+# 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
+
+ dir_top=`echo $the_subdir | sed 's,[^/][^/]*,..,g'`
+
+ trap "rm -f $the_subdir/${the_makefile}.in; exit 1" 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 = ${dir_top}"
+ echo "subdir = ${the_subdir}"
+ echo
+
+ bin_mods=`grep link=static ./config.modules | \
+ sed -e '/^#/d' -e 's/ .*/ /' -e 's/^name=/ /'`
+ dyn_mods="`grep link=dynamic ./config.modules | \
+ sed -e '/^#/d' -e 's/ .*/ /' -e 's/^name=/ /'`"
+ module_list="${bin_mods}${dyn_mods}"
+
+ if grep '^#define DYNAMIC ' config.h >/dev/null; then
+ is_dynamic=true
+ else
+ is_dynamic=false
+ fi
+
+ here_mddnames=
+ all_subdirs=
+ all_modobjs=
+ all_modules=
+ all_mdds=
+ all_mdhs=
+ all_proto=
+ lastsub=//
+ for module in $module_list; do
+ modfile="`grep '^name='$module' ' ./config.modules | \
+ sed -e 's/^.* modfile=//' -e 's/ .*//'`"
+ case $modfile in
+ $the_subdir/$lastsub/*) ;;
+ $the_subdir/*/*)
+ lastsub=`echo $modfile | sed 's,^'$the_subdir'/,,;s,/[^/]*$,,'`
+ case "$all_subdirs " in
+ *" $lastsub "* ) ;;
+ * )
+ all_subdirs="$all_subdirs $lastsub"
+ ;;
+ esac
+ ;;
+ $the_subdir/*)
+ mddname=`echo $modfile | sed 's,^.*/,,;s,\.mdd$,,'`
+ here_mddnames="$here_mddnames $mddname"
+ build=$is_dynamic
+ case $is_dynamic@$bin_mods in
+ *" $module "*)
+ build=true
+ all_modobjs="$all_modobjs modobjs.${mddname}" ;;
+ true@*)
+ all_modules="$all_modules ${mddname}.\$(DL_EXT)" ;;
+ esac
+ all_mdds="$all_mdds ${mddname}.mdd"
+ $build && all_mdhs="$all_mdhs ${mddname}.mdh"
+ $build && all_proto="$all_proto proto.${mddname}"
+ ;;
+ esac
+ done
+ 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 "ENTRYOBJ = \$(dir_src)/modentry..o"
+ echo "NNTRYOBJ ="
+ echo "ENTRYOPT = -emodentry"
+ echo "NNTRYOPT ="
+ 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=
+ other_exports=
+ remote_exports=
+ other_modules=
+ remote_modules=
+ for mddname in $here_mddnames; do
+
+ unset name moddeps nozshdep alwayslink hasexport
+ unset autofeatures autofeatures_emu
+ unset objects proto headers hdrdeps otherincs
+ . $top_srcdir/$the_subdir/${mddname}.mdd
+ q_name=`echo $name | sed 's,Q,Qq,g;s,_,Qu,g;s,/,Qs,g'`
+ test -n "${moddeps+set}" || moddeps=
+ test -n "$nozshdep" || moddeps="$moddeps zsh/main"
+ test -n "${proto+set}" ||
+ proto=`echo $objects '' | sed 's,\.o ,.syms ,g'`
+
+ dobjects=`echo $objects '' | sed 's,\.o ,..o ,g'`
+ modhdeps=
+ mododeps=
+ exportdeps=
+ imports=
+ q_moddeps=
+ for dep in $moddeps; do
+ depfile="`grep '^name='$dep' ' ./config.modules | \
+ sed -e 's/^.* modfile=//' -e 's/ .*//'`"
+ q_dep=`echo $dep | sed 's,Q,Qq,g;s,_,Qu,g;s,/,Qs,g'`
+ q_moddeps="$q_moddeps $q_dep"
+ eval `echo $depfile | sed 's,/\([^/]*\)\.mdd$,;depbase=\1,;s,^,loc=,'`
+ case "$binmod" in
+ *" $dep "* )
+ dep=zsh/main
+ ;;
+ esac
+
+ case $the_subdir in
+ $loc)
+ mdh="${depbase}.mdh"
+ export="${depbase}.export"
+ case "$dep" in
+ zsh/main )
+ mdll="\$(dir_top)/Src/libzsh-\$(VERSION).\$(DL_EXT) "
+ ;;
+ * )
+ mdll="${depbase}.\$(DL_EXT) "
+ ;;
+ esac
+ ;;
+ $loc/*)
+ mdh="\$(dir_top)/$loc/${depbase}.mdh"
+ case "$other_mdhs " in
+ *" $mdh "*) ;;
+ *) other_mdhs="$other_mdhs $mdh" ;;
+ esac
+ export="\$(dir_top)/$loc/${depbase}.export"
+ case "$other_exports " in
+ *" $export "*) ;;
+ *) other_exports="$other_exports $export" ;;
+ esac
+ case "$dep" in
+ zsh/main )
+ mdll="\$(dir_top)/Src/libzsh-\$(VERSION).\$(DL_EXT) "
+ ;;
+ * )
+ mdll="\$(dir_top)/$loc/${depbase}.\$(DL_EXT) "
+ ;;
+ esac
+ case "$other_modules " in
+ *" $mdll "*) ;;
+ *) other_modules="$other_modules $mdll" ;;
+ esac
+ ;;
+ *)
+ mdh="\$(dir_top)/$loc/${depbase}.mdh"
+ case "$remote_mdhs " in
+ *" $mdh "*) ;;
+ *) remote_mdhs="$remote_mdhs $mdh" ;;
+ esac
+ export="\$(dir_top)/$loc/${depbase}.export"
+ case "$remote_exports " in
+ *" $export "*) ;;
+ *) remote_exports="$remote_exports $export" ;;
+ esac
+ case "$dep" in
+ zsh/main )
+ mdll="\$(dir_top)/Src/libzsh-\$(VERSION).\$(DL_EXT) "
+ ;;
+ * )
+ mdll="\$(dir_top)/$loc/${depbase}.\$(DL_EXT) "
+ ;;
+ esac
+ case "$remote_modules " in
+ *" $mdll "*) ;;
+ *) remote_modules="$remote_modules $mdll" ;;
+ esac
+ ;;
+ esac
+ modhdeps="$modhdeps $mdh"
+ exportdeps="$exportdeps $export"
+ imports="$imports \$(IMPOPT)$export"
+ case "$mododeps " in
+ *" $mdll "* )
+ :
+ ;;
+ * )
+ mododeps="$mododeps $mdll"
+ ;;
+ esac
+ done
+
+ echo "##### ===== DEPENDENCIES GENERATED FROM ${mddname}.mdd ===== #####"
+ echo
+ echo "MODOBJS_${mddname} = $objects"
+ echo "MODDOBJS_${mddname} = $dobjects \$(@E@NTRYOBJ)"
+ echo "SYMS_${mddname} = $proto"
+ echo "EPRO_${mddname} = "`echo $proto '' | sed 's,\.syms ,.epro ,g'`
+ echo "INCS_${mddname} = \$(EPRO_${mddname}) $otherincs"
+ echo "EXPIMP_${mddname} = $imports \$(EXPOPT)$mddname.export"
+ echo "NXPIMP_${mddname} ="
+ echo "LINKMODS_${mddname} = $mododeps"
+ echo "NOLINKMODS_${mddname} = "
+ echo
+ echo "proto.${mddname}: \$(EPRO_${mddname})"
+ echo "\$(SYMS_${mddname}): \$(PROTODEPS)"
+ echo
+ echo "${mddname}.export: \$(SYMS_${mddname})"
+ echo " @( echo '#!'; cat \$(SYMS_${mddname}) | sed -n '/^X/{s/^X//;p;}' | sort -u ) > \$@"
+ echo
+ echo "modobjs.${mddname}: \$(MODOBJS_${mddname})"
+ echo " @echo '' \$(MODOBJS_${mddname}) $modobjs_sed>> \$(dir_src)/stamp-modobjs.tmp"
+ echo
+ if test -z "$alwayslink"; then
+ case " $all_modules" in *" ${mddname}."*)
+ echo "install.modules-here: install.modules.${mddname}"
+ echo "uninstall.modules-here: uninstall.modules.${mddname}"
+ echo
+ ;; esac
+ instsubdir=`echo $name | sed 's,^,/,;s,/[^/]*$,,'`
+ echo "install.modules.${mddname}: ${mddname}.\$(DL_EXT)"
+ echo " \$(SHELL) \$(sdir_top)/mkinstalldirs \$(DESTDIR)\$(MODDIR)${instsubdir}"
+ echo " \$(INSTALL_PROGRAM) \$(STRIPFLAGS) ${mddname}.\$(DL_EXT) \$(DESTDIR)\$(MODDIR)/${name}.\$(DL_EXT)"
+ echo
+ echo "uninstall.modules.${mddname}:"
+ echo " rm -f \$(DESTDIR)\$(MODDIR)/${name}.\$(DL_EXT)"
+ echo
+ echo "${mddname}.\$(DL_EXT): \$(MODDOBJS_${mddname}) ${mddname}.export $exportdeps \$(@LINKMODS@_${mddname})"
+ echo ' rm -f $@'
+ echo " \$(DLLINK) \$(@E@XPIMP_$mddname) \$(@E@NTRYOPT) \$(MODDOBJS_${mddname}) \$(@LINKMODS@_${mddname}) \$(LIBS) "
+ echo
+ fi
+ echo "${mddname}.mdhi: ${mddname}.mdhs \$(INCS_${mddname})"
+ echo " @test -f \$@ || echo 'do not delete this file' > \$@"
+ echo
+ echo "${mddname}.mdhs: ${mddname}.mdd"
+ echo " @\$(MAKE) -f \$(makefile) \$(MAKEDEFS) ${mddname}.mdh.tmp"
+ echo " @if cmp -s ${mddname}.mdh ${mddname}.mdh.tmp; then \\"
+ echo " rm -f ${mddname}.mdh.tmp; \\"
+ echo " echo \"\\\`${mddname}.mdh' is up to date.\"; \\"
+ echo " else \\"
+ echo " mv -f ${mddname}.mdh.tmp ${mddname}.mdh; \\"
+ echo " echo \"Updated \\\`${mddname}.mdh'.\"; \\"
+ echo " fi"
+ echo " echo 'timestamp for ${mddname}.mdh against ${mddname}.mdd' > \$@"
+ echo
+ echo "${mddname}.mdh: ${modhdeps} ${headers} ${hdrdeps} ${mddname}.mdhi"
+ echo " @\$(MAKE) -f \$(makefile) \$(MAKEDEFS) ${mddname}.mdh.tmp"
+ echo " @mv -f ${mddname}.mdh.tmp ${mddname}.mdh"
+ echo " @echo \"Updated \\\`${mddname}.mdh'.\""
+ echo
+ echo "${mddname}.mdh.tmp:"
+ echo " @( \\"
+ echo " echo '#ifndef have_${q_name}_module'; \\"
+ echo " echo '#define have_${q_name}_module'; \\"
+ echo " echo; \\"
+ echo " echo '# ifndef IMPORTING_MODULE_${q_name}'; \\"
+ echo " if test @SHORTBOOTNAMES@ = yes; then \\"
+ echo " echo '# ifndef MODULE'; \\"
+ echo " fi; \\"
+ echo " echo '# define boot_ boot_${q_name}'; \\"
+ echo " echo '# define cleanup_ cleanup_${q_name}'; \\"
+ echo " echo '# define features_ features_${q_name}'; \\"
+ echo " echo '# define enables_ enables_${q_name}'; \\"
+ echo " echo '# define setup_ setup_${q_name}'; \\"
+ echo " echo '# define finish_ finish_${q_name}'; \\"
+ echo " if test @SHORTBOOTNAMES@ = yes; then \\"
+ echo " echo '# endif /* !MODULE */'; \\"
+ echo " fi; \\"
+ echo " echo '# endif /* !IMPORTING_MODULE_${q_name} */'; \\"
+ echo " echo; \\"
+ if test -n "$moddeps"; then (
+ set x $q_moddeps
+ echo " echo '/* Module dependencies */'; \\"
+ for hdep in $modhdeps; do
+ shift
+ echo " echo '# define IMPORTING_MODULE_${1} 1'; \\"
+ echo " echo '# include \"${hdep}\"'; \\"
+ done
+ echo " echo; \\"
+ ) fi
+ if test -n "$headers"; then
+ echo " echo '/* Extra headers for this module */'; \\"
+ echo " for hdr in $headers; do \\"
+ echo " echo '# include \"'\$\$hdr'\"'; \\"
+ echo " done; \\"
+ echo " echo; \\"
+ fi
+ if test -n "$proto"; then
+ echo " echo '# undef mod_import_variable'; \\"
+ echo " echo '# undef mod_import_function'; \\"
+ echo " echo '# if defined(IMPORTING_MODULE_${q_name}) && defined(MODULE)'; \\"
+ echo " echo '# define mod_import_variable @MOD_IMPORT_VARIABLE@'; \\"
+ echo " echo '# define mod_import_function @MOD_IMPORT_FUNCTION@'; \\"
+ echo " echo '# else'; \\"
+ echo " echo '# define mod_import_function'; \\"
+ echo " echo '# define mod_import_variable'; \\"
+ echo " echo '# endif /* IMPORTING_MODULE_${q_name} && MODULE */'; \\"
+ echo " for epro in \$(EPRO_${mddname}); do \\"
+ echo " echo '# include \"'\$\$epro'\"'; \\"
+ echo " done; \\"
+ echo " echo '# undef mod_import_variable'; \\"
+ echo " echo '# define mod_import_variable'; \\"
+ echo " echo '# undef mod_import_variable'; \\"
+ echo " echo '# define mod_import_variable'; \\"
+ echo " echo '# ifndef mod_export'; \\"
+ echo " echo '# define mod_export @MOD_EXPORT@'; \\"
+ echo " echo '# endif /* mod_export */'; \\"
+ echo " echo; \\"
+ fi
+ echo " echo '#endif /* !have_${q_name}_module */'; \\"
+ echo " ) > \$@"
+ echo
+ echo "\$(MODOBJS_${mddname}) \$(MODDOBJS_${mddname}): ${mddname}.mdh"
+ sed -e '/^ *: *<< *\\Make *$/,/^Make$/!d' \
+ -e 's/^ *: *<< *\\Make *$//; /^Make$/d' \
+ < $top_srcdir/$the_subdir/${mddname}.mdd
+ echo
+
+ done
+
+ if test -n "$remote_mdhs$other_mdhs$remote_exports$other_exports$remote_modules$other_modules"; 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}:" | sed 's,^ ,,'
+ echo " false # A. should only happen with make -n"
+ echo
+ fi
+ for export in $remote_exports; do
+ echo "$export: FORCE"
+ echo " @cd @%@ && \$(MAKE) \$(MAKEDEFS) @%@$export"
+ echo
+ done | sed 's,^\(.*\)@%@\(.*\)@%@\(.*\)/\([^/]*\)$,\1\3\2\4,'
+ if test -n "$other_exports"; then
+ echo "${other_exports}:" | sed 's,^ ,,'
+ echo " false # B. should only happen with make -n"
+ echo
+ fi
+ for mdll in $remote_modules; do
+ echo "$mdll: FORCE"
+ echo " @cd @%@ && \$(MAKE) \$(MAKEDEFS) @%@$mdll"
+ echo
+ done | sed 's,^\(.*\)@%@\(.*\)@%@\(.*\)/\([^/]*\)$,\1\3\2\4,'
+ if test -n "$other_modules"; then
+ echo "${other_modules}:" | sed 's,^ ,,'
+ echo " false # C. 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}; exit 1" 1 2 15
+
+ ${CONFIG_SHELL-/bin/sh} ./config.status \
+ --file=$the_subdir/${the_makefile}:$the_subdir/${the_makefile}.in ||
+ exit 1
+fi
+
+exit 0
diff --git a/dotfiles/system/.zsh/modules/Src/module.c b/dotfiles/system/.zsh/modules/Src/module.c
new file mode 100644
index 0000000..4ae7831
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/module.c
@@ -0,0 +1,3641 @@
+/*
+ * 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"
+
+/*
+ * List of linked-in modules.
+ * This is set up at boot and remains for the life of the shell;
+ * entries do not appear in "zmodload" listings.
+ */
+
+/**/
+LinkList linkedmodules;
+
+/* $module_path ($MODULE_PATH) */
+
+/**/
+char **module_path;
+
+/* Hash of modules */
+
+/**/
+mod_export HashTable modulestab;
+
+/*
+ * Bit flags passed as the "flags" argument of a autofeaturefn_t.
+ * Used in other places, such as the final argument to
+ * do_module_features().
+ */
+enum {
+ /*
+ * `-i' option: ignore errors pertaining to redefinitions,
+ * or indicate to do_module_features() that it should be
+ * silent.
+ */
+ FEAT_IGNORE = 0x0001,
+ /* If a condition, condition is infix rather than prefix */
+ FEAT_INFIX = 0x0002,
+ /*
+ * Enable all features in the module when autoloading.
+ * This is the traditional zmodload -a behaviour;
+ * zmodload -Fa only enables features explicitly marked for
+ * autoloading.
+ */
+ FEAT_AUTOALL = 0x0004,
+ /*
+ * Remove feature: alternative to "-X:NAME" used if
+ * X is passed separately from NAME.
+ */
+ FEAT_REMOVE = 0x0008,
+ /*
+ * For do_module_features(). Check that any autoloads
+ * for the module are actually provided.
+ */
+ FEAT_CHECKAUTO = 0x0010
+};
+
+/*
+ * All functions to add or remove autoloadable features fit
+ * the following prototype.
+ *
+ * "module" is the name of the module.
+ *
+ * "feature" is the name of the feature, minus any type prefix.
+ *
+ * "flags" is a set of the bits above.
+ *
+ * The return value is 0 for success, -1 for failure with no
+ * message needed, and one of the following to indicate the calling
+ * function should print a message:
+ *
+ * 1: failed to add [type] `[feature]'
+ * 2: [feature]: no such [type]
+ * 3: [feature]: [type] is already defined
+ */
+typedef int (*autofeaturefn_t)(const char *module, const char *feature,
+ int flags);
+
+/* Bits in the second argument to find_module. */
+enum {
+ /*
+ * Resolve any aliases to the underlying module.
+ */
+ FINDMOD_ALIASP = 0x0001,
+ /*
+ * Create an element for the module in the list if
+ * it is not found.
+ */
+ FINDMOD_CREATE = 0x0002,
+};
+
+static void
+freemodulenode(HashNode hn)
+{
+ Module m = (Module) hn;
+
+ if (m->node.flags & MOD_ALIAS)
+ zsfree(m->u.alias);
+ zsfree(m->node.nam);
+ if (m->autoloads)
+ freelinklist(m->autoloads, freestr);
+ if (m->deps)
+ freelinklist(m->deps, freestr);
+ zfree(m, sizeof(*m));
+}
+
+/* flags argument to printmodulenode */
+enum {
+ /* -L flag, output zmodload commands */
+ PRINTMOD_LIST = 0x0001,
+ /* -e flag */
+ PRINTMOD_EXIST = 0x0002,
+ /* -A flag */
+ PRINTMOD_ALIAS = 0x0004,
+ /* -d flag */
+ PRINTMOD_DEPS = 0x0008,
+ /* -F flag */
+ PRINTMOD_FEATURES = 0x0010,
+ /* -l flag in combination with -L flag */
+ PRINTMOD_LISTALL = 0x0020,
+ /* -a flag */
+ PRINTMOD_AUTO = 0x0040
+};
+
+/* Scan function for printing module details */
+
+static void
+printmodulenode(HashNode hn, int flags)
+{
+ Module m = (Module)hn;
+ /*
+ * If we check for a module loaded under an alias, we
+ * need the name of the alias. We can use it in other
+ * cases, too.
+ */
+ const char *modname = m->node.nam;
+
+ if (flags & PRINTMOD_DEPS) {
+ /*
+ * Print the module's dependencies.
+ */
+ LinkNode n;
+
+ if (!m->deps)
+ return;
+
+ if (flags & PRINTMOD_LIST) {
+ printf("zmodload -d ");
+ if (modname[0] == '-')
+ fputs("-- ", stdout);
+ quotedzputs(modname, stdout);
+ } else {
+ nicezputs(modname, stdout);
+ putchar(':');
+ }
+ for (n = firstnode(m->deps); n; incnode(n)) {
+ putchar(' ');
+ if (flags & PRINTMOD_LIST)
+ quotedzputs((char *) getdata(n), stdout);
+ else
+ nicezputs((char *) getdata(n), stdout);
+ }
+ } else if (flags & PRINTMOD_EXIST) {
+ /*
+ * Just print the module name, provided the module is
+ * present under an alias or otherwise.
+ */
+ if (m->node.flags & MOD_ALIAS) {
+ if (!(flags & PRINTMOD_ALIAS) ||
+ !(m = find_module(m->u.alias, FINDMOD_ALIASP, NULL)))
+ return;
+ }
+ if (!m->u.handle || (m->node.flags & MOD_UNLOAD))
+ return;
+ nicezputs(modname, stdout);
+ } else if (m->node.flags & MOD_ALIAS) {
+ /*
+ * Normal listing, but for aliases.
+ */
+ if (flags & PRINTMOD_LIST) {
+ printf("zmodload -A ");
+ if (modname[0] == '-')
+ fputs("-- ", stdout);
+ quotedzputs(modname, stdout);
+ putchar('=');
+ quotedzputs(m->u.alias, stdout);
+ } else {
+ nicezputs(modname, stdout);
+ fputs(" -> ", stdout);
+ nicezputs(m->u.alias, stdout);
+ }
+ } else if (m->u.handle || (flags & PRINTMOD_AUTO)) {
+ /*
+ * Loaded module.
+ */
+ if (flags & PRINTMOD_LIST) {
+ /*
+ * List with -L format. Possibly we are printing
+ * features, either enables or autoloads.
+ */
+ char **features = NULL;
+ int *enables = NULL;
+ if (flags & PRINTMOD_AUTO) {
+ if (!m->autoloads || !firstnode(m->autoloads))
+ return;
+ } else if (flags & PRINTMOD_FEATURES) {
+ if (features_module(m, &features) ||
+ enables_module(m, &enables) ||
+ !*features)
+ return;
+ }
+ printf("zmodload ");
+ if (flags & PRINTMOD_AUTO) {
+ fputs("-Fa ", stdout);
+ } else if (features)
+ fputs("-F ", stdout);
+ if(modname[0] == '-')
+ fputs("-- ", stdout);
+ quotedzputs(modname, stdout);
+ if (flags & PRINTMOD_AUTO) {
+ LinkNode an;
+ for (an = firstnode(m->autoloads); an; incnode(an)) {
+ putchar(' ');
+ quotedzputs((char *)getdata(an), stdout);
+ }
+ } else if (features) {
+ const char *f;
+ while ((f = *features++)) {
+ int on = *enables++;
+ if (flags & PRINTMOD_LISTALL)
+ printf(" %s", on ? "+" : "-");
+ else if (!on)
+ continue;
+ else
+ putchar(' ');
+ quotedzputs(f, stdout);
+ }
+ }
+ } else /* -l */
+ nicezputs(modname, stdout);
+ } else
+ return;
+ putchar('\n');
+}
+
+/**/
+HashTable
+newmoduletable(int size, char const *name)
+{
+ HashTable ht;
+ ht = newhashtable(size, name, NULL);
+
+ ht->hash = hasher;
+ ht->emptytable = emptyhashtable;
+ ht->filltable = NULL;
+ ht->cmpnodes = strcmp;
+ ht->addnode = addhashnode;
+ /* DISABLED is not supported */
+ ht->getnode = gethashnode2;
+ ht->getnode2 = gethashnode2;
+ ht->removenode = removehashnode;
+ ht->disablenode = NULL;
+ ht->enablenode = NULL;
+ ht->freenode = freemodulenode;
+ ht->printnode = printmodulenode;
+
+ return ht;
+}
+
+/************************************************************************
+ * zsh/main standard module functions
+ ************************************************************************/
+
+/* The `zsh/main' 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
+setup_(UNUSED(Module m))
+{
+ return 0;
+}
+
+/**/
+int
+features_(UNUSED(Module m), UNUSED(char ***features))
+{
+ /*
+ * There are lots and lots of features, but they're not
+ * handled here.
+ */
+ return 1;
+}
+
+/**/
+int
+enables_(UNUSED(Module m), UNUSED(int **enables))
+{
+ return 1;
+}
+
+/**/
+int
+boot_(UNUSED(Module m))
+{
+ return 0;
+}
+
+/**/
+int
+cleanup_(UNUSED(Module m))
+{
+ return 0;
+}
+
+/**/
+int
+finish_(UNUSED(Module m))
+{
+ return 0;
+}
+
+
+/************************************************************************
+ * Module utility functions
+ ************************************************************************/
+
+/* This registers a builtin module. */
+
+/**/
+void
+register_module(char *n, Module_void_func setup,
+ Module_features_func features,
+ Module_enables_func enables,
+ Module_void_func boot,
+ Module_void_func cleanup,
+ Module_void_func finish)
+{
+ Linkedmod m;
+
+ m = (Linkedmod) zalloc(sizeof(*m));
+
+ m->name = ztrdup(n);
+ m->setup = setup;
+ m->features = features;
+ m->enables = enables;
+ m->boot = boot;
+ m->cleanup = cleanup;
+ m->finish = finish;
+
+ zaddlinknode(linkedmodules, m);
+}
+
+/* Check if a module is linked in. */
+
+/**/
+Linkedmod
+module_linked(char const *name)
+{
+ LinkNode node;
+
+ for (node = firstnode(linkedmodules); node; incnode(node))
+ if (!strcmp(((Linkedmod) getdata(node))->name, name))
+ return (Linkedmod) getdata(node);
+
+ return NULL;
+}
+
+
+/************************************************************************
+ * Support for the various feature types.
+ * First, builtins.
+ ************************************************************************/
+
+/* 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. */
+
+/**/
+static int
+addbuiltin(Builtin b)
+{
+ Builtin bn = (Builtin) builtintab->getnode2(builtintab, b->node.nam);
+ if (bn && (bn->node.flags & BINF_ADDED))
+ return 1;
+ if (bn)
+ builtintab->freenode(builtintab->removenode(builtintab, b->node.nam));
+ builtintab->addnode(builtintab, b->node.nam, b);
+ return 0;
+}
+
+/* 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. */
+
+/**/
+static int
+add_autobin(const char *module, const char *bnam, int flags)
+{
+ Builtin bn;
+ int ret;
+
+ bn = zshcalloc(sizeof(*bn));
+ bn->node.nam = ztrdup(bnam);
+ bn->optstr = ztrdup(module);
+ if (flags & FEAT_AUTOALL)
+ bn->node.flags |= BINF_AUTOALL;
+ if ((ret = addbuiltin(bn))) {
+ builtintab->freenode(&bn->node);
+ if (!(flags & FEAT_IGNORE))
+ return 1;
+ }
+ return 0;
+}
+
+/* Remove the builtin added previously by addbuiltin(). Returns *
+ * zero on succes and -1 if there is no builtin with that name. */
+
+/**/
+int
+deletebuiltin(const char *nam)
+{
+ Builtin bn;
+
+ bn = (Builtin) builtintab->removenode(builtintab, nam);
+ if (!bn)
+ return -1;
+ builtintab->freenode(&bn->node);
+ return 0;
+}
+
+/* Remove an autoloaded added by add_autobin */
+
+/**/
+static int
+del_autobin(UNUSED(const char *module), const char *bnam, int flags)
+{
+ Builtin bn = (Builtin) builtintab->getnode2(builtintab, bnam);
+ if (!bn) {
+ if(!(flags & FEAT_IGNORE))
+ return 2;
+ } else if (bn->node.flags & BINF_ADDED) {
+ if (!(flags & FEAT_IGNORE))
+ return 3;
+ } else
+ deletebuiltin(bnam);
+
+ return 0;
+}
+
+/*
+ * Manipulate a set of builtins. This should be called
+ * via setfeatureenables() (or, usually, via the next level up,
+ * handlefeatures()).
+ *
+ * "nam" is the name of the calling code builtin, probably "zmodload".
+ *
+ * "binl" is the builtin table containing an array of "size" builtins.
+ *
+ * "e" is either NULL, in which case all builtins in the
+ * table are removed, or else an array corresponding to "binl"
+ * with a 1 for builtins that are to be added and a 0 for builtins
+ * that are to be removed. Any builtin already in the appropriate
+ * state is left alone.
+ *
+ * Returns 1 on any error, 0 for success. The recommended way
+ * of handling errors is to compare the enables passed down
+ * with the set retrieved after the error to find what failed.
+ */
+
+/**/
+static int
+setbuiltins(char const *nam, Builtin binl, int size, int *e)
+{
+ int ret = 0, n;
+
+ for(n = 0; n < size; n++) {
+ Builtin b = &binl[n];
+ if (e && *e++) {
+ if (b->node.flags & BINF_ADDED)
+ continue;
+ if (addbuiltin(b)) {
+ zwarnnam(nam,
+ "name clash when adding builtin `%s'", b->node.nam);
+ ret = 1;
+ } else {
+ b->node.flags |= BINF_ADDED;
+ }
+ } else {
+ if (!(b->node.flags & BINF_ADDED))
+ continue;
+ if (deletebuiltin(b->node.nam)) {
+ zwarnnam(nam, "builtin `%s' already deleted", b->node.nam);
+ ret = 1;
+ } else {
+ b->node.flags &= ~BINF_ADDED;
+ }
+ }
+ }
+ return ret;
+}
+
+/*
+ * 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 0 on success, 1 for any failure.
+ *
+ * This should not be used from a module; instead, use handlefeatures().
+ */
+
+/**/
+mod_export int
+addbuiltins(char const *nam, Builtin binl, int size)
+{
+ int ret = 0, n;
+
+ for(n = 0; n < size; n++) {
+ Builtin b = &binl[n];
+ if(b->node.flags & BINF_ADDED)
+ continue;
+ if(addbuiltin(b)) {
+ zwarnnam(nam, "name clash when adding builtin `%s'", b->node.nam);
+ ret = 1;
+ } else {
+ b->node.flags |= BINF_ADDED;
+ }
+ }
+ return ret;
+}
+
+
+/************************************************************************
+ * Function wrappers.
+ ************************************************************************/
+
+/* The list of function wrappers defined. */
+
+/**/
+FuncWrap wrappers;
+
+/* This adds a definition for a wrapper. Return value is one in case of *
+ * error and zero if all went fine. */
+
+/**/
+mod_export int
+addwrapper(Module m, FuncWrap w)
+{
+ FuncWrap p, q;
+
+ /*
+ * We can't add a wrapper to an alias, since it's supposed
+ * to behave identically to the resolved module. This shouldn't
+ * happen since we usually add wrappers when a real module is
+ * loaded.
+ */
+ if (m->node.flags & MOD_ALIAS)
+ return 1;
+
+ if (w->flags & WRAPF_ADDED)
+ return 1;
+ for (p = wrappers, q = NULL; p; q = p, p = p->next);
+ if (q)
+ q->next = w;
+ else
+ wrappers = w;
+ w->next = NULL;
+ w->flags |= WRAPF_ADDED;
+ w->module = m;
+
+ return 0;
+}
+
+/* This removes the given wrapper definition from the list. Returned is *
+ * one in case of error and zero otherwise. */
+
+/**/
+mod_export int
+deletewrapper(Module m, FuncWrap w)
+{
+ FuncWrap p, q;
+
+ if (m->node.flags & MOD_ALIAS)
+ return 1;
+
+ if (w->flags & WRAPF_ADDED) {
+ for (p = wrappers, q = NULL; p && p != w; q = p, p = p->next);
+
+ if (p) {
+ if (q)
+ q->next = p->next;
+ else
+ wrappers = p->next;
+ p->flags &= ~WRAPF_ADDED;
+
+ return 0;
+ }
+ }
+ return 1;
+}
+
+
+/************************************************************************
+ * Conditions.
+ ************************************************************************/
+
+/* The list of module-defined conditions. */
+
+/**/
+mod_export Conddef condtab;
+
+/* This gets a condition definition with the given name. The first *
+ * argument says if we have to look for an infix condition. The last *
+ * argument is non-zero if we should autoload modules if needed. */
+
+/**/
+Conddef
+getconddef(int inf, const char *name, int autol)
+{
+ Conddef p;
+ int f = 1;
+ char *lookup, *s;
+
+ /* detokenize the Dash to the form encoded in lookup tables */
+ lookup = dupstring(name);
+ if (!lookup)
+ return NULL;
+ for (s = lookup; *s != '\0'; s++) {
+ if (*s == Dash)
+ *s = '-';
+ }
+
+ do {
+ for (p = condtab; p; p = p->next) {
+ if ((!!inf == !!(p->flags & CONDF_INFIX)) &&
+ !strcmp(lookup, p->name))
+ break;
+ }
+ if (autol && p && p->module) {
+ /*
+ * This is a definition for an autoloaded condition; load the
+ * module if we haven't tried that already.
+ */
+ if (f) {
+ (void)ensurefeature(p->module,
+ (p->flags & CONDF_INFIX) ? "C:" : "c:",
+ (p->flags & CONDF_AUTOALL) ? NULL : lookup);
+ f = 0;
+ p = NULL;
+ } else {
+ deleteconddef(p);
+ return NULL;
+ }
+ } else
+ break;
+ } while (!p);
+
+ return p;
+}
+
+/*
+ * This adds the given condition definition. The return value is zero on *
+ * success and 1 on failure. If there is a matching definition for an *
+ * autoloaded condition, it is removed.
+ *
+ * This is used for adding both an autoload definition or
+ * a real condition. In the latter case the caller is responsible
+ * for setting the CONDF_ADDED flag.
+ */
+
+/**/
+static int
+addconddef(Conddef c)
+{
+ Conddef p = getconddef((c->flags & CONDF_INFIX), c->name, 0);
+
+ if (p) {
+ if (!p->module || (p->flags & CONDF_ADDED))
+ return 1;
+ /* There is an autoload definition. */
+
+ deleteconddef(p);
+ }
+ c->next = condtab;
+ condtab = c;
+ return 0;
+}
+
+/* This removes the given condition definition from the list(s). If this *
+ * is a definition for a autoloaded condition, the memory is freed. */
+
+/**/
+int
+deleteconddef(Conddef c)
+{
+ Conddef p, q;
+
+ for (p = condtab, q = NULL; p && p != c; q = p, p = p->next);
+
+ if (p) {
+ if (q)
+ q->next = p->next;
+ else
+ condtab = p->next;
+
+ if (p->module) {
+ /* autoloaded, free it */
+ zsfree(p->name);
+ zsfree(p->module);
+ zfree(p, sizeof(*p));
+ }
+ return 0;
+ }
+ return -1;
+}
+
+/*
+ * Add or remove sets of conditions. The interface is
+ * identical to setbuiltins().
+ */
+
+/**/
+static int
+setconddefs(char const *nam, Conddef c, int size, int *e)
+{
+ int ret = 0;
+
+ while (size--) {
+ if (e && *e++) {
+ if (c->flags & CONDF_ADDED) {
+ c++;
+ continue;
+ }
+ if (addconddef(c)) {
+ zwarnnam(nam, "name clash when adding condition `%s'",
+ c->name);
+ ret = 1;
+ } else {
+ c->flags |= CONDF_ADDED;
+ }
+ } else {
+ if (!(c->flags & CONDF_ADDED)) {
+ c++;
+ continue;
+ }
+ if (deleteconddef(c)) {
+ zwarnnam(nam, "condition `%s' already deleted", c->name);
+ ret = 1;
+ } else {
+ c->flags &= ~CONDF_ADDED;
+ }
+ }
+ c++;
+ }
+ return ret;
+}
+
+/* This adds a definition for autoloading a module for a condition. */
+
+/**/
+static int
+add_autocond(const char *module, const char *cnam, int flags)
+{
+ Conddef c;
+
+ c = (Conddef) zalloc(sizeof(*c));
+
+ c->name = ztrdup(cnam);
+ c->flags = ((flags & FEAT_INFIX) ? CONDF_INFIX : 0);
+ if (flags & FEAT_AUTOALL)
+ c->flags |= CONDF_AUTOALL;
+ c->module = ztrdup(module);
+
+ if (addconddef(c)) {
+ zsfree(c->name);
+ zsfree(c->module);
+ zfree(c, sizeof(*c));
+
+ if (!(flags & FEAT_IGNORE))
+ return 1;
+ }
+ return 0;
+}
+
+/* Remove a condition added with add_autocond */
+
+/**/
+static int
+del_autocond(UNUSED(const char *modnam), const char *cnam, int flags)
+{
+ Conddef cd = getconddef((flags & FEAT_INFIX) ? 1 : 0, cnam, 0);
+
+ if (!cd) {
+ if (!(flags & FEAT_IGNORE)) {
+ return 2;
+ }
+ } else if (cd->flags & CONDF_ADDED) {
+ if (!(flags & FEAT_IGNORE))
+ return 3;
+ } else
+ deleteconddef(cd);
+
+ return 0;
+}
+
+/************************************************************************
+ * Hook functions.
+ ************************************************************************/
+
+/* This list of hook functions defined. */
+
+/**/
+Hookdef hooktab;
+
+/* Find a hook definition given the name. */
+
+/**/
+Hookdef
+gethookdef(char *n)
+{
+ Hookdef p;
+
+ for (p = hooktab; p; p = p->next)
+ if (!strcmp(n, p->name))
+ return p;
+ return NULL;
+}
+
+/* This adds the given hook definition. The return value is zero on *
+ * success and 1 on failure. */
+
+/**/
+int
+addhookdef(Hookdef h)
+{
+ if (gethookdef(h->name))
+ return 1;
+
+ h->next = hooktab;
+ hooktab = h;
+ h->funcs = znewlinklist();
+
+ return 0;
+}
+
+/*
+ * This adds multiple hook definitions. This is like addbuiltins().
+ * This allows a NULL module because we call it from init.c.
+ */
+
+/**/
+mod_export int
+addhookdefs(Module m, Hookdef h, int size)
+{
+ int ret = 0;
+
+ while (size--) {
+ if (addhookdef(h)) {
+ zwarnnam(m ? m->node.nam : NULL,
+ "name clash when adding hook `%s'", h->name);
+ ret = 1;
+ }
+ h++;
+ }
+ return ret;
+}
+
+/* Delete hook definitions. */
+
+/**/
+int
+deletehookdef(Hookdef h)
+{
+ Hookdef p, q;
+
+ for (p = hooktab, q = NULL; p && p != h; q = p, p = p->next);
+
+ if (!p)
+ return 1;
+
+ if (q)
+ q->next = p->next;
+ else
+ hooktab = p->next;
+ freelinklist(p->funcs, NULL);
+ return 0;
+}
+
+/* Remove multiple hook definitions. */
+
+/**/
+mod_export int
+deletehookdefs(UNUSED(Module m), Hookdef h, int size)
+{
+ int ret = 0;
+
+ while (size--) {
+ if (deletehookdef(h))
+ ret = 1;
+ h++;
+ }
+ return ret;
+}
+
+/* Add a function to a hook. */
+
+/**/
+int
+addhookdeffunc(Hookdef h, Hookfn f)
+{
+ zaddlinknode(h->funcs, (void *) f);
+
+ return 0;
+}
+
+/**/
+mod_export int
+addhookfunc(char *n, Hookfn f)
+{
+ Hookdef h = gethookdef(n);
+
+ if (h)
+ return addhookdeffunc(h, f);
+ return 1;
+}
+
+/* Delete a function from a hook. */
+
+/**/
+int
+deletehookdeffunc(Hookdef h, Hookfn f)
+{
+ LinkNode p;
+
+ for (p = firstnode(h->funcs); p; incnode(p))
+ if (f == (Hookfn) getdata(p)) {
+ remnode(h->funcs, p);
+ return 0;
+ }
+ return 1;
+}
+
+/* Delete a hook. */
+
+/**/
+mod_export int
+deletehookfunc(char *n, Hookfn f)
+{
+ Hookdef h = gethookdef(n);
+
+ if (h)
+ return deletehookdeffunc(h, f);
+ return 1;
+}
+
+/* Run the function(s) for a hook. */
+
+/**/
+mod_export int
+runhookdef(Hookdef h, void *d)
+{
+ if (empty(h->funcs)) {
+ if (h->def)
+ return h->def(h, d);
+ return 0;
+ } else if (h->flags & HOOKF_ALL) {
+ LinkNode p;
+ int r;
+
+ for (p = firstnode(h->funcs); p; incnode(p))
+ if ((r = ((Hookfn) getdata(p))(h, d)))
+ return r;
+ if (h->def)
+ return h->def(h, d);
+ return 0;
+ } else
+ return ((Hookfn) getdata(lastnode(h->funcs)))(h, d);
+}
+
+
+
+/************************************************************************
+ * Shell parameters.
+ ************************************************************************/
+
+/*
+ * Check that it's possible to add a parameter. This
+ * requires that either there's no parameter already present,
+ * or it's a global parameter marked for autoloading.
+ *
+ * The special status 2 is to indicate it didn't work but
+ * -i was in use so we didn't print a warning.
+ */
+
+static int
+checkaddparam(const char *nam, int opt_i)
+{
+ Param pm;
+
+ if (!(pm = (Param) gethashnode2(paramtab, nam)))
+ return 0;
+
+ if (pm->level || !(pm->node.flags & PM_AUTOLOAD)) {
+ /*
+ * -i suppresses "it's already that way" warnings,
+ * but not "this can't possibly work" warnings, so we print
+ * the message anyway if there's a local parameter blocking
+ * the parameter we want to add, not if there's a
+ * non-autoloadable parameter already there. This
+ * is consistent with the way add_auto* functions work.
+ */
+ if (!opt_i || !pm->level) {
+ zwarn("Can't add module parameter `%s': %s",
+ nam, pm->level ?
+ "local parameter exists" :
+ "parameter already exists");
+ return 1;
+ }
+ return 2;
+ }
+
+ unsetparam_pm(pm, 0, 1);
+ return 0;
+}
+
+/* This adds the given parameter definition. The return value is zero on *
+ * success and 1 on failure. */
+
+/**/
+int
+addparamdef(Paramdef d)
+{
+ Param pm;
+
+ if (checkaddparam(d->name, 0))
+ return 1;
+
+ if (d->getnfn) {
+ if (!(pm = createspecialhash(d->name, d->getnfn,
+ d->scantfn, d->flags)))
+ return 1;
+ }
+ else if (!(pm = createparam(d->name, d->flags)) &&
+ !(pm = (Param) paramtab->getnode(paramtab, d->name)))
+ return 1;
+
+ d->pm = pm;
+ pm->level = 0;
+ if (d->var)
+ pm->u.data = d->var;
+ if (d->var || d->gsu) {
+ /*
+ * If no get/set/unset class, use the appropriate
+ * variable type, else use the one supplied.
+ */
+ switch (PM_TYPE(pm->node.flags)) {
+ case PM_SCALAR:
+ pm->gsu.s = d->gsu ? (GsuScalar)d->gsu : &varscalar_gsu;
+ break;
+
+ case PM_INTEGER:
+ pm->gsu.i = d->gsu ? (GsuInteger)d->gsu : &varinteger_gsu;
+ break;
+
+ case PM_FFLOAT:
+ case PM_EFLOAT:
+ pm->gsu.f = d->gsu;
+ break;
+
+ case PM_ARRAY:
+ pm->gsu.a = d->gsu ? (GsuArray)d->gsu : &vararray_gsu;
+ break;
+
+ case PM_HASHED:
+ /* hashes may behave like standard hashes */
+ if (d->gsu)
+ pm->gsu.h = (GsuHash)d->gsu;
+ break;
+
+ default:
+ unsetparam_pm(pm, 0, 1);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* Delete parameters defined. No error checking yet. */
+
+/**/
+int
+deleteparamdef(Paramdef d)
+{
+ Param pm = (Param) paramtab->getnode(paramtab, d->name);
+
+ if (!pm)
+ return 1;
+ if (pm != d->pm) {
+ /*
+ * See if the parameter has been hidden. If so,
+ * bring it to the front to unset it.
+ */
+ Param prevpm, searchpm;
+ for (prevpm = pm, searchpm = pm->old;
+ searchpm;
+ prevpm = searchpm, searchpm = searchpm->old)
+ if (searchpm == d->pm)
+ break;
+
+ if (!searchpm)
+ return 1;
+
+ paramtab->removenode(paramtab, pm->node.nam);
+ prevpm->old = searchpm->old;
+ searchpm->old = pm;
+ paramtab->addnode(paramtab, searchpm->node.nam, searchpm);
+
+ pm = searchpm;
+ }
+ pm->node.flags = (pm->node.flags & ~PM_READONLY) | PM_REMOVABLE;
+ unsetparam_pm(pm, 0, 1);
+ d->pm = NULL;
+ return 0;
+}
+
+/*
+ * Add or remove sets of parameters. The interface is
+ * identical to setbuiltins().
+ */
+
+/**/
+static int
+setparamdefs(char const *nam, Paramdef d, int size, int *e)
+{
+ int ret = 0;
+
+ while (size--) {
+ if (e && *e++) {
+ if (d->pm) {
+ d++;
+ continue;
+ }
+ if (addparamdef(d)) {
+ zwarnnam(nam, "error when adding parameter `%s'", d->name);
+ ret = 1;
+ }
+ } else {
+ if (!d->pm) {
+ d++;
+ continue;
+ }
+ if (deleteparamdef(d)) {
+ zwarnnam(nam, "parameter `%s' already deleted", d->name);
+ ret = 1;
+ }
+ }
+ d++;
+ }
+ return ret;
+}
+
+/* This adds a definition for autoloading a module for a parameter. */
+
+/**/
+static int
+add_autoparam(const char *module, const char *pnam, int flags)
+{
+ Param pm;
+ int ret;
+
+ queue_signals();
+ if ((ret = checkaddparam(pnam, (flags & FEAT_IGNORE)))) {
+ unqueue_signals();
+ /*
+ * checkaddparam() has already printed a message if one was
+ * needed. If it wasn't owing to the presence of -i, ret is 2;
+ * for consistency with other add_auto* functions we return
+ * status 0 to indicate there's already such a parameter and
+ * we've been told not to worry if so.
+ */
+ return ret == 2 ? 0 : -1;
+ }
+
+ pm = setsparam(dupstring(pnam), ztrdup(module));
+
+ pm->node.flags |= PM_AUTOLOAD;
+ if (flags & FEAT_AUTOALL)
+ pm->node.flags |= PM_AUTOALL;
+ unqueue_signals();
+
+ return 0;
+}
+
+/* Remove a parameter added with add_autoparam() */
+
+/**/
+static int
+del_autoparam(UNUSED(const char *modnam), const char *pnam, int flags)
+{
+ Param pm = (Param) gethashnode2(paramtab, pnam);
+
+ if (!pm) {
+ if (!(flags & FEAT_IGNORE))
+ return 2;
+ } else if (!(pm->node.flags & PM_AUTOLOAD)) {
+ if (!(flags & FEAT_IGNORE))
+ return 3;
+ } else
+ unsetparam_pm(pm, 0, 1);
+
+ return 0;
+}
+
+/************************************************************************
+ * Math functions.
+ ************************************************************************/
+
+/* List of math functions. */
+
+/**/
+MathFunc mathfuncs;
+
+/*
+ * Remove a single math function form the list (utility function).
+ * This does not delete a module math function, that's deletemathfunc().
+ */
+
+/**/
+void
+removemathfunc(MathFunc previous, MathFunc current)
+{
+ if (previous)
+ previous->next = current->next;
+ else
+ mathfuncs = current->next;
+
+ zsfree(current->name);
+ zsfree(current->module);
+ zfree(current, sizeof(*current));
+}
+
+/* Find a math function in the list, handling autoload if necessary. */
+
+/**/
+MathFunc
+getmathfunc(const char *name, int autol)
+{
+ MathFunc p, q = NULL;
+
+ for (p = mathfuncs; p; q = p, p = p->next)
+ if (!strcmp(name, p->name)) {
+ if (autol && p->module && !(p->flags & MFF_USERFUNC)) {
+ char *n = dupstring(p->module);
+ int flags = p->flags;
+
+ removemathfunc(q, p);
+
+ (void)ensurefeature(n, "f:", (flags & MFF_AUTOALL) ? NULL :
+ name);
+
+ p = getmathfunc(name, 0);
+ if (!p) {
+ zerr("autoloading module %s failed to define math function: %s", n, name);
+ }
+ }
+ return p;
+ }
+
+ return NULL;
+}
+
+/* Add a single math function */
+
+/**/
+static int
+addmathfunc(MathFunc f)
+{
+ MathFunc p, q = NULL;
+
+ if (f->flags & MFF_ADDED)
+ return 1;
+
+ for (p = mathfuncs; p; q = p, p = p->next)
+ if (!strcmp(f->name, p->name)) {
+ if (p->module && !(p->flags & MFF_USERFUNC)) {
+ /*
+ * Autoloadable, replace.
+ */
+ removemathfunc(q, p);
+ break;
+ }
+ return 1;
+ }
+
+ f->next = mathfuncs;
+ mathfuncs = f;
+
+ return 0;
+}
+
+/* Delete a single math function */
+
+/**/
+mod_export int
+deletemathfunc(MathFunc f)
+{
+ MathFunc p, q;
+
+ for (p = mathfuncs, q = NULL; p && p != f; q = p, p = p->next);
+
+ if (p) {
+ if (q)
+ q->next = f->next;
+ else
+ mathfuncs = f->next;
+
+ /* the following applies to both unloaded and user-defined functions */
+ if (f->module) {
+ zsfree(f->name);
+ zsfree(f->module);
+ zfree(f, sizeof(*f));
+ } else
+ f->flags &= ~MFF_ADDED;
+
+ return 0;
+ }
+ return -1;
+}
+
+/*
+ * Add or remove sets of math functions. The interface is
+ * identical to setbuiltins().
+ */
+
+/**/
+static int
+setmathfuncs(char const *nam, MathFunc f, int size, int *e)
+{
+ int ret = 0;
+
+ while (size--) {
+ if (e && *e++) {
+ if (f->flags & MFF_ADDED) {
+ f++;
+ continue;
+ }
+ if (addmathfunc(f)) {
+ zwarnnam(nam, "name clash when adding math function `%s'",
+ f->name);
+ ret = 1;
+ } else {
+ f->flags |= MFF_ADDED;
+ }
+ } else {
+ if (!(f->flags & MFF_ADDED)) {
+ f++;
+ continue;
+ }
+ if (deletemathfunc(f)) {
+ zwarnnam(nam, "math function `%s' already deleted", f->name);
+ ret = 1;
+ } else {
+ f->flags &= ~MFF_ADDED;
+ }
+ }
+ f++;
+ }
+ return ret;
+}
+
+/* Add an autoload definition for a math function. */
+
+/**/
+static int
+add_automathfunc(const char *module, const char *fnam, int flags)
+{
+ MathFunc f;
+
+ f = (MathFunc) zalloc(sizeof(*f));
+
+ f->name = ztrdup(fnam);
+ f->module = ztrdup(module);
+ f->flags = 0;
+
+ if (addmathfunc(f)) {
+ zsfree(f->name);
+ zsfree(f->module);
+ zfree(f, sizeof(*f));
+
+ if (!(flags & FEAT_IGNORE))
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Remove a math function added with add_automathfunc() */
+
+/**/
+static int
+del_automathfunc(UNUSED(const char *modnam), const char *fnam, int flags)
+{
+ MathFunc f = getmathfunc(fnam, 0);
+
+ if (!f) {
+ if (!(flags & FEAT_IGNORE))
+ return 2;
+ } else if (f->flags & MFF_ADDED) {
+ if (!(flags & FEAT_IGNORE))
+ return 3;
+ } else
+ deletemathfunc(f);
+
+ return 0;
+}
+
+/************************************************************************
+ * Now support for dynamical loading and the fallback functions
+ * we use for loading if dynamical loading is not available.
+ ************************************************************************/
+
+/**/
+#ifdef DYNAMIC
+
+/**/
+#ifdef AIXDYNAMIC
+
+#include <sys/ldr.h>
+
+static char *dlerrstr[256];
+
+static void *
+load_and_bind(const char *fn)
+{
+ void *ret = (void *) load((char *) fn, L_NOAUTODEFER, NULL);
+
+ if (ret) {
+ Module m;
+ int i, err = loadbind(0, (void *) addbuiltin, ret);
+ for (i = 0; i < modulestab->hsize && !err; i++) {
+ for (m = (Module)modulestab->nodes[i]; m && !err;
+ m = (Module)m->node.next) {
+ if (!(m->node.flags & MOD_ALIAS) &&
+ m->u.handle && !(m->node.flags & MOD_LINKED))
+ err |= loadbind(0, m->u.handle, ret);
+ }
+ }
+
+ if (err) {
+ loadquery(L_GETMESSAGES, dlerrstr, sizeof(dlerrstr));
+ unload(ret);
+ ret = NULL;
+ }
+ } else
+ loadquery(L_GETMESSAGES, dlerrstr, sizeof(dlerrstr));
+
+ return ret;
+}
+
+#define dlopen(X,Y) load_and_bind(X)
+#define dlclose(X) unload(X)
+#define dlerror() (dlerrstr[0])
+#ifndef HAVE_DLERROR
+# define HAVE_DLERROR 1
+#endif
+
+/**/
+#else
+
+#ifdef HAVE_DLFCN_H
+# if defined(HAVE_DL_H) && defined(HPUX10DYNAMIC)
+# include <dl.h>
+# else
+# include <dlfcn.h>
+# endif
+#else
+# ifdef HAVE_DL_H
+# include <dl.h>
+# define RTLD_LAZY BIND_DEFERRED
+# define RTLD_GLOBAL DYNAMIC_PATH
+# else
+# include <sys/types.h>
+# include <nlist.h>
+# include <link.h>
+# endif
+#endif
+
+/**/
+#ifdef HPUX10DYNAMIC
+# define dlopen(file,mode) (void *)shl_load((file), (mode), (long) 0)
+# define dlclose(handle) shl_unload((shl_t)(handle))
+
+static
+void *
+hpux_dlsym(void *handle, char *name)
+{
+ void *sym_addr;
+ if (!shl_findsym((shl_t *)&handle, name, TYPE_UNDEFINED, &sym_addr))
+ return sym_addr;
+ return NULL;
+}
+
+# define dlsym(handle,name) hpux_dlsym(handle,name)
+# ifdef HAVE_DLERROR /* paranoia */
+# undef HAVE_DLERROR
+# endif
+#else
+# ifndef HAVE_DLCLOSE
+# define dlclose(X) ((X), 0)
+# endif
+/**/
+#endif
+
+#ifdef DLSYM_NEEDS_UNDERSCORE
+# define STR_SETUP "_setup_"
+# define STR_FEATURES "_features_"
+# define STR_ENABLES "_enables_"
+# define STR_BOOT "_boot_"
+# define STR_CLEANUP "_cleanup_"
+# define STR_FINISH "_finish_"
+#else /* !DLSYM_NEEDS_UNDERSCORE */
+# define STR_SETUP "setup_"
+# define STR_FEATURES "features_"
+# define STR_ENABLES "enables_"
+# define STR_BOOT "boot_"
+# define STR_CLEANUP "cleanup_"
+# define STR_FINISH "finish_"
+#endif /* !DLSYM_NEEDS_UNDERSCORE */
+
+/**/
+#endif /* !AIXDYNAMIC */
+
+#ifndef RTLD_LAZY
+# define RTLD_LAZY 1
+#endif
+#ifndef RTLD_GLOBAL
+# define RTLD_GLOBAL 0
+#endif
+
+/*
+ * Attempt to load a module. This is the lowest level of
+ * zsh function for dynamical modules. Returns the handle
+ * from the dynamic loader.
+ */
+
+/**/
+static void *
+try_load_module(char const *name)
+{
+ char buf[PATH_MAX + 1];
+ char **pp;
+ void *ret = NULL;
+ int l;
+
+ l = 1 + strlen(name) + 1 + strlen(DL_EXT);
+ for (pp = module_path; !ret && *pp; pp++) {
+ if (l + (**pp ? strlen(*pp) : 1) > PATH_MAX)
+ continue;
+ sprintf(buf, "%s/%s.%s", **pp ? *pp : ".", name, DL_EXT);
+ unmetafy(buf, NULL);
+ if (*buf) /* dlopen(NULL) returns a handle to the main binary */
+ ret = dlopen(buf, RTLD_LAZY | RTLD_GLOBAL);
+ }
+
+ return ret;
+}
+
+/*
+ * Load a module, with option to complain or not.
+ * Returns the handle from the dynamic loader.
+ */
+
+/**/
+static void *
+do_load_module(char const *name, int silent)
+{
+ void *ret;
+
+ ret = try_load_module(name);
+ if (!ret && !silent) {
+#ifdef HAVE_DLERROR
+ char *errstr = dlerror();
+ zwarn("failed to load module `%s': %s", name,
+ errstr ? metafy(errstr, -1, META_HEAPDUP) : "empty module path");
+#else
+ zwarn("failed to load module: %s", name);
+#endif
+ }
+ return ret;
+}
+
+/**/
+#else /* !DYNAMIC */
+
+/*
+ * Dummy loader when no dynamic loading available; always fails.
+ */
+
+/**/
+static void *
+do_load_module(char const *name, int silent)
+{
+ if (!silent)
+ zwarn("failed to load module: %s", name);
+
+ return NULL;
+}
+
+/**/
+#endif /* !DYNAMIC */
+
+/*
+ * Find a module in the list.
+ * flags is a set of bits defined in the enum above.
+ * If namep is set, this is set to point to the last alias value resolved,
+ * even if that module was not loaded. or the module name if no aliases.
+ * Hence this is always the physical module to load in a chain of aliases.
+ * Return NULL if the module named is not stored as a structure, or if we were
+ * resolving aliases and the final module named is not stored as a
+ * structure.
+ */
+/**/
+static Module
+find_module(const char *name, int flags, const char **namep)
+{
+ Module m;
+
+ m = (Module)modulestab->getnode2(modulestab, name);
+ if (m) {
+ if ((flags & FINDMOD_ALIASP) && (m->node.flags & MOD_ALIAS)) {
+ if (namep)
+ *namep = m->u.alias;
+ return find_module(m->u.alias, flags, namep);
+ }
+ if (namep)
+ *namep = m->node.nam;
+ return m;
+ }
+ if (!(flags & FINDMOD_CREATE))
+ return NULL;
+ m = zshcalloc(sizeof(*m));
+ modulestab->addnode(modulestab, ztrdup(name), m);
+ return m;
+}
+
+/*
+ * Unlink and free a module node from the linked list.
+ */
+
+/**/
+static void
+delete_module(Module m)
+{
+ modulestab->removenode(modulestab, m->node.nam);
+
+ modulestab->freenode(&m->node);
+}
+
+/*
+ * Return 1 if a module is fully loaded else zero.
+ * A linked module may be marked as unloaded even though
+ * we can't fully unload it; this returns 0 to try to
+ * make that state transparently like an unloaded module.
+ */
+
+/**/
+mod_export int
+module_loaded(const char *name)
+{
+ Module m;
+
+ return ((m = find_module(name, FINDMOD_ALIASP, NULL)) &&
+ m->u.handle &&
+ !(m->node.flags & MOD_UNLOAD));
+}
+
+/*
+ * Setup and cleanup functions: we don't search for aliases here,
+ * since they should have been resolved before we try to load or unload
+ * the module.
+ */
+
+/**/
+#ifdef DYNAMIC
+
+/**/
+#ifdef AIXDYNAMIC
+
+/**/
+static int
+dyn_setup_module(Module m)
+{
+ return ((int (*)_((int,Module, void*))) m->u.handle)(0, m, NULL);
+}
+
+/**/
+static int
+dyn_features_module(Module m, char ***features)
+{
+ return ((int (*)_((int,Module, void*))) m->u.handle)(4, m, features);
+}
+
+/**/
+static int
+dyn_enables_module(Module m, int **enables)
+{
+ return ((int (*)_((int,Module, void*))) m->u.handle)(5, m, enables);
+}
+
+/**/
+static int
+dyn_boot_module(Module m)
+{
+ return ((int (*)_((int,Module, void*))) m->u.handle)(1, m, NULL);
+}
+
+/**/
+static int
+dyn_cleanup_module(Module m)
+{
+ return ((int (*)_((int,Module, void*))) m->u.handle)(2, m, NULL);
+}
+
+/**/
+static int
+dyn_finish_module(Module m)
+{
+ return ((int (*)_((int,Module,void *))) m->u.handle)(3, m, NULL);
+}
+
+/**/
+#else
+
+static Module_generic_func
+module_func(Module m, char *name)
+{
+#ifdef DYNAMIC_NAME_CLASH_OK
+ return (Module_generic_func) dlsym(m->u.handle, name);
+#else /* !DYNAMIC_NAME_CLASH_OK */
+ VARARR(char, buf, strlen(name) + strlen(m->node.nam)*2 + 1);
+ char const *p;
+ char *q;
+ strcpy(buf, name);
+ q = strchr(buf, 0);
+ for(p = m->node.nam; *p; p++) {
+ if(*p == '/') {
+ *q++ = 'Q';
+ *q++ = 's';
+ } else if(*p == '_') {
+ *q++ = 'Q';
+ *q++ = 'u';
+ } else if(*p == 'Q') {
+ *q++ = 'Q';
+ *q++ = 'q';
+ } else
+ *q++ = *p;
+ }
+ *q = 0;
+ return (Module_generic_func) dlsym(m->u.handle, buf);
+#endif /* !DYNAMIC_NAME_CLASH_OK */
+}
+
+/**/
+static int
+dyn_setup_module(Module m)
+{
+ Module_void_func fn = (Module_void_func)module_func(m, STR_SETUP);
+
+ if (fn)
+ return fn(m);
+ zwarnnam(m->node.nam, "no setup function");
+ return 1;
+}
+
+/**/
+static int
+dyn_features_module(Module m, char ***features)
+{
+ Module_features_func fn =
+ (Module_features_func)module_func(m, STR_FEATURES);
+
+ if (fn)
+ return fn(m, features);
+ /* not a user-visible error if no features function */
+ return 1;
+}
+
+/**/
+static int
+dyn_enables_module(Module m, int **enables)
+{
+ Module_enables_func fn = (Module_enables_func)module_func(m, STR_ENABLES);
+
+ if (fn)
+ return fn(m, enables);
+ /* not a user-visible error if no enables function */
+ return 1;
+}
+
+/**/
+static int
+dyn_boot_module(Module m)
+{
+ Module_void_func fn = (Module_void_func)module_func(m, STR_BOOT);
+
+ if(fn)
+ return fn(m);
+ zwarnnam(m->node.nam, "no boot function");
+ return 1;
+}
+
+/**/
+static int
+dyn_cleanup_module(Module m)
+{
+ Module_void_func fn = (Module_void_func)module_func(m, STR_CLEANUP);
+
+ if(fn)
+ return fn(m);
+ zwarnnam(m->node.nam, "no cleanup function");
+ return 1;
+}
+
+/* Note that this function does more than just calling finish_foo(), *
+ * it really unloads the module. */
+
+/**/
+static int
+dyn_finish_module(Module m)
+{
+ Module_void_func fn = (Module_void_func)module_func(m, STR_FINISH);
+ int r;
+
+ if (fn)
+ r = fn(m);
+ else {
+ zwarnnam(m->node.nam, "no finish function");
+ r = 1;
+ }
+ dlclose(m->u.handle);
+ return r;
+}
+
+/**/
+#endif /* !AIXDYNAMIC */
+
+/**/
+static int
+setup_module(Module m)
+{
+ return ((m->node.flags & MOD_LINKED) ?
+ (m->u.linked->setup)(m) : dyn_setup_module(m));
+}
+
+/**/
+static int
+features_module(Module m, char ***features)
+{
+ return ((m->node.flags & MOD_LINKED) ?
+ (m->u.linked->features)(m, features) :
+ dyn_features_module(m, features));
+}
+
+/**/
+static int
+enables_module(Module m, int **enables)
+{
+ return ((m->node.flags & MOD_LINKED) ?
+ (m->u.linked->enables)(m, enables) :
+ dyn_enables_module(m, enables));
+}
+
+/**/
+static int
+boot_module(Module m)
+{
+ return ((m->node.flags & MOD_LINKED) ?
+ (m->u.linked->boot)(m) : dyn_boot_module(m));
+}
+
+/**/
+static int
+cleanup_module(Module m)
+{
+ return ((m->node.flags & MOD_LINKED) ?
+ (m->u.linked->cleanup)(m) : dyn_cleanup_module(m));
+}
+
+/**/
+static int
+finish_module(Module m)
+{
+ return ((m->node.flags & MOD_LINKED) ?
+ (m->u.linked->finish)(m) : dyn_finish_module(m));
+}
+
+/**/
+#else /* !DYNAMIC */
+
+/**/
+static int
+setup_module(Module m)
+{
+ return ((m->node.flags & MOD_LINKED) ? (m->u.linked->setup)(m) : 1);
+}
+
+/**/
+static int
+features_module(Module m, char ***features)
+{
+ return ((m->node.flags & MOD_LINKED) ? (m->u.linked->features)(m, features)
+ : 1);
+}
+
+/**/
+static int
+enables_module(Module m, int **enables)
+{
+ return ((m->node.flags & MOD_LINKED) ? (m->u.linked->enables)(m, enables)
+ : 1);
+}
+
+/**/
+static int
+boot_module(Module m)
+{
+ return ((m->node.flags & MOD_LINKED) ? (m->u.linked->boot)(m) : 1);
+}
+
+/**/
+static int
+cleanup_module(Module m)
+{
+ return ((m->node.flags & MOD_LINKED) ? (m->u.linked->cleanup)(m) : 1);
+}
+
+/**/
+static int
+finish_module(Module m)
+{
+ return ((m->node.flags & MOD_LINKED) ? (m->u.linked->finish)(m) : 1);
+}
+
+/**/
+#endif /* !DYNAMIC */
+
+
+/************************************************************************
+ * Functions called when manipulating modules
+ ************************************************************************/
+
+/*
+ * Set the features for the module, which must be loaded
+ * by now (though may not be fully set up).
+ *
+ * Return 0 for success, 1 for failure, 2 if some features
+ * couldn't be set by the module itself (non-existent features
+ * are tested here and cause 1 to be returned).
+ */
+
+/**/
+static int
+do_module_features(Module m, Feature_enables enablesarr, int flags)
+{
+ char **features;
+ int ret = 0;
+
+ if (features_module(m, &features) == 0) {
+ /*
+ * Features are supported. If we were passed
+ * a NULL array, enable all features, else
+ * enable only the features listed.
+ * (This may in principle be an empty array,
+ * although that's not very pointful.)
+ */
+ int *enables = NULL;
+ if (enables_module(m, &enables)) {
+ /* If features are supported, enables should be, too */
+ if (!(flags & FEAT_IGNORE))
+ zwarn("error getting enabled features for module `%s'",
+ m->node.nam);
+ return 1;
+ }
+
+ if ((flags & FEAT_CHECKAUTO) && m->autoloads) {
+ /*
+ * Check autoloads are available. Since these
+ * have been requested at some other point, they
+ * don't affect the return status unless something
+ * in enablesstr doesn't work.
+ */
+ LinkNode an, nextn;
+ for (an = firstnode(m->autoloads); an; an = nextn) {
+ char *al = (char *)getdata(an), **ptr;
+ /* careful, we can delete the current node */
+ nextn = nextnode(an);
+ for (ptr = features; *ptr; ptr++)
+ if (!strcmp(al, *ptr))
+ break;
+ if (!*ptr) {
+ char *arg[2];
+ if (!(flags & FEAT_IGNORE))
+ zwarn(
+ "module `%s' has no such feature: `%s': autoload cancelled",
+ m->node.nam, al);
+ /*
+ * This shouldn't happen, so it's not worth optimising
+ * the call to autofeatures...
+ */
+ arg[0] = al = dupstring(al);
+ arg[1] = NULL;
+ (void)autofeatures(NULL, m->node.nam, arg, 0,
+ FEAT_IGNORE|FEAT_REMOVE);
+ /*
+ * don't want to try to enable *that*...
+ * expunge it from the enable string.
+ */
+ if (enablesarr) {
+ Feature_enables fep;
+ for (fep = enablesarr; fep->str; fep++) {
+ char *str = fep->str;
+ if (*str == '+' || *str == '-')
+ str++;
+ if (fep->pat ? pattry(fep->pat, al) :
+ !strcmp(al, str)) {
+ /* can't enable it after all, so return 1 */
+ ret = 1;
+ while (fep->str) {
+ fep->str = fep[1].str;
+ fep->pat = fep[1].pat;
+ fep++;
+ }
+ if (!fep->pat)
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (enablesarr) {
+ Feature_enables fep;
+ for (fep = enablesarr; fep->str; fep++) {
+ char **fp, *esp = fep->str;
+ int on = 1, found = 0;
+ if (*esp == '+')
+ esp++;
+ else if (*esp == '-') {
+ on = 0;
+ esp++;
+ }
+ for (fp = features; *fp; fp++)
+ if (fep->pat ? pattry(fep->pat, *fp) : !strcmp(*fp, esp)) {
+ enables[fp - features] = on;
+ found++;
+ if (!fep->pat)
+ break;
+ }
+ if (!found) {
+ if (!(flags & FEAT_IGNORE))
+ zwarn(fep->pat ?
+ "module `%s' has no feature matching: `%s'" :
+ "module `%s' has no such feature: `%s'",
+ m->node.nam, esp);
+ return 1;
+ }
+ }
+ } else {
+ /*
+ * Enable all features. This is used when loading
+ * without using zmodload -F.
+ */
+ int n_features = arrlen(features);
+ int *ep;
+ for (ep = enables; n_features--; ep++)
+ *ep = 1;
+ }
+
+ if (enables_module(m, &enables))
+ return 2;
+ } else if (enablesarr) {
+ if (!(flags & FEAT_IGNORE))
+ zwarn("module `%s' does not support features", m->node.nam);
+ return 1;
+ }
+ /* Else it doesn't support features but we don't care. */
+
+ return ret;
+}
+
+/*
+ * Boot the module, including setting up features.
+ * As we've only just loaded the module, we don't yet
+ * know what features it supports, so we get them passed
+ * as a string.
+ *
+ * Returns 0 if OK, 1 if completely failed, 2 if some features
+ * couldn't be set up.
+ */
+
+/**/
+static int
+do_boot_module(Module m, Feature_enables enablesarr, int silent)
+{
+ int ret = do_module_features(m, enablesarr,
+ silent ? FEAT_IGNORE|FEAT_CHECKAUTO :
+ FEAT_CHECKAUTO);
+
+ if (ret == 1)
+ return 1;
+
+ if (boot_module(m))
+ return 1;
+ return ret;
+}
+
+/*
+ * Cleanup the module.
+ */
+
+/**/
+static int
+do_cleanup_module(Module m)
+{
+ return (m->node.flags & MOD_LINKED) ?
+ (m->u.linked && m->u.linked->cleanup(m)) :
+ (m->u.handle && cleanup_module(m));
+}
+
+/*
+ * Test a module name contains only valid characters: those
+ * allowed in a shell identifier plus slash. Return 1 if so.
+ */
+
+/**/
+static int
+modname_ok(char const *p)
+{
+ do {
+ p = itype_end(p, IIDENT, 0);
+ if (!*p)
+ return 1;
+ } while(*p++ == '/');
+ return 0;
+}
+
+/*
+ * High level function to load a module, encapsulating
+ * all the handling of module functions.
+ *
+ * "*enablesstr" is NULL if the caller is not feature-aware;
+ * then the module should turn on all features. If it
+ * is not NULL it points to an array of features to be
+ * turned on. This function is responsible for testing whether
+ * the module supports those features.
+ *
+ * If "silent" is 1, don't issue warnings for errors.
+ *
+ * Now returns 0 for success (changed post-4.3.4),
+ * 1 for complete failure, 2 if some features couldn't be set.
+ */
+
+/**/
+mod_export int
+load_module(char const *name, Feature_enables enablesarr, int silent)
+{
+ Module m;
+ void *handle = NULL;
+ Linkedmod linked;
+ int set, bootret;
+
+ if (!modname_ok(name)) {
+ if (!silent)
+ zerr("invalid module name `%s'", name);
+ return 1;
+ }
+ /*
+ * The following function call may alter name to the final name in a
+ * chain of aliases. This makes sure the actual module loaded
+ * is the right one.
+ */
+ queue_signals();
+ if (!(m = find_module(name, FINDMOD_ALIASP, &name))) {
+ if (!(linked = module_linked(name)) &&
+ !(handle = do_load_module(name, silent))) {
+ unqueue_signals();
+ return 1;
+ }
+ m = zshcalloc(sizeof(*m));
+ if (handle) {
+ m->u.handle = handle;
+ m->node.flags |= MOD_SETUP;
+ } else {
+ m->u.linked = linked;
+ m->node.flags |= MOD_SETUP | MOD_LINKED;
+ }
+ modulestab->addnode(modulestab, ztrdup(name), m);
+
+ if ((set = setup_module(m)) ||
+ (bootret = do_boot_module(m, enablesarr, silent)) == 1) {
+ if (!set)
+ do_cleanup_module(m);
+ finish_module(m);
+ delete_module(m);
+ unqueue_signals();
+ return 1;
+ }
+ m->node.flags |= MOD_INIT_S | MOD_INIT_B;
+ m->node.flags &= ~MOD_SETUP;
+ unqueue_signals();
+ return bootret;
+ }
+ if (m->node.flags & MOD_SETUP) {
+ unqueue_signals();
+ return 0;
+ }
+ if (m->node.flags & MOD_UNLOAD)
+ m->node.flags &= ~MOD_UNLOAD;
+ else if ((m->node.flags & MOD_LINKED) ? m->u.linked : m->u.handle) {
+ unqueue_signals();
+ return 0;
+ }
+ if (m->node.flags & MOD_BUSY) {
+ unqueue_signals();
+ zerr("circular dependencies for module ;%s", name);
+ return 1;
+ }
+ m->node.flags |= MOD_BUSY;
+ /*
+ * TODO: shouldn't we unload the module if one of
+ * its dependencies fails?
+ */
+ if (m->deps) {
+ LinkNode n;
+ for (n = firstnode(m->deps); n; incnode(n))
+ if (load_module((char *) getdata(n), NULL, silent) == 1) {
+ m->node.flags &= ~MOD_BUSY;
+ unqueue_signals();
+ return 1;
+ }
+ }
+ m->node.flags &= ~MOD_BUSY;
+ if (!m->u.handle) {
+ handle = NULL;
+ if (!(linked = module_linked(name)) &&
+ !(handle = do_load_module(name, silent))) {
+ unqueue_signals();
+ return 1;
+ }
+ if (handle) {
+ m->u.handle = handle;
+ m->node.flags |= MOD_SETUP;
+ } else {
+ m->u.linked = linked;
+ m->node.flags |= MOD_SETUP | MOD_LINKED;
+ }
+ if (setup_module(m)) {
+ finish_module(m);
+ if (handle)
+ m->u.handle = NULL;
+ else
+ m->u.linked = NULL;
+ m->node.flags &= ~MOD_SETUP;
+ unqueue_signals();
+ return 1;
+ }
+ m->node.flags |= MOD_INIT_S;
+ }
+ m->node.flags |= MOD_SETUP;
+ if ((bootret = do_boot_module(m, enablesarr, silent)) == 1) {
+ do_cleanup_module(m);
+ finish_module(m);
+ if (m->node.flags & MOD_LINKED)
+ m->u.linked = NULL;
+ else
+ m->u.handle = NULL;
+ m->node.flags &= ~MOD_SETUP;
+ unqueue_signals();
+ return 1;
+ }
+ m->node.flags |= MOD_INIT_B;
+ m->node.flags &= ~MOD_SETUP;
+ unqueue_signals();
+ return bootret;
+}
+
+/* This ensures that the module with the name given as the first argument
+ * is loaded.
+ * The other argument is the array of features to set. If this is NULL
+ * all features are enabled (even if the module was already loaded).
+ *
+ * If this is non-NULL the module features are set accordingly
+ * whether or not the module is loaded; it is an error if the
+ * module does not support the features passed (even if the feature
+ * is to be turned off) or if the module does not support features
+ * at all.
+ * The return value is 0 if the module was found or loaded
+ * (this changed post-4.3.4, because I got so confused---pws),
+ * 1 if loading failed completely, 2 if some features couldn't be set.
+ *
+ * This function behaves like load_module() except that it
+ * handles the case where the module was already loaded, and
+ * sets features accordingly.
+ */
+
+/**/
+mod_export int
+require_module(const char *module, Feature_enables features, int silent)
+{
+ Module m = NULL;
+ int ret = 0;
+
+ /* Resolve aliases and actual loadable module as for load_module */
+ queue_signals();
+ m = find_module(module, FINDMOD_ALIASP, &module);
+ if (!m || !m->u.handle ||
+ (m->node.flags & MOD_UNLOAD))
+ ret = load_module(module, features, silent);
+ else
+ ret = do_module_features(m, features, 0);
+ unqueue_signals();
+
+ return ret;
+}
+
+/*
+ * Indicate that the module named "name" depends on the module
+ * named "from".
+ */
+
+/**/
+void
+add_dep(const char *name, char *from)
+{
+ LinkNode node;
+ Module m;
+
+ /*
+ * If we were passed an alias, we must resolve it to a final
+ * module name (and maybe add the corresponding struct), since otherwise
+ * we would need to check all modules to see if they happen
+ * to be aliased to the same thing to implement dependencies properly.
+ *
+ * This should mean that an attempt to add an alias which would
+ * have the same name as a module which has dependencies is correctly
+ * rejected, because then the module named already exists as a non-alias.
+ * Better make sure. (There's no problem making a an alias which
+ * *points* to a module with dependencies, of course.)
+ */
+ m = find_module(name, FINDMOD_ALIASP|FINDMOD_CREATE, &name);
+ if (!m->deps)
+ m->deps = znewlinklist();
+ for (node = firstnode(m->deps);
+ node && strcmp((char *) getdata(node), from);
+ incnode(node));
+ if (!node)
+ zaddlinknode(m->deps, ztrdup(from));
+}
+
+/*
+ * Function to be used when scanning the builtins table to
+ * find and print autoloadable builtins.
+ */
+
+/**/
+static void
+autoloadscan(HashNode hn, int printflags)
+{
+ Builtin bn = (Builtin) hn;
+
+ if(bn->node.flags & BINF_ADDED)
+ return;
+ if(printflags & PRINT_LIST) {
+ fputs("zmodload -ab ", stdout);
+ if(bn->optstr[0] == '-')
+ fputs("-- ", stdout);
+ quotedzputs(bn->optstr, stdout);
+ if(strcmp(bn->node.nam, bn->optstr)) {
+ putchar(' ');
+ quotedzputs(bn->node.nam, stdout);
+ }
+ } else {
+ nicezputs(bn->node.nam, stdout);
+ if(strcmp(bn->node.nam, bn->optstr)) {
+ fputs(" (", stdout);
+ nicezputs(bn->optstr, stdout);
+ putchar(')');
+ }
+ }
+ putchar('\n');
+}
+
+
+/************************************************************************
+ * Handling for the zmodload builtin and its various options.
+ ************************************************************************/
+
+/*
+ * Main builtin entry point for zmodload.
+ */
+
+/**/
+int
+bin_zmodload(char *nam, char **args, Options ops, UNUSED(int func))
+{
+ int ops_bcpf = OPT_ISSET(ops,'b') || OPT_ISSET(ops,'c') ||
+ OPT_ISSET(ops,'p') || OPT_ISSET(ops,'f');
+ int ops_au = OPT_ISSET(ops,'a') || OPT_ISSET(ops,'u');
+ int ret = 1, autoopts;
+ /* options only allowed with -F */
+ char *fonly = "lP", *fp;
+
+ if (ops_bcpf && !ops_au) {
+ zwarnnam(nam, "-b, -c, -f, and -p must be combined with -a or -u");
+ return 1;
+ }
+ if (OPT_ISSET(ops,'F') && (ops_bcpf || OPT_ISSET(ops,'u'))) {
+ zwarnnam(nam, "-b, -c, -f, -p and -u cannot be combined with -F");
+ return 1;
+ }
+ if (OPT_ISSET(ops,'A') || OPT_ISSET(ops,'R')) {
+ if (ops_bcpf || ops_au || OPT_ISSET(ops,'d') ||
+ (OPT_ISSET(ops,'R') && OPT_ISSET(ops,'e'))) {
+ zwarnnam(nam, "illegal flags combined with -A or -R");
+ return 1;
+ }
+ if (!OPT_ISSET(ops,'e'))
+ return bin_zmodload_alias(nam, args, ops);
+ }
+ if (OPT_ISSET(ops,'d') && OPT_ISSET(ops,'a')) {
+ zwarnnam(nam, "-d cannot be combined with -a");
+ return 1;
+ }
+ if (OPT_ISSET(ops,'u') && !*args) {
+ zwarnnam(nam, "what do you want to unload?");
+ return 1;
+ }
+ if (OPT_ISSET(ops,'e') && (OPT_ISSET(ops,'I') || OPT_ISSET(ops,'L') ||
+ (OPT_ISSET(ops,'a') && !OPT_ISSET(ops,'F'))
+ || OPT_ISSET(ops,'d') ||
+ OPT_ISSET(ops,'i') || OPT_ISSET(ops,'u'))) {
+ zwarnnam(nam, "-e cannot be combined with other options");
+ /* except -F ... */
+ return 1;
+ }
+ for (fp = fonly; *fp; fp++) {
+ if (OPT_ISSET(ops,STOUC(*fp)) && !OPT_ISSET(ops,'F')) {
+ zwarnnam(nam, "-%c is only allowed with -F", *fp);
+ return 1;
+ }
+ }
+ queue_signals();
+ if (OPT_ISSET(ops, 'F'))
+ ret = bin_zmodload_features(nam, args, ops);
+ else if (OPT_ISSET(ops,'e'))
+ ret = bin_zmodload_exist(nam, args, ops);
+ else if (OPT_ISSET(ops,'d'))
+ ret = bin_zmodload_dep(nam, args, ops);
+ else if ((autoopts = OPT_ISSET(ops, 'b') + OPT_ISSET(ops, 'c') +
+ OPT_ISSET(ops, 'p') + OPT_ISSET(ops, 'f')) ||
+ /* zmodload -a is equivalent to zmodload -ab, annoyingly */
+ OPT_ISSET(ops, 'a')) {
+ if (autoopts > 1) {
+ zwarnnam(nam, "use only one of -b, -c, or -p");
+ ret = 1;
+ } else
+ ret = bin_zmodload_auto(nam, args, ops);
+ } else
+ ret = bin_zmodload_load(nam, args, ops);
+ unqueue_signals();
+
+ return ret;
+}
+
+/* zmodload -A */
+
+/**/
+static int
+bin_zmodload_alias(char *nam, char **args, Options ops)
+{
+ /*
+ * TODO: while it would be too nasty to have aliases, as opposed
+ * to real loadable modules, with dependencies --- just what would
+ * we need to load when, exactly? --- there is in principle no objection
+ * to making it possible to force an alias onto an existing unloaded
+ * module which has dependencies. This would simply transfer
+ * the dependencies down the line to the aliased-to module name.
+ * This is actually useful, since then you can alias zsh/zle=mytestzle
+ * to load another version of zle. But then what happens when the
+ * alias is removed? Do you transfer the dependencies back? And
+ * suppose other names are aliased to the same file? It might be
+ * kettle of fish best left unwormed.
+ */
+ Module m;
+
+ if (!*args) {
+ if (OPT_ISSET(ops,'R')) {
+ zwarnnam(nam, "no module alias to remove");
+ return 1;
+ }
+ scanhashtable(modulestab, 1, MOD_ALIAS, 0,
+ modulestab->printnode,
+ OPT_ISSET(ops,'L') ? PRINTMOD_LIST : 0);
+ return 0;
+ }
+
+ for (; *args; args++) {
+ char *eqpos = strchr(*args, '=');
+ char *aliasname = eqpos ? eqpos+1 : NULL;
+ if (eqpos)
+ *eqpos = '\0';
+ if (!modname_ok(*args)) {
+ zwarnnam(nam, "invalid module name `%s'", *args);
+ return 1;
+ }
+ if (OPT_ISSET(ops,'R')) {
+ if (aliasname) {
+ zwarnnam(nam, "bad syntax for removing module alias: %s",
+ *args);
+ return 1;
+ }
+ m = find_module(*args, 0, NULL);
+ if (m) {
+ if (!(m->node.flags & MOD_ALIAS)) {
+ zwarnnam(nam, "module is not an alias: %s", *args);
+ return 1;
+ }
+ delete_module(m);
+ } else {
+ zwarnnam(nam, "no such module alias: %s", *args);
+ return 1;
+ }
+ } else {
+ if (aliasname) {
+ const char *mname = aliasname;
+ if (!modname_ok(aliasname)) {
+ zwarnnam(nam, "invalid module name `%s'", aliasname);
+ return 1;
+ }
+ do {
+ if (!strcmp(mname, *args)) {
+ zwarnnam(nam, "module alias would refer to itself: %s",
+ *args);
+ return 1;
+ }
+ } while ((m = find_module(mname, 0, NULL))
+ && (m->node.flags & MOD_ALIAS)
+ && (mname = m->u.alias));
+ m = find_module(*args, 0, NULL);
+ if (m) {
+ if (!(m->node.flags & MOD_ALIAS)) {
+ zwarnnam(nam, "module is not an alias: %s", *args);
+ return 1;
+ }
+ zsfree(m->u.alias);
+ } else {
+ m = (Module) zshcalloc(sizeof(*m));
+ m->node.flags = MOD_ALIAS;
+ modulestab->addnode(modulestab, ztrdup(*args), m);
+ }
+ m->u.alias = ztrdup(aliasname);
+ } else {
+ if ((m = find_module(*args, 0, NULL))) {
+ if (m->node.flags & MOD_ALIAS)
+ modulestab->printnode(&m->node,
+ OPT_ISSET(ops,'L') ?
+ PRINTMOD_LIST : 0);
+ else {
+ zwarnnam(nam, "module is not an alias: %s", *args);
+ return 1;
+ }
+ } else {
+ zwarnnam(nam, "no such module alias: %s", *args);
+ return 1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* zmodload -e (without -F) */
+
+/**/
+static int
+bin_zmodload_exist(UNUSED(char *nam), char **args, Options ops)
+{
+ Module m;
+
+ if (!*args) {
+ scanhashtable(modulestab, 1, 0, 0, modulestab->printnode,
+ OPT_ISSET(ops,'A') ? PRINTMOD_EXIST|PRINTMOD_ALIAS :
+ PRINTMOD_EXIST);
+ return 0;
+ } else {
+ int ret = 0;
+
+ for (; !ret && *args; args++) {
+ if (!(m = find_module(*args, FINDMOD_ALIASP, NULL))
+ || !m->u.handle
+ || (m->node.flags & MOD_UNLOAD))
+ ret = 1;
+ }
+ return ret;
+ }
+}
+
+/* zmodload -d */
+
+/**/
+static int
+bin_zmodload_dep(UNUSED(char *nam), char **args, Options ops)
+{
+ Module m;
+ if (OPT_ISSET(ops,'u')) {
+ /* remove dependencies, which can't pertain to aliases */
+ const char *tnam = *args++;
+ m = find_module(tnam, FINDMOD_ALIASP, &tnam);
+ if (!m)
+ return 0;
+ if (*args && m->deps) {
+ do {
+ LinkNode dnode;
+ for (dnode = firstnode(m->deps); dnode; incnode(dnode))
+ if (!strcmp(*args, getdata(dnode))) {
+ zsfree(getdata(dnode));
+ remnode(m->deps, dnode);
+ 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->u.handle)
+ delete_module(m);
+ return 0;
+ } else if (!args[0] || !args[1]) {
+ /* list dependencies */
+ int depflags = OPT_ISSET(ops,'L') ?
+ PRINTMOD_DEPS|PRINTMOD_LIST : PRINTMOD_DEPS;
+ if (args[0]) {
+ if ((m = (Module)modulestab->getnode2(modulestab, args[0])))
+ modulestab->printnode(&m->node, depflags);
+ } else {
+ scanhashtable(modulestab, 1, 0, 0, modulestab->printnode,
+ depflags);
+ }
+ return 0;
+ } else {
+ /* add dependencies */
+ int ret = 0;
+ char *tnam = *args++;
+
+ for (; *args; args++)
+ add_dep(tnam, *args);
+ return ret;
+ }
+}
+
+/*
+ * Function for scanning the parameter table to find and print
+ * out autoloadable parameters.
+ */
+
+static void
+printautoparams(HashNode hn, int lon)
+{
+ Param pm = (Param) hn;
+
+ if (pm->node.flags & PM_AUTOLOAD) {
+ if (lon)
+ printf("zmodload -ap %s %s\n", pm->u.str, pm->node.nam);
+ else
+ printf("%s (%s)\n", pm->node.nam, pm->u.str);
+ }
+}
+
+/* zmodload -a/u [bcpf] */
+
+/**/
+static int
+bin_zmodload_auto(char *nam, char **args, Options ops)
+{
+ int fchar, flags;
+ char *modnam;
+
+ if (OPT_ISSET(ops,'c')) {
+ if (!*args) {
+ /* list autoloaded conditions */
+ Conddef p;
+
+ for (p = condtab; p; p = p->next) {
+ if (p->module) {
+ if (OPT_ISSET(ops,'L')) {
+ fputs("zmodload -ac", stdout);
+ if (p->flags & CONDF_INFIX)
+ putchar('I');
+ printf(" %s %s\n", p->module, p->name);
+ } else {
+ if (p->flags & CONDF_INFIX)
+ fputs("infix ", stdout);
+ else
+ fputs("post ", stdout);
+ printf("%s (%s)\n",p->name, p->module);
+ }
+ }
+ }
+ return 0;
+ }
+ fchar = OPT_ISSET(ops,'I') ? 'C' : 'c';
+ } else if (OPT_ISSET(ops,'p')) {
+ if (!*args) {
+ /* list autoloaded parameters */
+ scanhashtable(paramtab, 1, 0, 0, printautoparams,
+ OPT_ISSET(ops,'L'));
+ return 0;
+ }
+ fchar = 'p';
+ } else if (OPT_ISSET(ops,'f')) {
+ if (!*args) {
+ /* list autoloaded math functions */
+ MathFunc p;
+
+ for (p = mathfuncs; p; p = p->next) {
+ if (!(p->flags & MFF_USERFUNC) && p->module) {
+ if (OPT_ISSET(ops,'L')) {
+ fputs("zmodload -af", stdout);
+ printf(" %s %s\n", p->module, p->name);
+ } else
+ printf("%s (%s)\n",p->name, p->module);
+ }
+ }
+ return 0;
+ }
+ fchar = 'f';
+ } else {
+ /* builtins are the default; zmodload -ab or just zmodload -a */
+ if (!*args) {
+ /* list autoloaded builtins */
+ scanhashtable(builtintab, 1, 0, 0,
+ autoloadscan, OPT_ISSET(ops,'L') ? PRINT_LIST : 0);
+ return 0;
+ }
+ fchar = 'b';
+ }
+
+ flags = FEAT_AUTOALL;
+ if (OPT_ISSET(ops,'i'))
+ flags |= FEAT_IGNORE;
+ if (OPT_ISSET(ops,'u')) {
+ /* remove autoload */
+ flags |= FEAT_REMOVE;
+ modnam = NULL;
+ } else {
+ /* add autoload */
+ modnam = *args;
+
+ if (args[1])
+ args++;
+ }
+ return autofeatures(nam, modnam, args, fchar, flags);
+}
+
+/* Backend handler for zmodload -u */
+
+/**/
+int
+unload_module(Module m)
+{
+ int del;
+
+ /*
+ * Only unload the real module, so resolve aliases.
+ */
+ if (m->node.flags & MOD_ALIAS) {
+ m = find_module(m->u.alias, FINDMOD_ALIASP, NULL);
+ if (!m)
+ return 1;
+ }
+ /*
+ * We may need to clean up the module any time setup_ has been
+ * called. After cleanup_ is successful we are no longer in the
+ * booted state (because features etc. are deregistered), so remove
+ * MOD_INIT_B, and also MOD_INIT_S since we won't need to cleanup
+ * again if this succeeded.
+ */
+ if ((m->node.flags & MOD_INIT_S) &&
+ !(m->node.flags & MOD_UNLOAD) &&
+ do_cleanup_module(m))
+ return 1;
+ m->node.flags &= ~(MOD_INIT_B|MOD_INIT_S);
+
+ del = (m->node.flags & MOD_UNLOAD);
+
+ if (m->wrapper) {
+ m->node.flags |= MOD_UNLOAD;
+ return 0;
+ }
+ m->node.flags &= ~MOD_UNLOAD;
+
+ /*
+ * We always need to finish the module (and unload it)
+ * if it is present.
+ */
+ if (m->node.flags & MOD_LINKED) {
+ if (m->u.linked) {
+ m->u.linked->finish(m);
+ m->u.linked = NULL;
+ }
+ } else {
+ if (m->u.handle) {
+ finish_module(m);
+ m->u.handle = NULL;
+ }
+ }
+
+ if (del && m->deps) {
+ /* The module was unloaded delayed, unload all modules *
+ * on which it depended. */
+ LinkNode n;
+
+ for (n = firstnode(m->deps); n; incnode(n)) {
+ Module dm = find_module((char *) getdata(n),
+ FINDMOD_ALIASP, NULL);
+
+ if (dm &&
+ (dm->node.flags & MOD_UNLOAD)) {
+ /* See if this is the only module depending on it. */
+ Module am;
+ int du = 1, i;
+ /* Scan hash table the hard way */
+ for (i = 0; du && i < modulestab->hsize; i++) {
+ for (am = (Module)modulestab->nodes[i]; du && am;
+ am = (Module)am->node.next) {
+ LinkNode sn;
+ /*
+ * Don't scan the module we're unloading;
+ * ignore if no dependencies.
+ */
+ if (am == m || !am->deps)
+ continue;
+ /* Don't scan if not loaded nor linked */
+ if ((am->node.flags & MOD_LINKED) ?
+ !am->u.linked : !am->u.handle)
+ continue;
+ for (sn = firstnode(am->deps); du && sn;
+ incnode(sn)) {
+ if (!strcmp((char *) getdata(sn),
+ dm->node.nam))
+ du = 0;
+ }
+ }
+ }
+ if (du)
+ unload_module(dm);
+ }
+ }
+ }
+ if (m->autoloads && firstnode(m->autoloads)) {
+ /*
+ * Module has autoloadable features. Restore them
+ * so that the module will be reloaded when needed.
+ */
+ autofeatures("zsh", m->node.nam,
+ hlinklist2array(m->autoloads, 0), 0, FEAT_IGNORE);
+ } else if (!m->deps) {
+ delete_module(m);
+ }
+ return 0;
+}
+
+/*
+ * Unload a module by name (modname); nam is the command name.
+ * Optionally don't print some error messages (always print
+ * dependency errors).
+ */
+
+/**/
+int
+unload_named_module(char *modname, char *nam, int silent)
+{
+ const char *mname;
+ Module m;
+ int ret = 0;
+
+ m = find_module(modname, FINDMOD_ALIASP, &mname);
+ if (m) {
+ int i, del = 0;
+ Module dm;
+
+ for (i = 0; i < modulestab->hsize; i++) {
+ for (dm = (Module)modulestab->nodes[i]; dm;
+ dm = (Module)dm->node.next) {
+ LinkNode dn;
+ if (!dm->deps || !dm->u.handle)
+ continue;
+ for (dn = firstnode(dm->deps); dn; incnode(dn)) {
+ if (!strcmp((char *) getdata(dn), mname)) {
+ if (dm->node.flags & MOD_UNLOAD)
+ del = 1;
+ else {
+ zwarnnam(nam, "module %s is in use by another module and cannot be unloaded", mname);
+ return 1;
+ }
+ }
+ }
+ }
+ }
+ if (del)
+ m->wrapper++;
+ if (unload_module(m))
+ ret = 1;
+ if (del)
+ m->wrapper--;
+ } else if (!silent) {
+ zwarnnam(nam, "no such module %s", modname);
+ ret = 1;
+ }
+
+ return ret;
+}
+
+/* zmodload -u without -d */
+
+/**/
+static int
+bin_zmodload_load(char *nam, char **args, Options ops)
+{
+ int ret = 0;
+ if(OPT_ISSET(ops,'u')) {
+ /* unload modules */
+ for(; *args; args++) {
+ if (unload_named_module(*args, nam, OPT_ISSET(ops,'i')))
+ ret = 1;
+ }
+ return ret;
+ } else if(!*args) {
+ /* list modules */
+ scanhashtable(modulestab, 1, 0, MOD_UNLOAD|MOD_ALIAS,
+ modulestab->printnode,
+ OPT_ISSET(ops,'L') ? PRINTMOD_LIST : 0);
+ return 0;
+ } else {
+ /* load modules */
+ for (; *args; args++) {
+ int tmpret = require_module(*args, NULL, OPT_ISSET(ops,'s'));
+ if (tmpret && ret != 1)
+ ret = tmpret;
+ }
+
+ return ret;
+ }
+}
+
+/* zmodload -F */
+
+/**/
+static int
+bin_zmodload_features(const char *nam, char **args, Options ops)
+{
+ int iarg;
+ char *modname = *args;
+ Patprog *patprogs;
+ Feature_enables features, fep;
+
+ if (modname)
+ args++;
+ else if (OPT_ISSET(ops,'L')) {
+ int printflags = PRINTMOD_LIST|PRINTMOD_FEATURES;
+ if (OPT_ISSET(ops,'P')) {
+ zwarnnam(nam, "-P is only allowed with a module name");
+ return 1;
+ }
+ if (OPT_ISSET(ops,'l'))
+ printflags |= PRINTMOD_LISTALL;
+ if (OPT_ISSET(ops,'a'))
+ printflags |= PRINTMOD_AUTO;
+ scanhashtable(modulestab, 1, 0, MOD_ALIAS,
+ modulestab->printnode, printflags);
+ return 0;
+ }
+
+ if (!modname) {
+ zwarnnam(nam, "-F requires a module name");
+ return 1;
+ }
+
+ if (OPT_ISSET(ops,'m')) {
+ char **argp;
+ Patprog *patprogp;
+
+ /* not NULL terminated */
+ patprogp = patprogs =
+ (Patprog *)zhalloc(arrlen(args)*sizeof(Patprog));
+ for (argp = args; *argp; argp++, patprogp++) {
+ char *arg = *argp;
+ if (*arg == '+' || *arg == '-')
+ arg++;
+ tokenize(arg);
+ *patprogp = patcompile(arg, 0, 0);
+ }
+ } else
+ patprogs = NULL;
+
+ if (OPT_ISSET(ops,'l') || OPT_ISSET(ops,'L') || OPT_ISSET(ops,'e')) {
+ /*
+ * With option 'l', list all features one per line with + or -.
+ * With option 'L', list as zmodload statement showing
+ * only options turned on.
+ * With both options, list as zmodload showing options
+ * to be turned both on and off.
+ */
+ Module m;
+ char **features, **fp, **arrset = NULL, **arrp = NULL;
+ int *enables = NULL, *ep;
+ char *param = OPT_ARG_SAFE(ops,'P');
+
+ m = find_module(modname, FINDMOD_ALIASP, NULL);
+ if (OPT_ISSET(ops,'a')) {
+ LinkNode ln;
+ /*
+ * If there are no autoloads defined, return status 1.
+ */
+ if (!m || !m->autoloads)
+ return 1;
+ if (OPT_ISSET(ops,'e')) {
+ for (fp = args; *fp; fp++) {
+ char *fstr = *fp;
+ int sense = 1;
+ if (*fstr == '+')
+ fstr++;
+ else if (*fstr == '-') {
+ fstr++;
+ sense = 0;
+ }
+ if ((linknodebystring(m->autoloads, fstr) != NULL) !=
+ sense)
+ return 1;
+ }
+ return 0;
+ }
+ if (param) {
+ arrp = arrset = (char **)zalloc(sizeof(char*) *
+ (countlinknodes(m->autoloads)+1));
+ } else if (OPT_ISSET(ops,'L')) {
+ printf("zmodload -aF %s%c", m->node.nam,
+ m->autoloads && firstnode(m->autoloads) ? ' ' : '\n');
+ arrp = NULL;
+ }
+ for (ln = firstnode(m->autoloads); ln; incnode(ln)) {
+ char *al = (char *)getdata(ln);
+ if (param)
+ *arrp++ = ztrdup(al);
+ else
+ printf("%s%c", al,
+ OPT_ISSET(ops,'L') && nextnode(ln) ? ' ' : '\n');
+ }
+ if (param) {
+ *arrp = NULL;
+ if (!setaparam(param, arrset))
+ return 1;
+ }
+ return 0;
+ }
+ if (!m || !m->u.handle || (m->node.flags & MOD_UNLOAD)) {
+ if (!OPT_ISSET(ops,'e'))
+ zwarnnam(nam, "module `%s' is not yet loaded", modname);
+ return 1;
+ }
+ if (features_module(m, &features)) {
+ if (!OPT_ISSET(ops,'e'))
+ zwarnnam(nam, "module `%s' does not support features",
+ m->node.nam);
+ return 1;
+ }
+ if (enables_module(m, &enables)) {
+ /* this shouldn't ever happen, so don't silence this error */
+ zwarnnam(nam, "error getting enabled features for module `%s'",
+ m->node.nam);
+ return 1;
+ }
+ for (arrp = args, iarg = 0; *arrp; arrp++, iarg++) {
+ char *arg = *arrp;
+ int on, found = 0;
+ if (*arg == '-') {
+ on = 0;
+ arg++;
+ } else if (*arg == '+') {
+ on = 1;
+ arg++;
+ } else
+ on = -1;
+ for (fp = features, ep = enables; *fp; fp++, ep++) {
+ if (patprogs ? pattry(patprogs[iarg], *fp) :
+ !strcmp(arg, *fp)) {
+ /* for -e, check given state, if any */
+ if (OPT_ISSET(ops,'e') && on != -1 &&
+ on != (*ep & 1))
+ return 1;
+ found++;
+ if (!patprogs)
+ break;
+ }
+ }
+ if (!found) {
+ if (!OPT_ISSET(ops,'e'))
+ zwarnnam(nam, patprogs ?
+ "module `%s' has no feature matching: `%s'" :
+ "module `%s' has no such feature: `%s'",
+ modname, *arrp);
+ return 1;
+ }
+ }
+ if (OPT_ISSET(ops,'e')) /* yep, everything we want exists */
+ return 0;
+ if (param) {
+ int arrlen = 0;
+ for (fp = features, ep = enables; *fp; fp++, ep++) {
+ if (OPT_ISSET(ops, 'L') && !OPT_ISSET(ops, 'l') &&
+ !*ep)
+ continue;
+ if (*args) {
+ char **argp;
+ for (argp = args, iarg = 0; *argp; argp++, iarg++) {
+ char *arg = *argp;
+ /* ignore +/- for consistency */
+ if (*arg == '+' || *arg == '-')
+ arg++;
+ if (patprogs ? pattry(patprogs[iarg], *fp) :
+ !strcmp(*fp, arg))
+ break;
+ }
+ if (!*argp)
+ continue;
+ }
+ arrlen++;
+ }
+ arrp = arrset = zalloc(sizeof(char *) * (arrlen+1));
+ } else if (OPT_ISSET(ops, 'L'))
+ printf("zmodload -F %s ", m->node.nam);
+ for (fp = features, ep = enables; *fp; fp++, ep++) {
+ char *onoff;
+ int term;
+ if (*args) {
+ char **argp;
+ for (argp = args, iarg = 0; *argp; argp++, iarg++) {
+ char *arg = *argp;
+ if (*arg == '+' || *arg == '-')
+ arg++;
+ if (patprogs ? pattry(patprogs[iarg], *fp) :
+ !strcmp(*fp, *argp))
+ break;
+ }
+ if (!*argp)
+ continue;
+ }
+ if (OPT_ISSET(ops, 'L') && !OPT_ISSET(ops, 'l')) {
+ if (!*ep)
+ continue;
+ onoff = "";
+ } else if (*ep) {
+ onoff = "+";
+ } else {
+ onoff = "-";
+ }
+ if (param) {
+ *arrp++ = bicat(onoff, *fp);
+ } else {
+ if (OPT_ISSET(ops, 'L') && fp[1]) {
+ term = ' ';
+ } else {
+ term = '\n';
+ }
+ printf("%s%s%c", onoff, *fp, term);
+ }
+ }
+ if (param) {
+ *arrp = NULL;
+ if (!setaparam(param, arrset))
+ return 1;
+ }
+ return 0;
+ } else if (OPT_ISSET(ops,'P')) {
+ zwarnnam(nam, "-P can only be used with -l or -L");
+ return 1;
+ } else if (OPT_ISSET(ops,'a')) {
+ if (OPT_ISSET(ops,'m')) {
+ zwarnnam(nam, "-m cannot be used with -a");
+ return 1;
+ }
+ /*
+ * With zmodload -aF, we always use the effect of -i.
+ * The thinking is that marking a feature for
+ * autoload is separate from enabling or disabling it.
+ * Arguably we could do this with the zmodload -ab method
+ * but I've kept it there for old time's sake.
+ * The decoupling has meant FEAT_IGNORE/-i also
+ * suppresses an error for attempting to remove an
+ * autoload when the feature is enabled, which used
+ * to be a hard error before.
+ */
+ return autofeatures(nam, modname, args, 0, FEAT_IGNORE);
+ }
+
+ fep = features =
+ (Feature_enables)zhalloc((arrlen(args)+1)*sizeof(*fep));
+
+ while (*args) {
+ fep->str = *args++;
+ fep->pat = patprogs ? *patprogs++ : NULL;
+ fep++;
+ }
+ fep->str = NULL;
+ fep->pat = NULL;
+
+ return require_module(modname, features, OPT_ISSET(ops,'s'));
+}
+
+
+/************************************************************************
+ * Generic feature support.
+ * These functions are designed to be called by modules.
+ ************************************************************************/
+
+/*
+ * Construct a features array out of the list of concrete
+ * features given, leaving space for any abstract features
+ * to be added by the module itself.
+ *
+ * Note the memory is from the heap.
+ */
+
+/**/
+mod_export char **
+featuresarray(UNUSED(Module m), Features f)
+{
+ int bn_size = f->bn_size, cd_size = f->cd_size;
+ int mf_size = f->mf_size, pd_size = f->pd_size;
+ int features_size = bn_size + cd_size + pd_size + mf_size + f->n_abstract;
+ Builtin bnp = f->bn_list;
+ Conddef cdp = f->cd_list;
+ MathFunc mfp = f->mf_list;
+ Paramdef pdp = f->pd_list;
+ char **features = (char **)zhalloc((features_size + 1) * sizeof(char *));
+ char **featurep = features;
+
+ while (bn_size--)
+ *featurep++ = dyncat("b:", (bnp++)->node.nam);
+ while (cd_size--) {
+ *featurep++ = dyncat((cdp->flags & CONDF_INFIX) ? "C:" : "c:",
+ cdp->name);
+ cdp++;
+ }
+ while (mf_size--)
+ *featurep++ = dyncat("f:", (mfp++)->name);
+ while (pd_size--)
+ *featurep++ = dyncat("p:", (pdp++)->name);
+
+ features[features_size] = NULL;
+ return features;
+}
+
+/*
+ * Return the current set of enables for the features in a
+ * module using heap memory. Leave space for abstract
+ * features. The array is not zero terminated.
+ */
+/**/
+mod_export int *
+getfeatureenables(UNUSED(Module m), Features f)
+{
+ int bn_size = f->bn_size, cd_size = f->cd_size;
+ int mf_size = f->mf_size, pd_size = f->pd_size;
+ int features_size = bn_size + cd_size + mf_size + pd_size + f->n_abstract;
+ Builtin bnp = f->bn_list;
+ Conddef cdp = f->cd_list;
+ MathFunc mfp = f->mf_list;
+ Paramdef pdp = f->pd_list;
+ int *enables = zhalloc(sizeof(int) * features_size);
+ int *enablep = enables;
+
+ while (bn_size--)
+ *enablep++ = ((bnp++)->node.flags & BINF_ADDED) ? 1 : 0;
+ while (cd_size--)
+ *enablep++ = ((cdp++)->flags & CONDF_ADDED) ? 1 : 0;
+ while (mf_size--)
+ *enablep++ = ((mfp++)->flags & MFF_ADDED) ? 1 : 0;
+ while (pd_size--)
+ *enablep++ = (pdp++)->pm ? 1 : 0;
+
+ return enables;
+}
+
+/*
+ * Add or remove the concrete features passed in arguments,
+ * depending on the corresponding element of the array e.
+ * If e is NULL, disable everything.
+ * Return 0 for success, 1 for failure; does not attempt
+ * to imitate the return values of addbuiltins() etc.
+ * Any failure in adding a requested feature is an
+ * error.
+ */
+
+/**/
+mod_export int
+setfeatureenables(Module m, Features f, int *e)
+{
+ int ret = 0;
+
+ if (f->bn_size) {
+ if (setbuiltins(m->node.nam, f->bn_list, f->bn_size, e))
+ ret = 1;
+ if (e)
+ e += f->bn_size;
+ }
+ if (f->cd_size) {
+ if (setconddefs(m->node.nam, f->cd_list, f->cd_size, e))
+ ret = 1;
+ if (e)
+ e += f->cd_size;
+ }
+ if (f->mf_size) {
+ if (setmathfuncs(m->node.nam, f->mf_list, f->mf_size, e))
+ ret = 1;
+ if (e)
+ e += f->mf_size;
+ }
+ if (f->pd_size) {
+ if (setparamdefs(m->node.nam, f->pd_list, f->pd_size, e))
+ ret = 1;
+ if (e)
+ e += f->pd_size;
+ }
+ return ret;
+}
+
+/*
+ * Convenient front-end to get or set features which
+ * can be used in a module enables_() function.
+ */
+
+/**/
+mod_export int
+handlefeatures(Module m, Features f, int **enables)
+{
+ if (!enables || *enables)
+ return setfeatureenables(m, f, enables ? *enables : NULL);
+ *enables = getfeatureenables(m, f);
+ return 0;
+}
+
+/*
+ * Ensure module "modname" is providing feature with "prefix"
+ * and "feature" (e.g. "b:", "limit"). If feature is NULL,
+ * ensure all features are loaded (used for compatibility
+ * with the pre-feature autoloading behaviour).
+ *
+ * This will usually be called from the main shell to handle
+ * loading of an autoloadable feature.
+ *
+ * Returns 0 on success, 1 for error in module, 2 for error
+ * setting the feature. However, this isn't actually all
+ * that useful for testing immediately on an autoload since
+ * it could be a failure to autoload a different feature
+ * from the one we want. We could fix this but it's
+ * possible to test other ways.
+ */
+
+/**/
+mod_export int
+ensurefeature(const char *modname, const char *prefix, const char *feature)
+{
+ char *f;
+ struct feature_enables features[2];
+
+ if (!feature)
+ return require_module(modname, NULL, 0);
+ f = dyncat(prefix, feature);
+
+ features[0].str = f;
+ features[0].pat = NULL;
+ features[1].str = NULL;
+ features[1].pat = NULL;
+ return require_module(modname, features, 0);
+}
+
+/*
+ * Add autoloadable features for a given module.
+ */
+
+/**/
+int
+autofeatures(const char *cmdnam, const char *module, char **features,
+ int prefchar, int defflags)
+{
+ int ret = 0, subret;
+ Module defm, m;
+ char **modfeatures = NULL;
+ int *modenables = NULL;
+ if (module) {
+ defm = (Module)find_module(module,
+ FINDMOD_ALIASP|FINDMOD_CREATE, NULL);
+ if ((defm->node.flags & MOD_LINKED) ? defm->u.linked :
+ defm->u.handle) {
+ (void)features_module(defm, &modfeatures);
+ (void)enables_module(defm, &modenables);
+ }
+ } else
+ defm = NULL;
+
+ for (; *features; features++) {
+ char *fnam, *typnam, *feature;
+ int add, fchar, flags = defflags;
+ autofeaturefn_t fn;
+
+ if (prefchar) {
+ /*
+ * "features" is list of bare features with no
+ * type prefix; prefchar gives type character.
+ */
+ add = 1; /* unless overridden by flag */
+ fchar = prefchar;
+ fnam = *features;
+ feature = zhalloc(strlen(fnam) + 3);
+ sprintf(feature, "%c:%s", fchar, fnam);
+ } else {
+ feature = *features;
+ if (*feature == '-') {
+ add = 0;
+ feature++;
+ } else {
+ add = 1;
+ if (*feature == '+')
+ feature++;
+ }
+
+ if (!*feature || feature[1] != ':') {
+ zwarnnam(cmdnam, "bad format for autoloadable feature: `%s'",
+ feature);
+ ret = 1;
+ continue;
+ }
+ fnam = feature + 2;
+ fchar = feature[0];
+ }
+ if (flags & FEAT_REMOVE)
+ add = 0;
+
+ switch (fchar) {
+ case 'b':
+ fn = add ? add_autobin : del_autobin;
+ typnam = "builtin";
+ break;
+
+ case 'C':
+ flags |= FEAT_INFIX;
+ /* FALLTHROUGH */
+ case 'c':
+ fn = add ? add_autocond : del_autocond;
+ typnam = "condition";
+ break;
+
+ case 'f':
+ fn = add ? add_automathfunc : del_automathfunc;
+ typnam = "math function";
+ break;
+
+ case 'p':
+ fn = add ? add_autoparam : del_autoparam;
+ typnam = "parameter";
+ break;
+
+ default:
+ zwarnnam(cmdnam, "bad autoloadable feature type: `%c'",
+ fchar);
+ ret = 1;
+ continue;
+ }
+
+ if (strchr(fnam, '/')) {
+ zwarnnam(cmdnam, "%s: `/' is illegal in a %s", fnam, typnam);
+ ret = 1;
+ continue;
+ }
+
+ if (!module) {
+ /*
+ * Traditional un-autoload syntax doesn't tell us
+ * which module this came from.
+ */
+ int i;
+ for (i = 0, m = NULL; !m && i < modulestab->hsize; i++) {
+ for (m = (Module)modulestab->nodes[i]; m;
+ m = (Module)m->node.next) {
+ if (m->autoloads &&
+ linknodebystring(m->autoloads, feature))
+ break;
+ }
+ }
+ if (!m) {
+ if (!(flags & FEAT_IGNORE)) {
+ ret = 1;
+ zwarnnam(cmdnam, "%s: no such %s", fnam, typnam);
+ }
+ continue;
+ }
+ } else
+ m = defm;
+
+ subret = 0;
+ if (add) {
+ char **ptr;
+ if (modfeatures) {
+ /*
+ * If the module is already available, check that
+ * it does in fact provide the necessary feature.
+ */
+ for (ptr = modfeatures; *ptr; ptr++)
+ if (!strcmp(*ptr, feature))
+ break;
+ if (!*ptr) {
+ zwarnnam(cmdnam, "module `%s' has no such feature: `%s'",
+ m->node.nam, feature);
+ ret = 1;
+ continue;
+ }
+ /*
+ * If the feature is already provided by the module, there's
+ * nothing more to do.
+ */
+ if (modenables[ptr-modfeatures])
+ continue;
+ /*
+ * Otherwise, marking it for autoload will do the
+ * right thing when the feature is eventually used.
+ */
+ }
+ if (!m->autoloads) {
+ m->autoloads = znewlinklist();
+ zaddlinknode(m->autoloads, ztrdup(feature));
+ } else {
+ /* Insert in lexical order */
+ LinkNode ln, prev = (LinkNode)m->autoloads;
+ while ((ln = nextnode(prev))) {
+ int cmp = strcmp(feature, (char *)getdata(ln));
+ if (cmp == 0) {
+ /* Already there. Never an error. */
+ break;
+ }
+ if (cmp < 0) {
+ zinsertlinknode(m->autoloads, prev,
+ ztrdup(feature));
+ break;
+ }
+ prev = ln;
+ }
+ if (!ln)
+ zaddlinknode(m->autoloads, ztrdup(feature));
+ }
+ } else if (m->autoloads) {
+ LinkNode ln;
+ if ((ln = linknodebystring(m->autoloads, feature)))
+ zsfree((char *)remnode(m->autoloads, ln));
+ else {
+ /*
+ * With -i (or zmodload -Fa), removing an autoload
+ * that's not there is not an error.
+ */
+ subret = (flags & FEAT_IGNORE) ? -2 : 2;
+ }
+ }
+
+ if (subret == 0)
+ subret = fn(module, fnam, flags);
+
+ if (subret != 0) {
+ /* -2 indicates not an error, just skip running fn() */
+ if (subret != -2)
+ ret = 1;
+ switch (subret) {
+ case 1:
+ zwarnnam(cmdnam, "failed to add %s `%s'", typnam, fnam);
+ break;
+
+ case 2:
+ zwarnnam(cmdnam, "%s: no such %s", fnam, typnam);
+ break;
+
+ case 3:
+ zwarnnam(cmdnam, "%s: %s is already defined", fnam, typnam);
+ break;
+
+ default:
+ /* no (further) message needed */
+ break;
+ }
+ }
+ }
+
+ return ret;
+}
diff --git a/dotfiles/system/.zsh/modules/Src/options.c b/dotfiles/system/.zsh/modules/Src/options.c
new file mode 100644
index 0000000..600b649
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/options.c
@@ -0,0 +1,955 @@
+/*
+ * 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) */
+
+/**/
+mod_export int emulation;
+
+/* current sticky emulation: sticky = NULL means none */
+
+/**/
+mod_export Emulation_options sticky;
+
+/* the options; e.g. if opts[SHGLOB] != 0, SH_GLOB is turned on */
+
+/**/
+mod_export char opts[OPT_SIZE];
+
+/* Option name hash table */
+
+/**/
+mod_export HashTable optiontab;
+
+/* 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)
+
+/* option is relevant to emulation */
+#define OPT_EMULATE (EMULATE_UNUSED)
+/* option should never be set by emulate() */
+#define OPT_SPECIAL (EMULATE_UNUSED<<1)
+/* option is an alias to an other option */
+#define OPT_ALIAS (EMULATE_UNUSED<<2)
+
+#define defset(X, my_emulation) (!!((X)->node.flags & my_emulation))
+
+/*
+ * Note that option names should usually be fewer than 20 characters long
+ * to avoid formatting problems.
+ */
+static struct optname optns[] = {
+{{NULL, "aliases", OPT_EMULATE|OPT_ALL}, ALIASESOPT},
+{{NULL, "aliasfuncdef", OPT_EMULATE|OPT_BOURNE}, ALIASFUNCDEF},
+{{NULL, "allexport", OPT_EMULATE}, ALLEXPORT},
+{{NULL, "alwayslastprompt", OPT_ALL}, ALWAYSLASTPROMPT},
+{{NULL, "alwaystoend", 0}, ALWAYSTOEND},
+{{NULL, "appendcreate", OPT_EMULATE|OPT_BOURNE}, APPENDCREATE},
+{{NULL, "appendhistory", OPT_ALL}, APPENDHISTORY},
+{{NULL, "autocd", OPT_EMULATE}, AUTOCD},
+{{NULL, "autocontinue", 0}, AUTOCONTINUE},
+{{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_NONBOURNE}, BANGHIST},
+{{NULL, "bareglobqual", OPT_EMULATE|OPT_ZSH}, BAREGLOBQUAL},
+{{NULL, "bashautolist", 0}, BASHAUTOLIST},
+{{NULL, "bashrematch", 0}, BASHREMATCH},
+{{NULL, "beep", OPT_ALL}, BEEP},
+{{NULL, "bgnice", OPT_EMULATE|OPT_NONBOURNE},BGNICE},
+{{NULL, "braceccl", OPT_EMULATE}, BRACECCL},
+{{NULL, "bsdecho", OPT_EMULATE|OPT_SH}, BSDECHO},
+{{NULL, "caseglob", OPT_ALL}, CASEGLOB},
+{{NULL, "casematch", OPT_ALL}, CASEMATCH},
+{{NULL, "cbases", 0}, CBASES},
+{{NULL, "cprecedences", OPT_EMULATE|OPT_NONZSH}, CPRECEDENCES},
+{{NULL, "cdablevars", OPT_EMULATE}, CDABLEVARS},
+{{NULL, "chasedots", OPT_EMULATE}, CHASEDOTS},
+{{NULL, "chaselinks", OPT_EMULATE}, CHASELINKS},
+{{NULL, "checkjobs", OPT_EMULATE|OPT_ZSH}, CHECKJOBS},
+{{NULL, "checkrunningjobs", OPT_EMULATE|OPT_ZSH}, CHECKRUNNINGJOBS},
+{{NULL, "clobber", OPT_EMULATE|OPT_ALL}, CLOBBER},
+{{NULL, "combiningchars", 0}, COMBININGCHARS},
+{{NULL, "completealiases", 0}, COMPLETEALIASES},
+{{NULL, "completeinword", 0}, COMPLETEINWORD},
+{{NULL, "continueonerror", 0}, CONTINUEONERROR},
+{{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, "cshnullcmd", OPT_EMULATE|OPT_CSH}, CSHNULLCMD},
+{{NULL, "cshnullglob", OPT_EMULATE|OPT_CSH}, CSHNULLGLOB},
+{{NULL, "debugbeforecmd", OPT_ALL}, DEBUGBEFORECMD},
+{{NULL, "emacs", 0}, EMACSMODE},
+{{NULL, "equals", OPT_EMULATE|OPT_ZSH}, EQUALS},
+{{NULL, "errexit", OPT_EMULATE}, ERREXIT},
+{{NULL, "errreturn", OPT_EMULATE}, ERRRETURN},
+{{NULL, "exec", OPT_ALL}, EXECOPT},
+{{NULL, "extendedglob", OPT_EMULATE}, EXTENDEDGLOB},
+{{NULL, "extendedhistory", OPT_CSH}, EXTENDEDHISTORY},
+{{NULL, "evallineno", OPT_EMULATE|OPT_ZSH}, EVALLINENO},
+{{NULL, "flowcontrol", OPT_ALL}, FLOWCONTROL},
+{{NULL, "forcefloat", 0}, FORCEFLOAT},
+{{NULL, "functionargzero", OPT_EMULATE|OPT_NONBOURNE},FUNCTIONARGZERO},
+{{NULL, "glob", OPT_EMULATE|OPT_ALL}, GLOBOPT},
+{{NULL, "globalexport", OPT_EMULATE|OPT_ZSH}, GLOBALEXPORT},
+{{NULL, "globalrcs", OPT_ALL}, GLOBALRCS},
+{{NULL, "globassign", OPT_EMULATE|OPT_CSH}, GLOBASSIGN},
+{{NULL, "globcomplete", 0}, GLOBCOMPLETE},
+{{NULL, "globdots", OPT_EMULATE}, GLOBDOTS},
+{{NULL, "globstarshort", OPT_EMULATE}, GLOBSTARSHORT},
+{{NULL, "globsubst", OPT_EMULATE|OPT_NONZSH}, GLOBSUBST},
+{{NULL, "hashcmds", OPT_ALL}, HASHCMDS},
+{{NULL, "hashdirs", OPT_ALL}, HASHDIRS},
+{{NULL, "hashexecutablesonly", 0}, HASHEXECUTABLESONLY},
+{{NULL, "hashlistall", OPT_ALL}, HASHLISTALL},
+{{NULL, "histallowclobber", 0}, HISTALLOWCLOBBER},
+{{NULL, "histbeep", OPT_ALL}, HISTBEEP},
+{{NULL, "histexpiredupsfirst",0}, HISTEXPIREDUPSFIRST},
+{{NULL, "histfcntllock", 0}, HISTFCNTLLOCK},
+{{NULL, "histfindnodups", 0}, HISTFINDNODUPS},
+{{NULL, "histignorealldups", 0}, HISTIGNOREALLDUPS},
+{{NULL, "histignoredups", 0}, HISTIGNOREDUPS},
+{{NULL, "histignorespace", 0}, HISTIGNORESPACE},
+{{NULL, "histlexwords", 0}, HISTLEXWORDS},
+{{NULL, "histnofunctions", 0}, HISTNOFUNCTIONS},
+{{NULL, "histnostore", 0}, HISTNOSTORE},
+{{NULL, "histsubstpattern", OPT_EMULATE}, HISTSUBSTPATTERN},
+{{NULL, "histreduceblanks", 0}, HISTREDUCEBLANKS},
+{{NULL, "histsavebycopy", OPT_ALL}, HISTSAVEBYCOPY},
+{{NULL, "histsavenodups", 0}, HISTSAVENODUPS},
+{{NULL, "histverify", 0}, HISTVERIFY},
+{{NULL, "hup", OPT_EMULATE|OPT_ZSH}, HUP},
+{{NULL, "ignorebraces", OPT_EMULATE|OPT_SH}, IGNOREBRACES},
+{{NULL, "ignoreclosebraces", OPT_EMULATE}, IGNORECLOSEBRACES},
+{{NULL, "ignoreeof", 0}, IGNOREEOF},
+{{NULL, "incappendhistory", 0}, INCAPPENDHISTORY},
+{{NULL, "incappendhistorytime", 0}, INCAPPENDHISTORYTIME},
+{{NULL, "interactive", OPT_SPECIAL}, INTERACTIVE},
+{{NULL, "interactivecomments",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, "kshtypeset", 0}, KSHTYPESET},
+{{NULL, "kshzerosubscript", 0}, KSHZEROSUBSCRIPT},
+{{NULL, "listambiguous", OPT_ALL}, LISTAMBIGUOUS},
+{{NULL, "listbeep", OPT_ALL}, LISTBEEP},
+{{NULL, "listpacked", 0}, LISTPACKED},
+{{NULL, "listrowsfirst", 0}, LISTROWSFIRST},
+{{NULL, "listtypes", OPT_ALL}, LISTTYPES},
+{{NULL, "localoptions", OPT_EMULATE|OPT_KSH}, LOCALOPTIONS},
+{{NULL, "localloops", OPT_EMULATE}, LOCALLOOPS},
+{{NULL, "localpatterns", OPT_EMULATE}, LOCALPATTERNS},
+{{NULL, "localtraps", OPT_EMULATE|OPT_KSH}, LOCALTRAPS},
+{{NULL, "login", OPT_SPECIAL}, LOGINSHELL},
+{{NULL, "longlistjobs", 0}, LONGLISTJOBS},
+{{NULL, "magicequalsubst", OPT_EMULATE}, MAGICEQUALSUBST},
+{{NULL, "mailwarning", 0}, MAILWARNING},
+{{NULL, "markdirs", 0}, MARKDIRS},
+{{NULL, "menucomplete", 0}, MENUCOMPLETE},
+{{NULL, "monitor", OPT_SPECIAL}, MONITOR},
+{{NULL, "multibyte",
+#ifdef MULTIBYTE_SUPPORT
+ OPT_ALL
+#else
+ 0
+#endif
+ }, MULTIBYTE},
+{{NULL, "multifuncdef", OPT_EMULATE|OPT_ZSH}, MULTIFUNCDEF},
+{{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", OPT_EMULATE}, NUMERICGLOBSORT},
+{{NULL, "octalzeroes", OPT_EMULATE|OPT_SH}, OCTALZEROES},
+{{NULL, "overstrike", 0}, OVERSTRIKE},
+{{NULL, "pathdirs", OPT_EMULATE}, PATHDIRS},
+{{NULL, "pathscript", OPT_EMULATE|OPT_BOURNE}, PATHSCRIPT},
+{{NULL, "pipefail", OPT_EMULATE}, PIPEFAIL},
+{{NULL, "posixaliases", OPT_EMULATE|OPT_BOURNE}, POSIXALIASES},
+{{NULL, "posixargzero", OPT_EMULATE}, POSIXARGZERO},
+{{NULL, "posixbuiltins", OPT_EMULATE|OPT_BOURNE}, POSIXBUILTINS},
+{{NULL, "posixcd", OPT_EMULATE|OPT_BOURNE}, POSIXCD},
+{{NULL, "posixidentifiers", OPT_EMULATE|OPT_BOURNE}, POSIXIDENTIFIERS},
+{{NULL, "posixjobs", OPT_EMULATE|OPT_BOURNE}, POSIXJOBS},
+{{NULL, "posixstrings", OPT_EMULATE|OPT_BOURNE}, POSIXSTRINGS},
+{{NULL, "posixtraps", OPT_EMULATE|OPT_BOURNE}, POSIXTRAPS},
+{{NULL, "printeightbit", 0}, PRINTEIGHTBIT},
+{{NULL, "printexitvalue", 0}, PRINTEXITVALUE},
+{{NULL, "privileged", OPT_SPECIAL}, PRIVILEGED},
+{{NULL, "promptbang", OPT_KSH}, PROMPTBANG},
+{{NULL, "promptcr", OPT_ALL}, PROMPTCR},
+{{NULL, "promptpercent", OPT_NONBOURNE}, PROMPTPERCENT},
+{{NULL, "promptsp", OPT_ALL}, PROMPTSP},
+{{NULL, "promptsubst", OPT_BOURNE}, PROMPTSUBST},
+{{NULL, "pushdignoredups", OPT_EMULATE}, PUSHDIGNOREDUPS},
+{{NULL, "pushdminus", OPT_EMULATE}, PUSHDMINUS},
+{{NULL, "pushdsilent", 0}, PUSHDSILENT},
+{{NULL, "pushdtohome", OPT_EMULATE}, PUSHDTOHOME},
+{{NULL, "rcexpandparam", OPT_EMULATE}, RCEXPANDPARAM},
+{{NULL, "rcquotes", OPT_EMULATE}, RCQUOTES},
+{{NULL, "rcs", OPT_ALL}, RCS},
+{{NULL, "recexact", 0}, RECEXACT},
+{{NULL, "rematchpcre", 0}, REMATCHPCRE},
+{{NULL, "restricted", OPT_SPECIAL}, RESTRICTED},
+{{NULL, "rmstarsilent", OPT_BOURNE}, RMSTARSILENT},
+{{NULL, "rmstarwait", 0}, RMSTARWAIT},
+{{NULL, "sharehistory", OPT_KSH}, SHAREHISTORY},
+{{NULL, "shfileexpansion", OPT_EMULATE|OPT_BOURNE}, SHFILEEXPANSION},
+{{NULL, "shglob", OPT_EMULATE|OPT_BOURNE}, SHGLOB},
+{{NULL, "shinstdin", OPT_SPECIAL}, SHINSTDIN},
+{{NULL, "shnullcmd", OPT_EMULATE|OPT_BOURNE}, SHNULLCMD},
+{{NULL, "shoptionletters", OPT_EMULATE|OPT_BOURNE}, SHOPTIONLETTERS},
+{{NULL, "shortloops", OPT_EMULATE|OPT_NONBOURNE},SHORTLOOPS},
+{{NULL, "shwordsplit", OPT_EMULATE|OPT_BOURNE}, SHWORDSPLIT},
+{{NULL, "singlecommand", OPT_SPECIAL}, SINGLECOMMAND},
+{{NULL, "singlelinezle", OPT_KSH}, SINGLELINEZLE},
+{{NULL, "sourcetrace", 0}, SOURCETRACE},
+{{NULL, "sunkeyboardhack", 0}, SUNKEYBOARDHACK},
+{{NULL, "transientrprompt", 0}, TRANSIENTRPROMPT},
+{{NULL, "trapsasync", 0}, TRAPSASYNC},
+{{NULL, "typesetsilent", OPT_EMULATE|OPT_BOURNE}, TYPESETSILENT},
+{{NULL, "unset", OPT_EMULATE|OPT_BSHELL}, UNSET},
+{{NULL, "verbose", 0}, VERBOSE},
+{{NULL, "vi", 0}, VIMODE},
+{{NULL, "warncreateglobal", OPT_EMULATE}, WARNCREATEGLOBAL},
+{{NULL, "warnnestedvar", OPT_EMULATE}, WARNNESTEDVAR},
+{{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, "dvorak", 0}, DVORAK},
+{{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, /* use with set for arrays */
+ /* 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, /* in non-Bourne shells, end of options */
+ /* c */ 0, /* command follows */
+ /* d */ -GLOBALRCS,
+ /* e */ ERREXIT,
+ /* f */ -RCS,
+ /* g */ HISTIGNORESPACE,
+ /* h */ HISTIGNOREDUPS,
+ /* i */ INTERACTIVE,
+ /* j */ 0,
+ /* k */ INTERACTIVECOMMENTS,
+ /* l */ LOGINSHELL,
+ /* m */ MONITOR,
+ /* n */ -EXECOPT,
+ /* o */ 0, /* long option name follows */
+ /* 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 */ TRAPSASYNC,
+ /* 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, emulation))
+ printf("no%-19s %s\n", on->node.nam, isset(optno) ? "off" : "on");
+ else
+ printf("%-21s %s\n", on->node.nam, isset(optno) ? "on" : "off");
+ } else if (set == (isset(optno) ^ defset(on, emulation))) {
+ if (set ^ isset(optno))
+ fputs("no", stdout);
+ puts(on->node.nam);
+ }
+}
+
+/**/
+void
+createoptiontable(void)
+{
+ Optname on;
+
+ optiontab = newhashtable(101, "optiontab", NULL);
+
+ optiontab->hash = hasher;
+ optiontab->emptytable = NULL;
+ optiontab->filltable = NULL;
+ optiontab->cmpnodes = strcmp;
+ 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->node.nam; on++)
+ optiontab->addnode(optiontab, on->node.nam, on);
+}
+
+/* Emulation appropriate to the setemulate function */
+
+static int setemulate_emulation;
+
+/* Option array manipulated within the setemulate function */
+
+/**/
+static char *setemulate_opts;
+
+/* 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->node.flags & OPT_ALIAS) &&
+ ((fully && !(on->node.flags & OPT_SPECIAL)) ||
+ (on->node.flags & OPT_EMULATE)))
+ setemulate_opts[on->optno] = defset(on, setemulate_emulation);
+}
+
+/**/
+void
+installemulation(int new_emulation, char *new_opts)
+{
+ setemulate_emulation = new_emulation;
+ setemulate_opts = new_opts;
+ scanhashtable(optiontab, 0, 0, 0, setemulate,
+ !!(new_emulation & EMULATE_FULLY));
+}
+
+/**/
+void
+emulate(const char *zsh_name, int fully, int *new_emulation, char *new_opts)
+{
+ char ch = *zsh_name;
+
+ if (ch == 'r')
+ ch = zsh_name[1];
+
+ /* Work out the new emulation mode */
+ if (ch == 'c')
+ *new_emulation = EMULATE_CSH;
+ else if (ch == 'k')
+ *new_emulation = EMULATE_KSH;
+ else if (ch == 's' || ch == 'b')
+ *new_emulation = EMULATE_SH;
+ else
+ *new_emulation = EMULATE_ZSH;
+
+ if (fully)
+ *new_emulation |= EMULATE_FULLY;
+ installemulation(*new_emulation, new_opts);
+
+ if (funcstack && funcstack->tp == FS_FUNC) {
+ /*
+ * We are inside a function. Decide if it's traced.
+ * Pedantic note: the function in the function table isn't
+ * guaranteed to be what we're executing, but it's
+ * close enough.
+ */
+ Shfunc shf = (Shfunc)shfunctab->getnode(shfunctab, funcstack->name);
+ if (shf && (shf->node.flags & (PM_TAGGED|PM_TAGGED_LOCAL))) {
+ /* Tracing is on, so set xtrace */
+ new_opts[XTRACE] = 1;
+ }
+ }
+}
+
+/* setopt, unsetopt */
+
+/**/
+static void
+setoption(HashNode hn, int value)
+{
+ dosetopt(((Optname) hn)->optno, value, 0, opts);
+}
+
+/**/
+int
+bin_setopt(char *nam, char **args, UNUSED(Options 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");
+ inittyptab();
+ return 1;
+ }
+ if(!(optno = optlookup(*args)))
+ zwarnnam(nam, "no such option: %s", *args);
+ else if(dosetopt(optno, action, 0, opts))
+ zwarnnam(nam, "can't change option: %s", *args);
+ break;
+ } else if(**args == 'm') {
+ match = 1;
+ } else {
+ if (!(optno = optlookupc(**args)))
+ zwarnnam(nam, "bad option: -%c", **args);
+ else if(dosetopt(optno, action, 0, opts))
+ zwarnnam(nam, "can't change option: -%c", **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]);
+ else if(dosetopt(optno, !isun, 0, opts))
+ zwarnnam(nam, "can't change option: %s", args[-1]);
+ }
+ } else {
+ /* Globbing option (-m) set. */
+ while (*args) {
+ Patprog pprog;
+ char *s, *t;
+
+ t = s = dupstring(*args);
+ while (*t)
+ if (*t == '_')
+ chuck(t);
+ else {
+ /* See comment in optlookup() */
+ if (*t >= 'A' && *t <= 'Z')
+ *t = (*t - 'A') + 'a';
+ t++;
+ }
+
+ /* Expand the current arg. */
+ tokenize(s);
+ if (!(pprog = patcompile(s, PAT_HEAPDUP, NULL))) {
+ zwarnnam(nam, "bad pattern: %s", *args);
+ continue;
+ }
+ /* Loop over expansions. */
+ scanmatchtable(optiontab, pprog, 0, 0, OPT_ALIAS,
+ setoption, !isun);
+ args++;
+ }
+ }
+ inittyptab();
+ return 0;
+}
+
+/* Identify an option name */
+
+/**/
+mod_export 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 {
+ /*
+ * Some locales (in particular tr_TR.UTF-8) may
+ * have non-standard mappings of ASCII characters,
+ * so be careful. Option names must be ASCII so
+ * we don't need to be too clever.
+ */
+ if (*t >= 'A' && *t <= 'Z')
+ *t = (*t - 'A') + 'a';
+ 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->node.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. */
+
+/**/
+mod_export int
+dosetopt(int optno, int value, int force, char *new_opts)
+{
+ 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 == EXECOPT && !value && interact) {
+ /* cannot set noexec when interactive */
+ return -1;
+ } else if(!force && (optno == INTERACTIVE || optno == SHINSTDIN ||
+ optno == SINGLECOMMAND)) {
+ if (new_opts[optno] == value)
+ return 0;
+ /* 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
+ int ignore_err;
+ errno = 0;
+ /*
+ * Set the GID first as if we set the UID to non-privileged it
+ * might be impossible to restore the GID.
+ *
+ * Some OSes (possibly no longer around) have been known to
+ * fail silently the first time, so we attempt the change twice.
+ * If it fails we are guaranteed to pick this up the second
+ * time, so ignore the first time.
+ *
+ * Some versions of gcc make it hard to ignore the results the
+ * first time, hence the following. (These are probably not
+ * systems that require the doubled calls.)
+ */
+ ignore_err = setgid(getgid());
+ (void)ignore_err;
+ ignore_err = setuid(getuid());
+ (void)ignore_err;
+ if (setgid(getgid())) {
+ zwarn("failed to change group ID: %e", errno);
+ return -1;
+ } else if (setuid(getuid())) {
+ zwarn("failed to change user ID: %e", errno);
+ return -1;
+ }
+#else
+ zwarn("setuid not available");
+ return -1;
+#endif /* not HAVE_SETUID */
+#ifdef JOB_CONTROL
+ } else if (!force && optno == MONITOR && value) {
+ if (new_opts[optno] == value)
+ return 0;
+ if (SHTTY != -1) {
+ origpgrp = GETPGRP();
+ acquire_pgrp();
+ } else
+ return -1;
+#else
+ } else if(optno == MONITOR && value) {
+ return -1;
+#endif /* not JOB_CONTROL */
+#ifdef GETPWNAM_FAKED
+ } else if(optno == CDABLEVARS && value) {
+ return -1;
+#endif /* GETPWNAM_FAKED */
+ } else if ((optno == EMACSMODE || optno == VIMODE) && value) {
+ if (sticky && sticky->emulation)
+ return -1;
+ zleentry(ZLE_CMD_SET_KEYMAP, optno);
+ new_opts[(optno == EMACSMODE) ? VIMODE : EMACSMODE] = 0;
+ } else if (optno == SUNKEYBOARDHACK) {
+ /* for backward compatibility */
+ keyboardhackchar = (value ? '`' : '\0');
+ }
+ new_opts[optno] = value;
+ if (optno == BANGHIST || optno == SHINSTDIN)
+ inittyptab();
+ return 0;
+}
+
+/* Function to get value for special parameter `-' */
+
+/**/
+char *
+dashgetfn(UNUSED(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;
+}
+
+/* print options for set -o/+o */
+
+/**/
+void
+printoptionstates(int hadplus)
+{
+ scanhashtable(optiontab, 1, 0, OPT_ALIAS, printoptionnodestate, hadplus);
+}
+
+/**/
+static void
+printoptionnodestate(HashNode hn, int hadplus)
+{
+ Optname on = (Optname) hn;
+ int optno = on->optno;
+
+ if (hadplus) {
+ printf("set %co %s%s\n",
+ defset(on, emulation) != isset(optno) ? '-' : '+',
+ defset(on, emulation) ? "no" : "",
+ on->node.nam);
+ } else {
+ if (defset(on, emulation))
+ printf("no%-19s %s\n", on->node.nam, isset(optno) ? "off" : "on");
+ else
+ printf("%-21s %s\n", on->node.nam, isset(optno) ? "on" : "off");
+ }
+}
+
+/* Print option list for --help */
+
+/**/
+void
+printoptionlist(void)
+{
+ short *lp;
+ char c;
+
+ printf("\nNamed options:\n");
+ scanhashtable(optiontab, 1, 0, OPT_ALIAS, printoptionlist_printoption, 0);
+ printf("\nOption aliases:\n");
+ scanhashtable(optiontab, 1, OPT_ALIAS, 0, printoptionlist_printoption, 0);
+ printf("\nOption letters:\n");
+ for(lp = optletters, c = FIRST_OPT; c <= LAST_OPT; lp++, c++) {
+ if(!*lp)
+ continue;
+ printf(" -%c ", c);
+ printoptionlist_printequiv(*lp);
+ }
+}
+
+/**/
+static void
+printoptionlist_printoption(HashNode hn, UNUSED(int ignored))
+{
+ Optname on = (Optname) hn;
+
+ if(on->node.flags & OPT_ALIAS) {
+ printf(" --%-19s ", on->node.nam);
+ printoptionlist_printequiv(on->optno);
+ } else
+ printf(" --%s\n", on->node.nam);
+}
+
+/**/
+static void
+printoptionlist_printequiv(int optno)
+{
+ int isneg = optno < 0;
+
+ optno *= (isneg ? -1 : 1);
+ printf(" equivalent to --%s%s\n", isneg ? "no-" : "", optns[optno-1].node.nam);
+}
+
+/**/
+static char *print_emulate_opts;
+
+/**/
+static void
+print_emulate_option(HashNode hn, int fully)
+{
+ Optname on = (Optname) hn;
+
+ if (!(on->node.flags & OPT_ALIAS) &&
+ ((fully && !(on->node.flags & OPT_SPECIAL)) ||
+ (on->node.flags & OPT_EMULATE)))
+ {
+ if (!print_emulate_opts[on->optno])
+ fputs("no", stdout);
+ puts(on->node.nam);
+ }
+}
+
+/*
+ * List the settings of options associated with an emulation
+ */
+
+/**/
+void list_emulate_options(char *cmdopts, int fully)
+{
+ print_emulate_opts = cmdopts;
+ scanhashtable(optiontab, 1, 0, 0, print_emulate_option, fully);
+}
diff --git a/dotfiles/system/.zsh/modules/Src/params.c b/dotfiles/system/.zsh/modules/Src/params.c
new file mode 100644
index 0000000..a1c299f
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/params.c
@@ -0,0 +1,5884 @@
+/*
+ * 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"
+#ifdef CUSTOM_PATCHLEVEL
+#define ZSH_PATCHLEVEL CUSTOM_PATCHLEVEL
+#else
+#include "patchlevel.h"
+
+#include <math.h>
+
+/* If removed from the ChangeLog for some reason */
+#ifndef ZSH_PATCHLEVEL
+#define ZSH_PATCHLEVEL "unknown"
+#endif
+#endif
+
+/* what level of localness we are at */
+
+/**/
+mod_export int locallevel;
+
+/* Variables holding values of special parameters */
+
+/**/
+mod_export
+char **pparams, /* $argv */
+ **cdpath, /* $cdpath */
+ **fpath, /* $fpath */
+ **mailpath, /* $mailpath */
+ **manpath, /* $manpath */
+ **psvar, /* $psvar */
+ **watch, /* $watch */
+ **zsh_eval_context; /* $zsh_eval_context */
+/**/
+mod_export
+char **path, /* $path */
+ **fignore; /* $fignore */
+
+/**/
+mod_export
+char *argzero, /* $0 */
+ *posixzero, /* $0 */
+ *home, /* $HOME */
+ *nullcmd, /* $NULLCMD */
+ *oldpwd, /* $OLDPWD */
+ *zoptarg, /* $OPTARG */
+ *prompt, /* $PROMPT */
+ *prompt2, /* $PROMPT2 */
+ *prompt3, /* $PROMPT3 */
+ *prompt4, /* $PROMPT4 */
+ *readnullcmd, /* $READNULLCMD */
+ *rprompt, /* $RPROMPT */
+ *rprompt2, /* $RPROMPT2 */
+ *sprompt, /* $SPROMPT */
+ *wordchars; /* $WORDCHARS */
+/**/
+mod_export
+char *ifs, /* $IFS */
+ *postedit, /* $POSTEDIT */
+ *term, /* $TERM */
+ *zsh_terminfo, /* $TERMINFO */
+ *zsh_terminfodirs, /* $TERMINFO_DIRS */
+ *ttystrname, /* $TTY */
+ *pwd; /* $PWD */
+
+/**/
+mod_export
+zlong lastval, /* $? */
+ mypid, /* $$ */
+ lastpid, /* $! */
+ zterm_columns, /* $COLUMNS */
+ zterm_lines, /* $LINES */
+ rprompt_indent, /* $ZLE_RPROMPT_INDENT */
+ ppid, /* $PPID */
+ zsh_subshell; /* $ZSH_SUBSHELL */
+
+/* $FUNCNEST */
+/**/
+mod_export
+zlong zsh_funcnest =
+#ifdef MAX_FUNCTION_DEPTH
+ MAX_FUNCTION_DEPTH
+#else
+ /* Disabled by default but can be enabled at run time */
+ -1
+#endif
+ ;
+
+/**/
+zlong lineno, /* $LINENO */
+ zoptind, /* $OPTIND */
+ shlvl; /* $SHLVL */
+
+/* $histchars */
+
+/**/
+mod_export unsigned char bangchar;
+/**/
+unsigned char hatchar, hashchar;
+
+/**/
+unsigned char keyboardhackchar = '\0';
+
+/* $SECONDS = now.tv_sec - shtimer.tv_sec
+ * + (now.tv_usec - shtimer.tv_usec) / 1000000.0
+ * (rounded to an integer if the parameter is not set to float) */
+
+/**/
+struct timeval shtimer;
+
+/* 0 if this $TERM setup is usable, otherwise it contains TERM_* flags */
+
+/**/
+mod_export int termflags;
+
+/* Forward declaration */
+
+static void
+rprompt_indent_unsetfn(Param pm, int exp);
+
+/* Standard methods for get/set/unset pointers in parameters */
+
+/**/
+mod_export const struct gsu_scalar stdscalar_gsu =
+{ strgetfn, strsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_scalar varscalar_gsu =
+{ strvargetfn, strvarsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_scalar nullsetscalar_gsu =
+{ strgetfn, nullstrsetfn, NULL };
+
+/**/
+mod_export const struct gsu_integer stdinteger_gsu =
+{ intgetfn, intsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_integer varinteger_gsu =
+{ intvargetfn, intvarsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_integer nullsetinteger_gsu =
+{ intgetfn, NULL, NULL };
+
+/**/
+mod_export const struct gsu_float stdfloat_gsu =
+{ floatgetfn, floatsetfn, stdunsetfn };
+
+/**/
+mod_export const struct gsu_array stdarray_gsu =
+{ arrgetfn, arrsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_array vararray_gsu =
+{ arrvargetfn, arrvarsetfn, stdunsetfn };
+
+/**/
+mod_export const struct gsu_hash stdhash_gsu =
+{ hashgetfn, hashsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_hash nullsethash_gsu =
+{ hashgetfn, nullsethashfn, nullunsetfn };
+
+
+/* Non standard methods (not exported) */
+static const struct gsu_integer pound_gsu =
+{ poundgetfn, nullintsetfn, stdunsetfn };
+static const struct gsu_integer errno_gsu =
+{ errnogetfn, errnosetfn, stdunsetfn };
+static const struct gsu_integer gid_gsu =
+{ gidgetfn, gidsetfn, stdunsetfn };
+static const struct gsu_integer egid_gsu =
+{ egidgetfn, egidsetfn, stdunsetfn };
+static const struct gsu_integer histsize_gsu =
+{ histsizegetfn, histsizesetfn, stdunsetfn };
+static const struct gsu_integer random_gsu =
+{ randomgetfn, randomsetfn, stdunsetfn };
+static const struct gsu_integer savehist_gsu =
+{ savehistsizegetfn, savehistsizesetfn, stdunsetfn };
+static const struct gsu_integer intseconds_gsu =
+{ intsecondsgetfn, intsecondssetfn, stdunsetfn };
+static const struct gsu_float floatseconds_gsu =
+{ floatsecondsgetfn, floatsecondssetfn, stdunsetfn };
+static const struct gsu_integer uid_gsu =
+{ uidgetfn, uidsetfn, stdunsetfn };
+static const struct gsu_integer euid_gsu =
+{ euidgetfn, euidsetfn, stdunsetfn };
+static const struct gsu_integer ttyidle_gsu =
+{ ttyidlegetfn, nullintsetfn, stdunsetfn };
+
+static const struct gsu_scalar argzero_gsu =
+{ argzerogetfn, argzerosetfn, nullunsetfn };
+static const struct gsu_scalar username_gsu =
+{ usernamegetfn, usernamesetfn, stdunsetfn };
+static const struct gsu_scalar dash_gsu =
+{ dashgetfn, nullstrsetfn, stdunsetfn };
+static const struct gsu_scalar histchars_gsu =
+{ histcharsgetfn, histcharssetfn, stdunsetfn };
+static const struct gsu_scalar home_gsu =
+{ homegetfn, homesetfn, stdunsetfn };
+static const struct gsu_scalar term_gsu =
+{ termgetfn, termsetfn, stdunsetfn };
+static const struct gsu_scalar terminfo_gsu =
+{ terminfogetfn, terminfosetfn, stdunsetfn };
+static const struct gsu_scalar terminfodirs_gsu =
+{ terminfodirsgetfn, terminfodirssetfn, stdunsetfn };
+static const struct gsu_scalar wordchars_gsu =
+{ wordcharsgetfn, wordcharssetfn, stdunsetfn };
+static const struct gsu_scalar ifs_gsu =
+{ ifsgetfn, ifssetfn, stdunsetfn };
+static const struct gsu_scalar underscore_gsu =
+{ underscoregetfn, nullstrsetfn, stdunsetfn };
+static const struct gsu_scalar keyboard_hack_gsu =
+{ keyboardhackgetfn, keyboardhacksetfn, stdunsetfn };
+#ifdef USE_LOCALE
+static const struct gsu_scalar lc_blah_gsu =
+{ strgetfn, lcsetfn, stdunsetfn };
+static const struct gsu_scalar lang_gsu =
+{ strgetfn, langsetfn, stdunsetfn };
+static const struct gsu_scalar lc_all_gsu =
+{ strgetfn, lc_allsetfn, stdunsetfn };
+#endif
+
+static const struct gsu_integer varint_readonly_gsu =
+{ intvargetfn, nullintsetfn, stdunsetfn };
+static const struct gsu_integer zlevar_gsu =
+{ intvargetfn, zlevarsetfn, stdunsetfn };
+
+static const struct gsu_scalar colonarr_gsu =
+{ colonarrgetfn, colonarrsetfn, stdunsetfn };
+
+static const struct gsu_integer argc_gsu =
+{ poundgetfn, nullintsetfn, stdunsetfn };
+static const struct gsu_array pipestatus_gsu =
+{ pipestatgetfn, pipestatsetfn, stdunsetfn };
+
+static const struct gsu_integer rprompt_indent_gsu =
+{ intvargetfn, zlevarsetfn, rprompt_indent_unsetfn };
+
+/* Nodes for special parameters for parameter hash table */
+
+#ifdef HAVE_UNION_INIT
+# define BR(X) {X}
+typedef struct param initparam;
+#else
+# define BR(X) X
+typedef struct iparam {
+ struct hashnode *next;
+ char *nam; /* hash data */
+ int flags; /* PM_* flags (defined in zsh.h) */
+ void *value;
+ void *gsu; /* get/set/unset methods */
+ int base; /* output base */
+ int width; /* output 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 */
+} initparam;
+#endif
+
+static initparam special_params[] ={
+#define GSU(X) BR((GsuScalar)(void *)(&(X)))
+#define NULL_GSU BR((GsuScalar)(void *)NULL)
+#define IPDEF1(A,B,C) {{NULL,A,PM_INTEGER|PM_SPECIAL|C},BR(NULL),GSU(B),10,0,NULL,NULL,NULL,0}
+IPDEF1("#", pound_gsu, PM_READONLY),
+IPDEF1("ERRNO", errno_gsu, PM_UNSET),
+IPDEF1("GID", gid_gsu, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("EGID", egid_gsu, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("HISTSIZE", histsize_gsu, PM_RESTRICTED),
+IPDEF1("RANDOM", random_gsu, 0),
+IPDEF1("SAVEHIST", savehist_gsu, PM_RESTRICTED),
+IPDEF1("SECONDS", intseconds_gsu, 0),
+IPDEF1("UID", uid_gsu, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("EUID", euid_gsu, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("TTYIDLE", ttyidle_gsu, PM_READONLY),
+
+#define IPDEF2(A,B,C) {{NULL,A,PM_SCALAR|PM_SPECIAL|C},BR(NULL),GSU(B),0,0,NULL,NULL,NULL,0}
+IPDEF2("USERNAME", username_gsu, PM_DONTIMPORT|PM_RESTRICTED),
+IPDEF2("-", dash_gsu, PM_READONLY),
+IPDEF2("histchars", histchars_gsu, PM_DONTIMPORT),
+IPDEF2("HOME", home_gsu, PM_UNSET),
+IPDEF2("TERM", term_gsu, PM_UNSET),
+IPDEF2("TERMINFO", terminfo_gsu, PM_UNSET),
+IPDEF2("TERMINFO_DIRS", terminfodirs_gsu, PM_UNSET),
+IPDEF2("WORDCHARS", wordchars_gsu, 0),
+IPDEF2("IFS", ifs_gsu, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF2("_", underscore_gsu, PM_DONTIMPORT),
+IPDEF2("KEYBOARD_HACK", keyboard_hack_gsu, PM_DONTIMPORT),
+IPDEF2("0", argzero_gsu, 0),
+
+#ifdef USE_LOCALE
+# define LCIPDEF(name) IPDEF2(name, lc_blah_gsu, PM_UNSET)
+IPDEF2("LANG", lang_gsu, PM_UNSET),
+IPDEF2("LC_ALL", lc_all_gsu, 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_NUMERIC
+LCIPDEF("LC_NUMERIC"),
+# endif
+# ifdef LC_TIME
+LCIPDEF("LC_TIME"),
+# endif
+#endif /* USE_LOCALE */
+
+#define IPDEF4(A,B) {{NULL,A,PM_INTEGER|PM_READONLY|PM_SPECIAL},BR((void *)B),GSU(varint_readonly_gsu),10,0,NULL,NULL,NULL,0}
+IPDEF4("!", &lastpid),
+IPDEF4("$", &mypid),
+IPDEF4("?", &lastval),
+IPDEF4("HISTCMD", &curhist),
+IPDEF4("LINENO", &lineno),
+IPDEF4("PPID", &ppid),
+IPDEF4("ZSH_SUBSHELL", &zsh_subshell),
+
+#define IPDEF5(A,B,F) {{NULL,A,PM_INTEGER|PM_SPECIAL},BR((void *)B),GSU(F),10,0,NULL,NULL,NULL,0}
+#define IPDEF5U(A,B,F) {{NULL,A,PM_INTEGER|PM_SPECIAL|PM_UNSET},BR((void *)B),GSU(F),10,0,NULL,NULL,NULL,0}
+IPDEF5("COLUMNS", &zterm_columns, zlevar_gsu),
+IPDEF5("LINES", &zterm_lines, zlevar_gsu),
+IPDEF5U("ZLE_RPROMPT_INDENT", &rprompt_indent, rprompt_indent_gsu),
+IPDEF5("SHLVL", &shlvl, varinteger_gsu),
+IPDEF5("FUNCNEST", &zsh_funcnest, varinteger_gsu),
+
+/* Don't import internal integer status variables. */
+#define IPDEF6(A,B,F) {{NULL,A,PM_INTEGER|PM_SPECIAL|PM_DONTIMPORT},BR((void *)B),GSU(F),10,0,NULL,NULL,NULL,0}
+IPDEF6("OPTIND", &zoptind, varinteger_gsu),
+IPDEF6("TRY_BLOCK_ERROR", &try_errflag, varinteger_gsu),
+IPDEF6("TRY_BLOCK_INTERRUPT", &try_interrupt, varinteger_gsu),
+
+#define IPDEF7(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL},BR((void *)B),GSU(varscalar_gsu),0,0,NULL,NULL,NULL,0}
+#define IPDEF7R(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL|PM_DONTIMPORT_SUID},BR((void *)B),GSU(varscalar_gsu),0,0,NULL,NULL,NULL,0}
+#define IPDEF7U(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL|PM_UNSET},BR((void *)B),GSU(varscalar_gsu),0,0,NULL,NULL,NULL,0}
+IPDEF7("OPTARG", &zoptarg),
+IPDEF7("NULLCMD", &nullcmd),
+IPDEF7U("POSTEDIT", &postedit),
+IPDEF7("READNULLCMD", &readnullcmd),
+IPDEF7("PS1", &prompt),
+IPDEF7U("RPS1", &rprompt),
+IPDEF7U("RPROMPT", &rprompt),
+IPDEF7("PS2", &prompt2),
+IPDEF7U("RPS2", &rprompt2),
+IPDEF7U("RPROMPT2", &rprompt2),
+IPDEF7("PS3", &prompt3),
+IPDEF7R("PS4", &prompt4),
+IPDEF7("SPROMPT", &sprompt),
+
+#define IPDEF9F(A,B,C,D) {{NULL,A,D|PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT},BR((void *)B),GSU(vararray_gsu),0,0,NULL,C,NULL,0}
+#define IPDEF9(A,B,C) IPDEF9F(A,B,C,0)
+IPDEF9F("*", &pparams, NULL, PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT|PM_READONLY),
+IPDEF9F("@", &pparams, NULL, PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT|PM_READONLY),
+
+/*
+ * This empty row indicates the end of parameters available in
+ * all emulations.
+ */
+{{NULL,NULL,0},BR(NULL),NULL_GSU,0,0,NULL,NULL,NULL,0},
+
+#define IPDEF8(A,B,C,D) {{NULL,A,D|PM_SCALAR|PM_SPECIAL},BR((void *)B),GSU(colonarr_gsu),0,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),
+IPDEF8("ZSH_EVAL_CONTEXT", &zsh_eval_context, "zsh_eval_context", PM_READONLY),
+
+/* MODULE_PATH is not imported for security reasons */
+IPDEF8("MODULE_PATH", &module_path, "module_path", PM_DONTIMPORT|PM_RESTRICTED),
+
+#define IPDEF10(A,B) {{NULL,A,PM_ARRAY|PM_SPECIAL},BR(NULL),GSU(B),10,0,NULL,NULL,NULL,0}
+
+/*
+ * The following parameters are not available in sh/ksh compatibility *
+ * mode.
+ */
+
+/* All of these have sh compatible equivalents. */
+IPDEF1("ARGC", argc_gsu, PM_READONLY),
+IPDEF2("HISTCHARS", histchars_gsu, 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"),
+
+IPDEF9F("zsh_eval_context", &zsh_eval_context, "ZSH_EVAL_CONTEXT", PM_READONLY),
+
+IPDEF9F("module_path", &module_path, "MODULE_PATH", PM_RESTRICTED),
+IPDEF9F("path", &path, "PATH", PM_RESTRICTED),
+
+/* These are known to zsh alone. */
+
+IPDEF10("pipestatus", pipestatus_gsu),
+
+{{NULL,NULL,0},BR(NULL),NULL_GSU,0,0,NULL,NULL,NULL,0},
+};
+
+/*
+ * Alternative versions of colon-separated path parameters for
+ * sh emulation. These don't link to the array versions.
+ */
+static initparam special_params_sh[] = {
+IPDEF8("CDPATH", &cdpath, NULL, 0),
+IPDEF8("FIGNORE", &fignore, NULL, 0),
+IPDEF8("FPATH", &fpath, NULL, 0),
+IPDEF8("MAILPATH", &mailpath, NULL, 0),
+IPDEF8("WATCH", &watch, NULL, 0),
+IPDEF8("PATH", &path, NULL, PM_RESTRICTED),
+IPDEF8("PSVAR", &psvar, NULL, 0),
+IPDEF8("ZSH_EVAL_CONTEXT", &zsh_eval_context, NULL, PM_READONLY),
+
+/* MODULE_PATH is not imported for security reasons */
+IPDEF8("MODULE_PATH", &module_path, NULL, PM_DONTIMPORT|PM_RESTRICTED),
+
+{{NULL,NULL,0},BR(NULL),NULL_GSU,0,0,NULL,NULL,NULL,0},
+};
+
+/*
+ * Special way of referring to the positional parameters. Unlike $*
+ * and $@, this is not readonly. This parameter is not directly
+ * visible in user space.
+ */
+static initparam argvparam_pm = IPDEF9F("", &pparams, NULL, \
+ PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT);
+
+#undef BR
+
+#define IS_UNSET_VALUE(V) \
+ ((V) && (!(V)->pm || ((V)->pm->node.flags & PM_UNSET) || \
+ !(V)->pm->node.nam || !*(V)->pm->node.nam))
+
+static Param argvparam;
+
+/* hash table containing the parameters */
+
+/**/
+mod_export HashTable paramtab, realparamtab;
+
+/**/
+mod_export HashTable
+newparamtable(int size, char const *name)
+{
+ HashTable ht;
+ if (!size)
+ size = 17;
+ ht = newhashtable(size, name, NULL);
+
+ ht->hash = hasher;
+ ht->emptytable = emptyhashtable;
+ ht->filltable = NULL;
+ ht->cmpnodes = strcmp;
+ ht->addnode = addhashnode;
+ ht->getnode = getparamnode;
+ ht->getnode2 = gethashnode2;
+ ht->removenode = removehashnode;
+ ht->disablenode = NULL;
+ ht->enablenode = NULL;
+ ht->freenode = freeparamnode;
+ ht->printnode = printparamnode;
+
+ return ht;
+}
+
+/**/
+static HashNode
+getparamnode(HashTable ht, const char *nam)
+{
+ HashNode hn = gethashnode2(ht, nam);
+ Param pm = (Param) hn;
+
+ if (pm && pm->u.str && (pm->node.flags & PM_AUTOLOAD)) {
+ char *mn = dupstring(pm->u.str);
+
+ (void)ensurefeature(mn, "p:", (pm->node.flags & PM_AUTOALL) ? NULL :
+ nam);
+ hn = gethashnode2(ht, nam);
+ if (!hn) {
+ /*
+ * This used to be a warning, but surely if we allow
+ * stuff to go ahead with the autoload stub with
+ * no error status we're in for all sorts of mayhem?
+ */
+ zerr("autoloading module %s failed to define parameter: %s", mn,
+ nam);
+ }
+ }
+ return hn;
+}
+
+/* Copy a parameter hash table */
+
+static HashTable outtable;
+
+/**/
+static void
+scancopyparams(HashNode hn, UNUSED(int flags))
+{
+ /* Going into a real parameter, so always use permanent storage */
+ Param pm = (Param)hn;
+ Param tpm = (Param) zshcalloc(sizeof *tpm);
+ tpm->node.nam = ztrdup(pm->node.nam);
+ copyparam(tpm, pm, 0);
+ addhashnode(outtable, tpm->node.nam, tpm);
+}
+
+/**/
+HashTable
+copyparamtable(HashTable ht, char *name)
+{
+ HashTable nht = 0;
+ if (ht) {
+ nht = newparamtable(ht->hsize, name);
+ outtable = nht;
+ scanhashtable(ht, 0, 0, 0, scancopyparams, 0);
+ outtable = NULL;
+ }
+ return nht;
+}
+
+/* Flag to freeparamnode to unset the struct */
+
+static int delunset;
+
+/* Function to delete a parameter table. */
+
+/**/
+mod_export void
+deleteparamtable(HashTable t)
+{
+ /* The parameters in the hash table need to be unset *
+ * before being deleted. */
+ int odelunset = delunset;
+ delunset = 1;
+ deletehashtable(t);
+ delunset = odelunset;
+}
+
+static unsigned numparamvals;
+
+/**/
+mod_export void
+scancountparams(UNUSED(HashNode hn), int flags)
+{
+ ++numparamvals;
+ if ((flags & SCANPM_WANTKEYS) && (flags & SCANPM_WANTVALS))
+ ++numparamvals;
+}
+
+static Patprog scanprog;
+static char *scanstr;
+static char **paramvals;
+static Param foundparam;
+
+/**/
+static void
+scanparamvals(HashNode hn, int flags)
+{
+ struct value v;
+ Patprog prog;
+
+ if (numparamvals && !(flags & SCANPM_MATCHMANY) &&
+ (flags & (SCANPM_MATCHVAL|SCANPM_MATCHKEY|SCANPM_KEYMATCH)))
+ return;
+ v.pm = (Param)hn;
+ if ((flags & SCANPM_KEYMATCH)) {
+ char *tmp = dupstring(v.pm->node.nam);
+
+ tokenize(tmp);
+ remnulargs(tmp);
+
+ if (!(prog = patcompile(tmp, 0, NULL)) || !pattry(prog, scanstr))
+ return;
+ } else if ((flags & SCANPM_MATCHKEY) && !pattry(scanprog, v.pm->node.nam)) {
+ return;
+ }
+ foundparam = v.pm;
+ if (flags & SCANPM_WANTKEYS) {
+ paramvals[numparamvals++] = v.pm->node.nam;
+ if (!(flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)))
+ return;
+ }
+ v.isarr = (PM_TYPE(v.pm->node.flags) & (PM_ARRAY|PM_HASHED));
+ v.flags = 0;
+ v.start = 0;
+ v.end = -1;
+ paramvals[numparamvals] = getstrvalue(&v);
+ if (flags & SCANPM_MATCHVAL) {
+ if (pattry(scanprog, paramvals[numparamvals])) {
+ numparamvals += ((flags & SCANPM_WANTVALS) ? 1 :
+ !(flags & SCANPM_WANTKEYS));
+ } else if (flags & SCANPM_WANTKEYS)
+ --numparamvals; /* Value didn't match, discard key */
+ } else
+ ++numparamvals;
+ foundparam = NULL;
+}
+
+/**/
+char **
+paramvalarr(HashTable ht, int flags)
+{
+ DPUTS((flags & (SCANPM_MATCHKEY|SCANPM_MATCHVAL)) && !scanprog,
+ "BUG: scanning hash without scanprog set");
+ numparamvals = 0;
+ if (ht)
+ scanhashtable(ht, 0, 0, PM_UNSET, scancountparams, flags);
+ paramvals = (char **) zhalloc((numparamvals + 1) * sizeof(char *));
+ if (ht) {
+ numparamvals = 0;
+ scanhashtable(ht, 0, 0, PM_UNSET, scanparamvals, flags);
+ }
+ paramvals[numparamvals] = 0;
+ return paramvals;
+}
+
+/* Return the full array (no indexing) referred to by a Value. *
+ * The array value is cached for the lifetime of the Value. */
+
+/**/
+static char **
+getvaluearr(Value v)
+{
+ if (v->arr)
+ return v->arr;
+ else if (PM_TYPE(v->pm->node.flags) == PM_ARRAY)
+ return v->arr = v->pm->gsu.a->getfn(v->pm);
+ else if (PM_TYPE(v->pm->node.flags) == PM_HASHED) {
+ v->arr = paramvalarr(v->pm->gsu.h->getfn(v->pm), v->isarr);
+ /* Can't take numeric slices of associative arrays */
+ v->start = 0;
+ v->end = numparamvals + 1;
+ return v->arr;
+ } else
+ return NULL;
+}
+
+/* Return whether the variable is set *
+ * checks that array slices are within range *
+ * used for [[ -v ... ]] condition test */
+
+/**/
+int
+issetvar(char *name)
+{
+ struct value vbuf;
+ Value v;
+ int slice;
+ char **arr;
+
+ if (!(v = getvalue(&vbuf, &name, 1)) || *name)
+ return 0; /* no value or more chars after the variable name */
+ if (v->isarr & ~SCANPM_ARRONLY)
+ return v->end > 1; /* for extracted elements, end gives us a count */
+
+ slice = v->start != 0 || v->end != -1;
+ if (PM_TYPE(v->pm->node.flags) != PM_ARRAY || !slice)
+ return !slice && !(v->pm->node.flags & PM_UNSET);
+
+ if (!v->end) /* empty array slice */
+ return 0;
+ /* get the array and check end is within range */
+ if (!(arr = getvaluearr(v)))
+ return 0;
+ return arrlen_ge(arr, v->end < 0 ? - v->end : v->end);
+}
+
+/*
+ * Split environment string into (name, value) pair.
+ * this is used to avoid in-place editing of environment table
+ * that results in core dump on some systems
+ */
+
+static int
+split_env_string(char *env, char **name, char **value)
+{
+ char *str, *tenv;
+
+ if (!env || !name || !value)
+ return 0;
+
+ tenv = strcpy(zhalloc(strlen(env) + 1), env);
+ for (str = tenv; *str && *str != '='; str++) {
+ if (STOUC(*str) >= 128) {
+ /*
+ * We'll ignore environment variables with names not
+ * from the portable character set since we don't
+ * know of a good reason to accept them.
+ */
+ return 0;
+ }
+ }
+ if (str != tenv && *str == '=') {
+ *str = '\0';
+ *name = tenv;
+ *value = str + 1;
+ return 1;
+ } else
+ return 0;
+}
+
+/**
+ * Check parameter flags to see if parameter shouldn't be imported
+ * from environment at start.
+ *
+ * return 1: don't import: 0: ok to import.
+ */
+static int dontimport(int flags)
+{
+ /* If explicitly marked as don't export */
+ if (flags & PM_DONTIMPORT)
+ return 1;
+ /* If value already exported */
+ if (flags & PM_EXPORTED)
+ return 1;
+ /* If security issue when importing and running with some privilege */
+ if ((flags & PM_DONTIMPORT_SUID) && isset(PRIVILEGED))
+ return 1;
+ /* OK to import */
+ return 0;
+}
+
+/* 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;
+#if !defined(HAVE_PUTENV) && !defined(USE_SET_UNSET_ENV)
+ char **new_environ;
+ int envsize;
+#endif
+#ifndef USE_SET_UNSET_ENV
+ char **envp;
+#endif
+ char **envp2, **sigptr, **t;
+ char buf[50], *str, *iname, *ivalue, *hostnam;
+ int oae = opts[ALLEXPORT];
+#ifdef HAVE_UNAME
+ struct utsname unamebuf;
+ char *machinebuf;
+#endif
+
+ paramtab = realparamtab = newparamtable(151, "paramtab");
+
+ /* Add the special parameters to the hash table */
+ for (ip = special_params; ip->node.nam; ip++)
+ paramtab->addnode(paramtab, ztrdup(ip->node.nam), ip);
+ if (EMULATION(EMULATE_SH|EMULATE_KSH)) {
+ for (ip = special_params_sh; ip->node.nam; ip++)
+ paramtab->addnode(paramtab, ztrdup(ip->node.nam), ip);
+ } else {
+ while ((++ip)->node.nam)
+ paramtab->addnode(paramtab, ztrdup(ip->node.nam), ip);
+ }
+
+ argvparam = (Param) &argvparam_pm;
+
+ noerrs = 2;
+
+ /* 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 user has *
+ * given them in the environment. */
+ opts[ALLEXPORT] = 0;
+ setiparam("MAILCHECK", 60);
+ setiparam("LOGCHECK", 60);
+ setiparam("KEYTIMEOUT", 40);
+ setiparam("LISTMAX", 100);
+ /*
+ * We used to get the output baud rate here. However, that's
+ * pretty irrelevant to a terminal on an X display and can lead
+ * to unnecessary delays if it's wrong (which it probably is).
+ * Furthermore, even if the output is slow it's very likely
+ * to be because of WAN delays, not covered by the output
+ * baud rate.
+ * So allow the user to set it in the special cases where it's
+ * useful.
+ */
+ setsparam("TMPPREFIX", ztrdup_metafy(DEFAULT_TMPPREFIX));
+ setsparam("TIMEFMT", ztrdup_metafy(DEFAULT_TIMEFMT));
+ setsparam("WATCHFMT", ztrdup_metafy(default_watchfmt));
+
+ hostnam = (char *)zalloc(256);
+ gethostname(hostnam, 256);
+ setsparam("HOST", ztrdup_metafy(hostnam));
+ zfree(hostnam, 256);
+
+ setsparam("LOGNAME",
+ ztrdup_metafy((str = getlogin()) && *str ?
+ str : cached_username));
+
+#if !defined(HAVE_PUTENV) && !defined(USE_SET_UNSET_ENV)
+ /* Copy the environment variables we are inheriting to dynamic *
+ * memory, so we can do mallocs and frees on it. */
+ envsize = sizeof(char *)*(1 + arrlen(environ));
+ new_environ = (char **) zalloc(envsize);
+ memcpy(new_environ, environ, envsize);
+ environ = new_environ;
+#endif
+
+ /* Use heap allocation to avoid many small alloc/free calls */
+ pushheap();
+
+ /* Now incorporate environment variables we are inheriting *
+ * into the parameter hash table. Copy them into dynamic *
+ * memory so that we can free them if needed */
+ for (
+#ifndef USE_SET_UNSET_ENV
+ envp =
+#endif
+ envp2 = environ; *envp2; envp2++) {
+ if (split_env_string(*envp2, &iname, &ivalue)) {
+ if (!idigit(*iname) && isident(iname) && !strchr(iname, '[')) {
+ /*
+ * Parameters that aren't already in the parameter table
+ * aren't special to the shell, so it's always OK to
+ * import. Otherwise, check parameter flags.
+ */
+ if ((!(pm = (Param) paramtab->getnode(paramtab, iname)) ||
+ !dontimport(pm->node.flags)) &&
+ (pm = assignsparam(iname, metafy(ivalue, -1, META_DUP),
+ ASSPM_ENV_IMPORT))) {
+ pm->node.flags |= PM_EXPORTED;
+ if (pm->node.flags & PM_SPECIAL)
+ pm->env = mkenvstr (pm->node.nam,
+ getsparam(pm->node.nam), pm->node.flags);
+ else
+ pm->env = ztrdup(*envp2);
+#ifndef USE_SET_UNSET_ENV
+ *envp++ = pm->env;
+#endif
+ }
+ }
+ }
+ }
+ popheap();
+#ifndef USE_SET_UNSET_ENV
+ *envp = NULL;
+#endif
+ opts[ALLEXPORT] = oae;
+
+ /*
+ * For native emulation we always set the variable home
+ * (see setupvals()).
+ */
+ pm = (Param) paramtab->getnode(paramtab, "HOME");
+ if (EMULATION(EMULATE_ZSH))
+ {
+ pm->node.flags &= ~PM_UNSET;
+ if (!(pm->node.flags & PM_EXPORTED))
+ addenv(pm, home);
+ } else if (!home)
+ pm->node.flags |= PM_UNSET;
+ pm = (Param) paramtab->getnode(paramtab, "LOGNAME");
+ if (!(pm->node.flags & PM_EXPORTED))
+ addenv(pm, pm->u.str);
+ pm = (Param) paramtab->getnode(paramtab, "SHLVL");
+ sprintf(buf, "%d", (int)++shlvl);
+ /* shlvl value in environment needs updating unconditionally */
+ addenv(pm, buf);
+
+ /* Add the standard non-special parameters */
+ set_pwd_env();
+#ifdef HAVE_UNAME
+ if(uname(&unamebuf)) setsparam("CPUTYPE", ztrdup("unknown"));
+ else
+ {
+ machinebuf = ztrdup_metafy(unamebuf.machine);
+ setsparam("CPUTYPE", machinebuf);
+ }
+
+#else
+ setsparam("CPUTYPE", ztrdup_metafy("unknown"));
+#endif
+ setsparam("MACHTYPE", ztrdup_metafy(MACHTYPE));
+ setsparam("OSTYPE", ztrdup_metafy(OSTYPE));
+ setsparam("TTY", ztrdup_metafy(ttystrname));
+ setsparam("VENDOR", ztrdup_metafy(VENDOR));
+ setsparam("ZSH_ARGZERO", ztrdup(posixzero));
+ setsparam("ZSH_VERSION", ztrdup_metafy(ZSH_VERSION));
+ setsparam("ZSH_PATCHLEVEL", ztrdup_metafy(ZSH_PATCHLEVEL));
+ setaparam("signals", sigptr = zalloc((SIGCOUNT+4) * sizeof(char *)));
+ for (t = sigs; (*sigptr++ = ztrdup_metafy(*t++)); );
+
+ noerrs = 0;
+}
+
+/* assign various functions used for non-special parameters */
+
+/**/
+mod_export void
+assigngetset(Param pm)
+{
+ switch (PM_TYPE(pm->node.flags)) {
+ case PM_SCALAR:
+ pm->gsu.s = &stdscalar_gsu;
+ break;
+ case PM_INTEGER:
+ pm->gsu.i = &stdinteger_gsu;
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ pm->gsu.f = &stdfloat_gsu;
+ break;
+ case PM_ARRAY:
+ pm->gsu.a = &stdarray_gsu;
+ break;
+ case PM_HASHED:
+ pm->gsu.h = &stdhash_gsu;
+ break;
+ default:
+ DPUTS(1, "BUG: tried to create param node without valid flag");
+ break;
+ }
+}
+
+/* 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. */
+
+/**/
+mod_export Param
+createparam(char *name, int flags)
+{
+ Param pm, oldpm;
+
+ if (paramtab != realparamtab)
+ flags = (flags & ~PM_EXPORTED) | PM_HASHELEM;
+
+ if (name != nulstring) {
+ oldpm = (Param) (paramtab == realparamtab ?
+ /* gethashnode2() for direct table read */
+ gethashnode2(paramtab, name) :
+ paramtab->getnode(paramtab, name));
+
+ DPUTS(oldpm && oldpm->level > locallevel,
+ "BUG: old local parameter not deleted");
+ if (oldpm && (oldpm->level == locallevel || !(flags & PM_LOCAL))) {
+ if (isset(POSIXBUILTINS) && (oldpm->node.flags & PM_READONLY)) {
+ zerr("read-only variable: %s", name);
+ return NULL;
+ }
+ if ((oldpm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerr("%s: restricted", name);
+ return NULL;
+ }
+ if (!(oldpm->node.flags & PM_UNSET) ||
+ (oldpm->node.flags & PM_SPECIAL) ||
+ /* POSIXBUILTINS horror: we need to retain 'export' flags */
+ (isset(POSIXBUILTINS) && (oldpm->node.flags & PM_EXPORTED))) {
+ oldpm->node.flags &= ~PM_UNSET;
+ if ((oldpm->node.flags & PM_SPECIAL) && oldpm->ename) {
+ Param altpm =
+ (Param) paramtab->getnode(paramtab, oldpm->ename);
+ if (altpm)
+ altpm->node.flags &= ~PM_UNSET;
+ }
+ return NULL;
+ }
+
+ pm = oldpm;
+ pm->base = pm->width = 0;
+ oldpm = pm->old;
+ } else {
+ pm = (Param) zshcalloc(sizeof *pm);
+ if ((pm->old = oldpm)) {
+ /*
+ * needed to avoid freeing oldpm, but we do take it
+ * out of the environment when it's hidden.
+ */
+ if (oldpm->env)
+ delenv(oldpm);
+ paramtab->removenode(paramtab, name);
+ }
+ paramtab->addnode(paramtab, ztrdup(name), pm);
+ }
+
+ if (isset(ALLEXPORT) && !(flags & PM_HASHELEM))
+ flags |= PM_EXPORTED;
+ } else {
+ pm = (Param) hcalloc(sizeof *pm);
+ pm->node.nam = nulstring;
+ }
+ pm->node.flags = flags & ~PM_LOCAL;
+
+ if(!(pm->node.flags & PM_SPECIAL))
+ assigngetset(pm);
+ return pm;
+}
+
+/* Empty dummy function for special hash parameters. */
+
+/**/
+static void
+shempty(void)
+{
+}
+
+/*
+ * Create a simple special hash parameter.
+ *
+ * This is for hashes added internally --- it's not possible to add
+ * special hashes from shell commands. It's currently used
+ * - by addparamdef() for special parameters in the zsh/parameter
+ * module
+ * - by ztie for special parameters tied to databases.
+ */
+
+/**/
+mod_export Param
+createspecialhash(char *name, GetNodeFunc get, ScanTabFunc scan, int flags)
+{
+ Param pm;
+ HashTable ht;
+
+ if (!(pm = createparam(name, PM_SPECIAL|PM_HASHED|flags)))
+ return NULL;
+
+ /*
+ * If there's an old parameter, we'll put the new one at
+ * the current locallevel, so that the old parameter is
+ * exposed again after leaving the function. Otherwise,
+ * we'll leave it alone. Usually this means the parameter
+ * will stay in place until explicitly unloaded, however
+ * if the parameter was previously unset within a function
+ * we'll inherit the level of that function and follow the
+ * standard convention that the parameter remains local
+ * even if unset.
+ *
+ * These semantics are similar to those of a normal parameter set
+ * within a function without a local definition.
+ */
+ if (pm->old)
+ pm->level = locallevel;
+ pm->gsu.h = (flags & PM_READONLY) ? &stdhash_gsu :
+ &nullsethash_gsu;
+ pm->u.hash = ht = newhashtable(0, name, NULL);
+
+ ht->hash = hasher;
+ ht->emptytable = (TableFunc) shempty;
+ ht->filltable = NULL;
+ ht->addnode = (AddNodeFunc) shempty;
+ ht->getnode = ht->getnode2 = get;
+ ht->removenode = (RemoveNodeFunc) shempty;
+ ht->disablenode = NULL;
+ ht->enablenode = NULL;
+ ht->freenode = (FreeNodeFunc) shempty;
+ ht->printnode = printparamnode;
+ ht->scantab = scan;
+
+ return pm;
+}
+
+
+/*
+ * Copy a parameter
+ *
+ * If fakecopy is set, we are just saving the details of a special
+ * parameter. Otherwise, the result will be used as a real parameter
+ * and we need to do more work.
+ */
+
+/**/
+void
+copyparam(Param tpm, Param pm, int fakecopy)
+{
+ /*
+ * Note that tpm, into which we're copying, may not be in permanent
+ * storage. However, the values themselves are later used directly
+ * to set the parameter, so must be permanently allocated (in accordance
+ * with sets.?fn() usage).
+ */
+ tpm->node.flags = pm->node.flags;
+ tpm->base = pm->base;
+ tpm->width = pm->width;
+ tpm->level = pm->level;
+ if (!fakecopy)
+ tpm->node.flags &= ~PM_SPECIAL;
+ switch (PM_TYPE(pm->node.flags)) {
+ case PM_SCALAR:
+ tpm->u.str = ztrdup(pm->gsu.s->getfn(pm));
+ break;
+ case PM_INTEGER:
+ tpm->u.val = pm->gsu.i->getfn(pm);
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ tpm->u.dval = pm->gsu.f->getfn(pm);
+ break;
+ case PM_ARRAY:
+ tpm->u.arr = zarrdup(pm->gsu.a->getfn(pm));
+ break;
+ case PM_HASHED:
+ tpm->u.hash = copyparamtable(pm->gsu.h->getfn(pm), pm->node.nam);
+ break;
+ }
+ /*
+ * If the value is going to be passed as a real parameter (e.g. this is
+ * called from inside an associative array), we need the gets and sets
+ * functions to be useful.
+ *
+ * In this case we assume the saved parameter is not itself special,
+ * so we just use the standard functions. This is also why we switch off
+ * PM_SPECIAL.
+ */
+ if (!fakecopy)
+ assigngetset(tpm);
+}
+
+/* Return 1 if the string s is a valid identifier, else return 0. */
+
+/**/
+mod_export int
+isident(char *s)
+{
+ char *ss;
+
+ if (!*s) /* empty string is definitely not valid */
+ return 0;
+
+ if (idigit(*s)) {
+ /* If the first character is `s' is a digit, then all must be */
+ for (ss = ++s; *ss; ss++)
+ if (!idigit(*ss))
+ break;
+ } else {
+ /* Find the first character in `s' not in the iident type table */
+ ss = itype_end(s, IIDENT, 0);
+ }
+
+ /* If the next character is not [, then it is *
+ * definitely not a valid identifier. */
+ if (!*ss)
+ return 1;
+ if (s == ss)
+ return 0;
+ if (*ss != '[')
+ return 0;
+
+ /* Require balanced [ ] pairs with something between */
+ if (!(ss = parse_subscript(++ss, 1, ']')))
+ return 0;
+ untokenize(s);
+ return !ss[1];
+}
+
+/*
+ * Parse a single argument to a parameter subscript.
+ * The subscripts starts at *str; *str is updated (input/output)
+ *
+ * *inv is set to indicate if the subscript is reversed (output)
+ * v is the Value for the parameter being accessed (input; note
+ * v->isarr may be modified, and if v is a hash the parameter will
+ * be updated to the element of the hash)
+ * a2 is 1 if this is the second subscript of a range (input)
+ * *w is only set if we need to find the end of a word (input; should
+ * be set to 0 by the caller).
+ *
+ * The final two arguments are to support multibyte characters.
+ * If supplied they are set to the length of the character before
+ * the index position and the one at the index position. If
+ * multibyte characters are not in use they are set to 1 for
+ * consistency. Note they aren't fully handled if a2 is non-zero,
+ * since they aren't needed.
+ *
+ * Returns a raw offset into the value from the start or end (i.e.
+ * after the arithmetic for Meta and possible multibyte characters has
+ * been taken into account). This actually gives the offset *after*
+ * the character in question; subtract *prevcharlen if necessary.
+ */
+
+/**/
+static zlong
+getarg(char **str, int *inv, Value v, int a2, zlong *w,
+ int *prevcharlen, int *nextcharlen, int flags)
+{
+ int hasbeg = 0, word = 0, rev = 0, ind = 0, down = 0, l, i, ishash;
+ int keymatch = 0, needtok = 0, arglen, len, inpar = 0;
+ char *s = *str, *sep = NULL, *t, sav, *d, **ta, **p, *tt, c;
+ zlong num = 1, beg = 0, r = 0, quote_arg = 0;
+ Patprog pprog = NULL;
+
+ /*
+ * If in NO_EXEC mode, the parameters won't be set up properly,
+ * so just pretend everything is a hash for subscript parsing
+ */
+
+ ishash = (unset(EXECOPT) ||
+ (v->pm && PM_TYPE(v->pm->node.flags) == PM_HASHED));
+ if (prevcharlen)
+ *prevcharlen = 1;
+ if (nextcharlen)
+ *nextcharlen = 1;
+
+ /* first parse any subscription flags */
+ if (v->pm && (*s == '(' || *s == Inpar)) {
+ int escapes = 0;
+ int waste;
+ for (s++; *s != ')' && *s != Outpar && s != *str; s++) {
+ switch (*s) {
+ case 'r':
+ rev = 1;
+ keymatch = down = ind = 0;
+ break;
+ case 'R':
+ rev = down = 1;
+ keymatch = ind = 0;
+ break;
+ case 'k':
+ keymatch = ishash;
+ rev = 1;
+ down = ind = 0;
+ break;
+ case 'K':
+ keymatch = ishash;
+ rev = down = 1;
+ ind = 0;
+ break;
+ case 'i':
+ rev = ind = 1;
+ down = keymatch = 0;
+ break;
+ case 'I':
+ rev = ind = down = 1;
+ keymatch = 0;
+ 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':
+ quote_arg = 1;
+ break;
+ case 'n':
+ t = get_strarg(++s, &arglen);
+ if (!*t)
+ goto flagerr;
+ sav = *t;
+ *t = '\0';
+ num = mathevalarg(s + arglen, &d);
+ if (!num)
+ num = 1;
+ *t = sav;
+ s = t + arglen - 1;
+ break;
+ case 'b':
+ hasbeg = 1;
+ t = get_strarg(++s, &arglen);
+ if (!*t)
+ goto flagerr;
+ sav = *t;
+ *t = '\0';
+ if ((beg = mathevalarg(s + arglen, &d)) > 0)
+ beg--;
+ *t = sav;
+ s = t + arglen - 1;
+ 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, &arglen);
+ if (!*t)
+ goto flagerr;
+ sav = *t;
+ *t = '\0';
+ s += arglen;
+ sep = escapes ? getkeystring(s, &waste, GETKEYS_SEP, NULL)
+ : dupstring(s);
+ *t = sav;
+ s = t + arglen - 1;
+ break;
+ default:
+ flagerr:
+ num = 1;
+ word = rev = ind = down = keymatch = 0;
+ sep = NULL;
+ s = *str - 1;
+ }
+ }
+ if (s != *str)
+ s++;
+ }
+ if (num < 0) {
+ down = !down;
+ num = -num;
+ }
+ if (v->isarr & SCANPM_WANTKEYS)
+ *inv = (ind || !(v->isarr & SCANPM_WANTVALS));
+ else if (v->isarr & SCANPM_WANTVALS)
+ *inv = 0;
+ else {
+ if (v->isarr) {
+ if (ind) {
+ v->isarr |= SCANPM_WANTKEYS;
+ v->isarr &= ~SCANPM_WANTVALS;
+ } else if (rev)
+ v->isarr |= SCANPM_WANTVALS;
+ /*
+ * This catches the case where we are using "k" (rather
+ * than "K") on a hash.
+ */
+ if (!down && keymatch && ishash)
+ v->isarr &= ~SCANPM_MATCHMANY;
+ }
+ *inv = ind;
+ }
+
+ for (t = s, i = 0;
+ (c = *t) &&
+ ((c != Outbrack && (ishash || c != ',')) || i || inpar);
+ t++) {
+ /* Untokenize inull() except before brackets and double-quotes */
+ if (inull(c)) {
+ c = t[1];
+ if (c == '[' || c == ']' ||
+ c == '(' || c == ')' ||
+ c == '{' || c == '}') {
+ /* This test handles nested subscripts in hash keys */
+ if (ishash && i)
+ *t = ztokens[*t - Pound];
+ needtok = 1;
+ ++t;
+ } else if (c != '"')
+ *t = ztokens[*t - Pound];
+ continue;
+ }
+ /* Inbrack and Outbrack are probably never found here ... */
+ if (c == '[' || c == Inbrack)
+ i++;
+ else if (c == ']' || c == Outbrack)
+ i--;
+ if (c == '(' || c == Inpar)
+ inpar++;
+ else if (c == ')' || c == Outpar)
+ inpar--;
+ if (ispecial(c))
+ needtok = 1;
+ }
+ if (!c)
+ return 0;
+ *str = tt = t;
+
+ /*
+ * If in NO_EXEC mode, the parameters won't be set up properly,
+ * so there's no additional sanity checking we can do.
+ * Just return 0 now.
+ */
+ if (unset(EXECOPT))
+ return 0;
+
+ s = dupstrpfx(s, t - s);
+
+ /* If we're NOT reverse subscripting, strip the inull()s so brackets *
+ * are not backslashed after parsestr(). Otherwise leave them alone *
+ * so that the brackets will be escaped when we patcompile() or when *
+ * subscript arithmetic is performed (for nested subscripts). */
+ if (ishash && (keymatch || !rev))
+ remnulargs(s);
+ if (needtok) {
+ s = dupstring(s);
+ if (parsestr(&s))
+ return 0;
+ singsub(&s);
+ } else if (rev)
+ remnulargs(s); /* This is probably always a no-op, but ... */
+ if (!rev) {
+ if (ishash) {
+ HashTable ht = v->pm->gsu.h->getfn(v->pm);
+ if (!ht) {
+ if (flags & SCANPM_CHECKING)
+ return 0;
+ ht = newparamtable(17, v->pm->node.nam);
+ v->pm->gsu.h->setfn(v->pm, ht);
+ }
+ untokenize(s);
+ if (!(v->pm = (Param) ht->getnode(ht, s))) {
+ HashTable tht = paramtab;
+ paramtab = ht;
+ v->pm = createparam(s, PM_SCALAR|PM_UNSET);
+ paramtab = tht;
+ }
+ v->isarr = (*inv ? SCANPM_WANTINDEX : 0);
+ v->start = 0;
+ *inv = 0; /* We've already obtained the "index" (key) */
+ *w = v->end = -1;
+ r = isset(KSHARRAYS) ? 1 : 0;
+ } else {
+ r = mathevalarg(s, &s);
+ if (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 = (zlong)(s - t);
+
+ return (a2 ? s : d + 1) - t;
+ } else if (!v->isarr && !word) {
+ int lastcharlen = 1;
+ s = getstrvalue(v);
+ /*
+ * Note for the confused (= pws): the index r we
+ * have so far is that specified by the user. The value
+ * passed back is an offset from the start or end of
+ * the string. Hence it needs correcting at least
+ * for Meta characters and maybe for multibyte characters.
+ */
+ if (r > 0) {
+ zlong nchars = r;
+
+ MB_METACHARINIT();
+ for (t = s; nchars && *t; nchars--)
+ t += (lastcharlen = MB_METACHARLEN(t));
+ /* for consistency, keep any remainder off the end */
+ r = (zlong)(t - s) + nchars;
+ if (prevcharlen && !nchars /* ignore if off the end */)
+ *prevcharlen = lastcharlen;
+ if (nextcharlen && *t)
+ *nextcharlen = MB_METACHARLEN(t);
+ } else if (r == 0) {
+ if (prevcharlen)
+ *prevcharlen = 0;
+ if (nextcharlen && *s) {
+ MB_METACHARINIT();
+ *nextcharlen = MB_METACHARLEN(s);
+ }
+ } else {
+ zlong nchars = (zlong)MB_METASTRLEN(s) + r;
+
+ if (nchars < 0) {
+ /* make sure this isn't valid as a raw pointer */
+ r -= (zlong)strlen(s);
+ } else {
+ MB_METACHARINIT();
+ for (t = s; nchars && *t; nchars--)
+ t += (lastcharlen = MB_METACHARLEN(t));
+ r = - (zlong)strlen(t); /* keep negative */
+ if (prevcharlen)
+ *prevcharlen = lastcharlen;
+ if (nextcharlen && *t)
+ *nextcharlen = MB_METACHARLEN(t);
+ }
+ }
+ }
+ } else {
+ if (!v->isarr && !word && !quote_arg) {
+ l = strlen(s);
+ if (a2) {
+ if (!l || *s != '*') {
+ d = (char *) hcalloc(l + 2);
+ *d = '*';
+ strcpy(d + 1, s);
+ s = d;
+ }
+ } else {
+ if (!l || s[l - 1] != '*' || (l > 1 && s[l - 2] == '\\')) {
+ d = (char *) hcalloc(l + 2);
+ strcpy(d, s);
+ strcat(d, "*");
+ s = d;
+ }
+ }
+ }
+ if (!keymatch) {
+ if (quote_arg) {
+ untokenize(s);
+ /* Scalar (e) needs implicit asterisk tokens */
+ if (!v->isarr && !word) {
+ l = strlen(s);
+ d = (char *) hcalloc(l + 2);
+ if (a2) {
+ *d = Star;
+ strcpy(d + 1, s);
+ } else {
+ strcpy(d, s);
+ d[l] = Star;
+ d[l + 1] = '\0';
+ }
+ s = d;
+ }
+ } else
+ tokenize(s);
+ remnulargs(s);
+ pprog = patcompile(s, 0, NULL);
+ } else
+ pprog = NULL;
+
+ if (v->isarr) {
+ if (ishash) {
+ scanprog = pprog;
+ scanstr = s;
+ if (keymatch)
+ v->isarr |= SCANPM_KEYMATCH;
+ else {
+ if (!pprog)
+ return 1;
+ if (ind)
+ v->isarr |= SCANPM_MATCHKEY;
+ else
+ v->isarr |= SCANPM_MATCHVAL;
+ }
+ if (down)
+ v->isarr |= SCANPM_MATCHMANY;
+ if ((ta = getvaluearr(v)) &&
+ (*ta || ((v->isarr & SCANPM_MATCHMANY) &&
+ (v->isarr & (SCANPM_MATCHKEY | SCANPM_MATCHVAL |
+ SCANPM_KEYMATCH))))) {
+ *inv = (v->flags & VALFLAG_INV) ? 1 : 0;
+ *w = v->end;
+ scanprog = NULL;
+ return 1;
+ }
+ scanprog = NULL;
+ } else
+ ta = getarrvalue(v);
+ if (!ta || !*ta)
+ return !down;
+ len = arrlen(ta);
+ if (beg < 0)
+ beg += len;
+ if (down) {
+ if (beg < 0)
+ return 0;
+ } else if (beg >= len)
+ return len + 1;
+ if (beg >= 0 && beg < len) {
+ if (down) {
+ if (!hasbeg)
+ beg = len - 1;
+ for (r = 1 + beg, p = ta + beg; p >= ta; r--, p--) {
+ if (pprog && pattry(pprog, *p) && !--num)
+ return r;
+ }
+ } else
+ for (r = 1 + beg, p = ta + beg; *p; r++, p++)
+ if (pprog && pattry(pprog, *p) && !--num)
+ return r;
+ }
+ } else if (word) {
+ ta = sepsplit(d = s = getstrvalue(v), sep, 1, 1);
+ len = arrlen(ta);
+ if (beg < 0)
+ beg += len;
+ if (down) {
+ if (beg < 0)
+ return 0;
+ } else if (beg >= len)
+ return len + 1;
+ if (beg >= 0 && beg < len) {
+ if (down) {
+ if (!hasbeg)
+ beg = len - 1;
+ for (r = 1 + beg, p = ta + beg; p >= ta; p--, r--)
+ if (pprog && pattry(pprog, *p) && !--num)
+ break;
+ if (p < ta)
+ return 0;
+ } else {
+ for (r = 1 + beg, p = ta + beg; *p; r++, p++)
+ if (pprog && pattry(pprog, *p) && !--num)
+ break;
+ if (!*p)
+ return 0;
+ }
+ }
+ if (a2)
+ r++;
+ for (i = 0; (t = findword(&d, sep)) && *t; i++)
+ if (!--r) {
+ r = (zlong)(t - s + (a2 ? -1 : 1));
+ if (!a2 && *tt != ',')
+ *w = r + strlen(ta[i]) - 1;
+ return r;
+ }
+ return a2 ? -1 : 0;
+ } else {
+ /* Searching characters */
+ int slen;
+ d = getstrvalue(v);
+ if (!d || !*d)
+ return 0;
+ /*
+ * beg and len are character counts, not raw offsets.
+ * Remember we need to return a raw offset.
+ */
+ len = MB_METASTRLEN(d);
+ slen = strlen(d);
+ if (beg < 0)
+ beg += len;
+ MB_METACHARINIT();
+ if (beg >= 0 && beg < len) {
+ char *de = d + slen;
+
+ if (a2) {
+ /*
+ * Second argument: we don't need to
+ * handle prevcharlen or nextcharlen, but
+ * we do need to handle characters appropriately.
+ */
+ if (down) {
+ int nmatches = 0;
+ char *lastpos = NULL;
+
+ if (!hasbeg)
+ beg = len;
+
+ /*
+ * See below: we have to move forward,
+ * but need to count from the end.
+ */
+ for (t = d, r = 0; r <= beg; r++) {
+ sav = *t;
+ *t = '\0';
+ if (pprog && pattry(pprog, d)) {
+ nmatches++;
+ lastpos = t;
+ }
+ *t = sav;
+ if (t == de)
+ break;
+ t += MB_METACHARLEN(t);
+ }
+
+ if (nmatches >= num) {
+ if (num > 1) {
+ nmatches -= num;
+ MB_METACHARINIT();
+ for (t = d, r = 0; ; r++) {
+ sav = *t;
+ *t = '\0';
+ if (pprog && pattry(pprog, d) &&
+ nmatches-- == 0) {
+ lastpos = t;
+ *t = sav;
+ break;
+ }
+ *t = sav;
+ t += MB_METACHARLEN(t);
+ }
+ }
+ /* else lastpos is already OK */
+
+ return lastpos - d;
+ }
+ } else {
+ /*
+ * This handling of the b flag
+ * gives odd results, but this is the
+ * way it's always worked.
+ */
+ for (t = d; beg && t <= de; beg--)
+ t += MB_METACHARLEN(t);
+ for (;;) {
+ sav = *t;
+ *t = '\0';
+ if (pprog && pattry(pprog, d) && !--num) {
+ *t = sav;
+ /*
+ * This time, don't increment
+ * pointer, since it's already
+ * after everything we matched.
+ */
+ return t - d;
+ }
+ *t = sav;
+ if (t == de)
+ break;
+ t += MB_METACHARLEN(t);
+ }
+ }
+ } else {
+ /*
+ * First argument: this is the only case
+ * where we need prevcharlen and nextcharlen.
+ */
+ int lastcharlen;
+
+ if (down) {
+ int nmatches = 0;
+ char *lastpos = NULL;
+
+ if (!hasbeg)
+ beg = len;
+
+ /*
+ * We can only move forward through
+ * multibyte strings, so record the
+ * matches.
+ * Unfortunately the count num works
+ * from the end, so it's easy to get the
+ * last one but we need to repeat if
+ * we want another one.
+ */
+ for (t = d, r = 0; r <= beg; r++) {
+ if (pprog && pattry(pprog, t)) {
+ nmatches++;
+ lastpos = t;
+ }
+ if (t == de)
+ break;
+ t += MB_METACHARLEN(t);
+ }
+
+ if (nmatches >= num) {
+ if (num > 1) {
+ /*
+ * Need to start again and repeat
+ * to get the right match.
+ */
+ nmatches -= num;
+ MB_METACHARINIT();
+ for (t = d, r = 0; ; r++) {
+ if (pprog && pattry(pprog, t) &&
+ nmatches-- == 0) {
+ lastpos = t;
+ break;
+ }
+ t += MB_METACHARLEN(t);
+ }
+ }
+ /* else lastpos is already OK */
+
+ /* return pointer after matched char */
+ lastpos +=
+ (lastcharlen = MB_METACHARLEN(lastpos));
+ if (prevcharlen)
+ *prevcharlen = lastcharlen;
+ if (nextcharlen)
+ *nextcharlen = MB_METACHARLEN(lastpos);
+ return lastpos - d;
+ }
+
+ for (r = beg + 1, t = d + beg; t >= d; r--, t--) {
+ if (pprog && pattry(pprog, t) &&
+ !--num)
+ return r;
+ }
+ } else {
+ for (t = d; beg && t <= de; beg--)
+ t += MB_METACHARLEN(t);
+ for (;;) {
+ if (pprog && pattry(pprog, t) && !--num) {
+ /* return pointer after matched char */
+ t += (lastcharlen = MB_METACHARLEN(t));
+ if (prevcharlen)
+ *prevcharlen = lastcharlen;
+ if (nextcharlen)
+ *nextcharlen = MB_METACHARLEN(t);
+ return t - d;
+ }
+ if (t == de)
+ break;
+ t += MB_METACHARLEN(t);
+ }
+ }
+ }
+ }
+ return down ? 0 : slen + 1;
+ }
+ }
+ return r;
+}
+
+/*
+ * Parse a subscript.
+ *
+ * pptr: In/Out parameter. On entry, *ptr points to a "[foo]" string. On exit
+ * it will point one past the closing bracket.
+ *
+ * v: In/Out parameter. Its .start and .end members (at least) will be updated
+ * with the parsed indices.
+ *
+ * flags: can be either SCANPM_DQUOTED or zero. Other bits are not used.
+ */
+
+/**/
+int
+getindex(char **pptr, Value v, int flags)
+{
+ int start, end, inv = 0;
+ char *s = *pptr, *tbrack;
+
+ *s++ = '[';
+ /* Error handled after untokenizing */
+ s = parse_subscript(s, flags & SCANPM_DQUOTED, ']');
+ /* Now we untokenize everything except inull() markers so we can check *
+ * for the '*' and '@' special subscripts. The inull()s are removed *
+ * in getarg() after we know whether we're doing reverse indexing. */
+ for (tbrack = *pptr + 1; *tbrack && tbrack != s; tbrack++) {
+ if (inull(*tbrack) && !*++tbrack)
+ break;
+ if (itok(*tbrack)) /* Need to check for Nularg here? */
+ *tbrack = ztokens[*tbrack - Pound];
+ }
+ /* If we reached the end of the string (s == NULL) we have an error */
+ if (*tbrack)
+ *tbrack = Outbrack;
+ else {
+ zerr("invalid subscript");
+ *pptr = tbrack;
+ return 1;
+ }
+ s = *pptr + 1;
+ if ((s[0] == '*' || s[0] == '@') && s + 1 == tbrack) {
+ if ((v->isarr || IS_UNSET_VALUE(v)) && s[0] == '@')
+ v->isarr |= SCANPM_ISVAR_AT;
+ v->start = 0;
+ v->end = -1;
+ s += 2;
+ } else {
+ zlong we = 0, dummy;
+ int startprevlen, startnextlen;
+
+ start = getarg(&s, &inv, v, 0, &we, &startprevlen, &startnextlen,
+ flags);
+
+ if (inv) {
+ if (!v->isarr && start != 0) {
+ char *t, *p;
+ t = getstrvalue(v);
+ /*
+ * Note for the confused (= pws): this is an inverse
+ * offset so at this stage we need to convert from
+ * the immediate offset into the value that we have
+ * into a logical character position.
+ */
+ if (start > 0) {
+ int nstart = 0;
+ char *target = t + start - startprevlen;
+
+ p = t;
+ MB_METACHARINIT();
+ while (*p) {
+ /*
+ * move up characters, counting how many we
+ * found
+ */
+ p += MB_METACHARLEN(p);
+ if (p < target)
+ nstart++;
+ else {
+ if (p == target)
+ nstart++;
+ else
+ p = target; /* pretend we hit exactly */
+ break;
+ }
+ }
+ /* if start was too big, keep the difference */
+ start = nstart + (target - p) + 1;
+ } else {
+ zlong startoff = start + strlen(t);
+#ifdef DEBUG
+ dputs("BUG: can't have negative inverse offsets???");
+#endif
+ if (startoff < 0) {
+ /* invalid: keep index but don't dereference */
+ start = startoff;
+ } else {
+ /* find start in full characters */
+ MB_METACHARINIT();
+ for (p = t; p < t + startoff;)
+ p += MB_METACHARLEN(p);
+ start = - MB_METASTRLEN(p);
+ }
+ }
+ }
+ if (start > 0 && (isset(KSHARRAYS) || (v->pm->node.flags & PM_HASHED)))
+ start--;
+ if (v->isarr != SCANPM_WANTINDEX) {
+ v->flags |= VALFLAG_INV;
+ v->isarr = 0;
+ v->start = start;
+ v->end = start + 1;
+ }
+ if (*s == ',') {
+ zerr("invalid subscript");
+ *tbrack = ']';
+ *pptr = tbrack+1;
+ return 1;
+ }
+ if (s == tbrack)
+ s++;
+ } else {
+ int com;
+
+ if ((com = (*s == ','))) {
+ s++;
+ end = getarg(&s, &inv, v, 1, &dummy, NULL, NULL, flags);
+ } else {
+ end = we ? we : start;
+ }
+ if (start != end)
+ com = 1;
+ /*
+ * Somehow the logic sometimes forces us to use the previous
+ * or next character to what we would expect, which is
+ * why we had to calculate them in getarg().
+ */
+ if (start > 0)
+ start -= startprevlen;
+ else if (start == 0 && end == 0)
+ {
+ /*
+ * Strictly, this range is entirely off the
+ * start of the available index range.
+ * This can't happen with KSH_ARRAYS; we already
+ * altered the start index in getarg().
+ * Are we being strict?
+ */
+ if (isset(KSHZEROSUBSCRIPT)) {
+ /*
+ * We're not.
+ * Treat this as accessing the first element of the
+ * array.
+ */
+ end = startnextlen;
+ } else {
+ /*
+ * We are. Flag that this range is invalid
+ * for setting elements. Set the indexes
+ * to a range that returns empty for other accesses.
+ */
+ v->flags |= VALFLAG_EMPTY;
+ start = -1;
+ com = 1;
+ }
+ }
+ if (s == tbrack) {
+ s++;
+ if (v->isarr && !com &&
+ (!(v->isarr & SCANPM_MATCHMANY) ||
+ !(v->isarr & (SCANPM_MATCHKEY | SCANPM_MATCHVAL |
+ SCANPM_KEYMATCH))))
+ v->isarr = 0;
+ v->start = start;
+ v->end = end;
+ } else
+ s = *pptr;
+ }
+ }
+ *tbrack = ']';
+ *pptr = s;
+ return 0;
+}
+
+
+/**/
+mod_export Value
+getvalue(Value v, char **pptr, int bracks)
+{
+ return fetchvalue(v, pptr, bracks, 0);
+}
+
+/**/
+mod_export Value
+fetchvalue(Value v, char **pptr, int bracks, int flags)
+{
+ char *s, *t, *ie;
+ char sav, c;
+ int ppar = 0;
+
+ s = t = *pptr;
+
+ if (idigit(c = *s)) {
+ if (bracks >= 0)
+ ppar = zstrtol(s, &s, 10);
+ else
+ ppar = *s++ - '0';
+ }
+ else if ((ie = itype_end(s, IIDENT, 0)) != s)
+ s = ie;
+ else if (c == Quest)
+ *s++ = '?';
+ else if (c == Pound)
+ *s++ = '#';
+ else if (c == String)
+ *s++ = '$';
+ else if (c == Qstring)
+ *s++ = '$';
+ else if (c == Star)
+ *s++ = '*';
+ else if (IS_DASH(c))
+ *s++ = '-';
+ else if (c == '#' || c == '?' || c == '$' ||
+ c == '!' || c == '@' || c == '*')
+ s++;
+ else
+ return NULL;
+
+ if ((sav = *s))
+ *s = '\0';
+ if (ppar) {
+ if (v)
+ memset(v, 0, sizeof(*v));
+ else
+ v = (Value) hcalloc(sizeof *v);
+ v->pm = argvparam;
+ v->flags = 0;
+ v->start = ppar - 1;
+ v->end = ppar;
+ if (sav)
+ *s = sav;
+ } else {
+ Param pm;
+ int isvarat;
+
+ isvarat = (t[0] == '@' && !t[1]);
+ pm = (Param) paramtab->getnode(paramtab, *t == '0' ? "0" : t);
+ if (sav)
+ *s = sav;
+ *pptr = s;
+ if (!pm || (pm->node.flags & PM_UNSET))
+ return NULL;
+ if (v)
+ memset(v, 0, sizeof(*v));
+ else
+ v = (Value) hcalloc(sizeof *v);
+ if (PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)) {
+ /* Overload v->isarr as the flag bits for hashed arrays. */
+ v->isarr = flags | (isvarat ? SCANPM_ISVAR_AT : 0);
+ /* If no flags were passed, we need something to represent *
+ * `true' yet differ from an explicit WANTVALS. Use a *
+ * special flag for this case. */
+ if (!v->isarr)
+ v->isarr = SCANPM_ARRONLY;
+ }
+ v->pm = pm;
+ v->flags = 0;
+ v->start = 0;
+ v->end = -1;
+ if (bracks > 0 && (*s == '[' || *s == Inbrack)) {
+ if (getindex(&s, v, flags)) {
+ *pptr = s;
+ return v;
+ }
+ } else if (!(flags & SCANPM_ASSIGNING) && v->isarr &&
+ itype_end(t, IIDENT, 1) != t && isset(KSHARRAYS))
+ v->end = 1, v->isarr = 0;
+ }
+ if (!bracks && *s)
+ return NULL;
+ *pptr = s;
+#if 0
+ /*
+ * Check for large subscripts that might be erroneous.
+ * This code is too gross in several ways:
+ * - the limit is completely arbitrary
+ * - the test vetoes operations on existing arrays
+ * - it's not at all clear a general test on large arrays of
+ * this kind is any use.
+ *
+ * Until someone comes up with workable replacement code it's
+ * therefore commented out.
+ */
+ if (v->start > MAX_ARRLEN) {
+ zerr("subscript too %s: %d", "big", v->start + !isset(KSHARRAYS));
+ return NULL;
+ }
+ if (v->start < -MAX_ARRLEN) {
+ zerr("subscript too %s: %d", "small", v->start);
+ return NULL;
+ }
+ if (v->end > MAX_ARRLEN+1) {
+ zerr("subscript too %s: %d", "big", v->end - !!isset(KSHARRAYS));
+ return NULL;
+ }
+ if (v->end < -MAX_ARRLEN) {
+ zerr("subscript too %s: %d", "small", v->end);
+ return NULL;
+ }
+#endif
+ return v;
+}
+
+/**/
+mod_export char *
+getstrvalue(Value v)
+{
+ char *s, **ss;
+ char buf[BDIGBUFSIZE];
+ int len;
+
+ if (!v)
+ return hcalloc(1);
+
+ if ((v->flags & VALFLAG_INV) && !(v->pm->node.flags & PM_HASHED)) {
+ sprintf(buf, "%d", v->start);
+ s = dupstring(buf);
+ return s;
+ }
+
+ switch(PM_TYPE(v->pm->node.flags)) {
+ case PM_HASHED:
+ /* (!v->isarr) should be impossible unless emulating ksh */
+ if (!v->isarr && EMULATION(EMULATE_KSH)) {
+ s = dupstring("[0]");
+ if (getindex(&s, v, 0) == 0)
+ s = getstrvalue(v);
+ return s;
+ } /* else fall through */
+ case PM_ARRAY:
+ ss = getvaluearr(v);
+ if (v->isarr)
+ s = sepjoin(ss, NULL, 1);
+ else {
+ if (v->start < 0)
+ v->start += arrlen(ss);
+ s = (arrlen_le(ss, v->start) || v->start < 0) ?
+ (char *) hcalloc(1) : ss[v->start];
+ }
+ return s;
+ case PM_INTEGER:
+ convbase(buf, v->pm->gsu.i->getfn(v->pm), v->pm->base);
+ s = dupstring(buf);
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ s = convfloat(v->pm->gsu.f->getfn(v->pm),
+ v->pm->base, v->pm->node.flags, NULL);
+ break;
+ case PM_SCALAR:
+ s = v->pm->gsu.s->getfn(v->pm);
+ break;
+ default:
+ s = "";
+ DPUTS(1, "BUG: param node without valid type");
+ break;
+ }
+
+ if (v->flags & VALFLAG_SUBST) {
+ if (v->pm->node.flags & (PM_LEFT|PM_RIGHT_B|PM_RIGHT_Z)) {
+ unsigned int fwidth = v->pm->width ? v->pm->width : MB_METASTRLEN(s);
+ switch (v->pm->node.flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) {
+ char *t, *tend;
+ unsigned int t0;
+
+ case PM_LEFT:
+ case PM_LEFT | PM_RIGHT_Z:
+ t = s;
+ if (v->pm->node.flags & PM_RIGHT_Z)
+ while (*t == '0')
+ t++;
+ else
+ while (iblank(*t))
+ t++;
+ MB_METACHARINIT();
+ for (tend = t, t0 = 0; t0 < fwidth && *tend; t0++)
+ tend += MB_METACHARLEN(tend);
+ /*
+ * t0 is the number of characters from t used,
+ * hence (fwidth - t0) is the number of padding
+ * characters. fwidth is a misnomer: we use
+ * character counts, not character widths.
+ *
+ * (tend - t) is the number of bytes we need
+ * to get fwidth characters or the entire string;
+ * the characters may be multiple bytes.
+ */
+ fwidth -= t0; /* padding chars remaining */
+ t0 = tend - t; /* bytes to copy from string */
+ s = (char *) hcalloc(t0 + fwidth + 1);
+ memcpy(s, t, t0);
+ if (fwidth)
+ memset(s + t0, ' ', fwidth);
+ s[t0 + fwidth] = '\0';
+ break;
+ case PM_RIGHT_B:
+ case PM_RIGHT_Z:
+ case PM_RIGHT_Z | PM_RIGHT_B:
+ {
+ int zero = 1;
+ /* Calculate length in possibly multibyte chars */
+ unsigned int charlen = MB_METASTRLEN(s);
+
+ if (charlen < fwidth) {
+ char *valprefend = s;
+ int preflen;
+ if (v->pm->node.flags & PM_RIGHT_Z) {
+ /*
+ * This is a documented feature: when deciding
+ * whether to pad with zeroes, ignore
+ * leading blanks already in the value;
+ * only look for numbers after that.
+ * Not sure how useful this really is.
+ * It's certainly confusing to code around.
+ */
+ for (t = s; iblank(*t); t++)
+ ;
+ /*
+ * Allow padding after initial minus
+ * for numeric variables.
+ */
+ if ((v->pm->node.flags &
+ (PM_INTEGER|PM_EFLOAT|PM_FFLOAT)) &&
+ *t == '-')
+ t++;
+ /*
+ * Allow padding after initial 0x or
+ * base# for integer variables.
+ */
+ if (v->pm->node.flags & PM_INTEGER) {
+ if (isset(CBASES) &&
+ t[0] == '0' && t[1] == 'x')
+ t += 2;
+ else if ((valprefend = strchr(t, '#')))
+ t = valprefend + 1;
+ }
+ valprefend = t;
+ if (!*t)
+ zero = 0;
+ else if (v->pm->node.flags &
+ (PM_INTEGER|PM_EFLOAT|PM_FFLOAT)) {
+ /* zero always OK */
+ } else if (!idigit(*t))
+ zero = 0;
+ }
+ /* number of characters needed for padding */
+ fwidth -= charlen;
+ /* bytes from original string */
+ t0 = strlen(s);
+ t = (char *) hcalloc(fwidth + t0 + 1);
+ /* prefix guaranteed to be single byte chars */
+ preflen = valprefend - s;
+ memset(t + preflen,
+ (((v->pm->node.flags & PM_RIGHT_B)
+ || !zero) ? ' ' : '0'), fwidth);
+ /*
+ * Copy - or 0x or base# before any padding
+ * zeroes.
+ */
+ if (preflen)
+ memcpy(t, s, preflen);
+ memcpy(t + preflen + fwidth,
+ valprefend, t0 - preflen);
+ t[fwidth + t0] = '\0';
+ s = t;
+ } else {
+ /* Need to skip (charlen - fwidth) chars */
+ for (t0 = charlen - fwidth; t0; t0--)
+ s += MB_METACHARLEN(s);
+ }
+ }
+ break;
+ }
+ }
+ switch (v->pm->node.flags & (PM_LOWER | PM_UPPER)) {
+ case PM_LOWER:
+ s = casemodify(s, CASMOD_LOWER);
+ break;
+ case PM_UPPER:
+ s = casemodify(s, CASMOD_UPPER);
+ break;
+ }
+ }
+ if (v->start == 0 && v->end == -1)
+ return s;
+
+ len = strlen(s);
+ if (v->start < 0) {
+ v->start += len;
+ if (v->start < 0)
+ v->start = 0;
+ }
+ if (v->end < 0) {
+ v->end += len;
+ if (v->end >= 0) {
+ char *eptr = s + v->end;
+ if (*eptr)
+ v->end += MB_METACHARLEN(eptr);
+ }
+ }
+
+ s = (v->start > len) ? dupstring("") :
+ dupstring_wlen(s + v->start, len - v->start);
+
+ if (v->end <= v->start)
+ s[0] = '\0';
+ else if (v->end - v->start <= len - v->start)
+ s[v->end - v->start] = '\0';
+
+ return s;
+}
+
+static char *nular[] = {"", NULL};
+
+/**/
+mod_export char **
+getarrvalue(Value v)
+{
+ char **s;
+
+ if (!v)
+ return arrdup(nular);
+ else if (IS_UNSET_VALUE(v))
+ return arrdup(&nular[1]);
+ if (v->flags & VALFLAG_INV) {
+ char buf[DIGBUFSIZE];
+
+ s = arrdup(nular);
+ sprintf(buf, "%d", v->start);
+ s[0] = dupstring(buf);
+ return s;
+ }
+ s = getvaluearr(v);
+ if (v->start == 0 && v->end == -1)
+ return s;
+ if (v->start < 0)
+ v->start += arrlen(s);
+ if (v->end < 0)
+ v->end += arrlen(s) + 1;
+
+ /* Null if 1) array too short, 2) index still negative */
+ if (v->end <= v->start) {
+ s = arrdup_max(nular, 0);
+ }
+ else if (v->start < 0) {
+ s = arrdup_max(nular, 1);
+ }
+ else if (arrlen_le(s, v->start)) {
+ /* Handle $ary[i,i] consistently for any $i > $#ary
+ * and $ary[i,j] consistently for any $j > $i > $#ary
+ */
+ s = arrdup_max(nular, v->end - (v->start + 1));
+ }
+ else {
+ /* Copy to a point before the end of the source array:
+ * arrdup_max will copy at most v->end - v->start elements,
+ * starting from v->start element. Original code said:
+ * s[v->end - v->start] = NULL
+ * which means that there are exactly the same number of
+ * elements as the value of the above *0-based* index.
+ */
+ s = arrdup_max(s + v->start, v->end - v->start);
+ }
+
+ return s;
+}
+
+/**/
+mod_export zlong
+getintvalue(Value v)
+{
+ if (!v)
+ return 0;
+ if (v->flags & VALFLAG_INV)
+ return v->start;
+ if (v->isarr) {
+ char **arr = getarrvalue(v);
+ if (arr) {
+ char *scal = sepjoin(arr, NULL, 1);
+ return mathevali(scal);
+ } else
+ return 0;
+ }
+ if (PM_TYPE(v->pm->node.flags) == PM_INTEGER)
+ return v->pm->gsu.i->getfn(v->pm);
+ if (v->pm->node.flags & (PM_EFLOAT|PM_FFLOAT))
+ return (zlong)v->pm->gsu.f->getfn(v->pm);
+ return mathevali(getstrvalue(v));
+}
+
+/**/
+mnumber
+getnumvalue(Value v)
+{
+ mnumber mn;
+ mn.type = MN_INTEGER;
+
+
+ if (!v) {
+ mn.u.l = 0;
+ } else if (v->flags & VALFLAG_INV) {
+ mn.u.l = v->start;
+ } else if (v->isarr) {
+ char **arr = getarrvalue(v);
+ if (arr) {
+ char *scal = sepjoin(arr, NULL, 1);
+ return matheval(scal);
+ } else
+ mn.u.l = 0;
+ } else if (PM_TYPE(v->pm->node.flags) == PM_INTEGER) {
+ mn.u.l = v->pm->gsu.i->getfn(v->pm);
+ } else if (v->pm->node.flags & (PM_EFLOAT|PM_FFLOAT)) {
+ mn.type = MN_FLOAT;
+ mn.u.d = v->pm->gsu.f->getfn(v->pm);
+ } else
+ return matheval(getstrvalue(v));
+ return mn;
+}
+
+/**/
+void
+export_param(Param pm)
+{
+ char buf[BDIGBUFSIZE], *val;
+
+ if (PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)) {
+#if 0 /* Requires changes elsewhere in params.c and builtin.c */
+ if (EMULATION(EMULATE_KSH) /* isset(KSHARRAYS) */) {
+ struct value v;
+ v.isarr = 1;
+ v.flags = 0;
+ v.start = 0;
+ v.end = -1;
+ val = getstrvalue(&v);
+ } else
+#endif
+ return;
+ } else if (PM_TYPE(pm->node.flags) == PM_INTEGER)
+ convbase(val = buf, pm->gsu.i->getfn(pm), pm->base);
+ else if (pm->node.flags & (PM_EFLOAT|PM_FFLOAT))
+ val = convfloat(pm->gsu.f->getfn(pm), pm->base,
+ pm->node.flags, NULL);
+ else
+ val = pm->gsu.s->getfn(pm);
+
+ addenv(pm, val);
+}
+
+/**/
+mod_export void
+setstrvalue(Value v, char *val)
+{
+ assignstrvalue(v, val, 0);
+}
+
+/**/
+mod_export void
+assignstrvalue(Value v, char *val, int flags)
+{
+ if (unset(EXECOPT))
+ return;
+ if (v->pm->node.flags & PM_READONLY) {
+ zerr("read-only variable: %s", v->pm->node.nam);
+ zsfree(val);
+ return;
+ }
+ if ((v->pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerr("%s: restricted", v->pm->node.nam);
+ zsfree(val);
+ return;
+ }
+ if ((v->pm->node.flags & PM_HASHED) &&
+ (v->isarr & (SCANPM_MATCHMANY|SCANPM_ARRONLY))) {
+ zerr("%s: attempt to set slice of associative array", v->pm->node.nam);
+ zsfree(val);
+ return;
+ }
+ if (v->flags & VALFLAG_EMPTY) {
+ zerr("%s: assignment to invalid subscript range", v->pm->node.nam);
+ zsfree(val);
+ return;
+ }
+ v->pm->node.flags &= ~PM_UNSET;
+ switch (PM_TYPE(v->pm->node.flags)) {
+ case PM_SCALAR:
+ if (v->start == 0 && v->end == -1) {
+ v->pm->gsu.s->setfn(v->pm, val);
+ if ((v->pm->node.flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) &&
+ !v->pm->width)
+ v->pm->width = strlen(val);
+ } else {
+ char *z, *x;
+ int zlen, vlen, newsize;
+
+ z = v->pm->gsu.s->getfn(v->pm);
+ zlen = strlen(z);
+
+ if ((v->flags & VALFLAG_INV) && unset(KSHARRAYS))
+ v->start--, v->end--;
+ if (v->start < 0) {
+ v->start += zlen;
+ if (v->start < 0)
+ v->start = 0;
+ }
+ if (v->start > zlen)
+ v->start = zlen;
+ if (v->end < 0) {
+ v->end += zlen;
+ if (v->end < 0) {
+ v->end = 0;
+ } else if (v->end >= zlen) {
+ v->end = zlen;
+ } else {
+#ifdef MULTIBYTE_SUPPORT
+ if (isset(MULTIBYTE)) {
+ v->end += MB_METACHARLEN(z + v->end);
+ } else {
+ v->end++;
+ }
+#else
+ v->end++;
+#endif
+ }
+ }
+ else if (v->end > zlen)
+ v->end = zlen;
+
+ vlen = strlen(val);
+ /* Characters preceding start index +
+ characters of what is assigned +
+ characters following end index */
+ newsize = v->start + vlen + (zlen - v->end);
+
+ /* Does new size differ? */
+ if (newsize != zlen || v->pm->gsu.s->setfn != strsetfn) {
+ x = (char *) zalloc(newsize + 1);
+ strncpy(x, z, v->start);
+ strcpy(x + v->start, val);
+ strcat(x + v->start, z + v->end);
+ v->pm->gsu.s->setfn(v->pm, x);
+ } else {
+ Param pm = v->pm;
+ /* Size doesn't change, can limit actions to only
+ * overwriting bytes in already allocated string */
+ strncpy(z + v->start, val, vlen);
+ /* Implement remainder of strsetfn */
+ if (!(pm->node.flags & PM_HASHELEM) &&
+ ((pm->node.flags & PM_NAMEDDIR) ||
+ isset(AUTONAMEDIRS))) {
+ pm->node.flags |= PM_NAMEDDIR;
+ adduserdir(pm->node.nam, z, 0, 0);
+ }
+ }
+ zsfree(val);
+ }
+ break;
+ case PM_INTEGER:
+ if (val) {
+ zlong ival;
+ if (flags & ASSPM_ENV_IMPORT) {
+ char *ptr;
+ ival = zstrtol_underscore(val, &ptr, 0, 1);
+ } else
+ ival = mathevali(val);
+ v->pm->gsu.i->setfn(v->pm, ival);
+ if ((v->pm->node.flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) &&
+ !v->pm->width)
+ v->pm->width = strlen(val);
+ zsfree(val);
+ }
+ if (!v->pm->base && lastbase != -1)
+ v->pm->base = lastbase;
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ if (val) {
+ mnumber mn;
+ if (flags & ASSPM_ENV_IMPORT) {
+ char *ptr;
+ mn.type = MN_FLOAT;
+ mn.u.d = strtod(val, &ptr);
+ } else
+ mn = matheval(val);
+ v->pm->gsu.f->setfn(v->pm, (mn.type & MN_FLOAT) ? mn.u.d :
+ (double)mn.u.l);
+ if ((v->pm->node.flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) &&
+ !v->pm->width)
+ v->pm->width = strlen(val);
+ zsfree(val);
+ }
+ break;
+ case PM_ARRAY:
+ {
+ char **ss = (char **) zalloc(2 * sizeof(char *));
+
+ ss[0] = val;
+ ss[1] = NULL;
+ setarrvalue(v, ss);
+ }
+ break;
+ case PM_HASHED:
+ {
+ if (foundparam == NULL)
+ {
+ zerr("%s: attempt to set associative array to scalar",
+ v->pm->node.nam);
+ zsfree(val);
+ return;
+ }
+ else
+ foundparam->gsu.s->setfn(foundparam, val);
+ }
+ break;
+ }
+ if ((!v->pm->env && !(v->pm->node.flags & PM_EXPORTED) &&
+ !(isset(ALLEXPORT) && !(v->pm->node.flags & PM_HASHELEM))) ||
+ (v->pm->node.flags & PM_ARRAY) || v->pm->ename)
+ return;
+ export_param(v->pm);
+}
+
+/**/
+void
+setnumvalue(Value v, mnumber val)
+{
+ char buf[BDIGBUFSIZE], *p;
+
+ if (unset(EXECOPT))
+ return;
+ if (v->pm->node.flags & PM_READONLY) {
+ zerr("read-only variable: %s", v->pm->node.nam);
+ return;
+ }
+ if ((v->pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerr("%s: restricted", v->pm->node.nam);
+ return;
+ }
+ switch (PM_TYPE(v->pm->node.flags)) {
+ case PM_SCALAR:
+ case PM_ARRAY:
+ if ((val.type & MN_INTEGER) || outputradix) {
+ if (!(val.type & MN_INTEGER))
+ val.u.l = (zlong) val.u.d;
+ p = convbase_underscore(buf, val.u.l, outputradix,
+ outputunderscore);
+ } else
+ p = convfloat_underscore(val.u.d, outputunderscore);
+ setstrvalue(v, ztrdup(p));
+ break;
+ case PM_INTEGER:
+ v->pm->gsu.i->setfn(v->pm, (val.type & MN_INTEGER) ? val.u.l :
+ (zlong) val.u.d);
+ setstrvalue(v, NULL);
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ v->pm->gsu.f->setfn(v->pm, (val.type & MN_INTEGER) ?
+ (double)val.u.l : val.u.d);
+ setstrvalue(v, NULL);
+ break;
+ }
+}
+
+/**/
+mod_export void
+setarrvalue(Value v, char **val)
+{
+ if (unset(EXECOPT))
+ return;
+ if (v->pm->node.flags & PM_READONLY) {
+ zerr("read-only variable: %s", v->pm->node.nam);
+ freearray(val);
+ return;
+ }
+ if ((v->pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerr("%s: restricted", v->pm->node.nam);
+ freearray(val);
+ return;
+ }
+ if (!(PM_TYPE(v->pm->node.flags) & (PM_ARRAY|PM_HASHED))) {
+ freearray(val);
+ zerr("%s: attempt to assign array value to non-array",
+ v->pm->node.nam);
+ return;
+ }
+ if (v->flags & VALFLAG_EMPTY) {
+ zerr("%s: assignment to invalid subscript range", v->pm->node.nam);
+ freearray(val);
+ return;
+ }
+
+ if (v->start == 0 && v->end == -1) {
+ if (PM_TYPE(v->pm->node.flags) == PM_HASHED)
+ arrhashsetfn(v->pm, val, 0);
+ else
+ v->pm->gsu.a->setfn(v->pm, val);
+ } else if (v->start == -1 && v->end == 0 &&
+ PM_TYPE(v->pm->node.flags) == PM_HASHED) {
+ arrhashsetfn(v->pm, val, ASSPM_AUGMENT);
+ } else if ((PM_TYPE(v->pm->node.flags) == PM_HASHED)) {
+ freearray(val);
+ zerr("%s: attempt to set slice of associative array",
+ v->pm->node.nam);
+ return;
+ } else {
+ char **const old = v->pm->gsu.a->getfn(v->pm);
+ char **new;
+ char **p, **q, **r; /* index variables */
+ const int pre_assignment_length = arrlen(old);
+ int post_assignment_length;
+ int i;
+
+ q = old;
+
+ if ((v->flags & VALFLAG_INV) && unset(KSHARRAYS)) {
+ if (v->start > 0)
+ v->start--;
+ v->end--;
+ }
+ if (v->start < 0) {
+ v->start += pre_assignment_length;
+ if (v->start < 0)
+ v->start = 0;
+ }
+ if (v->end < 0) {
+ v->end += pre_assignment_length + 1;
+ if (v->end < 0)
+ v->end = 0;
+ }
+ if (v->end < v->start)
+ v->end = v->start;
+
+ post_assignment_length = v->start + arrlen(val);
+ if (v->end < pre_assignment_length) {
+ /*
+ * Allocate room for array elements between the end of the slice `v'
+ * and the original array's end.
+ */
+ post_assignment_length += pre_assignment_length - v->end;
+ }
+
+ if (pre_assignment_length == post_assignment_length
+ && v->pm->gsu.a->setfn == arrsetfn
+ /* ... and isn't something that arrsetfn() treats specially */
+ && 0 == (v->pm->node.flags & (PM_SPECIAL|PM_UNIQUE))
+ && NULL == v->pm->ename)
+ {
+ /* v->start is 0-based */
+ p = old + v->start;
+ for (r = val; *r;) {
+ /* Free previous string */
+ zsfree(*p);
+ /* Give away ownership of the string */
+ *p++ = *r++;
+ }
+ } else {
+ /* arr+=( ... )
+ * arr[${#arr}+x,...]=( ... ) */
+ if (post_assignment_length > pre_assignment_length &&
+ pre_assignment_length <= v->start &&
+ pre_assignment_length > 0 &&
+ v->pm->gsu.a->setfn == arrsetfn)
+ {
+ p = new = (char **) zrealloc(old, sizeof(char *)
+ * (post_assignment_length + 1));
+
+ p += pre_assignment_length; /* after old elements */
+
+ /* Consider 1 < 0, case for a=( 1 ); a[1,..] =
+ * 1 < 1, case for a=( 1 ); a[2,..] = */
+ if (pre_assignment_length < v->start) {
+ for (i = pre_assignment_length; i < v->start; i++) {
+ *p++ = ztrdup("");
+ }
+ }
+
+ for (r = val; *r;) {
+ /* Give away ownership of the string */
+ *p++ = *r++;
+ }
+
+ /* v->end doesn't matter:
+ * a=( 1 2 ); a[4,100]=( a b ); echo "${(q@)a}"
+ * 1 2 '' a b */
+ *p = NULL;
+
+ v->pm->u.arr = NULL;
+ v->pm->gsu.a->setfn(v->pm, new);
+ } else {
+ p = new = (char **) zalloc(sizeof(char *)
+ * (post_assignment_length + 1));
+ for (i = 0; i < v->start; i++)
+ *p++ = i < pre_assignment_length ? ztrdup(*q++) : ztrdup("");
+ for (r = val; *r;) {
+ /* Give away ownership of the string */
+ *p++ = *r++;
+ }
+ if (v->end < pre_assignment_length)
+ for (q = old + v->end; *q;)
+ *p++ = ztrdup(*q++);
+ *p = NULL;
+
+ v->pm->gsu.a->setfn(v->pm, new);
+ }
+
+ DPUTS2(p - new != post_assignment_length, "setarrvalue: wrong allocation: %d 1= %lu",
+ post_assignment_length, (unsigned long)(p - new));
+ }
+
+ /* Ownership of all strings has been
+ * given away, can plainly free */
+ free(val);
+ }
+}
+
+/* Retrieve an integer parameter */
+
+/**/
+mod_export zlong
+getiparam(char *s)
+{
+ struct value vbuf;
+ Value v;
+
+ if (!(v = getvalue(&vbuf, &s, 1)))
+ return 0;
+ return getintvalue(v);
+}
+
+/* Retrieve a numerical parameter, either integer or floating */
+
+/**/
+mnumber
+getnparam(char *s)
+{
+ struct value vbuf;
+ Value v;
+
+ if (!(v = getvalue(&vbuf, &s, 1))) {
+ mnumber mn;
+ mn.type = MN_INTEGER;
+ mn.u.l = 0;
+ return mn;
+ }
+ return getnumvalue(v);
+}
+
+/* Retrieve a scalar (string) parameter */
+
+/**/
+mod_export char *
+getsparam(char *s)
+{
+ struct value vbuf;
+ Value v;
+
+ if (!(v = getvalue(&vbuf, &s, 0)))
+ return NULL;
+ return getstrvalue(v);
+}
+
+/**/
+mod_export char *
+getsparam_u(char *s)
+{
+ if ((s = getsparam(s)))
+ return unmetafy(s, NULL);
+ return s;
+}
+
+/* Retrieve an array parameter */
+
+/**/
+mod_export char **
+getaparam(char *s)
+{
+ struct value vbuf;
+ Value v;
+
+ if (!idigit(*s) && (v = getvalue(&vbuf, &s, 0)) &&
+ PM_TYPE(v->pm->node.flags) == PM_ARRAY)
+ return v->pm->gsu.a->getfn(v->pm);
+ return NULL;
+}
+
+/* Retrieve an assoc array parameter as an array */
+
+/**/
+mod_export char **
+gethparam(char *s)
+{
+ struct value vbuf;
+ Value v;
+
+ if (!idigit(*s) && (v = getvalue(&vbuf, &s, 0)) &&
+ PM_TYPE(v->pm->node.flags) == PM_HASHED)
+ return paramvalarr(v->pm->gsu.h->getfn(v->pm), SCANPM_WANTVALS);
+ return NULL;
+}
+
+/* Retrieve the keys of an assoc array parameter as an array */
+
+/**/
+mod_export char **
+gethkparam(char *s)
+{
+ struct value vbuf;
+ Value v;
+
+ if (!idigit(*s) && (v = getvalue(&vbuf, &s, 0)) &&
+ PM_TYPE(v->pm->node.flags) == PM_HASHED)
+ return paramvalarr(v->pm->gsu.h->getfn(v->pm), SCANPM_WANTKEYS);
+ return NULL;
+}
+
+/*
+ * Function behind WARNCREATEGLOBAL and WARNNESTEDVAR option.
+ *
+ * For WARNNESTEDVAR:
+ * Called when the variable is created.
+ * Apply heuristics to see if this variable was just created
+ * globally but in a local context.
+ *
+ * For WARNNESTEDVAR:
+ * Called when the variable already exists and is set.
+ * Apply heuristics to see if this variable is setting
+ * a variable that was created in a less nested function
+ * or globally.
+ */
+
+/**/
+static void
+check_warn_pm(Param pm, const char *pmtype, int created,
+ int may_warn_about_nested_vars)
+{
+ Funcstack i;
+
+ if (!may_warn_about_nested_vars && !created)
+ return;
+
+ if (created && isset(WARNCREATEGLOBAL)) {
+ if (locallevel <= forklevel || pm->level != 0)
+ return;
+ } else if (!created && isset(WARNNESTEDVAR)) {
+ if (pm->level >= locallevel)
+ return;
+ } else
+ return;
+
+ if (pm->node.flags & PM_SPECIAL)
+ return;
+
+ for (i = funcstack; i; i = i->prev) {
+ if (i->tp == FS_FUNC) {
+ char *msg;
+ DPUTS(!i->name, "funcstack entry with no name");
+ msg = created ?
+ "%s parameter %s created globally in function %s" :
+ "%s parameter %s set in enclosing scope in function %s";
+ zwarn(msg, pmtype, pm->node.nam, i->name);
+ break;
+ }
+ }
+}
+
+/**/
+mod_export Param
+assignsparam(char *s, char *val, int flags)
+{
+ struct value vbuf;
+ Value v;
+ char *t = s;
+ char *ss, *copy, *var;
+ size_t lvar;
+ mnumber lhs, rhs;
+ int sstart, created = 0;
+
+ if (!isident(s)) {
+ zerr("not an identifier: %s", s);
+ zsfree(val);
+ errflag |= ERRFLAG_ERROR;
+ return NULL;
+ }
+ queue_signals();
+ if ((ss = strchr(s, '['))) {
+ *ss = '\0';
+ if (!(v = getvalue(&vbuf, &s, 1))) {
+ createparam(t, PM_ARRAY);
+ created = 1;
+ } else {
+ if (v->pm->node.flags & PM_READONLY) {
+ zerr("read-only variable: %s", v->pm->node.nam);
+ *ss = '[';
+ zsfree(val);
+ unqueue_signals();
+ return NULL;
+ }
+ /*
+ * Parameter defined here is a temporary bogus one.
+ * Don't warn about anything.
+ */
+ flags &= ~ASSPM_WARN;
+ }
+ *ss = '[';
+ v = NULL;
+ } else {
+ if (!(v = getvalue(&vbuf, &s, 1))) {
+ createparam(t, PM_SCALAR);
+ created = 1;
+ } else if ((((v->pm->node.flags & PM_ARRAY) && !(flags & ASSPM_AUGMENT)) ||
+ (v->pm->node.flags & PM_HASHED)) &&
+ !(v->pm->node.flags & (PM_SPECIAL|PM_TIED)) &&
+ unset(KSHARRAYS)) {
+ unsetparam(t);
+ createparam(t, PM_SCALAR);
+ /* not regarded as a new creation */
+ v = NULL;
+ }
+ }
+ if (!v && !(v = getvalue(&vbuf, &t, 1))) {
+ unqueue_signals();
+ zsfree(val);
+ /* errflag |= ERRFLAG_ERROR; */
+ return NULL;
+ }
+ if (flags & ASSPM_WARN)
+ check_warn_pm(v->pm, "scalar", created, 1);
+ if (flags & ASSPM_AUGMENT) {
+ if (v->start == 0 && v->end == -1) {
+ switch (PM_TYPE(v->pm->node.flags)) {
+ case PM_SCALAR:
+ v->start = INT_MAX; /* just append to scalar value */
+ break;
+ case PM_INTEGER:
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ rhs = matheval(val);
+ lhs = getnumvalue(v);
+ if (lhs.type == MN_FLOAT) {
+ if ((rhs.type) == MN_FLOAT)
+ lhs.u.d = lhs.u.d + rhs.u.d;
+ else
+ lhs.u.d = lhs.u.d + (double)rhs.u.l;
+ } else {
+ if ((rhs.type) == MN_INTEGER)
+ lhs.u.l = lhs.u.l + rhs.u.l;
+ else
+ lhs.u.l = lhs.u.l + (zlong)rhs.u.d;
+ }
+ setnumvalue(v, lhs);
+ unqueue_signals();
+ zsfree(val);
+ return v->pm; /* avoid later setstrvalue() call */
+ case PM_ARRAY:
+ if (unset(KSHARRAYS)) {
+ v->start = arrlen(v->pm->gsu.a->getfn(v->pm));
+ v->end = v->start + 1;
+ } else {
+ /* ksh appends scalar to first element */
+ v->end = 1;
+ goto kshappend;
+ }
+ break;
+ }
+ } else {
+ switch (PM_TYPE(v->pm->node.flags)) {
+ case PM_SCALAR:
+ if (v->end > 0)
+ v->start = v->end;
+ else
+ v->start = v->end = strlen(v->pm->gsu.s->getfn(v->pm)) +
+ v->end + 1;
+ break;
+ case PM_INTEGER:
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ unqueue_signals();
+ zerr("attempt to add to slice of a numeric variable");
+ zsfree(val);
+ return NULL;
+ case PM_ARRAY:
+ kshappend:
+ /* treat slice as the end element */
+ v->start = sstart = v->end > 0 ? v->end - 1 : v->end;
+ v->isarr = 0;
+ var = getstrvalue(v);
+ v->start = sstart;
+ copy = val;
+ lvar = strlen(var);
+ val = (char *)zalloc(lvar + strlen(val) + 1);
+ strcpy(val, var);
+ strcpy(val + lvar, copy);
+ zsfree(copy);
+ break;
+ }
+ }
+ }
+
+ assignstrvalue(v, val, flags);
+ unqueue_signals();
+ return v->pm;
+}
+
+/**/
+mod_export Param
+setsparam(char *s, char *val)
+{
+ return assignsparam(s, val, ASSPM_WARN);
+}
+
+/**/
+mod_export Param
+assignaparam(char *s, char **val, int flags)
+{
+ struct value vbuf;
+ Value v;
+ char *t = s;
+ char *ss;
+ int created = 0;
+ int may_warn_about_nested_vars = 1;
+
+ if (!isident(s)) {
+ zerr("not an identifier: %s", s);
+ freearray(val);
+ errflag |= ERRFLAG_ERROR;
+ return NULL;
+ }
+ queue_signals();
+ if ((ss = strchr(s, '['))) {
+ *ss = '\0';
+ if (!(v = getvalue(&vbuf, &s, 1))) {
+ createparam(t, PM_ARRAY);
+ created = 1;
+ } else {
+ may_warn_about_nested_vars = 0;
+ }
+ *ss = '[';
+ if (v && PM_TYPE(v->pm->node.flags) == PM_HASHED) {
+ unqueue_signals();
+ zerr("%s: attempt to set slice of associative array",
+ v->pm->node.nam);
+ freearray(val);
+ errflag |= ERRFLAG_ERROR;
+ return NULL;
+ }
+ v = NULL;
+ } else {
+ if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING))) {
+ createparam(t, PM_ARRAY);
+ created = 1;
+ } else if (!(PM_TYPE(v->pm->node.flags) & (PM_ARRAY|PM_HASHED)) &&
+ !(v->pm->node.flags & (PM_SPECIAL|PM_TIED))) {
+ int uniq = v->pm->node.flags & PM_UNIQUE;
+ if (flags & ASSPM_AUGMENT) {
+ /* insert old value at the beginning of the val array */
+ char **new;
+ int lv = arrlen(val);
+
+ new = (char **) zalloc(sizeof(char *) * (lv + 2));
+ *new = ztrdup(getstrvalue(v));
+ memcpy(new+1, val, sizeof(char *) * (lv + 1));
+ free(val);
+ val = new;
+ }
+ unsetparam(t);
+ createparam(t, PM_ARRAY | uniq);
+ v = NULL;
+ }
+ }
+ if (!v)
+ if (!(v = fetchvalue(&vbuf, &t, 1, SCANPM_ASSIGNING))) {
+ unqueue_signals();
+ freearray(val);
+ /* errflag |= ERRFLAG_ERROR; */
+ return NULL;
+ }
+
+ if (flags & ASSPM_WARN)
+ check_warn_pm(v->pm, "array", created, may_warn_about_nested_vars);
+
+ /*
+ * At this point, we may have array entries consisting of
+ * - a Marker element --- normally allocated array entry but
+ * with just Marker char and null
+ * - an array index element --- as normal for associative array,
+ * but non-standard for normal array which we handle now.
+ * - a value for the indexed element.
+ * This only applies if the flag ASSPM_KEY_VALUE is passed in,
+ * indicating prefork() detected this syntax.
+ *
+ * For associative arrays we just junk the Marker elements.
+ */
+ if (flags & ASSPM_KEY_VALUE) {
+ char **aptr;
+ if (PM_TYPE(v->pm->node.flags) & PM_ARRAY) {
+ /*
+ * This is an ordinary array with key / value pairs.
+ */
+ int maxlen, origlen, nextind;
+ char **fullval, **origptr;
+ zlong *subscripts = (zlong *)zhalloc(arrlen(val) * sizeof(zlong));
+ zlong *iptr = subscripts;
+ if (flags & ASSPM_AUGMENT) {
+ origptr = v->pm->gsu.a->getfn(v->pm);
+ maxlen = origlen = arrlen(origptr);
+ } else {
+ maxlen = origlen = 0;
+ origptr = NULL;
+ }
+ nextind = 0;
+ for (aptr = val; *aptr; ) {
+ if (**aptr == Marker) {
+ *iptr = mathevali(*++aptr);
+ if (*iptr < 0 ||
+ (!isset(KSHARRAYS) && *iptr == 0)) {
+ unqueue_signals();
+ zerr("bad subscript for direct array assignment: %s", *aptr);
+ freearray(val);
+ return NULL;
+ }
+ if (!isset(KSHARRAYS))
+ --*iptr;
+ nextind = *iptr + 1;
+ ++iptr;
+ aptr += 2;
+ } else {
+ ++nextind;
+ ++aptr;
+ }
+ if (nextind > maxlen)
+ maxlen = nextind;
+ }
+ fullval = zshcalloc((maxlen+1) * sizeof(char *));
+ if (!fullval) {
+ zerr("array too large");
+ freearray(val);
+ return NULL;
+ }
+ fullval[maxlen] = NULL;
+ if (flags & ASSPM_AUGMENT) {
+ char **srcptr = origptr;
+ for (aptr = fullval; aptr <= fullval + origlen; aptr++) {
+ *aptr = ztrdup(*srcptr);
+ srcptr++;
+ }
+ }
+ iptr = subscripts;
+ nextind = 0;
+ for (aptr = val; *aptr; ++aptr) {
+ char *old;
+ if (**aptr == Marker) {
+ int augment = ((*aptr)[1] == '+');
+ zsfree(*aptr);
+ zsfree(*++aptr); /* Index, no longer needed */
+ old = fullval[*iptr];
+ if (augment && old) {
+ fullval[*iptr] = bicat(old, *++aptr);
+ zsfree(*aptr);
+ } else {
+ fullval[*iptr] = *++aptr;
+ }
+ nextind = *iptr + 1;
+ ++iptr;
+ } else {
+ old = fullval[nextind];
+ fullval[nextind] = *aptr;
+ ++nextind;
+ }
+ if (old)
+ zsfree(old);
+ /* aptr now on value in both cases */
+ }
+ if (*aptr) { /* Shouldn't be possible */
+ DPUTS(1, "Extra element in key / value array");
+ zsfree(*aptr);
+ }
+ free(val);
+ for (aptr = fullval; aptr < fullval + maxlen; aptr++) {
+ /*
+ * Remember we don't have sparse arrays but and they're null
+ * terminated --- so any value we don't set has to be an
+ * empty string.
+ */
+ if (!*aptr)
+ *aptr = ztrdup("");
+ }
+ setarrvalue(v, fullval);
+ unqueue_signals();
+ return v->pm;
+ } else if (PM_TYPE(v->pm->node.flags & PM_HASHED)) {
+ /*
+ * We strictly enforce [key]=value syntax for associative
+ * arrays. Marker can only indicate a Marker / key / value
+ * triad; it cannot be there by accident.
+ *
+ * It's too inefficient to strip Markers here, and they
+ * can't be there in the other form --- so just ignore
+ * them willy nilly lower down.
+ */
+ for (aptr = val; *aptr; aptr += 3) {
+ if (**aptr != Marker) {
+ unqueue_signals();
+ freearray(val);
+ zerr("bad [key]=value syntax for associative array");
+ return NULL;
+ }
+ }
+ } else {
+ unqueue_signals();
+ freearray(val);
+ zerr("invalid use of [key]=value assignment syntax");
+ return NULL;
+ }
+ }
+
+ if (flags & ASSPM_AUGMENT) {
+ if (v->start == 0 && v->end == -1) {
+ if (PM_TYPE(v->pm->node.flags) & PM_ARRAY) {
+ v->start = arrlen(v->pm->gsu.a->getfn(v->pm));
+ v->end = v->start + 1;
+ } else if (PM_TYPE(v->pm->node.flags) & PM_HASHED)
+ v->start = -1, v->end = 0;
+ } else {
+ if (v->end > 0)
+ v->start = v->end--;
+ else if (PM_TYPE(v->pm->node.flags) & PM_ARRAY) {
+ v->end = arrlen(v->pm->gsu.a->getfn(v->pm)) + v->end;
+ v->start = v->end + 1;
+ }
+ }
+ }
+
+ setarrvalue(v, val);
+ unqueue_signals();
+ return v->pm;
+}
+
+
+/**/
+mod_export Param
+setaparam(char *s, char **aval)
+{
+ return assignaparam(s, aval, ASSPM_WARN);
+}
+
+/**/
+mod_export Param
+sethparam(char *s, char **val)
+{
+ struct value vbuf;
+ Value v;
+ char *t = s;
+ int checkcreate = 0;
+
+ if (!isident(s)) {
+ zerr("not an identifier: %s", s);
+ freearray(val);
+ errflag |= ERRFLAG_ERROR;
+ return NULL;
+ }
+ if (strchr(s, '[')) {
+ freearray(val);
+ zerr("nested associative arrays not yet supported");
+ errflag |= ERRFLAG_ERROR;
+ return NULL;
+ }
+ if (unset(EXECOPT))
+ return NULL;
+ queue_signals();
+ if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING))) {
+ createparam(t, PM_HASHED);
+ checkcreate = 1;
+ } else if (!(PM_TYPE(v->pm->node.flags) & PM_HASHED)) {
+ if (!(v->pm->node.flags & PM_SPECIAL)) {
+ unsetparam(t);
+ /* no WARNCREATEGLOBAL check here as parameter already existed */
+ createparam(t, PM_HASHED);
+ v = NULL;
+ } else {
+ zerr("%s: can't change type of a special parameter", t);
+ unqueue_signals();
+ return NULL;
+ }
+ }
+ if (!v)
+ if (!(v = fetchvalue(&vbuf, &t, 1, SCANPM_ASSIGNING))) {
+ unqueue_signals();
+ /* errflag |= ERRFLAG_ERROR; */
+ return NULL;
+ }
+ check_warn_pm(v->pm, "associative array", checkcreate, 1);
+ setarrvalue(v, val);
+ unqueue_signals();
+ return v->pm;
+}
+
+
+/*
+ * Set a generic shell number, floating point or integer.
+ * Option to warn on setting.
+ */
+
+/**/
+mod_export Param
+assignnparam(char *s, mnumber val, int flags)
+{
+ struct value vbuf;
+ Value v;
+ char *t = s, *ss;
+ Param pm;
+ int was_unset = 0;
+
+ if (!isident(s)) {
+ zerr("not an identifier: %s", s);
+ errflag |= ERRFLAG_ERROR;
+ return NULL;
+ }
+ if (unset(EXECOPT))
+ return NULL;
+ queue_signals();
+ ss = strchr(s, '[');
+ v = getvalue(&vbuf, &s, 1);
+ if (v && (v->pm->node.flags & (PM_ARRAY|PM_HASHED)) &&
+ !(v->pm->node.flags & (PM_SPECIAL|PM_TIED)) &&
+ /*
+ * not sure what KSHARRAYS has got to do with this...
+ * copied this from assignsparam().
+ */
+ unset(KSHARRAYS) && !ss) {
+ unsetparam_pm(v->pm, 0, 1);
+ was_unset = 1;
+ s = t;
+ v = NULL;
+ }
+ if (!v) {
+ /* s has been updated by getvalue, so check again */
+ ss = strchr(s, '[');
+ if (ss)
+ *ss = '\0';
+ pm = createparam(t, ss ? PM_ARRAY :
+ isset(POSIXIDENTIFIERS) ? PM_SCALAR :
+ (val.type & MN_INTEGER) ? PM_INTEGER : PM_FFLOAT);
+ if (!pm)
+ pm = (Param) paramtab->getnode(paramtab, t);
+ DPUTS(!pm, "BUG: parameter not created");
+ if (ss) {
+ *ss = '[';
+ } else if (val.type & MN_INTEGER) {
+ pm->base = outputradix;
+ }
+ if (!(v = getvalue(&vbuf, &t, 1))) {
+ DPUTS(!v, "BUG: value not found for new parameter");
+ /* errflag |= ERRFLAG_ERROR; */
+ unqueue_signals();
+ return NULL;
+ }
+ if (flags & ASSPM_WARN)
+ check_warn_pm(v->pm, "numeric", !was_unset, 1);
+ } else {
+ if (flags & ASSPM_WARN)
+ check_warn_pm(v->pm, "numeric", 0, 1);
+ }
+ setnumvalue(v, val);
+ unqueue_signals();
+ return v->pm;
+}
+
+/*
+ * Set a generic shell number, floating point or integer.
+ * Warn on setting based on option.
+ */
+
+/**/
+mod_export Param
+setnparam(char *s, mnumber val)
+{
+ return assignnparam(s, val, ASSPM_WARN);
+}
+
+/* Simplified interface to assignnparam */
+
+/**/
+mod_export Param
+assigniparam(char *s, zlong val, int flags)
+{
+ mnumber mnval;
+ mnval.type = MN_INTEGER;
+ mnval.u.l = val;
+ return assignnparam(s, mnval, flags);
+}
+
+/* Simplified interface to setnparam */
+
+/**/
+mod_export Param
+setiparam(char *s, zlong val)
+{
+ mnumber mnval;
+ mnval.type = MN_INTEGER;
+ mnval.u.l = val;
+ return assignnparam(s, mnval, ASSPM_WARN);
+}
+
+/*
+ * Set an integer parameter without forcing creation of an integer type.
+ * This is useful if the integer is going to be set to a parmaeter which
+ * would usually be scalar but may not exist.
+ */
+
+/**/
+mod_export Param
+setiparam_no_convert(char *s, zlong val)
+{
+ /*
+ * If the target is already an integer, thisgets converted
+ * back. Low technology rules.
+ */
+ char buf[BDIGBUFSIZE];
+ convbase(buf, val, 10);
+ return assignsparam(s, ztrdup(buf), ASSPM_WARN);
+}
+
+/* Unset a parameter */
+
+/**/
+mod_export void
+unsetparam(char *s)
+{
+ Param pm;
+
+ queue_signals();
+ if ((pm = (Param) (paramtab == realparamtab ?
+ /* getnode2() to avoid autoloading */
+ paramtab->getnode2(paramtab, s) :
+ paramtab->getnode(paramtab, s))))
+ unsetparam_pm(pm, 0, 1);
+ unqueue_signals();
+}
+
+/* Unset a parameter
+ *
+ * altflag: if true, don't remove pm->ename from the environment
+ * exp: See stdunsetfn()
+ */
+
+/**/
+mod_export int
+unsetparam_pm(Param pm, int altflag, int exp)
+{
+ Param oldpm, altpm;
+ char *altremove;
+
+ if ((pm->node.flags & PM_READONLY) && pm->level <= locallevel) {
+ zerr("read-only variable: %s", pm->node.nam);
+ return 1;
+ }
+ if ((pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerr("%s: restricted", pm->node.nam);
+ return 1;
+ }
+
+ if (pm->ename && !altflag)
+ altremove = ztrdup(pm->ename);
+ else
+ altremove = NULL;
+
+ if (!(pm->node.flags & PM_UNSET))
+ pm->gsu.s->unsetfn(pm, exp);
+ if (pm->env)
+ delenv(pm);
+
+ /* remove it under its alternate name if necessary */
+ if (altremove) {
+ altpm = (Param) paramtab->getnode(paramtab, altremove);
+ /* tied parameters are at the same local level as each other */
+ oldpm = NULL;
+ while (altpm && altpm->level > pm->level) {
+ /* param under alternate name hidden by a local */
+ oldpm = altpm;
+ altpm = altpm->old;
+ }
+ if (altpm) {
+ if (oldpm && !altpm->level) {
+ oldpm->old = NULL;
+ /* fudge things so removenode isn't called */
+ altpm->level = 1;
+ }
+ unsetparam_pm(altpm, 1, exp);
+ }
+
+ zsfree(altremove);
+ }
+
+ /*
+ * 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".
+ *
+ * Some specials, such as those used in zle, still need removing
+ * from the parameter table; they have the PM_REMOVABLE flag.
+ */
+ if ((pm->level && locallevel >= pm->level) ||
+ (pm->node.flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL)
+ return 0;
+
+ /* remove parameter node from table */
+ paramtab->removenode(paramtab, pm->node.nam);
+
+ if (pm->old) {
+ oldpm = pm->old;
+ paramtab->addnode(paramtab, oldpm->node.nam, oldpm);
+ if ((PM_TYPE(oldpm->node.flags) == PM_SCALAR) &&
+ !(pm->node.flags & PM_HASHELEM) &&
+ (oldpm->node.flags & PM_NAMEDDIR) &&
+ oldpm->gsu.s == &stdscalar_gsu)
+ adduserdir(oldpm->node.nam, oldpm->u.str, 0, 0);
+ if (oldpm->node.flags & PM_EXPORTED) {
+ /*
+ * Re-export the old value which we removed in typeset_single().
+ * I don't think we need to test for ALL_EXPORT here, since if
+ * it was used to export the parameter originally the parameter
+ * should still have the PM_EXPORTED flag.
+ */
+ export_param(oldpm);
+ }
+ }
+
+ paramtab->freenode(&pm->node); /* free parameter node */
+
+ return 0;
+}
+
+/* Standard function to unset a parameter. This is mostly delegated to *
+ * the specific set function.
+ *
+ * This could usefully be made type-specific, but then we need
+ * to be more careful when calling the unset method directly.
+ *
+ * The "exp"licit parameter should be nonzero for assignments and the
+ * unset command, and zero for implicit unset (e.g., end of scope).
+ * Currently this is used only by some modules.
+ */
+
+/**/
+mod_export void
+stdunsetfn(Param pm, UNUSED(int exp))
+{
+ switch (PM_TYPE(pm->node.flags)) {
+ case PM_SCALAR:
+ if (pm->gsu.s->setfn)
+ pm->gsu.s->setfn(pm, NULL);
+ break;
+
+ case PM_ARRAY:
+ if (pm->gsu.a->setfn)
+ pm->gsu.a->setfn(pm, NULL);
+ break;
+
+ case PM_HASHED:
+ if (pm->gsu.h->setfn)
+ pm->gsu.h->setfn(pm, NULL);
+ break;
+
+ default:
+ if (!(pm->node.flags & PM_SPECIAL))
+ pm->u.str = NULL;
+ break;
+ }
+ if ((pm->node.flags & (PM_SPECIAL|PM_TIED)) == PM_TIED) {
+ if (pm->ename) {
+ zsfree(pm->ename);
+ pm->ename = NULL;
+ }
+ pm->node.flags &= ~PM_TIED;
+ }
+ pm->node.flags |= PM_UNSET;
+}
+
+/* Function to get value of an integer parameter */
+
+/**/
+mod_export zlong
+intgetfn(Param pm)
+{
+ return pm->u.val;
+}
+
+/* Function to set value of an integer parameter */
+
+/**/
+static void
+intsetfn(Param pm, zlong x)
+{
+ pm->u.val = x;
+}
+
+/* Function to get value of a floating point parameter */
+
+/**/
+static double
+floatgetfn(Param pm)
+{
+ return pm->u.dval;
+}
+
+/* Function to set value of an integer parameter */
+
+/**/
+static void
+floatsetfn(Param pm, double x)
+{
+ pm->u.dval = x;
+}
+
+/* Function to get value of a scalar (string) parameter */
+
+/**/
+mod_export char *
+strgetfn(Param pm)
+{
+ return pm->u.str ? pm->u.str : (char *) hcalloc(1);
+}
+
+/* Function to set value of a scalar (string) parameter */
+
+/**/
+mod_export void
+strsetfn(Param pm, char *x)
+{
+ zsfree(pm->u.str);
+ pm->u.str = x;
+ if (!(pm->node.flags & PM_HASHELEM) &&
+ ((pm->node.flags & PM_NAMEDDIR) || isset(AUTONAMEDIRS))) {
+ pm->node.flags |= PM_NAMEDDIR;
+ adduserdir(pm->node.nam, x, 0, 0);
+ }
+ /* If you update this function, you may need to update the
+ * `Implement remainder of strsetfn' block in assignstrvalue(). */
+}
+
+/* Function to get value of an array parameter */
+
+static char *nullarray = NULL;
+
+/**/
+char **
+arrgetfn(Param pm)
+{
+ return pm->u.arr ? pm->u.arr : &nullarray;
+}
+
+/* Function to set value of an array parameter */
+
+/**/
+mod_export void
+arrsetfn(Param pm, char **x)
+{
+ if (pm->u.arr && pm->u.arr != x)
+ freearray(pm->u.arr);
+ if (pm->node.flags & PM_UNIQUE)
+ uniqarray(x);
+ pm->u.arr = x;
+ /* Arrays tied to colon-arrays may need to fix the environment */
+ if (pm->ename && x)
+ arrfixenv(pm->ename, x);
+ /* If you extend this function, update the list of conditions in
+ * setarrvalue(). */
+}
+
+/* Function to get value of an association parameter */
+
+/**/
+mod_export HashTable
+hashgetfn(Param pm)
+{
+ return pm->u.hash;
+}
+
+/* Function to set value of an association parameter */
+
+/**/
+mod_export void
+hashsetfn(Param pm, HashTable x)
+{
+ if (pm->u.hash && pm->u.hash != x)
+ deleteparamtable(pm->u.hash);
+ pm->u.hash = x;
+}
+
+/* Function to dispose of setting of an unsettable hash */
+
+/**/
+mod_export void
+nullsethashfn(UNUSED(Param pm), HashTable x)
+{
+ deleteparamtable(x);
+}
+
+/* Function to set value of an association parameter using key/value pairs */
+
+/**/
+static void
+arrhashsetfn(Param pm, char **val, int flags)
+{
+ /* Best not to shortcut this by using the existing hash table, *
+ * since that could cause trouble for special hashes. This way, *
+ * it's up to pm->gsu.h->setfn() what to do. */
+ int alen = 0;
+ HashTable opmtab = paramtab, ht = 0;
+ char **aptr;
+ Value v = (Value) hcalloc(sizeof *v);
+ v->end = -1;
+
+ for (aptr = val; *aptr; ++aptr) {
+ if (**aptr != Marker)
+ ++alen;
+ }
+
+ if (alen % 2) {
+ freearray(val);
+ zerr("bad set of key/value pairs for associative array");
+ return;
+ }
+ if (flags & ASSPM_AUGMENT) {
+ ht = paramtab = pm->gsu.h->getfn(pm);
+ }
+ if (alen && (!(flags & ASSPM_AUGMENT) || !paramtab)) {
+ ht = paramtab = newparamtable(17, pm->node.nam);
+ }
+ for (aptr = val; *aptr; ) {
+ int eltflags = 0;
+ if (**aptr == Marker) {
+ /* Either all elements have Marker or none. Checked in caller. */
+ if ((*aptr)[1] == '+') {
+ /* Actually, assignstrvalue currently doesn't handle this... */
+ eltflags = ASSPM_AUGMENT;
+ /* ...so we'll use the trick from setsparam(). */
+ v->start = INT_MAX;
+ } else {
+ v->start = 0;
+ }
+ v->end = -1;
+ zsfree(*aptr++);
+ }
+ /* The parameter name is ztrdup'd... */
+ v->pm = createparam(*aptr, PM_SCALAR|PM_UNSET);
+ /*
+ * createparam() doesn't return anything if the parameter
+ * already existed.
+ */
+ if (!v->pm)
+ v->pm = (Param) paramtab->getnode(paramtab, *aptr);
+ zsfree(*aptr++);
+ /* ...but we can use the value without copying. */
+ assignstrvalue(v, *aptr++, eltflags);
+ }
+ paramtab = opmtab;
+ pm->gsu.h->setfn(pm, ht);
+ free(val); /* not freearray() */
+}
+
+/*
+ * These functions are used as the set function for special parameters that
+ * cannot be set by the user. The set is incomplete as the only such
+ * parameters are scalar and integer.
+ */
+
+/**/
+mod_export void
+nullstrsetfn(UNUSED(Param pm), char *x)
+{
+ zsfree(x);
+}
+
+/**/
+mod_export void
+nullintsetfn(UNUSED(Param pm), UNUSED(zlong x))
+{}
+
+/**/
+mod_export void
+nullunsetfn(UNUSED(Param pm), UNUSED(int exp))
+{}
+
+
+/* Function to get value of generic special integer *
+ * parameter. data is pointer to global variable *
+ * containing the integer value. */
+
+/**/
+mod_export zlong
+intvargetfn(Param pm)
+{
+ return *pm->u.valptr;
+}
+
+/* Function to set value of generic special integer *
+ * parameter. data is pointer to global variable *
+ * where the value is to be stored. */
+
+/**/
+mod_export void
+intvarsetfn(Param pm, zlong x)
+{
+ *pm->u.valptr = 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, zlong x)
+{
+ zlong *p = pm->u.valptr;
+
+ *p = x;
+ if (p == &zterm_lines || p == &zterm_columns)
+ adjustwinsize(2 + (p == &zterm_columns));
+}
+
+
+/* Implements gsu_integer.unsetfn for ZLE_RPROMPT_INDENT; see stdunsetfn() */
+
+static void
+rprompt_indent_unsetfn(Param pm, int exp)
+{
+ stdunsetfn(pm, exp);
+ rprompt_indent = 1; /* Keep this in sync with init_term() */
+}
+
+/* Function to set value of generic special scalar *
+ * parameter. data is pointer to a character pointer *
+ * representing the scalar (string). */
+
+/**/
+mod_export 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). */
+
+/**/
+mod_export 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). */
+
+/**/
+mod_export char **
+arrvargetfn(Param pm)
+{
+ char **arrptr = *((char ***)pm->u.data);
+
+ return arrptr ? arrptr : &nullarray;
+}
+
+/* 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. */
+
+/**/
+mod_export void
+arrvarsetfn(Param pm, char **x)
+{
+ char ***dptr = (char ***)pm->u.data;
+
+ if (*dptr != x)
+ freearray(*dptr);
+ if (pm->node.flags & PM_UNIQUE)
+ uniqarray(x);
+ /*
+ * Special tied arrays point to variables accessible in other
+ * ways which need to be set to NULL. We can't do this
+ * with user tied variables since we can leak memory.
+ */
+ if ((pm->node.flags & PM_SPECIAL) && !x)
+ *dptr = mkarray(NULL);
+ else
+ *dptr = x;
+ if (pm->ename) {
+ if (x)
+ arrfixenv(pm->ename, x);
+ else if (*dptr == path)
+ pathchecked = path;
+ }
+}
+
+/**/
+char *
+colonarrgetfn(Param pm)
+{
+ char ***dptr = (char ***)pm->u.data;
+ return *dptr ? zjoin(*dptr, ':', 1) : "";
+}
+
+/**/
+void
+colonarrsetfn(Param pm, char *x)
+{
+ char ***dptr = (char ***)pm->u.data;
+ /*
+ * We have to make sure this is never NULL, since that
+ * can cause problems.
+ */
+ if (*dptr)
+ freearray(*dptr);
+ if (x)
+ *dptr = colonsplit(x, pm->node.flags & PM_UNIQUE);
+ else
+ *dptr = mkarray(NULL);
+ arrfixenv(pm->node.nam, *dptr);
+ zsfree(x);
+}
+
+/**/
+char *
+tiedarrgetfn(Param pm)
+{
+ struct tieddata *dptr = (struct tieddata *)pm->u.data;
+ return *dptr->arrptr ? zjoin(*dptr->arrptr, STOUC(dptr->joinchar), 1) : "";
+}
+
+/**/
+void
+tiedarrsetfn(Param pm, char *x)
+{
+ struct tieddata *dptr = (struct tieddata *)pm->u.data;
+
+ if (*dptr->arrptr)
+ freearray(*dptr->arrptr);
+ if (x) {
+ char sepbuf[3];
+ if (imeta(dptr->joinchar))
+ {
+ sepbuf[0] = Meta;
+ sepbuf[1] = dptr->joinchar ^ 32;
+ sepbuf[2] = '\0';
+ }
+ else
+ {
+ sepbuf[0] = dptr->joinchar;
+ sepbuf[1] = '\0';
+ }
+ *dptr->arrptr = sepsplit(x, sepbuf, 0, 0);
+ if (pm->node.flags & PM_UNIQUE)
+ uniqarray(*dptr->arrptr);
+ zsfree(x);
+ } else
+ *dptr->arrptr = NULL;
+ if (pm->ename)
+ arrfixenv(pm->node.nam, *dptr->arrptr);
+}
+
+/**/
+void
+tiedarrunsetfn(Param pm, UNUSED(int exp))
+{
+ /*
+ * Special unset function because we allocated a struct tieddata
+ * in typeset_single to hold the special data which we now
+ * need to delete.
+ */
+ pm->gsu.s->setfn(pm, NULL);
+ zfree(pm->u.data, sizeof(struct tieddata));
+ /* paranoia -- shouldn't need these, but in case we reuse the struct... */
+ pm->u.data = NULL;
+ zsfree(pm->ename);
+ pm->ename = NULL;
+ pm->node.flags &= ~PM_TIED;
+ pm->node.flags |= PM_UNSET;
+}
+
+/**/
+static void
+simple_arrayuniq(char **x, int freeok)
+{
+ char **t, **p = x;
+ char *hole = "";
+
+ /* Find duplicates and replace them with holes */
+ while (*++p)
+ for (t = x; t < p; t++)
+ if (*t != hole && !strcmp(*p, *t)) {
+ if (freeok)
+ zsfree(*p);
+ *p = hole;
+ break;
+ }
+ /* Swap non-holes into holes in optimal jumps */
+ for (p = t = x; *t != NULL; t++) {
+ if (*t == hole) {
+ while (*p == hole)
+ ++p;
+ if ((*t = *p) != NULL)
+ *p++ = hole;
+ } else if (p == t)
+ p++;
+ }
+ /* Erase all the remaining holes, just in case */
+ while (++t < p)
+ *t = NULL;
+}
+
+/**/
+static void
+arrayuniq_freenode(HashNode hn)
+{
+ (void)hn;
+}
+
+/**/
+HashTable
+newuniqtable(zlong size)
+{
+ HashTable ht = newhashtable((int)size, "arrayuniq", NULL);
+ /* ??? error checking */
+
+ ht->hash = hasher;
+ ht->emptytable = emptyhashtable;
+ ht->filltable = NULL;
+ ht->cmpnodes = strcmp;
+ ht->addnode = addhashnode;
+ ht->getnode = gethashnode2;
+ ht->getnode2 = gethashnode2;
+ ht->removenode = removehashnode;
+ ht->disablenode = disablehashnode;
+ ht->enablenode = enablehashnode;
+ ht->freenode = arrayuniq_freenode;
+ ht->printnode = NULL;
+
+ return ht;
+}
+
+/**/
+static void
+arrayuniq(char **x, int freeok)
+{
+ char **it, **write_it;
+ zlong array_size = arrlen(x);
+ HashTable ht;
+
+ if (array_size == 0)
+ return;
+ if (array_size < 10 || !(ht = newuniqtable(array_size + 1))) {
+ /* fallback to simpler routine */
+ simple_arrayuniq(x, freeok);
+ return;
+ }
+
+ for (it = x, write_it = x; *it;) {
+ if (! gethashnode2(ht, *it)) {
+ HashNode new_node = zhalloc(sizeof(struct hashnode));
+ if (!new_node) {
+ /* Oops, out of heap memory, no way to recover */
+ zerr("out of memory in arrayuniq");
+ break;
+ }
+ (void) addhashnode2(ht, *it, new_node);
+ *write_it = *it;
+ if (it != write_it)
+ *it = NULL;
+ ++write_it;
+ }
+ else {
+ if (freeok)
+ zsfree(*it);
+ *it = NULL;
+ }
+ ++it;
+ }
+
+ deletehashtable(ht);
+}
+
+/**/
+void
+uniqarray(char **x)
+{
+ if (!x || !*x)
+ return;
+ arrayuniq(x, !zheapptr(*x));
+}
+
+/**/
+void
+zhuniqarray(char **x)
+{
+ if (!x || !*x)
+ return;
+ arrayuniq(x, 0);
+}
+
+/* Function to get value of special parameter `#' and `ARGC' */
+
+/**/
+zlong
+poundgetfn(UNUSED(Param pm))
+{
+ return arrlen(pparams);
+}
+
+/* Function to get value for special parameter `RANDOM' */
+
+/**/
+zlong
+randomgetfn(UNUSED(Param pm))
+{
+ return rand() & 0x7fff;
+}
+
+/* Function to set value of special parameter `RANDOM' */
+
+/**/
+void
+randomsetfn(UNUSED(Param pm), zlong v)
+{
+ srand((unsigned int)v);
+}
+
+/* Function to get value for special parameter `SECONDS' */
+
+/**/
+zlong
+intsecondsgetfn(UNUSED(Param pm))
+{
+ struct timeval now;
+ struct timezone dummy_tz;
+
+ gettimeofday(&now, &dummy_tz);
+
+ return (zlong)(now.tv_sec - shtimer.tv_sec -
+ (now.tv_usec < shtimer.tv_usec ? 1 : 0));
+}
+
+/* Function to set value of special parameter `SECONDS' */
+
+/**/
+void
+intsecondssetfn(UNUSED(Param pm), zlong x)
+{
+ struct timeval now;
+ struct timezone dummy_tz;
+ zlong diff;
+
+ gettimeofday(&now, &dummy_tz);
+ diff = (zlong)now.tv_sec - x;
+ shtimer.tv_sec = diff;
+ if ((zlong)shtimer.tv_sec != diff)
+ zwarn("SECONDS truncated on assignment");
+ shtimer.tv_usec = now.tv_usec;
+}
+
+/**/
+double
+floatsecondsgetfn(UNUSED(Param pm))
+{
+ struct timeval now;
+ struct timezone dummy_tz;
+
+ gettimeofday(&now, &dummy_tz);
+
+ return (double)(now.tv_sec - shtimer.tv_sec) +
+ (double)(now.tv_usec - shtimer.tv_usec) / 1000000.0;
+}
+
+/**/
+void
+floatsecondssetfn(UNUSED(Param pm), double x)
+{
+ struct timeval now;
+ struct timezone dummy_tz;
+
+ gettimeofday(&now, &dummy_tz);
+ shtimer.tv_sec = now.tv_sec - (zlong)x;
+ shtimer.tv_usec = now.tv_usec - (zlong)((x - (zlong)x) * 1000000.0);
+}
+
+/**/
+double
+getrawseconds(void)
+{
+ return (double)shtimer.tv_sec + (double)shtimer.tv_usec / 1000000.0;
+}
+
+/**/
+void
+setrawseconds(double x)
+{
+ shtimer.tv_sec = (zlong)x;
+ shtimer.tv_usec = (zlong)((x - (zlong)x) * 1000000.0);
+}
+
+/**/
+int
+setsecondstype(Param pm, int on, int off)
+{
+ int newflags = (pm->node.flags | on) & ~off;
+ int tp = PM_TYPE(newflags);
+ /* Only one of the numeric types is allowed. */
+ if (tp == PM_EFLOAT || tp == PM_FFLOAT)
+ {
+ pm->gsu.f = &floatseconds_gsu;
+ }
+ else if (tp == PM_INTEGER)
+ {
+ pm->gsu.i = &intseconds_gsu;
+ }
+ else
+ return 1;
+ pm->node.flags = newflags;
+ return 0;
+}
+
+/* Function to get value for special parameter `USERNAME' */
+
+/**/
+char *
+usernamegetfn(UNUSED(Param pm))
+{
+ return get_username();
+}
+
+/* Function to set value of special parameter `USERNAME' */
+
+/**/
+void
+usernamesetfn(UNUSED(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 USE_INITGROUPS
+ initgroups(x, pswd->pw_gid);
+# endif
+ if (setgid(pswd->pw_gid))
+ zwarn("failed to change group ID: %e", errno);
+ else if (setuid(pswd->pw_uid))
+ zwarn("failed to change user ID: %e", errno);
+ else {
+ zsfree(cached_username);
+ cached_username = ztrdup(pswd->pw_name);
+ cached_uid = pswd->pw_uid;
+ }
+ }
+#endif /* HAVE_SETUID && HAVE_GETPWNAM */
+ zsfree(x);
+}
+
+/* Function to get value for special parameter `UID' */
+
+/**/
+zlong
+uidgetfn(UNUSED(Param pm))
+{
+ return getuid();
+}
+
+/* Function to set value of special parameter `UID' */
+
+/**/
+void
+uidsetfn(UNUSED(Param pm), zlong x)
+{
+#ifdef HAVE_SETUID
+ if (setuid((uid_t)x))
+ zerr("failed to change user ID: %e", errno);
+#endif
+}
+
+/* Function to get value for special parameter `EUID' */
+
+/**/
+zlong
+euidgetfn(UNUSED(Param pm))
+{
+ return geteuid();
+}
+
+/* Function to set value of special parameter `EUID' */
+
+/**/
+void
+euidsetfn(UNUSED(Param pm), zlong x)
+{
+#ifdef HAVE_SETEUID
+ if (seteuid((uid_t)x))
+ zerr("failed to change effective user ID: %e", errno);
+#endif
+}
+
+/* Function to get value for special parameter `GID' */
+
+/**/
+zlong
+gidgetfn(UNUSED(Param pm))
+{
+ return getgid();
+}
+
+/* Function to set value of special parameter `GID' */
+
+/**/
+void
+gidsetfn(UNUSED(Param pm), zlong x)
+{
+#ifdef HAVE_SETUID
+ if (setgid((gid_t)x))
+ zerr("failed to change group ID: %e", errno);
+#endif
+}
+
+/* Function to get value for special parameter `EGID' */
+
+/**/
+zlong
+egidgetfn(UNUSED(Param pm))
+{
+ return getegid();
+}
+
+/* Function to set value of special parameter `EGID' */
+
+/**/
+void
+egidsetfn(UNUSED(Param pm), zlong x)
+{
+#ifdef HAVE_SETEUID
+ if (setegid((gid_t)x))
+ zerr("failed to change effective group ID: %e", errno);
+#endif
+}
+
+/**/
+zlong
+ttyidlegetfn(UNUSED(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(UNUSED(Param pm))
+{
+ return ifs;
+}
+
+/* Function to set value of special parameter `IFS' */
+
+/**/
+void
+ifssetfn(UNUSED(Param pm), char *x)
+{
+ zsfree(ifs);
+ ifs = x;
+ inittyptab();
+}
+
+/* Functions to set value of special parameters `LANG' and `LC_*' */
+
+#ifdef USE_LOCALE
+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_NUMERIC
+ {"LC_NUMERIC", LC_NUMERIC},
+#endif
+#ifdef LC_TIME
+ {"LC_TIME", LC_TIME},
+#endif
+ {NULL, 0}
+};
+
+/**/
+static void
+setlang(char *x)
+{
+ struct localename *ln;
+ char *x2;
+
+ if ((x2 = getsparam_u("LC_ALL")) && *x2)
+ return;
+
+ /*
+ * Set the global locale to the value passed, but override
+ * this with any non-empty definitions for specific
+ * categories.
+ *
+ * We only use non-empty definitions because empty values aren't
+ * valid as locales; when passed to setlocale() they mean "use the
+ * environment variable", but if that's what we're setting the value
+ * from this is meaningless. So just all $LANG to show through in
+ * that case.
+ */
+ setlocale(LC_ALL, x ? unmeta(x) : "");
+ queue_signals();
+ for (ln = lc_names; ln->name; ln++)
+ if ((x = getsparam_u(ln->name)) && *x)
+ setlocale(ln->category, x);
+ unqueue_signals();
+}
+
+/**/
+void
+lc_allsetfn(Param pm, char *x)
+{
+ strsetfn(pm, x);
+ /*
+ * Treat an empty LC_ALL the same as an unset one,
+ * namely by using LANG as the default locale but overriding
+ * that with any LC_* that are set.
+ */
+ if (!x || !*x) {
+ x = getsparam_u("LANG");
+ if (x && *x) {
+ queue_signals();
+ setlang(x);
+ unqueue_signals();
+ }
+ }
+ else
+ setlocale(LC_ALL, unmeta(x));
+}
+
+/**/
+void
+langsetfn(Param pm, char *x)
+{
+ strsetfn(pm, x);
+ setlang(unmeta(x));
+}
+
+/**/
+void
+lcsetfn(Param pm, char *x)
+{
+ char *x2;
+ struct localename *ln;
+
+ strsetfn(pm, x);
+ if ((x2 = getsparam("LC_ALL")) && *x2)
+ return;
+ queue_signals();
+ /* Treat empty LC_* the same as unset. */
+ if (!x || !*x)
+ x = getsparam("LANG");
+
+ /*
+ * If we've got no non-empty string at this
+ * point (after checking $LANG, too),
+ * we shouldn't bother setting anything.
+ */
+ if (x && *x) {
+ for (ln = lc_names; ln->name; ln++)
+ if (!strcmp(ln->name, pm->node.nam))
+ setlocale(ln->category, unmeta(x));
+ }
+ unqueue_signals();
+}
+#endif /* USE_LOCALE */
+
+/* Function to set value for special parameter `0' */
+
+/**/
+static void
+argzerosetfn(UNUSED(Param pm), char *x)
+{
+ if (x) {
+ if (isset(POSIXARGZERO))
+ zerr("read-only variable: 0");
+ else {
+ zsfree(argzero);
+ argzero = ztrdup(x);
+ }
+ zsfree(x);
+ }
+}
+
+/* Function to get value for special parameter `0' */
+
+/**/
+static char *
+argzerogetfn(UNUSED(Param pm))
+{
+ if (isset(POSIXARGZERO))
+ return posixzero;
+ return argzero;
+}
+
+/* Function to get value for special parameter `HISTSIZE' */
+
+/**/
+zlong
+histsizegetfn(UNUSED(Param pm))
+{
+ return histsiz;
+}
+
+/* Function to set value of special parameter `HISTSIZE' */
+
+/**/
+void
+histsizesetfn(UNUSED(Param pm), zlong v)
+{
+ if ((histsiz = v) < 1)
+ histsiz = 1;
+ resizehistents();
+}
+
+/* Function to get value for special parameter `SAVEHIST' */
+
+/**/
+zlong
+savehistsizegetfn(UNUSED(Param pm))
+{
+ return savehistsiz;
+}
+
+/* Function to set value of special parameter `SAVEHIST' */
+
+/**/
+void
+savehistsizesetfn(UNUSED(Param pm), zlong v)
+{
+ if ((savehistsiz = v) < 0)
+ savehistsiz = 0;
+}
+
+/* Function to set value for special parameter `ERRNO' */
+
+/**/
+void
+errnosetfn(UNUSED(Param pm), zlong x)
+{
+ errno = (int)x;
+ if ((zlong)errno != x)
+ zwarn("errno truncated on assignment");
+}
+
+/* Function to get value for special parameter `ERRNO' */
+
+/**/
+zlong
+errnogetfn(UNUSED(Param pm))
+{
+ return errno;
+}
+
+/* Function to get value for special parameter `KEYBOARD_HACK' */
+
+/**/
+char *
+keyboardhackgetfn(UNUSED(Param pm))
+{
+ static char buf[2];
+
+ buf[0] = keyboardhackchar;
+ buf[1] = '\0';
+ return buf;
+}
+
+
+/* Function to set value of special parameter `KEYBOARD_HACK' */
+
+/**/
+void
+keyboardhacksetfn(UNUSED(Param pm), char *x)
+{
+ if (x) {
+ int len, i;
+
+ unmetafy(x, &len);
+ if (len > 1) {
+ len = 1;
+ zwarn("Only one KEYBOARD_HACK character can be defined"); /* could be changed if needed */
+ }
+ for (i = 0; i < len; i++) {
+ if (!isascii(STOUC(x[i]))) {
+ zwarn("KEYBOARD_HACK can only contain ASCII characters");
+ return;
+ }
+ }
+ keyboardhackchar = len ? STOUC(x[0]) : '\0';
+ free(x);
+ } else
+ keyboardhackchar = '\0';
+}
+
+/* Function to get value for special parameter `histchar' */
+
+/**/
+char *
+histcharsgetfn(UNUSED(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(UNUSED(Param pm), char *x)
+{
+ if (x) {
+ int len, i;
+
+ unmetafy(x, &len);
+ if (len > 3)
+ len = 3;
+ for (i = 0; i < len; i++) {
+ if (!isascii(STOUC(x[i]))) {
+ zwarn("HISTCHARS can only contain ASCII characters");
+ return;
+ }
+ }
+ bangchar = len ? STOUC(x[0]) : '\0';
+ hatchar = len > 1 ? STOUC(x[1]) : '\0';
+ hashchar = len > 2 ? STOUC(x[2]) : '\0';
+ free(x);
+ } else {
+ bangchar = '!';
+ hashchar = '#';
+ hatchar = '^';
+ }
+ inittyptab();
+}
+
+/* Function to get value for special parameter `HOME' */
+
+/**/
+char *
+homegetfn(UNUSED(Param pm))
+{
+ return home;
+}
+
+/* Function to set value of special parameter `HOME' */
+
+/**/
+void
+homesetfn(UNUSED(Param pm), char *x)
+{
+ zsfree(home);
+ if (x && isset(CHASELINKS) && (home = xsymlink(x, 0)))
+ zsfree(x);
+ else
+ home = x ? x : ztrdup("");
+ finddir(NULL);
+}
+
+/* Function to get value for special parameter `WORDCHARS' */
+
+/**/
+char *
+wordcharsgetfn(UNUSED(Param pm))
+{
+ return wordchars;
+}
+
+/* Function to set value of special parameter `WORDCHARS' */
+
+/**/
+void
+wordcharssetfn(UNUSED(Param pm), char *x)
+{
+ zsfree(wordchars);
+ wordchars = x;
+ inittyptab();
+}
+
+/* Function to get value for special parameter `_' */
+
+/**/
+char *
+underscoregetfn(UNUSED(Param pm))
+{
+ char *u = dupstring(zunderscore);
+
+ untokenize(u);
+ return u;
+}
+
+/* Function used when we need to reinitialise the terminal */
+
+static void
+term_reinit_from_pm(void)
+{
+ /* If non-interactive, delay setting up term till we need it. */
+ if (unset(INTERACTIVE) || !*term)
+ termflags |= TERM_UNKNOWN;
+ else
+ init_term();
+}
+
+/* Function to get value for special parameter `TERM' */
+
+/**/
+char *
+termgetfn(UNUSED(Param pm))
+{
+ return term;
+}
+
+/* Function to set value of special parameter `TERM' */
+
+/**/
+void
+termsetfn(UNUSED(Param pm), char *x)
+{
+ zsfree(term);
+ term = x ? x : ztrdup("");
+ term_reinit_from_pm();
+}
+
+/* Function to get value of special parameter `TERMINFO' */
+
+/**/
+char *
+terminfogetfn(UNUSED(Param pm))
+{
+ return zsh_terminfo ? zsh_terminfo : dupstring("");
+}
+
+/* Function to set value of special parameter `TERMINFO' */
+
+/**/
+void
+terminfosetfn(Param pm, char *x)
+{
+ zsfree(zsh_terminfo);
+ zsh_terminfo = x;
+
+ /*
+ * terminfo relies on the value being exported before
+ * we reinitialise the terminal. This is a bit inefficient.
+ */
+ if ((pm->node.flags & PM_EXPORTED) && x)
+ addenv(pm, x);
+
+ term_reinit_from_pm();
+}
+
+/* Function to get value of special parameter `TERMINFO_DIRS' */
+
+/**/
+char *
+terminfodirsgetfn(UNUSED(Param pm))
+{
+ return zsh_terminfodirs ? zsh_terminfodirs : dupstring("");
+}
+
+/* Function to set value of special parameter `TERMINFO_DIRS' */
+
+/**/
+void
+terminfodirssetfn(Param pm, char *x)
+{
+ zsfree(zsh_terminfodirs);
+ zsh_terminfodirs = x;
+
+ /*
+ * terminfo relies on the value being exported before
+ * we reinitialise the terminal. This is a bit inefficient.
+ */
+ if ((pm->node.flags & PM_EXPORTED) && x)
+ addenv(pm, x);
+
+ term_reinit_from_pm();
+}
+/* Function to get value for special parameter `pipestatus' */
+
+/**/
+static char **
+pipestatgetfn(UNUSED(Param pm))
+{
+ char **x = (char **) zhalloc((numpipestats + 1) * sizeof(char *));
+ char buf[DIGBUFSIZE], **p;
+ int *q, i;
+
+ for (p = x, q = pipestats, i = numpipestats; i--; p++, q++) {
+ sprintf(buf, "%d", *q);
+ *p = dupstring(buf);
+ }
+ *p = NULL;
+
+ return x;
+}
+
+/* Function to get value for special parameter `pipestatus' */
+
+/**/
+static void
+pipestatsetfn(UNUSED(Param pm), char **x)
+{
+ if (x) {
+ int i;
+
+ for (i = 0; *x && i < MAX_PIPESTATS; i++, x++)
+ pipestats[i] = atoi(*x);
+ numpipestats = i;
+ }
+ else
+ numpipestats = 0;
+}
+
+/**/
+void
+arrfixenv(char *s, char **t)
+{
+ Param pm;
+ int joinchar;
+
+ if (t == path)
+ cmdnamtab->emptytable(cmdnamtab);
+
+ pm = (Param) paramtab->getnode(paramtab, s);
+
+ /*
+ * Only one level of a parameter can be exported. Unless
+ * ALLEXPORT is set, this must be global.
+ */
+
+ if (pm->node.flags & PM_HASHELEM)
+ return;
+
+ if (isset(ALLEXPORT))
+ pm->node.flags |= PM_EXPORTED;
+
+ /*
+ * Do not "fix" parameters that were not exported
+ */
+
+ if (!(pm->node.flags & PM_EXPORTED))
+ return;
+
+ if (pm->node.flags & PM_TIED)
+ joinchar = STOUC(((struct tieddata *)pm->u.data)->joinchar);
+ else
+ joinchar = ':';
+
+ addenv(pm, t ? zjoin(t, joinchar, 1) : "");
+}
+
+
+/**/
+int
+zputenv(char *str)
+{
+ DPUTS(!str, "Attempt to put null string into environment.");
+#ifdef USE_SET_UNSET_ENV
+ /*
+ * If we are using unsetenv() to remove values from the
+ * environment, which is the safe thing to do, we
+ * need to use setenv() to put them there in the first place.
+ * Unfortunately this is a slightly different interface
+ * from what zputenv() assumes.
+ */
+ char *ptr;
+ int ret;
+
+ for (ptr = str; *ptr && STOUC(*ptr) < 128 && *ptr != '='; ptr++)
+ ;
+ if (STOUC(*ptr) >= 128) {
+ /*
+ * Environment variables not in the portable character
+ * set are non-standard and we don't really know of
+ * a use for them.
+ *
+ * We'll disable until someone complains.
+ */
+ return 1;
+ } else if (*ptr) {
+ *ptr = '\0';
+ ret = setenv(str, ptr+1, 1);
+ *ptr = '=';
+ } else {
+ /* safety first */
+ DPUTS(1, "bad environment string");
+ ret = setenv(str, ptr, 1);
+ }
+ return ret;
+#else
+#ifdef HAVE_PUTENV
+ return putenv(str);
+#else
+ char **ep;
+ int num_env;
+
+
+ /* First check if there is already an environment *
+ * variable matching string `name'. */
+ if (findenv(str, &num_env)) {
+ environ[num_env] = str;
+ } else {
+ /* 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 = str;
+ *(ep + 1) = NULL;
+ }
+ return 0;
+#endif
+#endif
+}
+
+/**/
+#ifndef USE_SET_UNSET_ENV
+/**/
+static int
+findenv(char *name, int *pos)
+{
+ char **ep, *eq;
+ int nlen;
+
+
+ eq = strchr(name, '=');
+ nlen = eq ? eq - name : (int)strlen(name);
+ for (ep = environ; *ep; ep++)
+ if (!strncmp (*ep, name, nlen) && *((*ep)+nlen) == '=') {
+ if (pos)
+ *pos = ep - environ;
+ return 1;
+ }
+
+ return 0;
+}
+/**/
+#endif
+
+/* Given *name = "foo", it searches the environment for string *
+ * "foo=bar", and returns a pointer to the beginning of "bar" */
+
+/**/
+mod_export char *
+zgetenv(char *name)
+{
+#ifdef HAVE_GETENV
+ return getenv(name);
+#else
+ 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;
+#endif
+}
+
+/**/
+static void
+copyenvstr(char *s, char *value, int flags)
+{
+ while (*s++) {
+ if ((*s = *value++) == Meta)
+ *s = *value++ ^ 32;
+ if (flags & PM_LOWER)
+ *s = tulower(*s);
+ else if (flags & PM_UPPER)
+ *s = tuupper(*s);
+ }
+}
+
+/**/
+void
+addenv(Param pm, char *value)
+{
+ char *newenv = 0;
+#ifndef USE_SET_UNSET_ENV
+ char *oldenv = 0, *env = 0;
+ int pos;
+
+ /*
+ * First check if there is already an environment
+ * variable matching string `name'.
+ */
+ if (findenv(pm->node.nam, &pos))
+ oldenv = environ[pos];
+#endif
+
+ newenv = mkenvstr(pm->node.nam, value, pm->node.flags);
+ if (zputenv(newenv)) {
+ zsfree(newenv);
+ pm->env = NULL;
+ return;
+ }
+#ifdef USE_SET_UNSET_ENV
+ /*
+ * If we are using setenv/unsetenv to manage the environment,
+ * we simply store the string we created in pm->env since
+ * memory management of the environment is handled entirely
+ * by the system.
+ *
+ * TODO: is this good enough to fix problem cases from
+ * the other branch? If so, we don't actually need to
+ * store pm->env at all, just a flag that the value was set.
+ */
+ if (pm->env)
+ zsfree(pm->env);
+ pm->env = newenv;
+ pm->node.flags |= PM_EXPORTED;
+#else
+ /*
+ * Under Cygwin we must use putenv() to maintain consistency.
+ * Unfortunately, current version (1.1.2) copies argument and may
+ * silently reuse existing environment string. This tries to
+ * check for both cases
+ */
+ if (findenv(pm->node.nam, &pos)) {
+ env = environ[pos];
+ if (env != oldenv)
+ zsfree(oldenv);
+ if (env != newenv)
+ zsfree(newenv);
+ pm->node.flags |= PM_EXPORTED;
+ pm->env = env;
+ return;
+ }
+
+ DPUTS(1, "addenv should never reach the end");
+ pm->env = NULL;
+#endif
+}
+
+
+/* Given strings *name = "foo", *value = "bar", *
+ * return a new string *str = "foo=bar". */
+
+/**/
+static char *
+mkenvstr(char *name, char *value, int flags)
+{
+ char *str, *s = value;
+ int len_name, len_value = 0;
+
+ len_name = strlen(name);
+ if (s)
+ while (*s && (*s++ != Meta || *s++ != 32))
+ len_value++;
+ s = str = (char *) zalloc(len_name + len_value + 2);
+ strcpy(s, name);
+ s += len_name;
+ *s = '=';
+ if (value)
+ copyenvstr(s, value, flags);
+ else
+ *++s = '\0';
+ 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. */
+
+
+#ifndef USE_SET_UNSET_ENV
+/**/
+void
+delenvvalue(char *x)
+{
+ char **ep;
+
+ for (ep = environ; *ep; ep++) {
+ if (*ep == x)
+ break;
+ }
+ if (*ep) {
+ for (; (ep[0] = ep[1]); ep++);
+ }
+ zsfree(x);
+}
+#endif
+
+
+/* Delete a pointer from the list of pointers to environment *
+ * variables by shifting all the other pointers up one slot. */
+
+/**/
+void
+delenv(Param pm)
+{
+#ifdef USE_SET_UNSET_ENV
+ unsetenv(pm->node.nam);
+ zsfree(pm->env);
+#else
+ delenvvalue(pm->env);
+#endif
+ pm->env = NULL;
+ /*
+ * Note we don't remove PM_EXPORT from the flags. This
+ * may be asking for trouble but we need to know later
+ * if we restore this parameter to its old value.
+ */
+}
+
+/*
+ * Guts of convbase: this version can return the number of digits
+ * sans any base discriminator.
+ */
+
+/**/
+void
+convbase_ptr(char *s, zlong v, int base, int *ndigits)
+{
+ int digs = 0;
+ zulong x;
+
+ if (v < 0)
+ *s++ = '-', v = -v;
+ if (base >= -1 && base <= 1)
+ base = -10;
+
+ if (base > 0) {
+ if (isset(CBASES) && base == 16)
+ sprintf(s, "0x");
+ else if (isset(CBASES) && base == 8 && isset(OCTALZEROES))
+ sprintf(s, "0");
+ else if (base != 10)
+ sprintf(s, "%d#", base);
+ else
+ *s = 0;
+ s += strlen(s);
+ } else
+ base = -base;
+ for (x = v; x; digs++)
+ x /= base;
+ if (!digs)
+ digs = 1;
+ if (ndigits)
+ *ndigits = digs;
+ s[digs--] = '\0';
+ x = v;
+ while (digs >= 0) {
+ int dig = x % base;
+
+ s[digs--] = (dig < 10) ? '0' + dig : dig - 10 + 'A';
+ x /= base;
+ }
+}
+
+/*
+ * Basic conversion of integer to a string given a base.
+ * If 0 base is 10.
+ * If negative no base discriminator is output.
+ */
+
+/**/
+mod_export void
+convbase(char *s, zlong v, int base)
+{
+ convbase_ptr(s, v, base, NULL);
+}
+
+/*
+ * Add underscores to converted integer for readability with given spacing.
+ * s is as for convbase: at least BDIGBUFSIZE.
+ * If underscores were added, returned value with underscores comes from
+ * heap, else the returned value is s.
+ */
+
+/**/
+char *
+convbase_underscore(char *s, zlong v, int base, int underscore)
+{
+ char *retptr, *sptr, *dptr;
+ int ndigits, nunderscore, mod, len;
+
+ convbase_ptr(s, v, base, &ndigits);
+
+ if (underscore <= 0)
+ return s;
+
+ nunderscore = (ndigits - 1) / underscore;
+ if (!nunderscore)
+ return s;
+ len = strlen(s);
+ retptr = zhalloc(len + nunderscore + 1);
+ mod = 0;
+ memcpy(retptr, s, len - ndigits);
+ sptr = s + len;
+ dptr = retptr + len + nunderscore;
+ /* copy the null */
+ *dptr-- = *sptr--;
+ for (;;) {
+ *dptr = *sptr;
+ if (!--ndigits)
+ break;
+ dptr--;
+ sptr--;
+ if (++mod == underscore) {
+ mod = 0;
+ *dptr-- = '_';
+ }
+ }
+
+ return retptr;
+}
+
+/*
+ * Convert a floating point value for output.
+ * Unlike convbase(), this has its own internal storage and returns
+ * a value from the heap.
+ */
+
+/**/
+char *
+convfloat(double dval, int digits, int flags, FILE *fout)
+{
+ char fmt[] = "%.*e";
+ char *prev_locale, *ret;
+
+ /*
+ * The difficulty with the buffer size is that a %f conversion
+ * prints all digits before the decimal point: with 64 bit doubles,
+ * that's around 310. We can't check without doing some quite
+ * serious floating point operations we'd like to avoid.
+ * Then we are liable to get all the digits
+ * we asked for after the decimal point, or we should at least
+ * bargain for it. So we just allocate 512 + digits. This
+ * should work until somebody decides on 128-bit doubles.
+ */
+ if (!(flags & (PM_EFLOAT|PM_FFLOAT))) {
+ /*
+ * Conversion from a floating point expression without using
+ * a variable. The best bet in this case just seems to be
+ * to use the general %g format with something like the maximum
+ * double precision.
+ */
+ fmt[3] = 'g';
+ if (!digits)
+ digits = 17;
+ } else {
+ if (flags & PM_FFLOAT)
+ fmt[3] = 'f';
+ if (digits <= 0)
+ digits = 10;
+ if (flags & PM_EFLOAT) {
+ /*
+ * Here, we are given the number of significant figures, but
+ * %e wants the number of decimal places (unlike %g)
+ */
+ digits--;
+ }
+ }
+#ifdef USE_LOCALE
+ prev_locale = dupstring(setlocale(LC_NUMERIC, NULL));
+ setlocale(LC_NUMERIC, "POSIX");
+#endif
+ if (fout) {
+ fprintf(fout, fmt, digits, dval);
+ ret = NULL;
+ } else {
+ VARARR(char, buf, 512 + digits);
+ if (isinf(dval))
+ ret = dupstring((dval < 0.0) ? "-Inf" : "Inf");
+ else if (isnan(dval))
+ ret = dupstring("NaN");
+ else {
+ sprintf(buf, fmt, digits, dval);
+ if (!strchr(buf, 'e') && !strchr(buf, '.'))
+ strcat(buf, ".");
+ ret = dupstring(buf);
+ }
+ }
+#ifdef USE_LOCALE
+ if (prev_locale) setlocale(LC_NUMERIC, prev_locale);
+#endif
+ return ret;
+}
+
+/*
+ * convert float to string with basic options but inserting underscores
+ * for readability.
+ */
+
+/**/
+char *convfloat_underscore(double dval, int underscore)
+{
+ int ndigits_int = 0, ndigits_frac = 0, nunderscore, len;
+ char *s, *retptr, *sptr, *dptr;
+
+ s = convfloat(dval, 0, 0, NULL);
+ if (underscore <= 0)
+ return s;
+
+ /*
+ * Count the number of digits before and after the decimal point, if any.
+ */
+ sptr = s;
+ if (*sptr == '-')
+ sptr++;
+ while (idigit(*sptr)) {
+ ndigits_int++;
+ sptr++;
+ }
+ if (*sptr == '.') {
+ sptr++;
+ while (idigit(*sptr)) {
+ ndigits_frac++;
+ sptr++;
+ }
+ }
+
+ /*
+ * Work out how many underscores to insert --- remember we
+ * put them in integer and fractional parts separately.
+ */
+ nunderscore = (ndigits_int-1) / underscore + (ndigits_frac-1) / underscore;
+ if (!nunderscore)
+ return s;
+ len = strlen(s);
+ dptr = retptr = zhalloc(len + nunderscore + 1);
+
+ /*
+ * Insert underscores in integer part.
+ * Grouping starts from the point in both directions.
+ */
+ sptr = s;
+ if (*sptr == '-')
+ *dptr++ = *sptr++;
+ while (ndigits_int) {
+ *dptr++ = *sptr++;
+ if (--ndigits_int && !(ndigits_int % underscore))
+ *dptr++ = '_';
+ }
+ if (ndigits_frac) {
+ /*
+ * Insert underscores in the fractional part.
+ */
+ int mod = 0;
+ /* decimal point, we already checked */
+ *dptr++ = *sptr++;
+ while (ndigits_frac) {
+ *dptr++ = *sptr++;
+ mod++;
+ if (--ndigits_frac && mod == underscore) {
+ *dptr++ = '_';
+ mod = 0;
+ }
+ }
+ }
+ /* Copy exponent and anything else up to null */
+ while ((*dptr++ = *sptr++))
+ ;
+ return retptr;
+}
+
+/* Start a parameter scope */
+
+/**/
+mod_export void
+startparamscope(void)
+{
+ locallevel++;
+}
+
+/* End a parameter scope: delete the parameters local to the scope. */
+
+/**/
+mod_export void
+endparamscope(void)
+{
+ queue_signals();
+ locallevel--;
+ /* This pops anything from a higher locallevel */
+ saveandpophiststack(0, HFILE_USE_OPTIONS);
+ scanhashtable(paramtab, 0, 0, 0, scanendscope, 0);
+ unqueue_signals();
+}
+
+/**/
+static void
+scanendscope(HashNode hn, UNUSED(int flags))
+{
+ Param pm = (Param)hn;
+ if (pm->level > locallevel) {
+ if ((pm->node.flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL) {
+ /*
+ * Removable specials are normal in that they can be removed
+ * to reveal an ordinary parameter beneath. Here we handle
+ * non-removable specials, which were made local by stealth
+ * (see newspecial code in typeset_single()). In fact the
+ * visible pm is always the same struct; the pm->old is
+ * just a place holder for old data and flags.
+ */
+ Param tpm = pm->old;
+
+ if (!strcmp(pm->node.nam, "SECONDS"))
+ {
+ setsecondstype(pm, PM_TYPE(tpm->node.flags), PM_TYPE(pm->node.flags));
+ /*
+ * We restore SECONDS by restoring its raw internal value
+ * that we cached off into tpm->u.dval.
+ */
+ setrawseconds(tpm->u.dval);
+ tpm->node.flags |= PM_NORESTORE;
+ }
+ DPUTS(!tpm || PM_TYPE(pm->node.flags) != PM_TYPE(tpm->node.flags) ||
+ !(tpm->node.flags & PM_SPECIAL),
+ "BUG: in restoring scope of special parameter");
+ pm->old = tpm->old;
+ pm->node.flags = (tpm->node.flags & ~PM_NORESTORE);
+ pm->level = tpm->level;
+ pm->base = tpm->base;
+ pm->width = tpm->width;
+ if (pm->env)
+ delenv(pm);
+
+ if (!(tpm->node.flags & (PM_NORESTORE|PM_READONLY)))
+ switch (PM_TYPE(pm->node.flags)) {
+ case PM_SCALAR:
+ pm->gsu.s->setfn(pm, tpm->u.str);
+ break;
+ case PM_INTEGER:
+ pm->gsu.i->setfn(pm, tpm->u.val);
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ pm->gsu.f->setfn(pm, tpm->u.dval);
+ break;
+ case PM_ARRAY:
+ pm->gsu.a->setfn(pm, tpm->u.arr);
+ break;
+ case PM_HASHED:
+ pm->gsu.h->setfn(pm, tpm->u.hash);
+ break;
+ }
+ zfree(tpm, sizeof(*tpm));
+
+ if (pm->node.flags & PM_EXPORTED)
+ export_param(pm);
+ } else
+ unsetparam_pm(pm, 0, 0);
+ }
+}
+
+
+/**********************************/
+/* Parameter Hash Table Functions */
+/**********************************/
+
+/**/
+void
+freeparamnode(HashNode hn)
+{
+ Param pm = (Param) hn;
+
+ /* The second argument of unsetfn() is used by modules to
+ * differentiate "exp"licit unset from implicit unset, as when
+ * a parameter is going out of scope. It's not clear which
+ * of these applies here, but passing 1 has always worked.
+ */
+ if (delunset)
+ pm->gsu.s->unsetfn(pm, 1);
+ zsfree(pm->node.nam);
+ /* If this variable was tied by the user, ename was ztrdup'd */
+ if (pm->node.flags & PM_TIED)
+ zsfree(pm->ename);
+ zfree(pm, sizeof(struct param));
+}
+
+/* Print a parameter */
+
+enum paramtypes_flags {
+ PMTF_USE_BASE = (1<<0),
+ PMTF_USE_WIDTH = (1<<1),
+ PMTF_TEST_LEVEL = (1<<2)
+};
+
+struct paramtypes {
+ int binflag; /* The relevant PM_FLAG(S) */
+ const char *string; /* String for verbose output */
+ int typeflag; /* Flag for typeset -? */
+ int flags; /* The enum above */
+};
+
+static const struct paramtypes pmtypes[] = {
+ { PM_AUTOLOAD, "undefined", 0, 0},
+ { PM_INTEGER, "integer", 'i', PMTF_USE_BASE},
+ { PM_EFLOAT, "float", 'E', 0},
+ { PM_FFLOAT, "float", 'F', 0},
+ { PM_ARRAY, "array", 'a', 0},
+ { PM_HASHED, "association", 'A', 0},
+ { 0, "local", 0, PMTF_TEST_LEVEL},
+ { PM_LEFT, "left justified", 'L', PMTF_USE_WIDTH},
+ { PM_RIGHT_B, "right justified", 'R', PMTF_USE_WIDTH},
+ { PM_RIGHT_Z, "zero filled", 'Z', PMTF_USE_WIDTH},
+ { PM_LOWER, "lowercase", 'l', 0},
+ { PM_UPPER, "uppercase", 'u', 0},
+ { PM_READONLY, "readonly", 'r', 0},
+ { PM_TAGGED, "tagged", 't', 0},
+ { PM_EXPORTED, "exported", 'x', 0}
+};
+
+#define PMTYPES_SIZE ((int)(sizeof(pmtypes)/sizeof(struct paramtypes)))
+
+static void
+printparamvalue(Param p, int printflags)
+{
+ char *t, **u;
+
+ if (!(printflags & PRINT_KV_PAIR))
+ putchar('=');
+
+ /* How the value is displayed depends *
+ * on the type of the parameter */
+ switch (PM_TYPE(p->node.flags)) {
+ case PM_SCALAR:
+ /* string: simple output */
+ if (p->gsu.s->getfn && (t = p->gsu.s->getfn(p)))
+ quotedzputs(t, stdout);
+ break;
+ case PM_INTEGER:
+ /* integer */
+#ifdef ZSH_64_BIT_TYPE
+ fputs(output64(p->gsu.i->getfn(p)), stdout);
+#else
+ printf("%ld", p->gsu.i->getfn(p));
+#endif
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ /* float */
+ convfloat(p->gsu.f->getfn(p), p->base, p->node.flags, stdout);
+ break;
+ case PM_ARRAY:
+ /* array */
+ if (!(printflags & PRINT_KV_PAIR)) {
+ putchar('(');
+ if (!(printflags & PRINT_LINE))
+ putchar(' ');
+ }
+ u = p->gsu.a->getfn(p);
+ if(*u) {
+ if (printflags & PRINT_LINE) {
+ if (printflags & PRINT_KV_PAIR)
+ printf(" ");
+ else
+ printf("\n ");
+ }
+ quotedzputs(*u++, stdout);
+ while (*u) {
+ if (printflags & PRINT_LINE)
+ printf("\n ");
+ else
+ putchar(' ');
+ quotedzputs(*u++, stdout);
+ }
+ if ((printflags & (PRINT_LINE|PRINT_KV_PAIR)) == PRINT_LINE)
+ putchar('\n');
+ }
+ if (!(printflags & PRINT_KV_PAIR)) {
+ if (!(printflags & PRINT_LINE))
+ putchar(' ');
+ putchar(')');
+ }
+ break;
+ case PM_HASHED:
+ /* association */
+ {
+ HashTable ht;
+ int found = 0;
+ if (!(printflags & PRINT_KV_PAIR)) {
+ putchar('(');
+ if (!(printflags & PRINT_LINE))
+ putchar(' ');
+ }
+ ht = p->gsu.h->getfn(p);
+ if (ht)
+ found = scanhashtable(ht, 1, 0, PM_UNSET,
+ ht->printnode, PRINT_KV_PAIR |
+ (printflags & PRINT_LINE));
+ if (!(printflags & PRINT_KV_PAIR)) {
+ if (found && (printflags & PRINT_LINE))
+ putchar('\n');
+ putchar(')');
+ }
+ }
+ break;
+ }
+ if ((printflags & (PRINT_KV_PAIR|PRINT_LINE)) == PRINT_KV_PAIR)
+ putchar(' ');
+ else if (!(printflags & PRINT_KV_PAIR))
+ putchar('\n');
+}
+
+/**/
+mod_export void
+printparamnode(HashNode hn, int printflags)
+{
+ Param p = (Param) hn;
+
+ if (p->node.flags & PM_UNSET) {
+ if (isset(POSIXBUILTINS) && (p->node.flags & PM_READONLY) &&
+ (printflags & PRINT_TYPESET))
+ {
+ /*
+ * Special POSIX rules: show the parameter as readonly
+ * even though it's unset, but with no value.
+ */
+ printflags |= PRINT_NAMEONLY;
+ }
+ else if (p->node.flags & PM_EXPORTED)
+ printflags |= PRINT_NAMEONLY;
+ else
+ return;
+ }
+ if (p->node.flags & PM_AUTOLOAD)
+ printflags |= PRINT_NAMEONLY;
+
+ if (printflags & PRINT_TYPESET) {
+ if ((p->node.flags & (PM_READONLY|PM_SPECIAL)) ==
+ (PM_READONLY|PM_SPECIAL) ||
+ (p->node.flags & PM_AUTOLOAD)) {
+ /*
+ * It's not possible to restore the state of
+ * these, so don't output.
+ */
+ return;
+ }
+ if (locallevel && p->level >= locallevel) {
+ printf("typeset "); /* printf("local "); */
+ } else if ((p->node.flags & PM_EXPORTED) &&
+ !(p->node.flags & (PM_ARRAY|PM_HASHED))) {
+ printf("export ");
+ } else if (locallevel) {
+ printf("typeset -g ");
+ } else
+ printf("typeset ");
+ }
+
+ /* Print the attributes of the parameter */
+ if (printflags & (PRINT_TYPE|PRINT_TYPESET)) {
+ int doneminus = 0, i;
+ const struct paramtypes *pmptr;
+
+ for (pmptr = pmtypes, i = 0; i < PMTYPES_SIZE; i++, pmptr++) {
+ int doprint = 0;
+ if (pmptr->flags & PMTF_TEST_LEVEL) {
+ if (p->level)
+ doprint = 1;
+ } else if ((pmptr->binflag != PM_EXPORTED || p->level ||
+ (p->node.flags & (PM_LOCAL|PM_ARRAY|PM_HASHED))) &&
+ (p->node.flags & pmptr->binflag))
+ doprint = 1;
+
+ if (doprint) {
+ if (printflags & PRINT_TYPESET) {
+ if (pmptr->typeflag) {
+ if (!doneminus) {
+ putchar('-');
+ doneminus = 1;
+ }
+ putchar(pmptr->typeflag);
+ }
+ } else
+ printf("%s ", pmptr->string);
+ if ((pmptr->flags & PMTF_USE_BASE) && p->base) {
+ printf("%d ", p->base);
+ doneminus = 0;
+ }
+ if ((pmptr->flags & PMTF_USE_WIDTH) && p->width) {
+ printf("%d ", p->width);
+ doneminus = 0;
+ }
+ }
+ }
+ if (doneminus)
+ putchar(' ');
+ }
+
+ if ((printflags & PRINT_NAMEONLY) ||
+ ((p->node.flags & PM_HIDEVAL) && !(printflags & PRINT_INCLUDEVALUE))) {
+ zputs(p->node.nam, stdout);
+ putchar('\n');
+ } else {
+ if (printflags & PRINT_KV_PAIR) {
+ if (printflags & PRINT_LINE)
+ printf("\n ");
+ putchar('[');
+ }
+ quotedzputs(p->node.nam, stdout);
+ if (printflags & PRINT_KV_PAIR)
+ printf("]=");
+
+ printparamvalue(p, printflags);
+ }
+}
diff --git a/dotfiles/system/.zsh/modules/Src/parse.c b/dotfiles/system/.zsh/modules/Src/parse.c
new file mode 100644
index 0000000..83383f1
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/parse.c
@@ -0,0 +1,3977 @@
+/*
+ * 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 */
+
+/**/
+mod_export int incmdpos;
+
+/**/
+int aliasspaceflag;
+
+/* != 0 if we are in the middle of a [[ ... ]] */
+
+/**/
+mod_export int incond;
+
+/* != 0 if we are after a redirection (for ctxtlex only) */
+
+/**/
+mod_export int inredir;
+
+/*
+ * 1 if we are about to read a case pattern
+ * -1 if we are not quite sure
+ * 0 otherwise
+ */
+
+/**/
+int incasepat;
+
+/* != 0 if we just read a newline */
+
+/**/
+int isnewlin;
+
+/* != 0 if we are after a for keyword */
+
+/**/
+int infor;
+
+/* != 0 if we are after a repeat keyword; if it's nonzero it's a 1-based index
+ * of the current token from the last-seen command position */
+
+/**/
+int inrepeat_; /* trailing underscore because of name clash with Zle/zle_vi.c */
+
+/* != 0 if parsing arguments of typeset etc. */
+
+/**/
+mod_export int intypeset;
+
+/* list of here-documents */
+
+/**/
+struct heredocs *hdocs;
+
+
+#define YYERROR(O) { tok = LEXERR; ecused = (O); return 0; }
+#define YYERRORV(O) { tok = LEXERR; ecused = (O); return; }
+#define COND_ERROR(X,Y) \
+ do { \
+ zwarn(X,Y); \
+ herrflush(); \
+ if (noerrs != 2) \
+ errflag |= ERRFLAG_ERROR; \
+ YYERROR(ecused) \
+ } while(0)
+
+
+/*
+ * Word code.
+ *
+ * The parser now produces word code, reducing memory consumption compared
+ * to the nested structs we had before.
+ *
+ * Word code layout:
+ *
+ * WC_END
+ * - end of program code
+ *
+ * WC_LIST
+ * - data contains type (sync, ...)
+ * - followed by code for this list
+ * - if not (type & Z_END), followed by next WC_LIST
+ *
+ * WC_SUBLIST
+ * - data contains type (&&, ||, END) and flags (coprog, not)
+ * - followed by code for sublist
+ * - if not (type == END), followed by next WC_SUBLIST
+ *
+ * WC_PIPE
+ * - data contains type (end, mid) and LINENO
+ * - if not (type == END), followed by offset to next WC_PIPE
+ * - followed by command
+ * - if not (type == END), followed by next WC_PIPE
+ *
+ * WC_REDIR
+ * - must precede command-code (or WC_ASSIGN)
+ * - data contains type (<, >, ...)
+ * - followed by fd1 and name from struct redir
+ * - for the extended form {var}>... where the fd is assigned
+ * to var, there is an extra item to contain var
+ *
+ * WC_ASSIGN
+ * - data contains type (scalar, array) and number of array-elements
+ * - followed by name and value
+ * Note variant for WC_TYPESET assignments: WC_ASSIGN_INC indicates
+ * a name with no equals, not an =+ which isn't valid here.
+ *
+ * WC_SIMPLE
+ * - data contains the number of arguments (plus command)
+ * - followed by strings
+ *
+ * WC_TYPESET
+ * Variant of WC_SIMPLE used when TYPESET reserved word found.
+ * - data contains the number of string arguments (plus command)
+ * - followed by strings
+ * - followed by number of assignments
+ * - followed by assignments if non-zero number.
+ *
+ * WC_SUBSH
+ * - data unused
+ * - followed by list
+ *
+ * WC_CURSH
+ * - data unused
+ * - followed by list
+ *
+ * WC_TIMED
+ * - data contains type (followed by pipe or not)
+ * - if (type == PIPE), followed by pipe
+ *
+ * WC_FUNCDEF
+ * - data contains offset to after body
+ * - followed by number of names
+ * - followed by names
+ * - followed by offset to first string
+ * - followed by length of string table
+ * - followed by number of patterns for body
+ * - followed by codes for body
+ * - followed by strings for body
+ *
+ * WC_FOR
+ * - data contains type (list, ...) and offset to after body
+ * - if (type == COND), followed by init, cond, advance expressions
+ * - else if (type == PPARAM), followed by param name
+ * - else if (type == LIST), followed by param name, num strings, strings
+ * - followed by body
+ *
+ * WC_SELECT
+ * - data contains type (list, ...) and offset to after body
+ * - if (type == PPARAM), followed by param name
+ * - else if (type == LIST), followed by param name, num strings, strings
+ * - followed by body
+ *
+ * WC_WHILE
+ * - data contains type (while, until) and offset to after body
+ * - followed by condition
+ * - followed by body
+ *
+ * WC_REPEAT
+ * - data contains offset to after body
+ * - followed by number-string
+ * - followed by body
+ *
+ * WC_CASE
+ * - first CASE is always of type HEAD, data contains offset to esac
+ * - after that CASEs of type OR (;;), AND (;&) and TESTAND (;|),
+ * data is offset to next case
+ * - each OR/AND/TESTAND case is followed by pattern, pattern-number, list
+ *
+ * WC_IF
+ * - first IF is of type HEAD, data contains offset to fi
+ * - after that IFs of type IF, ELIF, ELSE, data is offset to next
+ * - each non-HEAD is followed by condition (only IF, ELIF) and body
+ *
+ * WC_COND
+ * - data contains type
+ * - if (type == AND/OR), data contains offset to after this one,
+ * followed by two CONDs
+ * - else if (type == NOT), followed by COND
+ * - else if (type == MOD), followed by name and strings
+ * - else if (type == MODI), followed by name, left, right
+ * - else if (type == STR[N]EQ), followed by left, right, pattern-number
+ * - else if (has two args) followed by left, right
+ * - else followed by string
+ *
+ * WC_ARITH
+ * - followed by string (there's only one)
+ *
+ * WC_AUTOFN
+ * - only used by the autoload builtin
+ *
+ * Lists and sublists may also be simplified, indicated by the presence
+ * of the Z_SIMPLE or WC_SUBLIST_SIMPLE flags. In this case they are only
+ * followed by a slot containing the line number, not by a WC_SUBLIST or
+ * WC_PIPE, respectively. The real advantage of simplified lists and
+ * sublists is that they can be executed faster, see exec.c. In the
+ * parser, the test if a list can be simplified is done quite simply
+ * by passing a int* around which gets set to non-zero if the thing
+ * just parsed is `cmplx', i.e. may need to be run by forking or
+ * some such.
+ *
+ * In each of the above, strings are encoded as one word code. For empty
+ * strings this is the bit pattern 11x, the lowest bit is non-zero if the
+ * string contains tokens and zero otherwise (this is true for the other
+ * ways to encode strings, too). For short strings (one to three
+ * characters), this is the marker 01x with the 24 bits above that
+ * containing the characters. Longer strings are encoded as the offset
+ * into the strs character array stored in the eprog struct shifted by
+ * two and ored with the bit pattern 0x.
+ * The ecstrcode() function that adds the code for a string uses a simple
+ * binary tree of strings already added so that long strings are encoded
+ * only once.
+ *
+ * Note also that in the eprog struct the pattern, code, and string
+ * arrays all point to the same memory block.
+ *
+ *
+ * To make things even faster in future versions, we could not only
+ * test if the strings contain tokens, but instead what kind of
+ * expansions need to be done on strings. In the execution code we
+ * could then use these flags for a specialized version of prefork()
+ * to avoid a lot of string parsing and some more string duplication.
+ */
+
+/**/
+int eclen, ecused, ecnpats;
+/**/
+Wordcode ecbuf;
+/**/
+Eccstr ecstrs;
+/**/
+int ecsoffs, ecssub, ecnfunc;
+
+#define EC_INIT_SIZE 256
+#define EC_DOUBLE_THRESHOLD 32768
+#define EC_INCREMENT 1024
+
+/* save parse context */
+
+/**/
+void
+parse_context_save(struct parse_stack *ps, int toplevel)
+{
+ (void)toplevel;
+
+ ps->incmdpos = incmdpos;
+ ps->aliasspaceflag = aliasspaceflag;
+ ps->incond = incond;
+ ps->inredir = inredir;
+ ps->incasepat = incasepat;
+ ps->isnewlin = isnewlin;
+ ps->infor = infor;
+ ps->inrepeat_ = inrepeat_;
+ ps->intypeset = intypeset;
+
+ ps->hdocs = hdocs;
+ ps->eclen = eclen;
+ ps->ecused = ecused;
+ ps->ecnpats = ecnpats;
+ ps->ecbuf = ecbuf;
+ ps->ecstrs = ecstrs;
+ ps->ecsoffs = ecsoffs;
+ ps->ecssub = ecssub;
+ ps->ecnfunc = ecnfunc;
+ ecbuf = NULL;
+ hdocs = NULL;
+}
+
+/* restore parse context */
+
+/**/
+void
+parse_context_restore(const struct parse_stack *ps, int toplevel)
+{
+ (void)toplevel;
+
+ if (ecbuf)
+ zfree(ecbuf, eclen);
+
+ incmdpos = ps->incmdpos;
+ aliasspaceflag = ps->aliasspaceflag;
+ incond = ps->incond;
+ inredir = ps->inredir;
+ incasepat = ps->incasepat;
+ isnewlin = ps->isnewlin;
+ infor = ps->infor;
+ inrepeat_ = ps->inrepeat_;
+ intypeset = ps->intypeset;
+
+ hdocs = ps->hdocs;
+ eclen = ps->eclen;
+ ecused = ps->ecused;
+ ecnpats = ps->ecnpats;
+ ecbuf = ps->ecbuf;
+ ecstrs = ps->ecstrs;
+ ecsoffs = ps->ecsoffs;
+ ecssub = ps->ecssub;
+ ecnfunc = ps->ecnfunc;
+
+ errflag &= ~ERRFLAG_ERROR;
+}
+
+/* Adjust pointers in here-doc structs. */
+
+static void
+ecadjusthere(int p, int d)
+{
+ struct heredocs *h;
+
+ for (h = hdocs; h; h = h->next)
+ if (h->pc >= p)
+ h->pc += d;
+}
+
+/* Insert n free code-slots at position p. */
+
+static void
+ecispace(int p, int n)
+{
+ int m;
+
+ if ((eclen - ecused) < n) {
+ int a = (eclen < EC_DOUBLE_THRESHOLD ? eclen : EC_INCREMENT);
+
+ if (n > a) a = n;
+
+ ecbuf = (Wordcode) zrealloc((char *) ecbuf, (eclen + a) * sizeof(wordcode));
+ eclen += a;
+ }
+ if ((m = ecused - p) > 0)
+ memmove(ecbuf + p + n, ecbuf + p, m * sizeof(wordcode));
+ ecused += n;
+ ecadjusthere(p, n);
+}
+
+/* Add one wordcode. */
+
+static int
+ecadd(wordcode c)
+{
+ if ((eclen - ecused) < 1) {
+ int a = (eclen < EC_DOUBLE_THRESHOLD ? eclen : EC_INCREMENT);
+
+ ecbuf = (Wordcode) zrealloc((char *) ecbuf, (eclen + a) * sizeof(wordcode));
+ eclen += a;
+ }
+ ecbuf[ecused] = c;
+
+ return ecused++;
+}
+
+/* Delete a wordcode. */
+
+static void
+ecdel(int p)
+{
+ int n = ecused - p - 1;
+
+ if (n > 0)
+ memmove(ecbuf + p, ecbuf + p + 1, n * sizeof(wordcode));
+ ecused--;
+ ecadjusthere(p, -1);
+}
+
+/* Build the wordcode for a string. */
+
+static wordcode
+ecstrcode(char *s)
+{
+ int l, t;
+
+ unsigned val = hasher(s);
+
+ if ((l = strlen(s) + 1) && l <= 4) {
+ t = has_token(s);
+ wordcode c = (t ? 3 : 2);
+ switch (l) {
+ case 4: c |= ((wordcode) STOUC(s[2])) << 19;
+ case 3: c |= ((wordcode) STOUC(s[1])) << 11;
+ case 2: c |= ((wordcode) STOUC(s[0])) << 3; break;
+ case 1: c = (t ? 7 : 6); break;
+ }
+ return c;
+ } else {
+ Eccstr p, *pp;
+ int cmp;
+
+ for (pp = &ecstrs; (p = *pp); ) {
+ if (!(cmp = p->nfunc - ecnfunc) && !(cmp = (((signed)p->hashval) - ((signed)val))) && !(cmp = strcmp(p->str, s))) {
+ return p->offs;
+ }
+ pp = (cmp < 0 ? &(p->left) : &(p->right));
+ }
+
+ t = has_token(s);
+
+ p = *pp = (Eccstr) zhalloc(sizeof(*p));
+ p->left = p->right = 0;
+ p->offs = ((ecsoffs - ecssub) << 2) | (t ? 1 : 0);
+ p->aoffs = ecsoffs;
+ p->str = s;
+ p->nfunc = ecnfunc;
+ p->hashval = val;
+ ecsoffs += l;
+
+ return p->offs;
+ }
+}
+
+#define ecstr(S) ecadd(ecstrcode(S))
+
+#define par_save_list(C) \
+ do { \
+ int eu = ecused; \
+ par_list(C); \
+ if (eu == ecused) ecadd(WCB_END()); \
+ } while (0)
+#define par_save_list1(C) \
+ do { \
+ int eu = ecused; \
+ par_list1(C); \
+ if (eu == ecused) ecadd(WCB_END()); \
+ } while (0)
+
+
+/**/
+mod_export void
+init_parse_status(void)
+{
+ /*
+ * These variables are currently declared by the parser, so we
+ * initialise them here. Possibly they are more naturally declared
+ * by the lexical anaylser; however, as they are used for signalling
+ * between the two it's a bit ambiguous. We clear them when
+ * using the lexical analyser for strings as well as here.
+ */
+ incasepat = incond = inredir = infor = intypeset = 0;
+ inrepeat_ = 0;
+ incmdpos = 1;
+}
+
+/* Initialise wordcode buffer. */
+
+/**/
+void
+init_parse(void)
+{
+ queue_signals();
+
+ if (ecbuf) zfree(ecbuf, eclen);
+
+ ecbuf = (Wordcode) zalloc((eclen = EC_INIT_SIZE) * sizeof(wordcode));
+ ecused = 0;
+ ecstrs = NULL;
+ ecsoffs = ecnpats = 0;
+ ecssub = 0;
+ ecnfunc = 0;
+
+ init_parse_status();
+
+ unqueue_signals();
+}
+
+/* Build eprog. */
+
+/* careful: copy_ecstr is from arg1 to arg2, unlike memcpy */
+
+static void
+copy_ecstr(Eccstr s, char *p)
+{
+ while (s) {
+ memcpy(p + s->aoffs, s->str, strlen(s->str) + 1);
+ copy_ecstr(s->left, p);
+ s = s->right;
+ }
+}
+
+static Eprog
+bld_eprog(int heap)
+{
+ Eprog ret;
+ int l;
+
+ queue_signals();
+
+ ecadd(WCB_END());
+
+ ret = heap ? (Eprog) zhalloc(sizeof(*ret)) : (Eprog) zalloc(sizeof(*ret));
+ ret->len = ((ecnpats * sizeof(Patprog)) +
+ (ecused * sizeof(wordcode)) +
+ ecsoffs);
+ ret->npats = ecnpats;
+ ret->nref = heap ? -1 : 1;
+ ret->pats = heap ? (Patprog *) zhalloc(ret->len) :
+ (Patprog *) zshcalloc(ret->len);
+ ret->prog = (Wordcode) (ret->pats + ecnpats);
+ ret->strs = (char *) (ret->prog + ecused);
+ ret->shf = NULL;
+ ret->flags = heap ? EF_HEAP : EF_REAL;
+ ret->dump = NULL;
+ for (l = 0; l < ecnpats; l++)
+ ret->pats[l] = dummy_patprog1;
+ memcpy(ret->prog, ecbuf, ecused * sizeof(wordcode));
+ copy_ecstr(ecstrs, ret->strs);
+
+ zfree(ecbuf, eclen);
+ ecbuf = NULL;
+
+ unqueue_signals();
+
+ return ret;
+}
+
+/**/
+mod_export int
+empty_eprog(Eprog p)
+{
+ return (!p || !p->prog || *p->prog == WCB_END());
+}
+
+static void
+clear_hdocs(void)
+{
+ struct heredocs *p, *n;
+
+ for (p = hdocs; p; p = n) {
+ n = p->next;
+ zfree(p, sizeof(struct heredocs));
+ }
+ hdocs = NULL;
+}
+
+/*
+ * event : ENDINPUT
+ * | SEPER
+ * | sublist [ SEPER | AMPER | AMPERBANG ]
+ *
+ * cmdsubst indicates our event is part of a command-style
+ * substitution terminated by the token indicationg, usual closing
+ * parenthesis. In other cases endtok is ENDINPUT.
+ */
+
+/**/
+Eprog
+parse_event(int endtok)
+{
+ tok = ENDINPUT;
+ incmdpos = 1;
+ aliasspaceflag = 0;
+ zshlex();
+ init_parse();
+
+ if (!par_event(endtok)) {
+ clear_hdocs();
+ return NULL;
+ }
+ if (endtok != ENDINPUT) {
+ /* don't need to build an eprog for this */
+ return &dummy_eprog;
+ }
+ return bld_eprog(1);
+}
+
+/**/
+int
+par_event(int endtok)
+{
+ int r = 0, p, c = 0;
+
+ while (tok == SEPER) {
+ if (isnewlin > 0 && endtok == ENDINPUT)
+ return 0;
+ zshlex();
+ }
+ if (tok == ENDINPUT)
+ return 0;
+ if (tok == endtok)
+ return 1;
+
+ p = ecadd(0);
+
+ if (par_sublist(&c)) {
+ if (tok == ENDINPUT || tok == endtok) {
+ set_list_code(p, Z_SYNC, c);
+ r = 1;
+ } else if (tok == SEPER) {
+ set_list_code(p, Z_SYNC, c);
+ if (isnewlin <= 0 || endtok != ENDINPUT)
+ zshlex();
+ r = 1;
+ } else if (tok == AMPER) {
+ set_list_code(p, Z_ASYNC, c);
+ zshlex();
+ r = 1;
+ } else if (tok == AMPERBANG) {
+ set_list_code(p, (Z_ASYNC | Z_DISOWN), c);
+ zshlex();
+ r = 1;
+ }
+ }
+ if (!r) {
+ tok = LEXERR;
+ if (errflag) {
+ yyerror(0);
+ ecused--;
+ return 0;
+ }
+ yyerror(1);
+ herrflush();
+ if (noerrs != 2)
+ errflag |= ERRFLAG_ERROR;
+ ecused--;
+ return 0;
+ } else {
+ int oec = ecused;
+
+ if (!par_event(endtok)) {
+ ecused = oec;
+ ecbuf[p] |= wc_bdata(Z_END);
+ return errflag ? 0 : 1;
+ }
+ }
+ return 1;
+}
+
+/**/
+mod_export Eprog
+parse_list(void)
+{
+ int c = 0;
+
+ tok = ENDINPUT;
+ init_parse();
+ zshlex();
+ par_list(&c);
+ if (tok != ENDINPUT) {
+ clear_hdocs();
+ tok = LEXERR;
+ yyerror(0);
+ return NULL;
+ }
+ return bld_eprog(1);
+}
+
+/*
+ * This entry point is only used for bin_test, our attempt to
+ * provide compatibility with /bin/[ and /bin/test. Hence
+ * at this point condlex should always be set to testlex.
+ */
+
+/**/
+mod_export Eprog
+parse_cond(void)
+{
+ init_parse();
+
+ if (!par_cond()) {
+ clear_hdocs();
+ return NULL;
+ }
+ return bld_eprog(1);
+}
+
+/* This adds a list wordcode. The important bit about this is that it also
+ * tries to optimise this to a Z_SIMPLE list code. */
+
+/**/
+static void
+set_list_code(int p, int type, int cmplx)
+{
+ if (!cmplx && (type == Z_SYNC || type == (Z_SYNC | Z_END)) &&
+ WC_SUBLIST_TYPE(ecbuf[p + 1]) == WC_SUBLIST_END) {
+ int ispipe = !(WC_SUBLIST_FLAGS(ecbuf[p + 1]) & WC_SUBLIST_SIMPLE);
+ ecbuf[p] = WCB_LIST((type | Z_SIMPLE), ecused - 2 - p);
+ ecdel(p + 1);
+ if (ispipe)
+ ecbuf[p + 1] = WC_PIPE_LINENO(ecbuf[p + 1]);
+ } else
+ ecbuf[p] = WCB_LIST(type, 0);
+}
+
+/* The same for sublists. */
+
+/**/
+static void
+set_sublist_code(int p, int type, int flags, int skip, int cmplx)
+{
+ if (cmplx)
+ ecbuf[p] = WCB_SUBLIST(type, flags, skip);
+ else {
+ ecbuf[p] = WCB_SUBLIST(type, (flags | WC_SUBLIST_SIMPLE), skip);
+ ecbuf[p + 1] = WC_PIPE_LINENO(ecbuf[p + 1]);
+ }
+}
+
+/*
+ * list : { SEPER } [ sublist [ { SEPER | AMPER | AMPERBANG } list ] ]
+ */
+
+/**/
+static void
+par_list(int *cmplx)
+{
+ int p, lp = -1, c;
+
+ rec:
+
+ while (tok == SEPER)
+ zshlex();
+
+ p = ecadd(0);
+ c = 0;
+
+ if (par_sublist(&c)) {
+ *cmplx |= c;
+ if (tok == SEPER || tok == AMPER || tok == AMPERBANG) {
+ if (tok != SEPER)
+ *cmplx = 1;
+ set_list_code(p, ((tok == SEPER) ? Z_SYNC :
+ (tok == AMPER) ? Z_ASYNC :
+ (Z_ASYNC | Z_DISOWN)), c);
+ incmdpos = 1;
+ do {
+ zshlex();
+ } while (tok == SEPER);
+ lp = p;
+ goto rec;
+ } else
+ set_list_code(p, (Z_SYNC | Z_END), c);
+ } else {
+ ecused--;
+ if (lp >= 0)
+ ecbuf[lp] |= wc_bdata(Z_END);
+ }
+}
+
+/**/
+static void
+par_list1(int *cmplx)
+{
+ int p = ecadd(0), c = 0;
+
+ if (par_sublist(&c)) {
+ set_list_code(p, (Z_SYNC | Z_END), c);
+ *cmplx |= c;
+ } else
+ ecused--;
+}
+
+/*
+ * sublist : sublist2 [ ( DBAR | DAMPER ) { SEPER } sublist ]
+ */
+
+/**/
+static int
+par_sublist(int *cmplx)
+{
+ int f, p, c = 0;
+
+ p = ecadd(0);
+
+ if ((f = par_sublist2(&c)) != -1) {
+ int e = ecused;
+
+ *cmplx |= c;
+ if (tok == DBAR || tok == DAMPER) {
+ enum lextok qtok = tok;
+ int sl;
+
+ cmdpush(tok == DBAR ? CS_CMDOR : CS_CMDAND);
+ zshlex();
+ while (tok == SEPER)
+ zshlex();
+ sl = par_sublist(cmplx);
+ set_sublist_code(p, (sl ? (qtok == DBAR ?
+ WC_SUBLIST_OR : WC_SUBLIST_AND) :
+ WC_SUBLIST_END),
+ f, (e - 1 - p), c);
+ cmdpop();
+ } else {
+ if (tok == AMPER || tok == AMPERBANG) {
+ c = 1;
+ *cmplx |= c;
+ }
+ set_sublist_code(p, WC_SUBLIST_END, f, (e - 1 - p), c);
+ }
+ return 1;
+ } else {
+ ecused--;
+ return 0;
+ }
+}
+
+/*
+ * sublist2 : [ COPROC | BANG ] pline
+ */
+
+/**/
+static int
+par_sublist2(int *cmplx)
+{
+ int f = 0;
+
+ if (tok == COPROC) {
+ *cmplx = 1;
+ f |= WC_SUBLIST_COPROC;
+ zshlex();
+ } else if (tok == BANG) {
+ *cmplx = 1;
+ f |= WC_SUBLIST_NOT;
+ zshlex();
+ }
+ if (!par_pline(cmplx) && !f)
+ return -1;
+
+ return f;
+}
+
+/*
+ * pline : cmd [ ( BAR | BARAMP ) { SEPER } pline ]
+ */
+
+/**/
+static int
+par_pline(int *cmplx)
+{
+ int p;
+ zlong line = toklineno;
+
+ p = ecadd(0);
+
+ if (!par_cmd(cmplx, 0)) {
+ ecused--;
+ return 0;
+ }
+ if (tok == BAR) {
+ *cmplx = 1;
+ cmdpush(CS_PIPE);
+ zshlex();
+ while (tok == SEPER)
+ zshlex();
+ ecbuf[p] = WCB_PIPE(WC_PIPE_MID, (line >= 0 ? line + 1 : 0));
+ ecispace(p + 1, 1);
+ ecbuf[p + 1] = ecused - 1 - p;
+ if (!par_pline(cmplx)) {
+ tok = LEXERR;
+ }
+ cmdpop();
+ return 1;
+ } else if (tok == BARAMP) {
+ int r;
+
+ for (r = p + 1; wc_code(ecbuf[r]) == WC_REDIR;
+ r += WC_REDIR_WORDS(ecbuf[r]));
+
+ ecispace(r, 3);
+ ecbuf[r] = WCB_REDIR(REDIR_MERGEOUT);
+ ecbuf[r + 1] = 2;
+ ecbuf[r + 2] = ecstrcode("1");
+
+ *cmplx = 1;
+ cmdpush(CS_ERRPIPE);
+ zshlex();
+ while (tok == SEPER)
+ zshlex();
+ ecbuf[p] = WCB_PIPE(WC_PIPE_MID, (line >= 0 ? line + 1 : 0));
+ ecispace(p + 1, 1);
+ ecbuf[p + 1] = ecused - 1 - p;
+ if (!par_pline(cmplx)) {
+ tok = LEXERR;
+ }
+ cmdpop();
+ return 1;
+ } else {
+ ecbuf[p] = WCB_PIPE(WC_PIPE_END, (line >= 0 ? line + 1 : 0));
+ return 1;
+ }
+}
+
+/*
+ * cmd : { redir } ( for | case | if | while | repeat |
+ * subsh | funcdef | time | dinbrack | dinpar | simple ) { redir }
+ *
+ * zsh_construct is passed through to par_subsh(), q.v.
+ */
+
+/**/
+static int
+par_cmd(int *cmplx, int zsh_construct)
+{
+ int r, nr = 0;
+
+ r = ecused;
+
+ if (IS_REDIROP(tok)) {
+ *cmplx = 1;
+ while (IS_REDIROP(tok)) {
+ nr += par_redir(&r, NULL);
+ }
+ }
+ switch (tok) {
+ case FOR:
+ cmdpush(CS_FOR);
+ par_for(cmplx);
+ cmdpop();
+ break;
+ case FOREACH:
+ cmdpush(CS_FOREACH);
+ par_for(cmplx);
+ cmdpop();
+ break;
+ case SELECT:
+ *cmplx = 1;
+ cmdpush(CS_SELECT);
+ par_for(cmplx);
+ cmdpop();
+ break;
+ case CASE:
+ cmdpush(CS_CASE);
+ par_case(cmplx);
+ cmdpop();
+ break;
+ case IF:
+ par_if(cmplx);
+ break;
+ case WHILE:
+ cmdpush(CS_WHILE);
+ par_while(cmplx);
+ cmdpop();
+ break;
+ case UNTIL:
+ cmdpush(CS_UNTIL);
+ par_while(cmplx);
+ cmdpop();
+ break;
+ case REPEAT:
+ cmdpush(CS_REPEAT);
+ par_repeat(cmplx);
+ cmdpop();
+ break;
+ case INPAR:
+ *cmplx = 1;
+ cmdpush(CS_SUBSH);
+ par_subsh(cmplx, zsh_construct);
+ cmdpop();
+ break;
+ case INBRACE:
+ cmdpush(CS_CURSH);
+ par_subsh(cmplx, zsh_construct);
+ cmdpop();
+ break;
+ case FUNC:
+ cmdpush(CS_FUNCDEF);
+ par_funcdef(cmplx);
+ cmdpop();
+ break;
+ case DINBRACK:
+ cmdpush(CS_COND);
+ par_dinbrack();
+ cmdpop();
+ break;
+ case DINPAR:
+ ecadd(WCB_ARITH());
+ ecstr(tokstr);
+ zshlex();
+ break;
+ case TIME:
+ {
+ static int inpartime = 0;
+
+ if (!inpartime) {
+ *cmplx = 1;
+ inpartime = 1;
+ par_time();
+ inpartime = 0;
+ break;
+ }
+ }
+ tok = STRING;
+ /* fall through */
+ default:
+ {
+ int sr;
+
+ if (!(sr = par_simple(cmplx, nr))) {
+ if (!nr)
+ return 0;
+ } else {
+ /* Take account of redirections */
+ if (sr > 1) {
+ *cmplx = 1;
+ r += sr - 1;
+ }
+ }
+ }
+ break;
+ }
+ if (IS_REDIROP(tok)) {
+ *cmplx = 1;
+ while (IS_REDIROP(tok))
+ (void)par_redir(&r, NULL);
+ }
+ incmdpos = 1;
+ incasepat = 0;
+ incond = 0;
+ intypeset = 0;
+ return 1;
+}
+
+/*
+ * 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(int *cmplx)
+{
+ int oecused = ecused, csh = (tok == FOREACH), p, sel = (tok == SELECT);
+ int type;
+
+ p = ecadd(0);
+
+ incmdpos = 0;
+ infor = tok == FOR ? 2 : 0;
+ zshlex();
+ if (tok == DINPAR) {
+ zshlex();
+ if (tok != DINPAR)
+ YYERRORV(oecused);
+ ecstr(tokstr);
+ zshlex();
+ if (tok != DINPAR)
+ YYERRORV(oecused);
+ ecstr(tokstr);
+ zshlex();
+ if (tok != DOUTPAR)
+ YYERRORV(oecused);
+ ecstr(tokstr);
+ infor = 0;
+ incmdpos = 1;
+ zshlex();
+ type = WC_FOR_COND;
+ } else {
+ int np = 0, n, posix_in, ona = noaliases, onc = nocorrect;
+ infor = 0;
+ if (tok != STRING || !isident(tokstr))
+ YYERRORV(oecused);
+ if (!sel)
+ np = ecadd(0);
+ n = 0;
+ incmdpos = 1;
+ noaliases = nocorrect = 1;
+ for (;;) {
+ n++;
+ ecstr(tokstr);
+ zshlex();
+ if (tok != STRING || !strcmp(tokstr, "in") || sel)
+ break;
+ if (!isident(tokstr) || errflag)
+ {
+ noaliases = ona;
+ nocorrect = onc;
+ YYERRORV(oecused);
+ }
+ }
+ noaliases = ona;
+ nocorrect = onc;
+ if (!sel)
+ ecbuf[np] = n;
+ posix_in = isnewlin;
+ while (isnewlin)
+ zshlex();
+ if (tok == STRING && !strcmp(tokstr, "in")) {
+ incmdpos = 0;
+ zshlex();
+ np = ecadd(0);
+ n = par_wordlist();
+ if (tok != SEPER)
+ YYERRORV(oecused);
+ ecbuf[np] = n;
+ type = (sel ? WC_SELECT_LIST : WC_FOR_LIST);
+ } else if (!posix_in && tok == INPAR) {
+ incmdpos = 0;
+ zshlex();
+ np = ecadd(0);
+ n = par_nl_wordlist();
+ if (tok != OUTPAR)
+ YYERRORV(oecused);
+ ecbuf[np] = n;
+ incmdpos = 1;
+ zshlex();
+ type = (sel ? WC_SELECT_LIST : WC_FOR_LIST);
+ } else
+ type = (sel ? WC_SELECT_PPARAM : WC_FOR_PPARAM);
+ }
+ incmdpos = 1;
+ while (tok == SEPER)
+ zshlex();
+ if (tok == DOLOOP) {
+ zshlex();
+ par_save_list(cmplx);
+ if (tok != DONE)
+ YYERRORV(oecused);
+ incmdpos = 0;
+ zshlex();
+ } else if (tok == INBRACE) {
+ zshlex();
+ par_save_list(cmplx);
+ if (tok != OUTBRACE)
+ YYERRORV(oecused);
+ incmdpos = 0;
+ zshlex();
+ } else if (csh || isset(CSHJUNKIELOOPS)) {
+ par_save_list(cmplx);
+ if (tok != ZEND)
+ YYERRORV(oecused);
+ incmdpos = 0;
+ zshlex();
+ } else if (unset(SHORTLOOPS)) {
+ YYERRORV(oecused);
+ } else
+ par_save_list1(cmplx);
+
+ ecbuf[p] = (sel ?
+ WCB_SELECT(type, ecused - 1 - p) :
+ WCB_FOR(type, ecused - 1 - p));
+}
+
+/*
+ * case : CASE STRING { SEPER } ( "in" | INBRACE )
+ { { SEPER } STRING { BAR STRING } OUTPAR
+ list [ DSEMI | SEMIAMP | SEMIBAR ] }
+ { SEPER } ( "esac" | OUTBRACE )
+ */
+
+/**/
+static void
+par_case(int *cmplx)
+{
+ int oecused = ecused, brflag, p, pp, palts, type, nalts;
+ int ona, onc;
+
+ p = ecadd(0);
+
+ incmdpos = 0;
+ zshlex();
+ if (tok != STRING)
+ YYERRORV(oecused);
+ ecstr(tokstr);
+
+ incmdpos = 1;
+ ona = noaliases;
+ onc = nocorrect;
+ noaliases = nocorrect = 1;
+ zshlex();
+ while (tok == SEPER)
+ zshlex();
+ if (!(tok == STRING && !strcmp(tokstr, "in")) && tok != INBRACE)
+ {
+ noaliases = ona;
+ nocorrect = onc;
+ YYERRORV(oecused);
+ }
+ brflag = (tok == INBRACE);
+ incasepat = 1;
+ incmdpos = 0;
+ noaliases = ona;
+ nocorrect = onc;
+ zshlex();
+
+ for (;;) {
+ char *str;
+ int skip_zshlex;
+
+ while (tok == SEPER)
+ zshlex();
+ if (tok == OUTBRACE)
+ break;
+ if (tok == INPAR)
+ zshlex();
+ if (tok == BAR) {
+ str = dupstring("");
+ skip_zshlex = 1;
+ } else {
+ if (tok != STRING)
+ YYERRORV(oecused);
+ if (!strcmp(tokstr, "esac"))
+ break;
+ str = dupstring(tokstr);
+ skip_zshlex = 0;
+ }
+ type = WC_CASE_OR;
+ pp = ecadd(0);
+ palts = ecadd(0);
+ nalts = 0;
+ /*
+ * Hack here.
+ *
+ * [Pause for astonished hubbub to subside.]
+ *
+ * The next token we get may be
+ * - ")" or "|" if we're looking at an honest-to-god
+ * "case" pattern, either because there's no opening
+ * parenthesis, or because SH_GLOB is set and we
+ * managed to grab an initial "(" to mark the start
+ * of the case pattern.
+ * - Something else --- we don't care what --- because
+ * we're parsing a complete "(...)" as a complete
+ * zsh pattern. In that case, we treat this as a
+ * single instance of a case pattern but we pretend
+ * we're doing proper case parsing --- in which the
+ * parentheses and bar are in different words from
+ * the string, so may be separated by whitespace.
+ * So we quietly massage the whitespace and hope
+ * no one noticed. This is horrible, but it's
+ * unfortunately too difficult to combine traditional
+ * zsh patterns with a properly parsed case pattern
+ * without generating incompatibilities which aren't
+ * all that popular (I've discovered).
+ * - We can also end up with something other than ")" or "|"
+ * just because we're looking at garbage.
+ *
+ * Because of the second case, what happens next might
+ * be the start of the command after the pattern, so we
+ * need to treat it as in command position. Luckily
+ * this doesn't affect our ability to match a | or ) as
+ * these are valid on command lines.
+ */
+ incasepat = -1;
+ incmdpos = 1;
+ if (!skip_zshlex)
+ zshlex();
+ for (;;) {
+ if (tok == OUTPAR) {
+ ecstr(str);
+ ecadd(ecnpats++);
+ nalts++;
+
+ incasepat = 0;
+ incmdpos = 1;
+ zshlex();
+ break;
+ } else if (tok == BAR) {
+ ecstr(str);
+ ecadd(ecnpats++);
+ nalts++;
+
+ incasepat = 1;
+ incmdpos = 0;
+ } else {
+ if (!nalts && str[0] == Inpar) {
+ int pct = 0, sl;
+ char *s;
+
+ for (s = str; *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 + 1 || s[-2] != Meta))
+ chuck(--s);
+ }
+ if (*s == Outpar)
+ pct--;
+ }
+ if (*s || pct || s == str)
+ YYERRORV(oecused);
+ /* Simplify pattern by removing surrounding (...) */
+ sl = strlen(str);
+ DPUTS(*str != Inpar || str[sl - 1] != Outpar,
+ "BUG: strange case pattern");
+ str[sl - 1] = '\0';
+ chuck(str);
+ ecstr(str);
+ ecadd(ecnpats++);
+ nalts++;
+ break;
+ }
+ YYERRORV(oecused);
+ }
+
+ zshlex();
+ switch (tok) {
+ case STRING:
+ /* Normal case */
+ str = dupstring(tokstr);
+ zshlex();
+ break;
+
+ case OUTPAR:
+ case BAR:
+ /* Empty string */
+ str = dupstring("");
+ break;
+
+ default:
+ /* Oops. */
+ YYERRORV(oecused);
+ break;
+ }
+ }
+ incasepat = 0;
+ par_save_list(cmplx);
+ if (tok == SEMIAMP)
+ type = WC_CASE_AND;
+ else if (tok == SEMIBAR)
+ type = WC_CASE_TESTAND;
+ ecbuf[pp] = WCB_CASE(type, ecused - 1 - pp);
+ ecbuf[palts] = nalts;
+ if ((tok == ESAC && !brflag) || (tok == OUTBRACE && brflag))
+ break;
+ if (tok != DSEMI && tok != SEMIAMP && tok != SEMIBAR)
+ YYERRORV(oecused);
+ incasepat = 1;
+ incmdpos = 0;
+ zshlex();
+ }
+ incmdpos = 1;
+ incasepat = 0;
+ zshlex();
+
+ ecbuf[p] = WCB_CASE(WC_CASE_HEAD, ecused - 1 - p);
+}
+
+/*
+ * 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(int *cmplx)
+{
+ int oecused = ecused, p, pp, type, usebrace = 0;
+ enum lextok xtok;
+ unsigned char nc;
+
+ p = ecadd(0);
+
+ for (;;) {
+ xtok = tok;
+ cmdpush(xtok == IF ? CS_IF : CS_ELIF);
+ if (xtok == FI) {
+ incmdpos = 0;
+ zshlex();
+ break;
+ }
+ zshlex();
+ if (xtok == ELSE)
+ break;
+ while (tok == SEPER)
+ zshlex();
+ if (!(xtok == IF || xtok == ELIF)) {
+ cmdpop();
+ YYERRORV(oecused);
+ }
+ pp = ecadd(0);
+ type = (xtok == IF ? WC_IF_IF : WC_IF_ELIF);
+ par_save_list(cmplx);
+ incmdpos = 1;
+ if (tok == ENDINPUT) {
+ cmdpop();
+ YYERRORV(oecused);
+ }
+ while (tok == SEPER)
+ zshlex();
+ xtok = FI;
+ nc = cmdstack[cmdsp - 1] == CS_IF ? CS_IFTHEN : CS_ELIFTHEN;
+ if (tok == THEN) {
+ usebrace = 0;
+ cmdpop();
+ cmdpush(nc);
+ zshlex();
+ par_save_list(cmplx);
+ ecbuf[pp] = WCB_IF(type, ecused - 1 - pp);
+ incmdpos = 1;
+ cmdpop();
+ } else if (tok == INBRACE) {
+ usebrace = 1;
+ cmdpop();
+ cmdpush(nc);
+ zshlex();
+ par_save_list(cmplx);
+ if (tok != OUTBRACE) {
+ cmdpop();
+ YYERRORV(oecused);
+ }
+ ecbuf[pp] = WCB_IF(type, ecused - 1 - pp);
+ /* command word (else) allowed to follow immediately */
+ zshlex();
+ incmdpos = 1;
+ if (tok == SEPER)
+ break;
+ cmdpop();
+ } else if (unset(SHORTLOOPS)) {
+ cmdpop();
+ YYERRORV(oecused);
+ } else {
+ cmdpop();
+ cmdpush(nc);
+ par_save_list1(cmplx);
+ ecbuf[pp] = WCB_IF(type, ecused - 1 - pp);
+ incmdpos = 1;
+ break;
+ }
+ }
+ cmdpop();
+ if (xtok == ELSE || tok == ELSE) {
+ pp = ecadd(0);
+ cmdpush(CS_ELSE);
+ while (tok == SEPER)
+ zshlex();
+ if (tok == INBRACE && usebrace) {
+ zshlex();
+ par_save_list(cmplx);
+ if (tok != OUTBRACE) {
+ cmdpop();
+ YYERRORV(oecused);
+ }
+ } else {
+ par_save_list(cmplx);
+ if (tok != FI) {
+ cmdpop();
+ YYERRORV(oecused);
+ }
+ }
+ incmdpos = 0;
+ ecbuf[pp] = WCB_IF(WC_IF_ELSE, ecused - 1 - pp);
+ zshlex();
+ cmdpop();
+ }
+ ecbuf[p] = WCB_IF(WC_IF_HEAD, ecused - 1 - p);
+}
+
+/*
+ * while : ( WHILE | UNTIL ) ( INPAR list OUTPAR | list ) { SEPER }
+ ( DO list DONE | INBRACE list OUTBRACE | list ZEND )
+ */
+
+/**/
+static void
+par_while(int *cmplx)
+{
+ int oecused = ecused, p;
+ int type = (tok == UNTIL ? WC_WHILE_UNTIL : WC_WHILE_WHILE);
+
+ p = ecadd(0);
+ zshlex();
+ par_save_list(cmplx);
+ incmdpos = 1;
+ while (tok == SEPER)
+ zshlex();
+ if (tok == DOLOOP) {
+ zshlex();
+ par_save_list(cmplx);
+ if (tok != DONE)
+ YYERRORV(oecused);
+ incmdpos = 0;
+ zshlex();
+ } else if (tok == INBRACE) {
+ zshlex();
+ par_save_list(cmplx);
+ if (tok != OUTBRACE)
+ YYERRORV(oecused);
+ incmdpos = 0;
+ zshlex();
+ } else if (isset(CSHJUNKIELOOPS)) {
+ par_save_list(cmplx);
+ if (tok != ZEND)
+ YYERRORV(oecused);
+ zshlex();
+ } else if (unset(SHORTLOOPS)) {
+ YYERRORV(oecused);
+ } else
+ par_save_list1(cmplx);
+
+ ecbuf[p] = WCB_WHILE(type, ecused - 1 - p);
+}
+
+/*
+ * repeat : REPEAT STRING { SEPER } ( DO list DONE | list1 )
+ */
+
+/**/
+static void
+par_repeat(int *cmplx)
+{
+ /* ### what to do about inrepeat_ here? */
+ int oecused = ecused, p;
+
+ p = ecadd(0);
+
+ incmdpos = 0;
+ zshlex();
+ if (tok != STRING)
+ YYERRORV(oecused);
+ ecstr(tokstr);
+ incmdpos = 1;
+ zshlex();
+ while (tok == SEPER)
+ zshlex();
+ if (tok == DOLOOP) {
+ zshlex();
+ par_save_list(cmplx);
+ if (tok != DONE)
+ YYERRORV(oecused);
+ incmdpos = 0;
+ zshlex();
+ } else if (tok == INBRACE) {
+ zshlex();
+ par_save_list(cmplx);
+ if (tok != OUTBRACE)
+ YYERRORV(oecused);
+ incmdpos = 0;
+ zshlex();
+ } else if (isset(CSHJUNKIELOOPS)) {
+ par_save_list(cmplx);
+ if (tok != ZEND)
+ YYERRORV(oecused);
+ zshlex();
+ } else if (unset(SHORTLOOPS)) {
+ YYERRORV(oecused);
+ } else
+ par_save_list1(cmplx);
+
+ ecbuf[p] = WCB_REPEAT(ecused - 1 - p);
+}
+
+/*
+ * subsh : INPAR list OUTPAR |
+ * INBRACE list OUTBRACE [ "always" INBRACE list OUTBRACE ]
+ *
+ * With zsh_construct non-zero, we're doing a zsh special in which
+ * the following token is not considered in command position. This
+ * is used for arguments of anonymous functions.
+ */
+
+/**/
+static void
+par_subsh(int *cmplx, int zsh_construct)
+{
+ enum lextok otok = tok;
+ int oecused = ecused, p, pp;
+
+ p = ecadd(0);
+ /* Extra word only needed for always block */
+ pp = ecadd(0);
+ zshlex();
+ par_list(cmplx);
+ ecadd(WCB_END());
+ if (tok != ((otok == INPAR) ? OUTPAR : OUTBRACE))
+ YYERRORV(oecused);
+ incmdpos = !zsh_construct;
+ zshlex();
+
+ /* Optional always block. No intervening SEPERs allowed. */
+ if (otok == INBRACE && tok == STRING && !strcmp(tokstr, "always")) {
+ ecbuf[pp] = WCB_TRY(ecused - 1 - pp);
+ incmdpos = 1;
+ do {
+ zshlex();
+ } while (tok == SEPER);
+
+ if (tok != INBRACE)
+ YYERRORV(oecused);
+ cmdpop();
+ cmdpush(CS_ALWAYS);
+
+ zshlex();
+ par_save_list(cmplx);
+ while (tok == SEPER)
+ zshlex();
+
+ incmdpos = 1;
+
+ if (tok != OUTBRACE)
+ YYERRORV(oecused);
+ zshlex();
+ ecbuf[p] = WCB_TRY(ecused - 1 - p);
+ } else {
+ ecbuf[p] = (otok == INPAR ? WCB_SUBSH(ecused - 1 - p) :
+ WCB_CURSH(ecused - 1 - p));
+ }
+}
+
+/*
+ * funcdef : FUNCTION wordlist [ INOUTPAR ] { SEPER }
+ * ( list1 | INBRACE list OUTBRACE )
+ */
+
+/**/
+static void
+par_funcdef(int *cmplx)
+{
+ int oecused = ecused, num = 0, onp, p, c = 0;
+ int so, oecssub = ecssub;
+ zlong oldlineno = lineno;
+
+ lineno = 0;
+ nocorrect = 1;
+ incmdpos = 0;
+ zshlex();
+
+ p = ecadd(0);
+ ecadd(0);
+
+ while (tok == STRING) {
+ if ((*tokstr == Inbrace || *tokstr == '{') &&
+ !tokstr[1]) {
+ tok = INBRACE;
+ break;
+ }
+ ecstr(tokstr);
+ num++;
+ zshlex();
+ }
+ ecadd(0);
+ ecadd(0);
+ ecadd(0);
+
+ nocorrect = 0;
+ incmdpos = 1;
+ if (tok == INOUTPAR)
+ zshlex();
+ while (tok == SEPER)
+ zshlex();
+
+ ecnfunc++;
+ ecssub = so = ecsoffs;
+ onp = ecnpats;
+ ecnpats = 0;
+
+ if (tok == INBRACE) {
+ zshlex();
+ par_list(&c);
+ if (tok != OUTBRACE) {
+ lineno += oldlineno;
+ ecnpats = onp;
+ ecssub = oecssub;
+ YYERRORV(oecused);
+ }
+ if (num == 0) {
+ /* Anonymous function, possibly with arguments */
+ incmdpos = 0;
+ }
+ zshlex();
+ } else if (unset(SHORTLOOPS)) {
+ lineno += oldlineno;
+ ecnpats = onp;
+ ecssub = oecssub;
+ YYERRORV(oecused);
+ } else
+ par_list1(&c);
+
+ ecadd(WCB_END());
+ ecbuf[p + num + 2] = so - oecssub;
+ ecbuf[p + num + 3] = ecsoffs - so;
+ ecbuf[p + num + 4] = ecnpats;
+ ecbuf[p + 1] = num;
+
+ ecnpats = onp;
+ ecssub = oecssub;
+ ecnfunc++;
+
+ ecbuf[p] = WCB_FUNCDEF(ecused - 1 - p);
+
+ if (num == 0) {
+ /* Unnamed function */
+ int parg = ecadd(0);
+ ecadd(0);
+ while (tok == STRING) {
+ ecstr(tokstr);
+ num++;
+ zshlex();
+ }
+ if (num > 0)
+ *cmplx = 1;
+ ecbuf[parg] = ecused - parg; /*?*/
+ ecbuf[parg+1] = num;
+ }
+ lineno += oldlineno;
+}
+
+/*
+ * time : TIME sublist2
+ */
+
+/**/
+static void
+par_time(void)
+{
+ int p, f, c = 0;
+
+ zshlex();
+
+ p = ecadd(0);
+ ecadd(0);
+ if ((f = par_sublist2(&c)) < 0) {
+ ecused--;
+ ecbuf[p] = WCB_TIMED(WC_TIMED_EMPTY);
+ } else {
+ ecbuf[p] = WCB_TIMED(WC_TIMED_PIPE);
+ set_sublist_code(p + 1, WC_SUBLIST_END, f, ecused - 2 - p, c);
+ }
+}
+
+/*
+ * dinbrack : DINBRACK cond DOUTBRACK
+ */
+
+/**/
+static void
+par_dinbrack(void)
+{
+ int oecused = ecused;
+
+ incond = 1;
+ incmdpos = 0;
+ zshlex();
+ par_cond();
+ if (tok != DOUTBRACK)
+ YYERRORV(oecused);
+ incond = 0;
+ incmdpos = 1;
+ zshlex();
+}
+
+/*
+ * simple : { COMMAND | EXEC | NOGLOB | NOCORRECT | DASH }
+ { STRING | ENVSTRING | ENVARRAY wordlist OUTPAR | redir }
+ [ INOUTPAR { SEPER } ( list1 | INBRACE list OUTBRACE ) ]
+ *
+ * Returns 0 if no code, else 1 plus the number of code words
+ * used up by redirections.
+ */
+
+/**/
+static int
+par_simple(int *cmplx, int nr)
+{
+ int oecused = ecused, isnull = 1, r, argc = 0, p, isfunc = 0, sr = 0;
+ int c = *cmplx, nrediradd, assignments = 0, ppost = 0, is_typeset = 0;
+ char *hasalias = input_hasalias();
+ wordcode postassigns = 0;
+
+ r = ecused;
+ for (;;) {
+ if (tok == NOCORRECT) {
+ *cmplx = c = 1;
+ nocorrect = 1;
+ } else if (tok == ENVSTRING) {
+ char *ptr, *name, *str;
+
+ name = tokstr;
+ for (ptr = tokstr;
+ *ptr && *ptr != Inbrack && *ptr != '=' && *ptr != '+';
+ ptr++);
+ if (*ptr == Inbrack) skipparens(Inbrack, Outbrack, &ptr);
+ if (*ptr == '+') {
+ *ptr++ = '\0';
+ ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, WC_ASSIGN_INC, 0));
+ } else
+ ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, WC_ASSIGN_NEW, 0));
+
+ if (*ptr == '=') {
+ *ptr = '\0';
+ str = ptr + 1;
+ } else
+ equalsplit(tokstr, &str);
+ for (ptr = str; *ptr; ptr++) {
+ /*
+ * We can't treat this as "simple" if it contains
+ * expansions that require process subsitution, since then
+ * we need process handling.
+ */
+ if (ptr[1] == Inpar &&
+ (*ptr == Equals || *ptr == Inang || *ptr == OutangProc)) {
+ *cmplx = 1;
+ break;
+ }
+ }
+ ecstr(name);
+ ecstr(str);
+ isnull = 0;
+ assignments = 1;
+ } else if (tok == ENVARRAY) {
+ int oldcmdpos = incmdpos, n, type2;
+
+ /*
+ * We consider array setting cmplx because it can
+ * contain process substitutions, which need a valid job.
+ */
+ *cmplx = c = 1;
+ p = ecadd(0);
+ incmdpos = 0;
+ if ((type2 = strlen(tokstr) - 1) && tokstr[type2] == '+') {
+ tokstr[type2] = '\0';
+ type2 = WC_ASSIGN_INC;
+ } else
+ type2 = WC_ASSIGN_NEW;
+ ecstr(tokstr);
+ cmdpush(CS_ARRAY);
+ zshlex();
+ n = par_nl_wordlist();
+ ecbuf[p] = WCB_ASSIGN(WC_ASSIGN_ARRAY, type2, n);
+ cmdpop();
+ if (tok != OUTPAR)
+ YYERROR(oecused);
+ incmdpos = oldcmdpos;
+ isnull = 0;
+ assignments = 1;
+ } else if (IS_REDIROP(tok)) {
+ *cmplx = c = 1;
+ nr += par_redir(&r, NULL);
+ continue;
+ } else
+ break;
+ zshlex();
+ if (!hasalias)
+ hasalias = input_hasalias();
+ }
+ if (tok == AMPER || tok == AMPERBANG)
+ YYERROR(oecused);
+
+ p = ecadd(WCB_SIMPLE(0));
+
+ for (;;) {
+ if (tok == STRING || tok == TYPESET) {
+ int redir_var = 0;
+
+ *cmplx = 1;
+ incmdpos = 0;
+
+ if (tok == TYPESET)
+ intypeset = is_typeset = 1;
+
+ if (!isset(IGNOREBRACES) && *tokstr == Inbrace)
+ {
+ /* Look for redirs of the form {var}>file etc. */
+ char *eptr = tokstr + strlen(tokstr) - 1;
+ char *ptr = eptr;
+
+ if (*ptr == Outbrace && ptr > tokstr + 1)
+ {
+ if (itype_end(tokstr+1, IIDENT, 0) >= ptr)
+ {
+ char *toksave = tokstr;
+ char *idstring = dupstrpfx(tokstr+1, eptr-tokstr-1);
+ redir_var = 1;
+ zshlex();
+ if (!hasalias)
+ hasalias = input_hasalias();
+
+ if (IS_REDIROP(tok) && tokfd == -1)
+ {
+ *cmplx = c = 1;
+ nrediradd = par_redir(&r, idstring);
+ p += nrediradd;
+ sr += nrediradd;
+ }
+ else
+ {
+ ecstr(toksave);
+ argc++;
+ }
+ }
+ }
+ }
+
+ if (!redir_var)
+ {
+ if (postassigns) {
+ /*
+ * We're in the variable part of a typeset,
+ * but this doesn't have an assignment.
+ * We'll parse it as if it does, but mark
+ * it specially with WC_ASSIGN_INC.
+ */
+ postassigns++;
+ ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, WC_ASSIGN_INC, 0));
+ ecstr(tokstr);
+ ecstr(""); /* TBD can possibly optimise out */
+ } else {
+ ecstr(tokstr);
+ argc++;
+ }
+ zshlex();
+ if (!hasalias)
+ hasalias = input_hasalias();
+ }
+ } else if (IS_REDIROP(tok)) {
+ *cmplx = c = 1;
+ nrediradd = par_redir(&r, NULL);
+ p += nrediradd;
+ if (ppost)
+ ppost += nrediradd;
+ sr += nrediradd;
+ } else if (tok == ENVSTRING) {
+ char *ptr, *name, *str;
+
+ if (!postassigns++)
+ ppost = ecadd(0);
+
+ name = tokstr;
+ for (ptr = tokstr; *ptr && *ptr != Inbrack && *ptr != '=' && *ptr != '+';
+ ptr++);
+ if (*ptr == Inbrack) skipparens(Inbrack, Outbrack, &ptr);
+ ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, WC_ASSIGN_NEW, 0));
+
+ if (*ptr == '=') {
+ *ptr = '\0';
+ str = ptr + 1;
+ } else
+ equalsplit(tokstr, &str);
+ ecstr(name);
+ ecstr(str);
+ zshlex();
+ if (!hasalias)
+ hasalias = input_hasalias();
+ } else if (tok == ENVARRAY) {
+ int n, parr;
+
+ if (!postassigns++)
+ ppost = ecadd(0);
+
+ parr = ecadd(0);
+ ecstr(tokstr);
+ cmdpush(CS_ARRAY);
+ /*
+ * Careful here: this must be the typeset case,
+ * but we need to tell the lexer not to look
+ * for assignments until we've finished the
+ * present one.
+ */
+ intypeset = 0;
+ zshlex();
+ n = par_nl_wordlist();
+ ecbuf[parr] = WCB_ASSIGN(WC_ASSIGN_ARRAY, WC_ASSIGN_NEW, n);
+ cmdpop();
+ intypeset = 1;
+ if (tok != OUTPAR)
+ YYERROR(oecused);
+ zshlex();
+ } else if (tok == INOUTPAR) {
+ zlong oldlineno = lineno;
+ int onp, so, oecssub = ecssub;
+
+ /* Error if too many function definitions at once */
+ if (!isset(MULTIFUNCDEF) && argc > 1)
+ YYERROR(oecused);
+ /* Error if preceding assignments */
+ if (assignments || postassigns)
+ YYERROR(oecused);
+ if (hasalias && !isset(ALIASFUNCDEF) && argc &&
+ hasalias != input_hasalias()) {
+ zwarn("defining function based on alias `%s'", hasalias);
+ YYERROR(oecused);
+ }
+
+ *cmplx = c;
+ lineno = 0;
+ incmdpos = 1;
+ cmdpush(CS_FUNCDEF);
+ zshlex();
+ while (tok == SEPER)
+ zshlex();
+
+ ecispace(p + 1, 1);
+ ecbuf[p + 1] = argc;
+ ecadd(0);
+ ecadd(0);
+ ecadd(0);
+
+ ecnfunc++;
+ ecssub = so = ecsoffs;
+ onp = ecnpats;
+ ecnpats = 0;
+
+ if (tok == INBRACE) {
+ int c = 0;
+
+ zshlex();
+ par_list(&c);
+ if (tok != OUTBRACE) {
+ cmdpop();
+ lineno += oldlineno;
+ ecnpats = onp;
+ ecssub = oecssub;
+ YYERROR(oecused);
+ }
+ if (argc == 0) {
+ /* Anonymous function, possibly with arguments */
+ incmdpos = 0;
+ }
+ zshlex();
+ } else {
+ int ll, sl, c = 0;
+
+ ll = ecadd(0);
+ sl = ecadd(0);
+ (void)ecadd(WCB_PIPE(WC_PIPE_END, 0));
+
+ if (!par_cmd(&c, argc == 0)) {
+ cmdpop();
+ YYERROR(oecused);
+ }
+ if (argc == 0) {
+ /*
+ * Anonymous function, possibly with arguments.
+ * N.B. for cmplx structures in particular
+ * ( ... ) we rely on lower level code doing this
+ * to get the immediately following word (the
+ * first token after the ")" has already been
+ * read).
+ */
+ incmdpos = 0;
+ }
+
+ set_sublist_code(sl, WC_SUBLIST_END, 0, ecused - 1 - sl, c);
+ set_list_code(ll, (Z_SYNC | Z_END), c);
+ }
+ cmdpop();
+
+ ecadd(WCB_END());
+ ecbuf[p + argc + 2] = so - oecssub;
+ ecbuf[p + argc + 3] = ecsoffs - so;
+ ecbuf[p + argc + 4] = ecnpats;
+
+ ecnpats = onp;
+ ecssub = oecssub;
+ ecnfunc++;
+
+ ecbuf[p] = WCB_FUNCDEF(ecused - 1 - p);
+
+ if (argc == 0) {
+ /* Unnamed function */
+ int parg = ecadd(0);
+ ecadd(0);
+ while (tok == STRING || IS_REDIROP(tok)) {
+ if (tok == STRING)
+ {
+ ecstr(tokstr);
+ argc++;
+ zshlex();
+ } else {
+ *cmplx = c = 1;
+ nrediradd = par_redir(&r, NULL);
+ p += nrediradd;
+ if (ppost)
+ ppost += nrediradd;
+ sr += nrediradd;
+ parg += nrediradd;
+ }
+ }
+ if (argc > 0)
+ *cmplx = 1;
+ ecbuf[parg] = ecused - parg; /*?*/
+ ecbuf[parg+1] = argc;
+ }
+ lineno += oldlineno;
+
+ isfunc = 1;
+ isnull = 0;
+ break;
+ } else
+ break;
+ isnull = 0;
+ }
+ if (isnull && !(sr + nr)) {
+ ecused = p;
+ return 0;
+ }
+ incmdpos = 1;
+ intypeset = 0;
+
+ if (!isfunc) {
+ if (is_typeset) {
+ ecbuf[p] = WCB_TYPESET(argc);
+ if (postassigns)
+ ecbuf[ppost] = postassigns;
+ else
+ ecadd(0);
+ } else
+ ecbuf[p] = WCB_SIMPLE(argc);
+ }
+
+ return sr + 1;
+}
+
+/*
+ * redir : ( OUTANG | ... | TRINANG ) STRING
+ *
+ * Return number of code words required for redirection
+ */
+
+static int redirtab[TRINANG - OUTANG + 1] = {
+ REDIR_WRITE,
+ REDIR_WRITENOW,
+ REDIR_APP,
+ REDIR_APPNOW,
+ REDIR_READ,
+ REDIR_READWRITE,
+ REDIR_HEREDOC,
+ REDIR_HEREDOCDASH,
+ REDIR_MERGEIN,
+ REDIR_MERGEOUT,
+ REDIR_ERRWRITE,
+ REDIR_ERRWRITENOW,
+ REDIR_ERRAPP,
+ REDIR_ERRAPPNOW,
+ REDIR_HERESTR,
+};
+
+/**/
+static int
+par_redir(int *rp, char *idstring)
+{
+ int r = *rp, type, fd1, oldcmdpos, oldnc, ncodes;
+ char *name;
+
+ oldcmdpos = incmdpos;
+ incmdpos = 0;
+ oldnc = nocorrect;
+ if (tok != INANG && tok != INOUTANG)
+ nocorrect = 1;
+ type = redirtab[tok - OUTANG];
+ fd1 = tokfd;
+ zshlex();
+ if (tok != STRING && tok != ENVSTRING)
+ YYERROR(ecused);
+ incmdpos = oldcmdpos;
+ nocorrect = oldnc;
+
+ /* assign default fd */
+ if (fd1 == -1)
+ fd1 = IS_READFD(type) ? 0 : 1;
+
+ name = tokstr;
+
+ switch (type) {
+ case REDIR_HEREDOC:
+ case REDIR_HEREDOCDASH: {
+ /* <<[-] name */
+ struct heredocs **hd;
+ int htype = type;
+
+ /*
+ * Add two here for the string to remember the HERE
+ * terminator in raw and munged form.
+ */
+ if (idstring)
+ {
+ type |= REDIR_VARID_MASK;
+ ncodes = 6;
+ }
+ else
+ ncodes = 5;
+
+ /* If we ever to change the number of codes, we have to change
+ * the definition of WC_REDIR_WORDS. */
+ ecispace(r, ncodes);
+ *rp = r + ncodes;
+ ecbuf[r] = WCB_REDIR(type | REDIR_FROM_HEREDOC_MASK);
+ ecbuf[r + 1] = fd1;
+
+ /*
+ * r + 2: the HERE string we recover
+ * r + 3: the HERE document terminator, raw
+ * r + 4: the HERE document terminator, munged
+ */
+ if (idstring)
+ ecbuf[r + 5] = ecstrcode(idstring);
+
+ for (hd = &hdocs; *hd; hd = &(*hd)->next)
+ ;
+ *hd = zalloc(sizeof(struct heredocs));
+ (*hd)->next = NULL;
+ (*hd)->type = htype;
+ (*hd)->pc = r;
+ (*hd)->str = tokstr;
+
+ zshlex();
+ return ncodes;
+ }
+ case REDIR_WRITE:
+ case REDIR_WRITENOW:
+ if (tokstr[0] == OutangProc && tokstr[1] == Inpar)
+ /* > >(...) */
+ type = REDIR_OUTPIPE;
+ else if (tokstr[0] == Inang && tokstr[1] == Inpar)
+ YYERROR(ecused);
+ break;
+ case REDIR_READ:
+ if (tokstr[0] == Inang && tokstr[1] == Inpar)
+ /* < <(...) */
+ type = REDIR_INPIPE;
+ else if (tokstr[0] == OutangProc && tokstr[1] == Inpar)
+ YYERROR(ecused);
+ break;
+ case REDIR_READWRITE:
+ if ((tokstr[0] == Inang || tokstr[0] == OutangProc) &&
+ tokstr[1] == Inpar)
+ type = tokstr[0] == Inang ? REDIR_INPIPE : REDIR_OUTPIPE;
+ break;
+ }
+ zshlex();
+
+ /* If we ever to change the number of codes, we have to change
+ * the definition of WC_REDIR_WORDS. */
+ if (idstring)
+ {
+ type |= REDIR_VARID_MASK;
+ ncodes = 4;
+ }
+ else
+ ncodes = 3;
+
+ ecispace(r, ncodes);
+ *rp = r + ncodes;
+ ecbuf[r] = WCB_REDIR(type);
+ ecbuf[r + 1] = fd1;
+ ecbuf[r + 2] = ecstrcode(name);
+ if (idstring)
+ ecbuf[r + 3] = ecstrcode(idstring);
+
+ return ncodes;
+}
+
+/**/
+void
+setheredoc(int pc, int type, char *str, char *termstr, char *munged_termstr)
+{
+ ecbuf[pc] = WCB_REDIR(type | REDIR_FROM_HEREDOC_MASK);
+ ecbuf[pc + 2] = ecstrcode(str);
+ ecbuf[pc + 3] = ecstrcode(termstr);
+ ecbuf[pc + 4] = ecstrcode(munged_termstr);
+}
+
+/*
+ * wordlist : { STRING }
+ */
+
+/**/
+static int
+par_wordlist(void)
+{
+ int num = 0;
+ while (tok == STRING) {
+ ecstr(tokstr);
+ num++;
+ zshlex();
+ }
+ return num;
+}
+
+/*
+ * nl_wordlist : { STRING | SEPER }
+ */
+
+/**/
+static int
+par_nl_wordlist(void)
+{
+ int num = 0;
+
+ while (tok == STRING || tok == SEPER) {
+ if (tok != SEPER) {
+ ecstr(tokstr);
+ num++;
+ }
+ zshlex();
+ }
+ return num;
+}
+
+/*
+ * condlex is zshlex for normal parsing, but is altered to allow
+ * the test builtin to use par_cond.
+ */
+
+/**/
+void (*condlex) _((void)) = zshlex;
+
+/*
+ * cond : cond_1 { SEPER } [ DBAR { SEPER } cond ]
+ */
+
+#define COND_SEP() (tok == SEPER && condlex != testlex && *zshlextext != ';')
+
+/**/
+static int
+par_cond(void)
+{
+ int p = ecused, r;
+
+ r = par_cond_1();
+ while (COND_SEP())
+ condlex();
+ if (tok == DBAR) {
+ condlex();
+ while (COND_SEP())
+ condlex();
+ ecispace(p, 1);
+ par_cond();
+ ecbuf[p] = WCB_COND(COND_OR, ecused - 1 - p);
+ return 1;
+ }
+ return r;
+}
+
+/*
+ * cond_1 : cond_2 { SEPER } [ DAMPER { SEPER } cond_1 ]
+ */
+
+/**/
+static int
+par_cond_1(void)
+{
+ int r, p = ecused;
+
+ r = par_cond_2();
+ while (COND_SEP())
+ condlex();
+ if (tok == DAMPER) {
+ condlex();
+ while (COND_SEP())
+ condlex();
+ ecispace(p, 1);
+ par_cond_1();
+ ecbuf[p] = WCB_COND(COND_AND, ecused - 1 - p);
+ return 1;
+ }
+ return r;
+}
+
+/*
+ * Return 1 if condition matches. This also works for non-elided options.
+ *
+ * input is test string, may begin - or Dash.
+ * cond is condition following the -.
+ */
+static int check_cond(const char *input, const char *cond)
+{
+ if (!IS_DASH(input[0]))
+ return 0;
+ return !strcmp(input + 1, cond);
+}
+
+/*
+ * cond_2 : BANG cond_2
+ | INPAR { SEPER } cond_2 { SEPER } OUTPAR
+ | STRING STRING STRING
+ | STRING STRING
+ | STRING ( INANG | OUTANG ) STRING
+ */
+
+/**/
+static int
+par_cond_2(void)
+{
+ char *s1, *s2, *s3;
+ int dble = 0;
+ int n_testargs = (condlex == testlex) ? arrlen(testargs) + 1 : 0;
+
+ if (n_testargs) {
+ /* See the description of test in POSIX 1003.2 */
+ if (tok == NULLTOK)
+ /* no arguments: false */
+ return par_cond_double(dupstring("-n"), dupstring(""));
+ if (n_testargs == 1) {
+ /* one argument: [ foo ] is equivalent to [ -n foo ] */
+ s1 = tokstr;
+ condlex();
+ /* ksh behavior: [ -t ] means [ -t 1 ]; bash disagrees */
+ if (unset(POSIXBUILTINS) && check_cond(s1, "t"))
+ return par_cond_double(s1, dupstring("1"));
+ return par_cond_double(dupstring("-n"), s1);
+ }
+ if (n_testargs > 2) {
+ /* three arguments: if the second argument is a binary operator, *
+ * perform that binary test on the first and the third argument */
+ if (!strcmp(*testargs, "=") ||
+ !strcmp(*testargs, "==") ||
+ !strcmp(*testargs, "!=") ||
+ (IS_DASH(**testargs) && get_cond_num(*testargs + 1) >= 0)) {
+ s1 = tokstr;
+ condlex();
+ s2 = tokstr;
+ condlex();
+ s3 = tokstr;
+ condlex();
+ return par_cond_triple(s1, s2, s3);
+ }
+ }
+ /*
+ * We fall through here on any non-numeric infix operator
+ * or any other time there are at least two arguments.
+ */
+ } else
+ while (COND_SEP())
+ condlex();
+ if (tok == BANG) {
+ /*
+ * In "test" compatibility mode, "! -a ..." and "! -o ..."
+ * are treated as "[string] [and] ..." and "[string] [or] ...".
+ */
+ if (!(n_testargs > 1 && (check_cond(*testargs, "a") ||
+ check_cond(*testargs, "o"))))
+ {
+ condlex();
+ ecadd(WCB_COND(COND_NOT, 0));
+ return par_cond_2();
+ }
+ }
+ if (tok == INPAR) {
+ int r;
+
+ condlex();
+ while (COND_SEP())
+ condlex();
+ r = par_cond();
+ while (COND_SEP())
+ condlex();
+ if (tok != OUTPAR)
+ YYERROR(ecused);
+ condlex();
+ return r;
+ }
+ s1 = tokstr;
+ dble = (s1 && IS_DASH(*s1)
+ && (!n_testargs
+ || strspn(s1+1, "abcdefghknoprstuvwxzLONGS") == 1)
+ && !s1[2]);
+ if (tok != STRING) {
+ /* Check first argument for [[ STRING ]] re-interpretation */
+ if (s1 /* tok != DOUTBRACK && tok != DAMPER && tok != DBAR */
+ && tok != LEXERR && (!dble || n_testargs)) {
+ do condlex(); while (COND_SEP());
+ return par_cond_double(dupstring("-n"), s1);
+ } else
+ YYERROR(ecused);
+ }
+ condlex();
+ if (n_testargs == 2 && tok != STRING && tokstr && IS_DASH(s1[0])) {
+ /*
+ * Something like "test -z" followed by a token.
+ * We'll turn the token into a string (we've also
+ * checked it does have a string representation).
+ */
+ tok = STRING;
+ } else
+ while (COND_SEP())
+ condlex();
+ if (tok == INANG || tok == OUTANG) {
+ enum lextok xtok = tok;
+ do condlex(); while (COND_SEP());
+ if (tok != STRING)
+ YYERROR(ecused);
+ s3 = tokstr;
+ do condlex(); while (COND_SEP());
+ ecadd(WCB_COND((xtok == INANG ? COND_STRLT : COND_STRGTR), 0));
+ ecstr(s1);
+ ecstr(s3);
+ return 1;
+ }
+ if (tok != STRING) {
+ /*
+ * Check second argument in case semantics e.g. [ = -a = ]
+ * mean we have to go back and fix up the first one
+ */
+ if (tok != LEXERR) {
+ if (!dble || n_testargs)
+ return par_cond_double(dupstring("-n"), s1);
+ else
+ return par_cond_multi(s1, newlinklist());
+ } else
+ YYERROR(ecused);
+ }
+ s2 = tokstr;
+ if (!n_testargs)
+ dble = (s2 && IS_DASH(*s2) && !s2[2]);
+ incond++; /* parentheses do globbing */
+ do condlex(); while (COND_SEP());
+ incond--; /* parentheses do grouping */
+ if (tok == STRING && !dble) {
+ s3 = tokstr;
+ do condlex(); while (COND_SEP());
+ if (tok == STRING) {
+ LinkList l = newlinklist();
+
+ addlinknode(l, s2);
+ addlinknode(l, s3);
+
+ while (tok == STRING) {
+ addlinknode(l, tokstr);
+ do condlex(); while (COND_SEP());
+ }
+ return par_cond_multi(s1, l);
+ } else
+ return par_cond_triple(s1, s2, s3);
+ } else
+ return par_cond_double(s1, s2);
+}
+
+/**/
+static int
+par_cond_double(char *a, char *b)
+{
+ if (!IS_DASH(a[0]) || !a[1])
+ COND_ERROR("parse error: condition expected: %s", a);
+ else if (!a[2] && strspn(a+1, "abcdefgknoprstuvwxzhLONGS") == 1) {
+ ecadd(WCB_COND(a[1], 0));
+ ecstr(b);
+ } else {
+ ecadd(WCB_COND(COND_MOD, 1));
+ ecstr(a);
+ ecstr(b);
+ }
+ return 1;
+}
+
+/**/
+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 int
+par_cond_triple(char *a, char *b, char *c)
+{
+ int t0;
+
+ if ((b[0] == Equals || b[0] == '=') && !b[1]) {
+ ecadd(WCB_COND(COND_STREQ, 0));
+ ecstr(a);
+ ecstr(c);
+ ecadd(ecnpats++);
+ } else if ((b[0] == Equals || b[0] == '=') &&
+ (b[1] == Equals || b[1] == '=') && !b[2]) {
+ ecadd(WCB_COND(COND_STRDEQ, 0));
+ ecstr(a);
+ ecstr(c);
+ ecadd(ecnpats++);
+ } else if (b[0] == '!' && (b[1] == Equals || b[1] == '=') && !b[2]) {
+ ecadd(WCB_COND(COND_STRNEQ, 0));
+ ecstr(a);
+ ecstr(c);
+ ecadd(ecnpats++);
+ } else if ((b[0] == Equals || b[0] == '=') &&
+ (b[1] == '~' || b[1] == Tilde) && !b[2]) {
+ /* We become an implicit COND_MODI but do not provide the first
+ * item, it's skipped */
+ ecadd(WCB_COND(COND_REGEX, 0));
+ ecstr(a);
+ ecstr(c);
+ } else if (IS_DASH(b[0])) {
+ if ((t0 = get_cond_num(b + 1)) > -1) {
+ ecadd(WCB_COND(t0 + COND_NT, 0));
+ ecstr(a);
+ ecstr(c);
+ } else {
+ ecadd(WCB_COND(COND_MODI, 0));
+ ecstr(b);
+ ecstr(a);
+ ecstr(c);
+ }
+ } else if (IS_DASH(a[0]) && a[1]) {
+ ecadd(WCB_COND(COND_MOD, 2));
+ ecstr(a);
+ ecstr(b);
+ ecstr(c);
+ } else
+ COND_ERROR("condition expected: %s", b);
+
+ return 1;
+}
+
+/**/
+static int
+par_cond_multi(char *a, LinkList l)
+{
+ if (!IS_DASH(a[0]) || !a[1])
+ COND_ERROR("condition expected: %s", a);
+ else {
+ LinkNode n;
+
+ ecadd(WCB_COND(COND_MOD, countlinknodes(l)));
+ ecstr(a);
+ for (n = firstnode(l); n; incnode(n))
+ ecstr((char *) getdata(n));
+ }
+ return 1;
+}
+
+/**/
+static void
+yyerror(int noerr)
+{
+ int t0;
+ char *t;
+
+ if ((t = dupstring(zshlextext)))
+ untokenize(t);
+
+ for (t0 = 0; t0 != 20; t0++)
+ if (!t || !t[t0] || t[t0] == '\n')
+ break;
+ if (!(histdone & HISTFLAG_NOEXEC) && !(errflag & ERRFLAG_INT)) {
+ if (t0 == 20)
+ zwarn("parse error near `%l...'", t, 20);
+ else if (t0)
+ zwarn("parse error near `%l'", t, t0);
+ else
+ zwarn("parse error");
+ }
+ if (!noerr && noerrs != 2)
+ errflag |= ERRFLAG_ERROR;
+}
+
+/*
+ * Duplicate a programme list, on the heap if heap is 1, else
+ * in permanent storage.
+ *
+ * Be careful in case p is the Eprog for a function which will
+ * later be autoloaded. The shf element of the returned Eprog
+ * must be set appropriately by the caller. (Normally we create
+ * the Eprog in this case by using mkautofn.)
+ */
+
+/**/
+mod_export Eprog
+dupeprog(Eprog p, int heap)
+{
+ Eprog r;
+ int i;
+ Patprog *pp;
+
+ if (p == &dummy_eprog)
+ return p;
+
+ r = (heap ? (Eprog) zhalloc(sizeof(*r)) : (Eprog) zalloc(sizeof(*r)));
+ r->flags = (heap ? EF_HEAP : EF_REAL) | (p->flags & EF_RUN);
+ r->dump = NULL;
+ r->len = p->len;
+ r->npats = p->npats;
+ /*
+ * If Eprog is on the heap, reference count is not valid.
+ * Otherwise, initialise reference count to 1 so that a freeeprog()
+ * will delete it if it is not in use.
+ */
+ r->nref = heap ? -1 : 1;
+ pp = r->pats = (heap ? (Patprog *) hcalloc(r->len) :
+ (Patprog *) zshcalloc(r->len));
+ r->prog = (Wordcode) (r->pats + r->npats);
+ r->strs = ((char *) r->prog) + (p->strs - ((char *) p->prog));
+ memcpy(r->prog, p->prog, r->len - (p->npats * sizeof(Patprog)));
+ r->shf = NULL;
+
+ for (i = r->npats; i--; pp++)
+ *pp = dummy_patprog1;
+
+ return r;
+}
+
+
+/*
+ * Pair of functions to mark an Eprog as in use, and to delete it
+ * when it is no longer in use, by means of the reference count in
+ * then nref element.
+ *
+ * If nref is negative, the Eprog is on the heap and is never freed.
+ */
+
+/* Increase the reference count of an Eprog so it won't be deleted. */
+
+/**/
+mod_export void
+useeprog(Eprog p)
+{
+ if (p && p != &dummy_eprog && p->nref >= 0)
+ p->nref++;
+}
+
+/* Free an Eprog if we have finished with it */
+
+/**/
+mod_export void
+freeeprog(Eprog p)
+{
+ int i;
+ Patprog *pp;
+
+ if (p && p != &dummy_eprog) {
+ /* paranoia */
+ DPUTS(p->nref > 0 && (p->flags & EF_HEAP), "Heap EPROG has nref > 0");
+ DPUTS(p->nref < 0 && !(p->flags & EF_HEAP), "Real EPROG has nref < 0");
+ DPUTS(p->nref < -1, "Uninitialised EPROG nref");
+#ifdef MAX_FUNCTION_DEPTH
+ DPUTS(zsh_funcnest >=0 && p->nref > zsh_funcnest + 10,
+ "Overlarge EPROG nref");
+#endif
+ if (p->nref > 0 && !--p->nref) {
+ for (i = p->npats, pp = p->pats; i--; pp++)
+ freepatprog(*pp);
+ if (p->dump) {
+ decrdumpcount(p->dump);
+ zfree(p->pats, p->npats * sizeof(Patprog));
+ } else
+ zfree(p->pats, p->len);
+ zfree(p, sizeof(*p));
+ }
+ }
+}
+
+/**/
+char *
+ecgetstr(Estate s, int dup, int *tokflag)
+{
+ static char buf[4];
+ wordcode c = *s->pc++;
+ char *r;
+
+ if (c == 6 || c == 7)
+ r = "";
+ else if (c & 2) {
+ buf[0] = (char) ((c >> 3) & 0xff);
+ buf[1] = (char) ((c >> 11) & 0xff);
+ buf[2] = (char) ((c >> 19) & 0xff);
+ buf[3] = '\0';
+ r = dupstring(buf);
+ dup = EC_NODUP;
+ } else {
+ r = s->strs + (c >> 2);
+ }
+ if (tokflag)
+ *tokflag = (c & 1);
+
+ /*** Since function dump files are mapped read-only, avoiding to
+ * to duplicate strings when they don't contain tokens may fail
+ * when one of the many utility functions happens to write to
+ * one of the strings (without really modifying it).
+ * If that happens to you and you don't feel like debugging it,
+ * just change the line below to:
+ *
+ * return (dup ? dupstring(r) : r);
+ */
+
+ return ((dup == EC_DUP || (dup && (c & 1))) ? dupstring(r) : r);
+}
+
+/**/
+char *
+ecrawstr(Eprog p, Wordcode pc, int *tokflag)
+{
+ static char buf[4];
+ wordcode c = *pc;
+
+ if (c == 6 || c == 7) {
+ if (tokflag)
+ *tokflag = (c & 1);
+ return "";
+ } else if (c & 2) {
+ buf[0] = (char) ((c >> 3) & 0xff);
+ buf[1] = (char) ((c >> 11) & 0xff);
+ buf[2] = (char) ((c >> 19) & 0xff);
+ buf[3] = '\0';
+ if (tokflag)
+ *tokflag = (c & 1);
+ return buf;
+ } else {
+ if (tokflag)
+ *tokflag = (c & 1);
+ return p->strs + (c >> 2);
+ }
+}
+
+/**/
+char **
+ecgetarr(Estate s, int num, int dup, int *tokflag)
+{
+ char **ret, **rp;
+ int tf = 0, tmp = 0;
+
+ ret = rp = (char **) zhalloc((num + 1) * sizeof(char *));
+
+ while (num--) {
+ *rp++ = ecgetstr(s, dup, &tmp);
+ tf |= tmp;
+ }
+ *rp = NULL;
+ if (tokflag)
+ *tokflag = tf;
+
+ return ret;
+}
+
+/**/
+LinkList
+ecgetlist(Estate s, int num, int dup, int *tokflag)
+{
+ if (num) {
+ LinkList ret;
+ int i, tf = 0, tmp = 0;
+
+ ret = newsizedlist(num);
+ for (i = 0; i < num; i++) {
+ setsizednode(ret, i, ecgetstr(s, dup, &tmp));
+ tf |= tmp;
+ }
+ if (tokflag)
+ *tokflag = tf;
+ return ret;
+ }
+ if (tokflag)
+ *tokflag = 0;
+ return NULL;
+}
+
+/**/
+LinkList
+ecgetredirs(Estate s)
+{
+ LinkList ret = newlinklist();
+ wordcode code = *s->pc++;
+
+ while (wc_code(code) == WC_REDIR) {
+ Redir r = (Redir) zhalloc(sizeof(*r));
+
+ r->type = WC_REDIR_TYPE(code);
+ r->fd1 = *s->pc++;
+ r->name = ecgetstr(s, EC_DUP, NULL);
+ if (WC_REDIR_FROM_HEREDOC(code)) {
+ r->flags = REDIRF_FROM_HEREDOC;
+ r->here_terminator = ecgetstr(s, EC_DUP, NULL);
+ r->munged_here_terminator = ecgetstr(s, EC_DUP, NULL);
+ } else {
+ r->flags = 0;
+ r->here_terminator = NULL;
+ r->munged_here_terminator = NULL;
+ }
+ if (WC_REDIR_VARID(code))
+ r->varid = ecgetstr(s, EC_DUP, NULL);
+ else
+ r->varid = NULL;
+
+ addlinknode(ret, r);
+
+ code = *s->pc++;
+ }
+ s->pc--;
+
+ return ret;
+}
+
+/*
+ * Copy the consecutive set of redirections in the state at s.
+ * Return NULL if none, else an Eprog consisting only of the
+ * redirections from permanently allocated memory.
+ *
+ * s is left in the state ready for whatever follows the redirections.
+ */
+
+/**/
+Eprog
+eccopyredirs(Estate s)
+{
+ Wordcode pc = s->pc;
+ wordcode code = *pc;
+ int ncode, ncodes = 0, r;
+
+ if (wc_code(code) != WC_REDIR)
+ return NULL;
+
+ init_parse();
+
+ while (wc_code(code) == WC_REDIR) {
+#ifdef DEBUG
+ int type = WC_REDIR_TYPE(code);
+#endif
+
+ DPUTS(type == REDIR_HEREDOC || type == REDIR_HEREDOCDASH,
+ "unexpanded here document");
+
+ if (WC_REDIR_FROM_HEREDOC(code))
+ ncode = 5;
+ else
+ ncode = 3;
+ if (WC_REDIR_VARID(code))
+ ncode++;
+ pc += ncode;
+ ncodes += ncode;
+ code = *pc;
+ }
+ r = ecused;
+ ecispace(r, ncodes);
+
+ code = *s->pc;
+ while (wc_code(code) == WC_REDIR) {
+ s->pc++;
+
+ ecbuf[r++] = code;
+ /* fd1 */
+ ecbuf[r++] = *s->pc++;
+ /* name or HERE string */
+ /* No DUP needed as we'll copy into Eprog immediately below */
+ ecbuf[r++] = ecstrcode(ecgetstr(s, EC_NODUP, NULL));
+ if (WC_REDIR_FROM_HEREDOC(code))
+ {
+ /* terminator, raw */
+ ecbuf[r++] = ecstrcode(ecgetstr(s, EC_NODUP, NULL));
+ /* terminator, munged */
+ ecbuf[r++] = ecstrcode(ecgetstr(s, EC_NODUP, NULL));
+ }
+ if (WC_REDIR_VARID(code))
+ ecbuf[r++] = ecstrcode(ecgetstr(s, EC_NODUP, NULL));
+
+ code = *s->pc;
+ }
+
+ /* bld_eprog() appends a useful WC_END marker */
+ return bld_eprog(0);
+}
+
+/**/
+mod_export struct eprog dummy_eprog;
+
+static wordcode dummy_eprog_code;
+
+/**/
+void
+init_eprog(void)
+{
+ dummy_eprog_code = WCB_END();
+ dummy_eprog.len = sizeof(wordcode);
+ dummy_eprog.prog = &dummy_eprog_code;
+ dummy_eprog.strs = NULL;
+}
+
+/* Code for function dump files.
+ *
+ * Dump files consist of a header and the function bodies (the wordcode
+ * plus the string table) and that twice: once for the byte-order of the
+ * host the file was created on and once for the other byte-order. The
+ * header describes where the beginning of the `other' version is and it
+ * is up to the shell reading the file to decide which version it needs.
+ * This is done by checking if the first word is FD_MAGIC (then the
+ * shell reading the file has the same byte order as the one that created
+ * the file) or if it is FD_OMAGIC, then the `other' version has to be
+ * read.
+ * The header is the magic number, a word containing the flags (if the
+ * file should be mapped or read and if this header is the `other' one),
+ * the version string in a field of 40 characters and the descriptions
+ * for the functions in the dump file.
+ *
+ * NOTES:
+ * - This layout has to be kept; everything after it may be changed.
+ * - When incompatible changes are made, the FD_MAGIC and FD_OMAGIC
+ * numbers have to be changed.
+ *
+ * Each description consists of a struct fdhead followed by the name,
+ * aligned to sizeof(wordcode) (i.e. 4 bytes).
+ */
+
+#include "version.h"
+
+#define FD_EXT ".zwc"
+#define FD_MINMAP 4096
+
+#define FD_PRELEN 12
+#define FD_MAGIC 0x04050607
+#define FD_OMAGIC 0x07060504
+
+#define FDF_MAP 1
+#define FDF_OTHER 2
+
+typedef struct fdhead *FDHead;
+
+struct fdhead {
+ wordcode start; /* offset to function definition */
+ wordcode len; /* length of wordcode/strings */
+ wordcode npats; /* number of patterns needed */
+ wordcode strs; /* offset to strings */
+ wordcode hlen; /* header length (incl. name) */
+ wordcode flags; /* flags and offset to name tail */
+};
+
+#define fdheaderlen(f) (((Wordcode) (f))[FD_PRELEN])
+
+#define fdmagic(f) (((Wordcode) (f))[0])
+#define fdsetbyte(f,i,v) \
+ ((((unsigned char *) (((Wordcode) (f)) + 1))[i]) = ((unsigned char) (v)))
+#define fdbyte(f,i) ((wordcode) (((unsigned char *) (((Wordcode) (f)) + 1))[i]))
+#define fdflags(f) fdbyte(f, 0)
+#define fdsetflags(f,v) fdsetbyte(f, 0, v)
+#define fdother(f) (fdbyte(f, 1) + (fdbyte(f, 2) << 8) + (fdbyte(f, 3) << 16))
+#define fdsetother(f, o) \
+ do { \
+ fdsetbyte(f, 1, ((o) & 0xff)); \
+ fdsetbyte(f, 2, (((o) >> 8) & 0xff)); \
+ fdsetbyte(f, 3, (((o) >> 16) & 0xff)); \
+ } while (0)
+#define fdversion(f) ((char *) ((f) + 2))
+
+#define firstfdhead(f) ((FDHead) (((Wordcode) (f)) + FD_PRELEN))
+#define nextfdhead(f) ((FDHead) (((Wordcode) (f)) + (f)->hlen))
+
+#define fdhflags(f) (((FDHead) (f))->flags)
+#define fdhtail(f) (((FDHead) (f))->flags >> 2)
+#define fdhbldflags(f,t) ((f) | ((t) << 2))
+
+#define FDHF_KSHLOAD 1
+#define FDHF_ZSHLOAD 2
+
+#define fdname(f) ((char *) (((FDHead) (f)) + 1))
+
+/* This is used when building wordcode files. */
+
+typedef struct wcfunc *WCFunc;
+
+struct wcfunc {
+ char *name;
+ Eprog prog;
+ int flags;
+};
+
+/* Try to find the description for the given function name. */
+
+static FDHead
+dump_find_func(Wordcode h, char *name)
+{
+ FDHead n, e = (FDHead) (h + fdheaderlen(h));
+
+ for (n = firstfdhead(h); n < e; n = nextfdhead(n))
+ if (!strcmp(name, fdname(n) + fdhtail(n)))
+ return n;
+
+ return NULL;
+}
+
+/**/
+int
+bin_zcompile(char *nam, char **args, Options ops, UNUSED(int func))
+{
+ int map, flags, ret;
+ char *dump;
+
+ if ((OPT_ISSET(ops,'k') && OPT_ISSET(ops,'z')) ||
+ (OPT_ISSET(ops,'R') && OPT_ISSET(ops,'M')) ||
+ (OPT_ISSET(ops,'c') &&
+ (OPT_ISSET(ops,'U') || OPT_ISSET(ops,'k') || OPT_ISSET(ops,'z'))) ||
+ (!(OPT_ISSET(ops,'c') || OPT_ISSET(ops,'a')) && OPT_ISSET(ops,'m'))) {
+ zwarnnam(nam, "illegal combination of options");
+ return 1;
+ }
+ if ((OPT_ISSET(ops,'c') || OPT_ISSET(ops,'a')) && isset(KSHAUTOLOAD))
+ zwarnnam(nam, "functions will use zsh style autoloading");
+
+ flags = (OPT_ISSET(ops,'k') ? FDHF_KSHLOAD :
+ (OPT_ISSET(ops,'z') ? FDHF_ZSHLOAD : 0));
+
+ if (OPT_ISSET(ops,'t')) {
+ Wordcode f;
+
+ if (!*args) {
+ zwarnnam(nam, "too few arguments");
+ return 1;
+ }
+ if (!(f = load_dump_header(nam, (strsfx(FD_EXT, *args) ? *args :
+ dyncat(*args, FD_EXT)), 1)))
+ return 1;
+
+ if (args[1]) {
+ for (args++; *args; args++)
+ if (!dump_find_func(f, *args))
+ return 1;
+ return 0;
+ } else {
+ FDHead h, e = (FDHead) (f + fdheaderlen(f));
+
+ printf("zwc file (%s) for zsh-%s\n",
+ ((fdflags(f) & FDF_MAP) ? "mapped" : "read"), fdversion(f));
+ for (h = firstfdhead(f); h < e; h = nextfdhead(h))
+ printf("%s\n", fdname(h));
+ return 0;
+ }
+ }
+ if (!*args) {
+ zwarnnam(nam, "too few arguments");
+ return 1;
+ }
+ map = (OPT_ISSET(ops,'M') ? 2 : (OPT_ISSET(ops,'R') ? 0 : 1));
+
+ if (!args[1] && !(OPT_ISSET(ops,'c') || OPT_ISSET(ops,'a'))) {
+ queue_signals();
+ ret = build_dump(nam, dyncat(*args, FD_EXT), args, OPT_ISSET(ops,'U'),
+ map, flags);
+ unqueue_signals();
+ return ret;
+ }
+ dump = (strsfx(FD_EXT, *args) ? *args : dyncat(*args, FD_EXT));
+
+ queue_signals();
+ ret = ((OPT_ISSET(ops,'c') || OPT_ISSET(ops,'a')) ?
+ build_cur_dump(nam, dump, args + 1, OPT_ISSET(ops,'m'), map,
+ (OPT_ISSET(ops,'c') ? 1 : 0) |
+ (OPT_ISSET(ops,'a') ? 2 : 0)) :
+ build_dump(nam, dump, args + 1, OPT_ISSET(ops,'U'), map, flags));
+ unqueue_signals();
+
+ return ret;
+}
+
+/* Load the header of a dump file. Returns NULL if the file isn't a
+ * valid dump file. */
+
+/**/
+static Wordcode
+load_dump_header(char *nam, char *name, int err)
+{
+ int fd, v = 1;
+ wordcode buf[FD_PRELEN + 1];
+
+ if ((fd = open(name, O_RDONLY)) < 0) {
+ if (err)
+ zwarnnam(nam, "can't open zwc file: %s", name);
+ return NULL;
+ }
+ if (read(fd, buf, (FD_PRELEN + 1) * sizeof(wordcode)) !=
+ ((FD_PRELEN + 1) * sizeof(wordcode)) ||
+ (v = (fdmagic(buf) != FD_MAGIC && fdmagic(buf) != FD_OMAGIC)) ||
+ strcmp(fdversion(buf), ZSH_VERSION)) {
+ if (err) {
+ if (!v) {
+ zwarnnam(nam, "zwc file has wrong version (zsh-%s): %s",
+ fdversion(buf), name);
+ } else
+ zwarnnam(nam, "invalid zwc file: %s" , name);
+ }
+ close(fd);
+ return NULL;
+ } else {
+ int len;
+ Wordcode head;
+
+ if (fdmagic(buf) == FD_MAGIC) {
+ len = fdheaderlen(buf) * sizeof(wordcode);
+ head = (Wordcode) zhalloc(len);
+ }
+ else {
+ int o = fdother(buf);
+
+ if (lseek(fd, o, 0) == -1 ||
+ read(fd, buf, (FD_PRELEN + 1) * sizeof(wordcode)) !=
+ ((FD_PRELEN + 1) * sizeof(wordcode))) {
+ zwarnnam(nam, "invalid zwc file: %s" , name);
+ close(fd);
+ return NULL;
+ }
+ len = fdheaderlen(buf) * sizeof(wordcode);
+ head = (Wordcode) zhalloc(len);
+ }
+ memcpy(head, buf, (FD_PRELEN + 1) * sizeof(wordcode));
+
+ len -= (FD_PRELEN + 1) * sizeof(wordcode);
+ if (read(fd, head + (FD_PRELEN + 1), len) != len) {
+ close(fd);
+ zwarnnam(nam, "invalid zwc file: %s" , name);
+ return NULL;
+ }
+ close(fd);
+ return head;
+ }
+}
+
+/* Swap the bytes in a wordcode. */
+
+static void
+fdswap(Wordcode p, int n)
+{
+ wordcode c;
+
+ for (; n--; p++) {
+ c = *p;
+ *p = (((c & 0xff) << 24) |
+ ((c & 0xff00) << 8) |
+ ((c & 0xff0000) >> 8) |
+ ((c & 0xff000000) >> 24));
+ }
+}
+
+/* Write a dump file. */
+
+static void
+write_dump(int dfd, LinkList progs, int map, int hlen, int tlen)
+{
+ LinkNode node;
+ WCFunc wcf;
+ int other = 0, ohlen, tmp;
+ wordcode pre[FD_PRELEN];
+ char *tail, *n;
+ struct fdhead head;
+ Eprog prog;
+
+ if (map == 1)
+ map = (tlen >= FD_MINMAP);
+
+ memset(pre, 0, sizeof(wordcode) * FD_PRELEN);
+
+ for (ohlen = hlen; ; hlen = ohlen) {
+ fdmagic(pre) = (other ? FD_OMAGIC : FD_MAGIC);
+ fdsetflags(pre, ((map ? FDF_MAP : 0) | other));
+ fdsetother(pre, tlen);
+ strcpy(fdversion(pre), ZSH_VERSION);
+ write_loop(dfd, (char *)pre, FD_PRELEN * sizeof(wordcode));
+
+ for (node = firstnode(progs); node; incnode(node)) {
+ wcf = (WCFunc) getdata(node);
+ n = wcf->name;
+ prog = wcf->prog;
+ head.start = hlen;
+ hlen += (prog->len - (prog->npats * sizeof(Patprog)) +
+ sizeof(wordcode) - 1) / sizeof(wordcode);
+ head.len = prog->len - (prog->npats * sizeof(Patprog));
+ head.npats = prog->npats;
+ head.strs = prog->strs - ((char *) prog->prog);
+ head.hlen = (sizeof(struct fdhead) / sizeof(wordcode)) +
+ (strlen(n) + sizeof(wordcode)) / sizeof(wordcode);
+ if ((tail = strrchr(n, '/')))
+ tail++;
+ else
+ tail = n;
+ head.flags = fdhbldflags(wcf->flags, (tail - n));
+ if (other)
+ fdswap((Wordcode) &head, sizeof(head) / sizeof(wordcode));
+ write_loop(dfd, (char *)&head, sizeof(head));
+ tmp = strlen(n) + 1;
+ write_loop(dfd, n, tmp);
+ if ((tmp &= (sizeof(wordcode) - 1)))
+ write_loop(dfd, (char *)&head, sizeof(wordcode) - tmp);
+ }
+ for (node = firstnode(progs); node; incnode(node)) {
+ prog = ((WCFunc) getdata(node))->prog;
+ tmp = (prog->len - (prog->npats * sizeof(Patprog)) +
+ sizeof(wordcode) - 1) / sizeof(wordcode);
+ if (other)
+ fdswap(prog->prog, (((Wordcode) prog->strs) - prog->prog));
+ write_loop(dfd, (char *)prog->prog, tmp * sizeof(wordcode));
+ }
+ if (other)
+ break;
+ other = FDF_OTHER;
+ }
+}
+
+/**/
+static int
+build_dump(char *nam, char *dump, char **files, int ali, int map, int flags)
+{
+ int dfd, fd, hlen, tlen, flen, ona = noaliases;
+ LinkList progs;
+ char *file;
+ Eprog prog;
+ WCFunc wcf;
+
+ if (!strsfx(FD_EXT, dump))
+ dump = dyncat(dump, FD_EXT);
+
+ unlink(dump);
+ if ((dfd = open(dump, O_WRONLY|O_CREAT, 0444)) < 0) {
+ zwarnnam(nam, "can't write zwc file: %s", dump);
+ return 1;
+ }
+ progs = newlinklist();
+ noaliases = ali;
+
+ for (hlen = FD_PRELEN, tlen = 0; *files; files++) {
+ struct stat st;
+
+ if (check_cond(*files, "k")) {
+ flags = (flags & ~(FDHF_KSHLOAD | FDHF_ZSHLOAD)) | FDHF_KSHLOAD;
+ continue;
+ } else if (check_cond(*files, "z")) {
+ flags = (flags & ~(FDHF_KSHLOAD | FDHF_ZSHLOAD)) | FDHF_ZSHLOAD;
+ continue;
+ }
+ if ((fd = open(*files, O_RDONLY)) < 0 ||
+ fstat(fd, &st) != 0 || !S_ISREG(st.st_mode) ||
+ (flen = lseek(fd, 0, 2)) == -1) {
+ if (fd >= 0)
+ close(fd);
+ close(dfd);
+ zwarnnam(nam, "can't open file: %s", *files);
+ noaliases = ona;
+ unlink(dump);
+ return 1;
+ }
+ file = (char *) zalloc(flen + 1);
+ file[flen] = '\0';
+ lseek(fd, 0, 0);
+ if (read(fd, file, flen) != flen) {
+ close(fd);
+ close(dfd);
+ zfree(file, flen);
+ zwarnnam(nam, "can't read file: %s", *files);
+ noaliases = ona;
+ unlink(dump);
+ return 1;
+ }
+ close(fd);
+ file = metafy(file, flen, META_REALLOC);
+
+ if (!(prog = parse_string(file, 1)) || errflag) {
+ errflag &= ~ERRFLAG_ERROR;
+ close(dfd);
+ zfree(file, flen);
+ zwarnnam(nam, "can't read file: %s", *files);
+ noaliases = ona;
+ unlink(dump);
+ return 1;
+ }
+ zfree(file, flen);
+
+ wcf = (WCFunc) zhalloc(sizeof(*wcf));
+ wcf->name = *files;
+ wcf->prog = prog;
+ wcf->flags = ((prog->flags & EF_RUN) ? FDHF_KSHLOAD : flags);
+ addlinknode(progs, wcf);
+
+ flen = (strlen(*files) + sizeof(wordcode)) / sizeof(wordcode);
+ hlen += (sizeof(struct fdhead) / sizeof(wordcode)) + flen;
+
+ tlen += (prog->len - (prog->npats * sizeof(Patprog)) +
+ sizeof(wordcode) - 1) / sizeof(wordcode);
+ }
+ noaliases = ona;
+
+ tlen = (tlen + hlen) * sizeof(wordcode);
+
+ write_dump(dfd, progs, map, hlen, tlen);
+
+ close(dfd);
+
+ return 0;
+}
+
+static int
+cur_add_func(char *nam, Shfunc shf, LinkList names, LinkList progs,
+ int *hlen, int *tlen, int what)
+{
+ Eprog prog;
+ WCFunc wcf;
+
+ if (shf->node.flags & PM_UNDEFINED) {
+ int ona = noaliases;
+
+ if (!(what & 2)) {
+ zwarnnam(nam, "function is not loaded: %s", shf->node.nam);
+ return 1;
+ }
+ noaliases = (shf->node.flags & PM_UNALIASED);
+ if (!(prog = getfpfunc(shf->node.nam, NULL, NULL, NULL, 0)) ||
+ prog == &dummy_eprog) {
+ noaliases = ona;
+ zwarnnam(nam, "can't load function: %s", shf->node.nam);
+ return 1;
+ }
+ if (prog->dump)
+ prog = dupeprog(prog, 1);
+ noaliases = ona;
+ } else {
+ if (!(what & 1)) {
+ zwarnnam(nam, "function is already loaded: %s", shf->node.nam);
+ return 1;
+ }
+ prog = dupeprog(shf->funcdef, 1);
+ }
+ wcf = (WCFunc) zhalloc(sizeof(*wcf));
+ wcf->name = shf->node.nam;
+ wcf->prog = prog;
+ wcf->flags = ((prog->flags & EF_RUN) ? FDHF_KSHLOAD : FDHF_ZSHLOAD);
+ addlinknode(progs, wcf);
+ addlinknode(names, shf->node.nam);
+
+ *hlen += ((sizeof(struct fdhead) / sizeof(wordcode)) +
+ ((strlen(shf->node.nam) + sizeof(wordcode)) / sizeof(wordcode)));
+ *tlen += (prog->len - (prog->npats * sizeof(Patprog)) +
+ sizeof(wordcode) - 1) / sizeof(wordcode);
+
+ return 0;
+}
+
+/**/
+static int
+build_cur_dump(char *nam, char *dump, char **names, int match, int map,
+ int what)
+{
+ int dfd, hlen, tlen;
+ LinkList progs, lnames;
+ Shfunc shf = NULL;
+
+ if (!strsfx(FD_EXT, dump))
+ dump = dyncat(dump, FD_EXT);
+
+ unlink(dump);
+ if ((dfd = open(dump, O_WRONLY|O_CREAT, 0444)) < 0) {
+ zwarnnam(nam, "can't write zwc file: %s", dump);
+ return 1;
+ }
+ progs = newlinklist();
+ lnames = newlinklist();
+
+ hlen = FD_PRELEN;
+ tlen = 0;
+
+ if (!*names) {
+ int i;
+ HashNode hn;
+
+ for (i = 0; i < shfunctab->hsize; i++)
+ for (hn = shfunctab->nodes[i]; hn; hn = hn->next)
+ if (cur_add_func(nam, (Shfunc) hn, lnames, progs,
+ &hlen, &tlen, what)) {
+ errflag &= ~ERRFLAG_ERROR;
+ close(dfd);
+ unlink(dump);
+ return 1;
+ }
+ } else if (match) {
+ char *pat;
+ Patprog pprog;
+ int i;
+ HashNode hn;
+
+ for (; *names; names++) {
+ tokenize(pat = dupstring(*names));
+ /* Signal-safe here, caller queues signals */
+ if (!(pprog = patcompile(pat, PAT_STATIC, NULL))) {
+ zwarnnam(nam, "bad pattern: %s", *names);
+ close(dfd);
+ unlink(dump);
+ return 1;
+ }
+ for (i = 0; i < shfunctab->hsize; i++)
+ for (hn = shfunctab->nodes[i]; hn; hn = hn->next)
+ if (!linknodebydatum(lnames, hn->nam) &&
+ pattry(pprog, hn->nam) &&
+ cur_add_func(nam, (Shfunc) hn, lnames, progs,
+ &hlen, &tlen, what)) {
+ errflag &= ~ERRFLAG_ERROR;
+ close(dfd);
+ unlink(dump);
+ return 1;
+ }
+ }
+ } else {
+ for (; *names; names++) {
+ if (errflag ||
+ !(shf = (Shfunc) shfunctab->getnode(shfunctab, *names))) {
+ zwarnnam(nam, "unknown function: %s", *names);
+ errflag &= ~ERRFLAG_ERROR;
+ close(dfd);
+ unlink(dump);
+ return 1;
+ }
+ if (cur_add_func(nam, shf, lnames, progs, &hlen, &tlen, what)) {
+ errflag &= ~ERRFLAG_ERROR;
+ close(dfd);
+ unlink(dump);
+ return 1;
+ }
+ }
+ }
+ if (empty(progs)) {
+ zwarnnam(nam, "no functions");
+ errflag &= ~ERRFLAG_ERROR;
+ close(dfd);
+ unlink(dump);
+ return 1;
+ }
+ tlen = (tlen + hlen) * sizeof(wordcode);
+
+ write_dump(dfd, progs, map, hlen, tlen);
+
+ close(dfd);
+
+ return 0;
+}
+
+/**/
+#if defined(HAVE_SYS_MMAN_H) && defined(HAVE_MMAP) && defined(HAVE_MUNMAP)
+
+#include <sys/mman.h>
+
+/**/
+#if defined(MAP_SHARED) && defined(PROT_READ)
+
+/**/
+#define USE_MMAP 1
+
+/**/
+#endif
+/**/
+#endif
+
+/**/
+#ifdef USE_MMAP
+
+/* List of dump files mapped. */
+
+static FuncDump dumps;
+
+/**/
+static int
+zwcstat(char *filename, struct stat *buf)
+{
+ if (stat(filename, buf)) {
+#ifdef HAVE_FSTAT
+ FuncDump f;
+
+ for (f = dumps; f; f = f->next) {
+ if (!strncmp(filename, f->filename, strlen(f->filename)) &&
+ !fstat(f->fd, buf))
+ return 0;
+ }
+#endif
+ return 1;
+ } else return 0;
+}
+
+/* Load a dump file (i.e. map it). */
+
+static void
+load_dump_file(char *dump, struct stat *sbuf, int other, int len)
+{
+ FuncDump d;
+ Wordcode addr;
+ int fd, off, mlen;
+
+ if (other) {
+ static size_t pgsz = 0;
+
+ if (!pgsz) {
+
+#ifdef _SC_PAGESIZE
+ pgsz = sysconf(_SC_PAGESIZE); /* SVR4 */
+#else
+# ifdef _SC_PAGE_SIZE
+ pgsz = sysconf(_SC_PAGE_SIZE); /* HPUX */
+# else
+ pgsz = getpagesize();
+# endif
+#endif
+
+ pgsz--;
+ }
+ off = len & ~pgsz;
+ mlen = len + (len - off);
+ } else {
+ off = 0;
+ mlen = len;
+ }
+ if ((fd = open(dump, O_RDONLY)) < 0)
+ return;
+
+ fd = movefd(fd);
+ if (fd == -1)
+ return;
+
+ if ((addr = (Wordcode) mmap(NULL, mlen, PROT_READ, MAP_SHARED, fd, off)) ==
+ ((Wordcode) -1)) {
+ close(fd);
+ return;
+ }
+ d = (FuncDump) zalloc(sizeof(*d));
+ d->next = dumps;
+ dumps = d;
+ d->dev = sbuf->st_dev;
+ d->ino = sbuf->st_ino;
+ d->fd = fd;
+#ifdef FD_CLOEXEC
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+#endif
+ d->map = addr + (other ? (len - off) / sizeof(wordcode) : 0);
+ d->addr = addr;
+ d->len = len;
+ d->count = 0;
+ d->filename = ztrdup(dump);
+}
+
+#else
+
+#define zwcstat(f, b) (!!stat(f, b))
+
+/**/
+#endif
+
+/* Try to load a function from one of the possible wordcode files for it.
+ * The first argument is a element of $fpath, the second one is the name
+ * of the function searched and the last one is the possible name for the
+ * uncompiled function file (<path>/<func>). */
+
+/**/
+Eprog
+try_dump_file(char *path, char *name, char *file, int *ksh, int test_only)
+{
+ Eprog prog;
+ struct stat std, stc, stn;
+ int rd, rc, rn;
+ char *dig, *wc;
+
+ if (strsfx(FD_EXT, path)) {
+ queue_signals();
+ prog = check_dump_file(path, NULL, name, ksh, test_only);
+ unqueue_signals();
+ return prog;
+ }
+ dig = dyncat(path, FD_EXT);
+ wc = dyncat(file, FD_EXT);
+
+ rd = zwcstat(dig, &std);
+ rc = stat(wc, &stc);
+ rn = stat(file, &stn);
+
+ /* See if there is a digest file for the directory, it is younger than
+ * both the uncompiled function file and its compiled version (or they
+ * don't exist) and the digest file contains the definition for the
+ * function. */
+ queue_signals();
+ if (!rd &&
+ (rc || std.st_mtime >= stc.st_mtime) &&
+ (rn || std.st_mtime >= stn.st_mtime) &&
+ (prog = check_dump_file(dig, &std, name, ksh, test_only))) {
+ unqueue_signals();
+ return prog;
+ }
+ /* No digest file. Now look for the per-function compiled file. */
+ if (!rc &&
+ (rn || stc.st_mtime >= stn.st_mtime) &&
+ (prog = check_dump_file(wc, &stc, name, ksh, test_only))) {
+ unqueue_signals();
+ return prog;
+ }
+ /* No compiled file for the function. The caller (getfpfunc() will
+ * check if the directory contains the uncompiled file for it. */
+ unqueue_signals();
+ return NULL;
+}
+
+/* Almost the same, but for sourced files. */
+
+/**/
+Eprog
+try_source_file(char *file)
+{
+ Eprog prog;
+ struct stat stc, stn;
+ int rc, rn;
+ char *wc, *tail;
+
+ if ((tail = strrchr(file, '/')))
+ tail++;
+ else
+ tail = file;
+
+ if (strsfx(FD_EXT, file)) {
+ queue_signals();
+ prog = check_dump_file(file, NULL, tail, NULL, 0);
+ unqueue_signals();
+ return prog;
+ }
+ wc = dyncat(file, FD_EXT);
+
+ rc = stat(wc, &stc);
+ rn = stat(file, &stn);
+
+ queue_signals();
+ if (!rc && (rn || stc.st_mtime >= stn.st_mtime) &&
+ (prog = check_dump_file(wc, &stc, tail, NULL, 0))) {
+ unqueue_signals();
+ return prog;
+ }
+ unqueue_signals();
+ return NULL;
+}
+
+/* See if `file' names a wordcode dump file and that contains the
+ * definition for the function `name'. If so, return an eprog for it. */
+
+/**/
+static Eprog
+check_dump_file(char *file, struct stat *sbuf, char *name, int *ksh,
+ int test_only)
+{
+ int isrec = 0;
+ Wordcode d;
+ FDHead h;
+ FuncDump f;
+ struct stat lsbuf;
+
+ if (!sbuf) {
+ if (zwcstat(file, &lsbuf))
+ return NULL;
+ sbuf = &lsbuf;
+ }
+
+#ifdef USE_MMAP
+
+ rec:
+
+#endif
+
+ d = NULL;
+
+#ifdef USE_MMAP
+
+ for (f = dumps; f; f = f->next)
+ if (f->dev == sbuf->st_dev && f->ino == sbuf->st_ino) {
+ d = f->map;
+ break;
+ }
+
+#else
+
+ f = NULL;
+
+#endif
+
+ if (!f && (isrec || !(d = load_dump_header(NULL, file, 0))))
+ return NULL;
+
+ if ((h = dump_find_func(d, name))) {
+ /* Found the name. If the file is already mapped, return the eprog,
+ * otherwise map it and just go up. */
+ if (test_only)
+ {
+ /* This is all we need. Just return dummy. */
+ return &dummy_eprog;
+ }
+
+#ifdef USE_MMAP
+
+ if (f) {
+ Eprog prog = (Eprog) zalloc(sizeof(*prog));
+ Patprog *pp;
+ int np;
+
+ prog->flags = EF_MAP;
+ prog->len = h->len;
+ prog->npats = np = h->npats;
+ prog->nref = 1; /* allocated from permanent storage */
+ prog->pats = pp = (Patprog *) zalloc(np * sizeof(Patprog));
+ prog->prog = f->map + h->start;
+ prog->strs = ((char *) prog->prog) + h->strs;
+ prog->shf = NULL;
+ prog->dump = f;
+
+ incrdumpcount(f);
+
+ while (np--)
+ *pp++ = dummy_patprog1;
+
+ if (ksh)
+ *ksh = ((fdhflags(h) & FDHF_KSHLOAD) ? 2 :
+ ((fdhflags(h) & FDHF_ZSHLOAD) ? 0 : 1));
+
+ return prog;
+ } else if (fdflags(d) & FDF_MAP) {
+ load_dump_file(file, sbuf, (fdflags(d) & FDF_OTHER), fdother(d));
+ isrec = 1;
+ goto rec;
+ } else
+
+#endif
+
+ {
+ Eprog prog;
+ Patprog *pp;
+ int np, fd, po = h->npats * sizeof(Patprog);
+
+ if ((fd = open(file, O_RDONLY)) < 0 ||
+ lseek(fd, ((h->start * sizeof(wordcode)) +
+ ((fdflags(d) & FDF_OTHER) ? fdother(d) : 0)), 0) < 0) {
+ if (fd >= 0)
+ close(fd);
+ return NULL;
+ }
+ d = (Wordcode) zalloc(h->len + po);
+
+ if (read(fd, ((char *) d) + po, h->len) != (int)h->len) {
+ close(fd);
+ zfree(d, h->len);
+
+ return NULL;
+ }
+ close(fd);
+
+ prog = (Eprog) zalloc(sizeof(*prog));
+
+ prog->flags = EF_REAL;
+ prog->len = h->len + po;
+ prog->npats = np = h->npats;
+ prog->nref = 1; /* allocated from permanent storage */
+ prog->pats = pp = (Patprog *) d;
+ prog->prog = (Wordcode) (((char *) d) + po);
+ prog->strs = ((char *) prog->prog) + h->strs;
+ prog->shf = NULL;
+ prog->dump = f;
+
+ while (np--)
+ *pp++ = dummy_patprog1;
+
+ if (ksh)
+ *ksh = ((fdhflags(h) & FDHF_KSHLOAD) ? 2 :
+ ((fdhflags(h) & FDHF_ZSHLOAD) ? 0 : 1));
+
+ return prog;
+ }
+ }
+ return NULL;
+}
+
+#ifdef USE_MMAP
+
+/* Increment the reference counter for a dump file. */
+
+/**/
+void
+incrdumpcount(FuncDump f)
+{
+ f->count++;
+}
+
+/**/
+static void
+freedump(FuncDump f)
+{
+ munmap((void *) f->addr, f->len);
+ zclose(f->fd);
+ zsfree(f->filename);
+ zfree(f, sizeof(*f));
+}
+
+/* Decrement the reference counter for a dump file. If zero, unmap the file. */
+
+/**/
+void
+decrdumpcount(FuncDump f)
+{
+ f->count--;
+ if (!f->count) {
+ FuncDump p, q;
+
+ for (q = NULL, p = dumps; p && p != f; q = p, p = p->next);
+ if (p) {
+ if (q)
+ q->next = p->next;
+ else
+ dumps = p->next;
+ freedump(f);
+ }
+ }
+}
+
+#ifndef FD_CLOEXEC
+/**/
+mod_export void
+closedumps(void)
+{
+ while (dumps) {
+ FuncDump p = dumps->next;
+ freedump(dumps);
+ dumps = p;
+ }
+}
+#endif
+
+#else
+
+void
+incrdumpcount(FuncDump f)
+{
+}
+
+void
+decrdumpcount(FuncDump f)
+{
+}
+
+#ifndef FD_CLOEXEC
+/**/
+mod_export void
+closedumps(void)
+{
+}
+#endif
+
+#endif
+
+/**/
+int
+dump_autoload(char *nam, char *file, int on, Options ops, int func)
+{
+ Wordcode h;
+ FDHead n, e;
+ Shfunc shf;
+ int ret = 0;
+
+ if (!strsfx(FD_EXT, file))
+ file = dyncat(file, FD_EXT);
+
+ if (!(h = load_dump_header(nam, file, 1)))
+ return 1;
+
+ for (n = firstfdhead(h), e = (FDHead) (h + fdheaderlen(h)); n < e;
+ n = nextfdhead(n)) {
+ shf = (Shfunc) zshcalloc(sizeof *shf);
+ shf->node.flags = on;
+ shf->funcdef = mkautofn(shf);
+ shf->sticky = NULL;
+ shfunctab->addnode(shfunctab, ztrdup(fdname(n) + fdhtail(n)), shf);
+ if (OPT_ISSET(ops,'X') && eval_autoload(shf, shf->node.nam, ops, func))
+ ret = 1;
+ }
+ return ret;
+}
diff --git a/dotfiles/system/.zsh/modules/Src/pattern.c b/dotfiles/system/.zsh/modules/Src/pattern.c
new file mode 100644
index 0000000..737f5cd
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/pattern.c
@@ -0,0 +1,4336 @@
+/*
+ * pattern.c - pattern matching
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1999 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.
+ *
+ * Pattern matching code derived from the regexp library by Henry
+ * Spencer, which has the following copyright.
+ *
+ * Copyright (c) 1986 by University of Toronto.
+ * Written by Henry Spencer. Not derived from licensed software.
+ *
+ * Permission is granted to anyone to use this software for any
+ * purpose on any computer system, and to redistribute it freely,
+ * subject to the following restrictions:
+ *
+ * 1. The author is not responsible for the consequences of use of
+ * this software, no matter how awful, even if they arise
+ * from defects in it.
+ *
+ * 2. The origin of this software must not be misrepresented, either
+ * by explicit claim or by omission.
+ *
+ * 3. Altered versions must be plainly marked as such, and must not
+ * be misrepresented as being the original software.
+ *
+ * Eagle-eyed readers will notice this is an altered version. Incredibly
+ * sharp-eyed readers might even find bits that weren't altered.
+ *
+ *
+ * And I experienced a sense that, like certain regular
+ * expressions, seemed to match the day from beginning to end, so
+ * that I did not need to identify the parenthesised subexpression
+ * that told of dawn, nor the group of characters that indicated
+ * the moment when my grandfather returned home with news of
+ * Swann's departure for Paris; and the whole length of the month
+ * of May, as if matched by a closure, fitted into the buffer of my
+ * life with no sign of overflowing, turning the days, like a
+ * procession of insects that could consist of this or that
+ * species, into a random and unstructured repetition of different
+ * sequences, anchored from the first day of the month to the last
+ * in the same fashion as the weeks when I knew I would not see
+ * Gilberte and would search in vain for any occurrences of the
+ * string in the avenue of hawthorns by Tansonville, without my
+ * having to delimit explicitly the start or finish of the pattern.
+ *
+ * M. Proust, "In Search of Lost Files",
+ * bk I, "The Walk by Bourne's Place".
+ */
+
+#include "zsh.mdh"
+
+/*
+ * The following union is used mostly for alignment purposes.
+ * Normal nodes are longs, while certain nodes take a char * as an argument;
+ * here we make sure that they both work out to the same length.
+ * The compiled regexp we construct consists of upats stuck together;
+ * anything else to be added (strings, numbers) is stuck after and
+ * then aligned to a whole number of upat units.
+ *
+ * Note also that offsets are in terms of the sizes of these things.
+ */
+union upat {
+ long l;
+ unsigned char *p;
+};
+
+typedef union upat *Upat;
+
+#include "pattern.pro"
+
+/* Number of active parenthesized expressions allowed in backreferencing */
+#define NSUBEXP 9
+
+/* definition number opnd? meaning */
+#define P_END 0x00 /* no End of program. */
+#define P_EXCSYNC 0x01 /* no Test if following exclude already failed */
+#define P_EXCEND 0x02 /* no Test if exclude matched orig branch */
+#define P_BACK 0x03 /* no Match "", "next" ptr points backward. */
+#define P_EXACTLY 0x04 /* lstr Match this string. */
+#define P_NOTHING 0x05 /* no Match empty string. */
+#define P_ONEHASH 0x06 /* node Match this (simple) thing 0 or more times. */
+#define P_TWOHASH 0x07 /* node Match this (simple) thing 1 or more times. */
+#define P_GFLAGS 0x08 /* long Match nothing and set globbing flags */
+#define P_ISSTART 0x09 /* no Match start of string. */
+#define P_ISEND 0x0a /* no Match end of string. */
+#define P_COUNTSTART 0x0b /* no Initialise P_COUNT */
+#define P_COUNT 0x0c /* 3*long uc* node Match a number of repetitions */
+/* numbered so we can test bit 5 for a branch */
+#define P_BRANCH 0x20 /* node Match this alternative, or the next... */
+#define P_WBRANCH 0x21 /* uc* node P_BRANCH, but match at least 1 char */
+/* excludes are also branches, but have bit 4 set, too */
+#define P_EXCLUDE 0x30 /* uc* node Exclude this from previous branch */
+#define P_EXCLUDP 0x31 /* uc* node Exclude, using full file path so far */
+/* numbered so we can test bit 6 so as not to match initial '.' */
+#define P_ANY 0x40 /* no Match any one character. */
+#define P_ANYOF 0x41 /* str Match any character in this string. */
+#define P_ANYBUT 0x42 /* str Match any character not in this string. */
+#define P_STAR 0x43 /* no Match any set of characters. */
+#define P_NUMRNG 0x44 /* zr, zr Match a numeric range. */
+#define P_NUMFROM 0x45 /* zr Match a number >= X */
+#define P_NUMTO 0x46 /* zr Match a number <= X */
+#define P_NUMANY 0x47 /* no Match any set of decimal digits */
+/* spaces left for P_OPEN+n,... for backreferences */
+#define P_OPEN 0x80 /* no Mark this point in input as start of n. */
+#define P_CLOSE 0x90 /* no Analogous to OPEN. */
+/*
+ * no no argument
+ * zr the range type zrange_t: may be zlong or unsigned long
+ * char a single char
+ * uc* a pointer to unsigned char, used at run time and initialised
+ * to NULL.
+ * str null-terminated, metafied string
+ * lstr length as long then string, not null-terminated, unmetafied.
+ */
+
+/*
+ * Notes on usage:
+ * P_WBRANCH: This works like a branch and is used in complex closures,
+ * to ensure we don't succeed on a zero-length match of the pattern,
+ * since that would cause an infinite loop. We do this by recording
+ * the positions where we have already tried to match. See the
+ * P_WBRANCH test in patmatch().
+ *
+ * P_ANY, P_ANYOF: the operand is a null terminated
+ * string. Normal characters match as expected. Characters
+ * in the range Meta+PP_ALPHA..Meta+PP_UNKWN do the appropriate
+ * Posix range tests. This relies on imeta returning true for these
+ * characters. We treat unknown POSIX ranges as never matching.
+ * PP_RANGE means the next two (possibly metafied) characters form
+ * the limits of a range to test; it's too much like hard work to
+ * expand the range.
+ *
+ * P_EXCLUDE, P_EXCSYNC, PEXCEND: P_EXCLUDE appears in the pattern like
+ * P_BRANCH, but applies to the immediately preceding branch. The code in
+ * the corresponding branch is followed by a P_EXCSYNC, which simply
+ * acts as a marker that a P_EXCLUDE comes next. The P_EXCLUDE
+ * has a pointer to char embeded in it, which works
+ * like P_WBRANCH: if we get to the P_EXCSYNC, and we already matched
+ * up to the same position, fail. Thus we are forced to backtrack
+ * on closures in the P_BRANCH if the first attempt was excluded.
+ * Corresponding to P_EXCSYNC in the original branch, there is a
+ * P_EXCEND in the exclusion. If we get to this point, and we did
+ * *not* match in the original branch, the exclusion itself fails,
+ * otherwise it succeeds since we know the tail already matches,
+ * so P_EXCEND is the end of the exclusion test.
+ * The whole sorry mess looks like this, where the upper lines
+ * show the linkage of the branches, and the lower shows the linkage
+ * of their pattern arguments.
+ *
+ * --------------------- ----------------------
+ * ^ v ^ v
+ * ( <BRANCH>:apat-><EXCSYNC> <EXCLUDE>:excpat-><EXCEND> ) tail
+ * ^
+ * | |
+ * --------------------------------------
+ *
+ * P_EXCLUDP: this behaves exactly like P_EXCLUDE, with the sole exception
+ * that we prepend the path so far to the exclude pattern. This is
+ * for top level file globs, e.g. ** / *.c~*foo.c
+ * ^ I had to leave this space
+ * P_NUM*: zl is a zlong if that is 64-bit, else an unsigned long.
+ *
+ * P_COUNTSTART, P_COUNT: a P_COUNTSTART flags the start of a quantified
+ * closure (#cN,M) and is used to initialise the count. Executing
+ * the pattern leads back to the P_COUNT, while the next links of the
+ * P_COUNTSTART and P_COUNT lead to the tail of the pattern:
+ *
+ * ----------------
+ * v ^
+ * <COUNTSTART><COUNT>pattern<BACK> tail
+ * v v ^
+ * ------------------------
+ */
+
+#define P_OP(p) ((p)->l & 0xff)
+#define P_NEXT(p) ((p)->l >> 8)
+#define P_OPERAND(p) ((p) + 1)
+#define P_ISBRANCH(p) ((p)->l & 0x20)
+#define P_ISEXCLUDE(p) (((p)->l & 0x30) == 0x30)
+#define P_NOTDOT(p) ((p)->l & 0x40)
+
+/* Specific to lstr type, i.e. P_EXACTLY. */
+#define P_LS_LEN(p) ((p)[1].l) /* can be used as lvalue */
+#define P_LS_STR(p) ((char *)((p) + 2))
+
+/* Specific to P_COUNT: arguments as offset in nodes from operator */
+#define P_CT_CURRENT (1) /* Current count */
+#define P_CT_MIN (2) /* Minimum count */
+#define P_CT_MAX (3) /* Maximum count, -1 for none */
+#define P_CT_PTR (4) /* Pointer to last match start */
+#define P_CT_OPERAND (5) /* Operand of P_COUNT */
+
+/* Flags needed when pattern is executed */
+#define P_SIMPLE 0x01 /* Simple enough to be #/## operand. */
+#define P_HSTART 0x02 /* Starts with # or ##'d pattern. */
+#define P_PURESTR 0x04 /* Can be matched with a strcmp */
+
+#if defined(ZSH_64_BIT_TYPE) || defined(LONG_IS_64_BIT)
+typedef zlong zrange_t;
+#define ZRANGE_T_IS_SIGNED (1)
+#define ZRANGE_MAX ZLONG_MAX
+#else
+typedef unsigned long zrange_t;
+#define ZRANGE_MAX ULONG_MAX
+#endif
+
+#ifdef MULTIBYTE_SUPPORT
+/*
+ * Handle a byte that's not part of a valid character.
+ *
+ * This range in Unicode is recommended for purposes of this
+ * kind as it corresponds to invalid characters.
+ *
+ * Note that this strictly only works if wchar_t represents
+ * Unicode code points, which isn't necessarily true; however,
+ * converting an invalid character into an unknown format is
+ * a bit tricky...
+ */
+#define WCHAR_INVALID(ch) \
+ ((wchar_t) (0xDC00 + STOUC(ch)))
+#endif /* MULTIBYTE_SUPPORT */
+
+/*
+ * Array of characters corresponding to zpc_chars enum, which it must match.
+ */
+static const char zpc_chars[ZPC_COUNT] = {
+ '/', '\0', Bar, Outpar, Tilde, Inpar, Quest, Star, Inbrack, Inang,
+ Hat, Pound, Bnullkeep, Quest, Star, '+', Bang, '!', '@'
+};
+
+/*
+ * Corresponding strings used in enable/disable -p.
+ * NULL means no way of turning this on or off.
+ */
+/**/
+mod_export const char *zpc_strings[ZPC_COUNT] = {
+ NULL, NULL, "|", NULL, "~", "(", "?", "*", "[", "<",
+ "^", "#", NULL, "?(", "*(", "+(", "!(", "\\!(", "@("
+};
+
+/*
+ * Corresponding array of pattern disables as set by the user
+ * using "disable -p".
+ */
+/**/
+mod_export char zpc_disables[ZPC_COUNT];
+
+/*
+ * Stack of saved (compressed) zpc_disables for function scope.
+ */
+
+static struct zpc_disables_save *zpc_disables_stack;
+
+/*
+ * Characters which terminate a simple string (ZPC_COUNT) or
+ * an entire pattern segment (the first ZPC_SEG_COUNT).
+ * Each entry is either the corresponding character in zpc_chars
+ * or Marker which is guaranteed not to match a character in a
+ * pattern we are compiling.
+ *
+ * The complete list indicates characters that are special, so e.g.
+ * (testchar == special[ZPC_TILDE]) succeeds only if testchar is a Tilde
+ * *and* Tilde is currently special.
+ */
+
+/**/
+char zpc_special[ZPC_COUNT];
+
+/* Default size for pattern buffer */
+#define P_DEF_ALLOC 256
+
+/* Flags used in compilation */
+static char *patstart, *patparse; /* input pointers */
+static int patnpar; /* () count */
+static char *patcode; /* point of code emission */
+static long patsize; /* size of code */
+static char *patout; /* start of code emission string */
+static long patalloc; /* size allocated for same */
+
+/* Flags used in both compilation and execution */
+static int patflags; /* flags passed down to patcompile */
+static int patglobflags; /* globbing flags & approx */
+
+/*
+ * Increment pointer to metafied multibyte string.
+ */
+#ifdef MULTIBYTE_SUPPORT
+typedef wint_t patint_t;
+
+#define PEOF WEOF
+
+#define METACHARINC(x) ((void)metacharinc(&x))
+
+/*
+ * TODO: the shiftstate isn't well handled; we don't guarantee
+ * to maintain it properly between characters. If we don't
+ * need it we should use mbtowc() instead.
+ */
+static mbstate_t shiftstate;
+
+/*
+ * Multibyte version: it's (almost) as easy to return the
+ * value as not, so do so since we sometimes need it..
+ */
+static wchar_t
+metacharinc(char **x)
+{
+ char *inptr = *x;
+ char inchar;
+ size_t ret = MB_INVALID;
+ wchar_t wc;
+
+ /*
+ * Cheat if the top bit isn't set. This is second-guessing
+ * the library, but we know for sure that if the character
+ * set doesn't have the property that all bytes with the 8th
+ * bit clear are single characters then we are stuffed.
+ */
+ if (!(patglobflags & GF_MULTIBYTE) || !(STOUC(*inptr) & 0x80))
+ {
+ if (itok(*inptr))
+ inchar = ztokens[*inptr++ - Pound];
+ else if (*inptr == Meta) {
+ inptr++;
+ inchar = *inptr++ ^ 32;
+ } else {
+ inchar = *inptr++;
+ }
+ *x = inptr;
+ return (wchar_t)STOUC(inchar);
+ }
+
+ while (*inptr) {
+ if (itok(*inptr))
+ inchar = ztokens[*inptr++ - Pound];
+ else if (*inptr == Meta) {
+ inptr++;
+ inchar = *inptr++ ^ 32;
+ } else {
+ inchar = *inptr++;
+ }
+ ret = mbrtowc(&wc, &inchar, 1, &shiftstate);
+
+ if (ret == MB_INVALID)
+ break;
+ if (ret == MB_INCOMPLETE)
+ continue;
+ *x = inptr;
+ return wc;
+ }
+
+ /* Error. */
+ /* Reset the shift state for next time. */
+ memset(&shiftstate, 0, sizeof(shiftstate));
+ return WCHAR_INVALID(*(*x)++);
+}
+
+#else
+typedef int patint_t;
+
+#define PEOF EOF
+
+#define METACHARINC(x) ((void)((x) += (*(x) == Meta) ? 2 : 1))
+#endif
+
+/*
+ * Return unmetafied char from string (x is any char *).
+ * Used with MULTIBYTE_SUPPORT if the GF_MULTIBYTE is not
+ * in effect.
+ */
+#define UNMETA(x) (*(x) == Meta ? (x)[1] ^ 32 : *(x))
+
+/* Add n more characters, ensuring there is enough space. */
+
+enum {
+ PA_NOALIGN = 1,
+ PA_UNMETA = 2
+};
+
+/**/
+static void
+patadd(char *add, int ch, long n, int paflags)
+{
+ /* Make sure everything gets aligned unless we get PA_NOALIGN. */
+ long newpatsize = patsize + n;
+ if (!(paflags & PA_NOALIGN))
+ newpatsize = (newpatsize + sizeof(union upat) - 1) &
+ ~(sizeof(union upat) - 1);
+ if (patalloc < newpatsize) {
+ long newpatalloc =
+ 2*(newpatsize > patalloc ? newpatsize : patalloc);
+ patout = (char *)zrealloc((char *)patout, newpatalloc);
+ patcode = patout + patsize;
+ patalloc = newpatalloc;
+ }
+ patsize = newpatsize;
+ if (add) {
+ if (paflags & PA_UNMETA) {
+ /*
+ * Unmetafy and untokenize the string as we go.
+ * The Meta characters in add aren't counted in n.
+ */
+ while (n--) {
+ if (itok(*add))
+ *patcode++ = ztokens[*add++ - Pound];
+ else if (*add == Meta) {
+ add++;
+ *patcode++ = *add++ ^ 32;
+ } else {
+ *patcode++ = *add++;
+ }
+ }
+ } else {
+ while (n--)
+ *patcode++ = *add++;
+ }
+ } else
+ *patcode++ = ch;
+ patcode = patout + patsize;
+}
+
+static long rn_offs;
+/* operates on pointers to union upat, returns a pointer */
+#define PATNEXT(p) ((rn_offs = P_NEXT(p)) ? \
+ (P_OP(p) == P_BACK) ? \
+ ((p)-rn_offs) : ((p)+rn_offs) : NULL)
+
+/*
+ * Set up zpc_special with characters that end a string segment.
+ * "Marker" cannot occur in the pattern we are compiling so
+ * is used to mark "invalid".
+ */
+static void
+patcompcharsset(void)
+{
+ char *spp, *disp;
+ int i;
+
+ /* Initialise enabled special characters */
+ memcpy(zpc_special, zpc_chars, ZPC_COUNT);
+ /* Apply user disables from disable -p */
+ for (i = 0, spp = zpc_special, disp = zpc_disables;
+ i < ZPC_COUNT;
+ i++, spp++, disp++) {
+ if (*disp)
+ *spp = Marker;
+ }
+
+ if (!isset(EXTENDEDGLOB)) {
+ /* Extended glob characters are not active */
+ zpc_special[ZPC_TILDE] = zpc_special[ZPC_HAT] =
+ zpc_special[ZPC_HASH] = Marker;
+ }
+ if (!isset(KSHGLOB)) {
+ /*
+ * Ksh glob characters are not active.
+ * * and ? are shared with normal globbing, but for their
+ * use here we are looking for a following Inpar.
+ */
+ zpc_special[ZPC_KSH_QUEST] = zpc_special[ZPC_KSH_STAR] =
+ zpc_special[ZPC_KSH_PLUS] = zpc_special[ZPC_KSH_BANG] =
+ zpc_special[ZPC_KSH_BANG2] = zpc_special[ZPC_KSH_AT] = Marker;
+ }
+ /*
+ * Note that if we are using KSHGLOB, then we test for a following
+ * Inpar, not zpc_special[ZPC_INPAR]: the latter makes an Inpar on
+ * its own active. The zpc_special[ZPC_KSH_*] followed by any old Inpar
+ * discriminate ksh globbing.
+ */
+ if (isset(SHGLOB)) {
+ /*
+ * Grouping and numeric ranges are not valid.
+ * We do allow alternation, however; it's needed for
+ * "case". This may not be entirely consistent.
+ *
+ * Don't disable Outpar: we may need to match the end of KSHGLOB
+ * parentheses and it would be difficult to tell them apart.
+ */
+ zpc_special[ZPC_INPAR] = zpc_special[ZPC_INANG] = Marker;
+ }
+}
+
+/* Called before parsing a set of file matchs to initialize flags */
+
+/**/
+void
+patcompstart(void)
+{
+ patcompcharsset();
+ if (isset(CASEGLOB))
+ patglobflags = 0;
+ else
+ patglobflags = GF_IGNCASE;
+ if (isset(MULTIBYTE))
+ patglobflags |= GF_MULTIBYTE;
+}
+
+/*
+ * Top level pattern compilation subroutine
+ * exp is a null-terminated, metafied string.
+ * inflags is an or of some PAT_* flags.
+ * endexp, if non-null, is set to a pointer to the end of the
+ * part of exp which was compiled. This is used when
+ * compiling patterns for directories which must be
+ * matched recursively.
+ */
+
+/**/
+mod_export Patprog
+patcompile(char *exp, int inflags, char **endexp)
+{
+ int flags = 0;
+ long len = 0;
+ long startoff;
+ Upat pscan;
+ char *lng, *strp = NULL;
+ Patprog p;
+
+ queue_signals();
+
+ startoff = sizeof(struct patprog);
+ /* Ensure alignment of start of program string */
+ startoff = (startoff + sizeof(union upat) - 1) & ~(sizeof(union upat) - 1);
+
+ /* Allocate reasonable sized chunk if none, reduce size if too big */
+ if (patalloc != P_DEF_ALLOC)
+ patout = (char *)zrealloc(patout, patalloc = P_DEF_ALLOC);
+ patcode = patout + startoff;
+ patsize = patcode - patout;
+ patstart = patparse = exp;
+ /*
+ * Note global patnpar numbers parentheses 1..9, while patnpar
+ * in struct is actual count of parentheses.
+ */
+ patnpar = 1;
+ patflags = inflags & ~(PAT_PURES|PAT_HAS_EXCLUDP);
+
+ if (!(patflags & PAT_FILE)) {
+ patcompcharsset();
+ zpc_special[ZPC_SLASH] = Marker;
+ remnulargs(patparse);
+ if (isset(MULTIBYTE))
+ patglobflags = GF_MULTIBYTE;
+ else
+ patglobflags = 0;
+ }
+ if (patflags & PAT_LCMATCHUC)
+ patglobflags |= GF_LCMATCHUC;
+ /*
+ * Have to be set now, since they get updated during compilation.
+ */
+ ((Patprog)patout)->globflags = patglobflags;
+
+ if (!(patflags & PAT_ANY)) {
+ /* Look for a really pure string, with no tokens at all. */
+ if (!(patglobflags & ~GF_MULTIBYTE)
+#ifdef __CYGWIN__
+ /*
+ * If the OS treats files case-insensitively and we
+ * are looking at files, we don't need to use pattern
+ * matching to find the file.
+ */
+ || (!(patglobflags & ~GF_IGNCASE) && (patflags & PAT_FILE))
+#endif
+ )
+ {
+ /*
+ * Waah! I wish I understood this.
+ * Empty metafied strings have an initial Nularg.
+ * This never corresponds to a real character in
+ * a glob pattern or string, so skip it.
+ */
+ if (*exp == Nularg)
+ exp++;
+ for (strp = exp; *strp &&
+ (!(patflags & PAT_FILE) || *strp != '/') && !itok(*strp);
+ strp++)
+ ;
+ }
+ if (!strp || (*strp && *strp != '/')) {
+ /* No, do normal compilation. */
+ strp = NULL;
+ if (patcompswitch(0, &flags) == 0) {
+ unqueue_signals();
+ return NULL;
+ }
+ } else {
+ /*
+ * Yes, copy the string, and skip compilation altogether.
+ * Null terminate for the benefit of globbing.
+ * Leave metafied both for globbing and for our own
+ * efficiency.
+ */
+ patparse = strp;
+ len = strp - exp;
+ patadd(exp, 0, len + 1, 0);
+ patout[startoff + len] = '\0';
+ patflags |= PAT_PURES;
+ }
+ }
+
+ /* end of compilation: safe to use pointers */
+ p = (Patprog)patout;
+ p->startoff = startoff;
+ p->patstartch = '\0';
+ p->globend = patglobflags;
+ p->flags = patflags;
+ p->mustoff = 0;
+ p->size = patsize;
+ p->patmlen = len;
+ p->patnpar = patnpar-1;
+
+ if (!strp) {
+ pscan = (Upat)(patout + startoff);
+
+ if (!(patflags & PAT_ANY) && P_OP(PATNEXT(pscan)) == P_END) {
+ /* only one top level choice */
+ pscan = P_OPERAND(pscan);
+
+ if (flags & P_PURESTR) {
+ /*
+ * The pattern can be matched with a simple strncmp/strcmp.
+ * Careful in case we've overwritten the node for the next ptr.
+ */
+ char *dst = patout + startoff;
+ Upat next;
+ p->flags |= PAT_PURES;
+ for (; pscan; pscan = next) {
+ next = PATNEXT(pscan);
+ if (P_OP(pscan) == P_EXACTLY) {
+ char *opnd = P_LS_STR(pscan), *mtest;
+ long oplen = P_LS_LEN(pscan), ilen;
+ int nmeta = 0;
+ /*
+ * Unfortunately we unmetafied the string
+ * and we need to put any metacharacters
+ * back now we know it's a pure string.
+ * This shouldn't happen too often, it's
+ * just that there are some cases such
+ * as . and .. in files where we really
+ * need a pure string even if there are
+ * pattern characters flying around.
+ */
+ for (mtest = opnd, ilen = oplen; ilen;
+ mtest++, ilen--)
+ if (imeta(*mtest))
+ nmeta++;
+ if (nmeta) {
+ patadd(NULL, 0, nmeta, 0);
+ p = (Patprog)patout;
+ opnd = dupstring_wlen(opnd, oplen);
+ dst = patout + startoff;
+ }
+
+ while (oplen--) {
+ if (imeta(*opnd)) {
+ *dst++ = Meta;
+ *dst++ = *opnd++ ^ 32;
+ } else {
+ *dst++ = *opnd++;
+ }
+ }
+ /* Only one string in a PAT_PURES, so now done. */
+ break;
+ }
+ }
+ p->size = dst - patout;
+ /* patmlen is really strlen. We don't need a null. */
+ p->patmlen = p->size - startoff;
+ } else {
+ /* starting point info */
+ if (P_OP(pscan) == P_EXACTLY && !p->globflags &&
+ P_LS_LEN(pscan))
+ p->patstartch = *P_LS_STR(pscan);
+ /*
+ * Find the longest literal string in something expensive.
+ * This is itself not all that cheap if we have
+ * case-insensitive matching or approximation, so don't.
+ */
+ if ((flags & P_HSTART) && !p->globflags) {
+ lng = NULL;
+ len = 0;
+ for (; pscan; pscan = PATNEXT(pscan))
+ if (P_OP(pscan) == P_EXACTLY &&
+ P_LS_LEN(pscan) >= len) {
+ lng = P_LS_STR(pscan);
+ len = P_LS_LEN(pscan);
+ }
+ if (lng) {
+ p->mustoff = lng - patout;
+ p->patmlen = len;
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * The pattern was compiled in a fixed buffer: unless told otherwise,
+ * we stick the compiled pattern on the heap. This is necessary
+ * for files where we will often be compiling multiple segments at once.
+ * But if we get the ZDUP flag we always put it in zalloc()ed memory.
+ */
+ if (patflags & PAT_ZDUP) {
+ Patprog newp = (Patprog)zalloc(patsize);
+ memcpy((char *)newp, (char *)p, patsize);
+ p = newp;
+ } else if (!(patflags & PAT_STATIC)) {
+ Patprog newp = (Patprog)zhalloc(patsize);
+ memcpy((char *)newp, (char *)p, patsize);
+ p = newp;
+ }
+
+ if (endexp)
+ *endexp = patparse;
+
+ unqueue_signals();
+ return p;
+}
+
+/*
+ * Main body or parenthesized subexpression in pattern
+ * Parenthesis (and any ksh_glob gubbins) will have been removed.
+ */
+
+/**/
+static long
+patcompswitch(int paren, int *flagp)
+{
+ long starter, br, ender, excsync = 0;
+ int parno = 0;
+ int flags, gfchanged = 0;
+ long savglobflags = (long)patglobflags;
+ Upat ptr;
+
+ *flagp = 0;
+
+ if (paren && (patglobflags & GF_BACKREF) && patnpar <= NSUBEXP) {
+ /*
+ * parenthesized: make an open node.
+ * We can only refer to the first nine parentheses.
+ * For any others, we just use P_OPEN on its own; there's
+ * no gain in arbitrarily limiting the number of parentheses.
+ */
+ parno = patnpar++;
+ starter = patnode(P_OPEN + parno);
+ } else
+ starter = 0;
+
+ br = patnode(P_BRANCH);
+ if (!patcompbranch(&flags, paren))
+ return 0;
+ if (patglobflags != (int)savglobflags)
+ gfchanged++;
+ if (starter)
+ pattail(starter, br);
+ else
+ starter = br;
+
+ *flagp |= flags & (P_HSTART|P_PURESTR);
+
+ while (*patparse == zpc_chars[ZPC_BAR] ||
+ (*patparse == zpc_special[ZPC_TILDE] &&
+ (patparse[1] == '/' ||
+ !memchr(zpc_special, patparse[1], ZPC_SEG_COUNT)))) {
+ int tilde = *patparse++ == zpc_special[ZPC_TILDE];
+ long gfnode = 0, newbr;
+
+ *flagp &= ~P_PURESTR;
+
+ if (tilde) {
+ union upat up;
+ /* excsync remembers the P_EXCSYNC node before a chain of
+ * exclusions: all point back to this. only the
+ * original (non-excluded) branch gets a trailing P_EXCSYNC.
+ */
+ if (!excsync) {
+ excsync = patnode(P_EXCSYNC);
+ patoptail(br, excsync);
+ }
+ /*
+ * By default, approximations are turned off in exclusions:
+ * we need to do this here as otherwise the code compiling
+ * the exclusion doesn't know if the flags have really
+ * changed if the error count gets restored.
+ */
+ patglobflags &= ~0xff;
+ if (!(patflags & PAT_FILET) || paren) {
+ br = patnode(P_EXCLUDE);
+ } else {
+ /*
+ * At top level (paren == 0) in a file glob !(patflags
+ * &PAT_FILET) do the exclusion prepending the file path
+ * so far. We need to flag this to avoid unnecessarily
+ * copying the path.
+ */
+ br = patnode(P_EXCLUDP);
+ patflags |= PAT_HAS_EXCLUDP;
+ }
+ up.p = NULL;
+ patadd((char *)&up, 0, sizeof(up), 0);
+ /* / is not treated as special if we are at top level */
+ if (!paren && zpc_special[ZPC_SLASH] == '/') {
+ tilde++;
+ zpc_special[ZPC_SLASH] = Marker;
+ }
+ } else {
+ excsync = 0;
+ br = patnode(P_BRANCH);
+ /*
+ * The position of the following statements means globflags
+ * set in the main branch carry over to the exclusion.
+ */
+ if (!paren) {
+ patglobflags = 0;
+ if (((Patprog)patout)->globflags) {
+ /*
+ * If at top level, we need to reinitialize flags to zero,
+ * since (#i)foo|bar only applies to foo and we stuck
+ * the #i into the global flags.
+ * We could have done it so that they only got set in the
+ * first branch, but it's quite convenient having any
+ * global flags set in the header and not buried in the
+ * pattern. (Or maybe it isn't and we should
+ * forget this bit and always stick in an explicit GFLAGS
+ * statement instead of using the header.)
+ * Also, this can't happen for file globs where there are
+ * no top-level |'s.
+ *
+ * No gfchanged, as nothing to follow branch at top
+ * level.
+ */
+ union upat up;
+ gfnode = patnode(P_GFLAGS);
+ up.l = patglobflags;
+ patadd((char *)&up, 0, sizeof(union upat), 0);
+ }
+ } else {
+ patglobflags = (int)savglobflags;
+ }
+ }
+ newbr = patcompbranch(&flags, paren);
+ if (tilde == 2) {
+ /* restore special treatment of / */
+ zpc_special[ZPC_SLASH] = '/';
+ }
+ if (!newbr)
+ return 0;
+ if (gfnode)
+ pattail(gfnode, newbr);
+ if (!tilde && patglobflags != (int)savglobflags)
+ gfchanged++;
+ pattail(starter, br);
+ if (excsync)
+ patoptail(br, patnode(P_EXCEND));
+ *flagp |= flags & P_HSTART;
+ }
+
+ /*
+ * Make a closing node, hooking it to the end.
+ * Note that we can't optimize P_NOTHING out here, since another
+ * branch at that point would indicate the current choices continue,
+ * which they don't.
+ */
+ ender = patnode(paren ? parno ? P_CLOSE+parno : P_NOTHING : P_END);
+ pattail(starter, ender);
+
+ /*
+ * Hook the tails of the branches to the closing node,
+ * except for exclusions which terminate where they are.
+ */
+ for (ptr = (Upat)patout + starter; ptr; ptr = PATNEXT(ptr))
+ if (!P_ISEXCLUDE(ptr))
+ patoptail(ptr-(Upat)patout, ender);
+
+ /* check for proper termination */
+ if ((paren && *patparse++ != Outpar) ||
+ (!paren && *patparse &&
+ !((patflags & PAT_FILE) && *patparse == '/')))
+ return 0;
+
+ if (paren && gfchanged) {
+ /*
+ * Restore old values of flags when leaving parentheses.
+ * gfchanged detects a change in any branch (except exclusions
+ * which are separate), since we need to emit this even if
+ * a later branch happened to put the flags back.
+ */
+ pattail(ender, patnode(P_GFLAGS));
+ patglobflags = (int)savglobflags;
+ patadd((char *)&savglobflags, 0, sizeof(long), 0);
+ }
+
+ return starter;
+}
+
+/*
+ * Compile something ended by Bar, Outpar, Tilde, or end of string.
+ * Note the BRANCH or EXCLUDE tag must already have been omitted:
+ * this returns the position of the operand of that.
+ */
+
+/**/
+static long
+patcompbranch(int *flagp, int paren)
+{
+ long chain, latest = 0, starter;
+ int flags = 0;
+
+ *flagp = P_PURESTR;
+
+ starter = chain = 0;
+ while (!memchr(zpc_special, *patparse, ZPC_SEG_COUNT) ||
+ (*patparse == zpc_special[ZPC_TILDE] && patparse[1] != '/' &&
+ memchr(zpc_special, patparse[1], ZPC_SEG_COUNT))) {
+ if ((*patparse == zpc_special[ZPC_INPAR] &&
+ patparse[1] == zpc_special[ZPC_HASH]) ||
+ (*patparse == zpc_special[ZPC_KSH_AT] && patparse[1] == Inpar &&
+ patparse[2] == zpc_special[ZPC_HASH])) {
+ /* Globbing flags. */
+ char *pp1 = patparse;
+ int oldglobflags = patglobflags, ignore;
+ long assert;
+ patparse += (*patparse == '@') ? 3 : 2;
+ if (!patgetglobflags(&patparse, &assert, &ignore))
+ return 0;
+ if (!ignore) {
+ if (assert) {
+ /*
+ * Start/end assertion looking like flags, but
+ * actually handled as a normal node
+ */
+ latest = patnode(assert);
+ flags = 0;
+ } else {
+ if (pp1 == patstart) {
+ /* Right at start of pattern, the simplest case.
+ * Put them into the flags and don't emit anything.
+ */
+ ((Patprog)patout)->globflags = patglobflags;
+ continue;
+ } else if (!*patparse) {
+ /* Right at the end, so just leave the flags for
+ * the next Patprog in the chain to pick up.
+ */
+ break;
+ }
+ /*
+ * Otherwise, we have to stick them in as a pattern
+ * matching nothing.
+ */
+ if (oldglobflags != patglobflags) {
+ /* Flags changed */
+ union upat up;
+ latest = patnode(P_GFLAGS);
+ up.l = patglobflags;
+ patadd((char *)&up, 0, sizeof(union upat), 0);
+ } else {
+ /* No effect. */
+ continue;
+ }
+ }
+ } else if (!*patparse)
+ break;
+ else
+ continue;
+ } else if (*patparse == zpc_special[ZPC_HAT]) {
+ /*
+ * ^pat: anything but pat. For proper backtracking,
+ * etc., we turn this into (*~pat), except without the
+ * parentheses.
+ */
+ patparse++;
+ latest = patcompnot(0, &flags);
+ } else
+ latest = patcomppiece(&flags, paren);
+ if (!latest)
+ return 0;
+ if (!starter)
+ starter = latest;
+ if (!(flags & P_PURESTR))
+ *flagp &= ~P_PURESTR;
+ if (!chain)
+ *flagp |= flags & P_HSTART;
+ else
+ pattail(chain, latest);
+ chain = latest;
+ }
+ /* check if there was nothing in the loop, i.e. () */
+ if (!chain)
+ starter = patnode(P_NOTHING);
+
+ return starter;
+}
+
+/* get glob flags, return 1 for success, 0 for failure */
+
+/**/
+int
+patgetglobflags(char **strp, long *assertp, int *ignore)
+{
+ char *nptr, *ptr = *strp;
+ zlong ret;
+
+ *assertp = 0;
+ *ignore = 1;
+ /* (#X): assumes we are still positioned on the first X */
+ for (; *ptr && *ptr != Outpar; ptr++) {
+ if (*ptr == 'q') {
+ /* Glob qualifiers, ignored in pattern code */
+ while (*ptr && *ptr != Outpar)
+ ptr++;
+ break;
+ } else {
+ *ignore = 0;
+ switch (*ptr) {
+ case 'a':
+ /* Approximate matching, max no. of errors follows */
+ ret = zstrtol(++ptr, &nptr, 10);
+ /*
+ * We can't have more than 254, because we need 255 to
+ * mark 254 errors in wbranch and exclude sync strings
+ * (hypothetically --- hope no-one tries it).
+ */
+ if (ret < 0 || ret > 254 || ptr == nptr)
+ return 0;
+ patglobflags = (patglobflags & ~0xff) | (ret & 0xff);
+ ptr = nptr-1;
+ break;
+
+ case 'l':
+ /* Lowercase in pattern matches lower or upper in target */
+ patglobflags = (patglobflags & ~GF_IGNCASE) | GF_LCMATCHUC;
+ break;
+
+ case 'i':
+ /* Fully case insensitive */
+ patglobflags = (patglobflags & ~GF_LCMATCHUC) | GF_IGNCASE;
+ break;
+
+ case 'I':
+ /* Restore case sensitivity */
+ patglobflags &= ~(GF_LCMATCHUC|GF_IGNCASE);
+ break;
+
+ case 'b':
+ /* Make backreferences */
+ patglobflags |= GF_BACKREF;
+ break;
+
+ case 'B':
+ /* Don't make backreferences */
+ patglobflags &= ~GF_BACKREF;
+ break;
+
+ case 'm':
+ /* Make references to complete match */
+ patglobflags |= GF_MATCHREF;
+ break;
+
+ case 'M':
+ /* Don't */
+ patglobflags &= ~GF_MATCHREF;
+ break;
+
+ case 's':
+ *assertp = P_ISSTART;
+ break;
+
+ case 'e':
+ *assertp = P_ISEND;
+ break;
+
+ case 'u':
+ patglobflags |= GF_MULTIBYTE;
+ break;
+
+ case 'U':
+ patglobflags &= ~GF_MULTIBYTE;
+ break;
+
+ default:
+ return 0;
+ }
+ }
+ }
+ if (*ptr != Outpar)
+ return 0;
+ /* Start/end assertions must appear on their own. */
+ if (*assertp && (*strp)[1] != Outpar)
+ return 0;
+ *strp = ptr + 1;
+ return 1;
+}
+
+
+static const char *colon_stuffs[] = {
+ "alpha", "alnum", "ascii", "blank", "cntrl", "digit", "graph",
+ "lower", "print", "punct", "space", "upper", "xdigit", "IDENT",
+ "IFS", "IFSSPACE", "WORD", "INCOMPLETE", "INVALID", NULL
+};
+
+/*
+ * Handle the guts of a [:stuff:] character class element.
+ * start is the beginning of "stuff" and len is its length.
+ * This code is exported for the benefit of completion matching.
+ */
+
+/**/
+mod_export int
+range_type(char *start, int len)
+{
+ const char **csp;
+
+ for (csp = colon_stuffs; *csp; csp++) {
+ if (strlen(*csp) == len && !strncmp(start, *csp, len))
+ return (csp - colon_stuffs) + PP_FIRST;
+ }
+
+ return PP_UNKWN;
+}
+
+
+/*
+ * Convert the contents of a [...] or [^...] expression (just the
+ * ... part) back into a string. This is used by compfiles -p/-P
+ * for some reason. The compiled form (a metafied string) is
+ * passed in rangestr.
+ *
+ * If outstr is non-NULL the compiled form is placed there. It
+ * must be sufficiently long. A terminating NULL is appended.
+ *
+ * Return the length required, not including the terminating NULL.
+ *
+ * TODO: this is non-multibyte for now. It will need to be defined
+ * appropriately with MULTIBYTE_SUPPORT when the completion matching
+ * code catches up.
+ */
+
+/**/
+mod_export int
+pattern_range_to_string(char *rangestr, char *outstr)
+{
+ int len = 0;
+
+ while (*rangestr) {
+ if (imeta(STOUC(*rangestr))) {
+ int swtype = STOUC(*rangestr) - STOUC(Meta);
+
+ if (swtype == 0) {
+ /* Ordindary metafied character */
+ if (outstr)
+ {
+ *outstr++ = Meta;
+ *outstr++ = rangestr[1] ^ 32;
+ }
+ len += 2;
+ rangestr += 2;
+ } else if (swtype == PP_RANGE) {
+ /* X-Y range */
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ if (*rangestr == Meta) {
+ if (outstr) {
+ *outstr++ = Meta;
+ *outstr++ = rangestr[1];
+ }
+ len += 2;
+ rangestr += 2;
+ } else {
+ if (outstr)
+ *outstr++ = *rangestr;
+ len++;
+ rangestr++;
+ }
+
+ if (i == 0) {
+ if (outstr)
+ *outstr++ = '-';
+ len++;
+ }
+ }
+ } else if (swtype >= PP_FIRST && swtype <= PP_LAST) {
+ /* [:stuff:]; we need to output [: and :] */
+ const char *found = colon_stuffs[swtype - PP_FIRST];
+ int newlen = strlen(found);
+ if (outstr) {
+ strcpy(outstr, "[:");
+ outstr += 2;
+ memcpy(outstr, found, newlen);
+ outstr += newlen;
+ strcpy(outstr, ":]");
+ outstr += 2;
+ }
+ len += newlen + 4;
+ rangestr++;
+ } else {
+ /* shouldn't happen */
+ DPUTS(1, "BUG: unknown PP_ code in pattern range");
+ rangestr++;
+ }
+ } else {
+ /* ordinary character, guaranteed no Meta handling needed */
+ if (outstr)
+ *outstr++ = *rangestr;
+ len++;
+ rangestr++;
+ }
+ }
+
+ if (outstr)
+ *outstr = '\0';
+ return len;
+}
+
+/*
+ * compile a chunk such as a literal string or a [...] followed
+ * by a possible hash operator
+ */
+
+/**/
+static long
+patcomppiece(int *flagp, int paren)
+{
+ long starter = 0, next, op, opnd;
+ int flags, flags2, kshchar, len, ch, patch, nmeta;
+ int hash, count;
+ union upat up;
+ char *nptr, *str0, *ptr, *patprev;
+ zrange_t from, to;
+ char *charstart;
+
+ flags = 0;
+ str0 = patprev = patparse;
+ for (;;) {
+ /*
+ * Check if we have a string. First, we need to make sure
+ * the string doesn't introduce a ksh-like parenthesized expression.
+ */
+ kshchar = '\0';
+ if (*patparse && patparse[1] == Inpar) {
+ if (*patparse == zpc_special[ZPC_KSH_PLUS])
+ kshchar = STOUC('+');
+ else if (*patparse == zpc_special[ZPC_KSH_BANG])
+ kshchar = STOUC('!');
+ else if (*patparse == zpc_special[ZPC_KSH_BANG2])
+ kshchar = STOUC('!');
+ else if (*patparse == zpc_special[ZPC_KSH_AT])
+ kshchar = STOUC('@');
+ else if (*patparse == zpc_special[ZPC_KSH_STAR])
+ kshchar = STOUC('*');
+ else if (*patparse == zpc_special[ZPC_KSH_QUEST])
+ kshchar = STOUC('?');
+ }
+
+ /*
+ * If '(' is disabled as a pattern char, allow ')' as
+ * an ordinary string character if there are no parentheses to
+ * close. Don't allow it otherwise, it changes the syntax.
+ */
+ if (zpc_special[ZPC_INPAR] != Marker || *patparse != Outpar ||
+ paren) {
+ /*
+ * End of string (or no string at all) if ksh-type parentheses,
+ * or special character, unless that character is a tilde and
+ * the character following is an end-of-segment character. Thus
+ * tildes are not special if there is nothing following to
+ * be excluded.
+ *
+ * Don't look for X()-style kshglobs at this point; we've
+ * checked above for the case with parentheses and we don't
+ * want to match without parentheses.
+ */
+ if (kshchar ||
+ (memchr(zpc_special, *patparse, ZPC_NO_KSH_GLOB) &&
+ (*patparse != zpc_special[ZPC_TILDE] ||
+ patparse[1] == '/' ||
+ !memchr(zpc_special, patparse[1], ZPC_SEG_COUNT)))) {
+ break;
+ }
+ }
+
+ /* Remember the previous character for backtracking */
+ patprev = patparse;
+ METACHARINC(patparse);
+ }
+
+ if (patparse > str0) {
+ long slen = patparse - str0;
+ int morelen;
+
+ /* Ordinary string: cancel kshchar lookahead */
+ kshchar = '\0';
+ /*
+ * Assume it matches a simple string until we find otherwise.
+ */
+ flags |= P_PURESTR;
+ DPUTS(patparse == str0, "BUG: matched nothing in patcomppiece.");
+ /* more than one character matched? */
+ morelen = (patprev > str0);
+ /*
+ * If we have more than one character, a following hash
+ * or (#c...) only applies to the last, so backtrack one character.
+ */
+ if ((*patparse == zpc_special[ZPC_HASH] ||
+ (*patparse == zpc_special[ZPC_INPAR] &&
+ patparse[1] == zpc_special[ZPC_HASH] &&
+ patparse[2] == 'c') ||
+ (*patparse == zpc_special[ZPC_KSH_AT] &&
+ patparse[1] == Inpar &&
+ patparse[2] == zpc_special[ZPC_HASH] &&
+ patparse[3] == 'c')) && morelen)
+ patparse = patprev;
+ /*
+ * If len is 1, we can't have an active # following, so doesn't
+ * matter that we don't make X in `XX#' simple.
+ */
+ if (!morelen)
+ flags |= P_SIMPLE;
+ starter = patnode(P_EXACTLY);
+
+ /* Get length of string without metafication. */
+ nmeta = 0;
+ /* inherited from domatch, but why, exactly? */
+ if (*str0 == Nularg)
+ str0++;
+ for (ptr = str0; ptr < patparse; ptr++) {
+ if (*ptr == Meta) {
+ nmeta++;
+ ptr++;
+ }
+ }
+ slen = (patparse - str0) - nmeta;
+ /* First add length, which is a long */
+ patadd((char *)&slen, 0, sizeof(long), 0);
+ /*
+ * Then the string, not null terminated.
+ * Unmetafy and untokenize; pass the final length,
+ * which is what we need to allocate, i.e. not including
+ * a count for each Meta in the string.
+ */
+ patadd(str0, 0, slen, PA_UNMETA);
+ nptr = P_LS_STR((Upat)patout + starter);
+ /*
+ * It's much simpler to turn off pure string mode for
+ * any case-insensitive or approximate matching; usually,
+ * that is correct, or they wouldn't have been turned on.
+ * However, we need to make sure we match a "." or ".."
+ * in a file name as a pure string. There's a minor bug
+ * that this will also apply to something like
+ * ..(#a1).. (i.e. the (#a1) has no effect), but if you're
+ * going to write funny patterns, you get no sympathy from me.
+ */
+ if (patglobflags &
+#ifdef __CYGWIN__
+ /*
+ * As above: don't use pattern matching for files
+ * just because of case insensitivity if file system
+ * is known to be case insensitive.
+ *
+ * This is known to be necessary in at least one case:
+ * if "mount -c /" is in effect, so that drives appear
+ * directly under / instead of the usual /cygdrive, they
+ * aren't shown by readdir(). So it's vital we don't use
+ * globbing to find "/c", since that'll fail.
+ */
+ ((patflags & PAT_FILE) ?
+ (0xFF|GF_LCMATCHUC) :
+ (0xFF|GF_LCMATCHUC|GF_IGNCASE))
+#else
+ (0xFF|GF_LCMATCHUC|GF_IGNCASE)
+#endif
+ ) {
+ if (!(patflags & PAT_FILE))
+ flags &= ~P_PURESTR;
+ else if (!(nptr[0] == '.' &&
+ (slen == 1 || (nptr[1] == '.' && slen == 2))))
+ flags &= ~P_PURESTR;
+ }
+ } else {
+ if (kshchar)
+ patparse++;
+
+ patch = *patparse;
+ METACHARINC(patparse);
+ switch(patch) {
+ case Quest:
+ DPUTS(zpc_special[ZPC_QUEST] == Marker,
+ "Treating '?' as pattern character although disabled");
+ flags |= P_SIMPLE;
+ starter = patnode(P_ANY);
+ break;
+ case Star:
+ DPUTS(zpc_special[ZPC_STAR] == Marker,
+ "Treating '*' as pattern character although disabled");
+ /* kshchar is used as a sign that we can't have #'s. */
+ kshchar = -1;
+ starter = patnode(P_STAR);
+ break;
+ case Inbrack:
+ DPUTS(zpc_special[ZPC_INBRACK] == Marker,
+ "Treating '[' as pattern character although disabled");
+ flags |= P_SIMPLE;
+ if (*patparse == Hat || *patparse == Bang) {
+ patparse++;
+ starter = patnode(P_ANYBUT);
+ } else
+ starter = patnode(P_ANYOF);
+ /*
+ * []...] means match a "]" or other included characters.
+ * However, to be a bit helpful and for compatibility
+ * with other shells, don't take in that sense if
+ * there's no further "]". That's still imperfect,
+ * but it's all we can do --- we're required to
+ * treat [$var]*[$var]with empty var as [ ... ]
+ * containing "]*[".
+ */
+ if (*patparse == Outbrack && strchr(patparse+1, Outbrack)) {
+ patparse++;
+ patadd(NULL, ']', 1, PA_NOALIGN);
+ }
+ while (*patparse && *patparse != Outbrack) {
+ /* Meta is not a token */
+ if (*patparse == Inbrack && patparse[1] == ':' &&
+ (nptr = strchr(patparse+2, ':')) &&
+ nptr[1] == Outbrack) {
+ /* Posix range. */
+ patparse += 2;
+ len = nptr - patparse;
+ ch = range_type(patparse, len);
+ patparse = nptr + 2;
+ if (ch != PP_UNKWN)
+ patadd(NULL, STOUC(Meta) + ch, 1, PA_NOALIGN);
+ continue;
+ }
+ charstart = patparse;
+ METACHARINC(patparse);
+
+ if (*patparse == Dash && patparse[1] &&
+ patparse[1] != Outbrack) {
+ patadd(NULL, STOUC(Meta)+PP_RANGE, 1, PA_NOALIGN);
+ if (itok(*charstart)) {
+ patadd(0, STOUC(ztokens[*charstart - Pound]), 1,
+ PA_NOALIGN);
+ } else {
+ patadd(charstart, 0, patparse-charstart, PA_NOALIGN);
+ }
+ charstart = ++patparse; /* skip Dash token */
+ METACHARINC(patparse);
+ }
+ if (itok(*charstart)) {
+ patadd(0, STOUC(ztokens[*charstart - Pound]), 1,
+ PA_NOALIGN);
+ } else {
+ patadd(charstart, 0, patparse-charstart, PA_NOALIGN);
+ }
+ }
+ if (*patparse != Outbrack)
+ return 0;
+ patparse++;
+ /* terminate null string and fix alignment */
+ patadd(NULL, 0, 1, 0);
+ break;
+ case Inpar:
+ DPUTS(!kshchar && zpc_special[ZPC_INPAR] == Marker,
+ "Treating '(' as pattern character although disabled");
+ DPUTS(isset(SHGLOB) && !kshchar,
+ "Treating bare '(' as pattern character with SHGLOB");
+ if (kshchar == '!') {
+ /* This is nasty, we should really either handle all
+ * kshglobbing below or here. But most of the
+ * others look like non-ksh patterns, while this one
+ * doesn't, so we handle it here and leave the rest.
+ * We treat it like an extendedglob ^, except that
+ * it goes into parentheses.
+ *
+ * If we did do kshglob here, we could support
+ * the old behaviour that things like !(foo)##
+ * work, but it makes the code more complicated at
+ * the expense of allowing the user to do things
+ * they shouldn't.
+ */
+ if (!(starter = patcompnot(1, &flags2)))
+ return 0;
+ } else if (!(starter = patcompswitch(1, &flags2)))
+ return 0;
+ flags |= flags2 & P_HSTART;
+ break;
+ case Inang:
+ /* Numeric glob */
+ DPUTS(zpc_special[ZPC_INANG] == Marker,
+ "Treating '<' as pattern character although disabled");
+ DPUTS(isset(SHGLOB), "Treating <..> as numeric range with SHGLOB");
+ len = 0; /* beginning present 1, end present 2 */
+ if (idigit(*patparse)) {
+ from = (zrange_t) zstrtol((char *)patparse,
+ (char **)&nptr, 10);
+ patparse = nptr;
+ len |= 1;
+ }
+ DPUTS(!IS_DASH(*patparse), "BUG: - missing from numeric glob");
+ patparse++;
+ if (idigit(*patparse)) {
+ to = (zrange_t) zstrtol((char *)patparse,
+ (char **)&nptr, 10);
+ patparse = nptr;
+ len |= 2;
+ }
+ if (*patparse != Outang)
+ return 0;
+ patparse++;
+ switch(len) {
+ case 3:
+ starter = patnode(P_NUMRNG);
+ patadd((char *)&from, 0, sizeof(from), 0);
+ patadd((char *)&to, 0, sizeof(to), 0);
+ break;
+ case 2:
+ starter = patnode(P_NUMTO);
+ patadd((char *)&to, 0, sizeof(to), 0);
+ break;
+ case 1:
+ starter = patnode(P_NUMFROM);
+ patadd((char *)&from, 0, sizeof(from), 0);
+ break;
+ case 0:
+ starter = patnode(P_NUMANY);
+ break;
+ }
+ /* This can't be simple, because it isn't.
+ * Mention in manual that matching digits with [...]
+ * is more efficient.
+ */
+ break;
+ case Pound:
+ DPUTS(zpc_special[ZPC_HASH] == Marker,
+ "Treating '#' as pattern character although disabled");
+ DPUTS(!isset(EXTENDEDGLOB), "BUG: # not treated as string");
+ /*
+ * A hash here is an error; it should follow something
+ * repeatable.
+ */
+ return 0;
+ break;
+ case Bnullkeep:
+ /*
+ * Marker for restoring a backslash in output:
+ * does not match a character.
+ */
+ next = patcomppiece(flagp, paren);
+ /*
+ * Can't match a pure string since we need to do this
+ * as multiple chunks.
+ */
+ *flagp &= ~P_PURESTR;
+ return next;
+ break;
+#ifdef DEBUG
+ default:
+ dputs("BUG: character not handled in patcomppiece");
+ return 0;
+ break;
+#endif
+ }
+ }
+
+ count = 0;
+ if (!(hash = (*patparse == zpc_special[ZPC_HASH])) &&
+ !(count = ((*patparse == zpc_special[ZPC_INPAR] &&
+ patparse[1] == zpc_special[ZPC_HASH] &&
+ patparse[2] == 'c') ||
+ (*patparse == zpc_special[ZPC_KSH_AT] &&
+ patparse[1] == Inpar &&
+ patparse[2] == zpc_special[ZPC_HASH] &&
+ patparse[3] == 'c'))) &&
+ (kshchar <= 0 || kshchar == '@' || kshchar == '!')) {
+ *flagp = flags;
+ return starter;
+ }
+
+ /* too much at once doesn't currently work */
+ if (kshchar && (hash || count))
+ return 0;
+
+ if (kshchar == '*') {
+ op = P_ONEHASH;
+ *flagp = P_HSTART;
+ } else if (kshchar == '+') {
+ op = P_TWOHASH;
+ *flagp = P_HSTART;
+ } else if (kshchar == '?') {
+ op = 0;
+ *flagp = 0;
+ } else if (count) {
+ op = P_COUNT;
+ patparse += 3;
+ *flagp = P_HSTART;
+ } else if (*++patparse == zpc_special[ZPC_HASH]) {
+ op = P_TWOHASH;
+ patparse++;
+ *flagp = P_HSTART;
+ } else {
+ op = P_ONEHASH;
+ *flagp = P_HSTART;
+ }
+
+ /*
+ * Note optimizations with pointers into P_NOTHING branches: some
+ * should logically point to next node after current piece.
+ *
+ * Backtracking is also encoded in a slightly obscure way: the
+ * code emitted ensures we test the non-empty branch of complex
+ * patterns before the empty branch on each repetition. Hence
+ * each time we fail on a non-empty branch, we try the empty branch,
+ * which is equivalent to backtracking.
+ */
+ if (op == P_COUNT) {
+ /* (#cN,M) */
+ union upat countargs[P_CT_OPERAND];
+ char *opp = patparse;
+
+ countargs[0].l = P_COUNT;
+ countargs[P_CT_CURRENT].l = 0L;
+ countargs[P_CT_MIN].l = (long)zstrtol(patparse, &patparse, 10);
+ if (patparse == opp) {
+ /* missing number treated as zero */
+ countargs[P_CT_MIN].l = 0L;
+ }
+ if (*patparse != ',' && *patparse != Comma) {
+ /* either max = min or error */
+ if (*patparse != Outpar)
+ return 0;
+ countargs[P_CT_MAX].l = countargs[P_CT_MIN].l;
+ } else {
+ opp = ++patparse;
+ countargs[P_CT_MAX].l = (long)zstrtol(patparse, &patparse, 10);
+ if (*patparse != Outpar)
+ return 0;
+ if (patparse == opp) {
+ /* missing number treated as infinity: record as -1 */
+ countargs[P_CT_MAX].l = -1L;
+ }
+ }
+ patparse++;
+ countargs[P_CT_PTR].p = NULL;
+ /* Mark this chain as a min/max count... */
+ patinsert(P_COUNTSTART, starter, (char *)countargs, sizeof(countargs));
+ /*
+ * The next of the operand is a loop back to the P_COUNT. This is
+ * how we get recursion for the count. We don't loop back to
+ * the P_COUNTSTART; that's used for initialising the count
+ * and saving and restoring the count for any enclosing use
+ * of the match.
+ */
+ opnd = P_OPERAND(starter) + P_CT_OPERAND;
+ pattail(opnd, patnode(P_BACK));
+ pattail(opnd, P_OPERAND(starter));
+ /*
+ * The next of the counter operators is what follows the
+ * closure.
+ * This handles matching of the tail.
+ */
+ next = patnode(P_NOTHING);
+ pattail(starter, next);
+ pattail(P_OPERAND(starter), next);
+ } else if ((flags & P_SIMPLE) && (op == P_ONEHASH || op == P_TWOHASH) &&
+ P_OP((Upat)patout+starter) == P_ANY) {
+ /* Optimize ?# to *. Silly thing to do, since who would use
+ * use ?# ? But it makes the later code shorter.
+ */
+ Upat uptr = (Upat)patout + starter;
+ if (op == P_TWOHASH) {
+ /* ?## becomes ?* */
+ uptr->l = (uptr->l & ~0xff) | P_ANY;
+ pattail(starter, patnode(P_STAR));
+ } else {
+ uptr->l = (uptr->l & ~0xff) | P_STAR;
+ }
+ } else if ((flags & P_SIMPLE) && op && !(patglobflags & 0xff)) {
+ /* Simplify, but not if we need to look for approximations. */
+ patinsert(op, starter, NULL, 0);
+ } else if (op == P_ONEHASH) {
+ /* Emit x# as (x&|), where & means "self". */
+ up.p = NULL;
+ patinsert(P_WBRANCH, starter, (char *)&up, sizeof(up));
+ /* Either x */
+ patoptail(starter, patnode(P_BACK)); /* and loop */
+ patoptail(starter, starter); /* back */
+ pattail(starter, patnode(P_BRANCH)); /* or */
+ pattail(starter, patnode(P_NOTHING)); /* null. */
+ } else if (op == P_TWOHASH) {
+ /* Emit x## as x(&|) where & means "self". */
+ next = patnode(P_WBRANCH); /* Either */
+ up.p = NULL;
+ patadd((char *)&up, 0, sizeof(up), 0);
+ pattail(starter, next);
+ pattail(patnode(P_BACK), starter); /* loop back */
+ pattail(next, patnode(P_BRANCH)); /* or */
+ pattail(starter, patnode(P_NOTHING)); /* null. */
+ } else if (kshchar == '?') {
+ /* Emit ?(x) as (x|) */
+ patinsert(P_BRANCH, starter, NULL, 0); /* Either x */
+ pattail(starter, patnode(P_BRANCH)); /* or */
+ next = patnode(P_NOTHING); /* null */
+ pattail(starter, next);
+ patoptail(starter, next);
+ }
+ if (*patparse == zpc_special[ZPC_HASH])
+ return 0;
+
+ return starter;
+}
+
+/*
+ * Turn a ^foo (paren = 0) or !(foo) (paren = 1) into *~foo with
+ * parentheses if necessary. As you see, that's really quite easy.
+ */
+
+/**/
+static long
+patcompnot(int paren, int *flagsp)
+{
+ union upat up;
+ long excsync, br, excl, n, starter;
+ int dummy;
+
+ /* Here, we're matching a star at the start. */
+ *flagsp = P_HSTART;
+
+ starter = patnode(P_BRANCH);
+ br = patnode(P_STAR);
+ excsync = patnode(P_EXCSYNC);
+ pattail(br, excsync);
+ pattail(starter, excl = patnode(P_EXCLUDE));
+ up.p = NULL;
+ patadd((char *)&up, 0, sizeof(up), 0);
+ if (!(br = (paren ? patcompswitch(1, &dummy) : patcompbranch(&dummy, 0))))
+ return 0;
+ pattail(br, patnode(P_EXCEND));
+ n = patnode(P_NOTHING); /* just so much easier */
+ pattail(excsync, n);
+ pattail(excl, n);
+
+ return starter;
+}
+
+/* Emit a node */
+
+/**/
+static long
+patnode(long op)
+{
+ long starter = (Upat)patcode - (Upat)patout;
+ union upat up;
+
+ up.l = op;
+ patadd((char *)&up, 0, sizeof(union upat), 0);
+ return starter;
+}
+
+/*
+ * insert an operator in front of an already emitted operand:
+ * we relocate the operand. there had better be nothing else after.
+ */
+
+/**/
+static void
+patinsert(long op, int opnd, char *xtra, int sz)
+{
+ char *src, *dst, *opdst;
+ union upat buf, *lptr;
+
+ buf.l = 0;
+ patadd((char *)&buf, 0, sizeof(buf), 0);
+ if (sz)
+ patadd(xtra, 0, sz, 0);
+ src = patcode - sizeof(union upat) - sz;
+ dst = patcode;
+ opdst = patout + opnd * sizeof(union upat);
+ while (src > opdst)
+ *--dst = *--src;
+
+ /* A cast can't be an lvalue */
+ lptr = (Upat)opdst;
+ lptr->l = op;
+ opdst += sizeof(union upat);
+ while (sz--)
+ *opdst++ = *xtra++;
+}
+
+/* set the 'next' pointer at the end of a node chain */
+
+/**/
+static void
+pattail(long p, long val)
+{
+ Upat scan, temp;
+ long offset;
+
+ scan = (Upat)patout + p;
+ for (;;) {
+ if (!(temp = PATNEXT(scan)))
+ break;
+ scan = temp;
+ }
+
+ offset = (P_OP(scan) == P_BACK)
+ ? (scan - (Upat)patout) - val : val - (scan - (Upat)patout);
+
+ scan->l |= offset << 8;
+}
+
+/* do pattail, but on operand of first argument; nop if operandless */
+
+/**/
+static void
+patoptail(long p, long val)
+{
+ Upat ptr = (Upat)patout + p;
+ int op = P_OP(ptr);
+ if (!p || !P_ISBRANCH(ptr))
+ return;
+ if (op == P_BRANCH)
+ pattail(P_OPERAND(p), val);
+ else
+ pattail(P_OPERAND(p) + 1, val);
+}
+
+
+/*
+ * Run a pattern.
+ */
+struct rpat {
+ char *patinstart; /* Start of input string */
+ char *patinend; /* End of input string */
+ char *patinput; /* String input pointer */
+ char *patinpath; /* Full path for use with ~ exclusions */
+ int patinlen; /* Length of last successful match.
+ * Includes count of Meta characters.
+ */
+
+ char *patbeginp[NSUBEXP]; /* Pointer to backref beginnings */
+ char *patendp[NSUBEXP]; /* Pointer to backref ends */
+ int parsfound; /* parentheses (with backrefs) found */
+
+ int globdots; /* Glob initial dots? */
+};
+
+static struct rpat pattrystate;
+
+#define patinstart (pattrystate.patinstart)
+#define patinend (pattrystate.patinend)
+#define patinput (pattrystate.patinput)
+#define patinpath (pattrystate.patinpath)
+#define patinlen (pattrystate.patinlen)
+#define patbeginp (pattrystate.patbeginp)
+#define patendp (pattrystate.patendp)
+#define parsfound (pattrystate.parsfound)
+#define globdots (pattrystate.globdots)
+
+
+/*
+ * Character functions operating on unmetafied strings.
+ */
+#ifdef MULTIBYTE_SUPPORT
+
+/* Get a character from the start point in a string */
+#define CHARREF(x, y) charref((x), (y), (int *)NULL)
+static wchar_t
+charref(char *x, char *y, int *zmb_ind)
+{
+ wchar_t wc;
+ size_t ret;
+
+ if (!(patglobflags & GF_MULTIBYTE) || !(STOUC(*x) & 0x80))
+ return (wchar_t) STOUC(*x);
+
+ ret = mbrtowc(&wc, x, y-x, &shiftstate);
+
+ if (ret == MB_INVALID || ret == MB_INCOMPLETE) {
+ /* Error. */
+ /* Reset the shift state for next time. */
+ memset(&shiftstate, 0, sizeof(shiftstate));
+ if (zmb_ind)
+ *zmb_ind = (ret == MB_INVALID) ? ZMB_INVALID : ZMB_INCOMPLETE;
+ return WCHAR_INVALID(*x);
+ }
+
+ if (zmb_ind)
+ *zmb_ind = ZMB_VALID;
+ return wc;
+}
+
+/* Get a pointer to the next character */
+#define CHARNEXT(x, y) charnext((x), (y))
+static char *
+charnext(char *x, char *y)
+{
+ wchar_t wc;
+ size_t ret;
+
+ if (!(patglobflags & GF_MULTIBYTE) || !(STOUC(*x) & 0x80))
+ return x + 1;
+
+ ret = mbrtowc(&wc, x, y-x, &shiftstate);
+
+ if (ret == MB_INVALID || ret == MB_INCOMPLETE) {
+ /* Error. Treat as single byte. */
+ /* Reset the shift state for next time. */
+ memset(&shiftstate, 0, sizeof(shiftstate));
+ return x + 1;
+ }
+
+ /* Nulls here are normal characters */
+ return x + (ret ? ret : 1);
+}
+
+/* Increment a pointer past the current character. */
+#define CHARINC(x, y) ((x) = charnext((x), (y)))
+
+
+/* Get a character and increment */
+#define CHARREFINC(x, y, z) charrefinc(&(x), (y), (z))
+static wchar_t
+charrefinc(char **x, char *y, int *z)
+{
+ wchar_t wc;
+ size_t ret;
+
+ if (!(patglobflags & GF_MULTIBYTE) || !(STOUC(**x) & 0x80))
+ return (wchar_t) STOUC(*(*x)++);
+
+ ret = mbrtowc(&wc, *x, y-*x, &shiftstate);
+
+ if (ret == MB_INVALID || ret == MB_INCOMPLETE) {
+ /* Error. Treat as single byte, but flag. */
+ *z = 1;
+ /* Reset the shift state for next time. */
+ memset(&shiftstate, 0, sizeof(shiftstate));
+ return WCHAR_INVALID(*(*x)++);
+ }
+
+ /* Nulls here are normal characters */
+ *x += ret ? ret : 1;
+
+ return wc;
+}
+
+
+/*
+ * Counter the number of characters between two pointers, smaller first
+ *
+ * This is used when setting values in parameters, so we obey
+ * the MULTIBYTE option (even if it's been overridden locally).
+ */
+#define CHARSUB(x,y) charsub(x, y)
+static ptrdiff_t
+charsub(char *x, char *y)
+{
+ ptrdiff_t res = 0;
+ size_t ret;
+ wchar_t wc;
+
+ if (!isset(MULTIBYTE))
+ return y - x;
+
+ while (x < y) {
+ ret = mbrtowc(&wc, x, y-x, &shiftstate);
+
+ if (ret == MB_INVALID || ret == MB_INCOMPLETE) {
+ /* Error. Treat remainder as single characters */
+ return res + (y - x);
+ }
+
+ /* Treat nulls as normal characters */
+ if (!ret)
+ ret = 1;
+ res++;
+ x += ret;
+ }
+
+ return res;
+}
+
+#else /* no MULTIBYTE_SUPPORT */
+
+/* Get a character from the start point in a string */
+#define CHARREF(x, y) (STOUC(*(x)))
+/* Get a pointer to the next character */
+#define CHARNEXT(x, y) ((x)+1)
+/* Increment a pointer past the current character. */
+#define CHARINC(x, y) ((x)++)
+/* Get a character and increment */
+#define CHARREFINC(x, y, z) (STOUC(*(x)++))
+/* Counter the number of characters between two pointers, smaller first */
+#define CHARSUB(x,y) ((y) - (x))
+
+#endif /* MULTIBYTE_SUPPORT */
+
+/*
+ * The following need to be accessed in the globbing scanner for
+ * a multi-component file path. See horror story in glob.c.
+ */
+/**/
+int errsfound; /* Total error count so far */
+
+/**/
+int forceerrs; /* Forced maximum error count */
+
+/**/
+void
+pattrystart(void)
+{
+ forceerrs = -1;
+ errsfound = 0;
+}
+
+/*
+ * Fix up string length stuff.
+ *
+ * If we call patallocstr() with "force" to set things up early, it's
+ * done there, else it's done in pattryrefs(). The reason for the
+ * difference is in the latter case we may not be relying on
+ * patallocstr() having an effect.
+ */
+
+/**/
+static void
+patmungestring(char **string, int *stringlen, int *unmetalenin)
+{
+ /*
+ * Special signalling of empty tokenised string.
+ */
+ if (*stringlen > 0 && **string == Nularg) {
+ (*string)++;
+ /*
+ * If we don't have an unmetafied length
+ * and need it (we may not) we'll get it later.
+ */
+ if (*unmetalenin > 0)
+ (*unmetalenin)--;
+ if (*stringlen > 0)
+ (*stringlen)--;
+ }
+
+ /* Ensure we have a metafied length */
+ if (*stringlen < 0)
+ *stringlen = strlen(*string);
+}
+
+/*
+ * Allocate memeory for pattern match. Note this is specific to use
+ * of pattern *and* trial string.
+ *
+ * Unmetafy a trial string for use in pattern matching, if needed.
+ *
+ * If it is needed, returns a heap allocated string; if not needed,
+ * returns NULL.
+ *
+ * prog is the pattern to be executed.
+ * string is the metafied trial string.
+ * stringlen is it's length; it will be calculated if it's negative
+ * (this is a simple strlen()).
+ * unmetalen is the unmetafied length of the string, may be -1.
+ * force is 1 if we always unmetafy: this is useful if we are going
+ * to try again with different versions of the string. If this is
+ * called from pattryrefs() we don't force unmetafication as it won't
+ * be optimal. This option should be used if the resulting
+ * patstralloc is going to be passed to pattrylen() / pattryrefs().
+ * In patstralloc (supplied by caller, must last until last pattry is done)
+ * unmetalen is the unmetafied length of the string; it will be
+ * calculated if the input value is negative.
+ * unmetalenp is the umetafied length of a path segment preceeding
+ * the trial string needed for file mananagement; it is calculated as
+ * needed so does not need to be initialised.
+ * alloced is the memory allocated on the heap --- same as return value from
+ * function.
+ */
+/**/
+mod_export
+char *patallocstr(Patprog prog, char *string, int stringlen, int unmetalen,
+ int force, Patstralloc patstralloc)
+{
+ int needfullpath;
+
+ if (force)
+ patmungestring(&string, &stringlen, &unmetalen);
+
+ /*
+ * For a top-level ~-exclusion, we will need the full
+ * path to exclude, so copy the path so far and append the
+ * current test string.
+ */
+ needfullpath = (prog->flags & PAT_HAS_EXCLUDP) && pathpos;
+
+ /* Get the length of the full string when unmetafied. */
+ if (unmetalen < 0)
+ patstralloc->unmetalen = ztrsub(string + stringlen, string);
+ else
+ patstralloc->unmetalen = unmetalen;
+ if (needfullpath) {
+ patstralloc->unmetalenp = ztrsub(pathbuf + pathpos, pathbuf);
+ if (!patstralloc->unmetalenp)
+ needfullpath = 0;
+ } else
+ patstralloc->unmetalenp = 0;
+ /* Initialise cache area */
+ patstralloc->progstrunmeta = NULL;
+ patstralloc->progstrunmetalen = 0;
+
+ DPUTS(needfullpath && (prog->flags & (PAT_PURES|PAT_ANY)),
+ "rum sort of file exclusion");
+ /*
+ * Partly for efficiency, and partly for the convenience of
+ * globbing, we don't unmetafy pure string patterns, and
+ * there's no reason to if the pattern is just a *.
+ */
+ if (force ||
+ (!(prog->flags & (PAT_PURES|PAT_ANY))
+ && (needfullpath || patstralloc->unmetalen != stringlen))) {
+ /*
+ * We need to copy if we need to prepend the path so far
+ * (in which case we copy both chunks), or if we have
+ * Meta characters.
+ */
+ char *dst, *ptr;
+ int i, icopy, ncopy;
+
+ dst = patstralloc->alloced =
+ zhalloc(patstralloc->unmetalen + patstralloc->unmetalenp);
+
+ if (needfullpath) {
+ /* loop twice, copy path buffer first time */
+ ptr = pathbuf;
+ ncopy = patstralloc->unmetalenp;
+ } else {
+ /* just loop once, copy string with unmetafication */
+ ptr = string;
+ ncopy = patstralloc->unmetalen;
+ }
+ for (icopy = 0; icopy < 2; icopy++) {
+ for (i = 0; i < ncopy; i++) {
+ if (*ptr == Meta) {
+ ptr++;
+ *dst++ = *ptr++ ^ 32;
+ } else {
+ *dst++ = *ptr++;
+ }
+ }
+ if (!needfullpath)
+ break;
+ /* next time append test string to path so far */
+ ptr = string;
+ ncopy = patstralloc->unmetalen;
+ }
+ }
+ else
+ {
+ patstralloc->alloced = NULL;
+ }
+
+ return patstralloc->alloced;
+}
+
+
+/*
+ * Test prog against null-terminated, metafied string.
+ */
+
+/**/
+mod_export int
+pattry(Patprog prog, char *string)
+{
+ return pattryrefs(prog, string, -1, -1, NULL, 0, NULL, NULL, NULL);
+}
+
+/*
+ * Test prog against string of given length, no null termination
+ * but still metafied at this point. offset gives an offset
+ * to include in reported match indices
+ */
+
+/**/
+mod_export int
+pattrylen(Patprog prog, char *string, int len, int unmetalen,
+ Patstralloc patstralloc, int offset)
+{
+ return pattryrefs(prog, string, len, unmetalen, patstralloc, offset,
+ NULL, NULL, NULL);
+}
+
+/*
+ * Test prog against string with given lengths. The input
+ * string is metafied; stringlen is the raw string length, and
+ * unmetalen the number of characters in the original string (some
+ * of which may now be metafied). Either value may be -1
+ * to indicate a null-terminated string which will be counted. Note
+ * there may be a severe penalty for this if a lot of matching is done
+ * on one string.
+ *
+ * If patstralloc is not NULL it is used to optimise unmetafication
+ * of a trial string that may be passed (or any substring may be passed) to
+ * pattryrefs multiple times or the same pattern (N.B. so patstralloc
+ * depends on both prog *and* the trial string). This should only be
+ * done if there is no path prefix (pathpos == 0) as otherwise the path
+ * buffer and unmetafied string may not match. To do this,
+ * patallocstr() is callled (use force = 1 to ensure it is alway
+ * unmetafied); paststralloc points to existing storage. Memory is
+ * on the heap.
+ *
+ * patstralloc->alloced and patstralloc->unmetalen contain the
+ * unmetafied string and its length. In that case, the rules for the
+ * earlier arguments change:
+ * - string is an unmetafied string
+ * - stringlen is its unmetafied (i.e. actual) length
+ * - unmetalenin is not used.
+ * string and stringlen may refer to arbitrary substrings of
+ * patstralloc->alloced without any internal modification to patstralloc.
+ *
+ * patoffset is the position in the original string (not seen by
+ * the pattern module) at which we are trying to match.
+ * This is added in to the positions recorded in patbeginp and patendp
+ * when we are looking for substrings. Currently this only happens
+ * in the parameter substitution code. It refers to a real character
+ * offset, i.e. is already in the form ready for presentation to the
+ * general public --- this is necessary as we don't have the
+ * information to convert it down here.
+ *
+ * Note this is a character offset, i.e. a single possibly metafied and
+ * possibly multibyte character counts as 1.
+ *
+ * The last three arguments are used to report the positions for the
+ * backreferences. On entry, *nump should contain the maximum number
+ * of positions to report. In this case the match, mbegin, mend
+ * arrays are not altered.
+ *
+ * If nump is NULL but endp is not NULL, then *endp is set to the
+ * end position of the match, taking into account patinstart.
+ */
+
+/**/
+mod_export int
+pattryrefs(Patprog prog, char *string, int stringlen, int unmetalenin,
+ Patstralloc patstralloc, int patoffset,
+ int *nump, int *begp, int *endp)
+{
+ int i, maxnpos = 0, ret;
+ int origlen;
+ char **sp, **ep, *ptr;
+ char *progstr = (char *)prog + prog->startoff;
+ struct patstralloc patstralloc_struct;
+
+ if (nump) {
+ maxnpos = *nump;
+ *nump = 0;
+ }
+
+ if (!patstralloc)
+ patmungestring(&string, &stringlen, &unmetalenin);
+ origlen = stringlen;
+
+ if (patstralloc) {
+ DPUTS(!patstralloc->alloced,
+ "External unmetafy didn't actually unmetafy.");
+ DPUTS(patstralloc->unmetalenp,
+ "Ooh-err: pathpos with external unmetafy. I have bad vibes.");
+ patinpath = NULL;
+ patinstart = string;
+ /* stringlen is unmetafied length; unmetalenin is ignored */
+ } else {
+ patstralloc = &patstralloc_struct;
+ if (patallocstr(prog, string, stringlen, unmetalenin, 0, patstralloc)) {
+ patinstart = patstralloc->alloced + patstralloc->unmetalenp;
+ stringlen = patstralloc->unmetalen;
+ } else
+ patinstart = string;
+ if (patstralloc->unmetalenp)
+ patinpath = patstralloc->alloced;
+ else
+ patinpath = NULL;
+ }
+
+ patflags = prog->flags;
+ patinend = patinstart + stringlen;
+ /*
+ * From now on we do not require NULL termination of
+ * the test string. There should also be no more references
+ * to the variable string.
+ */
+
+ if (prog->flags & (PAT_PURES|PAT_ANY)) {
+ /*
+ * Either we are testing against a pure string,
+ * or we can match anything at all.
+ */
+ int pstrlen;
+ char *pstr;
+ if (patstralloc->alloced)
+ {
+ /*
+ * Unmetafied; we need pattern sring that's also unmetafied.
+ * We'll cache it in the patstralloc structure.
+ * Note it's on the heap.
+ */
+ if (!patstralloc->progstrunmeta)
+ {
+ patstralloc->progstrunmeta =
+ dupstrpfx(progstr, (int)prog->patmlen);
+ unmetafy(patstralloc->progstrunmeta,
+ &patstralloc->progstrunmetalen);
+ }
+ pstr = patstralloc->progstrunmeta;
+ pstrlen = patstralloc->progstrunmetalen;
+ }
+ else
+ {
+ /* Metafied. */
+ pstr = progstr;
+ pstrlen = (int)prog->patmlen;
+ }
+ if (prog->flags & PAT_ANY) {
+ /*
+ * Optimisation for a single "*": always matches
+ * (except for no_glob_dots, see below).
+ */
+ ret = 1;
+ } else {
+ /*
+ * Testing a pure string. See if initial
+ * components match.
+ */
+ int lendiff = stringlen - pstrlen;
+ if (lendiff < 0) {
+ /* No, the pattern string is too long. */
+ ret = 0;
+ } else if (!memcmp(pstr, patinstart, pstrlen)) {
+ /*
+ * Initial component matches. Matches either
+ * if lengths are the same or we are not anchored
+ * to the end of the string.
+ */
+ ret = !lendiff || (prog->flags & PAT_NOANCH);
+ } else {
+ /* No match. */
+ ret = 0;
+ }
+ }
+ if (ret) {
+ /*
+ * For files, we won't match initial "."s unless
+ * glob_dots is set.
+ */
+ if ((prog->flags & PAT_NOGLD) && *patinstart == '.') {
+ ret = 0;
+ } else {
+ /*
+ * Remember the length in case used for ${..#..} etc.
+ * In this case, we didn't unmetafy the pattern string
+ * In the orignal structure, but it might be unmetafied
+ * for use with an unmetafied test string.
+ */
+ patinlen = pstrlen;
+ /* if matching files, must update globbing flags */
+ patglobflags = prog->globend;
+
+ if ((patglobflags & GF_MATCHREF) &&
+ !(patflags & PAT_FILE)) {
+ char *str;
+ int mlen;
+
+ if (patstralloc->alloced) {
+ /*
+ * Unmetafied: pstrlen contains unmetafied
+ * length in bytes.
+ */
+ str = metafy(patinstart, pstrlen, META_DUP);
+ mlen = CHARSUB(patinstart, patinstart + pstrlen);
+ } else {
+ str = ztrduppfx(patinstart, patinlen);
+ /*
+ * Count the characters. We're not using CHARSUB()
+ * because the string is still metafied.
+ */
+ MB_METACHARINIT();
+ mlen = MB_METASTRLEN2END(patinstart, 0,
+ patinstart + patinlen);
+ }
+
+ setsparam("MATCH", str);
+ setiparam("MBEGIN",
+ (zlong)(patoffset + !isset(KSHARRAYS)));
+ setiparam("MEND",
+ (zlong)(mlen + patoffset +
+ !isset(KSHARRAYS) - 1));
+ }
+ }
+ }
+ } else {
+ /*
+ * Test for a `must match' string, unless we're scanning for a match
+ * in which case we don't need to do this each time.
+ */
+ ret = 1;
+ if (!(prog->flags & PAT_SCAN) && prog->mustoff)
+ {
+ char *testptr; /* start pointer into test string */
+ char *teststop; /* last point from which we can match */
+ char *patptr = (char *)prog + prog->mustoff;
+ int patlen = prog->patmlen;
+ int found = 0;
+
+ if (patlen > stringlen) {
+ /* Too long, can't match. */
+ ret = 0;
+ } else {
+ teststop = patinend - patlen;
+
+ for (testptr = patinstart; testptr <= teststop; testptr++)
+ {
+ if (!memcmp(testptr, patptr, patlen)) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found)
+ ret = 0;
+ }
+ }
+ if (!ret)
+ return 0;
+
+ patglobflags = prog->globflags;
+ if (!(patflags & PAT_FILE)) {
+ forceerrs = -1;
+ errsfound = 0;
+ }
+ globdots = !(patflags & PAT_NOGLD);
+ parsfound = 0;
+
+ patinput = patinstart;
+
+ if (patmatch((Upat)progstr)) {
+ /*
+ * we were lazy and didn't save the globflags if an exclusion
+ * failed, so set it now
+ */
+ patglobflags = prog->globend;
+
+ /*
+ * Record length of successful match, including Meta
+ * characters. Do it here so that patmatchlen() can return
+ * it even if we delete the pattern strings.
+ */
+ patinlen = patinput - patinstart;
+ /*
+ * Optimization: if we didn't find any Meta characters
+ * to begin with, we don't need to look for them now.
+ *
+ * For patstralloc pased in, we want the unmetafied length.
+ */
+ if (patstralloc == &patstralloc_struct &&
+ patstralloc->unmetalen != origlen) {
+ for (ptr = patinstart; ptr < patinput; ptr++)
+ if (imeta(*ptr))
+ patinlen++;
+ }
+
+ /*
+ * Should we clear backreferences and matches on a failed
+ * match?
+ */
+ if ((patglobflags & GF_MATCHREF) && !(patflags & PAT_FILE)) {
+ /*
+ * m flag: for global match. This carries no overhead
+ * in the pattern matching part.
+ *
+ * Remember the test pattern is already unmetafied.
+ */
+ char *str;
+ int mlen = CHARSUB(patinstart, patinput);
+
+ str = metafy(patinstart, patinput - patinstart, META_DUP);
+ setsparam("MATCH", str);
+ setiparam("MBEGIN", (zlong)(patoffset + !isset(KSHARRAYS)));
+ setiparam("MEND",
+ (zlong)(mlen + patoffset +
+ !isset(KSHARRAYS) - 1));
+ }
+ if (prog->patnpar && nump) {
+ /*
+ * b flag: for backreferences using parentheses. Reported
+ * directly.
+ */
+ *nump = prog->patnpar;
+
+ sp = patbeginp;
+ ep = patendp;
+
+ for (i = 0; i < prog->patnpar && i < maxnpos; i++) {
+ if (parsfound & (1 << i)) {
+ if (begp)
+ *begp++ = CHARSUB(patinstart, *sp) + patoffset;
+ if (endp)
+ *endp++ = CHARSUB(patinstart, *ep) + patoffset
+ - 1;
+ } else {
+ if (begp)
+ *begp++ = -1;
+ if (endp)
+ *endp++ = -1;
+ }
+
+ sp++;
+ ep++;
+ }
+ } else if (prog->patnpar && !(patflags & PAT_FILE)) {
+ /*
+ * b flag: for backreferences using parentheses.
+ */
+ int palen = prog->patnpar+1;
+ char **matcharr, **mbeginarr, **mendarr;
+ char numbuf[DIGBUFSIZE];
+
+ matcharr = zshcalloc(palen*sizeof(char *));
+ mbeginarr = zshcalloc(palen*sizeof(char *));
+ mendarr = zshcalloc(palen*sizeof(char *));
+
+ sp = patbeginp;
+ ep = patendp;
+
+ for (i = 0; i < prog->patnpar; i++) {
+ if (parsfound & (1 << i)) {
+ matcharr[i] = metafy(*sp, *ep - *sp, META_DUP);
+ /*
+ * mbegin and mend give indexes into the string
+ * in the standard notation, i.e. respecting
+ * KSHARRAYS, and with the end index giving
+ * the last character, not one beyond.
+ * For example, foo=foo; [[ $foo = (f)oo ]] gives
+ * (without KSHARRAYS) indexes 1 and 1, which
+ * corresponds to indexing as ${foo[1,1]}.
+ */
+ sprintf(numbuf, "%ld",
+ (long)(CHARSUB(patinstart, *sp) +
+ patoffset +
+ !isset(KSHARRAYS)));
+ mbeginarr[i] = ztrdup(numbuf);
+ sprintf(numbuf, "%ld",
+ (long)(CHARSUB(patinstart, *ep) +
+ patoffset +
+ !isset(KSHARRAYS) - 1));
+ mendarr[i] = ztrdup(numbuf);
+ } else {
+ /* Pattern wasn't set: either it was in an
+ * unmatched branch, or a hashed parenthesis
+ * that didn't match at all.
+ */
+ matcharr[i] = ztrdup("");
+ mbeginarr[i] = ztrdup("-1");
+ mendarr[i] = ztrdup("-1");
+ }
+ sp++;
+ ep++;
+ }
+ setaparam("match", matcharr);
+ setaparam("mbegin", mbeginarr);
+ setaparam("mend", mendarr);
+ }
+
+ if (!nump && endp) {
+ /*
+ * We just need the overall end position.
+ */
+ *endp = CHARSUB(patinstart, patinput) + patoffset;
+ }
+
+ ret = 1;
+ } else
+ ret = 0;
+ }
+
+ return ret;
+}
+
+/*
+ * Return length of previous succesful match. This is
+ * in metafied bytes, i.e. includes a count of Meta characters,
+ * unless the match was done on an unmetafied string using
+ * a patstralloc stuct, in which case it, too is unmetafed.
+ * Unusual and futile attempt at modular encapsulation.
+ */
+
+/**/
+int
+patmatchlen(void)
+{
+ return patinlen;
+}
+
+/*
+ * Match literal characters with case insensitivity test: the first
+ * comes from the input string, the second the current pattern.
+ */
+#ifdef MULTIBYTE_SUPPORT
+#define ISUPPER(x) iswupper(x)
+#define ISLOWER(x) iswlower(x)
+#define TOUPPER(x) towupper(x)
+#define TOLOWER(x) towlower(x)
+#define ISDIGIT(x) iswdigit(x)
+#else
+#define ISUPPER(x) isupper(x)
+#define ISLOWER(x) islower(x)
+#define TOUPPER(x) toupper(x)
+#define TOLOWER(x) tolower(x)
+#define ISDIGIT(x) idigit(x)
+#endif
+#define CHARMATCH(chin, chpa) (chin == chpa || \
+ ((patglobflags & GF_IGNCASE) ? \
+ ((ISUPPER(chin) ? TOLOWER(chin) : chin) == \
+ (ISUPPER(chpa) ? TOLOWER(chpa) : chpa)) : \
+ (patglobflags & GF_LCMATCHUC) ? \
+ (ISLOWER(chpa) && TOUPPER(chpa) == chin) : 0))
+
+/*
+ * The same but caching an expression from the first argument,
+ * Requires local charmatch_cache definition.
+ */
+#define CHARMATCH_EXPR(expr, chpa) \
+ (charmatch_cache = (expr), CHARMATCH(charmatch_cache, chpa))
+
+/*
+ * exactpos is used to remember how far down an exact string we have
+ * matched, if we are doing approximation and can therefore redo from
+ * the same point; we never need to otherwise.
+ *
+ * exactend is a pointer to the end of the string, which isn't
+ * null-terminated.
+ */
+static char *exactpos, *exactend;
+
+/*
+ * Main matching routine.
+ *
+ * Testing the tail end of a match is usually done by recursion, but
+ * we try to eliminate that in favour of looping for simple cases.
+ */
+
+/**/
+static int
+patmatch(Upat prog)
+{
+ /* Current and next nodes */
+ Upat scan = prog, next, opnd;
+ char *start, *save, *chrop, *chrend, *compend;
+ int savglobflags, op, no, min, fail = 0, saverrsfound;
+ zrange_t from, to, comp;
+ patint_t nextch;
+ int q = queue_signal_level();
+
+ /*
+ * To avoid overhead of saving state if there are no queued signals
+ * waiting, we pierce the signals.h veil and examine queue state.
+ */
+#define check_for_signals() do if (queue_front != queue_rear) { \
+ int savpatflags = patflags, savpatglobflags = patglobflags; \
+ char *savexactpos = exactpos, *savexactend = exactend; \
+ struct rpat savpattrystate = pattrystate; \
+ dont_queue_signals(); \
+ restore_queue_signals(q); \
+ exactpos = savexactpos; \
+ exactend = savexactend; \
+ patflags = savpatflags; \
+ patglobflags = savpatglobflags; \
+ pattrystate = savpattrystate; \
+ } while (0)
+
+ check_for_signals();
+
+ while (scan && !errflag) {
+ next = PATNEXT(scan);
+
+ if (!globdots && P_NOTDOT(scan) && patinput == patinstart &&
+ patinput < patinend && *patinput == '.')
+ return 0;
+
+ switch (P_OP(scan)) {
+ case P_ANY:
+ if (patinput == patinend)
+ fail = 1;
+ else
+ CHARINC(patinput, patinend);
+ break;
+ case P_EXACTLY:
+ /*
+ * acts as nothing if *chrop is null: this is used by
+ * approx code.
+ */
+ if (exactpos) {
+ chrop = exactpos;
+ chrend = exactend;
+ } else {
+ chrop = P_LS_STR(scan);
+ chrend = chrop + P_LS_LEN(scan);
+ }
+ exactpos = NULL;
+ while (chrop < chrend && patinput < patinend) {
+ char *savpatinput = patinput;
+ char *savchrop = chrop;
+ int badin = 0, badpa = 0;
+ /*
+ * Care with character matching:
+ * We do need to convert the character to wide
+ * representation if possible, because we may need
+ * to do case transformation. However, we should
+ * be careful in case one, but not the other, wasn't
+ * representable in the current locale---in that
+ * case they don't match even if the returned
+ * values (one properly converted, one raw) are
+ * the same.
+ */
+ patint_t chin = CHARREFINC(patinput, patinend, &badin);
+ patint_t chpa = CHARREFINC(chrop, chrend, &badpa);
+ if (!CHARMATCH(chin, chpa) || badin != badpa) {
+ fail = 1;
+ patinput = savpatinput;
+ chrop = savchrop;
+ break;
+ }
+ }
+ if (chrop < chrend) {
+ exactpos = chrop;
+ exactend = chrend;
+ fail = 1;
+ }
+ break;
+ case P_ANYOF:
+ case P_ANYBUT:
+ if (patinput == patinend)
+ fail = 1;
+ else {
+#ifdef MULTIBYTE_SUPPORT
+ int zmb_ind;
+ wchar_t cr = charref(patinput, patinend, &zmb_ind);
+ char *scanop = (char *)P_OPERAND(scan);
+ if (patglobflags & GF_MULTIBYTE) {
+ if (mb_patmatchrange(scanop, cr, zmb_ind, NULL, NULL) ^
+ (P_OP(scan) == P_ANYOF))
+ fail = 1;
+ else
+ CHARINC(patinput, patinend);
+ } else if (patmatchrange(scanop, (int)cr, NULL, NULL) ^
+ (P_OP(scan) == P_ANYOF))
+ fail = 1;
+ else
+ CHARINC(patinput, patinend);
+#else
+ if (patmatchrange((char *)P_OPERAND(scan),
+ CHARREF(patinput, patinend), NULL, NULL) ^
+ (P_OP(scan) == P_ANYOF))
+ fail = 1;
+ else
+ CHARINC(patinput, patinend);
+#endif
+ }
+ break;
+ case P_NUMRNG:
+ case P_NUMFROM:
+ case P_NUMTO:
+ /*
+ * To do this properly, we really have to treat numbers as
+ * closures: that's so things like <1-1000>33 will
+ * match 633 (they didn't up to 3.1.6). To avoid making this
+ * too inefficient, we see if there's an exact match next:
+ * if there is, and it's not a digit, we return 1 after
+ * the first attempt.
+ */
+ op = P_OP(scan);
+ start = (char *)P_OPERAND(scan);
+ from = to = 0;
+ if (op != P_NUMTO) {
+#ifdef ZSH_64_BIT_TYPE
+ /* We can't rely on pointer alignment being good enough. */
+ memcpy((char *)&from, start, sizeof(zrange_t));
+#else
+ from = *((zrange_t *) start);
+#endif
+ start += sizeof(zrange_t);
+ }
+ if (op != P_NUMFROM) {
+#ifdef ZSH_64_BIT_TYPE
+ memcpy((char *)&to, start, sizeof(zrange_t));
+#else
+ to = *((zrange_t *) start);
+#endif
+ }
+ start = compend = patinput;
+ comp = 0;
+ while (patinput < patinend && idigit(*patinput)) {
+ int out_of_range = 0;
+ int digit = *patinput - '0';
+ if (comp > ZRANGE_MAX / (zlong)10) {
+ out_of_range = 1;
+ } else {
+ zrange_t c10 = comp ? comp * 10 : 0;
+ if (ZRANGE_MAX - c10 < digit) {
+ out_of_range = 1;
+ } else {
+ comp = c10;
+ comp += digit;
+ }
+ }
+ patinput++;
+ compend++;
+
+ if (out_of_range ||
+ (comp & ((zrange_t)1 << (sizeof(comp)*8 -
+#ifdef ZRANGE_T_IS_SIGNED
+ 2
+#else
+ 1
+#endif
+ )))) {
+ /*
+ * Out of range (allowing for signedness, which
+ * we need if we are using zlongs).
+ * This is as far as we can go.
+ * If we're doing a range "from", skip all the
+ * remaining numbers. Otherwise, we can't
+ * match beyond the previous point anyway.
+ * Leave the pointer to the last calculated
+ * position (compend) where it was before.
+ */
+ if (op == P_NUMFROM) {
+ while (patinput < patinend && idigit(*patinput))
+ patinput++;
+ }
+ }
+ }
+ save = patinput;
+ no = 0;
+ while (patinput > start) {
+ /* if already too small, no power on earth can save it */
+ if (comp < from && patinput <= compend)
+ break;
+ if ((op == P_NUMFROM || comp <= to) && patmatch(next)) {
+ return 1;
+ }
+ if (!no && P_OP(next) == P_EXACTLY &&
+ (!P_LS_LEN(next) ||
+ !idigit(STOUC(*P_LS_STR(next)))) &&
+ !(patglobflags & 0xff))
+ return 0;
+ patinput = --save;
+ no++;
+ /*
+ * With a range start and an unrepresentable test
+ * number, we just back down the test string without
+ * changing the number until we get to a representable
+ * one.
+ */
+ if (patinput < compend)
+ comp /= 10;
+ }
+ patinput = start;
+ fail = 1;
+ break;
+ case P_NUMANY:
+ /* This is <->: any old set of digits, don't bother comparing */
+ start = patinput;
+ while (patinput < patinend && idigit(*patinput))
+ patinput++;
+ save = patinput;
+ no = 0;
+ while (patinput > start) {
+ if (patmatch(next))
+ return 1;
+ if (!no && P_OP(next) == P_EXACTLY &&
+ (!P_LS_LEN(next) ||
+ !idigit(*P_LS_STR(next))) &&
+ !(patglobflags & 0xff))
+ return 0;
+ patinput = --save;
+ no++;
+ }
+ patinput = start;
+ fail = 1;
+ break;
+ case P_NOTHING:
+ break;
+ case P_BACK:
+ break;
+ case P_GFLAGS:
+ patglobflags = P_OPERAND(scan)->l;
+ break;
+ case P_OPEN:
+ case P_OPEN+1:
+ case P_OPEN+2:
+ case P_OPEN+3:
+ case P_OPEN+4:
+ case P_OPEN+5:
+ case P_OPEN+6:
+ case P_OPEN+7:
+ case P_OPEN+8:
+ case P_OPEN+9:
+ no = P_OP(scan) - P_OPEN;
+ save = patinput;
+
+ if (patmatch(next)) {
+ /*
+ * Don't set patbeginp if some later invocation of
+ * the same parentheses already has.
+ */
+ if (no && !(parsfound & (1 << (no - 1)))) {
+ patbeginp[no-1] = save;
+ parsfound |= 1 << (no - 1);
+ }
+ return 1;
+ } else
+ return 0;
+ break;
+ case P_CLOSE:
+ case P_CLOSE+1:
+ case P_CLOSE+2:
+ case P_CLOSE+3:
+ case P_CLOSE+4:
+ case P_CLOSE+5:
+ case P_CLOSE+6:
+ case P_CLOSE+7:
+ case P_CLOSE+8:
+ case P_CLOSE+9:
+ no = P_OP(scan) - P_CLOSE;
+ save = patinput;
+
+ if (patmatch(next)) {
+ if (no && !(parsfound & (1 << (no + 15)))) {
+ patendp[no-1] = save;
+ parsfound |= 1 << (no + 15);
+ }
+ return 1;
+ } else
+ return 0;
+ break;
+ case P_EXCSYNC:
+ /* See the P_EXCLUDE code below for where syncptr comes from */
+ {
+ unsigned char *syncptr;
+ Upat after;
+ after = P_OPERAND(scan);
+ DPUTS(!P_ISEXCLUDE(after),
+ "BUG: EXCSYNC not followed by EXCLUDE.");
+ DPUTS(!P_OPERAND(after)->p,
+ "BUG: EXCSYNC not handled by EXCLUDE");
+ syncptr = P_OPERAND(after)->p + (patinput - patinstart);
+ /*
+ * If we already matched from here, this time we fail.
+ * See WBRANCH code for story about error count.
+ */
+ if (*syncptr && errsfound + 1 >= *syncptr)
+ return 0;
+ /*
+ * Else record that we (possibly) matched this time.
+ * No harm if we don't: then the previous test will just
+ * short cut the attempted match that is bound to fail.
+ * We never try to exclude something that has already
+ * failed anyway.
+ */
+ *syncptr = errsfound + 1;
+ }
+ break;
+ case P_EXCEND:
+ /*
+ * This is followed by a P_EXCSYNC, but only in the P_EXCLUDE
+ * branch. Actually, we don't bother following it: all we
+ * need to know is that we successfully matched so far up
+ * to the end of the asserted pattern; the endpoint
+ * in the target string is nulled out.
+ */
+ if (!(fail = (patinput < patinend)))
+ return 1;
+ break;
+ case P_BRANCH:
+ case P_WBRANCH:
+ /* P_EXCLUDE shouldn't occur without a P_BRANCH */
+ if (!P_ISBRANCH(next)) {
+ /* no choice, avoid recursion */
+ DPUTS(P_OP(scan) == P_WBRANCH,
+ "BUG: WBRANCH with no alternative.");
+ next = P_OPERAND(scan);
+ } else {
+ do {
+ save = patinput;
+ savglobflags = patglobflags;
+ saverrsfound = errsfound;
+ if (P_ISEXCLUDE(next)) {
+ /*
+ * The strategy is to test the asserted pattern,
+ * recording via P_EXCSYNC how far the part to
+ * be excluded matched. We then set the
+ * length of the test string to that
+ * point and see if the exclusion as far as
+ * P_EXCEND also matches that string.
+ * We need to keep testing the asserted pattern
+ * by backtracking, since the first attempt
+ * may be excluded while a later attempt may not.
+ * For this we keep a pointer just after
+ * the P_EXCLUDE which is tested by the P_EXCSYNC
+ * to see if we matched there last time, in which
+ * case we fail. If there is nothing to backtrack
+ * over, that doesn't matter: we should fail anyway.
+ * The pointer also tells us where the asserted
+ * pattern matched for use by the exclusion.
+ *
+ * It's hard to allocate space for this
+ * beforehand since we may need to do it
+ * recursively.
+ *
+ * P.S. in case you were wondering, this code
+ * is horrible.
+ */
+ Upat syncstrp;
+ char *origpatinend;
+ unsigned char *oldsyncstr;
+ char *matchpt = NULL;
+ int ret, savglobdots, matchederrs = 0;
+ int savparsfound = parsfound;
+ DPUTS(P_OP(scan) == P_WBRANCH,
+ "BUG: excluded WBRANCH");
+ syncstrp = P_OPERAND(next);
+ /*
+ * Unlike WBRANCH, each test at the same exclude
+ * sync point (due to an external loop) is separate,
+ * i.e testing (foo~bar)# is no different from
+ * (foo~bar)(foo~bar)... from the exclusion point
+ * of view, so we use a different sync string.
+ */
+ oldsyncstr = syncstrp->p;
+ syncstrp->p = (unsigned char *)
+ zshcalloc((patinend - patinstart) + 1);
+ origpatinend = patinend;
+ while ((ret = patmatch(P_OPERAND(scan)))) {
+ unsigned char *syncpt;
+ char *savpatinstart;
+ int savforce = forceerrs;
+ int savpatflags = patflags, synclen;
+ forceerrs = -1;
+ savglobdots = globdots;
+ matchederrs = errsfound;
+ matchpt = patinput; /* may not be end */
+ globdots = 1; /* OK to match . first */
+ /* Find the point where the scan
+ * matched the part to be excluded: because
+ * of backtracking, the one
+ * most recently matched will be the first.
+ * (Luckily, backtracking is done after all
+ * possibilities for approximation have been
+ * checked.)
+ */
+ for (syncpt = syncstrp->p; !*syncpt; syncpt++)
+ ;
+ synclen = syncpt - syncstrp->p;
+ if (patinstart + synclen != patinend) {
+ /*
+ * Temporarily mark the string as
+ * ending at this point.
+ */
+ DPUTS(patinstart + synclen > matchpt,
+ "BUG: EXCSYNC failed");
+
+ patinend = patinstart + synclen;
+ /*
+ * If this isn't really the end of the string,
+ * remember this for the (#e) assertion.
+ */
+ patflags |= PAT_NOTEND;
+ }
+ savpatinstart = patinstart;
+ next = PATNEXT(scan);
+ while (next && P_ISEXCLUDE(next)) {
+ patinput = save;
+ /*
+ * turn off approximations in exclusions:
+ * note we keep remaining patglobflags
+ * set by asserted branch (or previous
+ * excluded branches, for consistency).
+ */
+ patglobflags &= ~0xff;
+ errsfound = 0;
+ opnd = P_OPERAND(next) + 1;
+ if (P_OP(next) == P_EXCLUDP && patinpath) {
+ /*
+ * Top level exclusion with a file,
+ * applies to whole path so add the
+ * segments already matched.
+ * We copied these in front of the
+ * test pattern, so patinend doesn't
+ * need moving.
+ */
+ DPUTS(patinput != patinstart,
+ "BUG: not at start excluding path");
+ patinput = patinstart = patinpath;
+ }
+ if (patmatch(opnd)) {
+ ret = 0;
+ /*
+ * Another subtlety: if we exclude the
+ * match, any parentheses just found
+ * become invalidated.
+ */
+ parsfound = savparsfound;
+ }
+ if (patinpath) {
+ patinput = savpatinstart +
+ (patinput - patinstart);
+ patinstart = savpatinstart;
+ }
+ if (!ret)
+ break;
+ next = PATNEXT(next);
+ }
+ /*
+ * Restore original end position.
+ */
+ patinend = origpatinend;
+ patflags = savpatflags;
+ globdots = savglobdots;
+ forceerrs = savforce;
+ if (ret)
+ break;
+ patinput = save;
+ patglobflags = savglobflags;
+ errsfound = saverrsfound;
+ }
+ zfree((char *)syncstrp->p,
+ (patinend - patinstart) + 1);
+ syncstrp->p = oldsyncstr;
+ if (ret) {
+ patinput = matchpt;
+ errsfound = matchederrs;
+ return 1;
+ }
+ while ((scan = PATNEXT(scan)) &&
+ P_ISEXCLUDE(scan))
+ ;
+ } else {
+ int ret = 1, pfree = 0;
+ Upat ptrp = NULL;
+ unsigned char *ptr;
+ if (P_OP(scan) == P_WBRANCH) {
+ /*
+ * This is where we make sure that we are not
+ * repeatedly matching zero-length strings in
+ * a closure, which would cause an infinite loop,
+ * and also remove exponential behaviour in
+ * backtracking nested closures.
+ * The P_WBRANCH operator leaves a space for a
+ * uchar *, initialized to NULL, which is
+ * turned into a string the same length as the
+ * target string. Every time we match from a
+ * particular point in the target string, we
+ * stick a 1 at the corresponding point here.
+ * If we come round to the same branch again, and
+ * there is already a 1, then the test fails.
+ */
+ opnd = P_OPERAND(scan);
+ ptrp = opnd++;
+ if (!ptrp->p) {
+ ptrp->p = (unsigned char *)
+ zshcalloc((patinend - patinstart) + 1);
+ pfree = 1;
+ }
+ ptr = ptrp->p + (patinput - patinstart);
+
+ /*
+ * Without approximation, this is just a
+ * single bit test. With approximation, we
+ * need to know how many errors there were
+ * last time we made the test. If errsfound
+ * is now smaller than it was, hence we can
+ * make more approximations in the remaining
+ * code, we continue with the test.
+ * (This is why the max number of errors is
+ * 254, not 255.)
+ */
+ if (*ptr && errsfound + 1 >= *ptr)
+ ret = 0;
+ *ptr = errsfound + 1;
+ } else
+ opnd = P_OPERAND(scan);
+ if (ret)
+ ret = patmatch(opnd);
+ if (pfree) {
+ zfree((char *)ptrp->p,
+ (patinend - patinstart) + 1);
+ ptrp->p = NULL;
+ }
+ if (ret)
+ return 1;
+ scan = PATNEXT(scan);
+ }
+ patinput = save;
+ patglobflags = savglobflags;
+ errsfound = saverrsfound;
+ DPUTS(P_OP(scan) == P_WBRANCH,
+ "BUG: WBRANCH not first choice.");
+ next = PATNEXT(scan);
+ } while (scan && P_ISBRANCH(scan));
+ return 0;
+ }
+ break;
+ case P_STAR:
+ /* Handle specially for speed, although really P_ONEHASH+P_ANY */
+ while (P_OP(next) == P_STAR) {
+ /*
+ * If there's another * following we can optimise it
+ * out. Chains of *'s can give pathologically bad
+ * performance.
+ */
+ scan = next;
+ next = PATNEXT(scan);
+ }
+ /*FALLTHROUGH*/
+ case P_ONEHASH:
+ case P_TWOHASH:
+ /*
+ * This is just simple cases, matching one character.
+ * With approximations, we still handle * this way, since
+ * no approximation is ever necessary, but other closures
+ * are handled by the more complicated branching method
+ */
+ op = P_OP(scan);
+ /* Note that no counts possibly metafied characters */
+ start = patinput;
+ {
+ char *lastcharstart;
+ /*
+ * Array to record the start of characters for
+ * backtracking.
+ */
+ VARARR(char, charstart, patinend-patinput);
+ memset(charstart, 0, patinend-patinput);
+
+ if (op == P_STAR) {
+ for (no = 0; patinput < patinend;
+ CHARINC(patinput, patinend))
+ {
+ charstart[patinput-start] = 1;
+ no++;
+ }
+ /* simple optimization for reasonably common case */
+ if (P_OP(next) == P_END)
+ return 1;
+ } else {
+ DPUTS(patglobflags & 0xff,
+ "BUG: wrong backtracking with approximation.");
+ if (!globdots && P_NOTDOT(P_OPERAND(scan)) &&
+ patinput == patinstart && patinput < patinend &&
+ CHARREF(patinput, patinend) == ZWC('.'))
+ return 0;
+ no = patrepeat(P_OPERAND(scan), charstart);
+ }
+ min = (op == P_TWOHASH) ? 1 : 0;
+ /*
+ * Lookahead to avoid useless matches. This is not possible
+ * with approximation.
+ */
+ if (P_OP(next) == P_EXACTLY && P_LS_LEN(next) &&
+ !(patglobflags & 0xff)) {
+ char *nextop = P_LS_STR(next);
+#ifdef MULTIBYTE_SUPPORT
+ /* else second argument of CHARREF isn't used */
+ int nextlen = P_LS_LEN(next);
+#endif
+ /*
+ * If that P_EXACTLY is last (common in simple patterns,
+ * such as *.c), then it can be only be matched at one
+ * point in the test string, so record that.
+ */
+ if (P_OP(PATNEXT(next)) == P_END &&
+ !(patflags & PAT_NOANCH)) {
+ int ptlen = patinend - patinput;
+ int lenmatch = patinend -
+ (min ? CHARNEXT(start, patinend) : start);
+ /* Are we in the right range? */
+ if (P_LS_LEN(next) > lenmatch ||
+ P_LS_LEN(next) < ptlen)
+ return 0;
+ /* Yes, just position appropriately and test. */
+ patinput += ptlen - P_LS_LEN(next);
+ /*
+ * Here we will need to be careful that patinput is not
+ * in the middle of a multibyte character.
+ */
+ /* Continue loop with P_EXACTLY test. */
+ break;
+ }
+ nextch = CHARREF(nextop, nextop + nextlen);
+ } else
+ nextch = PEOF;
+ savglobflags = patglobflags;
+ saverrsfound = errsfound;
+ lastcharstart = charstart + (patinput - start);
+ if (no >= min) {
+ for (;;) {
+ patint_t charmatch_cache;
+ if (nextch == PEOF ||
+ (patinput < patinend &&
+ CHARMATCH_EXPR(CHARREF(patinput, patinend),
+ nextch))) {
+ if (patmatch(next))
+ return 1;
+ }
+ if (--no < min)
+ break;
+ /* find start of previous full character */
+ while (!*--lastcharstart)
+ DPUTS(lastcharstart < charstart,
+ "lastcharstart invalid");
+ patinput = start + (lastcharstart-charstart);
+ patglobflags = savglobflags;
+ errsfound = saverrsfound;
+ }
+ }
+ }
+ /*
+ * As with branches, the patmatch(next) stuff for *
+ * handles approximation, so we don't need to try
+ * anything here.
+ */
+ return 0;
+ case P_ISSTART:
+ if (patinput != patinstart || (patflags & PAT_NOTSTART))
+ fail = 1;
+ break;
+ case P_ISEND:
+ if (patinput < patinend || (patflags & PAT_NOTEND))
+ fail = 1;
+ break;
+ case P_COUNTSTART:
+ {
+ /*
+ * Save and restore the current count and the
+ * start pointer in case the pattern has been
+ * executed by a previous repetition of a
+ * closure.
+ */
+ long *curptr = &P_OPERAND(scan)[P_CT_CURRENT].l;
+ long savecount = *curptr;
+ unsigned char *saveptr = scan[P_CT_PTR].p;
+ int ret;
+
+ *curptr = 0L;
+ ret = patmatch(P_OPERAND(scan));
+ *curptr = savecount;
+ scan[P_CT_PTR].p = saveptr;
+ return ret;
+ }
+ case P_COUNT:
+ {
+ /* (#cN,M): execution is relatively straightforward */
+ long cur = scan[P_CT_CURRENT].l;
+ long min = scan[P_CT_MIN].l;
+ long max = scan[P_CT_MAX].l;
+
+ if (cur && cur >= min &&
+ (unsigned char *)patinput == scan[P_CT_PTR].p) {
+ /*
+ * Not at the first attempt to match so
+ * the previous attempt managed zero length.
+ * We can do this indefinitely so there's
+ * no point in going on. Simply try to
+ * match the remainder of the pattern.
+ */
+ return patmatch(next);
+ }
+ scan[P_CT_PTR].p = (unsigned char *)patinput;
+
+ if (max < 0 || cur < max) {
+ char *patinput_thistime = patinput;
+ scan[P_CT_CURRENT].l = cur + 1;
+ if (patmatch(scan + P_CT_OPERAND))
+ return 1;
+ scan[P_CT_CURRENT].l = cur;
+ patinput = patinput_thistime;
+ }
+ if (cur < min)
+ return 0;
+ return patmatch(next);
+ }
+ case P_END:
+ if (!(fail = (patinput < patinend && !(patflags & PAT_NOANCH))))
+ return 1;
+ break;
+#ifdef DEBUG
+ default:
+ dputs("BUG: bad operand in patmatch.");
+ return 0;
+ break;
+#endif
+ }
+
+ if (fail) {
+ if (errsfound < (patglobflags & 0xff) &&
+ (forceerrs == -1 || errsfound < forceerrs)) {
+ /*
+ * Approximation code. There are four possibilities
+ *
+ * 1. omit character from input string
+ * 2. transpose characters in input and pattern strings
+ * 3. omit character in both input and pattern strings
+ * 4. omit character from pattern string.
+ *
+ * which we try in that order.
+ *
+ * Of these, 2, 3 and 4 require an exact match string
+ * (P_EXACTLY) while 1, 2 and 3 require that we not
+ * have reached the end of the input string.
+ *
+ * Note in each case after making the approximation we
+ * need to retry the *same* pattern; this is what
+ * requires exactpos, a slightly doleful way of
+ * communicating with the exact character matcher.
+ */
+ char *savexact = exactpos;
+ save = patinput;
+ savglobflags = patglobflags;
+ saverrsfound = ++errsfound;
+ fail = 0;
+
+ DPUTS(P_OP(scan) != P_EXACTLY && exactpos,
+ "BUG: non-exact match has set exactpos");
+
+ /* Try omitting a character from the input string */
+ if (patinput < patinend) {
+ CHARINC(patinput, patinend);
+ /* If we are not on an exact match, then this is
+ * our last gasp effort, so we can optimize out
+ * the recursive call.
+ */
+ if (P_OP(scan) != P_EXACTLY)
+ continue;
+ if (patmatch(scan))
+ return 1;
+ }
+
+ if (P_OP(scan) == P_EXACTLY) {
+ char *nextexact = savexact;
+ DPUTS(!savexact,
+ "BUG: exact match has not set exactpos");
+ CHARINC(nextexact, exactend);
+
+ if (save < patinend) {
+ char *nextin = save;
+ CHARINC(nextin, patinend);
+ patglobflags = savglobflags;
+ errsfound = saverrsfound;
+ exactpos = savexact;
+
+ /*
+ * Try swapping two characters in patinput and
+ * exactpos
+ */
+ if (save < patinend && nextin < patinend &&
+ nextexact < exactend) {
+ patint_t cin0 = CHARREF(save, patinend);
+ patint_t cpa0 = CHARREF(exactpos, exactend);
+ patint_t cin1 = CHARREF(nextin, patinend);
+ patint_t cpa1 = CHARREF(nextexact, exactend);
+
+ if (CHARMATCH(cin0, cpa1) &&
+ CHARMATCH(cin1, cpa0)) {
+ patinput = nextin;
+ CHARINC(patinput, patinend);
+ exactpos = nextexact;
+ CHARINC(exactpos, exactend);
+ if (patmatch(scan))
+ return 1;
+
+ patglobflags = savglobflags;
+ errsfound = saverrsfound;
+ }
+ }
+
+ /*
+ * Try moving up both strings.
+ */
+ patinput = nextin;
+ exactpos = nextexact;
+ if (patmatch(scan))
+ return 1;
+
+ patinput = save;
+ patglobflags = savglobflags;
+ errsfound = saverrsfound;
+ exactpos = savexact;
+ }
+
+ DPUTS(exactpos == exactend, "approximating too far");
+ /*
+ * Try moving up the exact match pattern.
+ * This must be the last attempt, so just loop
+ * instead of calling recursively.
+ */
+ CHARINC(exactpos, exactend);
+ continue;
+ }
+ }
+ exactpos = NULL;
+ return 0;
+ }
+
+ scan = next;
+
+ /* Allow handlers to run once per loop */
+ check_for_signals();
+ }
+
+ return 0;
+}
+
+
+/**/
+#ifdef MULTIBYTE_SUPPORT
+
+/*
+ * See if character ch matches a pattern range specification.
+ * The null-terminated specification is in range; the test
+ * character is in ch.
+ *
+ * zmb is one of the enum defined above charref(), for indicating
+ * incomplete or invalid multibyte characters.
+ *
+ * indptr is used by completion matching, which is why this
+ * function is exported. If indptr is not NULL we set *indptr
+ * to the index of the character in the range string, adjusted
+ * in the case of "A-B" ranges such that A would count as its
+ * normal index (say IA), B would count as IA + (B-A), and any
+ * character within the range as appropriate. We're not strictly
+ * guaranteed this fits within a wint_t, but if this is Unicode
+ * in 32 bits we have a fair amount of distance left over.
+ *
+ * mtp is used in the same circumstances. *mtp returns the match type:
+ * 0 for a standard character, else the PP_ index. It's not
+ * useful if the match failed.
+ */
+
+/**/
+mod_export int
+mb_patmatchrange(char *range, wchar_t ch, int zmb_ind, wint_t *indptr, int *mtp)
+{
+ wchar_t r1, r2;
+
+ if (indptr)
+ *indptr = 0;
+ /*
+ * Careful here: unlike other strings, range is a NULL-terminated,
+ * metafied string, because we need to treat the Posix and hyphenated
+ * ranges specially.
+ */
+ while (*range) {
+ if (imeta(STOUC(*range))) {
+ int swtype = STOUC(*range++) - STOUC(Meta);
+ if (mtp)
+ *mtp = swtype;
+ switch (swtype) {
+ case 0:
+ /* ordinary metafied character */
+ range--;
+ if (metacharinc(&range) == ch)
+ return 1;
+ break;
+ case PP_ALPHA:
+ if (iswalpha(ch))
+ return 1;
+ break;
+ case PP_ALNUM:
+ if (iswalnum(ch))
+ return 1;
+ break;
+ case PP_ASCII:
+ if ((ch & ~0x7f) == 0)
+ return 1;
+ break;
+ case PP_BLANK:
+#if !defined(HAVE_ISWBLANK) && !defined(iswblank)
+/*
+ * iswblank() is GNU and C99. There's a remote chance that some
+ * systems still don't support it (but would support the other ones
+ * if MULTIBYTE_SUPPORT is enabled).
+ */
+#define iswblank(c) (c == L' ' || c == L'\t')
+#endif
+ if (iswblank(ch))
+ return 1;
+ break;
+ case PP_CNTRL:
+ if (iswcntrl(ch))
+ return 1;
+ break;
+ case PP_DIGIT:
+ if (iswdigit(ch))
+ return 1;
+ break;
+ case PP_GRAPH:
+ if (iswgraph(ch))
+ return 1;
+ break;
+ case PP_LOWER:
+ if (iswlower(ch))
+ return 1;
+ break;
+ case PP_PRINT:
+ if (WC_ISPRINT(ch))
+ return 1;
+ break;
+ case PP_PUNCT:
+ if (iswpunct(ch))
+ return 1;
+ break;
+ case PP_SPACE:
+ if (iswspace(ch))
+ return 1;
+ break;
+ case PP_UPPER:
+ if (iswupper(ch))
+ return 1;
+ break;
+ case PP_XDIGIT:
+ if (iswxdigit(ch))
+ return 1;
+ break;
+ case PP_IDENT:
+ if (wcsitype(ch, IIDENT))
+ return 1;
+ break;
+ case PP_IFS:
+ if (wcsitype(ch, ISEP))
+ return 1;
+ break;
+ case PP_IFSSPACE:
+ /* must be ASCII space character */
+ if (ch < 128 && iwsep((int)ch))
+ return 1;
+ break;
+ case PP_WORD:
+ if (wcsitype(ch, IWORD))
+ return 1;
+ break;
+ case PP_RANGE:
+ r1 = metacharinc(&range);
+ r2 = metacharinc(&range);
+ if (r1 <= ch && ch <= r2) {
+ if (indptr)
+ *indptr += ch - r1;
+ return 1;
+ }
+ /* Careful not to screw up counting with bogus range */
+ if (indptr && r1 < r2) {
+ /*
+ * This gets incremented again below to get
+ * us past the range end. This is correct.
+ */
+ *indptr += r2 - r1;
+ }
+ break;
+ case PP_INCOMPLETE:
+ if (zmb_ind == ZMB_INCOMPLETE)
+ return 1;
+ break;
+ case PP_INVALID:
+ if (zmb_ind == ZMB_INVALID)
+ return 1;
+ break;
+ case PP_UNKWN:
+ DPUTS(1, "BUG: unknown posix range passed through.\n");
+ break;
+ default:
+ DPUTS(1, "BUG: unknown metacharacter in range.");
+ break;
+ }
+ } else if (metacharinc(&range) == ch) {
+ if (mtp)
+ *mtp = 0;
+ return 1;
+ }
+ if (indptr)
+ (*indptr)++;
+ }
+ return 0;
+}
+
+
+/*
+ * This is effectively the reverse of mb_patmatchrange().
+ * Given a range descriptor of the same form, and an index into it,
+ * try to determine the character that is matched. If the index
+ * points to a [:...:] generic style match, set chr to WEOF and
+ * return the type in mtp instead. Return 1 if successful, 0 if
+ * there was no corresponding index. Note all pointer arguments
+ * must be non-null.
+ */
+
+/**/
+mod_export int
+mb_patmatchindex(char *range, wint_t ind, wint_t *chr, int *mtp)
+{
+ wchar_t r1, r2, rchr;
+ wint_t rdiff;
+
+ *chr = WEOF;
+ *mtp = 0;
+
+ while (*range) {
+ if (imeta(STOUC(*range))) {
+ int swtype = STOUC(*range++) - STOUC(Meta);
+ switch (swtype) {
+ case 0:
+ range--;
+ rchr = metacharinc(&range);
+ if (!ind) {
+ *chr = (wint_t) rchr;
+ return 1;
+ }
+ break;
+
+ case PP_ALPHA:
+ case PP_ALNUM:
+ case PP_ASCII:
+ case PP_BLANK:
+ case PP_CNTRL:
+ case PP_DIGIT:
+ case PP_GRAPH:
+ case PP_LOWER:
+ case PP_PRINT:
+ case PP_PUNCT:
+ case PP_SPACE:
+ case PP_UPPER:
+ case PP_XDIGIT:
+ case PP_IDENT:
+ case PP_IFS:
+ case PP_IFSSPACE:
+ case PP_WORD:
+ case PP_INCOMPLETE:
+ case PP_INVALID:
+ if (!ind) {
+ *mtp = swtype;
+ return 1;
+ }
+ break;
+
+ case PP_RANGE:
+ r1 = metacharinc(&range);
+ r2 = metacharinc(&range);
+ rdiff = (wint_t)r2 - (wint_t)r1;
+ if (rdiff >= ind) {
+ *chr = (wint_t)r1 + ind;
+ return 1;
+ }
+ /* note the extra decrement to ind below */
+ ind -= rdiff;
+ break;
+ case PP_UNKWN:
+ DPUTS(1, "BUG: unknown posix range passed through.\n");
+ break;
+ default:
+ DPUTS(1, "BUG: unknown metacharacter in range.");
+ break;
+ }
+ } else {
+ rchr = metacharinc(&range);
+ if (!ind) {
+ *chr = (wint_t)rchr;
+ return 1;
+ }
+ }
+ if (!ind--)
+ break;
+ }
+
+ /* No corresponding index. */
+ return 0;
+}
+
+/**/
+#endif /* MULTIBYTE_SUPPORT */
+
+/*
+ * Identical function to mb_patmatchrange() above for single-byte
+ * characters.
+ */
+
+/**/
+mod_export int
+patmatchrange(char *range, int ch, int *indptr, int *mtp)
+{
+ int r1, r2;
+
+ if (indptr)
+ *indptr = 0;
+ /*
+ * Careful here: unlike other strings, range is a NULL-terminated,
+ * metafied string, because we need to treat the Posix and hyphenated
+ * ranges specially.
+ */
+ for (; *range; range++) {
+ if (imeta(STOUC(*range))) {
+ int swtype = STOUC(*range) - STOUC(Meta);
+ if (mtp)
+ *mtp = swtype;
+ switch (swtype) {
+ case 0:
+ if (STOUC(*++range ^ 32) == ch)
+ return 1;
+ break;
+ case PP_ALPHA:
+ if (isalpha(ch))
+ return 1;
+ break;
+ case PP_ALNUM:
+ if (isalnum(ch))
+ return 1;
+ break;
+ case PP_ASCII:
+ if ((ch & ~0x7f) == 0)
+ return 1;
+ break;
+ case PP_BLANK:
+#if !defined(HAVE_ISBLANK) && !defined(isblank)
+/*
+ * isblank() is GNU and C99. There's a remote chance that some
+ * systems still don't support it.
+ */
+#define isblank(c) (c == ' ' || c == '\t')
+#endif
+ if (isblank(ch))
+ return 1;
+ break;
+ case PP_CNTRL:
+ if (iscntrl(ch))
+ return 1;
+ break;
+ case PP_DIGIT:
+ if (isdigit(ch))
+ return 1;
+ break;
+ case PP_GRAPH:
+ if (isgraph(ch))
+ return 1;
+ break;
+ case PP_LOWER:
+ if (islower(ch))
+ return 1;
+ break;
+ case PP_PRINT:
+ if (ZISPRINT(ch))
+ return 1;
+ break;
+ case PP_PUNCT:
+ if (ispunct(ch))
+ return 1;
+ break;
+ case PP_SPACE:
+ if (isspace(ch))
+ return 1;
+ break;
+ case PP_UPPER:
+ if (isupper(ch))
+ return 1;
+ break;
+ case PP_XDIGIT:
+ if (isxdigit(ch))
+ return 1;
+ break;
+ case PP_IDENT:
+ if (iident(ch))
+ return 1;
+ break;
+ case PP_IFS:
+ if (isep(ch))
+ return 1;
+ break;
+ case PP_IFSSPACE:
+ if (iwsep(ch))
+ return 1;
+ break;
+ case PP_WORD:
+ if (iword(ch))
+ return 1;
+ break;
+ case PP_RANGE:
+ range++;
+ r1 = STOUC(UNMETA(range));
+ METACHARINC(range);
+ r2 = STOUC(UNMETA(range));
+ if (*range == Meta)
+ range++;
+ if (r1 <= ch && ch <= r2) {
+ if (indptr)
+ *indptr += ch - r1;
+ return 1;
+ }
+ if (indptr && r1 < r2)
+ *indptr += r2 - r1;
+ break;
+ case PP_INCOMPLETE:
+ case PP_INVALID:
+ /* Never true if not in multibyte mode */
+ break;
+ case PP_UNKWN:
+ DPUTS(1, "BUG: unknown posix range passed through.\n");
+ break;
+ default:
+ DPUTS(1, "BUG: unknown metacharacter in range.");
+ break;
+ }
+ } else if (STOUC(*range) == ch) {
+ if (mtp)
+ *mtp = 0;
+ return 1;
+ }
+ if (indptr)
+ (*indptr)++;
+ }
+ return 0;
+}
+
+
+/**/
+#ifndef MULTIBYTE_SUPPORT
+
+/*
+ * Identical function to mb_patmatchindex() above for single-byte
+ * characters. Here -1 represents a character that needs a special type.
+ *
+ * Unlike patmatchrange, we only need this in ZLE, which always
+ * uses MULTIBYTE_SUPPORT if compiled in; hence we don't use
+ * this function in that case.
+ */
+
+/**/
+mod_export int
+patmatchindex(char *range, int ind, int *chr, int *mtp)
+{
+ int r1, r2, rdiff, rchr;
+
+ *chr = -1;
+ *mtp = 0;
+
+ for (; *range; range++) {
+ if (imeta(STOUC(*range))) {
+ int swtype = STOUC(*range) - STOUC(Meta);
+ switch (swtype) {
+ case 0:
+ /* ordinary metafied character */
+ rchr = STOUC(*++range) ^ 32;
+ if (!ind) {
+ *chr = rchr;
+ return 1;
+ }
+ break;
+
+ case PP_ALPHA:
+ case PP_ALNUM:
+ case PP_ASCII:
+ case PP_BLANK:
+ case PP_CNTRL:
+ case PP_DIGIT:
+ case PP_GRAPH:
+ case PP_LOWER:
+ case PP_PRINT:
+ case PP_PUNCT:
+ case PP_SPACE:
+ case PP_UPPER:
+ case PP_XDIGIT:
+ case PP_IDENT:
+ case PP_IFS:
+ case PP_IFSSPACE:
+ case PP_WORD:
+ case PP_INCOMPLETE:
+ case PP_INVALID:
+ if (!ind) {
+ *mtp = swtype;
+ return 1;
+ }
+ break;
+
+ case PP_RANGE:
+ range++;
+ r1 = STOUC(UNMETA(range));
+ METACHARINC(range);
+ r2 = STOUC(UNMETA(range));
+ if (*range == Meta)
+ range++;
+ rdiff = r2 - r1;
+ if (rdiff >= ind) {
+ *chr = r1 + ind;
+ return 1;
+ }
+ /* note the extra decrement to ind below */
+ ind -= rdiff;
+ break;
+ case PP_UNKWN:
+ DPUTS(1, "BUG: unknown posix range passed through.\n");
+ break;
+ default:
+ DPUTS(1, "BUG: unknown metacharacter in range.");
+ break;
+ }
+ } else {
+ if (!ind) {
+ *chr = STOUC(*range);
+ return 1;
+ }
+ }
+ if (!ind--)
+ break;
+ }
+
+ /* No corresponding index. */
+ return 0;
+}
+
+/**/
+#endif /* MULTIBYTE_SUPPORT */
+
+/*
+ * Repeatedly match something simple and say how many times.
+ * charstart is an array parallel to that starting at patinput
+ * and records the start of (possibly multibyte) characters
+ * to aid in later backtracking.
+ */
+
+/**/
+static int patrepeat(Upat p, char *charstart)
+{
+ int count = 0;
+ patint_t tch, charmatch_cache;
+ char *scan, *opnd;
+
+ scan = patinput;
+ opnd = (char *)P_OPERAND(p);
+
+ switch(P_OP(p)) {
+#ifdef DEBUG
+ case P_ANY:
+ dputs("BUG: ?# did not get optimized to *");
+ return 0;
+ break;
+#endif
+ case P_EXACTLY:
+ DPUTS(P_LS_LEN(p) != 1, "closure following more than one character");
+ tch = CHARREF(P_LS_STR(p), P_LS_STR(p) + P_LS_LEN(p));
+ while (scan < patinend &&
+ CHARMATCH_EXPR(CHARREF(scan, patinend), tch)) {
+ charstart[scan-patinput] = 1;
+ count++;
+ CHARINC(scan, patinend);
+ }
+ break;
+ case P_ANYOF:
+ case P_ANYBUT:
+ while (scan < patinend) {
+#ifdef MULTIBYTE_SUPPORT
+ int zmb_ind;
+ wchar_t cr = charref(scan, patinend, &zmb_ind);
+ if (patglobflags & GF_MULTIBYTE) {
+ if (mb_patmatchrange(opnd, cr, zmb_ind, NULL, NULL) ^
+ (P_OP(p) == P_ANYOF))
+ break;
+ } else if (patmatchrange(opnd, (int)cr, NULL, NULL) ^
+ (P_OP(p) == P_ANYOF))
+ break;
+#else
+ if (patmatchrange(opnd, CHARREF(scan, patinend), NULL, NULL) ^
+ (P_OP(p) == P_ANYOF))
+ break;
+#endif
+ charstart[scan-patinput] = 1;
+ count++;
+ CHARINC(scan, patinend);
+ }
+ break;
+#ifdef DEBUG
+ default:
+ dputs("BUG: something very strange is happening in patrepeat");
+ return 0;
+ break;
+#endif
+ }
+
+ patinput = scan;
+ return count;
+}
+
+/* Free a patprog. */
+
+/**/
+mod_export void
+freepatprog(Patprog prog)
+{
+ if (prog && prog != dummy_patprog1 && prog != dummy_patprog2)
+ zfree(prog, prog->size);
+}
+
+/* Disable or reenable a pattern character */
+
+/**/
+int
+pat_enables(const char *cmd, char **patp, int enable)
+{
+ int ret = 0;
+ const char **stringp;
+ char *disp;
+
+ if (!*patp) {
+ int done = 0;
+ for (stringp = zpc_strings, disp = zpc_disables;
+ stringp < zpc_strings + ZPC_COUNT;
+ stringp++, disp++) {
+ if (!*stringp)
+ continue;
+ if (enable ? *disp : !*disp)
+ continue;
+ if (done)
+ putc(' ', stdout);
+ printf("'%s'", *stringp);
+ done = 1;
+ }
+ if (done)
+ putc('\n', stdout);
+ return 0;
+ }
+
+ for (; *patp; patp++) {
+ for (stringp = zpc_strings, disp = zpc_disables;
+ stringp < zpc_strings + ZPC_COUNT;
+ stringp++, disp++) {
+ if (*stringp && !strcmp(*stringp, *patp)) {
+ *disp = (char)!enable;
+ break;
+ }
+ }
+ if (stringp == zpc_strings + ZPC_COUNT) {
+ zerrnam(cmd, "invalid pattern: %s", *patp);
+ ret = 1;
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * Save the current state of pattern disables, returning the saved value.
+ */
+
+/**/
+unsigned int
+savepatterndisables(void)
+{
+ unsigned int disables, bit;
+ char *disp;
+
+ disables = 0;
+ for (bit = 1, disp = zpc_disables;
+ disp < zpc_disables + ZPC_COUNT;
+ bit <<= 1, disp++) {
+ if (*disp)
+ disables |= bit;
+ }
+ return disables;
+}
+
+/*
+ * Function scope saving pattern enables.
+ */
+
+/**/
+void
+startpatternscope(void)
+{
+ Zpc_disables_save newdis;
+
+ newdis = (Zpc_disables_save)zalloc(sizeof(*newdis));
+ newdis->next = zpc_disables_stack;
+ newdis->disables = savepatterndisables();
+
+ zpc_disables_stack = newdis;
+}
+
+/*
+ * Restore completely the state of pattern disables.
+ */
+
+/**/
+void
+restorepatterndisables(unsigned int disables)
+{
+ char *disp;
+ unsigned int bit;
+
+ for (bit = 1, disp = zpc_disables;
+ disp < zpc_disables + ZPC_COUNT;
+ bit <<= 1, disp++) {
+ if (disables & bit)
+ *disp = 1;
+ else
+ *disp = 0;
+ }
+}
+
+/*
+ * Function scope to restore pattern enables if localpatterns is turned on.
+ */
+
+/**/
+void
+endpatternscope(void)
+{
+ Zpc_disables_save olddis;
+
+ olddis = zpc_disables_stack;
+ zpc_disables_stack = olddis->next;
+
+ if (isset(LOCALPATTERNS))
+ restorepatterndisables(olddis->disables);
+
+ zfree(olddis, sizeof(*olddis));
+}
+
+/* Reinitialise pattern disables */
+
+/**/
+void
+clearpatterndisables(void)
+{
+ memset(zpc_disables, 0, ZPC_COUNT);
+}
+
+
+/* Check to see if str is eligible for filename generation. */
+
+/**/
+mod_export int
+haswilds(char *str)
+{
+ char *start;
+
+ /* `[' 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] = '?';
+
+ /*
+ * Note that at this point zpc_special has not been set up.
+ */
+ start = str;
+ for (; *str; str++) {
+ switch (*str) {
+ case Inpar:
+ if ((!isset(SHGLOB) && !zpc_disables[ZPC_INPAR]) ||
+ (str > start && isset(KSHGLOB) &&
+ ((str[-1] == Quest && !zpc_disables[ZPC_KSH_QUEST]) ||
+ (str[-1] == Star && !zpc_disables[ZPC_KSH_STAR]) ||
+ (str[-1] == '+' && !zpc_disables[ZPC_KSH_PLUS]) ||
+ (str[-1] == Bang && !zpc_disables[ZPC_KSH_BANG]) ||
+ (str[-1] == '!' && !zpc_disables[ZPC_KSH_BANG2]) ||
+ (str[-1] == '@' && !zpc_disables[ZPC_KSH_AT]))))
+ return 1;
+ break;
+
+ case Bar:
+ if (!zpc_disables[ZPC_BAR])
+ return 1;
+ break;
+
+ case Star:
+ if (!zpc_disables[ZPC_STAR])
+ return 1;
+ break;
+
+ case Inbrack:
+ if (!zpc_disables[ZPC_INBRACK])
+ return 1;
+ break;
+
+ case Inang:
+ if (!zpc_disables[ZPC_INANG])
+ return 1;
+ break;
+
+ case Quest:
+ if (!zpc_disables[ZPC_QUEST])
+ return 1;
+ break;
+
+ case Pound:
+ if (isset(EXTENDEDGLOB) && !zpc_disables[ZPC_HASH])
+ return 1;
+ break;
+
+ case Hat:
+ if (isset(EXTENDEDGLOB) && !zpc_disables[ZPC_HAT])
+ return 1;
+ break;
+ }
+ }
+ return 0;
+}
diff --git a/dotfiles/system/.zsh/modules/Src/prompt.c b/dotfiles/system/.zsh/modules/Src/prompt.c
new file mode 100644
index 0000000..959ed8e
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/prompt.c
@@ -0,0 +1,2046 @@
+/*
+ * 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 */
+
+/**/
+mod_export unsigned txtattrmask;
+
+/* the command stack for use with %_ in prompts */
+
+/**/
+unsigned char *cmdstack;
+/**/
+int cmdsp;
+
+/* parser states, for %_ */
+
+static char *cmdnames[CS_COUNT] = {
+ "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", "always",
+};
+
+
+struct buf_vars;
+
+struct buf_vars {
+/* Previous set of prompt variables on the stack. */
+
+ struct buf_vars *last;
+
+/* The buffer into which an expanded and metafied prompt is being written, *
+ * and its size. */
+
+ char *buf;
+ int bufspc;
+
+/* bp is the pointer to the current position in the buffer, where the next *
+ * character will be added. */
+
+ char *bp;
+
+/* Position of the start of the current line in the buffer */
+
+ char *bufline;
+
+/* bp1 is an auxiliary 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. */
+
+ char *bp1;
+
+/* The format string, for %-expansion. */
+
+ char *fm;
+
+/* Non-zero if truncating the current segment of the buffer. */
+
+ int truncwidth;
+
+/* Current level of nesting of %{ / %} sequences. */
+
+ int dontcount;
+
+/* Level of %{ / %} surrounding a truncation segment. */
+
+ int trunccount;
+
+/* Strings to use for %r and %R (for the spelling prompt). */
+
+ char *rstring, *Rstring;
+};
+
+typedef struct buf_vars *Buf_vars;
+
+/* The currently active prompt output variables */
+static Buf_vars bv;
+
+/*
+ * Expand path p; maximum is npath segments where 0 means the whole path.
+ * If tilde is 1, try and find a named directory to use.
+ */
+
+static void
+promptpath(char *p, int npath, int tilde)
+{
+ char *modp = p;
+ Nameddir nd;
+
+ if (tilde && ((nd = finddir(p))))
+ modp = tricat("~", nd->node.nam, p + strlen(nd->dir));
+
+ if (npath) {
+ char *sptr;
+ if (npath > 0) {
+ for (sptr = modp + strlen(modp); sptr > modp; sptr--) {
+ if (*sptr == '/' && !--npath) {
+ sptr++;
+ break;
+ }
+ }
+ if (*sptr == '/' && sptr[1] && sptr != modp)
+ sptr++;
+ stradd(sptr);
+ } else {
+ char cbu;
+ for (sptr = modp+1; *sptr; sptr++)
+ if (*sptr == '/' && !++npath)
+ break;
+ cbu = *sptr;
+ *sptr = 0;
+ stradd(modp);
+ *sptr = cbu;
+ }
+ } else
+ stradd(modp);
+
+ if (p != modp)
+ zsfree(modp);
+}
+
+/*
+ * 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.
+ *
+ * txtchangep gives an integer controlling the attributes of
+ * the prompt. This is for use in zle to maintain the attributes
+ * consistenly. Other parts of the shell should not need to use it.
+ */
+
+/**/
+mod_export char *
+promptexpand(char *s, int ns, char *rs, char *Rs, unsigned int *txtchangep)
+{
+ struct buf_vars new_vars;
+
+ if(!s)
+ return ztrdup("");
+
+ if ((termflags & TERM_UNKNOWN) && (unset(INTERACTIVE)))
+ init_term();
+
+ if (isset(PROMPTSUBST)) {
+ int olderr = errflag;
+ int oldval = lastval;
+
+ s = dupstring(s);
+ if (!parsestr(&s))
+ singsub(&s);
+ /*
+ * We don't need the special Nularg hack here and we're
+ * going to be using Nularg for other things.
+ */
+ if (*s == Nularg && s[1] == '\0')
+ *s = '\0';
+
+ /*
+ * Ignore errors and status change in prompt substitution.
+ * However, keep any user interrupt error that occurred.
+ */
+ errflag = olderr | (errflag & ERRFLAG_INT);
+ lastval = oldval;
+ }
+
+ memset(&new_vars, 0, sizeof(new_vars));
+ new_vars.last = bv;
+ bv = &new_vars;
+
+ new_vars.rstring = rs;
+ new_vars.Rstring = Rs;
+ new_vars.fm = s;
+ new_vars.bufspc = 256;
+ new_vars.bp = new_vars.bufline = new_vars.buf = zshcalloc(new_vars.bufspc);
+ new_vars.bp1 = NULL;
+ new_vars.truncwidth = 0;
+
+ putpromptchar(1, '\0', txtchangep);
+ addbufspc(2);
+ if (new_vars.dontcount)
+ *new_vars.bp++ = Outpar;
+ *new_vars.bp = '\0';
+ if (!ns) {
+ /* If zero, Inpar, Outpar and Nularg should be removed. */
+ for (new_vars.bp = new_vars.buf; *new_vars.bp; ) {
+ if (*new_vars.bp == Meta)
+ new_vars.bp += 2;
+ else if (*new_vars.bp == Inpar || *new_vars.bp == Outpar ||
+ *new_vars.bp == Nularg)
+ chuck(new_vars.bp);
+ else
+ new_vars.bp++;
+ }
+ }
+
+ bv = new_vars.last;
+
+ return new_vars.buf;
+}
+
+/* Parse the argument for %F and %K */
+static int
+parsecolorchar(int arg, int is_fg)
+{
+ if (bv->fm[1] == '{') {
+ char *ep;
+ bv->fm += 2; /* skip over F{ */
+ if ((ep = strchr(bv->fm, '}'))) {
+ char oc = *ep, *col, *coll;
+ *ep = '\0';
+ /* expand the contents of the argument so you can use
+ * %v for example */
+ coll = col = promptexpand(bv->fm, 0, NULL, NULL, NULL);
+ *ep = oc;
+ arg = match_colour((const char **)&coll, is_fg, 0);
+ free(col);
+ bv->fm = ep;
+ } else {
+ arg = match_colour((const char **)&bv->fm, is_fg, 0);
+ if (*bv->fm != '}')
+ bv->fm--;
+ }
+ } else
+ arg = match_colour(NULL, 1, arg);
+ return arg;
+}
+
+/* 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, unsigned int *txtchangep)
+{
+ char *ss, *hostnam;
+ int t0, arg, test, sep, j, numjobs, len;
+ struct tm *tm;
+ struct timespec ts;
+ time_t timet;
+ Nameddir nd;
+
+ for (; *bv->fm && *bv->fm != endchar; bv->fm++) {
+ arg = 0;
+ if (*bv->fm == '%' && isset(PROMPTPERCENT)) {
+ int minus = 0;
+ bv->fm++;
+ if (*bv->fm == '-') {
+ minus = 1;
+ bv->fm++;
+ }
+ if (idigit(*bv->fm)) {
+ arg = zstrtol(bv->fm, &bv->fm, 10);
+ if (minus)
+ arg *= -1;
+ } else if (minus)
+ arg = -1;
+ if (*bv->fm == '(') {
+ int tc, otruncwidth;
+
+ if (idigit(*++bv->fm)) {
+ arg = zstrtol(bv->fm, &bv->fm, 10);
+ } else if (arg < 0) {
+ /* negative numbers don't make sense here */
+ arg *= -1;
+ }
+ test = 0;
+ ss = pwd;
+ switch (tc = *bv->fm) {
+ case 'c':
+ case '.':
+ case '~':
+ if ((nd = finddir(ss))) {
+ arg--;
+ ss += strlen(nd->dir);
+ } /*FALLTHROUGH*/
+ case '/':
+ case 'C':
+ /* `/' gives 0, `/any' gives 1, etc. */
+ if (*ss && *ss++ == '/' && *ss)
+ arg--;
+ 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() == (uid_t)arg)
+ test = 1;
+ break;
+ case 'g':
+ if (getegid() == (gid_t)arg)
+ test = 1;
+ break;
+ case 'j':
+ for (numjobs = 0, j = 1; j <= maxjob; j++)
+ if (jobtab[j].stat && jobtab[j].procs &&
+ !(jobtab[j].stat & STAT_NOPRINT)) numjobs++;
+ if (numjobs >= arg)
+ test = 1;
+ break;
+ case 'l':
+ *bv->bp = '\0';
+ countprompt(bv->bufline, &t0, 0, 0);
+ if (minus)
+ t0 = zterm_columns - t0;
+ if (t0 >= arg)
+ test = 1;
+ break;
+ case 'e':
+ {
+ Funcstack fsptr = funcstack;
+ test = arg;
+ while (fsptr && test > 0) {
+ test--;
+ fsptr = fsptr->prev;
+ }
+ test = !test;
+ }
+ 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_ge(psvar, arg))
+ test = 1;
+ break;
+ case 'V':
+ if (psvar && *psvar && arrlen_ge(psvar, arg)) {
+ if (*psvar[(arg ? arg : 1) - 1])
+ test = 1;
+ }
+ break;
+ case '_':
+ test = (cmdsp >= arg);
+ break;
+ case '!':
+ test = privasserted();
+ break;
+ default:
+ test = -1;
+ break;
+ }
+ if (!*bv->fm || !(sep = *++bv->fm))
+ return 0;
+ bv->fm++;
+ /* Don't do the current truncation until we get back */
+ otruncwidth = bv->truncwidth;
+ bv->truncwidth = 0;
+ if (!putpromptchar(test == 1 && doprint, sep,
+ txtchangep) || !*++bv->fm ||
+ !putpromptchar(test == 0 && doprint, ')',
+ txtchangep)) {
+ bv->truncwidth = otruncwidth;
+ return 0;
+ }
+ bv->truncwidth = otruncwidth;
+ continue;
+ }
+ if (!doprint)
+ switch(*bv->fm) {
+ case '[':
+ while(idigit(*++bv->fm));
+ while(*++bv->fm != ']');
+ continue;
+ case '<':
+ while(*++bv->fm != '<');
+ continue;
+ case '>':
+ while(*++bv->fm != '>');
+ continue;
+ case 'D':
+ if(bv->fm[1]=='{')
+ while(*++bv->fm != '}');
+ continue;
+ default:
+ continue;
+ }
+ switch (*bv->fm) {
+ case '~':
+ promptpath(pwd, arg, 1);
+ break;
+ case 'd':
+ case '/':
+ promptpath(pwd, arg, 0);
+ break;
+ case 'c':
+ case '.':
+ promptpath(pwd, arg ? arg : 1, 1);
+ break;
+ case 'C':
+ promptpath(pwd, arg ? arg : 1, 0);
+ break;
+ case 'N':
+ promptpath(scriptname ? scriptname : argzero, arg, 0);
+ break;
+ case 'h':
+ case '!':
+ addbufspc(DIGBUFSIZE);
+ convbase(bv->bp, curhist, 10);
+ bv->bp += strlen(bv->bp);
+ break;
+ case 'j':
+ for (numjobs = 0, j = 1; j <= maxjob; j++)
+ if (jobtab[j].stat && jobtab[j].procs &&
+ !(jobtab[j].stat & STAT_NOPRINT)) numjobs++;
+ addbufspc(DIGBUFSIZE);
+ sprintf(bv->bp, "%d", numjobs);
+ bv->bp += strlen(bv->bp);
+ break;
+ case 'M':
+ queue_signals();
+ if ((hostnam = getsparam("HOST")))
+ stradd(hostnam);
+ unqueue_signals();
+ break;
+ case 'm':
+ if (!arg)
+ arg++;
+ queue_signals();
+ if (!(hostnam = getsparam("HOST"))) {
+ unqueue_signals();
+ break;
+ }
+ if (arg < 0) {
+ for (ss = hostnam + strlen(hostnam); ss > hostnam; ss--)
+ if (ss[-1] == '.' && !++arg)
+ break;
+ stradd(ss);
+ } else {
+ for (ss = hostnam; *ss; ss++)
+ if (*ss == '.' && !--arg)
+ break;
+ stradd(*ss ? dupstrpfx(hostnam, ss - hostnam) : hostnam);
+ }
+ unqueue_signals();
+ break;
+ case 'S':
+ txtchangeset(txtchangep, TXTSTANDOUT, TXTNOSTANDOUT);
+ txtset(TXTSTANDOUT);
+ tsetcap(TCSTANDOUTBEG, TSC_PROMPT);
+ break;
+ case 's':
+ txtchangeset(txtchangep, TXTNOSTANDOUT, TXTSTANDOUT);
+ txtunset(TXTSTANDOUT);
+ tsetcap(TCSTANDOUTEND, TSC_PROMPT|TSC_DIRTY);
+ break;
+ case 'B':
+ txtchangeset(txtchangep, TXTBOLDFACE, TXTNOBOLDFACE);
+ txtset(TXTBOLDFACE);
+ tsetcap(TCBOLDFACEBEG, TSC_PROMPT|TSC_DIRTY);
+ break;
+ case 'b':
+ txtchangeset(txtchangep, TXTNOBOLDFACE, TXTBOLDFACE);
+ txtunset(TXTBOLDFACE);
+ tsetcap(TCALLATTRSOFF, TSC_PROMPT|TSC_DIRTY);
+ break;
+ case 'U':
+ txtchangeset(txtchangep, TXTUNDERLINE, TXTNOUNDERLINE);
+ txtset(TXTUNDERLINE);
+ tsetcap(TCUNDERLINEBEG, TSC_PROMPT);
+ break;
+ case 'u':
+ txtchangeset(txtchangep, TXTNOUNDERLINE, TXTUNDERLINE);
+ txtunset(TXTUNDERLINE);
+ tsetcap(TCUNDERLINEEND, TSC_PROMPT|TSC_DIRTY);
+ break;
+ case 'F':
+ arg = parsecolorchar(arg, 1);
+ if (arg >= 0 && !(arg & TXTNOFGCOLOUR)) {
+ txtchangeset(txtchangep, arg & TXT_ATTR_FG_ON_MASK,
+ TXTNOFGCOLOUR | TXT_ATTR_FG_COL_MASK);
+ txtunset(TXT_ATTR_FG_COL_MASK);
+ txtset(arg & TXT_ATTR_FG_ON_MASK);
+ set_colour_attribute(arg, COL_SEQ_FG, TSC_PROMPT);
+ break;
+ }
+ /* else FALLTHROUGH */
+ case 'f':
+ txtchangeset(txtchangep, TXTNOFGCOLOUR, TXT_ATTR_FG_ON_MASK);
+ txtunset(TXT_ATTR_FG_ON_MASK);
+ set_colour_attribute(TXTNOFGCOLOUR, COL_SEQ_FG, TSC_PROMPT);
+ break;
+ case 'K':
+ arg = parsecolorchar(arg, 0);
+ if (arg >= 0 && !(arg & TXTNOBGCOLOUR)) {
+ txtchangeset(txtchangep, arg & TXT_ATTR_BG_ON_MASK,
+ TXTNOBGCOLOUR | TXT_ATTR_BG_COL_MASK);
+ txtunset(TXT_ATTR_BG_COL_MASK);
+ txtset(arg & TXT_ATTR_BG_ON_MASK);
+ set_colour_attribute(arg, COL_SEQ_BG, TSC_PROMPT);
+ break;
+ }
+ /* else FALLTHROUGH */
+ case 'k':
+ txtchangeset(txtchangep, TXTNOBGCOLOUR, TXT_ATTR_BG_ON_MASK);
+ txtunset(TXT_ATTR_BG_ON_MASK);
+ set_colour_attribute(TXTNOBGCOLOUR, COL_SEQ_BG, TSC_PROMPT);
+ break;
+ case '[':
+ if (idigit(*++bv->fm))
+ arg = zstrtol(bv->fm, &bv->fm, 10);
+ if (!prompttrunc(arg, ']', doprint, endchar, txtchangep))
+ return *bv->fm;
+ break;
+ case '<':
+ case '>':
+ /* Test (minus) here so -0 means "at the right margin" */
+ if (minus) {
+ *bv->bp = '\0';
+ countprompt(bv->bufline, &t0, 0, 0);
+ arg = zterm_columns - t0 + arg;
+ if (arg <= 0)
+ arg = 1;
+ }
+ if (!prompttrunc(arg, *bv->fm, doprint, endchar, txtchangep))
+ return *bv->fm;
+ break;
+ case '{': /*}*/
+ if (!bv->dontcount++) {
+ addbufspc(1);
+ *bv->bp++ = Inpar;
+ }
+ if (arg <= 0)
+ break;
+ /* else */
+ /* FALLTHROUGH */
+ case 'G':
+ if (arg > 0) {
+ addbufspc(arg);
+ while (arg--)
+ *bv->bp++ = Nularg;
+ } else {
+ addbufspc(1);
+ *bv->bp++ = Nularg;
+ }
+ break;
+ case /*{*/ '}':
+ if (bv->trunccount && bv->trunccount >= bv->dontcount)
+ return *bv->fm;
+ if (bv->dontcount && !--bv->dontcount) {
+ addbufspc(1);
+ *bv->bp++ = Outpar;
+ }
+ break;
+ case 't':
+ case '@':
+ case 'T':
+ case '*':
+ case 'w':
+ case 'W':
+ case 'D':
+ {
+ char *tmfmt, *dd, *tmbuf = NULL;
+
+ switch (*bv->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 (bv->fm[1] == '{' /*}*/) {
+ for (ss = bv->fm + 2; *ss && *ss != /*{*/ '}'; ss++)
+ if(*ss == '\\' && ss[1])
+ ss++;
+ dd = tmfmt = tmbuf = zalloc(ss - bv->fm);
+ for (ss = bv->fm + 2; *ss && *ss != /*{*/ '}';
+ ss++) {
+ if(*ss == '\\' && ss[1])
+ ss++;
+ *dd++ = *ss;
+ }
+ *dd = 0;
+ bv->fm = ss - !*ss;
+ if (!*tmfmt) {
+ free(tmbuf);
+ continue;
+ }
+ } else
+ tmfmt = "%y-%m-%d";
+ break;
+ default:
+ tmfmt = "%l:%M%p";
+ break;
+ }
+ zgettime(&ts);
+ tm = localtime(&ts.tv_sec);
+ /*
+ * Hack because strftime won't say how
+ * much space it actually needs. Try to add it
+ * a few times until it works. Some formats don't
+ * actually have a length, so we could go on for
+ * ever.
+ */
+ for(j = 0, t0 = strlen(tmfmt)*8; j < 3; j++, t0*=2) {
+ addbufspc(t0);
+ if ((len = ztrftime(bv->bp, t0, tmfmt, tm, ts.tv_nsec))
+ >= 0)
+ break;
+ }
+ /* There is enough room for this because addbufspc(t0)
+ * allocates room for t0 * 2 bytes. */
+ if (len >= 0)
+ metafy(bv->bp, len, META_NOALLOC);
+ bv->bp += strlen(bv->bp);
+ zsfree(tmbuf);
+ 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 'y':
+ if (*ttystrname) {
+ ss = (strncmp(ttystrname, "/dev/", 5) ?
+ ttystrname : ttystrname + 5);
+ stradd(ss);
+ } else
+ stradd("()");
+ break;
+ case 'L':
+ addbufspc(DIGBUFSIZE);
+#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
+ sprintf(bv->bp, "%lld", shlvl);
+#else
+ sprintf(bv->bp, "%ld", (long)shlvl);
+#endif
+ bv->bp += strlen(bv->bp);
+ break;
+ case '?':
+ addbufspc(DIGBUFSIZE);
+#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
+ sprintf(bv->bp, "%lld", lastval);
+#else
+ sprintf(bv->bp, "%ld", (long)lastval);
+#endif
+ bv->bp += strlen(bv->bp);
+ break;
+ case '%':
+ case ')':
+ addbufspc(1);
+ *bv->bp++ = *bv->fm;
+ break;
+ case '#':
+ addbufspc(1);
+ *bv->bp++ = privasserted() ? '#' : '%';
+ break;
+ case 'v':
+ if (!arg)
+ arg = 1;
+ else if (arg < 0)
+ arg += arrlen(psvar) + 1;
+ if (arg > 0 && arrlen_ge(psvar, arg))
+ stradd(psvar[arg - 1]);
+ break;
+ case 'E':
+ tsetcap(TCCLEAREOL, TSC_PROMPT);
+ break;
+ case '^':
+ if (cmdsp) {
+ if (arg >= 0) {
+ if (arg > cmdsp || arg == 0)
+ arg = cmdsp;
+ for (t0 = cmdsp - 1; arg--; t0--) {
+ stradd(cmdnames[cmdstack[t0]]);
+ if (arg) {
+ addbufspc(1);
+ *bv->bp++=' ';
+ }
+ }
+ } else {
+ arg = -arg;
+ if (arg > cmdsp)
+ arg = cmdsp;
+ for (t0 = arg - 1; arg--; t0--) {
+ stradd(cmdnames[cmdstack[t0]]);
+ if (arg) {
+ addbufspc(1);
+ *bv->bp++=' ';
+ }
+ }
+ }
+ }
+ break;
+ case '_':
+ if (cmdsp) {
+ if (arg >= 0) {
+ if (arg > cmdsp || arg == 0)
+ arg = cmdsp;
+ for (t0 = cmdsp - arg; arg--; t0++) {
+ stradd(cmdnames[cmdstack[t0]]);
+ if (arg) {
+ addbufspc(1);
+ *bv->bp++=' ';
+ }
+ }
+ } else {
+ arg = -arg;
+ if (arg > cmdsp)
+ arg = cmdsp;
+ for (t0 = 0; arg--; t0++) {
+ stradd(cmdnames[cmdstack[t0]]);
+ if (arg) {
+ addbufspc(1);
+ *bv->bp++=' ';
+ }
+ }
+ }
+ }
+ break;
+ case 'r':
+ if(bv->rstring)
+ stradd(bv->rstring);
+ break;
+ case 'R':
+ if(bv->Rstring)
+ stradd(bv->Rstring);
+ break;
+ case 'e':
+ {
+ int depth = 0;
+ Funcstack fsptr = funcstack;
+ while (fsptr) {
+ depth++;
+ fsptr = fsptr->prev;
+ }
+ addbufspc(DIGBUFSIZE);
+ sprintf(bv->bp, "%d", depth);
+ bv->bp += strlen(bv->bp);
+ break;
+ }
+ case 'I':
+ if (funcstack && funcstack->tp != FS_SOURCE &&
+ !IN_EVAL_TRAP()) {
+ /*
+ * We're in a function or an eval with
+ * EVALLINENO. Calculate the line number in
+ * the file.
+ */
+ zlong flineno = lineno + funcstack->flineno;
+ /* take account of eval line nos. starting at 1 */
+ if (funcstack->tp == FS_EVAL)
+ lineno--;
+ addbufspc(DIGBUFSIZE);
+#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
+ sprintf(bv->bp, "%lld", flineno);
+#else
+ sprintf(bv->bp, "%ld", (long)flineno);
+#endif
+ bv->bp += strlen(bv->bp);
+ break;
+ }
+ /* else we're in a file and lineno is already correct */
+ /* FALLTHROUGH */
+ case 'i':
+ addbufspc(DIGBUFSIZE);
+#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
+ sprintf(bv->bp, "%lld", lineno);
+#else
+ sprintf(bv->bp, "%ld", (long)lineno);
+#endif
+ bv->bp += strlen(bv->bp);
+ break;
+ case 'x':
+ if (funcstack && funcstack->tp != FS_SOURCE &&
+ !IN_EVAL_TRAP())
+ promptpath(funcstack->filename ? funcstack->filename : "",
+ arg, 0);
+ else
+ promptpath(scriptfilename ? scriptfilename : argzero,
+ arg, 0);
+ break;
+ case '\0':
+ return 0;
+ case Meta:
+ bv->fm++;
+ break;
+ }
+ } else if(*bv->fm == '!' && isset(PROMPTBANG)) {
+ if(doprint) {
+ if(bv->fm[1] == '!') {
+ bv->fm++;
+ addbufspc(1);
+ pputc('!');
+ } else {
+ addbufspc(DIGBUFSIZE);
+ convbase(bv->bp, curhist, 10);
+ bv->bp += strlen(bv->bp);
+ }
+ }
+ } else {
+ char c = *bv->fm == Meta ? *++bv->fm ^ 32 : *bv->fm;
+
+ if (doprint) {
+ addbufspc(1);
+ pputc(c);
+ }
+ }
+ }
+
+ return *bv->fm;
+}
+
+/* pputc adds a character to the buffer, metafying. There must *
+ * already be space. */
+
+/**/
+static void
+pputc(char c)
+{
+ if (imeta(c)) {
+ *bv->bp++ = Meta;
+ c ^= 32;
+ }
+ *bv->bp++ = c;
+ if (c == '\n' && !bv->dontcount)
+ bv->bufline = bv->bp;
+}
+
+/* Make sure there is room for `need' more characters in the buffer. */
+
+/**/
+static void
+addbufspc(int need)
+{
+ need *= 2; /* for metafication */
+ if((bv->bp - bv->buf) + need > bv->bufspc) {
+ int bo = bv->bp - bv->buf;
+ int bo1 = bv->bp1 ? bv->bp1 - bv->buf : -1;
+ ptrdiff_t bufline_off = bv->bufline ? bv->bufline - bv->buf : -1;
+
+ if(need & 255)
+ need = (need | 255) + 1;
+ bv->buf = realloc(bv->buf, bv->bufspc += need);
+ memset(bv->buf + bv->bufspc - need, 0, need);
+ bv->bp = bv->buf + bo;
+ if(bo1 != -1)
+ bv->bp1 = bv->buf + bo1;
+ if (bufline_off != -1)
+ bv->bufline = bv->buf + bufline_off;
+ }
+}
+
+/* stradd() adds a metafied string to the prompt, *
+ * in a visible representation. */
+
+/**/
+void
+stradd(char *d)
+{
+#ifdef MULTIBYTE_SUPPORT
+ char *ums, *ups;
+ int upslen, eol = 0;
+ mbstate_t mbs;
+
+ memset(&mbs, 0, sizeof mbs);
+ ums = ztrdup(d);
+ ups = unmetafy(ums, &upslen);
+
+ /*
+ * We now have a raw string of possibly multibyte characters.
+ * Read each character one by one.
+ */
+ while (upslen > 0) {
+ wchar_t cc;
+ char *pc;
+ size_t cnt = eol ? MB_INVALID : mbrtowc(&cc, ups, upslen, &mbs);
+
+ switch (cnt) {
+ case MB_INCOMPLETE:
+ eol = 1;
+ /* FALL THROUGH */
+ case MB_INVALID:
+ /* Bad character. Take the next byte on its own. */
+ pc = nicechar(*ups);
+ cnt = 1;
+ memset(&mbs, 0, sizeof mbs);
+ break;
+ case 0:
+ cnt = 1;
+ /* FALL THROUGH */
+ default:
+ /* Take full wide character in one go */
+ mb_charinit();
+ pc = wcs_nicechar(cc, NULL, NULL);
+ break;
+ }
+ /* Keep output as metafied string. */
+ addbufspc(strlen(pc));
+
+ upslen -= cnt;
+ ups += cnt;
+
+ /* Put printed representation into the buffer */
+ while (*pc)
+ *bv->bp++ = *pc++;
+ }
+
+ free(ums);
+#else
+ char *ps, *pc;
+ addbufspc(niceztrlen(d));
+ /* This loop puts the nice representation of the string into the
+ * prompt buffer. */
+ for (ps = d; *ps; ps++) {
+ for (pc = nicechar(*ps == Meta ? *++ps^32 : *ps); *pc; pc++)
+ *bv->bp++ = *pc;
+ }
+#endif
+}
+
+/* tsetcap(), among other things, can write a termcap string into the buffer. */
+
+/**/
+mod_export void
+tsetcap(int cap, int flags)
+{
+ if (tccan(cap) && !isset(SINGLELINEZLE) &&
+ !(termflags & (TERM_NOUP|TERM_BAD|TERM_UNKNOWN))) {
+ switch (flags & TSC_OUTPUT_MASK) {
+ case TSC_RAW:
+ tputs(tcstr[cap], 1, putraw);
+ break;
+ case 0:
+ default:
+ tputs(tcstr[cap], 1, putshout);
+ break;
+ case TSC_PROMPT:
+ if (!bv->dontcount) {
+ addbufspc(1);
+ *bv->bp++ = Inpar;
+ }
+ tputs(tcstr[cap], 1, putstr);
+ if (!bv->dontcount) {
+ 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--)
+ *bv->bp++ = Nularg;
+ *bv->bp++ = Outpar;
+ }
+ break;
+ }
+
+ if (flags & TSC_DIRTY) {
+ flags &= ~TSC_DIRTY;
+ if (txtisset(TXTBOLDFACE) && cap != TCBOLDFACEBEG)
+ tsetcap(TCBOLDFACEBEG, flags);
+ if (txtisset(TXTSTANDOUT))
+ tsetcap(TCSTANDOUTBEG, flags);
+ if (txtisset(TXTUNDERLINE))
+ tsetcap(TCUNDERLINEBEG, flags);
+ if (txtisset(TXTFGCOLOUR))
+ set_colour_attribute(txtattrmask, COL_SEQ_FG, TSC_PROMPT);
+ if (txtisset(TXTBGCOLOUR))
+ set_colour_attribute(txtattrmask, COL_SEQ_BG, TSC_PROMPT);
+ }
+ }
+}
+
+/**/
+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.
+ * Passing `overf' as -1 means to ignore columns (absolute width).
+ *
+ * If multibyte is enabled, take account of multibyte characters
+ * by locating them and finding out their screen width.
+ */
+
+/**/
+mod_export void
+countprompt(char *str, int *wp, int *hp, int overf)
+{
+ int w = 0, h = 1;
+ int s = 1;
+#ifdef MULTIBYTE_SUPPORT
+ int wcw, multi = 0;
+ char inchar;
+ mbstate_t mbs;
+ wchar_t wc;
+
+ memset(&mbs, 0, sizeof(mbs));
+#endif
+
+ for (; *str; str++) {
+ if (w > zterm_columns && overf >= 0) {
+ w = 0;
+ h++;
+ }
+ /*
+ * Input string should be metafied, so tokens in it should
+ * be real tokens, even if there are multibyte characters.
+ */
+ if (*str == Inpar)
+ s = 0;
+ else if (*str == Outpar)
+ s = 1;
+ else if (*str == Nularg)
+ w++;
+ else if (s) {
+ if (*str == Meta) {
+#ifdef MULTIBYTE_SUPPORT
+ inchar = *++str ^ 32;
+#else
+ str++;
+#endif
+ } else {
+#ifdef MULTIBYTE_SUPPORT
+ /*
+ * Don't look for tab or newline in the middle
+ * of a multibyte character. Otherwise, we are
+ * relying on the character set being an extension
+ * of ASCII so it's safe to test a single byte.
+ */
+ if (!multi) {
+#endif
+ if (*str == '\t') {
+ w = (w | 7) + 1;
+ continue;
+ } else if (*str == '\n') {
+ w = 0;
+ h++;
+ continue;
+ }
+#ifdef MULTIBYTE_SUPPORT
+ }
+
+ inchar = *str;
+#endif
+ }
+
+#ifdef MULTIBYTE_SUPPORT
+ switch (mbrtowc(&wc, &inchar, 1, &mbs)) {
+ case MB_INCOMPLETE:
+ /* Character is incomplete -- keep looking. */
+ multi = 1;
+ break;
+ case MB_INVALID:
+ memset(&mbs, 0, sizeof mbs);
+ /* Invalid character: assume single width. */
+ multi = 0;
+ w++;
+ break;
+ case 0:
+ multi = 0;
+ break;
+ default:
+ /*
+ * If the character isn't printable, WCWIDTH() returns
+ * -1. We assume width 1.
+ */
+ wcw = WCWIDTH(wc);
+ if (wcw >= 0)
+ w += wcw;
+ else
+ w++;
+ multi = 0;
+ break;
+ }
+#else
+ w++;
+#endif
+ }
+ }
+ /*
+ * multi may still be set if we were in the middle of the character.
+ * This isn't easy to handle generally; just assume there's no
+ * output.
+ */
+ if(w >= zterm_columns && overf >= 0) {
+ if (!overf || w > zterm_columns) {
+ w = 0;
+ h++;
+ }
+ }
+ if(wp)
+ *wp = w;
+ if(hp)
+ *hp = h;
+}
+
+/**/
+static int
+prompttrunc(int arg, int truncchar, int doprint, int endchar,
+ unsigned int *txtchangep)
+{
+ if (arg > 0) {
+ char ch = *bv->fm, *ptr, *truncstr;
+ int truncatleft = ch == '<';
+ int w = bv->bp - bv->buf;
+
+ /*
+ * If there is already a truncation active, return so that
+ * can be finished, backing up so that the new truncation
+ * can be started afterwards.
+ */
+ if (bv->truncwidth) {
+ while (*--bv->fm != '%')
+ ;
+ bv->fm--;
+ return 0;
+ }
+
+ bv->truncwidth = arg;
+ if (*bv->fm != ']')
+ bv->fm++;
+ while (*bv->fm && *bv->fm != truncchar) {
+ if (*bv->fm == '\\' && bv->fm[1])
+ ++bv->fm;
+ addbufspc(1);
+ *bv->bp++ = *bv->fm++;
+ }
+ if (!*bv->fm)
+ return 0;
+ if (bv->bp - bv->buf == w && truncchar == ']') {
+ addbufspc(1);
+ *bv->bp++ = '<';
+ }
+ ptr = bv->buf + w; /* addbufspc() may have realloc()'d bv->buf */
+ /*
+ * Now:
+ * bv->buf is the start of the output prompt buffer
+ * ptr is the start of the truncation string
+ * bv->bp is the end of the truncation string
+ */
+ truncstr = ztrduppfx(ptr, bv->bp - ptr);
+
+ bv->bp = ptr;
+ w = bv->bp - bv->buf;
+ bv->fm++;
+ bv->trunccount = bv->dontcount;
+ putpromptchar(doprint, endchar, txtchangep);
+ bv->trunccount = 0;
+ ptr = bv->buf + w; /* putpromptchar() may have realloc()'d */
+ *bv->bp = '\0';
+ /*
+ * Now:
+ * ptr is the start of the truncation string and also
+ * where we need to start putting any truncated output
+ * bv->bp is the end of the string we have just added, which
+ * may need truncating.
+ */
+
+ /*
+ * w below is screen width if multibyte support is enabled
+ * (note that above it was a raw string pointer difference).
+ * It's the full width of the string we may need to truncate.
+ *
+ * bv->truncwidth has come from the user, so we interpret this
+ * as a screen width, too.
+ */
+ countprompt(ptr, &w, 0, -1);
+ if (w > bv->truncwidth) {
+ /*
+ * We need to truncate. t points to the truncation string
+ * -- which is inserted literally, without nice
+ * representation. twidth is its printing width, and maxwidth
+ * is the amount of the main string that we want to keep.
+ * Note that if the truncation string is longer than the
+ * truncation length (twidth > bv->truncwidth), the truncation
+ * string is used in full.
+ */
+ char *t = truncstr;
+ int fullen = bv->bp - ptr;
+ int twidth, maxwidth;
+ int ntrunc = strlen(t);
+
+ twidth = MB_METASTRWIDTH(t);
+ if (twidth < bv->truncwidth) {
+ maxwidth = bv->truncwidth - twidth;
+ /*
+ * It's not safe to assume there are no invisible substrings
+ * just because the width is less than the full string
+ * length since there may be multibyte characters.
+ */
+ addbufspc(ntrunc+1);
+ /* may have realloc'd */
+ ptr = bv->bp - fullen;
+
+ if (truncatleft) {
+ /*
+ * To truncate at the left, selectively copy
+ * maxwidth bytes from the main prompt, preceded
+ * by the truncation string in full.
+ *
+ * We're overwriting the string containing the
+ * text to be truncated, so copy it. We've
+ * just ensured there's sufficient space at the
+ * end of the prompt string.
+ *
+ * Pointer into text to be truncated.
+ */
+ char *fulltextptr, *fulltext;
+ int remw;
+#ifdef MULTIBYTE_SUPPORT
+ mbstate_t mbs;
+ memset(&mbs, 0, sizeof mbs);
+#endif
+
+ fulltextptr = fulltext = ptr + ntrunc;
+ memmove(fulltext, ptr, fullen);
+ fulltext[fullen] = '\0';
+
+ /* Copy the truncstr into place. */
+ while (*t)
+ *ptr++ = *t++;
+
+ /*
+ * Find the point in the text at which we should
+ * start copying, i.e. when the remaining width
+ * is less than or equal to the maximum width.
+ */
+ remw = w;
+ while (remw > maxwidth && *fulltextptr) {
+ if (*fulltextptr == Inpar) {
+ /*
+ * Text marked as invisible: copy
+ * regardless, since we don't know what
+ * this does. It only affects the width
+ * if there are Nularg's present.
+ * However, even in that case we
+ * can't break the sequence down, so
+ * we still loop over the entire group.
+ */
+ for (;;) {
+ *ptr++ = *fulltextptr;
+ if (*fulltextptr == '\0' ||
+ *fulltextptr++ == Outpar)
+ break;
+ if (fulltextptr[-1] == Nularg)
+ remw--;
+ }
+ } else {
+#ifdef MULTIBYTE_SUPPORT
+ /*
+ * Normal text: build up a multibyte character.
+ */
+ char inchar;
+ wchar_t cc;
+ int wcw;
+
+ /*
+ * careful: string is still metafied (we
+ * need that because we don't know a
+ * priori when to stop and the resulting
+ * string must be metafied).
+ */
+ if (*fulltextptr == Meta)
+ inchar = *++fulltextptr ^ 32;
+ else
+ inchar = *fulltextptr;
+ fulltextptr++;
+ switch (mbrtowc(&cc, &inchar, 1, &mbs)) {
+ case MB_INCOMPLETE:
+ /* Incomplete multibyte character. */
+ break;
+ case MB_INVALID:
+ /* Reset invalid state. */
+ memset(&mbs, 0, sizeof mbs);
+ /* FALL THROUGH */
+ case 0:
+ /* Assume a single-byte character. */
+ remw--;
+ break;
+ default:
+ wcw = WCWIDTH(cc);
+ if (wcw >= 0)
+ remw -= wcw;
+ else
+ remw--;
+ break;
+ }
+#else
+ /* Single byte character */
+ if (*fulltextptr == Meta)
+ fulltextptr++;
+ fulltextptr++;
+ remw--;
+#endif
+ }
+ }
+
+ /*
+ * Now simply copy the rest of the text. Still
+ * metafied, so this is easy.
+ */
+ while (*fulltextptr)
+ *ptr++ = *fulltextptr++;
+ /* Mark the end of copying */
+ bv->bp = ptr;
+ } else {
+ /*
+ * Truncating at the right is easier: just leave
+ * enough characters until we have reached the
+ * maximum width.
+ */
+ char *skiptext = ptr;
+#ifdef MULTIBYTE_SUPPORT
+ mbstate_t mbs;
+ memset(&mbs, 0, sizeof mbs);
+#endif
+
+ while (maxwidth > 0 && *skiptext) {
+ if (*skiptext == Inpar) {
+ /* see comment on left truncation above */
+ for (;;) {
+ if (*skiptext == '\0' ||
+ *skiptext++ == Outpar)
+ break;
+ if (skiptext[-1] == Nularg)
+ maxwidth--;
+ }
+ } else {
+#ifdef MULTIBYTE_SUPPORT
+ char inchar;
+ wchar_t cc;
+ int wcw;
+
+ if (*skiptext == Meta)
+ inchar = *++skiptext ^ 32;
+ else
+ inchar = *skiptext;
+ skiptext++;
+ switch (mbrtowc(&cc, &inchar, 1, &mbs)) {
+ case MB_INCOMPLETE:
+ /* Incomplete character. */
+ break;
+ case MB_INVALID:
+ /* Reset invalid state. */
+ memset(&mbs, 0, sizeof mbs);
+ /* FALL THROUGH */
+ case 0:
+ /* Assume a single-byte character. */
+ maxwidth--;
+ break;
+ default:
+ wcw = WCWIDTH(cc);
+ if (wcw >= 0)
+ maxwidth -= wcw;
+ else
+ maxwidth--;
+ break;
+ }
+#else
+ if (*skiptext == Meta)
+ skiptext++;
+ skiptext++;
+ maxwidth--;
+#endif
+ }
+ }
+ /*
+ * We don't need the visible text from now on,
+ * but we'd better copy any invisible bits.
+ * History dictates that these go after the
+ * truncation string. This is sensible since
+ * they may, for example, turn off an effect which
+ * should apply to all text at this point.
+ *
+ * Copy the truncstr.
+ */
+ ptr = skiptext;
+ while (*t)
+ *ptr++ = *t++;
+ bv->bp = ptr;
+ if (*skiptext) {
+ /* Move remaining text so we don't overwrite it */
+ memmove(bv->bp, skiptext, strlen(skiptext)+1);
+ skiptext = bv->bp;
+
+ /*
+ * Copy anything we want, updating bv->bp
+ */
+ while (*skiptext) {
+ if (*skiptext == Inpar) {
+ for (;;) {
+ *bv->bp++ = *skiptext;
+ if (*skiptext == Outpar ||
+ *skiptext == '\0')
+ break;
+ skiptext++;
+ }
+ }
+ else
+ skiptext++;
+ }
+ }
+ }
+ } else {
+ /* Just copy truncstr; no other text appears. */
+ while (*t)
+ *ptr++ = *t++;
+ bv->bp = ptr;
+ }
+ *bv->bp = '\0';
+ }
+ zsfree(truncstr);
+ bv->truncwidth = 0;
+ /*
+ * We may have returned early from the previous putpromptchar *
+ * because we found another truncation following this one. *
+ * In that case we need to do the rest now. *
+ */
+ if (!*bv->fm)
+ return 0;
+ if (*bv->fm != endchar) {
+ bv->fm++;
+ /*
+ * With bv->truncwidth set to zero, we always reach endchar *
+ * (or the terminating NULL) this time round. *
+ */
+ if (!putpromptchar(doprint, endchar, txtchangep))
+ return 0;
+ }
+ /* Now we have to trick it into matching endchar again */
+ bv->fm--;
+ } else {
+ if (*bv->fm != endchar)
+ bv->fm++;
+ while(*bv->fm && *bv->fm != truncchar) {
+ if (*bv->fm == '\\' && bv->fm[1])
+ bv->fm++;
+ bv->fm++;
+ }
+ if (bv->truncwidth || !*bv->fm)
+ return 0;
+ }
+ return 1;
+}
+
+/**/
+void
+cmdpush(int cmdtok)
+{
+ if (cmdsp >= 0 && cmdsp < CMDSTACKSZ)
+ cmdstack[cmdsp++] = (unsigned char)cmdtok;
+}
+
+/**/
+void
+cmdpop(void)
+{
+ if (cmdsp <= 0) {
+ DPUTS(1, "BUG: cmdstack empty");
+ fflush(stderr);
+ } else
+ cmdsp--;
+}
+
+
+/*****************************************************************************
+ * Utilities dealing with colour and other forms of highlighting.
+ *
+ * These are shared by prompts and by zle, so it's easiest to have them
+ * in the main shell.
+ *****************************************************************************/
+
+/* Defines standard ANSI colour names in index order */
+static const char *ansi_colours[] = {
+ "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white",
+ "default", NULL
+};
+
+/* Defines the available types of highlighting */
+struct highlight {
+ const char *name;
+ int mask_on;
+ int mask_off;
+};
+
+static const struct highlight highlights[] = {
+ { "none", 0, TXT_ATTR_ON_MASK },
+ { "bold", TXTBOLDFACE, 0 },
+ { "standout", TXTSTANDOUT, 0 },
+ { "underline", TXTUNDERLINE, 0 },
+ { NULL, 0, 0 }
+};
+
+/*
+ * Return index of ANSI colour for which *teststrp is an abbreviation.
+ * Any non-alphabetic character ends the abbreviation.
+ * 8 is the special value for default (note this is *not* the
+ * right sequence for default which is typically 9).
+ * -1 is failure.
+ */
+
+static int
+match_named_colour(const char **teststrp)
+{
+ const char *teststr = *teststrp, *end, **cptr;
+ int len;
+
+ for (end = teststr; ialpha(*end); end++)
+ ;
+ len = end - teststr;
+ *teststrp = end;
+
+ for (cptr = ansi_colours; *cptr; cptr++) {
+ if (!strncmp(teststr, *cptr, len))
+ return cptr - ansi_colours;
+ }
+
+ return -1;
+}
+
+/*
+ * Match just the colour part of a highlight specification.
+ * If teststrp is NULL, use the already parsed numeric colour.
+ * Return the attributes to set in the attribute variable.
+ * Return -1 for out of range. Does not check the character
+ * following the colour specification.
+ */
+
+/**/
+mod_export int
+match_colour(const char **teststrp, int is_fg, int colour)
+{
+ int shft, on, named = 0, tc;
+
+ if (teststrp) {
+ if ((named = ialpha(**teststrp))) {
+ colour = match_named_colour(teststrp);
+ if (colour == 8) {
+ /* default */
+ return is_fg ? TXTNOFGCOLOUR : TXTNOBGCOLOUR;
+ }
+ }
+ else
+ colour = (int)zstrtol(*teststrp, (char **)teststrp, 10);
+ }
+ if (colour < 0 || colour >= 256)
+ return -1;
+ if (is_fg) {
+ shft = TXT_ATTR_FG_COL_SHIFT;
+ on = TXTFGCOLOUR;
+ tc = TCFGCOLOUR;
+ } else {
+ shft = TXT_ATTR_BG_COL_SHIFT;
+ on = TXTBGCOLOUR;
+ tc = TCBGCOLOUR;
+ }
+ /*
+ * Try termcap for numbered characters if posible.
+ * Don't for named characters, since our best bet
+ * of getting the names right is with ANSI sequences.
+ */
+ if (!named && tccan(tc)) {
+ if (tccolours >= 0 && colour >= tccolours) {
+ /*
+ * Out of range of termcap colours.
+ * Can we assume ANSI colours work?
+ */
+ if (colour > 7)
+ return -1; /* No. */
+ } else {
+ /*
+ * We can handle termcap colours and the number
+ * is in range, so use termcap.
+ */
+ on |= is_fg ? TXT_ATTR_FG_TERMCAP :
+ TXT_ATTR_BG_TERMCAP;
+ }
+ }
+ return on | (colour << shft);
+}
+
+/*
+ * Match a set of highlights in the given teststr.
+ * Set *on_var to reflect the values found.
+ */
+
+/**/
+mod_export void
+match_highlight(const char *teststr, int *on_var)
+{
+ int found = 1;
+
+ *on_var = 0;
+ while (found && *teststr) {
+ const struct highlight *hl;
+
+ found = 0;
+ if (strpfx("fg=", teststr) || strpfx("bg=", teststr)) {
+ int is_fg = (teststr[0] == 'f'), atr;
+
+ teststr += 3;
+ atr = match_colour(&teststr, is_fg, 0);
+ if (*teststr == ',')
+ teststr++;
+ else if (*teststr)
+ break;
+ found = 1;
+ /* skip out of range colours but keep scanning attributes */
+ if (atr >= 0)
+ *on_var |= atr;
+ } else {
+ for (hl = highlights; hl->name; hl++) {
+ if (strpfx(hl->name, teststr)) {
+ const char *val = teststr + strlen(hl->name);
+
+ if (*val == ',')
+ val++;
+ else if (*val)
+ break;
+
+ *on_var |= hl->mask_on;
+ *on_var &= ~hl->mask_off;
+ teststr = val;
+ found = 1;
+ }
+ }
+ }
+ }
+}
+
+/*
+ * Count or output a string for colour information: used
+ * by output_highlight().
+ */
+
+static int
+output_colour(int colour, int fg_bg, int use_tc, char *buf)
+{
+ int atrlen = 3, len;
+ char *ptr = buf;
+ if (buf) {
+ strcpy(ptr, fg_bg == COL_SEQ_FG ? "fg=" : "bg=");
+ ptr += 3;
+ }
+ /* colour should only be > 7 if using termcap but let's be safe */
+ if (use_tc || colour > 7) {
+ char digbuf[DIGBUFSIZE];
+ sprintf(digbuf, "%d", colour);
+ len = strlen(digbuf);
+ atrlen += len;
+ if (buf)
+ strcpy(ptr, digbuf);
+ } else {
+ len = strlen(ansi_colours[colour]);
+ atrlen += len;
+ if (buf)
+ strcpy(ptr, ansi_colours[colour]);
+ }
+
+ return atrlen;
+}
+
+/*
+ * Count the length needed for outputting highlighting information
+ * as a string based on the bits for the attributes.
+ *
+ * If buf is not NULL, output the strings into the buffer, too.
+ * As conventional with strings, the allocated length should be
+ * at least the returned value plus 1 for the NUL byte.
+ */
+
+/**/
+mod_export int
+output_highlight(int atr, char *buf)
+{
+ const struct highlight *hp;
+ int atrlen = 0, len;
+ char *ptr = buf;
+
+ if (atr & TXTFGCOLOUR) {
+ len = output_colour(txtchangeget(atr, TXT_ATTR_FG_COL),
+ COL_SEQ_FG,
+ (atr & TXT_ATTR_FG_TERMCAP),
+ ptr);
+ atrlen += len;
+ if (buf)
+ ptr += len;
+ }
+ if (atr & TXTBGCOLOUR) {
+ if (atrlen) {
+ atrlen++;
+ if (buf) {
+ strcpy(ptr, ",");
+ ptr++;
+ }
+ }
+ len = output_colour(txtchangeget(atr, TXT_ATTR_BG_COL),
+ COL_SEQ_BG,
+ (atr & TXT_ATTR_BG_TERMCAP),
+ ptr);
+ atrlen += len;
+ if (buf)
+ ptr += len;
+ }
+ for (hp = highlights; hp->name; hp++) {
+ if (hp->mask_on & atr) {
+ if (atrlen) {
+ atrlen++;
+ if (buf) {
+ strcpy(ptr, ",");
+ ptr++;
+ }
+ }
+ len = strlen(hp->name);
+ atrlen += len;
+ if (buf) {
+ strcpy(ptr, hp->name);
+ ptr += len;
+ }
+ }
+ }
+
+ if (atrlen == 0) {
+ if (buf)
+ strcpy(ptr, "none");
+ return 4;
+ }
+ return atrlen;
+}
+
+/* Structure and array for holding special colour terminal sequences */
+
+/* Start of escape sequence for foreground colour */
+#define TC_COL_FG_START "\033[3"
+/* End of escape sequence for foreground colour */
+#define TC_COL_FG_END "m"
+/* Code to reset foreground colour */
+#define TC_COL_FG_DEFAULT "9"
+
+/* Start of escape sequence for background colour */
+#define TC_COL_BG_START "\033[4"
+/* End of escape sequence for background colour */
+#define TC_COL_BG_END "m"
+/* Code to reset background colour */
+#define TC_COL_BG_DEFAULT "9"
+
+struct colour_sequences {
+ char *start; /* Escape sequence start */
+ char *end; /* Escape sequence terminator */
+ char *def; /* Code to reset default colour */
+};
+static struct colour_sequences fg_bg_sequences[2];
+
+/*
+ * We need a buffer for colour sequence composition. It may
+ * vary depending on the sequences set. However, it's inefficient
+ * allocating it separately every time we send a colour sequence,
+ * so do it once per refresh.
+ */
+static char *colseq_buf;
+
+/*
+ * Count how often this has been allocated, for recursive usage.
+ */
+static int colseq_buf_allocs;
+
+/**/
+void
+set_default_colour_sequences(void)
+{
+ fg_bg_sequences[COL_SEQ_FG].start = ztrdup(TC_COL_FG_START);
+ fg_bg_sequences[COL_SEQ_FG].end = ztrdup(TC_COL_FG_END);
+ fg_bg_sequences[COL_SEQ_FG].def = ztrdup(TC_COL_FG_DEFAULT);
+
+ fg_bg_sequences[COL_SEQ_BG].start = ztrdup(TC_COL_BG_START);
+ fg_bg_sequences[COL_SEQ_BG].end = ztrdup(TC_COL_BG_END);
+ fg_bg_sequences[COL_SEQ_BG].def = ztrdup(TC_COL_BG_DEFAULT);
+}
+
+static void
+set_colour_code(char *str, char **var)
+{
+ char *keyseq;
+ int len;
+
+ zsfree(*var);
+ keyseq = getkeystring(str, &len, GETKEYS_BINDKEY, NULL);
+ *var = metafy(keyseq, len, META_DUP);
+}
+
+/* Allocate buffer for colour code composition */
+
+/**/
+mod_export void
+allocate_colour_buffer(void)
+{
+ char **atrs;
+ int lenfg, lenbg, len;
+
+ if (colseq_buf_allocs++)
+ return;
+
+ atrs = getaparam("zle_highlight");
+ if (atrs) {
+ for (; *atrs; atrs++) {
+ if (strpfx("fg_start_code:", *atrs)) {
+ set_colour_code(*atrs + 14, &fg_bg_sequences[COL_SEQ_FG].start);
+ } else if (strpfx("fg_default_code:", *atrs)) {
+ set_colour_code(*atrs + 16, &fg_bg_sequences[COL_SEQ_FG].def);
+ } else if (strpfx("fg_end_code:", *atrs)) {
+ set_colour_code(*atrs + 12, &fg_bg_sequences[COL_SEQ_FG].end);
+ } else if (strpfx("bg_start_code:", *atrs)) {
+ set_colour_code(*atrs + 14, &fg_bg_sequences[COL_SEQ_BG].start);
+ } else if (strpfx("bg_default_code:", *atrs)) {
+ set_colour_code(*atrs + 16, &fg_bg_sequences[COL_SEQ_BG].def);
+ } else if (strpfx("bg_end_code:", *atrs)) {
+ set_colour_code(*atrs + 12, &fg_bg_sequences[COL_SEQ_BG].end);
+ }
+ }
+ }
+
+ lenfg = strlen(fg_bg_sequences[COL_SEQ_FG].def);
+ /* always need 1 character for non-default code */
+ if (lenfg < 1)
+ lenfg = 1;
+ lenfg += strlen(fg_bg_sequences[COL_SEQ_FG].start) +
+ strlen(fg_bg_sequences[COL_SEQ_FG].end);
+
+ lenbg = strlen(fg_bg_sequences[COL_SEQ_BG].def);
+ /* always need 1 character for non-default code */
+ if (lenbg < 1)
+ lenbg = 1;
+ lenbg += strlen(fg_bg_sequences[COL_SEQ_BG].start) +
+ strlen(fg_bg_sequences[COL_SEQ_BG].end);
+
+ len = lenfg > lenbg ? lenfg : lenbg;
+ colseq_buf = (char *)zalloc(len+1);
+}
+
+/* Free the colour buffer previously allocated. */
+
+/**/
+mod_export void
+free_colour_buffer(void)
+{
+ if (--colseq_buf_allocs)
+ return;
+
+ DPUTS(!colseq_buf, "Freeing colour sequence buffer without alloc");
+ /* Free buffer for colour code composition */
+ free(colseq_buf);
+ colseq_buf = NULL;
+}
+
+/*
+ * Handle outputting of a colour for prompts or zle.
+ * colour is the numeric colour, 0 to 255 (or less if termcap
+ * says fewer are supported).
+ * fg_bg indicates if we're changing the foreground or background.
+ * tc indicates the termcap code to use, if appropriate.
+ * def indicates if we're resetting the default colour.
+ * use_termcap indicates if we should use termcap to output colours.
+ * flags is either 0 or TSC_PROMPT.
+ */
+
+/**/
+mod_export void
+set_colour_attribute(int atr, int fg_bg, int flags)
+{
+ char *ptr;
+ int do_free, is_prompt = (flags & TSC_PROMPT) ? 1 : 0;
+ int colour, tc, def, use_termcap;
+
+ if (fg_bg == COL_SEQ_FG) {
+ colour = txtchangeget(atr, TXT_ATTR_FG_COL);
+ tc = TCFGCOLOUR;
+ def = txtchangeisset(atr, TXTNOFGCOLOUR);
+ use_termcap = txtchangeisset(atr, TXT_ATTR_FG_TERMCAP);
+ } else {
+ colour = txtchangeget(atr, TXT_ATTR_BG_COL);
+ tc = TCBGCOLOUR;
+ def = txtchangeisset(atr, TXTNOBGCOLOUR);
+ use_termcap = txtchangeisset(atr, TXT_ATTR_BG_TERMCAP);
+ }
+
+ /*
+ * If we're not restoring the default, and either have a
+ * colour value that is too large for ANSI, or have been told
+ * to use the termcap sequence, try to use the termcap sequence.
+ *
+ * We have already sanitised the values we allow from the
+ * highlighting variables, so much of this shouldn't be
+ * necessary at this point, but we might as well be safe.
+ */
+ if (!def && (colour > 7 || use_termcap)) {
+ /*
+ * We can if it's available, and either we couldn't get
+ * the maximum number of colours, or the colour is in range.
+ */
+ if (tccan(tc) && (tccolours < 0 || colour < tccolours))
+ {
+ if (is_prompt)
+ {
+ if (!bv->dontcount) {
+ addbufspc(1);
+ *bv->bp++ = Inpar;
+ }
+ tputs(tgoto(tcstr[tc], colour, colour), 1, putstr);
+ if (!bv->dontcount) {
+ addbufspc(1);
+ *bv->bp++ = Outpar;
+ }
+ } else {
+ tputs(tgoto(tcstr[tc], colour, colour), 1, putshout);
+ }
+ /* That worked. */
+ return;
+ }
+ /*
+ * Nope, that didn't work.
+ * If 0 to 7, assume standard ANSI works, otherwise it won't.
+ */
+ if (colour > 7)
+ return;
+ }
+
+ if ((do_free = (colseq_buf == NULL))) {
+ /* This can happen when moving the cursor in trashzle() */
+ allocate_colour_buffer();
+ }
+
+ strcpy(colseq_buf, fg_bg_sequences[fg_bg].start);
+
+ ptr = colseq_buf + strlen(colseq_buf);
+ if (def) {
+ strcpy(ptr, fg_bg_sequences[fg_bg].def);
+ while (*ptr)
+ ptr++;
+ } else
+ *ptr++ = colour + '0';
+ strcpy(ptr, fg_bg_sequences[fg_bg].end);
+
+ if (is_prompt) {
+ if (!bv->dontcount) {
+ addbufspc(1);
+ *bv->bp++ = Inpar;
+ }
+ tputs(colseq_buf, 1, putstr);
+ if (!bv->dontcount) {
+ addbufspc(1);
+ *bv->bp++ = Outpar;
+ }
+ } else
+ tputs(colseq_buf, 1, putshout);
+
+ if (do_free)
+ free_colour_buffer();
+}
diff --git a/dotfiles/system/.zsh/modules/Src/prototypes.h b/dotfiles/system/.zsh/modules/Src/prototypes.h
new file mode 100644
index 0000000..e3db4f5
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/prototypes.h
@@ -0,0 +1,134 @@
+/*
+ * 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
+
+#if !(defined(USES_TERMCAP_H) || defined(USES_TERM_H))
+/*
+ * These prototypes are only used where we don't have the
+ * headers. In some cases they need tweaking.
+ * TBD: we'd much prefer to get hold of the header where
+ * these are defined.
+ */
+#ifdef _AIX
+#define TC_CONST const
+#else
+#define TC_CONST
+#endif
+extern int tgetent _((char *bp, TC_CONST char *name));
+extern int tgetnum _((char *id));
+extern int tgetflag _((char *id));
+extern char *tgetstr _((char *id, char **area));
+extern int tputs _((TC_CONST char *cp, int affcnt, int (*outc) (int)));
+#undef TC_CONST
+#endif
+
+/*
+ * Some systems that do have termcap headers nonetheless don't
+ * declare tgoto, so we detect if that is missing separately.
+ */
+#ifdef TGOTO_PROTO_MISSING
+char *tgoto(const char *cap, int col, int row);
+#endif
+
+/* MISSING PROTOTYPES FOR VARIOUS OPERATING SYSTEMS */
+
+#if defined(__hpux) && defined(_HPUX_SOURCE) && !defined(_XPG4_EXTENDED)
+# 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
+# ifndef HAVE_MKNOD_PROTO
+int mknod _((const char *pathname, int mode, dev_t device));
+# endif
+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 *, size_t));
+#endif
diff --git a/dotfiles/system/.zsh/modules/Src/signals.c b/dotfiles/system/.zsh/modules/Src/signals.c
new file mode 100644
index 0000000..20c6fdf
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/signals.c
@@ -0,0 +1,1479 @@
+/*
+ * 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. */
+
+/**/
+mod_export int sigtrapped[VSIGCOUNT];
+
+/*
+ * Trap programme lists for each signal.
+ *
+ * If (sigtrapped[sig] & ZSIG_FUNC) is set, this isn't used.
+ * The corresponding shell function is used instead.
+ *
+ * Otherwise, if sigtrapped[sig] is not zero, this is NULL when a signal
+ * is to be ignored, and if not NULL contains the programme list to be
+ * eval'd.
+ */
+
+/**/
+mod_export Eprog siglists[VSIGCOUNT];
+
+/* Total count of trapped signals */
+
+/**/
+mod_export int nsigtrapped;
+
+/* Running an exit trap? */
+
+/**/
+int in_exit_trap;
+
+/*
+ * Flag that exit trap has been set in POSIX mode.
+ * The setter's expectation is therefore that it is run
+ * on programme exit, not function exit.
+ */
+
+/**/
+static int exit_trap_posix;
+
+/* Variables used by signal queueing */
+
+/**/
+mod_export int queueing_enabled, queue_front, queue_rear;
+/**/
+mod_export int signal_queue[MAX_QUEUE_SIZE];
+/**/
+mod_export sigset_t signal_mask_queue[MAX_QUEUE_SIZE];
+#ifdef DEBUG
+/**/
+mod_export int queue_in;
+#endif
+
+/* Variables used by trap queueing */
+
+/**/
+mod_export int trap_queueing_enabled, trap_queue_front, trap_queue_rear;
+/**/
+mod_export int trap_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. */
+
+/**/
+mod_export void
+install_handler(int sig)
+{
+#ifdef POSIX_SIGNALS
+ struct sigaction act;
+
+ act.sa_handler = (SIGNAL_HANDTYPE) zhandler;
+ 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) zhandler;
+ 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, zhandler);
+# else /* NO_SIGNAL_BLOCKING (bummer) */
+ signal(sig, zhandler);
+
+# endif /* SYSV_SIGNALS */
+# endif /* BSD_SIGNALS */
+#endif /* POSIX_SIGNALS */
+}
+
+/* enable ^C interrupts */
+
+/**/
+mod_export void
+intr(void)
+{
+ if (interact)
+ install_handler(SIGINT);
+}
+
+/* disable ^C interrupts */
+
+#if 0 /**/
+void
+nointr(void)
+{
+ if (interact)
+ signal_ignore(SIGINT);
+}
+#endif
+
+/* temporarily block ^C interrupts */
+
+/**/
+mod_export void
+holdintr(void)
+{
+ if (interact)
+ signal_block(signal_mask(SIGINT));
+}
+
+/* release ^C interrupts */
+
+/**/
+mod_export void
+noholdintr(void)
+{
+ if (interact)
+ signal_unblock(signal_mask(SIGINT));
+}
+
+/* create a signal mask containing *
+ * only the given signal */
+
+/**/
+mod_export 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. */
+
+/**/
+#ifndef BSD_SIGNALS
+
+/**/
+mod_export sigset_t
+signal_block(sigset_t set)
+{
+ sigset_t oset;
+
+#ifdef POSIX_SIGNALS
+ sigprocmask(SIG_BLOCK, &set, &oset);
+
+#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 /* POSIX_SIGNALS */
+
+ return oset;
+}
+
+/**/
+#endif /* BSD_SIGNALS */
+
+/* Unblock the signals in the given signal *
+ * set. Return the old signal set. */
+
+/**/
+mod_export 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 */
+
+/**/
+mod_export 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(UNUSED(int sig), int wait_cmd)
+{
+ int ret;
+
+#if defined(POSIX_SIGNALS) || defined(BSD_SIGNALS)
+ sigset_t set;
+# if defined(POSIX_SIGNALS) && defined(BROKEN_POSIX_SIGSUSPEND)
+ sigset_t oset;
+# endif
+
+ sigemptyset(&set);
+
+ /* SIGINT from the terminal driver needs to interrupt "wait"
+ * and to cause traps to fire, but otherwise should not be
+ * handled by the shell until after any foreground job has
+ * a chance to decide whether to exit on that signal.
+ */
+ if (!(wait_cmd || isset(TRAPSASYNC) ||
+ (sigtrapped[SIGINT] & ~ZSIG_IGNORED)))
+ sigaddset(&set, SIGINT);
+#endif /* POSIX_SIGNALS || BSD_SIGNALS */
+
+#ifdef POSIX_SIGNALS
+# ifdef BROKEN_POSIX_SIGSUSPEND
+ sigprocmask(SIG_SETMASK, &set, &oset);
+ ret = pause();
+ sigprocmask(SIG_SETMASK, &oset, NULL);
+# else /* not BROKEN_POSIX_SIGSUSPEND */
+ ret = sigsuspend(&set);
+# endif /* BROKEN_POSIX_SIGSUSPEND */
+#else /* not POSIX_SIGNALS */
+# ifdef BSD_SIGNALS
+ 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 do wait_cmd stuff 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;
+}
+
+/* last signal we handled: race prone, or what? */
+/**/
+int last_signal;
+
+/*
+ * Wait for any processes that have changed state.
+ *
+ * The main use for this is in the SIGCHLD handler. However,
+ * we also use it to pick up status changes of jobs when
+ * updating jobs.
+ */
+/**/
+void
+wait_for_processes(void)
+{
+ /* keep WAITING until no more child processes to reap */
+ for (;;) {
+ /* save the errno, since WAIT may change it */
+ int old_errno = errno;
+ int status;
+ Job jn;
+ Process pn;
+ pid_t pid;
+ pid_t *procsubpid = &cmdoutpid;
+ int *procsubval = &cmdoutval;
+ int cont = 0;
+ struct execstack *es = exstack;
+
+ /*
+ * Reap the child process.
+ * If we want usage information, we need to use wait3.
+ */
+#if defined(HAVE_WAIT3) || defined(HAVE_WAITPID)
+# ifdef WCONTINUED
+# define WAITFLAGS (WNOHANG|WUNTRACED|WCONTINUED)
+# else
+# define WAITFLAGS (WNOHANG|WUNTRACED)
+# endif
+#endif
+#ifdef HAVE_WAIT3
+# ifdef HAVE_GETRUSAGE
+ struct rusage ru;
+
+ pid = wait3((void *)&status, WAITFLAGS, &ru);
+# else
+ pid = wait3((void *)&status, WAITFLAGS, NULL);
+# endif
+#else
+# ifdef HAVE_WAITPID
+ pid = waitpid(-1, &status, WAITFLAGS);
+# else
+ pid = wait(&status);
+# endif
+#endif
+
+ 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);
+ use_cmdoutval = 1;
+ get_usage();
+ cont = 1;
+ break;
+ }
+ if (!es)
+ break;
+ procsubpid = &es->cmdoutpid;
+ procsubval = &es->cmdoutval;
+ es = es->next;
+ }
+ if (cont)
+ continue;
+
+ /* check for WAIT error */
+ if (pid == -1) {
+ if (errno != ECHILD)
+ zerr("wait failed: %e", errno);
+ /* WAIT changed errno, so restore the original */
+ errno = old_errno;
+ break;
+ }
+
+ /* This is necessary to be sure queueing_enabled > 0 when
+ * we enter printjob() from update_job(), so that we don't
+ * decrement to zero in should_report_time() and improperly
+ * run other handlers in the middle of processing this one */
+ queue_signals();
+
+ /*
+ * Find the process and job containing this pid and
+ * update it.
+ */
+ if (findproc(pid, &jn, &pn, 0)) {
+ if (((jn->stat & STAT_BUILTIN) ||
+ (list_pipe &&
+ (thisjob == -1 ||
+ (jobtab[thisjob].stat & STAT_BUILTIN)))) &&
+ WIFSTOPPED(status) && WSTOPSIG(status) == SIGTSTP) {
+ killjb(jn, SIGCONT);
+ zwarn("job can't be suspended");
+ } else {
+#if defined(HAVE_WAIT3) && defined(HAVE_GETRUSAGE)
+ struct timezone dummy_tz;
+ gettimeofday(&pn->endtime, &dummy_tz);
+#ifdef WIFCONTINUED
+ if (WIFCONTINUED(status))
+ pn->status = SP_RUNNING;
+ else
+#endif
+ pn->status = status;
+ pn->ti = ru;
+#else
+ update_process(pn, status);
+#endif
+ if (WIFEXITED(status) &&
+ pn->pid == jn->gleader &&
+ killpg(pn->pid, 0) == -1) {
+ jn->gleader = 0;
+ if (!(jn->stat & STAT_NOSTTY)) {
+ /*
+ * This PID was in control of the terminal;
+ * reclaim terminal now it has exited.
+ * It's still possible some future forked
+ * process of this job will become group
+ * leader, however.
+ */
+ attachtty(mypgrp);
+ }
+ }
+ }
+ update_job(jn);
+ } else if (findproc(pid, &jn, &pn, 1)) {
+ pn->status = 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.
+ */
+ get_usage();
+ }
+ /*
+ * Accumulate a list of older jobs. We only do this for
+ * background jobs, which is something in the job table
+ * that's not marked as in the current shell or as shell builtin
+ * and is not equal to the current foreground job.
+ */
+ if (jn && !(jn->stat & (STAT_CURSH|STAT_BUILTIN)) &&
+ jn - jobtab != thisjob) {
+ int val = (WIFSIGNALED(status) ?
+ 0200 | WTERMSIG(status) :
+ (WIFSTOPPED(status) ?
+ 0200 | WEXITSTATUS(status) :
+ WEXITSTATUS(status)));
+ addbgstatus(pid, val);
+ }
+
+ unqueue_signals();
+ }
+}
+
+/* the signal handler */
+
+/**/
+mod_export void
+zhandler(int sig)
+{
+ sigset_t newmask, oldmask;
+
+#if defined(NO_SIGNAL_BLOCKING)
+ int do_jump;
+ signal_jmp_buf jump_to;
+#endif
+
+ last_signal = sig;
+ signal_process(sig);
+
+ sigfillset(&newmask);
+ /* Block all signals temporarily */
+ oldmask = signal_block(newmask);
+
+#if defined(NO_SIGNAL_BLOCKING)
+ /* do we need to longjmp to signal_suspend */
+ do_jump = suspend_longjmp;
+ /* In case a SIGCHLD somehow arrives */
+ suspend_longjmp = 0;
+
+ /* Traps can cause nested signal_suspend() */
+ if (sig == SIGCHLD) {
+ if (do_jump) {
+ /* Copy suspend_jmp_buf */
+ jump_to = suspend_jmp_buf;
+ }
+ }
+#endif
+
+ /* Are we queueing signals now? */
+ if (queueing_enabled) {
+ int temp_rear = ++queue_rear % MAX_QUEUE_SIZE;
+
+ DPUTS(temp_rear == queue_front, "BUG: signal queue full");
+ /* Make sure it's not full (extremely unlikely) */
+ if (temp_rear != queue_front) {
+ /* ok, not full, so add to queue */
+ queue_rear = temp_rear;
+ /* save signal caught */
+ signal_queue[queue_rear] = sig;
+ /* save current signal mask */
+ signal_mask_queue[queue_rear] = oldmask;
+ }
+ signal_reset(sig);
+ return;
+ }
+
+ /* Reset signal mask, signal traps ok now */
+ signal_setmask(oldmask);
+
+ switch (sig) {
+ case SIGCHLD:
+ wait_for_processes();
+ break;
+
+ case SIGPIPE:
+ if (!handletrap(SIGPIPE)) {
+ if (!interact)
+ _exit(SIGPIPE);
+ else if (!isatty(SHTTY)) {
+ stopmsg = 1;
+ zexit(SIGPIPE, 1);
+ }
+ }
+ break;
+
+ case SIGHUP:
+ if (!handletrap(SIGHUP)) {
+ stopmsg = 1;
+ zexit(SIGHUP, 1);
+ }
+ break;
+
+ case SIGINT:
+ if (!handletrap(SIGINT)) {
+ if ((isset(PRIVILEGED) || isset(RESTRICTED)) &&
+ isset(INTERACTIVE) && (noerrexit & NOERREXIT_SIGNAL))
+ zexit(SIGINT, 1);
+ if (list_pipe || chline || simple_pline) {
+ breaks = loops;
+ errflag |= ERRFLAG_INT;
+ inerrflush();
+ check_cursh_sig(SIGINT);
+ }
+ lastval = 128 + SIGINT;
+ }
+ break;
+
+#ifdef SIGWINCH
+ case SIGWINCH:
+ adjustwinsize(1); /* check window size and adjust */
+ (void) handletrap(SIGWINCH);
+ break;
+#endif
+
+ case SIGALRM:
+ if (!handletrap(SIGALRM)) {
+ int idle = ttyidlegetfn(NULL);
+ int tmout = getiparam("TMOUT");
+ if (idle >= 0 && idle < tmout)
+ alarm(tmout - idle);
+ else {
+ /*
+ * We want to exit now.
+ * Cancel all errors, including a user interrupt
+ * which is now redundant.
+ */
+ errflag = noerrs = 0;
+ zwarn("timeout");
+ stopmsg = 1;
+ zexit(SIGALRM, 1);
+ }
+ }
+ break;
+
+ default:
+ (void) handletrap(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 (jobtab[i].gleader != getpid() &&
+ killpg(jobtab[i].gleader, SIGHUP) != -1)
+ killed++;
+ }
+ if (killed)
+ zwarn("warning: %d jobs SIGHUPed", 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)
+ if (killpg(pn->pid, sig) == -1)
+ if (kill(pn->pid, sig) == -1 && errno != ESRCH)
+ err = -1;
+
+ /*
+ * Note this does not kill the last process,
+ * which is assumed to be the one controlling the
+ * subjob, i.e. the forked zsh that was originally
+ * list_pipe_pid...
+ */
+ for (pn = jn->procs; pn->next; pn = pn->next)
+ if (kill(pn->pid, sig) == -1 && errno != ESRCH)
+ err = -1;
+
+ /*
+ * ...we only continue that once the external processes
+ * currently associated with the subjob are finished.
+ */
+ if (!jobtab[jn->other].procs && pn)
+ if (kill(pn->pid, sig) == -1 && errno != ESRCH)
+ err = -1;
+
+ return err;
+ }
+ if (killpg(jobtab[jn->other].gleader, sig) == -1 && errno != ESRCH)
+ err = -1;
+
+ if (killpg(jn->gleader, sig) == -1 && errno != ESRCH)
+ err = -1;
+
+ return err;
+ }
+ else
+ return killpg(jn->gleader, sig);
+ }
+ for (pn = jn->procs; pn; pn = pn->next) {
+ /*
+ * Do not kill this job's process if it's already dead as its
+ * pid could have been reused by the system.
+ * As the PID doesn't exist don't return an error.
+ */
+ if (pn->status == SP_RUNNING || WIFSTOPPED(pn->status)) {
+ /*
+ * kill -0 on a job is pointless. We still call kill() for each process
+ * in case the user cares about it but we ignore its outcome.
+ */
+ if ((err = kill(pn->pid, sig)) == -1 && errno != ESRCH && sig != 0)
+ return -1;
+ }
+ }
+ return err;
+}
+
+/*
+ * List for saving traps. We don't usually have that many traps
+ * at once, so just use a linked list.
+ */
+struct savetrap {
+ int sig, flags, local, posix;
+ void *list;
+};
+
+static LinkList savetraps;
+static int dontsavetrap;
+
+/*
+ * Save the current trap by copying it. This does nothing to
+ * the existing value of sigtrapped or siglists.
+ */
+
+static void
+dosavetrap(int sig, int level)
+{
+ struct savetrap *st;
+ st = (struct savetrap *)zalloc(sizeof(*st));
+ st->sig = sig;
+ st->local = level;
+ st->posix = (sig == SIGEXIT) ? exit_trap_posix : 0;
+ if ((st->flags = sigtrapped[sig]) & ZSIG_FUNC) {
+ /*
+ * Get the old function: this assumes we haven't added
+ * the new one yet.
+ */
+ Shfunc shf, newshf = NULL;
+ if ((shf = (Shfunc)gettrapnode(sig, 1))) {
+ /* Copy the node for saving */
+ newshf = (Shfunc) zshcalloc(sizeof(*newshf));
+ newshf->node.nam = ztrdup(shf->node.nam);
+ newshf->node.flags = shf->node.flags;
+ newshf->funcdef = dupeprog(shf->funcdef, 0);
+ if (shf->node.flags & PM_LOADDIR) {
+ dircache_set(&newshf->filename, shf->filename);
+ } else {
+ newshf->filename = ztrdup(shf->filename);
+ }
+ if (shf->sticky) {
+ newshf->sticky = sticky_emulation_dup(shf->sticky, 0);
+ } else
+ newshf->sticky = 0;
+ if (shf->node.flags & PM_UNDEFINED)
+ newshf->funcdef->shf = newshf;
+ }
+#ifdef DEBUG
+ else dputs("BUG: no function present with function trap flag set.");
+#endif
+ DPUTS(siglists[sig], "BUG: function signal has eval list, too.");
+ st->list = newshf;
+ } else if (sigtrapped[sig]) {
+ st->list = siglists[sig] ? dupeprog(siglists[sig], 0) : NULL;
+ } else {
+ DPUTS(siglists[sig], "BUG: siglists not null for untrapped signal");
+ st->list = NULL;
+ }
+ if (!savetraps)
+ savetraps = znewlinklist();
+ /*
+ * Put this at the front of the list
+ */
+ zinsertlinknode(savetraps, (LinkNode)savetraps, st);
+}
+
+
+/*
+ * Set a trap: note this does not handle manipulation of
+ * the function table for TRAPNAL functions.
+ *
+ * sig is the signal number.
+ *
+ * l is the list to be eval'd for a trap defined with the "trap"
+ * builtin and should be NULL for a function trap.
+ *
+ * flags includes any additional flags to be or'd into sigtrapped[sig],
+ * in particular ZSIG_FUNC; the basic flags will be assigned within
+ * settrap.
+ */
+
+/**/
+mod_export int
+settrap(int sig, Eprog l, int flags)
+{
+ if (sig == -1)
+ return 1;
+ if (jobbing && (sig == SIGTTOU || sig == SIGTSTP || sig == SIGTTIN)) {
+ zerr("can't trap SIG%s in interactive shells", sigs[sig]);
+ return 1;
+ }
+
+ /*
+ * Call unsettrap() unconditionally, to make sure trap is saved
+ * if necessary.
+ */
+ queue_signals();
+ unsettrap(sig);
+
+ DPUTS((flags & ZSIG_FUNC) && l,
+ "BUG: trap function has passed eval list, too");
+ siglists[sig] = l;
+ if (!(flags & ZSIG_FUNC) && empty_eprog(l)) {
+ sigtrapped[sig] = ZSIG_IGNORED;
+ if (sig && sig <= SIGCOUNT &&
+#ifdef SIGWINCH
+ sig != SIGWINCH &&
+#endif
+ sig != SIGCHLD)
+ signal_ignore(sig);
+ } else {
+ nsigtrapped++;
+ sigtrapped[sig] = ZSIG_TRAPPED;
+ if (sig && sig <= SIGCOUNT &&
+#ifdef SIGWINCH
+ sig != SIGWINCH &&
+#endif
+ sig != SIGCHLD)
+ install_handler(sig);
+ }
+ sigtrapped[sig] |= flags;
+ /*
+ * Note that introducing the locallevel does not affect whether
+ * sigtrapped[sig] is zero or not, i.e. a test without a mask
+ * works just the same.
+ */
+ if (sig == SIGEXIT) {
+ /* Make POSIX behaviour of EXIT trap sticky */
+ exit_trap_posix = isset(POSIXTRAPS);
+ /* POSIX exit traps are not local. */
+ if (!exit_trap_posix)
+ sigtrapped[sig] |= (locallevel << ZSIG_SHIFT);
+ }
+ else
+ sigtrapped[sig] |= (locallevel << ZSIG_SHIFT);
+ unqueue_signals();
+ return 0;
+}
+
+/**/
+void
+unsettrap(int sig)
+{
+ HashNode hn;
+
+ queue_signals();
+ hn = removetrap(sig);
+ if (hn)
+ shfunctab->freenode(hn);
+ unqueue_signals();
+}
+
+/**/
+HashNode
+removetrap(int sig)
+{
+ int trapped;
+
+ if (sig == -1 ||
+ (jobbing && (sig == SIGTTOU || sig == SIGTSTP || sig == SIGTTIN)))
+ return NULL;
+
+ queue_signals();
+ trapped = sigtrapped[sig];
+ /*
+ * Note that we save the trap here even if there isn't an existing
+ * one, to aid in removing this one. However, if there's
+ * already one at the current locallevel we just overwrite it.
+ *
+ * Note we save EXIT traps based on the *current* setting of
+ * POSIXTRAPS --- so if there is POSIX EXIT trap set but
+ * we are in native mode it can be saved, replaced by a function
+ * trap, and then restored.
+ */
+ if (!dontsavetrap &&
+ (sig == SIGEXIT ? !isset(POSIXTRAPS) : isset(LOCALTRAPS)) &&
+ locallevel &&
+ (!trapped || locallevel > (sigtrapped[sig] >> ZSIG_SHIFT)))
+ dosavetrap(sig, locallevel);
+
+ if (!trapped) {
+ unqueue_signals();
+ return NULL;
+ }
+ if (sigtrapped[sig] & ZSIG_TRAPPED)
+ nsigtrapped--;
+ 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 == SIGPIPE && interact && !forklevel)
+ install_handler(sig);
+ else if (sig && sig <= SIGCOUNT &&
+#ifdef SIGWINCH
+ sig != SIGWINCH &&
+#endif
+ sig != SIGCHLD)
+ signal_default(sig);
+ if (sig == SIGEXIT)
+ exit_trap_posix = 0;
+
+ /*
+ * At this point we free the appropriate structs. If we don't
+ * want that to happen then either the function should already have been
+ * removed from shfunctab, or the entry in siglists should have been set
+ * to NULL. This is no longer necessary for saving traps as that
+ * copies the structures, so here we are remove the originals.
+ * That causes a little inefficiency, but a good deal more reliability.
+ */
+ if (trapped & ZSIG_FUNC) {
+ HashNode node = gettrapnode(sig, 1);
+
+ /*
+ * As in dosavetrap(), don't call removeshfuncnode() because
+ * that calls back into unsettrap();
+ */
+ if (node)
+ removehashnode(shfunctab, node->nam);
+ unqueue_signals();
+
+ return node;
+ } else if (siglists[sig]) {
+ freeeprog(siglists[sig]);
+ siglists[sig] = NULL;
+ }
+ unqueue_signals();
+
+ return NULL;
+}
+
+/**/
+void
+starttrapscope(void)
+{
+ /* No special SIGEXIT behaviour inside another trap. */
+ if (intrap)
+ return;
+
+ /*
+ * SIGEXIT needs to be restored at the current locallevel,
+ * so give it the next higher one. dosavetrap() is called
+ * automatically where necessary.
+ */
+ if (sigtrapped[SIGEXIT] && !exit_trap_posix) {
+ locallevel++;
+ unsettrap(SIGEXIT);
+ locallevel--;
+ }
+}
+
+/*
+ * Reset traps after the end of a function: must be called after
+ * endparamscope() so that the locallevel has been decremented.
+ */
+
+/**/
+void
+endtrapscope(void)
+{
+ LinkNode ln;
+ struct savetrap *st;
+ int exittr = 0;
+ void *exitfn = NULL;
+
+ /*
+ * Remember the exit trap, but don't run it until
+ * after all the other traps have been put back.
+ * Don't do this inside another trap.
+ */
+ if (!intrap &&
+ !exit_trap_posix && (exittr = sigtrapped[SIGEXIT])) {
+ if (exittr & ZSIG_FUNC) {
+ exitfn = removehashnode(shfunctab, "TRAPEXIT");
+ } else {
+ exitfn = siglists[SIGEXIT];
+ siglists[SIGEXIT] = NULL;
+ }
+ if (sigtrapped[SIGEXIT] & ZSIG_TRAPPED)
+ nsigtrapped--;
+ sigtrapped[SIGEXIT] = 0;
+ }
+
+ if (savetraps) {
+ while ((ln = firstnode(savetraps)) &&
+ (st = (struct savetrap *) ln->dat) &&
+ st->local > locallevel) {
+ int sig = st->sig;
+
+ remnode(savetraps, ln);
+
+ if (st->flags && (st->list != NULL)) {
+ /* prevent settrap from saving this */
+ dontsavetrap++;
+ if (st->flags & ZSIG_FUNC)
+ settrap(sig, NULL, ZSIG_FUNC);
+ else
+ settrap(sig, (Eprog) st->list, 0);
+ if (sig == SIGEXIT)
+ exit_trap_posix = st->posix;
+ dontsavetrap--;
+ /*
+ * counting of nsigtrapped should presumably be handled
+ * in settrap...
+ */
+ DPUTS((sigtrapped[sig] ^ st->flags) & ZSIG_TRAPPED,
+ "BUG: settrap didn't restore correct ZSIG_TRAPPED");
+ if ((sigtrapped[sig] = st->flags) & ZSIG_FUNC)
+ shfunctab->addnode(shfunctab, ((Shfunc)st->list)->node.nam,
+ (Shfunc) st->list);
+ } else if (sigtrapped[sig]) {
+ /*
+ * Don't restore the old state if someone has set a
+ * POSIX-style exit trap --- allow this to propagate.
+ */
+ if (sig != SIGEXIT || !exit_trap_posix)
+ unsettrap(sig);
+ }
+
+ zfree(st, sizeof(*st));
+ }
+ }
+
+ if (exittr) {
+ /*
+ * We already made sure this wasn't set as a POSIX exit trap.
+ * We respect the user's intention when the trap in question
+ * was set.
+ */
+ dotrapargs(SIGEXIT, &exittr, exitfn);
+ if (exittr & ZSIG_FUNC)
+ shfunctab->freenode((HashNode)exitfn);
+ else
+ freeeprog(exitfn);
+ }
+ DPUTS(!locallevel && savetraps && firstnode(savetraps),
+ "BUG: still saved traps outside all function scope");
+}
+
+
+/*
+ * Decide whether a trap needs handling.
+ * If so, see if the trap should be run now or queued.
+ * Return 1 if the trap has been or will be handled.
+ * This only needs to be called in place of dotrap() in the
+ * signal handler, since it's only while waiting for children
+ * to exit that we queue traps.
+ */
+/**/
+static int
+handletrap(int sig)
+{
+ if (!sigtrapped[sig])
+ return 0;
+
+ if (trap_queueing_enabled)
+ {
+ /* Code borrowed from signal queueing */
+ int temp_rear = ++trap_queue_rear % MAX_QUEUE_SIZE;
+
+ DPUTS(temp_rear == trap_queue_front, "BUG: trap queue full");
+ /* If queue is not full... */
+ if (temp_rear != trap_queue_front) {
+ trap_queue_rear = temp_rear;
+ trap_queue[trap_queue_rear] = sig;
+ }
+ return 1;
+ }
+
+ dotrap(sig);
+
+ if (sig == SIGALRM)
+ {
+ int tmout;
+ /*
+ * Reset the alarm.
+ * It seems slightly more natural to do this when the
+ * trap is run, rather than when it's queued, since
+ * the user doesn't see the latter.
+ */
+ if ((tmout = getiparam("TMOUT")))
+ alarm(tmout);
+ }
+
+ return 1;
+}
+
+
+/*
+ * Queue traps if they shouldn't be run asynchronously, i.e.
+ * we're not in the wait builtin and TRAPSASYNC isn't set, when
+ * waiting for children to exit.
+ *
+ * Note that unlike signal queuing this should only be called
+ * in single matching pairs and can't be nested. It is
+ * only needed when waiting for a job or process to finish.
+ *
+ * There is presumably a race setting this up: we shouldn't be running
+ * traps between forking a foreground process and this point, either.
+ */
+/**/
+void
+queue_traps(int wait_cmd)
+{
+ if (!isset(TRAPSASYNC) && !wait_cmd) {
+ /*
+ * Traps need to be handled synchronously, so
+ * enable queueing.
+ */
+ trap_queueing_enabled = 1;
+ }
+}
+
+
+/*
+ * Disable trap queuing and run the traps.
+ */
+/**/
+void
+unqueue_traps(void)
+{
+ trap_queueing_enabled = 0;
+ while (trap_queue_front != trap_queue_rear) {
+ trap_queue_front = (trap_queue_front + 1) % MAX_QUEUE_SIZE;
+ (void) handletrap(trap_queue[trap_queue_front]);
+ }
+}
+
+
+/* Execute a trap function for a given signal, possibly
+ * with non-standard sigtrapped & siglists values
+ */
+
+/* Are we already executing a trap? */
+/**/
+int intrap;
+
+/* Is the current trap a function? */
+
+/**/
+int trapisfunc;
+
+/*
+ * If the current trap is not a function, at what function depth
+ * did the trap get called?
+ */
+/**/
+int traplocallevel;
+
+/*
+ * sig is the signal number.
+ * *sigtr is the value to be taken as the field in sigtrapped (since
+ * that may have changed by this point if we are exiting).
+ * sigfn is an Eprog with a non-function eval list, or a Shfunc
+ * with a function trap. It may be NULL with an ignored signal.
+ */
+
+/**/
+static void
+dotrapargs(int sig, int *sigtr, void *sigfn)
+{
+ LinkList args;
+ char *name, num[4];
+ int obreaks = breaks;
+ int oretflag = retflag;
+ int olastval = lastval;
+ int isfunc;
+ int traperr, new_trap_state, new_trap_return;
+
+ /* 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). *
+ * *
+ * This test is now replicated in dotrap(). */
+ if ((*sigtr & ZSIG_IGNORED) || !sigfn || errflag)
+ return;
+
+ /*
+ * Never execute special (synchronous) traps inside other traps.
+ * This can cause unexpected code execution when more than one
+ * of these is set.
+ *
+ * The down side is that it's harder to debug traps. I don't think
+ * that's a big issue.
+ */
+ if (intrap) {
+ switch (sig) {
+ case SIGEXIT:
+ case SIGDEBUG:
+ case SIGZERR:
+ return;
+ }
+ }
+
+ queue_signals(); /* Any time we manage memory or global state */
+
+ intrap++;
+ *sigtr |= ZSIG_IGNORED;
+
+ zcontext_save();
+ /* execsave will save the old trap_return and trap_state */
+ execsave();
+ breaks = retflag = 0;
+ traplocallevel = locallevel;
+ runhookdef(BEFORETRAPHOOK, NULL);
+ if (*sigtr & ZSIG_FUNC) {
+ int osc = sfcontext, old_incompfunc = incompfunc;
+ HashNode hn = gettrapnode(sig, 0);
+
+ args = znewlinklist();
+ /*
+ * In case of multiple names, try to get
+ * a hint of the name in use from the function table.
+ * In special cases, e.g. EXIT traps, the function
+ * has already been removed. Then it's OK to
+ * use the standard name.
+ */
+ if (hn) {
+ name = ztrdup(hn->nam);
+ } else {
+ name = (char *) zalloc(5 + strlen(sigs[sig]));
+ sprintf(name, "TRAP%s", sigs[sig]);
+ }
+ zaddlinknode(args, name);
+ sprintf(num, "%d", sig);
+ zaddlinknode(args, num);
+
+ trap_return = -1; /* incremented by doshfunc */
+ trap_state = TRAP_STATE_PRIMED;
+ trapisfunc = isfunc = 1;
+
+ sfcontext = SFC_SIGNAL;
+ incompfunc = 0;
+ doshfunc((Shfunc)sigfn, args, 1); /* manages signal queueing */
+ sfcontext = osc;
+ incompfunc= old_incompfunc;
+ freelinklist(args, (FreeFunc) NULL);
+ zsfree(name);
+ } else {
+ trap_return = -2; /* not incremented, used at current level */
+ trap_state = TRAP_STATE_PRIMED;
+ trapisfunc = isfunc = 0;
+
+ execode((Eprog)sigfn, 1, 0, "trap"); /* manages signal queueing */
+ }
+ runhookdef(AFTERTRAPHOOK, NULL);
+
+ traperr = errflag;
+
+ /* Grab values before they are restored */
+ new_trap_state = trap_state;
+ new_trap_return = trap_return;
+
+ execrestore();
+ zcontext_restore();
+
+ if (new_trap_state == TRAP_STATE_FORCE_RETURN &&
+ /* zero return from function isn't special */
+ !(isfunc && new_trap_return == 0)) {
+ if (isfunc) {
+ breaks = loops;
+ /*
+ * For SIGINT we behave the same as the default behaviour
+ * i.e. we set the error bit indicating an interrupt.
+ * We do this with SIGQUIT, too, even though we don't
+ * handle SIGQUIT by default. That's to try to make
+ * it behave a bit more like its normal behaviour when
+ * the trap handler has told us that's what it wants.
+ */
+ if (sig == SIGINT || sig == SIGQUIT)
+ errflag |= ERRFLAG_INT;
+ else
+ errflag |= ERRFLAG_ERROR;
+ }
+ lastval = new_trap_return;
+ /* return triggered */
+ retflag = 1;
+ } else {
+ if (traperr && !EMULATION(EMULATE_SH))
+ lastval = 1;
+ else {
+ /*
+ * With no explicit forced return, we keep the
+ * lastval from before the trap ran.
+ */
+ lastval = olastval;
+ }
+ if (try_tryflag) {
+ if (traperr)
+ errflag |= ERRFLAG_ERROR;
+ else
+ errflag &= ~ERRFLAG_ERROR;
+ }
+ breaks += obreaks;
+ /* return not triggered: restore old flag */
+ retflag = oretflag;
+ if (breaks > loops)
+ breaks = loops;
+ }
+
+ /*
+ * If zle was running while the trap was executed, see if we
+ * need to restore the display.
+ */
+ if (zleactive && resetneeded)
+ zleentry(ZLE_CMD_REFRESH);
+
+ if (*sigtr != ZSIG_IGNORED)
+ *sigtr &= ~ZSIG_IGNORED;
+ intrap--;
+
+ unqueue_signals();
+}
+
+/* Standard call to execute a trap for a given signal. */
+
+/**/
+void
+dotrap(int sig)
+{
+ void *funcprog;
+ int q = queue_signal_level();
+
+ if (sigtrapped[sig] & ZSIG_FUNC) {
+ HashNode hn = gettrapnode(sig, 0);
+ if (hn)
+ funcprog = hn;
+ else {
+#ifdef DEBUG
+ dputs("BUG: running function trap which has escaped.");
+#endif
+ funcprog = NULL;
+ }
+ } else
+ funcprog = siglists[sig];
+
+ /*
+ * Copied from dotrapargs().
+ * (In fact, the gain from duplicating this appears to be virtually
+ * zero. Not sure why it's here.)
+ */
+ if ((sigtrapped[sig] & ZSIG_IGNORED) || !funcprog || errflag)
+ return;
+
+ dont_queue_signals();
+
+ if (sig == SIGEXIT)
+ ++in_exit_trap;
+
+ dotrapargs(sig, sigtrapped+sig, funcprog);
+
+ if (sig == SIGEXIT)
+ --in_exit_trap;
+
+ restore_queue_signals(q);
+}
diff --git a/dotfiles/system/.zsh/modules/Src/signals.h b/dotfiles/system/.zsh/modules/Src/signals.h
new file mode 100644
index 0000000..41ac88c
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/signals.h
@@ -0,0 +1,142 @@
+/*
+ * 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 void (*)_((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(sigchld_mask)
+#define child_unblock() signal_unblock(sigchld_mask)
+
+#ifdef SIGWINCH
+# define winch_block() signal_block(signal_mask(SIGWINCH))
+# define winch_unblock() signal_unblock(signal_mask(SIGWINCH))
+#else
+# define winch_block() 0
+# define winch_unblock() 0
+#endif
+
+/* 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. */
+
+#define MAX_QUEUE_SIZE 128
+
+#define run_queued_signals() do { \
+ 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]); \
+ zhandler(signal_queue[queue_front]); /* handle queued signal */ \
+ signal_setmask(oset); \
+ } \
+} while (0)
+
+#ifdef DEBUG
+
+#define queue_signals() (queue_in++, queueing_enabled++)
+
+#define unqueue_signals() do { \
+ DPUTS(!queueing_enabled, "BUG: unqueue_signals called but not queueing"); \
+ --queue_in; \
+ if (!--queueing_enabled) run_queued_signals(); \
+} while (0)
+
+#define dont_queue_signals() do { \
+ queue_in = queueing_enabled; \
+ queueing_enabled = 0; \
+ run_queued_signals(); \
+} while (0)
+
+#define restore_queue_signals(q) do { \
+ DPUTS2(queueing_enabled && queue_in != q, \
+ "BUG: q = %d != queue_in = %d", q, queue_in); \
+ queue_in = (queueing_enabled = (q)); \
+} while (0)
+
+#else /* !DEBUG */
+
+#define queue_signals() (queueing_enabled++)
+
+#define unqueue_signals() do { \
+ if (!--queueing_enabled) run_queued_signals(); \
+} while (0)
+
+#define dont_queue_signals() do { \
+ queueing_enabled = 0; \
+ run_queued_signals(); \
+} while (0)
+
+#define restore_queue_signals(q) (queueing_enabled = (q))
+
+#endif /* DEBUG */
+
+#define queue_signal_level() queueing_enabled
+
+#ifdef BSD_SIGNALS
+#define signal_block(S) sigblock(S)
+#else
+extern sigset_t signal_block _((sigset_t));
+#endif /* BSD_SIGNALS */
+
+extern sigset_t signal_unblock _((sigset_t));
diff --git a/dotfiles/system/.zsh/modules/Src/signames1.awk b/dotfiles/system/.zsh/modules/Src/signames1.awk
new file mode 100644
index 0000000..27d21ac
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/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/dotfiles/system/.zsh/modules/Src/signames2.awk b/dotfiles/system/.zsh/modules/Src/signames2.awk
new file mode 100644
index 0000000..4d15681
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/signames2.awk
@@ -0,0 +1,106 @@
+#
+# {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
+#
+/^[\t ]*XXNAMES XXSIG[A-Z][A-Z0-9]*[\t ][\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 (signam == "CHLD" && sig[signum] == "CLD") sig[signum] = ""
+ if (signam == "POLL" && sig[signum] == "IO") sig[signum] = ""
+ 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 "#define sigmsg(sig) ((sig) <= SIGCOUNT ? sig_msg[sig]"
+ printf " : %c%s%c)", 34, "unknown signal", 34
+ printf "\n"
+ printf "/**/\n"
+ printf "mod_export char *sig_msg[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/dotfiles/system/.zsh/modules/Src/string.c b/dotfiles/system/.zsh/modules/Src/string.c
new file mode 100644
index 0000000..9e14ef9
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/string.c
@@ -0,0 +1,213 @@
+/*
+ * string.c - string manipulation
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 2000 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 "zsh.mdh"
+
+/**/
+mod_export char *
+dupstring(const char *s)
+{
+ char *t;
+
+ if (!s)
+ return NULL;
+ t = (char *) zhalloc(strlen((char *)s) + 1);
+ strcpy(t, s);
+ return t;
+}
+
+/* Duplicate string on heap when length is known */
+
+/**/
+mod_export char *
+dupstring_wlen(const char *s, unsigned len)
+{
+ char *t;
+
+ if (!s)
+ return NULL;
+ t = (char *) zhalloc(len + 1);
+ memcpy(t, s, len);
+ t[len] = '\0';
+ return t;
+}
+
+/* Duplicate string on heap, returning length of string */
+
+/**/
+mod_export char *
+dupstring_glen(const char *s, unsigned *len_ret)
+{
+ char *t;
+
+ if (!s)
+ return NULL;
+ t = (char *) zhalloc((*len_ret = strlen((char *)s)) + 1);
+ strcpy(t, s);
+ return t;
+}
+
+/**/
+mod_export char *
+ztrdup(const char *s)
+{
+ char *t;
+
+ if (!s)
+ return NULL;
+ t = (char *)zalloc(strlen((char *)s) + 1);
+ strcpy(t, s);
+ return t;
+}
+
+/**/
+#ifdef MULTIBYTE_SUPPORT
+/**/
+mod_export wchar_t *
+wcs_ztrdup(const wchar_t *s)
+{
+ wchar_t *t;
+
+ if (!s)
+ return NULL;
+ t = (wchar_t *)zalloc(sizeof(wchar_t) * (wcslen((wchar_t *)s) + 1));
+ wcscpy(t, s);
+ return t;
+}
+/**/
+#endif /* MULTIBYTE_SUPPORT */
+
+
+/* concatenate s1, s2, and s3 in dynamically allocated buffer */
+
+/**/
+mod_export char *
+tricat(char const *s1, char const *s2, char const *s3)
+{
+ /* This version always uses permanently-allocated space. */
+ char *ptr;
+ size_t l1 = strlen(s1);
+ size_t l2 = strlen(s2);
+
+ ptr = (char *)zalloc(l1 + l2 + strlen(s3) + 1);
+ strcpy(ptr, s1);
+ strcpy(ptr + l1, s2);
+ strcpy(ptr + l1 + l2, s3);
+ return ptr;
+}
+
+/**/
+mod_export char *
+zhtricat(char const *s1, char const *s2, char const *s3)
+{
+ char *ptr;
+ size_t l1 = strlen(s1);
+ size_t l2 = strlen(s2);
+
+ ptr = (char *)zhalloc(l1 + l2 + strlen(s3) + 1);
+ strcpy(ptr, s1);
+ strcpy(ptr + l1, s2);
+ strcpy(ptr + l1 + l2, s3);
+ return ptr;
+}
+
+/* concatenate s1 and s2 in dynamically allocated buffer */
+
+/**/
+mod_export char *
+dyncat(const char *s1, const char *s2)
+{
+ /* This version always uses space from the current heap. */
+ char *ptr;
+ size_t l1 = strlen(s1);
+
+ ptr = (char *)zhalloc(l1 + strlen(s2) + 1);
+ strcpy(ptr, s1);
+ strcpy(ptr + l1, s2);
+ return ptr;
+}
+
+/**/
+mod_export char *
+bicat(const char *s1, const char *s2)
+{
+ /* This version always uses permanently-allocated space. */
+ char *ptr;
+ size_t l1 = strlen(s1);
+
+ ptr = (char *)zalloc(l1 + strlen(s2) + 1);
+ strcpy(ptr, s1);
+ strcpy(ptr + l1, s2);
+ return ptr;
+}
+
+/* like dupstring(), but with a specified length */
+
+/**/
+mod_export char *
+dupstrpfx(const char *s, int len)
+{
+ char *r = zhalloc(len + 1);
+
+ memcpy(r, s, len);
+ r[len] = '\0';
+ return r;
+}
+
+/**/
+mod_export char *
+ztrduppfx(const char *s, int len)
+{
+ /* This version always uses permanently-allocated space. */
+ 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. */
+
+/**/
+mod_export char *
+appstr(char *base, char const *append)
+{
+ return strcat(realloc(base, strlen(base) + strlen(append) + 1), append);
+}
+
+/* Return a pointer to the last character of a string,
+ unless the string is empty. */
+
+/**/
+mod_export char *
+strend(char *str)
+{
+ if (*str == '\0')
+ return str;
+ return str + strlen (str) - 1;
+}
diff --git a/dotfiles/system/.zsh/modules/Src/utils.c b/dotfiles/system/.zsh/modules/Src/utils.c
new file mode 100644
index 0000000..075d272
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/utils.c
@@ -0,0 +1,7520 @@
+/*
+ * 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"
+
+/* name of script being sourced */
+
+/**/
+mod_export char *scriptname; /* is sometimes a function name */
+
+/* filename of script or other file containing code source e.g. autoload */
+
+/**/
+mod_export char *scriptfilename;
+
+/* != 0 if we are in a new style completion function */
+
+/**/
+mod_export int incompfunc;
+
+#ifdef MULTIBYTE_SUPPORT
+struct widechar_array {
+ wchar_t *chars;
+ size_t len;
+};
+typedef struct widechar_array *Widechar_array;
+
+/*
+ * The wordchars variable turned into a wide character array.
+ * This is much more convenient for testing.
+ */
+static struct widechar_array wordchars_wide;
+
+/*
+ * The same for the separators (IFS) array.
+ */
+static struct widechar_array ifs_wide;
+
+/* Function to set one of the above from the multibyte array */
+
+static void
+set_widearray(char *mb_array, Widechar_array wca)
+{
+ if (wca->chars) {
+ free(wca->chars);
+ wca->chars = NULL;
+ }
+ wca->len = 0;
+
+ if (!isset(MULTIBYTE))
+ return;
+
+ if (mb_array) {
+ VARARR(wchar_t, tmpwcs, strlen(mb_array));
+ wchar_t *wcptr = tmpwcs;
+ wint_t wci;
+
+ mb_charinit();
+ while (*mb_array) {
+ int mblen;
+
+ if (STOUC(*mb_array) <= 0x7f) {
+ mb_array++;
+ *wcptr++ = (wchar_t)*mb_array;
+ continue;
+ }
+
+ mblen = mb_metacharlenconv(mb_array, &wci);
+
+ if (!mblen)
+ break;
+ /* No good unless all characters are convertible */
+ if (wci == WEOF)
+ return;
+ *wcptr++ = (wchar_t)wci;
+#ifdef DEBUG
+ /*
+ * This generates a warning from the compiler (and is
+ * indeed useless) if chars are unsigned. It's
+ * extreme paranoia anyway.
+ */
+ if (wcptr[-1] < 0)
+ fprintf(stderr, "BUG: Bad cast to wchar_t\n");
+#endif
+ mb_array += mblen;
+ }
+
+ wca->len = wcptr - tmpwcs;
+ wca->chars = (wchar_t *)zalloc(wca->len * sizeof(wchar_t));
+ wmemcpy(wca->chars, tmpwcs, wca->len);
+ }
+}
+#endif
+
+
+/* Print an error
+
+ The following functions use the following printf-like format codes
+ (implemented by zerrmsg()):
+
+ Code Argument types Prints
+ %s const char * C string (null terminated)
+ %l const char *, int C string of given length (null not required)
+ %L long decimal value
+ %d int decimal value
+ %% (none) literal '%'
+ %c int character at that codepoint
+ %e int strerror() message (argument is typically 'errno')
+ */
+
+static void
+zwarning(const char *cmd, const char *fmt, va_list ap)
+{
+ if (isatty(2))
+ zleentry(ZLE_CMD_TRASH);
+
+ char *prefix = scriptname ? scriptname : (argzero ? argzero : "");
+
+ if (cmd) {
+ if (unset(SHINSTDIN) || locallevel) {
+ nicezputs(prefix, stderr);
+ fputc((unsigned char)':', stderr);
+ }
+ nicezputs(cmd, stderr);
+ fputc((unsigned char)':', stderr);
+ } else {
+ /*
+ * scriptname is set when sourcing scripts, so that we get the
+ * correct name instead of the generic name of whatever
+ * program/script is running. It's also set in shell functions,
+ * so test locallevel, too.
+ */
+ nicezputs((isset(SHINSTDIN) && !locallevel) ? "zsh" : prefix, stderr);
+ fputc((unsigned char)':', stderr);
+ }
+
+ zerrmsg(stderr, fmt, ap);
+}
+
+
+/**/
+mod_export void
+zerr(VA_ALIST1(const char *fmt))
+VA_DCL
+{
+ va_list ap;
+ VA_DEF_ARG(const char *fmt);
+
+ if (errflag || noerrs) {
+ if (noerrs < 2)
+ errflag |= ERRFLAG_ERROR;
+ return;
+ }
+ errflag |= ERRFLAG_ERROR;
+
+ VA_START(ap, fmt);
+ VA_GET_ARG(ap, fmt, const char *);
+ zwarning(NULL, fmt, ap);
+ va_end(ap);
+}
+
+/**/
+mod_export void
+zerrnam(VA_ALIST2(const char *cmd, const char *fmt))
+VA_DCL
+{
+ va_list ap;
+ VA_DEF_ARG(const char *cmd);
+ VA_DEF_ARG(const char *fmt);
+
+ if (errflag || noerrs)
+ return;
+ errflag |= ERRFLAG_ERROR;
+
+ VA_START(ap, fmt);
+ VA_GET_ARG(ap, cmd, const char *);
+ VA_GET_ARG(ap, fmt, const char *);
+ zwarning(cmd, fmt, ap);
+ va_end(ap);
+}
+
+/**/
+mod_export void
+zwarn(VA_ALIST1(const char *fmt))
+VA_DCL
+{
+ va_list ap;
+ VA_DEF_ARG(const char *fmt);
+
+ if (errflag || noerrs)
+ return;
+
+ VA_START(ap, fmt);
+ VA_GET_ARG(ap, fmt, const char *);
+ zwarning(NULL, fmt, ap);
+ va_end(ap);
+}
+
+/**/
+mod_export void
+zwarnnam(VA_ALIST2(const char *cmd, const char *fmt))
+VA_DCL
+{
+ va_list ap;
+ VA_DEF_ARG(const char *cmd);
+ VA_DEF_ARG(const char *fmt);
+
+ if (errflag || noerrs)
+ return;
+
+ VA_START(ap, fmt);
+ VA_GET_ARG(ap, cmd, const char *);
+ VA_GET_ARG(ap, fmt, const char *);
+ zwarning(cmd, fmt, ap);
+ va_end(ap);
+}
+
+
+#ifdef DEBUG
+
+/**/
+mod_export void
+dputs(VA_ALIST1(const char *message))
+VA_DCL
+{
+ char *filename;
+ FILE *file;
+ va_list ap;
+ VA_DEF_ARG(const char *message);
+
+ VA_START(ap, message);
+ VA_GET_ARG(ap, message, const char *);
+ if ((filename = getsparam_u("ZSH_DEBUG_LOG")) != NULL &&
+ (file = fopen(filename, "a")) != NULL) {
+ zerrmsg(file, message, ap);
+ fclose(file);
+ } else
+ zerrmsg(stderr, message, ap);
+ va_end(ap);
+}
+
+#endif /* DEBUG */
+
+#ifdef __CYGWIN__
+/*
+ * This works around an occasional problem with dllwrap on Cygwin, seen
+ * on at least two installations. It fails to find the last symbol
+ * exported in alphabetical order (in our case zwarnnam). Until this is
+ * properly categorised and fixed we add a dummy symbol at the end.
+ */
+mod_export void
+zz_plural_z_alpha(void)
+{
+}
+#endif
+
+/**/
+void
+zerrmsg(FILE *file, const char *fmt, va_list ap)
+{
+ const char *str;
+ int num;
+#ifdef DEBUG
+ long lnum;
+#endif
+#ifdef HAVE_STRERROR_R
+#define ERRBUFSIZE (80)
+ int olderrno;
+ char errbuf[ERRBUFSIZE];
+#endif
+ char *errmsg;
+
+ if ((unset(SHINSTDIN) || locallevel) && lineno) {
+#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
+ fprintf(file, "%lld: ", lineno);
+#else
+ fprintf(file, "%ld: ", (long)lineno);
+#endif
+ } else
+ fputc((unsigned char)' ', file);
+
+ while (*fmt)
+ if (*fmt == '%') {
+ fmt++;
+ switch (*fmt++) {
+ case 's':
+ str = va_arg(ap, const char *);
+ nicezputs(str, file);
+ break;
+ case 'l': {
+ char *s;
+ str = va_arg(ap, const char *);
+ num = va_arg(ap, int);
+ num = metalen(str, num);
+ s = zhalloc(num + 1);
+ memcpy(s, str, num);
+ s[num] = '\0';
+ nicezputs(s, file);
+ break;
+ }
+#ifdef DEBUG
+ case 'L':
+ lnum = va_arg(ap, long);
+ fprintf(file, "%ld", lnum);
+ break;
+#endif
+ case 'd':
+ num = va_arg(ap, int);
+ fprintf(file, "%d", num);
+ break;
+ case '%':
+ putc('%', file);
+ break;
+ case 'c':
+ num = va_arg(ap, int);
+#ifdef MULTIBYTE_SUPPORT
+ mb_charinit();
+ zputs(wcs_nicechar(num, NULL, NULL), file);
+#else
+ zputs(nicechar(num), file);
+#endif
+ break;
+ case 'e':
+ /* print the corresponding message for this errno */
+ num = va_arg(ap, int);
+ if (num == EINTR) {
+ fputs("interrupt\n", file);
+ errflag |= ERRFLAG_ERROR;
+ return;
+ }
+ errmsg = strerror(num);
+ /* 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(errmsg, file);
+ else {
+ fputc(tulower(errmsg[0]), file);
+ fputs(errmsg + 1, file);
+ }
+ break;
+ /* When adding format codes, update the comment above zwarning(). */
+ }
+ } else {
+ putc(*fmt == Meta ? *++fmt ^ 32 : *fmt, file);
+ fmt++;
+ }
+ putc('\n', file);
+ fflush(file);
+}
+
+/*
+ * Wrapper for setupterm() and del_curterm().
+ * These are called from terminfo.c and termcap.c.
+ */
+static int term_count; /* reference count of cur_term */
+
+/**/
+mod_export void
+zsetupterm(void)
+{
+#ifdef HAVE_SETUPTERM
+ int errret;
+
+ DPUTS(term_count < 0 || (term_count > 0 && !cur_term),
+ "inconsistent term_count and/or cur_term");
+ /*
+ * Just because we can't set up the terminal doesn't
+ * mean the modules hasn't booted---TERM may change,
+ * and it should be handled dynamically---so ignore errors here.
+ */
+ if (term_count++ == 0)
+ (void)setupterm((char *)0, 1, &errret);
+#endif
+}
+
+/**/
+mod_export void
+zdeleteterm(void)
+{
+#ifdef HAVE_SETUPTERM
+ DPUTS(term_count < 1 || !cur_term,
+ "inconsistent term_count and/or cur_term");
+ if (--term_count == 0)
+ del_curterm(cur_term);
+#endif
+}
+
+/* Output a single character, for the termcap routines. *
+ * This is used instead of putchar since it can be a macro. */
+
+/**/
+mod_export int
+putraw(int c)
+{
+ putc(c, stdout);
+ return 0;
+}
+
+/* Output a single character, for the termcap routines. */
+
+/**/
+mod_export int
+putshout(int c)
+{
+ putc(c, shout);
+ return 0;
+}
+
+#ifdef MULTIBYTE_SUPPORT
+/*
+ * 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.
+ *
+ * Note that the returned string is metafied, so that it must be
+ * treated like any other zsh internal string (and not, for example,
+ * output directly).
+ *
+ * This function is used even if MULTIBYTE_SUPPORT is defined: we
+ * use it as a fallback in case we couldn't identify a wide character
+ * in a multibyte string.
+ */
+
+/**/
+mod_export char *
+nicechar_sel(int c, int quotable)
+{
+ static char buf[10];
+ char *s = buf;
+ c &= 0xff;
+ if (ZISPRINT(c))
+ goto done;
+ if (c & 0x80) {
+ if (isset(PRINTEIGHTBIT))
+ goto done;
+ *s++ = '\\';
+ *s++ = 'M';
+ *s++ = '-';
+ c &= 0x7f;
+ if(ZISPRINT(c))
+ goto done;
+ }
+ if (c == 0x7f) {
+ if (quotable) {
+ *s++ = '\\';
+ *s++ = 'C';
+ *s++ = '-';
+ } else
+ *s++ = '^';
+ c = '?';
+ } else if (c == '\n') {
+ *s++ = '\\';
+ c = 'n';
+ } else if (c == '\t') {
+ *s++ = '\\';
+ c = 't';
+ } else if (c < 0x20) {
+ if (quotable) {
+ *s++ = '\\';
+ *s++ = 'C';
+ *s++ = '-';
+ } else
+ *s++ = '^';
+ c += 0x40;
+ }
+ done:
+ /*
+ * The resulting string is still metafied, so check if
+ * we are returning a character in the range that needs metafication.
+ * This can't happen if the character is printed "nicely", so
+ * this results in a maximum of two bytes total (plus the null).
+ */
+ if (imeta(c)) {
+ *s++ = Meta;
+ *s++ = c ^ 32;
+ } else
+ *s++ = c;
+ *s = 0;
+ return buf;
+}
+
+/**/
+mod_export char *
+nicechar(int c)
+{
+ return nicechar_sel(c, 0);
+}
+
+#else /* MULTIBYTE_SUPPORT */
+
+/**/
+mod_export char *
+nicechar(int c)
+{
+ static char buf[10];
+ char *s = buf;
+ c &= 0xff;
+ if (ZISPRINT(c))
+ goto done;
+ if (c & 0x80) {
+ if (isset(PRINTEIGHTBIT))
+ goto done;
+ *s++ = '\\';
+ *s++ = 'M';
+ *s++ = '-';
+ c &= 0x7f;
+ if(ZISPRINT(c))
+ goto done;
+ }
+ if (c == 0x7f) {
+ *s++ = '\\';
+ *s++ = 'C';
+ *s++ = '-';
+ c = '?';
+ } else if (c == '\n') {
+ *s++ = '\\';
+ c = 'n';
+ } else if (c == '\t') {
+ *s++ = '\\';
+ c = 't';
+ } else if (c < 0x20) {
+ *s++ = '\\';
+ *s++ = 'C';
+ *s++ = '-';
+ c += 0x40;
+ }
+ done:
+ /*
+ * The resulting string is still metafied, so check if
+ * we are returning a character in the range that needs metafication.
+ * This can't happen if the character is printed "nicely", so
+ * this results in a maximum of two bytes total (plus the null).
+ */
+ if (imeta(c)) {
+ *s++ = Meta;
+ *s++ = c ^ 32;
+ } else
+ *s++ = c;
+ *s = 0;
+ return buf;
+}
+
+#endif /* MULTIBYTE_SUPPORT */
+
+/*
+ * Return 1 if nicechar() would reformat this character.
+ */
+
+/**/
+mod_export int
+is_nicechar(int c)
+{
+ c &= 0xff;
+ if (ZISPRINT(c))
+ return 0;
+ if (c & 0x80)
+ return !isset(PRINTEIGHTBIT);
+ return (c == 0x7f || c == '\n' || c == '\t' || c < 0x20);
+}
+
+/**/
+#ifdef MULTIBYTE_SUPPORT
+static mbstate_t mb_shiftstate;
+
+/*
+ * Initialise multibyte state: called before a sequence of
+ * wcs_nicechar(), mb_metacharlenconv(), or
+ * mb_charlenconv().
+ */
+
+/**/
+mod_export void
+mb_charinit(void)
+{
+ memset(&mb_shiftstate, 0, sizeof(mb_shiftstate));
+}
+
+/*
+ * The number of bytes we need to allocate for a "nice" representation
+ * of a multibyte character.
+ *
+ * We double MB_CUR_MAX to take account of the fact that
+ * we may need to metafy. In fact the representation probably
+ * doesn't allow every character to be in the meta range, but
+ * we don't need to be too pedantic.
+ *
+ * The 12 is for the output of a UCS-4 code; we don't actually
+ * need this at the same time as MB_CUR_MAX, but again it's
+ * not worth calculating more exactly.
+ */
+#define NICECHAR_MAX (12 + 2*MB_CUR_MAX)
+/*
+ * Input a wide character. Output a printable representation,
+ * which is a metafied multibyte string. With widthp return
+ * the printing width.
+ *
+ * swide, if non-NULL, is used to help the completion code, which needs
+ * to know the printing width of the each part of the representation.
+ * *swide is set to the part of the returned string where the wide
+ * character starts. Any string up to that point is ASCII characters,
+ * so the width of it is (*swide - <return_value>). Anything left is
+ * a single wide character corresponding to the remaining width.
+ * Either the initial ASCII part or the wide character part may be empty
+ * (but not both). (Note the complication that the wide character
+ * part may contain metafied characters.)
+ *
+ * The caller needs to call mb_charinit() before the first call, to
+ * set up the multibyte shift state for a range of characters.
+ */
+
+/**/
+mod_export char *
+wcs_nicechar_sel(wchar_t c, size_t *widthp, char **swidep, int quotable)
+{
+ static char *buf;
+ static int bufalloc = 0, newalloc;
+ char *s, *mbptr;
+ int ret = 0;
+ VARARR(char, mbstr, MB_CUR_MAX);
+
+ /*
+ * We want buf to persist beyond the return. MB_CUR_MAX and hence
+ * NICECHAR_MAX may not be constant, so we have to allocate this at
+ * run time. (We could probably get away with just allocating a
+ * large buffer, in practice.) For efficiency, only reallocate if
+ * we really need to, since this function will be called frequently.
+ */
+ newalloc = NICECHAR_MAX;
+ if (bufalloc != newalloc)
+ {
+ bufalloc = newalloc;
+ buf = (char *)zrealloc(buf, bufalloc);
+ }
+
+ s = buf;
+ if (!WC_ISPRINT(c) && (c < 0x80 || !isset(PRINTEIGHTBIT))) {
+ if (c == 0x7f) {
+ if (quotable) {
+ *s++ = '\\';
+ *s++ = 'C';
+ *s++ = '-';
+ } else
+ *s++ = '^';
+ c = '?';
+ } else if (c == L'\n') {
+ *s++ = '\\';
+ c = 'n';
+ } else if (c == L'\t') {
+ *s++ = '\\';
+ c = 't';
+ } else if (c < 0x20) {
+ if (quotable) {
+ *s++ = '\\';
+ *s++ = 'C';
+ *s++ = '-';
+ } else
+ *s++ = '^';
+ c += 0x40;
+ } else if (c >= 0x80) {
+ ret = -1;
+ }
+ }
+
+ if (ret != -1)
+ ret = wcrtomb(mbstr, c, &mb_shiftstate);
+
+ if (ret == -1) {
+ memset(&mb_shiftstate, 0, sizeof(mb_shiftstate));
+ /*
+ * Can't or don't want to convert character: use UCS-2 or
+ * UCS-4 code in print escape format.
+ *
+ * This comparison fails and generates a compiler warning
+ * if wchar_t is 16 bits, but the code is still correct.
+ */
+ if (c >= 0x10000) {
+ sprintf(buf, "\\U%.8x", (unsigned int)c);
+ if (widthp)
+ *widthp = 10;
+ } else if (c >= 0x100) {
+ sprintf(buf, "\\u%.4x", (unsigned int)c);
+ if (widthp)
+ *widthp = 6;
+ } else {
+ strcpy(buf, nicechar((int)c));
+ /*
+ * There may be metafied characters from nicechar(),
+ * so compute width and end position independently.
+ */
+ if (widthp)
+ *widthp = ztrlen(buf);
+ if (swidep)
+ *swidep = buf + strlen(buf);
+ return buf;
+ }
+ if (swidep)
+ *swidep = widthp ? buf + *widthp : buf;
+ return buf;
+ }
+
+ if (widthp) {
+ int wcw = WCWIDTH(c);
+ *widthp = (s - buf);
+ if (wcw >= 0)
+ *widthp += wcw;
+ else
+ (*widthp)++;
+ }
+ if (swidep)
+ *swidep = s;
+ for (mbptr = mbstr; ret; s++, mbptr++, ret--) {
+ DPUTS(s >= buf + NICECHAR_MAX,
+ "BUG: buffer too small in wcs_nicechar");
+ if (imeta(*mbptr)) {
+ *s++ = Meta;
+ DPUTS(s >= buf + NICECHAR_MAX,
+ "BUG: buffer too small for metafied char in wcs_nicechar");
+ *s = *mbptr ^ 32;
+ } else {
+ *s = *mbptr;
+ }
+ }
+ *s = 0;
+ return buf;
+}
+
+/**/
+mod_export char *
+wcs_nicechar(wchar_t c, size_t *widthp, char **swidep)
+{
+ return wcs_nicechar_sel(c, widthp, swidep, 0);
+}
+
+/*
+ * Return 1 if wcs_nicechar() would reformat this character for display.
+ */
+
+/**/
+mod_export int is_wcs_nicechar(wchar_t c)
+{
+ if (!WC_ISPRINT(c) && (c < 0x80 || !isset(PRINTEIGHTBIT))) {
+ if (c == 0x7f || c == L'\n' || c == L'\t' || c < 0x20)
+ return 1;
+ if (c >= 0x80) {
+ return (c >= 0x100);
+ }
+ }
+ return 0;
+}
+
+/**/
+mod_export int
+zwcwidth(wint_t wc)
+{
+ int wcw;
+ /* assume a single-byte character if not valid */
+ if (wc == WEOF || unset(MULTIBYTE))
+ return 1;
+ wcw = WCWIDTH(wc);
+ /* if not printable, assume width 1 */
+ if (wcw < 0)
+ return 1;
+ return wcw;
+}
+
+/**/
+#endif /* MULTIBYTE_SUPPORT */
+
+/*
+ * Search the path for prog and return the file name.
+ * The returned value is unmetafied and in the unmeta storage
+ * area (N.B. should be duplicated if not used immediately and not
+ * equal to *namep).
+ *
+ * If namep is not NULL, *namep is set to the metafied programme
+ * name, which is in heap storage.
+ */
+/**/
+char *
+pathprog(char *prog, char **namep)
+{
+ char **pp, ppmaxlen = 0, *buf, *funmeta;
+ struct stat st;
+
+ for (pp = path; *pp; pp++)
+ {
+ int len = strlen(*pp);
+ if (len > ppmaxlen)
+ ppmaxlen = len;
+ }
+ buf = zhalloc(ppmaxlen + strlen(prog) + 2);
+ for (pp = path; *pp; pp++) {
+ sprintf(buf, "%s/%s", *pp, prog);
+ funmeta = unmeta(buf);
+ if (access(funmeta, F_OK) == 0 &&
+ stat(funmeta, &st) >= 0 &&
+ !S_ISDIR(st.st_mode)) {
+ if (namep)
+ *namep = buf;
+ return funmeta;
+ }
+ }
+
+ return NULL;
+}
+
+/* get a symlink-free pathname for s relative to PWD */
+
+/**/
+char *
+findpwd(char *s)
+{
+ char *t;
+
+ if (*s == '/')
+ return xsymlink(s, 0);
+ s = tricat((pwd[1]) ? pwd : "", "/", s);
+ t = xsymlink(s, 0);
+ zsfree(s);
+ return t;
+}
+
+/* Check whether a string contains the *
+ * name of the present directory. */
+
+/**/
+int
+ispwd(char *s)
+{
+ struct stat sbuf, tbuf;
+
+ /* POSIX: environment PWD must be absolute */
+ if (*s != '/')
+ return 0;
+
+ if (stat((s = unmeta(s)), &sbuf) == 0 && stat(".", &tbuf) == 0)
+ if (sbuf.st_dev == tbuf.st_dev && sbuf.st_ino == tbuf.st_ino) {
+ /* POSIX: No element of $PWD may be "." or ".." */
+ while (*s) {
+ if (s[0] == '.' &&
+ (!s[1] || s[1] == '/' ||
+ (s[1] == '.' && (!s[2] || s[2] == '/'))))
+ break;
+ while (*s++ != '/' && *s)
+ continue;
+ }
+ return !*s;
+ }
+ return 0;
+}
+
+static char xbuf[PATH_MAX*2+1];
+
+/**/
+static char **
+slashsplit(char *s)
+{
+ char *t, **r, **q;
+ int t0;
+
+ if (!*s)
+ return (char **) zshcalloc(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 */
+
+/**/
+static int
+xsymlinks(char *s, int full)
+{
+ char **pp, **opp;
+ char xbuf2[PATH_MAX*3+1], xbuf3[PATH_MAX*2+1];
+ int t0, ret = 0;
+ zulong xbuflen = strlen(xbuf), pplen;
+
+ opp = pp = slashsplit(s);
+ for (; xbuflen < sizeof(xbuf) && *pp && ret >= 0; pp++) {
+ if (!strcmp(*pp, "."))
+ continue;
+ if (!strcmp(*pp, "..")) {
+ char *p;
+
+ if (!strcmp(xbuf, "/"))
+ continue;
+ if (!*xbuf)
+ continue;
+ p = xbuf + xbuflen;
+ while (*--p != '/')
+ xbuflen--;
+ *p = '\0';
+ /* The \0 isn't included in the length */
+ xbuflen--;
+ continue;
+ }
+ /* Includes null byte. */
+ pplen = strlen(*pp) + 1;
+ if (xbuflen + pplen + 1 > sizeof(xbuf2)) {
+ *xbuf = 0;
+ ret = -1;
+ break;
+ }
+ memcpy(xbuf2, xbuf, xbuflen);
+ xbuf2[xbuflen] = '/';
+ memcpy(xbuf2 + xbuflen + 1, *pp, pplen);
+ t0 = readlink(unmeta(xbuf2), xbuf3, PATH_MAX);
+ if (t0 == -1) {
+ if ((xbuflen += pplen) < sizeof(xbuf)) {
+ strcat(xbuf, "/");
+ strcat(xbuf, *pp);
+ } else {
+ *xbuf = 0;
+ ret = -1;
+ break;
+ }
+ } else {
+ ret = 1;
+ metafy(xbuf3, t0, META_NOALLOC);
+ if (!full) {
+ /*
+ * If only one expansion requested, ensure the
+ * full path is in xbuf.
+ */
+ zulong len = xbuflen;
+ if (*xbuf3 == '/')
+ strcpy(xbuf, xbuf3);
+ else if ((len += strlen(xbuf3) + 1) < sizeof(xbuf)) {
+ strcpy(xbuf + xbuflen, "/");
+ strcpy(xbuf + xbuflen + 1, xbuf3);
+ } else {
+ *xbuf = 0;
+ ret = -1;
+ break;
+ }
+
+ while (*++pp) {
+ zulong newlen = len + strlen(*pp) + 1;
+ if (newlen < sizeof(xbuf)) {
+ strcpy(xbuf + len, "/");
+ strcpy(xbuf + len + 1, *pp);
+ len = newlen;
+ } else {
+ *xbuf = 01;
+ ret = -1;
+ break;
+ }
+ }
+ /*
+ * No need to update xbuflen, we're finished
+ * the expansion (for now).
+ */
+ break;
+ }
+ if (*xbuf3 == '/') {
+ strcpy(xbuf, "");
+ if (xsymlinks(xbuf3 + 1, 1) < 0)
+ ret = -1;
+ else
+ xbuflen = strlen(xbuf);
+ } else
+ if (xsymlinks(xbuf3, 1) < 0)
+ ret = -1;
+ else
+ xbuflen = strlen(xbuf);
+ }
+ }
+ freearray(opp);
+ return ret;
+}
+
+/*
+ * expand symlinks in s, and remove other weird things:
+ * note that this always expands symlinks.
+ *
+ * 'heap' indicates whether to malloc() or allocate on the heap.
+ */
+
+/**/
+char *
+xsymlink(char *s, int heap)
+{
+ if (*s != '/')
+ return NULL;
+ *xbuf = '\0';
+ if (xsymlinks(s + 1, 1) < 0)
+ zwarn("path expansion failed, using root directory");
+ if (!*xbuf)
+ return heap ? dupstring("/") : ztrdup("/");
+ return heap ? dupstring(xbuf) : ztrdup(xbuf);
+}
+
+/**/
+void
+print_if_link(char *s, int all)
+{
+ if (*s == '/') {
+ *xbuf = '\0';
+ if (all) {
+ char *start = s + 1;
+ char xbuflink[PATH_MAX+1];
+ for (;;) {
+ if (xsymlinks(start, 0) > 0) {
+ printf(" -> ");
+ zputs(*xbuf ? xbuf : "/", stdout);
+ if (!*xbuf)
+ break;
+ strcpy(xbuflink, xbuf);
+ start = xbuflink + 1;
+ *xbuf = '\0';
+ } else {
+ break;
+ }
+ }
+ } else {
+ if (xsymlinks(s + 1, 1) > 0)
+ printf(" -> "), zputs(*xbuf ? xbuf : "/", stdout);
+ }
+ }
+}
+
+/* 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->node.nam), f);
+ fputs(unmeta(s + strlen(d->dir)), f);
+ }
+}
+
+/*
+ * Substitute a directory using a name.
+ * If there is none, return the original argument.
+ *
+ * At this level all strings involved are metafied.
+ */
+
+/**/
+char *
+substnamedir(char *s)
+{
+ Nameddir d = finddir(s);
+
+ if (!d)
+ return quotestring(s, QT_BACKSLASH);
+ return zhtricat("~", d->node.nam, quotestring(s + strlen(d->dir),
+ QT_BACKSLASH));
+}
+
+
+/* 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 = getuid();
+#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, UNUSED(int flags))
+{
+ Nameddir nd = (Nameddir) hn;
+
+ if(nd->diff > finddir_best && !dircmp(nd->dir, finddir_full)
+ && !(nd->node.flags & ND_NOABBREV)) {
+ 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.
+ *
+ * s here is metafied.
+ */
+
+/**/
+Nameddir
+finddir(char *s)
+{
+ static struct nameddir homenode = { {NULL, "", 0}, NULL, 0 };
+ static int ffsz;
+ char **ares;
+ int len;
+
+ /* 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 ? home : "";
+ homenode.diff = home ? strlen(home) : 0;
+ if(homenode.diff==1)
+ homenode.diff = 0;
+ if(!finddir_full)
+ finddir_full = zalloc(ffsz = PATH_MAX+1);
+ finddir_full[0] = 0;
+ return finddir_last = NULL;
+ }
+
+#if 0
+ /*
+ * It's not safe to use the cache while we have function
+ * transformations, and it's not clear it's worth the
+ * complexity of guessing here whether subst_string_by_hook
+ * is going to turn up the goods.
+ */
+ if (!strcmp(s, finddir_full) && *finddir_full)
+ return finddir_last;
+#endif
+
+ if ((int)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(&homenode.node, 0);
+ scanhashtable(nameddirtab, 0, 0, 0, finddir_scan, 0);
+
+ ares = subst_string_by_hook("zsh_directory_name", "d", finddir_full);
+ if (ares && arrlen_ge(ares, 2) &&
+ (len = (int)zstrtol(ares[1], NULL, 10)) > finddir_best) {
+ /* better duplicate this string since it's come from REPLY */
+ finddir_last = (Nameddir)hcalloc(sizeof(struct nameddir));
+ finddir_last->node.nam = zhtricat("[", dupstring(ares[0]), "]");
+ finddir_last->dir = dupstrpfx(finddir_full, len);
+ finddir_last->diff = len - strlen(finddir_last->node.nam);
+ finddir_best = len;
+ }
+
+ return finddir_last;
+}
+
+/* add a named directory */
+
+/**/
+mod_export void
+adduserdir(char *s, char *t, int flags, int always)
+{
+ Nameddir nd;
+ char *eptr;
+
+ /* 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) zshcalloc(sizeof *nd);
+ nd->node.flags = flags;
+ eptr = t + strlen(t);
+ while (eptr > t && eptr[-1] == '/')
+ eptr--;
+ if (eptr == t) {
+ /*
+ * Don't abbreviate multiple slashes at the start of a
+ * named directory, since these are sometimes used for
+ * special purposes.
+ */
+ nd->dir = metafy(t, -1, META_DUP);
+ } else
+ nd->dir = metafy(t, eptr - t, META_DUP);
+ /* The variables PWD and OLDPWD are not to be displayed as ~PWD etc. */
+ if (!strcmp(s, "PWD") || !strcmp(s, "OLDPWD"))
+ nd->node.flags |= ND_NOABBREV;
+ nameddirtab->addnode(nameddirtab, metafy(s, -1, META_DUP), 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->node.flags) == PM_SCALAR) &&
+ (str = getsparam(name)) && *str == '/') {
+ pm->node.flags |= PM_NAMEDDIR;
+ 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 = isset(CHASELINKS) ? xsymlink(pw->pw_dir, 0)
+ : ztrdup(pw->pw_dir);
+ if (dir) {
+ adduserdir(name, dir, ND_USERNAME, 1);
+ str = dupstring(dir);
+ zsfree(dir);
+ return str;
+ } else
+ return dupstring(pw->pw_dir);
+ }
+ }
+#endif /* HAVE_GETPWNAM */
+
+ /* There are no more possible sources of directory names, so give up. */
+ return NULL;
+}
+
+/*
+ * Compare directories. Both are metafied.
+ */
+
+/**/
+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.
+ * The data is a Prepromptfn.
+ */
+
+static LinkList prepromptfns;
+
+/* Add a function to the list of pre-prompt functions. */
+
+/**/
+mod_export void
+addprepromptfn(voidvoidfnptr_t func)
+{
+ Prepromptfn ppdat = (Prepromptfn)zalloc(sizeof(struct prepromptfn));
+ ppdat->func = func;
+ if (!prepromptfns)
+ prepromptfns = znewlinklist();
+ zaddlinknode(prepromptfns, ppdat);
+}
+
+/* Remove a function from the list of pre-prompt functions. */
+
+/**/
+mod_export void
+delprepromptfn(voidvoidfnptr_t func)
+{
+ LinkNode ln;
+
+ for (ln = firstnode(prepromptfns); ln; ln = nextnode(ln)) {
+ Prepromptfn ppdat = (Prepromptfn)getdata(ln);
+ if (ppdat->func == func) {
+ (void)remnode(prepromptfns, ln);
+ zfree(ppdat, sizeof(struct prepromptfn));
+ return;
+ }
+ }
+#ifdef DEBUG
+ dputs("BUG: failed to delete node from prepromptfns");
+#endif
+}
+
+/*
+ * Functions to call at a particular time even if not at
+ * the prompt. This is handled by zle. The data is a
+ * Timedfn. The functions must be in time order, but this
+ * is enforced by addtimedfn().
+ *
+ * Note on debugging: the code in sched.c currently assumes it's
+ * the only user of timedfns for the purposes of checking whether
+ * there's a function on the list. If this becomes no longer the case,
+ * the DPUTS() tests in sched.c need rewriting.
+ */
+
+/**/
+mod_export LinkList timedfns;
+
+/* Add a function to the list of timed functions. */
+
+/**/
+mod_export void
+addtimedfn(voidvoidfnptr_t func, time_t when)
+{
+ Timedfn tfdat = (Timedfn)zalloc(sizeof(struct timedfn));
+ tfdat->func = func;
+ tfdat->when = when;
+
+ if (!timedfns) {
+ timedfns = znewlinklist();
+ zaddlinknode(timedfns, tfdat);
+ } else {
+ LinkNode ln = firstnode(timedfns);
+
+ /*
+ * Insert the new element in the linked list. We do
+ * rather too much work here since the standard
+ * functions insert after a given node, whereas we
+ * want to insert the new data before the first element
+ * with a greater time.
+ *
+ * In practice, the only use of timed functions is
+ * sched, which only adds the one function; so this
+ * whole branch isn't used beyond the following block.
+ */
+ if (!ln) {
+ zaddlinknode(timedfns, tfdat);
+ return;
+ }
+ for (;;) {
+ Timedfn tfdat2;
+ LinkNode next = nextnode(ln);
+ if (!next) {
+ zaddlinknode(timedfns, tfdat);
+ return;
+ }
+ tfdat2 = (Timedfn)getdata(next);
+ if (when < tfdat2->when) {
+ zinsertlinknode(timedfns, ln, tfdat);
+ return;
+ }
+ ln = next;
+ }
+ }
+}
+
+/*
+ * Delete a function from the list of timed functions.
+ * Note that if the function apperas multiple times only
+ * the first occurrence will be removed.
+ *
+ * Note also that when zle calls the function it does *not*
+ * automatically delete the entry from the list. That must
+ * be done by the function called. This is recommended as otherwise
+ * the function will keep being called immediately. (It just so
+ * happens this "feature" fits in well with the only current use
+ * of timed functions.)
+ */
+
+/**/
+mod_export void
+deltimedfn(voidvoidfnptr_t func)
+{
+ LinkNode ln;
+
+ for (ln = firstnode(timedfns); ln; ln = nextnode(ln)) {
+ Timedfn ppdat = (Timedfn)getdata(ln);
+ if (ppdat->func == func) {
+ (void)remnode(timedfns, ln);
+ zfree(ppdat, sizeof(struct timedfn));
+ return;
+ }
+ }
+#ifdef DEBUG
+ dputs("BUG: failed to delete node from timedfns");
+#endif
+}
+
+/* the last time we checked mail */
+
+/**/
+time_t lastmailcheck;
+
+/* the last time we checked the people in the WATCH variable */
+
+/**/
+time_t lastwatch;
+
+/*
+ * Call a function given by "name" with optional arguments
+ * "lnklist". If these are present the first argument is the function name.
+ *
+ * If "arrayp" is not zero, we also look through
+ * the array "name"_functions and execute functions found there.
+ *
+ * If "retval" is not NULL, the return value of the first hook function to
+ * return non-zero is stored in *"retval". The return value is not otherwise
+ * available as the calling context is restored.
+ *
+ * Returns 0 if at least one function was called (regardless of that function's
+ * exit status), and 1 otherwise.
+ */
+
+/**/
+mod_export int
+callhookfunc(char *name, LinkList lnklst, int arrayp, int *retval)
+{
+ Shfunc shfunc;
+ /*
+ * Save stopmsg, since user doesn't get a chance to respond
+ * to a list of jobs generated in a hook.
+ */
+ int osc = sfcontext, osm = stopmsg, stat = 1, ret = 0;
+ int old_incompfunc = incompfunc;
+
+ sfcontext = SFC_HOOK;
+ incompfunc = 0;
+
+ if ((shfunc = getshfunc(name))) {
+ ret = doshfunc(shfunc, lnklst, 1);
+ stat = 0;
+ }
+
+ if (arrayp) {
+ char **arrptr;
+ int namlen = strlen(name);
+ VARARR(char, arrnam, namlen + HOOK_SUFFIX_LEN);
+ memcpy(arrnam, name, namlen);
+ memcpy(arrnam + namlen, HOOK_SUFFIX, HOOK_SUFFIX_LEN);
+
+ if ((arrptr = getaparam(arrnam))) {
+ arrptr = arrdup(arrptr);
+ for (; *arrptr; arrptr++) {
+ if ((shfunc = getshfunc(*arrptr))) {
+ int newret = doshfunc(shfunc, lnklst, 1);
+ if (!ret)
+ ret = newret;
+ stat = 0;
+ }
+ }
+ }
+ }
+
+ sfcontext = osc;
+ stopmsg = osm;
+ incompfunc = old_incompfunc;
+
+ if (retval)
+ *retval = ret;
+ return stat;
+}
+
+/* do pre-prompt stuff */
+
+/**/
+void
+preprompt(void)
+{
+ static time_t lastperiodic;
+ time_t currentmailcheck;
+ LinkNode ln;
+ zlong period = getiparam("PERIOD");
+ zlong mailcheck = getiparam("MAILCHECK");
+
+ /*
+ * Handle any pending window size changes before we compute prompts,
+ * then block them again to avoid interrupts during prompt display.
+ */
+ winch_unblock();
+ winch_block();
+
+ if (isset(PROMPTSP) && isset(PROMPTCR) && !use_exit_printed && shout) {
+ /* The PROMPT_SP heuristic will move the prompt down to a new line
+ * if there was any dangling output on the line (assuming the terminal
+ * has automatic margins, but we try even if hasam isn't set).
+ * Unfortunately it interacts badly with ZLE displaying message
+ * when ^D has been pressed. So just disable PROMPT_SP logic in
+ * this case */
+ char *eolmark = getsparam("PROMPT_EOL_MARK");
+ char *str;
+ int percents = opts[PROMPTPERCENT], w = 0;
+ if (!eolmark)
+ eolmark = "%B%S%#%s%b";
+ opts[PROMPTPERCENT] = 1;
+ str = promptexpand(eolmark, 1, NULL, NULL, NULL);
+ countprompt(str, &w, 0, -1);
+ opts[PROMPTPERCENT] = percents;
+ zputs(str, shout);
+ fprintf(shout, "%*s\r%*s\r", (int)zterm_columns - w - !hasxn,
+ "", w, "");
+ fflush(shout);
+ free(str);
+ }
+
+ /* 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. */
+ callhookfunc("precmd", NULL, 1, NULL);
+ if (errflag)
+ return;
+
+ /* If 1) the parameter PERIOD exists, 2) a hook function for *
+ * "periodic" exists, 3) it's been greater than PERIOD since we *
+ * executed any such hook, then execute it now. */
+ if (period && ((zlong)time(NULL) > (zlong)lastperiodic + period) &&
+ !callhookfunc("periodic", NULL, 1, NULL))
+ 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 */
+ currentmailcheck = time(NULL);
+ if (mailcheck &&
+ (zlong) difftime(currentmailcheck, lastmailcheck) > mailcheck) {
+ char *mailfile;
+
+ if (mailpath && *mailpath && **mailpath)
+ checkmailpath(mailpath);
+ else {
+ queue_signals();
+ if ((mailfile = getsparam("MAIL")) && *mailfile) {
+ char *x[2];
+
+ x[0] = mailfile;
+ x[1] = NULL;
+ checkmailpath(x);
+ }
+ unqueue_signals();
+ }
+ lastmailcheck = currentmailcheck;
+ }
+
+ if (prepromptfns) {
+ for(ln = firstnode(prepromptfns); ln; ln = nextnode(ln)) {
+ Prepromptfn ppnode = (Prepromptfn)getdata(ln);
+ ppnode->func();
+ }
+ }
+}
+
+/**/
+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);
+ } else if (mailstat(unmeta(*s), &st) == -1) {
+ if (errno != ENOENT)
+ zerr("%e: %s", errno, *s);
+ } else if (S_ISDIR(st.st_mode)) {
+ LinkList l;
+ DIR *lock = opendir(unmeta(*s));
+ char buf[PATH_MAX * 2 + 1], **arr, **ap;
+ int buflen, ct = 1;
+
+ if (lock) {
+ char *fn;
+
+ pushheap();
+ l = newlinklist();
+ while ((fn = zreaddir(lock, 1)) && !errflag) {
+ if (u)
+ buflen = snprintf(buf, sizeof(buf), "%s/%s?%s", *s, fn, u);
+ else
+ buflen = snprintf(buf, sizeof(buf), "%s/%s", *s, fn);
+ if (buflen < 0 || buflen >= (int)sizeof(buf))
+ continue;
+ addlinknode(l, dupstring(buf));
+ ct++;
+ }
+ closedir(lock);
+ ap = arr = (char **) zhalloc(ct * sizeof(char *));
+
+ while ((*ap++ = (char *)ugetnode(l)));
+ checkmailpath(arr);
+ popheap();
+ }
+ } else if (shout) {
+ 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;
+ int uusav = underscoreused;
+
+ usav = zalloc(underscoreused);
+
+ if (usav)
+ memcpy(usav, zunderscore, underscoreused);
+
+ setunderscore(*s);
+
+ u = dupstring(u);
+ if (!parsestr(&u)) {
+ singsub(&u);
+ zputs(u, shout);
+ fputc('\n', shout);
+ fflush(shout);
+ }
+ if (usav) {
+ setunderscore(usav);
+ zfree(usav, uusav);
+ }
+ }
+ }
+ 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++;
+ }
+}
+
+/* This prints the XTRACE prompt. */
+
+/**/
+FILE *xtrerr = 0;
+
+/**/
+void
+printprompt4(void)
+{
+ if (!xtrerr)
+ xtrerr = stderr;
+ if (prompt4) {
+ int l, t = opts[XTRACE];
+ char *s = dupstring(prompt4);
+
+ opts[XTRACE] = 0;
+ unmetafy(s, &l);
+ s = unmetafy(promptexpand(metafy(s, l, META_NOALLOC),
+ 0, NULL, NULL, NULL), &l);
+ opts[XTRACE] = t;
+
+ fprintf(xtrerr, "%s", s);
+ free(s);
+ }
+}
+
+/**/
+mod_export void
+freestr(void *a)
+{
+ zsfree(a);
+}
+
+/**/
+mod_export 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", 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
+ }
+}
+
+/**/
+mod_export 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
+ while (tcsetattr(SHTTY, TCSADRAIN, &ti->tio) == -1 && errno == EINTR)
+ ;
+# else
+ while (ioctl(SHTTY, TCSETS, &ti->tio) == -1 && errno == EINTR)
+ ;
+# endif
+ /* zerr("settyinfo: %e",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 */
+
+/**/
+mod_export struct ttyinfo shttyinfo;
+
+/* != 0 if we need to call resetvideo() */
+
+/**/
+mod_export int resetneeded;
+
+#ifdef TIOCGWINSZ
+/* window size changed */
+
+/**/
+mod_export int winchanged;
+#endif
+
+static int
+adjustlines(int signalled)
+{
+ int oldlines = zterm_lines;
+
+#ifdef TIOCGWINSZ
+ if (signalled || zterm_lines <= 0)
+ zterm_lines = shttyinfo.winsize.ws_row;
+ else
+ shttyinfo.winsize.ws_row = zterm_lines;
+#endif /* TIOCGWINSZ */
+ if (zterm_lines <= 0) {
+ DPUTS(signalled && zterm_lines < 0,
+ "BUG: Impossible TIOCGWINSZ rows");
+ zterm_lines = tclines > 0 ? tclines : 24;
+ }
+
+ if (zterm_lines > 2)
+ termflags &= ~TERM_SHORT;
+ else
+ termflags |= TERM_SHORT;
+
+ return (zterm_lines != oldlines);
+}
+
+static int
+adjustcolumns(int signalled)
+{
+ int oldcolumns = zterm_columns;
+
+#ifdef TIOCGWINSZ
+ if (signalled || zterm_columns <= 0)
+ zterm_columns = shttyinfo.winsize.ws_col;
+ else
+ shttyinfo.winsize.ws_col = zterm_columns;
+#endif /* TIOCGWINSZ */
+ if (zterm_columns <= 0) {
+ DPUTS(signalled && zterm_columns < 0,
+ "BUG: Impossible TIOCGWINSZ cols");
+ zterm_columns = tccolumns > 0 ? tccolumns : 80;
+ }
+
+ if (zterm_columns > 2)
+ termflags &= ~TERM_NARROW;
+ else
+ termflags |= TERM_NARROW;
+
+ return (zterm_columns != oldcolumns);
+}
+
+/* check the size of the window and adjust if necessary. *
+ * The value of from: *
+ * 0: called from update_job or setupvals *
+ * 1: called from the SIGWINCH handler *
+ * 2: called from the LINES parameter callback *
+ * 3: called from the COLUMNS parameter callback */
+
+/**/
+void
+adjustwinsize(int from)
+{
+ static int getwinsz = 1;
+#ifdef TIOCGWINSZ
+ int ttyrows = shttyinfo.winsize.ws_row;
+ int ttycols = shttyinfo.winsize.ws_col;
+#endif
+ int resetzle = 0;
+
+ if (getwinsz || from == 1) {
+#ifdef TIOCGWINSZ
+ if (SHTTY == -1)
+ return;
+ if (ioctl(SHTTY, TIOCGWINSZ, (char *)&shttyinfo.winsize) == 0) {
+ resetzle = (ttyrows != shttyinfo.winsize.ws_row ||
+ ttycols != shttyinfo.winsize.ws_col);
+ if (from == 0 && resetzle && ttyrows && ttycols)
+ from = 1; /* Signal missed while a job owned the tty? */
+ ttyrows = shttyinfo.winsize.ws_row;
+ ttycols = shttyinfo.winsize.ws_col;
+ } else {
+ /* Set to value from environment on failure */
+ shttyinfo.winsize.ws_row = zterm_lines;
+ shttyinfo.winsize.ws_col = zterm_columns;
+ resetzle = (from == 1);
+ }
+#else
+ resetzle = from == 1;
+#endif /* TIOCGWINSZ */
+ } /* else
+ return; */
+
+ switch (from) {
+ case 0:
+ case 1:
+ getwinsz = 0;
+ /* Calling setiparam() here calls this function recursively, but *
+ * because we've already called adjustlines() and adjustcolumns() *
+ * here, recursive calls are no-ops unless a signal intervenes. *
+ * The commented "else return;" above might be a safe shortcut, *
+ * but I'm concerned about what happens on race conditions; e.g., *
+ * suppose the user resizes his xterm during `eval $(resize)'? */
+ if (adjustlines(from) && zgetenv("LINES"))
+ setiparam("LINES", zterm_lines);
+ if (adjustcolumns(from) && zgetenv("COLUMNS"))
+ setiparam("COLUMNS", zterm_columns);
+ getwinsz = 1;
+ break;
+ case 2:
+ resetzle = adjustlines(0);
+ break;
+ case 3:
+ resetzle = adjustcolumns(0);
+ break;
+ }
+
+#ifdef TIOCGWINSZ
+ if (interact && from >= 2 &&
+ (shttyinfo.winsize.ws_row != ttyrows ||
+ shttyinfo.winsize.ws_col != ttycols)) {
+ /* shttyinfo.winsize is already set up correctly */
+ /* ioctl(SHTTY, TIOCSWINSZ, (char *)&shttyinfo.winsize); */
+ }
+#endif /* TIOCGWINSZ */
+
+ if (zleactive && resetzle) {
+#ifdef TIOCGWINSZ
+ winchanged =
+#endif /* TIOCGWINSZ */
+ resetneeded = 1;
+ zleentry(ZLE_CMD_RESET_PROMPT);
+ zleentry(ZLE_CMD_REFRESH);
+ }
+}
+
+/*
+ * Ensure the fdtable is large enough for fd, and that the
+ * maximum fd is set appropriately.
+ */
+static void
+check_fd_table(int fd)
+{
+ if (fd <= max_zsh_fd)
+ return;
+
+ if (fd >= fdtable_size) {
+ int old_size = fdtable_size;
+ while (fd >= fdtable_size)
+ fdtable = zrealloc(fdtable,
+ (fdtable_size *= 2)*sizeof(*fdtable));
+ memset(fdtable + old_size, 0,
+ (fdtable_size - old_size) * sizeof(*fdtable));
+ }
+ max_zsh_fd = fd;
+}
+
+/* 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. */
+
+/**/
+mod_export 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
+ /*
+ * To close or not to close if fe is -1?
+ * If it is -1, we haven't moved the fd, so if we close
+ * it we lose it; but we're probably not going to be able
+ * to use it in situ anyway. So probably better to avoid a leak.
+ */
+ zclose(fd);
+ fd = fe;
+ }
+ if(fd != -1) {
+ check_fd_table(fd);
+ fdtable[fd] = FDT_INTERNAL;
+ }
+ return fd;
+}
+
+/*
+ * Move fd x to y. If x == -1, fd y is closed.
+ * Returns y for success, -1 for failure.
+ */
+
+/**/
+mod_export int
+redup(int x, int y)
+{
+ int ret = y;
+
+ if(x < 0)
+ zclose(y);
+ else if (x != y) {
+ if (dup2(x, y) == -1) {
+ ret = -1;
+ } else {
+ check_fd_table(y);
+ fdtable[y] = fdtable[x];
+ if (fdtable[y] == FDT_FLOCK || fdtable[y] == FDT_FLOCK_EXEC)
+ fdtable[y] = FDT_INTERNAL;
+ }
+ /*
+ * Closing any fd to the locked file releases the lock.
+ * This isn't expected to happen, it's here for completeness.
+ */
+ if (fdtable[x] == FDT_FLOCK)
+ fdtable_flocks--;
+ zclose(x);
+ }
+
+ return ret;
+}
+
+/*
+ * Add an fd opened ithin a module.
+ *
+ * fdt is the type of the fd; see the FDT_ definitions in zsh.h.
+ * The most likely falures are:
+ *
+ * FDT_EXTERNAL: the fd can be used within the shell for normal I/O but
+ * it will not be closed automatically or by normal shell syntax.
+ *
+ * FDT_MODULE: as FDT_EXTERNAL, but it can only be closed by the module
+ * (which should included zclose() as part of the sequence), not by
+ * the standard shell syntax for closing file descriptors.
+ *
+ * FDT_INTERNAL: fd is treated like others created by the shell for
+ * internal use; it can be closed and will be closed by the shell if it
+ * exec's or performs an exec with a fork optimised out.
+ *
+ * Safe if fd is -1 to indicate failure.
+ */
+/**/
+mod_export void
+addmodulefd(int fd, int fdt)
+{
+ if (fd >= 0) {
+ check_fd_table(fd);
+ fdtable[fd] = fdt;
+ }
+}
+
+/**/
+
+/*
+ * Indicate that an fd has a file lock; if cloexec is 1 it will be closed
+ * on exec.
+ * The fd should already be known to fdtable (e.g. by movefd).
+ * Note the fdtable code doesn't care what sort of lock
+ * is used; this simply prevents the main shell exiting prematurely
+ * when it holds a lock.
+ */
+
+/**/
+mod_export void
+addlockfd(int fd, int cloexec)
+{
+ if (cloexec) {
+ if (fdtable[fd] != FDT_FLOCK)
+ fdtable_flocks++;
+ fdtable[fd] = FDT_FLOCK;
+ } else {
+ fdtable[fd] = FDT_FLOCK_EXEC;
+ }
+}
+
+/* Close the given fd, and clear it from fdtable. */
+
+/**/
+mod_export int
+zclose(int fd)
+{
+ if (fd >= 0) {
+ /*
+ * Careful: we allow closing of arbitrary fd's, beyond
+ * max_zsh_fd. In that case we don't try anything clever.
+ */
+ if (fd <= max_zsh_fd) {
+ if (fdtable[fd] == FDT_FLOCK)
+ fdtable_flocks--;
+ fdtable[fd] = FDT_UNUSED;
+ while (max_zsh_fd > 0 && fdtable[max_zsh_fd] == FDT_UNUSED)
+ max_zsh_fd--;
+ if (fd == coprocin)
+ coprocin = -1;
+ if (fd == coprocout)
+ coprocout = -1;
+ }
+ return close(fd);
+ }
+ return -1;
+}
+
+/*
+ * Close an fd returning 0 if used for locking; return -1 if it isn't.
+ */
+
+/**/
+mod_export int
+zcloselockfd(int fd)
+{
+ if (fd > max_zsh_fd)
+ return -1;
+ if (fdtable[fd] != FDT_FLOCK && fdtable[fd] != FDT_FLOCK_EXEC)
+ return -1;
+ zclose(fd);
+ return 0;
+}
+
+#ifdef HAVE__MKTEMP
+extern char *_mktemp(char *);
+#endif
+
+/* Get a unique filename for use as a temporary file. If "prefix" is
+ * NULL, the name is relative to $TMPPREFIX; If it is non-NULL, the
+ * unique suffix includes a prefixed '.' for improved readability. If
+ * "use_heap" is true, we allocate the returned name on the heap.
+ * The string passed as "prefix" is expected to be metafied. */
+
+/**/
+mod_export char *
+gettempname(const char *prefix, int use_heap)
+{
+ char *ret, *suffix = prefix ? ".XXXXXX" : "XXXXXX";
+
+ queue_signals();
+ if (!prefix && !(prefix = getsparam("TMPPREFIX")))
+ prefix = DEFAULT_TMPPREFIX;
+ if (use_heap)
+ ret = dyncat(unmeta(prefix), suffix);
+ else
+ ret = bicat(unmeta(prefix), suffix);
+
+#ifdef HAVE__MKTEMP
+ /* Zsh uses mktemp() safely, so silence the warnings */
+ ret = (char *) _mktemp(ret);
+#else
+ ret = (char *) mktemp(ret);
+#endif
+ unqueue_signals();
+
+ return ret;
+}
+
+/* The gettempfile() "prefix" is expected to be metafied, see hist.c
+ * and gettempname(). */
+
+/**/
+mod_export int
+gettempfile(const char *prefix, int use_heap, char **tempname)
+{
+ char *fn;
+ int fd;
+ mode_t old_umask;
+#if HAVE_MKSTEMP
+ char *suffix = prefix ? ".XXXXXX" : "XXXXXX";
+
+ queue_signals();
+ old_umask = umask(0177);
+ if (!prefix && !(prefix = getsparam("TMPPREFIX")))
+ prefix = DEFAULT_TMPPREFIX;
+ if (use_heap)
+ fn = dyncat(unmeta(prefix), suffix);
+ else
+ fn = bicat(unmeta(prefix), suffix);
+
+ fd = mkstemp(fn);
+ if (fd < 0) {
+ if (!use_heap)
+ free(fn);
+ fn = NULL;
+ }
+#else
+ int failures = 0;
+
+ queue_signals();
+ old_umask = umask(0177);
+ do {
+ if (!(fn = gettempname(prefix, use_heap))) {
+ fd = -1;
+ break;
+ }
+ if ((fd = open(fn, O_RDWR | O_CREAT | O_EXCL, 0600)) >= 0)
+ break;
+ if (!use_heap)
+ free(fn);
+ fn = NULL;
+ } while (errno == EEXIST && ++failures < 16);
+#endif
+ *tempname = fn;
+
+ umask(old_umask);
+ unqueue_signals();
+ return fd;
+}
+
+/* Check if a string contains a token */
+
+/**/
+mod_export int
+has_token(const char *s)
+{
+ while(*s)
+ if(itok(*s++))
+ return 1;
+ return 0;
+}
+
+/* Delete a character in a string */
+
+/**/
+mod_export void
+chuck(char *str)
+{
+ while ((str[0] = str[1]))
+ str++;
+}
+
+/**/
+mod_export int
+tulower(int c)
+{
+ c &= 0xff;
+ return (isupper(c) ? tolower(c) : c);
+}
+
+/**/
+mod_export 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 */
+
+/**/
+mod_export void
+strucpy(char **s, char *t)
+{
+ char *u = *s;
+
+ while ((*u++ = *t++));
+ *s = u - 1;
+}
+
+/**/
+mod_export void
+struncpy(char **s, char *t, int n)
+{
+ char *u = *s;
+
+ while (n-- && (*u = *t++))
+ u++;
+ *s = u;
+ if (n > 0) /* just one null-byte will do, unlike strncpy(3) */
+ *u = '\0';
+}
+
+/* Return the number of elements in an array of pointers. *
+ * It doesn't count the NULL pointer at the end. */
+
+/**/
+mod_export int
+arrlen(char **s)
+{
+ int count;
+
+ for (count = 0; *s; s++, count++);
+ return count;
+}
+
+/* Return TRUE iff arrlen(s) >= lower_bound, but more efficiently. */
+
+/**/
+mod_export char
+arrlen_ge(char **s, unsigned lower_bound)
+{
+ while (lower_bound--)
+ if (!*s++)
+ return 0 /* FALSE */;
+
+ return 1 /* TRUE */;
+}
+
+/* Return TRUE iff arrlen(s) > lower_bound, but more efficiently. */
+
+/**/
+mod_export char
+arrlen_gt(char **s, unsigned lower_bound)
+{
+ return arrlen_ge(s, 1+lower_bound);
+}
+
+/* Return TRUE iff arrlen(s) <= upper_bound, but more efficiently. */
+
+/**/
+mod_export char
+arrlen_le(char **s, unsigned upper_bound)
+{
+ return arrlen_lt(s, 1+upper_bound);
+}
+
+/* Return TRUE iff arrlen(s) < upper_bound, but more efficiently. */
+
+/**/
+mod_export char
+arrlen_lt(char **s, unsigned upper_bound)
+{
+ return !arrlen_ge(s, upper_bound);
+}
+
+/* Skip over a balanced pair of parenthesis. */
+
+/**/
+mod_export 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;
+}
+
+/**/
+mod_export zlong
+zstrtol(const char *s, char **t, int base)
+{
+ return zstrtol_underscore(s, t, base, 0);
+}
+
+/* Convert string to zlong (see zsh.h). This function (without the z) *
+ * is contained in the ANSI standard C library, but a lot of them seem *
+ * to be broken. */
+
+/**/
+mod_export zlong
+zstrtol_underscore(const char *s, char **t, int base, int underscore)
+{
+ const char *inp, *trunc = NULL;
+ zulong calc = 0, newcalc = 0;
+ int neg;
+
+ while (inblank(*s))
+ s++;
+
+ if ((neg = IS_DASH(*s)))
+ s++;
+ else if (*s == '+')
+ s++;
+
+ if (!base) {
+ if (*s != '0')
+ base = 10;
+ else if (*++s == 'x' || *s == 'X')
+ base = 16, s++;
+ else if (*s == 'b' || *s == 'B')
+ base = 2, s++;
+ else
+ base = 8;
+ }
+ inp = s;
+ if (base < 2 || base > 36) {
+ zerr("invalid base (must be 2 to 36 inclusive): %d", base);
+ return (zlong)0;
+ } else if (base <= 10) {
+ for (; (*s >= '0' && *s < ('0' + base)) ||
+ (underscore && *s == '_'); s++) {
+ if (trunc || *s == '_')
+ continue;
+ newcalc = calc * base + *s - '0';
+ if (newcalc < calc)
+ {
+ trunc = s;
+ continue;
+ }
+ calc = newcalc;
+ }
+ } else {
+ for (; idigit(*s) || (*s >= 'a' && *s < ('a' + base - 10))
+ || (*s >= 'A' && *s < ('A' + base - 10))
+ || (underscore && *s == '_'); s++) {
+ if (trunc || *s == '_')
+ continue;
+ newcalc = calc*base + (idigit(*s) ? (*s - '0') : (*s & 0x1f) + 9);
+ if (newcalc < calc)
+ {
+ trunc = s;
+ continue;
+ }
+ calc = newcalc;
+ }
+ }
+
+ /*
+ * Special case: check for a number that was just too long for
+ * signed notation.
+ * Extra special case: the lowest negative number would trigger
+ * the first test, but is actually representable correctly.
+ * This is a 1 in the top bit, all others zero, so test for
+ * that explicitly.
+ */
+ if (!trunc && (zlong)calc < 0 &&
+ (!neg || calc & ~((zulong)1 << (8*sizeof(zulong)-1))))
+ {
+ trunc = s - 1;
+ calc /= base;
+ }
+
+ if (trunc)
+ zwarn("number truncated after %d digits: %s", (int)(trunc - inp), inp);
+
+ if (t)
+ *t = (char *)s;
+ return neg ? -(zlong)calc : (zlong)calc;
+}
+
+/*
+ * If s represents a complete unsigned integer (and nothing else)
+ * return 1 and set retval to the value. Otherwise return 0.
+ *
+ * Underscores are always allowed.
+ *
+ * Sensitive to OCTAL_ZEROES.
+ */
+
+/**/
+mod_export int
+zstrtoul_underscore(const char *s, zulong *retval)
+{
+ zulong calc = 0, newcalc = 0, base;
+
+ if (*s == '+')
+ s++;
+
+ if (*s != '0')
+ base = 10;
+ else if (*++s == 'x' || *s == 'X')
+ base = 16, s++;
+ else if (*s == 'b' || *s == 'B')
+ base = 2, s++;
+ else
+ base = isset(OCTALZEROES) ? 8 : 10;
+ if (base <= 10) {
+ for (; (*s >= '0' && *s < ('0' + base)) ||
+ *s == '_'; s++) {
+ if (*s == '_')
+ continue;
+ newcalc = calc * base + *s - '0';
+ if (newcalc < calc)
+ {
+ return 0;
+ }
+ calc = newcalc;
+ }
+ } else {
+ for (; idigit(*s) || (*s >= 'a' && *s < ('a' + base - 10))
+ || (*s >= 'A' && *s < ('A' + base - 10))
+ || *s == '_'; s++) {
+ if (*s == '_')
+ continue;
+ newcalc = calc*base + (idigit(*s) ? (*s - '0') : (*s & 0x1f) + 9);
+ if (newcalc < calc)
+ {
+ return 0;
+ }
+ calc = newcalc;
+ }
+ }
+
+ if (*s)
+ return 0;
+ *retval = calc;
+ return 1;
+}
+
+/**/
+mod_export int
+setblock_fd(int turnonblocking, int fd, long *modep)
+{
+#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;
+
+ if (!fstat(fd, &st) && !S_ISREG(st.st_mode)) {
+ *modep = fcntl(fd, F_GETFL, 0);
+ if (*modep != -1) {
+ if (!turnonblocking) {
+ /* We want to know if blocking was off */
+ if ((*modep & NONBLOCK) ||
+ !fcntl(fd, F_SETFL, *modep | NONBLOCK))
+ return 1;
+ } else if ((*modep & NONBLOCK) &&
+ !fcntl(fd, F_SETFL, *modep & ~NONBLOCK)) {
+ /* Here we want to know if the state changed */
+ return 1;
+ }
+ }
+ } else
+#endif /* NONBLOCK */
+ *modep = -1;
+ return 0;
+
+#undef NONBLOCK
+}
+
+/**/
+int
+setblock_stdin(void)
+{
+ long mode;
+ return setblock_fd(1, 0, &mode);
+}
+
+/*
+ * Check for pending input on fd. If polltty is set, we may need to
+ * use termio to look for input. As a final resort, go to non-blocking
+ * input and try to read a character, which in this case will be
+ * returned in *readchar.
+ *
+ * Note that apart from setting (and restoring) non-blocking input,
+ * this function does not change the input mode. The calling function
+ * should have set cbreak mode if necessary.
+ *
+ * fd may be -1 to sleep until the timeout in microseconds. This is a
+ * fallback for old systems that don't have nanosleep(). Some very old
+ * systems might not have select: get with it, daddy-o.
+ */
+
+/**/
+mod_export int
+read_poll(int fd, int *readchar, int polltty, zlong microseconds)
+{
+ int ret = -1;
+ long mode = -1;
+ char c;
+#ifdef HAVE_SELECT
+ fd_set foofd;
+ struct timeval expire_tv;
+#else
+#ifdef FIONREAD
+ int val;
+#endif
+#endif
+#ifdef HAS_TIO
+ struct ttyinfo ti;
+#endif
+
+ if (fd < 0 || (polltty && !isatty(fd)))
+ polltty = 0; /* no tty to poll */
+
+#if defined(HAS_TIO) && !defined(__CYGWIN__)
+ /*
+ * Under Solaris, at least, reading from the terminal in non-canonical
+ * mode requires that we use the VMIN mechanism to poll. Any attempt
+ * to check any other way, or to set the terminal to non-blocking mode
+ * and poll that way, fails; it will just for canonical mode input.
+ * We should probably use this mechanism if the user has set non-canonical
+ * mode, in which case testing here for isatty() and ~ICANON would be
+ * better than testing whether bin_read() set it, but for now we've got
+ * enough problems.
+ *
+ * Under Cygwin, you won't be surprised to here, this mechanism,
+ * although present, doesn't work, and we *have* to use ordinary
+ * non-blocking reads to find out if there is a character present
+ * in non-canonical mode.
+ *
+ * I am assuming Solaris is nearer the UNIX norm. This is not necessarily
+ * as plausible as it sounds, but it seems the right way to guess.
+ * pws 2000/06/26
+ */
+ if (polltty && fd >= 0) {
+ gettyinfo(&ti);
+ if ((polltty = ti.tio.c_cc[VMIN])) {
+ ti.tio.c_cc[VMIN] = 0;
+ /* termios timeout is 10ths of a second */
+ ti.tio.c_cc[VTIME] = (int) (microseconds / (zlong)100000);
+ settyinfo(&ti);
+ }
+ }
+#else
+ polltty = 0;
+#endif
+#ifdef HAVE_SELECT
+ expire_tv.tv_sec = (int) (microseconds / (zlong)1000000);
+ expire_tv.tv_usec = microseconds % (zlong)1000000;
+ FD_ZERO(&foofd);
+ if (fd > -1) {
+ FD_SET(fd, &foofd);
+ ret = select(fd+1, (SELECT_ARG_2_T) &foofd, NULL, NULL, &expire_tv);
+ } else
+ ret = select(0, NULL, NULL, NULL, &expire_tv);
+#else
+ if (fd < 0) {
+ /* OK, can't do that. Just quietly sleep for a second. */
+ sleep(1);
+ return 1;
+ }
+#ifdef FIONREAD
+ if (ioctl(fd, FIONREAD, (char *) &val) == 0)
+ ret = (val > 0);
+#endif
+#endif
+
+ if (fd >= 0 && ret < 0 && !errflag) {
+ /*
+ * Final attempt: set non-blocking read and try to read a character.
+ * Praise Bill, this works under Cygwin (nothing else seems to).
+ */
+ if ((polltty || setblock_fd(0, fd, &mode)) && read(fd, &c, 1) > 0) {
+ *readchar = c;
+ ret = 1;
+ }
+ if (mode != -1)
+ fcntl(fd, F_SETFL, mode);
+ }
+#ifdef HAS_TIO
+ if (polltty) {
+ ti.tio.c_cc[VMIN] = 1;
+ ti.tio.c_cc[VTIME] = 0;
+ settyinfo(&ti);
+ }
+#endif
+ return (ret > 0);
+}
+
+/*
+ * Sleep for the given number of microseconds --- must be within
+ * range of a long at the moment, but this is only used for
+ * limited internal purposes.
+ */
+
+/**/
+int
+zsleep(long us)
+{
+#ifdef HAVE_NANOSLEEP
+ struct timespec sleeptime;
+
+ sleeptime.tv_sec = (time_t)us / (time_t)1000000;
+ sleeptime.tv_nsec = (us % 1000000L) * 1000L;
+ for (;;) {
+ struct timespec rem;
+ int ret = nanosleep(&sleeptime, &rem);
+
+ if (ret == 0)
+ return 1;
+ else if (errno != EINTR)
+ return 0;
+ sleeptime = rem;
+ }
+#else
+ int dummy;
+ return read_poll(-1, &dummy, 0, us);
+#endif
+}
+
+/**
+ * Sleep for time (fairly) randomly up to max_us microseconds.
+ * Don't let the wallclock time extend beyond end_time.
+ * Return 1 if that seemed to work, else 0.
+ *
+ * For best results max_us should be a multiple of 2**16 or large
+ * enough that it doesn't matter.
+ */
+
+/**/
+int
+zsleep_random(long max_us, time_t end_time)
+{
+ long r;
+ time_t now = time(NULL);
+
+ /*
+ * Randomish backoff. Doesn't need to be fundamentally
+ * unpredictable, just probably unlike the value another
+ * exiting shell is using. On some systems the bottom 16
+ * bits aren't that random but the use here doesn't
+ * really care.
+ */
+ r = (long)(rand() & 0xFFFF);
+ /*
+ * Turn this into a fraction of sleep_us. Again, this
+ * doesn't need to be particularly accurate and the base time
+ * is sufficient that we can do the division first and not
+ * worry about the range.
+ */
+ r = (max_us >> 16) * r;
+ /*
+ * Don't sleep beyond timeout.
+ * Not that important as timeout is ridiculously long, but
+ * if there's an interface, interface to it...
+ */
+ while (r && now + (time_t)(r / 1000000) > end_time)
+ r >>= 1;
+ if (r) /* pedantry */
+ return zsleep(r);
+ return 0;
+}
+
+/**/
+int
+checkrmall(char *s)
+{
+ DIR *rmd;
+ int count = 0;
+ if (!shout)
+ return 1;
+ if (*s != '/') {
+ if (pwd[1])
+ s = zhtricat(pwd, "/", s);
+ else
+ s = dyncat("/", s);
+ }
+ const int max_count = 100;
+ if ((rmd = opendir(unmeta(s)))) {
+ int ignoredots = !isset(GLOBDOTS);
+ char *fname;
+
+ while ((fname = zreaddir(rmd, 1))) {
+ if (ignoredots && *fname == '.')
+ continue;
+ count++;
+ if (count > max_count)
+ break;
+ }
+ closedir(rmd);
+ }
+ if (count > max_count)
+ fprintf(shout, "zsh: sure you want to delete more than %d files in ",
+ max_count);
+ else if (count == 1)
+ fprintf(shout, "zsh: sure you want to delete the only file in ");
+ else if (count > 0)
+ fprintf(shout, "zsh: sure you want to delete all %d files in ",
+ count);
+ else {
+ /* We don't know how many files the glob will expand to; see 41707. */
+ fprintf(shout, "zsh: sure you want to delete all the files in ");
+ }
+ nicezputs(s, shout);
+ if(isset(RMSTARWAIT)) {
+ fputs("? (waiting ten seconds)", shout);
+ fflush(shout);
+ zbeep();
+ sleep(10);
+ fputc('\n', shout);
+ }
+ if (errflag)
+ return 0;
+ fputs(" [yn]? ", shout);
+ fflush(shout);
+ zbeep();
+ return (getquery("ny", 1) == 'y');
+}
+
+/**/
+mod_export ssize_t
+read_loop(int fd, char *buf, size_t len)
+{
+ ssize_t got = len;
+
+ while (1) {
+ ssize_t ret = read(fd, buf, len);
+ if (ret == len)
+ break;
+ if (ret <= 0) {
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ if (fd != SHTTY)
+ zwarn("read failed: %e", errno);
+ }
+ return ret;
+ }
+ buf += ret;
+ len -= ret;
+ }
+
+ return got;
+}
+
+/**/
+mod_export ssize_t
+write_loop(int fd, const char *buf, size_t len)
+{
+ ssize_t wrote = len;
+
+ while (1) {
+ ssize_t ret = write(fd, buf, len);
+ if (ret == len)
+ break;
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ if (fd != SHTTY)
+ zwarn("write failed: %e", errno);
+ return -1;
+ }
+ buf += ret;
+ len -= ret;
+ }
+
+ return wrote;
+}
+
+static int
+read1char(int echo)
+{
+ char c;
+ int q = queue_signal_level();
+
+ dont_queue_signals();
+ while (read(SHTTY, &c, 1) != 1) {
+ if (errno != EINTR || errflag || retflag || breaks || contflag) {
+ restore_queue_signals(q);
+ return -1;
+ }
+ }
+ restore_queue_signals(q);
+ if (echo)
+ write_loop(SHTTY, &c, 1);
+ return STOUC(c);
+}
+
+/**/
+mod_export int
+noquery(int purge)
+{
+ int val = 0;
+
+#ifdef FIONREAD
+ char c;
+
+ ioctl(SHTTY, FIONREAD, (char *)&val);
+ if (purge) {
+ for (; val; val--) {
+ if (read(SHTTY, &c, 1) != 1) {
+ /* Do nothing... */
+ }
+ }
+ }
+#endif
+
+ return val;
+}
+
+/**/
+int
+getquery(char *valid_chars, int purge)
+{
+ int c, d, nl = 0;
+ int isem = !strcmp(term, "emacs");
+ struct ttyinfo ti;
+
+ attachtty(mypgrp);
+
+ gettyinfo(&ti);
+#ifdef HAS_TIO
+ ti.tio.c_lflag &= ~ECHO;
+ if (!isem) {
+ ti.tio.c_lflag &= ~ICANON;
+ ti.tio.c_cc[VMIN] = 1;
+ ti.tio.c_cc[VTIME] = 0;
+ }
+#else
+ ti.sgttyb.sg_flags &= ~ECHO;
+ if (!isem)
+ ti.sgttyb.sg_flags |= CBREAK;
+#endif
+ settyinfo(&ti);
+
+ if (noquery(purge)) {
+ if (!isem)
+ settyinfo(&shttyinfo);
+ write_loop(SHTTY, "n\n", 2);
+ return 'n';
+ }
+
+ while ((c = read1char(0)) >= 0) {
+ if (c == 'Y')
+ c = 'y';
+ else if (c == 'N')
+ c = 'n';
+ if (!valid_chars)
+ break;
+ if (c == '\n') {
+ c = *valid_chars;
+ nl = 1;
+ break;
+ }
+ if (strchr(valid_chars, c)) {
+ nl = 1;
+ break;
+ }
+ zbeep();
+ }
+ if (c >= 0) {
+ char buf = (char)c;
+ write_loop(SHTTY, &buf, 1);
+ }
+ if (nl)
+ write_loop(SHTTY, "\n", 1);
+
+ if (isem) {
+ if (c != '\n')
+ while ((d = read1char(1)) >= 0 && d != '\n');
+ } else {
+ if (c != '\n' && !valid_chars) {
+#ifdef MULTIBYTE_SUPPORT
+ if (isset(MULTIBYTE) && c >= 0) {
+ /*
+ * No waiting for a valid character, and no draining;
+ * we should ensure we haven't stopped in the middle
+ * of a multibyte character.
+ */
+ mbstate_t mbs;
+ char cc = (char)c;
+ memset(&mbs, 0, sizeof(mbs));
+ for (;;) {
+ size_t ret = mbrlen(&cc, 1, &mbs);
+
+ if (ret != MB_INCOMPLETE)
+ break;
+ c = read1char(1);
+ if (c < 0)
+ break;
+ cc = (char)c;
+ }
+ }
+#endif
+ write_loop(SHTTY, "\n", 1);
+ }
+ }
+ settyinfo(&shttyinfo);
+ return c;
+}
+
+static int d;
+static char *guess, *best;
+static Patprog spckpat, spnamepat;
+
+/**/
+static void
+spscan(HashNode hn, UNUSED(int scanflags))
+{
+ int nd;
+
+ if (spckpat && pattry(spckpat, hn->nam))
+ return;
+
+ 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 */
+
+/**/
+mod_export void
+spckword(char **s, int hist, int cmd, int ask)
+{
+ char *t, *correct_ignore;
+ char ic = '\0';
+ int preflen = 0;
+ int autocd = cmd && isset(AUTOCD) && strcmp(*s, ".") && strcmp(*s, "..");
+
+ if ((histdone & HISTFLAG_NOEXEC) || **s == '-' || **s == '%')
+ return;
+ if (!strcmp(*s, "in"))
+ return;
+ if (!(*s)[0] || !(*s)[1])
+ return;
+ if (cmd) {
+ 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 ((correct_ignore = getsparam("CORRECT_IGNORE")) != NULL) {
+ tokenize(correct_ignore = dupstring(correct_ignore));
+ remnulargs(correct_ignore);
+ spckpat = patcompile(correct_ignore, 0, NULL);
+ } else
+ spckpat = NULL;
+
+ if ((correct_ignore = getsparam("CORRECT_IGNORE_FILE")) != NULL) {
+ tokenize(correct_ignore = dupstring(correct_ignore));
+ remnulargs(correct_ignore);
+ spnamepat = patcompile(correct_ignore, 0, NULL);
+ } else
+ spnamepat = NULL;
+
+ if (**s == String && !*t) {
+ guess = *s + 1;
+ if (itype_end(guess, IIDENT, 1) == 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) {
+ int ne;
+ ic = *guess;
+ if (!*++t)
+ return;
+ guess = dupstring(guess);
+ ne = noerrs;
+ noerrs = 2;
+ singsub(&guess);
+ noerrs = ne;
+ if (!guess)
+ return;
+ preflen = strlen(guess) - strlen(t);
+ }
+ if (access(unmeta(guess), F_OK) == 0)
+ return;
+ best = spname(guess);
+ 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 (autocd) {
+ char **pp;
+ for (pp = cdpath; *pp; pp++) {
+ char bestcd[PATH_MAX + 1];
+ int thisdist;
+ /* Less than d here, instead of less than or equal *
+ * as used in spscan(), so that an autocd is chosen *
+ * only when it is better than anything so far, and *
+ * so we prefer directories earlier in the cdpath. */
+ if ((thisdist = mindist(*pp, *s, bestcd, 1)) < d) {
+ best = dupstring(bestcd);
+ d = thisdist;
+ }
+ }
+ }
+ }
+ }
+ if (errflag)
+ return;
+ if (best && (int)strlen(best) > 1 && strcmp(best, guess)) {
+ int x;
+ if (ic) {
+ char *u;
+ 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 *) zhalloc(t - *s + strlen(best + preflen) + 1);
+ strncpy(u, *s, t - *s);
+ strcpy(u + (t - *s), best + preflen);
+ } else {
+ u = (char *) zhalloc(strlen(best) + 2);
+ *u = '\0';
+ strcpy(u + 1, best);
+ }
+ best = u;
+ guess = *s;
+ *guess = *best = ztokens[ic - Pound];
+ }
+ if (ask) {
+ if (noquery(0)) {
+ x = 'n';
+ } else if (shout) {
+ char *pptbuf;
+ pptbuf = promptexpand(sprompt, 0, best, guess, NULL);
+ zputs(pptbuf, shout);
+ free(pptbuf);
+ fflush(shout);
+ zbeep();
+ x = getquery("nyae", 0);
+ if (cmd && x == 'n')
+ pathchecked = path;
+ } else
+ x = 'n';
+ } else
+ x = 'y';
+ if (x == 'y') {
+ *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;
+ }
+}
+
+/*
+ * Helper for ztrftime. Called with a pointer to the length left
+ * in the buffer, and a new string length to decrement from that.
+ * Returns 0 if the new length fits, 1 otherwise. We assume a terminating
+ * NUL and return 1 if that doesn't fit.
+ */
+
+static int
+ztrftimebuf(int *bufsizeptr, int decr)
+{
+ if (*bufsizeptr <= decr)
+ return 1;
+ *bufsizeptr -= decr;
+ return 0;
+}
+
+/*
+ * Like the system function, this returns the number of characters
+ * copied, not including the terminating NUL. This may be zero
+ * if the string didn't fit.
+ *
+ * As an extension, try to detect an error in strftime --- typically
+ * not enough memory --- and return -1. Not guaranteed to be portable,
+ * since the strftime() interface doesn't make any guarantees about
+ * the state of the buffer if it returns zero.
+ *
+ * fmt is metafied, but we need to unmetafy it on the fly to
+ * pass into strftime / combine with the output from strftime.
+ * The return value in buf is not metafied.
+ */
+
+/**/
+mod_export int
+ztrftime(char *buf, int bufsize, char *fmt, struct tm *tm, long nsec)
+{
+ int hr12;
+#ifdef HAVE_STRFTIME
+ int decr;
+ char *fmtstart;
+#else
+ 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"};
+#endif
+ char *origbuf = buf;
+
+
+ while (*fmt) {
+ if (*fmt == Meta) {
+ int chr = fmt[1] ^ 32;
+ if (ztrftimebuf(&bufsize, 1))
+ return -1;
+ *buf++ = chr;
+ fmt += 2;
+ } else if (*fmt == '%') {
+ int strip;
+ int digs = 3;
+
+#ifdef HAVE_STRFTIME
+ fmtstart =
+#endif
+ fmt++;
+
+ if (*fmt == '-') {
+ strip = 1;
+ fmt++;
+ } else
+ strip = 0;
+ if (idigit(*fmt)) {
+ /* Digit --- only useful with . */
+ char *dstart = fmt;
+ char *dend = fmt+1;
+ while (idigit(*dend))
+ dend++;
+ if (*dend == '.') {
+ fmt = dend;
+ digs = atoi(dstart);
+ }
+ }
+ /*
+ * Assume this format will take up at least two
+ * characters. Not always true, but if that matters
+ * we are so close to the edge it's not a big deal.
+ * Fix up some longer cases specially when we get to them.
+ */
+ if (ztrftimebuf(&bufsize, 2))
+ return -1;
+#ifdef HAVE_STRFTIME
+ /* Our internal handling doesn't handle padding and other gnu extensions,
+ * so here we detect them and pass over to strftime(). We don't want
+ * to do this unconditionally though, as we have some extensions that
+ * strftime() doesn't have (%., %f, %L and %K) */
+morefmt:
+ if (!((fmt - fmtstart == 1) || (fmt - fmtstart == 2 && strip) || *fmt == '.')) {
+ while (*fmt && strchr("OE^#_-0123456789", *fmt))
+ fmt++;
+ if (*fmt) {
+ fmt++;
+ goto strftimehandling;
+ }
+ }
+#endif
+ switch (*fmt++) {
+ case '.':
+ if (ztrftimebuf(&bufsize, digs))
+ return -1;
+ if (digs > 9)
+ digs = 9;
+ if (digs < 9) {
+ int trunc;
+ for (trunc = 8 - digs; trunc; trunc--)
+ nsec /= 10;
+ nsec = (nsec + 8) / 10;
+ }
+ sprintf(buf, "%0*ld", digs, nsec);
+ buf += digs;
+ break;
+ case '\0':
+ /* Guard against premature end of string */
+ *buf++ = '%';
+ fmt--;
+ break;
+ case 'f':
+ strip = 1;
+ /* FALLTHROUGH */
+ case 'e':
+ if (tm->tm_mday > 9)
+ *buf++ = '0' + tm->tm_mday / 10;
+ else if (!strip)
+ *buf++ = ' ';
+ *buf++ = '0' + tm->tm_mday % 10;
+ break;
+ case 'K':
+ strip = 1;
+ /* FALLTHROUGH */
+ case 'H':
+ case 'k':
+ if (tm->tm_hour > 9)
+ *buf++ = '0' + tm->tm_hour / 10;
+ else if (!strip) {
+ if (fmt[-1] == 'H')
+ *buf++ = '0';
+ else
+ *buf++ = ' ';
+ }
+ *buf++ = '0' + tm->tm_hour % 10;
+ break;
+ case 'L':
+ strip = 1;
+ /* FALLTHROUGH */
+ case 'l':
+ hr12 = tm->tm_hour % 12;
+ if (hr12 == 0)
+ hr12 = 12;
+ if (hr12 > 9)
+ *buf++ = '1';
+ else if (!strip)
+ *buf++ = ' ';
+
+ *buf++ = '0' + (hr12 % 10);
+ break;
+ case 'd':
+ if (tm->tm_mday > 9 || !strip)
+ *buf++ = '0' + tm->tm_mday / 10;
+ *buf++ = '0' + tm->tm_mday % 10;
+ break;
+ case 'm':
+ if (tm->tm_mon > 8 || !strip)
+ *buf++ = '0' + (tm->tm_mon + 1) / 10;
+ *buf++ = '0' + (tm->tm_mon + 1) % 10;
+ break;
+ case 'M':
+ if (tm->tm_min > 9 || !strip)
+ *buf++ = '0' + tm->tm_min / 10;
+ *buf++ = '0' + tm->tm_min % 10;
+ break;
+ case 'N':
+ if (ztrftimebuf(&bufsize, 9))
+ return -1;
+ sprintf(buf, "%09ld", nsec);
+ buf += 9;
+ break;
+ case 'S':
+ if (tm->tm_sec > 9 || !strip)
+ *buf++ = '0' + tm->tm_sec / 10;
+ *buf++ = '0' + tm->tm_sec % 10;
+ break;
+ case 'y':
+ if (tm->tm_year > 9 || !strip)
+ *buf++ = '0' + (tm->tm_year / 10) % 10;
+ *buf++ = '0' + tm->tm_year % 10;
+ break;
+#ifndef HAVE_STRFTIME
+ case 'Y':
+ {
+ int year, digits, testyear;
+ year = tm->tm_year + 1900;
+ digits = 1;
+ testyear = year;
+ while (testyear > 9) {
+ digits++;
+ testyear /= 10;
+ }
+ if (ztrftimebuf(&bufsize, digits))
+ return -1;
+ sprintf(buf, "%d", year);
+ buf += digits;
+ break;
+ }
+ case 'a':
+ if (ztrftimebuf(&bufsize, strlen(astr[tm->tm_wday]) - 2))
+ return -1;
+ strucpy(&buf, astr[tm->tm_wday]);
+ break;
+ case 'b':
+ if (ztrftimebuf(&bufsize, strlen(estr[tm->tm_mon]) - 2))
+ return -1;
+ 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
+ case 'E':
+ case 'O':
+ case '^':
+ case '#':
+ case '_':
+ case '-':
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ goto morefmt;
+strftimehandling:
+ default:
+ /*
+ * Remember we've already allowed for two characters
+ * in the accounting in bufsize (but nowhere else).
+ */
+ {
+ char origchar = fmt[-1];
+ int size = fmt - fmtstart;
+ char *tmp, *last;
+ tmp = zhalloc(size + 1);
+ strncpy(tmp, fmtstart, size);
+ last = fmt-1;
+ if (*last == Meta) {
+ /*
+ * This is for consistency in counting:
+ * a metafiable character isn't actually
+ * a valid strftime descriptor.
+ *
+ * Previous characters were explicitly checked,
+ * so can't be metafied.
+ */
+ *last = *++fmt ^ 32;
+ }
+ tmp[size] = '\0';
+ *buf = '\1';
+ if (!strftime(buf, bufsize + 2, tmp, tm))
+ {
+ /*
+ * Some locales don't have strings for
+ * AM/PM, so empty output is valid.
+ */
+ if (*buf || (origchar != 'p' && origchar != 'P')) {
+ if (*buf) {
+ buf[0] = '\0';
+ return -1;
+ }
+ return 0;
+ }
+ }
+ decr = strlen(buf);
+ buf += decr;
+ bufsize -= decr - 2;
+ }
+#endif
+ break;
+ }
+ } else {
+ if (ztrftimebuf(&bufsize, 1))
+ return -1;
+ *buf++ = *fmt++;
+ }
+ }
+ *buf = '\0';
+ return buf - origbuf;
+}
+
+/**/
+mod_export char *
+zjoin(char **arr, int delim, int heap)
+{
+ int len = 0;
+ char **s, *ret, *ptr;
+
+ for (s = arr; *s; s++)
+ len += strlen(*s) + 1 + (imeta(delim) ? 1 : 0);
+ if (!len)
+ return heap? "" : ztrdup("");
+ ptr = ret = (char *) (heap ? zhalloc(len) : zalloc(len));
+ for (s = arr; *s; s++) {
+ strucpy(&ptr, *s);
+ if (imeta(delim)) {
+ *ptr++ = Meta;
+ *ptr++ = delim ^ 32;
+ }
+ else
+ *ptr++ = delim;
+ }
+ ptr[-1 - (imeta(delim) ? 1 : 0)] = '\0';
+ return ret;
+}
+
+/* Split a string containing a colon separated list *
+ * of items into an array of strings. */
+
+/**/
+mod_export 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 ((int)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;
+
+ /*
+ * Don't need to handle mutlibyte characters, they can't
+ * be IWSEP. Do need to check for metafication.
+ */
+ while (*t && iwsep(*t == Meta ? t[1] ^ 32 : *t)) {
+ if (*t == Meta)
+ t++;
+ t++;
+ i++;
+ }
+ *s = t;
+ return i;
+}
+
+/*
+ * haven't worked out what allownull does; it's passed down from
+ * sepsplit but all the cases it's used are either 0 or 1 without
+ * a comment. it seems to be something to do with the `nulstring'
+ * which i think is some kind of a metafication thing, so probably
+ * allownull's value is associated with whether we are using
+ * metafied strings.
+ * see findsep() below for handling of `quote' argument
+ */
+
+/**/
+mod_export char **
+spacesplit(char *s, int allownull, int heap, int quote)
+{
+ char *t, **ret, **ptr;
+ int l = sizeof(*ret) * (wordcount(s, NULL, -!allownull) + 1);
+ char *(*dup)(const char *) = (heap ? dupstring : ztrdup);
+
+ /* ### TODO: s/calloc/alloc/ */
+ ptr = ret = (char **) (heap ? hcalloc(l) : zshcalloc(l));
+
+ if (quote) {
+ /*
+ * we will be stripping quoted separators by hacking string,
+ * so make sure it's hackable.
+ */
+ s = dupstring(s);
+ }
+
+ t = s;
+ skipwsep(&s);
+ MB_METACHARINIT();
+ if (*s && itype_end(s, ISEP, 1) != s)
+ *ptr++ = dup(allownull ? "" : nulstring);
+ else if (!allownull && t != s)
+ *ptr++ = dup("");
+ while (*s) {
+ char *iend = itype_end(s, ISEP, 1);
+ if (iend != s) {
+ s = iend;
+ skipwsep(&s);
+ }
+ else if (quote && *s == '\\') {
+ s++;
+ skipwsep(&s);
+ }
+ t = s;
+ (void)findsep(&s, NULL, quote);
+ if (s > t || allownull) {
+ *ptr = (char *) (heap ? zhalloc((s - t) + 1) :
+ zalloc((s - t) + 1));
+ ztrncpy(*ptr++, t, s - t);
+ } else
+ *ptr++ = dup(nulstring);
+ t = s;
+ skipwsep(&s);
+ }
+ if (!allownull && t != s)
+ *ptr++ = dup("");
+ *ptr = NULL;
+ return ret;
+}
+
+/*
+ * Find a separator. Return 0 if already at separator, 1 if separator
+ * found later, else -1. (Historical note: used to return length into
+ * string but this is all that is necessary and is less ambiguous with
+ * multibyte characters around.)
+ *
+ * *s is the string we are looking along, which will be updated
+ * to the point we have got to.
+ *
+ * sep is a possibly multicharacter separator to look for. If NULL,
+ * use normal separator characters. If *sep is NULL, split on individual
+ * characters.
+ *
+ * quote is a flag that '\<sep>' should not be treated as a separator.
+ * in this case we need to be able to strip the backslash directly
+ * in the string, so the calling function must have sent us something
+ * modifiable. currently this only works for sep == NULL. also in
+ * in this case only, we need to turn \\ into \.
+ */
+
+/**/
+static int
+findsep(char **s, char *sep, int quote)
+{
+ /*
+ */
+ int i, ilen;
+ char *t, *tt;
+ convchar_t c;
+
+ MB_METACHARINIT();
+ if (!sep) {
+ for (t = *s; *t; t += ilen) {
+ if (quote && *t == '\\') {
+ if (t[1] == '\\') {
+ chuck(t);
+ ilen = 1;
+ continue;
+ } else {
+ ilen = MB_METACHARLENCONV(t+1, &c);
+ if (WC_ZISTYPE(c, ISEP)) {
+ chuck(t);
+ /* then advance over new character, length ilen */
+ } else {
+ /* treat *t (backslash) as normal byte */
+ if (isep(*t))
+ break;
+ ilen = 1;
+ }
+ }
+ } else {
+ ilen = MB_METACHARLENCONV(t, &c);
+ if (WC_ZISTYPE(c, ISEP))
+ break;
+ }
+ }
+ i = (t > *s);
+ *s = t;
+ return i;
+ }
+ if (!sep[0]) {
+ /*
+ * NULL separator just means advance past first character,
+ * if any.
+ */
+ if (**s) {
+ *s += MB_METACHARLEN(*s);
+ return 1;
+ }
+ return -1;
+ }
+ for (i = 0; **s; i++) {
+ /*
+ * The following works for multibyte characters by virtue of
+ * the fact that sep may be a string (and we don't care how
+ * it divides up, we need to match all of it).
+ */
+ for (t = sep, tt = *s; *t && *tt && *t == *tt; t++, tt++);
+ if (!*t)
+ return (i > 0);
+ *s += MB_METACHARLEN(*s);
+ }
+ 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, 0)) {
+ r = *s += sl;
+ }
+ return r;
+ }
+ MB_METACHARINIT();
+ for (t = *s; *t; t += sl) {
+ convchar_t c;
+ sl = MB_METACHARLENCONV(t, &c);
+ if (!WC_ZISTYPE(c, ISEP))
+ break;
+ }
+ *s = t;
+ (void)findsep(s, sep, 0);
+ 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)) >= 0; s += sl)
+ if ((c || mul) && (sl || *(s + sl)))
+ r++;
+ } else {
+ char *t = s;
+
+ r = 0;
+ if (mul <= 0)
+ skipwsep(&s);
+ if ((*s && itype_end(s, ISEP, 1) != s) ||
+ (mul < 0 && t != s))
+ r++;
+ for (; *s; r++) {
+ char *ie = itype_end(s, ISEP, 1);
+ if (ie != s) {
+ s = ie;
+ if (mul <= 0)
+ skipwsep(&s);
+ }
+ (void)findsep(&s, NULL, 0);
+ t = s;
+ if (mul <= 0)
+ skipwsep(&s);
+ }
+ if (mul < 0 && t != s)
+ r++;
+ }
+ return r;
+}
+
+/**/
+mod_export char *
+sepjoin(char **s, char *sep, int heap)
+{
+ char *r, *p, **t;
+ int l, sl;
+ char sepbuf[2];
+
+ if (!*s)
+ return heap ? "" : ztrdup("");
+ if (!sep) {
+ /* optimise common case that ifs[0] is space */
+ if (ifs && *ifs != ' ') {
+ MB_METACHARINIT();
+ sep = dupstrpfx(ifs, MB_METACHARLEN(ifs));
+ } else {
+ p = sep = sepbuf;
+ *p++ = ' ';
+ *p = '\0';
+ }
+ }
+ sl = strlen(sep);
+ for (t = s, l = 1 - sl; *t; l += strlen(*t) + sl, t++);
+ r = p = (char *) (heap ? zhalloc(l) : zalloc(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 heap)
+{
+ int n, sl;
+ char *t, *tt, **r, **p;
+
+ /* Null string? Treat as empty string. */
+ if (s[0] == Nularg && !s[1])
+ s++;
+
+ if (!sep)
+ return spacesplit(s, allownull, heap, 0);
+
+ sl = strlen(sep);
+ n = wordcount(s, sep, 1);
+ r = p = (char **) (heap ? zhalloc((n + 1) * sizeof(char *)) :
+ zalloc((n + 1) * sizeof(char *)));
+
+ for (t = s; n--;) {
+ tt = t;
+ (void)findsep(&t, sep, 0);
+ *p = (char *) (heap ? zhalloc(t - tt + 1) :
+ zalloc(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 */
+
+/**/
+mod_export Shfunc
+getshfunc(char *nam)
+{
+ return (Shfunc) shfunctab->getnode(shfunctab, nam);
+}
+
+/*
+ * Call the function func to substitute string orig by setting
+ * the parameter reply.
+ * Return the array from reply, or NULL if the function returned
+ * non-zero status.
+ * The returned value comes directly from the parameter and
+ * so should be used before there is any chance of that
+ * being changed or unset.
+ * If arg1 is not NULL, it is used as an initial argument to
+ * the function, with the original string as the second argument.
+ */
+
+/**/
+char **
+subst_string_by_func(Shfunc func, char *arg1, char *orig)
+{
+ int osc = sfcontext, osm = stopmsg, old_incompfunc = incompfunc;
+ LinkList l = newlinklist();
+ char **ret;
+
+ addlinknode(l, func->node.nam);
+ if (arg1)
+ addlinknode(l, arg1);
+ addlinknode(l, orig);
+ sfcontext = SFC_SUBST;
+ incompfunc = 0;
+
+ if (doshfunc(func, l, 1))
+ ret = NULL;
+ else
+ ret = getaparam("reply");
+
+ sfcontext = osc;
+ stopmsg = osm;
+ incompfunc = old_incompfunc;
+ return ret;
+}
+
+/**
+ * Front end to subst_string_by_func to use hook-like logic.
+ * name can refer to a function, and name + "_hook" can refer
+ * to an array containing a list of functions. The functions
+ * are tried in order until one returns success.
+ */
+/**/
+char **
+subst_string_by_hook(char *name, char *arg1, char *orig)
+{
+ Shfunc func;
+ char **ret = NULL;
+
+ if ((func = getshfunc(name))) {
+ ret = subst_string_by_func(func, arg1, orig);
+ }
+
+ if (!ret) {
+ char **arrptr;
+ int namlen = strlen(name);
+ VARARR(char, arrnam, namlen + HOOK_SUFFIX_LEN);
+ memcpy(arrnam, name, namlen);
+ memcpy(arrnam + namlen, HOOK_SUFFIX, HOOK_SUFFIX_LEN);
+
+ if ((arrptr = getaparam(arrnam))) {
+ /* Guard against internal modification of the array */
+ arrptr = arrdup(arrptr);
+ for (; *arrptr; arrptr++) {
+ if ((func = getshfunc(*arrptr))) {
+ ret = subst_string_by_func(func, arg1, orig);
+ if (ret)
+ break;
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+/**/
+mod_export char **
+mkarray(char *s)
+{
+ char **t = (char **) zalloc((s) ? (2 * sizeof s) : (sizeof s));
+
+ if ((*t = s))
+ t[1] = NULL;
+ return t;
+}
+
+/**/
+mod_export char **
+hmkarray(char *s)
+{
+ char **t = (char **) zhalloc((s) ? (2 * sizeof s) : (sizeof s));
+
+ if ((*t = s))
+ t[1] = NULL;
+ return t;
+}
+
+/**/
+mod_export void
+zbeep(void)
+{
+ char *vb;
+ queue_signals();
+ if ((vb = getsparam_u("ZBEEP"))) {
+ int len;
+ vb = getkeystring(vb, &len, GETKEYS_BINDKEY, NULL);
+ write_loop(SHTTY, vb, len);
+ } else if (isset(BEEP))
+ write_loop(SHTTY, "\07", 1);
+ unqueue_signals();
+}
+
+/**/
+mod_export void
+freearray(char **s)
+{
+ char **t = s;
+
+ DPUTS(!s, "freearray() with zero argument");
+
+ 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;
+}
+
+
+/* the ztypes table */
+
+/**/
+mod_export short int typtab[256];
+static int typtab_flags = 0;
+
+/* initialize the ztypes table */
+
+/**/
+void
+inittyptab(void)
+{
+ int t0;
+ char *s;
+
+ if (!(typtab_flags & ZTF_INIT)) {
+ typtab_flags = ZTF_INIT;
+ if (interact && isset(SHINSTDIN))
+ typtab_flags |= ZTF_INTERACT;
+ }
+
+ queue_signals();
+
+ memset(typtab, 0, sizeof(typtab));
+ 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;
+#ifndef MULTIBYTE_SUPPORT
+ /*
+ * This really doesn't seem to me the right thing to do when
+ * we have multibyte character support... it was a hack to assume
+ * eight bit characters `worked' for some values of work before
+ * we could test for them properly. I'm not 100% convinced
+ * having IIDENT here is a good idea at all, but this code
+ * should disappear into history...
+ */
+ for (t0 = 0240; t0 != 0400; t0++)
+ typtab[t0] = IALPHA | IALNUM | IIDENT | IUSER | IWORD;
+#endif
+ /* typtab['.'] |= IIDENT; */ /* Allow '.' in variable names - broken */
+ typtab['_'] = IIDENT | IUSER;
+ typtab['-'] = typtab['.'] = typtab[STOUC(Dash)] = 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(LAST_NORMAL_TOK); t0++)
+ typtab[t0] |= ITOK | IMETA;
+ for (t0 = (int)STOUC(Snull); t0 <= (int)STOUC(Nularg); t0++)
+ typtab[t0] |= ITOK | IMETA | INULL;
+ for (s = ifs ? ifs : EMULATION(EMULATE_KSH|EMULATE_SH) ?
+ DEFAULT_IFS_SH : DEFAULT_IFS; *s; s++) {
+ int c = STOUC(*s == Meta ? *++s ^ 32 : *s);
+#ifdef MULTIBYTE_SUPPORT
+ if (!isascii(c)) {
+ /* see comment for wordchars below */
+ continue;
+ }
+#endif
+ if (inblank(c)) {
+ if (s[1] == c)
+ s++;
+ else
+ typtab[c] |= IWSEP;
+ }
+ typtab[c] |= ISEP;
+ }
+ for (s = wordchars ? wordchars : DEFAULT_WORDCHARS; *s; s++) {
+ int c = STOUC(*s == Meta ? *++s ^ 32 : *s);
+#ifdef MULTIBYTE_SUPPORT
+ if (!isascii(c)) {
+ /*
+ * If we have support for multibyte characters, we don't
+ * handle non-ASCII characters here; instead, we turn
+ * wordchars into a wide character array.
+ * (We may actually have a single-byte 8-bit character set,
+ * but it works the same way.)
+ */
+ continue;
+ }
+#endif
+ typtab[c] |= IWORD;
+ }
+#ifdef MULTIBYTE_SUPPORT
+ set_widearray(wordchars, &wordchars_wide);
+ set_widearray(ifs ? ifs : EMULATION(EMULATE_KSH|EMULATE_SH) ?
+ DEFAULT_IFS_SH : DEFAULT_IFS, &ifs_wide);
+#endif
+ for (s = SPECCHARS; *s; s++)
+ typtab[STOUC(*s)] |= ISPECIAL;
+ if (typtab_flags & ZTF_SP_COMMA)
+ typtab[STOUC(',')] |= ISPECIAL;
+ if (isset(BANGHIST) && bangchar && (typtab_flags & ZTF_INTERACT)) {
+ typtab_flags |= ZTF_BANGCHAR;
+ typtab[bangchar] |= ISPECIAL;
+ } else
+ typtab_flags &= ~ZTF_BANGCHAR;
+ for (s = PATCHARS; *s; s++)
+ typtab[STOUC(*s)] |= IPATTERN;
+
+ unqueue_signals();
+}
+
+/**/
+mod_export void
+makecommaspecial(int yesno)
+{
+ if (yesno != 0) {
+ typtab_flags |= ZTF_SP_COMMA;
+ typtab[STOUC(',')] |= ISPECIAL;
+ } else {
+ typtab_flags &= ~ZTF_SP_COMMA;
+ typtab[STOUC(',')] &= ~ISPECIAL;
+ }
+}
+
+/**/
+mod_export void
+makebangspecial(int yesno)
+{
+ /* Name and call signature for congruence with makecommaspecial(),
+ * but in this case when yesno is nonzero we defer to the state
+ * saved by inittyptab().
+ */
+ if (yesno == 0) {
+ typtab[bangchar] &= ~ISPECIAL;
+ } else if (typtab_flags & ZTF_BANGCHAR) {
+ typtab[bangchar] |= ISPECIAL;
+ }
+}
+
+
+/**/
+#ifdef MULTIBYTE_SUPPORT
+/* A wide-character version of the iblank() macro. */
+/**/
+mod_export int
+wcsiblank(wint_t wc)
+{
+ if (iswspace(wc) && wc != L'\n')
+ return 1;
+ return 0;
+}
+
+/*
+ * zistype macro extended to support wide characters.
+ * Works for IIDENT, IWORD, IALNUM, ISEP.
+ * We don't need this for IWSEP because that only applies to
+ * a fixed set of ASCII characters.
+ * Note here that use of multibyte mode is not tested:
+ * that's because for ZLE this is unconditional,
+ * not dependent on the option. The caller must decide.
+ */
+
+/**/
+mod_export int
+wcsitype(wchar_t c, int itype)
+{
+ int len;
+ mbstate_t mbs;
+ VARARR(char, outstr, MB_CUR_MAX);
+
+ if (!isset(MULTIBYTE))
+ return zistype(c, itype);
+
+ /*
+ * Strategy: the shell requires that the multibyte representation
+ * be an extension of ASCII. So see if converting the character
+ * produces an ASCII character. If it does, use zistype on that.
+ * If it doesn't, use iswalnum on the original character.
+ * If that fails, resort to the appropriate wide character array.
+ */
+ memset(&mbs, 0, sizeof(mbs));
+ len = wcrtomb(outstr, c, &mbs);
+
+ if (len == 0) {
+ /* NULL is special */
+ return zistype(0, itype);
+ } else if (len == 1 && isascii(outstr[0])) {
+ return zistype(outstr[0], itype);
+ } else {
+ switch (itype) {
+ case IIDENT:
+ if (!isset(POSIXIDENTIFIERS))
+ return 0;
+ return iswalnum(c);
+
+ case IWORD:
+ if (iswalnum(c))
+ return 1;
+ /*
+ * If we are handling combining characters, any punctuation
+ * characters with zero width needs to be considered part of
+ * a word. If we are not handling combining characters then
+ * logically they are still part of the word, even if they
+ * don't get displayed properly, so always do this.
+ */
+ if (IS_COMBINING(c))
+ return 1;
+ return !!wmemchr(wordchars_wide.chars, c, wordchars_wide.len);
+
+ case ISEP:
+ return !!wmemchr(ifs_wide.chars, c, ifs_wide.len);
+
+ default:
+ return iswalnum(c);
+ }
+ }
+}
+
+/**/
+#endif
+
+
+/*
+ * Find the end of a set of characters in the set specified by itype;
+ * one of IALNUM, IIDENT, IWORD or IUSER. For non-ASCII characters, we assume
+ * alphanumerics are part of the set, with the exception that
+ * identifiers are not treated that way if POSIXIDENTIFIERS is set.
+ *
+ * See notes above for identifiers.
+ * Returns the same pointer as passed if not on an identifier character.
+ * If "once" is set, just test the first character, i.e. (outptr !=
+ * inptr) tests whether the first character is valid in an identifier.
+ *
+ * Currently this is only called with itype IIDENT, IUSER or ISEP.
+ */
+
+/**/
+mod_export char *
+itype_end(const char *ptr, int itype, int once)
+{
+#ifdef MULTIBYTE_SUPPORT
+ if (isset(MULTIBYTE) &&
+ (itype != IIDENT || !isset(POSIXIDENTIFIERS))) {
+ mb_charinit();
+ while (*ptr) {
+ int len;
+ if (itok(*ptr)) {
+ /* Not untokenised yet --- can happen in raw command line */
+ len = 1;
+ if (!zistype(*ptr,itype))
+ break;
+ } else {
+ wint_t wc;
+ len = mb_metacharlenconv(ptr, &wc);
+
+ if (!len)
+ break;
+
+ if (wc == WEOF) {
+ /* invalid, treat as single character */
+ int chr = STOUC(*ptr == Meta ? ptr[1] ^ 32 : *ptr);
+ /* in this case non-ASCII characters can't match */
+ if (chr > 127 || !zistype(chr,itype))
+ break;
+ } else if (len == 1 && isascii(*ptr)) {
+ /* ASCII: can't be metafied, use standard test */
+ if (!zistype(*ptr,itype))
+ break;
+ } else {
+ /*
+ * Valid non-ASCII character.
+ */
+ switch (itype) {
+ case IWORD:
+ if (!iswalnum(wc) &&
+ !wmemchr(wordchars_wide.chars, wc,
+ wordchars_wide.len))
+ return (char *)ptr;
+ break;
+
+ case ISEP:
+ if (!wmemchr(ifs_wide.chars, wc, ifs_wide.len))
+ return (char *)ptr;
+ break;
+
+ default:
+ if (!iswalnum(wc))
+ return (char *)ptr;
+ }
+ }
+ }
+ ptr += len;
+
+ if (once)
+ break;
+ }
+ } else
+#endif
+ for (;;) {
+ int chr = STOUC(*ptr == Meta ? ptr[1] ^ 32 : *ptr);
+ if (!zistype(chr,itype))
+ break;
+ ptr += (*ptr == Meta) ? 2 : 1;
+
+ if (once)
+ break;
+ }
+
+ /*
+ * Nasty. The first argument is const char * because we
+ * don't modify it here. However, we really want to pass
+ * back the same type as was passed down, to allow idioms like
+ * p = itype_end(p, IIDENT, 0);
+ * So returning a const char * isn't really the right thing to do.
+ * Without having two different functions the following seems
+ * to be the best we can do.
+ */
+ return (char *)ptr;
+}
+
+/**/
+mod_export char **
+arrdup(char **s)
+{
+ char **x, **y;
+
+ y = x = (char **) zhalloc(sizeof(char *) * (arrlen(s) + 1));
+
+ while ((*x++ = dupstring(*s++)));
+
+ return y;
+}
+
+/* Duplicate at most max elements of the array s with heap memory */
+
+/**/
+mod_export char **
+arrdup_max(char **s, unsigned max)
+{
+ char **x, **y, **send;
+ int len = 0;
+
+ if (max)
+ len = arrlen(s);
+
+ /* Limit has sense only if not equal to len */
+ if (max > len)
+ max = len;
+
+ y = x = (char **) zhalloc(sizeof(char *) * (max + 1));
+
+ send = s + max;
+ while (s < send)
+ *x++ = dupstring(*s++);
+ *x = NULL;
+
+ return y;
+}
+
+/**/
+mod_export char **
+zarrdup(char **s)
+{
+ char **x, **y;
+
+ y = x = (char **) zalloc(sizeof(char *) * (arrlen(s) + 1));
+
+ while ((*x++ = ztrdup(*s++)));
+
+ return y;
+}
+
+/**/
+#ifdef MULTIBYTE_SUPPORT
+/**/
+mod_export wchar_t **
+wcs_zarrdup(wchar_t **s)
+{
+ wchar_t **x, **y;
+
+ y = x = (wchar_t **) zalloc(sizeof(wchar_t *) * (arrlen((char **)s) + 1));
+
+ while ((*x++ = wcs_ztrdup(*s++)));
+
+ return y;
+}
+/**/
+#endif /* MULTIBYTE_SUPPORT */
+
+/**/
+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 = oldname;
+ int bestdist = 0, thisdist, thresh, maxthresh = 0;
+
+ /* This loop corrects each directory component of the path, stopping *
+ * when any correction distance would exceed the distance threshold. *
+ * NULL is returned only if the first component cannot be corrected; *
+ * otherwise a copy of oldname with a corrected prefix is returned. *
+ * Rationale for this, if there ever was any, has been forgotten. */
+ for (;;) {
+ while (*old == '/') {
+ if (new >= newname + sizeof(newname) - 1)
+ return NULL;
+ *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';
+ /* Every component is allowed a single distance 2 correction or two *
+ * distance 1 corrections. Longer ones get additional corrections. */
+ thresh = (int)(p - spnameguess) / 4 + 1;
+ if (thresh < 3)
+ thresh = 3;
+ else if (thresh > 100)
+ thresh = 100;
+ thisdist = mindist(newname, spnameguess, spnamebest, *old == '/');
+ if (thisdist >= thresh) {
+ /* The next test is always true, except for the first path *
+ * component. We could initialize bestdist to some large *
+ * constant instead, and then compare to that constant here, *
+ * because an invariant is that we've never exceeded the *
+ * threshold for any component so far; but I think that looks *
+ * odd to the human reader, and we may make use of the total *
+ * distance for all corrections at some point in the future. */
+ if (bestdist < maxthresh) {
+ struncpy(&new, spnameguess, sizeof(newname) - (new - newname));
+ struncpy(&new, old, sizeof(newname) - (new - newname));
+ return (new >= newname + sizeof(newname) -1) ? NULL : newname;
+ } else
+ return NULL;
+ } else {
+ maxthresh = bestdist + thresh;
+ bestdist += thisdist;
+ }
+ for (p = spnamebest; (*new = *p++);) {
+ if (new >= newname + sizeof(newname) - 1)
+ return NULL;
+ new++;
+ }
+ }
+}
+
+/**/
+static int
+mindist(char *dir, char *mindistguess, char *mindistbest, int wantdir)
+{
+ int mindistd, nd;
+ DIR *dd;
+ char *fn;
+ char *buf;
+ struct stat st;
+ size_t dirlen;
+
+ if (dir[0] == '\0')
+ dir = ".";
+ mindistd = 100;
+
+ if (!(buf = zalloc((dirlen = strlen(dir)) + strlen(mindistguess) + 2)))
+ return 0;
+ sprintf(buf, "%s/%s", dir, mindistguess);
+
+ if (stat(unmeta(buf), &st) == 0 && (!wantdir || S_ISDIR(st.st_mode))) {
+ strcpy(mindistbest, mindistguess);
+ free(buf);
+ return 0;
+ }
+
+ if ((dd = opendir(unmeta(dir)))) {
+ while ((fn = zreaddir(dd, 0))) {
+ if (spnamepat && pattry(spnamepat, fn))
+ continue;
+ nd = spdist(fn, mindistguess,
+ (int)strlen(mindistguess) / 4 + 1);
+ if (nd <= mindistd) {
+ if (wantdir) {
+ if (!(buf = zrealloc(buf, dirlen + strlen(fn) + 2)))
+ continue;
+ sprintf(buf, "%s/%s", dir, fn);
+ if (stat(unmeta(buf), &st) != 0 || !S_ISDIR(st.st_mode))
+ continue;
+ }
+ strcpy(mindistbest, fn);
+ mindistd = nd;
+ if (mindistd == 0)
+ break;
+ }
+ }
+ closedir(dd);
+ }
+ free(buf);
+ return mindistd;
+}
+
+/**/
+static int
+spdist(char *s, char *t, int thresh)
+{
+ /* TODO: Correction for non-ASCII and multibyte-input keyboards. */
+ char *p, *q;
+ const char qwertykeymap[] =
+ "\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";
+ const char dvorakkeymap[] =
+ "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\
+\t1234567890[]\t\
+\t',.pyfgcrl/=\t\
+\taoeuidhtns-\n\t\
+\t;qjkxbmwvz\t\t\t\
+\n\n\n\n\n\n\n\n\n\n\n\n\n\n\
+\t!@#$%^&*(){}\t\
+\t\"<>PYFGCRL?+\t\
+\tAOEUIDHTNS_\n\t\
+\t:QJKXBMWVZ\n\n\t\
+\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
+ const char *keymap;
+ if ( isset( DVORAK ) )
+ keymap = dvorakkeymap;
+ else
+ keymap = qwertykeymap;
+
+ 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 */
+
+/**/
+mod_export void
+attachtty(pid_t pgrp)
+{
+ static int ep = 0;
+
+ if (jobbing && interact) {
+#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)
+ {
+ zwarn("can't set tty pgrp: %e", errno);
+ fflush(stderr);
+ }
+ opts[MONITOR] = 0;
+ ep = 1;
+ }
+ }
+ }
+}
+
+/* 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;
+}
+
+
+/* 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 parameter 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 *
+ * string 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 */
+
+/**/
+mod_export 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(zhalloc(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 invalid 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;
+}
+
+
+/*
+ * Duplicate a string, metafying it as we go.
+ *
+ * Typically, this is used only for strings imported from outside
+ * zsh, as strings internally are either already metafied or passed
+ * around with an associated length.
+ */
+/**/
+mod_export char *
+ztrdup_metafy(const char *s)
+{
+ /* To mimic ztrdup() behaviour */
+ if (!s)
+ return NULL;
+ /*
+ * metafy() does lots of different things, so the pointer
+ * isn't const. Using it with META_DUP should be safe.
+ */
+ return metafy((char *)s, -1, META_DUP);
+}
+
+
+/*
+ * Take a null-terminated, metafied string in s into a literal
+ * representation by converting in place. The length is in *len
+ * len is non-NULL; if len is NULL, you don't know the length of
+ * the final string, but if it's to be supplied to some system
+ * routine that always uses NULL termination, such as a filename
+ * interpreter, that doesn't matter. Note the NULL termination
+ * is always copied for purposes of that kind.
+ */
+
+/**/
+mod_export char *
+unmetafy(char *s, int *len)
+{
+ char *p, *t;
+
+ for (p = s; *p && *p != Meta; p++);
+ for (t = p; (*t = *p++);)
+ if (*t++ == Meta && *p)
+ t[-1] = *p++ ^ 32;
+ if (len)
+ *len = t - s;
+ return s;
+}
+
+/* Return the character length of a metafied substring, given the *
+ * unmetafied substring length. */
+
+/**/
+mod_export 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, sized to fit. If there is no Meta character
+ * the original string is returned.
+ */
+
+/**/
+mod_export char *
+unmeta(const char *file_name)
+{
+ static char *fn;
+ static int sz;
+ char *p;
+ const char *t;
+ int newsz, meta;
+
+ if (!file_name)
+ return NULL;
+
+ meta = 0;
+ for (t = file_name; *t; t++) {
+ if (*t == Meta)
+ meta = 1;
+ }
+ if (!meta) {
+ /*
+ * don't need allocation... free if it's long, see below
+ */
+ if (sz > 4 * PATH_MAX) {
+ zfree(fn, sz);
+ fn = NULL;
+ sz = 0;
+ }
+ return (char *) file_name;
+ }
+
+ newsz = (t - file_name) + 1;
+ /*
+ * Optimisation: don't resize if we don't have to.
+ * We need a new allocation if
+ * - nothing was allocated before
+ * - the new string is larger than the old one
+ * - the old string was larger than an arbitrary limit but the
+ * new string isn't so that we free up significant space by resizing.
+ */
+ if (!fn || newsz > sz || (sz > 4 * PATH_MAX && newsz <= 4 * PATH_MAX))
+ {
+ if (fn)
+ zfree(fn, sz);
+ sz = newsz;
+ fn = (char *)zalloc(sz);
+ if (!fn) {
+ sz = 0;
+ /*
+ * will quite likely crash in the caller anyway...
+ */
+ return NULL;
+ }
+ }
+
+ for (t = file_name, p = fn; *t; p++)
+ if ((*p = *t++) == Meta && *t)
+ *p = *t++ ^ 32;
+ *p = '\0';
+ return fn;
+}
+
+/*
+ * Unmetafy just one character and store the number of bytes it occupied.
+ */
+/**/
+mod_export convchar_t
+unmeta_one(const char *in, int *sz)
+{
+ convchar_t wc;
+ int newsz;
+#ifdef MULTIBYTE_SUPPORT
+ mbstate_t wstate;
+#endif
+
+ if (!sz)
+ sz = &newsz;
+ *sz = 0;
+
+ if (!in || !*in)
+ return 0;
+
+#ifdef MULTIBYTE_SUPPORT
+ memset(&wstate, 0, sizeof(wstate));
+ *sz = mb_metacharlenconv_r(in, &wc, &wstate);
+#else
+ if (in[0] == Meta) {
+ *sz = 2;
+ wc = STOUC(in[1] ^ 32);
+ } else {
+ *sz = 1;
+ wc = STOUC(in[0]);
+ }
+#endif
+ return wc;
+}
+
+/*
+ * Unmetafy and compare two strings, comparing unsigned character values.
+ * "a\0" sorts after "a".
+ *
+ * Currently this is only used in hash table sorting, where the
+ * keys are names of hash nodes and where we don't use strcoll();
+ * it's not clear if that's right but it does guarantee the ordering
+ * of shell structures on output.
+ *
+ * As we don't use strcoll(), it seems overkill to convert multibyte
+ * characters to wide characters for comparison every time. In the case
+ * of UTF-8, Unicode ordering is preserved when sorted raw, and for
+ * other character sets we rely on an extension of ASCII so the result,
+ * while it may not be correct, is at least rational.
+ */
+
+/**/
+int
+ztrcmp(char const *s1, 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 the unmetafied length of a metafied string. */
+
+/**/
+mod_export 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");
+ break;
+ } else
+#endif
+ s++;
+ }
+ }
+ return l;
+}
+
+#ifndef MULTIBYTE_SUPPORT
+/*
+ * ztrlen() but with explicit end point for non-null-terminated
+ * segments. eptr may not be NULL.
+ */
+
+/**/
+mod_export int
+ztrlenend(char const *s, char const *eptr)
+{
+ int l;
+
+ for (l = 0; s < eptr; l++) {
+ if (*s++ == Meta) {
+#ifdef DEBUG
+ if (! *s) {
+ fprintf(stderr,
+ "BUG: unexpected end of string in ztrlenend()\n");
+ break;
+ } else
+#endif
+ s++;
+ }
+ }
+ return l;
+}
+
+#endif /* MULTIBYTE_SUPPORT */
+
+/* Subtract two pointers in a metafied string. */
+
+/**/
+mod_export 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;
+}
+
+/*
+ * Wrapper for readdir().
+ *
+ * If ignoredots is true, skip the "." and ".." entries.
+ *
+ * When __APPLE__ is defined, recode dirent names from UTF-8-MAC to UTF-8.
+ *
+ * Return the dirent's name, metafied.
+ */
+
+/**/
+mod_export char *
+zreaddir(DIR *dir, int ignoredots)
+{
+ struct dirent *de;
+#if defined(HAVE_ICONV) && defined(__APPLE__)
+ static iconv_t conv_ds = (iconv_t)0;
+ static char *conv_name = 0;
+ char *conv_name_ptr, *orig_name_ptr;
+ size_t conv_name_len, orig_name_len;
+#endif
+
+ 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])));
+
+#if defined(HAVE_ICONV) && defined(__APPLE__)
+ if (!conv_ds)
+ conv_ds = iconv_open("UTF-8", "UTF-8-MAC");
+ if (conv_ds != (iconv_t)(-1)) {
+ /* Force initial state in case re-using conv_ds */
+ (void) iconv(conv_ds, 0, &orig_name_len, 0, &conv_name_len);
+
+ orig_name_ptr = de->d_name;
+ orig_name_len = strlen(de->d_name);
+ conv_name = zrealloc(conv_name, orig_name_len+1);
+ conv_name_ptr = conv_name;
+ conv_name_len = orig_name_len;
+ if (iconv(conv_ds,
+ &orig_name_ptr, &orig_name_len,
+ &conv_name_ptr, &conv_name_len) != (size_t)(-1) &&
+ orig_name_len == 0) {
+ /* Completely converted, metafy and return */
+ *conv_name_ptr = '\0';
+ return metafy(conv_name, -1, META_STATIC);
+ }
+ /* Error, or conversion incomplete, keep the original name */
+ }
+#endif
+
+ return metafy(de->d_name, -1, META_STATIC);
+}
+
+/* Unmetafy and output a string. Tokens are skipped. */
+
+/**/
+mod_export 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;
+}
+
+#ifndef MULTIBYTE_SUPPORT
+/* Create a visibly-represented duplicate of a string. */
+
+/**/
+mod_export char *
+nicedup(char const *s, int heap)
+{
+ int c, len = strlen(s) * 5 + 1;
+ VARARR(char, buf, len);
+ char *p = buf, *n;
+
+ while ((c = *s++)) {
+ if (itok(c)) {
+ if (c <= Comma)
+ c = ztokens[c - Pound];
+ else
+ continue;
+ }
+ if (c == Meta)
+ c = *s++ ^ 32;
+ /* The result here is metafied */
+ n = nicechar(c);
+ while(*n)
+ *p++ = *n++;
+ }
+ *p = '\0';
+ return heap ? dupstring(buf) : ztrdup(buf);
+}
+#endif
+
+/**/
+mod_export char *
+nicedupstring(char const *s)
+{
+ return nicedup(s, 1);
+}
+
+
+#ifndef MULTIBYTE_SUPPORT
+/* Unmetafy and output a string, displaying special characters readably. */
+
+/**/
+mod_export 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(zputs(nicechar(c), stream) < 0)
+ return EOF;
+ }
+ return 0;
+}
+
+
+/* Return the length of the visible representation of a metafied string. */
+
+/**/
+mod_export 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(c));
+ }
+ return l;
+}
+#endif
+
+
+/**/
+#ifdef MULTIBYTE_SUPPORT
+/*
+ * Version of both nicezputs() and niceztrlen() for use with multibyte
+ * characters. Input is a metafied string; output is the screen width of
+ * the string.
+ *
+ * If the FILE * is not NULL, output to that, too.
+ *
+ * If outstrp is not NULL, set *outstrp to a zalloc'd version of
+ * the output (still metafied).
+ *
+ * If flags contains NICEFLAG_HEAP, use the heap for *outstrp, else
+ * zalloc.
+ * If flags contsins NICEFLAG_QUOTE, the output is going to be within
+ * $'...', so quote "'" and "\" with a backslash.
+ */
+
+/**/
+mod_export size_t
+mb_niceformat(const char *s, FILE *stream, char **outstrp, int flags)
+{
+ size_t l = 0, newl;
+ int umlen, outalloc, outleft, eol = 0;
+ wchar_t c;
+ char *ums, *ptr, *fmt, *outstr, *outptr;
+ mbstate_t mbs;
+
+ if (outstrp) {
+ outleft = outalloc = 5 * strlen(s);
+ outptr = outstr = zalloc(outalloc);
+ } else {
+ outleft = outalloc = 0;
+ outptr = outstr = NULL;
+ }
+
+ ums = ztrdup(s);
+ /*
+ * is this necessary at this point? niceztrlen does this
+ * but it's used in lots of places. however, one day this may
+ * be, too.
+ */
+ untokenize(ums);
+ ptr = unmetafy(ums, &umlen);
+
+ memset(&mbs, 0, sizeof mbs);
+ while (umlen > 0) {
+ size_t cnt = eol ? MB_INVALID : mbrtowc(&c, ptr, umlen, &mbs);
+
+ switch (cnt) {
+ case MB_INCOMPLETE:
+ eol = 1;
+ /* FALL THROUGH */
+ case MB_INVALID:
+ /* The byte didn't convert, so output it as a \M-... sequence. */
+ fmt = nicechar_sel(*ptr, flags & NICEFLAG_QUOTE);
+ newl = strlen(fmt);
+ cnt = 1;
+ /* Get mbs out of its undefined state. */
+ memset(&mbs, 0, sizeof mbs);
+ break;
+ case 0:
+ /* Careful: converting '\0' returns 0, but a '\0' is a
+ * real character for us, so we should consume 1 byte. */
+ cnt = 1;
+ /* FALL THROUGH */
+ default:
+ if (c == L'\'' && (flags & NICEFLAG_QUOTE)) {
+ fmt = "\\'";
+ newl = 2;
+ }
+ else if (c == L'\\' && (flags & NICEFLAG_QUOTE)) {
+ fmt = "\\\\";
+ newl = 2;
+ }
+ else
+ fmt = wcs_nicechar_sel(c, &newl, NULL, flags & NICEFLAG_QUOTE);
+ break;
+ }
+
+ umlen -= cnt;
+ ptr += cnt;
+ l += newl;
+
+ if (stream)
+ zputs(fmt, stream);
+ if (outstr) {
+ /* Append to output string */
+ int outlen = strlen(fmt);
+ if (outlen >= outleft) {
+ /* Reallocate to twice the length */
+ int outoffset = outptr - outstr;
+
+ outleft += outalloc;
+ outalloc *= 2;
+ outstr = zrealloc(outstr, outalloc);
+ outptr = outstr + outoffset;
+ }
+ memcpy(outptr, fmt, outlen);
+ /* Update start position */
+ outptr += outlen;
+ /* Update available bytes */
+ outleft -= outlen;
+ }
+ }
+
+ free(ums);
+ if (outstrp) {
+ *outptr = '\0';
+ /* Use more efficient storage for returned string */
+ if (flags & NICEFLAG_NODUP)
+ *outstrp = outstr;
+ else {
+ *outstrp = (flags & NICEFLAG_HEAP) ? dupstring(outstr) :
+ ztrdup(outstr);
+ free(outstr);
+ }
+ }
+
+ return l;
+}
+
+/*
+ * Return 1 if mb_niceformat() would reformat this string, else 0.
+ */
+
+/**/
+mod_export int
+is_mb_niceformat(const char *s)
+{
+ int umlen, eol = 0, ret = 0;
+ wchar_t c;
+ char *ums, *ptr;
+ mbstate_t mbs;
+
+ ums = ztrdup(s);
+ untokenize(ums);
+ ptr = unmetafy(ums, &umlen);
+
+ memset(&mbs, 0, sizeof mbs);
+ while (umlen > 0) {
+ size_t cnt = eol ? MB_INVALID : mbrtowc(&c, ptr, umlen, &mbs);
+
+ switch (cnt) {
+ case MB_INCOMPLETE:
+ eol = 1;
+ /* FALL THROUGH */
+ case MB_INVALID:
+ /* The byte didn't convert, so output it as a \M-... sequence. */
+ if (is_nicechar(*ptr)) {
+ ret = 1;
+ break;
+ }
+ cnt = 1;
+ /* Get mbs out of its undefined state. */
+ memset(&mbs, 0, sizeof mbs);
+ break;
+ case 0:
+ /* Careful: converting '\0' returns 0, but a '\0' is a
+ * real character for us, so we should consume 1 byte. */
+ cnt = 1;
+ /* FALL THROUGH */
+ default:
+ if (is_wcs_nicechar(c))
+ ret = 1;
+ break;
+ }
+
+ if (ret)
+ break;
+
+ umlen -= cnt;
+ ptr += cnt;
+ }
+
+ free(ums);
+
+ return ret;
+}
+
+/* ztrdup multibyte string with nice formatting */
+
+/**/
+mod_export char *
+nicedup(const char *s, int heap)
+{
+ char *retstr;
+
+ (void)mb_niceformat(s, NULL, &retstr, heap ? NICEFLAG_HEAP : 0);
+
+ return retstr;
+}
+
+
+/*
+ * The guts of mb_metacharlenconv(). This version assumes we are
+ * processing a true multibyte character string without tokens, and
+ * takes the shift state as an argument.
+ */
+
+/**/
+mod_export int
+mb_metacharlenconv_r(const char *s, wint_t *wcp, mbstate_t *mbsp)
+{
+ size_t ret = MB_INVALID;
+ char inchar;
+ const char *ptr;
+ wchar_t wc;
+
+ if (STOUC(*s) <= 0x7f) {
+ if (wcp)
+ *wcp = (wint_t)*s;
+ return 1;
+ }
+
+ for (ptr = s; *ptr; ) {
+ if (*ptr == Meta) {
+ inchar = *++ptr ^ 32;
+ DPUTS(!*ptr,
+ "BUG: unexpected end of string in mb_metacharlen()\n");
+ } else if (imeta(*ptr)) {
+ /*
+ * As this is metafied input, this is a token --- this
+ * can't be a part of the string. It might be
+ * something on the end of an unbracketed parameter
+ * reference, for example.
+ */
+ break;
+ } else
+ inchar = *ptr;
+ ptr++;
+ ret = mbrtowc(&wc, &inchar, 1, mbsp);
+
+ if (ret == MB_INVALID)
+ break;
+ if (ret == MB_INCOMPLETE)
+ continue;
+ if (wcp)
+ *wcp = wc;
+ return ptr - s;
+ }
+
+ if (wcp)
+ *wcp = WEOF;
+ /* No valid multibyte sequence */
+ memset(mbsp, 0, sizeof(*mbsp));
+ if (ptr > s) {
+ return 1 + (*s == Meta); /* Treat as single byte character */
+ } else
+ return 0; /* Probably shouldn't happen */
+}
+
+/*
+ * Length of metafied string s which contains the next multibyte
+ * character; single (possibly metafied) character if string is not null
+ * but character is not valid (e.g. possibly incomplete at end of string).
+ * Returned value is guaranteed not to reach beyond the end of the
+ * string (assuming correct metafication).
+ *
+ * If wcp is not NULL, the converted wide character is stored there.
+ * If no conversion could be done WEOF is used.
+ */
+
+/**/
+mod_export int
+mb_metacharlenconv(const char *s, wint_t *wcp)
+{
+ if (!isset(MULTIBYTE) || STOUC(*s) <= 0x7f) {
+ /* treat as single byte, possibly metafied */
+ if (wcp)
+ *wcp = (wint_t)(*s == Meta ? s[1] ^ 32 : *s);
+ return 1 + (*s == Meta);
+ }
+ /*
+ * We have to handle tokens here, since we may be looking
+ * through a tokenized input. Obviously this isn't
+ * a valid multibyte character, so just return WEOF
+ * and let the caller handle it as a single character.
+ *
+ * TODO: I've a sneaking suspicion we could do more here
+ * to prevent the caller always needing to handle invalid
+ * characters specially, but sometimes it may need to know.
+ */
+ if (itok(*s)) {
+ if (wcp)
+ *wcp = WEOF;
+ return 1;
+ }
+
+ return mb_metacharlenconv_r(s, wcp, &mb_shiftstate);
+}
+
+/*
+ * Total number of multibyte characters in metafied string s.
+ * Same answer as iterating mb_metacharlen() and counting calls
+ * until end of string.
+ *
+ * If width is 1, return total character width rather than number.
+ * If width is greater than 1, return 1 if character has non-zero width,
+ * else 0.
+ *
+ * Ends if either *ptr is '\0', the normal case (eptr may be NULL for
+ * this), or ptr is eptr (i.e. *eptr is where the null would be if null
+ * terminated) for strings not delimited by nulls --- note these are
+ * still metafied.
+ */
+
+/**/
+mod_export int
+mb_metastrlenend(char *ptr, int width, char *eptr)
+{
+ char inchar, *laststart;
+ size_t ret;
+ wchar_t wc;
+ int num, num_in_char, complete;
+
+ if (!isset(MULTIBYTE) || MB_CUR_MAX == 1)
+ return eptr ? (int)(eptr - ptr) : ztrlen(ptr);
+
+ laststart = ptr;
+ ret = MB_INVALID;
+ num = num_in_char = 0;
+ complete = 1;
+
+ memset(&mb_shiftstate, 0, sizeof(mb_shiftstate));
+ while (*ptr && !(eptr && ptr >= eptr)) {
+ if (*ptr == Meta)
+ inchar = *++ptr ^ 32;
+ else
+ inchar = *ptr;
+ ptr++;
+
+ if (complete && STOUC(inchar) <= STOUC(0x7f)) {
+ /*
+ * We rely on 7-bit US-ASCII as a subset, so skip
+ * multibyte handling if we have such a character.
+ */
+ num++;
+ laststart = ptr;
+ num_in_char = 0;
+ continue;
+ }
+
+ ret = mbrtowc(&wc, &inchar, 1, &mb_shiftstate);
+
+ if (ret == MB_INCOMPLETE) {
+ /*
+ * "num_in_char" is only used for incomplete characters.
+ * The assumption is that we will output all trailing octets
+ * that form part of an incomplete character as a single
+ * character (of single width) if we don't get a complete
+ * character. This is purely pragmatic --- I'm not aware
+ * of a standard way of dealing with incomplete characters.
+ *
+ * If we do get a complete character, num_in_char
+ * becomes irrelevant and is set to zero
+ *
+ * This is in contrast to "num" which counts the characters
+ * or widths in complete characters. The two are summed,
+ * so we don't count characters twice.
+ */
+ num_in_char++;
+ complete = 0;
+ } else {
+ if (ret == MB_INVALID) {
+ /* Reset, treat as single character */
+ memset(&mb_shiftstate, 0, sizeof(mb_shiftstate));
+ ptr = laststart + (*laststart == Meta) + 1;
+ num++;
+ } else if (width) {
+ /*
+ * Returns -1 if not a printable character. We
+ * turn this into 0.
+ */
+ int wcw = WCWIDTH(wc);
+ if (wcw > 0) {
+ if (width == 1)
+ num += wcw;
+ else
+ num++;
+ }
+ } else
+ num++;
+ laststart = ptr;
+ num_in_char = 0;
+ complete = 1;
+ }
+ }
+
+ /* If incomplete, treat remainder as trailing single character */
+ return num + (num_in_char ? 1 : 0);
+}
+
+/*
+ * The equivalent of mb_metacharlenconv_r() for
+ * strings that aren't metafied and hence have
+ * explicit lengths.
+ */
+
+/**/
+mod_export int
+mb_charlenconv_r(const char *s, int slen, wint_t *wcp, mbstate_t *mbsp)
+{
+ size_t ret = MB_INVALID;
+ char inchar;
+ const char *ptr;
+ wchar_t wc;
+
+ if (slen && STOUC(*s) <= 0x7f) {
+ if (wcp)
+ *wcp = (wint_t)*s;
+ return 1;
+ }
+
+ for (ptr = s; slen; ) {
+ inchar = *ptr;
+ ptr++;
+ slen--;
+ ret = mbrtowc(&wc, &inchar, 1, mbsp);
+
+ if (ret == MB_INVALID)
+ break;
+ if (ret == MB_INCOMPLETE)
+ continue;
+ if (wcp)
+ *wcp = wc;
+ return ptr - s;
+ }
+
+ if (wcp)
+ *wcp = WEOF;
+ /* No valid multibyte sequence */
+ memset(mbsp, 0, sizeof(*mbsp));
+ if (ptr > s) {
+ return 1; /* Treat as single byte character */
+ } else
+ return 0; /* Probably shouldn't happen */
+}
+
+/*
+ * The equivalent of mb_metacharlenconv() for
+ * strings that aren't metafied and hence have
+ * explicit lengths;
+ */
+
+/**/
+mod_export int
+mb_charlenconv(const char *s, int slen, wint_t *wcp)
+{
+ if (!isset(MULTIBYTE) || STOUC(*s) <= 0x7f) {
+ if (wcp)
+ *wcp = (wint_t)*s;
+ return 1;
+ }
+
+ return mb_charlenconv_r(s, slen, wcp, &mb_shiftstate);
+}
+
+/**/
+#else
+
+/* Simple replacement for mb_metacharlenconv */
+
+/**/
+mod_export int
+metacharlenconv(const char *x, int *c)
+{
+ /*
+ * Here we don't use STOUC() on the chars since they
+ * may be compared against other chars and this will fail
+ * if chars are signed and the high bit is set.
+ */
+ if (*x == Meta) {
+ if (c)
+ *c = x[1] ^ 32;
+ return 2;
+ }
+ if (c)
+ *c = (char)*x;
+ return 1;
+}
+
+/* Simple replacement for mb_charlenconv */
+
+/**/
+mod_export int
+charlenconv(const char *x, int len, int *c)
+{
+ if (!len) {
+ if (c)
+ *c = '\0';
+ return 0;
+ }
+
+ if (c)
+ *c = (char)*x;
+ return 1;
+}
+
+/**/
+#endif /* MULTIBYTE_SUPPORT */
+
+/*
+ * Expand tabs to given width, with given starting position on line.
+ * len is length of unmetafied string in bytes.
+ * Output to fout.
+ * Return the end position on the line, i.e. if this is 0 modulo width
+ * the next character is aligned with a tab stop.
+ *
+ * If all is set, all tabs are expanded, else only leading tabs.
+ */
+
+/**/
+mod_export int
+zexpandtabs(const char *s, int len, int width, int startpos, FILE *fout,
+ int all)
+{
+ int at_start = 1;
+
+#ifdef MULTIBYTE_SUPPORT
+ mbstate_t mbs;
+ size_t ret;
+ wchar_t wc;
+
+ memset(&mbs, 0, sizeof(mbs));
+#endif
+
+ while (len) {
+ if (*s == '\t') {
+ if (all || at_start) {
+ s++;
+ len--;
+ if (width <= 0 || !(startpos % width)) {
+ /* always output at least one space */
+ fputc(' ', fout);
+ startpos++;
+ }
+ if (width <= 0)
+ continue; /* paranoia */
+ while (startpos % width) {
+ fputc(' ', fout);
+ startpos++;
+ }
+ } else {
+ /*
+ * Leave tab alone.
+ * Guess width to apply... we might get this wrong.
+ * This is only needed if there's a following string
+ * that needs tabs expanding, which is unusual.
+ */
+ startpos += width - startpos % width;
+ s++;
+ len--;
+ fputc('\t', fout);
+ }
+ continue;
+ } else if (*s == '\n' || *s == '\r') {
+ fputc(*s, fout);
+ s++;
+ len--;
+ startpos = 0;
+ at_start = 1;
+ continue;
+ }
+
+ at_start = 0;
+#ifdef MULTIBYTE_SUPPORT
+ if (isset(MULTIBYTE)) {
+ const char *sstart = s;
+ ret = mbrtowc(&wc, s, len, &mbs);
+ if (ret == MB_INVALID) {
+ /* Assume single character per character */
+ memset(&mbs, 0, sizeof(mbs));
+ s++;
+ len--;
+ } else if (ret == MB_INCOMPLETE) {
+ /* incomplete at end --- assume likewise, best we've got */
+ s++;
+ len--;
+ } else {
+ s += ret;
+ len -= (int)ret;
+ }
+ if (ret == MB_INVALID || ret == MB_INCOMPLETE) {
+ startpos++;
+ } else {
+ int wcw = WCWIDTH(wc);
+ if (wcw > 0) /* paranoia */
+ startpos += wcw;
+ }
+ fwrite(sstart, s - sstart, 1, fout);
+
+ continue;
+ }
+#endif /* MULTIBYTE_SUPPORT */
+ fputc(*s, fout);
+ s++;
+ len--;
+ startpos++;
+ }
+
+ return startpos;
+}
+
+/* check for special characters in the string */
+
+/**/
+mod_export int
+hasspecial(char const *s)
+{
+ for (; *s; s++) {
+ if (ispecial(*s == Meta ? *++s ^ 32 : *s))
+ return 1;
+ }
+ return 0;
+}
+
+
+static char *
+addunprintable(char *v, const char *u, const char *uend)
+{
+ for (; u < uend; u++) {
+ /*
+ * Just do this byte by byte; there's no great
+ * advantage in being clever with multibyte
+ * characters if we don't think they're printable.
+ */
+ int c;
+ if (*u == Meta)
+ c = STOUC(*++u ^ 32);
+ else
+ c = STOUC(*u);
+ switch (c) {
+ case '\0':
+ *v++ = '\\';
+ *v++ = '0';
+ if ('0' <= u[1] && u[1] <= '7') {
+ *v++ = '0';
+ *v++ = '0';
+ }
+ break;
+
+ case '\007': *v++ = '\\'; *v++ = 'a'; break;
+ case '\b': *v++ = '\\'; *v++ = 'b'; break;
+ case '\f': *v++ = '\\'; *v++ = 'f'; break;
+ case '\n': *v++ = '\\'; *v++ = 'n'; break;
+ case '\r': *v++ = '\\'; *v++ = 'r'; break;
+ case '\t': *v++ = '\\'; *v++ = 't'; break;
+ case '\v': *v++ = '\\'; *v++ = 'v'; break;
+
+ default:
+ *v++ = '\\';
+ *v++ = '0' + ((c >> 6) & 7);
+ *v++ = '0' + ((c >> 3) & 7);
+ *v++ = '0' + (c & 7);
+ break;
+ }
+ }
+
+ return v;
+}
+
+/*
+ * Quote the string s and return the result as a string from the heap.
+ *
+ * The last argument is a QT_ value defined in zsh.h other than QT_NONE.
+ *
+ * Most quote styles other than backslash assume the quotes are to
+ * be added outside quotestring(). QT_SINGLE_OPTIONAL is different:
+ * the single quotes are only added where necessary, so the
+ * whole expression is handled here.
+ *
+ * The string may be metafied and contain tokens.
+ */
+
+/**/
+mod_export char *
+quotestring(const char *s, int instring)
+{
+ const char *u;
+ char *v;
+ int alloclen;
+ char *buf;
+ int shownull = 0;
+ /*
+ * quotesub is used with QT_SINGLE_OPTIONAL.
+ * quotesub = 0: mechanism not active
+ * quotesub = 1: mechanism pending, no "'" yet;
+ * needs adding at quotestart.
+ * quotesub = 2: mechanism active, added opening "'"; need
+ * closing "'".
+ */
+ int quotesub = 0, slen;
+ char *quotestart;
+ convchar_t cc;
+ const char *uend;
+
+ slen = strlen(s);
+ switch (instring)
+ {
+ case QT_BACKSLASH_SHOWNULL:
+ shownull = 1;
+ instring = QT_BACKSLASH;
+ /*FALLTHROUGH*/
+ case QT_BACKSLASH:
+ /*
+ * With QT_BACKSLASH we may need to use $'\300' stuff.
+ * Keep memory usage within limits by allocating temporary
+ * storage and using heap for correct size at end.
+ */
+ alloclen = slen * 7 + 1;
+ break;
+
+ case QT_BACKSLASH_PATTERN:
+ alloclen = slen * 2 + 1;
+ break;
+
+ case QT_SINGLE_OPTIONAL:
+ /*
+ * Here, we may need to add single quotes.
+ * Always show empty strings.
+ */
+ alloclen = slen * 4 + 3;
+ quotesub = shownull = 1;
+ break;
+
+ default:
+ alloclen = slen * 4 + 1;
+ break;
+ }
+ if (!*s && shownull)
+ alloclen += 2; /* for '' */
+
+ quotestart = v = buf = zshcalloc(alloclen);
+
+ DPUTS(instring < QT_BACKSLASH || instring == QT_BACKTICK ||
+ instring > QT_BACKSLASH_PATTERN,
+ "BUG: bad quote type in quotestring");
+ u = s;
+ if (instring == QT_DOLLARS) {
+ /*
+ * The only way to get Nularg here is when
+ * it is placeholding for the empty string?
+ */
+ if (inull(*u))
+ u++;
+ /*
+ * As we test for printability here we need to be able
+ * to look for multibyte characters.
+ */
+ MB_METACHARINIT();
+ while (*u) {
+ uend = u + MB_METACHARLENCONV(u, &cc);
+
+ if (
+#ifdef MULTIBYTE_SUPPORT
+ cc != WEOF &&
+#endif
+ WC_ISPRINT(cc)) {
+ switch (cc) {
+ case ZWC('\\'):
+ case ZWC('\''):
+ *v++ = '\\';
+ break;
+
+ default:
+ if (isset(BANGHIST) && cc == (wchar_t)bangchar)
+ *v++ = '\\';
+ break;
+ }
+ while (u < uend)
+ *v++ = *u++;
+ } else {
+ /* Not printable */
+ v = addunprintable(v, u, uend);
+ u = uend;
+ }
+ }
+ } else if (instring == QT_BACKSLASH_PATTERN) {
+ while (*u) {
+ if (ipattern(*u))
+ *v++ = '\\';
+ *v++ = *u++;
+ }
+ } else {
+ if (shownull) {
+ /* We can't show an empty string with just backslash quoting. */
+ if (!*u) {
+ *v++ = '\'';
+ *v++ = '\'';
+ }
+ }
+ /*
+ * Here there are syntactic special characters, so
+ * we start by going through bytewise.
+ */
+ while (*u) {
+ int dobackslash = 0;
+ if (*u == Tick || *u == Qtick) {
+ char c = *u++;
+
+ *v++ = c;
+ while (*u && *u != c)
+ *v++ = *u++;
+ *v++ = c;
+ if (*u)
+ u++;
+ continue;
+ } else if ((*u == Qstring || *u == '$') && u[1] == '\'' &&
+ instring == QT_DOUBLE) {
+ /*
+ * We don't need to quote $'...' inside a double-quoted
+ * string. This is largely cosmetic; it looks neater
+ * if we don't but it doesn't do any harm since the
+ * \ is stripped.
+ */
+ *v++ = *u++;
+ } else if ((*u == String || *u == Qstring) &&
+ (u[1] == Inpar || u[1] == Inbrack || u[1] == Inbrace)) {
+ char c = (u[1] == Inpar ? Outpar : (u[1] == Inbrace ?
+ Outbrace : Outbrack));
+ char beg = *u;
+ int level = 0;
+
+ *v++ = *u++;
+ *v++ = *u++;
+ while (*u && (*u != c || level)) {
+ if (*u == beg)
+ level++;
+ else if (*u == c)
+ level--;
+ *v++ = *u++;
+ }
+ if (*u)
+ *v++ = *u++;
+ continue;
+ }
+ else if (ispecial(*u) &&
+ ((*u != '=' && *u != '~') ||
+ u == s ||
+ (isset(MAGICEQUALSUBST) &&
+ (u[-1] == '=' || u[-1] == ':')) ||
+ (*u == '~' && isset(EXTENDEDGLOB))) &&
+ (instring == QT_BACKSLASH ||
+ instring == QT_SINGLE_OPTIONAL ||
+ (isset(BANGHIST) && *u == (char)bangchar &&
+ instring != QT_SINGLE) ||
+ (instring == QT_DOUBLE &&
+ (*u == '$' || *u == '`' || *u == '\"' || *u == '\\')) ||
+ (instring == QT_SINGLE && *u == '\''))) {
+ if (instring == QT_SINGLE_OPTIONAL) {
+ if (quotesub == 1) {
+ /*
+ * We haven't yet had to quote at the start.
+ */
+ if (*u == '\'') {
+ /*
+ * We don't need to.
+ */
+ *v++ = '\\';
+ } else {
+ /*
+ * It's now time to add quotes.
+ */
+ if (v > quotestart)
+ {
+ char *addq;
+
+ for (addq = v; addq > quotestart; addq--)
+ *addq = addq[-1];
+ }
+ *quotestart = '\'';
+ v++;
+ quotesub = 2;
+ }
+ *v++ = *u++;
+ /*
+ * Next place to start quotes is here.
+ */
+ quotestart = v;
+ } else if (*u == '\'') {
+ if (unset(RCQUOTES)) {
+ *v++ = '\'';
+ *v++ = '\\';
+ *v++ = '\'';
+ /* Don't restart quotes unless we need them */
+ quotesub = 1;
+ quotestart = v;
+ } else {
+ /* simplest just to use '' always */
+ *v++ = '\'';
+ *v++ = '\'';
+ }
+ /* dealt with */
+ u++;
+ } else {
+ /* else already quoting, just add */
+ *v++ = *u++;
+ }
+ continue;
+ } else if (*u == '\n' ||
+ (instring == QT_SINGLE && *u == '\'')) {
+ if (*u == '\n') {
+ *v++ = '$';
+ *v++ = '\'';
+ *v++ = '\\';
+ *v++ = 'n';
+ *v++ = '\'';
+ } else if (unset(RCQUOTES)) {
+ *v++ = '\'';
+ if (*u == '\'')
+ *v++ = '\\';
+ *v++ = *u;
+ *v++ = '\'';
+ } else
+ *v++ = '\'', *v++ = '\'';
+ u++;
+ continue;
+ } else {
+ /*
+ * We'll need a backslash, but don't add it
+ * yet since if the character isn't printable
+ * we'll have to upgrade it to $'...'.
+ */
+ dobackslash = 1;
+ }
+ }
+
+ if (itok(*u) || instring != QT_BACKSLASH) {
+ /* Needs to be passed straight through. */
+ if (dobackslash)
+ *v++ = '\\';
+ if (*u == Inparmath) {
+ /*
+ * Already syntactically quoted: don't
+ * add more.
+ */
+ int inmath = 1;
+ *v++ = *u++;
+ for (;;) {
+ char uc = *u;
+ *v++ = *u++;
+ if (uc == '\0')
+ break;
+ else if (uc == Outparmath && !--inmath)
+ break;
+ else if (uc == Inparmath)
+ ++inmath;
+ }
+ } else
+ *v++ = *u++;
+ continue;
+ }
+
+ /*
+ * Now check if the output is unprintable in the
+ * current character set.
+ */
+ uend = u + MB_METACHARLENCONV(u, &cc);
+ if (
+#ifdef MULTIBYTE_SUPPORT
+ cc != WEOF &&
+#endif
+ WC_ISPRINT(cc)) {
+ if (dobackslash)
+ *v++ = '\\';
+ while (u < uend) {
+ if (*u == Meta)
+ *v++ = *u++;
+ *v++ = *u++;
+ }
+ } else {
+ /* Not printable */
+ *v++ = '$';
+ *v++ = '\'';
+ v = addunprintable(v, u, uend);
+ *v++ = '\'';
+ u = uend;
+ }
+ }
+ }
+ if (quotesub == 2)
+ *v++ = '\'';
+ *v = '\0';
+
+ v = dupstring(buf);
+ zfree(buf, alloclen);
+ return v;
+}
+
+/*
+ * Unmetafy and output a string, quoted if it contains special
+ * characters.
+ *
+ * If stream is NULL, return the same output with any allocation on the
+ * heap.
+ */
+
+/**/
+mod_export char *
+quotedzputs(char const *s, FILE *stream)
+{
+ int inquote = 0, c;
+ char *outstr, *ptr;
+
+ /* check for empty string */
+ if(!*s) {
+ if (!stream)
+ return dupstring("''");
+ fputs("''", stream);
+ return NULL;
+ }
+
+#ifdef MULTIBYTE_SUPPORT
+ if (is_mb_niceformat(s)) {
+ if (stream) {
+ fputs("$'", stream);
+ mb_niceformat(s, stream, NULL, NICEFLAG_QUOTE);
+ fputc('\'', stream);
+ return NULL;
+ } else {
+ char *substr;
+ mb_niceformat(s, NULL, &substr, NICEFLAG_QUOTE|NICEFLAG_NODUP);
+ outstr = (char *)zhalloc(4 + strlen(substr));
+ sprintf(outstr, "$'%s'", substr);
+ free(substr);
+ return outstr;
+ }
+ }
+#endif /* MULTIBYTE_SUPPORT */
+
+ if (!hasspecial(s)) {
+ if (stream) {
+ zputs(s, stream);
+ return NULL;
+ } else {
+ return dupstring(s);
+ }
+ }
+
+ if (!stream) {
+ const char *cptr;
+ int l = strlen(s) + 2;
+ for (cptr = s; *cptr; cptr++) {
+ if (*cptr == Meta)
+ cptr++;
+ else if (*cptr == '\'')
+ l += isset(RCQUOTES) ? 1 : 3;
+ }
+ ptr = outstr = zhalloc(l + 1);
+ } else {
+ ptr = outstr = NULL;
+ }
+ if (isset(RCQUOTES)) {
+ /* use rc-style quotes-within-quotes for the whole string */
+ if (stream) {
+ if (fputc('\'', stream) < 0)
+ return NULL;
+ } else
+ *ptr++ = '\'';
+ while(*s) {
+ if (*s == Dash)
+ c = '-';
+ else if (*s == Meta)
+ c = *++s ^ 32;
+ else
+ c = *s;
+ s++;
+ if (c == '\'') {
+ if (stream) {
+ if (fputc('\'', stream) < 0)
+ return NULL;
+ } else
+ *ptr++ = '\'';
+ } else if (c == '\n' && isset(CSHJUNKIEQUOTES)) {
+ if (stream) {
+ if (fputc('\\', stream) < 0)
+ return NULL;
+ } else
+ *ptr++ = '\\';
+ }
+ if (stream) {
+ if (fputc(c, stream) < 0)
+ return NULL;
+ } else {
+ if (imeta(c)) {
+ *ptr++ = Meta;
+ *ptr++ = c ^ 32;
+ } else
+ *ptr++ = c;
+ }
+ }
+ if (stream) {
+ if (fputc('\'', stream) < 0)
+ return NULL;
+ } else
+ *ptr++ = '\'';
+ } else {
+ /* use Bourne-style quoting, avoiding empty quoted strings */
+ while (*s) {
+ if (*s == Dash)
+ c = '-';
+ else if (*s == Meta)
+ c = *++s ^ 32;
+ else
+ c = *s;
+ s++;
+ if (c == '\'') {
+ if (inquote) {
+ if (stream) {
+ if (putc('\'', stream) < 0)
+ return NULL;
+ } else
+ *ptr++ = '\'';
+ inquote=0;
+ }
+ if (stream) {
+ if (fputs("\\'", stream) < 0)
+ return NULL;
+ } else {
+ *ptr++ = '\\';
+ *ptr++ = '\'';
+ }
+ } else {
+ if (!inquote) {
+ if (stream) {
+ if (fputc('\'', stream) < 0)
+ return NULL;
+ } else
+ *ptr++ = '\'';
+ inquote=1;
+ }
+ if (c == '\n' && isset(CSHJUNKIEQUOTES)) {
+ if (stream) {
+ if (fputc('\\', stream) < 0)
+ return NULL;
+ } else
+ *ptr++ = '\\';
+ }
+ if (stream) {
+ if (fputc(c, stream) < 0)
+ return NULL;
+ } else {
+ if (imeta(c)) {
+ *ptr++ = Meta;
+ *ptr++ = c ^ 32;
+ } else
+ *ptr++ = c;
+ }
+ }
+ }
+ if (inquote) {
+ if (stream) {
+ if (fputc('\'', stream) < 0)
+ return NULL;
+ } else
+ *ptr++ = '\'';
+ }
+ }
+ if (!stream)
+ *ptr++ = '\0';
+
+ return outstr;
+}
+
+/* Double-quote a metafied string. */
+
+/**/
+mod_export 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;
+}
+
+/* Unmetafy and output a string, double quoting it in its entirety. */
+
+#if 0 /**/
+int
+dquotedzputs(char const *s, FILE *stream)
+{
+ char *d = dquotedztrdup(s);
+ int ret = zputs(d, stream);
+
+ zsfree(d);
+ return ret;
+}
+#endif
+
+# if defined(HAVE_NL_LANGINFO) && defined(CODESET) && !defined(__STDC_ISO_10646__)
+/* Convert a character from UCS4 encoding to UTF-8 */
+
+static size_t
+ucs4toutf8(char *dest, unsigned int wval)
+{
+ size_t len;
+
+ if (wval < 0x80)
+ len = 1;
+ else if (wval < 0x800)
+ len = 2;
+ else if (wval < 0x10000)
+ len = 3;
+ else if (wval < 0x200000)
+ len = 4;
+ else if (wval < 0x4000000)
+ len = 5;
+ else
+ len = 6;
+
+ switch (len) { /* falls through except to the last case */
+ case 6: dest[5] = (wval & 0x3f) | 0x80; wval >>= 6;
+ case 5: dest[4] = (wval & 0x3f) | 0x80; wval >>= 6;
+ case 4: dest[3] = (wval & 0x3f) | 0x80; wval >>= 6;
+ case 3: dest[2] = (wval & 0x3f) | 0x80; wval >>= 6;
+ case 2: dest[1] = (wval & 0x3f) | 0x80; wval >>= 6;
+ *dest = wval | ((0xfc << (6 - len)) & 0xfc);
+ break;
+ case 1: *dest = wval;
+ }
+
+ return len;
+}
+#endif
+
+
+/*
+ * The following only occurs once or twice in the code, but in different
+ * places depending how character set conversion is implemented.
+ */
+#define CHARSET_FAILED() \
+ if (how & GETKEY_DOLLAR_QUOTE) { \
+ while ((*tdest++ = *++s)) { \
+ if (how & GETKEY_UPDATE_OFFSET) { \
+ if (s - sstart > *misc) \
+ (*misc)++; \
+ } \
+ if (*s == Snull) { \
+ *len = (s - sstart) + 1; \
+ *tdest = '\0'; \
+ return buf; \
+ } \
+ } \
+ *len = tdest - buf; \
+ return buf; \
+ } \
+ *t = '\0'; \
+ *len = t - buf; \
+ return buf
+
+/*
+ * Decode a key string, turning it into the literal characters.
+ * The value returned is a newly allocated string from the heap.
+ *
+ * The length is returned in *len. This is usually the length of
+ * the final unmetafied string. The exception is the case of
+ * a complete GETKEY_DOLLAR_QUOTE conversion where *len is the
+ * length of the input string which has been used (up to and including
+ * the terminating single quote); as the final string is metafied and
+ * NULL-terminated its length is not required. If both GETKEY_DOLLAR_QUOTE
+ * and GETKEY_UPDATE_OFFSET are present in "how", the string is not
+ * expected to be terminated (this is used in completion to parse
+ * a partial $'...'-quoted string) and the length passed back is
+ * that of the converted string. Note in both cases that this is a length
+ * in bytes (i.e. the same as given by a raw pointer difference), not
+ * characters, which may occupy multiple bytes.
+ *
+ * how is a set of bits from the GETKEY_ values defined in zsh.h;
+ * not all combinations of bits are useful. Callers will typically
+ * use one of the GETKEYS_ values which define sets of bits.
+ * Note, for example that:
+ * - GETKEY_SINGLE_CHAR must not be combined with GETKEY_DOLLAR_QUOTE.
+ * - GETKEY_UPDATE_OFFSET is only allowed if GETKEY_DOLLAR_QUOTE is
+ * also present.
+ *
+ * *misc is used for various purposes:
+ * - If GETKEY_BACKSLASH_MINUS is set, it indicates the presence
+ * of \- in the input.
+ * - If GETKEY_BACKSLASH_C is set, it indicates the presence
+ * of \c in the input.
+ * - If GETKEY_UPDATE_OFFSET is set, it is set on input to some
+ * mystical completion offset and is updated to a new offset based
+ * on the converted characters. All Hail the Completion System
+ * [makes the mystic completion system runic sign in the air].
+ *
+ * The return value is unmetafied unless GETKEY_DOLLAR_QUOTE is
+ * in use.
+ */
+
+/**/
+mod_export char *
+getkeystring(char *s, int *len, int how, int *misc)
+{
+ char *buf, tmp[1];
+ char *t, *tdest = NULL, *u = NULL, *sstart = s, *tbuf = NULL;
+ char svchar = '\0';
+ int meta = 0, control = 0, ignoring = 0;
+ int i;
+#if defined(HAVE_WCHAR_H) && defined(HAVE_WCTOMB) && defined(__STDC_ISO_10646__)
+ wint_t wval;
+ int count;
+#else
+ unsigned int wval;
+# if defined(HAVE_NL_LANGINFO) && defined(CODESET)
+# if defined(HAVE_ICONV)
+ iconv_t cd;
+ char inbuf[4];
+ size_t inbytes, outbytes;
+# endif
+ size_t count;
+# endif
+#endif
+
+ DPUTS((how & GETKEY_UPDATE_OFFSET) &&
+ (how & ~(GETKEYS_DOLLARS_QUOTE|GETKEY_UPDATE_OFFSET)),
+ "BUG: offset updating in getkeystring only supported with $'.");
+ DPUTS((how & (GETKEY_DOLLAR_QUOTE|GETKEY_SINGLE_CHAR)) ==
+ (GETKEY_DOLLAR_QUOTE|GETKEY_SINGLE_CHAR),
+ "BUG: incompatible options in getkeystring");
+
+ if (how & GETKEY_SINGLE_CHAR)
+ t = buf = tmp;
+ else {
+ /* Length including terminating NULL */
+ int maxlen = 1;
+ /*
+ * We're not necessarily guaranteed the output string will
+ * be no longer than the input with \u and \U when output
+ * characters need to be metafied. As this is the only
+ * case where the string can get longer (?I think),
+ * include it in the allocation length here but don't
+ * bother taking account of other factors.
+ */
+ for (t = s; *t; t++) {
+ if (*t == '\\') {
+ if (!t[1]) {
+ maxlen++;
+ break;
+ }
+ if (t[1] == 'u' || t[1] == 'U')
+ maxlen += MB_CUR_MAX * 2;
+ else
+ maxlen += 2;
+ /* skip the backslash and the following character */
+ t++;
+ } else
+ maxlen++;
+ }
+ if (how & GETKEY_DOLLAR_QUOTE) {
+ /*
+ * We're going to unmetafy into a new string, but
+ * to get a proper metafied input we're going to metafy
+ * into an intermediate buffer. This is necessary if we have
+ * \u and \U's with multiple metafied bytes. We can't
+ * simply remetafy the entire string because there may
+ * be tokens (indeed, we know there are lexical nulls floating
+ * around), so we have to be aware character by character
+ * what we are converting.
+ *
+ * In this case, buf is the final buffer (as usual),
+ * but t points into a temporary buffer that just has
+ * to be long enough to hold the result of one escape
+ * code transformation. We count this is a full multibyte
+ * character (MB_CUR_MAX) with every character metafied
+ * (*2) plus a little bit of fuzz (for e.g. the odd backslash).
+ */
+ buf = tdest = zhalloc(maxlen);
+ t = tbuf = zhalloc(MB_CUR_MAX * 3 + 1);
+ } else {
+ t = buf = zhalloc(maxlen);
+ }
+ }
+ for (; *s; s++) {
+ if (*s == '\\' && s[1]) {
+ int miscadded;
+ if ((how & GETKEY_UPDATE_OFFSET) && s - sstart < *misc) {
+ (*misc)--;
+ miscadded = 1;
+ } else
+ miscadded = 0;
+ 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 (!(how & GETKEY_EMACS)) {
+ *t++ = '\\', s--;
+ if (miscadded)
+ (*misc)++;
+ continue;
+ }
+ /* FALL THROUGH */
+ case 'e':
+ *t++ = '\033';
+ break;
+ case 'M':
+ /* HERE: GETKEY_UPDATE_OFFSET */
+ if (how & GETKEY_EMACS) {
+ if (s[1] == '-')
+ s++;
+ meta = 1 + control; /* preserve the order of ^ and meta */
+ } else {
+ if (miscadded)
+ (*misc)++;
+ *t++ = '\\', s--;
+ }
+ continue;
+ case 'C':
+ /* HERE: GETKEY_UPDATE_OFFSET */
+ if (how & GETKEY_EMACS) {
+ if (s[1] == '-')
+ s++;
+ control = 1;
+ } else {
+ if (miscadded)
+ (*misc)++;
+ *t++ = '\\', s--;
+ }
+ continue;
+ case Meta:
+ if (miscadded)
+ (*misc)++;
+ *t++ = '\\', s--;
+ break;
+ case '-':
+ if (how & GETKEY_BACKSLASH_MINUS) {
+ *misc = 1;
+ break;
+ }
+ goto def;
+ case 'c':
+ if (how & GETKEY_BACKSLASH_C) {
+ *misc = 1;
+ *t = '\0';
+ *len = t - buf;
+ return buf;
+ }
+ goto def;
+ case 'U':
+ if ((how & GETKEY_UPDATE_OFFSET) && s - sstart < *misc)
+ (*misc) -= 4;
+ /* FALLTHROUGH */
+ case 'u':
+ if ((how & GETKEY_UPDATE_OFFSET) && s - sstart < *misc) {
+ (*misc) -= 6; /* HERE don't really believe this */
+ /*
+ * We've now adjusted the offset for all the input
+ * characters, so we need to add for each
+ * byte of output below.
+ */
+ }
+ wval = 0;
+ for (i=(*s == 'u' ? 4 : 8); i>0; i--) {
+ if (*++s && idigit(*s))
+ wval = wval * 16 + (*s - '0');
+ else if (*s && ((*s >= 'a' && *s <= 'f') ||
+ (*s >= 'A' && *s <= 'F')))
+ wval = wval * 16 + (*s & 0x1f) + 9;
+ else {
+ s--;
+ break;
+ }
+ }
+ if (how & GETKEY_SINGLE_CHAR) {
+ *misc = wval;
+ return s+1;
+ }
+#if defined(HAVE_WCHAR_H) && defined(HAVE_WCTOMB) && defined(__STDC_ISO_10646__)
+ count = wctomb(t, (wchar_t)wval);
+ if (count == -1) {
+ zerr("character not in range");
+ CHARSET_FAILED();
+ }
+ if ((how & GETKEY_UPDATE_OFFSET) && s - sstart < *misc)
+ (*misc) += count;
+ t += count;
+# else
+# if defined(HAVE_NL_LANGINFO) && defined(CODESET)
+ if (!strcmp(nl_langinfo(CODESET), "UTF-8")) {
+ count = ucs4toutf8(t, wval);
+ t += count;
+ if ((how & GETKEY_UPDATE_OFFSET) && s - sstart < *misc)
+ (*misc) += count;
+ } else {
+# ifdef HAVE_ICONV
+ ICONV_CONST char *inptr = inbuf;
+ const char *codesetstr = nl_langinfo(CODESET);
+ inbytes = 4;
+ outbytes = 6;
+ /* store value in big endian form */
+ for (i=3;i>=0;i--) {
+ inbuf[i] = wval & 0xff;
+ wval >>= 8;
+ }
+
+ /*
+ * If the code set isn't handled, we'd better
+ * assume it's US-ASCII rather than just failing
+ * hopelessly. Solaris has a weird habit of
+ * returning 646. This is handled by the
+ * native iconv(), but not by GNU iconv; what's
+ * more, some versions of the native iconv don't
+ * handle standard names like ASCII.
+ *
+ * This should only be a problem if there's a
+ * mismatch between the NLS and the iconv in use,
+ * which probably only means if libiconv is in use.
+ * We checked at configure time if our libraries
+ * pulled in _libiconv_version, which should be
+ * a good test.
+ *
+ * It shouldn't ever be NULL, but while we're
+ * being paranoid...
+ */
+#ifdef ICONV_FROM_LIBICONV
+ if (!codesetstr || !*codesetstr)
+ codesetstr = "US-ASCII";
+#endif
+ cd = iconv_open(codesetstr, "UCS-4BE");
+#ifdef ICONV_FROM_LIBICONV
+ if (cd == (iconv_t)-1 && !strcmp(codesetstr, "646")) {
+ codesetstr = "US-ASCII";
+ cd = iconv_open(codesetstr, "UCS-4BE");
+ }
+#endif
+ if (cd == (iconv_t)-1) {
+ zerr("cannot do charset conversion (iconv failed)");
+ CHARSET_FAILED();
+ }
+ count = iconv(cd, &inptr, &inbytes, &t, &outbytes);
+ iconv_close(cd);
+ if (count == (size_t)-1) {
+ zerr("character not in range");
+ CHARSET_FAILED();
+ }
+ if ((how & GETKEY_UPDATE_OFFSET) && s - sstart < *misc)
+ (*misc) += count;
+# else
+ zerr("cannot do charset conversion (iconv not available)");
+ CHARSET_FAILED();
+# endif
+ }
+# else
+ zerr("cannot do charset conversion (NLS not supported)");
+ CHARSET_FAILED();
+# endif
+# endif
+ if (how & GETKEY_DOLLAR_QUOTE) {
+ char *t2;
+ for (t2 = tbuf; t2 < t; t2++) {
+ if (imeta(*t2)) {
+ *tdest++ = Meta;
+ *tdest++ = *t2 ^ 32;
+ } else
+ *tdest++ = *t2;
+ }
+ /* reset temporary buffer after handling */
+ t = tbuf;
+ }
+ continue;
+ case '\'':
+ case '\\':
+ if (how & GETKEY_DOLLAR_QUOTE) {
+ /*
+ * Usually \' and \\ will have the initial
+ * \ turned into a Bnull, however that's not
+ * necessarily the case when called from
+ * completion.
+ */
+ *t++ = *s;
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ def:
+ /* HERE: GETKEY_UPDATE_OFFSET? */
+ if ((idigit(*s) && *s < '8') || *s == 'x') {
+ if (!(how & GETKEY_OCTAL_ESC)) {
+ 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 ((how & GETKEY_PRINTF_PERCENT) && t[-1] == '%')
+ *t++ = '%';
+ if (svchar) {
+ u[3] = svchar;
+ svchar = '\0';
+ }
+ s--;
+ } else {
+ if (!(how & GETKEY_EMACS) && *s != '\\') {
+ if (miscadded)
+ (*misc)++;
+ *t++ = '\\';
+ }
+ *t++ = *s;
+ }
+ break;
+ }
+ } else if ((how & GETKEY_DOLLAR_QUOTE) && *s == Snull) {
+ /* return length to following character */
+ *len = (s - sstart) + 1;
+ *tdest = '\0';
+ return buf;
+ } else if (*s == '^' && !control && (how & GETKEY_CTRL) && s[1]) {
+ control = 1;
+ continue;
+#ifdef MULTIBYTE_SUPPORT
+ } else if ((how & GETKEY_SINGLE_CHAR) &&
+ isset(MULTIBYTE) && STOUC(*s) > 127) {
+ wint_t wc;
+ int len;
+ len = mb_metacharlenconv(s, &wc);
+ if (wc != WEOF) {
+ *misc = (int)wc;
+ return s + len;
+ }
+#endif
+
+ } else if (*s == Meta)
+ *t++ = *++s ^ 32;
+ else {
+ if (itok(*s)) {
+ /*
+ * We need to be quite careful here. We haven't
+ * necessarily got an input stream with all tokens
+ * removed, so the majority of tokens need passing
+ * through untouched and without Meta handling.
+ * However, me may need to handle tokenized
+ * backslashes.
+ */
+ if (meta || control) {
+ /*
+ * Presumably we should be using meta or control
+ * on the character representing the token.
+ *
+ * Special case: $'\M-\\' where the token is a Bnull.
+ * This time we dump the Bnull since we're
+ * replacing the whole thing. The lexer
+ * doesn't know about the meta or control modifiers.
+ */
+ if ((how & GETKEY_DOLLAR_QUOTE) && *s == Bnull)
+ *t++ = *++s;
+ else
+ *t++ = ztokens[*s - Pound];
+ } else if (how & GETKEY_DOLLAR_QUOTE) {
+ /*
+ * We don't want to metafy this, it's a real
+ * token.
+ */
+ *tdest++ = *s;
+ if (*s == Bnull) {
+ /*
+ * Bnull is a backslash which quotes a couple
+ * of special characters that always appear
+ * literally next. See strquote handling
+ * in gettokstr() in lex.c. We need
+ * to retain the Bnull (as above) so that quote
+ * handling in completion can tell where the
+ * backslash was.
+ */
+ *tdest++ = *++s;
+ }
+ /* reset temporary buffer, now handled */
+ t = tbuf;
+ continue;
+ } else
+ *t++ = *s;
+ } 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 (how & GETKEY_DOLLAR_QUOTE) {
+ char *t2;
+ for (t2 = tbuf; t2 < t; t2++) {
+ /*
+ * In POSIX mode, an embedded NULL is discarded and
+ * terminates processing. It just does, that's why.
+ */
+ if (isset(POSIXSTRINGS)) {
+ if (*t2 == '\0')
+ ignoring = 1;
+ if (ignoring)
+ break;
+ }
+ if (imeta(*t2)) {
+ *tdest++ = Meta;
+ *tdest++ = *t2 ^ 32;
+ } else {
+ *tdest++ = *t2;
+ }
+ }
+ /*
+ * Reset use of temporary buffer.
+ */
+ t = tbuf;
+ }
+ if ((how & GETKEY_SINGLE_CHAR) && t != tmp) {
+ *misc = STOUC(tmp[0]);
+ return s + 1;
+ }
+ }
+ /*
+ * When called from completion, where we use GETKEY_UPDATE_OFFSET to
+ * update the index into the metafied editor line, we don't necessarily
+ * have the end of a $'...' quotation, else we should do.
+ */
+ DPUTS((how & (GETKEY_DOLLAR_QUOTE|GETKEY_UPDATE_OFFSET)) ==
+ GETKEY_DOLLAR_QUOTE, "BUG: unterminated $' substitution");
+ *t = '\0';
+ if (how & GETKEY_DOLLAR_QUOTE)
+ *tdest = '\0';
+ if (how & GETKEY_SINGLE_CHAR)
+ *misc = 0;
+ else
+ *len = ((how & GETKEY_DOLLAR_QUOTE) ? tdest : t) - buf;
+ return buf;
+}
+
+/* Return non-zero if s is a prefix of t. */
+
+/**/
+mod_export int
+strpfx(const char *s, const char *t)
+{
+ while (*s && *s == *t)
+ s++, t++;
+ return !*s;
+}
+
+/* Return non-zero if s is a suffix of t. */
+
+/**/
+mod_export int
+strsfx(char *s, char *t)
+{
+ int ls = strlen(s), lt = strlen(t);
+
+ if (ls <= lt)
+ return !strcmp(t + lt - ls, s);
+ return 0;
+}
+
+/**/
+static int
+upchdir(int n)
+{
+ char buf[PATH_MAX+1];
+ 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;
+}
+
+/*
+ * Initialize a "struct dirsav".
+ * The structure will be set to the directory we want to save
+ * the first time we change to a different directory.
+ */
+
+/**/
+mod_export void
+init_dirsav(Dirsav d)
+{
+ d->ino = d->dev = 0;
+ d->dirname = NULL;
+ d->dirfd = d->level = -1;
+}
+
+/*
+ * 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.
+ *
+ * path is an unmetafied but null-terminated string, as needed by system
+ * calls.
+ */
+
+/**/
+mod_export 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
+#ifdef HAVE_FCHDIR
+ int close_dir = 0;
+#endif
+
+ if (!d) {
+ init_dirsav(&ds);
+ d = &ds;
+ }
+#ifdef HAVE_LSTAT
+ if ((*path == '/' || !hard) &&
+ (d != &ds || hard)){
+#else
+ if (*path == '/') {
+#endif
+ level = -1;
+#ifndef HAVE_FCHDIR
+ 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
+#ifdef HAVE_FCHDIR
+ if (d->dirfd < 0) {
+ close_dir = 1;
+ if ((d->dirfd = open(".", O_RDONLY | O_NOCTTY)) < 0 &&
+ zgetdir(d) && *d->dirname != '/')
+ d->dirfd = open("..", O_RDONLY | O_NOCTTY);
+ }
+#endif
+ if (*path == '/')
+ if (chdir("/") < 0)
+ zwarn("failed to chdir(/): %e", errno);
+ for(;;) {
+ while(*path == '/')
+ path++;
+ if(!*path) {
+ if (d == &ds)
+ zsfree(ds.dirname);
+ else
+ d->level = level;
+#ifdef HAVE_FCHDIR
+ if (d->dirfd >=0 && close_dir) {
+ close(d->dirfd);
+ d->dirfd = -1;
+ }
+#endif
+ 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)) {
+ int restoreerr = errno;
+ int i;
+ /*
+ * Failed to restore the directory.
+ * Just be definite, cd to root and report the result.
+ */
+ for (i = 0; i < 2; i++) {
+ const char *cdest;
+ if (i)
+ cdest = "/";
+ else {
+ if (!home)
+ continue;
+ cdest = home;
+ }
+ zsfree(pwd);
+ pwd = ztrdup(cdest);
+ if (chdir(pwd) == 0)
+ break;
+ }
+ if (i == 2)
+ zerr("lost current directory, failed to cd to /: %e", errno);
+ else
+ zerr("lost current directory: %e: changed to `%s'", restoreerr,
+ pwd);
+ if (d == &ds)
+ zsfree(ds.dirname);
+#ifdef HAVE_FCHDIR
+ if (d->dirfd >=0 && close_dir) {
+ close(d->dirfd);
+ d->dirfd = -1;
+ }
+#endif
+ errno = err;
+ return -2;
+ }
+ if (d == &ds)
+ zsfree(ds.dirname);
+#ifdef HAVE_FCHDIR
+ if (d->dirfd >=0 && close_dir) {
+ close(d->dirfd);
+ d->dirfd = -1;
+ }
+#endif
+ errno = err;
+ return -1;
+#endif /* HAVE_LSTAT */
+}
+
+/**/
+mod_export 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;
+}
+
+
+/* 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_free(caps);
+ return 1;
+ }
+ }
+ cap_free(caps);
+ }
+#endif /* HAVE_CAP_GET_PROC */
+ return 0;
+}
+
+/**/
+mod_export 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;
+}
+
+#ifdef MAILDIR_SUPPORT
+/*
+ * Stat a file. If it's a maildir, check all messages
+ * in the maildir and present the grand total as a file.
+ * The fields in the 'struct stat' are from the mail directory.
+ * The following fields are emulated:
+ *
+ * st_nlink always 1
+ * st_size total number of bytes in all files
+ * st_blocks total number of messages
+ * st_atime access time of newest file in maildir
+ * st_mtime modify time of newest file in maildir
+ * st_mode S_IFDIR changed to S_IFREG
+ *
+ * This is good enough for most mail-checking applications.
+ */
+
+/**/
+int
+mailstat(char *path, struct stat *st)
+{
+ DIR *dd;
+ struct dirent *fn;
+ struct stat st_ret, st_tmp;
+ static struct stat st_ret_last;
+ char *dir, *file = 0;
+ int i;
+ time_t atime = 0, mtime = 0;
+ size_t plen = strlen(path), dlen;
+
+ /* First see if it's a directory. */
+ if ((i = stat(path, st)) != 0 || !S_ISDIR(st->st_mode))
+ return i;
+
+ st_ret = *st;
+ st_ret.st_nlink = 1;
+ st_ret.st_size = 0;
+ st_ret.st_blocks = 0;
+ st_ret.st_mode &= ~S_IFDIR;
+ st_ret.st_mode |= S_IFREG;
+
+ /* See if cur/ is present */
+ dir = appstr(ztrdup(path), "/cur");
+ if (stat(dir, &st_tmp) || !S_ISDIR(st_tmp.st_mode)) return 0;
+ st_ret.st_atime = st_tmp.st_atime;
+
+ /* See if tmp/ is present */
+ dir[plen] = 0;
+ dir = appstr(dir, "/tmp");
+ if (stat(dir, &st_tmp) || !S_ISDIR(st_tmp.st_mode)) return 0;
+ st_ret.st_mtime = st_tmp.st_mtime;
+
+ /* And new/ */
+ dir[plen] = 0;
+ dir = appstr(dir, "/new");
+ if (stat(dir, &st_tmp) || !S_ISDIR(st_tmp.st_mode)) return 0;
+ st_ret.st_mtime = st_tmp.st_mtime;
+
+#if THERE_IS_EXACTLY_ONE_MAILDIR_IN_MAILPATH
+ {
+ static struct stat st_new_last;
+ /* Optimization - if new/ didn't change, nothing else did. */
+ if (st_tmp.st_dev == st_new_last.st_dev &&
+ st_tmp.st_ino == st_new_last.st_ino &&
+ st_tmp.st_atime == st_new_last.st_atime &&
+ st_tmp.st_mtime == st_new_last.st_mtime) {
+ *st = st_ret_last;
+ return 0;
+ }
+ st_new_last = st_tmp;
+ }
+#endif
+
+ /* Loop over new/ and cur/ */
+ for (i = 0; i < 2; i++) {
+ dir[plen] = 0;
+ dir = appstr(dir, i ? "/cur" : "/new");
+ if ((dd = opendir(dir)) == NULL) {
+ zsfree(file);
+ zsfree(dir);
+ return 0;
+ }
+ dlen = strlen(dir) + 1; /* include the "/" */
+ while ((fn = readdir(dd)) != NULL) {
+ if (fn->d_name[0] == '.')
+ continue;
+ if (file) {
+ file[dlen] = 0;
+ file = appstr(file, fn->d_name);
+ } else {
+ file = tricat(dir, "/", fn->d_name);
+ }
+ if (stat(file, &st_tmp) != 0)
+ continue;
+ st_ret.st_size += st_tmp.st_size;
+ st_ret.st_blocks++;
+ if (st_tmp.st_atime != st_tmp.st_mtime &&
+ st_tmp.st_atime > atime)
+ atime = st_tmp.st_atime;
+ if (st_tmp.st_mtime > mtime)
+ mtime = st_tmp.st_mtime;
+ }
+ closedir(dd);
+ }
+ zsfree(file);
+ zsfree(dir);
+
+ if (atime) st_ret.st_atime = atime;
+ if (mtime) st_ret.st_mtime = mtime;
+
+ *st = st_ret_last = st_ret;
+ return 0;
+}
+#endif
diff --git a/dotfiles/system/.zsh/modules/Src/wcwidth9.h b/dotfiles/system/.zsh/modules/Src/wcwidth9.h
new file mode 100644
index 0000000..448f548
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/wcwidth9.h
@@ -0,0 +1,1325 @@
+#ifndef WCWIDTH9_H
+#define WCWIDTH9_H
+
+#include <stdlib.h>
+#include <stdbool.h>
+
+struct wcwidth9_interval {
+ long first;
+ long last;
+};
+
+static const struct wcwidth9_interval wcwidth9_private[] = {
+ {0x00e000, 0x00f8ff},
+ {0x0f0000, 0x0ffffd},
+ {0x100000, 0x10fffd},
+};
+
+static const struct wcwidth9_interval wcwidth9_nonprint[] = {
+ {0x0000, 0x001f},
+ {0x007f, 0x009f},
+ {0x00ad, 0x00ad},
+ {0x070f, 0x070f},
+ {0x180b, 0x180e},
+ {0x200b, 0x200f},
+ {0x2028, 0x2029},
+ {0x202a, 0x202e},
+ {0x206a, 0x206f},
+ {0xd800, 0xdfff},
+ {0xfeff, 0xfeff},
+ {0xfff9, 0xfffb},
+ {0xfffe, 0xffff},
+};
+
+static const struct wcwidth9_interval wcwidth9_combining[] = {
+ {0x0300, 0x036f},
+ {0x0483, 0x0489},
+ {0x0591, 0x05bd},
+ {0x05bf, 0x05bf},
+ {0x05c1, 0x05c2},
+ {0x05c4, 0x05c5},
+ {0x05c7, 0x05c7},
+ {0x0610, 0x061a},
+ {0x064b, 0x065f},
+ {0x0670, 0x0670},
+ {0x06d6, 0x06dc},
+ {0x06df, 0x06e4},
+ {0x06e7, 0x06e8},
+ {0x06ea, 0x06ed},
+ {0x0711, 0x0711},
+ {0x0730, 0x074a},
+ {0x07a6, 0x07b0},
+ {0x07eb, 0x07f3},
+ {0x0816, 0x0819},
+ {0x081b, 0x0823},
+ {0x0825, 0x0827},
+ {0x0829, 0x082d},
+ {0x0859, 0x085b},
+ {0x08d4, 0x08e1},
+ {0x08e3, 0x0903},
+ {0x093a, 0x093c},
+ {0x093e, 0x094f},
+ {0x0951, 0x0957},
+ {0x0962, 0x0963},
+ {0x0981, 0x0983},
+ {0x09bc, 0x09bc},
+ {0x09be, 0x09c4},
+ {0x09c7, 0x09c8},
+ {0x09cb, 0x09cd},
+ {0x09d7, 0x09d7},
+ {0x09e2, 0x09e3},
+ {0x0a01, 0x0a03},
+ {0x0a3c, 0x0a3c},
+ {0x0a3e, 0x0a42},
+ {0x0a47, 0x0a48},
+ {0x0a4b, 0x0a4d},
+ {0x0a51, 0x0a51},
+ {0x0a70, 0x0a71},
+ {0x0a75, 0x0a75},
+ {0x0a81, 0x0a83},
+ {0x0abc, 0x0abc},
+ {0x0abe, 0x0ac5},
+ {0x0ac7, 0x0ac9},
+ {0x0acb, 0x0acd},
+ {0x0ae2, 0x0ae3},
+ {0x0b01, 0x0b03},
+ {0x0b3c, 0x0b3c},
+ {0x0b3e, 0x0b44},
+ {0x0b47, 0x0b48},
+ {0x0b4b, 0x0b4d},
+ {0x0b56, 0x0b57},
+ {0x0b62, 0x0b63},
+ {0x0b82, 0x0b82},
+ {0x0bbe, 0x0bc2},
+ {0x0bc6, 0x0bc8},
+ {0x0bca, 0x0bcd},
+ {0x0bd7, 0x0bd7},
+ {0x0c00, 0x0c03},
+ {0x0c3e, 0x0c44},
+ {0x0c46, 0x0c48},
+ {0x0c4a, 0x0c4d},
+ {0x0c55, 0x0c56},
+ {0x0c62, 0x0c63},
+ {0x0c81, 0x0c83},
+ {0x0cbc, 0x0cbc},
+ {0x0cbe, 0x0cc4},
+ {0x0cc6, 0x0cc8},
+ {0x0cca, 0x0ccd},
+ {0x0cd5, 0x0cd6},
+ {0x0ce2, 0x0ce3},
+ {0x0d01, 0x0d03},
+ {0x0d3e, 0x0d44},
+ {0x0d46, 0x0d48},
+ {0x0d4a, 0x0d4d},
+ {0x0d57, 0x0d57},
+ {0x0d62, 0x0d63},
+ {0x0d82, 0x0d83},
+ {0x0dca, 0x0dca},
+ {0x0dcf, 0x0dd4},
+ {0x0dd6, 0x0dd6},
+ {0x0dd8, 0x0ddf},
+ {0x0df2, 0x0df3},
+ {0x0e31, 0x0e31},
+ {0x0e34, 0x0e3a},
+ {0x0e47, 0x0e4e},
+ {0x0eb1, 0x0eb1},
+ {0x0eb4, 0x0eb9},
+ {0x0ebb, 0x0ebc},
+ {0x0ec8, 0x0ecd},
+ {0x0f18, 0x0f19},
+ {0x0f35, 0x0f35},
+ {0x0f37, 0x0f37},
+ {0x0f39, 0x0f39},
+ {0x0f3e, 0x0f3f},
+ {0x0f71, 0x0f84},
+ {0x0f86, 0x0f87},
+ {0x0f8d, 0x0f97},
+ {0x0f99, 0x0fbc},
+ {0x0fc6, 0x0fc6},
+ {0x102b, 0x103e},
+ {0x1056, 0x1059},
+ {0x105e, 0x1060},
+ {0x1062, 0x1064},
+ {0x1067, 0x106d},
+ {0x1071, 0x1074},
+ {0x1082, 0x108d},
+ {0x108f, 0x108f},
+ {0x109a, 0x109d},
+ {0x135d, 0x135f},
+ {0x1712, 0x1714},
+ {0x1732, 0x1734},
+ {0x1752, 0x1753},
+ {0x1772, 0x1773},
+ {0x17b4, 0x17d3},
+ {0x17dd, 0x17dd},
+ {0x180b, 0x180d},
+ {0x1885, 0x1886},
+ {0x18a9, 0x18a9},
+ {0x1920, 0x192b},
+ {0x1930, 0x193b},
+ {0x1a17, 0x1a1b},
+ {0x1a55, 0x1a5e},
+ {0x1a60, 0x1a7c},
+ {0x1a7f, 0x1a7f},
+ {0x1ab0, 0x1abe},
+ {0x1b00, 0x1b04},
+ {0x1b34, 0x1b44},
+ {0x1b6b, 0x1b73},
+ {0x1b80, 0x1b82},
+ {0x1ba1, 0x1bad},
+ {0x1be6, 0x1bf3},
+ {0x1c24, 0x1c37},
+ {0x1cd0, 0x1cd2},
+ {0x1cd4, 0x1ce8},
+ {0x1ced, 0x1ced},
+ {0x1cf2, 0x1cf4},
+ {0x1cf8, 0x1cf9},
+ {0x1dc0, 0x1df5},
+ {0x1dfb, 0x1dff},
+ {0x20d0, 0x20f0},
+ {0x2cef, 0x2cf1},
+ {0x2d7f, 0x2d7f},
+ {0x2de0, 0x2dff},
+ {0x302a, 0x302f},
+ {0x3099, 0x309a},
+ {0xa66f, 0xa672},
+ {0xa674, 0xa67d},
+ {0xa69e, 0xa69f},
+ {0xa6f0, 0xa6f1},
+ {0xa802, 0xa802},
+ {0xa806, 0xa806},
+ {0xa80b, 0xa80b},
+ {0xa823, 0xa827},
+ {0xa880, 0xa881},
+ {0xa8b4, 0xa8c5},
+ {0xa8e0, 0xa8f1},
+ {0xa926, 0xa92d},
+ {0xa947, 0xa953},
+ {0xa980, 0xa983},
+ {0xa9b3, 0xa9c0},
+ {0xa9e5, 0xa9e5},
+ {0xaa29, 0xaa36},
+ {0xaa43, 0xaa43},
+ {0xaa4c, 0xaa4d},
+ {0xaa7b, 0xaa7d},
+ {0xaab0, 0xaab0},
+ {0xaab2, 0xaab4},
+ {0xaab7, 0xaab8},
+ {0xaabe, 0xaabf},
+ {0xaac1, 0xaac1},
+ {0xaaeb, 0xaaef},
+ {0xaaf5, 0xaaf6},
+ {0xabe3, 0xabea},
+ {0xabec, 0xabed},
+ {0xfb1e, 0xfb1e},
+ {0xfe00, 0xfe0f},
+ {0xfe20, 0xfe2f},
+ {0x101fd, 0x101fd},
+ {0x102e0, 0x102e0},
+ {0x10376, 0x1037a},
+ {0x10a01, 0x10a03},
+ {0x10a05, 0x10a06},
+ {0x10a0c, 0x10a0f},
+ {0x10a38, 0x10a3a},
+ {0x10a3f, 0x10a3f},
+ {0x10ae5, 0x10ae6},
+ {0x11000, 0x11002},
+ {0x11038, 0x11046},
+ {0x1107f, 0x11082},
+ {0x110b0, 0x110ba},
+ {0x11100, 0x11102},
+ {0x11127, 0x11134},
+ {0x11173, 0x11173},
+ {0x11180, 0x11182},
+ {0x111b3, 0x111c0},
+ {0x111ca, 0x111cc},
+ {0x1122c, 0x11237},
+ {0x1123e, 0x1123e},
+ {0x112df, 0x112ea},
+ {0x11300, 0x11303},
+ {0x1133c, 0x1133c},
+ {0x1133e, 0x11344},
+ {0x11347, 0x11348},
+ {0x1134b, 0x1134d},
+ {0x11357, 0x11357},
+ {0x11362, 0x11363},
+ {0x11366, 0x1136c},
+ {0x11370, 0x11374},
+ {0x11435, 0x11446},
+ {0x114b0, 0x114c3},
+ {0x115af, 0x115b5},
+ {0x115b8, 0x115c0},
+ {0x115dc, 0x115dd},
+ {0x11630, 0x11640},
+ {0x116ab, 0x116b7},
+ {0x1171d, 0x1172b},
+ {0x11c2f, 0x11c36},
+ {0x11c38, 0x11c3f},
+ {0x11c92, 0x11ca7},
+ {0x11ca9, 0x11cb6},
+ {0x16af0, 0x16af4},
+ {0x16b30, 0x16b36},
+ {0x16f51, 0x16f7e},
+ {0x16f8f, 0x16f92},
+ {0x1bc9d, 0x1bc9e},
+ {0x1d165, 0x1d169},
+ {0x1d16d, 0x1d172},
+ {0x1d17b, 0x1d182},
+ {0x1d185, 0x1d18b},
+ {0x1d1aa, 0x1d1ad},
+ {0x1d242, 0x1d244},
+ {0x1da00, 0x1da36},
+ {0x1da3b, 0x1da6c},
+ {0x1da75, 0x1da75},
+ {0x1da84, 0x1da84},
+ {0x1da9b, 0x1da9f},
+ {0x1daa1, 0x1daaf},
+ {0x1e000, 0x1e006},
+ {0x1e008, 0x1e018},
+ {0x1e01b, 0x1e021},
+ {0x1e023, 0x1e024},
+ {0x1e026, 0x1e02a},
+ {0x1e8d0, 0x1e8d6},
+ {0x1e944, 0x1e94a},
+ {0xe0100, 0xe01ef},
+};
+
+static const struct wcwidth9_interval wcwidth9_doublewidth[] = {
+ {0x1100, 0x115f},
+ {0x231a, 0x231b},
+ {0x2329, 0x232a},
+ {0x23e9, 0x23ec},
+ {0x23f0, 0x23f0},
+ {0x23f3, 0x23f3},
+ {0x25fd, 0x25fe},
+ {0x2614, 0x2615},
+ {0x2648, 0x2653},
+ {0x267f, 0x267f},
+ {0x2693, 0x2693},
+ {0x26a1, 0x26a1},
+ {0x26aa, 0x26ab},
+ {0x26bd, 0x26be},
+ {0x26c4, 0x26c5},
+ {0x26ce, 0x26ce},
+ {0x26d4, 0x26d4},
+ {0x26ea, 0x26ea},
+ {0x26f2, 0x26f3},
+ {0x26f5, 0x26f5},
+ {0x26fa, 0x26fa},
+ {0x26fd, 0x26fd},
+ {0x2705, 0x2705},
+ {0x270a, 0x270b},
+ {0x2728, 0x2728},
+ {0x274c, 0x274c},
+ {0x274e, 0x274e},
+ {0x2753, 0x2755},
+ {0x2757, 0x2757},
+ {0x2795, 0x2797},
+ {0x27b0, 0x27b0},
+ {0x27bf, 0x27bf},
+ {0x2b1b, 0x2b1c},
+ {0x2b50, 0x2b50},
+ {0x2b55, 0x2b55},
+ {0x2e80, 0x2e99},
+ {0x2e9b, 0x2ef3},
+ {0x2f00, 0x2fd5},
+ {0x2ff0, 0x2ffb},
+ {0x3000, 0x303e},
+ {0x3041, 0x3096},
+ {0x3099, 0x30ff},
+ {0x3105, 0x312d},
+ {0x3131, 0x318e},
+ {0x3190, 0x31ba},
+ {0x31c0, 0x31e3},
+ {0x31f0, 0x321e},
+ {0x3220, 0x3247},
+ {0x3250, 0x32fe},
+ {0x3300, 0x4dbf},
+ {0x4e00, 0xa48c},
+ {0xa490, 0xa4c6},
+ {0xa960, 0xa97c},
+ {0xac00, 0xd7a3},
+ {0xf900, 0xfaff},
+ {0xfe10, 0xfe19},
+ {0xfe30, 0xfe52},
+ {0xfe54, 0xfe66},
+ {0xfe68, 0xfe6b},
+ {0xff01, 0xff60},
+ {0xffe0, 0xffe6},
+ {0x16fe0, 0x16fe0},
+ {0x17000, 0x187ec},
+ {0x18800, 0x18af2},
+ {0x1b000, 0x1b001},
+ {0x1f004, 0x1f004},
+ {0x1f0cf, 0x1f0cf},
+ {0x1f18e, 0x1f18e},
+ {0x1f191, 0x1f19a},
+ {0x1f200, 0x1f202},
+ {0x1f210, 0x1f23b},
+ {0x1f240, 0x1f248},
+ {0x1f250, 0x1f251},
+ {0x1f300, 0x1f320},
+ {0x1f32d, 0x1f335},
+ {0x1f337, 0x1f37c},
+ {0x1f37e, 0x1f393},
+ {0x1f3a0, 0x1f3ca},
+ {0x1f3cf, 0x1f3d3},
+ {0x1f3e0, 0x1f3f0},
+ {0x1f3f4, 0x1f3f4},
+ {0x1f3f8, 0x1f43e},
+ {0x1f440, 0x1f440},
+ {0x1f442, 0x1f4fc},
+ {0x1f4ff, 0x1f53d},
+ {0x1f54b, 0x1f54e},
+ {0x1f550, 0x1f567},
+ {0x1f57a, 0x1f57a},
+ {0x1f595, 0x1f596},
+ {0x1f5a4, 0x1f5a4},
+ {0x1f5fb, 0x1f64f},
+ {0x1f680, 0x1f6c5},
+ {0x1f6cc, 0x1f6cc},
+ {0x1f6d0, 0x1f6d2},
+ {0x1f6eb, 0x1f6ec},
+ {0x1f6f4, 0x1f6f6},
+ {0x1f910, 0x1f91e},
+ {0x1f920, 0x1f927},
+ {0x1f930, 0x1f930},
+ {0x1f933, 0x1f93e},
+ {0x1f940, 0x1f94b},
+ {0x1f950, 0x1f95e},
+ {0x1f980, 0x1f991},
+ {0x1f9c0, 0x1f9c0},
+ {0x20000, 0x2fffd},
+ {0x30000, 0x3fffd},
+};
+
+static const struct wcwidth9_interval wcwidth9_ambiguous[] = {
+ {0x00a1, 0x00a1},
+ {0x00a4, 0x00a4},
+ {0x00a7, 0x00a8},
+ {0x00aa, 0x00aa},
+ {0x00ad, 0x00ae},
+ {0x00b0, 0x00b4},
+ {0x00b6, 0x00ba},
+ {0x00bc, 0x00bf},
+ {0x00c6, 0x00c6},
+ {0x00d0, 0x00d0},
+ {0x00d7, 0x00d8},
+ {0x00de, 0x00e1},
+ {0x00e6, 0x00e6},
+ {0x00e8, 0x00ea},
+ {0x00ec, 0x00ed},
+ {0x00f0, 0x00f0},
+ {0x00f2, 0x00f3},
+ {0x00f7, 0x00fa},
+ {0x00fc, 0x00fc},
+ {0x00fe, 0x00fe},
+ {0x0101, 0x0101},
+ {0x0111, 0x0111},
+ {0x0113, 0x0113},
+ {0x011b, 0x011b},
+ {0x0126, 0x0127},
+ {0x012b, 0x012b},
+ {0x0131, 0x0133},
+ {0x0138, 0x0138},
+ {0x013f, 0x0142},
+ {0x0144, 0x0144},
+ {0x0148, 0x014b},
+ {0x014d, 0x014d},
+ {0x0152, 0x0153},
+ {0x0166, 0x0167},
+ {0x016b, 0x016b},
+ {0x01ce, 0x01ce},
+ {0x01d0, 0x01d0},
+ {0x01d2, 0x01d2},
+ {0x01d4, 0x01d4},
+ {0x01d6, 0x01d6},
+ {0x01d8, 0x01d8},
+ {0x01da, 0x01da},
+ {0x01dc, 0x01dc},
+ {0x0251, 0x0251},
+ {0x0261, 0x0261},
+ {0x02c4, 0x02c4},
+ {0x02c7, 0x02c7},
+ {0x02c9, 0x02cb},
+ {0x02cd, 0x02cd},
+ {0x02d0, 0x02d0},
+ {0x02d8, 0x02db},
+ {0x02dd, 0x02dd},
+ {0x02df, 0x02df},
+ {0x0300, 0x036f},
+ {0x0391, 0x03a1},
+ {0x03a3, 0x03a9},
+ {0x03b1, 0x03c1},
+ {0x03c3, 0x03c9},
+ {0x0401, 0x0401},
+ {0x0410, 0x044f},
+ {0x0451, 0x0451},
+ {0x2010, 0x2010},
+ {0x2013, 0x2016},
+ {0x2018, 0x2019},
+ {0x201c, 0x201d},
+ {0x2020, 0x2022},
+ {0x2024, 0x2027},
+ {0x2030, 0x2030},
+ {0x2032, 0x2033},
+ {0x2035, 0x2035},
+ {0x203b, 0x203b},
+ {0x203e, 0x203e},
+ {0x2074, 0x2074},
+ {0x207f, 0x207f},
+ {0x2081, 0x2084},
+ {0x20ac, 0x20ac},
+ {0x2103, 0x2103},
+ {0x2105, 0x2105},
+ {0x2109, 0x2109},
+ {0x2113, 0x2113},
+ {0x2116, 0x2116},
+ {0x2121, 0x2122},
+ {0x2126, 0x2126},
+ {0x212b, 0x212b},
+ {0x2153, 0x2154},
+ {0x215b, 0x215e},
+ {0x2160, 0x216b},
+ {0x2170, 0x2179},
+ {0x2189, 0x2189},
+ {0x2190, 0x2199},
+ {0x21b8, 0x21b9},
+ {0x21d2, 0x21d2},
+ {0x21d4, 0x21d4},
+ {0x21e7, 0x21e7},
+ {0x2200, 0x2200},
+ {0x2202, 0x2203},
+ {0x2207, 0x2208},
+ {0x220b, 0x220b},
+ {0x220f, 0x220f},
+ {0x2211, 0x2211},
+ {0x2215, 0x2215},
+ {0x221a, 0x221a},
+ {0x221d, 0x2220},
+ {0x2223, 0x2223},
+ {0x2225, 0x2225},
+ {0x2227, 0x222c},
+ {0x222e, 0x222e},
+ {0x2234, 0x2237},
+ {0x223c, 0x223d},
+ {0x2248, 0x2248},
+ {0x224c, 0x224c},
+ {0x2252, 0x2252},
+ {0x2260, 0x2261},
+ {0x2264, 0x2267},
+ {0x226a, 0x226b},
+ {0x226e, 0x226f},
+ {0x2282, 0x2283},
+ {0x2286, 0x2287},
+ {0x2295, 0x2295},
+ {0x2299, 0x2299},
+ {0x22a5, 0x22a5},
+ {0x22bf, 0x22bf},
+ {0x2312, 0x2312},
+ {0x2460, 0x24e9},
+ {0x24eb, 0x254b},
+ {0x2550, 0x2573},
+ {0x2580, 0x258f},
+ {0x2592, 0x2595},
+ {0x25a0, 0x25a1},
+ {0x25a3, 0x25a9},
+ {0x25b2, 0x25b3},
+ {0x25b6, 0x25b7},
+ {0x25bc, 0x25bd},
+ {0x25c0, 0x25c1},
+ {0x25c6, 0x25c8},
+ {0x25cb, 0x25cb},
+ {0x25ce, 0x25d1},
+ {0x25e2, 0x25e5},
+ {0x25ef, 0x25ef},
+ {0x2605, 0x2606},
+ {0x2609, 0x2609},
+ {0x260e, 0x260f},
+ {0x261c, 0x261c},
+ {0x261e, 0x261e},
+ {0x2640, 0x2640},
+ {0x2642, 0x2642},
+ {0x2660, 0x2661},
+ {0x2663, 0x2665},
+ {0x2667, 0x266a},
+ {0x266c, 0x266d},
+ {0x266f, 0x266f},
+ {0x269e, 0x269f},
+ {0x26bf, 0x26bf},
+ {0x26c6, 0x26cd},
+ {0x26cf, 0x26d3},
+ {0x26d5, 0x26e1},
+ {0x26e3, 0x26e3},
+ {0x26e8, 0x26e9},
+ {0x26eb, 0x26f1},
+ {0x26f4, 0x26f4},
+ {0x26f6, 0x26f9},
+ {0x26fb, 0x26fc},
+ {0x26fe, 0x26ff},
+ {0x273d, 0x273d},
+ {0x2776, 0x277f},
+ {0x2b56, 0x2b59},
+ {0x3248, 0x324f},
+ {0xe000, 0xf8ff},
+ {0xfe00, 0xfe0f},
+ {0xfffd, 0xfffd},
+ {0x1f100, 0x1f10a},
+ {0x1f110, 0x1f12d},
+ {0x1f130, 0x1f169},
+ {0x1f170, 0x1f18d},
+ {0x1f18f, 0x1f190},
+ {0x1f19b, 0x1f1ac},
+ {0xe0100, 0xe01ef},
+ {0xf0000, 0xffffd},
+ {0x100000, 0x10fffd},
+};
+
+static const struct wcwidth9_interval wcwidth9_emoji_width[] = {
+ {0x1f1e6, 0x1f1ff},
+ {0x1f321, 0x1f321},
+ {0x1f324, 0x1f32c},
+ {0x1f336, 0x1f336},
+ {0x1f37d, 0x1f37d},
+ {0x1f396, 0x1f397},
+ {0x1f399, 0x1f39b},
+ {0x1f39e, 0x1f39f},
+ {0x1f3cb, 0x1f3ce},
+ {0x1f3d4, 0x1f3df},
+ {0x1f3f3, 0x1f3f5},
+ {0x1f3f7, 0x1f3f7},
+ {0x1f43f, 0x1f43f},
+ {0x1f441, 0x1f441},
+ {0x1f4fd, 0x1f4fd},
+ {0x1f549, 0x1f54a},
+ {0x1f56f, 0x1f570},
+ {0x1f573, 0x1f579},
+ {0x1f587, 0x1f587},
+ {0x1f58a, 0x1f58d},
+ {0x1f590, 0x1f590},
+ {0x1f5a5, 0x1f5a5},
+ {0x1f5a8, 0x1f5a8},
+ {0x1f5b1, 0x1f5b2},
+ {0x1f5bc, 0x1f5bc},
+ {0x1f5c2, 0x1f5c4},
+ {0x1f5d1, 0x1f5d3},
+ {0x1f5dc, 0x1f5de},
+ {0x1f5e1, 0x1f5e1},
+ {0x1f5e3, 0x1f5e3},
+ {0x1f5e8, 0x1f5e8},
+ {0x1f5ef, 0x1f5ef},
+ {0x1f5f3, 0x1f5f3},
+ {0x1f5fa, 0x1f5fa},
+ {0x1f6cb, 0x1f6cf},
+ {0x1f6e0, 0x1f6e5},
+ {0x1f6e9, 0x1f6e9},
+ {0x1f6f0, 0x1f6f0},
+ {0x1f6f3, 0x1f6f3},
+};
+
+static const struct wcwidth9_interval wcwidth9_not_assigned[] = {
+ {0x0378, 0x0379},
+ {0x0380, 0x0383},
+ {0x038b, 0x038b},
+ {0x038d, 0x038d},
+ {0x03a2, 0x03a2},
+ {0x0530, 0x0530},
+ {0x0557, 0x0558},
+ {0x0560, 0x0560},
+ {0x0588, 0x0588},
+ {0x058b, 0x058c},
+ {0x0590, 0x0590},
+ {0x05c8, 0x05cf},
+ {0x05eb, 0x05ef},
+ {0x05f5, 0x05ff},
+ {0x061d, 0x061d},
+ {0x070e, 0x070e},
+ {0x074b, 0x074c},
+ {0x07b2, 0x07bf},
+ {0x07fb, 0x07ff},
+ {0x082e, 0x082f},
+ {0x083f, 0x083f},
+ {0x085c, 0x085d},
+ {0x085f, 0x089f},
+ {0x08b5, 0x08b5},
+ {0x08be, 0x08d3},
+ {0x0984, 0x0984},
+ {0x098d, 0x098e},
+ {0x0991, 0x0992},
+ {0x09a9, 0x09a9},
+ {0x09b1, 0x09b1},
+ {0x09b3, 0x09b5},
+ {0x09ba, 0x09bb},
+ {0x09c5, 0x09c6},
+ {0x09c9, 0x09ca},
+ {0x09cf, 0x09d6},
+ {0x09d8, 0x09db},
+ {0x09de, 0x09de},
+ {0x09e4, 0x09e5},
+ {0x09fc, 0x0a00},
+ {0x0a04, 0x0a04},
+ {0x0a0b, 0x0a0e},
+ {0x0a11, 0x0a12},
+ {0x0a29, 0x0a29},
+ {0x0a31, 0x0a31},
+ {0x0a34, 0x0a34},
+ {0x0a37, 0x0a37},
+ {0x0a3a, 0x0a3b},
+ {0x0a3d, 0x0a3d},
+ {0x0a43, 0x0a46},
+ {0x0a49, 0x0a4a},
+ {0x0a4e, 0x0a50},
+ {0x0a52, 0x0a58},
+ {0x0a5d, 0x0a5d},
+ {0x0a5f, 0x0a65},
+ {0x0a76, 0x0a80},
+ {0x0a84, 0x0a84},
+ {0x0a8e, 0x0a8e},
+ {0x0a92, 0x0a92},
+ {0x0aa9, 0x0aa9},
+ {0x0ab1, 0x0ab1},
+ {0x0ab4, 0x0ab4},
+ {0x0aba, 0x0abb},
+ {0x0ac6, 0x0ac6},
+ {0x0aca, 0x0aca},
+ {0x0ace, 0x0acf},
+ {0x0ad1, 0x0adf},
+ {0x0ae4, 0x0ae5},
+ {0x0af2, 0x0af8},
+ {0x0afa, 0x0b00},
+ {0x0b04, 0x0b04},
+ {0x0b0d, 0x0b0e},
+ {0x0b11, 0x0b12},
+ {0x0b29, 0x0b29},
+ {0x0b31, 0x0b31},
+ {0x0b34, 0x0b34},
+ {0x0b3a, 0x0b3b},
+ {0x0b45, 0x0b46},
+ {0x0b49, 0x0b4a},
+ {0x0b4e, 0x0b55},
+ {0x0b58, 0x0b5b},
+ {0x0b5e, 0x0b5e},
+ {0x0b64, 0x0b65},
+ {0x0b78, 0x0b81},
+ {0x0b84, 0x0b84},
+ {0x0b8b, 0x0b8d},
+ {0x0b91, 0x0b91},
+ {0x0b96, 0x0b98},
+ {0x0b9b, 0x0b9b},
+ {0x0b9d, 0x0b9d},
+ {0x0ba0, 0x0ba2},
+ {0x0ba5, 0x0ba7},
+ {0x0bab, 0x0bad},
+ {0x0bba, 0x0bbd},
+ {0x0bc3, 0x0bc5},
+ {0x0bc9, 0x0bc9},
+ {0x0bce, 0x0bcf},
+ {0x0bd1, 0x0bd6},
+ {0x0bd8, 0x0be5},
+ {0x0bfb, 0x0bff},
+ {0x0c04, 0x0c04},
+ {0x0c0d, 0x0c0d},
+ {0x0c11, 0x0c11},
+ {0x0c29, 0x0c29},
+ {0x0c3a, 0x0c3c},
+ {0x0c45, 0x0c45},
+ {0x0c49, 0x0c49},
+ {0x0c4e, 0x0c54},
+ {0x0c57, 0x0c57},
+ {0x0c5b, 0x0c5f},
+ {0x0c64, 0x0c65},
+ {0x0c70, 0x0c77},
+ {0x0c84, 0x0c84},
+ {0x0c8d, 0x0c8d},
+ {0x0c91, 0x0c91},
+ {0x0ca9, 0x0ca9},
+ {0x0cb4, 0x0cb4},
+ {0x0cba, 0x0cbb},
+ {0x0cc5, 0x0cc5},
+ {0x0cc9, 0x0cc9},
+ {0x0cce, 0x0cd4},
+ {0x0cd7, 0x0cdd},
+ {0x0cdf, 0x0cdf},
+ {0x0ce4, 0x0ce5},
+ {0x0cf0, 0x0cf0},
+ {0x0cf3, 0x0d00},
+ {0x0d04, 0x0d04},
+ {0x0d0d, 0x0d0d},
+ {0x0d11, 0x0d11},
+ {0x0d3b, 0x0d3c},
+ {0x0d45, 0x0d45},
+ {0x0d49, 0x0d49},
+ {0x0d50, 0x0d53},
+ {0x0d64, 0x0d65},
+ {0x0d80, 0x0d81},
+ {0x0d84, 0x0d84},
+ {0x0d97, 0x0d99},
+ {0x0db2, 0x0db2},
+ {0x0dbc, 0x0dbc},
+ {0x0dbe, 0x0dbf},
+ {0x0dc7, 0x0dc9},
+ {0x0dcb, 0x0dce},
+ {0x0dd5, 0x0dd5},
+ {0x0dd7, 0x0dd7},
+ {0x0de0, 0x0de5},
+ {0x0df0, 0x0df1},
+ {0x0df5, 0x0e00},
+ {0x0e3b, 0x0e3e},
+ {0x0e5c, 0x0e80},
+ {0x0e83, 0x0e83},
+ {0x0e85, 0x0e86},
+ {0x0e89, 0x0e89},
+ {0x0e8b, 0x0e8c},
+ {0x0e8e, 0x0e93},
+ {0x0e98, 0x0e98},
+ {0x0ea0, 0x0ea0},
+ {0x0ea4, 0x0ea4},
+ {0x0ea6, 0x0ea6},
+ {0x0ea8, 0x0ea9},
+ {0x0eac, 0x0eac},
+ {0x0eba, 0x0eba},
+ {0x0ebe, 0x0ebf},
+ {0x0ec5, 0x0ec5},
+ {0x0ec7, 0x0ec7},
+ {0x0ece, 0x0ecf},
+ {0x0eda, 0x0edb},
+ {0x0ee0, 0x0eff},
+ {0x0f48, 0x0f48},
+ {0x0f6d, 0x0f70},
+ {0x0f98, 0x0f98},
+ {0x0fbd, 0x0fbd},
+ {0x0fcd, 0x0fcd},
+ {0x0fdb, 0x0fff},
+ {0x10c6, 0x10c6},
+ {0x10c8, 0x10cc},
+ {0x10ce, 0x10cf},
+ {0x1249, 0x1249},
+ {0x124e, 0x124f},
+ {0x1257, 0x1257},
+ {0x1259, 0x1259},
+ {0x125e, 0x125f},
+ {0x1289, 0x1289},
+ {0x128e, 0x128f},
+ {0x12b1, 0x12b1},
+ {0x12b6, 0x12b7},
+ {0x12bf, 0x12bf},
+ {0x12c1, 0x12c1},
+ {0x12c6, 0x12c7},
+ {0x12d7, 0x12d7},
+ {0x1311, 0x1311},
+ {0x1316, 0x1317},
+ {0x135b, 0x135c},
+ {0x137d, 0x137f},
+ {0x139a, 0x139f},
+ {0x13f6, 0x13f7},
+ {0x13fe, 0x13ff},
+ {0x169d, 0x169f},
+ {0x16f9, 0x16ff},
+ {0x170d, 0x170d},
+ {0x1715, 0x171f},
+ {0x1737, 0x173f},
+ {0x1754, 0x175f},
+ {0x176d, 0x176d},
+ {0x1771, 0x1771},
+ {0x1774, 0x177f},
+ {0x17de, 0x17df},
+ {0x17ea, 0x17ef},
+ {0x17fa, 0x17ff},
+ {0x180f, 0x180f},
+ {0x181a, 0x181f},
+ {0x1878, 0x187f},
+ {0x18ab, 0x18af},
+ {0x18f6, 0x18ff},
+ {0x191f, 0x191f},
+ {0x192c, 0x192f},
+ {0x193c, 0x193f},
+ {0x1941, 0x1943},
+ {0x196e, 0x196f},
+ {0x1975, 0x197f},
+ {0x19ac, 0x19af},
+ {0x19ca, 0x19cf},
+ {0x19db, 0x19dd},
+ {0x1a1c, 0x1a1d},
+ {0x1a5f, 0x1a5f},
+ {0x1a7d, 0x1a7e},
+ {0x1a8a, 0x1a8f},
+ {0x1a9a, 0x1a9f},
+ {0x1aae, 0x1aaf},
+ {0x1abf, 0x1aff},
+ {0x1b4c, 0x1b4f},
+ {0x1b7d, 0x1b7f},
+ {0x1bf4, 0x1bfb},
+ {0x1c38, 0x1c3a},
+ {0x1c4a, 0x1c4c},
+ {0x1c89, 0x1cbf},
+ {0x1cc8, 0x1ccf},
+ {0x1cf7, 0x1cf7},
+ {0x1cfa, 0x1cff},
+ {0x1df6, 0x1dfa},
+ {0x1f16, 0x1f17},
+ {0x1f1e, 0x1f1f},
+ {0x1f46, 0x1f47},
+ {0x1f4e, 0x1f4f},
+ {0x1f58, 0x1f58},
+ {0x1f5a, 0x1f5a},
+ {0x1f5c, 0x1f5c},
+ {0x1f5e, 0x1f5e},
+ {0x1f7e, 0x1f7f},
+ {0x1fb5, 0x1fb5},
+ {0x1fc5, 0x1fc5},
+ {0x1fd4, 0x1fd5},
+ {0x1fdc, 0x1fdc},
+ {0x1ff0, 0x1ff1},
+ {0x1ff5, 0x1ff5},
+ {0x1fff, 0x1fff},
+ {0x2065, 0x2065},
+ {0x2072, 0x2073},
+ {0x208f, 0x208f},
+ {0x209d, 0x209f},
+ {0x20bf, 0x20cf},
+ {0x20f1, 0x20ff},
+ {0x218c, 0x218f},
+ {0x23ff, 0x23ff},
+ {0x2427, 0x243f},
+ {0x244b, 0x245f},
+ {0x2b74, 0x2b75},
+ {0x2b96, 0x2b97},
+ {0x2bba, 0x2bbc},
+ {0x2bc9, 0x2bc9},
+ {0x2bd2, 0x2beb},
+ {0x2bf0, 0x2bff},
+ {0x2c2f, 0x2c2f},
+ {0x2c5f, 0x2c5f},
+ {0x2cf4, 0x2cf8},
+ {0x2d26, 0x2d26},
+ {0x2d28, 0x2d2c},
+ {0x2d2e, 0x2d2f},
+ {0x2d68, 0x2d6e},
+ {0x2d71, 0x2d7e},
+ {0x2d97, 0x2d9f},
+ {0x2da7, 0x2da7},
+ {0x2daf, 0x2daf},
+ {0x2db7, 0x2db7},
+ {0x2dbf, 0x2dbf},
+ {0x2dc7, 0x2dc7},
+ {0x2dcf, 0x2dcf},
+ {0x2dd7, 0x2dd7},
+ {0x2ddf, 0x2ddf},
+ {0x2e45, 0x2e7f},
+ {0x2e9a, 0x2e9a},
+ {0x2ef4, 0x2eff},
+ {0x2fd6, 0x2fef},
+ {0x2ffc, 0x2fff},
+ {0x3040, 0x3040},
+ {0x3097, 0x3098},
+ {0x3100, 0x3104},
+ {0x312e, 0x3130},
+ {0x318f, 0x318f},
+ {0x31bb, 0x31bf},
+ {0x31e4, 0x31ef},
+ {0x321f, 0x321f},
+ {0x32ff, 0x32ff},
+ {0x4db6, 0x4dbf},
+ {0x9fd6, 0x9fff},
+ {0xa48d, 0xa48f},
+ {0xa4c7, 0xa4cf},
+ {0xa62c, 0xa63f},
+ {0xa6f8, 0xa6ff},
+ {0xa7af, 0xa7af},
+ {0xa7b8, 0xa7f6},
+ {0xa82c, 0xa82f},
+ {0xa83a, 0xa83f},
+ {0xa878, 0xa87f},
+ {0xa8c6, 0xa8cd},
+ {0xa8da, 0xa8df},
+ {0xa8fe, 0xa8ff},
+ {0xa954, 0xa95e},
+ {0xa97d, 0xa97f},
+ {0xa9ce, 0xa9ce},
+ {0xa9da, 0xa9dd},
+ {0xa9ff, 0xa9ff},
+ {0xaa37, 0xaa3f},
+ {0xaa4e, 0xaa4f},
+ {0xaa5a, 0xaa5b},
+ {0xaac3, 0xaada},
+ {0xaaf7, 0xab00},
+ {0xab07, 0xab08},
+ {0xab0f, 0xab10},
+ {0xab17, 0xab1f},
+ {0xab27, 0xab27},
+ {0xab2f, 0xab2f},
+ {0xab66, 0xab6f},
+ {0xabee, 0xabef},
+ {0xabfa, 0xabff},
+ {0xd7a4, 0xd7af},
+ {0xd7c7, 0xd7ca},
+ {0xd7fc, 0xd7ff},
+ {0xfa6e, 0xfa6f},
+ {0xfada, 0xfaff},
+ {0xfb07, 0xfb12},
+ {0xfb18, 0xfb1c},
+ {0xfb37, 0xfb37},
+ {0xfb3d, 0xfb3d},
+ {0xfb3f, 0xfb3f},
+ {0xfb42, 0xfb42},
+ {0xfb45, 0xfb45},
+ {0xfbc2, 0xfbd2},
+ {0xfd40, 0xfd4f},
+ {0xfd90, 0xfd91},
+ {0xfdc8, 0xfdef},
+ {0xfdfe, 0xfdff},
+ {0xfe1a, 0xfe1f},
+ {0xfe53, 0xfe53},
+ {0xfe67, 0xfe67},
+ {0xfe6c, 0xfe6f},
+ {0xfe75, 0xfe75},
+ {0xfefd, 0xfefe},
+ {0xff00, 0xff00},
+ {0xffbf, 0xffc1},
+ {0xffc8, 0xffc9},
+ {0xffd0, 0xffd1},
+ {0xffd8, 0xffd9},
+ {0xffdd, 0xffdf},
+ {0xffe7, 0xffe7},
+ {0xffef, 0xfff8},
+ {0xfffe, 0xffff},
+ {0x1000c, 0x1000c},
+ {0x10027, 0x10027},
+ {0x1003b, 0x1003b},
+ {0x1003e, 0x1003e},
+ {0x1004e, 0x1004f},
+ {0x1005e, 0x1007f},
+ {0x100fb, 0x100ff},
+ {0x10103, 0x10106},
+ {0x10134, 0x10136},
+ {0x1018f, 0x1018f},
+ {0x1019c, 0x1019f},
+ {0x101a1, 0x101cf},
+ {0x101fe, 0x1027f},
+ {0x1029d, 0x1029f},
+ {0x102d1, 0x102df},
+ {0x102fc, 0x102ff},
+ {0x10324, 0x1032f},
+ {0x1034b, 0x1034f},
+ {0x1037b, 0x1037f},
+ {0x1039e, 0x1039e},
+ {0x103c4, 0x103c7},
+ {0x103d6, 0x103ff},
+ {0x1049e, 0x1049f},
+ {0x104aa, 0x104af},
+ {0x104d4, 0x104d7},
+ {0x104fc, 0x104ff},
+ {0x10528, 0x1052f},
+ {0x10564, 0x1056e},
+ {0x10570, 0x105ff},
+ {0x10737, 0x1073f},
+ {0x10756, 0x1075f},
+ {0x10768, 0x107ff},
+ {0x10806, 0x10807},
+ {0x10809, 0x10809},
+ {0x10836, 0x10836},
+ {0x10839, 0x1083b},
+ {0x1083d, 0x1083e},
+ {0x10856, 0x10856},
+ {0x1089f, 0x108a6},
+ {0x108b0, 0x108df},
+ {0x108f3, 0x108f3},
+ {0x108f6, 0x108fa},
+ {0x1091c, 0x1091e},
+ {0x1093a, 0x1093e},
+ {0x10940, 0x1097f},
+ {0x109b8, 0x109bb},
+ {0x109d0, 0x109d1},
+ {0x10a04, 0x10a04},
+ {0x10a07, 0x10a0b},
+ {0x10a14, 0x10a14},
+ {0x10a18, 0x10a18},
+ {0x10a34, 0x10a37},
+ {0x10a3b, 0x10a3e},
+ {0x10a48, 0x10a4f},
+ {0x10a59, 0x10a5f},
+ {0x10aa0, 0x10abf},
+ {0x10ae7, 0x10aea},
+ {0x10af7, 0x10aff},
+ {0x10b36, 0x10b38},
+ {0x10b56, 0x10b57},
+ {0x10b73, 0x10b77},
+ {0x10b92, 0x10b98},
+ {0x10b9d, 0x10ba8},
+ {0x10bb0, 0x10bff},
+ {0x10c49, 0x10c7f},
+ {0x10cb3, 0x10cbf},
+ {0x10cf3, 0x10cf9},
+ {0x10d00, 0x10e5f},
+ {0x10e7f, 0x10fff},
+ {0x1104e, 0x11051},
+ {0x11070, 0x1107e},
+ {0x110c2, 0x110cf},
+ {0x110e9, 0x110ef},
+ {0x110fa, 0x110ff},
+ {0x11135, 0x11135},
+ {0x11144, 0x1114f},
+ {0x11177, 0x1117f},
+ {0x111ce, 0x111cf},
+ {0x111e0, 0x111e0},
+ {0x111f5, 0x111ff},
+ {0x11212, 0x11212},
+ {0x1123f, 0x1127f},
+ {0x11287, 0x11287},
+ {0x11289, 0x11289},
+ {0x1128e, 0x1128e},
+ {0x1129e, 0x1129e},
+ {0x112aa, 0x112af},
+ {0x112eb, 0x112ef},
+ {0x112fa, 0x112ff},
+ {0x11304, 0x11304},
+ {0x1130d, 0x1130e},
+ {0x11311, 0x11312},
+ {0x11329, 0x11329},
+ {0x11331, 0x11331},
+ {0x11334, 0x11334},
+ {0x1133a, 0x1133b},
+ {0x11345, 0x11346},
+ {0x11349, 0x1134a},
+ {0x1134e, 0x1134f},
+ {0x11351, 0x11356},
+ {0x11358, 0x1135c},
+ {0x11364, 0x11365},
+ {0x1136d, 0x1136f},
+ {0x11375, 0x113ff},
+ {0x1145a, 0x1145a},
+ {0x1145c, 0x1145c},
+ {0x1145e, 0x1147f},
+ {0x114c8, 0x114cf},
+ {0x114da, 0x1157f},
+ {0x115b6, 0x115b7},
+ {0x115de, 0x115ff},
+ {0x11645, 0x1164f},
+ {0x1165a, 0x1165f},
+ {0x1166d, 0x1167f},
+ {0x116b8, 0x116bf},
+ {0x116ca, 0x116ff},
+ {0x1171a, 0x1171c},
+ {0x1172c, 0x1172f},
+ {0x11740, 0x1189f},
+ {0x118f3, 0x118fe},
+ {0x11900, 0x11abf},
+ {0x11af9, 0x11bff},
+ {0x11c09, 0x11c09},
+ {0x11c37, 0x11c37},
+ {0x11c46, 0x11c4f},
+ {0x11c6d, 0x11c6f},
+ {0x11c90, 0x11c91},
+ {0x11ca8, 0x11ca8},
+ {0x11cb7, 0x11fff},
+ {0x1239a, 0x123ff},
+ {0x1246f, 0x1246f},
+ {0x12475, 0x1247f},
+ {0x12544, 0x12fff},
+ {0x1342f, 0x143ff},
+ {0x14647, 0x167ff},
+ {0x16a39, 0x16a3f},
+ {0x16a5f, 0x16a5f},
+ {0x16a6a, 0x16a6d},
+ {0x16a70, 0x16acf},
+ {0x16aee, 0x16aef},
+ {0x16af6, 0x16aff},
+ {0x16b46, 0x16b4f},
+ {0x16b5a, 0x16b5a},
+ {0x16b62, 0x16b62},
+ {0x16b78, 0x16b7c},
+ {0x16b90, 0x16eff},
+ {0x16f45, 0x16f4f},
+ {0x16f7f, 0x16f8e},
+ {0x16fa0, 0x16fdf},
+ {0x16fe1, 0x16fff},
+ {0x187ed, 0x187ff},
+ {0x18af3, 0x1afff},
+ {0x1b002, 0x1bbff},
+ {0x1bc6b, 0x1bc6f},
+ {0x1bc7d, 0x1bc7f},
+ {0x1bc89, 0x1bc8f},
+ {0x1bc9a, 0x1bc9b},
+ {0x1bca4, 0x1cfff},
+ {0x1d0f6, 0x1d0ff},
+ {0x1d127, 0x1d128},
+ {0x1d1e9, 0x1d1ff},
+ {0x1d246, 0x1d2ff},
+ {0x1d357, 0x1d35f},
+ {0x1d372, 0x1d3ff},
+ {0x1d455, 0x1d455},
+ {0x1d49d, 0x1d49d},
+ {0x1d4a0, 0x1d4a1},
+ {0x1d4a3, 0x1d4a4},
+ {0x1d4a7, 0x1d4a8},
+ {0x1d4ad, 0x1d4ad},
+ {0x1d4ba, 0x1d4ba},
+ {0x1d4bc, 0x1d4bc},
+ {0x1d4c4, 0x1d4c4},
+ {0x1d506, 0x1d506},
+ {0x1d50b, 0x1d50c},
+ {0x1d515, 0x1d515},
+ {0x1d51d, 0x1d51d},
+ {0x1d53a, 0x1d53a},
+ {0x1d53f, 0x1d53f},
+ {0x1d545, 0x1d545},
+ {0x1d547, 0x1d549},
+ {0x1d551, 0x1d551},
+ {0x1d6a6, 0x1d6a7},
+ {0x1d7cc, 0x1d7cd},
+ {0x1da8c, 0x1da9a},
+ {0x1daa0, 0x1daa0},
+ {0x1dab0, 0x1dfff},
+ {0x1e007, 0x1e007},
+ {0x1e019, 0x1e01a},
+ {0x1e022, 0x1e022},
+ {0x1e025, 0x1e025},
+ {0x1e02b, 0x1e7ff},
+ {0x1e8c5, 0x1e8c6},
+ {0x1e8d7, 0x1e8ff},
+ {0x1e94b, 0x1e94f},
+ {0x1e95a, 0x1e95d},
+ {0x1e960, 0x1edff},
+ {0x1ee04, 0x1ee04},
+ {0x1ee20, 0x1ee20},
+ {0x1ee23, 0x1ee23},
+ {0x1ee25, 0x1ee26},
+ {0x1ee28, 0x1ee28},
+ {0x1ee33, 0x1ee33},
+ {0x1ee38, 0x1ee38},
+ {0x1ee3a, 0x1ee3a},
+ {0x1ee3c, 0x1ee41},
+ {0x1ee43, 0x1ee46},
+ {0x1ee48, 0x1ee48},
+ {0x1ee4a, 0x1ee4a},
+ {0x1ee4c, 0x1ee4c},
+ {0x1ee50, 0x1ee50},
+ {0x1ee53, 0x1ee53},
+ {0x1ee55, 0x1ee56},
+ {0x1ee58, 0x1ee58},
+ {0x1ee5a, 0x1ee5a},
+ {0x1ee5c, 0x1ee5c},
+ {0x1ee5e, 0x1ee5e},
+ {0x1ee60, 0x1ee60},
+ {0x1ee63, 0x1ee63},
+ {0x1ee65, 0x1ee66},
+ {0x1ee6b, 0x1ee6b},
+ {0x1ee73, 0x1ee73},
+ {0x1ee78, 0x1ee78},
+ {0x1ee7d, 0x1ee7d},
+ {0x1ee7f, 0x1ee7f},
+ {0x1ee8a, 0x1ee8a},
+ {0x1ee9c, 0x1eea0},
+ {0x1eea4, 0x1eea4},
+ {0x1eeaa, 0x1eeaa},
+ {0x1eebc, 0x1eeef},
+ {0x1eef2, 0x1efff},
+ {0x1f02c, 0x1f02f},
+ {0x1f094, 0x1f09f},
+ {0x1f0af, 0x1f0b0},
+ {0x1f0c0, 0x1f0c0},
+ {0x1f0d0, 0x1f0d0},
+ {0x1f0f6, 0x1f0ff},
+ {0x1f10d, 0x1f10f},
+ {0x1f12f, 0x1f12f},
+ {0x1f16c, 0x1f16f},
+ {0x1f1ad, 0x1f1e5},
+ {0x1f203, 0x1f20f},
+ {0x1f23c, 0x1f23f},
+ {0x1f249, 0x1f24f},
+ {0x1f252, 0x1f2ff},
+ {0x1f6d3, 0x1f6df},
+ {0x1f6ed, 0x1f6ef},
+ {0x1f6f7, 0x1f6ff},
+ {0x1f774, 0x1f77f},
+ {0x1f7d5, 0x1f7ff},
+ {0x1f80c, 0x1f80f},
+ {0x1f848, 0x1f84f},
+ {0x1f85a, 0x1f85f},
+ {0x1f888, 0x1f88f},
+ {0x1f8ae, 0x1f90f},
+ {0x1f91f, 0x1f91f},
+ {0x1f928, 0x1f92f},
+ {0x1f931, 0x1f932},
+ {0x1f93f, 0x1f93f},
+ {0x1f94c, 0x1f94f},
+ {0x1f95f, 0x1f97f},
+ {0x1f992, 0x1f9bf},
+ {0x1f9c1, 0x1ffff},
+ {0x2a6d7, 0x2a6ff},
+ {0x2b735, 0x2b73f},
+ {0x2b81e, 0x2b81f},
+ {0x2cea2, 0x2f7ff},
+ {0x2fa1e, 0xe0000},
+ {0xe0002, 0xe001f},
+ {0xe0080, 0xe00ff},
+ {0xe01f0, 0xeffff},
+ {0xffffe, 0xfffff},
+};
+
+#define WCWIDTH9_ARRAY_SIZE(arr) ((sizeof(arr)/sizeof((arr)[0])) / ((size_t)(!(sizeof(arr) % sizeof((arr)[0])))))
+
+static inline bool wcwidth9_intable(const struct wcwidth9_interval *table, size_t n_items, int c) {
+ int mid, bot, top;
+
+ if (c < table[0].first) {
+ return false;
+ }
+
+ bot = 0;
+ top = (int)(n_items - 1);
+ while (top >= bot) {
+ mid = (bot + top) / 2;
+
+ if (table[mid].last < c) {
+ bot = mid + 1;
+ } else if (table[mid].first > c) {
+ top = mid - 1;
+ } else {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static inline int wcwidth9(int c) {
+ if (c == 0) {
+ return 0;
+ }
+ if (c < 0|| c > 0x10ffff) {
+ return -1;
+ }
+
+ if (wcwidth9_intable(wcwidth9_nonprint, WCWIDTH9_ARRAY_SIZE(wcwidth9_nonprint), c)) {
+ return -1;
+ }
+
+ if (wcwidth9_intable(wcwidth9_combining, WCWIDTH9_ARRAY_SIZE(wcwidth9_combining), c)) {
+ return 0;
+ }
+
+ if (wcwidth9_intable(wcwidth9_not_assigned, WCWIDTH9_ARRAY_SIZE(wcwidth9_not_assigned), c)) {
+ return -1;
+ }
+
+ if (wcwidth9_intable(wcwidth9_private, WCWIDTH9_ARRAY_SIZE(wcwidth9_private), c)) {
+ return -3;
+ }
+
+ if (wcwidth9_intable(wcwidth9_ambiguous, WCWIDTH9_ARRAY_SIZE(wcwidth9_ambiguous), c)) {
+ return -2;
+ }
+
+ if (wcwidth9_intable(wcwidth9_doublewidth, WCWIDTH9_ARRAY_SIZE(wcwidth9_doublewidth), c)) {
+ return 2;
+ }
+
+ if (wcwidth9_intable(wcwidth9_emoji_width, WCWIDTH9_ARRAY_SIZE(wcwidth9_emoji_width), c)) {
+ return 2;
+ }
+
+ return 1;
+}
+
+#endif /* WCWIDTH9_H */
diff --git a/dotfiles/system/.zsh/modules/Src/zsh.h b/dotfiles/system/.zsh/modules/Src/zsh.h
new file mode 100644
index 0000000..8e7f20b
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/zsh.h
@@ -0,0 +1,3305 @@
+/*
+ * 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.
+ *
+ */
+
+/* A few typical macros */
+#define minimum(a,b) ((a) < (b) ? (a) : (b))
+
+/*
+ * Our longest integer type: will be a 64 bit either if long already is,
+ * or if we found some alternative such as long long.
+ */
+#ifdef ZSH_64_BIT_TYPE
+typedef ZSH_64_BIT_TYPE zlong;
+#if defined(ZLONG_IS_LONG_LONG) && defined(LLONG_MAX)
+#define ZLONG_MAX LLONG_MAX
+#else
+#ifdef ZLONG_IS_LONG_64
+#define ZLONG_MAX LONG_MAX
+#else
+/* umm... */
+#define ZLONG_MAX ((zlong)9223372036854775807)
+#endif
+#endif
+#ifdef ZSH_64_BIT_UTYPE
+typedef ZSH_64_BIT_UTYPE zulong;
+#else
+typedef unsigned zlong zulong;
+#endif
+#else
+typedef long zlong;
+typedef unsigned long zulong;
+#define ZLONG_MAX LONG_MAX
+#endif
+
+/*
+ * Work out how to define large integer constants that will fit
+ * in a zlong.
+ */
+#if defined(ZSH_64_BIT_TYPE) || defined(LONG_IS_64_BIT)
+/* We have some 64-bit type */
+#ifdef LONG_IS_64_BIT
+/* It's long */
+#define ZLONG_CONST(x) x ## l
+#else
+/* It's long long */
+#ifdef ZLONG_IS_LONG_LONG
+#define ZLONG_CONST(x) x ## ll
+#else
+/*
+ * There's some 64-bit type, but we don't know what it is.
+ * We'll just cast it and hope the compiler does the right thing.
+ */
+#define ZLONG_CONST(x) ((zlong)x)
+#endif
+#endif
+#else
+/* We're stuck with long */
+#define ZLONG_CONST(x) (x ## l)
+#endif
+
+/*
+ * Double float support requires 64-bit alignment, so if longs and
+ * pointers are less we need to pad out.
+ */
+#ifndef LONG_IS_64_BIT
+# define PAD_64_BIT 1
+#endif
+
+/* math.c */
+typedef struct {
+ union {
+ zlong l;
+ double d;
+ } u;
+ int type;
+} mnumber;
+
+#define MN_INTEGER 1 /* mnumber is integer */
+#define MN_FLOAT 2 /* mnumber is floating point */
+#define MN_UNSET 4 /* mnumber not yet retrieved */
+
+typedef struct mathfunc *MathFunc;
+typedef mnumber (*NumMathFunc)(char *, int, mnumber *, int);
+typedef mnumber (*StrMathFunc)(char *, char *, int);
+
+struct mathfunc {
+ MathFunc next;
+ char *name;
+ int flags; /* MFF_* flags defined below */
+ NumMathFunc nfunc;
+ StrMathFunc sfunc;
+ char *module;
+ int minargs;
+ int maxargs;
+ int funcid;
+};
+
+/* Math function takes a string argument */
+#define MFF_STR 1
+/* Math function has been loaded from library */
+#define MFF_ADDED 2
+/* Math function is implemented by a shell function */
+#define MFF_USERFUNC 4
+/* When autoloading, enable all features in module */
+#define MFF_AUTOALL 8
+
+
+#define NUMMATHFUNC(name, func, min, max, id) \
+ { NULL, name, 0, func, NULL, NULL, min, max, id }
+#define STRMATHFUNC(name, func, id) \
+ { NULL, name, MFF_STR, NULL, func, NULL, 0, 0, id }
+
+/* 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 "
+
+/* As specified in the standard (POSIX 2008) */
+
+#define DEFAULT_IFS_SH " \t\n"
+
+/*
+ * Character tokens.
+ * These should match the characters in ztokens, defined in lex.c
+ */
+#define Pound ((char) 0x84)
+#define String ((char) 0x85)
+#define Hat ((char) 0x86)
+#define Star ((char) 0x87)
+#define Inpar ((char) 0x88)
+#define Inparmath ((char) 0x89)
+#define Outpar ((char) 0x8a)
+#define Outparmath ((char) 0x8b)
+#define Qstring ((char) 0x8c)
+#define Equals ((char) 0x8d)
+#define Bar ((char) 0x8e)
+#define Inbrace ((char) 0x8f)
+#define Outbrace ((char) 0x90)
+#define Inbrack ((char) 0x91)
+#define Outbrack ((char) 0x92)
+#define Tick ((char) 0x93)
+#define Inang ((char) 0x94)
+#define Outang ((char) 0x95)
+#define OutangProc ((char) 0x96)
+#define Quest ((char) 0x97)
+#define Tilde ((char) 0x98)
+#define Qtick ((char) 0x99)
+#define Comma ((char) 0x9a)
+#define Dash ((char) 0x9b) /* Only in patterns */
+#define Bang ((char) 0x9c) /* Only in patterns */
+/*
+ * Marks the last of the group above.
+ * Remaining tokens are even more special.
+ */
+#define LAST_NORMAL_TOK Bang
+/*
+ * Null arguments: placeholders for single and double quotes
+ * and backslashes.
+ */
+#define Snull ((char) 0x9d)
+#define Dnull ((char) 0x9e)
+#define Bnull ((char) 0x9f)
+/*
+ * Backslash which will be returned to "\" instead of being stripped
+ * when we turn the string into a printable format.
+ */
+#define Bnullkeep ((char) 0xa0)
+/*
+ * Null argument that does not correspond to any character.
+ * This should be last as it does not appear in ztokens and
+ * is used to initialise the IMETA type in inittyptab().
+ */
+#define Nularg ((char) 0xa1)
+
+/*
+ * Take care to update the use of IMETA appropriately when adding
+ * tokens here.
+ */
+/*
+ * Marker is used in the following special circumstances:
+ * - In paramsubst for rc_expand_param.
+ * - In pattern character arrays as guaranteed not to mark a character in
+ * a string.
+ * - In assignments with the ASSPM_KEY_VALUE flag set in order to
+ * mark that there is a key / value pair following. If this
+ * comes from [key]=value the Marker is followed by a null;
+ * if from [key]+=value the Marker is followed by a '+' then a null.
+ * All the above are local uses --- any case where the Marker has
+ * escaped beyond the context in question is an error.
+ */
+#define Marker ((char) 0xa2)
+
+/* chars that need to be quoted if meant literally */
+
+#define SPECCHARS "#$^*()=|{}[]`<>?~;&\n\t \\\'\""
+
+/* chars that need to be quoted for pattern matching */
+
+#define PATCHARS "#^*()|[]<>?~\\"
+
+/*
+ * Check for a possibly tokenized dash.
+ *
+ * A dash only needs to be a token in a character range, [a-z], but
+ * it's difficult in general to ensure that. So it's turned into
+ * a token at the usual point in the lexer. However, we need
+ * to check for a literal dash at many points.
+ */
+#define IS_DASH(x) ((x) == '-' || (x) == Dash)
+
+/*
+ * Types of quote. This is used in various places, so care needs
+ * to be taken when changing them. (Oooh, don't you look surprised.)
+ * - Passed to quotestring() to indicate style. This is the ultimate
+ * destiny of most of the other uses of members of the enum.
+ * - In paramsubst(), to count q's in parameter substitution.
+ * - In the completion code, where we maintain a stack of quotation types.
+ */
+enum {
+ /*
+ * No quote. Not a valid quote, but useful in the substitution
+ * and completion code to indicate we're not doing any quoting.
+ */
+ QT_NONE,
+ /* Backslash: \ */
+ QT_BACKSLASH,
+ /* Single quote: ' */
+ QT_SINGLE,
+ /* Double quote: " */
+ QT_DOUBLE,
+ /* Print-style quote: $' */
+ QT_DOLLARS,
+ /*
+ * Backtick: `
+ * Not understood by many parts of the code; here for a convenience
+ * in those cases where we need to represent a complete set.
+ */
+ QT_BACKTICK,
+ /*
+ * Single quotes, but the default is not to quote unless necessary.
+ * This is only useful as an argument to quotestring().
+ */
+ QT_SINGLE_OPTIONAL,
+ /*
+ * Only quote pattern characters.
+ * ${(b)foo} guarantees that ${~foo} matches the string
+ * contained in foo.
+ */
+ QT_BACKSLASH_PATTERN,
+ /*
+ * As QT_BACKSLASH, but a NULL string is shown as ''.
+ */
+ QT_BACKSLASH_SHOWNULL,
+ /*
+ * Quoting as produced by quotedzputs(), used for human
+ * readability of parameter values.
+ */
+ QT_QUOTEDZPUTS
+};
+
+#define QT_IS_SINGLE(x) ((x) == QT_SINGLE || (x) == QT_SINGLE_OPTIONAL)
+
+/*
+ * Lexical tokens: unlike the character tokens above, these never
+ * appear in strings and don't necessarily represent a single character.
+ */
+
+enum lextok {
+ 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,
+ SEMIBAR,
+ DOUTBRACK,
+ STRING,
+ ENVSTRING, /* 35 */
+ ENVARRAY,
+ ENDINPUT,
+ LEXERR,
+
+ /* Tokens for reserved words */
+ BANG, /* ! */
+ DINBRACK, /* [[ */ /* 40 */
+ INBRACE, /* { */
+ OUTBRACE, /* } */
+ CASE, /* case */
+ COPROC, /* coproc */
+ DOLOOP, /* do */ /* 45 */
+ DONE, /* done */
+ ELIF, /* elif */
+ ELSE, /* else */
+ ZEND, /* end */
+ ESAC, /* esac */ /* 50 */
+ FI, /* fi */
+ FOR, /* for */
+ FOREACH, /* foreach */
+ FUNC, /* function */
+ IF, /* if */ /* 55 */
+ NOCORRECT, /* nocorrect */
+ REPEAT, /* repeat */
+ SELECT, /* select */
+ THEN, /* then */
+ TIME, /* time */ /* 60 */
+ UNTIL, /* until */
+ WHILE, /* while */
+ TYPESET /* typeset or similar */
+};
+
+/* 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 {
+ REDIR_WRITE, /* > */
+ REDIR_WRITENOW, /* >| */
+ REDIR_APP, /* >> */
+ REDIR_APPNOW, /* >>| */
+ REDIR_ERRWRITE, /* &>, >& */
+ REDIR_ERRWRITENOW, /* >&| */
+ REDIR_ERRAPP, /* >>& */
+ REDIR_ERRAPPNOW, /* >>&| */
+ REDIR_READWRITE, /* <> */
+ REDIR_READ, /* < */
+ REDIR_HEREDOC, /* << */
+ REDIR_HEREDOCDASH, /* <<- */
+ REDIR_HERESTR, /* <<< */
+ REDIR_MERGEIN, /* <&n */
+ REDIR_MERGEOUT, /* >&n */
+ REDIR_CLOSE, /* >&-, <&- */
+ REDIR_INPIPE, /* < <(...) */
+ REDIR_OUTPIPE /* > >(...) */
+};
+#define REDIR_TYPE_MASK (0x1f)
+/* Redir using {var} syntax */
+#define REDIR_VARID_MASK (0x20)
+/* Mark here-string that came from a here-document */
+#define REDIR_FROM_HEREDOC_MASK (0x40)
+
+#define IS_WRITE_FILE(X) ((X)>=REDIR_WRITE && (X)<=REDIR_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)>=REDIR_ERRWRITE && (X)<=REDIR_ERRAPPNOW)
+#define IS_READFD(X) (((X)>=REDIR_READWRITE && (X)<=REDIR_MERGEIN) || (X)==REDIR_INPIPE)
+#define IS_REDIROP(X) ((X)>=OUTANG && (X)<=TRINANG)
+
+/*
+ * Values for the fdtable array. They say under what circumstances
+ * the fd will be close. The fdtable is an unsigned char, so these are
+ * #define's rather than an enum.
+ */
+/* Entry not used. */
+#define FDT_UNUSED 0
+/*
+ * Entry used internally by the shell, should not be visible to other
+ * processes.
+ */
+#define FDT_INTERNAL 1
+/*
+ * Entry visible to other processes, for example created using
+ * the {varid}> file syntax.
+ */
+#define FDT_EXTERNAL 2
+/*
+ * Entry visible to other processes but controlled by a module.
+ * The difference from FDT_EXTERNAL is that closing this using
+ * standard fd syntax will fail as there is some tidying up that
+ * needs to be done by the module's own mechanism.
+ */
+#define FDT_MODULE 3
+/*
+ * Entry used by output from the XTRACE option.
+ */
+#define FDT_XTRACE 4
+/*
+ * Entry used for file locking.
+ */
+#define FDT_FLOCK 5
+/*
+ * As above, but the fd is not marked for closing on exec,
+ * so the shell can still exec the last process.
+ */
+#define FDT_FLOCK_EXEC 6
+/*
+ * Entry used by a process substition.
+ * This marker is not tested internally as we associated the file
+ * descriptor with a job for closing.
+ *
+ * This is not used unless PATH_DEV_FD is defined.
+ */
+#define FDT_PROC_SUBST 7
+/*
+ * Mask to get the basic FDT type.
+ */
+#define FDT_TYPE_MASK 15
+
+/*
+ * Bit flag that fd is saved for later restoration.
+ * Currently this is only use with FDT_INTERNAL. We use this fact so as
+ * not to have to mask checks against other types.
+ */
+#define FDT_SAVED_MASK 16
+
+/* 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. */
+#define INP_HISTCONT (1<<5) /* stack is continued from history expn. */
+#define INP_LINENO (1<<6) /* update line number */
+#define INP_APPEND (1<<7) /* Append new lines to allow backup */
+#define INP_RAW_KEEP (1<<8) /* Input needed in raw mode even if alias */
+
+/* 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
+
+/* Context to save and restore (bit fields) */
+enum {
+ /* History mechanism */
+ ZCONTEXT_HIST = (1<<0),
+ /* Lexical analyser */
+ ZCONTEXT_LEX = (1<<1),
+ /* Parser */
+ ZCONTEXT_PARSE = (1<<2)
+};
+
+/**************************/
+/* Abstract types for zsh */
+/**************************/
+
+typedef struct alias *Alias;
+typedef struct asgment *Asgment;
+typedef struct builtin *Builtin;
+typedef struct cmdnam *Cmdnam;
+typedef struct complist *Complist;
+typedef struct conddef *Conddef;
+typedef struct dirsav *Dirsav;
+typedef struct emulation_options *Emulation_options;
+typedef struct execcmd_params *Execcmd_params;
+typedef struct features *Features;
+typedef struct feature_enables *Feature_enables;
+typedef struct funcstack *Funcstack;
+typedef struct funcwrap *FuncWrap;
+typedef struct hashnode *HashNode;
+typedef struct hashtable *HashTable;
+typedef struct heap *Heap;
+typedef struct heapstack *Heapstack;
+typedef struct histent *Histent;
+typedef struct hookdef *Hookdef;
+typedef struct imatchdata *Imatchdata;
+typedef struct jobfile *Jobfile;
+typedef struct job *Job;
+typedef struct linkedmod *Linkedmod;
+typedef struct linknode *LinkNode;
+typedef union linkroot *LinkList;
+typedef struct module *Module;
+typedef struct nameddir *Nameddir;
+typedef struct options *Options;
+typedef struct optname *Optname;
+typedef struct param *Param;
+typedef struct paramdef *Paramdef;
+typedef struct patstralloc *Patstralloc;
+typedef struct patprog *Patprog;
+typedef struct prepromptfn *Prepromptfn;
+typedef struct process *Process;
+typedef struct redir *Redir;
+typedef struct reswd *Reswd;
+typedef struct shfunc *Shfunc;
+typedef struct timedfn *Timedfn;
+typedef struct value *Value;
+
+/********************************/
+/* Definitions for linked lists */
+/********************************/
+
+/* linked list abstract data type */
+
+struct linknode {
+ LinkNode next;
+ LinkNode prev;
+ void *dat;
+};
+
+struct linklist {
+ LinkNode first;
+ LinkNode last;
+ int flags;
+};
+
+union linkroot {
+ struct linklist list;
+ struct linknode node;
+};
+
+/* Macros for manipulating link lists */
+
+#define firstnode(X) ((X)->list.first)
+#define lastnode(X) ((X)->list.last)
+#define peekfirst(X) (firstnode(X)->dat)
+#define peeklast(X) (lastnode(X)->dat)
+#define addlinknode(X,Y) insertlinknode(X,lastnode(X),Y)
+#define zaddlinknode(X,Y) zinsertlinknode(X,lastnode(X),Y)
+#define uaddlinknode(X,Y) uinsertlinknode(X,lastnode(X),Y)
+#define empty(X) (firstnode(X) == NULL)
+#define nonempty(X) (firstnode(X) != NULL)
+#define getaddrdata(X) (&((X)->dat))
+#define getdata(X) ((X)->dat)
+#define setdata(X,Y) ((X)->dat = (Y))
+#define nextnode(X) ((X)->next)
+#define prevnode(X) ((X)->prev)
+#define pushnode(X,Y) insertlinknode(X,&(X)->node,Y)
+#define zpushnode(X,Y) zinsertlinknode(X,&(X)->node,Y)
+#define incnode(X) (X = nextnode(X))
+#define decnode(X) (X = prevnode(X))
+#define firsthist() (hist_ring? hist_ring->down->histnum : curhist)
+#define setsizednode(X,Y,Z) (firstnode(X)[(Y)].dat = (void *) (Z))
+
+/* stack allocated linked lists */
+
+#define local_list0(N) union linkroot N
+#define init_list0(N) \
+ do { \
+ (N).list.first = NULL; \
+ (N).list.last = &(N).node; \
+ (N).list.flags = 0; \
+ } while (0)
+#define local_list1(N) union linkroot N; struct linknode __n0
+#define init_list1(N,V0) \
+ do { \
+ (N).list.first = &__n0; \
+ (N).list.last = &__n0; \
+ (N).list.flags = 0; \
+ __n0.next = NULL; \
+ __n0.prev = &(N).node; \
+ __n0.dat = (void *) (V0); \
+ } while (0)
+
+/*************************************/
+/* Specific elements of linked lists */
+/*************************************/
+
+typedef void (*voidvoidfnptr_t) _((void));
+
+/*
+ * Element of the prepromptfns list.
+ */
+struct prepromptfn {
+ voidvoidfnptr_t func;
+};
+
+
+/*
+ * Element of the timedfns list.
+ */
+struct timedfn {
+ voidvoidfnptr_t func;
+ time_t when;
+};
+
+/********************************/
+/* Definitions for syntax trees */
+/********************************/
+
+/* 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 (&|) */
+/* (1<<4) is used for Z_END, see the wordcode definitions */
+/* (1<<5) is used for Z_SIMPLE, see the wordcode definitions */
+
+/*
+ * Condition types.
+ *
+ * Careful when changing these: both cond_binary_ops in text.c and
+ * condstr in cond.c depend on these. (The zsh motto is "two instances
+ * are better than one". Or something.)
+ */
+
+#define COND_NOT 0
+#define COND_AND 1
+#define COND_OR 2
+#define COND_STREQ 3
+#define COND_STRDEQ 4
+#define COND_STRNEQ 5
+#define COND_STRLT 6
+#define COND_STRGTR 7
+#define COND_NT 8
+#define COND_OT 9
+#define COND_EF 10
+#define COND_EQ 11
+#define COND_NE 12
+#define COND_LT 13
+#define COND_GT 14
+#define COND_LE 15
+#define COND_GE 16
+#define COND_REGEX 17
+#define COND_MOD 18
+#define COND_MODI 19
+
+typedef int (*CondHandler) _((char **, int));
+
+struct conddef {
+ Conddef next; /* next in list */
+ char *name; /* the condition name */
+ int flags; /* see CONDF_* below */
+ CondHandler handler; /* handler function */
+ int min; /* minimum number of strings */
+ int max; /* maximum number of strings */
+ int condid; /* for overloading handler functions */
+ char *module; /* module to autoload */
+};
+
+/* Condition is an infix */
+#define CONDF_INFIX 1
+/* Condition has been loaded from library */
+#define CONDF_ADDED 2
+/* When autoloading, enable all features in library */
+#define CONDF_AUTOALL 4
+
+#define CONDDEF(name, flags, handler, min, max, condid) \
+ { NULL, name, flags, handler, min, max, condid, NULL }
+
+/* Flags for redirections */
+
+enum {
+ /* Mark a here-string that came from a here-document */
+ REDIRF_FROM_HEREDOC = 1
+};
+
+/* tree element for redirection lists */
+
+struct redir {
+ int type;
+ int flags;
+ int fd1, fd2;
+ char *name;
+ char *varid;
+ char *here_terminator;
+ char *munged_here_terminator;
+};
+
+/* 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 */
+};
+
+/* lvalue for variable assignment/expansion */
+
+struct value {
+ int isarr;
+ Param pm; /* parameter node */
+ int flags; /* flags defined below */
+ int start; /* first element of array slice, or -1 */
+ int end; /* 1-rel last element of array slice, or -1 */
+ char **arr; /* cache for hash turned into array */
+};
+
+enum {
+ VALFLAG_INV = 0x0001, /* We are performing inverse subscripting */
+ VALFLAG_EMPTY = 0x0002, /* Subscripted range is empty */
+ VALFLAG_SUBST = 0x0004 /* Substitution, so apply padding, case flags */
+};
+
+#define MAX_ARRLEN 262144
+
+/********************************************/
+/* Definitions for word code */
+/********************************************/
+
+typedef unsigned int wordcode;
+typedef wordcode *Wordcode;
+
+typedef struct funcdump *FuncDump;
+typedef struct eprog *Eprog;
+
+struct funcdump {
+ FuncDump next; /* next in list */
+ dev_t dev; /* device */
+ ino_t ino; /* indoe number */
+ int fd; /* file descriptor */
+ Wordcode map; /* pointer to header */
+ Wordcode addr; /* mapped region */
+ int len; /* length */
+ int count; /* reference count */
+ char *filename;
+};
+
+/*
+ * A note on the use of reference counts in Eprogs.
+ *
+ * When an Eprog is created, nref is set to -1 if the Eprog is on the
+ * heap; then no attempt is ever made to free it. (This information is
+ * already present in EF_HEAP; we use the redundancy for debugging
+ * checks.)
+ *
+ * Otherwise, nref is initialised to 1. Calling freeprog() decrements
+ * nref and frees the Eprog if the count is now zero. When the Eprog
+ * is in use, we call useeprog() at the start and freeprog() at the
+ * end to increment and decrement the reference counts. If an attempt
+ * is made to free the Eprog from within, this will then take place
+ * when execution is finished, typically in the call to freeeprog()
+ * in execode(). If the Eprog was on the heap, neither useeprog()
+ * nor freeeprog() has any effect.
+ */
+struct eprog {
+ int flags; /* EF_* below */
+ int len; /* total block length */
+ int npats; /* Patprog cache size */
+ int nref; /* number of references: delete when zero */
+ Patprog *pats; /* the memory block, the patterns */
+ Wordcode prog; /* memory block ctd, the code */
+ char *strs; /* memory block ctd, the strings */
+ Shfunc shf; /* shell function for autoload */
+ FuncDump dump; /* dump file this is in */
+};
+
+#define EF_REAL 1
+#define EF_HEAP 2
+#define EF_MAP 4
+#define EF_RUN 8
+
+typedef struct estate *Estate;
+
+struct estate {
+ Eprog prog; /* the eprog executed */
+ Wordcode pc; /* program counter, current pos */
+ char *strs; /* strings from prog */
+};
+
+typedef struct eccstr *Eccstr;
+
+struct eccstr {
+ Eccstr left, right;
+ char *str;
+ wordcode offs, aoffs;
+ int nfunc;
+ int hashval;
+};
+
+#define EC_NODUP 0
+#define EC_DUP 1
+#define EC_DUPTOK 2
+
+#define WC_CODEBITS 5
+
+#define wc_code(C) ((C) & ((wordcode) ((1 << WC_CODEBITS) - 1)))
+#define wc_data(C) ((C) >> WC_CODEBITS)
+#define wc_bdata(D) ((D) << WC_CODEBITS)
+#define wc_bld(C,D) (((wordcode) (C)) | (((wordcode) (D)) << WC_CODEBITS))
+
+#define WC_END 0
+#define WC_LIST 1
+#define WC_SUBLIST 2
+#define WC_PIPE 3
+#define WC_REDIR 4
+#define WC_ASSIGN 5
+#define WC_SIMPLE 6
+#define WC_TYPESET 7
+#define WC_SUBSH 8
+#define WC_CURSH 9
+#define WC_TIMED 10
+#define WC_FUNCDEF 11
+#define WC_FOR 12
+#define WC_SELECT 13
+#define WC_WHILE 14
+#define WC_REPEAT 15
+#define WC_CASE 16
+#define WC_IF 17
+#define WC_COND 18
+#define WC_ARITH 19
+#define WC_AUTOFN 20
+#define WC_TRY 21
+
+/* increment as necessary */
+#define WC_COUNT 22
+
+#define WCB_END() wc_bld(WC_END, 0)
+
+#define WC_LIST_TYPE(C) wc_data(C)
+#define Z_END (1<<4)
+#define Z_SIMPLE (1<<5)
+#define WC_LIST_FREE (6) /* Next bit available in integer */
+#define WC_LIST_SKIP(C) (wc_data(C) >> WC_LIST_FREE)
+#define WCB_LIST(T,O) wc_bld(WC_LIST, ((T) | ((O) << WC_LIST_FREE)))
+
+#define WC_SUBLIST_TYPE(C) (wc_data(C) & ((wordcode) 3))
+#define WC_SUBLIST_END 0
+#define WC_SUBLIST_AND 1
+#define WC_SUBLIST_OR 2
+#define WC_SUBLIST_FLAGS(C) (wc_data(C) & ((wordcode) 0x1c))
+#define WC_SUBLIST_COPROC 4
+#define WC_SUBLIST_NOT 8
+#define WC_SUBLIST_SIMPLE 16
+#define WC_SUBLIST_FREE (5) /* Next bit available in integer */
+#define WC_SUBLIST_SKIP(C) (wc_data(C) >> WC_SUBLIST_FREE)
+#define WCB_SUBLIST(T,F,O) wc_bld(WC_SUBLIST, \
+ ((T) | (F) | ((O) << WC_SUBLIST_FREE)))
+
+#define WC_PIPE_TYPE(C) (wc_data(C) & ((wordcode) 1))
+#define WC_PIPE_END 0
+#define WC_PIPE_MID 1
+#define WC_PIPE_LINENO(C) (wc_data(C) >> 1)
+#define WCB_PIPE(T,L) wc_bld(WC_PIPE, ((T) | ((L) << 1)))
+
+#define WC_REDIR_TYPE(C) ((int)(wc_data(C) & REDIR_TYPE_MASK))
+#define WC_REDIR_VARID(C) ((int)(wc_data(C) & REDIR_VARID_MASK))
+#define WC_REDIR_FROM_HEREDOC(C) ((int)(wc_data(C) & REDIR_FROM_HEREDOC_MASK))
+#define WCB_REDIR(T) wc_bld(WC_REDIR, (T))
+/* Size of redir is 4 words if REDIR_VARID_MASK is set, else 3 */
+#define WC_REDIR_WORDS(C) \
+ ((WC_REDIR_VARID(C) ? 4 : 3) + \
+ (WC_REDIR_FROM_HEREDOC(C) ? 2 : 0))
+
+#define WC_ASSIGN_TYPE(C) (wc_data(C) & ((wordcode) 1))
+#define WC_ASSIGN_TYPE2(C) ((wc_data(C) & ((wordcode) 2)) >> 1)
+#define WC_ASSIGN_SCALAR 0
+#define WC_ASSIGN_ARRAY 1
+#define WC_ASSIGN_NEW 0
+/*
+ * In normal assignment, this indicate += to append.
+ * In assignment following a typeset, where that's not allowed,
+ * we overload this to indicate a variable without an
+ * assignment.
+ */
+#define WC_ASSIGN_INC 1
+#define WC_ASSIGN_NUM(C) (wc_data(C) >> 2)
+#define WCB_ASSIGN(T,A,N) wc_bld(WC_ASSIGN, ((T) | ((A) << 1) | ((N) << 2)))
+
+#define WC_SIMPLE_ARGC(C) wc_data(C)
+#define WCB_SIMPLE(N) wc_bld(WC_SIMPLE, (N))
+
+#define WC_TYPESET_ARGC(C) wc_data(C)
+#define WCB_TYPESET(N) wc_bld(WC_TYPESET, (N))
+
+#define WC_SUBSH_SKIP(C) wc_data(C)
+#define WCB_SUBSH(O) wc_bld(WC_SUBSH, (O))
+
+#define WC_CURSH_SKIP(C) wc_data(C)
+#define WCB_CURSH(O) wc_bld(WC_CURSH, (O))
+
+#define WC_TIMED_TYPE(C) wc_data(C)
+#define WC_TIMED_EMPTY 0
+#define WC_TIMED_PIPE 1
+#define WCB_TIMED(T) wc_bld(WC_TIMED, (T))
+
+#define WC_FUNCDEF_SKIP(C) wc_data(C)
+#define WCB_FUNCDEF(O) wc_bld(WC_FUNCDEF, (O))
+
+#define WC_FOR_TYPE(C) (wc_data(C) & 3)
+#define WC_FOR_PPARAM 0
+#define WC_FOR_LIST 1
+#define WC_FOR_COND 2
+#define WC_FOR_SKIP(C) (wc_data(C) >> 2)
+#define WCB_FOR(T,O) wc_bld(WC_FOR, ((T) | ((O) << 2)))
+
+#define WC_SELECT_TYPE(C) (wc_data(C) & 1)
+#define WC_SELECT_PPARAM 0
+#define WC_SELECT_LIST 1
+#define WC_SELECT_SKIP(C) (wc_data(C) >> 1)
+#define WCB_SELECT(T,O) wc_bld(WC_SELECT, ((T) | ((O) << 1)))
+
+#define WC_WHILE_TYPE(C) (wc_data(C) & 1)
+#define WC_WHILE_WHILE 0
+#define WC_WHILE_UNTIL 1
+#define WC_WHILE_SKIP(C) (wc_data(C) >> 1)
+#define WCB_WHILE(T,O) wc_bld(WC_WHILE, ((T) | ((O) << 1)))
+
+#define WC_REPEAT_SKIP(C) wc_data(C)
+#define WCB_REPEAT(O) wc_bld(WC_REPEAT, (O))
+
+#define WC_TRY_SKIP(C) wc_data(C)
+#define WCB_TRY(O) wc_bld(WC_TRY, (O))
+
+#define WC_CASE_TYPE(C) (wc_data(C) & 7)
+#define WC_CASE_HEAD 0
+#define WC_CASE_OR 1
+#define WC_CASE_AND 2
+#define WC_CASE_TESTAND 3
+#define WC_CASE_FREE (3) /* Next bit available in integer */
+#define WC_CASE_SKIP(C) (wc_data(C) >> WC_CASE_FREE)
+#define WCB_CASE(T,O) wc_bld(WC_CASE, ((T) | ((O) << WC_CASE_FREE)))
+
+#define WC_IF_TYPE(C) (wc_data(C) & 3)
+#define WC_IF_HEAD 0
+#define WC_IF_IF 1
+#define WC_IF_ELIF 2
+#define WC_IF_ELSE 3
+#define WC_IF_SKIP(C) (wc_data(C) >> 2)
+#define WCB_IF(T,O) wc_bld(WC_IF, ((T) | ((O) << 2)))
+
+#define WC_COND_TYPE(C) (wc_data(C) & 127)
+#define WC_COND_SKIP(C) (wc_data(C) >> 7)
+#define WCB_COND(T,O) wc_bld(WC_COND, ((T) | ((O) << 7)))
+
+#define WCB_ARITH() wc_bld(WC_ARITH, 0)
+
+#define WCB_AUTOFN() wc_bld(WC_AUTOFN, 0)
+
+/********************************************/
+/* Definitions for job table and job control */
+/********************************************/
+
+/* Entry in filelist linked list in job table */
+
+struct jobfile {
+ /* Record to be deleted or closed */
+ union {
+ char *name; /* Name of file to delete */
+ int fd; /* File descriptor to close */
+ } u;
+ /* Discriminant */
+ int is_fd;
+};
+
+/* entry in the job table */
+
+struct job {
+ pid_t gleader; /* process group leader of this job */
+ pid_t other; /* subjob id (SUPERJOB)
+ * or subshell pid (SUBJOB) */
+ int stat; /* see STATs below */
+ char *pwd; /* current working dir of shell when *
+ * this job was spawned */
+ struct process *procs; /* list of processes */
+ struct process *auxprocs; /* auxiliary processes e.g multios */
+ LinkList filelist; /* list of files to delete when done */
+ /* elements are struct jobfile */
+ int stty_in_env; /* if STTY=... is present */
+ struct ttyinfo *ty; /* the modes specified by STTY */
+};
+
+#define STAT_CHANGED (0x0001) /* status changed and not reported */
+#define STAT_STOPPED (0x0002) /* all procs stopped or exited */
+#define STAT_TIMED (0x0004) /* job is being timed */
+#define STAT_DONE (0x0008) /* job is done */
+#define STAT_LOCKED (0x0010) /* shell is finished creating this job, */
+ /* may be deleted from job table */
+#define STAT_NOPRINT (0x0020) /* job was killed internally, */
+ /* we don't want to show that */
+#define STAT_INUSE (0x0040) /* this job entry is in use */
+#define STAT_SUPERJOB (0x0080) /* job has a subjob */
+#define STAT_SUBJOB (0x0100) /* job is a subjob */
+#define STAT_WASSUPER (0x0200) /* was a super-job, sub-job needs to be */
+ /* deleted */
+#define STAT_CURSH (0x0400) /* last command is in current shell */
+#define STAT_NOSTTY (0x0800) /* the tty settings are not inherited */
+ /* from this job when it exits. */
+#define STAT_ATTACH (0x1000) /* delay reattaching shell to tty */
+#define STAT_SUBLEADER (0x2000) /* is super-job, but leader is sub-shell */
+
+#define STAT_BUILTIN (0x4000) /* job at tail of pipeline is a builtin */
+#define STAT_SUBJOB_ORPHANED (0x8000)
+ /* STAT_SUBJOB with STAT_SUPERJOB exited */
+#define STAT_DISOWN (0x10000) /* STAT_SUPERJOB with disown pending */
+
+#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
+
+/* Size to initialise the job table to, and to increment it by when needed. */
+#define MAXJOBS_ALLOC (50)
+
+/* node in job process lists */
+
+#ifdef HAVE_GETRUSAGE
+typedef struct rusage child_times_t;
+#else
+typedef struct timeinfo child_times_t;
+#endif
+
+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() */
+ child_times_t ti;
+ struct timeval bgtime; /* time job was spawned */
+ struct timeval endtime; /* time job exited */
+};
+
+struct execstack {
+ struct execstack *next;
+
+ 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 use_cmdoutval;
+ pid_t procsubstpid;
+ int trap_return;
+ int trap_state;
+ int trapisfunc;
+ int traplocallevel;
+ int noerrs;
+ int this_noerrexit;
+ char *underscore;
+};
+
+struct heredocs {
+ struct heredocs *next;
+ int type;
+ int pc;
+ char *str;
+};
+
+struct dirsav {
+ int dirfd, level;
+ char *dirname;
+ dev_t dev;
+ ino_t ino;
+};
+
+#define MAX_PIPESTATS 256
+
+/*******************************/
+/* Definitions for Hash Tables */
+/*******************************/
+
+typedef void *(*VFunc) _((void *));
+typedef void (*FreeFunc) _((void *));
+
+typedef unsigned (*HashFunc) _((const char *));
+typedef void (*TableFunc) _((HashTable));
+/*
+ * Note that this is deliberately "char *", not "const char *",
+ * since the AddNodeFunc is passed a pointer to a string that
+ * will be stored and later freed.
+ */
+typedef void (*AddNodeFunc) _((HashTable, char *, void *));
+typedef HashNode (*GetNodeFunc) _((HashTable, const char *));
+typedef HashNode (*RemoveNodeFunc) _((HashTable, const char *));
+typedef void (*FreeNodeFunc) _((HashNode));
+typedef int (*CompareFunc) _((const char *, const char *));
+
+/* type of function that is passed to *
+ * scanhashtable or scanmatchtable */
+typedef void (*ScanFunc) _((HashNode, int));
+typedef void (*ScanTabFunc) _((HashTable, ScanFunc, 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 */
+ void *tmpdata;
+
+ /* 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 */
+ CompareFunc cmpnodes; /* pointer to function to compare two nodes */
+ 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 */
+ ScanTabFunc scantab; /* pointer to function to scan table */
+
+#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 option table */
+
+struct optname {
+ struct hashnode node;
+ int optno; /* option number */
+};
+
+/* node in shell reserved word hash table (reswdtab) */
+
+struct reswd {
+ struct hashnode node;
+ int token; /* corresponding lexer token */
+};
+
+/* node in alias hash table (aliastab) */
+
+struct alias {
+ struct hashnode node;
+ char *text; /* expansion of alias */
+ int inuse; /* alias is being expanded */
+};
+
+/* bit 0 of flags is the DISABLED flag */
+/* is this alias global? */
+#define ALIAS_GLOBAL (1<<1)
+/* is this an alias for suffix handling? */
+#define ALIAS_SUFFIX (1<<2)
+
+/* structure for foo=bar assignments */
+
+struct asgment {
+ struct linknode node;
+ char *name;
+ int flags;
+ union {
+ char *scalar;
+ LinkList array;
+ } value;
+};
+
+/* Flags for flags element of asgment */
+enum {
+ /* Array value */
+ ASG_ARRAY = 1,
+ /* Key / value array pair */
+ ASG_KEY_VALUE = 2
+};
+
+/*
+ * Assignment is array?
+ */
+#define ASG_ARRAYP(asg) ((asg)->flags & ASG_ARRAY)
+
+/*
+ * Assignment has value?
+ * If the assignment is an arrray, then it certainly has a value --- we
+ * can only tell if there's an expicit assignment.
+ */
+
+#define ASG_VALUEP(asg) (ASG_ARRAYP(asg) || \
+ ((asg)->value.scalar != (char *)0))
+
+/* node in command path hash table (cmdnamtab) */
+
+struct cmdnam {
+ struct hashnode node;
+ 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 {
+ struct hashnode node;
+ char *filename; /* Name of file located in.
+ For not yet autoloaded file, name
+ of explicit directory, if not NULL. */
+ zlong lineno; /* line number in above file */
+ Eprog funcdef; /* function definition */
+ Eprog redir; /* redirections to apply */
+ Emulation_options sticky; /* sticky emulation definitions, if any */
+};
+
+/* Shell function context types. */
+
+#define SFC_NONE 0 /* no function running */
+#define SFC_DIRECT 1 /* called directly from the user */
+#define SFC_SIGNAL 2 /* signal handler */
+#define SFC_HOOK 3 /* one of the special functions */
+#define SFC_WIDGET 4 /* user defined widget */
+#define SFC_COMPLETE 5 /* called from completion code */
+#define SFC_CWIDGET 6 /* new style completion widget */
+#define SFC_SUBST 7 /* used to perform substitution task */
+
+/* tp in funcstack */
+
+enum {
+ FS_SOURCE,
+ FS_FUNC,
+ FS_EVAL
+};
+
+/* node in function stack */
+
+struct funcstack {
+ Funcstack prev; /* previous in stack */
+ char *name; /* name of function/sourced file called */
+ char *filename; /* file function resides in */
+ char *caller; /* name of caller */
+ zlong flineno; /* line number in file */
+ zlong lineno; /* line offset from beginning of function */
+ int tp; /* type of entry: sourced file, func, eval */
+};
+
+/* node in list of function call wrappers */
+
+typedef int (*WrapFunc) _((Eprog, FuncWrap, char *));
+
+struct funcwrap {
+ FuncWrap next;
+ int flags;
+ WrapFunc handler;
+ Module module;
+};
+
+#define WRAPF_ADDED 1
+
+#define WRAPDEF(func) \
+ { NULL, 0, func, NULL }
+
+/*
+ * User-defined hook arrays
+ */
+
+/* Name appended to function name to get hook array */
+#define HOOK_SUFFIX "_functions"
+/* Length of that including NUL byte */
+#define HOOK_SUFFIX_LEN 11
+
+/* node in builtin command hash table (builtintab) */
+
+/*
+ * Handling of options.
+ *
+ * Option strings are standard in that a trailing `:' indicates
+ * a mandatory argument. In addition, `::' indicates an optional
+ * argument which must immediately follow the option letter if it is present.
+ * `:%' indicates an optional numeric argument which may follow
+ * the option letter or be in the next word; the only test is
+ * that the next character is a digit, and no actual conversion is done.
+ */
+
+#define MAX_OPS 128
+
+/* Macros taking struct option * and char argument */
+/* Option was set as -X */
+#define OPT_MINUS(ops,c) ((ops)->ind[c] & 1)
+/* Option was set as +X */
+#define OPT_PLUS(ops,c) ((ops)->ind[c] & 2)
+/*
+ * Option was set any old how, maybe including an argument
+ * (cheap test when we don't care). Some bits of code
+ * expect this to be 1 or 0.
+ */
+#define OPT_ISSET(ops,c) ((ops)->ind[c] != 0)
+/* Option has an argument */
+#define OPT_HASARG(ops,c) ((ops)->ind[c] > 3)
+/* The argument for the option; not safe if it doesn't have one */
+#define OPT_ARG(ops,c) ((ops)->args[((ops)->ind[c] >> 2) - 1])
+/* Ditto, but safely returns NULL if there is no argument. */
+#define OPT_ARG_SAFE(ops,c) (OPT_HASARG(ops,c) ? OPT_ARG(ops,c) : NULL)
+
+struct options {
+ unsigned char ind[MAX_OPS];
+ char **args;
+ int argscount, argsalloc;
+};
+
+/* Flags to parseargs() */
+
+enum {
+ PARSEARGS_TOPLEVEL = 0x1, /* Call to initialise shell */
+ PARSEARGS_LOGIN = 0x2 /* Shell is login shell */
+};
+
+
+/*
+ * Handler arguments are: builtin name, null-terminated argument
+ * list excluding command name, option structure, the funcid element from the
+ * builtin structure.
+ */
+
+typedef int (*HandlerFunc) _((char *, char **, Options, int));
+typedef int (*HandlerFuncAssign) _((char *, char **, LinkList, Options, int));
+#define NULLBINCMD ((HandlerFunc) 0)
+
+struct builtin {
+ struct hashnode node;
+ 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_PRINTOPTS (1<<2)
+#define BINF_ADDED (1<<3) /* is in the builtins hash table */
+#define BINF_MAGICEQUALS (1<<4) /* needs automatic MAGIC_EQUAL_SUBST substitution */
+#define BINF_PREFIX (1<<5)
+#define BINF_DASH (1<<6)
+#define BINF_BUILTIN (1<<7)
+#define BINF_COMMAND (1<<8)
+#define BINF_EXEC (1<<9)
+#define BINF_NOGLOB (1<<10)
+#define BINF_PSPECIAL (1<<11)
+/* Builtin option handling */
+#define BINF_SKIPINVALID (1<<12) /* Treat invalid option as argument */
+#define BINF_KEEPNUM (1<<13) /* `[-+]NUM' can be an option */
+#define BINF_SKIPDASH (1<<14) /* Treat `-' as argument (maybe `+') */
+#define BINF_DASHDASHVALID (1<<15) /* Handle `--' even if SKIPINVALD */
+#define BINF_CLEARENV (1<<16) /* new process started with cleared env */
+#define BINF_AUTOALL (1<<17) /* autoload all features at once */
+ /*
+ * Handles options itself. This is only useful if the option string for a
+ * builtin with an empty option string. It is used to indicate that "--"
+ * does not terminate options.
+ */
+#define BINF_HANDLES_OPTS (1<<18)
+/*
+ * Handles the assignement interface. The argv list actually contains
+ * two nested litsts, the first of normal arguments, and the second of
+ * assignment structures.
+ */
+#define BINF_ASSIGN (1<<19)
+
+/**
+ * Parameters passed to execcmd().
+ * These are not opaque --- they are also used by the pipeline manager.
+ */
+struct execcmd_params {
+ LinkList args; /* All command prefixes, arguments & options */
+ LinkList redir; /* Redirections */
+ Wordcode beg; /* The code at the start of the command */
+ Wordcode varspc; /* The code for assignment parsed as such */
+ Wordcode assignspc; /* The code for assignment parsed as typeset */
+ int type; /* The WC_* type of the command */
+ int postassigns; /* The number of assignspc assiguments */
+ int htok; /* tokens in parameter list */
+};
+
+struct module {
+ struct hashnode node;
+ union {
+ void *handle;
+ Linkedmod linked;
+ char *alias;
+ } u;
+ LinkList autoloads;
+ LinkList deps;
+ int wrapper;
+};
+
+/* We are in the process of loading the module */
+#define MOD_BUSY (1<<0)
+/*
+ * We are in the process of unloading the module.
+ * Note this is not needed to indicate a module is actually
+ * unloaded: for that, the handle (or linked pointer) is set to NULL.
+ */
+#define MOD_UNLOAD (1<<1)
+/* We are in the process of setting up the module */
+#define MOD_SETUP (1<<2)
+/* Module is statically linked into the main binary */
+#define MOD_LINKED (1<<3)
+/* Module setup has been carried out (and module has not been finished) */
+#define MOD_INIT_S (1<<4)
+/* Module boot has been carried out (and module has not been finished) */
+#define MOD_INIT_B (1<<5)
+/* Module record is an alias */
+#define MOD_ALIAS (1<<6)
+
+typedef int (*Module_generic_func) _((void));
+typedef int (*Module_void_func) _((Module));
+typedef int (*Module_features_func) _((Module, char ***));
+typedef int (*Module_enables_func) _((Module, int **));
+
+struct linkedmod {
+ char *name;
+ Module_void_func setup;
+ Module_features_func features;
+ Module_enables_func enables;
+ Module_void_func boot;
+ Module_void_func cleanup;
+ Module_void_func finish;
+};
+
+/*
+ * Structure combining all the concrete features available in
+ * a module and with space for information about abstract features.
+ */
+struct features {
+ /* List of builtins provided by the module and the size thereof */
+ Builtin bn_list;
+ int bn_size;
+ /* List of conditions provided by the module and the size thereof */
+ Conddef cd_list;
+ int cd_size;
+ /* List of math functions provided by the module and the size thereof */
+ MathFunc mf_list;
+ int mf_size;
+ /* List of parameters provided by the module and the size thereof */
+ Paramdef pd_list;
+ int pd_size;
+ /* Number of abstract features */
+ int n_abstract;
+};
+
+/*
+ * Structure describing enables for one feature.
+ */
+struct feature_enables {
+ /* String feature to enable (N.B. no leading +/- allowed) */
+ char *str;
+ /* Optional compiled pattern for str sans +/-, NULL for string match */
+ Patprog pat;
+};
+
+/* C-function hooks */
+
+typedef int (*Hookfn) _((Hookdef, void *));
+
+struct hookdef {
+ Hookdef next;
+ char *name;
+ Hookfn def;
+ int flags;
+ LinkList funcs;
+};
+
+#define HOOKF_ALL 1
+
+#define HOOKDEF(name, func, flags) { NULL, name, (Hookfn) func, flags, NULL }
+
+/*
+ * Types used in pattern matching. Most of these longs could probably
+ * happily be ints.
+ */
+
+struct patprog {
+ long startoff; /* length before start of programme */
+ long size; /* total size from start of struct */
+ long mustoff; /* offset to string that must be present */
+ long patmlen; /* length of pure string or longest match */
+ int globflags; /* globbing flags to set at start */
+ int globend; /* globbing flags set after finish */
+ int flags; /* PAT_* flags */
+ int patnpar; /* number of active parentheses */
+ char patstartch;
+};
+
+struct patstralloc {
+ int unmetalen; /* Unmetafied length of trial string */
+ int unmetalenp; /* Unmetafied length of path prefix.
+ If 0, no path prefix. */
+ char *alloced; /* Allocated string, may be NULL */
+ char *progstrunmeta; /* Unmetafied pure string in pattern, cached */
+ int progstrunmetalen; /* Length of the foregoing */
+};
+
+/* Flags used in pattern matchers (Patprog) and passed down to patcompile */
+
+#define PAT_HEAPDUP 0x0000 /* Dummy flag for default behavior */
+#define PAT_FILE 0x0001 /* Pattern is a file name */
+#define PAT_FILET 0x0002 /* Pattern is top level file, affects ~ */
+#define PAT_ANY 0x0004 /* Match anything (cheap "*") */
+#define PAT_NOANCH 0x0008 /* Not anchored at end */
+#define PAT_NOGLD 0x0010 /* Don't glob dots */
+#define PAT_PURES 0x0020 /* Pattern is a pure string: set internally */
+#define PAT_STATIC 0x0040 /* Don't copy pattern to heap as per default */
+#define PAT_SCAN 0x0080 /* Scanning, so don't try must-match test */
+#define PAT_ZDUP 0x0100 /* Copy pattern in real memory */
+#define PAT_NOTSTART 0x0200 /* Start of string is not real start */
+#define PAT_NOTEND 0x0400 /* End of string is not real end */
+#define PAT_HAS_EXCLUDP 0x0800 /* (internal): top-level path1~path2. */
+#define PAT_LCMATCHUC 0x1000 /* equivalent to setting (#l) */
+
+/**
+ * Indexes into the array of active pattern characters.
+ * This must match the array zpc_chars in pattern.c.
+ */
+enum zpc_chars {
+ /*
+ * These characters both terminate a pattern segment and
+ * a pure string segment.
+ */
+ ZPC_SLASH, /* / active as file separator */
+ ZPC_NULL, /* \0 as string terminator */
+ ZPC_BAR, /* | for "or" */
+ ZPC_OUTPAR, /* ) for grouping */
+ ZPC_TILDE, /* ~ for exclusion (extended glob) */
+ ZPC_SEG_COUNT, /* No. of the above characters */
+ /*
+ * These characters terminate a pure string segment.
+ */
+ ZPC_INPAR = ZPC_SEG_COUNT, /* ( for grouping */
+ ZPC_QUEST, /* ? as wildcard */
+ ZPC_STAR, /* * as wildcard */
+ ZPC_INBRACK, /* [ for character class */
+ ZPC_INANG, /* < for numeric glob */
+ ZPC_HAT, /* ^ for exclusion (extended glob) */
+ ZPC_HASH, /* # for repetition (extended glob) */
+ ZPC_BNULLKEEP, /* Special backslashed null not removed */
+ /*
+ * These characters are only valid before a parenthesis
+ */
+ ZPC_NO_KSH_GLOB,
+ ZPC_KSH_QUEST = ZPC_NO_KSH_GLOB, /* ? for ?(...) in KSH_GLOB */
+ ZPC_KSH_STAR, /* * for *(...) in KSH_GLOB */
+ ZPC_KSH_PLUS, /* + for +(...) in KSH_GLOB */
+ ZPC_KSH_BANG, /* ! for !(...) in KSH_GLOB */
+ ZPC_KSH_BANG2, /* ! for !(...) in KSH_GLOB, untokenised */
+ ZPC_KSH_AT, /* @ for @(...) in KSH_GLOB */
+ ZPC_COUNT /* Number of special chararacters */
+};
+
+/*
+ * Structure to save disables special characters for function scope.
+ */
+struct zpc_disables_save {
+ struct zpc_disables_save *next;
+ /*
+ * Bit vector of ZPC_COUNT disabled characters.
+ * We'll live dangerously and assume ZPC_COUNT is no greater
+ * than the number of bits in an unsigned int.
+ */
+ unsigned int disables;
+};
+
+typedef struct zpc_disables_save *Zpc_disables_save;
+
+/*
+ * Special match types used in character classes. These
+ * are represented as tokens, with Meta added. The character
+ * class is represented as a metafied string, with only these
+ * tokens special. Note that an active leading "!" or "^" for
+ * negation is not part of the string but is flagged in the
+ * surrounding context.
+ *
+ * These types are also used in character and equivalence classes
+ * in completion matching.
+ *
+ * This must be kept ordered by the array colon_stuffs in pattern.c.
+ */
+/* Special value for first definition */
+#define PP_FIRST 1
+/* POSIX-defined types: [:alpha:] etc. */
+#define PP_ALPHA 1
+#define PP_ALNUM 2
+#define PP_ASCII 3
+#define PP_BLANK 4
+#define PP_CNTRL 5
+#define PP_DIGIT 6
+#define PP_GRAPH 7
+#define PP_LOWER 8
+#define PP_PRINT 9
+#define PP_PUNCT 10
+#define PP_SPACE 11
+#define PP_UPPER 12
+#define PP_XDIGIT 13
+/* Zsh additions: [:IDENT:] etc. */
+#define PP_IDENT 14
+#define PP_IFS 15
+#define PP_IFSSPACE 16
+#define PP_WORD 17
+#define PP_INCOMPLETE 18
+#define PP_INVALID 19
+/* Special value for last definition */
+#define PP_LAST 19
+
+/* Unknown type. Not used in a valid token. */
+#define PP_UNKWN 20
+/* Range: token followed by the (possibly multibyte) start and end */
+#define PP_RANGE 21
+
+/*
+ * Argument to get_match_ret() in glob.c
+ */
+struct imatchdata {
+ /* Metafied trial string */
+ char *mstr;
+ /* Its length */
+ int mlen;
+ /* Unmetafied string */
+ char *ustr;
+ /* Its length */
+ int ulen;
+ /* Flags (SUB_*) */
+ int flags;
+ /* Replacement string (metafied) */
+ char *replstr;
+ /*
+ * List of bits of matches to concatenate with replacement string.
+ * The data is a struct repldata. It is not used in cases like
+ * ${...//#foo/bar} even though SUB_GLOBAL is set, since the match
+ * is anchored. It goes on the heap.
+ */
+ LinkList repllist;
+};
+
+/* Globbing flags: lower 8 bits gives approx count */
+#define GF_LCMATCHUC 0x0100
+#define GF_IGNCASE 0x0200
+#define GF_BACKREF 0x0400
+#define GF_MATCHREF 0x0800
+#define GF_MULTIBYTE 0x1000 /* Use multibyte if supported by build */
+
+enum {
+ /* Valid multibyte character from charref */
+ ZMB_VALID,
+ /* Incomplete multibyte character from charref */
+ ZMB_INCOMPLETE,
+ /* Invalid multibyte character charref */
+ ZMB_INVALID
+};
+
+/* Dummy Patprog pointers. Used mainly in executable code, but the
+ * pattern code needs to know about it, too. */
+
+#define dummy_patprog1 ((Patprog) 1)
+#define dummy_patprog2 ((Patprog) 2)
+
+/* standard node types for get/set/unset union in parameter */
+
+/*
+ * note non-standard const in pointer declaration: structures are
+ * assumed to be read-only.
+ */
+typedef const struct gsu_scalar *GsuScalar;
+typedef const struct gsu_integer *GsuInteger;
+typedef const struct gsu_float *GsuFloat;
+typedef const struct gsu_array *GsuArray;
+typedef const struct gsu_hash *GsuHash;
+
+struct gsu_scalar {
+ char *(*getfn) _((Param));
+ void (*setfn) _((Param, char *));
+ void (*unsetfn) _((Param, int));
+};
+
+struct gsu_integer {
+ zlong (*getfn) _((Param));
+ void (*setfn) _((Param, zlong));
+ void (*unsetfn) _((Param, int));
+};
+
+struct gsu_float {
+ double (*getfn) _((Param));
+ void (*setfn) _((Param, double));
+ void (*unsetfn) _((Param, int));
+};
+
+struct gsu_array {
+ char **(*getfn) _((Param));
+ void (*setfn) _((Param, char **));
+ void (*unsetfn) _((Param, int));
+};
+
+struct gsu_hash {
+ HashTable (*getfn) _((Param));
+ void (*setfn) _((Param, HashTable));
+ void (*unsetfn) _((Param, int));
+};
+
+
+/* node used in parameter hash table (paramtab) */
+
+struct param {
+ struct hashnode node;
+
+ /* 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) */
+ zlong val; /* value if declared integer (PM_INTEGER) */
+ zlong *valptr; /* value if special pointer to integer */
+ double dval; /* value if declared float
+ (PM_EFLOAT|PM_FFLOAT) */
+ HashTable hash; /* value if declared assoc (PM_HASHED) */
+ } u;
+
+ /*
+ * get/set/unset methods.
+ *
+ * Unlike the data union, this points to a single instance
+ * for every type (although there are special types, e.g.
+ * tied arrays have a different gsu_scalar struct from the
+ * normal one). It's really a poor man's vtable.
+ */
+ union {
+ GsuScalar s;
+ GsuInteger i;
+ GsuFloat f;
+ GsuArray a;
+ GsuHash h;
+ } gsu;
+
+ int base; /* output base or floating point prec */
+ int width; /* 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 */
+};
+
+/* structure stored in struct param's u.data by tied arrays */
+struct tieddata {
+ char ***arrptr; /* pointer to corresponding array */
+ int joinchar; /* character used to join arrays */
+};
+
+/* flags for parameters */
+
+/* parameter types */
+#define PM_SCALAR 0 /* scalar */
+#define PM_ARRAY (1<<0) /* array */
+#define PM_INTEGER (1<<1) /* integer */
+#define PM_EFLOAT (1<<2) /* double with %e output */
+#define PM_FFLOAT (1<<3) /* double with %f output */
+#define PM_HASHED (1<<4) /* association */
+
+#define PM_TYPE(X) \
+ (X & (PM_SCALAR|PM_INTEGER|PM_EFLOAT|PM_FFLOAT|PM_ARRAY|PM_HASHED))
+
+#define PM_LEFT (1<<5) /* left justify, remove leading blanks */
+#define PM_RIGHT_B (1<<6) /* right justify, fill with leading blanks */
+#define PM_RIGHT_Z (1<<7) /* right justify, fill with leading zeros */
+#define PM_LOWER (1<<8) /* all lower case */
+
+/* The following are the same since they *
+ * both represent -u option to typeset */
+#define PM_UPPER (1<<9) /* all upper case */
+#define PM_UNDEFINED (1<<9) /* undefined (autoloaded) shell function */
+
+#define PM_READONLY (1<<10) /* readonly */
+#define PM_TAGGED (1<<11) /* tagged */
+#define PM_EXPORTED (1<<12) /* exported */
+#define PM_ABSPATH_USED (1<<12) /* (function): loaded using absolute path */
+
+/* The following are the same since they *
+ * both represent -U option to typeset */
+#define PM_UNIQUE (1<<13) /* remove duplicates */
+#define PM_UNALIASED (1<<13) /* do not expand aliases when autoloading */
+
+#define PM_HIDE (1<<14) /* Special behaviour hidden by local */
+#define PM_CUR_FPATH (1<<14) /* (function): can use $fpath with filename */
+#define PM_HIDEVAL (1<<15) /* Value not shown in `typeset' commands */
+#define PM_WARNNESTED (1<<15) /* (function): non-recursive WARNNESTEDVAR */
+#define PM_TIED (1<<16) /* array tied to colon-path or v.v. */
+#define PM_TAGGED_LOCAL (1<<16) /* (function): non-recursive PM_TAGGED */
+
+#define PM_KSHSTORED (1<<17) /* function stored in ksh form */
+#define PM_ZSHSTORED (1<<18) /* function stored in zsh form */
+
+/* Remaining flags do not correspond directly to command line arguments */
+#define PM_DONTIMPORT_SUID (1<<19) /* do not import if running setuid */
+#define PM_LOADDIR (1<<19) /* (function) filename gives load directory */
+#define PM_SINGLE (1<<20) /* special can only have a single instance */
+#define PM_ANONYMOUS (1<<20) /* (function) anonymous function */
+#define PM_LOCAL (1<<21) /* this parameter will be made local */
+#define PM_SPECIAL (1<<22) /* special builtin parameter */
+#define PM_DONTIMPORT (1<<23) /* do not import this variable */
+#define PM_RESTRICTED (1<<24) /* cannot be changed in restricted mode */
+#define PM_UNSET (1<<25) /* has null value */
+#define PM_REMOVABLE (1<<26) /* special can be removed from paramtab */
+#define PM_AUTOLOAD (1<<27) /* autoloaded from module */
+#define PM_NORESTORE (1<<28) /* do not restore value of local special */
+#define PM_AUTOALL (1<<28) /* autoload all features in module
+ * when loading: valid only if PM_AUTOLOAD
+ * is also present.
+ */
+#define PM_HASHELEM (1<<29) /* is a hash-element */
+#define PM_NAMEDDIR (1<<30) /* has a corresponding nameddirtab entry */
+
+/* The option string corresponds to the first of the variables above */
+#define TYPESET_OPTSTR "aiEFALRZlurtxUhHTkz"
+
+/* These typeset options take an optional numeric argument */
+#define TYPESET_OPTNUM "LRZiEF"
+
+/* Flags for extracting elements of arrays and associative arrays */
+#define SCANPM_WANTVALS (1<<0) /* Return value includes hash values */
+#define SCANPM_WANTKEYS (1<<1) /* Return value includes hash keys */
+#define SCANPM_WANTINDEX (1<<2) /* Return value includes array index */
+#define SCANPM_MATCHKEY (1<<3) /* Subscript matched against key */
+#define SCANPM_MATCHVAL (1<<4) /* Subscript matched against value */
+#define SCANPM_MATCHMANY (1<<5) /* Subscript matched repeatedly, return all */
+#define SCANPM_ASSIGNING (1<<6) /* Assigning whole array/hash */
+#define SCANPM_KEYMATCH (1<<7) /* keys of hash treated as patterns */
+#define SCANPM_DQUOTED (1<<8) /* substitution was double-quoted
+ * (only used for testing early end of
+ * subscript)
+ */
+#define SCANPM_ARRONLY (1<<9) /* value is array but we don't
+ * necessarily want to match multiple
+ * elements
+ */
+#define SCANPM_CHECKING (1<<10) /* Check if set, no need to create */
+/* "$foo[@]"-style substitution
+ * Only sign bit is significant
+ */
+#define SCANPM_ISVAR_AT ((int)(((unsigned int)-1)<<15))
+
+/*
+ * Flags for doing matches inside parameter substitutions, i.e.
+ * ${...#...} and friends. This could be an enum, but so
+ * could a lot of other things.
+ */
+
+#define SUB_END 0x0001 /* match end instead of beginning, % or %% */
+#define SUB_LONG 0x0002 /* % or # doubled, get longest match */
+#define SUB_SUBSTR 0x0004 /* match a substring */
+#define SUB_MATCH 0x0008 /* include the matched portion */
+#define SUB_REST 0x0010 /* include the unmatched portion */
+#define SUB_BIND 0x0020 /* index of beginning of string */
+#define SUB_EIND 0x0040 /* index of end of string */
+#define SUB_LEN 0x0080 /* length of match */
+#define SUB_ALL 0x0100 /* match complete string */
+#define SUB_GLOBAL 0x0200 /* global substitution ${..//all/these} */
+#define SUB_DOSUBST 0x0400 /* replacement string needs substituting */
+#define SUB_RETFAIL 0x0800 /* return status 0 if no match */
+#define SUB_START 0x1000 /* force match at start with SUB_END
+ * and no SUB_SUBSTR */
+#define SUB_LIST 0x2000 /* no substitution, return list of matches */
+
+/*
+ * Structure recording multiple matches inside a test string.
+ * b and e are the beginning and end of the match.
+ * replstr is the replacement string, if any.
+ */
+struct repldata {
+ int b, e; /* beginning and end of chunk to replace */
+ char *replstr; /* replacement string to use */
+};
+typedef struct repldata *Repldata;
+
+/*
+ * Flags to zshtokenize.
+ */
+enum {
+ /* Do glob substitution */
+ ZSHTOK_SUBST = 0x0001,
+ /* Use sh-style globbing */
+ ZSHTOK_SHGLOB = 0x0002
+};
+
+/* Flags as the second argument to prefork */
+enum {
+ /* argument handled like typeset foo=bar */
+ PREFORK_TYPESET = 0x01,
+ /* argument handled like the RHS of foo=bar */
+ PREFORK_ASSIGN = 0x02,
+ /* single word substitution */
+ PREFORK_SINGLE = 0x04,
+ /* explicitly split nested substitution */
+ PREFORK_SPLIT = 0x08,
+ /* SHWORDSPLIT in parameter expn */
+ PREFORK_SHWORDSPLIT = 0x10,
+ /* SHWORDSPLIT forced off in nested subst */
+ PREFORK_NOSHWORDSPLIT = 0x20,
+ /* Prefork is part of a parameter subexpression */
+ PREFORK_SUBEXP = 0x40,
+ /* Prefork detected an assignment list with [key]=value syntax,
+ * Only used on return from prefork, not meaningful passed down.
+ * Also used as flag to globlist.
+ */
+ PREFORK_KEY_VALUE = 0x80,
+ /* No untokenise: used only as flag to globlist */
+ PREFORK_NO_UNTOK = 0x100
+};
+
+/*
+ * Bit flags passed back from multsub() to paramsubst().
+ * Some flags go from a nested parmsubst() through the enclosing
+ * stringsubst() and prefork().
+ */
+enum {
+ /*
+ * Set if the string had whitespace at the start
+ * that should cause word splitting against any preceeding string.
+ */
+ MULTSUB_WS_AT_START = 1,
+ /*
+ * Set if the string had whitespace at the end
+ * that should cause word splitting against any following string.
+ */
+ MULTSUB_WS_AT_END = 2,
+ /*
+ * Set by nested paramsubst() to indicate the return
+ * value is a parameter name, rather than a value.
+ */
+ MULTSUB_PARAM_NAME = 4
+};
+
+/*
+ * Structure for adding parameters in a module.
+ * The flags should declare the type; note PM_SCALAR is zero.
+ *
+ * Special hashes are recognized by getnfn so the PM_HASHED
+ * is optional. These get slightly non-standard attention:
+ * the function createspecialhash is used to create them.
+ *
+ * The get/set/unset attribute may be NULL; in that case the
+ * parameter is assigned methods suitable for handling the
+ * tie variable var, if that is not NULL, else standard methods.
+ *
+ * pm is set when the parameter is added to the parameter table
+ * and serves as a flag that the parameter has been added.
+ */
+struct paramdef {
+ char *name;
+ int flags;
+ void *var; /* tied internal variable, if any */
+ const void *gsu; /* get/set/unset structure, if special */
+ GetNodeFunc getnfn; /* function to get node, if special hash */
+ ScanTabFunc scantfn; /* function to scan table, if special hash */
+ Param pm; /* structure inserted into param table */
+};
+
+/*
+ * Shorthand for common uses of adding parameters, with no special
+ * hash properties.
+ */
+#define PARAMDEF(name, flags, var, gsu) \
+ { name, flags, (void *) var, (void *) gsu, \
+ NULL, NULL, NULL \
+ }
+/*
+ * Note that the following definitions are appropriate for defining
+ * parameters that reference a variable (var). Hence the get/set/unset
+ * methods used will assume var needs dereferencing to get the value.
+ */
+#define INTPARAMDEF(name, var) \
+ { name, PM_INTEGER, (void *) var, NULL, NULL, NULL, NULL }
+#define STRPARAMDEF(name, var) \
+ { name, PM_SCALAR, (void *) var, NULL, NULL, NULL, NULL }
+#define ARRPARAMDEF(name, var) \
+ { name, PM_ARRAY, (void *) var, NULL, NULL, NULL, NULL }
+/*
+ * The following is appropriate for a module function that behaves
+ * in a special fashion. Parameters used in a module that don't
+ * have special behaviour shouldn't be declared in a table but
+ * should just be added with the standard parameter functions.
+ *
+ * These parameters are not marked as removable, since they
+ * shouldn't be loaded as local parameters, unlike the special
+ * Zle parameters that are added and removed on each call to Zle.
+ * We add the PM_REMOVABLE flag when removing the feature corresponding
+ * to the parameter.
+ */
+#define SPECIALPMDEF(name, flags, gsufn, getfn, scanfn) \
+ { name, flags | PM_SPECIAL | PM_HIDE | PM_HIDEVAL, \
+ NULL, gsufn, getfn, scanfn, NULL }
+
+/*
+ * Flags for assignsparam and assignaparam.
+ */
+enum {
+ /* Add to rather than override value */
+ ASSPM_AUGMENT = 1 << 0,
+ /* Test for warning if creating global variable in function */
+ ASSPM_WARN_CREATE = 1 << 1,
+ /* Test for warning if using nested variable in function */
+ ASSPM_WARN_NESTED = 1 << 2,
+ ASSPM_WARN = (ASSPM_WARN_CREATE|ASSPM_WARN_NESTED),
+ /* Import from environment, so exercise care evaluating value */
+ ASSPM_ENV_IMPORT = 1 << 3,
+ /* Array is key / value pairs.
+ * This is normal for associative arrays but variant behaviour for
+ * normal arrays.
+ */
+ ASSPM_KEY_VALUE = 1 << 4
+};
+
+/* node for named directory hash table (nameddirtab) */
+
+struct nameddir {
+ struct hashnode node;
+ 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 */
+#define ND_NOABBREV (1<<2) /* never print as abbrev (PWD or OLDPWD) */
+
+/* Storage for single group/name mapping */
+typedef struct {
+ /* Name of group */
+ char *name;
+ /* Group identifier */
+ gid_t gid;
+} groupmap;
+typedef groupmap *Groupmap;
+
+/* Storage for a set of group/name mappings */
+typedef struct {
+ /* The set of name to gid mappings */
+ Groupmap array;
+ /* A count of the valid entries in groupmap. */
+ int num;
+} groupset;
+typedef groupset *Groupset;
+
+/* flags for controlling printing of hash table nodes */
+#define PRINT_NAMEONLY (1<<0)
+#define PRINT_TYPE (1<<1)
+#define PRINT_LIST (1<<2)
+#define PRINT_KV_PAIR (1<<3)
+#define PRINT_INCLUDEVALUE (1<<4)
+#define PRINT_TYPESET (1<<5)
+#define PRINT_LINE (1<<6)
+
+/* flags for printing for the whence builtin */
+#define PRINT_WHENCE_CSH (1<<7)
+#define PRINT_WHENCE_VERBOSE (1<<8)
+#define PRINT_WHENCE_SIMPLE (1<<9)
+#define PRINT_WHENCE_FUNCDEF (1<<10)
+#define PRINT_WHENCE_WORD (1<<11)
+
+/* Return values from loop() */
+
+enum loop_return {
+ /* Loop executed OK */
+ LOOP_OK,
+ /* Loop executed no code */
+ LOOP_EMPTY,
+ /* Loop encountered an error */
+ LOOP_ERROR
+};
+
+/* Return values from source() */
+
+enum source_return {
+ /* Source ran OK */
+ SOURCE_OK = 0,
+ /* File not found */
+ SOURCE_NOT_FOUND = 1,
+ /* Internal error sourcing file */
+ SOURCE_ERROR = 2
+};
+
+enum noerrexit_bits {
+ /* Suppress ERR_EXIT and traps: global */
+ NOERREXIT_EXIT = 1,
+ /* Suppress ERR_RETURN: per function call */
+ NOERREXIT_RETURN = 2,
+ /* NOERREXIT only needed on way down */
+ NOERREXIT_UNTIL_EXEC = 4,
+ /* Force exit on SIGINT */
+ NOERREXIT_SIGNAL = 8
+};
+
+/***********************************/
+/* Definitions for history control */
+/***********************************/
+
+/* history entry */
+
+struct histent {
+ struct hashnode node;
+
+ Histent up; /* previous line (moving upward) */
+ Histent down; /* next line (moving downward) */
+ char *zle_text; /* the edited history line,
+ * a metafied, NULL-terminated string,
+ * i.e the same format as the original
+ * entry
+ */
+ 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 */
+ zlong histnum; /* A sequential history number */
+};
+
+#define HIST_MAKEUNIQUE 0x00000001 /* Kill this new entry if not unique */
+#define HIST_OLD 0x00000002 /* Command is already written to disk*/
+#define HIST_READ 0x00000004 /* Command was read back from disk*/
+#define HIST_DUP 0x00000008 /* Command duplicates a later line */
+#define HIST_FOREIGN 0x00000010 /* Command came from another shell */
+#define HIST_TMPSTORE 0x00000020 /* Kill when user enters another cmd */
+#define HIST_NOWRITE 0x00000040 /* Keep internally but don't write */
+
+#define GETHIST_UPWARD (-1)
+#define GETHIST_DOWNWARD 1
+#define GETHIST_EXACT 0
+
+/* 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
+#define HISTFLAG_SETTY 8
+
+#define HFILE_APPEND 0x0001
+#define HFILE_SKIPOLD 0x0002
+#define HFILE_SKIPDUPS 0x0004
+#define HFILE_SKIPFOREIGN 0x0008
+#define HFILE_FAST 0x0010
+#define HFILE_NO_REWRITE 0x0020
+#define HFILE_USE_OPTIONS 0x8000
+
+/*
+ * Flags argument to bufferwords() used
+ * also by lexflags variable.
+ */
+/*
+ * Kick the lexer into special string-analysis
+ * mode without parsing. Any bit set in
+ * the flags has this effect, but this
+ * has otherwise all the default effects.
+ */
+#define LEXFLAGS_ACTIVE 0x0001
+/*
+ * Being used from zle. This is slightly more intrusive
+ * (=> grotesquely non-modular) than use from within
+ * the main shell, so it's a separate flag.
+ */
+#define LEXFLAGS_ZLE 0x0002
+/*
+ * Parse comments and treat each comment as a single string
+ */
+#define LEXFLAGS_COMMENTS_KEEP 0x0004
+/*
+ * Parse comments and strip them.
+ */
+#define LEXFLAGS_COMMENTS_STRIP 0x0008
+/*
+ * Either of the above
+ */
+#define LEXFLAGS_COMMENTS (LEXFLAGS_COMMENTS_KEEP|LEXFLAGS_COMMENTS_STRIP)
+/*
+ * Treat newlines as whitespace
+ */
+#define LEXFLAGS_NEWLINE 0x0010
+
+/******************************************/
+/* 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
+/* In a parameter name in an assignment. */
+#define IN_PAR 5
+
+
+/******************************/
+/* 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 */
+
+/* Test for a shell emulation. Use this rather than emulation directly. */
+#define EMULATION(X) (emulation & (X))
+
+/* Return only base shell emulation field. */
+#define SHELL_EMULATION() (emulation & ((1<<5)-1))
+
+/* Additional flags */
+
+#define EMULATE_FULLY (1<<5) /* "emulate -R" in effect */
+/*
+ * Higher bits are used in options.c, record lowest unused bit...
+ */
+#define EMULATE_UNUSED (1<<6)
+
+/* option indices */
+
+enum {
+ OPT_INVALID,
+ ALIASESOPT,
+ ALIASFUNCDEF,
+ ALLEXPORT,
+ ALWAYSLASTPROMPT,
+ ALWAYSTOEND,
+ APPENDHISTORY,
+ AUTOCD,
+ AUTOCONTINUE,
+ AUTOLIST,
+ AUTOMENU,
+ AUTONAMEDIRS,
+ AUTOPARAMKEYS,
+ AUTOPARAMSLASH,
+ AUTOPUSHD,
+ AUTOREMOVESLASH,
+ AUTORESUME,
+ BADPATTERN,
+ BANGHIST,
+ BAREGLOBQUAL,
+ BASHAUTOLIST,
+ BASHREMATCH,
+ BEEP,
+ BGNICE,
+ BRACECCL,
+ BSDECHO,
+ CASEGLOB,
+ CASEMATCH,
+ CBASES,
+ CDABLEVARS,
+ CHASEDOTS,
+ CHASELINKS,
+ CHECKJOBS,
+ CHECKRUNNINGJOBS,
+ CLOBBER,
+ APPENDCREATE,
+ COMBININGCHARS,
+ COMPLETEALIASES,
+ COMPLETEINWORD,
+ CORRECT,
+ CORRECTALL,
+ CONTINUEONERROR,
+ CPRECEDENCES,
+ CSHJUNKIEHISTORY,
+ CSHJUNKIELOOPS,
+ CSHJUNKIEQUOTES,
+ CSHNULLCMD,
+ CSHNULLGLOB,
+ DEBUGBEFORECMD,
+ EMACSMODE,
+ EQUALS,
+ ERREXIT,
+ ERRRETURN,
+ EXECOPT,
+ EXTENDEDGLOB,
+ EXTENDEDHISTORY,
+ EVALLINENO,
+ FLOWCONTROL,
+ FORCEFLOAT,
+ FUNCTIONARGZERO,
+ GLOBOPT,
+ GLOBALEXPORT,
+ GLOBALRCS,
+ GLOBASSIGN,
+ GLOBCOMPLETE,
+ GLOBDOTS,
+ GLOBSTARSHORT,
+ GLOBSUBST,
+ HASHCMDS,
+ HASHDIRS,
+ HASHEXECUTABLESONLY,
+ HASHLISTALL,
+ HISTALLOWCLOBBER,
+ HISTBEEP,
+ HISTEXPIREDUPSFIRST,
+ HISTFCNTLLOCK,
+ HISTFINDNODUPS,
+ HISTIGNOREALLDUPS,
+ HISTIGNOREDUPS,
+ HISTIGNORESPACE,
+ HISTLEXWORDS,
+ HISTNOFUNCTIONS,
+ HISTNOSTORE,
+ HISTREDUCEBLANKS,
+ HISTSAVEBYCOPY,
+ HISTSAVENODUPS,
+ HISTSUBSTPATTERN,
+ HISTVERIFY,
+ HUP,
+ IGNOREBRACES,
+ IGNORECLOSEBRACES,
+ IGNOREEOF,
+ INCAPPENDHISTORY,
+ INCAPPENDHISTORYTIME,
+ INTERACTIVE,
+ INTERACTIVECOMMENTS,
+ KSHARRAYS,
+ KSHAUTOLOAD,
+ KSHGLOB,
+ KSHOPTIONPRINT,
+ KSHTYPESET,
+ KSHZEROSUBSCRIPT,
+ LISTAMBIGUOUS,
+ LISTBEEP,
+ LISTPACKED,
+ LISTROWSFIRST,
+ LISTTYPES,
+ LOCALLOOPS,
+ LOCALOPTIONS,
+ LOCALPATTERNS,
+ LOCALTRAPS,
+ LOGINSHELL,
+ LONGLISTJOBS,
+ MAGICEQUALSUBST,
+ MAILWARNING,
+ MARKDIRS,
+ MENUCOMPLETE,
+ MONITOR,
+ MULTIBYTE,
+ MULTIFUNCDEF,
+ MULTIOS,
+ NOMATCH,
+ NOTIFY,
+ NULLGLOB,
+ NUMERICGLOBSORT,
+ OCTALZEROES,
+ OVERSTRIKE,
+ PATHDIRS,
+ PATHSCRIPT,
+ PIPEFAIL,
+ POSIXALIASES,
+ POSIXARGZERO,
+ POSIXBUILTINS,
+ POSIXCD,
+ POSIXIDENTIFIERS,
+ POSIXJOBS,
+ POSIXSTRINGS,
+ POSIXTRAPS,
+ PRINTEIGHTBIT,
+ PRINTEXITVALUE,
+ PRIVILEGED,
+ PROMPTBANG,
+ PROMPTCR,
+ PROMPTPERCENT,
+ PROMPTSP,
+ PROMPTSUBST,
+ PUSHDIGNOREDUPS,
+ PUSHDMINUS,
+ PUSHDSILENT,
+ PUSHDTOHOME,
+ RCEXPANDPARAM,
+ RCQUOTES,
+ RCS,
+ RECEXACT,
+ REMATCHPCRE,
+ RESTRICTED,
+ RMSTARSILENT,
+ RMSTARWAIT,
+ SHAREHISTORY,
+ SHFILEEXPANSION,
+ SHGLOB,
+ SHINSTDIN,
+ SHNULLCMD,
+ SHOPTIONLETTERS,
+ SHORTLOOPS,
+ SHWORDSPLIT,
+ SINGLECOMMAND,
+ SINGLELINEZLE,
+ SOURCETRACE,
+ SUNKEYBOARDHACK,
+ TRANSIENTRPROMPT,
+ TRAPSASYNC,
+ TYPESETSILENT,
+ UNSET,
+ VERBOSE,
+ VIMODE,
+ WARNCREATEGLOBAL,
+ WARNNESTEDVAR,
+ XTRACE,
+ USEZLE,
+ DVORAK,
+ OPT_SIZE
+};
+
+/*
+ * Size required to fit an option number.
+ * If OPT_SIZE goes above 256 this will need to expand.
+ */
+typedef unsigned char OptIndex;
+
+#undef isset
+#define isset(X) (opts[X])
+#define unset(X) (!opts[X])
+
+#define interact (isset(INTERACTIVE))
+#define jobbing (isset(MONITOR))
+#define islogin (isset(LOGINSHELL))
+
+/*
+ * Record of emulation and options that need to be set
+ * for a full "emulate".
+ */
+struct emulation_options {
+ /* The emulation itself */
+ int emulation;
+ /* The number of options in on_opts. */
+ int n_on_opts;
+ /* The number of options in off_opts. */
+ int n_off_opts;
+ /*
+ * Array of options to be turned on.
+ * Only options specified explicitly in the emulate command
+ * are recorded. Null if n_on_opts is zero.
+ */
+ OptIndex *on_opts;
+ /* Array of options to be turned off, similar. */
+ OptIndex *off_opts;
+};
+
+/***********************************************/
+/* Definitions 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
+};
+
+#ifndef __INTERIX
+/* 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
+# ifdef XTABS
+#define SGTABTYPE XTABS
+# endif
+# endif
+# 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 TCHORIZPOS 24
+#define TCUPCURSOR 25
+#define TCDOWNCURSOR 26
+#define TCLEFTCURSOR 27
+#define TCRIGHTCURSOR 28
+#define TCSAVECURSOR 29
+#define TCRESTRCURSOR 30
+#define TCBACKSPACE 31
+#define TCFGCOLOUR 32
+#define TCBGCOLOUR 33
+#define TC_COUNT 34
+
+#define tccan(X) (tclen[X])
+
+/*
+ * Text attributes for displaying in ZLE
+ */
+
+#define TXTBOLDFACE 0x0001
+#define TXTSTANDOUT 0x0002
+#define TXTUNDERLINE 0x0004
+#define TXTFGCOLOUR 0x0008
+#define TXTBGCOLOUR 0x0010
+
+#define TXT_ATTR_ON_MASK 0x001F
+
+#define txtisset(X) (txtattrmask & (X))
+#define txtset(X) (txtattrmask |= (X))
+#define txtunset(X) (txtattrmask &= ~(X))
+
+#define TXTNOBOLDFACE 0x0020
+#define TXTNOSTANDOUT 0x0040
+#define TXTNOUNDERLINE 0x0080
+#define TXTNOFGCOLOUR 0x0100
+#define TXTNOBGCOLOUR 0x0200
+
+#define TXT_ATTR_OFF_MASK 0x03E0
+/* Bits to shift off right to get on */
+#define TXT_ATTR_OFF_ON_SHIFT 5
+#define TXT_ATTR_OFF_FROM_ON(attr) \
+ (((attr) & TXT_ATTR_ON_MASK) << TXT_ATTR_OFF_ON_SHIFT)
+#define TXT_ATTR_ON_FROM_OFF(attr) \
+ (((attr) & TXT_ATTR_OFF_MASK) >> TXT_ATTR_OFF_ON_SHIFT)
+/*
+ * Indicates to zle_refresh.c that the character entry is an
+ * index into the list of multiword symbols.
+ */
+#define TXT_MULTIWORD_MASK 0x0400
+
+/* Mask for colour to use in foreground */
+#define TXT_ATTR_FG_COL_MASK 0x000FF000
+/* Bits to shift the foreground colour */
+#define TXT_ATTR_FG_COL_SHIFT (12)
+/* Mask for colour to use in background */
+#define TXT_ATTR_BG_COL_MASK 0x0FF00000
+/* Bits to shift the background colour */
+#define TXT_ATTR_BG_COL_SHIFT (20)
+
+/* Flag to use termcap AF sequence to set colour, if available */
+#define TXT_ATTR_FG_TERMCAP 0x10000000
+/* Flag to use termcap AB sequence to set colour, if available */
+#define TXT_ATTR_BG_TERMCAP 0x20000000
+
+/* Things to turn on, including values for the colour elements */
+#define TXT_ATTR_ON_VALUES_MASK \
+ (TXT_ATTR_ON_MASK|TXT_ATTR_FG_COL_MASK|TXT_ATTR_BG_COL_MASK|\
+ TXT_ATTR_FG_TERMCAP|TXT_ATTR_BG_TERMCAP)
+
+/* Mask out everything to do with setting a foreground colour */
+#define TXT_ATTR_FG_ON_MASK \
+ (TXTFGCOLOUR|TXT_ATTR_FG_COL_MASK|TXT_ATTR_FG_TERMCAP)
+
+/* Mask out everything to do with setting a background colour */
+#define TXT_ATTR_BG_ON_MASK \
+ (TXTBGCOLOUR|TXT_ATTR_BG_COL_MASK|TXT_ATTR_BG_TERMCAP)
+
+/* Mask out everything to do with activating colours */
+#define TXT_ATTR_COLOUR_ON_MASK \
+ (TXT_ATTR_FG_ON_MASK|TXT_ATTR_BG_ON_MASK)
+
+#define txtchangeisset(T,X) ((T) & (X))
+#define txtchangeget(T,A) (((T) & A ## _MASK) >> A ## _SHIFT)
+#define txtchangeset(T, X, Y) ((void)(T && (*T &= ~(Y), *T |= (X))))
+
+/*
+ * For outputting sequences to change colour: specify foreground
+ * or background.
+ */
+#define COL_SEQ_FG (0)
+#define COL_SEQ_BG (1)
+#define COL_SEQ_COUNT (2)
+
+/*
+ * Flags to testcap() and set_colour_attribute (which currently only
+ * handles TSC_PROMPT).
+ */
+enum {
+ /* Raw output: use stdout rather than shout */
+ TSC_RAW = 0x0001,
+ /* Output to current prompt buffer: only used when assembling prompt */
+ TSC_PROMPT = 0x0002,
+ /* Mask to get the output mode */
+ TSC_OUTPUT_MASK = 0x0003,
+ /* Change needs reset of other attributes */
+ TSC_DIRTY = 0x0004
+};
+
+/****************************************/
+/* Definitions for the %_ prompt escape */
+/****************************************/
+
+#define CMDSTACKSZ 256
+
+#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
+#define CS_ALWAYS 31
+
+/* Increment as necessary */
+#define CS_COUNT 32
+
+/*********************
+ * Memory management *
+ *********************/
+
+/*
+ * A Heapid is a type for identifying, uniquely up to the point where
+ * the count of new identifiers wraps. all heaps that are or
+ * (importantly) have been valid. Each valid heap is given an
+ * identifier, and every time we push a heap we save the old identifier
+ * and give the heap a new identifier so that when the heap is popped
+ * or freed we can spot anything using invalid memory from the popped
+ * heap.
+ *
+ * We could make this unsigned long long if we wanted a big range.
+ */
+typedef unsigned int Heapid;
+
+#ifdef ZSH_HEAP_DEBUG
+
+/* printf format specifier corresponding to Heapid */
+#define HEAPID_FMT "%x"
+
+/* Marker that memory is permanently allocated */
+#define HEAPID_PERMANENT (UINT_MAX)
+
+/*
+ * Heap debug verbosity.
+ * Bits to be 'or'ed into the variable also called heap_debug_verbosity.
+ */
+enum heap_debug_verbosity {
+ /* Report when we push a heap */
+ HDV_PUSH = 0x01,
+ /* Report when we pop a heap */
+ HDV_POP = 0x02,
+ /* Report when we create a new heap from which to allocate */
+ HDV_CREATE = 0x04,
+ /* Report every time we free a complete heap */
+ HDV_FREE = 0x08,
+ /* Report when we temporarily install a new set of heaps */
+ HDV_NEW = 0x10,
+ /* Report when we restore an old set of heaps */
+ HDV_OLD = 0x20,
+ /* Report when we temporarily switch heaps */
+ HDV_SWITCH = 0x40,
+ /*
+ * Report every time we allocate memory from the heap.
+ * This is very verbose, and arguably not very useful: we
+ * would expect to allocate memory from a heap we create.
+ * For much debugging heap_debug_verbosity = 0x7f should be sufficient.
+ */
+ HDV_ALLOC = 0x80
+};
+
+#define HEAP_ERROR(heap_id) \
+ fprintf(stderr, "%s:%d: HEAP DEBUG: invalid heap: " HEAPID_FMT ".\n", \
+ __FILE__, __LINE__, heap_id)
+#endif
+
+/* heappush saves the current heap state using this structure */
+
+struct heapstack {
+ struct heapstack *next; /* next one in list for this heap */
+ size_t used;
+#ifdef ZSH_HEAP_DEBUG
+ Heapid heap_id;
+#endif
+};
+
+/* A zsh heap. */
+
+struct heap {
+ struct heap *next; /* next one */
+ size_t size; /* size of heap */
+ size_t used; /* bytes used from the heap */
+ struct heapstack *sp; /* used by pushheap() to save the value used */
+
+#ifdef ZSH_HEAP_DEBUG
+ unsigned int heap_id;
+#endif
+
+/* Uncomment the following if the struct needs padding to 64-bit size. */
+/* Make sure sizeof(heap) is a multiple of 8
+#if defined(PAD_64_BIT) && !defined(__GNUC__)
+ size_t dummy;
+#endif
+*/
+#define arena(X) ((char *) (X) + sizeof(struct heap))
+}
+#if defined(PAD_64_BIT) && defined(__GNUC__)
+ __attribute__ ((aligned (8)))
+#endif
+;
+
+# define NEWHEAPS(h) do { Heap _switch_oldheaps = h = new_heaps(); do
+# define OLDHEAPS while (0); old_heaps(_switch_oldheaps); } while (0);
+
+# define SWITCHHEAPS(o, h) do { o = switch_heaps(h); do
+# define SWITCHBACKHEAPS(o) while (0); switch_heaps(o); } while (0);
+
+/****************/
+/* Debug macros */
+/****************/
+
+#ifdef DEBUG
+#define STRINGIFY_LITERAL(x) # x
+#define STRINGIFY(x) STRINGIFY_LITERAL(x)
+#define ERRMSG(x) (__FILE__ ":" STRINGIFY(__LINE__) ": " x)
+# define DPUTS(X,Y) if (!(X)) {;} else dputs(ERRMSG(Y))
+# define DPUTS1(X,Y,Z1) if (!(X)) {;} else dputs(ERRMSG(Y), Z1)
+# define DPUTS2(X,Y,Z1,Z2) if (!(X)) {;} else dputs(ERRMSG(Y), Z1, Z2)
+# define DPUTS3(X,Y,Z1,Z2,Z3) if (!(X)) {;} else dputs(ERRMSG(Y), Z1, Z2, Z3)
+#else
+# define DPUTS(X,Y)
+# define DPUTS1(X,Y,Z1)
+# define DPUTS2(X,Y,Z1,Z2)
+# define DPUTS3(X,Y,Z1,Z2,Z3)
+#endif
+
+/**************************/
+/* Signal handling macros */
+/**************************/
+
+/* These used in the sigtrapped[] array */
+
+#define ZSIG_TRAPPED (1<<0) /* Signal is trapped */
+#define ZSIG_IGNORED (1<<1) /* Signal is ignored */
+#define ZSIG_FUNC (1<<2) /* Trap is a function, not an eval list */
+/* Mask to get the above flags */
+#define ZSIG_MASK (ZSIG_TRAPPED|ZSIG_IGNORED|ZSIG_FUNC)
+/* No. of bits to shift local level when storing in sigtrapped */
+#define ZSIG_ALIAS (1<<3) /* Trap is stored under an alias */
+#define ZSIG_SHIFT 4
+
+/*
+ * State of traps, stored in trap_state.
+ */
+enum trap_state {
+ /* Traps are not active; trap_return is not useful. */
+ TRAP_STATE_INACTIVE,
+ /*
+ * Traps are set but haven't triggered; trap_return gives
+ * minus function depth.
+ */
+ TRAP_STATE_PRIMED,
+ /*
+ * Trap has triggered to force a return; trap_return givens
+ * return value.
+ */
+ TRAP_STATE_FORCE_RETURN
+};
+
+#define IN_EVAL_TRAP() \
+ (intrap && !trapisfunc && traplocallevel == locallevel)
+
+/*
+ * Bits in the errflag variable.
+ */
+enum errflag_bits {
+ /*
+ * Standard internal error bit.
+ */
+ ERRFLAG_ERROR = 1,
+ /*
+ * User interrupt.
+ */
+ ERRFLAG_INT = 2,
+ /*
+ * Hard error --- return to top-level prompt in interactive
+ * shell. In non-interactive shell we'll typically already
+ * have exited. This is reset by "errflag = 0" in
+ * loop(toplevel = 1, ...).
+ */
+ ERRFLAG_HARD = 4
+};
+
+/***********/
+/* Sorting */
+/***********/
+
+typedef int (*CompareFn) _((const void *, const void *));
+
+enum {
+ SORTIT_ANYOLDHOW = 0, /* Defaults */
+ SORTIT_IGNORING_CASE = 1,
+ SORTIT_NUMERICALLY = 2,
+ SORTIT_BACKWARDS = 4,
+ /*
+ * Ignore backslashes that quote another character---which may
+ * be another backslash; the second backslash is active.
+ */
+ SORTIT_IGNORING_BACKSLASHES = 8,
+ /*
+ * Ignored by strmetasort(); used by paramsubst() to indicate
+ * there is some sorting to do.
+ */
+ SORTIT_SOMEHOW = 16,
+};
+
+/*
+ * Element of array passed to qsort().
+ */
+struct sortelt {
+ /* The original string. */
+ char *orig;
+ /* The string used for comparison. */
+ const char *cmp;
+ /*
+ * The length of the string if passed down to the sort algorithm.
+ * Used to sort the lengths together with the strings.
+ */
+ int origlen;
+ /*
+ * The length of the string, if needed, else -1.
+ * The length is only needed if there are embededded nulls.
+ */
+ int len;
+};
+
+typedef struct sortelt *SortElt;
+
+/*********************************************************/
+/* Structures to save and restore for individual modules */
+/*********************************************************/
+
+/* History */
+struct hist_stack {
+ int histactive;
+ int histdone;
+ int stophist;
+ int hlinesz;
+ zlong defev;
+ char *hline;
+ char *hptr;
+ short *chwords;
+ int chwordlen;
+ int chwordpos;
+ int (*hgetc) _((void));
+ void (*hungetc) _((int));
+ void (*hwaddc) _((int));
+ void (*hwbegin) _((int));
+ void (*hwabort) _((void));
+ void (*hwend) _((void));
+ void (*addtoline) _((int));
+ unsigned char *cstack;
+ int csp;
+ int hist_keep_comment;
+};
+
+/*
+ * State of a lexical token buffer.
+ *
+ * It would be neater to include the pointer to the start of the buffer,
+ * however the current code structure means that the standard instance
+ * of this, tokstr, is visible in lots of places, so that's not
+ * convenient.
+ */
+
+struct lexbufstate {
+ /*
+ * Next character to be added.
+ * Set to NULL when the buffer is to be visible from elsewhere.
+ */
+ char *ptr;
+ /* Allocated buffer size */
+ int siz;
+ /* Length in use */
+ int len;
+};
+
+/* Lexical analyser */
+struct lex_stack {
+ int dbparens;
+ int isfirstln;
+ int isfirstch;
+ int lexflags;
+ enum lextok tok;
+ char *tokstr;
+ char *zshlextext;
+ struct lexbufstate lexbuf;
+ int lex_add_raw;
+ char *tokstr_raw;
+ struct lexbufstate lexbuf_raw;
+ int lexstop;
+ zlong toklineno;
+};
+
+/* Parser */
+struct parse_stack {
+ struct heredocs *hdocs;
+
+ int incmdpos;
+ int aliasspaceflag;
+ int incond;
+ int inredir;
+ int incasepat;
+ int isnewlin;
+ int infor;
+ int inrepeat_;
+ int intypeset;
+
+ int eclen, ecused, ecnpats;
+ Wordcode ecbuf;
+ Eccstr ecstrs;
+ int ecsoffs, ecssub, ecnfunc;
+};
+
+/************************/
+/* Flags to casemodifiy */
+/************************/
+
+enum {
+ CASMOD_NONE, /* dummy for tests */
+ CASMOD_UPPER,
+ CASMOD_LOWER,
+ CASMOD_CAPS
+};
+
+/*******************************************/
+/* Flags to third argument of getkeystring */
+/*******************************************/
+
+/*
+ * By default handles some subset of \-escapes. The following bits
+ * turn on extra features.
+ */
+enum {
+ /*
+ * Handle octal where the first digit is non-zero e.g. \3, \33, \333
+ * Otherwise \0333 etc. is handled, i.e. one of \0123 or \123 will
+ * work, but not both.
+ */
+ GETKEY_OCTAL_ESC = (1 << 0),
+ /*
+ * Handle Emacs-like key sequences \C-x etc.
+ * Also treat \E like \e and use backslashes to escape the
+ * next character if not special, i.e. do all the things we
+ * don't do with the echo builtin.
+ */
+ GETKEY_EMACS = (1 << 1),
+ /* Handle ^X etc. */
+ GETKEY_CTRL = (1 << 2),
+ /* Handle \c (uses misc arg to getkeystring()) */
+ GETKEY_BACKSLASH_C = (1 << 3),
+ /* Do $'...' quoting (len arg to getkeystring() not used) */
+ GETKEY_DOLLAR_QUOTE = (1 << 4),
+ /* Handle \- (uses misc arg to getkeystring()) */
+ GETKEY_BACKSLASH_MINUS = (1 << 5),
+ /* Parse only one character (len arg to getkeystring() not used) */
+ GETKEY_SINGLE_CHAR = (1 << 6),
+ /*
+ * If beyond offset in misc arg, add 1 to it for each character removed.
+ * Yes, I know that doesn't seem to make much sense.
+ * It's for use in completion, comprenez?
+ */
+ GETKEY_UPDATE_OFFSET = (1 << 7),
+ /*
+ * When replacing numeric escapes for printf format strings, % -> %%
+ */
+ GETKEY_PRINTF_PERCENT = (1 << 8)
+};
+
+/*
+ * Standard combinations used within the shell.
+ * Note GETKEYS_... instead of GETKEY_...: this is important in some cases.
+ */
+/* echo builtin */
+#define GETKEYS_ECHO (GETKEY_BACKSLASH_C)
+/* printf format string: \123 -> S, \0123 -> NL 3, \045 -> %% */
+#define GETKEYS_PRINTF_FMT \
+ (GETKEY_OCTAL_ESC|GETKEY_BACKSLASH_C|GETKEY_PRINTF_PERCENT)
+/* printf argument: \123 -> \123, \0123 -> S */
+#define GETKEYS_PRINTF_ARG (GETKEY_BACKSLASH_C)
+/* Full print without -e */
+#define GETKEYS_PRINT (GETKEY_OCTAL_ESC|GETKEY_BACKSLASH_C|GETKEY_EMACS)
+/* bindkey */
+#define GETKEYS_BINDKEY (GETKEY_OCTAL_ESC|GETKEY_EMACS|GETKEY_CTRL)
+/* $'...' */
+#define GETKEYS_DOLLARS_QUOTE (GETKEY_OCTAL_ESC|GETKEY_EMACS|GETKEY_DOLLAR_QUOTE)
+/* Single character for math processing */
+#define GETKEYS_MATH \
+ (GETKEY_OCTAL_ESC|GETKEY_EMACS|GETKEY_CTRL|GETKEY_SINGLE_CHAR)
+/* Used to process separators etc. with print-style escapes */
+#define GETKEYS_SEP (GETKEY_OCTAL_ESC|GETKEY_EMACS)
+/* Used for suffix removal */
+#define GETKEYS_SUFFIX \
+ (GETKEY_OCTAL_ESC|GETKEY_EMACS|GETKEY_CTRL|GETKEY_BACKSLASH_MINUS)
+
+/**********************************/
+/* Flags to third argument of zle */
+/**********************************/
+
+#define ZLRF_HISTORY 0x01 /* OK to access the history list */
+#define ZLRF_NOSETTY 0x02 /* Don't set tty before return */
+#define ZLRF_IGNOREEOF 0x04 /* Ignore an EOF from the keyboard */
+
+/***************************/
+/* Context of zleread call */
+/***************************/
+
+enum {
+ ZLCON_LINE_START, /* Command line at PS1 */
+ ZLCON_LINE_CONT, /* Command line at PS2 */
+ ZLCON_SELECT, /* Select loop */
+ ZLCON_VARED /* Vared command */
+};
+
+/****************/
+/* Entry points */
+/****************/
+
+/* compctl entry point pointers */
+
+typedef int (*CompctlReadFn) _((char *, char **, Options, char *));
+
+/* ZLE entry point pointer */
+
+typedef char * (*ZleEntryPoint)(int cmd, va_list ap);
+
+/* Commands to pass to entry point */
+
+enum {
+ ZLE_CMD_GET_LINE,
+ ZLE_CMD_READ,
+ ZLE_CMD_ADD_TO_LINE,
+ ZLE_CMD_TRASH,
+ ZLE_CMD_RESET_PROMPT,
+ ZLE_CMD_REFRESH,
+ ZLE_CMD_SET_KEYMAP,
+ ZLE_CMD_GET_KEY,
+ ZLE_CMD_SET_HIST_LINE
+};
+
+/***************************************/
+/* Hooks in core. */
+/***************************************/
+
+#define EXITHOOK (zshhooks + 0)
+#define BEFORETRAPHOOK (zshhooks + 1)
+#define AFTERTRAPHOOK (zshhooks + 2)
+
+#ifdef MULTIBYTE_SUPPORT
+/* Final argument to mb_niceformat() */
+enum {
+ NICEFLAG_HEAP = 1, /* Heap allocation where needed */
+ NICEFLAG_QUOTE = 2, /* Result will appear in $'...' */
+ NICEFLAG_NODUP = 4, /* Leave allocated */
+};
+
+/* Metafied input */
+#define nicezputs(str, outs) (void)mb_niceformat((str), (outs), NULL, 0)
+#define MB_METACHARINIT() mb_charinit()
+typedef wint_t convchar_t;
+#define MB_METACHARLENCONV(str, cp) mb_metacharlenconv((str), (cp))
+#define MB_METACHARLEN(str) mb_metacharlenconv(str, NULL)
+#define MB_METASTRLEN(str) mb_metastrlenend(str, 0, NULL)
+#define MB_METASTRWIDTH(str) mb_metastrlenend(str, 1, NULL)
+#define MB_METASTRLEN2(str, widthp) mb_metastrlenend(str, widthp, NULL)
+#define MB_METASTRLEN2END(str, widthp, eptr) \
+ mb_metastrlenend(str, widthp, eptr)
+
+/* Unmetafined input */
+#define MB_CHARINIT() mb_charinit()
+#define MB_CHARLENCONV(str, len, cp) mb_charlenconv((str), (len), (cp))
+#define MB_CHARLEN(str, len) mb_charlenconv((str), (len), NULL)
+
+/*
+ * We replace broken implementations with one that uses Unicode
+ * characters directly as wide characters. In principle this is only
+ * likely to work if __STDC_ISO_10646__ is defined, since that's pretty
+ * much what the definition tells us. However, we happen to know this
+ * works on MacOS which doesn't define that.
+ */
+#ifdef ENABLE_UNICODE9
+#define WCWIDTH(wc) u9_wcwidth(wc)
+#else
+#define WCWIDTH(wc) wcwidth(wc)
+#endif
+/*
+ * Note WCWIDTH_WINT() takes wint_t, typically as a convchar_t.
+ * It's written to use the wint_t from mb_metacharlenconv() without
+ * further tests.
+ *
+ * This version has a non-multibyte definition that simply returns
+ * 1. We never expose WCWIDTH() in the non-multibyte world since
+ * it's just a proxy for wcwidth() itself.
+ */
+#define WCWIDTH_WINT(wc) zwcwidth(wc)
+
+#define MB_INCOMPLETE ((size_t)-2)
+#define MB_INVALID ((size_t)-1)
+
+/*
+ * MB_CUR_MAX is the maximum number of bytes that a single wide
+ * character will convert into. We use it to keep strings
+ * sufficiently long. It should always be defined, but if it isn't
+ * just assume we are using Unicode which requires 6 characters.
+ * (Note that it's not necessarily defined to a constant.)
+ */
+#ifndef MB_CUR_MAX
+#define MB_CUR_MAX 6
+#endif
+
+/* Convert character or string to wide character or string */
+#define ZWC(c) L ## c
+#define ZWS(s) L ## s
+
+/*
+ * Test for a combining character.
+ *
+ * wc is assumed to be a wchar_t (i.e. we don't need zwcwidth).
+ *
+ * Pedantic note: in Unicode, a combining character need not be
+ * zero length. However, we are concerned here about display;
+ * we simply need to know whether the character will be displayed
+ * on top of another one. We use "combining character" in this
+ * sense throughout the shell. I am not aware of a way of
+ * detecting the Unicode trait in standard libraries.
+ */
+#define IS_COMBINING(wc) (wc != 0 && WCWIDTH(wc) == 0)
+/*
+ * Test for the base of a combining character.
+ *
+ * We assume a combining character can be successfully displayed with
+ * any non-space printable character, which is what a graphic character
+ * is, as long as it has non-zero width. We need to avoid all forms of
+ * space because the shell will split words on any whitespace.
+ */
+#define IS_BASECHAR(wc) (iswgraph(wc) && WCWIDTH(wc) > 0)
+
+#else /* not MULTIBYTE_SUPPORT */
+
+#define MB_METACHARINIT()
+typedef int convchar_t;
+#define MB_METACHARLENCONV(str, cp) metacharlenconv((str), (cp))
+#define MB_METACHARLEN(str) (*(str) == Meta ? 2 : 1)
+#define MB_METASTRLEN(str) ztrlen(str)
+#define MB_METASTRWIDTH(str) ztrlen(str)
+#define MB_METASTRLEN2(str, widthp) ztrlen(str)
+#define MB_METASTRLEN2END(str, widthp, eptr) ztrlenend(str, eptr)
+
+#define MB_CHARINIT()
+#define MB_CHARLENCONV(str, len, cp) charlenconv((str), (len), (cp))
+#define MB_CHARLEN(str, len) ((len) ? 1 : 0)
+
+#define WCWIDTH_WINT(c) (1)
+
+/* Leave character or string as is. */
+#define ZWC(c) c
+#define ZWS(s) s
+
+#endif /* MULTIBYTE_SUPPORT */
diff --git a/dotfiles/system/.zsh/modules/Src/zsh.mdd b/dotfiles/system/.zsh/modules/Src/zsh.mdd
new file mode 100644
index 0000000..d95f5d5
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/zsh.mdd
@@ -0,0 +1,147 @@
+name=zsh/main
+link=static
+load=yes
+# load=static should replace use of alwayslink
+functions='Functions/Chpwd/* Functions/Exceptions/* Functions/Math/* Functions/Misc/* Functions/MIME/* Functions/Prompts/* Functions/VCS_Info/* Functions/VCS_Info/Backends/*'
+
+nozshdep=1
+alwayslink=1
+
+# autofeatures not specified because of alwayslink
+
+objects="signames.o builtin.o module.o lex.o exec.o mem.o \
+string.o parse.o hashtable.o init.o input.o loop.o utils.o params.o options.o \
+signals.o pattern.o prompt.o compat.o jobs.o glob.o"
+
+headers="../config.h zsh_system.h zsh.h sigcount.h signals.h \
+prototypes.h hashtable.h ztype.h"
+hdrdeps="zshcurses.h zshterm.h"
+
+:<<\Make
+@CONFIG_MK@
+
+# If we're using gcc as the preprocessor, get rid of the additional
+# lines generated by the preprocessor as they can confuse the script.
+# We don't need these in other cases either, but can't necessarily rely
+# on the option to remove them being the same.
+signames.c: signames1.awk signames2.awk ../config.h @SIGNAL_H@
+ $(AWK) -f $(sdir)/signames1.awk @SIGNAL_H@ >sigtmp.c
+ case "`$(CPP) --version </dev/null 2>&1`" in \
+ *"Free Software Foundation"*) \
+ $(CPP) -P sigtmp.c >sigtmp.out;; \
+ *) \
+ $(CPP) sigtmp.c >sigtmp.out;; \
+ esac
+ $(AWK) -f $(sdir)/signames2.awk sigtmp.out > $@
+ rm -f sigtmp.c sigtmp.out
+
+sigcount.h: signames.c
+ grep 'define.*SIGCOUNT' signames.c > $@
+
+init.o: bltinmods.list zshpaths.h zshxmods.h
+
+init.o params.o parse.o: version.h
+
+params.o: patchlevel.h
+
+version.h: $(sdir_top)/Config/version.mk zshcurses.h zshterm.h
+ echo '#define ZSH_VERSION "'$(VERSION)'"' > $@
+
+patchlevel.h: FORCE
+ @if [ -f $(sdir)/$@.release ]; then \
+ cp -f $(sdir)/$@.release $@; \
+ else \
+ echo '#define ZSH_PATCHLEVEL "'`cd $(sdir) && git describe --tags --long`'"' > $@.tmp; \
+ cmp $@ $@.tmp >/dev/null 2>&1 && rm -f $@.tmp || mv $@.tmp $@; \
+ fi
+FORCE:
+
+zshcurses.h: ../config.h
+ @if test x$(ZSH_CURSES_H) != x; then \
+ echo "#include <$(ZSH_CURSES_H)>" >zshcurses.h; \
+ else \
+ echo >zshcurses.h; \
+ fi
+
+zshterm.h: ../config.h
+ @if test x$(ZSH_TERM_H) != x; then \
+ echo "#include <$(ZSH_TERM_H)>" >zshterm.h; \
+ else \
+ echo >zshterm.h; \
+ fi
+
+zshpaths.h: Makemod $(CONFIG_INCS)
+ @echo '#define MODULE_DIR "'$(MODDIR)'"' > zshpaths.h.tmp
+ @if test x$(sitescriptdir) != xno; then \
+ echo '#define SITESCRIPT_DIR "'$(sitescriptdir)'"' >> zshpaths.h.tmp; \
+ fi
+ @if test x$(scriptdir) != xno; then \
+ echo '#define SCRIPT_DIR "'$(scriptdir)'"' >> zshpaths.h.tmp; \
+ fi
+ @if test x$(sitefndir) != xno; then \
+ echo '#define SITEFPATH_DIR "'$(sitefndir)'"' >> zshpaths.h.tmp; \
+ fi
+ @if test x$(fixed_sitefndir) != x; then \
+ echo '#define FIXED_FPATH_DIR "'$(fixed_sitefndir)'"' >> zshpaths.h.tmp; \
+ fi
+ @if test x$(fndir) != xno; then \
+ echo '#define FPATH_DIR "'$(fndir)'"' >> zshpaths.h.tmp; \
+ if test x$(FUNCTIONS_SUBDIRS) != x && \
+ test x$(FUNCTIONS_SUBDIRS) != xno; then \
+ fpath_tmp="`grep ' functions=.' \
+ $(dir_top)/config.modules | sed -e '/^#/d' -e '/ link=no/d' \
+ -e 's/^.* functions=//'`"; \
+ fpath_tmp=`for f in $$fpath_tmp; do \
+ echo $$f | sed -e 's%^Functions/%%' -e 's%/[^/]*$$%%' -e 's%/\*%%'; \
+ done | grep -v Scripts | sort | uniq`; \
+ fpath_tmp=`echo $$fpath_tmp | sed 's/ /\", \"/g'`; \
+ echo "#define FPATH_SUBDIRS { \"$$fpath_tmp\" }" \
+ >>zshpaths.h.tmp; \
+ fi; \
+ fi
+ @if test x$(additionalfpath) != x; then \
+ fpath_tmp="`echo $(additionalfpath) | sed -e 's:,:\", \":g'`"; \
+ echo "#define ADDITIONAL_FPATH { \"$$fpath_tmp\" }" >> zshpaths.h.tmp; \
+ fi
+ @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 mkbltnmlst.sh $(dir_top)/config.modules
+ srcdir='$(sdir)' CFMOD='$(dir_top)/config.modules' \
+ $(SHELL) $(sdir)/mkbltnmlst.sh $@
+
+zshxmods.h: $(dir_top)/config.modules
+ @echo "Creating \`$@'."
+ @( \
+ for q_mod in `grep ' load=yes' $(dir_top)/config.modules | \
+ grep ' link=static' | sed -e '/^#/d' -e 's/ .*//' \
+ -e 's/^name=//' -e 's,Q,Qq,g;s,_,Qu,g;s,/,Qs,g'`; do \
+ test x$q_mod = xzshQsmain && continue; \
+ echo "#define LINKED_XMOD_$$q_mod 1"; \
+ done; \
+ for q_mod in `grep ' load=yes' $(dir_top)/config.modules | \
+ grep ' link=dynamic' | sed -e '/^#/d' -e 's/ .*//' \
+ -e 's/^name=//' -e 's,Q,Qq,g;s,_,Qu,g;s,/,Qs,g'`; do \
+ test x$q_mod = x && continue; \
+ echo "#ifdef DYNAMIC"; \
+ echo "# define UNLINKED_XMOD_$$q_mod 1"; \
+ echo "#endif"; \
+ 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.epro
+ $(CC) -c -I. -I$(sdir_top)/Src $(CPPFLAGS) $(DEFS) $(CFLAGS) -o $@ $(sdir)/main.c
+
+main.syms: $(PROTODEPS)
+proto.zsh: main.epro
+Make
diff --git a/dotfiles/system/.zsh/modules/Src/zsh.rc b/dotfiles/system/.zsh/modules/Src/zsh.rc
new file mode 100644
index 0000000..93c82ba
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/zsh.rc
@@ -0,0 +1,8 @@
+// Use this file as follows
+//
+// myapp.exe : myapp.o myapp.res
+// gcc -mwindows myapp.o myapp.res -o $@
+//
+// myapp.res : myapp.rc resource.h
+// windres $< -O coff -o $@
+IDR_MAINFRAME ICON DISCARDABLE "zsh.ico"
diff --git a/dotfiles/system/.zsh/modules/Src/zsh_system.h b/dotfiles/system/.zsh/modules/Src/zsh_system.h
new file mode 100644
index 0000000..8289ee9
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/zsh_system.h
@@ -0,0 +1,900 @@
+/*
+ * 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.
+ *
+ */
+
+#if 0
+/*
+ * Setting _XPG_IV here is actually wrong and is not needed
+ * with currently supported versions (5.43C20 and above)
+ */
+#ifdef sinix
+# define _XPG_IV 1
+#endif
+#endif
+
+#if defined(__linux) || defined(__GNU__) || defined(__GLIBC__) || defined(LIBC_MUSL) || defined(__CYGWIN__)
+/*
+ * Turn on numerous extensions.
+ * This is in order to get the functions for manipulating /dev/ptmx.
+ */
+#define _GNU_SOURCE 1
+#endif
+#ifdef LIBC_MUSL
+#define _POSIX_C_SOURCE 200809L
+#endif
+
+/* NeXT has half-implemented POSIX support *
+ * which currently fools configure */
+#ifdef __NeXT__
+# undef HAVE_TERMIOS_H
+# undef HAVE_SYS_UTSNAME_H
+#endif
+
+#ifndef ZSH_NO_XOPEN
+# ifdef ZSH_CURSES_SOURCE
+# define _XOPEN_SOURCE_EXTENDED 1
+# else
+# ifdef MULTIBYTE_SUPPORT
+/*
+ * Needed for wcwidth() which is part of XSI.
+ * Various other uses of the interface mean we can't get away with just
+ * _XOPEN_SOURCE.
+ */
+# define _XOPEN_SOURCE_EXTENDED 1
+# endif /* MULTIBYTE_SUPPORT */
+# endif /* ZSH_CURSES_SOURCE */
+#endif /* ZSH_NO_XOPEN */
+
+/*
+ * Solaris by default zeroes all elements of the tm structure in
+ * strptime(). Unfortunately that gives us no way of telling whether
+ * the tm_isdst element has been set from the input pattern. If it
+ * hasn't we want it to be -1 (undetermined) on input to mktime(). So
+ * we stop strptime() zeroing the struct tm and instead set all the
+ * elements ourselves.
+ *
+ * This is likely to be harmless everywhere else.
+ */
+#define _STRPTIME_DONTZERO
+
+#ifdef PROTOTYPES
+# define _(Args) Args
+#else
+# define _(Args) ()
+#endif
+
+#ifndef HAVE_ALLOCA
+# define alloca zhalloc
+#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
+
+/*
+ * libc.h in an optional package for Debian Linux is broken (it
+ * defines dup() as a synonym for dup2(), which has a different
+ * number of arguments), so just include it for next.
+ */
+#ifdef __NeXT__
+# ifdef HAVE_LIBC_H
+# include <libc.h>
+# endif
+#endif
+
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_STDDEF_H
+/*
+ * Seen on Solaris 8 with gcc: stddef defines offsetof, which clashes
+ * with system.h's definition of the symbol unless we include this
+ * first. Otherwise, this will be hooked in by wchar.h, too late
+ * for comfort.
+ */
+#include <stddef.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
+
+/*
+ * Stuff with variable arguments. We use definitions to make the
+ * same code work with varargs (the original K&R-style, just to
+ * be maximally compatible) and stdarg (which all modern systems
+ * should have).
+ *
+ * Ideally this should somehow be merged with the tricks performed
+ * with "_" in makepro.awk, but I don't understand makepro.awk.
+ * Currently we simply rely on the fact that makepro.awk has been
+ * hacked to leave alone argument lists that already contains VA_ALIST
+ * except for removing the VA_DCL and turning VA_ALIST into VA_ALIST_PROTO.
+ */
+#ifdef HAVE_STDARG_H
+# include <stdarg.h>
+# define VA_ALIST1(x) x, ...
+# define VA_ALIST2(x,y) x, y, ...
+# define VA_ALIST_PROTO1(x) VA_ALIST1(x)
+# define VA_ALIST_PROTO2(x,y) VA_ALIST2(x,y)
+# define VA_DCL
+# define VA_DEF_ARG(x)
+# define VA_START(ap,x) va_start(ap, x)
+# define VA_GET_ARG(ap,x,t)
+#else
+# if HAVE_VARARGS_H
+# include <varargs.h>
+# define VA_ALIST1(x) va_alist
+# define VA_ALIST2(x,y) va_alist
+/*
+ * In prototypes, assume K&R form and remove the variable list.
+ * This is about the best we can do without second-guessing the way
+ * varargs works on this system. The _ trick should be able to
+ * do this for us but we've turned it off here.
+ */
+# define VA_ALIST_PROTO1(x)
+# define VA_ALIST_PROTO2(x,y)
+# define VA_DCL va_dcl
+# define VA_DEF_ARG(x) x
+# define VA_START(ap,x) va_start(ap);
+# define VA_GET_ARG(ap,x,t) (x = va_arg(ap, t))
+# else
+# error "Your system has neither stdarg.h or varargs.h."
+# endif
+#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 */
+#if !defined(HAVE_STRUCT_TIMEZONE) && !defined(ZSH_OOT_MODULE)
+struct timezone {
+ int tz_minuteswest;
+ int tz_dsttime;
+};
+#endif
+
+/* Used to provide compatibility with clock_gettime() */
+#if !defined(HAVE_STRUCT_TIMESPEC) && !defined(ZSH_OOT_MODULE)
+struct timespec {
+ time_t tv_sec;
+ long tv_nsec;
+};
+#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 USE_STACK_ALLOCATION
+#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
+#else
+# define VARARR(X,Y,Z) X *(Y) = (X *) zhalloc(sizeof(X) * (Z))
+#endif
+
+/* we should handle unlimited sizes from pathconf(_PC_PATH_MAX) */
+/* but this is too much trouble */
+#ifndef PATH_MAX
+# ifdef MAXPATHLEN
+# define PATH_MAX MAXPATHLEN
+# else
+# ifdef _POSIX_PATH_MAX
+# define PATH_MAX _POSIX_PATH_MAX
+# else
+ /* so we will just pick something */
+# define PATH_MAX 1024
+# endif
+# endif
+#endif
+
+/*
+ * The number of file descriptors we'll allocate initially.
+ * We will reallocate later if necessary.
+ */
+#define ZSH_INITIAL_OPEN_MAX 64
+#ifndef OPEN_MAX
+# ifdef NOFILE
+# define OPEN_MAX NOFILE
+# else
+ /* so we will just pick something */
+# define OPEN_MAX ZSH_INITIAL_OPEN_MAX
+# endif
+#endif
+#ifndef HAVE_SYSCONF
+# define zopenmax() ((long) (OPEN_MAX > ZSH_INITIAL_OPEN_MAX ? \
+ ZSH_INITIAL_OPEN_MAX : OPEN_MAX))
+#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
+#elif defined(SELECT_IN_SYS_SOCKET_H)
+# include <sys/socket.h>
+#endif
+
+#if defined(__APPLE__) && defined(HAVE_SELECT)
+/*
+ * Prefer select() to poll() on MacOS X since poll() is known
+ * to be problematic in 10.4
+ */
+#undef HAVE_POLL
+#undef HAVE_POLL_H
+#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 */
+
+#if defined(GWINSZ_IN_SYS_IOCTL) || defined(IOCTL_IN_SYS_IOCTL)
+# 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 *
+ * (or with ZSH_64_BIT_TYPE maybe -LONG_LONG_MAX-1) *
+ * converted to printable decimal form including the sign and the *
+ * terminating null character. Below 0.30103 > lg 2. *
+ * BDIGBUFSIZE is for a number converted to printable binary form. */
+#define DIGBUFSIZE ((int)(((sizeof(zlong) * 8) - 1) * 30103/100000) + 3)
+#define BDIGBUFSIZE ((int)((sizeof(zlong) * 8) + 4))
+
+/* 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_ISDOOR
+# 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_ISDOOR) && defined(S_IFDOOR) /* Solaris */
+# define S_ISDOOR(m) (((m) & S_IFMT) == S_IFDOOR)
+#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_ISDOOR
+# define S_ISDOOR(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_LCHOWN
+# define lchown chown
+#endif
+
+#ifndef HAVE_MEMCPY
+# define memcpy memmove
+#endif
+
+#ifndef HAVE_MEMMOVE
+# ifndef memmove
+static char *zmmv;
+# define memmove(dest, src, len) (bcopy((src), zmmv = (dest), (len)), zmmv)
+# endif
+#endif
+
+#ifndef offsetof
+# define offsetof(TYPE, MEM) ((char *)&((TYPE *)0)->MEM - (char *)(TYPE *)0)
+#endif
+
+extern char **environ;
+
+/*
+ * We always need setenv and unsetenv in pairs, because
+ * we don't know how to do memory management on the values set.
+ */
+#if defined(HAVE_SETENV) && defined(HAVE_UNSETENV) && !defined(__APPLE__)
+# define USE_SET_UNSET_ENV
+#endif
+
+
+/* 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
+
+#ifdef _LARGEFILE_SOURCE
+#ifdef HAVE_FSEEKO
+#define fseek fseeko
+#endif
+#ifdef HAVE_FTELLO
+#define ftell ftello
+#endif
+#endif
+
+/* Can't support job control without working tcsetgrp() */
+#ifdef BROKEN_TCSETPGRP
+#undef JOB_CONTROL
+#endif /* BROKEN_TCSETPGRP */
+
+#ifdef BROKEN_KILL_ESRCH
+#undef ESRCH
+#define ESRCH EINVAL
+#endif /* BROKEN_KILL_ESRCH */
+
+/* Can we do locale stuff? */
+#undef USE_LOCALE
+#if defined(CONFIG_LOCALE) && defined(HAVE_SETLOCALE) && defined(LC_ALL)
+# define USE_LOCALE 1
+#endif /* CONFIG_LOCALE && HAVE_SETLOCALE && LC_ALL */
+
+#ifndef MAILDIR_SUPPORT
+#define mailstat(X,Y) stat(X,Y)
+#endif
+
+#ifdef __CYGWIN__
+# include <sys/cygwin.h>
+# define IS_DIRSEP(c) ((c) == '/' || (c) == '\\')
+#else
+# define IS_DIRSEP(c) ((c) == '/')
+#endif
+
+#if defined(__GNUC__) && (!defined(__APPLE__) || defined(__clang__))
+/* Does the OS X port of gcc still gag on __attribute__? */
+#define UNUSED(x) x __attribute__((__unused__))
+#else
+#define UNUSED(x) x
+#endif
+
+/*
+ * The MULTIBYTE_SUPPORT configure-define specifies that we want to enable
+ * complete Unicode conversion between wide characters and multibyte strings.
+ */
+#if defined MULTIBYTE_SUPPORT \
+ || (defined HAVE_WCHAR_H && defined HAVE_WCTOMB && defined __STDC_ISO_10646__)
+/*
+ * If MULTIBYTE_SUPPORT is not defined, these includes provide a subset of
+ * Unicode support that makes the \u and \U printf escape sequences work.
+ */
+
+#if defined(__hpux) && !defined(_INCLUDE__STDC_A1_SOURCE)
+#define _INCLUDE__STDC_A1_SOURCE
+#endif
+
+# include <wchar.h>
+# include <wctype.h>
+#endif
+#ifdef HAVE_LANGINFO_H
+# include <langinfo.h>
+# ifdef HAVE_ICONV
+# include <iconv.h>
+# endif
+#endif
+
+#if defined(HAVE_INITGROUPS) && !defined(DISABLE_DYNAMIC_NSS)
+# define USE_INITGROUPS
+#endif
+
+#if defined(HAVE_GETGRGID) && !defined(DISABLE_DYNAMIC_NSS)
+# define USE_GETGRGID
+#endif
+
+#if defined(HAVE_GETGRNAM) && !defined(DISABLE_DYNAMIC_NSS)
+# define USE_GETGRNAM
+#endif
+
+#if defined(HAVE_GETPWENT) && !defined(DISABLE_DYNAMIC_NSS)
+# define USE_GETPWENT
+#endif
+
+#if defined(HAVE_GETPWNAM) && !defined(DISABLE_DYNAMIC_NSS)
+# define USE_GETPWNAM
+#endif
+
+#if defined(HAVE_GETPWUID) && !defined(DISABLE_DYNAMIC_NSS)
+# define USE_GETPWUID
+#endif
+
+#ifdef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC
+# define GET_ST_ATIME_NSEC(st) (st).st_atim.tv_nsec
+#elif HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC
+# define GET_ST_ATIME_NSEC(st) (st).st_atimespec.tv_nsec
+#elif HAVE_STRUCT_STAT_ST_ATIMENSEC
+# define GET_ST_ATIME_NSEC(st) (st).st_atimensec
+#endif
+#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
+# define GET_ST_MTIME_NSEC(st) (st).st_mtim.tv_nsec
+#elif HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
+# define GET_ST_MTIME_NSEC(st) (st).st_mtimespec.tv_nsec
+#elif HAVE_STRUCT_STAT_ST_MTIMENSEC
+# define GET_ST_MTIME_NSEC(st) (st).st_mtimensec
+#endif
+#ifdef HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC
+# define GET_ST_CTIME_NSEC(st) (st).st_ctim.tv_nsec
+#elif HAVE_STRUCT_STAT_ST_CTIMESPEC_TV_NSEC
+# define GET_ST_CTIME_NSEC(st) (st).st_ctimespec.tv_nsec
+#elif HAVE_STRUCT_STAT_ST_CTIMENSEC
+# define GET_ST_CTIME_NSEC(st) (st).st_ctimensec
+#endif
+
+#if defined(HAVE_TGETENT) && !defined(ZSH_NO_TERM_HANDLING)
+# if defined(ZSH_HAVE_CURSES_H) && defined(ZSH_HAVE_TERM_H)
+# define USES_TERM_H 1
+# else
+# ifdef HAVE_TERMCAP_H
+# define USES_TERMCAP_H 1
+# endif
+# endif
+
+# ifdef USES_TERM_H
+# ifdef HAVE_TERMIO_H
+# include <termio.h>
+# endif
+# ifdef ZSH_HAVE_CURSES_H
+# include "zshcurses.h"
+# endif
+# include "zshterm.h"
+# else
+# ifdef USES_TERMCAP_H
+# include <termcap.h>
+# endif
+# endif
+#endif
+
+#ifdef HAVE_SRAND_DETERMINISTIC
+# define srand srand_deterministic
+#endif
+
+#ifdef ZSH_VALGRIND
+# include "valgrind/valgrind.h"
+# include "valgrind/memcheck.h"
+#endif
diff --git a/dotfiles/system/.zsh/modules/Src/ztype.h b/dotfiles/system/.zsh/modules/Src/ztype.h
new file mode 100644
index 0000000..ae72367
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/ztype.h
@@ -0,0 +1,89 @@
+/*
+ * 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 INULL (1 << 14)
+#define IPATTERN (1 << 15)
+#define zistype(X,Y) (typtab[STOUC(X)] & Y)
+#define idigit(X) zistype(X,IDIGIT)
+#define ialnum(X) zistype(X,IALNUM)
+#define iblank(X) zistype(X,IBLANK) /* blank, not including \n */
+#define inblank(X) zistype(X,INBLANK) /* blank or \n */
+#define itok(X) zistype(X,ITOK)
+#define isep(X) zistype(X,ISEP)
+#define ialpha(X) zistype(X,IALPHA)
+#define iident(X) zistype(X,IIDENT)
+#define iuser(X) zistype(X,IUSER) /* username char */
+#define icntrl(X) zistype(X,ICNTRL)
+#define iword(X) zistype(X,IWORD)
+#define ispecial(X) zistype(X,ISPECIAL)
+#define imeta(X) zistype(X,IMETA)
+#define iwsep(X) zistype(X,IWSEP)
+#define inull(X) zistype(X,INULL)
+#define ipattern(X) zistype(X,IPATTERN)
+
+/*
+ * Bit flags for typtab_flags --- preserved after
+ * shell initialisation.
+ */
+#define ZTF_INIT (0x0001) /* One-off initialisation done */
+#define ZTF_INTERACT (0x0002) /* Shell interative and reading from stdin */
+#define ZTF_SP_COMMA (0x0004) /* Treat comma as a special characters */
+#define ZTF_BANGCHAR (0x0008) /* Treat bangchar as a special character */
+
+#ifdef MULTIBYTE_SUPPORT
+#define WC_ZISTYPE(X,Y) wcsitype((X),(Y))
+# ifdef ENABLE_UNICODE9
+# define WC_ISPRINT(X) u9_iswprint(X)
+# else
+# define WC_ISPRINT(X) iswprint(X)
+# endif
+#else
+#define WC_ZISTYPE(X,Y) zistype((X),(Y))
+#define WC_ISPRINT(X) isprint(X)
+#endif
+
+#if defined(__APPLE__) && defined(BROKEN_ISPRINT)
+#define ZISPRINT(c) isprint_ascii(c)
+#else
+#define ZISPRINT(c) isprint(c)
+#endif